Skip to content

Commit ac602a9

Browse files
committed
Merge remote-tracking branch 'origin/main' into telemetry-data
2 parents 5c53842 + 77513d9 commit ac602a9

File tree

11 files changed

+206
-23
lines changed

11 files changed

+206
-23
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Atlas MCP Server
1+
# MongoDB MCP Server
22

33
A Model Context Protocol server for interacting with MongoDB Atlas. This project implements a Model Context Protocol (MCP) server enabling AI assistants to interact with MongoDB Atlas resources through natural language.
44

@@ -40,7 +40,7 @@ Step 1: Add the mcp server to VSCode configuration
4040

4141
- Press `Cmd + Shift + P` and type `MCP: Add MCP Server` and select it.
4242
- Select command (Stdio).
43-
- Input command `npx -y @mongodb-js/mongodb-mcp-server`.
43+
- Input command `npx -y mongodb-mcp-server`.
4444
- Choose between user / workspace
4545
- Add arguments to the file
4646

@@ -54,7 +54,7 @@ Note: the file should look like:
5454
"command": "npx",
5555
"args": [
5656
"-y",
57-
"@mongodb-js/mongodb-mcp-server"
57+
"mongodb-mcp-server"
5858
]
5959
}
6060
}
@@ -82,7 +82,7 @@ Paste the mcp server configuration into the file
8282
"mcpServers": {
8383
"MongoDB": {
8484
"command": "npx",
85-
"args": ["-y", "@mongodb-js/mongodb-mcp-server"]
85+
"args": ["-y", "mongodb-mcp-server"]
8686
}
8787
}
8888
}
@@ -228,7 +228,7 @@ export MDB_MCP_LOG_PATH="/path/to/logs"
228228
Pass configuration options as command-line arguments when starting the server:
229229

230230
```shell
231-
npx -y @mongodb-js/mongodb-mcp-server --apiClientId="your-atlas-client-id" --apiClientSecret="your-atlas-client-secret" --connectionString="mongodb+srv://username:[email protected]/myDatabase" --logPath=/path/to/logs
231+
npx -y mongodb-mcp-server --apiClientId="your-atlas-client-id" --apiClientSecret="your-atlas-client-secret" --connectionString="mongodb+srv://username:[email protected]/myDatabase" --logPath=/path/to/logs
232232
```
233233

234234
## 🤝 Contributing

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
2-
"name": "@mongodb-js/mongodb-mcp-server",
2+
"name": "mongodb-mcp-server",
33
"description": "MongoDB Model Context Protocol Server",
4-
"version": "0.0.3",
4+
"version": "0.0.4",
55
"main": "dist/index.js",
66
"author": "MongoDB <[email protected]>",
77
"homepage": "https://github.com/mongodb-js/mongodb-mcp-server",

src/server.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { Telemetry } from "./telemetry/telemetry.js";
1010
import { UserConfig } from "./config.js";
1111
import { type ServerEvent } from "./telemetry/types.js";
1212
import { type ServerCommand } from "./telemetry/types.js";
13+
import { CallToolRequestSchema, CallToolResult } from "@modelcontextprotocol/sdk/types.js";
14+
import assert from "assert";
1315

