diff --git a/src/server.ts b/src/server.ts index fd16c75d..14bea760 100644 --- a/src/server.ts +++ b/src/server.ts @@ -8,6 +8,8 @@ import { mongoLogId } from "mongodb-log-writer"; import { ObjectId } from "mongodb"; import { Telemetry } from "./telemetry/telemetry.js"; import { UserConfig } from "./config.js"; +import { CallToolRequestSchema, CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import assert from "assert"; export interface ServerOptions { session: Session; @@ -33,6 +35,29 @@ export class Server { this.registerTools(); this.registerResources(); + // This is a workaround for an issue we've seen with some models, where they'll see that everything in the `arguments` + // object is optional, and then not pass it at all. However, the MCP server expects the `arguments` object to be if + // the tool accepts any arguments, even if they're all optional. + // + // see: https://github.com/modelcontextprotocol/typescript-sdk/blob/131776764536b5fdca642df51230a3746fb4ade0/src/server/mcp.ts#L705 + // Since paramsSchema here is not undefined, the server will create a non-optional z.object from it. + const existingHandler = ( + this.mcpServer.server["_requestHandlers"] as Map< + string, + (request: unknown, extra: unknown) => Promise + > + ).get(CallToolRequestSchema.shape.method.value); + + assert(existingHandler, "No existing handler found for CallToolRequestSchema"); + + this.mcpServer.server.setRequestHandler(CallToolRequestSchema, (request, extra): Promise => { + if (!request.params.arguments) { + request.params.arguments = {}; + } + + return existingHandler(request, extra); + }); + await initializeLogger(this.mcpServer, this.userConfig.logPath); await this.mcpServer.connect(transport); diff --git a/tests/integration/tools/mongodb/metadata/connect.test.ts b/tests/integration/tools/mongodb/metadata/connect.test.ts index 017c6779..3b5eb6c5 100644 --- a/tests/integration/tools/mongodb/metadata/connect.test.ts +++ b/tests/integration/tools/mongodb/metadata/connect.test.ts @@ -15,6 +15,23 @@ describeWithMongoDB("Connect tool", (integration) => { }, ]); + describe("without arguments", () => { + it("prompts for connection string if not set", async () => { + const response = await integration.mcpClient().callTool({ name: "connect" }); + const content = getResponseContent(response.content); + expect(content).toContain("No connection details provided"); + }); + + it("connects to the database if connection string is set", async () => { + config.connectionString = integration.connectionString(); + + const response = await integration.mcpClient().callTool({ name: "connect" }); + const content = getResponseContent(response.content); + expect(content).toContain("Successfully connected"); + expect(content).toContain(integration.connectionString()); + }); + }); + describe("with default config", () => { describe("without connection string", () => { it("prompts for connection string", async () => {