Langfuse Prompt Management MCP Server

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { ListPromptsRequestSchema, ListPromptsRequest, ListPromptsResult, GetPromptRequestSchema, GetPromptRequest, GetPromptResult, CallToolResult, } from "@modelcontextprotocol/sdk/types.js"; import { Langfuse, ChatPromptClient } from "langfuse"; import { extractVariables } from "./utils.js"; import { z } from "zod"; // Requires Environment Variables const langfuse = new Langfuse(); // Create MCP server instance with a "prompts" capability. const server = new McpServer( { name: "langfuse-prompts", version: "1.0.0", }, { capabilities: { prompts: {}, }, } ); async function listPromptsHandler( request: ListPromptsRequest ): Promise<ListPromptsResult> { try { const cursor = request.params?.cursor; const page = cursor ? Number(cursor) : 1; if (cursor !== undefined && isNaN(page)) { throw new Error("Cursor must be a valid number"); } const res = await langfuse.api.promptsList({ limit: 100, page, label: "production", }); const resPrompts: ListPromptsResult["prompts"] = await Promise.all( res.data.map(async (i) => { const prompt = await langfuse.getPrompt(i.name, undefined, { cacheTtlSeconds: 0, }); const variables = extractVariables(JSON.stringify(prompt.prompt)); return { name: i.name, arguments: variables.map((v) => ({ name: v, required: false, })), }; }) ); return { prompts: resPrompts, nextCursor: res.meta.totalPages > page ? (page + 1).toString() : undefined, }; } catch (error) { console.error("Error fetching prompts:", error); throw new Error("Failed to fetch prompts"); } } async function getPromptHandler( request: GetPromptRequest ): Promise<GetPromptResult> { const promptName: string = request.params.name; const args = request.params.arguments || {}; try { // Initialize Langfuse client and fetch the prompt by name. let compiledTextPrompt: string | undefined; let compiledChatPrompt: ChatPromptClient["prompt"] | undefined; // Langfuse chat prompt type try { // try chat prompt type first const prompt = await langfuse.getPrompt(promptName, undefined, { type: "chat", }); if (prompt.type !== "chat") { throw new Error(`Prompt '${promptName}' is not a chat prompt`); } compiledChatPrompt = prompt.compile(args); } catch (error) { // fallback to text prompt type const prompt = await langfuse.getPrompt(promptName, undefined, { type: "text", }); compiledTextPrompt = prompt.compile(args); } if (compiledChatPrompt) { const result: GetPromptResult = { messages: compiledChatPrompt.map((msg) => ({ role: ["ai", "assistant"].includes(msg.role) ? "assistant" : "user", content: { type: "text", text: msg.content, }, })), }; return result; } else if (compiledTextPrompt) { const result: GetPromptResult = { messages: [ { role: "user", content: { type: "text", text: compiledTextPrompt }, }, ], }; return result; } else { throw new Error(`Failed to get prompt for '${promptName}'`); } } catch (error: any) { throw new Error( `Failed to get prompt for '${promptName}': ${error.message}` ); } } // Register handlers server.server.setRequestHandler(ListPromptsRequestSchema, listPromptsHandler); server.server.setRequestHandler(GetPromptRequestSchema, getPromptHandler); // Tools for compatibility server.tool( "get-prompts", "Get prompts that are stored in Langfuse", { cursor: z .string() .optional() .describe("Cursor to paginate through prompts"), }, async (args) => { try { const res = await listPromptsHandler({ method: "prompts/list", params: { cursor: args.cursor, }, }); const parsedRes: CallToolResult = { content: res.prompts.map((p) => ({ type: "text", text: JSON.stringify(p), })), }; return parsedRes; } catch (error) { return { content: [{ type: "text", text: "Error: " + error }], isError: true, }; } } ); server.tool( "get-prompt", "Get a prompt that is stored in Langfuse", { name: z .string() .describe( "Name of the prompt to retrieve, use get-prompts to get a list of prompts" ), arguments: z .record(z.string()) .optional() .describe( 'Arguments with prompt variables to pass to the prompt template, json object, e.g. {"<name>":"<value>"}' ), }, async (args, extra) => { try { const res = await getPromptHandler({ method: "prompts/get", params: { name: args.name, arguments: args.arguments, }, }); const parsedRes: CallToolResult = { content: [ { type: "text", text: JSON.stringify(res), }, ], }; return parsedRes; } catch (error) { return { content: [{ type: "text", text: "Error: " + error }], isError: true, }; } } ); async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Langfuse Prompts MCP Server running on stdio"); } main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); });