Skip to content

Commit 6505080

Browse files
committed
Merge branch 'main' into ni/integration-dbstats
2 parents b959b6a + 55618a6 commit 6505080

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+826
-326
lines changed

.github/workflows/code_health.yaml

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ jobs:
3131
cache: "npm"
3232
- name: Install dependencies
3333
run: npm ci
34-
- name: Run style check
35-
run: npm run generate
34+
- run: npm run generate
3635

3736
run-tests:
3837
strategy:
@@ -44,6 +43,26 @@ jobs:
4443
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
4544
if: matrix.os != 'windows-latest'
4645
- uses: actions/checkout@v4
46+
- uses: actions/setup-node@v4
47+
with:
48+
node-version-file: package.json
49+
cache: "npm"
50+
- name: Install dependencies
51+
run: npm ci
52+
- name: Run tests
53+
run: npm test
54+
- name: Upload test results
55+
if: always() && matrix.os == 'ubuntu-latest'
56+
uses: actions/upload-artifact@v4
57+
with:
58+
name: test-results
59+
path: coverage/lcov.info
60+
61+
run-atlas-tests:
62+
runs-on: ubuntu-latest
63+
steps:
64+
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
65+
- uses: actions/checkout@v4
4766
- uses: actions/setup-node@v4
4867
with:
4968
node-version-file: package.json
@@ -55,10 +74,40 @@ jobs:
5574
MDB_MCP_API_CLIENT_ID: ${{ secrets.TEST_ATLAS_CLIENT_ID }}
5675
MDB_MCP_API_CLIENT_SECRET: ${{ secrets.TEST_ATLAS_CLIENT_SECRET }}
5776
MDB_MCP_API_BASE_URL: ${{ vars.TEST_ATLAS_BASE_URL }}
58-
run: npm test
77+
run: npm test -- --testPathIgnorePatterns "tests/integration/tools/mongodb" --testPathIgnorePatterns "tests/integration/[^/]+\.ts"
78+
- name: Upload test results
79+
uses: actions/upload-artifact@v4
80+
if: always()
81+
with:
82+
name: atlas-test-results
83+
path: coverage/lcov.info
84+
coverage:
85+
runs-on: ubuntu-latest
86+
needs: [run-tests, run-atlas-tests]
87+
if: always()
88+
steps:
89+
- uses: actions/checkout@v4
90+
- uses: actions/setup-node@v4
91+
with:
92+
node-version-file: package.json
93+
cache: "npm"
94+
- name: Install dependencies
95+
run: npm ci
96+
- name: Download test results
97+
uses: actions/download-artifact@v4
98+
with:
99+
name: test-results
100+
path: coverage/mongodb
101+
- name: Download atlas test results
102+
uses: actions/download-artifact@v4
103+
with:
104+
name: atlas-test-results
105+
path: coverage/atlas
106+
- name: Merge coverage reports
107+
run: |
108+
npx -y lcov-result-merger@5.0.1 "coverage/*/lcov.info" "coverage/lcov.info"
59109
- name: Coveralls GitHub Action
60110
uses: coverallsapp/github-action@v2.3.6
61-
if: matrix.os == 'ubuntu-latest'
62111
with:
63112
file: coverage/lcov.info
64113
git-branch: ${{ github.head_ref || github.ref_name }}

package-lock.json

Lines changed: 30 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"jest-environment-node": "^29.7.0",
4848
"jest-extended": "^4.0.2",
4949
"mongodb-runner": "^5.8.2",
50+
"native-machine-id": "^0.0.8",
5051
"openapi-types": "^12.1.3",
5152
"openapi-typescript": "^7.6.1",
5253
"prettier": "^3.5.3",
@@ -61,6 +62,7 @@
6162
"@mongodb-js/devtools-connect": "^3.7.2",
6263
"@mongosh/service-provider-node-driver": "^3.6.0",
6364
"bson": "^6.10.3",
65+
"lru-cache": "^11.1.0",
6466
"mongodb": "^6.15.0",
6567
"mongodb-log-writer": "^2.4.1",
6668
"mongodb-redact": "^1.1.6",

src/common/atlas/apiClient.ts

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
import config from "../../config.js";
2-
import createClient, { Client, FetchOptions, Middleware } from "openapi-fetch";
1+
import createClient, { Client, Middleware } from "openapi-fetch";
2+
import type { FetchOptions } from "openapi-fetch";
33
import { AccessToken, ClientCredentials } from "simple-oauth2";
44
import { ApiClientError } from "./apiClientError.js";
55
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";
610

711
const ATLAS_API_VERSION = "2025-03-12";
812

