mcp-grocy
Version:
Model Context Protocol (MCP) server for Grocy integration
142 lines (141 loc) • 4.24 kB
JavaScript
/**
* Simplified base tool handler
*/
import apiClient from '../api/client.js';
import { ErrorHandler, ValidationError } from '../utils/errors.js';
import { logger } from '../utils/logger.js';
export class BaseToolHandler {
/**
* Create a standardized success result
*/
createSuccess(data, message) {
return {
content: [
{
type: 'text',
text: message || 'Operation completed successfully'
},
{
type: 'text',
text: JSON.stringify(data, null, 2)
}
]
};
}
/**
* Create a standardized error result
*/
createError(message, details) {
return {
content: [
{
type: 'text',
text: `Error: ${message}`
},
...(details ? [{
type: 'text',
text: JSON.stringify(details, null, 2)
}] : [])
],
isError: true
};
}
/**
* Validate required parameters
*/
validateRequired(params, required) {
const missing = required.filter(field => {
const value = params[field];
return value === undefined || value === null || value === '';
});
if (missing.length > 0) {
throw new ValidationError(`Missing required parameters: ${missing.join(', ')}`, 'parameter validation');
}
}
/**
* Safely make API calls with error handling
*/
async apiCall(endpoint, method = 'GET', data, options) {
return ErrorHandler.handleAsync(async () => {
const response = await apiClient.request(endpoint, {
method,
body: data,
queryParams: options?.queryParams || {}
});
return response.data;
}, `API ${method} ${endpoint}`);
}
/**
* Handle tool execution with standardized error handling
*/
async executeToolHandler(handler) {
try {
return await handler();
}
catch (error) {
ErrorHandler.logError(error, 'tool execution');
if (error instanceof ValidationError) {
return this.createError(error.message);
}
const message = error instanceof Error ? error.message : 'Internal error';
return this.createError(`Tool execution failed: ${message}`);
}
}
/**
* Safe JSON formatting
*/
safeStringify(data) {
try {
return JSON.stringify(data, null, 2);
}
catch (error) {
logger.warn('Failed to stringify data', 'TOOLS', { error });
return '[Unable to format data]';
}
}
/**
* Filter object fields based on allowlist
*/
filterFields(objects, fields) {
return objects.map(obj => {
const filtered = {};
fields.forEach(field => {
if (field in obj) {
filtered[field] = obj[field];
}
});
return filtered;
});
}
/**
* Parse and validate array parameter
*/
parseArrayParam(value, paramName) {
if (!value) {
throw new ValidationError(`${paramName} is required`);
}
if (!Array.isArray(value)) {
throw new ValidationError(`${paramName} must be an array`);
}
if (value.length === 0) {
throw new ValidationError(`${paramName} cannot be empty`);
}
return value.map(v => String(v));
}
/**
* Parse and validate numeric parameter
*/
parseNumberParam(value, paramName, required = true) {
if (value === undefined || value === null) {
if (required) {
throw new ValidationError(`${paramName} is required`);
}
return undefined;
}
const num = Number(value);
if (isNaN(num)) {
throw new ValidationError(`${paramName} must be a valid number`);
}
return num;
}
}