UNPKG

mcp-access-inteligence-search

Version:
279 lines (278 loc) 11.4 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'fs'; import { dirname, join } from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8')); const { name, version } = pkg; const JWT_TOKEN = process.env.JWT_TOKEN; if (!JWT_TOKEN) { throw new Error('JWT_TOKEN environment variable is required'); } const API_BASE_URL = 'https://agents-backend-qqtpi.ondigitalocean.app'; // Set up logging const LOG_DIR = join(__dirname, '..', 'logs'); const LOG_FILE = join(LOG_DIR, 'access-intelligence.log'); // Ensure log directory exists if (!existsSync(LOG_DIR)) { mkdirSync(LOG_DIR, { recursive: true }); } // Logger function to write to file function logToFile(message) { const timestamp = new Date().toISOString(); const logEntry = `[${timestamp}] ${message}\n`; try { appendFileSync(LOG_FILE, logEntry); } catch (error) { console.error(`Failed to write to log file: ${error}`); } } // Define the Access Intelligence search tool const ACCESS_INTELLIGENCE_TOOL = { name: 'search', description: 'Search for information sources to help with the message reply using Access Intelligence API', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'The search query to find relevant information sources to help with the message reply', }, context: { type: 'array', items: { type: 'string', }, description: 'Optional context strings to help with the search', }, }, required: ['query'], }, }; // Create the server const server = new Server({ name: 'access-intelligence', version }, { capabilities: { tools: {}, }, }); // Type guard for Access Intelligence search arguments function isAccessIntelligenceArgs(args) { return (typeof args === 'object' && args !== null && 'query' in args && typeof args.query === 'string'); } // Download a template by slug or id async function downloadTemplate(identifier, params) { logToFile(`Downloading template with identifier: "${identifier}"`); try { const url = new URL(`${API_BASE_URL}/template/${identifier}`); if (params && Object.keys(params).length > 0) { Object.entries(params).forEach(([key, value]) => { url.searchParams.append(key, value); }); logToFile(`Adding parameters to template request: ${JSON.stringify(params)}`); } const response = await fetch(url.toString(), { method: 'GET', headers: { 'Authorization': `Bearer ${JWT_TOKEN}`, }, }); if (!response.ok) { const errorText = await response.text(); const errorMessage = `Template download failed with status ${response.status}: ${errorText}`; logToFile(`Template Download Error: ${errorMessage}`); return null; } const template = await response.json(); logToFile(`Template response: ${JSON.stringify(template)}`); const templateName = template.title || template.name || identifier; logToFile(`Successfully downloaded template: "${templateName}"`); return { content: template.content, name: templateName, id: template.id, slug: template.slug || identifier, params: params }; } catch (error) { const errorMessage = `Failed to download template: ${error instanceof Error ? error.message : String(error)}`; logToFile(`Template Download Error: ${errorMessage}`); return null; } } // Perform Access Intelligence search async function performAccessIntelligenceSearch(query, context = []) { logToFile(`Access Intelligence Search Request - Query: "${query}", Context: ${JSON.stringify(context)}`); try { const response = await fetch(`${API_BASE_URL}/rag/search`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${JWT_TOKEN}`, }, body: JSON.stringify({ query, context, }), }); if (!response.ok) { const errorText = await response.text(); const errorMessage = `API request failed with status ${response.status}: ${errorText}`; logToFile(`Access Intelligence Search Error: ${errorMessage}`); throw new Error(errorMessage); } const searchResults = await response.json(); logToFile(`Access Intelligence Search Response - Results: ${JSON.stringify(searchResults)}`); if (!searchResults || searchResults.length === 0) { logToFile("No search results returned from API"); return { combinedText: "", summary: "No access intelligence found matching your query." }; } const validResults = searchResults.filter(template => { if (!template.id && !template.slug) { logToFile(`Warning: Template result missing both id and slug: ${JSON.stringify(template)}`); return false; } return true; }); logToFile(`Found ${validResults.length} templates with valid identifiers out of ${searchResults.length} total results`); if (validResults.length === 0) { logToFile("No valid templates with identifiers found in search results"); return { combinedText: "", summary: "Found search results, but no valid access intelligence could be retrieved." }; } const downloadPromises = validResults.map(template => { const identifier = template.id || template.slug; const templateParams = {}; if (template.params && template.params.length > 0) { template.params.forEach(param => { if (param.recommended) { templateParams[param.name] = param.recommended; } else { switch (param.type.toLowerCase()) { case 'number': case 'integer': if (param.name.toLowerCase() === 'limit') { templateParams[param.name] = '10'; } else { templateParams[param.name] = '1'; } break; case 'string': templateParams[param.name] = 'sample'; break; case 'boolean': templateParams[param.name] = 'true'; break; default: templateParams[param.name] = ''; } } }); logToFile(`Generated parameters for template ${identifier}: ${JSON.stringify(templateParams)}`); } return downloadTemplate(identifier, templateParams); }); const downloadedTemplates = await Promise.all(downloadPromises); const validTemplates = downloadedTemplates.filter((template) => template !== null); if (validTemplates.length === 0) { logToFile("No templates were successfully downloaded"); return { combinedText: "", summary: "Found access intelligence, but failed to download any of them." }; } const combinedText = validTemplates.map(template => { let templateText = `# ${template.name}\n\n`; if (template.params && Object.keys(template.params).length > 0) { templateText += `Parameters used: ${JSON.stringify(template.params)}\n\n`; } templateText += template.content; return templateText; }).join('\n\n\n\n'); const templateSummary = validTemplates.map(template => template.name).join(', '); logToFile(`Downloaded and combined ${validTemplates.length} templates: ${templateSummary}`); return { combinedText, summary: `Found ${validTemplates.length} access intelligence results: ${templateSummary}` }; } catch (error) { const errorMessage = `Failed to search access intelligence: ${error instanceof Error ? error.message : String(error)}`; logToFile(`Access Intelligence Search Error: ${errorMessage}`); throw new Error(errorMessage); } } // Set up request handlers server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ACCESS_INTELLIGENCE_TOOL], })); server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const { name, arguments: args } = request.params; logToFile(`Tool Call Request - Tool: "${name}", Args: ${JSON.stringify(args)}`); if (!args) { throw new Error("No arguments provided"); } if (name === "search") { if (!isAccessIntelligenceArgs(args)) { throw new Error("Invalid arguments for access intelligence search"); } const { query, context = [] } = args; const results = await performAccessIntelligenceSearch(query, context); return { content: [ { type: "text", text: results.summary }, { type: "text", text: results.combinedText } ], isError: false, }; } else { return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true, }; } } catch (error) { const errorMessage = `Error: ${error instanceof Error ? error.message : String(error)}`; logToFile(`Tool Call Error: ${errorMessage}`); return { content: [ { type: "text", text: errorMessage, }, ], isError: true, }; } }); // Start the server async function runServer() { logToFile("Server starting"); const transport = new StdioServerTransport(); await server.connect(transport); logToFile("Server connected to stdio transport"); console.error("Access Intelligence MCP Server running on stdio"); } runServer().catch((error) => { logToFile(`Server startup error: ${error}`); console.error("Server error:", error); process.exit(1); });