Skip to main content
Glama

MCPMan

by semistrict
callback-server.ts5.9 kB
import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http"; import { URL } from "node:url"; export interface OAuthCallbackResult { code?: string; error?: string; error_description?: string; state?: string; } export class OAuthCallbackServer { private server?: Server; private port: number; constructor(port: number = 8080) { this.port = port; } async start(): Promise<string> { return new Promise((resolve, reject) => { this.server = createServer((req, res) => { // Handle favicon requests if (req.url === "/favicon.ico") { res.writeHead(404); res.end(); return; } // Parse callback URL const url = new URL(req.url || "", `http://localhost:${this.port}`); if (url.pathname === "/oauth/callback") { this.handleCallback(req, res, url); } else { res.writeHead(404, { "Content-Type": "text/html; charset=utf-8" }); res.end(` <html> <body> <h1>MCPMan OAuth Callback</h1> <p>This endpoint is only for OAuth callbacks.</p> </body> </html> `); } }); this.server.listen(this.port, () => { const callbackUrl = `http://localhost:${this.port}/oauth/callback`; resolve(callbackUrl); }); this.server.on("error", (error) => { reject(error); }); }); } private handleCallback(_req: IncomingMessage, res: ServerResponse, url: URL): void { const code = url.searchParams.get("code"); const error = url.searchParams.get("error"); const errorDescription = url.searchParams.get("error_description"); const state = url.searchParams.get("state"); if (code) { console.log(`✅ OAuth authorization successful`); res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }); res.end(` <html> <head> <title>Authorization Successful</title> <style> body { font-family: Arial, sans-serif; text-align: center; padding: 50px; } .success { color: #28a745; } .container { max-width: 500px; margin: 0 auto; } </style> </head> <body> <div class="container"> <h1 class="success">✅ Authorization Successful!</h1> <p>MCPMan has received the authorization code.</p> <p>You can close this window and return to the terminal.</p> <script> // Auto-close window after 3 seconds setTimeout(() => { window.close(); }, 3000); </script> </div> </body> </html> `); // Store the authorization code for retrieval this.authorizationCode = code || undefined; this.authorizationState = state || undefined; } else if (error) { console.error(`❌ OAuth authorization failed: ${error}`); if (errorDescription) { console.error(` Description: ${errorDescription}`); } res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" }); res.end(` <html> <head> <title>Authorization Failed</title> <style> body { font-family: Arial, sans-serif; text-align: center; padding: 50px; } .error { color: #dc3545; } .container { max-width: 500px; margin: 0 auto; } </style> </head> <body> <div class="container"> <h1 class="error">❌ Authorization Failed</h1> <p><strong>Error:</strong> ${error}</p> ${errorDescription ? `<p><strong>Description:</strong> ${errorDescription}</p>` : ""} <p>Please close this window and check the terminal for instructions.</p> </div> </body> </html> `); this.authorizationError = error || undefined; this.authorizationErrorDescription = errorDescription || undefined; } else { res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" }); res.end(` <html> <body> <h1>Invalid OAuth Callback</h1> <p>No authorization code or error received.</p> </body> </html> `); } } // Storage for authorization results private authorizationCode?: string; private authorizationState?: string; private authorizationError?: string; private authorizationErrorDescription?: string; async waitForCallback(timeoutMs: number = 300000): Promise<OAuthCallbackResult> { return new Promise((resolve, reject) => { const startTime = Date.now(); const checkResult = () => { if (this.authorizationCode) { resolve({ code: this.authorizationCode, state: this.authorizationState, }); return; } if (this.authorizationError) { resolve({ error: this.authorizationError, error_description: this.authorizationErrorDescription, }); return; } if (Date.now() - startTime > timeoutMs) { reject(new Error("OAuth callback timeout")); return; } // Check again in 100ms setTimeout(checkResult, 100); }; checkResult(); }); } async stop(): Promise<void> { return new Promise((resolve) => { if (this.server) { this.server.close(() => { resolve(); }); } else { resolve(); } }); } reset(): void { this.authorizationCode = undefined; this.authorizationState = undefined; this.authorizationError = undefined; this.authorizationErrorDescription = undefined; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/semistrict/mcpman'

If you have feedback or need assistance with the MCP directory API, please join our Discord server