@@ -67,7 +71,7 @@ export class ApiClient {
6771
baseUrl: options?.baseUrl || "https://cloud.mongodb.com/",
6872
userAgent:
6973
options?.userAgent ||
70-
`AtlasMCP/${config.version} (${process.platform}; ${process.arch}; ${process.env.HOSTNAME || "unknown"})`,
74+
`AtlasMCP/${packageInfo.version} (${process.platform}; ${process.arch}; ${process.env.HOSTNAME || "unknown"})`,
7175
};
7276

7377
this.client = createClient<paths>({
@@ -93,6 +97,15 @@ export class ApiClient {
9397
this.client.use(this.errorMiddleware);
9498
}
9599

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+
96109
public async getIpInfo(): Promise<{
97110
currentIpv4Address: string;
98111
}> {
@@ -118,6 +131,32 @@ export class ApiClient {
118131
}>;
119132
}
120133

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+
121160
// DO NOT EDIT. This is auto-generated code.
122161
async listClustersForAllProjects(options?: FetchOptions<operations["listClustersForAllProjects"]>) {
123162
const { data } = await this.client.GET("/api/atlas/v2/clusters", options);

src/config.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ import path from "path";
22
import os from "os";
33
import argv from "yargs-parser";
44

5-
import packageJson from "../package.json" with { type: "json" };
65
import { ReadConcernLevel, ReadPreferenceMode, W } from "mongodb";
76

87
// If we decide to support non-string config options, we'll need to extend the mechanism for parsing
98
// env variables.
10-
interface UserConfig {
9+
export interface UserConfig {
1110
apiBaseUrl?: string;
1211
apiClientId?: string;
1312
apiClientSecret?: string;
13+
telemetry?: "enabled" | "disabled";
1414
logPath: string;
1515
connectionString?: string;
1616
connectOptions: {
@@ -33,19 +33,12 @@ const defaults: UserConfig = {
3333
disabledTools: [],
3434
};
3535

36-
const mergedUserConfig = {
36+
export const config = {
3737
...defaults,
3838
...getEnvConfig(),
3939
...getCliConfig(),
4040
};
4141

42-
const config = {
43-
...mergedUserConfig,
44-
version: packageJson.version,
45-
};
46-
47-
export default config;
48-
4942
function getLogPath(): string {
5043
const localDataPath =
5144
process.platform === "win32"

src/index.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,31 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
44
import logger from "./logger.js";
55
import { mongoLogId } from "mongodb-log-writer";
66
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7-
import config from "./config.js";
7+
import { config } from "./config.js";
88
import { Session } from "./session.js";
99
import { Server } from "./server.js";
10+
import { packageInfo } from "./packageInfo.js";
1011

1112
try {
12-
const session = new Session();
13+
const session = new Session({
14+
apiBaseUrl: config.apiBaseUrl,
15+
apiClientId: config.apiClientId,
16+
apiClientSecret: config.apiClientSecret,
17+
});
1318
const mcpServer = new McpServer({
14-
name: "MongoDB Atlas",
15-
version: config.version,
19+
name: packageInfo.mcpServerName,
20+
version: packageInfo.version,
1621
});
17-
1822
const server = new Server({
1923
mcpServer,
2024
session,
25+
userConfig: config,
2126
});
2227

2328
const transport = new StdioServerTransport();
2429

2530
await server.connect(transport);
2631
} catch (error: unknown) {
2732
logger.emergency(mongoLogId(1_000_004), "server", `Fatal error running server: ${error as string}`);
28-
2933
process.exit(1);
3034
}

src/logger.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import fs from "fs/promises";
22
import { MongoLogId, MongoLogManager, MongoLogWriter } from "mongodb-log-writer";
3-
import config from "./config.js";
43
import redact from "mongodb-redact";
54
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
65
import { LoggingMessageNotification } from "@modelcontextprotocol/sdk/types.js";
@@ -98,11 +97,11 @@ class ProxyingLogger extends LoggerBase {
9897
const logger = new ProxyingLogger();
9998
export default logger;
10099

101-
export async function initializeLogger(server: McpServer): Promise<void> {
102-
await fs.mkdir(config.logPath, { recursive: true });
100+
export async function initializeLogger(server: McpServer, logPath: string): Promise<void> {
101+
await fs.mkdir(logPath, { recursive: true });
103102

104103
const manager = new MongoLogManager({
105-
directory: config.logPath,
104+
directory: logPath,
106105
retentionDays: 30,
107106
onwarn: console.warn,
108107
onerror: console.error,

src/packageInfo.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import packageJson from "../package.json" with { type: "json" };
2+
3+
export const packageInfo = {
4+
version: packageJson.version,
5+
mcpServerName: "MongoDB MCP Server",
6+
};

0 commit comments

Comments
 (0)