UNPKG

park-ui-mcp-server

Version:

A Model Context Protocol (MCP) server for Park UI component library, providing AI assistants with access to Park UI components, APIs, examples, and documentation.

240 lines (239 loc) 11.3 kB
/** * Request handler setup for the Park UI MCP server. * * This file configures how the server responds to various MCP requests by setting up * handlers for resources, resource templates, tools, and prompts. * * Updated for MCP SDK 1.16.0 with improved error handling and request processing. */ import { CallToolRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ReadResourceRequestSchema, ListToolsRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema } from "@modelcontextprotocol/sdk/types.js"; import { resourceHandlers, resources } from "./resources.js"; import { promptHandlers, prompts } from "./prompts.js"; import { toolHandlers } from "./tools/index.js"; import { getResourceTemplate, resourceTemplates, } from "./resource-templates.js"; import { z } from "zod"; import { validateAndSanitizeParams } from './utils/validation.js'; import { circuitBreakers } from './utils/circuit-breaker.js'; import { logError, logInfo } from './utils/logger.js'; // Define basic component schemas here for tool validation const componentSchema = { componentName: z.string(), framework: z.enum(['react', 'vue', 'solid']).optional() }; const searchSchema = { query: z.string(), framework: z.enum(['react', 'vue', 'solid']).optional() }; const exampleSchema = { exampleType: z.string(), framework: z.enum(['react', 'vue', 'solid']).optional() }; const docsSchema = { topic: z.string() }; const installationSchema = { componentName: z.string(), framework: z.enum(['react', 'vue', 'solid']).optional() }; /** * Wrapper function to handle requests with simple error handling */ async function handleRequest(method, params, handler) { try { // Validate and sanitize input parameters const validatedParams = validateAndSanitizeParams(method, params); // Execute the handler with circuit breaker protection for external calls const result = await circuitBreakers.external.execute(() => handler(validatedParams)); return result; } catch (error) { logError(`Error in ${method}`, error); throw error; } } /** * Sets up all request handlers for the MCP server * Following MCP SDK 1.16.0 best practices for handler registration * @param server - The MCP server instance */ export const setupHandlers = (server) => { logInfo('Setting up request handlers...'); // List available resources when clients request them server.setRequestHandler(ListResourcesRequestSchema, async (request) => { return await handleRequest('list_resources', request.params, async () => ({ resources })); }); // Resource Templates server.setRequestHandler(ListResourceTemplatesRequestSchema, async (request) => { return await handleRequest('list_resource_templates', request.params, async () => ({ resourceTemplates })); }); // List available tools server.setRequestHandler(ListToolsRequestSchema, async (request) => { return await handleRequest('list_tools', request.params, async () => { // Return the tools that are registered with the server const registeredTools = [ { name: 'get_parkui_component', description: 'Get information and examples for a specific Park UI component', inputSchema: { type: 'object', properties: { componentName: { type: 'string', description: 'Name of the Park UI component (e.g., "button", "accordion", "avatar")', }, framework: { type: 'string', enum: ['react', 'vue', 'solid'], description: 'Framework to get component for (defaults to react)', }, }, required: ['componentName'], }, }, { name: 'list_parkui_components', description: 'Get all available Park UI components', inputSchema: { type: 'object', properties: { category: { type: 'string', description: 'Filter by category (form, navigation, display, feedback)', }, framework: { type: 'string', enum: ['react', 'vue', 'solid'], description: 'Framework to list components for (defaults to react)', }, }, }, }, { name: 'get_parkui_installation', description: 'Get installation instructions for Park UI components', inputSchema: { type: 'object', properties: { componentName: { type: 'string', description: 'Name of the component to install', }, framework: { type: 'string', enum: ['react', 'vue', 'solid'], description: 'Target framework (defaults to react)', }, }, required: ['componentName'], }, }, { name: 'get_parkui_example', description: 'Get example code for a specific Park UI component pattern', inputSchema: { type: 'object', properties: { exampleType: { type: 'string', description: 'Type of example (e.g., "form-validation", "custom-theme", "responsive-layout")', }, framework: { type: 'string', enum: ['react', 'vue', 'solid'], description: 'Framework for the example (defaults to react)', }, }, required: ['exampleType'], }, }, { name: 'search_parkui_examples', description: 'Search Park UI examples and code snippets', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query for examples', }, framework: { type: 'string', enum: ['react', 'vue', 'solid'], description: 'Framework to search within (defaults to all)', }, }, required: ['query'], }, }, { name: 'get_parkui_docs', description: 'Get documentation for Park UI features and concepts', inputSchema: { type: 'object', properties: { topic: { type: 'string', description: 'Documentation topic (e.g., "getting-started", "theming", "panda-css")', }, }, required: ['topic'], }, }, ]; return { tools: registeredTools }; }); }); // Return resource content when clients request it server.setRequestHandler(ReadResourceRequestSchema, async (request) => { return await handleRequest('read_resource', request.params, async (validatedParams) => { const { uri } = validatedParams; // Check if this is a static resource const resourceHandler = resourceHandlers[uri]; if (resourceHandler) { const result = await Promise.resolve(resourceHandler()); return { contentType: result.contentType, contents: [{ uri: uri, text: result.content }] }; } // Check if this is a generated resource from a template const resourceTemplateHandler = getResourceTemplate(uri); if (resourceTemplateHandler) { const result = await Promise.resolve(resourceTemplateHandler()); return { contentType: result.contentType, contents: [{ uri: uri, text: result.content }] }; } throw new Error(`Resource not found: ${uri}`); }); }); // List available prompts server.setRequestHandler(ListPromptsRequestSchema, async (request) => { return await handleRequest('list_prompts', request.params, async () => ({ prompts: Object.values(prompts) })); }); // Get specific prompt content with optional arguments server.setRequestHandler(GetPromptRequestSchema, async (request) => { return await handleRequest('get_prompt', request.params, async (validatedParams) => { const { name, arguments: args } = validatedParams; const promptHandler = promptHandlers[name]; if (!promptHandler) { throw new Error(`Prompt not found: ${name}`); } return promptHandler(args); }); }); // Tool request Handler - executes the requested tool with provided parameters server.setRequestHandler(CallToolRequestSchema, async (request) => { return await handleRequest('call_tool', request.params, async (validatedParams) => { const { name, arguments: params } = validatedParams; if (!name || typeof name !== 'string') { throw new Error("Tool name is required"); } const handler = toolHandlers[name]; if (!handler) { throw new Error(`Tool not found: ${name}`); } // Execute handler with circuit breaker protection const result = await circuitBreakers.external.execute(() => Promise.resolve(handler(params || {}))); return result; }); }); // Add global error handler server.onerror = (error) => { logError('MCP server error', error); }; logInfo('Handlers setup complete'); };