@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
JavaScript
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);
}
}