@munlib/mongodb-readonly-mcp 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/README.md +323 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +436 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
# MongoDB Read-Only MCP
|
|
2
|
+
|
|
3
|
+
Servidor MCP en Node.js/TypeScript para consultar una o varias conexiones MongoDB desde OpenCode sin exponer herramientas de escritura.
|
|
4
|
+
|
|
5
|
+
## Capacidades
|
|
6
|
+
|
|
7
|
+
- Lista conexiones configuradas sin mostrar credenciales.
|
|
8
|
+
- Lista bases de datos y colecciones.
|
|
9
|
+
- Ejecuta `find`, `count`, `distinct` y pipelines `aggregate` en modo lectura.
|
|
10
|
+
- Busca un valor en varias colecciones de una base cuando no sabes dónde está la información.
|
|
11
|
+
- Bloquea etapas de agregación de escritura o administración como `$out`, `$merge`, `$currentOp` y `$listSessions`.
|
|
12
|
+
- Limita resultados y tiempo de consulta para evitar respuestas enormes o consultas colgadas.
|
|
13
|
+
|
|
14
|
+
## Requisitos
|
|
15
|
+
|
|
16
|
+
- Node.js 20 o superior.
|
|
17
|
+
- Acceso de red a MongoDB.
|
|
18
|
+
- Un usuario MongoDB idealmente con permisos de solo lectura (`read` o `readAnyDatabase`).
|
|
19
|
+
|
|
20
|
+
## Uso con NPX
|
|
21
|
+
|
|
22
|
+
Una vez publicado en npm, se puede consumir sin clonar el repositorio:
|
|
23
|
+
|
|
24
|
+
```powershell
|
|
25
|
+
npx -y @munlib/mongodb-readonly-mcp
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Instalación Para Desarrollo
|
|
29
|
+
|
|
30
|
+
```powershell
|
|
31
|
+
npm install
|
|
32
|
+
npm run build
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Configuración Segura
|
|
36
|
+
|
|
37
|
+
No guardes credenciales reales en el repositorio. Usa variables de entorno desde OpenCode, tu terminal o un gestor de secretos.
|
|
38
|
+
|
|
39
|
+
### Una Conexión
|
|
40
|
+
|
|
41
|
+
```powershell
|
|
42
|
+
$env:MONGO_NAME = "mongoKubernet"
|
|
43
|
+
$env:MONGO_URI = "mongodb://usuario:password@192.168.xx.xx:27017/admin?authSource=admin"
|
|
44
|
+
npm start
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Varias Conexiones
|
|
48
|
+
|
|
49
|
+
```powershell
|
|
50
|
+
$env:MONGO_CONNECTIONS = '[{"name":"mongoKubernet","uri":"mongodb://usuario:password@192.168.xx.xx:27017/admin?authSource=admin"},{"name":"local","uri":"mongodb://localhost:27017/admin"}]'
|
|
51
|
+
npm start
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
También puedes usar una URL larga como la de Studio 3T siempre que sea una URI MongoDB válida:
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
mongodb://admin:password@192.168.xx.xx:27017/admin?retryWrites=true&loadBalanced=false&serverSelectionTimeoutMS=5000&connectTimeoutMS=10000&authSource=admin&authMechanism=SCRAM-SHA-1
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Variables Disponibles
|
|
61
|
+
|
|
62
|
+
| Variable | Descripción | Default |
|
|
63
|
+
| --- | --- | --- |
|
|
64
|
+
| `MONGO_CONNECTIONS` | JSON con múltiples conexiones: `[{"name":"...","uri":"..."}]`. | - |
|
|
65
|
+
| `MONGO_URI` | URI de una sola conexión. | - |
|
|
66
|
+
| `MONGO_NAME` | Nombre lógico para `MONGO_URI`. | `default` |
|
|
67
|
+
| `MONGO_DEFAULT_LIMIT` | Límite por defecto de documentos devueltos. | `25` |
|
|
68
|
+
| `MONGO_MAX_LIMIT` | Límite máximo permitido. | `100` |
|
|
69
|
+
| `MONGO_QUERY_TIMEOUT_MS` | Timeout por consulta. | `15000` |
|
|
70
|
+
| `MONGO_SEARCH_COLLECTION_LIMIT` | Máximo de colecciones escaneadas por búsqueda global. | `50` |
|
|
71
|
+
| `MONGO_SEARCH_FIELDS_LIMIT` | Máximo de campos detectados por colección para búsqueda global. | `30` |
|
|
72
|
+
|
|
73
|
+
## Configuración en OpenCode
|
|
74
|
+
|
|
75
|
+
Agrega un MCP local en tu `opencode.json` o `opencode.jsonc`. El campo `command` debe ser un arreglo.
|
|
76
|
+
|
|
77
|
+
### Usando NPM
|
|
78
|
+
|
|
79
|
+
Esta es la forma recomendada para otros equipos o máquinas:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"$schema": "https://opencode.ai/config.json",
|
|
84
|
+
"mcp": {
|
|
85
|
+
"mongodb-readonly": {
|
|
86
|
+
"type": "local",
|
|
87
|
+
"enabled": true,
|
|
88
|
+
"command": ["npx", "-y", "@munlib/mongodb-readonly-mcp"],
|
|
89
|
+
"env": {
|
|
90
|
+
"MONGO_CONNECTIONS": "[{\"name\":\"mongoKubernet\",\"uri\":\"mongodb://usuario:password@192.168.xx.xx:27017/admin?authSource=admin\"}]",
|
|
91
|
+
"MONGO_DEFAULT_LIMIT": "25",
|
|
92
|
+
"MONGO_MAX_LIMIT": "100",
|
|
93
|
+
"MONGO_QUERY_TIMEOUT_MS": "15000",
|
|
94
|
+
"MONGO_SEARCH_COLLECTION_LIMIT": "50",
|
|
95
|
+
"MONGO_SEARCH_FIELDS_LIMIT": "30"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Después de cambiar `opencode.json`, reinicia OpenCode para que cargue el MCP.
|
|
103
|
+
|
|
104
|
+
### Usando Build Local
|
|
105
|
+
|
|
106
|
+
Para desarrollo local también puedes apuntar al archivo compilado:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"$schema": "https://opencode.ai/config.json",
|
|
111
|
+
"mcp": {
|
|
112
|
+
"mongodb-readonly": {
|
|
113
|
+
"type": "local",
|
|
114
|
+
"enabled": true,
|
|
115
|
+
"command": ["node", "C:/ruta/al/proyecto/dist/index.js"],
|
|
116
|
+
"env": {
|
|
117
|
+
"MONGO_CONNECTIONS": "[{\"name\":\"mongoKubernet\",\"uri\":\"mongodb://usuario:password@192.168.xx.xx:27017/admin?authSource=admin\"}]"
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Herramientas MCP
|
|
125
|
+
|
|
126
|
+
| Herramienta | Uso |
|
|
127
|
+
| --- | --- |
|
|
128
|
+
| `mongo_list_connections` | Ver nombres de conexiones configuradas. |
|
|
129
|
+
| `mongo_list_databases` | Ver bases disponibles en una conexión. |
|
|
130
|
+
| `mongo_list_collections` | Ver colecciones de una base. |
|
|
131
|
+
| `mongo_find_documents` | Consultar documentos con filtro, proyección, orden, límite y skip. |
|
|
132
|
+
| `mongo_count_documents` | Contar documentos por filtro. |
|
|
133
|
+
| `mongo_distinct_values` | Obtener valores distintos de un campo. |
|
|
134
|
+
| `mongo_aggregate_readonly` | Ejecutar agregaciones de lectura. |
|
|
135
|
+
| `mongo_search_database` | Buscar un valor en varias colecciones de una base. |
|
|
136
|
+
|
|
137
|
+
## Ejemplos de Preguntas en OpenCode
|
|
138
|
+
|
|
139
|
+
- "Lista las bases de datos disponibles en `mongoKubernet`."
|
|
140
|
+
- "En la base `apiLogs`, lista las colecciones."
|
|
141
|
+
- "Busca el id `64f...` en toda la base `backoffice`."
|
|
142
|
+
- "En `ecommerce_mercados_prod.orders`, encuentra documentos donde `customer.email` sea `correo@dominio.com`."
|
|
143
|
+
- "Cuenta documentos en `logs.requests` para el endpoint `/api/login`."
|
|
144
|
+
|
|
145
|
+
## Garantías y Límites de Solo Lectura
|
|
146
|
+
|
|
147
|
+
Este MCP no expone operaciones `insert`, `update`, `delete`, `drop`, `create`, `renameCollection` ni comandos arbitrarios. Para agregaciones, bloquea etapas conocidas que escriben o exponen información administrativa.
|
|
148
|
+
|
|
149
|
+
La recomendación fuerte es usar además un usuario MongoDB con permisos de solo lectura. La seguridad real debe aplicarse en la base de datos, no solo en el MCP.
|
|
150
|
+
|
|
151
|
+
## Publicación en NPM
|
|
152
|
+
|
|
153
|
+
El paquete está preparado para publicarse como:
|
|
154
|
+
|
|
155
|
+
```text
|
|
156
|
+
@munlib/mongodb-readonly-mcp
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Antes de publicar, verifica que no existan credenciales en archivos versionados. Los scripts ejecutan `build`, `typecheck`, `npm pack --dry-run` y piden confirmación antes de publicar.
|
|
160
|
+
|
|
161
|
+
### Windows PowerShell
|
|
162
|
+
|
|
163
|
+
Modo guiado:
|
|
164
|
+
|
|
165
|
+
```powershell
|
|
166
|
+
npm run publish:npm
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Solo validar sin publicar:
|
|
170
|
+
|
|
171
|
+
```powershell
|
|
172
|
+
npm run publish:dry-run
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Publicar una actualización patch:
|
|
176
|
+
|
|
177
|
+
```powershell
|
|
178
|
+
pwsh -File scripts/publish-npm.ps1 -Bump patch
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Si npm exige 2FA al publicar, el script pedirá el código OTP. También puedes pasarlo directamente:
|
|
182
|
+
|
|
183
|
+
```powershell
|
|
184
|
+
pwsh -File scripts/publish-npm.ps1 -Bump none -Otp 123456
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
También puedes publicar usando un Access Token de npm sin guardar credenciales en el proyecto:
|
|
188
|
+
|
|
189
|
+
```powershell
|
|
190
|
+
$env:NPM_TOKEN = "npm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
191
|
+
pwsh -File scripts/publish-npm.ps1 -Bump none
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Linux o macOS
|
|
195
|
+
|
|
196
|
+
Modo guiado:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
npm run publish:npm:bash
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
En el modo guiado puedes escribir el número de la opción o el nombre directamente, por ejemplo `1`, `none`, `patch`, `minor`, `major` o `prerelease`.
|
|
203
|
+
|
|
204
|
+
Solo validar sin publicar:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
npm run publish:npm:bash -- --dry-run
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Solo validar sin publicar y sin pedir login:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
npm run publish:npm:bash -- --bump none --dry-run --skip-login
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Primera publicación sin cambiar la versión:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
npm run publish:npm:bash -- --bump none
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Publicar una actualización patch:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
npm run publish:npm:bash -- --bump patch
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Si npm exige 2FA al publicar, el script pedirá el código OTP. También puedes pasarlo directamente:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
npm run publish:npm:bash -- --bump none --otp 123456
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
También puedes publicar usando un Access Token de npm sin guardar credenciales en el proyecto:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
export NPM_TOKEN="npm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
238
|
+
npm run publish:npm:bash -- --bump none
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Comprobación Manual Rápida
|
|
242
|
+
|
|
243
|
+
Si solo quieres revisar el paquete sin iniciar sesión ni publicar:
|
|
244
|
+
|
|
245
|
+
```powershell
|
|
246
|
+
npm run publish:check
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Login y Credenciales
|
|
250
|
+
|
|
251
|
+
Los scripts no guardan credenciales. Si no hay sesión activa, ejecutan:
|
|
252
|
+
|
|
253
|
+
```powershell
|
|
254
|
+
npm login
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
`npm login` pedirá usuario, contraseña y 2FA si tu cuenta lo tiene activado, o abrirá el navegador según la configuración actual de npm.
|
|
258
|
+
|
|
259
|
+
Si al publicar aparece `E403 Two-factor authentication or granular access token with bypass 2fa enabled is required`, significa que npm requiere un código 2FA para esa publicación. Vuelve a ejecutar el script y escribe el código OTP actual de tu app autenticadora cuando lo solicite.
|
|
260
|
+
|
|
261
|
+
También puedes usar un token granular de npm con permiso de publicación y `bypass 2FA` habilitado, pero para uso manual es más simple usar el OTP.
|
|
262
|
+
|
|
263
|
+
### Publicar con Access Token de NPM
|
|
264
|
+
|
|
265
|
+
Sí se puede publicar directamente con un Access Token. Es la opción recomendada si tu cuenta u organización exige 2FA y el OTP manual falla.
|
|
266
|
+
|
|
267
|
+
En npm crea un token desde `Access Tokens`. Para publicar paquetes necesitas un token con permisos de publicación sobre `@munlib/mongodb-readonly-mcp` o sobre la organización `munlib`.
|
|
268
|
+
|
|
269
|
+
Recomendaciones para el token:
|
|
270
|
+
|
|
271
|
+
- Usa un token granular si npm te da esa opción.
|
|
272
|
+
- Dale permiso de lectura/escritura solo al paquete u organización necesaria.
|
|
273
|
+
- Habilita `bypass 2FA` si npm lo solicita para publicaciones automatizadas.
|
|
274
|
+
- No pegues el token en `README.md`, `.env`, `opencode.jsonc` ni commits.
|
|
275
|
+
- Si se filtra, revócalo inmediatamente desde npm.
|
|
276
|
+
|
|
277
|
+
Los scripts leen el token desde `NPM_TOKEN`, crean un `.npmrc` temporal fuera del proyecto, publican con ese archivo y lo eliminan al terminar.
|
|
278
|
+
|
|
279
|
+
Windows PowerShell:
|
|
280
|
+
|
|
281
|
+
```powershell
|
|
282
|
+
$env:NPM_TOKEN = "npm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
283
|
+
npm run publish:npm
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Linux o macOS:
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
export NPM_TOKEN="npm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
290
|
+
npm run publish:npm:bash -- --bump none
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Para probar sin publicar:
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
export NPM_TOKEN="npm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
297
|
+
npm run publish:npm:bash -- --bump none --dry-run
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Si no quieres dejar el token en la sesión después de publicar, elimínalo:
|
|
301
|
+
|
|
302
|
+
```powershell
|
|
303
|
+
Remove-Item Env:NPM_TOKEN
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
unset NPM_TOKEN
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Versiones
|
|
311
|
+
|
|
312
|
+
Para primera publicación, selecciona `none` cuando el script pregunte el tipo de publicación.
|
|
313
|
+
|
|
314
|
+
Para actualizaciones, usa:
|
|
315
|
+
|
|
316
|
+
```powershell
|
|
317
|
+
patch # 0.1.0 -> 0.1.1
|
|
318
|
+
minor # 0.1.0 -> 0.2.0
|
|
319
|
+
major # 0.1.0 -> 1.0.0
|
|
320
|
+
prerelease # 0.1.0 -> 0.1.1-0
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Los scripts usan `npm version --no-git-tag-version`, por lo que actualizan `package.json` y `package-lock.json`, pero no crean commits ni tags automáticamente.
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "dotenv/config";
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { MongoClient, ObjectId } from "mongodb";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
const forbiddenAggregationStages = new Set([
|
|
8
|
+
"$out",
|
|
9
|
+
"$merge",
|
|
10
|
+
"$documents",
|
|
11
|
+
"$currentOp",
|
|
12
|
+
"$listSessions",
|
|
13
|
+
"$listLocalSessions",
|
|
14
|
+
]);
|
|
15
|
+
const jsonObjectSchema = z.record(z.unknown()).default({});
|
|
16
|
+
const config = loadRuntimeConfig();
|
|
17
|
+
const connections = new Map();
|
|
18
|
+
const server = new McpServer({
|
|
19
|
+
name: "mongodb-readonly-mcp",
|
|
20
|
+
version: "0.1.0",
|
|
21
|
+
});
|
|
22
|
+
server.registerTool("mongo_list_connections", {
|
|
23
|
+
title: "List MongoDB connections",
|
|
24
|
+
description: "Lists configured MongoDB connections without exposing credentials.",
|
|
25
|
+
inputSchema: {},
|
|
26
|
+
}, async () => asText({ connections: config.connections.map(({ name }) => ({ name })) }));
|
|
27
|
+
server.registerTool("mongo_list_databases", {
|
|
28
|
+
title: "List MongoDB databases",
|
|
29
|
+
description: "Lists databases available in a configured MongoDB connection.",
|
|
30
|
+
inputSchema: {
|
|
31
|
+
connection: z.string().optional().describe("Connection name. Optional when only one connection is configured."),
|
|
32
|
+
},
|
|
33
|
+
}, async ({ connection }) => {
|
|
34
|
+
const { client, config: selected } = getConnection(connection);
|
|
35
|
+
const result = await client.db("admin").admin().listDatabases({ nameOnly: false });
|
|
36
|
+
return asText({
|
|
37
|
+
connection: selected.name,
|
|
38
|
+
databases: result.databases.map((database) => ({
|
|
39
|
+
name: database.name,
|
|
40
|
+
sizeOnDisk: database.sizeOnDisk,
|
|
41
|
+
empty: database.empty,
|
|
42
|
+
})),
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
server.registerTool("mongo_list_collections", {
|
|
46
|
+
title: "List MongoDB collections",
|
|
47
|
+
description: "Lists collections from a database.",
|
|
48
|
+
inputSchema: {
|
|
49
|
+
connection: z.string().optional().describe("Connection name. Optional when only one connection is configured."),
|
|
50
|
+
database: z.string().min(1).describe("Database name."),
|
|
51
|
+
},
|
|
52
|
+
}, async ({ connection, database }) => {
|
|
53
|
+
const { client, config: selected } = getConnection(connection);
|
|
54
|
+
const collections = await client.db(database).listCollections({}, { nameOnly: false }).toArray();
|
|
55
|
+
return asText({
|
|
56
|
+
connection: selected.name,
|
|
57
|
+
database,
|
|
58
|
+
collections: collections.map((collection) => ({
|
|
59
|
+
name: collection.name,
|
|
60
|
+
type: collection.type,
|
|
61
|
+
})),
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
server.registerTool("mongo_find_documents", {
|
|
65
|
+
title: "Find MongoDB documents",
|
|
66
|
+
description: "Runs a read-only find query on a collection. Accepts JSON filter, projection and sort objects.",
|
|
67
|
+
inputSchema: {
|
|
68
|
+
connection: z.string().optional().describe("Connection name. Optional when only one connection is configured."),
|
|
69
|
+
database: z.string().min(1),
|
|
70
|
+
collection: z.string().min(1),
|
|
71
|
+
filter: jsonObjectSchema.describe("MongoDB filter object. Example: {\"_id\": \"65...\"}"),
|
|
72
|
+
projection: jsonObjectSchema.optional().describe("MongoDB projection object."),
|
|
73
|
+
sort: jsonObjectSchema.optional().describe("MongoDB sort object."),
|
|
74
|
+
limit: z.number().int().positive().optional().describe("Maximum documents to return."),
|
|
75
|
+
skip: z.number().int().nonnegative().optional().describe("Documents to skip."),
|
|
76
|
+
},
|
|
77
|
+
}, async ({ connection, database, collection, filter, projection, sort, limit, skip }) => {
|
|
78
|
+
const { client, config: selected } = getConnection(connection);
|
|
79
|
+
const safeLimit = normalizeLimit(limit);
|
|
80
|
+
const parsedFilter = normalizeFilter(filter ?? {});
|
|
81
|
+
const options = {
|
|
82
|
+
limit: safeLimit,
|
|
83
|
+
maxTimeMS: config.queryTimeoutMs,
|
|
84
|
+
...(projection ? { projection: projection } : {}),
|
|
85
|
+
...(sort ? { sort: sort } : {}),
|
|
86
|
+
...(skip ? { skip } : {}),
|
|
87
|
+
};
|
|
88
|
+
const docs = await client.db(database).collection(collection).find(parsedFilter, options).toArray();
|
|
89
|
+
return asText({
|
|
90
|
+
connection: selected.name,
|
|
91
|
+
database,
|
|
92
|
+
collection,
|
|
93
|
+
count: docs.length,
|
|
94
|
+
limit: safeLimit,
|
|
95
|
+
documents: sanitizeMongoValues(docs),
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
server.registerTool("mongo_count_documents", {
|
|
99
|
+
title: "Count MongoDB documents",
|
|
100
|
+
description: "Counts documents matching a read-only filter in a collection.",
|
|
101
|
+
inputSchema: {
|
|
102
|
+
connection: z.string().optional().describe("Connection name. Optional when only one connection is configured."),
|
|
103
|
+
database: z.string().min(1),
|
|
104
|
+
collection: z.string().min(1),
|
|
105
|
+
filter: jsonObjectSchema.describe("MongoDB filter object."),
|
|
106
|
+
},
|
|
107
|
+
}, async ({ connection, database, collection, filter }) => {
|
|
108
|
+
const { client, config: selected } = getConnection(connection);
|
|
109
|
+
const count = await client
|
|
110
|
+
.db(database)
|
|
111
|
+
.collection(collection)
|
|
112
|
+
.countDocuments(normalizeFilter(filter ?? {}), { maxTimeMS: config.queryTimeoutMs });
|
|
113
|
+
return asText({ connection: selected.name, database, collection, count });
|
|
114
|
+
});
|
|
115
|
+
server.registerTool("mongo_distinct_values", {
|
|
116
|
+
title: "Distinct MongoDB values",
|
|
117
|
+
description: "Returns distinct values for a field using an optional read-only filter.",
|
|
118
|
+
inputSchema: {
|
|
119
|
+
connection: z.string().optional().describe("Connection name. Optional when only one connection is configured."),
|
|
120
|
+
database: z.string().min(1),
|
|
121
|
+
collection: z.string().min(1),
|
|
122
|
+
field: z.string().min(1),
|
|
123
|
+
filter: jsonObjectSchema.describe("MongoDB filter object."),
|
|
124
|
+
limit: z.number().int().positive().optional().describe("Maximum values to return."),
|
|
125
|
+
},
|
|
126
|
+
}, async ({ connection, database, collection, field, filter, limit }) => {
|
|
127
|
+
const { client, config: selected } = getConnection(connection);
|
|
128
|
+
const safeLimit = normalizeLimit(limit);
|
|
129
|
+
const values = await client
|
|
130
|
+
.db(database)
|
|
131
|
+
.collection(collection)
|
|
132
|
+
.distinct(field, normalizeFilter(filter ?? {}), { maxTimeMS: config.queryTimeoutMs });
|
|
133
|
+
return asText({
|
|
134
|
+
connection: selected.name,
|
|
135
|
+
database,
|
|
136
|
+
collection,
|
|
137
|
+
field,
|
|
138
|
+
count: values.length,
|
|
139
|
+
limit: safeLimit,
|
|
140
|
+
values: sanitizeMongoValues(values.slice(0, safeLimit)),
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
server.registerTool("mongo_aggregate_readonly", {
|
|
144
|
+
title: "Aggregate MongoDB documents read-only",
|
|
145
|
+
description: "Runs a read-only aggregation pipeline. Write or administrative stages such as $out and $merge are blocked.",
|
|
146
|
+
inputSchema: {
|
|
147
|
+
connection: z.string().optional().describe("Connection name. Optional when only one connection is configured."),
|
|
148
|
+
database: z.string().min(1),
|
|
149
|
+
collection: z.string().min(1),
|
|
150
|
+
pipeline: z.array(z.record(z.unknown())).describe("Aggregation pipeline."),
|
|
151
|
+
limit: z.number().int().positive().optional().describe("Maximum documents to return. A final $limit is added automatically."),
|
|
152
|
+
},
|
|
153
|
+
}, async ({ connection, database, collection, pipeline, limit }) => {
|
|
154
|
+
assertReadonlyPipeline(pipeline);
|
|
155
|
+
const { client, config: selected } = getConnection(connection);
|
|
156
|
+
const safeLimit = normalizeLimit(limit);
|
|
157
|
+
const limitedPipeline = [...pipeline, { $limit: safeLimit }];
|
|
158
|
+
const docs = await client
|
|
159
|
+
.db(database)
|
|
160
|
+
.collection(collection)
|
|
161
|
+
.aggregate(limitedPipeline, { maxTimeMS: config.queryTimeoutMs, allowDiskUse: false })
|
|
162
|
+
.toArray();
|
|
163
|
+
return asText({
|
|
164
|
+
connection: selected.name,
|
|
165
|
+
database,
|
|
166
|
+
collection,
|
|
167
|
+
count: docs.length,
|
|
168
|
+
limit: safeLimit,
|
|
169
|
+
documents: sanitizeMongoValues(docs),
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
server.registerTool("mongo_search_database", {
|
|
173
|
+
title: "Search MongoDB database",
|
|
174
|
+
description: "Searches across collections in one database for an ObjectId, exact scalar value, or text in string fields. Useful for investigation when the target collection is unknown.",
|
|
175
|
+
inputSchema: {
|
|
176
|
+
connection: z.string().optional().describe("Connection name. Optional when only one connection is configured."),
|
|
177
|
+
database: z.string().min(1),
|
|
178
|
+
query: z.string().min(1).describe("ObjectId, id, email, text fragment, or any scalar value to search."),
|
|
179
|
+
collections: z.array(z.string().min(1)).optional().describe("Optional collection names. If omitted, collections are discovered."),
|
|
180
|
+
limitPerCollection: z.number().int().positive().optional().describe("Maximum matching documents per collection."),
|
|
181
|
+
},
|
|
182
|
+
}, async ({ connection, database, query, collections: requestedCollections, limitPerCollection }) => {
|
|
183
|
+
const { client, config: selected } = getConnection(connection);
|
|
184
|
+
const db = client.db(database);
|
|
185
|
+
const safeLimit = normalizeLimit(limitPerCollection);
|
|
186
|
+
const collections = requestedCollections?.length
|
|
187
|
+
? requestedCollections.slice(0, config.searchCollectionLimit)
|
|
188
|
+
: (await db.listCollections({}, { nameOnly: true }).toArray())
|
|
189
|
+
.map((collection) => collection.name)
|
|
190
|
+
.slice(0, config.searchCollectionLimit);
|
|
191
|
+
const results = [];
|
|
192
|
+
for (const collectionName of collections) {
|
|
193
|
+
const collection = db.collection(collectionName);
|
|
194
|
+
const fields = await discoverSearchableFields(collection, config.searchFieldsLimit);
|
|
195
|
+
const filter = buildSearchFilter(query, fields);
|
|
196
|
+
const documents = await collection.find(filter, { limit: safeLimit, maxTimeMS: config.queryTimeoutMs }).toArray();
|
|
197
|
+
if (documents.length > 0) {
|
|
198
|
+
results.push({
|
|
199
|
+
collection: collectionName,
|
|
200
|
+
count: documents.length,
|
|
201
|
+
searchedFields: fields,
|
|
202
|
+
documents: sanitizeMongoValues(documents),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return asText({
|
|
207
|
+
connection: selected.name,
|
|
208
|
+
database,
|
|
209
|
+
query,
|
|
210
|
+
searchedCollections: collections.length,
|
|
211
|
+
limitPerCollection: safeLimit,
|
|
212
|
+
results,
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
async function main() {
|
|
216
|
+
for (const connection of config.connections) {
|
|
217
|
+
const client = new MongoClient(connection.uri, {
|
|
218
|
+
appName: "mongodb-readonly-mcp",
|
|
219
|
+
serverSelectionTimeoutMS: config.queryTimeoutMs,
|
|
220
|
+
connectTimeoutMS: config.queryTimeoutMs,
|
|
221
|
+
});
|
|
222
|
+
await client.connect();
|
|
223
|
+
connections.set(connection.name, { config: connection, client });
|
|
224
|
+
}
|
|
225
|
+
const transport = new StdioServerTransport();
|
|
226
|
+
await server.connect(transport);
|
|
227
|
+
}
|
|
228
|
+
function loadRuntimeConfig() {
|
|
229
|
+
const connections = loadConnections();
|
|
230
|
+
return {
|
|
231
|
+
connections,
|
|
232
|
+
defaultLimit: readPositiveInteger("MONGO_DEFAULT_LIMIT", 25),
|
|
233
|
+
maxLimit: readPositiveInteger("MONGO_MAX_LIMIT", 100),
|
|
234
|
+
queryTimeoutMs: readPositiveInteger("MONGO_QUERY_TIMEOUT_MS", 15_000),
|
|
235
|
+
searchCollectionLimit: readPositiveInteger("MONGO_SEARCH_COLLECTION_LIMIT", 50),
|
|
236
|
+
searchFieldsLimit: readPositiveInteger("MONGO_SEARCH_FIELDS_LIMIT", 30),
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function loadConnections() {
|
|
240
|
+
const rawConnections = process.env.MONGO_CONNECTIONS;
|
|
241
|
+
if (rawConnections) {
|
|
242
|
+
const parsed = JSON.parse(rawConnections);
|
|
243
|
+
const connections = z
|
|
244
|
+
.array(z.object({
|
|
245
|
+
name: z.string().min(1),
|
|
246
|
+
uri: z.string().min(1).startsWith("mongodb"),
|
|
247
|
+
}))
|
|
248
|
+
.min(1)
|
|
249
|
+
.parse(parsed);
|
|
250
|
+
ensureUniqueConnectionNames(connections);
|
|
251
|
+
return connections;
|
|
252
|
+
}
|
|
253
|
+
const uri = process.env.MONGO_URI;
|
|
254
|
+
if (uri) {
|
|
255
|
+
return [{ name: process.env.MONGO_NAME || "default", uri }];
|
|
256
|
+
}
|
|
257
|
+
throw new Error("Configure MONGO_CONNECTIONS or MONGO_URI before starting the MCP server.");
|
|
258
|
+
}
|
|
259
|
+
function ensureUniqueConnectionNames(connections) {
|
|
260
|
+
const seen = new Set();
|
|
261
|
+
for (const connection of connections) {
|
|
262
|
+
if (seen.has(connection.name)) {
|
|
263
|
+
throw new Error(`Duplicate MongoDB connection name: ${connection.name}`);
|
|
264
|
+
}
|
|
265
|
+
seen.add(connection.name);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function readPositiveInteger(name, fallback) {
|
|
269
|
+
const value = process.env[name];
|
|
270
|
+
if (!value)
|
|
271
|
+
return fallback;
|
|
272
|
+
const parsed = Number(value);
|
|
273
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
274
|
+
throw new Error(`${name} must be a positive integer.`);
|
|
275
|
+
}
|
|
276
|
+
return parsed;
|
|
277
|
+
}
|
|
278
|
+
function getConnection(name) {
|
|
279
|
+
if (name) {
|
|
280
|
+
const connection = connections.get(name);
|
|
281
|
+
if (!connection) {
|
|
282
|
+
throw new Error(`Unknown MongoDB connection: ${name}`);
|
|
283
|
+
}
|
|
284
|
+
return connection;
|
|
285
|
+
}
|
|
286
|
+
if (connections.size === 1) {
|
|
287
|
+
const connection = connections.values().next().value;
|
|
288
|
+
if (connection)
|
|
289
|
+
return connection;
|
|
290
|
+
}
|
|
291
|
+
throw new Error("Connection name is required because multiple MongoDB connections are configured.");
|
|
292
|
+
}
|
|
293
|
+
function normalizeLimit(limit) {
|
|
294
|
+
return Math.min(limit ?? config.defaultLimit, config.maxLimit);
|
|
295
|
+
}
|
|
296
|
+
function normalizeFilter(filter) {
|
|
297
|
+
return convertObjectIds(filter);
|
|
298
|
+
}
|
|
299
|
+
function convertObjectIds(value) {
|
|
300
|
+
if (Array.isArray(value)) {
|
|
301
|
+
return value.map(convertObjectIds);
|
|
302
|
+
}
|
|
303
|
+
if (!value || typeof value !== "object") {
|
|
304
|
+
return value;
|
|
305
|
+
}
|
|
306
|
+
const result = {};
|
|
307
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
308
|
+
if ((key === "_id" || key.endsWith("Id") || key.endsWith("_id")) && typeof raw === "string" && ObjectId.isValid(raw)) {
|
|
309
|
+
result[key] = new ObjectId(raw);
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
result[key] = convertObjectIds(raw);
|
|
313
|
+
}
|
|
314
|
+
return result;
|
|
315
|
+
}
|
|
316
|
+
function assertReadonlyPipeline(pipeline) {
|
|
317
|
+
for (const stage of pipeline) {
|
|
318
|
+
for (const stageName of Object.keys(stage)) {
|
|
319
|
+
if (forbiddenAggregationStages.has(stageName)) {
|
|
320
|
+
throw new Error(`Aggregation stage ${stageName} is not allowed in read-only mode.`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
assertNoForbiddenNestedStage(stage);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
function assertNoForbiddenNestedStage(value) {
|
|
327
|
+
if (Array.isArray(value)) {
|
|
328
|
+
value.forEach(assertNoForbiddenNestedStage);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (!value || typeof value !== "object")
|
|
332
|
+
return;
|
|
333
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
334
|
+
if (forbiddenAggregationStages.has(key)) {
|
|
335
|
+
throw new Error(`Aggregation stage ${key} is not allowed in read-only mode.`);
|
|
336
|
+
}
|
|
337
|
+
assertNoForbiddenNestedStage(nestedValue);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
async function discoverSearchableFields(collection, limit) {
|
|
341
|
+
const sample = await collection.find({}, { limit: 5, maxTimeMS: config.queryTimeoutMs }).toArray();
|
|
342
|
+
const fields = new Set(["_id"]);
|
|
343
|
+
for (const document of sample) {
|
|
344
|
+
collectScalarFields(document, "", fields, limit);
|
|
345
|
+
if (fields.size >= limit)
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
return [...fields].slice(0, limit);
|
|
349
|
+
}
|
|
350
|
+
function collectScalarFields(value, prefix, fields, limit) {
|
|
351
|
+
if (fields.size >= limit || !value || typeof value !== "object" || Array.isArray(value))
|
|
352
|
+
return;
|
|
353
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
354
|
+
if (fields.size >= limit)
|
|
355
|
+
return;
|
|
356
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
357
|
+
if (nestedValue === null || ["string", "number", "boolean"].includes(typeof nestedValue) || nestedValue instanceof Date || nestedValue instanceof ObjectId) {
|
|
358
|
+
fields.add(path);
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
collectScalarFields(nestedValue, path, fields, limit);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
function buildSearchFilter(query, fields) {
|
|
365
|
+
const or = [];
|
|
366
|
+
if (ObjectId.isValid(query)) {
|
|
367
|
+
or.push({ _id: new ObjectId(query) });
|
|
368
|
+
}
|
|
369
|
+
or.push({ _id: query });
|
|
370
|
+
const numeric = Number(query);
|
|
371
|
+
const hasNumericValue = query.trim() !== "" && Number.isFinite(numeric);
|
|
372
|
+
const escaped = escapeRegex(query);
|
|
373
|
+
for (const field of fields.filter((field) => field !== "_id")) {
|
|
374
|
+
or.push({ [field]: query });
|
|
375
|
+
or.push({ [field]: { $regex: escaped, $options: "i" } });
|
|
376
|
+
if (hasNumericValue) {
|
|
377
|
+
or.push({ [field]: numeric });
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return { $or: or };
|
|
381
|
+
}
|
|
382
|
+
function escapeRegex(value) {
|
|
383
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
384
|
+
}
|
|
385
|
+
function sanitizeMongoValues(value) {
|
|
386
|
+
if (value === null || ["boolean", "number", "string"].includes(typeof value)) {
|
|
387
|
+
return value;
|
|
388
|
+
}
|
|
389
|
+
if (value instanceof ObjectId) {
|
|
390
|
+
return value.toHexString();
|
|
391
|
+
}
|
|
392
|
+
if (value instanceof Date) {
|
|
393
|
+
return value.toISOString();
|
|
394
|
+
}
|
|
395
|
+
if (Array.isArray(value)) {
|
|
396
|
+
return value.map(sanitizeMongoValues);
|
|
397
|
+
}
|
|
398
|
+
if (value && typeof value === "object") {
|
|
399
|
+
const result = {};
|
|
400
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
401
|
+
result[key] = sanitizeMongoValues(nestedValue);
|
|
402
|
+
}
|
|
403
|
+
return result;
|
|
404
|
+
}
|
|
405
|
+
return String(value);
|
|
406
|
+
}
|
|
407
|
+
function asText(value) {
|
|
408
|
+
return {
|
|
409
|
+
content: [
|
|
410
|
+
{
|
|
411
|
+
type: "text",
|
|
412
|
+
text: JSON.stringify(value, null, 2),
|
|
413
|
+
},
|
|
414
|
+
],
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
process.on("SIGINT", async () => {
|
|
418
|
+
await closeConnections();
|
|
419
|
+
process.exit(0);
|
|
420
|
+
});
|
|
421
|
+
process.on("SIGTERM", async () => {
|
|
422
|
+
await closeConnections();
|
|
423
|
+
process.exit(0);
|
|
424
|
+
});
|
|
425
|
+
async function closeConnections() {
|
|
426
|
+
await Promise.all([...connections.values()].map(({ client }) => client.close(true)));
|
|
427
|
+
}
|
|
428
|
+
main().catch((error) => {
|
|
429
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
430
|
+
console.error(redactMongoUris(message));
|
|
431
|
+
process.exit(1);
|
|
432
|
+
});
|
|
433
|
+
function redactMongoUris(message) {
|
|
434
|
+
return message.replace(/mongodb(?:\+srv)?:\/\/[^\s"']+/gi, "mongodb://<redacted>");
|
|
435
|
+
}
|
|
436
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@munlib/mongodb-readonly-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Read-only MCP server for querying one or more MongoDB connections.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mongodb-readonly-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc -p tsconfig.json",
|
|
11
|
+
"dev": "tsx src/index.ts",
|
|
12
|
+
"publish:check": "npm run build && npm run typecheck && npm pack --dry-run",
|
|
13
|
+
"publish:dry-run": "pwsh -File scripts/publish-npm.ps1 -Bump none -DryRun -SkipLogin",
|
|
14
|
+
"publish:npm": "pwsh -File scripts/publish-npm.ps1",
|
|
15
|
+
"publish:npm:bash": "bash scripts/publish-npm.sh",
|
|
16
|
+
"prepublishOnly": "npm run build && npm run typecheck",
|
|
17
|
+
"start": "node dist/index.js",
|
|
18
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"README.md",
|
|
23
|
+
"package.json"
|
|
24
|
+
],
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"mcp",
|
|
30
|
+
"mongodb",
|
|
31
|
+
"readonly",
|
|
32
|
+
"model-context-protocol"
|
|
33
|
+
],
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=20"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@modelcontextprotocol/sdk": "^1.17.5",
|
|
40
|
+
"dotenv": "^16.4.7",
|
|
41
|
+
"mongodb": "^6.12.0",
|
|
42
|
+
"zod": "^3.24.1"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^22.10.7",
|
|
46
|
+
"tsx": "^4.19.2",
|
|
47
|
+
"typescript": "^5.7.3"
|
|
48
|
+
}
|
|
49
|
+
}
|