UNPKG

qeek-mcp-assistant

Version:

QEEK MCP Server - AI assistant for QEEK codebase questions. Connect to your Mastra service via Model Context Protocol.

294 lines (266 loc) 8.17 kB
#!/usr/bin/env node /** * QEEK MCP Server - Production Ready * * This script creates an MCP-compatible server that bridges to the QEEK * Mastra service running at the production endpoint. * * Trigger: Use "qeek" at the start of your prompt to activate the code assistant * Authentication: Uses QEEK_TOKEN environment variable */ const { loadToken, API_CONFIG } = require('./config'); const MASTRA_API_URL = API_CONFIG.PRODUCTION_URL; class QeekMCPServer { constructor() { this.token = null; this.tools = [ { name: "ask_code_assistant", description: "🎯 QEEK Codebase Assistant - ONLY use this tool when user's prompt starts with the word 'qeek'. This AI assistant analyzes the QEEK repository structure, explains functionality, and helps with tickets, UI components, and business logic. Examples: 'qeek tell me about tickets functionality', 'qeek how does authentication work?', 'qeek explain the database schema'", inputSchema: { type: "object", properties: { question: { type: "string", description: "The user's question about the QEEK codebase. This should be the exact question that starts with 'qeek'. Do not modify or paraphrase - pass the original question including the 'qeek' prefix." } }, required: ["question"] } } ]; } async initialize() { // Load authentication token try { // Try environment variable first, then stored token this.token = process.env.QEEK_TOKEN || loadToken(); if (!this.token) { console.error('❌ QEEK_TOKEN not found. Please run: npx qeek-mcp-assistant setup'); process.exit(1); } } catch (error) { console.error('❌ Authentication error:', error.message); process.exit(1); } // Send server info with trigger pattern this.sendResponse({ jsonrpc: "2.0", result: { protocolVersion: "2024-11-05", capabilities: { tools: {}, prompts: { listChanged: true } }, serverInfo: { name: "QEEK Code Assistant", version: "1.0.0", description: "🎯 AI assistant for QEEK codebase questions. ONLY activated when user starts their prompt with 'qeek'. Example: 'qeek tell me about the tickets functionality'" } } }); } async listTools() { this.sendResponse({ jsonrpc: "2.0", result: { tools: this.tools } }); } async listPrompts() { this.sendResponse({ jsonrpc: "2.0", result: { prompts: [ { name: "qeek_trigger", description: "Trigger the QEEK code assistant by starting your message with 'qeek'", arguments: [ { name: "query", description: "Your question about the QEEK codebase", required: true } ] } ] } }); } async callTool(name, arguments_) { if (name === "ask_code_assistant") { try { // Extract the question and strictly check for qeek trigger let question = arguments_.question || ''; // Strictly validate that the question starts with "qeek" if (!question.toLowerCase().startsWith('qeek')) { this.sendResponse({ jsonrpc: "2.0", error: { code: -32602, message: "Invalid trigger", data: "This tool should only be used when the user's prompt starts with 'qeek'. Please ensure the question begins with the word 'qeek'." } }); return; } const response = await this.askCodeAssistant(question); this.sendResponse({ jsonrpc: "2.0", result: { content: [ { type: "text", text: response } ] } }); } catch (error) { this.sendResponse({ jsonrpc: "2.0", error: { code: -32603, message: "Internal error", data: error.message } }); } } else { this.sendResponse({ jsonrpc: "2.0", error: { code: -32601, message: "Method not found", data: `Unknown tool: ${name}` } }); } } async askCodeAssistant(question) { const response = await fetch(`${MASTRA_API_URL}/api/agents/codeAssistantAgent/stream`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': `Bearer ${this.token}`, }, body: JSON.stringify({ messages: [ { role: 'user', content: question } ] }) }); if (!response.ok) { throw new Error(`Mastra API error: ${response.status} ${response.statusText}`); } // Handle streaming response const reader = response.body.getReader(); const decoder = new TextDecoder(); let result = ''; try { while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { try { const data = JSON.parse(line.slice(6)); if (data.type === 'text' && data.text) { result += data.text; } } catch (e) { // Ignore parsing errors for partial chunks } } else if (line.startsWith('0:')) { // Handle direct text chunks (as seen in the curl output) result += line.slice(2); // Remove the "0:" prefix } } } } finally { reader.releaseLock(); } return result || 'No response received from the code assistant.'; } sendResponse(response) { console.log(JSON.stringify(response)); } async handleRequest(request) { try { const parsed = typeof request === 'string' ? JSON.parse(request) : request; switch (parsed.method) { case 'initialize': await this.initialize(); break; case 'tools/list': await this.listTools(); break; case 'tools/call': await this.callTool(parsed.params.name, parsed.params.arguments || {}); break; case 'prompts/list': await this.listPrompts(); break; default: this.sendResponse({ jsonrpc: "2.0", error: { code: -32601, message: "Method not found", data: `Unknown method: ${parsed.method}` }, id: parsed.id }); } } catch (error) { this.sendResponse({ jsonrpc: "2.0", error: { code: -32700, message: "Parse error", data: error.message } }); } } start() { console.error('🚀 QEEK MCP Server starting...'); console.error('🎯 Trigger pattern: "qeek"'); console.error('📡 MCP Server ready - listening for requests...'); process.stdin.setEncoding('utf8'); process.stdin.on('data', (data) => { const lines = data.trim().split('\n'); for (const line of lines) { if (line.trim()) { this.handleRequest(line.trim()); } } }); process.stdin.on('end', () => { process.exit(0); }); // Handle process termination process.on('SIGINT', () => process.exit(0)); process.on('SIGTERM', () => process.exit(0)); } } // Add fetch polyfill for Node.js < 18 if (typeof fetch === 'undefined') { global.fetch = require('node-fetch'); } // Export for testing module.exports = { QeekMCPServer }; // Start the MCP server if called directly if (require.main === module) { const server = new QeekMCPServer(); server.start(); }