Skip to content

McpServer: unknown tool-call keys are silently stripped; Tool.StrictJsonSchema is not honoured and there is no way to decode strictly #8218

Closed

Description

@spencerbeggs

What version of Effect is running?

4.0.0-rc.115 (also current main, packages/effect/src/unstable/ai/McpServer.ts and Toolkit.ts)

What steps can reproduce the bug?

Register a tool with a struct parameter schema through McpServer.toolkit and call it with an extra, unknown key (for example a misspelled parameter).

import { Effect, Layer, Schema } from "effect"
import { McpServer, Tool, Toolkit } from "effect/unstable/ai"

const Echo = Tool.make("echo", {
  parameters: { text: Schema.String, limit: Schema.optionalKey(Schema.Number) },
  success: Schema.String
})

const toolkit = Toolkit.make(Echo)

const ToolkitLayer = toolkit.toLayer({
  echo: ({ text }) => Effect.succeed(text)
})

// Serve over stdio with McpServer.layerStdio and call:
// { "method": "tools/call", "params": { "name": "echo",
//   "arguments": { "text": "hi", "limti": 5 } } }

What is the expected behavior?

Some way for the server to reject the call, since limti is not an accepted parameter. Either:

  1. McpServer honours the existing Tool.StrictJsonSchema annotation: when a tool is strict, tools/list serves additionalProperties: false on every object level of inputSchema, and tools/call decodes with onExcessProperty: "error" and returns the same InvalidParams / isError path used for a wrong type; or
  2. McpServer.toolkit (or Toolkit.toLayer) accepts Schema parse options so a server can opt in.

What do you see instead?

The call succeeds. Toolkit.ts decodes parameters with a bare Schema.decodeUnknownEffect(tool.parametersSchema) (line 254), so Schema's default onExcessProperty: "ignore" silently drops limti, and the handler runs with limit unset. The served inputSchema has no additionalProperties, so clients cannot detect the problem either.

Tool.StrictJsonSchema exists but is only consulted by LLM provider clients (it maps to OpenAI's strict: true); McpServer never reads it.

Additional information

This matters more for an MCP server than for an ordinary API: the caller is a model that cannot see that a key was dropped, so a typo turns into a silently different tool call with a default value. We worked around it with a custom registration over McpServer.McpServer.addTool that walks the raw payload for unknown keys and rewrites the served schema with additionalProperties: false; happy to upstream that shape if option 1 is the preferred direction.

Related but distinct: #6355 is about a client rejecting additionalProperties in a served schema; this issue is about the server accepting keys it never declared.

Activity

  1. lloydrichards commented on Sep 13, 2026

    @lloydrichards
    Contributor

    might want to wait til the next rc cause I just rewrote a lot of the McpServer in order to get the new v2026-07-28 protocol working and part of that was a shopping list of issues:

    🤖 We made 34 correctness-focused repairs:

    • 9 fixed bugs already present on main, including JSON-RPC error handling, cancellation, batch replies, request metadata, elicitation schemas, notification scheduling, and request context.
    • 4 fixed existing RPC/MCP weaknesses exposed by the new work, mainly queue backpressure, disconnected-client cleanup, HTTP streaming, and response framing.
    • 21 fixed issues introduced while implementing the July protocol, including routing headers, capability checks, multi-round-trip requests, subscriptions, notification isolation, reverse-response ownership, and request lifecycle cleanup.
      Of the 20 concrete bugs raised directly in PR review comments, 5 existed on main and 15 were introduced by the branch.

    You can check #7265 and #8201. But if its still an issue once thats live, let me know and I'll have a look

    Edit: actually I can just check now and see 💡

  2. spencerbeggs commented on Sep 14, 2026

    @spencerbeggs
    ContributorAuthor

    @lloydrichards Awesome. Looking forward to it. I migrated 3 mcps from @modelcontextprotocol/* to Native MCP at rc.115 and it went really well.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions