Skip to main content
Glama
https-transport.ts•9.43 kB
import express, { Request, Response } from 'express'; import https from 'https'; import http from 'http'; import fs from 'fs'; import path from 'path'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import { ConfigLoader } from '../config/config-loader.js'; import { OAuthServer } from '../auth/oauth-server.js'; import { TokenManager } from '../auth/token-manager.js'; import { UmbrellaMcpServer } from '../server.js'; import { debugLog } from '../utils/debug-logger.js'; export class HttpsTransport { private config = ConfigLoader.getInstance(); private oauthServer: OAuthServer; private tokenManager: TokenManager; private mcpServer: UmbrellaMcpServer; private app: express.Application; constructor(mcpServer: UmbrellaMcpServer) { this.mcpServer = mcpServer; this.oauthServer = new OAuthServer(); this.tokenManager = new TokenManager(); this.app = this.oauthServer.getApp(); this.setupStaticFiles(); this.setupMcpEndpoints(); } private setupStaticFiles(): void { // Serve static assets from public directory const publicPath = path.join(process.cwd(), 'public'); this.app.use('/assets', express.static(path.join(publicPath, 'assets'))); } private setupMcpEndpoints(): void { // Protected MCP endpoint - HEAD request for capability check this.app.head('/mcp', (req, res) => { if (!req.header('authorization')) { this.writeBearerChallenge(res); return res.status(401).end(); } return res.status(200).end(); }); // Unified MCP endpoint handler for both GET (SSE) and POST (JSON-RPC) const mcpHandler = async (req: Request, res: Response) => { const auth = req.header('authorization'); if (!auth) { this.writeBearerChallenge(res); if (req.method === 'POST') { const issuer = this.config.getConfig().auth.issuer; return res.status(401).json({ jsonrpc: '2.0', error: { code: -32000, message: 'Authentication required. Please authorize via OAuth.', data: { authorization_url: new URL('authorize', issuer).toString(), token_endpoint: new URL('oauth/token', issuer).toString() } }, id: req.body?.id ?? null }); } return res.status(401).end(); } try { // Verify token and extract Umbrella credentials const token = auth.replace(/^Bearer\s+/i, ''); const { payload, umbrellaAuth } = await this.tokenManager.verifyToken(token); // Check if token is expired if (this.tokenManager.isTokenExpired(payload.exp)) { this.writeBearerChallenge(res); if (req.method === 'POST') { return res.status(401).json({ jsonrpc: '2.0', error: { code: -32001, message: 'Token expired. Please re-authenticate.' }, id: req.body?.id ?? null }); } return res.status(401).end(); } // Set Umbrella auth for the MCP server session if (this.config.isDebugMode()) { console.error('[HTTPS-TRANSPORT] Setting auth from token for:', payload.sub); } const authResult = await this.mcpServer.getSessionManager().setAuthFromToken({ Authorization: umbrellaAuth.Authorization, userEmail: payload.sub, clientId: payload.clientId, userManagementInfo: umbrellaAuth.userManagementInfo // Pass the stored auth method from token }); if (this.config.isDebugMode()) { console.error('[HTTPS-TRANSPORT] Auth set result:', authResult); } // Get the MCP server instance const server = this.mcpServer.getServer(); // Create a new transport for this request debugLog.logState("Transport", "Step 1: Creating new StreamableHTTPServerTransport"); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined }); // Connect the server to the transport debugLog.logState("Transport", "Step 2: Connecting server to transport"); await server.connect(transport); debugLog.logState("Transport", "Step 3: Server connected successfully"); // Let the transport handle the request (both GET/SSE and POST) debugLog.logState("Transport", "Step 4: About to handle request"); await (transport as any).handleRequest(req, res, req.body); debugLog.logState("Transport", "Step 5: Request handled successfully"); } catch (error: any) { if (this.config.isDebugMode()) { console.error('[MCP] Error:', error.message); } this.writeBearerChallenge(res); if (req.method === 'POST') { return res.status(401).json({ jsonrpc: '2.0', error: { code: -32000, message: 'Invalid token' }, id: req.body?.id ?? null }); } return res.status(401).end(); } }; // Register both GET and POST handlers this.app.get('/mcp', mcpHandler); this.app.post('/mcp', mcpHandler); // Add root path welcome page this.app.get('/', (req, res) => { const issuer = this.config.getConfig().auth.issuer; // Read the welcome page content from file const welcomePagePath = path.join(process.cwd(), 'welcome-page-preview.html'); try { let htmlContent = fs.readFileSync(welcomePagePath, 'utf8'); // Replace placeholder URLs with dynamic ones htmlContent = htmlContent.replace(/https:\/\/welfare-adaptive-jade-biggest\.trycloudflare\.com/g, issuer.replace(/\/$/, '')); res.setHeader('Content-Type', 'text/html'); res.send(htmlContent); } catch (error) { // Fallback if file doesn't exist const mcpEndpoint = new URL('mcp', issuer).toString(); res.setHeader('Content-Type', 'text/html'); res.send(` <html> <head><title>Umbrella MCP Server</title></head> <body style="font-family: Arial, sans-serif; margin: 2rem; text-align: center;"> <h1>🌂 Umbrella MCP Server</h1> <p>Welcome to the Umbrella MCP Server for Claude Desktop integration!</p> <h2>MCP Endpoint:</h2> <code style="background: #f0f0f0; padding: 0.5rem; border-radius: 4px;">${mcpEndpoint}</code> </body> </html> `); } }); } private writeBearerChallenge(res: Response): void { const authConfig = this.config.getConfig().auth; const issuer = authConfig.issuer; const resourceUrl = new URL('mcp', issuer).toString(); const params = [ 'realm="umbrella-mcp"', 'scope="openid profile email"', 'error="invalid_token"', 'error_description="No bearer token on request"', `issuer="${issuer}"`, `resource="${resourceUrl}"`, `authorization_endpoint="${new URL('authorize', issuer)}"`, `token_endpoint="${new URL('oauth/token', issuer)}"`, `registration_endpoint="${new URL('register', issuer)}"` ]; res.setHeader('WWW-Authenticate', `Bearer ${params.join(', ')}`); } async start(): Promise<void> { const serverConfig = this.config.getConfig().server; const port = serverConfig.port; // Check if SSL certificates exist const hasSSLCerts = fs.existsSync(serverConfig.httpsOptions.keyPath) && fs.existsSync(serverConfig.httpsOptions.certPath); let server: https.Server | http.Server; let protocol: string; if (hasSSLCerts) { // Use HTTPS if certificates are available server = https.createServer({ key: fs.readFileSync(serverConfig.httpsOptions.keyPath), cert: fs.readFileSync(serverConfig.httpsOptions.certPath) }, this.app); protocol = 'HTTPS'; } else { // Fallback to HTTP if no certificates console.log('[HTTP] SSL certificates not found, running in HTTP mode'); console.log('[HTTP] For HTTPS mode, run: openssl req -x509 -newkey rsa:4096 -keyout certs/server-key.pem -out certs/server-cert.pem -days 365 -nodes -subj "/CN=localhost"'); server = http.createServer(this.app); protocol = 'HTTP'; } server.listen(port, () => { const issuer = this.config.getConfig().auth.issuer; const mode = serverConfig.production ? 'PRODUCTION' : 'DEVELOPMENT'; const scheme = hasSSLCerts ? 'https' : 'http'; console.log(`[${protocol}] MCP OAuth server running in ${mode} mode on port ${port} (${protocol})`); console.log(`[${protocol}] Local server: ${scheme}://127.0.0.1:${port}/`); if (this.config.getConfig().cloudflare.currentTunnelUrl) { console.log(`[${protocol}] Tunnel URL: ${this.config.getConfig().cloudflare.currentTunnelUrl}`); console.log(`[${protocol}] OAuth endpoints: ${issuer}`); console.log(`[${protocol}] MCP endpoint: ${new URL('mcp', issuer)}`); } else if (serverConfig.production) { console.log(`[${protocol}] OAuth endpoints: ${issuer}`); console.log(`[${protocol}] MCP endpoint: ${new URL('mcp', issuer)}`); } }); } }

Latest Blog Posts

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/daviddraiumbrella/invoice-monitoring'

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