UNPKG

sourcegraph-mcp-server

Version:

A Model Context Protocol (MCP) server that connects AI assistants to Sourcegraph code search with natural language capabilities

128 lines (110 loc) 4.36 kB
#!/usr/bin/env node import express from "express"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { createServer } from "./mcp-server"; import dotenv from 'dotenv'; // Load environment variables dotenv.config(); async function main() { console.log('Starting Sourcegraph MCP Server...'); console.log(`SOURCEGRAPH_URL: ${process.env.SOURCEGRAPH_URL ? 'Set' : 'NOT SET'}`); console.log(`SOURCEGRAPH_TOKEN: ${process.env.SOURCEGRAPH_TOKEN ? 'Set (redacted)' : 'NOT SET'}`); // Create the server with debug mode disabled to prevent console output const server = createServer(); // Create Express app const app = express(); const port = process.env.MCP_PORT || 3002; // Configure CORS app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.header('Access-Control-Allow-Headers', 'Content-Type'); if (req.method === 'OPTIONS') { res.sendStatus(200); } else { next(); } }); // Server-side connections const connections = new Map(); // Handle SSE connections app.get("/sse", async (req, res) => { // Generate a unique connection ID (UUID format) const sessionId = req.query.sessionId as string || `${Math.random().toString(36).substring(2, 15)}-${Date.now().toString(36)}`; try { console.log(`New SSE connection established with session ID: ${sessionId}`); // Create a new SSE transport const transport = new SSEServerTransport(`/messages?sessionId=${sessionId}`, res); connections.set(sessionId, transport); // Connect the server to this transport await server.connect(transport); console.log(`Server connected to transport for session ${sessionId}`); } catch (error: any) { console.error('Error establishing SSE connection:', error); // If headers already sent due to transport.start(), don't try to set them again if (!res.headersSent) { res.status(500).end(`Server error: ${error.message || 'Unknown error'}`); } } // Handle client disconnect req.on("close", () => { if (connections.has(sessionId)) { connections.delete(sessionId); console.log(`Client ${sessionId} disconnected`); } }); }); // Handle messages from client app.post("/messages", express.json(), async (req, res) => { try { // Extract sessionId from query parameters const sessionId = req.body.connectionId || req.query.sessionId as string; // Process message silently to avoid breaking JSON response if (!sessionId || !connections.has(sessionId)) { // If no sessionId or connection not found, try the first connection if (connections.size === 0) { return res.status(400).json({ error: "No active connections" }); } // Use the first available connection without logging to avoid breaking JSON const transport = connections.values().next().value; await transport.handlePostMessage(req, res, req.body); } else { // Use the specific connection for this session without logging const transport = connections.get(sessionId); await transport.handlePostMessage(req, res, req.body); } } catch (error: any) { console.error('Error handling message:', error); if (!res.headersSent) { res.status(500).json({ error: `Internal server error: ${error.message || 'Unknown error'}` }); } } }); // Health check endpoint app.get("/", (req, res) => { res.json({ name: "Sourcegraph MCP Server", version: "1.0.0", status: "running", tools: [ "echo", "search-code", "search-commits", "search-diffs", "debug" ] }); }); // Start the server app.listen(port, () => { console.log(`Sourcegraph MCP Server running on http://localhost:${port}`); console.log(`- Connect to /sse for server-sent events`); console.log(`- Send messages to /messages endpoint`); console.log(`- Available tools: echo, search-code, search-commits, search-diffs, debug`); }); } main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); });