Repository navigation
MCP Tool inputSchema.properties stripped when registering tools from external MCP servers #912
Description
Environment
- Runtime: Freebuff (AI Agent Runtime)
- Connected MCP Server:
marionette_mcpv0.6.0(by LeanCode) - Underlying Library:
mcp_dartv2.3.0/v1.3.0
Symptom
When a third-party Model Context Protocol (MCP) server registers a tool with an inputSchema containing named properties, the Freebuff runtime registers the tool with properties: {}. As a result, the AI agent is unable to pass parameters to the tool.
Example
The connect tool in marionette_mcp is defined as follows:
// marionette_mcp source (vm_service_context.dart)
server.registerTool(
'connect',
inputSchema: ToolInputSchema(
properties: {
'uri': JsonSchema.string(
description: 'VM service URI (e.g., ws://127.0.0.1:8181/ws)...',
),
},
required: ['uri'],
),
...
);
This generates standard JSON Schema in the MCP tools/list response:
{
"name": "connect",
"inputSchema": {
"type": "object",
"properties": {
"uri": {
"type": "string",
"description": "VM service URI..."
}
},
"required": ["uri"]
}
}
However, Freebuff interprets the tool parameter schema as:
{
"parameters": {
"type": "object",
"properties": {},
"additionalProperties": false
}
}
Impact
This affects every tool requiring parameters. Because properties is empty and additionalProperties is set to false, the AI agent cannot invoke any tool that accepts input arguments.
Execution Error
Attempting to call connect results in:
Invalid parameters for marionette__connect:
path: ["uri"] → "expected string, received undefined"
Note: The validation error path
["uri"]indicates that a secondary validation layer correctly parses the schema (it knowsuriis required), but the primary tool parameter definition exposed to the AI model retainsproperties: {}.
Evidence
- Source Code Schema Definition:
marionette_mcpv0.6.0(vm_service_context.dart, lines 62–68) explicitly definesproperties: {'uri': JsonSchema.string(...)}andrequired: ['uri']. - Correct Serialization:
mcp_dart'sJsonObject.toJson()correctly outputs the properties:
return {
'type': 'object',
if (properties != null)
'properties': properties!.map((k, v) => MapEntry(k, _jsonSchemaValue(v))),
if (required != null && required!.isNotEmpty) 'required': required,
...
};
- Not a
marionette_mcpVersion Issue: Tested across major versions (0.6.0,0.5.0,0.4.0,0.1.0) — all reproduce the identicalproperties: {}behavior. - Not an
mcp_dartVersion Issue: Tested on bothv1.3.0andv2.3.0with identical results.
Root Cause (Hypothesis)
The Freebuff MCP tool registration pipeline likely:
- Receives the
tools/listJSON-RPC response correctly. - Parses
inputSchemaas a JSON Schema object. - Drops the
propertiesmap when building the internal tool definition — either by instantiatingtype: "object"without mapping overproperties, or via a schema parser that discardsproperties.
Suggested Fix
In the Freebuff runtime's MCP client implementation (where tools/list responses are ingested):
- Pass-through Properties: Ensure
inputSchema.propertiesis preserved and mapped into the internal tool definition rather than being replaced with an empty object. - Preserve Required Fields: Ensure
inputSchema.requiredis properly plumbed through to mark mandatory parameters. - Verification: Test with MCP servers registering parameterized tools (e.g.,
marionette_mcp,firebase-mcp-server) and verify that tool parameters are visible to and callable by the agent.
Workaround
- None available on the user side. The fix must be made within the runtime's MCP client handling logic.
Activity
codebuff-team commentedon Aug 29, 2026
Marking this stale - there has been no activity here for 30 days. It will close in 7 days unless someone comments.
This is backlog upkeep, not a verdict on the issue. A single comment keeps it open, and anything closed this way can be reopened.
enieuwy commentedon Sep 2, 2026
Not stale — still reproduces on 0.0.166, and I have per-model numbers that may narrow it.
Same failure text as the report above, against a local stdio MCP server of my own (Go,
.agents/mcp.json, three write tools plus two read tools):The client's own log records every one of those calls with
"input":{}, so the arguments are already gone by the time it validates. Worth stating plainly: my server is written in Go and has no zod anywhere in it, and the calls never arrive over the stdio transport at all — nothing is logged server-side, and the JSON-RPC error the server would return (-32602) never appears. The validation that fails is entirely inside the client, before the request is sent.It looks model-dependent, not registration-wide
This is the part that surprised me. I read the client's chat logs for every session I had retained and counted
<server>__*calls by whether the recordedinputwas empty:input:{}report_findingreport_progressreport_progressreport_findingreport_progressreport_findingquery_ledgerreport_progressOn the same build (0.0.163), against the same server and the same
tools/listresponse, MiMo 2.5 carried arguments on 48 of 49 calls while gpt-5.6 luna carried none of 4. GLM 5.3 Flash is the extreme case: 50 of 50 empty, no exceptions, ever.If
propertieswere only being dropped when the tool is registered, I would expect every model to be equally unable to pass arguments. It is not what I measure. So either there is a second loss in the per-provider tool-call serialization path, or the stripped registration is survivable for some models (which can apparently still produce correct argument names) and fatal for others. #1004 reports the same"input":{}withexpected string at path ["code"], received undefined, also on gpt-5.6 luna, and its author notes that "with other llm mcp work" — which fits the same split.Schema shape is not the trigger
noteis a plain{"type":"string"}that is required — no unions, no$ref, no nesting. It is stripped for GLM 5.3 Flash and delivered intact for MiMo 2.5 on the same server. Two of my other tools do advertise union types (number | string) for tolerance, but they fail and succeed with exactly the same per-model pattern, so the union is not what decides it.Why it is expensive from the outside
The failure is silent in both directions. The agent believed it was reporting and said so in its transcript — "all show
{}as received" — while my server saw no traffic and correctly logged nothing. There is no error on either side of the transport, which is a genuinely hard signal to attribute. It cost me two days and I nearly retired a perfectly good model over it.One suggestion in addition to the fix already proposed above: when the client refuses a tool call because the parsed arguments are empty, it would help enormously if that were surfaced as a visible tool error in the session rather than only recorded in the client log. Right now the only way to find it is to go read the log by hand after the fact.
Happy to supply the
tools/listresponse, the client log lines, or a minimal Go server that reproduces it if any of that is useful.hsm207 commentedon Sep 3, 2026
I hit the same issue, and it was a major blocker because none of my essential MCP tools were usable. So I dug into Freebuff's MCP client source to find out why the arguments were dropped.
Besides the schema stripping reported here, I found three more MCP-related bugs. All four are fixed in #1259, with the analysis in the PR.
I verified the fix end-to-end against
@modelcontextprotocol/server-everything, where every tool works exceptsimulate-research-query, which needs the experimental Tasks API this client doesn't implement yet. Since that server is the standard MCP example, the fix should apply to any MCP server.codebuff-team commentedon Oct 5, 2026
Marking this stale - there has been no activity here for 31 days. It will close in 7 days unless someone comments.
This is backlog upkeep, not a verdict on the issue. A single comment keeps it open, and anything closed this way can be reopened.
hsm207 commentedon Oct 6, 2026
it's not stale