diff --git a/.vscode/launch.json b/.vscode/launch.json index 969a92f2..0756e2d0 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"], + "cwd": "${workspaceFolder}", + "envFile": "${workspaceFolder}/.env" + }, { "type": "node", "request": "launch", diff --git a/src/common/atlas/apiClient.ts b/src/common/atlas/apiClient.ts index 1773aba5..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,13 +95,25 @@ export class ApiClient { } public hasCredentials(): boolean { - return !!(this.oauth2Client && this.accessToken); + return !!this.oauth2Client; } public async validateAccessToken(): Promise { await this.getAccessToken(); } + public async close(): Promise { + if (this.accessToken) { + 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; + } + } + public async getIpInfo(): Promise<{ currentIpv4Address: string; }> { 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/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"); } 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 e407e250..e5da149f 100644 --- a/tests/integration/helpers.ts +++ b/tests/integration/helpers.ts @@ -84,8 +84,8 @@ export function setupIntegrationTest(getUserConfig: () => UserConfig): Integrati }); afterEach(async () => { - if (mcpServer) { - await mcpServer.session.close(); + if (mcpServer && !mcpServer.session.connectedAtlasCluster) { + await mcpServer.session.disconnect(); } });