1416
export interface ServerOptions {
1517
session: Session;
@@ -37,6 +39,29 @@ export class Server {
3739
this.registerTools();
3840
this.registerResources();
3941

42+
// This is a workaround for an issue we've seen with some models, where they'll see that everything in the `arguments`
43+
// object is optional, and then not pass it at all. However, the MCP server expects the `arguments` object to be if
44+
// the tool accepts any arguments, even if they're all optional.
45+
//
46+
// see: https://github.com/modelcontextprotocol/typescript-sdk/blob/131776764536b5fdca642df51230a3746fb4ade0/src/server/mcp.ts#L705
47+
// Since paramsSchema here is not undefined, the server will create a non-optional z.object from it.
48+
const existingHandler = (
49+
this.mcpServer.server["_requestHandlers"] as Map<
50+
string,
51+
(request: unknown, extra: unknown) => Promise<CallToolResult>
52+
>
53+
).get(CallToolRequestSchema.shape.method.value);
54+
55+
assert(existingHandler, "No existing handler found for CallToolRequestSchema");
56+
57+
this.mcpServer.server.setRequestHandler(CallToolRequestSchema, (request, extra): Promise<CallToolResult> => {
58+
if (!request.params.arguments) {
59+
request.params.arguments = {};
60+
}
61+
62+
return existingHandler(request, extra);
63+
});
64+
4065
await initializeLogger(this.mcpServer, this.userConfig.logPath);
4166

4267
await this.mcpServer.connect(transport);

src/tools/mongodb/metadata/logs.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2+
import { MongoDBToolBase } from "../mongodbTool.js";
3+
import { ToolArgs, OperationType } from "../../tool.js";
4+
import { z } from "zod";
5+
6+
export class LogsTool extends MongoDBToolBase {
7+
protected name = "mongodb-logs";
8+
protected description = "Returns the most recent logged mongod events";
9+
protected argsShape = {
10+
type: z
11+
.enum(["global", "startupWarnings"])
12+
.optional()
13+
.default("global")
14+
.describe(
15+
"The type of logs to return. Global returns all recent log entries, while startupWarnings returns only warnings and errors from when the process started."
16+
),
17+
limit: z
18+
.number()
19+
.int()
20+
.max(1024)
21+
.min(1)
22+
.optional()
23+
.default(50)
24+
.describe("The maximum number of log entries to return."),
25+
};
26+
27+
protected operationType: OperationType = "metadata";
28+
29+
protected async execute({ type, limit }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
30+
const provider = await this.ensureConnected();
31+
32+
const result = await provider.runCommandWithCheck("admin", {
33+
getLog: type,
34+
});
35+
36+
const logs = (result.log as string[]).slice(0, limit);
37+
38+
return {
39+
content: [
40+
{
41+
text: `Found: ${result.totalLinesWritten} messages`,
42+
type: "text",
43+
},
44+
45+
...logs.map(
46+
(log) =>
47+
({
48+
text: log,
49+
type: "text",
50+
}) as const
51+
),
52+
],
53+
};
54+
}
55+
}

src/tools/mongodb/tools.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { DropDatabaseTool } from "./delete/dropDatabase.js";
1717
import { DropCollectionTool } from "./delete/dropCollection.js";
1818
import { ExplainTool } from "./metadata/explain.js";
1919
import { CreateCollectionTool } from "./create/createCollection.js";
20+
import { LogsTool } from "./metadata/logs.js";
2021

2122
export const MongoDbTools = [
2223
ConnectTool,
@@ -38,4 +39,5 @@ export const MongoDbTools = [
3839
DropCollectionTool,
3940
ExplainTool,
4041
CreateCollectionTool,
42+
LogsTool,
4143
];

tests/integration/helpers.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,17 @@ export function setupIntegrationTest(userConfig: UserConfig = config): Integrati
101101
};
102102
}
103103

104-
export function getResponseContent(content: unknown): string {
104+
export function getResponseContent(content: unknown | { content: unknown }): string {
105105
return getResponseElements(content)
106106
.map((item) => item.text)
107107
.join("\n");
108108
}
109109

110-
export function getResponseElements(content: unknown): { type: string; text: string }[] {
110+
export function getResponseElements(content: unknown | { content: unknown }): { type: string; text: string }[] {
111+
if (typeof content === "object" && content !== null && "content" in content) {
112+
content = (content as { content: unknown }).content;
113+
}
114+
111115
expect(Array.isArray(content)).toBe(true);
112116

113117
const response = content as { type: string; text: string }[];

tests/integration/tools/mongodb/delete/dropDatabase.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ describeWithMongoDB("dropDatabase tool", (integration) => {
2121
it("can drop non-existing database", async () => {
2222
let { databases } = await integration.mongoClient().db("").admin().listDatabases();
2323

24-
const preDropLength = databases.length;
24+
expect(databases.find((db) => db.name === integration.randomDbName())).toBeUndefined();
2525

2626
await integration.connectMcpClient();
2727
const response = await integration.mcpClient().callTool({
@@ -36,7 +36,6 @@ describeWithMongoDB("dropDatabase tool", (integration) => {
3636

3737
({ databases } = await integration.mongoClient().db("").admin().listDatabases());
3838

39-
expect(databases).toHaveLength(preDropLength);
4039
expect(databases.find((db) => db.name === integration.randomDbName())).toBeUndefined();
4140
});
4241

tests/integration/tools/mongodb/metadata/connect.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,23 @@ describeWithMongoDB("Connect tool", (integration) => {
1515
},
1616
]);
1717

18+
describe("without arguments", () => {
19+
it("prompts for connection string if not set", async () => {
20+
const response = await integration.mcpClient().callTool({ name: "connect" });
21+
const content = getResponseContent(response.content);
22+
expect(content).toContain("No connection details provided");
23+
});
24+
25+
it("connects to the database if connection string is set", async () => {
26+
config.connectionString = integration.connectionString();
27+
28+
const response = await integration.mcpClient().callTool({ name: "connect" });
29+
const content = getResponseContent(response.content);
30+
expect(content).toContain("Successfully connected");
31+
expect(content).toContain(integration.connectionString());
32+
});
33+
});
34+
1835
describe("with default config", () => {
1936
describe("without connection string", () => {
2037
it("prompts for connection string", async () => {

tests/integration/tools/mongodb/metadata/dbStats.test.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -82,15 +82,13 @@ describeWithMongoDB("dbStats tool", (integration) => {
8282
}
8383
});
8484

85-
describe("when not connected", () => {
86-
validateAutoConnectBehavior(integration, "db-stats", () => {
87-
return {
88-
args: {
89-
database: integration.randomDbName(),
90-
collection: "foo",
91-
},
92-
expectedResponse: `Statistics for database ${integration.randomDbName()}`,
93-
};
94-
});
85+
validateAutoConnectBehavior(integration, "db-stats", () => {
86+
return {
87+
args: {
88+
database: integration.randomDbName(),
89+
collection: "foo",
90+
},
91+
expectedResponse: `Statistics for database ${integration.randomDbName()}`,
92+
};
9593
});
9694
});

0 commit comments

Comments
 (0)