Files
at-mintel/packages/glitchtip-mcp/src/index.ts
Marc Mintel 8486261555
All checks were successful
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 1s
Monorepo Pipeline / 🧪 Test (push) Successful in 1m1s
Monorepo Pipeline / 🏗️ Build (push) Successful in 2m44s
Monorepo Pipeline / 🧹 Lint (push) Successful in 2m55s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been skipped
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been skipped
fix(mcp): refactor all mcp servers to use multi-session sse transport
2026-03-10 13:32:16 +01:00

190 lines
6.7 KiB
TypeScript

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express, { Request, Response } from 'express';
import crypto from 'crypto';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from "@modelcontextprotocol/sdk/types.js";
import axios from "axios";
import https from "https";
const GLITCHTIP_BASE_URL = process.env.GLITCHTIP_BASE_URL || "https://glitchtip.infra.mintel.me";
const GLITCHTIP_API_KEY = process.env.GLITCHTIP_API_KEY;
if (!GLITCHTIP_API_KEY) {
console.error("Warning: GLITCHTIP_API_KEY is not set. API calls will fail.");
}
const httpsAgent = new https.Agent({
rejectUnauthorized: false, // For internal infra
});
const glitchtipClient = axios.create({
baseURL: `${GLITCHTIP_BASE_URL}/api/0`,
headers: { Authorization: `Bearer ${GLITCHTIP_API_KEY}` },
httpsAgent
});
const LIST_PROJECTS_TOOL: Tool = {
name: "glitchtip_list_projects",
description: "List all projects and organizations in GlitchTip",
inputSchema: { type: "object", properties: {} },
};
const LIST_ISSUES_TOOL: Tool = {
name: "glitchtip_list_issues",
description: "List issues (errors) for a specific project",
inputSchema: {
type: "object",
properties: {
organization_slug: { type: "string", description: "The organization slug" },
project_slug: { type: "string", description: "The project slug" },
query: { type: "string", description: "Optional query filter (e.g., 'is:unresolved')" },
limit: { type: "number", description: "Maximum number of issues to return (default: 20)" },
},
required: ["organization_slug", "project_slug"],
},
};
const GET_ISSUE_DETAILS_TOOL: Tool = {
name: "glitchtip_get_issue_details",
description: "Get detailed information about a specific issue, including stack trace",
inputSchema: {
type: "object",
properties: {
issue_id: { type: "string", description: "The ID of the issue" },
},
required: ["issue_id"],
},
};
const UPDATE_ISSUE_TOOL: Tool = {
name: "glitchtip_update_issue",
description: "Update the status of an issue (e.g., resolve it)",
inputSchema: {
type: "object",
properties: {
issue_id: { type: "string", description: "The ID of the issue" },
status: { type: "string", enum: ["resolved", "unresolved", "ignored"], description: "The new status" },
},
required: ["issue_id", "status"],
},
};
const server = new Server(
{ name: "glitchtip-mcp", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
LIST_PROJECTS_TOOL,
LIST_ISSUES_TOOL,
GET_ISSUE_DETAILS_TOOL,
UPDATE_ISSUE_TOOL,
],
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "glitchtip_list_projects") {
try {
const res = await glitchtipClient.get('/projects/');
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
} catch (e: any) {
return { isError: true, content: [{ type: "text", text: `Error: ${e.message}` }] };
}
}
if (request.params.name === "glitchtip_list_issues") {
const { organization_slug, project_slug, query, limit = 20 } = request.params.arguments as any;
try {
const res = await glitchtipClient.get(`/projects/${organization_slug}/${project_slug}/issues/`, {
params: { query, limit }
});
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
} catch (e: any) {
return { isError: true, content: [{ type: "text", text: `Error: ${e.message}` }] };
}
}
if (request.params.name === "glitchtip_get_issue_details") {
const { issue_id } = request.params.arguments as any;
try {
const res = await glitchtipClient.get(`/issues/${issue_id}/`);
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
} catch (e: any) {
return { isError: true, content: [{ type: "text", text: `Error: ${e.message}` }] };
}
}
if (request.params.name === "glitchtip_update_issue") {
const { issue_id, status } = request.params.arguments as any;
try {
const res = await glitchtipClient.put(`/issues/${issue_id}/`, { status });
return { content: [{ type: "text", text: `Issue ${issue_id} status updated to ${status}.` }] };
} catch (e: any) {
return { isError: true, content: [{ type: "text", text: `Error: ${e.message}` }] };
}
}
throw new Error(`Unknown tool: ${request.params.name}`);
});
async function run() {
const isStdio = process.argv.includes('--stdio');
if (isStdio) {
const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('GlitchTip MCP server is running on stdio');
} else {
const app = express();
const transports = new Map<string, SSEServerTransport>();
app.use((req, _res, next) => {
console.error(`${req.method} ${req.url}`);
next();
});
app.get('/sse', async (req, res) => {
const sessionId = crypto.randomUUID();
console.error(`New SSE connection: ${sessionId}`);
const transport = new SSEServerTransport(`/message/${sessionId}`, res);
transports.set(sessionId, transport);
req.on('close', () => {
console.error(`SSE connection closed: ${sessionId}`);
transports.delete(sessionId);
});
await server.connect(transport);
});
app.post('/message/:sessionId', async (req: Request, res: Response) => {
const sessionId = req.params.sessionId;
const transport = transports.get(sessionId as string);
if (!transport) {
console.error(`No transport found for session: ${sessionId}`);
res.status(400).send('No active SSE connection for this session');
return;
}
await transport.handlePostMessage(req, res);
});
const PORT = process.env.GLITCHTIP_MCP_PORT || 3005;
app.listen(PORT, () => {
console.error(`GlitchTip MCP server running on http://localhost:${PORT}/sse`);
});
}
}
run().catch((err) => {
console.error("Fatal error:", err);
process.exit(1);
});