@mpurdon/mcp-mongodb 0.1.0
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/LICENSE +21 -0
- package/README.md +148 -0
- package/dist/config.js +83 -0
- package/dist/config.js.map +1 -0
- package/dist/connection.js +120 -0
- package/dist/connection.js.map +1 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/environment.js +85 -0
- package/dist/tools/environment.js.map +1 -0
- package/dist/tools/read.js +145 -0
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/write.js +188 -0
- package/dist/tools/write.js.map +1 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matthew Purdon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# @mpurdon/mcp-mongodb
|
|
2
|
+
|
|
3
|
+
A local **stdio** MCP server that lets Claude query and manage MongoDB across `dev` / `stg` / `prd` environments, with a safety gate on write operations in production.
|
|
4
|
+
|
|
5
|
+
- Three named environments selectable at runtime (`switch_environment`)
|
|
6
|
+
- Read tools (`find`, `aggregate`, `count`, `list_*`) work in every environment
|
|
7
|
+
- Write tools (`insert_*`, `update_*`, `delete_*`) require `confirmed: true` when the active environment is `prd`
|
|
8
|
+
- `drop_collection` requires `confirmed: true` in **every** environment
|
|
9
|
+
- Connection strings are never returned over the wire — only hostnames
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx -y @mpurdon/mcp-mongodb # downloads & runs the latest published build
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
No global install needed — the host (Claude Desktop / Cowork / Code) runs it via
|
|
20
|
+
`npx` per session. The fastest way to register it is the monorepo configurator:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx @mpurdon/mcp-servers configure
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
…or configure it manually with the steps below.
|
|
27
|
+
|
|
28
|
+
## Setup
|
|
29
|
+
|
|
30
|
+
### 1. Create the config file
|
|
31
|
+
|
|
32
|
+
The server reads `~/.mongodb-mcp/config.json`. Create it with your real connection strings:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
mkdir -p ~/.mongodb-mcp
|
|
36
|
+
cat > ~/.mongodb-mcp/config.json <<'EOF'
|
|
37
|
+
{
|
|
38
|
+
"defaultEnvironment": "dev",
|
|
39
|
+
"environments": {
|
|
40
|
+
"dev": { "connectionString": "mongodb://localhost:27017", "name": "Development" },
|
|
41
|
+
"stg": { "connectionString": "mongodb://user:pass@staging-host:27017/?authSource=admin", "name": "Staging" },
|
|
42
|
+
"prd": { "connectionString": "mongodb://user:pass@prod-host:27017/?authSource=admin", "name": "Production" }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
EOF
|
|
46
|
+
chmod 600 ~/.mongodb-mcp/config.json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
> The file contains credentials. `chmod 600` ensures only your user can read it.
|
|
50
|
+
|
|
51
|
+
### 2. Register with your Claude host
|
|
52
|
+
|
|
53
|
+
Add this to your host's MCP config (or let `npx @mpurdon/mcp-servers configure` do it):
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"mcpServers": {
|
|
58
|
+
"mongodb": {
|
|
59
|
+
"command": "npx",
|
|
60
|
+
"args": ["-y", "@mpurdon/mcp-mongodb"],
|
|
61
|
+
"env": {}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Config file location per host:
|
|
68
|
+
|
|
69
|
+
- **Claude Desktop** — `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
|
|
70
|
+
- **Claude Code** — `~/.claude.json` (user) or a project `.mcp.json`
|
|
71
|
+
- **Claude Cowork** — the workspace's `.mcp.json`
|
|
72
|
+
|
|
73
|
+
Restart the host.
|
|
74
|
+
|
|
75
|
+
### 3. Verify
|
|
76
|
+
|
|
77
|
+
Ask: _"What MongoDB environment am I connected to?"_ — Claude should call `current_environment` and return the active env, display name, host, and whether writes are restricted.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Tool reference
|
|
82
|
+
|
|
83
|
+
### Environment
|
|
84
|
+
|
|
85
|
+
| Tool | Description |
|
|
86
|
+
| ------------------------- | -------------------------------------------------------------- |
|
|
87
|
+
| `switch_environment(env)` | Reconnect to `dev`, `stg`, or `prd`. |
|
|
88
|
+
| `current_environment()` | Return active env, display name, host, write-restriction flag. |
|
|
89
|
+
|
|
90
|
+
### Read (unrestricted)
|
|
91
|
+
|
|
92
|
+
| Tool | Description |
|
|
93
|
+
| ----------------------------------------------------------------- | ------------------------------------------ |
|
|
94
|
+
| `list_databases()` | List all databases. |
|
|
95
|
+
| `list_collections(database)` | List collections in a db. |
|
|
96
|
+
| `find(database, collection, filter?, projection?, limit?, sort?)` | Query docs. Default `limit=20`, max `500`. |
|
|
97
|
+
| `aggregate(database, collection, pipeline)` | Run a pipeline. Capped at 500 results. |
|
|
98
|
+
| `count(database, collection, filter?)` | Count matching docs. |
|
|
99
|
+
|
|
100
|
+
### Write (require `confirmed: true` in `prd`)
|
|
101
|
+
|
|
102
|
+
- `insert_one(database, collection, document)`
|
|
103
|
+
- `insert_many(database, collection, documents)`
|
|
104
|
+
- `update_one(database, collection, filter, update, upsert?)`
|
|
105
|
+
- `update_many(database, collection, filter, update)`
|
|
106
|
+
- `delete_one(database, collection, filter)`
|
|
107
|
+
- `delete_many(database, collection, filter)`
|
|
108
|
+
|
|
109
|
+
When called in `prd` without `confirmed: true`, the server returns a structured `confirmationRequired` payload describing the planned execution. Re-invoke the same tool with `confirmed: true` to actually run it.
|
|
110
|
+
|
|
111
|
+
### Always-confirm
|
|
112
|
+
|
|
113
|
+
- `drop_collection(database, collection)` — requires `confirmed: true` in **every** environment.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Security notes
|
|
118
|
+
|
|
119
|
+
- **No credentials in responses.** Status responses include only the host (`host:port`), never the username/password or full URI.
|
|
120
|
+
- **Config file permissions.** Use `chmod 600 ~/.mongodb-mcp/config.json`.
|
|
121
|
+
- **Stdio only.** No TCP socket is opened; the server only accepts MCP traffic from its stdin (Claude Desktop spawns it per session).
|
|
122
|
+
- **Input validation.** All tool parameters are validated with Zod before any MongoDB call.
|
|
123
|
+
- **Production gate.** Writes in `prd` are rejected unless the caller explicitly opts in with `confirmed: true`. `drop_collection` is gated in every environment.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Development
|
|
128
|
+
|
|
129
|
+
From the monorepo root:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
pnpm --filter @mpurdon/mcp-mongodb dev # run from source (tsx)
|
|
133
|
+
pnpm --filter @mpurdon/mcp-mongodb typecheck # type-check only
|
|
134
|
+
pnpm --filter @mpurdon/mcp-mongodb build # build to dist/
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Project layout
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
src/
|
|
141
|
+
index.ts # server entry — loads config, wires transport
|
|
142
|
+
config.ts # config schema + safe host extraction
|
|
143
|
+
connection.ts # singleton MongoClient manager
|
|
144
|
+
tools/
|
|
145
|
+
environment.ts # switch_environment, current_environment
|
|
146
|
+
read.ts # list_databases, list_collections, find, aggregate, count
|
|
147
|
+
write.ts # insert/update/delete/drop with prod confirmation guard
|
|
148
|
+
```
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
export const CONFIG_PATH = join(homedir(), ".mongodb-mcp", "config.json");
|
|
6
|
+
export const EnvKey = z.enum(["dev", "stg", "prd"]);
|
|
7
|
+
const EnvironmentSchema = z
|
|
8
|
+
.object({
|
|
9
|
+
connectionString: z.string().min(1, "connectionString is required"),
|
|
10
|
+
name: z.string().min(1, "name is required"),
|
|
11
|
+
})
|
|
12
|
+
.strict();
|
|
13
|
+
export const ConfigSchema = z
|
|
14
|
+
.object({
|
|
15
|
+
defaultEnvironment: EnvKey,
|
|
16
|
+
environments: z
|
|
17
|
+
.object({
|
|
18
|
+
dev: EnvironmentSchema,
|
|
19
|
+
stg: EnvironmentSchema,
|
|
20
|
+
prd: EnvironmentSchema,
|
|
21
|
+
})
|
|
22
|
+
.strict(),
|
|
23
|
+
})
|
|
24
|
+
.strict();
|
|
25
|
+
export class ConfigError extends Error {
|
|
26
|
+
constructor(message) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = "ConfigError";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const SAMPLE_CONFIG = `{
|
|
32
|
+
"defaultEnvironment": "dev",
|
|
33
|
+
"environments": {
|
|
34
|
+
"dev": { "connectionString": "mongodb://localhost:27017", "name": "Development" },
|
|
35
|
+
"stg": { "connectionString": "mongodb://user:pass@staging-host:27017/?authSource=admin", "name": "Staging" },
|
|
36
|
+
"prd": { "connectionString": "mongodb://user:pass@prod-host:27017/?authSource=admin", "name": "Production" }
|
|
37
|
+
}
|
|
38
|
+
}`;
|
|
39
|
+
export function loadConfig(path = CONFIG_PATH) {
|
|
40
|
+
if (!existsSync(path)) {
|
|
41
|
+
throw new ConfigError(`MongoDB MCP config file not found at ${path}.\n\n` +
|
|
42
|
+
`Create it with the following contents (fill in real connection strings):\n\n` +
|
|
43
|
+
`mkdir -p "$(dirname '${path}')"\n` +
|
|
44
|
+
`cat > '${path}' <<'EOF'\n${SAMPLE_CONFIG}\nEOF\n`);
|
|
45
|
+
}
|
|
46
|
+
let raw;
|
|
47
|
+
try {
|
|
48
|
+
raw = readFileSync(path, "utf8");
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
throw new ConfigError(`Failed to read config at ${path}: ${err.message}`);
|
|
52
|
+
}
|
|
53
|
+
let parsed;
|
|
54
|
+
try {
|
|
55
|
+
parsed = JSON.parse(raw);
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
throw new ConfigError(`Config at ${path} is not valid JSON: ${err.message}`);
|
|
59
|
+
}
|
|
60
|
+
const result = ConfigSchema.safeParse(parsed);
|
|
61
|
+
if (!result.success) {
|
|
62
|
+
throw new ConfigError(`Config at ${path} failed validation:\n${result.error.issues
|
|
63
|
+
.map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`)
|
|
64
|
+
.join("\n")}`);
|
|
65
|
+
}
|
|
66
|
+
return result.data;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Extract a non-sensitive host descriptor from a MongoDB connection string.
|
|
70
|
+
* Never returns credentials or the full URI.
|
|
71
|
+
*/
|
|
72
|
+
export function safeHostFromUri(uri) {
|
|
73
|
+
try {
|
|
74
|
+
// mongodb:// and mongodb+srv:// are URL-parseable by WHATWG URL.
|
|
75
|
+
const u = new URL(uri);
|
|
76
|
+
// u.host includes hostname[:port]; strip credentials by construction (URL exposes them in u.username/u.password).
|
|
77
|
+
return u.host || "(unknown host)";
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return "(unparseable host)";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { MongoClient } from "mongodb";
|
|
2
|
+
import { safeHostFromUri } from "./config.js";
|
|
3
|
+
/**
|
|
4
|
+
* Singleton connection manager. Holds one active MongoClient at a time.
|
|
5
|
+
* Calling `switchTo` closes the existing client and opens a new one.
|
|
6
|
+
*/
|
|
7
|
+
export class ConnectionManager {
|
|
8
|
+
client = null;
|
|
9
|
+
_connectingPromise = null;
|
|
10
|
+
activeEnv;
|
|
11
|
+
config;
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.activeEnv = config.defaultEnvironment;
|
|
15
|
+
}
|
|
16
|
+
getActiveEnv() {
|
|
17
|
+
return this.activeEnv;
|
|
18
|
+
}
|
|
19
|
+
getActiveEnvName() {
|
|
20
|
+
return this.config.environments[this.activeEnv].name;
|
|
21
|
+
}
|
|
22
|
+
getActiveHost() {
|
|
23
|
+
return safeHostFromUri(this.config.environments[this.activeEnv].connectionString);
|
|
24
|
+
}
|
|
25
|
+
isProduction() {
|
|
26
|
+
return this.activeEnv === "prd";
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get the connected client for the active environment, connecting lazily.
|
|
30
|
+
*
|
|
31
|
+
* Uses a shared in-flight promise so concurrent callers awaiting an initial
|
|
32
|
+
* connection do not each create a separate MongoClient (TOCTOU race).
|
|
33
|
+
*/
|
|
34
|
+
async getClient() {
|
|
35
|
+
if (this.client)
|
|
36
|
+
return this.client;
|
|
37
|
+
if (this._connectingPromise)
|
|
38
|
+
return this._connectingPromise;
|
|
39
|
+
this._connectingPromise = this._connect().finally(() => {
|
|
40
|
+
this._connectingPromise = null;
|
|
41
|
+
});
|
|
42
|
+
return this._connectingPromise;
|
|
43
|
+
}
|
|
44
|
+
async _connect() {
|
|
45
|
+
const uri = this.config.environments[this.activeEnv].connectionString;
|
|
46
|
+
const client = new MongoClient(uri, {
|
|
47
|
+
// Reasonable defaults for an interactive MCP server.
|
|
48
|
+
serverSelectionTimeoutMS: 10_000,
|
|
49
|
+
connectTimeoutMS: 10_000,
|
|
50
|
+
socketTimeoutMS: 30_000,
|
|
51
|
+
maxPoolSize: 5,
|
|
52
|
+
minPoolSize: 1,
|
|
53
|
+
});
|
|
54
|
+
// If the topology is closed (network blip, server restart, idle eviction),
|
|
55
|
+
// drop our cached reference so the next getClient() reconnects cleanly.
|
|
56
|
+
client.on("topologyClosed", () => {
|
|
57
|
+
if (this.client === client)
|
|
58
|
+
this.client = null;
|
|
59
|
+
});
|
|
60
|
+
client.on("close", () => {
|
|
61
|
+
if (this.client === client)
|
|
62
|
+
this.client = null;
|
|
63
|
+
});
|
|
64
|
+
await client.connect();
|
|
65
|
+
this.client = client;
|
|
66
|
+
return client;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Switch to a different environment. Closes the current client and opens a new one
|
|
70
|
+
* to verify the new connection works before returning.
|
|
71
|
+
*/
|
|
72
|
+
async switchTo(env) {
|
|
73
|
+
this._connectingPromise = null;
|
|
74
|
+
if (this.client) {
|
|
75
|
+
try {
|
|
76
|
+
await this.client.close();
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// ignore close errors during switch
|
|
80
|
+
}
|
|
81
|
+
this.client = null;
|
|
82
|
+
}
|
|
83
|
+
this.activeEnv = env;
|
|
84
|
+
// Eagerly connect so a bad config surfaces immediately.
|
|
85
|
+
await this.getClient();
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Re-read a freshly parsed config without restarting the server.
|
|
89
|
+
* Closes the current connection and reconnects to the same active environment
|
|
90
|
+
* using the updated connection string.
|
|
91
|
+
*/
|
|
92
|
+
async reloadConfig(newConfig) {
|
|
93
|
+
this._connectingPromise = null;
|
|
94
|
+
if (this.client) {
|
|
95
|
+
try {
|
|
96
|
+
await this.client.close();
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// ignore close errors during reload
|
|
100
|
+
}
|
|
101
|
+
this.client = null;
|
|
102
|
+
}
|
|
103
|
+
this.config = newConfig;
|
|
104
|
+
// Keep the current env but reconnect with the (potentially new) URI.
|
|
105
|
+
await this.getClient();
|
|
106
|
+
}
|
|
107
|
+
async close() {
|
|
108
|
+
this._connectingPromise = null;
|
|
109
|
+
if (this.client) {
|
|
110
|
+
try {
|
|
111
|
+
await this.client.close();
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// ignore
|
|
115
|
+
}
|
|
116
|
+
this.client = null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=connection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { loadConfig, ConfigError } from "./config.js";
|
|
5
|
+
import { ConnectionManager } from "./connection.js";
|
|
6
|
+
import { registerEnvironmentTools } from "./tools/environment.js";
|
|
7
|
+
import { registerReadTools } from "./tools/read.js";
|
|
8
|
+
import { registerWriteTools } from "./tools/write.js";
|
|
9
|
+
async function main() {
|
|
10
|
+
let config;
|
|
11
|
+
try {
|
|
12
|
+
config = loadConfig();
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
if (err instanceof ConfigError) {
|
|
16
|
+
// Write to stderr so it surfaces in the host's MCP log without
|
|
17
|
+
// corrupting the stdio JSON-RPC stream on stdout.
|
|
18
|
+
process.stderr.write(`[mongodb] ${err.message}\n`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
throw err;
|
|
22
|
+
}
|
|
23
|
+
const conn = new ConnectionManager(config);
|
|
24
|
+
const server = new McpServer({
|
|
25
|
+
name: "mongodb",
|
|
26
|
+
version: "0.1.0",
|
|
27
|
+
}, {
|
|
28
|
+
instructions: "MongoDB MCP server. Switch environments with switch_environment; check status with current_environment. Read tools are unrestricted. Write tools require confirmed=true when the active environment is 'prd'. drop_collection ALWAYS requires confirmed=true.",
|
|
29
|
+
});
|
|
30
|
+
registerEnvironmentTools(server, conn);
|
|
31
|
+
registerReadTools(server, conn);
|
|
32
|
+
registerWriteTools(server, conn);
|
|
33
|
+
const transport = new StdioServerTransport();
|
|
34
|
+
await server.connect(transport);
|
|
35
|
+
const shutdown = async () => {
|
|
36
|
+
try {
|
|
37
|
+
await conn.close();
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
process.on("SIGINT", shutdown);
|
|
44
|
+
process.on("SIGTERM", shutdown);
|
|
45
|
+
}
|
|
46
|
+
// Prevent unhandled rejections (e.g. a MongoDB op that times out after the
|
|
47
|
+
// MCP layer already returned) from crashing the process and resetting state.
|
|
48
|
+
process.on("unhandledRejection", (reason) => {
|
|
49
|
+
process.stderr.write(`[mongodb] unhandled rejection (ignored): ${reason}\n`);
|
|
50
|
+
});
|
|
51
|
+
main().catch((err) => {
|
|
52
|
+
process.stderr.write(`[mongodb] fatal: ${err.stack ?? err}\n`);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
});
|
|
55
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD,KAAK,UAAU,IAAI;IACjB,IAAI,MAAM,CAAC;IACX,IAAI,CAAC;QACH,MAAM,GAAG,UAAU,EAAE,CAAC;IACxB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,WAAW,EAAE,CAAC;YAC/B,+DAA+D;YAC/D,kDAAkD;YAClD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;YACnD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAE3C,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B;QACE,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,OAAO;KACjB,EACD;QACE,YAAY,EACV,+PAA+P;KAClQ,CACF,CAAC;IAEF,wBAAwB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACvC,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAChC,kBAAkB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAEjC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE;QAC1B,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACrB,CAAC;gBAAS,CAAC;YACT,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAClC,CAAC;AAED,2EAA2E;AAC3E,6EAA6E;AAC7E,OAAO,CAAC,EAAE,CAAC,oBAAoB,EAAE,CAAC,MAAM,EAAE,EAAE;IAC1C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,4CAA4C,MAAM,IAAI,CAAC,CAAC;AAC/E,CAAC,CAAC,CAAC;AAEH,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAqB,GAAa,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC,CAAC;IAC1E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { EnvKey, loadConfig, safeHostFromUri, ConfigError } from "../config.js";
|
|
3
|
+
const textResult = (obj) => ({
|
|
4
|
+
content: [{ type: "text", text: JSON.stringify(obj) }],
|
|
5
|
+
});
|
|
6
|
+
const errorResult = (message) => ({
|
|
7
|
+
isError: true,
|
|
8
|
+
content: [{ type: "text", text: message }],
|
|
9
|
+
});
|
|
10
|
+
export function registerEnvironmentTools(server, conn) {
|
|
11
|
+
server.tool("switch_environment", "Switch the active MongoDB environment (dev|stg|prd). Reconnects and verifies the new connection.", { env: EnvKey }, async ({ env }) => {
|
|
12
|
+
try {
|
|
13
|
+
await conn.switchTo(env);
|
|
14
|
+
return textResult({
|
|
15
|
+
active: env,
|
|
16
|
+
name: conn.getActiveEnvName(),
|
|
17
|
+
host: conn.getActiveHost(),
|
|
18
|
+
writesRestricted: conn.isProduction(),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
return errorResult(`Failed to switch to '${env}': ${err.message}`);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
server.tool("current_environment", "Return the active environment, its display name, the connection host (no credentials), and whether writes require confirmation.", {}, async () => {
|
|
26
|
+
return textResult({
|
|
27
|
+
active: conn.getActiveEnv(),
|
|
28
|
+
name: conn.getActiveEnvName(),
|
|
29
|
+
host: conn.getActiveHost(),
|
|
30
|
+
writesRestricted: conn.isProduction(),
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
server.tool("ping", "Ping the active MongoDB connection. Returns env, host, and round-trip time in ms.", {}, async () => {
|
|
34
|
+
const start = Date.now();
|
|
35
|
+
try {
|
|
36
|
+
const client = await conn.getClient();
|
|
37
|
+
await client.db("admin").command({ ping: 1 });
|
|
38
|
+
return textResult({
|
|
39
|
+
ok: true,
|
|
40
|
+
env: conn.getActiveEnv(),
|
|
41
|
+
host: conn.getActiveHost(),
|
|
42
|
+
roundTripMs: Date.now() - start,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
return errorResult(`ping failed (env=${conn.getActiveEnv()}, host=${conn.getActiveHost()}): ${err.message}`);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
server.tool("reload_config", "Re-read ~/.mongodb-mcp/config.json and reconnect without restarting Claude Desktop. Useful after updating connection strings.", {}, async () => {
|
|
50
|
+
let newConfig;
|
|
51
|
+
try {
|
|
52
|
+
newConfig = loadConfig();
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
return errorResult(err instanceof ConfigError
|
|
56
|
+
? `Config error: ${err.message}`
|
|
57
|
+
: `Failed to read config: ${err.message}`);
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
await conn.reloadConfig(newConfig);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
return errorResult(`Config loaded but reconnect failed: ${err.message}`);
|
|
64
|
+
}
|
|
65
|
+
const envs = Object.entries(newConfig.environments).map(([key, val]) => ({
|
|
66
|
+
env: key,
|
|
67
|
+
name: val.name,
|
|
68
|
+
host: safeHostFromUri(val.connectionString),
|
|
69
|
+
}));
|
|
70
|
+
return textResult({
|
|
71
|
+
reloaded: true,
|
|
72
|
+
active: conn.getActiveEnv(),
|
|
73
|
+
name: conn.getActiveEnvName(),
|
|
74
|
+
host: conn.getActiveHost(),
|
|
75
|
+
writesRestricted: conn.isProduction(),
|
|
76
|
+
environments: envs,
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
// Re-exported helpers used by other tool modules.
|
|
81
|
+
export const _internal = { textResult, errorResult };
|
|
82
|
+
export { textResult, errorResult };
|
|
83
|
+
// Validation helpers used by read.ts / write.ts.
|
|
84
|
+
export const NonEmptyString = z.string().min(1);
|
|
85
|
+
//# sourceMappingURL=environment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"environment.js","sourceRoot":"","sources":["../../src/tools/environment.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { textResult, errorResult, NonEmptyString } from "./environment.js";
|
|
3
|
+
const FilterSchema = z.record(z.unknown()).optional();
|
|
4
|
+
const ProjectionSchema = z
|
|
5
|
+
.record(z.union([z.literal(0), z.literal(1), z.boolean()]))
|
|
6
|
+
.optional();
|
|
7
|
+
const SortSchema = z.record(z.union([z.literal(1), z.literal(-1)])).optional();
|
|
8
|
+
const PipelineSchema = z.array(z.record(z.unknown()));
|
|
9
|
+
const DEFAULT_FIND_LIMIT = 20;
|
|
10
|
+
const MAX_FIND_LIMIT = 500;
|
|
11
|
+
const MAX_AGG_RESULTS = 500;
|
|
12
|
+
// Server-side time budget for every operation. Set below the MCP ~30s timeout
|
|
13
|
+
// so MongoDB aborts cleanly and we can return an errorResult instead of an
|
|
14
|
+
// unhandled rejection after the MCP layer has already given up.
|
|
15
|
+
const OP_MAX_TIME_MS = 25_000;
|
|
16
|
+
// Aggregation stages that write to the database. We block these in every
|
|
17
|
+
// environment because they bypass the write tools' confirmation guard.
|
|
18
|
+
const DISALLOWED_AGG_STAGES = ["$out", "$merge"];
|
|
19
|
+
export function registerReadTools(server, conn) {
|
|
20
|
+
server.tool("list_databases", "List all databases on the active MongoDB connection.", {}, async () => {
|
|
21
|
+
try {
|
|
22
|
+
const client = await conn.getClient();
|
|
23
|
+
const admin = client.db().admin();
|
|
24
|
+
const result = await admin.listDatabases();
|
|
25
|
+
return textResult({
|
|
26
|
+
env: conn.getActiveEnv(),
|
|
27
|
+
databases: result.databases.map((d) => ({
|
|
28
|
+
name: d.name,
|
|
29
|
+
sizeOnDisk: d.sizeOnDisk,
|
|
30
|
+
empty: d.empty,
|
|
31
|
+
})),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
return errorResult(`list_databases failed: ${err.message}`);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
server.tool("list_collections", "List all collections in a database.", { database: NonEmptyString }, async ({ database }) => {
|
|
39
|
+
try {
|
|
40
|
+
const client = await conn.getClient();
|
|
41
|
+
const cols = await client.db(database).listCollections().toArray();
|
|
42
|
+
return textResult({
|
|
43
|
+
env: conn.getActiveEnv(),
|
|
44
|
+
database,
|
|
45
|
+
collections: cols.map((c) => ({ name: c.name, type: c.type })),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
return errorResult(`list_collections failed: ${err.message}`);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
server.tool("find", "Query documents. Defaults to limit=20, max 500. Returns matched documents.", {
|
|
53
|
+
database: NonEmptyString,
|
|
54
|
+
collection: NonEmptyString,
|
|
55
|
+
filter: FilterSchema,
|
|
56
|
+
projection: ProjectionSchema,
|
|
57
|
+
limit: z.number().int().positive().max(MAX_FIND_LIMIT).optional(),
|
|
58
|
+
sort: SortSchema,
|
|
59
|
+
}, async ({ database, collection, filter, projection, limit, sort }) => {
|
|
60
|
+
try {
|
|
61
|
+
const client = await conn.getClient();
|
|
62
|
+
const col = client.db(database).collection(collection);
|
|
63
|
+
const cursor = col.find((filter ?? {}), {
|
|
64
|
+
projection: projection,
|
|
65
|
+
limit: limit ?? DEFAULT_FIND_LIMIT,
|
|
66
|
+
sort: sort,
|
|
67
|
+
maxTimeMS: OP_MAX_TIME_MS,
|
|
68
|
+
});
|
|
69
|
+
const docs = await cursor.toArray();
|
|
70
|
+
return textResult({
|
|
71
|
+
env: conn.getActiveEnv(),
|
|
72
|
+
database,
|
|
73
|
+
collection,
|
|
74
|
+
count: docs.length,
|
|
75
|
+
limit: limit ?? DEFAULT_FIND_LIMIT,
|
|
76
|
+
documents: docs,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
return errorResult(`find failed: ${err.message}`);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
server.tool("aggregate", "Run an aggregation pipeline. Result capped at 500 documents.", {
|
|
84
|
+
database: NonEmptyString,
|
|
85
|
+
collection: NonEmptyString,
|
|
86
|
+
pipeline: PipelineSchema,
|
|
87
|
+
}, async ({ database, collection, pipeline }) => {
|
|
88
|
+
try {
|
|
89
|
+
// Reject write-capable stages ($out, $merge) before sending to the server.
|
|
90
|
+
// These bypass the write tools' confirmation flow entirely.
|
|
91
|
+
for (const stage of pipeline) {
|
|
92
|
+
for (const key of Object.keys(stage)) {
|
|
93
|
+
if (DISALLOWED_AGG_STAGES.includes(key)) {
|
|
94
|
+
return errorResult(`aggregate refused: stage '${key}' performs writes and is blocked in all environments. ` +
|
|
95
|
+
`Use the dedicated write tools (insert/update/delete) with confirmed=true if needed.`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const client = await conn.getClient();
|
|
100
|
+
const col = client.db(database).collection(collection);
|
|
101
|
+
const docs = await col
|
|
102
|
+
.aggregate(pipeline, { maxTimeMS: OP_MAX_TIME_MS })
|
|
103
|
+
.limit(MAX_AGG_RESULTS)
|
|
104
|
+
.toArray();
|
|
105
|
+
return textResult({
|
|
106
|
+
env: conn.getActiveEnv(),
|
|
107
|
+
database,
|
|
108
|
+
collection,
|
|
109
|
+
count: docs.length,
|
|
110
|
+
capped: docs.length >= MAX_AGG_RESULTS,
|
|
111
|
+
documents: docs,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
return errorResult(`aggregate failed: ${err.message}`);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
server.tool("count", "Count documents. With no filter uses estimatedDocumentCount (fast, metadata-based). With a filter uses countDocuments (exact but slower).", {
|
|
119
|
+
database: NonEmptyString,
|
|
120
|
+
collection: NonEmptyString,
|
|
121
|
+
filter: FilterSchema,
|
|
122
|
+
}, async ({ database, collection, filter }) => {
|
|
123
|
+
try {
|
|
124
|
+
const client = await conn.getClient();
|
|
125
|
+
const col = client.db(database).collection(collection);
|
|
126
|
+
const hasFilter = filter && Object.keys(filter).length > 0;
|
|
127
|
+
const n = hasFilter
|
|
128
|
+
? await col.countDocuments(filter, {
|
|
129
|
+
maxTimeMS: OP_MAX_TIME_MS,
|
|
130
|
+
})
|
|
131
|
+
: await col.estimatedDocumentCount({ maxTimeMS: OP_MAX_TIME_MS });
|
|
132
|
+
return textResult({
|
|
133
|
+
env: conn.getActiveEnv(),
|
|
134
|
+
database,
|
|
135
|
+
collection,
|
|
136
|
+
count: n,
|
|
137
|
+
estimated: !hasFilter,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
return errorResult(`count failed: ${err.message}`);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=read.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/tools/read.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { textResult, errorResult, NonEmptyString } from "./environment.js";
|
|
3
|
+
const DocumentSchema = z.record(z.unknown());
|
|
4
|
+
const FilterSchema = z.record(z.unknown());
|
|
5
|
+
const UpdateSchema = z.record(z.unknown());
|
|
6
|
+
/**
|
|
7
|
+
* Wrap a write handler so that, when the active env is `prd` and `confirmed`
|
|
8
|
+
* isn't explicitly true, we return a structured confirmation-required response
|
|
9
|
+
* instead of executing.
|
|
10
|
+
*
|
|
11
|
+
* Pass `alwaysConfirm: true` for extra-dangerous ops (e.g. drop_collection)
|
|
12
|
+
* which require confirmation in every environment.
|
|
13
|
+
*/
|
|
14
|
+
function withProdGuard(conn, opName, describe, execute, alwaysConfirm = false) {
|
|
15
|
+
return async (args) => {
|
|
16
|
+
const needsConfirm = alwaysConfirm || conn.isProduction();
|
|
17
|
+
if (needsConfirm && !args.confirmed) {
|
|
18
|
+
return textResult({
|
|
19
|
+
confirmationRequired: true,
|
|
20
|
+
operation: opName,
|
|
21
|
+
environment: conn.getActiveEnv(),
|
|
22
|
+
environmentName: conn.getActiveEnvName(),
|
|
23
|
+
host: conn.getActiveHost(),
|
|
24
|
+
reason: alwaysConfirm
|
|
25
|
+
? `${opName} is destructive and requires explicit confirmation in every environment.`
|
|
26
|
+
: `Active environment is production. Re-invoke with confirmed=true to execute.`,
|
|
27
|
+
plannedExecution: describe(args),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const result = await execute(args);
|
|
32
|
+
return textResult({
|
|
33
|
+
env: conn.getActiveEnv(),
|
|
34
|
+
operation: opName,
|
|
35
|
+
...result,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
return errorResult(`${opName} failed: ${err.message}`);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function registerWriteTools(server, conn) {
|
|
44
|
+
// insert_one
|
|
45
|
+
server.tool("insert_one", "Insert one document. In prd, requires confirmed=true.", {
|
|
46
|
+
database: NonEmptyString,
|
|
47
|
+
collection: NonEmptyString,
|
|
48
|
+
document: DocumentSchema,
|
|
49
|
+
confirmed: z.boolean().optional(),
|
|
50
|
+
}, withProdGuard(conn, "insert_one", (a) => ({
|
|
51
|
+
database: a.database,
|
|
52
|
+
collection: a.collection,
|
|
53
|
+
document: a.document,
|
|
54
|
+
}), async (a) => {
|
|
55
|
+
const client = await conn.getClient();
|
|
56
|
+
const r = await client
|
|
57
|
+
.db(a.database)
|
|
58
|
+
.collection(a.collection)
|
|
59
|
+
.insertOne(a.document);
|
|
60
|
+
return { acknowledged: r.acknowledged, insertedId: r.insertedId };
|
|
61
|
+
}));
|
|
62
|
+
// insert_many
|
|
63
|
+
server.tool("insert_many", "Insert multiple documents. In prd, requires confirmed=true.", {
|
|
64
|
+
database: NonEmptyString,
|
|
65
|
+
collection: NonEmptyString,
|
|
66
|
+
documents: z.array(DocumentSchema).min(1),
|
|
67
|
+
confirmed: z.boolean().optional(),
|
|
68
|
+
}, withProdGuard(conn, "insert_many", (a) => ({
|
|
69
|
+
database: a.database,
|
|
70
|
+
collection: a.collection,
|
|
71
|
+
documentCount: a.documents.length,
|
|
72
|
+
firstDocumentPreview: a.documents[0],
|
|
73
|
+
}), async (a) => {
|
|
74
|
+
const client = await conn.getClient();
|
|
75
|
+
const r = await client
|
|
76
|
+
.db(a.database)
|
|
77
|
+
.collection(a.collection)
|
|
78
|
+
.insertMany(a.documents);
|
|
79
|
+
return {
|
|
80
|
+
acknowledged: r.acknowledged,
|
|
81
|
+
insertedCount: r.insertedCount,
|
|
82
|
+
insertedIds: r.insertedIds,
|
|
83
|
+
};
|
|
84
|
+
}));
|
|
85
|
+
// update_one
|
|
86
|
+
server.tool("update_one", "Update one matching document. In prd, requires confirmed=true.", {
|
|
87
|
+
database: NonEmptyString,
|
|
88
|
+
collection: NonEmptyString,
|
|
89
|
+
filter: FilterSchema,
|
|
90
|
+
update: UpdateSchema,
|
|
91
|
+
upsert: z.boolean().optional(),
|
|
92
|
+
confirmed: z.boolean().optional(),
|
|
93
|
+
}, withProdGuard(conn, "update_one", (a) => ({
|
|
94
|
+
database: a.database,
|
|
95
|
+
collection: a.collection,
|
|
96
|
+
filter: a.filter,
|
|
97
|
+
update: a.update,
|
|
98
|
+
upsert: a.upsert ?? false,
|
|
99
|
+
}), async (a) => {
|
|
100
|
+
const client = await conn.getClient();
|
|
101
|
+
const r = await client
|
|
102
|
+
.db(a.database)
|
|
103
|
+
.collection(a.collection)
|
|
104
|
+
.updateOne(a.filter, a.update, { upsert: a.upsert ?? false });
|
|
105
|
+
return {
|
|
106
|
+
acknowledged: r.acknowledged,
|
|
107
|
+
matchedCount: r.matchedCount,
|
|
108
|
+
modifiedCount: r.modifiedCount,
|
|
109
|
+
upsertedCount: r.upsertedCount,
|
|
110
|
+
upsertedId: r.upsertedId,
|
|
111
|
+
};
|
|
112
|
+
}));
|
|
113
|
+
// update_many
|
|
114
|
+
server.tool("update_many", "Update all matching documents. In prd, requires confirmed=true.", {
|
|
115
|
+
database: NonEmptyString,
|
|
116
|
+
collection: NonEmptyString,
|
|
117
|
+
filter: FilterSchema,
|
|
118
|
+
update: UpdateSchema,
|
|
119
|
+
confirmed: z.boolean().optional(),
|
|
120
|
+
}, withProdGuard(conn, "update_many", (a) => ({
|
|
121
|
+
database: a.database,
|
|
122
|
+
collection: a.collection,
|
|
123
|
+
filter: a.filter,
|
|
124
|
+
update: a.update,
|
|
125
|
+
}), async (a) => {
|
|
126
|
+
const client = await conn.getClient();
|
|
127
|
+
const r = await client
|
|
128
|
+
.db(a.database)
|
|
129
|
+
.collection(a.collection)
|
|
130
|
+
.updateMany(a.filter, a.update);
|
|
131
|
+
return {
|
|
132
|
+
acknowledged: r.acknowledged,
|
|
133
|
+
matchedCount: r.matchedCount,
|
|
134
|
+
modifiedCount: r.modifiedCount,
|
|
135
|
+
};
|
|
136
|
+
}));
|
|
137
|
+
// delete_one
|
|
138
|
+
server.tool("delete_one", "Delete one matching document. In prd, requires confirmed=true.", {
|
|
139
|
+
database: NonEmptyString,
|
|
140
|
+
collection: NonEmptyString,
|
|
141
|
+
filter: FilterSchema,
|
|
142
|
+
confirmed: z.boolean().optional(),
|
|
143
|
+
}, withProdGuard(conn, "delete_one", (a) => ({
|
|
144
|
+
database: a.database,
|
|
145
|
+
collection: a.collection,
|
|
146
|
+
filter: a.filter,
|
|
147
|
+
}), async (a) => {
|
|
148
|
+
const client = await conn.getClient();
|
|
149
|
+
const r = await client
|
|
150
|
+
.db(a.database)
|
|
151
|
+
.collection(a.collection)
|
|
152
|
+
.deleteOne(a.filter);
|
|
153
|
+
return { acknowledged: r.acknowledged, deletedCount: r.deletedCount };
|
|
154
|
+
}));
|
|
155
|
+
// delete_many
|
|
156
|
+
server.tool("delete_many", "Delete all matching documents. In prd, requires confirmed=true.", {
|
|
157
|
+
database: NonEmptyString,
|
|
158
|
+
collection: NonEmptyString,
|
|
159
|
+
filter: FilterSchema,
|
|
160
|
+
confirmed: z.boolean().optional(),
|
|
161
|
+
}, withProdGuard(conn, "delete_many", (a) => ({
|
|
162
|
+
database: a.database,
|
|
163
|
+
collection: a.collection,
|
|
164
|
+
filter: a.filter,
|
|
165
|
+
}), async (a) => {
|
|
166
|
+
const client = await conn.getClient();
|
|
167
|
+
const r = await client
|
|
168
|
+
.db(a.database)
|
|
169
|
+
.collection(a.collection)
|
|
170
|
+
.deleteMany(a.filter);
|
|
171
|
+
return { acknowledged: r.acknowledged, deletedCount: r.deletedCount };
|
|
172
|
+
}));
|
|
173
|
+
// drop_collection — ALWAYS requires confirmation
|
|
174
|
+
server.tool("drop_collection", "Drop an entire collection. Requires confirmed=true in EVERY environment (dev/stg/prd).", {
|
|
175
|
+
database: NonEmptyString,
|
|
176
|
+
collection: NonEmptyString,
|
|
177
|
+
confirmed: z.boolean().optional(),
|
|
178
|
+
}, withProdGuard(conn, "drop_collection", (a) => ({ database: a.database, collection: a.collection }), async (a) => {
|
|
179
|
+
const client = await conn.getClient();
|
|
180
|
+
const dropped = await client
|
|
181
|
+
.db(a.database)
|
|
182
|
+
.collection(a.collection)
|
|
183
|
+
.drop();
|
|
184
|
+
return { dropped };
|
|
185
|
+
},
|
|
186
|
+
/* alwaysConfirm */ true));
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write.js","sourceRoot":"","sources":["../../src/tools/write.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mpurdon/mcp-mongodb",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Local stdio MCP server for MongoDB with multi-environment support and production write protection.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mongodb": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"model-context-protocol",
|
|
19
|
+
"mongodb",
|
|
20
|
+
"claude"
|
|
21
|
+
],
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": "Matthew Purdon",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/mpurdon/mcp-servers.git",
|
|
27
|
+
"directory": "packages/mongodb"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
31
|
+
"mongodb": "^6.10.0",
|
|
32
|
+
"zod": "^3.23.8"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^22.10.2",
|
|
36
|
+
"eslint": "^9.17.0",
|
|
37
|
+
"tsx": "^4.19.2",
|
|
38
|
+
"typescript": "^5.7.2",
|
|
39
|
+
"@mpurdon/tsconfig": "0.0.0",
|
|
40
|
+
"@mpurdon/eslint-config": "0.0.0"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=20"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsc",
|
|
47
|
+
"dev": "tsx src/index.ts",
|
|
48
|
+
"start": "node dist/index.js",
|
|
49
|
+
"typecheck": "tsc --noEmit",
|
|
50
|
+
"lint": "eslint ."
|
|
51
|
+
}
|
|
52
|
+
}
|