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
JavaScript
/**
* 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');
};