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
JavaScript
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: `[ -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(
`[ -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: `[ -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: `[ -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: `[ -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