fix(mcp): preserve metadata across tool pages (#35439)

This commit is contained in:
Aiden Cline 2026-07-05 22:28:28 -05:00 committed by GitHub
commit 2b34df94fa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 111 additions and 1 deletions

View file

@ -1,6 +1,10 @@
import { describe, expect, test } from "bun:test"
import type { Client } from "@modelcontextprotocol/sdk/client/index.js"
import { Client } from "@modelcontextprotocol/sdk/client/index.js"
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"
import { Server } from "@modelcontextprotocol/sdk/server/index.js"
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js"
import { McpCatalog } from "@/mcp/catalog"
import { Effect } from "effect"
const options = { toolCallId: "call_mcp", abortSignal: new AbortController().signal } as any
@ -45,3 +49,59 @@ describe("McpCatalog.convertTool", () => {
})
})
})
test("preserves output schema validation across paginated tool discovery", async () => {
const server = new Server({ name: "pagination", version: "1.0.0" }, { capabilities: { tools: {} } })
server.setRequestHandler(ListToolsRequestSchema, ({ params }) =>
Promise.resolve(
params?.cursor === "page-2"
? {
tools: [
{
name: "second",
inputSchema: { type: "object" },
outputSchema: {
type: "object",
properties: { value: { type: "number" } },
required: ["value"],
},
},
],
}
: {
tools: [
{
name: "first",
inputSchema: { type: "object" },
outputSchema: {
type: "object",
properties: { value: { type: "string" } },
required: ["value"],
},
},
],
nextCursor: "page-2",
},
),
)
server.setRequestHandler(CallToolRequestSchema, ({ params }) =>
Promise.resolve({
content: [],
structuredContent: { value: params.name === "first" ? 42 : 1 },
}),
)
const client = new Client({ name: "pagination-test", version: "1.0.0" })
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair()
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)])
try {
const tools = await Effect.runPromise(McpCatalog.defs(client))
expect(tools?.map((tool) => tool.name)).toEqual(["first", "second"])
await expect(client.callTool({ name: "first", arguments: {} })).rejects.toThrow(
"Structured content does not match the tool's output schema",
)
} finally {
await Promise.all([client.close(), server.close()])
}
})