UNPKG

@systemprompt/mcp-taskchecker

Version:

Model Context Protocol (MCP) server for intelligent task management, evaluation scoring, and session-based workflow tracking. Seamlessly integrates with AI assistants to provide structured task orchestration, real-time progress monitoring, and comprehensi

204 lines (203 loc) โ€ข 7.68 kB
#!/usr/bin/env node import dotenv from "dotenv"; dotenv.config(); import express from "express"; import { randomUUID } from "node:crypto"; import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js"; import { serverConfig, serverCapabilities } from "./config/server-config.js"; import { handleListTools, handleToolCall } from "./handlers/tool-handlers.js"; export class TaskCheckerMCPServer { app; transports = new Map(); servers = new Map(); sessionTimeouts = new Map(); SESSION_TIMEOUT_MS = 300000; // 5 minutes constructor() { // Initialize Express app this.app = express(); this.setupRoutes(); } createMCPServer() { const server = new Server(serverConfig, serverCapabilities); server.setRequestHandler(ListToolsRequestSchema, handleListTools); server.setRequestHandler(CallToolRequestSchema, handleToolCall); return server; } setSessionTimeout(sessionId) { // Clear existing timeout if any const existingTimeout = this.sessionTimeouts.get(sessionId); if (existingTimeout) { clearTimeout(existingTimeout); } // Set new timeout const timeout = setTimeout(() => { console.log(`โฐ Session timeout: ${sessionId}`); this.cleanupSession(sessionId); }, this.SESSION_TIMEOUT_MS); this.sessionTimeouts.set(sessionId, timeout); } cleanupSession(sessionId) { const transport = this.transports.get(sessionId); const server = this.servers.get(sessionId); const timeout = this.sessionTimeouts.get(sessionId); if (transport) { transport.close(); this.transports.delete(sessionId); } if (server) { server.close(); this.servers.delete(sessionId); } if (timeout) { clearTimeout(timeout); this.sessionTimeouts.delete(sessionId); } console.log(`๐Ÿ—‘๏ธ Cleaned up session: ${sessionId}`); } async handleMCPRequest(req, res) { try { const sessionId = req.headers['mcp-session-id']; let transport; let server; if (sessionId && this.transports.has(sessionId)) { // Reuse existing transport for valid session transport = this.transports.get(sessionId); server = this.servers.get(sessionId); console.log(`๐Ÿ“ฆ Reusing transport for session: ${sessionId}`); // Reset session timeout on activity this.setSessionTimeout(sessionId); } else if (!sessionId) { // Create new transport for new session (no session ID means initialization) const newSessionId = randomUUID(); transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => newSessionId, onsessioninitialized: (sessionId) => { console.log(`๐Ÿ”— Session initialized: ${sessionId}`); this.transports.set(sessionId, transport); this.servers.set(sessionId, server); // Set timeout for new session this.setSessionTimeout(sessionId); } }); server = this.createMCPServer(); await server.connect(transport); console.log(`๐Ÿ†• Created new transport for session: ${newSessionId}`); } else { // Invalid session ID res.status(400).json({ jsonrpc: "2.0", error: { code: -32600, message: "Invalid or expired session" }, id: null }); return; } // Handle the request with the appropriate transport await transport.handleRequest(req, res); } catch (error) { console.error("Error handling MCP request:", error); res.status(500).json({ jsonrpc: "2.0", error: { code: -32603, message: "Internal error" }, id: null }); } } setupRoutes() { // MCP endpoint (no JSON parsing - transport handles raw stream) this.app.all("/mcp", async (req, res) => { await this.handleMCPRequest(req, res); }); // Health check this.app.get("/health", (req, res) => { res.json({ status: "ok", service: "taskchecker-mcp-server", transport: "http", activeSessions: this.transports.size, capabilities: { mcp: true, }, }); }); // Root endpoint this.app.get("/", (req, res) => { const baseUrl = `${req.protocol}://${req.get("host")}`; res.json({ service: "Task Checker MCP Server", version: "1.0.0", transport: "http", activeSessions: this.transports.size, endpoints: { mcp: `${baseUrl}/mcp`, health: `${baseUrl}/health`, }, }); }); // Session cleanup endpoint (for testing) this.app.delete("/sessions/:sessionId", (req, res) => { const sessionId = req.params.sessionId; if (this.transports.has(sessionId)) { this.cleanupSession(sessionId); res.json({ message: "Session cleaned up", sessionId }); } else { res.status(404).json({ error: "Session not found", sessionId }); } }); // List active sessions (for debugging) this.app.get("/sessions", (req, res) => { res.json({ activeSessions: Array.from(this.transports.keys()), count: this.transports.size }); }); } async start(port = 3000) { this.app.listen(port, '0.0.0.0', () => { console.log(`๐Ÿš€ Task Checker MCP Server (HTTP Transport) running on port ${port}`); console.log(`๐Ÿ“ก MCP endpoint: http://localhost:${port}/mcp`); console.log(`โค๏ธ Health: http://localhost:${port}/health`); console.log(`๐Ÿ”ง Sessions endpoint: http://localhost:${port}/sessions`); }); } cleanup() { console.log(`๐Ÿงน Cleaning up ${this.transports.size} active sessions...`); for (const [sessionId] of this.transports) { this.cleanupSession(sessionId); } } } // Handle graceful shutdown process.on('SIGINT', () => { console.log('\nShutting down gracefully...'); process.exit(0); }); process.on('SIGTERM', () => { console.log('\nShutting down gracefully...'); process.exit(0); }); // Main execution async function main() { const port = parseInt(process.env.PORT || "3000", 10); const server = new TaskCheckerMCPServer(); // Cleanup on exit process.on('exit', () => { server.cleanup(); }); await server.start(port); } main().catch((error) => { console.error("Fatal error:", error); process.exit(1); });