Skip to content

Commit c40eeab

Browse files
authored
chore: revoke access tokens on server shutdown [MCP-53] (#352)
1 parent 27c52b4 commit c40eeab

File tree

6 files changed

+38
-8
lines changed

6 files changed

+38
-8
lines changed

.vscode/launch.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@
44
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
55
"version": "0.2.0",
66
"configurations": [
7+
{
8+
"type": "node",
9+
"request": "launch",
10+
"name": "Launch Tests",
11+
"runtimeExecutable": "npm",
12+
"runtimeArgs": ["test"],
13+
"cwd": "${workspaceFolder}",
14+
"envFile": "${workspaceFolder}/.env"
15+
},
716
{
817
"type": "node",
918
"request": "launch",

src/common/atlas/apiClient.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { ApiClientError } from "./apiClientError.js";
55
import { paths, operations } from "./openapi.js";
66
import { CommonProperties, TelemetryEvent } from "../../telemetry/types.js";
77
import { packageInfo } from "../../helpers/packageInfo.js";
8+
import logger, { LogId } from "../../logger.js";
89

910
const ATLAS_API_VERSION = "2025-03-12";
1011

@@ -34,9 +35,7 @@ export class ApiClient {
3435

3536
private getAccessToken = async () => {
3637
if (this.oauth2Client && (!this.accessToken || this.accessToken.expired())) {
37-
this.accessToken = await this.oauth2Client.getToken({
38-
agent: this.options.userAgent,
39-
});
38+
this.accessToken = await this.oauth2Client.getToken({});
4039
}
4140
return this.accessToken?.token.access_token as string | undefined;
4241
};
@@ -49,7 +48,9 @@ export class ApiClient {
4948

5049
try {
5150
const accessToken = await this.getAccessToken();
52-
request.headers.set("Authorization", `Bearer ${accessToken}`);
51+
if (accessToken) {
52+
request.headers.set("Authorization", `Bearer ${accessToken}`);
53+
}
5354
return request;
5455
} catch {
5556
// ignore not availble tokens, API will return 401
@@ -81,20 +82,38 @@ export class ApiClient {
8182
auth: {
8283
tokenHost: this.options.baseUrl,
8384
tokenPath: "/api/oauth/token",
85+
revokePath: "/api/oauth/revoke",
86+
},
87+
http: {
88+
headers: {
89+
"User-Agent": this.options.userAgent,
90+
},
8491
},
8592
});
8693
this.client.use(this.authMiddleware);
8794
}
8895
}
8996

9097
public hasCredentials(): boolean {
91-
return !!(this.oauth2Client && this.accessToken);
98+
return !!this.oauth2Client;
9299
}
93100

94101
public async validateAccessToken(): Promise<void> {
95102
await this.getAccessToken();
96103
}
97104

105+
public async close(): Promise<void> {
106+
if (this.accessToken) {
107+
try {
108+
await this.accessToken.revoke("access_token");
109+
} catch (error: unknown) {
110+
const err = error instanceof Error ? error : new Error(String(error));
111+
logger.error(LogId.atlasApiRevokeFailure, "apiClient", `Failed to revoke access token: ${err.message}`);
112+
}
113+
this.accessToken = undefined;
114+
}
115+
}
116+
98117
public async getIpInfo(): Promise<{
99118
currentIpv4Address: string;
100119
}> {

src/logger.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const LogId = {
1919
atlasInspectFailure: mongoLogId(1_001_004),
2020
atlasConnectAttempt: mongoLogId(1_001_005),
2121
atlasConnectSucceeded: mongoLogId(1_001_006),
22+
atlasApiRevokeFailure: mongoLogId(1_001_007),
2223

2324
telemetryDisabled: mongoLogId(1_002_001),
2425
telemetryEmitFailure: mongoLogId(1_002_002),

src/session.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export class Session extends EventEmitter<{
9393

9494
async close(): Promise<void> {
9595
await this.disconnect();
96+
await this.apiClient.close();
9697
this.emit("close");
9798
}
9899

src/tools/mongodb/metadata/connect.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export class ConnectTool extends MongoDBToolBase {
4646

4747
constructor(session: Session, config: UserConfig, telemetry: Telemetry) {
4848
super(session, config, telemetry);
49-
session.on("close", () => {
49+
session.on("disconnect", () => {
5050
this.updateMetadata();
5151
});
5252
}

tests/integration/helpers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ export function setupIntegrationTest(getUserConfig: () => UserConfig): Integrati
8484
});
8585

8686
afterEach(async () => {
87-
if (mcpServer) {
88-
await mcpServer.session.close();
87+
if (mcpServer && !mcpServer.session.connectedAtlasCluster) {
88+
await mcpServer.session.disconnect();
8989
}
9090
});
9191

0 commit comments

Comments
 (0)