Skip to main content
Glama

CongressMCP-bridge

index.js9.8 kB
#!/usr/bin/env node /** * Congressional MCP Bridge - Connects Claude Desktop to Congressional MCP Server * Handles authentication and request forwarding */ const https = require('https'); const os = require('os'); const path = require('path'); const fs = require('fs'); const SERVER_BASE = 'https://api-cmcp.lawgiver.ai'; let sessionId = null; let isInitialized = false; // Get API key from environment variable (set by Claude Desktop) const API_KEY = process.env.CONGRESSMCP_API_KEY; if (!API_KEY) { console.error('🚫 ERROR: CONGRESSMCP_API_KEY environment variable not set\n'); console.error('📋 SETUP INSTRUCTIONS:'); console.error('1. Get your API key at: https://congressmcp.lawgiver.ai'); console.error('2. Add to Claude Desktop config:'); console.error(' {'); console.error(' "mcpServers": {'); console.error(' "congressmcp": {'); console.error(' "command": "npx",'); console.error(' "args": ["-y", "congressmcp"],'); console.error(' "env": {'); console.error(' "CONGRESSMCP_API_KEY": "your-api-key-here"'); console.error(' }'); console.error(' }'); console.error(' }'); console.error(' }'); console.error('3. Restart Claude Desktop\n'); console.error('📧 Need help? Email: support@congressmcp.lawgiver.ai'); process.exit(1); } // Simple in-memory session store for the bridge process class SessionManager { constructor() { this.sessionId = null; this.isInitialized = false; } async ensureInitialized() { if (this.isInitialized && this.sessionId) { return; } console.error(`DEBUG: Initializing new MCP session with authentication...`); // Step 1: Send initialize const initMessage = { jsonrpc: "2.0", id: Date.now(), // Use timestamp as unique ID method: "initialize", params: { protocolVersion: "2024-11-05", capabilities: { roots: { listChanged: true }, sampling: {} }, clientInfo: { name: "congressmcp-bridge", version: "1.1.0" } } }; const initResponse = await this.makeRequest(initMessage, false); if (initResponse && initResponse.result) { // Step 2: Send initialized notification const notificationMessage = { jsonrpc: "2.0", method: "notifications/initialized" }; await this.makeRequest(notificationMessage, true); this.isInitialized = true; console.error(`DEBUG: MCP session fully initialized with ID: ${this.sessionId}`); } else { throw new Error(`Initialize failed: ${JSON.stringify(initResponse)}`); } } makeRequest(message, useSession = true) { return new Promise((resolve, reject) => { const data = JSON.stringify(message); const headers = { 'Content-Type': 'application/json', 'Accept': 'application/json, text/event-stream', 'Content-Length': Buffer.byteLength(data), 'User-Agent': 'congressmcp-bridge/1.1.0', 'Authorization': `Bearer ${API_KEY}` }; if (useSession && this.sessionId) { headers['MCP-Session-ID'] = this.sessionId; } console.error(`DEBUG: Authenticated request to /mcp/`); const req = https.request(`${SERVER_BASE}/mcp/`, { method: 'POST', headers: headers, timeout: 30000 }, (res) => { // Extract or update session ID if (res.headers['mcp-session-id']) { this.sessionId = res.headers['mcp-session-id']; } let responseData = ''; res.on('data', (chunk) => responseData += chunk); res.on('end', () => { console.error(`DEBUG: Response status: ${res.statusCode}`); console.error(`DEBUG: Response length: ${responseData.length} chars`); if (res.statusCode !== 200 && res.statusCode !== 202) { reject(new Error(`HTTP ${res.statusCode}: ${responseData}`)); return; } // Handle SSE format if (res.headers['content-type']?.includes('text/event-stream')) { const lines = responseData.split('\n'); let jsonData = null; for (const line of lines) { const trimmed = line.trim(); if (trimmed.startsWith('data: ')) { const data = trimmed.substring(6).trim(); if (data && data !== '[DONE]' && data !== '') { try { jsonData = JSON.parse(data); console.error(`DEBUG: Parsed response data successfully`); break; } catch (e) { console.error(`DEBUG: Failed to parse: "${data}"`); } } } } if (jsonData) { resolve(jsonData); } else { // For notifications, empty response is OK (HTTP 202) if (message.method && message.method.startsWith('notifications/')) { console.error(`DEBUG: Empty response for notification (HTTP ${res.statusCode} - expected)`); resolve(null); } else if (res.statusCode === 202) { console.error(`DEBUG: HTTP 202 response - operation accepted`); resolve(null); } else { console.error(`DEBUG: No JSON found in SSE response`); console.error(`DEBUG: Full response: "${responseData}"`); reject(new Error(`No valid JSON data in response`)); } } } else { // Regular JSON response if (responseData.trim() === '') { // Empty response - check if this is expected if (message.method && message.method.startsWith('notifications/')) { console.error(`DEBUG: Empty response for notification (expected)`); resolve(null); } else if (res.statusCode === 202) { console.error(`DEBUG: HTTP 202 with empty body (accepted)`); resolve(null); } else { reject(new Error(`Unexpected empty response`)); } } else { try { const jsonResponse = JSON.parse(responseData); resolve(jsonResponse); } catch (e) { reject(new Error(`Invalid JSON: ${responseData}`)); } } } }); }); req.on('error', reject); req.on('timeout', () => { req.destroy(); reject(new Error('Request timeout')); }); req.write(data); req.end(); }); } async sendMessage(message) { try { // Special handling for initialize if (message.method === 'initialize') { this.sessionId = null; this.isInitialized = false; await this.ensureInitialized(); // Return a successful initialize response return { jsonrpc: "2.0", id: message.id, result: { protocolVersion: "2024-11-05", capabilities: { experimental: {}, prompts: { listChanged: false }, resources: { subscribe: false, listChanged: false }, tools: { listChanged: false } }, serverInfo: { name: "Congress MCP", version: "1.9.2" } } }; } // For other requests, ensure we're initialized await this.ensureInitialized(); // Send the actual request return await this.makeRequest(message, true); } catch (error) { console.error(`DEBUG: sendMessage error: ${error.message}`); return { jsonrpc: "2.0", id: message.id !== undefined ? message.id : null, error: { code: -32603, message: error.message } }; } } } // Create global session manager const sessionManager = new SessionManager(); // Process multiple stdin inputs without reinitializing let inputBuffer = ''; // Main stdio handler async function main() { console.error(`DEBUG: Congressional MCP Bridge with persistent sessions starting`); process.stdin.on('data', async (data) => { inputBuffer += data.toString(); // Process complete lines let lines = inputBuffer.split('\n'); inputBuffer = lines.pop() || ''; // Keep incomplete line in buffer for (const line of lines) { if (!line.trim()) continue; try { const message = JSON.parse(line.trim()); console.error(`DEBUG: Processing: ${message.method || 'notification'} (ID: ${message.id})`); const response = await sessionManager.sendMessage(message); if (response !== undefined && response !== null) { console.log(JSON.stringify(response)); } } catch (error) { console.error(`DEBUG: Parse error: ${error.message}`); console.log(JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32700, message: `Parse error: ${error.message}` } })); } } }); process.stdin.resume(); process.stdin.setEncoding('utf8'); process.on('SIGINT', () => { console.error('DEBUG: Bridge shutting down'); process.exit(0); }); process.on('SIGTERM', () => { console.error('DEBUG: Bridge shutting down'); process.exit(0); }); } main().catch(error => { console.error(`Bridge failed to start: ${error.message}`); process.exit(1); });

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/amurshak/congressmcp-bridge'

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