Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit d1d26e6

Browse files
authored
fix: Use JSON for stdio, similiar to the HTTP Transport and use EJSON deserialize when necessary MCP-207 (#571)
1 parent 3dbbc76 commit d1d26e6

File tree

13 files changed

+65
-146
lines changed

13 files changed

+65
-146
lines changed

‎src/tools/args.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { z, type ZodString } from "zod";
2+
import { EJSON } from "bson";
23

34
const NO_UNICODE_REGEX = /^[\x20-\x7E]*$/;
45
export const NO_UNICODE_ERROR = "String cannot contain special characters or Unicode symbols";
@@ -68,3 +69,15 @@ export const AtlasArgs = {
6869
password: (): z.ZodString =>
6970
z.string().min(1, "Password is required").max(100, "Password must be 100 characters or less"),
7071
};
72+
73+
function toEJSON<T extends object | undefined>(value: T): T {
74+
if (!value) {
75+
return value;
76+
}
77+
78+
return EJSON.deserialize(value, { relaxed: false }) as T;
79+
}
80+
81+
export function zEJSON(): z.AnyZodObject {
82+
return z.object({}).passthrough().transform(toEJSON) as unknown as z.AnyZodObject;
83+
}

‎src/tools/mongodb/create/insertMany.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ import { z } from "zod";
22
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
33
import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
44
import type { ToolArgs, OperationType } from "../../tool.js";
5+
import { zEJSON } from "../../args.js";
56

67
export class InsertManyTool extends MongoDBToolBase {
78
public name = "insert-many";
89
protected description = "Insert an array of documents into a MongoDB collection";
910
protected argsShape = {
1011
...DbOperationArgs,
1112
documents: z
12-
.array(z.object({}).passthrough().describe("An individual MongoDB document"))
13+
.array(zEJSON().describe("An individual MongoDB document"))
1314
.describe(
1415
"The array of documents to insert, matching the syntax of the document argument of db.collection.insertMany()"
1516
),

‎src/tools/mongodb/delete/deleteMany.ts‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
1-
import { z } from "zod";
21
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
32
import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
43
import type { ToolArgs, OperationType } from "../../tool.js";
54
import { checkIndexUsage } from "../../../helpers/indexCheck.js";
65
import { EJSON } from "bson";
6+
import { zEJSON } from "../../args.js";
77

88
export class DeleteManyTool extends MongoDBToolBase {
99
public name = "delete-many";
1010
protected description = "Removes all documents that match the filter from a MongoDB collection";
1111
protected argsShape = {
1212
...DbOperationArgs,
13-
filter: z
14-
.object({})
15-
.passthrough()
13+
filter: zEJSON()
1614
.optional()
1715
.describe(
1816
"The query filter, specifying the deletion criteria. Matches the syntax of the filter argument of db.collection.deleteMany()"

‎src/tools/mongodb/read/aggregate.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import { formatUntrustedData } from "../../tool.js";
66
import { checkIndexUsage } from "../../../helpers/indexCheck.js";
77
import { EJSON } from "bson";
88
import { ErrorCodes, MongoDBError } from "../../../common/errors.js";
9+
import { zEJSON } from "../../args.js";
910

1011
export const AggregateArgs = {
11-
pipeline: z.array(z.object({}).passthrough()).describe("An array of aggregation stages to execute"),
12+
pipeline: z.array(zEJSON()).describe("An array of aggregation stages to execute"),
1213
};
1314

1415
export class AggregateTool extends MongoDBToolBase {

‎src/tools/mongodb/read/count.ts‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
22
import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
33
import type { ToolArgs, OperationType } from "../../tool.js";
4-
import { z } from "zod";
54
import { checkIndexUsage } from "../../../helpers/indexCheck.js";
5+
import { zEJSON } from "../../args.js";
66

77
export const CountArgs = {
8-
query: z
9-
.object({})
10-
.passthrough()
8+
query: zEJSON()
119
.optional()
1210
.describe(
1311
"A filter/query parameter. Allows users to filter the documents to count. Matches the syntax of the filter argument of db.collection.count()."

‎src/tools/mongodb/read/find.ts‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@ import { formatUntrustedData } from "../../tool.js";
66
import type { SortDirection } from "mongodb";
77
import { checkIndexUsage } from "../../../helpers/indexCheck.js";
88
import { EJSON } from "bson";
9+
import { zEJSON } from "../../args.js";
910

1011
export const FindArgs = {
11-
filter: z
12-
.object({})
13-
.passthrough()
12+
filter: zEJSON()
1413
.optional()
1514
.describe("The query filter, matching the syntax of the query argument of db.collection.find()"),
1615
projection: z

‎src/tools/mongodb/update/updateMany.ts‎

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,21 @@ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
33
import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
44
import type { ToolArgs, OperationType } from "../../tool.js";
55
import { checkIndexUsage } from "../../../helpers/indexCheck.js";
6+
import { zEJSON } from "../../args.js";
67

78
export class UpdateManyTool extends MongoDBToolBase {
89
public name = "update-many";
910
protected description = "Updates all documents that match the specified filter for a collection";
1011
protected argsShape = {
1112
...DbOperationArgs,
12-
filter: z
13-
.object({})
14-
.passthrough()
13+
filter: zEJSON()
1514
.optional()
1615
.describe(
1716
"The selection criteria for the update, matching the syntax of the filter argument of db.collection.updateOne()"
1817
),
19-
update: z
20-
.object({})
21-
.passthrough()
22-
.describe("An update document describing the modifications to apply using update operator expressions"),
18+
update: zEJSON().describe(
19+
"An update document describing the modifications to apply using update operator expressions"
20+
),
2321
upsert: z
2422
.boolean()
2523
.optional()

‎src/transports/stdio.ts‎

Lines changed: 1 addition & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,8 @@
1-
import { EJSON } from "bson";
2-
import type { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
3-
import { JSONRPCMessageSchema } from "@modelcontextprotocol/sdk/types.js";
41
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
52
import { LogId } from "../common/logger.js";
63
import type { Server } from "../server.js";
74
import { TransportRunnerBase, type TransportRunnerConfig } from "./base.js";
85

9-
// This is almost a copy of ReadBuffer from @modelcontextprotocol/sdk
10-
// but it uses EJSON.parse instead of JSON.parse to handle BSON types
11-
export class EJsonReadBuffer {
12-
private _buffer?: Buffer;
13-
14-
append(chunk: Buffer): void {
15-
this._buffer = this._buffer ? Buffer.concat([this._buffer, chunk]) : chunk;
16-
}
17-
18-
readMessage(): JSONRPCMessage | null {
19-
if (!this._buffer) {
20-
return null;
21-
}
22-
23-
const index = this._buffer.indexOf("\n");
24-
if (index === -1) {
25-
return null;
26-
}
27-
28-
const line = this._buffer.toString("utf8", 0, index).replace(/\r$/, "");
29-
this._buffer = this._buffer.subarray(index + 1);
30-
31-
// This is using EJSON.parse instead of JSON.parse to handle BSON types
32-
return JSONRPCMessageSchema.parse(EJSON.parse(line));
33-
}
34-
35-
clear(): void {
36-
this._buffer = undefined;
37-
}
38-
}
39-
40-
// This is a hacky workaround for https://github.com/mongodb-js/mongodb-mcp-server/issues/211
41-
// The underlying issue is that StdioServerTransport uses JSON.parse to deserialize
42-
// messages, but that doesn't handle bson types, such as ObjectId when serialized as EJSON.
43-
//
44-
// This function creates a StdioServerTransport and replaces the internal readBuffer with EJsonReadBuffer
45-
// that uses EJson.parse instead.
46-
export function createStdioTransport(): StdioServerTransport {
47-
const server = new StdioServerTransport();
48-
server["_readBuffer"] = new EJsonReadBuffer();
49-
50-
return server;
51-
}
52-
536
export class StdioRunner extends TransportRunnerBase {
547
private server: Server | undefined;
558

@@ -60,8 +13,7 @@ export class StdioRunner extends TransportRunnerBase {
6013
async start(): Promise<void> {
6114
try {
6215
this.server = await this.setupServer();
63-
64-
const transport = createStdioTransport();
16+
const transport = new StdioServerTransport();
6517

6618
await this.server.connect(transport);
6719
} catch (error: unknown) {

‎tests/integration/indexCheck.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ describe("IndexCheck integration tests", () => {
8080
arguments: {
8181
database: integration.randomDbName(),
8282
collection: "find-test-collection",
83-
filter: { _id: docs[0]?._id }, // Uses _id index (IDHACK)
83+
filter: { _id: {$oid: docs[0]?._id} }, // Uses _id index (IDHACK)
8484
},
8585
});
8686

‎tests/integration/tools/mongodb/create/insertMany.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ describeWithMongoDB("insertMany tool", (integration) => {
7676
arguments: {
7777
database: integration.randomDbName(),
7878
collection: "coll1",
79-
documents: [{ prop1: "value1", _id: insertedIds[0] }],
79+
documents: [{ prop1: "value1", _id: {$oid: insertedIds[0]} }],
8080
},
8181
});
8282

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /