diff --git a/package-lock.json b/package-lock.json index a60b03a40..591c7640c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@babel/core": "^7.25.8", "@babel/parser": "^7.25.8", "@babel/traverse": "^7.25.7", - "@himanshusinghs/mongodb-mcp-server": "^0.3.1", + "@himanshusinghs/mongodb-mcp-server": "^0.3.5", "@mongodb-js/compass-components": "^1.38.1", "@mongodb-js/connection-form": "^1.52.3", "@mongodb-js/connection-info": "^0.17.1", @@ -5686,9 +5686,9 @@ "license": "MIT" }, "node_modules/@himanshusinghs/mongodb-mcp-server": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@himanshusinghs/mongodb-mcp-server/-/mongodb-mcp-server-0.3.1.tgz", - "integrity": "sha512-H/QfueuEg7/qiUH8EI8NDP//7RsBy/iP6EwSemrz5fymovQSUnxsJnwiKlPM267HGpSDwT5QrJpo6Td6jfGA7A==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@himanshusinghs/mongodb-mcp-server/-/mongodb-mcp-server-0.3.5.tgz", + "integrity": "sha512-Pm7+vAz7S4o6o94BJ3g3WkFxoUx9TVe3iBFfEElt6WfG+L/lUCBbxsPVWYhkNHcHqqOhhu6HAChFngHeyAb3YA==", "license": "Apache-2.0", "dependencies": { "@modelcontextprotocol/sdk": "^1.17.4", diff --git a/package.json b/package.json index d528f0dc5..17a361a14 100644 --- a/package.json +++ b/package.json @@ -1396,7 +1396,7 @@ "@babel/core": "^7.25.8", "@babel/parser": "^7.25.8", "@babel/traverse": "^7.25.7", - "@himanshusinghs/mongodb-mcp-server": "^0.3.1", + "@himanshusinghs/mongodb-mcp-server": "^0.3.5", "@mongodb-js/compass-components": "^1.38.1", "@mongodb-js/connection-form": "^1.52.3", "@mongodb-js/connection-info": "^0.17.1", diff --git a/src/mcp/mcpConnectionErrorHandler.ts b/src/mcp/mcpConnectionErrorHandler.ts new file mode 100644 index 000000000..f61e959f4 --- /dev/null +++ b/src/mcp/mcpConnectionErrorHandler.ts @@ -0,0 +1,71 @@ +import { + ErrorCodes, + type ConnectionErrorHandler, +} from '@himanshusinghs/mongodb-mcp-server'; +import type ConnectionController from '../connectionController'; + +export const createMCPConnectionErrorHandler = ( + connectionController: Pick< + ConnectionController, + 'getSavedConnections' | 'getActiveConnectionName' + >, +): ConnectionErrorHandler => { + return (error) => { + const activeConnectionName = connectionController.getActiveConnectionName(); + const availableConnections = connectionController + .getSavedConnections() + .map(({ name }) => ({ name })); + + const connectionGuidance: string[] = availableConnections.length + ? [ + `Following connections are available in the Sidebar for the user to connect to: ${JSON.stringify(availableConnections)}. Note to LLM: Provide user with the list of connections available and ask them to select a connection in the Sidebar.`, + 'Note to LLM: Alternatively, if the user wish to establish a new MongoDB connection, then ask them to follow our official guide on the following link: https://www.mongodb.com/docs/mongodb-vscode/connect/#create-a-connection', + ] + : [ + 'The user has no connections saved in their MongoDB Extension sidebar.', + 'Note to LLM: Ask the user to follow our official guide on the following link: https://www.mongodb.com/docs/mongodb-vscode/connect/#create-a-connection to connect to a MongoDB cluster.', + ]; + + switch (error.code) { + case ErrorCodes.NotConnectedToMongoDB: + return { + errorHandled: true, + result: { + content: [ + { + type: 'text', + text: 'You need to connect to a MongoDB instance before you can access its data.', + }, + ...connectionGuidance.map<{ type: 'text'; text: string }>( + (text) => ({ type: 'text', text }), + ), + ], + }, + }; + case ErrorCodes.MisconfiguredConnectionString: { + const resolutionGuidance: string[] = [ + 'Note to LLM: Ask the user to reconnect to the desired connection. If the problem persist even after that then advice them to raise an issue on the VSCode project board - https://github.com/mongodb-js/vscode/issues', + ...connectionGuidance, + ]; + return { + errorHandled: true, + result: { + content: [ + { + type: 'text', + text: `MCP server is having trouble connecting to ${activeConnectionName ? activeConnectionName : 'the selected connection in the MongoDB VSCode extension'}.`, + }, + ...resolutionGuidance.map<{ type: 'text'; text: string }>( + (text) => ({ type: 'text', text }), + ), + ], + }, + }; + } + default: + return { + errorHandled: false, + }; + } + }; +}; diff --git a/src/mcp/mcpConnectionManager.ts b/src/mcp/mcpConnectionManager.ts index 4057bd2e9..a1e9edd61 100644 --- a/src/mcp/mcpConnectionManager.ts +++ b/src/mcp/mcpConnectionManager.ts @@ -31,10 +31,9 @@ export class MCPConnectionManager extends ConnectionManager { connect(): Promise { return Promise.reject( new Error( - [ - 'MongoDB MCP Server in MongoDB VSCode extension makes use of the connection that the MongoDB VSCode extension is connected to.', - "To connect, choose a connection from MongoDB VSCode extensions's sidepanel - https://www.mongodb.com/docs/mongodb-vscode/connect/#connect-to-your-mongodb-deployment", - ].join(' '), + // eslint-disable-next-line no-multi-str + "MongoDB MCP Server in MongoDB VSCode extension makes use of the connection that the MongoDB VSCode extension is connected to. \ +To connect, choose a connection from MongoDB VSCode extensions's sidepanel - https://www.mongodb.com/docs/mongodb-vscode/connect/#connect-to-your-mongodb-deployment", ), ); } diff --git a/src/mcp/mcpController.ts b/src/mcp/mcpController.ts index c75994f72..0ca90a856 100644 --- a/src/mcp/mcpController.ts +++ b/src/mcp/mcpController.ts @@ -15,6 +15,7 @@ import type ConnectionController from '../connectionController'; import { createLogger } from '../logging'; import type { MCPConnectParams } from './mcpConnectionManager'; import { MCPConnectionManager } from './mcpConnectionManager'; +import { createMCPConnectionErrorHandler } from './mcpConnectionErrorHandler'; type mcpServerStartupConfig = 'ask' | 'enabled' | 'disabled'; @@ -90,11 +91,14 @@ export class MCPController { return connectionManager; }; - const runner = new StreamableHttpRunner( - mcpConfig, + const runner = new StreamableHttpRunner({ + userConfig: mcpConfig, createConnectionManager, - [new VSCodeMCPLogger()], - ); + connectionErrorHandler: createMCPConnectionErrorHandler( + this.connectionController, + ), + additionalLoggers: [new VSCodeMCPLogger()], + }); await runner.start(); this.server = { diff --git a/src/test/suite/mcp/mcpConnectionErrorHandler.test.ts b/src/test/suite/mcp/mcpConnectionErrorHandler.test.ts new file mode 100644 index 000000000..c1db22f5c --- /dev/null +++ b/src/test/suite/mcp/mcpConnectionErrorHandler.test.ts @@ -0,0 +1,90 @@ +import { expect } from 'chai'; +import { beforeEach } from 'mocha'; +import { createMCPConnectionErrorHandler } from '../../../mcp/mcpConnectionErrorHandler'; +import ConnectionController from '../../../connectionController'; +import { ExtensionContextStub } from '../stubs'; +import { StorageController } from '../../../storage'; +import { TelemetryService } from '../../../telemetry'; +import { StatusView } from '../../../views'; +import type { + ConnectionErrorHandled, + ConnectionErrorHandlerContext, +} from '@himanshusinghs/mongodb-mcp-server'; +import { ErrorCodes } from '@himanshusinghs/mongodb-mcp-server'; + +class MongoDBError extends Error { + constructor( + public code: + | ErrorCodes.NotConnectedToMongoDB + | ErrorCodes.MisconfiguredConnectionString, + message: string, + ) { + super(message); + } +} + +suite('mcpConnectionErrorHandler suite', () => { + let connectionController: ConnectionController; + beforeEach(() => { + const extensionContext = new ExtensionContextStub(); + const testStorageController = new StorageController(extensionContext); + const testTelemetryService = new TelemetryService( + testStorageController, + extensionContext, + ); + connectionController = new ConnectionController({ + statusView: new StatusView(extensionContext), + storageController: testStorageController, + telemetryService: testTelemetryService, + }); + }); + + test('should handle NotConnectedToMongoDB error', () => { + const handler = createMCPConnectionErrorHandler(connectionController); + const result = handler( + new MongoDBError( + ErrorCodes.NotConnectedToMongoDB, + 'Not connected to MongoDB', + ), + {} as ConnectionErrorHandlerContext, + ) as ConnectionErrorHandled; + + expect(result.errorHandled).to.be.true; + expect(result.result.content).to.deep.contain({ + type: 'text', + text: 'You need to connect to a MongoDB instance before you can access its data.', + }); + }); + + test('should handle MisconfiguredConnectionString error', () => { + const handler = createMCPConnectionErrorHandler(connectionController); + const result = handler( + new MongoDBError( + ErrorCodes.MisconfiguredConnectionString, + 'Misconfigured MongoDB string', + ), + {} as ConnectionErrorHandlerContext, + ) as ConnectionErrorHandled; + + expect(result.errorHandled).to.be.true; + expect(result.result.content).to.deep.contain({ + type: 'text', + text: 'MCP server is having trouble connecting to the selected connection in the MongoDB VSCode extension.', + }); + }); + + test('should not handle any other errors', () => { + const handler = createMCPConnectionErrorHandler(connectionController); + expect( + handler( + new MongoDBError(ErrorCodes.ForbiddenCollscan as any, 'Some error'), + {} as any, + ), + ).to.deep.equal({ + errorHandled: false, + }); + expect(handler(new Error('Some error') as any, {} as any)).to.deep.equal({ + errorHandled: false, + }); + }); +});