Skip to content

MCP Tool inputSchema.properties stripped when registering tools from external MCP servers #912

Description

@billyandco

Environment

  • Runtime: Freebuff (AI Agent Runtime)
  • Connected MCP Server: marionette_mcp v0.6.0 (by LeanCode)
  • Underlying Library: mcp_dart v2.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 knows uri is required), but the primary tool parameter definition exposed to the AI model retains properties: {}.


Evidence

  1. Source Code Schema Definition: marionette_mcp v0.6.0 (vm_service_context.dart, lines 62–68) explicitly defines properties: {'uri': JsonSchema.string(...)} and required: ['uri'].
  2. Correct Serialization: mcp_dart's JsonObject.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,
  ...
};
  1. Not a marionette_mcp Version Issue: Tested across major versions (0.6.0, 0.5.0, 0.4.0, 0.1.0) — all reproduce the identical properties: {} behavior.
  2. Not an mcp_dart Version Issue: Tested on both v1.3.0 and v2.3.0 with identical results.

Root Cause (Hypothesis)

The Freebuff MCP tool registration pipeline likely:

  1. Receives the tools/list JSON-RPC response correctly.
  2. Parses inputSchema as a JSON Schema object.
  3. Drops the properties map when building the internal tool definition — either by instantiating type: "object" without mapping over properties, or via a schema parser that discards properties.

Suggested Fix

In the Freebuff runtime's MCP client implementation (where tools/list responses are ingested):

  1. Pass-through Properties: Ensure inputSchema.properties is preserved and mapped into the internal tool definition rather than being replaced with an empty object.
  2. Preserve Required Fields: Ensure inputSchema.required is properly plumbed through to mark mandatory parameters.
  3. 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

  1. added
    bot:triagedClassified by the community triage bot
    type:bugA defect in the code with a reproducible failure
    on Aug 19, 2026
  2. added
    staleNo activity after a maintainer request; queued for closing
    on Aug 29, 2026
  3. codebuff-team commented on Aug 29, 2026

    @codebuff-team
    Contributor

    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.

  4. enieuwy commented on Sep 2, 2026

    @enieuwy

    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):

    Invalid parameters for <server>__report_progress:
      path: ["note"]  →  "Invalid input: expected string, received undefined"
    

    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 recorded input was empty:

    model client build tool input:{} carried args
    GLM 5.3 Flash 0.0.166 report_finding 14 0
    GLM 5.3 Flash 0.0.166 report_progress 36 0
    gpt-5.6 luna 0.0.163 report_progress 4 0
    gpt-5.6 luna 0.0.166 report_finding 0 1
    gpt-5.6 luna 0.0.166 report_progress 1 0
    MiMo 2.5 0.0.163 report_finding 1 40
    MiMo 2.5 0.0.163 query_ledger 0 12
    MiMo 2.5 0.0.163 report_progress 0 8

    On the same build (0.0.163), against the same server and the same tools/list response, 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 properties were 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":{} with expected 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

    note is 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/list response, the client log lines, or a minimal Go server that reproduces it if any of that is useful.

  5. removed
    staleNo activity after a maintainer request; queued for closing
    on Sep 3, 2026
  6. hsm207 commented on Sep 3, 2026

    @hsm207

    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 except simulate-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.

  7. added
    staleNo activity after a maintainer request; queued for closing
    on Oct 5, 2026
  8. codebuff-team commented on Oct 5, 2026

    @codebuff-team
    Contributor

    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.

  9. hsm207 commented on Oct 6, 2026

    @hsm207

    it's not stale

  10. removed
    staleNo activity after a maintainer request; queued for closing
    on Oct 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bot:triagedClassified by the community triage bottype:bugA defect in the code with a reproducible failure

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions