Skip to content

Commit 40076ed

Browse files
committed
fix: refresh token
1 parent 55be4be commit 40076ed

File tree

7 files changed

+92
-64
lines changed

7 files changed

+92
-64
lines changed

dist/atlas-api.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ export async function callAtlasAPI(endpoint, method = 'GET', body) {
2727
headers: {
2828
'Authorization': `Bearer ${token}`,
2929
'Content-Type': 'application/json',
30-
'Accept': 'application/vnd.atlas.2025-04-07+json'
30+
'Accept': 'application/vnd.atlas.2025-04-07+json',
31+
credentials: 'include',
3132
},
3233
body: body ? JSON.stringify(body) : undefined,
3334
});

dist/auth.js

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const __dirname = path.dirname(__filename);
99
const wait = (milliseconds) => {
1010
return new Promise(resolve => setTimeout(resolve, milliseconds));
1111
};
12-
const TOKEN_FILE = path.resolve(__dirname, "token.json");
12+
const TOKEN_FILE = process.env.TOKEN_FILE || path.resolve(__dirname, "token.json");
1313
export const authState = {
1414
deviceCode: "",
1515
verificationUri: "",
@@ -95,15 +95,11 @@ export async function pollToken() {
9595
throw new Error("Authentication timed out. Please restart the process.");
9696
}
9797
export function saveToken(token) {
98+
globalState.auth = true;
99+
authState.token = token;
98100
fs.writeFileSync(TOKEN_FILE, JSON.stringify({ token }));
99101
log("info", "Token saved to file.");
100102
}
101-
export function getToken() {
102-
if (!authState.token) {
103-
loadToken();
104-
}
105-
return authState.token;
106-
}
107103
function loadToken() {
108104
if (fs.existsSync(TOKEN_FILE)) {
109105
try {
@@ -129,19 +125,25 @@ export async function isAuthenticated() {
129125
if (globalState.auth) {
130126
return true;
131127
}
128+
const token = await getToken();
129+
return globalState.auth;
130+
}
131+
export async function getToken() {
132132
// Try to load token from file if not already loaded
133133
if (!authState.token) {
134134
loadToken();
135135
}
136136
if (!authState.token) {
137+
globalState.auth = false;
137138
log("info", "No token found after loading");
138-
return false;
139+
return null;
139140
}
140141
// Validate the existing token
141142
try {
142143
log("info", "Validating token...");
143144
if (validateToken(authState.token)) {
144-
return true;
145+
globalState.auth = true;
146+
return authState.token;
145147
}
146148
// If the token is invalid, attempt to refresh it
147149
log("info", "Token is invalid, refreshing...");
@@ -151,15 +153,15 @@ export async function isAuthenticated() {
151153
globalState.auth = true;
152154
log("info", "Token refreshed successfully.");
153155
saveToken(refreshedToken);
154-
return true;
156+
return refreshedToken;
155157
}
156158
log("error", "Failed to refresh token.");
157159
}
158160
catch (error) {
159161
log("error", `Error during token validation or refresh: ${error}`);
160162
}
161163
globalState.auth = false;
162-
return false;
164+
return null;
163165
}
164166
function validateToken(tokenData) {
165167
try {
@@ -169,7 +171,7 @@ function validateToken(tokenData) {
169171
}
170172
// If expiry is zero value (not set), consider token not expired (like in Go)
171173
if (!tokenData.expiry) {
172-
return true;
174+
return false;
173175
}
174176
// Match the Go code's expiryDelta concept (10 seconds)
175177
const expiryDelta = 10 * 1000; // 10 seconds in milliseconds
@@ -206,7 +208,10 @@ async function refreshToken(token) {
206208
});
207209
if (response.ok) {
208210
const data = await response.json();
209-
return data;
211+
const buf = Buffer.from(data.access_token.split('.')[1], 'base64').toString();
212+
const jwt = JSON.parse(buf);
213+
const expiry = new Date(jwt.exp * 1000);
214+
return { ...data, expiry };
210215
}
211216
}
212217
catch (error) {

dist/index.js

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ process.env.VERSION = "1.0.0";
77
// Initialize server with logging capability
88
const server = new McpServer({
99
name: "MongoDB Atlas",
10-
version: process.env.VERSION || "1.0.0",
10+
version: process.env.VERSION,
1111
capabilities: {
1212
logging: { enabled: true },
1313
tools: { listChanged: false }
@@ -19,24 +19,24 @@ server.server.registerCapabilities({
1919
tools: { enabled: true },
2020
});
2121
export function log(targetLevel, data) {
22-
// Convert objects to string for better logging
23-
const message = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
24-
// Always log to console as a fallback
25-
console[targetLevel === "debug" ? "log" : targetLevel](message);
26-
// Only attempt MCP logging after the server is connected
27-
if (server.server && server.server.transport) {
28-
try {
29-
// Use the underlying Server instance which has the sendLoggingMessage method
30-
server.server.sendLoggingMessage({
31-
level: targetLevel,
32-
data: message,
33-
});
34-
}
35-
catch (e) {
36-
// Just use console logging if MCP logging fails
37-
console.error("MCP logging failed, using console logging instead");
38-
}
39-
}
22+
console.error(`${targetLevel}: ${data}`);
23+
// // Convert objects to string for better logging
24+
// const message = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
25+
// // Always log to console as a fallback
26+
// console[targetLevel === "debug" ? "log" : targetLevel](message);
27+
// // Only attempt MCP logging after the server is connected
28+
// if (server.server && server.server.transport) {
29+
// try {
30+
// // Use the underlying Server instance which has the sendLoggingMessage method
31+
// server.server.sendLoggingMessage({
32+
// level: targetLevel,
33+
// data: message,
34+
// });
35+
// } catch (e) {
36+
// // Just use console logging if MCP logging fails
37+
// console.error("MCP logging failed, using console logging instead");
38+
// }
39+
// }
4040
}
4141
export const globalState = {
4242
auth: false,
@@ -46,6 +46,8 @@ defineTools(server, globalState);
4646
async function runServer() {
4747
const transport = new StdioServerTransport();
4848
await server.connect(transport);
49+
// const response = await callAtlasAPI<any>('/groups/6273e01999910271a6b56768/clusters');
50+
// console.error(response);
4951
}
5052
runServer().catch((error) => {
5153
console.error(`Fatal error running server:`, error);

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"build:addshebang": "echo '#!/usr/bin/env node' > dist/index2.js && cat dist/index.js >> dist/index2.js && mv dist/index2.js dist/index.js",
1010
"build:chmod": "chmod +x dist/index.js",
1111
"build": "npm run build:compile && npm run build:addshebang && npm run build:chmod",
12-
"inspect": "npm run build && npx @modelcontextprotocol/inspector -- node dist/index.js"
12+
"inspect": "npm run build && npx @modelcontextprotocol/inspector -- dist/index.js"
1313
},
1414
"license": "MIT",
1515
"devDependencies": {
@@ -21,5 +21,8 @@
2121
},
2222
"dependencies": {
2323
"@types/express": "^5.0.1"
24+
},
25+
"engines": {
26+
"node": ">=23.0.0"
2427
}
2528
}

src/atlas-api.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ export async function callAtlasAPI<T = any>(endpoint: string, method: string = '
6464
headers: {
6565
'Authorization': `Bearer ${token}`,
6666
'Content-Type': 'application/json',
67-
'Accept': 'application/vnd.atlas.2025-04-07+json'
67+
'Accept': 'application/vnd.atlas.2025-04-07+json',
68+
credentials: 'include',
6869
},
6970
body: body ? JSON.stringify(body) : undefined,
7071
});

src/auth.ts

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ const wait = (milliseconds: number): Promise<void> => {
1212
return new Promise(resolve => setTimeout(resolve, milliseconds));
1313
};
1414

15-
const TOKEN_FILE = path.resolve(__dirname, "token.json");
15+
16+
17+
const TOKEN_FILE = process.env.TOKEN_FILE || path.resolve(__dirname, "token.json");
1618

1719
export interface AuthState {
1820
deviceCode: string;
@@ -131,15 +133,12 @@ export async function pollToken() {
131133
}
132134

133135
export function saveToken(token: OAuthToken) {
136+
globalState.auth = true;
137+
authState.token = token;
134138
fs.writeFileSync(TOKEN_FILE, JSON.stringify({ token }));
135139
log("info", "Token saved to file.");
136140
}
137141

138-
export function getToken(): OAuthToken | undefined {
139-
if (!authState.token) {loadToken();}
140-
return authState.token;
141-
}
142-
143142
function loadToken(): { token?: OAuthToken } | undefined {
144143
if (fs.existsSync(TOKEN_FILE)) {
145144
try {
@@ -152,6 +151,7 @@ function loadToken(): { token?: OAuthToken } | undefined {
152151
return data;
153152
}
154153
log("info", "Token file exists but doesn't contain a valid token structure");
154+
155155
} catch (error) {
156156
log("error", `Error parsing token file: ${error}`);
157157
}
@@ -165,22 +165,29 @@ export async function isAuthenticated(): Promise<boolean> {
165165
if (globalState.auth) {
166166
return true;
167167
}
168+
169+
const token = await getToken();
170+
return globalState.auth;
171+
}
168172

173+
export async function getToken(): Promise<OAuthToken | null> {
169174
// Try to load token from file if not already loaded
170175
if (!authState.token) {
171176
loadToken();
172177
}
173178

174179
if (!authState.token) {
180+
globalState.auth = false;
175181
log("info", "No token found after loading");
176-
return false;
182+
return null;
177183
}
178184

179185
// Validate the existing token
180186
try {
181187
log("info", "Validating token...");
182188
if (validateToken(authState.token)) {
183-
return true;
189+
globalState.auth = true;
190+
return authState.token;
184191
}
185192

186193
// If the token is invalid, attempt to refresh it
@@ -191,15 +198,15 @@ export async function isAuthenticated(): Promise<boolean> {
191198
globalState.auth = true;
192199
log("info", "Token refreshed successfully.");
193200
saveToken(refreshedToken);
194-
return true;
201+
return refreshedToken;
195202
}
196203
log("error", "Failed to refresh token.");
197204
} catch (error) {
198205
log("error", `Error during token validation or refresh: ${error}`);
199206
}
200207

201208
globalState.auth = false;
202-
return false;
209+
return null;
203210
}
204211

205212
function validateToken(tokenData: OAuthToken): boolean {
@@ -211,7 +218,7 @@ function validateToken(tokenData: OAuthToken): boolean {
211218

212219
// If expiry is zero value (not set), consider token not expired (like in Go)
213220
if (!tokenData.expiry) {
214-
return true;
221+
return false;
215222
}
216223

217224
// Match the Go code's expiryDelta concept (10 seconds)
@@ -254,7 +261,12 @@ async function refreshToken(token: string): Promise<OAuthToken | null> {
254261

255262
if (response.ok) {
256263
const data = await response.json() as OAuthToken;
257-
return data;
264+
265+
const buf = Buffer.from(data.access_token.split('.')[1], 'base64').toString()
266+
const jwt = JSON.parse(buf);
267+
const expiry = new Date(jwt.exp * 1000);
268+
269+
return {...data, expiry};
258270
}
259271
} catch (error) {
260272
log("info", `Error refreshing token: ${error}`);

src/index.ts

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
22
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
33
import { defineTools } from "./tools.js";
4+
import { callAtlasAPI } from "./atlas-api.js";
45

56
// Set process env version
67
process.env.VERSION = "1.0.0";
@@ -22,25 +23,26 @@ server.server.registerCapabilities({
2223
});
2324

2425
export function log(targetLevel: "info" | "debug" | "error", data: string | unknown) {
25-
// Convert objects to string for better logging
26-
const message = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
26+
console.error(`${targetLevel}: ${data}`);
27+
// // Convert objects to string for better logging
28+
// const message = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
2729

28-
// Always log to console as a fallback
29-
console[targetLevel === "debug" ? "log" : targetLevel](message);
30+
// // Always log to console as a fallback
31+
// console[targetLevel === "debug" ? "log" : targetLevel](message);
3032

31-
// Only attempt MCP logging after the server is connected
32-
if (server.server && server.server.transport) {
33-
try {
34-
// Use the underlying Server instance which has the sendLoggingMessage method
35-
server.server.sendLoggingMessage({
36-
level: targetLevel,
37-
data: message,
38-
});
39-
} catch (e) {
40-
// Just use console logging if MCP logging fails
41-
console.error("MCP logging failed, using console logging instead");
42-
}
43-
}
33+
// // Only attempt MCP logging after the server is connected
34+
// if (server.server && server.server.transport) {
35+
// try {
36+
// // Use the underlying Server instance which has the sendLoggingMessage method
37+
// server.server.sendLoggingMessage({
38+
// level: targetLevel,
39+
// data: message,
40+
// });
41+
// } catch (e) {
42+
// // Just use console logging if MCP logging fails
43+
// console.error("MCP logging failed, using console logging instead");
44+
// }
45+
// }
4446
}
4547

4648
export interface GlobalState {
@@ -63,3 +65,5 @@ runServer().catch((error) => {
6365
console.error(`Fatal error running server:`, error);
6466
process.exit(1);
6567
});
68+
69+

0 commit comments

Comments
 (0)