Skip to content

Commit 3d9f963

Browse files
committed
fix: tests and token refactor
1 parent 2febc5d commit 3d9f963

File tree

3 files changed

+51
-62
lines changed

3 files changed

+51
-62
lines changed

src/common/atlas/apiClient.ts

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -51,39 +51,40 @@ export interface ApiClientOptions {
5151

5252
export class ApiClient {
5353
private token?: OAuthToken;
54-
private saveToken?: saveTokenFunction;
55-
private client = createClient<paths>({
54+
private readonly saveToken?: saveTokenFunction;
55+
private readonly client = createClient<paths>({
5656
baseUrl: config.apiBaseUrl,
5757
headers: {
5858
"User-Agent": config.userAgent,
5959
Accept: `application/vnd.atlas.${config.atlasApiVersion}+json`,
6060
},
6161
});
62-
private authMiddleware = (apiClient: ApiClient): Middleware => ({
63-
async onRequest({ request, schemaPath }) {
62+
63+
private readonly authMiddleware: Middleware = {
64+
onRequest: async ({ request, schemaPath }) => {
6465
if (schemaPath.startsWith("/api/private/unauth") || schemaPath.startsWith("/api/oauth")) {
6566
return undefined;
6667
}
67-
if (await apiClient.validateToken()) {
68-
request.headers.set("Authorization", `Bearer ${apiClient.token!.access_token}`);
68+
if (this.token && (await this.validateToken())) {
69+
request.headers.set("Authorization", `Bearer ${this.token.access_token}`);
6970
return request;
7071
}
7172
},
72-
});
73-
private errorMiddleware = (): Middleware => ({
73+
};
74+
private readonly errorMiddleware: Middleware = {
7475
async onResponse({ response }) {
7576
if (!response.ok) {
7677
throw await ApiClientError.fromResponse(response);
7778
}
7879
},
79-
});
80+
};
8081

8182
constructor(options: ApiClientOptions) {
8283
const { token, saveToken } = options;
8384
this.token = token;
8485
this.saveToken = saveToken;
85-
this.client.use(this.authMiddleware(this));
86-
this.client.use(this.errorMiddleware());
86+
this.client.use(this.authMiddleware);
87+
this.client.use(this.errorMiddleware);
8788
}
8889

8990
static fromState(state: State): ApiClient {
@@ -173,7 +174,7 @@ export class ApiClient {
173174
}
174175
}
175176

176-
async refreshToken(token?: OAuthToken): Promise<OAuthToken | null> {
177+
async refreshToken(token: OAuthToken): Promise<OAuthToken> {
177178
const endpoint = "api/private/unauth/account/device/token";
178179
const url = new URL(endpoint, config.apiBaseUrl);
179180
const response = await fetch(url, {
@@ -184,7 +185,7 @@ export class ApiClient {
184185
},
185186
body: new URLSearchParams({
186187
client_id: config.clientId,
187-
refresh_token: (token || this.token)?.refresh_token || "",
188+
refresh_token: token.refresh_token,
188189
grant_type: "refresh_token",
189190
scope: "openid profile offline_access",
190191
}).toString(),
@@ -207,7 +208,7 @@ export class ApiClient {
207208
return await this.storeToken(tokenToStore);
208209
}
209210

210-
async revokeToken(token?: OAuthToken): Promise<void> {
211+
async revokeToken(token: OAuthToken): Promise<void> {
211212
const endpoint = "api/private/unauth/account/device/token";
212213
const url = new URL(endpoint, config.apiBaseUrl);
213214
const response = await fetch(url, {
@@ -219,7 +220,7 @@ export class ApiClient {
219220
},
220221
body: new URLSearchParams({
221222
client_id: config.clientId,
222-
token: (token || this.token)?.access_token || "",
223+
token: token.access_token || "",
223224
token_type_hint: "refresh_token",
224225
}).toString(),
225226
});
@@ -235,9 +236,8 @@ export class ApiClient {
235236
return;
236237
}
237238

238-
private checkTokenExpiry(token?: OAuthToken): boolean {
239+
private checkTokenExpiry(token: OAuthToken): boolean {
239240
try {
240-
token = token || this.token;
241241
if (!token || !token.access_token) {
242242
return false;
243243
}
@@ -252,21 +252,25 @@ export class ApiClient {
252252
}
253253
}
254254

255-
async validateToken(token?: OAuthToken): Promise<boolean> {
256-
if (this.checkTokenExpiry(token)) {
255+
async validateToken(): Promise<boolean> {
256+
if (!this.token) {
257+
return false;
258+
}
259+
260+
if (this.checkTokenExpiry(this.token)) {
257261
return true;
258262
}
259263

260264
try {
261-
await this.refreshToken(token);
265+
await this.refreshToken(this.token);
262266
return true;
263267
} catch {
264268
return false;
265269
}
266270
}
267271

268272
async getIpInfo() {
269-
if (!(await this.validateToken())) {
273+
if (!this.token || !(await this.validateToken())) {
270274
throw new Error("Not Authenticated");
271275
}
272276

@@ -276,7 +280,7 @@ export class ApiClient {
276280
method: "GET",
277281
headers: {
278282
Accept: "application/json",
279-
Authorization: `Bearer ${this.token!.access_token}`,
283+
Authorization: `Bearer ${this.token.access_token}`,
280284
"User-Agent": config.userAgent,
281285
},
282286
});

src/index.ts

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,26 @@ import { State } from "./state.js";
88
import { registerAtlasTools } from "./tools/atlas/tools.js";
99
import { registerMongoDBTools } from "./tools/mongodb/index.js";
1010

11-
export async function runServer() {
12-
try {
13-
const state = new State();
14-
await state.loadCredentials();
11+
try {
12+
const state = new State();
13+
await state.loadCredentials();
1514

16-
const apiClient = ApiClient.fromState(state);
15+
const apiClient = ApiClient.fromState(state);
1716

18-
const mcp = new McpServer({
19-
name: "MongoDB Atlas",
20-
version: config.version,
21-
});
17+
const mcp = new McpServer({
18+
name: "MongoDB Atlas",
19+
version: config.version,
20+
});
2221

23-
mcp.server.registerCapabilities({ logging: {} });
22+
mcp.server.registerCapabilities({ logging: {} });
2423

25-
const transport = new StdioServerTransport();
26-
await mcp.server.connect(transport);
24+
const transport = new StdioServerTransport();
25+
await mcp.server.connect(transport);
2726

28-
registerAtlasTools(mcp, state, apiClient);
29-
registerMongoDBTools(mcp, state);
30-
} catch (error) {
31-
logger.emergency(mongoLogId(1_000_004), "server", `Fatal error running server: ${error}`);
27+
registerAtlasTools(mcp, state, apiClient);
28+
registerMongoDBTools(mcp, state);
29+
} catch (error) {
30+
logger.emergency(mongoLogId(1_000_004), "server", `Fatal error running server: ${error}`);
3231

33-
process.exit(1);
34-
}
32+
process.exit(1);
3533
}
36-
37-
runServer();

tests/unit/index.test.ts

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,14 @@
11
import { describe, it } from "@jest/globals";
2-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3-
4-
// mock the StdioServerTransport
5-
jest.mock("@modelcontextprotocol/sdk/server/stdio");
6-
// mock Server class and its methods
7-
jest.mock("../../src/server.ts", () => {
8-
return {
9-
Server: jest.fn().mockImplementation(() => {
10-
return {
11-
connect: jest.fn().mockImplementation((transport) => {
12-
return new Promise((resolve) => {
13-
resolve(transport);
14-
});
15-
}),
16-
};
17-
}),
18-
};
19-
});
2+
import { State } from "../../src/state";
203

214
describe("Server initialization", () => {
22-
it("should create a server instance", async () => {
23-
await expect(StdioServerTransport).toHaveBeenCalled();
5+
it("should define a default state", async () => {
6+
const state = new State();
7+
8+
expect(state.credentials).toEqual({
9+
auth: {
10+
status: "not_auth",
11+
},
12+
});
2413
});
2514
});

0 commit comments

Comments
 (0)