Skip to main content
Glama
expert-suggestion-limited-permission-mcp.md5.87 kB
Short version: run the same server in two modes and expose different tools per mode. Don’t rely on the Langfuse token for scoping; enforce semantics in your MCP server and config/CLI. Here’s how I’d do it. --- ## 1. Core idea You already noticed the key constraint: Langfuse API keys today are project-level and generally allow both read & write. ([Langfuse][1]) So “read-only” vs “read-write” must be enforced by your MCP server, not by the key. With MCP, the safest + most idiomatic approach is: * Only **expose read-only tools** when in read-only mode. * Expose **read + write tools** when in read-write mode. * Enforce mode again inside tool handlers as a defense in depth. If a tool doesn’t exist in the server’s advertised tool list, the client / model cannot call it. ([Model Context Protocol][2]) --- ## 2. How to expose the two modes (practical pattern) ### a) Two entrypoints (recommended) Ship a single package with two binaries: * `langfuse-mcp-ro` → read-only * `langfuse-mcp` (or `langfuse-mcp-rw`) → read-write In `package.json`: ```json { "bin": { "langfuse-mcp": "./dist/cli-rw.js", "langfuse-mcp-ro": "./dist/cli-ro.js" } } ``` Each just calls a shared `createServer({ mode: "ro" | "rw" })`. This gives users a very clear install story in MCP clients (Claude Desktop, mcp.co, etc): **Read-only config:** ```json { "command": "npx", "args": ["-y", "langfuse-mcp-server", "ro"] } ``` **Read-write config (explicit opt-in):** ```json { "command": "npx", "args": ["-y", "langfuse-mcp-server", "rw"] } ``` (Exact CLI shape is up to you; above is illustrative.) ### b) Or: single binary with a `--mode` flag If you prefer one entrypoint: ```bash npx langfuse-mcp-server --mode=readonly npx langfuse-mcp-server --mode=readwrite ``` Plus support `LANGFUSE_MCP_MODE=readonly|readwrite` as an alternative for mcp.co configs that like env-based setup. **Important:** default to **read-only**, and make read-write explicit (`--mode=rw`). --- ## 3. Enforcing it inside your MCP server ### a) Tool surface by mode At startup: ```ts type Mode = "readonly" | "readwrite"; function getMode(): Mode { const m = process.env.LANGFUSE_MCP_MODE || "readonly"; return m === "readwrite" ? "readwrite" : "readonly"; } const mode = getMode(); const tools: McpTool[] = [ // always allowed: read-only tools listProjects, getTrace, listTraces, getObservation, // etc ]; if (mode === "readwrite") { tools.push( createScore, updateTraceMetadata, deleteTraceSafely, // any mutating ops ); } server.setTools(tools); ``` Now, in read-only mode, write tools are literally not advertised; clients can’t call them. ### b) Defense in depth in handlers Even for write tools, check mode at runtime: ```ts function assertWriteEnabled(mode: Mode) { if (mode !== "readwrite") { throw new McpError({ code: "PERMISSION_DENIED", message: "This Langfuse MCP server is running in read-only mode; writes are disabled.", }); } } const createScore: McpTool = { name: "langfuse_create_score", description: "Create a score on a trace", inputSchema: { /* ... */ }, async invoke(args, ctx) { assertWriteEnabled(ctx.mode); // perform write via Langfuse API } }; ``` This prevents accidental exposure if someone misconfigures the tool list. --- ## 4. What users actually choose For your README / mcp.co entry, I’d document two installation options: 1. **Default (safe)** – Read-only * Uses your Langfuse API key. * Only exposes tools that **never mutate** (fetch traces, spans, scores, datasets, etc). * Recommended for most users. 2. **Advanced** – Read-write * Requires explicit `--mode=readwrite` or `langfuse-mcp` binary. * Exposes tools that can create/update scores, tags, metadata, etc. * Add a clear warning that models can modify Langfuse data. That mirrors best practice advice for MCP-style integrations: use least privilege + explicit consent. ([Model Context Protocol][3]) --- ## 5. Extra hardening (nice-to-have) If you want to go a bit further (partly opinionated/speculative): 1. **Prefix write tools clearly** * e.g. `langfuse.write.create_score`, `langfuse.write.tag_trace` * Makes their intent obvious in MCP clients. 2. **Optional confirmation guard** * Env flag like `LANGFUSE_MCP_WRITE_CONFIRMATION=true` to log or require a specific argument (`confirmed: true`) for destructive ops. 3. **Separate Langfuse project for write mode (if users care)** * Suggest (in docs) using a different Langfuse project / key for experimentation vs prod, even though technically both keys are RW. ([Langfuse][1]) 4. **Telemetry** * Log all write operations (tool name, trace id, etc.) so users can audit what the MCP-driven agent is doing. --- If you’d like, I can sketch the actual TS/Node skeleton for `langfuse-mcp-server` with mode handling, tool registration, and a couple of concrete Langfuse calls wired in. **Confidence:** High. (Based on MCP spec & patterns from existing MCP servers; exact Langfuse key scoping details are from current docs.) **Key references (URLs in code form):** ```text MCP server concepts: https://modelcontextprotocol.io/docs/learn/server-concepts MCP tools spec: https://modelcontextprotocol.io/specification/2025-06-18/server/tools Langfuse RBAC/API: https://langfuse.com/docs/administration/rbac Supabase MCP guide (read-only pattern): https://supabase.com/docs/guides/getting-started/mcp ``` [1]: https://langfuse.com/docs/administration/rbac?utm_source=chatgpt.com "Role-Based Access Controls in Langfuse" [2]: https://modelcontextprotocol.io/specification/2025-06-18/server/tools?utm_source=chatgpt.com "Tools" [3]: https://modelcontextprotocol.io/docs/learn/server-concepts?utm_source=chatgpt.com "Understanding MCP servers"

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/therealsachin/langfuse-mcp-server'

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