Skip to content

Commit 9a376ac

Browse files
blvafmenezes
authored andcommitted
working code
1 parent c847762 commit 9a376ac

File tree

8 files changed

+522
-47
lines changed

8 files changed

+522
-47
lines changed

.github/prompts/mcp-dev.prompt.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
You will use all your knowledge to implement the Atlas MCP server.
2+
To understand how to develop the MCP server, you also need some context about Atlas.
3+
4+
1. https://github.com/mongodb/atlas-sdk-go is an auto-generated Atlas Golang SDK
5+
2. https://github.com/mongodb/openapi is a great open source of truth to the Atlas Admin API, https://github.com/mongodb/openapi/blob/main/openapi/v2/openapi-2025-03-12.yaml contains the latest available API documentation.
6+
3. https://github.com/mongodb/mongodb-atlas-cli is a great open source of truth for how to autogenerate code from the OpenAPI spec.
7+
4. https://github.com/mongodb/mongodb-atlas-cli/tree/master/internal/cli/authis a great open source of truth for how to implement authentication.

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
node_modules
2-
.vscode/mcp.json
2+
.vscode/mcp.json
3+
4+
# Environment variables
5+
.env

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,15 @@ npm install
1919
npm run build
2020
```
2121

22-
## Troubleshooting
22+
## Troubleshooting (Via VSCode Insiders)
2323

24+
### Restart server
25+
- Run `npm run build` to re-build the server if you made changes to the code
26+
- Press `Cmd + Shift + P` and type List MCP Servers
27+
- Select the MCP server you want to restart
28+
- Select the option to restart the server
29+
30+
### Logs
2431
To see MCP logs, check https://code.visualstudio.com/docs/copilot/chat/mcp-servers.
2532

2633
- Press `Cmd + Shift + P` and type List MCP Servers

dist/index.js

Lines changed: 140 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,151 @@
11
#!/usr/bin/env node
2-
"use strict";
3-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
4-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
5-
return new (P || (P = Promise))(function (resolve, reject) {
6-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
7-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
8-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
9-
step((generator = generator.apply(thisArg, _arguments || [])).next());
10-
});
11-
};
12-
Object.defineProperty(exports, "__esModule", { value: true });
13-
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
14-
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
2+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4+
import express from "express"; // Fixing type imports
5+
import dotenv from "dotenv";
6+
dotenv.config();
7+
// Replace require() with dynamic import for node-fetch
8+
const fetchDynamic = async () => (await import("node-fetch")).default;
159
function wait(milliseconds) {
1610
return new Promise(resolve => setTimeout(resolve, milliseconds));
1711
}
18-
const server = new mcp_js_1.McpServer({
12+
const server = new McpServer({
1913
name: "MongoDB Atlas",
2014
version: "1.0.0"
2115
});
16+
// Move clientId to a state variable
2217
var state = {
2318
auth: false,
19+
token: "", // Added token property
20+
deviceCode: "",
21+
verificationUri: "",
22+
userCode: "",
23+
clientId: process.env.CLIENT_ID || "0oabtxactgS3gHIR0297", // Moved clientId to state
2424
};
25-
server.tool("auth", "Authenticate to atlas", (_a) => __awaiter(void 0, [_a], void 0, function* ({}) {
26-
yield wait(1000);
27-
setTimeout(() => {
28-
console.error("Authenticated");
29-
state.auth = true;
30-
}, 10000);
25+
const app = express();
26+
let authCode = "";
27+
app.get("/callback", (req, res) => {
28+
authCode = req.query.code;
29+
res.send("Authentication successful! You can close this tab.");
30+
});
31+
// Update the device code request to align with the Atlas Go SDK
32+
server.tool("auth", "Authenticate to Atlas", async ({}) => {
33+
console.log("Starting authentication process...");
34+
const authUrl = "https://cloud.mongodb.com/api/private/unauth/account/device/authorize"; // Updated endpoint
35+
console.log("Client ID:", state.clientId);
36+
// Step 1: Request a device code
37+
const deviceCodeResponse = await (await fetchDynamic())(authUrl, {
38+
method: "POST",
39+
headers: {
40+
"Content-Type": "application/x-www-form-urlencoded",
41+
},
42+
body: new URLSearchParams({
43+
client_id: state.clientId, // Use state.clientId
44+
scope: "openid",
45+
}).toString(),
46+
});
47+
const responseText = await deviceCodeResponse.text(); // Capture the full response body
48+
console.log("Device Code Response Body:", responseText);
49+
if (!deviceCodeResponse.ok) {
50+
console.error("Failed to initiate authentication:", deviceCodeResponse.statusText);
51+
return {
52+
content: [{ type: "text", text: `Failed to initiate authentication: ${deviceCodeResponse.statusText}` }],
53+
};
54+
}
55+
const deviceCodeData = JSON.parse(responseText); // Parse the response body
56+
console.log(`Please authenticate by visiting the following URL: ${deviceCodeData.verification_uri}`);
57+
console.log(`Enter the code: ${deviceCodeData.user_code}`);
58+
// Store the device code data for further use
59+
state.deviceCode = deviceCodeData.device_code;
60+
state.verificationUri = deviceCodeData.verification_uri;
61+
state.userCode = deviceCodeData.user_code;
3162
return {
32-
content: [{ type: "text", text: "Navigate to http://cloud.mongodb.com and input code A56T88." }],
63+
content: [
64+
{ type: "text", text: `Please authenticate by visiting ${deviceCodeData.verification_uri} and entering the code ${deviceCodeData.user_code}` },
65+
],
3366
};
34-
}));
35-
server.tool("list-clusters", "Lists clusters", (_a) => __awaiter(void 0, [_a], void 0, function* ({}) {
36-
yield wait(1000);
67+
});
68+
// Add PollToken functionality to the auth tool
69+
server.tool("poll-token", "Poll for Access Token", async ({}) => {
70+
console.log("Starting token polling process...");
71+
if (!state.deviceCode) {
72+
console.error("Device code not found. Please initiate authentication first.");
73+
return {
74+
content: [{ type: "text", text: "Device code not found. Please initiate authentication first." }],
75+
};
76+
}
77+
const tokenEndpoint = "https://cloud.mongodb.com/api/private/unauth/account/device/token";
78+
const interval = 5 * 1000; // Default polling interval in milliseconds
79+
const expiresAt = Date.now() + 15 * 60 * 1000; // Assume 15 minutes expiration for the device code
80+
while (Date.now() < expiresAt) {
81+
await wait(interval);
82+
try {
83+
const tokenResponse = await (await fetchDynamic())(tokenEndpoint, {
84+
method: "POST",
85+
headers: {
86+
"Content-Type": "application/x-www-form-urlencoded",
87+
},
88+
body: new URLSearchParams({
89+
client_id: state.clientId, // Use state.clientId
90+
device_code: state.deviceCode,
91+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
92+
}).toString(),
93+
});
94+
const responseText = await tokenResponse.text();
95+
console.log("Token Response Body:", responseText);
96+
if (tokenResponse.ok) {
97+
const tokenData = JSON.parse(responseText);
98+
console.log("Authentication successful. Token received:", tokenData.access_token);
99+
// Store the token
100+
state.auth = true;
101+
state.token = tokenData.access_token;
102+
return {
103+
content: [{ type: "text", text: "Authentication successful! You are now logged in." }],
104+
};
105+
}
106+
else {
107+
console.error("Token Response Error:", responseText);
108+
const errorResponse = JSON.parse(responseText);
109+
if (errorResponse.error === "authorization_pending") {
110+
console.log("Authorization pending. Retrying...");
111+
continue;
112+
}
113+
else if (errorResponse.error === "expired_token") {
114+
console.error("Device code expired. Please restart the authentication process.");
115+
return {
116+
content: [{ type: "text", text: "Device code expired. Please restart the authentication process." }],
117+
};
118+
}
119+
else {
120+
console.error("Failed to authenticate:", errorResponse.error_description || "Unknown error");
121+
return {
122+
content: [{ type: "text", text: `Failed to authenticate: ${errorResponse.error_description || "Unknown error"}` }],
123+
};
124+
}
125+
}
126+
}
127+
catch (error) {
128+
if (error instanceof Error) {
129+
console.error("Unexpected error during token polling:", error);
130+
return {
131+
content: [{ type: "text", text: `Unexpected error during token polling: ${error.message}` }],
132+
};
133+
}
134+
else {
135+
console.error("Unexpected non-Error object during token polling:", error);
136+
return {
137+
content: [{ type: "text", text: "Unexpected error during token polling." }],
138+
};
139+
}
140+
}
141+
}
142+
console.error("Authentication timed out. Please restart the process.");
143+
return {
144+
content: [{ type: "text", text: "Authentication timed out. Please restart the process." }],
145+
};
146+
});
147+
server.tool("list-clusters", "Lists clusters", async ({}) => {
148+
await wait(1000);
37149
if (!state.auth) {
38150
return {
39151
content: [{ type: "text", text: "Not authenticated" }],
@@ -50,12 +162,10 @@ server.tool("list-clusters", "Lists clusters", (_a) => __awaiter(void 0, [_a], v
50162
return {
51163
content: [{ type: "text", text }],
52164
};
53-
}));
54-
function runServer() {
55-
return __awaiter(this, void 0, void 0, function* () {
56-
const transport = new stdio_js_1.StdioServerTransport();
57-
yield server.connect(transport);
58-
});
165+
});
166+
async function runServer() {
167+
const transport = new StdioServerTransport();
168+
await server.connect(transport);
59169
}
60170
runServer().catch((error) => {
61171
console.error("Fatal error running server:", error);

0 commit comments

Comments
 (0)