UNPKG

haloapi-mcp-tools

Version:

Model Context Protocol (MCP) server for interacting with the HaloPSA API

346 lines (320 loc) 8.86 kB
#!/usr/bin/env node /** * Standalone MCP Server for HaloPSA * * A simple, standalone MCP server implementation designed to work with Claude. * This server implements the Model Context Protocol directly without dependencies * on the @modelcontextprotocol/sdk package, making it more resilient to SDK changes. */ // CommonJS style imports for better compatibility const express = require('express'); const http = require('http'); const cors = require('cors'); const dotenv = require('dotenv'); const axios = require('axios'); // Load environment variables dotenv.config(); // Create express app const app = express(); const server = http.createServer(app); // Configure middleware app.use(express.json()); app.use(cors()); // Get port from environment or default const port = process.env.PORT || process.env.MCP_SERVER_PORT || 3000; // Configure HaloPSA API client const haloApiClient = axios.create({ baseURL: process.env.HALO_API_URL || 'https://example.halopsa.com/api', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.HALO_API_KEY || ''}` }, timeout: parseInt(process.env.HALO_API_TIMEOUT || '30000', 10) }); // Tool prefix const toolPrefix = process.env.TOOL_PREFIX || 'halo'; // Available tools const tools = [ { name: `${toolPrefix}-get-tickets`, description: 'Get a list of tickets with optional filtering', schema: { type: 'object', properties: { status: { type: 'string', enum: ['open', 'closed', 'pending', 'all'], description: 'Filter tickets by status' }, page: { type: 'number', description: 'Page number for pagination' } } }, handler: async (args) => { try { // In a real implementation, this would call the HaloPSA API // For now, we return mock data return { content: [ { type: 'text', text: JSON.stringify({ tickets: [ { id: 1, subject: 'Server issues', status: args.status || 'open' }, { id: 2, subject: 'Email configuration', status: args.status || 'open' } ], total: 2, page: args.page || 1 }, null, 2) } ] }; } catch (error) { console.error('Error in get-tickets handler:', error); return { isError: true, content: [ { type: 'text', text: `Error retrieving tickets: ${error.message}` } ] }; } } }, { name: `${toolPrefix}-create-ticket`, description: 'Create a new ticket', schema: { type: 'object', properties: { subject: { type: 'string', description: 'Ticket subject' }, description: { type: 'string', description: 'Ticket description' }, priority: { type: 'string', enum: ['low', 'medium', 'high', 'critical'], description: 'Ticket priority' } }, required: ['subject', 'description'] }, handler: async (args) => { try { // In a real implementation, this would call the HaloPSA API // For now, we return mock data return { content: [ { type: 'text', text: `Ticket created successfully with subject: ${args.subject}` } ] }; } catch (error) { console.error('Error in create-ticket handler:', error); return { isError: true, content: [ { type: 'text', text: `Error creating ticket: ${error.message}` } ] }; } } }, { name: `${toolPrefix}-get-ticket`, description: 'Get detailed information about a specific ticket', schema: { type: 'object', properties: { id: { type: 'number', description: 'Ticket ID' } }, required: ['id'] }, handler: async (args) => { try { // In a real implementation, this would call the HaloPSA API // For now, we return mock data return { content: [ { type: 'text', text: JSON.stringify({ id: args.id, subject: 'Sample Ticket', description: 'This is a sample ticket description', status: 'open', priority: 'medium', created: '2025-03-24T12:34:56Z', updated: '2025-03-24T13:45:12Z', assignee: 'John Smith', requester: 'Jane Doe' }, null, 2) } ] }; } catch (error) { console.error(`Error in get-ticket handler for ticket ${args.id}:`, error); return { isError: true, content: [ { type: 'text', text: `Error retrieving ticket ${args.id}: ${error.message}` } ] }; } } } ]; // MCP protocol implementation app.post('/', async (req, res) => { try { console.log(`Received request: ${req.body.method}`); const { method, id, params } = req.body; // Handle initialize request if (method === 'initialize') { return res.json({ jsonrpc: '2.0', id, result: { protocolVersion: params.protocolVersion, serverInfo: { name: 'haloapi-mcp-tools', version: '1.0.1' }, capabilities: { tools: {} } } }); } // Handle tools/list request if (method === 'tools/list') { return res.json({ jsonrpc: '2.0', id, result: { tools: tools.map(tool => ({ name: tool.name, description: tool.description, schema: tool.schema })) } }); } // Handle tools/call request if (method === 'tools/call') { const { name, args } = params; const tool = tools.find(t => t.name === name); if (!tool) { return res.json({ jsonrpc: '2.0', id, error: { code: -32601, message: `Tool not found: ${name}` } }); } try { const result = await tool.handler(args); return res.json({ jsonrpc: '2.0', id, result }); } catch (error) { console.error(`Error executing tool ${name}:`, error); return res.json({ jsonrpc: '2.0', id, error: { code: -32603, message: `Error executing tool ${name}: ${error.message}` } }); } } // Handle ping request if (method === 'ping') { return res.json({ jsonrpc: '2.0', id, result: {} }); } // Handle unsupported methods return res.json({ jsonrpc: '2.0', id, error: { code: -32601, message: `Method not supported: ${method}` } }); } catch (error) { console.error('Error handling request:', error); res.json({ jsonrpc: '2.0', id: req.body.id, error: { code: -32603, message: `Internal error: ${error.message}` } }); } }); // Start server server.listen(port, () => { console.log(`MCP Server running on http://localhost:${port}`); console.log(''); console.log('====================================='); console.log(' HaloPSA MCP Server is now running'); console.log('====================================='); console.log(''); console.log('To connect Claude Desktop to this server:'); console.log(''); console.log('1. Open Claude Desktop'); console.log('2. Go to Settings > Extensions'); console.log('3. Click "Add MCP Server"'); console.log('4. Enter the following details:'); console.log(` - URL: http://localhost:${port}`); console.log(' - Name: HaloPSA'); console.log('5. Click "Save"'); console.log(''); console.log('You can now use HaloPSA tools in your Claude conversations'); console.log(''); console.log('Press Ctrl+C to stop the server'); }); // Handle process termination process.on('SIGINT', () => { console.log('Received SIGINT signal, shutting down...'); server.close(() => { console.log('Server closed'); process.exit(0); }); }); process.on('SIGTERM', () => { console.log('Received SIGTERM signal, shutting down...'); server.close(() => { console.log('Server closed'); process.exit(0); }); });