import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js"; import express from "express"; import axios from "axios"; import dotenv from "dotenv"; dotenv.config(); const app = express(); app.use(express.json()); const GITLAB_TOKEN = process.env.GITLAB_TOKEN; const MCP_REMOTE_KEY = process.env.MCP_REMOTE_KEY; const gitlab = axios.create({ baseURL: "https://gitlab.com/api/v4", headers: { "PRIVATE-TOKEN": GITLAB_TOKEN } }); // Initialize server once const server = new Server({ name: "gitlab-mcp", version: "1.0.0", }, { capabilities: { tools: {} } }); // Tool Discovery server.setRequestHandler(ListToolsRequestSchema, async () => { console.log("📋 Tool discovery requested."); return { tools: [{ name: "create_issue", description: "Create a new issue in a GitLab project", inputSchema: { type: "object", properties: { project_id: { type: "number" }, title: { type: "string" }, description: { type: "string" } }, required: ["project_id", "title"] } }] }; }); // Tool Execution server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; console.log(`đŸ› ī¸ EXECUTING: ${name}`); if (name === "create_issue") { try { const res = await gitlab.post(`/projects/${args.project_id}/issues`, { title: args.title, description: args.description }); console.log("✅ Issue created!"); return { content: [{ type: "text", text: `Success! Created: ${res.data.web_url}` }] }; } catch (err) { console.error("❌ GitLab Error:", err.message); return { isError: true, content: [{ type: "text", text: err.message }] }; } } }); let transport; app.all("/sse", async (req, res) => { const apiKey = req.header("x-api-key"); if (!apiKey || apiKey !== MCP_REMOTE_KEY) { console.warn("đŸšĢ Unauthorized."); return res.status(401).send("Unauthorized"); } // --- CRITICAL FIX: If transport exists, close it or clear it --- if (transport) { console.log("🔄 Cleaning up old transport..."); transport = null; } console.log("🚀 Establishing new SSE Handshake..."); transport = new SSEServerTransport("/messages", res); try { // We use a separate logic to ensure we don't 're-connect' the same server instance // if it's already in a connected state. await server.connect(transport); console.log("✅ Connection Ready."); } catch (err) { // If it says "Already connected", we just ignore it and keep going console.log("â„šī¸ Server session maintained."); } }); app.post("/messages", async (req, res) => { if (transport) { await transport.handlePostMessage(req, res); } else { res.status(400).send("No active session."); } }); app.listen(3000, () => console.log("✅ Server live on 3000"));