UNPKG

@polymerdao/mcp-polymer

Version:

A Model Context Protocol (MCP) server for blockchain event verification using Polymer's Prove API.

188 lines (187 loc) 8.44 kB
import { z } from 'zod'; import { OpenRpcSpecLoader, convertMethodParamsToZodObject } from './openrpc.js'; export class DynamicToolRegistry { constructor(specUrl, polymerClient, verifier, config) { this.specLoader = new OpenRpcSpecLoader(specUrl); this.polymerClient = polymerClient; this.verifier = verifier; this.config = config; } async initialize() { await this.specLoader.loadSpec(); // Get the API URL from the OpenRPC spec based on environment this.apiUrl = this.specLoader.getServerUrl(this.config.environment); // Fall back to config if not found in spec if (!this.apiUrl) { this.apiUrl = this.config.polymerApiBase; } console.error(`Using API URL from OpenRPC spec: ${this.apiUrl}`); } async registerAllTools(server) { const methods = this.specLoader.getAllMethods(); for (const method of methods) { await this.registerToolFromMethod(server, method); } // Register special introspection tools await this.registerIntrospectionTools(server); } async registerToolFromMethod(server, method) { try { const inputSchema = convertMethodParamsToZodObject(method); server.registerTool(method.name, { title: method.summary || this.formatMethodName(method.name), description: method.description || `Call ${method.name} method on Polymer API`, inputSchema: inputSchema.shape }, async (input) => { const result = await this.callPolymerMethod(method.name, input); return result; }); } catch (error) { console.error(`Failed to register tool for method ${method.name}:`, error); } } async callPolymerMethod(methodName, params) { try { // Check if method expects no parameters const method = this.specLoader.getMethodByName(methodName); if (method && method.params.length === 0) { // Method expects no parameters, ensure we pass empty array params = []; } // Special handling for different method categories if (methodName.startsWith('polymer_')) { return await this.handlePolymerMethod(methodName, params); } else if (methodName.startsWith('info_')) { return await this.handleInfoMethod(methodName, params); } else if (methodName.startsWith('log_')) { return await this.handleLogMethod(methodName, params); } else if (methodName.startsWith('execute_')) { return await this.handleExecuteMethod(methodName, params); } else if (methodName.startsWith('proof_')) { return await this.handleProofMethod(methodName, params); } else if (methodName.startsWith('connect_')) { return await this.handleConnectMethod(methodName, params); } else { // Generic method call return await this.genericMethodCall(methodName, params); } } catch (error) { throw new Error(`Method ${methodName} failed: ${error}`); } } async handlePolymerMethod(methodName, params) { // For now, use generic method call for all polymer methods // The existing polymer client methods use the old API endpoint // TODO: Update polymer client to use the spec URL return await this.genericMethodCall(methodName, params); } async handleInfoMethod(methodName, params) { // Use generic method call for all info methods // The existing client methods use the old API endpoint return await this.genericMethodCall(methodName, params); } async handleLogMethod(methodName, params) { // Log methods - use generic call for now, could add specific handling return await this.genericMethodCall(methodName, params); } async handleExecuteMethod(methodName, params) { // Execute methods - use generic call, parameter conversion handled in genericMethodCall return await this.genericMethodCall(methodName, params); } async handleProofMethod(methodName, params) { // Proof methods - use generic call, parameter conversion handled in genericMethodCall return await this.genericMethodCall(methodName, params); } async handleConnectMethod(methodName, params) { // Connect methods - use generic call for now, could add specific handling return await this.genericMethodCall(methodName, params); } async genericMethodCall(methodName, params) { // Convert parameters based on OpenRPC spec let jsonRpcParams; if (!Array.isArray(params) && typeof params === 'object' && params !== null) { // Get method definition to understand parameter structure const method = this.specLoader.getMethodByName(methodName); if (method && method.params.length > 0) { // Check if this method expects a single object parameter or multiple positional parameters if (method.params.length === 1 && method.params[0].schema.type === 'object') { // Single object parameter - pass the params object as is jsonRpcParams = [params]; } else { // Multiple positional parameters - convert named params to positional array jsonRpcParams = method.params.map(param => params[param.name]); } } else { // No params defined, use empty array jsonRpcParams = []; } } else { // Already an array or empty jsonRpcParams = params; } // Generic JSON-RPC call to Polymer API // Use the API URL from the OpenRPC spec const response = await this.polymerClient.callGenericMethod(methodName, jsonRpcParams, this.config.apiKey, this.apiUrl); return { content: [{ type: "text", text: JSON.stringify(response) }] }; } async registerIntrospectionTools(server) { // Tool to get the full OpenRPC spec server.registerTool("get_openrpc_spec", { title: "Get OpenRPC Specification", description: "Retrieve the complete OpenRPC specification for the Polymer API", inputSchema: {} }, async () => { const spec = this.specLoader.getSpec(); return { content: [{ type: "text", text: JSON.stringify(spec, null, 2) }] }; }); // Tool to list all available methods server.registerTool("list_available_methods", { title: "List Available Methods", description: "Get a list of all available API methods with descriptions", inputSchema: {} }, async () => { const methods = this.specLoader.getAllMethods().map(method => ({ name: method.name, summary: method.summary, description: method.description, paramCount: method.params.length })); return { content: [{ type: "text", text: JSON.stringify(methods, null, 2) }] }; }); // Tool to get method details server.registerTool("get_method_details", { title: "Get Method Details", description: "Get detailed information about a specific API method", inputSchema: { method_name: z.string() } }, async (input) => { const method = this.specLoader.getMethodByName(input.method_name); if (!method) { throw new Error(`Method ${input.method_name} not found`); } return { content: [{ type: "text", text: JSON.stringify(method, null, 2) }] }; }); } formatMethodName(methodName) { return methodName .split('_') .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); } getAvailableMethods() { return this.specLoader.getMethodNames(); } getMethodsByCategory(prefix) { return this.specLoader.getMethodsByCategory(prefix); } }