This is a submission for DEV's Summer Bug Smash: Clear the Lineup powered by Sentry.
Project Overview
I picked huggingface/smolagents, the 28k+ star agent framework where agents literally think in Python code. It had a bug open since issue #1108 that anyone combining MCP tools with agent serialization would eventually slam into.
Call agent.to_dict() on a CodeAgent holding MCP tools (or save() or push_to_hub(), same path) and you get this beauty:
ValueError: Tool validation failed for MCPAdaptTool:
Parameters in __init__ must have default values, found required parameters: name, description, inputs, output_type
- forward: Name 'func' is undefined.
- forward: Name 'mcp' is undefined.
func is undefined? mcp is undefined? I never wrote a forward method. If you hit this in the wild you'd have zero idea what you did wrong. Spoiler: you did nothing wrong.
Bug Fix or Performance Improvement
smolagents serializes tools by reconstructing standalone Python source for the tool class. Tool.to_dict calls validate_tool_attributes() which does static AST analysis, then instance_to_source() so Tool.from_code can rebuild the tool later from source alone.
That contract can never hold for MCP tools. MCPAdaptTool is generated at runtime by mcpadapt inside a closure. Its __init__ takes required parameters and its forward closes over the live MCP client session (func, mcp, logger...). The tool's actual behavior lives on the MCP server, not in Python source. There is no source to reconstruct, so the AST validator chokes on a class that was never meant to pass it.
The interesting part: Tool.to_dict already fails fast with a clear message for three other runtime generated wrappers (Spaces, LangChain, Gradio). MCP tools were just missing from that guard.
Sometimes the right fix isn't making the impossible possible, it's failing loudly and helpfully. Recreating a live MCP session from serialized state would mean silently re-establishing server connections with credentials and trust decisions the library has no business making. So I extended the existing guard to detect MCP tools and raise this instead:
ValueError: Cannot serialize MCP tool 'echo_tool': it wraps a live MCP server session, which cannot be
saved as standalone code. Remove MCP tools from your agent before calling to_dict, save or push_to_hub,
and recreate them with MCPClient or ToolCollection.from_mcp when loading the agent.
From "what is func" to "here's exactly what to do instead" in one guard clause.
Code
Raise informative error when serializing MCP tools
#2528
Fixes #1108
Calling to_dict() (and therefore save() or push_to_hub()) on an agent that holds MCP tools crashes with a confusing internal error:
ValueError: Tool validation failed for MCPAdaptTool:
Parameters in __init__ must have default values, found required parameters: name, description, inputs, output_type
- forward: Name 'func' is undefined.
- forward: Name 'mcp' is undefined.
...
Reproduction (stdio MCP server, same shape as the tests in tests/test_mcp_client.py):
from mcp import StdioServerParameters
from smolagents import CodeAgent, InferenceClientModel
from smolagents.mcp_client import MCPClient
server_parameters = StdioServerParameters(command="python", args=["-c", echo_server_script])
with MCPClient(server_parameters) as tools:
agent = CodeAgent(model=InferenceClientModel(), tools=list(tools))
agent.to_dict() # ValueError: Tool validation failed for MCPAdaptTool: ...
Tool.to_dict serializes a tool by reconstructing standalone source code for its class: it calls validate_tool_attributes(self.__class__) and instance_to_source(...) so that Tool.from_code can later rebuild the tool from that source alone.
That contract cannot hold for MCP tools. MCPAdaptTool is generated at runtime by mcpadapt, its __init__ takes required parameters and its forward is a closure over the live MCP client session (func, mcp, logger, ...). The tool's behavior lives on the MCP server, not in Python source, and the underlying connection is not serializable, so source reconstruction fails validation with the cryptic error above.
Tool.to_dict already fails fast with a clear message for the other three runtime generated wrapper classes (SpaceToolWrapper, LangChainToolWrapper, GradioToolWrapper). MCP tools were missing from that guard.
Extend the existing guard in Tool.to_dict to detect MCP tools and raise an actionable error:
ValueError: Cannot serialize MCP tool 'echo_tool': it wraps a live MCP server session, which cannot be
saved as standalone code. Remove MCP tools from your agent before calling to_dict, save or push_to_hub,
and recreate them with MCPClient or ToolCollection.from_mcp when loading the agent.
Detection matches the runtime class name, following the existing convention in the same block, since mcpadapt is an optional dependency. The from_dict direction needs no change: serialization now fails fast with a clear message, and recreating a live MCP session is a user decision (server lifecycle, credentials, trust) that Tool.from_code could never perform safely.
A note documenting the limitation is added to the MCP section of the tools tutorial.
Two tests in tests/test_mcp_client.py, using the existing echo_server_script stdio fixture:
-
test_mcp_tool_to_dict_raises_informative_error:tool.to_dict()raises the clear error. -
test_agent_to_dict_with_mcp_tool_raises_informative_error:CodeAgent.to_dict()raises the clear error (the exact scenario from the issue).
Both fail on main with the old Tool validation failed for MCPAdaptTool error and pass with this change. make quality passes. tests/test_mcp_client.py (7 passed), tests/test_tools.py and the agent serialization tests in tests/test_agents.py pass locally; the two pre-existing failures in test_integration_from_mcp_with_streamable_http and test_integration_from_mcp_with_sse also fail on a clean main checkout (local port binding) and are unrelated.
PR: https://github.com/huggingface/smolagents/pull/2528 (Fixes #1108)
Two new tests using the existing stdio echo server fixture, both fail on main and pass with the fix. make quality clean. Docs note added to the MCP tools tutorial so nobody has to learn this the hard way again.
My Improvements
- Users hitting this now get an actionable error instead of AST validator internals
- The fix follows the repo's existing convention exactly (same guard block, same style as the Space/LangChain/Gradio cases), which is what makes a one-commit PR actually mergeable
- Documented the limitation where users would look for it
- Regression tests covering both the raw tool and the full CodeAgent scenario from the original issue
Best Use of Sentry
This is where it gets fun. I built a small demo agent app (a weather checkpoint agent using my patched smolagents with an MCP tool) and wired in the Sentry Python SDK with error monitoring, tracing and AI agent monitoring before touching the fix.
Step 1: catch the crash. Running the demo on unpatched smolagents, the cryptic ValueError landed straight in Sentry as an unhandled issue with the full 20+ line "undefined name" spam captured.
Step 2: let Seer take a shot. I ran Seer root cause analysis on the captured issue. Its diagnosis, fully independent of my PR:
MCPAdaptTool is a dynamically generated inner class created by mcpadapt's SmolAgentsAdapter.adapt() closure, so its init has required parameters and its methods reference closure variables that are not visible as class-level attributes... making it fundamentally incompatible with smolagents' static source-code validation.
Which is, almost line for line, the root cause I wrote in the PR. AI-assisted debugging where the AI and the human converge on the same diagnosis independently is exactly the confidence check you want before shipping a fix upstream.

Step 3: verify and resolve. Same demo on the patched version runs clean, agent traces show the gen_ai spans (invoke_agent, execute_tool) nested under the workflow and the Sentry issue is marked resolved.
Used the bugsmash26 code for the $100 credits too. Thanks Sentry 🛹
What I learned
Serialization boundaries are where abstractions leak. smolagents' "tools are source code" model is elegant right up until a tool is actually a live network session wearing a Tool costume. The mature move for a library isn't to pretend otherwise, it's to name the limitation clearly at the exact moment the user hits it.
Also: watching Seer independently arrive at your root cause is a genuinely great feeling. Like a second engineer nodding at your RCA.
Built during DEV's first Summer Bug Smash. Find me on GitHub @himanshu748 or X @jhahimanshu653.



Top comments (0)