feat(mcps): add glitchtip-mcp on port 3005
This commit is contained in:
25
packages/glitchtip-mcp/package.json
Normal file
25
packages/glitchtip-mcp/package.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "@mintel/glitchtip-mcp",
|
||||
"version": "1.9.10",
|
||||
"description": "GlitchTip Error Tracking MCP server for Mintel infrastructure",
|
||||
"main": "dist/index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "node dist/start.js",
|
||||
"dev": "tsx watch src/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.5.0",
|
||||
"axios": "^1.7.2",
|
||||
"dotenv": "^17.3.1",
|
||||
"express": "^5.2.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/node": "^20.14.10",
|
||||
"typescript": "^5.5.3",
|
||||
"tsx": "^4.19.2"
|
||||
}
|
||||
}
|
||||
157
packages/glitchtip-mcp/src/index.ts
Normal file
157
packages/glitchtip-mcp/src/index.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
||||
import express from 'express';
|
||||
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 app = express();
|
||||
let transport: SSEServerTransport | null = null;
|
||||
|
||||
app.get('/sse', async (req, res) => {
|
||||
transport = new SSEServerTransport('/message', res);
|
||||
await server.connect(transport);
|
||||
});
|
||||
|
||||
app.post('/message', async (req, res) => {
|
||||
if (!transport) { res.status(400).send('No active SSE connection'); 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);
|
||||
});
|
||||
13
packages/glitchtip-mcp/src/start.ts
Normal file
13
packages/glitchtip-mcp/src/start.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { config } from 'dotenv';
|
||||
import { resolve } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
|
||||
config({ path: resolve(__dirname, '../../../.env.local') });
|
||||
config({ path: resolve(__dirname, '../../../.env') });
|
||||
|
||||
import('./index.js').catch(err => {
|
||||
console.error('Failed to start GlitchTip MCP Server:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
16
packages/glitchtip-mcp/tsconfig.json
Normal file
16
packages/glitchtip-mcp/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user