mcp-mongo-server 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +123 -0
- package/build/index.js +420 -0
- package/build/types.js +143 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# MCP MongoDB Server
|
|
2
|
+
---
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
A Model Context Protocol server that provides access to MongoDB databases. This server enables LLMs to inspect collection schemas and execute read-only queries.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
### Resources
|
|
12
|
+
- List and access collections via `mongodb://` URIs
|
|
13
|
+
- Each collection has a name, description and schema
|
|
14
|
+
- JSON mime type for schema access
|
|
15
|
+
|
|
16
|
+
### Tools
|
|
17
|
+
- **query**
|
|
18
|
+
- Execute read-only MongoDB queries against the connected database
|
|
19
|
+
- Input: `query` (object): The MongoDB query to execute
|
|
20
|
+
- All queries are executed with read-only permissions
|
|
21
|
+
|
|
22
|
+
- **aggregate**
|
|
23
|
+
- Execute read-only MongoDB queries against the connected database
|
|
24
|
+
- Input: `aggregate` (object): The MongoDB query to execute
|
|
25
|
+
- All queries are executed with read-only permissions
|
|
26
|
+
|
|
27
|
+
### Prompts
|
|
28
|
+
- `query` - Provide insights about the collection's structure, data types, and basic statistics
|
|
29
|
+
- Input: `collection` (string): The name of the collection to analyze
|
|
30
|
+
- Output: `text` (string): A summary of the collection's structure, data types, and basic statistics
|
|
31
|
+
- `aggregate` - Provide insights about the collection's structure, data types, and basic statistics
|
|
32
|
+
- Input: `collection` (string): The name of the collection to analyze
|
|
33
|
+
- Output: `text` (string): A summary of the collection's structure, data types, and basic statistics
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
## Development
|
|
38
|
+
|
|
39
|
+
Install dependencies:
|
|
40
|
+
```bash
|
|
41
|
+
npm install
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Build the server:
|
|
45
|
+
```bash
|
|
46
|
+
npm run build
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
For development with auto-rebuild:
|
|
50
|
+
```bash
|
|
51
|
+
npm run watch
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
To use with Claude Desktop, add the server config:
|
|
57
|
+
|
|
58
|
+
On MacOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
59
|
+
|
|
60
|
+
On Windows: `%APPDATA%/Claude/claude_desktop_config.json`
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"mcpServers": {
|
|
65
|
+
"mongodb": {
|
|
66
|
+
"command": "node",
|
|
67
|
+
"args": [
|
|
68
|
+
"~/mcp-mongo-server/build/index.js",
|
|
69
|
+
"mongodb://muhammed:kilic@mongodb.localhost/namespace"
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
### Debugging
|
|
78
|
+
|
|
79
|
+
Since MCP servers communicate over stdio, debugging can be challenging. We recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector), which is available as a package script:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npm run inspector
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The Inspector will provide a URL to access debugging tools in your browser.
|
|
86
|
+
|
|
87
|
+
## Components
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
### Resources
|
|
91
|
+
|
|
92
|
+
The server provides schema information for each collection in the database:
|
|
93
|
+
|
|
94
|
+
- **Collection Schemas** (`mongodb://<host>/<collection>/schema`)
|
|
95
|
+
- JSON schema information for each collection
|
|
96
|
+
- Includes field names and data types
|
|
97
|
+
- Automatically inferred from collection documents
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
## Usage with Claude Desktop
|
|
101
|
+
|
|
102
|
+
To use this server with the Claude Desktop app, add the following configuration to the "mcpServers" section of your `claude_desktop_config.json`:
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"mcpServers": {
|
|
107
|
+
"mongodb": {
|
|
108
|
+
"command": "npx",
|
|
109
|
+
"args": [
|
|
110
|
+
"-y",
|
|
111
|
+
"mcp-mongo-server",
|
|
112
|
+
"mongodb://muhammed:kilic@mongodb.localhost/sample_namespace"
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Replace `/sample_namespace` with your database name.
|
|
120
|
+
|
|
121
|
+
## License
|
|
122
|
+
|
|
123
|
+
This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository.
|
package/build/index.js
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* This is an MCP server that implements a MongoDB interface.
|
|
4
|
+
* It demonstrates core MCP concepts by allowing:
|
|
5
|
+
* - Listing collections as resources
|
|
6
|
+
* - Reading collection schemas and contents
|
|
7
|
+
* - Executing MongoDB queries via tools
|
|
8
|
+
* - Providing collection summaries via prompts
|
|
9
|
+
*/
|
|
10
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
11
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
12
|
+
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ListResourceTemplatesRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
13
|
+
import { MongoClient, ServerApiVersion } from "mongodb";
|
|
14
|
+
/**
|
|
15
|
+
* MongoDB connection client and database reference
|
|
16
|
+
*/
|
|
17
|
+
let client = null;
|
|
18
|
+
let db = null;
|
|
19
|
+
/**
|
|
20
|
+
* Create an MCP server with capabilities for resources (to list/read collections),
|
|
21
|
+
* tools (to query data), and prompts (to analyze collections).
|
|
22
|
+
*/
|
|
23
|
+
const server = new Server({
|
|
24
|
+
name: "mongodb",
|
|
25
|
+
version: "0.1.0",
|
|
26
|
+
}, {
|
|
27
|
+
capabilities: {
|
|
28
|
+
resources: {},
|
|
29
|
+
tools: {},
|
|
30
|
+
prompts: {},
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* Initialize MongoDB connection
|
|
35
|
+
*/
|
|
36
|
+
async function connectToMongoDB(url) {
|
|
37
|
+
try {
|
|
38
|
+
client = new MongoClient(url, {
|
|
39
|
+
serverApi: {
|
|
40
|
+
version: ServerApiVersion.v1,
|
|
41
|
+
strict: true,
|
|
42
|
+
deprecationErrors: true,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
await client.connect();
|
|
46
|
+
db = client.db();
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
console.error("Failed to connect to MongoDB:", error);
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Handler for listing available collections as resources.
|
|
56
|
+
* Each collection is exposed as a resource with:
|
|
57
|
+
* - A mongodb:// URI scheme
|
|
58
|
+
* - JSON MIME type
|
|
59
|
+
* - Collection name and description
|
|
60
|
+
*/
|
|
61
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
62
|
+
try {
|
|
63
|
+
const collections = await db.listCollections().toArray();
|
|
64
|
+
return {
|
|
65
|
+
resources: collections.map((collection) => ({
|
|
66
|
+
uri: `mongodb:///${collection.name}`,
|
|
67
|
+
mimeType: "application/json",
|
|
68
|
+
name: collection.name,
|
|
69
|
+
description: `MongoDB collection: ${collection.name}`,
|
|
70
|
+
})),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (error instanceof Error) {
|
|
75
|
+
throw new Error(`Failed to list collections: ${error.message}`);
|
|
76
|
+
}
|
|
77
|
+
throw new Error('Failed to list collections: Unknown error');
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
/**
|
|
81
|
+
* Handler for reading a collection's schema or contents.
|
|
82
|
+
* Takes a mongodb:// URI and returns the collection info as JSON.
|
|
83
|
+
*/
|
|
84
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
85
|
+
const url = new URL(request.params.uri);
|
|
86
|
+
const collectionName = url.pathname.replace(/^\//, "");
|
|
87
|
+
try {
|
|
88
|
+
const collection = db.collection(collectionName);
|
|
89
|
+
const sample = await collection.findOne({});
|
|
90
|
+
const indexes = await collection.indexes();
|
|
91
|
+
// Infer schema from sample document
|
|
92
|
+
const schema = sample ? {
|
|
93
|
+
type: "collection",
|
|
94
|
+
name: collectionName,
|
|
95
|
+
fields: Object.entries(sample).map(([key, value]) => ({
|
|
96
|
+
name: key,
|
|
97
|
+
type: typeof value,
|
|
98
|
+
})),
|
|
99
|
+
indexes: indexes.map((idx) => ({
|
|
100
|
+
name: idx.name,
|
|
101
|
+
keys: idx.key,
|
|
102
|
+
})),
|
|
103
|
+
} : {
|
|
104
|
+
type: "collection",
|
|
105
|
+
name: collectionName,
|
|
106
|
+
fields: [],
|
|
107
|
+
indexes: [],
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
contents: [{
|
|
111
|
+
uri: request.params.uri,
|
|
112
|
+
mimeType: "application/json",
|
|
113
|
+
text: JSON.stringify(schema, null, 2)
|
|
114
|
+
}]
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
if (error instanceof Error) {
|
|
119
|
+
throw new Error(`Failed to read collection ${collectionName}: ${error.message}`);
|
|
120
|
+
}
|
|
121
|
+
throw new Error(`Failed to read collection ${collectionName}: Unknown error`);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
/**
|
|
125
|
+
* Handler that lists available tools.
|
|
126
|
+
* Exposes MongoDB query tools for interacting with collections.
|
|
127
|
+
*/
|
|
128
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
129
|
+
return {
|
|
130
|
+
tools: [
|
|
131
|
+
{
|
|
132
|
+
name: "query",
|
|
133
|
+
description: "Execute a MongoDB query",
|
|
134
|
+
inputSchema: {
|
|
135
|
+
type: "object",
|
|
136
|
+
properties: {
|
|
137
|
+
collection: {
|
|
138
|
+
type: "string",
|
|
139
|
+
description: "Name of the collection to query"
|
|
140
|
+
},
|
|
141
|
+
filter: {
|
|
142
|
+
type: "object",
|
|
143
|
+
description: "MongoDB query filter"
|
|
144
|
+
},
|
|
145
|
+
projection: {
|
|
146
|
+
type: "object",
|
|
147
|
+
description: "Fields to include/exclude"
|
|
148
|
+
},
|
|
149
|
+
limit: {
|
|
150
|
+
type: "number",
|
|
151
|
+
description: "Maximum number of documents to return"
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
required: ["collection"]
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: "aggregate",
|
|
159
|
+
description: "Execute a MongoDB aggregation pipeline",
|
|
160
|
+
inputSchema: {
|
|
161
|
+
type: "object",
|
|
162
|
+
properties: {
|
|
163
|
+
collection: {
|
|
164
|
+
type: "string",
|
|
165
|
+
description: "Name of the collection to aggregate"
|
|
166
|
+
},
|
|
167
|
+
pipeline: {
|
|
168
|
+
type: "array",
|
|
169
|
+
description: "Aggregation pipeline stages"
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
required: ["collection", "pipeline"]
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
/**
|
|
179
|
+
* Handler for MongoDB tools.
|
|
180
|
+
* Executes queries and returns results.
|
|
181
|
+
*/
|
|
182
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
183
|
+
const collection = db.collection(request.params.arguments?.collection);
|
|
184
|
+
switch (request.params.name) {
|
|
185
|
+
case "query": {
|
|
186
|
+
const { filter, projection, limit } = request.params.arguments || {};
|
|
187
|
+
// Validate collection name to prevent access to system collections
|
|
188
|
+
if (collection.collectionName.startsWith('system.')) {
|
|
189
|
+
throw new Error('Access to system collections is not allowed');
|
|
190
|
+
}
|
|
191
|
+
// Validate and parse filter
|
|
192
|
+
let queryFilter = {};
|
|
193
|
+
if (filter) {
|
|
194
|
+
if (typeof filter === 'string') {
|
|
195
|
+
try {
|
|
196
|
+
queryFilter = JSON.parse(filter);
|
|
197
|
+
}
|
|
198
|
+
catch (e) {
|
|
199
|
+
throw new Error('Invalid filter format: must be a valid JSON object');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
else if (typeof filter === 'object' && filter !== null && !Array.isArray(filter)) {
|
|
203
|
+
queryFilter = filter;
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
throw new Error('Query filter must be a plain object or ObjectId');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Execute the find operation with error handling
|
|
210
|
+
try {
|
|
211
|
+
const cursor = collection.find(queryFilter, {
|
|
212
|
+
projection,
|
|
213
|
+
limit: limit || 100
|
|
214
|
+
});
|
|
215
|
+
const results = await cursor.toArray();
|
|
216
|
+
return {
|
|
217
|
+
content: [{
|
|
218
|
+
type: "text",
|
|
219
|
+
text: JSON.stringify(results, null, 2)
|
|
220
|
+
}]
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
if (error instanceof Error) {
|
|
225
|
+
throw new Error(`Failed to query collection ${collection.collectionName}: ${error.message}`);
|
|
226
|
+
}
|
|
227
|
+
throw new Error(`Failed to query collection ${collection.collectionName}: Unknown error`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
case "aggregate": {
|
|
231
|
+
const { pipeline } = request.params.arguments || {};
|
|
232
|
+
if (!Array.isArray(pipeline)) {
|
|
233
|
+
throw new Error("Pipeline must be an array");
|
|
234
|
+
}
|
|
235
|
+
// Validate collection name to prevent access to system collections
|
|
236
|
+
if (collection.collectionName.startsWith('system.')) {
|
|
237
|
+
throw new Error('Access to system collections is not allowed');
|
|
238
|
+
}
|
|
239
|
+
// Execute the aggregation operation with error handling
|
|
240
|
+
try {
|
|
241
|
+
const results = await collection.aggregate(pipeline).toArray();
|
|
242
|
+
return {
|
|
243
|
+
content: [{
|
|
244
|
+
type: "text",
|
|
245
|
+
text: JSON.stringify(results, null, 2)
|
|
246
|
+
}]
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
if (error instanceof Error) {
|
|
251
|
+
throw new Error(`Failed to aggregate collection ${collection.collectionName}: ${error.message}`);
|
|
252
|
+
}
|
|
253
|
+
throw new Error(`Failed to aggregate collection ${collection.collectionName}: Unknown error`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
default:
|
|
257
|
+
throw new Error("Unknown tool");
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
/**
|
|
261
|
+
* Handler that lists available prompts.
|
|
262
|
+
* Exposes prompts for analyzing collections.
|
|
263
|
+
*/
|
|
264
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
265
|
+
return {
|
|
266
|
+
prompts: [
|
|
267
|
+
{
|
|
268
|
+
name: "analyze_collection",
|
|
269
|
+
description: "Analyze a MongoDB collection structure and contents",
|
|
270
|
+
arguments: [
|
|
271
|
+
{
|
|
272
|
+
name: "collection",
|
|
273
|
+
description: "Name of the collection to analyze",
|
|
274
|
+
required: true
|
|
275
|
+
}
|
|
276
|
+
]
|
|
277
|
+
}
|
|
278
|
+
]
|
|
279
|
+
};
|
|
280
|
+
});
|
|
281
|
+
/**
|
|
282
|
+
* Handler for collection analysis prompt.
|
|
283
|
+
* Returns a prompt that requests analysis of a collection's structure and data.
|
|
284
|
+
*/
|
|
285
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
286
|
+
if (request.params.name !== "analyze_collection") {
|
|
287
|
+
throw new Error("Unknown prompt");
|
|
288
|
+
}
|
|
289
|
+
const collectionName = request.params.arguments?.collection;
|
|
290
|
+
if (!collectionName) {
|
|
291
|
+
throw new Error("Collection name is required");
|
|
292
|
+
}
|
|
293
|
+
try {
|
|
294
|
+
const collection = db.collection(collectionName);
|
|
295
|
+
// Validate collection name to prevent access to system collections
|
|
296
|
+
if (collection.collectionName.startsWith('system.')) {
|
|
297
|
+
throw new Error('Access to system collections is not allowed');
|
|
298
|
+
}
|
|
299
|
+
const schema = await collection.findOne({});
|
|
300
|
+
// Get basic collection stats - just count in API v1
|
|
301
|
+
const stats = await collection.aggregate([
|
|
302
|
+
{
|
|
303
|
+
$collStats: {
|
|
304
|
+
count: {}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
]).toArray();
|
|
308
|
+
// Also get a sample of documents to show data distribution
|
|
309
|
+
const sampleDocs = await collection.find({})
|
|
310
|
+
.limit(5)
|
|
311
|
+
.toArray();
|
|
312
|
+
return {
|
|
313
|
+
messages: [
|
|
314
|
+
{
|
|
315
|
+
role: "user",
|
|
316
|
+
content: {
|
|
317
|
+
type: "text",
|
|
318
|
+
text: `Please analyze the following MongoDB collection:
|
|
319
|
+
Collection: ${collectionName}
|
|
320
|
+
|
|
321
|
+
Schema:
|
|
322
|
+
${JSON.stringify(schema, null, 2)}
|
|
323
|
+
|
|
324
|
+
Stats:
|
|
325
|
+
Document count: ${stats[0]?.count || 'unknown'}
|
|
326
|
+
|
|
327
|
+
Sample documents:
|
|
328
|
+
${JSON.stringify(sampleDocs, null, 2)}`
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
role: "user",
|
|
333
|
+
content: {
|
|
334
|
+
type: "text",
|
|
335
|
+
text: "Provide insights about the collection's structure, data types, and basic statistics."
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
]
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
if (error instanceof Error) {
|
|
343
|
+
throw new Error(`Failed to analyze collection ${collectionName}: ${error.message}`);
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
throw new Error(`Failed to analyze collection ${collectionName}: Unknown error`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
/**
|
|
351
|
+
* Handler for listing templates.
|
|
352
|
+
* Exposes templates for constructing MongoDB queries.
|
|
353
|
+
*/
|
|
354
|
+
server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
355
|
+
return {
|
|
356
|
+
resourceTemplates: [
|
|
357
|
+
{
|
|
358
|
+
name: "mongodb_query",
|
|
359
|
+
description: "Template for constructing MongoDB queries",
|
|
360
|
+
uriTemplate: "mongodb:///{collection}",
|
|
361
|
+
text: `To query MongoDB collections, you can use these operators:
|
|
362
|
+
|
|
363
|
+
Filter operators:
|
|
364
|
+
- $eq: Matches values equal to a specified value
|
|
365
|
+
- $gt/$gte: Matches values greater than (or equal to) a specified value
|
|
366
|
+
- $lt/$lte: Matches values less than (or equal to) a specified value
|
|
367
|
+
- $in: Matches any of the values in an array
|
|
368
|
+
- $nin: Matches none of the values in an array
|
|
369
|
+
- $ne: Matches values not equal to a specified value
|
|
370
|
+
- $exists: Matches documents that have the specified field
|
|
371
|
+
|
|
372
|
+
Example queries:
|
|
373
|
+
1. Find documents where age > 21:
|
|
374
|
+
{ "age": { "$gt": 21 } }
|
|
375
|
+
|
|
376
|
+
2. Find documents with specific status:
|
|
377
|
+
{ "status": { "$in": ["active", "pending"] } }
|
|
378
|
+
|
|
379
|
+
3. Find documents with existing email:
|
|
380
|
+
{ "email": { "$exists": true } }
|
|
381
|
+
|
|
382
|
+
Use these patterns to construct MongoDB queries.`
|
|
383
|
+
}
|
|
384
|
+
]
|
|
385
|
+
};
|
|
386
|
+
});
|
|
387
|
+
/**
|
|
388
|
+
* Start the server using stdio transport and initialize MongoDB connection.
|
|
389
|
+
*/
|
|
390
|
+
async function main() {
|
|
391
|
+
const args = process.argv.slice(2);
|
|
392
|
+
if (args.length === 0) {
|
|
393
|
+
console.error("Please provide a MongoDB connection URL as a command-line argument");
|
|
394
|
+
process.exit(1);
|
|
395
|
+
}
|
|
396
|
+
const connected = await connectToMongoDB(args[0]);
|
|
397
|
+
if (!connected) {
|
|
398
|
+
console.error("Failed to connect to MongoDB");
|
|
399
|
+
process.exit(1);
|
|
400
|
+
}
|
|
401
|
+
const transport = new StdioServerTransport();
|
|
402
|
+
await server.connect(transport);
|
|
403
|
+
}
|
|
404
|
+
// Handle cleanup
|
|
405
|
+
process.on("SIGINT", async () => {
|
|
406
|
+
if (client) {
|
|
407
|
+
await client.close();
|
|
408
|
+
}
|
|
409
|
+
process.exit(0);
|
|
410
|
+
});
|
|
411
|
+
process.on("SIGTERM", async () => {
|
|
412
|
+
if (client) {
|
|
413
|
+
await client.close();
|
|
414
|
+
}
|
|
415
|
+
process.exit(0);
|
|
416
|
+
});
|
|
417
|
+
main().catch((error) => {
|
|
418
|
+
console.error("Server error:", error);
|
|
419
|
+
process.exit(1);
|
|
420
|
+
});
|
package/build/types.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { CallToolRequestSchema, ToolSchema, ResourceSchema, TextResourceContentsSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
// MongoDB Collection Schema
|
|
4
|
+
export const MongoCollectionSchema = ResourceSchema.extend({
|
|
5
|
+
collectionName: z.string(),
|
|
6
|
+
databaseName: z.string(),
|
|
7
|
+
indexes: z.array(z.object({
|
|
8
|
+
name: z.string(),
|
|
9
|
+
keys: z.record(z.union([z.number(), z.string()])),
|
|
10
|
+
unique: z.optional(z.boolean()),
|
|
11
|
+
})).optional(),
|
|
12
|
+
});
|
|
13
|
+
// MongoDB Document Schema
|
|
14
|
+
export const MongoDocumentSchema = TextResourceContentsSchema.extend({
|
|
15
|
+
document: z.record(z.unknown()),
|
|
16
|
+
});
|
|
17
|
+
// MongoDB Query Operators
|
|
18
|
+
export const MongoQueryOperatorSchema = z.object({
|
|
19
|
+
$eq: z.unknown().optional(),
|
|
20
|
+
$gt: z.unknown().optional(),
|
|
21
|
+
$gte: z.unknown().optional(),
|
|
22
|
+
$in: z.array(z.unknown()).optional(),
|
|
23
|
+
$lt: z.unknown().optional(),
|
|
24
|
+
$lte: z.unknown().optional(),
|
|
25
|
+
$ne: z.unknown().optional(),
|
|
26
|
+
$nin: z.array(z.unknown()).optional(),
|
|
27
|
+
$and: z.array(z.unknown()).optional(),
|
|
28
|
+
$not: z.unknown().optional(),
|
|
29
|
+
$nor: z.array(z.unknown()).optional(),
|
|
30
|
+
$or: z.array(z.unknown()).optional(),
|
|
31
|
+
$exists: z.boolean().optional(),
|
|
32
|
+
$type: z.union([z.string(), z.number()]).optional(),
|
|
33
|
+
$expr: z.unknown().optional(),
|
|
34
|
+
$regex: z.string().optional(),
|
|
35
|
+
$options: z.string().optional(),
|
|
36
|
+
});
|
|
37
|
+
// MongoDB Sort Options
|
|
38
|
+
export const MongoSortSchema = z.record(z.union([z.literal(1), z.literal(-1)]));
|
|
39
|
+
// MongoDB Query Tool Schema
|
|
40
|
+
export const MongoQueryToolSchema = ToolSchema.extend({
|
|
41
|
+
inputSchema: z.object({
|
|
42
|
+
type: z.literal("object"),
|
|
43
|
+
properties: z.object({
|
|
44
|
+
collection: z.string(),
|
|
45
|
+
filter: z.record(z.union([z.unknown(), MongoQueryOperatorSchema])),
|
|
46
|
+
projection: z.record(z.union([z.literal(0), z.literal(1)])).optional(),
|
|
47
|
+
sort: MongoSortSchema.optional(),
|
|
48
|
+
limit: z.number().int().positive().optional(),
|
|
49
|
+
skip: z.number().int().nonnegative().optional(),
|
|
50
|
+
}).passthrough(),
|
|
51
|
+
required: z.array(z.literal("collection")),
|
|
52
|
+
}),
|
|
53
|
+
});
|
|
54
|
+
// MongoDB Aggregate Tool Schema
|
|
55
|
+
export const MongoAggregateToolSchema = ToolSchema.extend({
|
|
56
|
+
inputSchema: z.object({
|
|
57
|
+
type: z.literal("object"),
|
|
58
|
+
properties: z.object({
|
|
59
|
+
collection: z.string(),
|
|
60
|
+
pipeline: z.array(z.record(z.unknown())),
|
|
61
|
+
}).passthrough(),
|
|
62
|
+
required: z.array(z.literal("collection")),
|
|
63
|
+
}),
|
|
64
|
+
});
|
|
65
|
+
// MongoDB Count Tool Schema
|
|
66
|
+
export const MongoCountToolSchema = ToolSchema.extend({
|
|
67
|
+
inputSchema: z.object({
|
|
68
|
+
type: z.literal("object"),
|
|
69
|
+
properties: z.object({
|
|
70
|
+
collection: z.string(),
|
|
71
|
+
filter: z.record(z.union([z.unknown(), MongoQueryOperatorSchema])).optional(),
|
|
72
|
+
}).passthrough(),
|
|
73
|
+
required: z.array(z.literal("collection")),
|
|
74
|
+
}),
|
|
75
|
+
});
|
|
76
|
+
// MongoDB Distinct Tool Schema
|
|
77
|
+
export const MongoDistinctToolSchema = ToolSchema.extend({
|
|
78
|
+
inputSchema: z.object({
|
|
79
|
+
type: z.literal("object"),
|
|
80
|
+
properties: z.object({
|
|
81
|
+
collection: z.string(),
|
|
82
|
+
field: z.string(),
|
|
83
|
+
filter: z.record(z.union([z.unknown(), MongoQueryOperatorSchema])).optional(),
|
|
84
|
+
}).passthrough(),
|
|
85
|
+
required: z.array(z.literal("collection")),
|
|
86
|
+
}),
|
|
87
|
+
});
|
|
88
|
+
// MongoDB Call Tool Request Schema
|
|
89
|
+
export const MongoCallToolRequestSchema = CallToolRequestSchema.extend({
|
|
90
|
+
params: z.object({
|
|
91
|
+
name: z.enum(["query", "aggregate", "count", "distinct"]),
|
|
92
|
+
arguments: z.union([
|
|
93
|
+
MongoQueryToolSchema.shape.inputSchema,
|
|
94
|
+
MongoAggregateToolSchema.shape.inputSchema,
|
|
95
|
+
MongoCountToolSchema.shape.inputSchema,
|
|
96
|
+
MongoDistinctToolSchema.shape.inputSchema,
|
|
97
|
+
]),
|
|
98
|
+
}),
|
|
99
|
+
});
|
|
100
|
+
// MongoDB Error Types
|
|
101
|
+
export const MongoErrorCodeSchema = z.enum([
|
|
102
|
+
"INVALID_QUERY",
|
|
103
|
+
"COLLECTION_NOT_FOUND",
|
|
104
|
+
"DATABASE_NOT_FOUND",
|
|
105
|
+
"INVALID_PIPELINE",
|
|
106
|
+
"INVALID_PROJECTION",
|
|
107
|
+
"INVALID_SORT",
|
|
108
|
+
"CONNECTION_ERROR",
|
|
109
|
+
"TIMEOUT",
|
|
110
|
+
"UNAUTHORIZED",
|
|
111
|
+
]);
|
|
112
|
+
export const MongoErrorSchema = z.object({
|
|
113
|
+
code: MongoErrorCodeSchema,
|
|
114
|
+
message: z.string(),
|
|
115
|
+
details: z.unknown().optional(),
|
|
116
|
+
});
|
|
117
|
+
// MongoDB Schema Inference Types
|
|
118
|
+
export const MongoFieldSchemaSchema = z.object({
|
|
119
|
+
type: z.union([
|
|
120
|
+
z.literal("string"),
|
|
121
|
+
z.literal("number"),
|
|
122
|
+
z.literal("boolean"),
|
|
123
|
+
z.literal("date"),
|
|
124
|
+
z.literal("objectId"),
|
|
125
|
+
z.literal("array"),
|
|
126
|
+
z.literal("object"),
|
|
127
|
+
z.literal("null"),
|
|
128
|
+
z.literal("mixed"),
|
|
129
|
+
]),
|
|
130
|
+
required: z.boolean().optional(),
|
|
131
|
+
unique: z.boolean().optional(),
|
|
132
|
+
indexed: z.boolean().optional(),
|
|
133
|
+
items: z.lazy(() => MongoFieldSchemaSchema).optional(),
|
|
134
|
+
properties: z.record(z.lazy(() => MongoFieldSchemaSchema)).optional(),
|
|
135
|
+
});
|
|
136
|
+
export const MongoCollectionSchemaSchema = z.object({
|
|
137
|
+
name: z.string(),
|
|
138
|
+
fields: z.record(MongoFieldSchemaSchema),
|
|
139
|
+
options: z.object({
|
|
140
|
+
timestamps: z.boolean().optional(),
|
|
141
|
+
strict: z.boolean().optional(),
|
|
142
|
+
}).optional(),
|
|
143
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcp-mongo-server",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "A Model Context Protocol server for MongoDB connections",
|
|
5
|
+
"private": false,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mongodb": "./build/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"build"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
|
|
15
|
+
"prepare": "npm run build",
|
|
16
|
+
"watch": "tsc --watch",
|
|
17
|
+
"inspector": "npx @modelcontextprotocol/inspector build/index.js"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@modelcontextprotocol/sdk": "0.6.0",
|
|
21
|
+
"@types/mongodb": "^4.0.6",
|
|
22
|
+
"mongodb": "^6.11.0",
|
|
23
|
+
"zod": "^3.23.8"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^20.11.24",
|
|
27
|
+
"typescript": "^5.3.3"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"claude",
|
|
34
|
+
"claude-desktop",
|
|
35
|
+
"mcp",
|
|
36
|
+
"mongo",
|
|
37
|
+
"mongodb",
|
|
38
|
+
"model-context-protocol",
|
|
39
|
+
"llm",
|
|
40
|
+
"mcp-server",
|
|
41
|
+
"mcp-protocol",
|
|
42
|
+
"mcp-connector",
|
|
43
|
+
"mcp-connector-mongodb"
|
|
44
|
+
],
|
|
45
|
+
"author": "Muhammed Kilic <kiliczsh>",
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "https://github.com/kiliczsh/mcp-mongo-server"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/kiliczsh/mcp-mongo-server"
|
|
52
|
+
}
|