haloapi-mcp-tools
Version:
Model Context Protocol (MCP) server for interacting with the HaloPSA API
346 lines (320 loc) • 8.86 kB
JavaScript
/**
* 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);
});
});