mongodb-mcp-server 0.1.2 → 0.2.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.
Files changed (270) hide show
  1. package/.github/pull_request_template.md +5 -0
  2. package/.github/workflows/accuracy-tests.yml +55 -0
  3. package/.github/workflows/check.yml +1 -1
  4. package/.github/workflows/code_health.yaml +4 -4
  5. package/.github/workflows/code_health_fork.yaml +0 -14
  6. package/.github/workflows/dependabot_pr.yaml +26 -0
  7. package/.github/workflows/docker.yaml +1 -1
  8. package/.github/workflows/jira-issue.yml +72 -0
  9. package/.smithery/smithery.yaml +10 -0
  10. package/.vscode/extensions.json +1 -1
  11. package/.vscode/launch.json +11 -1
  12. package/.vscode/settings.json +1 -11
  13. package/Dockerfile +1 -0
  14. package/README.md +132 -31
  15. package/dist/common/atlas/accessListUtils.js +36 -0
  16. package/dist/common/atlas/accessListUtils.js.map +1 -0
  17. package/dist/common/atlas/apiClient.js +25 -6
  18. package/dist/common/atlas/apiClient.js.map +1 -1
  19. package/dist/common/atlas/cluster.js +1 -1
  20. package/dist/common/atlas/cluster.js.map +1 -1
  21. package/dist/{config.js → common/config.js} +8 -1
  22. package/dist/common/config.js.map +1 -0
  23. package/dist/{errors.js → common/errors.js} +1 -0
  24. package/dist/common/errors.js.map +1 -0
  25. package/dist/{logger.js → common/logger.js} +20 -19
  26. package/dist/common/logger.js.map +1 -0
  27. package/dist/common/managedTimeout.js +20 -0
  28. package/dist/common/managedTimeout.js.map +1 -0
  29. package/dist/common/packageInfo.js.map +1 -0
  30. package/dist/{session.js → common/session.js} +20 -21
  31. package/dist/common/session.js.map +1 -0
  32. package/dist/common/sessionStore.js +73 -0
  33. package/dist/common/sessionStore.js.map +1 -0
  34. package/dist/helpers/container.js +28 -0
  35. package/dist/helpers/container.js.map +1 -0
  36. package/dist/helpers/generatePassword.js.map +1 -0
  37. package/dist/helpers/indexCheck.js +63 -0
  38. package/dist/helpers/indexCheck.js.map +1 -0
  39. package/dist/index.js +30 -37
  40. package/dist/index.js.map +1 -1
  41. package/dist/server.js +44 -7
  42. package/dist/server.js.map +1 -1
  43. package/dist/telemetry/constants.js +1 -1
  44. package/dist/telemetry/constants.js.map +1 -1
  45. package/dist/telemetry/telemetry.js +86 -116
  46. package/dist/telemetry/telemetry.js.map +1 -1
  47. package/dist/tools/atlas/atlasTool.js +3 -3
  48. package/dist/tools/atlas/atlasTool.js.map +1 -1
  49. package/dist/tools/atlas/connect/connectCluster.js +198 -0
  50. package/dist/tools/atlas/connect/connectCluster.js.map +1 -0
  51. package/dist/tools/atlas/create/createAccessList.js +9 -10
  52. package/dist/tools/atlas/create/createAccessList.js.map +1 -1
  53. package/dist/tools/atlas/create/createDBUser.js +3 -1
  54. package/dist/tools/atlas/create/createDBUser.js.map +1 -1
  55. package/dist/tools/atlas/create/createFreeCluster.js +2 -0
  56. package/dist/tools/atlas/create/createFreeCluster.js.map +1 -1
  57. package/dist/tools/atlas/create/createProject.js.map +1 -1
  58. package/dist/tools/atlas/read/inspectAccessList.js.map +1 -1
  59. package/dist/tools/atlas/read/inspectCluster.js.map +1 -1
  60. package/dist/tools/atlas/read/listAlerts.js.map +1 -1
  61. package/dist/tools/atlas/read/listClusters.js.map +1 -1
  62. package/dist/tools/atlas/read/listDBUsers.js.map +1 -1
  63. package/dist/tools/atlas/read/listOrgs.js.map +1 -1
  64. package/dist/tools/atlas/read/listProjects.js.map +1 -1
  65. package/dist/tools/atlas/tools.js +1 -1
  66. package/dist/tools/atlas/tools.js.map +1 -1
  67. package/dist/tools/mongodb/{metadata → connect}/connect.js +7 -4
  68. package/dist/tools/mongodb/connect/connect.js.map +1 -0
  69. package/dist/tools/mongodb/create/createCollection.js.map +1 -1
  70. package/dist/tools/mongodb/create/createIndex.js +1 -1
  71. package/dist/tools/mongodb/create/createIndex.js.map +1 -1
  72. package/dist/tools/mongodb/create/insertMany.js +1 -1
  73. package/dist/tools/mongodb/create/insertMany.js.map +1 -1
  74. package/dist/tools/mongodb/delete/deleteMany.js +20 -1
  75. package/dist/tools/mongodb/delete/deleteMany.js.map +1 -1
  76. package/dist/tools/mongodb/delete/dropCollection.js.map +1 -1
  77. package/dist/tools/mongodb/delete/dropDatabase.js.map +1 -1
  78. package/dist/tools/mongodb/metadata/collectionSchema.js.map +1 -1
  79. package/dist/tools/mongodb/metadata/collectionStorageSize.js.map +1 -1
  80. package/dist/tools/mongodb/metadata/dbStats.js.map +1 -1
  81. package/dist/tools/mongodb/metadata/explain.js +2 -2
  82. package/dist/tools/mongodb/metadata/explain.js.map +1 -1
  83. package/dist/tools/mongodb/metadata/listCollections.js.map +1 -1
  84. package/dist/tools/mongodb/metadata/listDatabases.js.map +1 -1
  85. package/dist/tools/mongodb/metadata/logs.js.map +1 -1
  86. package/dist/tools/mongodb/mongodbTool.js +47 -10
  87. package/dist/tools/mongodb/mongodbTool.js.map +1 -1
  88. package/dist/tools/mongodb/read/aggregate.js +10 -1
  89. package/dist/tools/mongodb/read/aggregate.js.map +1 -1
  90. package/dist/tools/mongodb/read/collectionIndexes.js.map +1 -1
  91. package/dist/tools/mongodb/read/count.js +15 -1
  92. package/dist/tools/mongodb/read/count.js.map +1 -1
  93. package/dist/tools/mongodb/read/find.js +14 -4
  94. package/dist/tools/mongodb/read/find.js.map +1 -1
  95. package/dist/tools/mongodb/tools.js +1 -1
  96. package/dist/tools/mongodb/tools.js.map +1 -1
  97. package/dist/tools/mongodb/update/renameCollection.js.map +1 -1
  98. package/dist/tools/mongodb/update/updateMany.js +24 -2
  99. package/dist/tools/mongodb/update/updateMany.js.map +1 -1
  100. package/dist/tools/tool.js +12 -9
  101. package/dist/tools/tool.js.map +1 -1
  102. package/dist/transports/base.js +26 -0
  103. package/dist/transports/base.js.map +1 -0
  104. package/dist/{helpers/EJsonTransport.js → transports/stdio.js} +24 -2
  105. package/dist/transports/stdio.js.map +1 -0
  106. package/dist/transports/streamableHttp.js +140 -0
  107. package/dist/transports/streamableHttp.js.map +1 -0
  108. package/eslint.config.js +13 -4
  109. package/package.json +43 -33
  110. package/resources/test-summary-template.html +415 -0
  111. package/scripts/accuracy/generateTestSummary.ts +335 -0
  112. package/scripts/accuracy/runAccuracyTests.sh +45 -0
  113. package/scripts/accuracy/updateAccuracyRunStatus.ts +21 -0
  114. package/src/common/atlas/accessListUtils.ts +54 -0
  115. package/src/common/atlas/apiClient.ts +25 -6
  116. package/src/common/atlas/cluster.ts +1 -1
  117. package/src/{config.ts → common/config.ts} +16 -2
  118. package/src/{errors.ts → common/errors.ts} +1 -0
  119. package/src/{logger.ts → common/logger.ts} +21 -24
  120. package/src/common/managedTimeout.ts +27 -0
  121. package/src/{session.ts → common/session.ts} +24 -26
  122. package/src/common/sessionStore.ts +111 -0
  123. package/src/helpers/container.ts +35 -0
  124. package/src/helpers/indexCheck.ts +83 -0
  125. package/src/index.ts +30 -40
  126. package/src/server.ts +55 -11
  127. package/src/telemetry/constants.ts +1 -1
  128. package/src/telemetry/telemetry.ts +109 -153
  129. package/src/telemetry/types.ts +2 -1
  130. package/src/tools/atlas/atlasTool.ts +4 -4
  131. package/src/tools/atlas/connect/connectCluster.ts +259 -0
  132. package/src/tools/atlas/create/createAccessList.ts +15 -13
  133. package/src/tools/atlas/create/createDBUser.ts +5 -3
  134. package/src/tools/atlas/create/createFreeCluster.ts +4 -2
  135. package/src/tools/atlas/create/createProject.ts +2 -2
  136. package/src/tools/atlas/read/inspectAccessList.ts +2 -2
  137. package/src/tools/atlas/read/inspectCluster.ts +2 -2
  138. package/src/tools/atlas/read/listAlerts.ts +2 -2
  139. package/src/tools/atlas/read/listClusters.ts +2 -2
  140. package/src/tools/atlas/read/listDBUsers.ts +2 -2
  141. package/src/tools/atlas/read/listOrgs.ts +2 -2
  142. package/src/tools/atlas/read/listProjects.ts +2 -2
  143. package/src/tools/atlas/tools.ts +1 -1
  144. package/src/tools/mongodb/{metadata → connect}/connect.ts +12 -9
  145. package/src/tools/mongodb/create/createCollection.ts +2 -2
  146. package/src/tools/mongodb/create/createIndex.ts +3 -3
  147. package/src/tools/mongodb/create/insertMany.ts +3 -3
  148. package/src/tools/mongodb/delete/deleteMany.ts +24 -3
  149. package/src/tools/mongodb/delete/dropCollection.ts +2 -2
  150. package/src/tools/mongodb/delete/dropDatabase.ts +2 -2
  151. package/src/tools/mongodb/metadata/collectionSchema.ts +2 -2
  152. package/src/tools/mongodb/metadata/collectionStorageSize.ts +2 -2
  153. package/src/tools/mongodb/metadata/dbStats.ts +2 -2
  154. package/src/tools/mongodb/metadata/explain.ts +4 -4
  155. package/src/tools/mongodb/metadata/listCollections.ts +2 -2
  156. package/src/tools/mongodb/metadata/listDatabases.ts +2 -2
  157. package/src/tools/mongodb/metadata/logs.ts +2 -2
  158. package/src/tools/mongodb/mongodbTool.ts +60 -14
  159. package/src/tools/mongodb/read/aggregate.ts +14 -3
  160. package/src/tools/mongodb/read/collectionIndexes.ts +2 -2
  161. package/src/tools/mongodb/read/count.ts +19 -3
  162. package/src/tools/mongodb/read/find.ts +20 -6
  163. package/src/tools/mongodb/tools.ts +1 -1
  164. package/src/tools/mongodb/update/renameCollection.ts +2 -2
  165. package/src/tools/mongodb/update/updateMany.ts +28 -4
  166. package/src/tools/tool.ts +23 -18
  167. package/src/transports/base.ts +34 -0
  168. package/src/{helpers/EJsonTransport.ts → transports/stdio.ts} +30 -1
  169. package/src/transports/streamableHttp.ts +178 -0
  170. package/tests/accuracy/aggregate.test.ts +27 -0
  171. package/tests/accuracy/collectionIndexes.test.ts +40 -0
  172. package/tests/accuracy/collectionSchema.test.ts +28 -0
  173. package/tests/accuracy/collectionStorageSize.test.ts +41 -0
  174. package/tests/accuracy/count.test.ts +44 -0
  175. package/tests/accuracy/createCollection.test.ts +46 -0
  176. package/tests/accuracy/createIndex.test.ts +37 -0
  177. package/tests/accuracy/dbStats.test.ts +15 -0
  178. package/tests/accuracy/deleteMany.test.ts +44 -0
  179. package/tests/accuracy/dropCollection.test.ts +74 -0
  180. package/tests/accuracy/dropDatabase.test.ts +41 -0
  181. package/tests/accuracy/explain.test.ts +73 -0
  182. package/tests/accuracy/find.test.ts +114 -0
  183. package/tests/accuracy/insertMany.test.ts +48 -0
  184. package/tests/accuracy/listCollections.test.ts +60 -0
  185. package/tests/accuracy/listDatabases.test.ts +31 -0
  186. package/tests/accuracy/logs.test.ts +28 -0
  187. package/tests/accuracy/renameCollection.test.ts +31 -0
  188. package/tests/accuracy/sdk/accuracyResultStorage/diskStorage.ts +189 -0
  189. package/tests/accuracy/sdk/accuracyResultStorage/getAccuracyResultStorage.ts +11 -0
  190. package/tests/accuracy/sdk/accuracyResultStorage/mongodbStorage.ts +151 -0
  191. package/tests/accuracy/sdk/accuracyResultStorage/resultStorage.ts +117 -0
  192. package/tests/accuracy/sdk/accuracyScorer.ts +93 -0
  193. package/tests/accuracy/sdk/accuracyTestingClient.ts +94 -0
  194. package/tests/accuracy/sdk/agent.ts +56 -0
  195. package/tests/accuracy/sdk/constants.ts +26 -0
  196. package/tests/accuracy/sdk/describeAccuracyTests.ts +126 -0
  197. package/tests/accuracy/sdk/gitInfo.ts +7 -0
  198. package/tests/accuracy/sdk/matcher.ts +193 -0
  199. package/tests/accuracy/sdk/models.ts +95 -0
  200. package/tests/accuracy/test-data-dumps/comics.books.json +417 -0
  201. package/tests/accuracy/test-data-dumps/comics.characters.json +402 -0
  202. package/tests/accuracy/test-data-dumps/mflix.movies.json +496 -0
  203. package/tests/accuracy/test-data-dumps/mflix.shows.json +572 -0
  204. package/tests/accuracy/updateMany.test.ts +42 -0
  205. package/tests/integration/helpers.ts +9 -9
  206. package/tests/integration/indexCheck.test.ts +464 -0
  207. package/tests/integration/server.test.ts +6 -4
  208. package/tests/integration/telemetry.test.ts +29 -0
  209. package/tests/integration/tools/atlas/accessLists.test.ts +22 -2
  210. package/tests/integration/tools/atlas/alerts.test.ts +3 -2
  211. package/tests/integration/tools/atlas/atlasHelpers.ts +3 -0
  212. package/tests/integration/tools/atlas/clusters.test.ts +68 -16
  213. package/tests/integration/tools/atlas/dbUsers.test.ts +14 -1
  214. package/tests/integration/tools/atlas/orgs.test.ts +2 -1
  215. package/tests/integration/tools/atlas/projects.test.ts +4 -3
  216. package/tests/integration/tools/mongodb/{metadata → connect}/connect.test.ts +34 -3
  217. package/tests/integration/tools/mongodb/create/createCollection.test.ts +1 -0
  218. package/tests/integration/tools/mongodb/create/createIndex.test.ts +1 -0
  219. package/tests/integration/tools/mongodb/create/insertMany.test.ts +1 -0
  220. package/tests/integration/tools/mongodb/delete/deleteMany.test.ts +1 -0
  221. package/tests/integration/tools/mongodb/delete/dropCollection.test.ts +1 -1
  222. package/tests/integration/tools/mongodb/delete/dropDatabase.test.ts +1 -0
  223. package/tests/integration/tools/mongodb/metadata/collectionSchema.test.ts +1 -0
  224. package/tests/integration/tools/mongodb/metadata/collectionStorageSize.test.ts +1 -0
  225. package/tests/integration/tools/mongodb/metadata/dbStats.test.ts +1 -0
  226. package/tests/integration/tools/mongodb/metadata/explain.test.ts +1 -0
  227. package/tests/integration/tools/mongodb/metadata/listCollections.test.ts +1 -0
  228. package/tests/integration/tools/mongodb/metadata/listDatabases.test.ts +3 -2
  229. package/tests/integration/tools/mongodb/metadata/logs.test.ts +1 -0
  230. package/tests/integration/tools/mongodb/mongodbHelpers.ts +67 -2
  231. package/tests/integration/tools/mongodb/read/aggregate.test.ts +2 -1
  232. package/tests/integration/tools/mongodb/read/collectionIndexes.test.ts +1 -0
  233. package/tests/integration/tools/mongodb/read/count.test.ts +1 -0
  234. package/tests/integration/tools/mongodb/read/find.test.ts +2 -1
  235. package/tests/integration/tools/mongodb/update/renameCollection.test.ts +1 -0
  236. package/tests/integration/tools/mongodb/update/updateMany.test.ts +1 -0
  237. package/tests/integration/transports/stdio.test.ts +40 -0
  238. package/tests/integration/transports/streamableHttp.test.ts +56 -0
  239. package/tests/matchers/toIncludeSameMembers.test.ts +59 -0
  240. package/tests/matchers/toIncludeSameMembers.ts +12 -0
  241. package/tests/setup.ts +7 -0
  242. package/tests/unit/accessListUtils.test.ts +39 -0
  243. package/tests/unit/accuracyScorer.test.ts +390 -0
  244. package/tests/unit/{apiClient.test.ts → common/apiClient.test.ts} +15 -15
  245. package/tests/unit/common/managedTimeout.test.ts +67 -0
  246. package/tests/unit/{session.test.ts → common/session.test.ts} +7 -12
  247. package/tests/unit/helpers/indexCheck.test.ts +150 -0
  248. package/tests/unit/telemetry.test.ts +99 -137
  249. package/tests/unit/{EJsonTransport.test.ts → transports/stdio.test.ts} +4 -4
  250. package/tests/vitest.d.ts +11 -0
  251. package/tsconfig.json +0 -1
  252. package/{tsconfig.jest.json → tsconfig.test.json} +1 -2
  253. package/vitest.config.ts +41 -0
  254. package/dist/common/atlas/generatePassword.js.map +0 -1
  255. package/dist/config.js.map +0 -1
  256. package/dist/errors.js.map +0 -1
  257. package/dist/helpers/EJsonTransport.js.map +0 -1
  258. package/dist/helpers/packageInfo.js.map +0 -1
  259. package/dist/logger.js.map +0 -1
  260. package/dist/session.js.map +0 -1
  261. package/dist/tools/atlas/metadata/connectCluster.js +0 -100
  262. package/dist/tools/atlas/metadata/connectCluster.js.map +0 -1
  263. package/dist/tools/mongodb/metadata/connect.js.map +0 -1
  264. package/global.d.ts +0 -1
  265. package/jest.config.cjs +0 -22
  266. package/src/tools/atlas/metadata/connectCluster.ts +0 -121
  267. /package/dist/{helpers → common}/packageInfo.js +0 -0
  268. /package/dist/{common/atlas → helpers}/generatePassword.js +0 -0
  269. /package/src/{helpers → common}/packageInfo.ts +0 -0
  270. /package/src/{common/atlas → helpers}/generatePassword.ts +0 -0
