Skip to content

Conversation

Kludex
Copy link
Member

@Kludex Kludex commented Oct 3, 2025

No description provided.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the final PR Bugbot will review for you during this billing cycle

Your free Bugbot reviews will reset on November 9

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

Bug: Image Data Decoding Error

The map_from_pai_messages function incorrectly calls base64.b64decode() on image chunk.data. Since chunk.data is already binary, not a base64 string, this causes a decoding error and prevents proper image content conversion for MCP.

pydantic_ai_slim/pydantic_ai/_mcp.py#L93-L95

if isinstance(chunk, str):
add_msg('user', mcp_types.TextContent(type='text', text=chunk))
elif isinstance(chunk, messages.BinaryContent) and chunk.is_image:

Fix in Cursor Fix in Web


Copy link

github-actions bot commented Oct 3, 2025

Docs Preview

commit: 0f5e987
Preview URL: https://67f184e5-pydantic-ai-previews.pydantic.workers.dev

server_name: str | None = None,
tool_name: str | None = None,
tool_description: str | None = None,
# TODO(Marcelo): Should this actually be a factory that is created in every tool call?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a union of static deps and a deps factory makes sense, if the deps factory would get the tool call _meta.

name=tool_name,
description=tool_description,
inputSchema={'type': 'object', 'properties': {'prompt': {'type': 'string'}}},
# TODO(Marcelo): How do I get this?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's not currently a nice way to get this, but it'd be useful to have a new output_json_schema property on AbstractAgent.

In the case of the concrete Agent, it would depend on agent._output_schema:

  • if StructuredTextOutputSchema (superclass of NativeOutputSchema and PromptedOutputSchema), get it from .object_def.json_schema
  • if it's OutputSchemaWithoutMode, get it from .processor.object_def.json_schema
  • if it's PlainTextOutputSchema, it's just {'type': 'string'}
  • if it's ToolOutputSchema, we need to create a union schema of all .toolset.processors using UnionOutputProcessor, which currently takes a sequence of output types and creates ObjectOutputProcessors for them on the fly, but could also take a sequence of ObjectOutputProcessors (the ones we get from .toolset.processors) directly. Once we have that UnionOutputProcessor, the schema is on .object_def.json_schema
  • if it's ToolOrTextOutputSchema, it's the above or {'type': 'string'}

Note that this changes a bit with some refactoring I did in #2970, but directionally it's the same: there's not currently a nice way to get this, and it's especially tricky for tool output, because we don't have a union of all types handy.

I should be able to implement this pretty quickly through, once that images PR with the output types refactor merges.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this PR wait for it then?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll wait for it then.

raise ValueError(f'Unknown tool: {name}')

# TODO(Marcelo): Should we pass the `message_history` instead?
prompt = cast(str, args['prompt'])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use a typed dict for args so we don't have to cast?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, that would be incorrect... What I actually need to check if 'prompt' is in args, and check that it's a string.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd expect the library to validate the args match the type hint, no?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explained via Slack - answering here: no.

if name != tool_name:
raise ValueError(f'Unknown tool: {name}')

# TODO(Marcelo): Should we pass the `message_history` instead?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think just the prompt is fine, when would the LLM generate an entire message history?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, I think the point is that we need to maintain the history in the session...

Good point!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may need to create a database abstraction here. 🤔

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure the tool should be stateful like that? If it's essentially a subagent, wouldn't multiple calls be expected to start separate subagents? I think continuing the conversation should be explicit, with some conversation ID returned and passed in

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, if the client wants to create a new conversation, they can open a new session.

I think continuing the conversation should be explicit, with some conversation ID returned and passed in

The MCP spec handles this with a session ID.


return dict(result=result.output)

app.list_tools()(list_tools)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These could be decorators right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decorators inside a function are treated as misused type-wise.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lame

@Kludex Kludex marked this pull request as draft October 6, 2025 09:30
@Kludex
Copy link
Member Author

Kludex commented Oct 6, 2025

PR pending work on #3076 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants