UNPKG

genkitx-mcp

Version:

A Genkit plugin that provides interoperability between Genkit and Model Context Protocol (MCP). Both client and server use cases are supported.

189 lines 5.8 kB
import { GenkitError, Message } from "genkit"; import { toJsonSchema } from "@genkit-ai/core/schema"; import { logger } from "genkit/logging"; import { toToolDefinition } from "genkit/tool"; class GenkitMcpServer { ai; options; server; actionsResolved = false; toolActions = []; promptActions = []; constructor(ai, options) { this.ai = ai; this.options = options; this.setup(); } async setup() { if (this.actionsResolved) return; const { Server } = await import("@modelcontextprotocol/sdk/server/index.js"); this.server = new Server( { name: this.options.name, version: this.options.version || "1.0.0" }, { capabilities: { prompts: {}, tools: {} } } ); const { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListToolsRequestSchema } = await import("@modelcontextprotocol/sdk/types.js"); this.server.setRequestHandler( ListToolsRequestSchema, this.listTools.bind(this) ); this.server.setRequestHandler( CallToolRequestSchema, this.callTool.bind(this) ); this.server.setRequestHandler( ListPromptsRequestSchema, this.listPrompts.bind(this) ); this.server.setRequestHandler( GetPromptRequestSchema, this.getPrompt.bind(this) ); const allActions = await this.ai.registry.listActions(); const toolList = []; const promptList = []; for (const k in allActions) { if (k.startsWith("/tool/")) { toolList.push(allActions[k]); } else if (k.startsWith("/prompt/")) { promptList.push(allActions[k]); } } this.toolActions = toolList; this.promptActions = promptList; this.actionsResolved = true; } async listTools(req) { await this.setup(); return { tools: this.toolActions.map((t) => { const def = toToolDefinition(t); return { name: def.name, inputSchema: def.inputSchema || { type: "object" }, description: def.description }; }) }; } async callTool(req) { await this.setup(); const tool = this.toolActions.find( (t) => t.__action.name === req.params.name ); if (!tool) throw new GenkitError({ status: "NOT_FOUND", message: `Tried to call tool '${req.params.name}' but it could not be found.` }); const result = await tool(req.params.arguments); return { content: [{ type: "text", text: JSON.stringify(result) }] }; } async listPrompts(req) { await this.setup(); return { prompts: this.promptActions.map((p) => { return { name: p.__action.name, description: p.__action.description, arguments: toMcpPromptArguments(p) }; }) }; } async getPrompt(req) { await this.setup(); const prompt = this.promptActions.find( (p) => p.__action.name === req.params.name ); if (!prompt) throw new GenkitError({ status: "NOT_FOUND", message: `[@genkit-ai/mcp] Tried to call prompt '${req.params.name}' but it could not be found.` }); const result = await prompt(req.params.arguments); return { description: prompt.__action.description, messages: result.messages.map(toMcpPromptMessage) }; } async start(transport) { if (!transport) { const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js"); transport = new StdioServerTransport(); } await this.setup(); await this.server.connect(transport); logger.info( `[@genkit-ai/mcp] MCP server '${this.options.name}' started successfully.` ); } } function toMcpPromptArguments(p) { const jsonSchema = toJsonSchema({ schema: p.__action.inputSchema, jsonSchema: p.__action.inputJsonSchema }); if (!jsonSchema) return void 0; if (!jsonSchema.properties) throw new GenkitError({ status: "FAILED_PRECONDITION", message: "[@genkit-ai/mcp] MCP prompts must take objects as input schema." }); const args = []; for (const k in jsonSchema.properties) { const { type, description } = jsonSchema.properties[k]; if (type !== "string" && (!Array.isArray(type) || !type.includes("string"))) { throw new GenkitError({ status: "FAILED_PRECONDITION", message: `[@genkit-ai/mcp] MCP prompts may only take string arguments, but ${p.__action.name} has property '${k}' of type '${type}'.` }); } args.push({ name: k, description, required: jsonSchema.required?.includes(k) }); } return args; } const ROLE_MAP = { model: "assistant", user: "user" }; function toMcpPromptMessage(messageData) { if (messageData.role !== "model" && messageData.role !== "user") { throw new GenkitError({ status: "UNIMPLEMENTED", message: `[@genkit-ai/mcp] MCP prompt messages do not support role '${messageData.role}'. Only 'user' and 'model' messages are supported.` }); } const message = new Message(messageData); const common = { role: ROLE_MAP[messageData.role] }; if (message.media) { const { url, contentType } = message.media; if (!url.startsWith("data:")) throw new GenkitError({ status: "UNIMPLEMENTED", message: `[@genkit-ai/mcp] MCP prompt messages only support base64 data images.` }); const mimeType = contentType || url.substring(url.indexOf(":") + 1, url.indexOf(";")); const data = url.substring(url.indexOf(",") + 1); return { ...common, content: { type: "image", mimeType, data } }; } else { return { ...common, content: { type: "text", text: message.text } }; } } export { GenkitMcpServer }; //# sourceMappingURL=server.mjs.map