From 64cb9bfa2cca360fd817b2f6943ff777b3545789 Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Wed, 26 Nov 2025 21:35:06 +0100 Subject: [PATCH 1/2] chore: add developer guide and code examples to extend MCP server via library exports --- MCP_SERVER_LIBRARY.md | 973 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 973 insertions(+) create mode 100644 MCP_SERVER_LIBRARY.md diff --git a/MCP_SERVER_LIBRARY.md b/MCP_SERVER_LIBRARY.md new file mode 100644 index 00000000..25e69c91 --- /dev/null +++ b/MCP_SERVER_LIBRARY.md @@ -0,0 +1,973 @@ +# Developer's Guide to Embedding and Extending the MongoDB MCP Server + +This guide explains how to embed and extend the MongoDB MCP Server as a library to customize its core functionality and behavior for your specific use cases. + +## 📚 Table of Contents + +- [Overview](#overview) +- [Installation](#installation) +- [Core Concepts](#core-concepts) +- [Use Cases](#use-cases) + - [Use Case 1: Override Server Configuration](#use-case-1-override-server-configuration) + - [Use Case 2: Per-Session Configuration](#use-case-2-per-session-configuration) + - [Use Case 3: Adding Custom Tools](#use-case-3-adding-custom-tools) +- [API Reference](#api-reference) +- [Advanced Topics](#advanced-topics) +- [Examples](#examples) + +## Overview + +The MongoDB MCP Server can be embedded in your own Node.js applications and customized to meet specific requirements. The library exports provide full control over: + +- Server configuration and initialization +- Per-session(MCP Client session) configuration hooks +- Custom tool registration +- Connection management and Connection error handling + +## Installation + +Install the MongoDB MCP Server package: + +```bash +npm install mongodb-mcp-server +``` + +The package provides both CommonJS and ES Module exports. + +## Core Concepts + +### Exported Modules + +The library exports are organized in two entry points: + +**Main Library (`mongodb-mcp-server`):** + +```typescript +import { + Server, + Session, + UserConfig, + UserConfigSchema, + parseCliArgumentsAsUserConfig, + StreamableHttpRunner, + StdioRunner, + TransportRunnerBase, + LoggerBase, + Telemetry, + Keychain, + Elicitation, + MongoDBError, + ErrorCodes, + connectionErrorHandler, + createMCPConnectionManager, + applyConfigOverrides, + // ... and more +} from "mongodb-mcp-server"; +``` + +**Tools (`mongodb-mcp-server/tools`):** + +```typescript +import { ToolBase } from "mongodb-mcp-server/tools"; +``` + +For detailed documentation of these exports and their usage, see the [API Reference](#api-reference) section. + +### Architecture + +The MongoDB MCP Server library follows a modular architecture: + +- **Transport Runners**: `StdioRunner` and `StreamableHttpRunner` manage the MCP transport layer +- **Server**: Core server that wraps the MCP Server and registers tools and resources +- **Session**: Per-client(MCP Client) connection and configuration state +- **Tools**: Individual capabilities exposed to the MCP client +- **Configuration**: User configuration with override mechanisms + +## Use Cases + +### Use Case 1: Override Server Configuration + +Configure the MCP server with custom settings, such as HTTP headers for authentication before establishing session for an MCP Client. + +#### Example: Setting HTTP Headers for Authentication + +```typescript +import { StreamableHttpRunner, UserConfigSchema } from "mongodb-mcp-server"; + +// Create a custom configuration with HTTP headers +const config = UserConfigSchema.parse({ + transport: "http", + httpPort: 3000, + httpHost: "127.0.0.1", + httpHeaders: { + "x-api-key": "your-secret-api-key", + }, + // Or your own connection string + connectionString: "mongodb://localhost:27017", + // Enable read-only mode for enhanced security + readOnly: true, +}); + +// Initialize and start the server +const runner = new StreamableHttpRunner({ userConfig: config }); +await runner.start(); + +console.log(`MongoDB MCP Server listening on ${runner.serverAddress}`); +``` + +Clients connecting to this server must include the specified headers in their requests, otherwise their Session initialization request is declined. + +#### Example: Customizing Tool Availability + +```typescript +import { StdioRunner, UserConfigSchema } from "mongodb-mcp-server"; + +const config = UserConfigSchema.parse({ + transport: "stdio", + // Or your own connection string + connectionString: "mongodb://localhost:27017", + // Disable write operations + readOnly: true, + // Disable specific tool categories + disabledTools: ["atlas", "atlas-local"], + // Customize tools requiring confirmation + confirmationRequiredTools: ["find", "aggregate"], + // Set query limits + maxDocumentsPerQuery: 50, + maxBytesPerQuery: 10 * 1024 * 1024, // 10MB +}); + +const runner = new StdioRunner({ userConfig: config }); +await runner.start(); +``` + +### Use Case 2: Per-Session Configuration + +Customize configuration for each MCP client session, enabling user-specific permissions and settings. + +#### Example: User-Based Tool Permissions + +```typescript +import { + UserConfigSchema, + StreamableHttpRunner, + type TransportRunnerConfig, +} from "mongodb-mcp-server"; +import type { OperationType } from "mongodb-mcp-server/tools"; + +// Example interface for user roles and permissions +interface UserPermissions { + role: "admin" | "developer" | "analyst"; + allowedOperations: OperationType[]; + maxDocuments: number; +} + +// Mock function to fetch user permissions (replace with your auth logic) +async function getUserPermissions(userId: string): Promise { + const userDb = { + "user-123": { + role: "analyst", + allowedOperations: ["read", "metadata"], + maxDocuments: 100, + }, + "user-456": { + role: "developer", + allowedOperations: ["read", "metadata", "create", "update"], + maxDocuments: 500, + }, + "user-789": { + role: "admin", + allowedOperations: ["read", "metadata", "create", "update", "delete"], + maxDocuments: 1000, + }, + } as Record; + + return ( + userDb[userId] || { + role: "analyst", + allowedOperations: ["read"], + maxDocuments: 10, + } + ); +} + +// Base configuration for all sessions +const baseConfig = UserConfigSchema.parse({ + transport: "http", + httpPort: 3000, + httpHost: "127.0.0.1", +}); + +// Session configuration hook +const createSessionConfig: TransportRunnerConfig["createSessionConfig"] = + async ({ userConfig, request }) => { + // Extract user ID from request headers + const userId = request?.headers?.["x-user-id"]; + + if (typeof userId !== "string") { + throw new Error("User authentication required: x-user-id header missing"); + } + + // Fetch user permissions + const permissions = await getUserPermissions(userId); + + // Build disabled tools based on permissions + const allOperations: OperationType[] = [ + "read", + "metadata", + "create", + "update", + "delete", + "connect", + ]; + + const disabledOperations = allOperations.filter( + (op) => !permissions.allowedOperations.includes(op) + ); + + // Return customized configuration for this session + return { + ...userConfig, + disabledTools: disabledOperations, + maxDocumentsPerQuery: permissions.maxDocuments, + // Analysts get read-only access + readOnly: permissions.role === "analyst", + }; + }; + +// Initialize the server with session configuration hook +const runner = new StreamableHttpRunner({ + userConfig: baseConfig, + createSessionConfig, +}); + +await runner.start(); +console.log( + `MongoDB MCP Server running with per-user permissions at ${runner.serverAddress}` +); +``` + +#### Example: Dynamic Connection String Selection + +```typescript +import { + UserConfigSchema, + StreamableHttpRunner, + type TransportRunnerConfig, +} from "mongodb-mcp-server"; + +// Connection strings for different environments +const connectionStrings = { + production: process.env.MONGODB_PRODUCTION_URI, + staging: process.env.MONGODB_STAGING_URI, + development: process.env.MONGODB_DEV_URI, +}; + +const createSessionConfig: TransportRunnerConfig["createSessionConfig"] = + async ({ userConfig, request }) => { + // Get environment from request header + const environment = request?.headers?.[ + "x-environment" + ] as keyof typeof connectionStrings; + + if (!environment || !connectionStrings[environment]) { + throw new Error("Invalid or missing x-environment header"); + } + + return { + ...userConfig, + connectionString: connectionStrings[environment], + // Production is read-only + readOnly: environment === "production", + }; + }; + +const runner = new StreamableHttpRunner({ + userConfig: UserConfigSchema.parse({ + transport: "http", + httpPort: 3000, + httpHost: "127.0.0.1", + }), + createSessionConfig, +}); + +await runner.start(); +console.log( + `MongoDB MCP Server running with dynamic connection selection at ${runner.serverAddress}` +); +``` + +#### Example: Integration with Request Overrides + +The library supports request-level configuration overrides when `allowRequestOverrides` is enabled. You can combine this with `createSessionConfig` for fine-grained control: + +```typescript +import { + applyConfigOverrides, + UserConfigSchema, + StreamableHttpRunner, + type UserConfig, + type TransportRunnerConfig, +} from "mongodb-mcp-server"; + +// Example interface for user roles and permissions +interface UserPermissions { + role: "admin" | "developer" | "analyst"; + requestOverridesAllowed: boolean; +} + +// Mock function to fetch user permissions (replace with your auth logic) +async function getUserPermissions(userId: string): Promise { + const userDb = { + "user-123": { + role: "analyst", + requestOverridesAllowed: false, + }, + "user-456": { + role: "developer", + requestOverridesAllowed: true, + }, + "user-789": { + role: "admin", + requestOverridesAllowed: true, + }, + } as Record; + + return ( + userDb[userId] || { + role: "analyst", + requestOverridesAllowed: false, + } + ); +} + +// Base configuration for all sessions +const baseConfig = UserConfigSchema.parse({ + transport: "http", + httpPort: 3000, + httpHost: "127.0.0.1", +}); + +const createSessionConfig: TransportRunnerConfig["createSessionConfig"] = + async ({ userConfig, request }) => { + // Extract user ID from request headers + const userId = request?.headers?.["x-user-id"]; + + if (typeof userId !== "string") { + throw new Error("User authentication required: x-user-id header missing"); + } + + // Fetch user permissions + const permissions = await getUserPermissions(userId); + + // Generate a base config based on the user permissions + const roleBasedConfig: UserConfig = { + ...baseConfig, + allowRequestOverrides: permissions.requestOverridesAllowed, + }; + + // Now attempt to apply the overrides. For roles where overrides are not + // allowed, the default override application function will throw and reject + // the initialization request. + return applyConfigOverrides({ baseConfig: roleBasedConfig, request }); + }; + +const runner = new StreamableHttpRunner({ + userConfig: UserConfigSchema.parse({ + transport: "http", + httpPort: 3000, + httpHost: "127.0.0.1", + allowRequestOverrides: true, + connectionString: process.env.MDB_MCP_CONNECTION_STRING, + }), + createSessionConfig, +}); + +await runner.start(); +console.log( + `MongoDB MCP Server running with role-based request overrides at ${runner.serverAddress}` +); +``` + +### Use Case 3: Adding Custom Tools + +Extend the MCP server with custom tools tailored to your application's needs. + +#### Example: Connection Selector Tool + +This example shows how to create a custom tool that provides users with a list of pre-configured database connections: + +```typescript +import { z } from "zod"; +import { + StdioRunner, + UserConfigSchema, + type UserConfig, +} from "mongodb-mcp-server"; +import { + ToolBase, + type ToolCategory, + type OperationType, +} from "mongodb-mcp-server/tools"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; + +// Define available connections +const AVAILABLE_CONNECTIONS = { + "prod-analytics": { + name: "Production Analytics", + connectionString: process.env.MONGODB_PROD_ANALYTICS_URI!, + description: "Production analytics database (read-only)", + readOnly: true, + }, + "staging-main": { + name: "Staging Main", + connectionString: process.env.MONGODB_STAGING_URI!, + description: "Staging environment database", + readOnly: false, + }, + "dev-local": { + name: "Development Local", + connectionString: "mongodb://localhost:27017/dev", + description: "Local development database", + readOnly: false, + }, +}; + +// Custom tool to list available connections. We are expecting LLM to call this +// tool to make user aware of possible connections the MCP server could be +// connected to. +class ListConnectionsTool extends ToolBase { + override name = "list-connections"; + override category = "mongodb" as ToolCategory; + override operationType = "metadata" as OperationType; + protected override description = + "Lists all available pre-configured MongoDB connections"; + protected override argsShape = {}; + + protected override async execute(): Promise { + // Ensure that we don't leak the actual connection strings to the model + // context. + const connections = Object.entries(AVAILABLE_CONNECTIONS).map( + ([id, conn]) => ({ + id, + name: conn.name, + description: conn.description, + readOnly: conn.readOnly, + }) + ); + + return { + content: [ + { + type: "text", + text: JSON.stringify(connections), + }, + ], + }; + } + + // We don't want to report any telemetry for this tool so leaving it empty. + protected override resolveTelemetryMetadata() { + return {}; + } +} + +// Custom tool to select a specific connection. Once user is made aware of list +// of connections, they can mention the name of the connection and LLM is then +// expected to call this tool with the name of the connection and the tool will +// internally connect to the pre-configured connection string. Notice how we +// never leak any connection details in the LLM context and maintain the +// effective communication using opaque connection identifiers. +class SelectConnectionTool extends ToolBase { + override name = "select-connection"; + override category = "mongodb" as ToolCategory; + override operationType = "metadata" as OperationType; + protected override description = + "Select and connect to a pre-configured MongoDB connection by ID"; + protected override argsShape = { + connectionId: z + .enum(Object.keys(AVAILABLE_CONNECTIONS) as [string, ...string[]]) + .describe("The ID of the connection to select"), + }; + + protected override async execute(args: { + connectionId: string; + }): Promise { + const { connectionId } = args; + const connection = AVAILABLE_CONNECTIONS[connectionId]; + + if (!connection) { + return { + content: [ + { + type: "text", + text: `Error: Connection '${connectionId}' not found. Use the list-connections tool to see available connections.`, + }, + ], + isError: true, + }; + } + + try { + // Disconnect from current connection if any + await this.session.disconnect(); + + // Connect to the new connection using the MongoDB MCP's own + // ConnectionManager. The inbuilt connection manager is capable of + // handling all the connection related task as long as we are able to + // provide a `ConnectionInfo` like object to connect. + await this.session.connectionManager.connect({ + connectionString: connection.connectionString, + }); + + return { + content: [ + { + type: "text", + text: `Successfully switched to connection '${ + connection.name + }' (${connectionId})${ + connection.readOnly ? " in READ-ONLY mode" : "" + }.`, + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: `Failed to switch to connection '${connectionId}': ${ + error instanceof Error ? error.message : String(error) + }`, + }, + ], + isError: true, + }; + } + } + + // We don't want to report any telemetry for this tool so leaving it empty. + protected override resolveTelemetryMetadata() { + return {}; + } +} + +// Initialize the server with custom tools +const runner = new StdioRunner({ + userConfig: UserConfigSchema.parse({ + transport: "stdio", + // Don't provide a default connection string + connectionString: undefined, + // Disable existing connect tools and Atlas tools + disabledTools: ["connect", "switch-connection", "atlas"], + }), + // Add the new connection tools to list and connect to pre-configured + // connection + additionalTools: [ListConnectionsTool, SelectConnectionTool], +}); + +await runner.start(); +console.log( + `MongoDB MCP Server running with custom connection selector tools at ${runner.serverAddress}` +); +``` + +## API Reference + +### TransportRunnerConfig + +Configuration options for initializing transport runners. + +```typescript +interface TransportRunnerConfig { + /** + * Base user configuration for the server. + * + * If you wish to parse CLI arguments or ENV variables the same way as MongoDB + * MCP server does, you may use `parseCliArgumentsAsUserConfig()` exported + * through the library interface or write your own parser logic. + * + * Optionally, you can also use UserConfigSchema to create a default + * configuration - `UserConfigSchema.parse({})`. See "UserConfigSchema" + * section for details. + */ + userConfig: UserConfig; + + /** + * Custom connection manager factory (optional). + * + * Only needed if your application needs to handle MongoDB connections + * differently and outside of MongoDB MCP server. + * + * See "Custom Connection Management" section for details. + */ + createConnectionManager?: ConnectionManagerFactoryFn; + + /** + * Custom connection error handler (optional). + * + * Allows you to customize how connection errors are handled and presented to + * users. Generally required only if you're handling connections by yourself + * (using `createConnectionManager`). + * + * See "Custom Error Handling" section for details. + */ + connectionErrorHandler?: ConnectionErrorHandler; + + /** + * Custom Atlas Local client factory (optional). + * + * Allows you to generate your own Atlas client. Useful if you plan to + * connect to private Atlas API endpoints. + */ + createAtlasLocalClient?: AtlasLocalClientFactoryFn; + + /** + * Additional loggers to use (optional). + * + * Loggers specified here will receive all log events from the server. See + * "Custom Logging" section for details. + */ + additionalLoggers?: LoggerBase[]; + + /** + * Custom telemetry properties (optional). + * + * Properties added here will be included in all telemetry events. + */ + telemetryProperties?: Partial; + + /** + * Additional custom tools to register (optional). + * + * Tools specified here will be registered alongside the default MongoDB MCP + * tools. Each tool must extend the ToolBase class. + * + * See "Use Case 3: Adding Custom Tools" for examples. + */ + additionalTools?: (new (params: ToolConstructorParams) => ToolBase)[]; + + /** + * Hook to customize configuration per session (optional). + * + * Called before each session is created, allowing you to: + * - Fetch configuration from external sources (secrets managers, APIs) + * - Apply user-specific permissions and limits + * - Modify connection strings dynamically + * - Validate authentication credentials + * + * This hook is called for each new MCP client connection. + * For stdio transport, this is called once at server startup. + * For HTTP transport, this is called for each new session. + * + * @param context.userConfig - The base user configuration + * @param context.request - Request context (headers, query params) for HTTP transport + * @returns Modified user configuration for this session + * @throws Error if authentication fails or configuration is invalid + */ + createSessionConfig?: (context: { + userConfig: UserConfig; + request?: RequestContext; + }) => Promise | UserConfig; +} +``` + +### ToolBase + +Base class for implementing custom tools. + +```typescript +abstract class ToolBase { + /** Unique name for the tool */ + abstract name: string; + + /** Tool category: 'mongodb', 'atlas', or 'atlas-local' */ + abstract category: ToolCategory; + + /** Operation type: 'metadata', 'read', 'create', 'update', 'delete', or 'connect' */ + abstract operationType: OperationType; + + /** Description shown to the MCP client */ + protected abstract description: string; + + /** Zod schema for tool arguments */ + protected abstract argsShape: ZodRawShape; + + /** Execute the tool with given arguments */ + protected abstract execute( + ...args: ToolCallbackArgs + ): Promise; + + /** Resolve telemetry metadata for the tool execution */ + protected abstract resolveTelemetryMetadata( + result: CallToolResult, + ...args: Parameters> + ): TelemetryToolMetadata; + + /** Access to the session (connection, logger, etc.) */ + protected readonly session: Session; + + /** Access to the server configuration */ + protected readonly config: UserConfig; + + /** Access to the telemetry service */ + protected readonly telemetry: Telemetry; + + /** Access to the elicitation service */ + protected readonly elicitation: Elicitation; +} +``` + +### UserConfig + +Server configuration options. See the [Configuration Options](README.md#configuration-options) section in the main README for a complete list of available configuration fields. + +### UserConfigSchema + +Zod schema for validating and creating UserConfig objects with default values. This is useful when you want to create a base configuration without parsing CLI arguments or environment variables. + +```typescript +import { UserConfigSchema } from "mongodb-mcp-server"; + +// Create a config with all default values +const defaultConfig = UserConfigSchema.parse({}); + +// Create a config with some custom values, rest will be defaults +const customConfig = UserConfigSchema.parse({ + transport: "http", + httpPort: 8080, + readOnly: true, +}); +``` + +This approach ensures you get all the default values without having to specify every configuration key manually. + +### parseCliArgumentsAsUserConfig + +Utility function to parse command-line arguments and environment variables into a UserConfig object, using the same parsing logic as the MongoDB MCP server CLI. + +_Note: This is what MongoDB MCP server uses internally._ + +```typescript +function parseCliArgumentsAsUserConfig(options?: { + args?: string[]; + helpers?: CreateUserConfigHelpers; +}): UserConfig; +``` + +**Example:** + +```typescript +import { parseCliArgumentsAsUserConfig, StdioRunner } from "mongodb-mcp-server"; + +// Parse config from process.argv and environment variables +const config = parseCliArgumentsAsUserConfig(); + +const runner = new StdioRunner({ userConfig: config }); +await runner.start(); +``` + +### applyConfigOverrides + +Utility function to manually apply request-based configuration overrides. + +_Note: This is what MongoDB MCP server uses internally._ + +```typescript +function applyConfigOverrides(params: { + baseConfig: UserConfig; + request?: RequestContext; +}): UserConfig; +``` + +## Advanced Topics + +### Custom Connection Management + +You can provide a custom connection manager factory to control how the MongoDB MCP server connects to a MongoDB instance. The only use case for this is if connection handling is done differently in your application. For example, the [MongoDB extension for VS Code](https://github.com/mongodb-js/vscode/blob/f45a4c774ffc01e9aed38f6ef00224bf921d9784/src/mcp/mcpConnectionManager.ts#L30) provides its own implementation of ConnectionManager because the connection handling is done by the extension itself. + +The default connection manager factory (`createMCPConnectionManager`) is also exported if you need to use the default implementation. + +```typescript +import { + StreamableHttpRunner, + createMCPConnectionManager, +} from "mongodb-mcp-server"; +import type { ConnectionManagerFactoryFn } from "mongodb-mcp-server"; + +// Using the default connection manager (this is the default behavior) +const runner1 = new StreamableHttpRunner({ + userConfig: config, + createConnectionManager: createMCPConnectionManager, +}); + +// Or provide a custom connection manager +const customConnectionManager: ConnectionManagerFactoryFn = async ({ + logger, + userConfig, + deviceId, +}) => { + // Return a custom ConnectionManager implementation + // that could delegate to your application's existing connection logic +}; + +const runner2 = new StreamableHttpRunner({ + userConfig: config, + createConnectionManager: customConnectionManager, +}); +``` + +### Custom Error Handling + +Provide custom error handling for connection errors. The error handler receives `MongoDBError` instances with specific error codes, and can choose to handle them or let the default handler take over. + +The default connection error handler (`connectionErrorHandler`) is also exported if you need to use the default implementation. + +**Error Types:** + +The error handler receives `MongoDBError` instances with one of the following error codes: + +- `ErrorCodes.NotConnectedToMongoDB` - Thrown when a tool requires a connection but none exists +- `ErrorCodes.MisconfiguredConnectionString` - Thrown when the connection string provided through `UserConfig` is invalid + +**ConnectionErrorHandlerContext:** + +```typescript +interface ConnectionErrorHandlerContext { + /** List of all available tools that can be suggested to the user */ + availableTools: ToolBase[]; + /** Current state of the connection manager */ + connectionState: AnyConnectionState; +} +``` + +**Example:** + +```typescript +import { + StreamableHttpRunner, + UserConfigSchema, + ErrorCodes, + connectionErrorHandler as defaultConnectionErrorHandler, +} from "mongodb-mcp-server"; +import type { ConnectionErrorHandler } from "mongodb-mcp-server"; + +// Using the default error handler (this is the default behavior) +const runner1 = new StreamableHttpRunner({ + userConfig: UserConfigSchema.parse({}), + connectionErrorHandler: defaultConnectionErrorHandler, +}); + +// Or provide a custom error handler +const customErrorHandler: ConnectionErrorHandler = (error, context) => { + // error is a MongoDBError with specific error codes + console.error("Connection error:", error.code, error.message); + + // Access available tools and connection state + const connectTools = context.availableTools.filter( + (t) => t.operationType === "connect" + ); + + if (error.code === ErrorCodes.NotConnectedToMongoDB) { + // Provide custom error message + return { + errorHandled: true, + result: { + content: [ + { + type: "text", + text: "Please connect to MongoDB first using one of the available connect tools.", + }, + ], + isError: true, + }, + }; + } + + // Delegate to default handler for other errors + return defaultConnectionErrorHandler(error, context); +}; + +const runner2 = new StreamableHttpRunner({ + userConfig: config, + connectionErrorHandler: customErrorHandler, +}); +``` + +### Custom Logging + +Add custom loggers to capture server events: + +```typescript +import { + StreamableHttpRunner, + LoggerBase, + UserConfigSchema, + type LogPayload, +} from "mongodb-mcp-server"; + +class CustomLogger extends LoggerBase { + log(payload: LogPayload): void { + // Send to your logging service + console.log(`[${payload.id}] ${payload.message}`); + } + + // Implement other log level methods... +} + +const runner = new StreamableHttpRunner({ + userConfig: UserConfigSchema.parse({}), + additionalLoggers: [new CustomLogger()], +}); +``` + +## Examples + +For complete working examples of embedding and extending the MongoDB MCP Server, refer to: + +- **Use Case Examples**: See the detailed examples in the [Use Cases](#use-cases) section above +- **MongoDB VS Code Extension**: Real-world integration of MongoDB MCP server in our extension at [mongodb-js/vscode](https://github.com/mongodb-js/vscode) +- **Custom Tools**: The [Use Case 3: Adding Custom Tools](#use-case-3-adding-custom-tools) section demonstrates creating custom connection selector tools + +## Best Practices + +### Security + +1. **Never expose connection strings or API credentials in logs or error messages** +2. **Apply the principle of least privilege when creating session configuration** +3. **Ensure only expected HTTP header and query parameters overrides are applied** +4. **Validate all inputs in custom tools** + +### Performance + +1. **Set appropriate `maxDocumentsPerQuery` and `maxBytesPerQuery` limits as this might affect runtime memory usage** +2. **Use `indexCheck: true` to ensure only indexed queries are run by the server** + +### Development + +1. **Test custom tools thoroughly before deployment** +2. **Implement comprehensive error handling in custom tools** + +## Troubleshooting + +### Common Issues + +**Problem:** Custom tools not appearing in the tool list + +- **Solution:** Ensure the tool class extends `ToolBase` and is passed in `additionalTools` +- **Solution:** Check that the tool's `verifyAllowed()` returns true and the tool is not accidentally disabled by config (disabledTools) + +**Problem:** Configuration overrides not working + +- **Solution:** Enable `allowRequestOverrides: true` in the base configuration +- **Solution:** Check that the configuration field allows overrides (see `overrideBehavior` in schema) + +**Problem:** Tool name collision error + +- **Solution:** Ensure your custom tools have unique names that don't conflict with built-in tools +- **Solution:** Check the list of built-in tool names in the [Supported Tools](README.md#supported-tools) section + +## Support + +For issues, questions, or contributions, please refer to the main [Contributing Guide](CONTRIBUTING.md) and open an issue on [GitHub](https://github.com/mongodb-js/mongodb-mcp-server). From 3c82e49ab86d9c65045bb3e8adc1a98b78740b5e Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Fri, 28 Nov 2025 14:29:50 +0100 Subject: [PATCH 2/2] chore: move essential documentation to TSDoc --- MCP_SERVER_LIBRARY.md | 331 ++++++++++++++++++++++---------------- src/tools/tool.ts | 355 ++++++++++++++++++++++++++++++++++++++--- src/transports/base.ts | 121 +++++++++++++- 3 files changed, 653 insertions(+), 154 deletions(-) diff --git a/MCP_SERVER_LIBRARY.md b/MCP_SERVER_LIBRARY.md index 25e69c91..3dfd7209 100644 --- a/MCP_SERVER_LIBRARY.md +++ b/MCP_SERVER_LIBRARY.md @@ -11,6 +11,7 @@ This guide explains how to embed and extend the MongoDB MCP Server as a library - [Use Case 1: Override Server Configuration](#use-case-1-override-server-configuration) - [Use Case 2: Per-Session Configuration](#use-case-2-per-session-configuration) - [Use Case 3: Adding Custom Tools](#use-case-3-adding-custom-tools) + - [Use Case 4: Selective Tool Registration](#use-case-4-selective-tool-registration) - [API Reference](#api-reference) - [Advanced Topics](#advanced-topics) - [Examples](#examples) @@ -20,7 +21,7 @@ This guide explains how to embed and extend the MongoDB MCP Server as a library The MongoDB MCP Server can be embedded in your own Node.js applications and customized to meet specific requirements. The library exports provide full control over: - Server configuration and initialization -- Per-session(MCP Client session) configuration hooks +- Per-session (MCP Client session) configuration hooks - Custom tool registration - Connection management and Connection error handling @@ -68,7 +69,13 @@ import { **Tools (`mongodb-mcp-server/tools`):** ```typescript -import { ToolBase } from "mongodb-mcp-server/tools"; +import { + ToolBase, + AllTools, + MongoDbTools, + AtlasTools, + AtlasLocalTools, +} from "mongodb-mcp-server/tools"; ``` For detailed documentation of these exports and their usage, see the [API Reference](#api-reference) section. @@ -79,7 +86,7 @@ The MongoDB MCP Server library follows a modular architecture: - **Transport Runners**: `StdioRunner` and `StreamableHttpRunner` manage the MCP transport layer - **Server**: Core server that wraps the MCP Server and registers tools and resources -- **Session**: Per-client(MCP Client) connection and configuration state +- **Session**: Per-client (MCP Client) connection and configuration state - **Tools**: Individual capabilities exposed to the MCP client - **Configuration**: User configuration with override mechanisms @@ -437,9 +444,9 @@ const AVAILABLE_CONNECTIONS = { // tool to make user aware of possible connections the MCP server could be // connected to. class ListConnectionsTool extends ToolBase { - override name = "list-connections"; - override category = "mongodb" as ToolCategory; - override operationType = "metadata" as OperationType; + static toolName = "list-connections"; + static category: ToolCategory = "mongodb"; + static operationType: OperationType = "metadata"; protected override description = "Lists all available pre-configured MongoDB connections"; protected override argsShape = {}; @@ -479,9 +486,9 @@ class ListConnectionsTool extends ToolBase { // never leak any connection details in the LLM context and maintain the // effective communication using opaque connection identifiers. class SelectConnectionTool extends ToolBase { - override name = "select-connection"; - override category = "mongodb" as ToolCategory; - override operationType = "metadata" as OperationType; + static toolName = "select-connection"; + static category: ToolCategory = "mongodb"; + static operationType: OperationType = "metadata"; protected override description = "Select and connect to a pre-configured MongoDB connection by ID"; protected override argsShape = { @@ -573,150 +580,205 @@ console.log( ); ``` -## API Reference +### Use Case 4: Selective Tool Registration -### TransportRunnerConfig +Register only specific internal MongoDB tools alongside custom tools, giving you complete control over the available toolset. + +#### Example: Minimal Toolset with Custom Integration -Configuration options for initializing transport runners. +This example shows how to selectively enable only specific MongoDB tools (`aggregate`, `connect`, and `switch-connection`) while disabling all others, and adding a custom tool for application-specific functionality: ```typescript -interface TransportRunnerConfig { - /** - * Base user configuration for the server. - * - * If you wish to parse CLI arguments or ENV variables the same way as MongoDB - * MCP server does, you may use `parseCliArgumentsAsUserConfig()` exported - * through the library interface or write your own parser logic. - * - * Optionally, you can also use UserConfigSchema to create a default - * configuration - `UserConfigSchema.parse({})`. See "UserConfigSchema" - * section for details. - */ - userConfig: UserConfig; - - /** - * Custom connection manager factory (optional). - * - * Only needed if your application needs to handle MongoDB connections - * differently and outside of MongoDB MCP server. - * - * See "Custom Connection Management" section for details. - */ - createConnectionManager?: ConnectionManagerFactoryFn; - - /** - * Custom connection error handler (optional). - * - * Allows you to customize how connection errors are handled and presented to - * users. Generally required only if you're handling connections by yourself - * (using `createConnectionManager`). - * - * See "Custom Error Handling" section for details. - */ - connectionErrorHandler?: ConnectionErrorHandler; - - /** - * Custom Atlas Local client factory (optional). - * - * Allows you to generate your own Atlas client. Useful if you plan to - * connect to private Atlas API endpoints. - */ - createAtlasLocalClient?: AtlasLocalClientFactoryFn; - - /** - * Additional loggers to use (optional). - * - * Loggers specified here will receive all log events from the server. See - * "Custom Logging" section for details. - */ - additionalLoggers?: LoggerBase[]; - - /** - * Custom telemetry properties (optional). - * - * Properties added here will be included in all telemetry events. - */ - telemetryProperties?: Partial; - - /** - * Additional custom tools to register (optional). - * - * Tools specified here will be registered alongside the default MongoDB MCP - * tools. Each tool must extend the ToolBase class. - * - * See "Use Case 3: Adding Custom Tools" for examples. - */ - additionalTools?: (new (params: ToolConstructorParams) => ToolBase)[]; - - /** - * Hook to customize configuration per session (optional). - * - * Called before each session is created, allowing you to: - * - Fetch configuration from external sources (secrets managers, APIs) - * - Apply user-specific permissions and limits - * - Modify connection strings dynamically - * - Validate authentication credentials - * - * This hook is called for each new MCP client connection. - * For stdio transport, this is called once at server startup. - * For HTTP transport, this is called for each new session. - * - * @param context.userConfig - The base user configuration - * @param context.request - Request context (headers, query params) for HTTP transport - * @returns Modified user configuration for this session - * @throws Error if authentication fails or configuration is invalid - */ - createSessionConfig?: (context: { - userConfig: UserConfig; - request?: RequestContext; - }) => Promise | UserConfig; +import { z } from "zod"; +import { StreamableHttpRunner, UserConfigSchema } from "mongodb-mcp-server"; +import { + type ToolCategory, + type OperationType, + AllTools, + ToolBase, +} from "mongodb-mcp-server/tools"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; + +// Custom tool to fetch ticket details from your application +class GetTicketDetailsTool extends ToolBase { + static toolName = "get-ticket-details"; + static category: ToolCategory = "mongodb"; + static operationType: OperationType = "read"; + + protected override description = + "Retrieves detailed information about a support ticket from the tickets collection"; + + protected override argsShape = { + ticketId: z.string().describe("The unique identifier of the ticket"), + }; + + protected override async execute(args: { + ticketId: string; + }): Promise { + const { ticketId } = args; + + try { + // Ensure connected to MongoDB + await this.session.ensureConnected(); + + // Fetch ticket from the database + const ticket = await this.session.db + .collection("tickets") + .findOne({ ticketId }); + + if (!ticket) { + return { + content: [ + { + type: "text", + text: `No ticket found with ID: ${ticketId}`, + }, + ], + isError: true, + }; + } + + return { + content: [ + { + type: "text", + text: JSON.stringify(ticket, null, 2), + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: `Error fetching ticket: ${ + error instanceof Error ? error.message : String(error) + }`, + }, + ], + isError: true, + }; + } + } + + protected override resolveTelemetryMetadata() { + return {}; + } } + +// Get all MongoDB tool names except the ones we want to keep +const allowedTools = ["aggregate", "connect", "switch-connection"]; +const disabledMongoDbTools = AllTools.filter( + (tool) => !allowedTools.includes(tool.toolName) +).map((tool) => tool.toolName); + +// Initialize the server with minimal toolset +const runner = new StreamableHttpRunner({ + userConfig: UserConfigSchema.parse({ + transport: "stdio", + httpPort: "3000", + httpHost: "127.0.0.1", + connectionString: process.env.MDB_MCP_CONNECTION_STRING, + // Disable all MongoDB tools except aggregate, connect, and switch-connection + // Also disable Atlas and Atlas Local tools + disabledTools: [...disabledMongoDbTools, "atlas", "atlas-local"], + }), + // Register our custom tool + additionalTools: [GetTicketDetailsTool], +}); + +await runner.start(); +console.log("MongoDB MCP Server running with minimal toolset"); ``` -### ToolBase +In this configuration: + +- The server will **only** have access to three internal MongoDB tools: `aggregate`, `connect`, and `switch-connection` +- All other internal tools (find, insert, update, etc.) are disabled via `disabledTools` +- The custom `get-ticket-details` tool provides application-specific functionality + +This approach is useful when you want to: + +- Create a focused MCP server for a particular use case +- Limit LLM capabilities to specific operations +- Combine selective internal tools with domain-specific custom tools + +## API Reference + +### TransportRunnerConfig + +Configuration options for initializing transport runners (`StdioRunner`, `StreamableHttpRunner`). -Base class for implementing custom tools. +See the TypeScript definition in [`src/transports/base.ts`](./src/transports/base.ts) for detailed documentation of all available options. + +### ToolBase and ToolClass + +Base class and type for implementing custom MCP tools. + +See the TypeScript documentation in [`src/tools/tool.ts`](./src/tools/tool.ts) for: + +- Detailed explanation of `ToolBase` abstract class and `ToolClass` type +- Documentation of all available protected members + +### Tool Collections + +The library exports collections of internal tool classes that can be used for selective tool registration or extension. + +#### AllTools + +Array containing all internal tool classes (MongoDB, Atlas, and Atlas Local tools combined). ```typescript -abstract class ToolBase { - /** Unique name for the tool */ - abstract name: string; +import { AllTools } from "mongodb-mcp-server/tools"; - /** Tool category: 'mongodb', 'atlas', or 'atlas-local' */ - abstract category: ToolCategory; +// Get all internal tool names +const allToolNames = AllTools.map((tool) => tool.toolName); - /** Operation type: 'metadata', 'read', 'create', 'update', 'delete', or 'connect' */ - abstract operationType: OperationType; +// Find a specific tool +const aggregateTool = AllTools.find((tool) => tool.toolName === "aggregate"); - /** Description shown to the MCP client */ - protected abstract description: string; +// Filter tools by category +const mongodbOnlyTools = AllTools.filter((tool) => tool.category === "mongodb"); - /** Zod schema for tool arguments */ - protected abstract argsShape: ZodRawShape; +// Filter tools by operationType +const connectionRelatedTools = AllTools.filter( + (tool) => tool.operationType === "connect" +); +``` - /** Execute the tool with given arguments */ - protected abstract execute( - ...args: ToolCallbackArgs - ): Promise; +#### MongoDbTools - /** Resolve telemetry metadata for the tool execution */ - protected abstract resolveTelemetryMetadata( - result: CallToolResult, - ...args: Parameters> - ): TelemetryToolMetadata; +Array containing only MongoDB-specific tool classes (tools that interact with MongoDB deployments). - /** Access to the session (connection, logger, etc.) */ - protected readonly session: Session; +```typescript +import { MongoDbTools } from "mongodb-mcp-server/tools"; - /** Access to the server configuration */ - protected readonly config: UserConfig; +// Get all MongoDB tool names +const mongoToolNames = MongoDbTools.map((tool) => tool.toolName); +// Example: ["connect", "switch-connection", "find", "aggregate", "insert-many", ...] +``` - /** Access to the telemetry service */ - protected readonly telemetry: Telemetry; +#### AtlasTools - /** Access to the elicitation service */ - protected readonly elicitation: Elicitation; -} +Array containing only MongoDB Atlas-specific tool classes (tools that interact with Atlas API). + +```typescript +import { AtlasTools } from "mongodb-mcp-server/tools"; + +// Get all Atlas tool names +const atlasToolNames = AtlasTools.map((tool) => tool.toolName); +// Example: ["list-clusters", "create-cluster", "delete-cluster", ...] +``` + +#### AtlasLocalTools + +Array containing only Atlas Local-specific tool classes (tools that interact with local Atlas deployments). + +```typescript +import { AtlasLocalTools } from "mongodb-mcp-server/tools"; + +// Get all Atlas Local tool names +const atlasLocalToolNames = AtlasLocalTools.map((tool) => tool.toolName); ``` ### UserConfig @@ -781,6 +843,8 @@ function applyConfigOverrides(params: { }): UserConfig; ``` +See "Example: Integration with Request Overrides" for further details on how to use this function. + ## Advanced Topics ### Custom Connection Management @@ -928,7 +992,6 @@ For complete working examples of embedding and extending the MongoDB MCP Server, - **Use Case Examples**: See the detailed examples in the [Use Cases](#use-cases) section above - **MongoDB VS Code Extension**: Real-world integration of MongoDB MCP server in our extension at [mongodb-js/vscode](https://github.com/mongodb-js/vscode) -- **Custom Tools**: The [Use Case 3: Adding Custom Tools](#use-case-3-adding-custom-tools) section demonstrates creating custom connection selector tools ## Best Practices diff --git a/src/tools/tool.ts b/src/tools/tool.ts index 769f3efd..cf4630d7 100644 --- a/src/tools/tool.ts +++ b/src/tools/tool.ts @@ -39,53 +39,253 @@ export type OperationType = "metadata" | "read" | "create" | "delete" | "update" */ export type ToolCategory = "mongodb" | "atlas" | "atlas-local"; +/** + * Parameters passed to the constructor of all tools that extend `ToolBase`. + * + * The MongoDB MCP Server automatically injects these parameters when + * constructing tools and registering to the MCP Server. + * + * See `Server.registerTools` method in `src/server.ts` for further reference. + */ export type ToolConstructorParams = { + /** + * The unique name of the tool (injected from the static `toolName` property + * on the Tool class). + */ name: string; + + /** + * The category of the tool (injected from the static `category` property on + * the Tool class). + */ category: ToolCategory; + + /** + * The type of operation the tool performs (injected from the static + * `operationType` property on the Tool class). + */ operationType: OperationType; + + /** + * An instance of Session class providing access to MongoDB connections, + * loggers, etc. + * + * See `src/session.ts` for further reference. + */ session: Session; + + /** + * The configuration object that MCP session was started with. + * + * See `src/common/config/userConfig.ts` for further reference. + */ config: UserConfig; + + /** + * The telemetry service for tracking tool usage. + * + * See `src/telemetry/telemetry.ts` for further reference. + */ telemetry: Telemetry; + + /** + * The elicitation service for requesting user confirmation. + * + * See `src/elicitation.ts` for further reference. + */ elicitation: Elicitation; }; /** - * The type for a ToolImplementation that the MongoDB MCP Server works with. - * This is the same as `ToolBase` abstract class plus some static properties - * that are used by the `Server` to automatically inject some constructor - * parameters when initializing individual tools. + * The type that all tool classes must conform to when implementing custom tools + * for the MongoDB MCP Server. + * + * This type enforces that tool classes have static properties (`toolName`, + * `category`, `operationType`) which are further injected during instantiation + * of Tool classes. + * + * @example + * ```typescript + * import { StreamableHttpRunner, UserConfigSchema } from "mongodb-mcp-server" + * import { ToolBase, type ToolClass, type ToolCategory, type OperationType } from "mongodb-mcp-server/tools"; + * import { z } from "zod"; + * + * class MyCustomTool extends ToolBase { + * // Required static properties for ToolClass conformance + * static toolName = "my-custom-tool"; + * static category: ToolCategory = "mongodb"; + * static operationType: OperationType = "read"; + * + * // Required abstract properties + * protected description = "My custom tool description"; + * protected argsShape = { + * query: z.string().describe("The query parameter"), + * }; + * + * // Required abstract method: implement the tool's logic + * protected async execute(args) { + * // Tool implementation + * return { + * content: [{ type: "text", text: "Result" }], + * }; + * } + * + * // Required abstract method: provide telemetry metadata + * protected resolveTelemetryMetadata() { + * return {}; // Return empty object if no custom telemetry needed + * } + * } + * + * const runner = new StreamableHttpRunner({ + * userConfig: UserConfigSchema.parse({}), + * // This will work only if the class correctly conforms to ToolClass type, which in our case it does. + * additionalTools: [MyCustomTool], + * }); + * ``` */ export type ToolClass = { + /** Constructor signature for the tool class */ new (params: ToolConstructorParams): ToolBase; + + /** + * The unique name of the tool (must be unique across all tools in the + * server) + */ toolName: string; + + /** The category of the tool (mongodb, atlas, or atlas-local) */ category: ToolCategory; + + /** The type of operation the tool performs */ operationType: OperationType; }; /** - * @class - * Abstract base class for all MCP tools. + * Abstract base class for implementing MCP tools in the MongoDB MCP Server. + * + * All tools (both internal and custom) must extend this class to ensure a + * consistent interface and proper integration with the server. + * + * ## Creating a Custom Tool + * + * To create a custom tool, you must: + * 1. Extend the `ToolBase` class + * 2. Define static properties: `toolName`, `category`, `operationType` + * 3. Implement required abstract members: `description`, `argsShape`, + * `execute()`, `resolveTelemetryMetadata()` + * 4. Ensure your class conforms to the `ToolClass` type + * + * @example Basic Custom Tool + * ```typescript + * import { StreamableHttpRunner, UserConfigSchema } from "mongodb-mcp-server" + * import { ToolBase, type ToolClass, type ToolCategory, type OperationType } from "mongodb-mcp-server/tools"; + * import { z } from "zod"; + * + * class MyCustomTool extends ToolBase { + * // Required static properties for ToolClass conformance + * static toolName = "my-custom-tool"; + * static category: ToolCategory = "mongodb"; + * static operationType: OperationType = "read"; * - * Tool implementations must extend this class to ensure a consistent interface. + * // Required abstract properties + * protected description = "My custom tool description"; + * protected argsShape = { + * query: z.string().describe("The query parameter"), + * }; * - * Subclasses must additionally conform to `ToolImplementation` type so the - * following properties can automatically be set by Server, via the constructor: - * - `name: string` — The unique name of the tool, derived from - * `ToolImplementation.toolName`. - * - `category: ToolCategory` — The category of the tool (e.g., "mongodb", - * "atlas"), derived from `ToolImplementation.category`. - * - `operationType: OperationType` — The type of operation the tool performs, - * derived from `ToolImplementation.operationType`. + * // Required abstract method: implement the tool's logic + * protected async execute(args) { + * // Tool implementation + * return { + * content: [{ type: "text", text: "Result" }], + * }; + * } + * + * // Required abstract method: provide telemetry metadata + * protected resolveTelemetryMetadata() { + * return {}; // Return empty object if no custom telemetry needed + * } + * } + * + * const runner = new StreamableHttpRunner({ + * userConfig: UserConfigSchema.parse({}), + * // This will work only if the class correctly conforms to ToolClass type, which in our case it does. + * additionalTools: [MyCustomTool], + * }); + * ``` + * + * ## Protected Members Available to Subclasses + * + * - `session` - Access to MongoDB connection, logger, and other session + * resources + * - `config` - Server configuration (`UserConfig`) + * - `telemetry` - Telemetry service for tracking usage + * - `elicitation` - Service for requesting user confirmations + * + * ## Instance Properties Set by Constructor + * + * The following properties are automatically set when the tool is instantiated + * by the server (derived from the static properties): + * - `name` - The tool's unique name (from static `toolName`) + * - `category` - The tool's category (from static `category`) + * - `operationType` - The tool's operation type (from static `operationType`) + * + * ## Optional Overrideable Methods + * + * - `getConfirmationMessage()` - Customize the confirmation prompt for tools + * requiring user approval + * - `handleError()` - Customize error handling behavior + * + * @see {@link ToolClass} for the type that tool classes must conform to + * @see {@link ToolConstructorParams} for the parameters passed to the + * constructor */ export abstract class ToolBase { + /** + * The unique name of this tool instance. + * + * Automatically set from the static `toolName` property during + * construction. + */ public name: string; + /** + * The category of this tool instance. + * + * Automatically set from the static `category` property during + * construction. + */ public category: ToolCategory; + /** + * The operation type of this tool instance. + * + * Automatically set from the static `operationType` property during + * construction. + */ public operationType: OperationType; + /** + * Human-readable description of what the tool does. + * + * This is shown to the MCP client and helps the LLM understand when to use + * this tool. + */ protected abstract description: string; + /** + * Zod schema defining the tool's arguments. + * + * Use an empty object `{}` if the tool takes no arguments. + * + * @example + * ```typescript + * protected argsShape = { + * query: z.string().describe("The search query"), + * limit: z.number().optional().describe("Maximum results to return"), + * }; + * ``` + */ protected abstract argsShape: ZodRawShape; private registeredTool: RegisteredTool | undefined; @@ -118,16 +318,64 @@ export abstract class ToolBase { return annotations; } + /** + * Execute the tool with the provided arguments. + * + * This is the core implementation of your tool's functionality. It receives + * validated arguments (validated against `argsShape`) and must return a + * result conforming to the MCP protocol. + * + * @param args - The validated arguments passed to the tool + * @returns A promise resolving to the tool execution result + * + * @example + * ```typescript + * protected async execute(args: { query: string }): Promise { + * const results = await this.session.db.collection('items').find({ + * name: { $regex: args.query, $options: 'i' } + * }).toArray(); + * + * return { + * content: [{ + * type: "text", + * text: JSON.stringify(results), + * }], + * }; + * } + * ``` + */ protected abstract execute(...args: ToolCallbackArgs): Promise; - /** Get the confirmation message for the tool. Can be overridden to provide a more specific message. */ + /** + * Get the confirmation message shown to users when this tool requires explicit approval. + * + * Override this method to provide a more specific and helpful confirmation + * message based on the tool's arguments. + * + * @param args - The tool arguments + * @returns The confirmation message to display to the user + * + * @example + * ```typescript + * protected getConfirmationMessage(args: { database: string }): string { + * return `You are about to delete the database "${args.database}". This action cannot be undone. Proceed?`; + * } + * ``` + */ // eslint-disable-next-line @typescript-eslint/no-unused-vars protected getConfirmationMessage(...args: ToolCallbackArgs): string { return `You are about to execute the \`${this.name}\` tool which requires additional confirmation. Would you like to proceed?`; } - /** Check if the user has confirmed the tool execution, if required by the configuration. - * Always returns true if confirmation is not required. + /** + * Check if the user has confirmed the tool execution (if required by configuration). + * + * This method automatically checks if the tool name is in the + * `confirmationRequiredTools` configuration list and requests user + * confirmation via the elicitation service if needed. + * + * @param args - The tool arguments + * @returns A promise resolving to `true` if confirmed or confirmation not required, `false` otherwise */ public async verifyConfirmed(args: ToolCallbackArgs): Promise { if (!this.config.confirmationRequiredTools.includes(this.name)) { @@ -137,10 +385,30 @@ export abstract class ToolBase { return this.elicitation.requestConfirmation(this.getConfirmationMessage(...args)); } + /** + * Access to the session instance. + * Provides access to MongoDB connections, loggers, connection manager, and other session-level resources. + */ protected readonly session: Session; + + /** + * Access to the server configuration. + * Contains all user configuration settings including connection strings, feature flags, and operational limits. + */ protected readonly config: UserConfig; + + /** + * Access to the telemetry service. + * Use this to emit custom telemetry events if needed. + */ protected readonly telemetry: Telemetry; + + /** + * Access to the elicitation service. + * Use this to request user confirmations or inputs during tool execution. + */ protected readonly elicitation: Elicitation; + constructor({ name, category, operationType, session, config, telemetry, elicitation }: ToolConstructorParams) { this.name = name; this.category = category; @@ -272,6 +540,33 @@ export abstract class ToolBase { return true; } + /** + * Handle errors that occur during tool execution. + * + * Override this method to provide custom error handling logic. The default + * implementation returns a simple error message. + * + * @param error - The error that was thrown + * @param args - The arguments that were passed to the tool + * @returns A CallToolResult with error information + * + * @example + * ```typescript + * protected handleError(error: unknown, args: { query: string }): CallToolResult { + * if (error instanceof MongoError && error.code === 11000) { + * return { + * content: [{ + * type: "text", + * text: `Duplicate key error for query: ${args.query}`, + * }], + * isError: true, + * }; + * } + * // Fall back to default error handling + * return super.handleError(error, args); + * } + * ``` + */ // This method is intended to be overridden by subclasses to handle errors protected handleError( error: unknown, @@ -289,6 +584,30 @@ export abstract class ToolBase { }; } + /** + * Resolve telemetry metadata for this tool execution. + * + * This method is called after every tool execution to collect metadata + * for telemetry events. Return an object with custom properties you want + * to track, or an empty object if no custom telemetry is needed. + * + * @param result - The result of the tool execution + * @param args - The arguments and context passed to the tool + * @returns An object containing telemetry metadata + * + * @example + * ```typescript + * protected resolveTelemetryMetadata( + * result: CallToolResult, + * args: { query: string } + * ): TelemetryToolMetadata { + * return { + * query_length: args.query.length, + * result_count: result.isError ? 0 : JSON.parse(result.content[0].text).length, + * }; + * } + * ``` + */ protected abstract resolveTelemetryMetadata( result: CallToolResult, ...args: Parameters> diff --git a/src/transports/base.ts b/src/transports/base.ts index 89d488f2..ff82b7f1 100644 --- a/src/transports/base.ts +++ b/src/transports/base.ts @@ -28,22 +28,139 @@ export type RequestContext = { query?: Record; }; +/** + * A function to dynamically generate `UserConfig` object, potentially unique to + * each MCP client session. + * + * The function is passed a config context object containing: + * 1. `userConfig`: The base `UserConfig` object that MongoDB MCP Server was + * started with, either through parsed CLI arguments or a static + * configuration injected through `TransportRunnerConfig` + * 2. `request`: An optional, `RequestContext` object, available only when + * MongoDB MCP server is running over HTTP transport, that contains headers + * and query parameters received in MCP session initialization object. + */ type CreateSessionConfigFn = (context: { userConfig: UserConfig; request?: RequestContext; }) => Promise | UserConfig; +/** + * Configuration options for customizing how transport runners are initialized. + * This includes specifying the base user configuration, providing custom + * connection management, and other advanced options. + * + * You may want to customize this configuration if you need to: + * - Provide a custom user configuration for different environments or users. + * - Override the default connection management to MongoDB deployments. + * + * In most cases, just providing the `UserConfig` object is sufficient, but + * advanced use-cases (such as embedding the MCP server in another application + * or supporting custom authentication flows) may require customizing other + * `TransportRunnerConfig` options as well. + */ export type TransportRunnerConfig = { + /** + * Base user configuration for the server. + * + * Can be generated by parsing CLI arguments, environment variables or + * written manually while conforming the interface requirements of + * `UserConfig`. + * + * To parse CLI arguments and environment variables in order to generate a + * `UserConfig` object, you can use `createUserConfig` function, also + * exported as `parseCliArgumentsAsUserConfig` through MCP server library + * exports. + * + * Optionally, you can also use `UserConfigSchema` (available through MCP + * server library exports) to create a default configuration - + * `UserConfigSchema.parse({})`. + */ userConfig: UserConfig; + + /** + * An optional factory function to generates an instance of + * `ConnectionManager`. When not provided, MongoDB MCP Server uses an + * internal implementation to manage connection to MongoDB deployments. + * + * Customize this only if the use-case involves handling the MongoDB + * connections differently and outside of MongoDB MCP server. + */ createConnectionManager?: ConnectionManagerFactoryFn; + + /** + * An optional function to handle connection related errors. When not + * provided, MongoDB MCP Server uses an internal implementation to handle + * the errors raised by internal implementation of `ConnectionManager` + * class. + * + * Customize this only if you need to handle the Connection errors different + * from the internal implementation or if you have provided a different + * implementation of `ConnectionManager` that might raise errors unknown to + * default internal connection error handler. + */ connectionErrorHandler?: ConnectionErrorHandler; + + /** + * An optional factory function to create a client for working with Atlas + * local deployments. When not provided, MongoDB MCP Server uses an internal + * implementation to create the local Atlas client. + */ createAtlasLocalClient?: AtlasLocalClientFactoryFn; + + /** + * An optional list of loggers to be used in addition to the default logger + * implementations. When not provided, MongoDB MCP Server will not utilize + * any loggers other than the default that it works with. + * + * Customize this only if the default enabled loggers (disk/stderr/mcp) are + * not covering your use-case. + */ additionalLoggers?: LoggerBase[]; + + /** + * An optional key value pair of telemetry properties that are reported to + * the telemetry backend. Most, if not all, of the properties are captured + * automatically. + */ telemetryProperties?: Partial; + + /** + * An optional list of tools to be registered to the MongoDB MCP Server. + * When not provided, MongoDB MCP Server will not register any tools other + * than the internal tools that it knows of. + * + * Tools specified here will be registered alongside the default MongoDB MCP + * Server tools. It is important to ensure that the name of each of the + * additional tools are unique at-least for the running MongoDB MCP Server + * instance. + * + * Note: Additional tools cannot use the same name as that of internal + * MongoDB tools. + * + * To ensure that you provide compliant tool implementations extend your + * tool implementation using `ToolBase` class and ensure that they conform + * to `ToolClass` type. + * + * @see {@link ToolClass} for the type that tool classes must conform to + * @see {@link ToolConstructorParams} for the parameters passed to the + * constructor + */ additionalTools?: ToolClass[]; + /** - * Hook which allows library consumers to fetch configuration from external sources (e.g., secrets managers, APIs) - * or modify the existing configuration before the session is created. + * An optional function to hook into session configuration lifecycle and + * provide session specific configuration (`UserConfig`). + * + * The function is called before each session is created, allowing you to: + * - Fetch configuration from external sources (secrets managers, APIs) + * - Apply user-specific permissions and limits + * - Modify connection strings dynamically + * - Validate authentication credentials + * + * This function is called for each new MCP client connection. For stdio + * transport, this is called once at server startup. For HTTP transport, + * this is called for each new session. */ createSessionConfig?: CreateSessionConfigFn; };