Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -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",
Expand Down
71 changes: 71 additions & 0 deletions src/mcp/mcpConnectionErrorHandler.ts
Original file line number Diff line number Diff line change
@@ -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'
>,
Comment on lines +8 to +11
Copy link
Contributor

Choose a reason for hiding this comment

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

Why the pick here - why not just get the complete controller?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because the code does not really depend on anything more than the required methods and the consuming side shouldn't have to be bound to pass anything more than the required methods?

): 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,
};
}
};
};
7 changes: 3 additions & 4 deletions src/mcp/mcpConnectionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,9 @@ export class MCPConnectionManager extends ConnectionManager {
connect(): Promise<AnyConnectionState> {
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",
),
);
}
Expand Down
12 changes: 8 additions & 4 deletions src/mcp/mcpController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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 = {
Expand Down
90 changes: 90 additions & 0 deletions src/test/suite/mcp/mcpConnectionErrorHandler.test.ts
Original file line number Diff line number Diff line change
@@ -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,
});
});
});
Loading