Skip to main content
Glama
Tommertom

Plugwise MCP Server

by Tommertom
tool-adapter.ts6.59 kB
import { createTool } from '@mastra/core/tools'; import { z } from 'zod'; import { McpClient, McpTool } from './mcp-client.js'; export interface MastraToolFromMcp { id: string; description: string; inputSchema: z.ZodObject<Record<string, z.ZodTypeAny>>; execute: (input: Record<string, unknown>) => Promise<unknown>; } export class McpToolAdapter { private mcpClient: McpClient; private tools: Map<string, MastraToolFromMcp> = new Map(); constructor(mcpClient: McpClient) { this.mcpClient = mcpClient; } async loadTools(): Promise<Record<string, unknown>> { if (!this.mcpClient.connected()) { throw new Error('MCP client must be connected before loading tools'); } const mcpTools = await this.mcpClient.listTools(); console.error(`[MCP Adapter] Loaded ${mcpTools.length} tools from MCP server`); const mastraTools: Record<string, unknown> = {}; for (const mcpTool of mcpTools) { const mastraTool = this.convertMcpToolToMastraTool(mcpTool); this.tools.set(mcpTool.name, mastraTool); mastraTools[mcpTool.name] = createTool({ id: mastraTool.id, description: mastraTool.description, inputSchema: mastraTool.inputSchema, execute: async (context: any) => { // Extract the actual input from the Mastra context // Mastra wraps tool parameters in a nested context.context structure const input: Record<string, unknown> = {}; // First, collect any direct parameters (excluding Mastra framework properties) for (const [key, value] of Object.entries(context)) { // Skip Mastra framework properties if (!['mastra', 'runId', 'threadId', 'resourceId', 'agentName', 'tracingContext', 'writableStream', 'tracingPolicy', 'requireApproval', 'description', 'model', 'context', 'runtimeContext', 'writer'].includes(key)) { input[key] = value; } } // Tool parameters are nested in context.context if (context.context && typeof context.context === 'object') { Object.assign(input, context.context); } console.error(`[MCP Adapter] Calling ${mastraTool.id} with input:`, JSON.stringify(input)); return mastraTool.execute(input); }, }); } return mastraTools; } private convertMcpToolToMastraTool(mcpTool: McpTool): MastraToolFromMcp { const zodSchema = this.convertJsonSchemaToZod(mcpTool.inputSchema); return { id: mcpTool.name, description: mcpTool.description || `MCP tool: ${mcpTool.name}`, inputSchema: zodSchema, execute: async (input: Record<string, unknown>) => { try { const result = await this.mcpClient.callTool(mcpTool.name, input); return this.formatToolResult(result); } catch (error) { console.error(`[MCP Adapter] Error executing tool ${mcpTool.name}:`, error); throw error; } }, }; } private convertJsonSchemaToZod(jsonSchema: Record<string, unknown>): z.ZodObject<Record<string, z.ZodTypeAny>> { const properties = (jsonSchema.properties as Record<string, unknown>) || {}; const required = (jsonSchema.required as string[]) || []; const zodFields: Record<string, z.ZodTypeAny> = {}; for (const [key, value] of Object.entries(properties)) { const prop = value as Record<string, unknown>; let zodType: z.ZodTypeAny; switch (prop.type) { case 'string': zodType = z.string(); if (prop.description && typeof prop.description === 'string') { zodType = zodType.describe(prop.description); } break; case 'number': case 'integer': zodType = z.number(); if (prop.description && typeof prop.description === 'string') { zodType = zodType.describe(prop.description); } break; case 'boolean': zodType = z.boolean(); if (prop.description && typeof prop.description === 'string') { zodType = zodType.describe(prop.description); } break; case 'array': zodType = z.array(z.unknown()); if (prop.description && typeof prop.description === 'string') { zodType = zodType.describe(prop.description); } break; case 'object': zodType = z.record(z.unknown()); if (prop.description && typeof prop.description === 'string') { zodType = zodType.describe(prop.description); } break; default: zodType = z.unknown(); if (prop.description && typeof prop.description === 'string') { zodType = zodType.describe(prop.description); } } if (!required.includes(key)) { zodType = zodType.optional(); } zodFields[key] = zodType; } return z.object(zodFields); } private formatToolResult(result: unknown): string { if (Array.isArray(result)) { return result .map((item: unknown) => { if (typeof item === 'object' && item !== null && 'type' in item && item.type === 'text' && 'text' in item) { return String(item.text); } return JSON.stringify(item); }) .join('\n'); } if (typeof result === 'object' && result !== null) { return JSON.stringify(result, null, 2); } return String(result); } getTools(): Map<string, MastraToolFromMcp> { return this.tools; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Tommertom/plugwise-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server