Skip to main content
Glama
sse.ts4.94 kB
import express from "express"; import axios from "axios"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { BearerAuthMiddlewareOptions, requireBearerAuth, } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js"; import { OAuthTokenVerifier } from "@modelcontextprotocol/sdk/server/auth/provider.js"; import { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js"; import { NAME, VERSION } from "../common"; import setupServer from "./setup_server"; const DEFAULT_CLIENT_ID = "hyperbrowser-server-client"; const DEFAULT_SCOPES: string[] = ["mcp:tools"]; // Define any default scopes for the static token if needed const API_KEY_VALIDATION_CACHE_DURATION_MS = 5 * 60 * 1000; // Cache validation for 5 minutes interface CachedApiKeyInfo { validatedAt: number; // We could store user details from /api/me here if needed in AuthInfo.extra // For now, just knowing it's valid is enough. } class MyTokenVerifier implements OAuthTokenVerifier { private validatedApiKeysCache = new Map<string, CachedApiKeyInfo>(); async verifyAccessToken(apiKey: string): Promise<AuthInfo> { if (!apiKey) { throw new Error("API key is missing"); } // Check cache first const cachedInfo = this.validatedApiKeysCache.get(apiKey); if ( cachedInfo && Date.now() - cachedInfo.validatedAt < API_KEY_VALIDATION_CACHE_DURATION_MS ) { return { token: apiKey, // Still use the original API key as the token clientId: DEFAULT_CLIENT_ID, scopes: DEFAULT_SCOPES, extra: { isSSE: true, // any other details from /api/me if stored in CachedApiKeyInfo }, }; } // If not in cache or stale, validate with /api/me try { const response = await axios.request({ method: "get", maxBodyLength: Infinity, url: "https://app.hyperbrowser.ai/api/me", headers: { "x-api-key": apiKey, }, }); if (response.status === 200) { // Store in cache this.validatedApiKeysCache.set(apiKey, { validatedAt: Date.now() }); return { token: apiKey, // Use the original API key clientId: DEFAULT_CLIENT_ID, scopes: DEFAULT_SCOPES, extra: { isSSE: true, // Potentially add user details from response.data if needed // userId: response.data.id, }, }; } else { // Should not happen if axios throws on non-2xx, but as a safeguard this.validatedApiKeysCache.delete(apiKey); // Remove if validation fails throw new Error( `API key validation failed with status: ${response.status}` ); } } catch (error: any) { this.validatedApiKeysCache.delete(apiKey); // Ensure removal on any error during validation if (axios.isAxiosError(error) && error.response) { throw new Error( `Invalid API key or validation service error (Status: ${error.response.status})` ); } else { throw new Error("Error during API key validation"); } } } } function setupSSE(app: express.Application, server: McpServer) { setupServer(server); console.log("Setting up SSE server"); let transport: SSEServerTransport; const authOptions: BearerAuthMiddlewareOptions = { verifier: new MyTokenVerifier(), }; // Check for command-line flags to determine if auth should be enabled const enableAuth = process.argv.includes("--sse") && process.argv.includes("--serve"); if (enableAuth) { console.log("SSE Authentication is ENABLED (flags --sse and --serve are present)."); app.get("/sse", requireBearerAuth(authOptions), async (req, res) => { transport = new SSEServerTransport("/messages", res); await server.connect(transport); }); app.post("/messages", requireBearerAuth(authOptions), async (req, res) => { if (!transport) { res.status(400).send("No transport found"); return; } await transport.handlePostMessage(req, res); }); } else { console.log("SSE Authentication is DISABLED (flags --sse and --serve are not both present)."); app.get("/sse", async (req, res) => { transport = new SSEServerTransport("/messages", res); await server.connect(transport); }); app.post("/messages", async (req, res) => { if (!transport) { res.status(400).send("No transport found"); return; } await transport.handlePostMessage(req, res); }); } } export async function createSSEServer() { const app = express(); const server = new McpServer( { name: NAME, version: VERSION, }, { capabilities: { resources: {}, }, } ); setupSSE(app, server); return app; }

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/BACH-AI-Tools/bach-hyperbrowser-mcp'

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