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

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

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