@@ -12,11 +12,17 @@ export const LogId = {
12
12
  serverCloseRequested: mongoLogId(1_000_003),
13
13
  serverClosed: mongoLogId(1_000_004),
14
14
  serverCloseFailure: mongoLogId(1_000_005),
15
+ serverDuplicateLoggers: mongoLogId(1_000_006),
15
16
 
16
17
  atlasCheckCredentials: mongoLogId(1_001_001),
17
18
  atlasDeleteDatabaseUserFailure: mongoLogId(1_001_002),
18
19
  atlasConnectFailure: mongoLogId(1_001_003),
19
20
  atlasInspectFailure: mongoLogId(1_001_004),
21
+ atlasConnectAttempt: mongoLogId(1_001_005),
22
+ atlasConnectSucceeded: mongoLogId(1_001_006),
23
+ atlasApiRevokeFailure: mongoLogId(1_001_007),
24
+ atlasIpAccessListAdded: mongoLogId(1_001_008),
25
+ atlasIpAccessListAddFailure: mongoLogId(1_001_009),
20
26
 
21
27
  telemetryDisabled: mongoLogId(1_002_001),
22
28
  telemetryEmitFailure: mongoLogId(1_002_002),
@@ -25,7 +31,6 @@ export const LogId = {
25
31
  telemetryMetadataError: mongoLogId(1_002_005),
26
32
  telemetryDeviceIdFailure: mongoLogId(1_002_006),
27
33
  telemetryDeviceIdTimeout: mongoLogId(1_002_007),
28
- telemetryContainerEnvFailure: mongoLogId(1_002_008),
29
34
 
30
35
  toolExecute: mongoLogId(1_003_001),
31
36
  toolExecuteFailure: mongoLogId(1_003_002),
@@ -35,9 +40,16 @@ export const LogId = {
35
40
  mongodbDisconnectFailure: mongoLogId(1_004_002),
36
41
 
37
42
  toolUpdateFailure: mongoLogId(1_005_001),
43
+
44
+ streamableHttpTransportStarted: mongoLogId(1_006_001),
45
+ streamableHttpTransportSessionCloseFailure: mongoLogId(1_006_002),
46
+ streamableHttpTransportSessionCloseNotification: mongoLogId(1_006_003),
47
+ streamableHttpTransportSessionCloseNotificationFailure: mongoLogId(1_006_004),
48
+ streamableHttpTransportRequestFailure: mongoLogId(1_006_005),
49
+ streamableHttpTransportCloseFailure: mongoLogId(1_006_006),
38
50
  } as const;
39
51
 
40
- abstract class LoggerBase {
52
+ export abstract class LoggerBase {
41
53
  abstract log(level: LogLevel, id: MongoLogId, context: string, message: string): void;
42
54
 
43
55
  info(id: MongoLogId, context: string, message: string): void {
@@ -72,14 +84,14 @@ abstract class LoggerBase {
72
84
  }
73
85
  }
74
86
 
75
- class ConsoleLogger extends LoggerBase {
87
+ export class ConsoleLogger extends LoggerBase {
76
88
  log(level: LogLevel, id: MongoLogId, context: string, message: string): void {
77
89
  message = redact(message);
78
- console.error(`[${level.toUpperCase()}] ${id.__value} - ${context}: ${message}`);
90
+ console.error(`[${level.toUpperCase()}] ${id.__value} - ${context}: ${message} (${process.pid})`);
79
91
  }
80
92
  }
81
93
 
82
- class DiskLogger extends LoggerBase {
94
+ export class DiskLogger extends LoggerBase {
83
95
  private constructor(private logWriter: MongoLogWriter) {
84
96
  super();
85
97
  }
@@ -131,7 +143,7 @@ class DiskLogger extends LoggerBase {
131
143
  }
132
144
  }
133
145
 
134
- class McpLogger extends LoggerBase {
146
+ export class McpLogger extends LoggerBase {
135
147
  constructor(private server: McpServer) {
136
148
  super();
137
149
  }
@@ -150,18 +162,12 @@ class McpLogger extends LoggerBase {
150
162
  }
151
163
 
152
164
  class CompositeLogger extends LoggerBase {
153
- private loggers: LoggerBase[];
165
+ private loggers: LoggerBase[] = [];
154
166
 
155
167
  constructor(...loggers: LoggerBase[]) {
156
168
  super();
157
169
 
158
- if (loggers.length === 0) {
159
- // default to ConsoleLogger
160
- this.loggers = [new ConsoleLogger()];
161
- return;
162
- }
163
-
164
- this.loggers = [...loggers];
170
+ this.setLoggers(...loggers);
165
171
  }
166
172
 
167
173
  setLoggers(...loggers: LoggerBase[]): void {
@@ -178,14 +184,5 @@ class CompositeLogger extends LoggerBase {
178
184
  }
179
185
  }
180
186
 
181
- const logger = new CompositeLogger();
187
+ const logger = new CompositeLogger(new ConsoleLogger());
182
188
  export default logger;
183
-
184
- export async function initializeLogger(server: McpServer, logPath: string): Promise<LoggerBase> {
185
- const diskLogger = await DiskLogger.fromPath(logPath);
186
- const mcpLogger = new McpLogger(server);
187
-
188
- logger.setLoggers(mcpLogger, diskLogger);
189
-
190
- return logger;
191
- }
@@ -0,0 +1,27 @@
1
+ export interface ManagedTimeout {
2
+ cancel: () => void;
3
+ restart: () => void;
4
+ }
5
+
6
+ export function setManagedTimeout(callback: () => Promise<void> | void, timeoutMS: number): ManagedTimeout {
7
+ let timeoutId: NodeJS.Timeout | undefined = setTimeout(() => {
8
+ void callback();
9
+ }, timeoutMS);
10
+
11
+ function cancel() {
12
+ clearTimeout(timeoutId);
13
+ timeoutId = undefined;
14
+ }
15
+
16
+ function restart() {
17
+ cancel();
18
+ timeoutId = setTimeout(() => {
19
+ void callback();
20
+ }, timeoutMS);
21
+ }
22
+
23
+ return {
24
+ cancel,
25
+ restart,
26
+ };
27
+ }
@@ -1,11 +1,11 @@
1
1
  import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver";
2
- import { ApiClient, ApiClientCredentials } from "./common/atlas/apiClient.js";
2
+ import { ApiClient, ApiClientCredentials } from "./atlas/apiClient.js";
3
3
  import { Implementation } from "@modelcontextprotocol/sdk/types.js";
4
4
  import logger, { LogId } from "./logger.js";
5
5
  import EventEmitter from "events";
6
6
  import { ConnectOptions } from "./config.js";
7
- import { setAppNameParamIfMissing } from "./helpers/connectionOptions.js";
8
- import { packageInfo } from "./helpers/packageInfo.js";
7
+ import { setAppNameParamIfMissing } from "../helpers/connectionOptions.js";
8
+ import { packageInfo } from "./packageInfo.js";
9
9
 
10
10
  export interface SessionOptions {
11
11
  apiBaseUrl: string;
@@ -67,35 +67,33 @@ export class Session extends EventEmitter<{
67
67
  }
68
68
  this.serviceProvider = undefined;
69
69
  }
70
- if (!this.connectedAtlasCluster) {
71
- this.emit("disconnect");
72
- return;
73
- }
74
- void this.apiClient
75
- .deleteDatabaseUser({
76
- params: {
77
- path: {
78
- groupId: this.connectedAtlasCluster.projectId,
79
- username: this.connectedAtlasCluster.username,
80
- databaseName: "admin",
70
+ if (this.connectedAtlasCluster?.username && this.connectedAtlasCluster?.projectId) {
71
+ void this.apiClient
72
+ .deleteDatabaseUser({
73
+ params: {
74
+ path: {
75
+ groupId: this.connectedAtlasCluster.projectId,
76
+ username: this.connectedAtlasCluster.username,
77
+ databaseName: "admin",
78
+ },
81
79
  },
82
- },
83
- })
84
- .catch((err: unknown) => {
85
- const error = err instanceof Error ? err : new Error(String(err));
86
- logger.error(
87
- LogId.atlasDeleteDatabaseUserFailure,
88
- "atlas-connect-cluster",
89
- `Error deleting previous database user: ${error.message}`
90
- );
91
- });
92
- this.connectedAtlasCluster = undefined;
93
-
80
+ })
81
+ .catch((err: unknown) => {
82
+ const error = err instanceof Error ? err : new Error(String(err));
83
+ logger.error(
84
+ LogId.atlasDeleteDatabaseUserFailure,
85
+ "atlas-connect-cluster",
86
+ `Error deleting previous database user: ${error.message}`
87
+ );
88
+ });
89
+ this.connectedAtlasCluster = undefined;
90
+ }
94
91
  this.emit("disconnect");
95
92
  }
96
93
 
97
94
  async close(): Promise<void> {
98
95
  await this.disconnect();
96
+ await this.apiClient.close();
99
97
  this.emit("close");
100
98
  }
101
99
 
@@ -0,0 +1,111 @@
1
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import logger, { LogId, LoggerBase, McpLogger } from "./logger.js";
4
+ import { ManagedTimeout, setManagedTimeout } from "./managedTimeout.js";
5
+
6
+ export class SessionStore {
7
+ private sessions: {
8
+ [sessionId: string]: {
9
+ logger: LoggerBase;
10
+ transport: StreamableHTTPServerTransport;
11
+ abortTimeout: ManagedTimeout;
12
+ notificationTimeout: ManagedTimeout;
13
+ };
14
+ } = {};
15
+
16
+ constructor(
17
+ private readonly idleTimeoutMS: number,
18
+ private readonly notificationTimeoutMS: number
19
+ ) {
20
+ if (idleTimeoutMS <= 0) {
21
+ throw new Error("idleTimeoutMS must be greater than 0");
22
+ }
23
+ if (notificationTimeoutMS <= 0) {
24
+ throw new Error("notificationTimeoutMS must be greater than 0");
25
+ }
26
+ if (idleTimeoutMS <= notificationTimeoutMS) {
27
+ throw new Error("idleTimeoutMS must be greater than notificationTimeoutMS");
28
+ }
29
+ }
30
+
31
+ getSession(sessionId: string): StreamableHTTPServerTransport | undefined {
32
+ this.resetTimeout(sessionId);
33
+ return this.sessions[sessionId]?.transport;
34
+ }
35
+
36
+ private resetTimeout(sessionId: string): void {
37
+ const session = this.sessions[sessionId];
38
+ if (!session) {
39
+ return;
40
+ }
41
+
42
+ session.abortTimeout.restart();
43
+
44
+ session.notificationTimeout.restart();
45
+ }
46
+
47
+ private sendNotification(sessionId: string): void {
48
+ const session = this.sessions[sessionId];
49
+ if (!session) {
50
+ logger.warning(
51
+ LogId.streamableHttpTransportSessionCloseNotificationFailure,
52
+ "sessionStore",
53
+ `session ${sessionId} not found, no notification delivered`
54
+ );
55
+ return;
56
+ }
57
+ session.logger.info(
58
+ LogId.streamableHttpTransportSessionCloseNotification,
59
+ "sessionStore",
60
+ "Session is about to be closed due to inactivity"
61
+ );
62
+ }
63
+
64
+ setSession(sessionId: string, transport: StreamableHTTPServerTransport, mcpServer: McpServer): void {
65
+ const session = this.sessions[sessionId];
66
+ if (session) {
67
+ throw new Error(`Session ${sessionId} already exists`);
68
+ }
69
+ const abortTimeout = setManagedTimeout(async () => {
70
+ if (this.sessions[sessionId]) {
71
+ this.sessions[sessionId].logger.info(
72
+ LogId.streamableHttpTransportSessionCloseNotification,
73
+ "sessionStore",
74
+ "Session closed due to inactivity"
75
+ );
76
+
77
+ await this.closeSession(sessionId);
78
+ }
79
+ }, this.idleTimeoutMS);
80
+ const notificationTimeout = setManagedTimeout(
81
+ () => this.sendNotification(sessionId),
82
+ this.notificationTimeoutMS
83
+ );
84
+ this.sessions[sessionId] = { logger: new McpLogger(mcpServer), transport, abortTimeout, notificationTimeout };
85
+ }
86
+
87
+ async closeSession(sessionId: string, closeTransport: boolean = true): Promise<void> {
88
+ const session = this.sessions[sessionId];
89
+ if (!session) {
90
+ throw new Error(`Session ${sessionId} not found`);
91
+ }
92
+ session.abortTimeout.cancel();
93
+ session.notificationTimeout.cancel();
94
+ if (closeTransport) {
95
+ try {
96
+ await session.transport.close();
97
+ } catch (error) {
98
+ logger.error(
99
+ LogId.streamableHttpTransportSessionCloseFailure,
100
+ "streamableHttpTransport",
101
+ `Error closing transport ${sessionId}: ${error instanceof Error ? error.message : String(error)}`
102
+ );
103
+ }
104
+ }
105
+ delete this.sessions[sessionId];
106
+ }
107
+
108
+ async closeAllSessions(): Promise<void> {
109
+ await Promise.all(Object.keys(this.sessions).map((sessionId) => this.closeSession(sessionId)));
110
+ }
111
+ }
@@ -0,0 +1,35 @@
1
+ import fs from "fs/promises";
2
+
3
+ let containerEnv: boolean | undefined;
4
+
5
+ export async function detectContainerEnv(): Promise<boolean> {
6
+ if (containerEnv !== undefined) {
7
+ return containerEnv;
8
+ }
9
+
10
+ const detect = async function (): Promise<boolean> {
11
+ if (process.platform !== "linux") {
12
+ return false; // we only support linux containers for now
13
+ }
14
+
15
+ if (process.env.container) {
16
+ return true;
17
+ }
18
+
19
+ const exists = await Promise.all(
20
+ ["/.dockerenv", "/run/.containerenv", "/var/run/.containerenv"].map(async (file) => {
21
+ try {
22
+ await fs.access(file);
23
+ return true;
24
+ } catch {
25
+ return false;
26
+ }
27
+ })
28
+ );
29
+
30
+ return exists.includes(true);
31
+ };
32
+
33
+ containerEnv = await detect();
34
+ return containerEnv;
35
+ }
@@ -0,0 +1,83 @@
1
+ import { Document } from "mongodb";
2
+ import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver";
3
+ import { ErrorCodes, MongoDBError } from "../common/errors.js";
4
+
5
+ /**
6
+ * Check if the query plan uses an index
7
+ * @param explainResult The result of the explain query
8
+ * @returns true if an index is used, false if it's a full collection scan
9
+ */
10
+ export function usesIndex(explainResult: Document): boolean {
11
+ const queryPlanner = explainResult?.queryPlanner as Document | undefined;
12
+ const winningPlan = queryPlanner?.winningPlan as Document | undefined;
13
+ const stage = winningPlan?.stage as string | undefined;
14
+ const inputStage = winningPlan?.inputStage as Document | undefined;
15
+
16
+ // Check for index scan stages (including MongoDB 8.0+ stages)
17
+ const indexScanStages = [
18
+ "IXSCAN",
19
+ "COUNT_SCAN",
20
+ "EXPRESS_IXSCAN",
21
+ "EXPRESS_CLUSTERED_IXSCAN",
22
+ "EXPRESS_UPDATE",
23
+ "EXPRESS_DELETE",
24
+ "IDHACK",
25
+ ];
26
+
27
+ if (stage && indexScanStages.includes(stage)) {
28
+ return true;
29
+ }
30
+
31
+ if (inputStage && inputStage.stage && indexScanStages.includes(inputStage.stage as string)) {
32
+ return true;
33
+ }
34
+
35
+ // Recursively check deeper stages
36
+ if (inputStage && inputStage.inputStage) {
37
+ return usesIndex({ queryPlanner: { winningPlan: inputStage } });
38
+ }
39
+
40
+ if (stage === "COLLSCAN") {
41
+ return false;
42
+ }
43
+
44
+ // Default to false (conservative approach)
45
+ return false;
46
+ }
47
+
48
+ /**
49
+ * Generate an error message for index check failure
50
+ */
51
+ export function getIndexCheckErrorMessage(database: string, collection: string, operation: string): string {
52
+ return `Index check failed: The ${operation} operation on "${database}.${collection}" performs a collection scan (COLLSCAN) instead of using an index. Consider adding an index for better performance. Use 'explain' tool for query plan analysis or 'collection-indexes' to view existing indexes. To disable this check, set MDB_MCP_INDEX_CHECK to false.`;
53
+ }
54
+
55
+ /**
56
+ * Generic function to perform index usage check
57
+ */
58
+ export async function checkIndexUsage(
59
+ provider: NodeDriverServiceProvider,
60
+ database: string,
61
+ collection: string,
62
+ operation: string,
63
+ explainCallback: () => Promise<Document>
64
+ ): Promise<void> {
65
+ try {
66
+ const explainResult = await explainCallback();
67
+
68
+ if (!usesIndex(explainResult)) {
69
+ throw new MongoDBError(
70
+ ErrorCodes.ForbiddenCollscan,
71
+ getIndexCheckErrorMessage(database, collection, operation)
72
+ );
73
+ }
74
+ } catch (error) {
75
+ if (error instanceof MongoDBError && error.code === ErrorCodes.ForbiddenCollscan) {
76
+ throw error;
77
+ }
78
+
79
+ // If explain itself fails, log but do not prevent query execution
80
+ // This avoids blocking normal queries in special cases (e.g., permission issues)
81
+ console.warn(`Index check failed to execute explain for ${operation} on ${database}.${collection}:`, error);
82
+ }
83
+ }
package/src/index.ts CHANGED
@@ -1,58 +1,48 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import logger, { LogId } from "./logger.js";
4
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
- import { config } from "./config.js";
6
- import { Session } from "./session.js";
7
- import { Server } from "./server.js";
8
- import { packageInfo } from "./helpers/packageInfo.js";
9
- import { Telemetry } from "./telemetry/telemetry.js";
10
- import { createEJsonTransport } from "./helpers/EJsonTransport.js";
3
+ import logger, { LogId } from "./common/logger.js";
4
+ import { config } from "./common/config.js";
5
+ import { StdioRunner } from "./transports/stdio.js";
6
+ import { StreamableHttpRunner } from "./transports/streamableHttp.js";
11
7
 
12
- try {
13
- const session = new Session({
14
- apiBaseUrl: config.apiBaseUrl,
15
- apiClientId: config.apiClientId,
16
- apiClientSecret: config.apiClientSecret,
17
- });
18
- const mcpServer = new McpServer({
19
- name: packageInfo.mcpServerName,
20
- version: packageInfo.version,
21
- });
22
-
23
- const telemetry = Telemetry.create(session, config);
24
-
25
- const server = new Server({
26
- mcpServer,
27
- session,
28
- telemetry,
29
- userConfig: config,
30
- });
31
-
32
- const transport = createEJsonTransport();
8
+ async function main() {
9
+ const transportRunner = config.transport === "stdio" ? new StdioRunner(config) : new StreamableHttpRunner(config);
33
10
 
34
11
  const shutdown = () => {
35
12
  logger.info(LogId.serverCloseRequested, "server", `Server close requested`);
36
13
 
37
- server
14
+ transportRunner
38
15
  .close()
39
16
  .then(() => {
40
- logger.info(LogId.serverClosed, "server", `Server closed successfully`);
17
+ logger.info(LogId.serverClosed, "server", `Server closed`);
41
18
  process.exit(0);
42
19
  })
43
- .catch((err: unknown) => {
44
- const error = err instanceof Error ? err : new Error(String(err));
45
- logger.error(LogId.serverCloseFailure, "server", `Error closing server: ${error.message}`);
20
+ .catch((error: unknown) => {
21
+ logger.error(LogId.serverCloseFailure, "server", `Error closing server: ${error as string}`);
46
22
  process.exit(1);
47
23
  });
48
24
  };
49
25
 
50
- process.once("SIGINT", shutdown);
51
- process.once("SIGTERM", shutdown);
52
- process.once("SIGQUIT", shutdown);
26
+ process.on("SIGINT", shutdown);
27
+ process.on("SIGABRT", shutdown);
28
+ process.on("SIGTERM", shutdown);
29
+ process.on("SIGQUIT", shutdown);
30
+
31
+ try {
32
+ await transportRunner.start();
33
+ } catch (error: unknown) {
34
+ logger.info(LogId.serverCloseRequested, "server", "Closing server");
35
+ try {
36
+ await transportRunner.close();
37
+ logger.info(LogId.serverClosed, "server", "Server closed");
38
+ } catch (error: unknown) {
39
+ logger.error(LogId.serverCloseFailure, "server", `Error closing server: ${error as string}`);
40
+ }
41
+ throw error;
42
+ }
43
+ }
53
44
 
54
- await server.connect(transport);
55
- } catch (error: unknown) {
45
+ main().catch((error: unknown) => {
56
46
  logger.emergency(LogId.serverStartFailure, "server", `Fatal error running server: ${error as string}`);
57
47
  process.exit(1);
58
- }
48
+ });
package/src/server.ts CHANGED
@@ -1,16 +1,17 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { Session } from "./session.js";
2
+ import { Session } from "./common/session.js";
3
3
  import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
4
4
  import { AtlasTools } from "./tools/atlas/tools.js";
5
5
  import { MongoDbTools } from "./tools/mongodb/tools.js";
6
- import logger, { initializeLogger, LogId } from "./logger.js";
6
+ import logger, { LogId, LoggerBase, McpLogger, DiskLogger, ConsoleLogger } from "./common/logger.js";
7
7
  import { ObjectId } from "mongodb";
8
8
  import { Telemetry } from "./telemetry/telemetry.js";
9
- import { UserConfig } from "./config.js";
9
+ import { UserConfig } from "./common/config.js";
10
10
  import { type ServerEvent } from "./telemetry/types.js";
11
11
  import { type ServerCommand } from "./telemetry/types.js";
12
12
  import { CallToolRequestSchema, CallToolResult } from "@modelcontextprotocol/sdk/types.js";
13
13
  import assert from "assert";
14
+ import { ToolBase } from "./tools/tool.js";
14
15
 
15
16
  export interface ServerOptions {
16
17
  session: Session;
@@ -21,9 +22,10 @@ export interface ServerOptions {
21
22
 
22
23
  export class Server {
23
24
  public readonly session: Session;
24
- private readonly mcpServer: McpServer;
25
+ public readonly mcpServer: McpServer;
25
26
  private readonly telemetry: Telemetry;
26
27
  public readonly userConfig: UserConfig;
28
+ public readonly tools: ToolBase[] = [];
27
29
  private readonly startTime: number;
28
30
 
29
31
  constructor({ session, mcpServer, userConfig, telemetry }: ServerOptions) {
@@ -35,6 +37,8 @@ export class Server {
35
37
  }
36
38
 
37
39
  async connect(transport: Transport): Promise<void> {
40
+ await this.validateConfig();
41
+
38
42
  this.mcpServer.server.registerCapabilities({ logging: {} });
39
43
 
40
44
  this.registerTools();
@@ -63,9 +67,17 @@ export class Server {
63
67
  return existingHandler(request, extra);
64
68
  });
65
69
 
66
- await initializeLogger(this.mcpServer, this.userConfig.logPath);
67
-
68
- await this.mcpServer.connect(transport);
70
+ const loggers: LoggerBase[] = [];
71
+ if (this.userConfig.loggers.includes("mcp")) {
72
+ loggers.push(new McpLogger(this.mcpServer));
73
+ }
74
+ if (this.userConfig.loggers.includes("disk")) {
75
+ loggers.push(await DiskLogger.fromPath(this.userConfig.logPath));
76
+ }
77
+ if (this.userConfig.loggers.includes("stderr")) {
78
+ loggers.push(new ConsoleLogger());
79
+ }
80
+ logger.setLoggers(...loggers);
69
81
 
70
82
  this.mcpServer.server.oninitialized = () => {
71
83
  this.session.setAgentRunner(this.mcpServer.server.getClientVersion());
@@ -90,7 +102,7 @@ export class Server {
90
102
  this.emitServerEvent("stop", Date.now() - closeTime, error);
91
103
  };
92
104
 
93
- await this.validateConfig();
105
+ await this.mcpServer.connect(transport);
94
106
  }
95
107
 
96
108
  async close(): Promise<void> {
@@ -130,12 +142,15 @@ export class Server {
130
142
  }
131
143
  }
132
144
 
133
- this.telemetry.emitEvents([event]);
145
+ this.telemetry.emitEvents([event]).catch(() => {});
134
146
  }
135
147
 
136
148
  private registerTools() {
137
- for (const tool of [...AtlasTools, ...MongoDbTools]) {
138
- new tool(this.session, this.userConfig, this.telemetry).register(this.mcpServer);
149
+ for (const toolConstructor of [...AtlasTools, ...MongoDbTools]) {
150
+ const tool = new toolConstructor(this.session, this.userConfig, this.telemetry);
151
+ if (tool.register(this)) {
152
+ this.tools.push(tool);
153
+ }
139
154
  }
140
155
  }
141
156
 
@@ -174,6 +189,35 @@ export class Server {
174
189
  }
175
190
 
176
191
  private async validateConfig(): Promise<void> {
192
+ const transport = this.userConfig.transport as string;
193
+ if (transport !== "http" && transport !== "stdio") {
194
+ throw new Error(`Invalid transport: ${transport}`);
195
+ }
196
+
197
+ const telemetry = this.userConfig.telemetry as string;
198
+ if (telemetry !== "enabled" && telemetry !== "disabled") {
199
+ throw new Error(`Invalid telemetry: ${telemetry}`);
200
+ }
201
+
202
+ if (this.userConfig.httpPort < 1 || this.userConfig.httpPort > 65535) {
203
+ throw new Error(`Invalid httpPort: ${this.userConfig.httpPort}`);
204
+ }
205
+
206
+ if (this.userConfig.loggers.length === 0) {
207
+ throw new Error("No loggers found in config");
208
+ }
209
+
210
+ const loggerTypes = new Set(this.userConfig.loggers);
211
+ if (loggerTypes.size !== this.userConfig.loggers.length) {
212
+ throw new Error("Duplicate loggers found in config");
213
+ }
214
+
215
+ for (const loggerType of this.userConfig.loggers as string[]) {
216
+ if (loggerType !== "mcp" && loggerType !== "disk" && loggerType !== "stderr") {
217
+ throw new Error(`Invalid logger: ${loggerType}`);
218
+ }
219
+ }
220
+
177
221
  if (this.userConfig.connectionString) {
178
222
  try {
179
223
  await this.session.connectToMongoDB(this.userConfig.connectionString, this.userConfig.connectOptions);
@@ -1,4 +1,4 @@
1
- import { packageInfo } from "../helpers/packageInfo.js";
1
+ import { packageInfo } from "../common/packageInfo.js";
2
2
  import { type CommonStaticProperties } from "./types.js";
3
3
 
4
4
  /**