Skip to content

Commit c1b3eea

Browse files
chore: MCP-237 tests to check that tools not usable are not even registered
1 parent 319cab3 commit c1b3eea

File tree

2 files changed

+68
-31
lines changed

2 files changed

+68
-31
lines changed

src/server.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
UnsubscribeRequestSchema,
1919
} from "@modelcontextprotocol/sdk/types.js";
2020
import assert from "assert";
21-
import type { ToolBase } from "./tools/tool.js";
21+
import type { ToolBase, ToolConstructorParams } from "./tools/tool.js";
2222
import { validateConnectionString } from "./helpers/connectionOptions.js";
2323
import { packageInfo } from "./common/packageInfo.js";
2424
import { type ConnectionErrorHandler } from "./common/connectionErrorHandler.js";
@@ -31,6 +31,7 @@ export interface ServerOptions {
3131
telemetry: Telemetry;
3232
elicitation: Elicitation;
3333
connectionErrorHandler: ConnectionErrorHandler;
34+
toolConstructors?: (new (params: ToolConstructorParams) => ToolBase)[];
3435
}
3536

3637
export class Server {
@@ -39,6 +40,7 @@ export class Server {
3940
private readonly telemetry: Telemetry;
4041
public readonly userConfig: UserConfig;
4142
public readonly elicitation: Elicitation;
43+
private readonly toolConstructors: (new (params: ToolConstructorParams) => ToolBase)[];
4244
public readonly tools: ToolBase[] = [];
4345
public readonly connectionErrorHandler: ConnectionErrorHandler;
4446

@@ -51,14 +53,23 @@ export class Server {
5153
private readonly startTime: number;
5254
private readonly subscriptions = new Set<string>();
5355

54-
constructor({ session, mcpServer, userConfig, telemetry, connectionErrorHandler, elicitation }: ServerOptions) {
56+
constructor({
57+
session,
58+
mcpServer,
59+
userConfig,
60+
telemetry,
61+
connectionErrorHandler,
62+
elicitation,
63+
toolConstructors,
64+
}: ServerOptions) {
5565
this.startTime = Date.now();
5666
this.session = session;
5767
this.telemetry = telemetry;
5868
this.mcpServer = mcpServer;
5969
this.userConfig = userConfig;
6070
this.elicitation = elicitation;
6171
this.connectionErrorHandler = connectionErrorHandler;
72+
this.toolConstructors = toolConstructors ?? [...AtlasTools, ...MongoDbTools];
6273
}
6374

6475
async connect(transport: Transport): Promise<void> {
@@ -206,7 +217,7 @@ export class Server {
206217
}
207218

208219
private registerTools(): void {
209-
for (const toolConstructor of [...AtlasTools, ...MongoDbTools]) {
220+
for (const toolConstructor of this.toolConstructors) {
210221
const tool = new toolConstructor({
211222
session: this.session,
212223
config: this.userConfig,

tests/integration/tools/mongodb/mongodbTool.test.ts

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { vi, it, describe, beforeEach, afterEach, type MockedFunction, afterAll, expect } from "vitest";
1+
import { vi, it, describe, beforeEach, afterEach, afterAll, expect } from "vitest";
22
import { type CallToolResult } from "@modelcontextprotocol/sdk/types.js";
33
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
44
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
55
import { MongoDBToolBase } from "../../../../src/tools/mongodb/mongodbTool.js";
6-
import { type OperationType } from "../../../../src/tools/tool.js";
6+
import { type ToolBase, type ToolConstructorParams, type OperationType } from "../../../../src/tools/tool.js";
77
import { defaultDriverOptions, type UserConfig } from "../../../../src/common/config.js";
88
import { MCPConnectionManager } from "../../../../src/common/connectionManager.js";
99
import { Session } from "../../../../src/common/session.js";
@@ -19,6 +19,7 @@ import { setupMongoDBIntegrationTest } from "./mongodbHelpers.js";
1919
import { ErrorCodes } from "../../../../src/common/errors.js";
2020
import { Keychain } from "../../../../src/common/keychain.js";
2121
import { Elicitation } from "../../../../src/elicitation.js";
22+
import { MongoDbTools } from "../../../../src/tools/mongodb/tools.js";
2223

2324
const injectedErrorHandler: ConnectionErrorHandler = (error) => {
2425
switch (error.code) {
@@ -51,29 +52,45 @@ const injectedErrorHandler: ConnectionErrorHandler = (error) => {
5152
}
5253
};
5354

54-
describe("MongoDBTool implementations", () => {
55-
const mdbIntegration = setupMongoDBIntegrationTest({ enterprise: false }, []);
56-
const executeStub: MockedFunction<() => Promise<CallToolResult>> = vi
57-
.fn()
58-
.mockResolvedValue({ content: [{ type: "text", text: "Something" }] });
59-
class RandomTool extends MongoDBToolBase {
60-
name = "Random";
61-
operationType: OperationType = "read";
62-
protected description = "This is a tool.";
63-
protected argsShape = {};
64-
public async execute(): Promise<CallToolResult> {
65-
await this.ensureConnected();
66-
return executeStub();
55+
class RandomTool extends MongoDBToolBase {
56+
name = "Random";
57+
operationType: OperationType = "read";
58+
protected description = "This is a tool.";
59+
protected argsShape = {};
60+
public async execute(): Promise<CallToolResult> {
61+
await this.ensureConnected();
62+
return { content: [{ type: "text", text: "Something" }] };
63+
}
64+
}
65+
66+
class UnusableVoyageTool extends MongoDBToolBase {
67+
name = "UnusableVoyageTool";
68+
operationType: OperationType = "read";
69+
protected description = "This is a Voyage tool.";
70+
protected argsShape = {};
71+
72+
override verifyAllowed(): boolean {
73+
if (this.config.voyageApiKey.trim()) {
74+
return super.verifyAllowed();
6775
}
76+
return false;
77+
}
78+
public async execute(): Promise<CallToolResult> {
79+
await this.ensureConnected();
80+
return { content: [{ type: "text", text: "Something" }] };
6881
}
82+
}
83+
84+
describe("MongoDBTool implementations", () => {
85+
const mdbIntegration = setupMongoDBIntegrationTest({ enterprise: false }, []);
6986

70-
let tool: RandomTool | undefined;
7187
let mcpClient: Client | undefined;
7288
let mcpServer: Server | undefined;
7389
let deviceId: DeviceId | undefined;
7490

7591
async function cleanupAndStartServer(
7692
config: Partial<UserConfig> | undefined = {},
93+
toolConstructors: (new (params: ToolConstructorParams) => ToolBase)[] = [...MongoDbTools, RandomTool],
7794
errorHandler: ConnectionErrorHandler | undefined = connectionErrorHandler
7895
): Promise<void> {
7996
await cleanup();
@@ -126,16 +143,9 @@ describe("MongoDBTool implementations", () => {
126143
mcpServer: internalMcpServer,
127144
connectionErrorHandler: errorHandler,
128145
elicitation,
146+
toolConstructors,
129147
});
130148

131-
tool = new RandomTool({
132-
session,
133-
config: userConfig,
134-
telemetry,
135-
elicitation,
136-
});
137-
tool.register(mcpServer);
138-
139149
await mcpServer.connect(serverTransport);
140150
await mcpClient.connect(clientTransport);
141151
}
@@ -150,8 +160,6 @@ describe("MongoDBTool implementations", () => {
150160

151161
deviceId?.close();
152162
deviceId = undefined;
153-
154-
tool = undefined;
155163
}
156164

157165
beforeEach(async () => {
@@ -232,7 +240,7 @@ describe("MongoDBTool implementations", () => {
232240

233241
describe("when MCP is using injected connection error handler", () => {
234242
beforeEach(async () => {
235-
await cleanupAndStartServer(defaultTestConfig, injectedErrorHandler);
243+
await cleanupAndStartServer(defaultTestConfig, [...MongoDbTools, RandomTool], injectedErrorHandler);
236244
});
237245

238246
describe("and comes across a MongoDB Error - NotConnectedToMongoDB", () => {
@@ -256,7 +264,11 @@ describe("MongoDBTool implementations", () => {
256264
describe("and comes across a MongoDB Error - MisconfiguredConnectionString", () => {
257265
it("should handle the error", async () => {
258266
// This is a misconfigured connection string
259-
await cleanupAndStartServer({ connectionString: "mongodb://localhost:1234" }, injectedErrorHandler);
267+
await cleanupAndStartServer(
268+
{ connectionString: "mongodb://localhost:1234" },
269+
[...MongoDbTools, RandomTool],
270+
injectedErrorHandler
271+
);
260272
const toolResponse = await mcpClient?.callTool({
261273
name: "Random",
262274
arguments: {},
@@ -278,6 +290,7 @@ describe("MongoDBTool implementations", () => {
278290
// This is a misconfigured connection string
279291
await cleanupAndStartServer(
280292
{ connectionString: mdbIntegration.connectionString(), indexCheck: true },
293+
[...MongoDbTools, RandomTool],
281294
injectedErrorHandler
282295
);
283296
const toolResponse = await mcpClient?.callTool({
@@ -299,4 +312,17 @@ describe("MongoDBTool implementations", () => {
299312
});
300313
});
301314
});
315+
316+
describe("when a tool is not usable", () => {
317+
it("should not even be registered", async () => {
318+
await cleanupAndStartServer(
319+
{ connectionString: mdbIntegration.connectionString(), indexCheck: true },
320+
[RandomTool, UnusableVoyageTool],
321+
injectedErrorHandler
322+
);
323+
const tools = await mcpClient?.listTools({});
324+
expect(tools?.tools).toHaveLength(1);
325+
expect(tools?.tools.find((tool) => tool.name === "UnusableTool")).toBeUndefined();
326+
});
327+
});
302328
});

0 commit comments

Comments
 (0)