Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"yaml": "^2.7.1"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.8.0",
"@modelcontextprotocol/sdk": "^1.11.2",
"@mongodb-js/device-id": "^0.2.1",
"@mongodb-js/devtools-connect": "^3.7.2",
"@mongosh/service-provider-node-driver": "^3.6.0",
Expand Down
4 changes: 3 additions & 1 deletion src/common/atlas/apiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ export class ApiClient {

private getAccessToken = async () => {
if (this.oauth2Client && (!this.accessToken || this.accessToken.expired())) {
this.accessToken = await this.oauth2Client.getToken({});
this.accessToken = await this.oauth2Client.getToken({
agent: this.options.userAgent,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flyby - user agents

});
}
return this.accessToken?.token.access_token as string | undefined;
};
Expand Down
1 change: 0 additions & 1 deletion src/tools/mongodb/metadata/listDatabases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export class ListDatabasesTool extends MongoDBToolBase {
protected name = "list-databases";
protected description = "List all databases for a MongoDB connection";
protected argsShape = {};

protected operationType: OperationType = "metadata";

protected async execute(): Promise<CallToolResult> {
Expand Down
35 changes: 33 additions & 2 deletions src/tools/tool.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z, type ZodRawShape, type ZodNever, AnyZodObject } from "zod";
import type { McpServer, RegisteredTool, ToolCallback } from "@modelcontextprotocol/sdk/server/mcp.js";
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import type { CallToolResult, ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
import { Session } from "../session.js";
import logger, { LogId } from "../logger.js";
import { Telemetry } from "../telemetry/telemetry.js";
Expand All @@ -27,6 +27,34 @@ export abstract class ToolBase {

protected abstract argsShape: ZodRawShape;

protected get annotations(): ToolAnnotations {
const annotations: ToolAnnotations = {
title: this.name,
description: this.description,
};

switch (this.operationType) {
case "read":
case "metadata":
annotations.readOnlyHint = true;
annotations.destructiveHint = false;
break;
case "delete":
annotations.readOnlyHint = false;
annotations.destructiveHint = true;
break;
case "create":
case "update":
annotations.destructiveHint = false;
annotations.readOnlyHint = false;
break;
default:
break;
}

return annotations;
}

protected abstract execute(...args: Parameters<ToolCallback<typeof this.argsShape>>): Promise<CallToolResult>;

constructor(
Expand Down Expand Up @@ -56,7 +84,7 @@ export abstract class ToolBase {
}
};

server.tool(this.name, this.description, this.argsShape, callback);
server.tool(this.name, this.description, this.argsShape, this.annotations, callback);

// This is very similar to RegisteredTool.update, but without the bugs around the name.
// In the upstream update method, the name is captured in the closure and not updated when
Expand All @@ -65,14 +93,17 @@ export abstract class ToolBase {
this.update = (updates: { name?: string; description?: string; inputSchema?: AnyZodObject }) => {
const tools = server["_registeredTools"] as { [toolName: string]: RegisteredTool };
const existingTool = tools[this.name];
existingTool.annotations = this.annotations;

if (updates.name && updates.name !== this.name) {
existingTool.annotations.title = updates.name;
delete tools[this.name];
this.name = updates.name;
tools[this.name] = existingTool;
}

if (updates.description) {
existingTool.annotations.description = updates.description;
existingTool.description = updates.description;
this.description = updates.description;
}
Expand Down
3 changes: 3 additions & 0 deletions tests/integration/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,9 @@ export function validateToolMetadata(
expectDefined(tool);
expect(tool.description).toBe(description);

expectDefined(tool.annotations);
expect(tool.annotations.title).toBe(name);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add a small test for the readOnlyHint and stuff too?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep! added

expect(tool.annotations.description).toBe(description);
const toolParameters = getParameters(tool);
expect(toolParameters).toHaveLength(parameters.length);
expect(toolParameters).toIncludeAllMembers(parameters);
Expand Down
Loading