genkit icon indicating copy to clipboard operation
genkit copied to clipboard

expose flows in genkitx-mcp as MCP tools

Open codercatdev opened this issue 1 month ago • 0 comments

Is your feature request related to a problem? Please describe. I am using genkit flows well, but I really need my MCP to interact at the flow level not necessarily the tool level.

Describe the solution you'd like It would be better to wrap each flow as an MCP tool as well.

Describe alternatives you've considered I am doing it manually now

function createMCPServer(uid: string): Server {
    const server = new Server(
        {
            name: 'flipfeeds-mcp-server',
            version: '1.0.0',
        },
        {
            capabilities: {
                tools: {},
            },
        }
    );

    // Register tool list handler
    server.setRequestHandler(ListToolsRequestSchema, async () => {
        // Get all Genkit flows and expose them as MCP tools
        const actions = await ai.registry.listActions();

        // Filter for flows only - exclude models and other action types
        const allActions = Object.values(actions);
        const flows = allActions.filter((a: any) => {
            // Flows are callable functions with __action metadata
            // Exclude googleai models (they start with 'googleai/')
            const flowName = a.__action?.name || '';
            return (
                typeof a === 'function' &&
                a.__action &&
                !flowName.startsWith('googleai/') &&
                flowName !== 'generate' // Exclude the default 'generate' flow
            );
        });

        console.log(
            `Found ${flows.length} FlipFeeds flows (filtered out ${allActions.length - flows.length} models/other actions)`
        );

        const tools = flows.map((flow: any) => {
            const flowAction = flow.__action;
            const flowName = flowAction.name;

            console.log('Processing flow:', flowName);

            // Convert Zod schema to JSON schema for MCP
            const inputSchema = flowAction.inputSchema || {};

            return {
                name: flowName,
                description: flowAction.description || `Execute the ${flowName} flow`,
                inputSchema: {
                    type: 'object',
                    properties: inputSchema._def?.shape
                        ? Object.entries(inputSchema._def.shape).reduce(
                              (acc: any, [key, value]: [string, any]) => {
                                  acc[key] = {
                                      type:
                                          value._def?.typeName === 'ZodString'
                                              ? 'string'
                                              : value._def?.typeName === 'ZodNumber'
                                                ? 'number'
                                                : value._def?.typeName === 'ZodBoolean'
                                                  ? 'boolean'
                                                  : value._def?.typeName === 'ZodArray'
                                                    ? 'array'
                                                    : value._def?.typeName === 'ZodObject'
                                                      ? 'object'
                                                      : 'string',
                                      description: value._def?.description || `${key} parameter`,
                                  };
                                  return acc;
                              },
                              {}
                          )
                        : {},
                },
            };
        });

        console.log(`Listing ${tools.length} MCP tools for user ${uid}`);

        return {
            tools,
        };
    });

    // Register tool call handler
    server.setRequestHandler(CallToolRequestSchema, async (request) => {
        const { name, arguments: args } = request.params;

        console.log(`Executing flow: ${name} for user ${uid}`);
        console.log('Flow arguments:', args);

        try {
            // Get the flow from Genkit registry
            const actions = await ai.registry.listActions();
            const flowEntry = Object.values(actions).find((a: any) => a.__action?.name === name);

            if (!flowEntry) {
                throw new Error(`Flow not found: ${name}`);
            }

            // Inject uid into the arguments if not already present
            const flowArgs = {
                uid,
                ...args,
            };

            // Execute the flow - flowEntry is the callable flow function
            const result = await flowEntry(flowArgs);

            // Return the result as MCP content
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(result, null, 2),
                    },
                ],
            };
        } catch (error) {
            const errorMessage = error instanceof Error ? error.message : 'Unknown error';
            console.error(`Error executing flow ${name}:`, errorMessage);

            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify({ error: errorMessage }, null, 2),
                    },
                ],
                isError: true,
            };
        }
    });

    return server;
}

Additional context Add any other context or screenshots about the feature request here.

codercatdev avatar Nov 12 '25 16:11 codercatdev