diff --git a/src/server.ts b/src/server.ts index 72d9c4f9..e3e399a0 100644 --- a/src/server.ts +++ b/src/server.ts @@ -5,6 +5,7 @@ import { AtlasTools } from "./tools/atlas/tools.js"; import { MongoDbTools } from "./tools/mongodb/tools.js"; import logger, { initializeLogger } from "./logger.js"; import { mongoLogId } from "mongodb-log-writer"; +import config from "./config.js"; export class Server { public readonly session: Session; @@ -19,6 +20,7 @@ export class Server { this.mcpServer.server.registerCapabilities({ logging: {} }); this.registerTools(); + this.registerResources(); await initializeLogger(this.mcpServer); @@ -37,4 +39,26 @@ export class Server { new tool(this.session).register(this.mcpServer); } } + + private registerResources() { + if (config.connectionString) { + this.mcpServer.resource( + "connection-string", + "config://connection-string", + { + description: "Preconfigured connection string that will be used as a default in the `connect` tool", + }, + (uri) => { + return { + contents: [ + { + text: `Preconfigured connection string: ${config.connectionString}`, + uri: uri.href, + }, + ], + }; + } + ); + } + } } diff --git a/src/tools/mongodb/metadata/connect.ts b/src/tools/mongodb/metadata/connect.ts index 98d5b015..fad117da 100644 --- a/src/tools/mongodb/metadata/connect.ts +++ b/src/tools/mongodb/metadata/connect.ts @@ -9,38 +9,46 @@ export class ConnectTool extends MongoDBToolBase { protected name = "connect"; protected description = "Connect to a MongoDB instance"; protected argsShape = { - connectionStringOrClusterName: z - .string() + options: z + .array( + z + .union([ + z.object({ + connectionString: z + .string() + .describe("MongoDB connection string (in the mongodb:// or mongodb+srv:// format)"), + }), + z.object({ + clusterName: z.string().describe("MongoDB cluster name"), + }), + ]) + .optional() + ) .optional() - .describe("MongoDB connection string (in the mongodb:// or mongodb+srv:// format) or cluster name"), + .describe( + "Options for connecting to MongoDB. If not provided, the connection string from the config://connection-string resource will be used. If the user hasn't specified Atlas cluster name or a connection string explicitly and the `config://connection-string` resource is present, always invoke this with no arguments." + ), }; protected operationType: OperationType = "metadata"; - protected async execute({ - connectionStringOrClusterName, - }: ToolArgs): Promise { - connectionStringOrClusterName ??= config.connectionString; - if (!connectionStringOrClusterName) { + protected async execute({ options: optionsArr }: ToolArgs): Promise { + const options = optionsArr?.[0]; + let connectionString: string; + if (!options && !config.connectionString) { return { content: [ { type: "text", text: "No connection details provided." }, { type: "text", text: "Please provide either a connection string or a cluster name" }, - { - type: "text", - text: "Alternatively, you can use the default deployment at mongodb://localhost:27017", - }, ], }; } - let connectionString: string; - - if ( - connectionStringOrClusterName.startsWith("mongodb://") || - connectionStringOrClusterName.startsWith("mongodb+srv://") - ) { - connectionString = connectionStringOrClusterName; + if (!options) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + connectionString = config.connectionString!; + } else if ("connectionString" in options) { + connectionString = options.connectionString; } else { // TODO: https://github.com/mongodb-js/mongodb-mcp-server/issues/19 // We don't support connecting via cluster name since we'd need to obtain the user credentials diff --git a/tests/integration/helpers.ts b/tests/integration/helpers.ts index f8f797d0..bd951979 100644 --- a/tests/integration/helpers.ts +++ b/tests/integration/helpers.ts @@ -148,7 +148,7 @@ export function setupIntegrationTest(): { connectMcpClient: async () => { await getMcpClient().callTool({ name: "connect", - arguments: { connectionStringOrClusterName: getConnectionString() }, + arguments: { options: [{ connectionString: getConnectionString() }] }, }); }, randomDbName: () => randomDbName, diff --git a/tests/integration/tools/mongodb/metadata/connect.test.ts b/tests/integration/tools/mongodb/metadata/connect.test.ts index 28cb6eb0..a62f5e8d 100644 --- a/tests/integration/tools/mongodb/metadata/connect.test.ts +++ b/tests/integration/tools/mongodb/metadata/connect.test.ts @@ -13,9 +13,10 @@ describe("Connect tool", () => { validateParameters(connectTool, [ { - name: "connectionStringOrClusterName", - description: "MongoDB connection string (in the mongodb:// or mongodb+srv:// format) or cluster name", - type: "string", + name: "options", + description: + "Options for connecting to MongoDB. If not provided, the connection string from the config://connection-string resource will be used. If the user hasn't specified Atlas cluster name or a connection string explicitly and the `config://connection-string` resource is present, always invoke this with no arguments.", + type: "array", required: false, }, ]); @@ -27,7 +28,6 @@ describe("Connect tool", () => { const response = await integration.mcpClient().callTool({ name: "connect", arguments: {} }); const content = getResponseContent(response.content); expect(content).toContain("No connection details provided"); - expect(content).toContain("mongodb://localhost:27017"); }); }); @@ -35,7 +35,13 @@ describe("Connect tool", () => { it("connects to the database", async () => { const response = await integration.mcpClient().callTool({ name: "connect", - arguments: { connectionStringOrClusterName: integration.connectionString() }, + arguments: { + options: [ + { + connectionString: integration.connectionString(), + }, + ], + }, }); const content = getResponseContent(response.content); expect(content).toContain("Successfully connected"); @@ -47,7 +53,7 @@ describe("Connect tool", () => { it("returns error message", async () => { const response = await integration.mcpClient().callTool({ name: "connect", - arguments: { connectionStringOrClusterName: "mongodb://localhost:12345" }, + arguments: { options: [{ connectionString: "mongodb://localhost:12345" }] }, }); const content = getResponseContent(response.content); expect(content).toContain("Error running connect"); @@ -74,7 +80,13 @@ describe("Connect tool", () => { const newConnectionString = `${integration.connectionString()}?appName=foo-bar`; const response = await integration.mcpClient().callTool({ name: "connect", - arguments: { connectionStringOrClusterName: newConnectionString }, + arguments: { + options: [ + { + connectionString: newConnectionString, + }, + ], + }, }); const content = getResponseContent(response.content); expect(content).toContain("Successfully connected"); @@ -85,7 +97,13 @@ describe("Connect tool", () => { it("suggests the config connection string if set", async () => { const response = await integration.mcpClient().callTool({ name: "connect", - arguments: { connectionStringOrClusterName: "mongodb://localhost:12345" }, + arguments: { + options: [ + { + connectionString: "mongodb://localhost:12345", + }, + ], + }, }); const content = getResponseContent(response.content); expect(content).toContain("Failed to connect to MongoDB at 'mongodb://localhost:12345'"); @@ -98,7 +116,13 @@ describe("Connect tool", () => { config.connectionString = "mongodb://localhost:12345"; const response = await integration.mcpClient().callTool({ name: "connect", - arguments: { connectionStringOrClusterName: "mongodb://localhost:12345" }, + arguments: { + options: [ + { + connectionString: "mongodb://localhost:12345", + }, + ], + }, }); const content = getResponseContent(response.content);