Skip to content

Client drops _meta from input_required results when allowInputRequired: true (2026-07-28) #2861

Description

@KushGabani

Describe the bug

With protocol revision 2026-07-28, a client that calls a tool with { allowInputRequired: true } gets an InputRequiredResult that has no _meta, even when the server's result includes _meta.

InputRequiredResult is a Result, so _meta is valid on it, and the SDK's own InputRequiredResultSchema (wireResult(...)) includes _meta. But the manual input_required path rebuilds the result from only inputRequests and requestState, so the field is lost. A server cannot send result-level metadata (for example display hints for the inputRequests) to a client that fulfils rounds by itself.

Where it happens

The field is dropped in two places:

  1. core-internal/src/wire/rev2026-07-28/codec.ts, in decodeResult, in the input_required branch:
    return {
        kind: 'input_required',
        inputRequests,
        ...(typeof requestState === 'string' && { requestState })
    };
  2. core-internal/src/shared/inputRequiredEngine.ts, in manualInputRequiredValue:
    return {
        resultType: 'input_required',
        inputRequests: decoded.inputRequests,
        ...(decoded.requestState !== undefined && { requestState: decoded.requestState })
    };

The transport's JSONRPCMessageSchema.parse keeps result._meta. The field is lost only in these two steps.

To reproduce

Server: return this from a tools/call handler:

{
  "resultType": "input_required",
  "requestState": "state-1",
  "inputRequests": {
    "approval": {
      "method": "elicitation/create",
      "params": { "mode": "form", "message": "Allow?", "requestedSchema": { "type": "object", "properties": {} } }
    }
  },
  "_meta": { "example/hint": { "tools": ["a"] } }
}

Client:

const client = new Client(
  { name: "repro", version: "0.0.0" },
  {
    capabilities: { elicitation: { form: {} } },
    supportedProtocolVersions: ["2026-07-28"],
    versionNegotiation: { mode: { pin: "2026-07-28" } },
    inputRequired: { autoFulfill: false },
  },
);
await client.connect(new StreamableHTTPClientTransport(new URL(url)));
const result = await client.callTool({ name: "tool", arguments: {} }, { allowInputRequired: true });
console.log(result._meta); // undefined

Expected behavior

result._meta equals the _meta that the server sent, the same as for a complete result.

Actual behavior

result._meta is undefined.

Suggested fix

Carry _meta through both steps:

// codec.ts, input_required branch
const meta = raw['_meta'];
return {
    kind: 'input_required',
    inputRequests,
    ...(typeof requestState === 'string' && { requestState }),
    ...(isPlainObject(meta) && { _meta: meta })
};

// inputRequiredEngine.ts
return {
    resultType: 'input_required',
    inputRequests: decoded.inputRequests as InputRequiredResult['inputRequests'],
    ...(decoded.requestState !== undefined && { requestState: decoded.requestState }),
    ...(decoded._meta !== undefined && { _meta: decoded._meta })
};

We use this change as a local pnpm patch on @modelcontextprotocol/client@2.0.0, and a real-HTTP test confirms that _meta reaches the caller.

Versions

  • @modelcontextprotocol/client 2.0.0 (the same code is in 2.1.0)
  • Protocol revision 2026-07-28
  • Node.js 22.19.0

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

    v2Ideas, requests and plans for v2 of the SDK which will incorporate major changes and fixes

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions