congressmcp
Advanced tools
+15
-302
| #!/usr/bin/env node | ||
| /** | ||
| * Congressional MCP Bridge - Connects Claude Desktop to Congressional MCP Server | ||
| * Handles authentication and request forwarding | ||
| */ | ||
| console.error(` | ||
| ======================================== | ||
| CongressMCP has moved! | ||
| ======================================== | ||
| const https = require('https'); | ||
| const os = require('os'); | ||
| const path = require('path'); | ||
| const fs = require('fs'); | ||
| This NPM package is deprecated. | ||
| The server now runs locally via Python. | ||
| const SERVER_BASE = 'https://api-cmcp.lawgiver.ai'; | ||
| let sessionId = null; | ||
| let isInitialized = false; | ||
| Install the new version: | ||
| uvx congressmcp | ||
| // Get API key from environment variable (set by Claude Desktop) | ||
| const API_KEY = process.env.CONGRESSMCP_API_KEY; | ||
| Or with pip: | ||
| pip install congressmcp | ||
| 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); | ||
| } | ||
| Setup guide: | ||
| https://github.com/amurshak/congressMCP | ||
| ======================================== | ||
| `); | ||
| // 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); | ||
| }); | ||
| process.exit(1); |
+9
-8
| { | ||
| "name": "congressmcp", | ||
| "version": "1.2.0", | ||
| "description": "MCP bridge for Congressional data - access 42+ legislative tools through Claude Desktop and other MCP clients. Now with improved domain architecture.", | ||
| "version": "2.0.0", | ||
| "description": "DEPRECATED - Use uvx congressmcp instead", | ||
| "deprecated": "Use uvx congressmcp instead. See https://github.com/amurshak/congressMCP", | ||
| "main": "index.js", | ||
@@ -14,10 +15,10 @@ "bin": { | ||
| "keywords": [ | ||
| "mcp", | ||
| "mcp", | ||
| "model-context-protocol", | ||
| "congress", | ||
| "congressional", | ||
| "legislative", | ||
| "congress", | ||
| "congressional", | ||
| "legislative", | ||
| "bills", | ||
| "claude-desktop", | ||
| "api", | ||
| "api", | ||
| "government", | ||
@@ -54,2 +55,2 @@ "politics", | ||
| ] | ||
| } | ||
| } |
+11
-72
@@ -1,79 +0,18 @@ | ||
| # Congressional MCP | ||
| # congressmcp (DEPRECATED) | ||
| Connect Claude Desktop to US Congressional data - bills, amendments, members, committees, and more. | ||
| **This NPM package is no longer maintained.** CongressMCP v2.0.0 runs locally as a Python MCP server — no bridge, no API key, no hosted server needed. | ||
| ## Installation | ||
| ## Migration | ||
| ```bash | ||
| npm install -g congressmcp | ||
| ``` | ||
| 1. Uninstall: `npm uninstall -g congressmcp` | ||
| 2. Install the new local server: `uvx congressmcp` (requires Python 3.10+ and [uv](https://docs.astral.sh/uv/)) | ||
| 3. Get a free Congress.gov API key at [api.congress.gov/sign-up](https://api.congress.gov/sign-up/) | ||
| 4. See the setup guide: [github.com/amurshak/congressMCP](https://github.com/amurshak/congressMCP) | ||
| ## Setup | ||
| ## What Changed | ||
| 1. **Get your API key:** | ||
| - Visit: https://congressmcp.lawgiver.ai | ||
| - Sign up for a free account | ||
| - Check your email for your API key | ||
| CongressMCP was converted from a hosted SaaS service to a free, open-source, local-first MCP server. The NPM bridge that connected Claude Desktop to the hosted backend is no longer needed — the server now runs locally via stdio. | ||
| 2. **Configure Claude Desktop:** | ||
| ## License | ||
| Add this to your Claude Desktop config with your API key: | ||
| **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json` | ||
| **Windows:** `%APPDATA%\Claude\claude_desktop_config.json` | ||
| **Linux:** `~/.config/Claude/claude_desktop_config.json` | ||
| ```json | ||
| { | ||
| "mcpServers": { | ||
| "congressmcp": { | ||
| "command": "npx", | ||
| "args": [ | ||
| "-y", | ||
| "congressmcp" | ||
| ], | ||
| "env": { | ||
| "CONGRESSMCP_API_KEY": "your-api-key-here" | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
| 3. **Restart Claude Desktop** to activate the connection. | ||
| ## Usage | ||
| Ask Claude about: | ||
| - Bills: "Search for recent climate bills" | ||
| - Members: "Who represents California in the Senate?" | ||
| - Committees: "What committees handle healthcare?" | ||
| - Amendments: "Show amendments to HR 1234" | ||
| - Roll Call Votes: "How did senators vote on the infrastructure bill?" | ||
| - And much more legislative data! | ||
| ## Known Limitations | ||
| ⚠️ **Important Architectural Notice:** | ||
| Currently, all users share a single Congress.gov API key on the backend server. This means: | ||
| - **Rate Limits**: All users share the same 5,000 requests/hour quota from Congress.gov | ||
| - **Scalability**: May hit rate limits with many concurrent users | ||
| - **Architecture**: Not the ideal long-term solution | ||
| **Planned Fix (v2.0):** Future versions will require users to provide their own Congress.gov API key for individual rate limits and better scalability. This will require: | ||
| 1. Obtaining a free Congress.gov API key at: https://api.congress.gov/sign-up/ | ||
| 2. Adding `CONGRESS_GOV_API_KEY` to your Claude config | ||
| **Current Status:** This limitation doesn't affect functionality for early users, but please be aware that heavy usage may experience rate limiting. | ||
| ## Subscription Tiers | ||
| - **FREE**: 200 API calls/month, basic tools | ||
| - **PRO**: $29/month, 5,000 calls, all tools | ||
| - **ENTERPRISE**: Custom pricing, unlimited usage | ||
| ## Support | ||
| alex@lawgiver.ai | ||
| MIT |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
1
-66.67%4304
-71.57%16
-94.01%19
-75.95%