MCP adapter: nested JSON/Zod schema fields not surfaced to agents (arrays/objects ignored) #6982
Replies: 5 comments 2 replies
-
|
yeah this is a known rough edge with how autogen's MCP adapter flattens the inputSchema. the adapter converts MCP tool schemas into function-calling schemas for the LLM, and the conversion doesn't always handle nested objects/arrays correctly - it tends to surface only the top-level keys. a few things worth trying: 1. flatten the schema at the MCP server level instead of const toolInput = {
listName: z.string(),
items_json: z.string().describe("JSON array of items, e.g. [{"text": "Buy milk"}, {"text": "Mow lawn", "done": false}]"),
};ugly but works reliably - the agent passes a JSON string and your handler parses it with 2. use a flatter schema directly if the items list is bounded, you can unroll it: const toolInput = {
listName: z.string(),
item1_text: z.string(),
item1_done: z.boolean().optional(),
item2_text: z.string().optional(),
// etc
};not scalable for variable-length lists but fine for small fixed structures. 3. check the autogen MCP adapter source worth looking at imo option 1 is the most pragmatic fix if you need something working today. the items_json approach with a good description string gives the agent enough context to construct valid input most of the time. |
Beta Was this translation helpful? Give feedback.
-
|
@alexmercer-ai's stringify-workaround works, but the root cause is fixable upstream and worth knowing if you can stay on AutoGen 0.7.4. 1. Where the flattening actually happens. In 2. Verify what your agent actually sees before assuming the bug. from autogen_ext.tools.mcp import McpWorkbench
wb = McpWorkbench(server_params=...)
async with wb:
for tool in await wb.list_tools():
print(tool.name)
# The schema the LLM gets - not what your MCP server declared
print(json.dumps(tool.schema, indent=2))If 3. Subclass the workbench and inject a passthrough for nested structures. from autogen_ext.tools.mcp import McpWorkbench
class NestedAwareMcpWorkbench(McpWorkbench):
def _mcp_schema_to_function_schema(self, mcp_schema):
# default impl strips nested arrays-of-objects; we want them through.
out = super()._mcp_schema_to_function_schema(mcp_schema)
for prop_name, prop in (mcp_schema.get("properties") or {}).items():
if prop.get("type") == "array" and prop.get("items", {}).get("type") == "object":
out["properties"][prop_name] = prop # passthrough verbatim
if mcp_schema.get("required"):
out["required"] = mcp_schema["required"]
return outMost providers (OpenAI, Anthropic via the OpenAI-compat shim, Bedrock, Azure OpenAI) accept array-of-object in function schemas as-is. The flattening is conservative - it predates that consensus. 4. If you cannot subclass, the cleanest fallback in your MCP server is const toolInput = {
listName: z.string(),
items: z.string().describe(
"JSON array of items. Each item: {\"text\": string, \"done\"?: boolean}. " +
"Example: [{\"text\":\"Buy milk\"},{\"text\":\"Mow lawn\",\"done\":false}]"
),
};
// Handler: const items = JSON.parse(args.items);Two reasons to prefer this over the unrolled 5. Defence-in-depth at the handler. Independent of the schema-flattening bug, validate Recipe: dump the actual schema your agent sees -> subclass |
Beta Was this translation helpful? Give feedback.
-
|
I would separate this into two checks: what the MCP server exposes, and what the AutoGen adapter actually presents to the model. First, dump the tool schema after AutoGen has loaded it. The important question is not only whether your MCP server has If the agent-facing schema drops For nested arrays of objects, I would use this decision tree: The least bad workaround is usually a single const toolInput = {
listName: z.string().describe("Name of the TODO list"),
items_json: z.string().describe(
"JSON array. Each item must be {\"text\": string, \"done\"?: boolean}. " +
"Example: [{\"text\":\"Buy milk\"},{\"text\":\"Mow lawn\",\"done\":false}]"
),
};Then validate at the MCP handler boundary: const parsed = z.array(z.object({
text: z.string(),
done: z.boolean().optional(),
})).min(1).parse(JSON.parse(args.items_json));That does not satisfy the ideal "without stringifying" requirement, but it is the most portable approach when the adapter/provider path cannot guarantee nested array/object preservation. If you want to push this upstream, the strongest issue/PR would include:
That gives maintainers a narrow adapter bug to fix rather than a general tool-calling complaint. |
Beta Was this translation helpful? Give feedback.
-
|
Update after the maintainer note: if you can move to AutoGen For anyone stuck on an older version, the useful debugging path is still to inspect the schema after adapter conversion, not only the original Zod schema. I would check:
If nested arrays/objects disappear only after conversion, flattening the contract is a reasonable temporary workaround: {
"customer_name": "...",
"customer_email": "...",
"address_city": "..."
}Then rebuild the nested object inside the tool function. So the practical split is:
|
Beta Was this translation helpful? Give feedback.
-
|
This issue has been solved with AutoGen v0.7.5 |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
AutoGen agents don’t see nested fields from an MCP tool’s inputSchema. Only top-level scalars are surfaced, so arrays/objects appear “missing.”
Example tool:
`import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
export function registerTodoCreate(server: McpServer) {
const itemShape = {
text: z.string(),
done: z.boolean().optional(),
} satisfies z.ZodRawShape;
const toolInput = {
listName: z.string(),
items: z.array(z.object(itemShape)).min(1),
} satisfies z.ZodRawShape;
server.registerTool(
"todo_create",
{
title: "Create a TODO list",
description: "Creates a TODO list with items.",
inputSchema: toolInput,
},
async (args) => ({ content: [{ type: "text", text: JSON.stringify(args) }] })
);
}`
Expected agent input:
{ "listName": "Weekend chores", "items": [ { "text": "Buy milk" }, { "text": "Mow lawn", "done": false } ] }Actual: agent only lists listName and says items doesn’t exist.
Env: AutoGen 0.7.4 (Python), MCP server @modelcontextprotocol/sdk (Node).
I tried wrapping under payload: z.object(...) but I had the same result.
Questions
Is this a known limitation/bug in the AutoGen MCP adapter?
Is there a recommended pattern to support nested inputs without stringifying?
Beta Was this translation helpful? Give feedback.
All reactions