import { z } from 'zod'; /** * Common tool helper utilities for creating MCP tools */ /** * Create a simple echo/ping tool for testing */ export function createEchoTool() { return { name: 'echo', description: 'Echo back the provided message', parameters: z.object({ message: z.string().describe('Message to echo back') }), execute: async ({ message }: { message: string }) => { return createMCPToolResult({ echo: message, timestamp: new Date().toISOString() }); } }; } /** * Create a version info tool */ export function createVersionTool(version: string, additionalInfo?: Record) { return { name: 'version', description: 'Get server version information', parameters: z.object({}), execute: async () => { return createMCPToolResult({ version, ...additionalInfo, timestamp: new Date().toISOString() }); } }; } /** * Create a status tool that reports server status */ export function createStatusTool( statusProvider: () => Promise> | Record ) { return { name: 'status', description: 'Get current server status', parameters: z.object({}), execute: async () => { const status = await statusProvider(); return createMCPToolResult({ ...status, timestamp: new Date().toISOString() }); } }; } /** * Wrap a tool with error handling */ export function withErrorHandling( tool: any, errorHandler?: (error: Error) => string ) { const originalExecute = tool.execute; return { ...tool, execute: async (params: any) => { try { return await originalExecute(params); } catch (error) { const errorMessage = errorHandler ? errorHandler(error as Error) : `Error: ${(error as Error).message}`; return createMCPToolResult({ error: true, message: errorMessage, timestamp: new Date().toISOString() }); } } }; } /** * Wrap a tool with logging */ export function withLogging( tool: any, logger?: (message: string, data?: any) => void ) { const originalExecute = tool.execute; const log = logger || console.log; return { ...tool, execute: async (params: any) => { log(`[${tool.name}] Executing with params:`, params); const start = Date.now(); try { const result = await originalExecute(params); const duration = Date.now() - start; log(`[${tool.name}] Completed in ${duration}ms`); return result; } catch (error) { const duration = Date.now() - start; log(`[${tool.name}] Failed after ${duration}ms:`, error); throw error; } } }; } /** * MCPToolResult - MCP protocol response format (content-only) * * This is the format FastMCP returns after wrapping tool responses. * Tools return JSON strings, FastMCP wraps them as {content: [{type: 'text', text: '...'}]}. * * Use extractMCPResult() to parse the content[0].text string back to structured data. */ export interface MCPToolResult { content: Array<{ type: 'text'; text: string }>; isError?: boolean; } /** * Extract result from MCPToolResult (content-only format) * * Parses content[0].text as JSON to get structured data. * FastMCP wraps tool responses as {content: [{type: 'text', text: '...'}]}. * * @param result Tool execution result (MCPToolResult or plain object) * @returns Structured data parsed from content[0].text or plain object * * @example * // MCPToolResult format (from FastMCP) * const result = { * content: [{ type: 'text', text: '{"data":"value"}' }] * }; * extractMCPResult(result); // { data: 'value' } * * @example * // Plain object (backward compatibility for direct tool calls) * const result = { plain: 'object' }; * extractMCPResult(result); // { plain: 'object' } */ export function extractMCPResult(result: unknown): Record { // Handle JSON string (from createMCPToolResult) if (typeof result === 'string') { try { return JSON.parse(result) as Record; } catch (error) { return { error: 'Failed to parse result string as JSON', raw: result }; } } // Parse content[0].text if available (MCPToolResult format from FastMCP) if ( result && typeof result === 'object' && 'content' in result && Array.isArray(result.content) && result.content.length > 0 && 'text' in result.content[0] ) { try { return JSON.parse(result.content[0].text as string) as Record; } catch (error) { return { error: 'Failed to parse content[0].text as JSON', raw: result.content[0].text }; } } // Plain object - return as-is (backward compatibility for direct tool calls in tests) if (result && typeof result === 'object') { return result as Record; } // Fallback for null, undefined, etc. return { error: 'Invalid tool result format' }; } /** * Create MCP tool result from data * * Returns JSON string that FastMCP automatically wraps as {content: [{type: 'text', text: '...'}]}. * This is the content-only format - no structuredContent field. * * @param data The data to serialize * @param meta Optional metadata to merge into the response * @returns JSON string representation * * @example * // Simple usage * const result = createMCPToolResult({ response: 'test' }); * // Returns: '{"response":"test"}' * // FastMCP wraps as: {content: [{type: 'text', text: '{"response":"test"}'}]} * * @example * // With metadata * const result = createMCPToolResult( * { response: 'test' }, * { conversationId: '123' } * ); * // Returns: '{"response":"test","conversationId":"123"}' */ export function createMCPToolResult( data: Record, meta?: Record ): string { return JSON.stringify({ ...data, ...(meta || {}) }); }