From 67263f6993cfb3a04470085193fa548e3e54dd1b Mon Sep 17 00:00:00 2001 From: Filipe Constantinov Menezes Date: Thu, 10 Jul 2025 13:20:45 +0100 Subject: [PATCH 1/4] chore: revoke access tokens on server shutdown [MCP-53] --- src/common/atlas/apiClient.ts | 6 ++++++ src/server.ts | 2 +- src/session.ts | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/common/atlas/apiClient.ts b/src/common/atlas/apiClient.ts index 1773aba5..136c9099 100644 --- a/src/common/atlas/apiClient.ts +++ b/src/common/atlas/apiClient.ts @@ -95,6 +95,12 @@ export class ApiClient { await this.getAccessToken(); } + public async close(): Promise { + if (this.accessToken) { + await this.accessToken.revokeAll(); + } + } + public async getIpInfo(): Promise<{ currentIpv4Address: string; }> { diff --git a/src/server.ts b/src/server.ts index 31a99ded..69d612ab 100644 --- a/src/server.ts +++ b/src/server.ts @@ -101,9 +101,9 @@ export class Server { } async close(): Promise { + await this.mcpServer.close(); await this.telemetry.close(); await this.session.close(); - await this.mcpServer.close(); } /** diff --git a/src/session.ts b/src/session.ts index d6df810b..eafec2a2 100644 --- a/src/session.ts +++ b/src/session.ts @@ -93,6 +93,7 @@ export class Session extends EventEmitter<{ async close(): Promise { await this.disconnect(); + await this.apiClient.close(); this.emit("close"); } From 7c69be7a0addff51a354e170876dafbc526d4194 Mon Sep 17 00:00:00 2001 From: Filipe Constantinov Menezes Date: Thu, 10 Jul 2025 15:17:17 +0100 Subject: [PATCH 2/4] fix: tests --- .vscode/launch.json | 9 +++++++++ src/common/atlas/apiClient.ts | 25 +++++++++++++++++++------ src/logger.ts | 1 + src/server.ts | 2 +- tests/integration/helpers.ts | 5 +++-- 5 files changed, 33 insertions(+), 9 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 969a92f2..a65d7563 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,15 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Tests", + "runtimeExecutable": "npm", + "runtimeArgs": ["test", "--", "tests/integration/tools/atlas/clusters.test.ts"], + "cwd": "${workspaceFolder}", + "envFile": "${workspaceFolder}/.env" + }, { "type": "node", "request": "launch", diff --git a/src/common/atlas/apiClient.ts b/src/common/atlas/apiClient.ts index 136c9099..c45f565d 100644 --- a/src/common/atlas/apiClient.ts +++ b/src/common/atlas/apiClient.ts @@ -5,6 +5,7 @@ import { ApiClientError } from "./apiClientError.js"; import { paths, operations } from "./openapi.js"; import { CommonProperties, TelemetryEvent } from "../../telemetry/types.js"; import { packageInfo } from "../../helpers/packageInfo.js"; +import logger, { LogId } from "../../logger.js"; const ATLAS_API_VERSION = "2025-03-12"; @@ -34,9 +35,7 @@ export class ApiClient { private getAccessToken = async () => { if (this.oauth2Client && (!this.accessToken || this.accessToken.expired())) { - this.accessToken = await this.oauth2Client.getToken({ - agent: this.options.userAgent, - }); + this.accessToken = await this.oauth2Client.getToken({}); } return this.accessToken?.token.access_token as string | undefined; }; @@ -49,7 +48,9 @@ export class ApiClient { try { const accessToken = await this.getAccessToken(); - request.headers.set("Authorization", `Bearer ${accessToken}`); + if (accessToken) { + request.headers.set("Authorization", `Bearer ${accessToken}`); + } return request; } catch { // ignore not availble tokens, API will return 401 @@ -81,6 +82,12 @@ export class ApiClient { auth: { tokenHost: this.options.baseUrl, tokenPath: "/api/oauth/token", + revokePath: "/api/oauth/revoke", + }, + http: { + headers: { + "User-Agent": this.options.userAgent, + }, }, }); this.client.use(this.authMiddleware); @@ -88,7 +95,7 @@ export class ApiClient { } public hasCredentials(): boolean { - return !!(this.oauth2Client && this.accessToken); + return !!this.oauth2Client; } public async validateAccessToken(): Promise { @@ -97,7 +104,13 @@ export class ApiClient { public async close(): Promise { if (this.accessToken) { - await this.accessToken.revokeAll(); + try { + await this.accessToken.revoke("access_token"); + } catch (error: unknown) { + const err = error instanceof Error ? error : new Error(String(error)); + logger.error(LogId.atlasApiRevokeFailure, "apiClient", `Failed to revoke access token: ${err.message}`); + } + this.accessToken = undefined; } } diff --git a/src/logger.ts b/src/logger.ts index 8c1e7ff2..b1fb78a9 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -19,6 +19,7 @@ export const LogId = { atlasInspectFailure: mongoLogId(1_001_004), atlasConnectAttempt: mongoLogId(1_001_005), atlasConnectSucceeded: mongoLogId(1_001_006), + atlasApiRevokeFailure: mongoLogId(1_001_007), telemetryDisabled: mongoLogId(1_002_001), telemetryEmitFailure: mongoLogId(1_002_002), diff --git a/src/server.ts b/src/server.ts index 69d612ab..31a99ded 100644 --- a/src/server.ts +++ b/src/server.ts @@ -101,9 +101,9 @@ export class Server { } async close(): Promise { - await this.mcpServer.close(); await this.telemetry.close(); await this.session.close(); + await this.mcpServer.close(); } /** diff --git a/tests/integration/helpers.ts b/tests/integration/helpers.ts index e407e250..f2b1a641 100644 --- a/tests/integration/helpers.ts +++ b/tests/integration/helpers.ts @@ -84,8 +84,9 @@ export function setupIntegrationTest(getUserConfig: () => UserConfig): Integrati }); afterEach(async () => { - if (mcpServer) { - await mcpServer.session.close(); + if (mcpServer && !mcpServer.session.connectedAtlasCluster) { + await mcpServer.session.disconnect(); + mcpServer.session.emit("close"); } }); From 200ef4849c5522d85825614fe8efd1409b8d28d8 Mon Sep 17 00:00:00 2001 From: Filipe Constantinov Menezes Date: Thu, 10 Jul 2025 16:27:00 +0100 Subject: [PATCH 3/4] fix --- .vscode/launch.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index a65d7563..0756e2d0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,7 @@ "request": "launch", "name": "Launch Tests", "runtimeExecutable": "npm", - "runtimeArgs": ["test", "--", "tests/integration/tools/atlas/clusters.test.ts"], + "runtimeArgs": ["test"], "cwd": "${workspaceFolder}", "envFile": "${workspaceFolder}/.env" }, From 93e97077ca85763a43ee9fac9a0af543a832e113 Mon Sep 17 00:00:00 2001 From: Filipe Constantinov Menezes Date: Thu, 10 Jul 2025 16:31:10 +0100 Subject: [PATCH 4/4] fix: disconnect --- src/tools/mongodb/metadata/connect.ts | 2 +- tests/integration/helpers.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tools/mongodb/metadata/connect.ts b/src/tools/mongodb/metadata/connect.ts index defbf47f..57822001 100644 --- a/src/tools/mongodb/metadata/connect.ts +++ b/src/tools/mongodb/metadata/connect.ts @@ -46,7 +46,7 @@ export class ConnectTool extends MongoDBToolBase { constructor(session: Session, config: UserConfig, telemetry: Telemetry) { super(session, config, telemetry); - session.on("close", () => { + session.on("disconnect", () => { this.updateMetadata(); }); } diff --git a/tests/integration/helpers.ts b/tests/integration/helpers.ts index f2b1a641..e5da149f 100644 --- a/tests/integration/helpers.ts +++ b/tests/integration/helpers.ts @@ -86,7 +86,6 @@ export function setupIntegrationTest(getUserConfig: () => UserConfig): Integrati afterEach(async () => { if (mcpServer && !mcpServer.session.connectedAtlasCluster) { await mcpServer.session.disconnect(); - mcpServer.session.emit("close"); } });