mongodb-mcp-server 0.0.4 → 0.0.5
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/.github/workflows/code_health.yaml +53 -4
- package/.github/workflows/prepare_release.yaml +4 -4
- package/README.md +2 -0
- package/dist/common/atlas/apiClient.js +30 -2
- package/dist/common/atlas/apiClient.js.map +1 -1
- package/dist/config.js +2 -7
- package/dist/config.js.map +1 -1
- package/dist/index.js +10 -4
- package/dist/index.js.map +1 -1
- package/dist/logger.js +52 -27
- package/dist/logger.js.map +1 -1
- package/dist/packageInfo.js +6 -0
- package/dist/packageInfo.js.map +1 -0
- package/dist/server.js +71 -8
- package/dist/server.js.map +1 -1
- package/dist/session.js +17 -12
- package/dist/session.js.map +1 -1
- package/dist/telemetry/constants.js +15 -0
- package/dist/telemetry/constants.js.map +1 -0
- package/dist/telemetry/eventCache.js +53 -0
- package/dist/telemetry/eventCache.js.map +1 -0
- package/dist/telemetry/telemetry.js +96 -0
- package/dist/telemetry/telemetry.js.map +1 -0
- package/dist/telemetry/types.js +2 -0
- package/dist/telemetry/types.js.map +1 -0
- package/dist/tools/atlas/atlasTool.js +8 -3
- package/dist/tools/atlas/atlasTool.js.map +1 -1
- package/dist/tools/atlas/createAccessList.js +0 -1
- package/dist/tools/atlas/createAccessList.js.map +1 -1
- package/dist/tools/atlas/createDBUser.js +0 -1
- package/dist/tools/atlas/createDBUser.js.map +1 -1
- package/dist/tools/atlas/createFreeCluster.js +0 -1
- package/dist/tools/atlas/createFreeCluster.js.map +1 -1
- package/dist/tools/atlas/createProject.js +0 -1
- package/dist/tools/atlas/createProject.js.map +1 -1
- package/dist/tools/atlas/inspectAccessList.js +0 -1
- package/dist/tools/atlas/inspectAccessList.js.map +1 -1
- package/dist/tools/atlas/inspectCluster.js +0 -1
- package/dist/tools/atlas/inspectCluster.js.map +1 -1
- package/dist/tools/atlas/listClusters.js +0 -1
- package/dist/tools/atlas/listClusters.js.map +1 -1
- package/dist/tools/atlas/listDBUsers.js +0 -1
- package/dist/tools/atlas/listDBUsers.js.map +1 -1
- package/dist/tools/atlas/listOrgs.js +0 -1
- package/dist/tools/atlas/listOrgs.js.map +1 -1
- package/dist/tools/atlas/listProjects.js +10 -4
- package/dist/tools/atlas/listProjects.js.map +1 -1
- package/dist/tools/mongodb/metadata/collectionSchema.js +15 -13
- package/dist/tools/mongodb/metadata/collectionSchema.js.map +1 -1
- package/dist/tools/mongodb/metadata/collectionStorageSize.js +32 -3
- package/dist/tools/mongodb/metadata/collectionStorageSize.js.map +1 -1
- package/dist/tools/mongodb/metadata/connect.js +5 -6
- package/dist/tools/mongodb/metadata/connect.js.map +1 -1
- package/dist/tools/mongodb/metadata/dbStats.js +6 -1
- package/dist/tools/mongodb/metadata/dbStats.js.map +1 -1
- package/dist/tools/mongodb/metadata/explain.js +14 -7
- package/dist/tools/mongodb/metadata/explain.js.map +1 -1
- package/dist/tools/mongodb/metadata/logs.js +45 -0
- package/dist/tools/mongodb/metadata/logs.js.map +1 -0
- package/dist/tools/mongodb/mongodbTool.js +10 -11
- package/dist/tools/mongodb/mongodbTool.js.map +1 -1
- package/dist/tools/mongodb/read/aggregate.js +3 -3
- package/dist/tools/mongodb/read/aggregate.js.map +1 -1
- package/dist/tools/mongodb/read/collectionIndexes.js +24 -5
- package/dist/tools/mongodb/read/collectionIndexes.js.map +1 -1
- package/dist/tools/mongodb/read/find.js +3 -2
- package/dist/tools/mongodb/read/find.js.map +1 -1
- package/dist/tools/mongodb/tools.js +2 -0
- package/dist/tools/mongodb/tools.js.map +1 -1
- package/dist/tools/mongodb/update/renameCollection.js +28 -4
- package/dist/tools/mongodb/update/renameCollection.js.map +1 -1
- package/dist/tools/mongodb/update/updateMany.js +5 -7
- package/dist/tools/mongodb/update/updateMany.js.map +1 -1
- package/dist/tools/tool.js +39 -10
- package/dist/tools/tool.js.map +1 -1
- package/eslint.config.js +29 -10
- package/global.d.ts +1 -0
- package/package.json +6 -2
- package/scripts/apply.ts +9 -7
- package/scripts/filter.ts +3 -2
- package/src/common/atlas/apiClient.ts +48 -7
- package/src/config.ts +4 -10
- package/src/index.ts +10 -6
- package/src/logger.ts +66 -28
- package/src/packageInfo.ts +6 -0
- package/src/server.ts +103 -9
- package/src/session.ts +34 -17
- package/src/telemetry/constants.ts +15 -0
- package/src/telemetry/eventCache.ts +62 -0
- package/src/telemetry/telemetry.ts +137 -0
- package/src/telemetry/types.ts +60 -0
- package/src/tools/atlas/atlasTool.ts +7 -5
- package/src/tools/atlas/createAccessList.ts +0 -2
- package/src/tools/atlas/createDBUser.ts +0 -2
- package/src/tools/atlas/createFreeCluster.ts +0 -2
- package/src/tools/atlas/createProject.ts +0 -1
- package/src/tools/atlas/inspectAccessList.ts +0 -2
- package/src/tools/atlas/inspectCluster.ts +0 -2
- package/src/tools/atlas/listClusters.ts +0 -2
- package/src/tools/atlas/listDBUsers.ts +0 -2
- package/src/tools/atlas/listOrgs.ts +0 -2
- package/src/tools/atlas/listProjects.ts +12 -4
- package/src/tools/mongodb/metadata/collectionSchema.ts +16 -14
- package/src/tools/mongodb/metadata/collectionStorageSize.ts +41 -3
- package/src/tools/mongodb/metadata/connect.ts +5 -6
- package/src/tools/mongodb/metadata/dbStats.ts +6 -1
- package/src/tools/mongodb/metadata/explain.ts +20 -7
- package/src/tools/mongodb/metadata/logs.ts +55 -0
- package/src/tools/mongodb/mongodbTool.ts +12 -15
- package/src/tools/mongodb/read/aggregate.ts +3 -3
- package/src/tools/mongodb/read/collectionIndexes.ts +29 -5
- package/src/tools/mongodb/read/find.ts +3 -2
- package/src/tools/mongodb/tools.ts +2 -0
- package/src/tools/mongodb/update/renameCollection.ts +33 -4
- package/src/tools/mongodb/update/updateMany.ts +5 -7
- package/src/tools/tool.ts +51 -15
- package/tests/integration/helpers.ts +84 -107
- package/tests/integration/inMemoryTransport.ts +3 -2
- package/tests/integration/server.test.ts +47 -21
- package/tests/integration/tools/atlas/accessLists.test.ts +13 -15
- package/tests/integration/tools/atlas/atlasHelpers.ts +3 -8
- package/tests/integration/tools/atlas/clusters.test.ts +12 -13
- package/tests/integration/tools/atlas/dbUsers.test.ts +9 -9
- package/tests/integration/tools/atlas/orgs.test.ts +4 -4
- package/tests/integration/tools/atlas/projects.test.ts +10 -12
- package/tests/integration/tools/mongodb/create/createCollection.test.ts +19 -62
- package/tests/integration/tools/mongodb/create/createIndex.test.ts +41 -87
- package/tests/integration/tools/mongodb/create/insertMany.test.ts +35 -78
- package/tests/integration/tools/mongodb/delete/deleteMany.test.ts +25 -62
- package/tests/integration/tools/mongodb/delete/dropCollection.test.ts +22 -71
- package/tests/integration/tools/mongodb/delete/dropDatabase.test.ts +29 -63
- package/tests/integration/tools/mongodb/metadata/collectionSchema.test.ts +154 -0
- package/tests/integration/tools/mongodb/metadata/collectionStorageSize.test.ts +86 -0
- package/tests/integration/tools/mongodb/metadata/connect.test.ts +33 -23
- package/tests/integration/tools/mongodb/metadata/dbStats.test.ts +104 -0
- package/tests/integration/tools/mongodb/metadata/explain.test.ts +171 -0
- package/tests/integration/tools/mongodb/metadata/listCollections.test.ts +28 -56
- package/tests/integration/tools/mongodb/metadata/listDatabases.test.ts +32 -26
- package/tests/integration/tools/mongodb/metadata/logs.test.ts +83 -0
- package/tests/integration/tools/mongodb/mongodbHelpers.ts +165 -0
- package/tests/integration/tools/mongodb/read/aggregate.test.ts +99 -0
- package/tests/integration/tools/mongodb/read/collectionIndexes.test.ts +99 -0
- package/tests/integration/tools/mongodb/read/count.test.ts +31 -79
- package/tests/integration/tools/mongodb/read/find.test.ts +182 -0
- package/tests/integration/tools/mongodb/update/renameCollection.test.ts +194 -0
- package/tests/integration/tools/mongodb/update/updateMany.test.ts +238 -0
- package/tsconfig.jest.json +2 -1
- package/tsconfig.json +1 -1
- package/tsconfig.lint.json +8 -0
package/eslint.config.js
CHANGED
|
@@ -2,18 +2,34 @@ import { defineConfig, globalIgnores } from "eslint/config";
|
|
|
2
2
|
import js from "@eslint/js";
|
|
3
3
|
import globals from "globals";
|
|
4
4
|
import tseslint from "typescript-eslint";
|
|
5
|
-
import
|
|
5
|
+
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
|
|
6
|
+
import jestPlugin from "eslint-plugin-jest";
|
|
6
7
|
|
|
7
|
-
const
|
|
8
|
+
const testFiles = ["tests/**/*.test.ts", "tests/**/*.ts"];
|
|
9
|
+
|
|
10
|
+
const files = [...testFiles, "src/**/*.ts", "scripts/**/*.ts"];
|
|
8
11
|
|
|
9
12
|
export default defineConfig([
|
|
10
13
|
{ files, plugins: { js }, extends: ["js/recommended"] },
|
|
11
14
|
{ files, languageOptions: { globals: globals.node } },
|
|
15
|
+
{
|
|
16
|
+
files: testFiles,
|
|
17
|
+
plugins: {
|
|
18
|
+
jest: jestPlugin,
|
|
19
|
+
},
|
|
20
|
+
languageOptions: {
|
|
21
|
+
globals: {
|
|
22
|
+
...globals.node,
|
|
23
|
+
...jestPlugin.environments.globals.globals,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
12
27
|
tseslint.configs.recommendedTypeChecked,
|
|
13
28
|
{
|
|
29
|
+
files,
|
|
14
30
|
languageOptions: {
|
|
15
31
|
parserOptions: {
|
|
16
|
-
|
|
32
|
+
project: "./tsconfig.lint.json",
|
|
17
33
|
tsconfigRootDir: import.meta.dirname,
|
|
18
34
|
},
|
|
19
35
|
},
|
|
@@ -25,11 +41,14 @@ export default defineConfig([
|
|
|
25
41
|
"@typescript-eslint/no-non-null-assertion": "error",
|
|
26
42
|
},
|
|
27
43
|
},
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
44
|
+
globalIgnores([
|
|
45
|
+
"node_modules",
|
|
46
|
+
"dist",
|
|
47
|
+
"src/common/atlas/openapi.d.ts",
|
|
48
|
+
"coverage",
|
|
49
|
+
"global.d.ts",
|
|
50
|
+
"eslint.config.js",
|
|
51
|
+
"jest.config.js",
|
|
52
|
+
]),
|
|
53
|
+
eslintPluginPrettierRecommended,
|
|
35
54
|
]);
|
package/global.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "jest-extended";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mongodb-mcp-server",
|
|
3
3
|
"description": "MongoDB Model Context Protocol Server",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.5",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"author": "MongoDB <info@mongodb.com>",
|
|
7
7
|
"homepage": "https://github.com/mongodb-js/mongodb-mcp-server",
|
|
@@ -41,12 +41,15 @@
|
|
|
41
41
|
"@types/simple-oauth2": "^5.0.7",
|
|
42
42
|
"@types/yargs-parser": "^21.0.3",
|
|
43
43
|
"eslint": "^9.24.0",
|
|
44
|
-
"eslint-config-prettier": "^10.1.
|
|
44
|
+
"eslint-config-prettier": "^10.1.2",
|
|
45
|
+
"eslint-plugin-jest": "^28.11.0",
|
|
46
|
+
"eslint-plugin-prettier": "^5.2.6",
|
|
45
47
|
"globals": "^16.0.0",
|
|
46
48
|
"jest": "^29.7.0",
|
|
47
49
|
"jest-environment-node": "^29.7.0",
|
|
48
50
|
"jest-extended": "^4.0.2",
|
|
49
51
|
"mongodb-runner": "^5.8.2",
|
|
52
|
+
"native-machine-id": "^0.1.0",
|
|
50
53
|
"openapi-types": "^12.1.3",
|
|
51
54
|
"openapi-typescript": "^7.6.1",
|
|
52
55
|
"prettier": "^3.5.3",
|
|
@@ -61,6 +64,7 @@
|
|
|
61
64
|
"@mongodb-js/devtools-connect": "^3.7.2",
|
|
62
65
|
"@mongosh/service-provider-node-driver": "^3.6.0",
|
|
63
66
|
"bson": "^6.10.3",
|
|
67
|
+
"lru-cache": "^11.1.0",
|
|
64
68
|
"mongodb": "^6.15.0",
|
|
65
69
|
"mongodb-log-writer": "^2.4.1",
|
|
66
70
|
"mongodb-redact": "^1.1.6",
|
package/scripts/apply.ts
CHANGED
|
@@ -9,13 +9,15 @@ function findObjectFromRef<T>(obj: T | OpenAPIV3_1.ReferenceObject, openapi: Ope
|
|
|
9
9
|
}
|
|
10
10
|
const paramParts = ref.split("/");
|
|
11
11
|
paramParts.shift(); // Remove the first part which is always '#'
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
let foundObj: Record<string, unknown> = openapi;
|
|
13
14
|
while (true) {
|
|
14
15
|
const part = paramParts.shift();
|
|
15
16
|
if (!part) {
|
|
16
17
|
break;
|
|
17
18
|
}
|
|
18
|
-
|
|
19
|
+
|
|
20
|
+
foundObj = foundObj[part] as Record<string, unknown>;
|
|
19
21
|
}
|
|
20
22
|
return foundObj as T;
|
|
21
23
|
}
|
|
@@ -28,7 +30,7 @@ async function main() {
|
|
|
28
30
|
process.exit(1);
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
const specFile =
|
|
33
|
+
const specFile = await fs.readFile(spec as string, "utf8");
|
|
32
34
|
|
|
33
35
|
const operations: {
|
|
34
36
|
path: string;
|
|
@@ -42,7 +44,7 @@ async function main() {
|
|
|
42
44
|
const openapi = JSON.parse(specFile) as OpenAPIV3_1.Document;
|
|
43
45
|
for (const path in openapi.paths) {
|
|
44
46
|
for (const method in openapi.paths[path]) {
|
|
45
|
-
const operation
|
|
47
|
+
const operation = openapi.paths[path][method] as OpenAPIV3_1.OperationObject;
|
|
46
48
|
|
|
47
49
|
if (!operation.operationId || !operation.tags?.length) {
|
|
48
50
|
continue;
|
|
@@ -101,9 +103,9 @@ async function main() {
|
|
|
101
103
|
})
|
|
102
104
|
.join("\n");
|
|
103
105
|
|
|
104
|
-
const templateFile =
|
|
106
|
+
const templateFile = await fs.readFile(file as string, "utf8");
|
|
105
107
|
const templateLines = templateFile.split("\n");
|
|
106
|
-
|
|
108
|
+
const outputLines: string[] = [];
|
|
107
109
|
let addLines = true;
|
|
108
110
|
for (const line of templateLines) {
|
|
109
111
|
if (line.includes("DO NOT EDIT. This is auto-generated code.")) {
|
|
@@ -120,7 +122,7 @@ async function main() {
|
|
|
120
122
|
}
|
|
121
123
|
const output = outputLines.join("\n");
|
|
122
124
|
|
|
123
|
-
await fs.writeFile(file, output, "utf8");
|
|
125
|
+
await fs.writeFile(file as string, output, "utf8");
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
main().catch((error) => {
|
package/scripts/filter.ts
CHANGED
|
@@ -8,6 +8,7 @@ async function readStdin() {
|
|
|
8
8
|
reject(err);
|
|
9
9
|
});
|
|
10
10
|
process.stdin.on("data", (chunk) => {
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
|
11
12
|
data += chunk;
|
|
12
13
|
});
|
|
13
14
|
process.stdin.on("end", () => {
|
|
@@ -42,8 +43,8 @@ function filterOpenapi(openapi: OpenAPIV3_1.Document): OpenAPIV3_1.Document {
|
|
|
42
43
|
for (const path in openapi.paths) {
|
|
43
44
|
const filteredMethods = {} as OpenAPIV3_1.PathItemObject;
|
|
44
45
|
for (const method in openapi.paths[path]) {
|
|
45
|
-
if (allowedOperations.includes(openapi.paths[path][method].operationId)) {
|
|
46
|
-
filteredMethods[method] = openapi.paths[path][method];
|
|
46
|
+
if (allowedOperations.includes((openapi.paths[path][method] as { operationId: string }).operationId)) {
|
|
47
|
+
filteredMethods[method] = openapi.paths[path][method] as OpenAPIV3_1.OperationObject;
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
if (Object.keys(filteredMethods).length > 0) {
|
|
@@ -1,16 +1,22 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import createClient, { Client, Middleware } from "openapi-fetch";
|
|
2
|
+
import type { FetchOptions } from "openapi-fetch";
|
|
3
3
|
import { AccessToken, ClientCredentials } from "simple-oauth2";
|
|
4
4
|
import { ApiClientError } from "./apiClientError.js";
|
|
5
5
|
import { paths, operations } from "./openapi.js";
|
|
6
|
+
import { BaseEvent } from "../../telemetry/types.js";
|
|
7
|
+
import { mongoLogId } from "mongodb-log-writer";
|
|
8
|
+
import logger from "../../logger.js";
|
|
9
|
+
import { packageInfo } from "../../packageInfo.js";
|
|
6
10
|
|
|
7
11
|
const ATLAS_API_VERSION = "2025-03-12";
|
|
8
12
|
|
|
13
|
+
export interface ApiClientCredentials {
|
|
14
|
+
clientId: string;
|
|
15
|
+
clientSecret: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
9
18
|
export interface ApiClientOptions {
|
|
10
|
-
credentials?:
|
|
11
|
-
clientId: string;
|
|
12
|
-
clientSecret: string;
|
|
13
|
-
};
|
|
19
|
+
credentials?: ApiClientCredentials;
|
|
14
20
|
baseUrl?: string;
|
|
15
21
|
userAgent?: string;
|
|
16
22
|
}
|
|
@@ -65,7 +71,7 @@ export class ApiClient {
|
|
|
65
71
|
baseUrl: options?.baseUrl || "https://cloud.mongodb.com/",
|
|
66
72
|
userAgent:
|
|
67
73
|
options?.userAgent ||
|
|
68
|
-
`AtlasMCP/${
|
|
74
|
+
`AtlasMCP/${packageInfo.version} (${process.platform}; ${process.arch}; ${process.env.HOSTNAME || "unknown"})`,
|
|
69
75
|
};
|
|
70
76
|
|
|
71
77
|
this.client = createClient<paths>({
|
|
@@ -91,6 +97,15 @@ export class ApiClient {
|
|
|
91
97
|
this.client.use(this.errorMiddleware);
|
|
92
98
|
}
|
|
93
99
|
|
|
100
|
+
public hasCredentials(): boolean {
|
|
101
|
+
logger.info(
|
|
102
|
+
mongoLogId(1_000_000),
|
|
103
|
+
"api-client",
|
|
104
|
+
`Checking if API client has credentials: ${!!(this.oauth2Client && this.accessToken)}`
|
|
105
|
+
);
|
|
106
|
+
return !!(this.oauth2Client && this.accessToken);
|
|
107
|
+
}
|
|
108
|
+
|
|
94
109
|
public async getIpInfo(): Promise<{
|
|
95
110
|
currentIpv4Address: string;
|
|
96
111
|
}> {
|
|
@@ -116,6 +131,32 @@ export class ApiClient {
|
|
|
116
131
|
}>;
|
|
117
132
|
}
|
|
118
133
|
|
|
134
|
+
async sendEvents(events: BaseEvent[]): Promise<void> {
|
|
135
|
+
let endpoint = "api/private/unauth/telemetry/events";
|
|
136
|
+
const headers: Record<string, string> = {
|
|
137
|
+
Accept: "application/json",
|
|
138
|
+
"Content-Type": "application/json",
|
|
139
|
+
"User-Agent": this.options.userAgent,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const accessToken = await this.getAccessToken();
|
|
143
|
+
if (accessToken) {
|
|
144
|
+
endpoint = "api/private/v1.0/telemetry/events";
|
|
145
|
+
headers["Authorization"] = `Bearer ${accessToken}`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const url = new URL(endpoint, this.options.baseUrl);
|
|
149
|
+
const response = await fetch(url, {
|
|
150
|
+
method: "POST",
|
|
151
|
+
headers,
|
|
152
|
+
body: JSON.stringify(events),
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
if (!response.ok) {
|
|
156
|
+
throw await ApiClientError.fromResponse(response);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
119
160
|
// DO NOT EDIT. This is auto-generated code.
|
|
120
161
|
async listClustersForAllProjects(options?: FetchOptions<operations["listClustersForAllProjects"]>) {
|
|
121
162
|
const { data } = await this.client.GET("/api/atlas/v2/clusters", options);
|
package/src/config.ts
CHANGED
|
@@ -2,15 +2,15 @@ import path from "path";
|
|
|
2
2
|
import os from "os";
|
|
3
3
|
import argv from "yargs-parser";
|
|
4
4
|
|
|
5
|
-
import packageJson from "../package.json" with { type: "json" };
|
|
6
5
|
import { ReadConcernLevel, ReadPreferenceMode, W } from "mongodb";
|
|
7
6
|
|
|
8
7
|
// If we decide to support non-string config options, we'll need to extend the mechanism for parsing
|
|
9
8
|
// env variables.
|
|
10
|
-
interface UserConfig {
|
|
9
|
+
export interface UserConfig {
|
|
11
10
|
apiBaseUrl?: string;
|
|
12
11
|
apiClientId?: string;
|
|
13
12
|
apiClientSecret?: string;
|
|
13
|
+
telemetry?: "enabled" | "disabled";
|
|
14
14
|
logPath: string;
|
|
15
15
|
connectionString?: string;
|
|
16
16
|
connectOptions: {
|
|
@@ -31,21 +31,15 @@ const defaults: UserConfig = {
|
|
|
31
31
|
timeoutMS: 30_000,
|
|
32
32
|
},
|
|
33
33
|
disabledTools: [],
|
|
34
|
+
telemetry: "disabled",
|
|
34
35
|
};
|
|
35
36
|
|
|
36
|
-
const
|
|
37
|
+
export const config = {
|
|
37
38
|
...defaults,
|
|
38
39
|
...getEnvConfig(),
|
|
39
40
|
...getCliConfig(),
|
|
40
41
|
};
|
|
41
42
|
|
|
42
|
-
const config = {
|
|
43
|
-
...mergedUserConfig,
|
|
44
|
-
version: packageJson.version,
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
export default config;
|
|
48
|
-
|
|
49
43
|
function getLogPath(): string {
|
|
50
44
|
const localDataPath =
|
|
51
45
|
process.platform === "win32"
|
package/src/index.ts
CHANGED
|
@@ -4,20 +4,25 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
4
4
|
import logger from "./logger.js";
|
|
5
5
|
import { mongoLogId } from "mongodb-log-writer";
|
|
6
6
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
|
-
import config from "./config.js";
|
|
7
|
+
import { config } from "./config.js";
|
|
8
8
|
import { Session } from "./session.js";
|
|
9
9
|
import { Server } from "./server.js";
|
|
10
|
+
import { packageInfo } from "./packageInfo.js";
|
|
10
11
|
|
|
11
12
|
try {
|
|
12
|
-
const session = new Session(
|
|
13
|
+
const session = new Session({
|
|
14
|
+
apiBaseUrl: config.apiBaseUrl,
|
|
15
|
+
apiClientId: config.apiClientId,
|
|
16
|
+
apiClientSecret: config.apiClientSecret,
|
|
17
|
+
});
|
|
13
18
|
const mcpServer = new McpServer({
|
|
14
|
-
name:
|
|
15
|
-
version:
|
|
19
|
+
name: packageInfo.mcpServerName,
|
|
20
|
+
version: packageInfo.version,
|
|
16
21
|
});
|
|
17
|
-
|
|
18
22
|
const server = new Server({
|
|
19
23
|
mcpServer,
|
|
20
24
|
session,
|
|
25
|
+
userConfig: config,
|
|
21
26
|
});
|
|
22
27
|
|
|
23
28
|
const transport = new StdioServerTransport();
|
|
@@ -25,6 +30,5 @@ try {
|
|
|
25
30
|
await server.connect(transport);
|
|
26
31
|
} catch (error: unknown) {
|
|
27
32
|
logger.emergency(mongoLogId(1_000_004), "server", `Fatal error running server: ${error as string}`);
|
|
28
|
-
|
|
29
33
|
process.exit(1);
|
|
30
34
|
}
|
package/src/logger.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import fs from "fs/promises";
|
|
2
2
|
import { MongoLogId, MongoLogManager, MongoLogWriter } from "mongodb-log-writer";
|
|
3
|
-
import config from "./config.js";
|
|
4
3
|
import redact from "mongodb-redact";
|
|
5
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
5
|
import { LoggingMessageNotification } from "@modelcontextprotocol/sdk/types.js";
|
|
@@ -9,6 +8,7 @@ export type LogLevel = LoggingMessageNotification["params"]["level"];
|
|
|
9
8
|
|
|
10
9
|
abstract class LoggerBase {
|
|
11
10
|
abstract log(level: LogLevel, id: MongoLogId, context: string, message: string): void;
|
|
11
|
+
|
|
12
12
|
info(id: MongoLogId, context: string, message: string): void {
|
|
13
13
|
this.log("info", id, context, message);
|
|
14
14
|
}
|
|
@@ -48,22 +48,35 @@ class ConsoleLogger extends LoggerBase {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
class
|
|
52
|
-
constructor(
|
|
53
|
-
private logWriter: MongoLogWriter,
|
|
54
|
-
private server: McpServer
|
|
55
|
-
) {
|
|
51
|
+
class DiskLogger extends LoggerBase {
|
|
52
|
+
private constructor(private logWriter: MongoLogWriter) {
|
|
56
53
|
super();
|
|
57
54
|
}
|
|
58
55
|
|
|
56
|
+
static async fromPath(logPath: string): Promise<DiskLogger> {
|
|
57
|
+
await fs.mkdir(logPath, { recursive: true });
|
|
58
|
+
|
|
59
|
+
const manager = new MongoLogManager({
|
|
60
|
+
directory: logPath,
|
|
61
|
+
retentionDays: 30,
|
|
62
|
+
onwarn: console.warn,
|
|
63
|
+
onerror: console.error,
|
|
64
|
+
gzip: false,
|
|
65
|
+
retentionGB: 1,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
await manager.cleanupOldLogFiles();
|
|
69
|
+
|
|
70
|
+
const logWriter = await manager.createLogWriter();
|
|
71
|
+
|
|
72
|
+
return new DiskLogger(logWriter);
|
|
73
|
+
}
|
|
74
|
+
|
|
59
75
|
log(level: LogLevel, id: MongoLogId, context: string, message: string): void {
|
|
60
76
|
message = redact(message);
|
|
61
77
|
const mongoDBLevel = this.mapToMongoDBLogLevel(level);
|
|
78
|
+
|
|
62
79
|
this.logWriter[mongoDBLevel]("MONGODB-MCP", id, context, message);
|
|
63
|
-
void this.server.server.sendLoggingMessage({
|
|
64
|
-
level,
|
|
65
|
-
data: `[${context}]: ${message}`,
|
|
66
|
-
});
|
|
67
80
|
}
|
|
68
81
|
|
|
69
82
|
private mapToMongoDBLogLevel(level: LogLevel): "info" | "warn" | "error" | "debug" | "fatal" {
|
|
@@ -87,31 +100,56 @@ class Logger extends LoggerBase {
|
|
|
87
100
|
}
|
|
88
101
|
}
|
|
89
102
|
|
|
90
|
-
class
|
|
91
|
-
private
|
|
103
|
+
class McpLogger extends LoggerBase {
|
|
104
|
+
constructor(private server: McpServer) {
|
|
105
|
+
super();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
log(level: LogLevel, _: MongoLogId, context: string, message: string): void {
|
|
109
|
+
void this.server.server.sendLoggingMessage({
|
|
110
|
+
level,
|
|
111
|
+
data: `[${context}]: ${message}`,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
class CompositeLogger extends LoggerBase {
|
|
117
|
+
private loggers: LoggerBase[];
|
|
118
|
+
|
|
119
|
+
constructor(...loggers: LoggerBase[]) {
|
|
120
|
+
super();
|
|
121
|
+
|
|
122
|
+
if (loggers.length === 0) {
|
|
123
|
+
// default to ConsoleLogger
|
|
124
|
+
this.loggers = [new ConsoleLogger()];
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.loggers = [...loggers];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
setLoggers(...loggers: LoggerBase[]): void {
|
|
132
|
+
if (loggers.length === 0) {
|
|
133
|
+
throw new Error("At least one logger must be provided");
|
|
134
|
+
}
|
|
135
|
+
this.loggers = [...loggers];
|
|
136
|
+
}
|
|
92
137
|
|
|
93
138
|
log(level: LogLevel, id: MongoLogId, context: string, message: string): void {
|
|
94
|
-
|
|
139
|
+
for (const logger of this.loggers) {
|
|
140
|
+
logger.log(level, id, context, message);
|
|
141
|
+
}
|
|
95
142
|
}
|
|
96
143
|
}
|
|
97
144
|
|
|
98
|
-
const logger = new
|
|
145
|
+
const logger = new CompositeLogger();
|
|
99
146
|
export default logger;
|
|
100
147
|
|
|
101
|
-
export async function initializeLogger(server: McpServer): Promise<
|
|
102
|
-
await
|
|
103
|
-
|
|
104
|
-
const manager = new MongoLogManager({
|
|
105
|
-
directory: config.logPath,
|
|
106
|
-
retentionDays: 30,
|
|
107
|
-
onwarn: console.warn,
|
|
108
|
-
onerror: console.error,
|
|
109
|
-
gzip: false,
|
|
110
|
-
retentionGB: 1,
|
|
111
|
-
});
|
|
148
|
+
export async function initializeLogger(server: McpServer, logPath: string): Promise<LoggerBase> {
|
|
149
|
+
const diskLogger = await DiskLogger.fromPath(logPath);
|
|
150
|
+
const mcpLogger = new McpLogger(server);
|
|
112
151
|
|
|
113
|
-
|
|
152
|
+
logger.setLoggers(mcpLogger, diskLogger);
|
|
114
153
|
|
|
115
|
-
|
|
116
|
-
logger["internalLogger"] = new Logger(logWriter, server);
|
|
154
|
+
return logger;
|
|
117
155
|
}
|
package/src/server.ts
CHANGED
|
@@ -5,28 +5,89 @@ import { AtlasTools } from "./tools/atlas/tools.js";
|
|
|
5
5
|
import { MongoDbTools } from "./tools/mongodb/tools.js";
|
|
6
6
|
import logger, { initializeLogger } from "./logger.js";
|
|
7
7
|
import { mongoLogId } from "mongodb-log-writer";
|
|
8
|
-
import
|
|
8
|
+
import { ObjectId } from "mongodb";
|
|
9
|
+
import { Telemetry } from "./telemetry/telemetry.js";
|
|
10
|
+
import { UserConfig } from "./config.js";
|
|
11
|
+
import { type ServerEvent } from "./telemetry/types.js";
|
|
12
|
+
import { type ServerCommand } from "./telemetry/types.js";
|
|
13
|
+
import { CallToolRequestSchema, CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
14
|
+
import assert from "assert";
|
|
15
|
+
|
|
16
|
+
export interface ServerOptions {
|
|
17
|
+
session: Session;
|
|
18
|
+
userConfig: UserConfig;
|
|
19
|
+
mcpServer: McpServer;
|
|
20
|
+
}
|
|
9
21
|
|
|
10
22
|
export class Server {
|
|
11
23
|
public readonly session: Session;
|
|
12
24
|
private readonly mcpServer: McpServer;
|
|
25
|
+
private readonly telemetry: Telemetry;
|
|
26
|
+
private readonly userConfig: UserConfig;
|
|
27
|
+
private readonly startTime: number;
|
|
13
28
|
|
|
14
|
-
constructor({ mcpServer,
|
|
15
|
-
this.
|
|
29
|
+
constructor({ session, mcpServer, userConfig }: ServerOptions) {
|
|
30
|
+
this.startTime = Date.now();
|
|
16
31
|
this.session = session;
|
|
32
|
+
this.telemetry = new Telemetry(session);
|
|
33
|
+
this.mcpServer = mcpServer;
|
|
34
|
+
this.userConfig = userConfig;
|
|
17
35
|
}
|
|
18
36
|
|
|
19
37
|
async connect(transport: Transport) {
|
|
20
38
|
this.mcpServer.server.registerCapabilities({ logging: {} });
|
|
21
|
-
|
|
22
39
|
this.registerTools();
|
|
23
40
|
this.registerResources();
|
|
24
41
|
|
|
25
|
-
|
|
42
|
+
// This is a workaround for an issue we've seen with some models, where they'll see that everything in the `arguments`
|
|
43
|
+
// object is optional, and then not pass it at all. However, the MCP server expects the `arguments` object to be if
|
|
44
|
+
// the tool accepts any arguments, even if they're all optional.
|
|
45
|
+
//
|
|
46
|
+
// see: https://github.com/modelcontextprotocol/typescript-sdk/blob/131776764536b5fdca642df51230a3746fb4ade0/src/server/mcp.ts#L705
|
|
47
|
+
// Since paramsSchema here is not undefined, the server will create a non-optional z.object from it.
|
|
48
|
+
const existingHandler = (
|
|
49
|
+
this.mcpServer.server["_requestHandlers"] as Map<
|
|
50
|
+
string,
|
|
51
|
+
(request: unknown, extra: unknown) => Promise<CallToolResult>
|
|
52
|
+
>
|
|
53
|
+
).get(CallToolRequestSchema.shape.method.value);
|
|
54
|
+
|
|
55
|
+
assert(existingHandler, "No existing handler found for CallToolRequestSchema");
|
|
56
|
+
|
|
57
|
+
this.mcpServer.server.setRequestHandler(CallToolRequestSchema, (request, extra): Promise<CallToolResult> => {
|
|
58
|
+
if (!request.params.arguments) {
|
|
59
|
+
request.params.arguments = {};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return existingHandler(request, extra);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await initializeLogger(this.mcpServer, this.userConfig.logPath);
|
|
26
66
|
|
|
27
67
|
await this.mcpServer.connect(transport);
|
|
28
68
|
|
|
29
|
-
|
|
69
|
+
this.mcpServer.server.oninitialized = () => {
|
|
70
|
+
this.session.setAgentRunner(this.mcpServer.server.getClientVersion());
|
|
71
|
+
this.session.sessionId = new ObjectId().toString();
|
|
72
|
+
|
|
73
|
+
logger.info(
|
|
74
|
+
mongoLogId(1_000_004),
|
|
75
|
+
"server",
|
|
76
|
+
`Server started with transport ${transport.constructor.name} and agent runner ${this.session.agentRunner?.name}`
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
this.emitServerEvent("start", Date.now() - this.startTime);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
this.mcpServer.server.onclose = () => {
|
|
83
|
+
const closeTime = Date.now();
|
|
84
|
+
this.emitServerEvent("stop", Date.now() - closeTime);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
this.mcpServer.server.onerror = (error: Error) => {
|
|
88
|
+
const closeTime = Date.now();
|
|
89
|
+
this.emitServerEvent("stop", Date.now() - closeTime, error);
|
|
90
|
+
};
|
|
30
91
|
}
|
|
31
92
|
|
|
32
93
|
async close(): Promise<void> {
|
|
@@ -34,14 +95,47 @@ export class Server {
|
|
|
34
95
|
await this.mcpServer.close();
|
|
35
96
|
}
|
|
36
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Emits a server event
|
|
100
|
+
* @param command - The server command (e.g., "start", "stop", "register", "deregister")
|
|
101
|
+
* @param additionalProperties - Additional properties specific to the event
|
|
102
|
+
*/
|
|
103
|
+
emitServerEvent(command: ServerCommand, commandDuration: number, error?: Error) {
|
|
104
|
+
const event: ServerEvent = {
|
|
105
|
+
timestamp: new Date().toISOString(),
|
|
106
|
+
source: "mdbmcp",
|
|
107
|
+
properties: {
|
|
108
|
+
...this.telemetry.getCommonProperties(),
|
|
109
|
+
result: "success",
|
|
110
|
+
duration_ms: commandDuration,
|
|
111
|
+
component: "server",
|
|
112
|
+
category: "other",
|
|
113
|
+
command: command,
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
if (command === "start") {
|
|
118
|
+
event.properties.startup_time_ms = commandDuration;
|
|
119
|
+
}
|
|
120
|
+
if (command === "stop") {
|
|
121
|
+
event.properties.runtime_duration_ms = Date.now() - this.startTime;
|
|
122
|
+
if (error) {
|
|
123
|
+
event.properties.result = "failure";
|
|
124
|
+
event.properties.reason = error.message;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.telemetry.emitEvents([event]).catch(() => {});
|
|
129
|
+
}
|
|
130
|
+
|
|
37
131
|
private registerTools() {
|
|
38
132
|
for (const tool of [...AtlasTools, ...MongoDbTools]) {
|
|
39
|
-
new tool(this.session).register(this.mcpServer);
|
|
133
|
+
new tool(this.session, this.userConfig, this.telemetry).register(this.mcpServer);
|
|
40
134
|
}
|
|
41
135
|
}
|
|
42
136
|
|
|
43
137
|
private registerResources() {
|
|
44
|
-
if (
|
|
138
|
+
if (this.userConfig.connectionString) {
|
|
45
139
|
this.mcpServer.resource(
|
|
46
140
|
"connection-string",
|
|
47
141
|
"config://connection-string",
|
|
@@ -52,7 +146,7 @@ export class Server {
|
|
|
52
146
|
return {
|
|
53
147
|
contents: [
|
|
54
148
|
{
|
|
55
|
-
text: `Preconfigured connection string: ${
|
|
149
|
+
text: `Preconfigured connection string: ${this.userConfig.connectionString}`,
|
|
56
150
|
uri: uri.href,
|
|
57
151
|
},
|
|
58
152
|
],
|