# fastmcp-langfuse

Plug Langfuse observability into any [FastMCP v3](https://gofastmcp.com) server. One function call, zero boilerplate.

```python
from langfuse import get_client
get_client()

from fastmcp import FastMCP
from fastmcp_langfuse import instrument

mcp = FastMCP("My Server")
instrument(mcp)

@mcp.tool()
def anything(x: str) -> str:   # fully traced, just like that
    return "hello"
```

Three imports, one `instrument()` call. Vibecode the rest -- every tool, resource, and prompt gets session tracking, I/O capture, environment tags, and trace URLs automatically.

## Install

```bash
pip install fastmcp-langfuse
# or
uv add fastmcp-langfuse
```

You'll also need `langfuse` in your project -- this library sets the right OTel span attributes that the Langfuse SDK picks up, but doesn't import it directly.

## What you get for free

Once you call `instrument(mcp)`, every handler registered after it gets:

- **Input/output** -- function args serialised as input, return value as output
- **Session & user tracking** -- `session_id` and `client_id` from FastMCP context
- **Environment & release tags** -- tag your traces with `production`, `staging`, etc.
- **Trace URLs** -- clickable Langfuse links printed to your console

No decorators on every handler. No manual span enrichment. Just works.

## Configure it

Pass kwargs directly or use a config object -- whatever feels right:

```python
# kwargs
instrument(mcp, environment="production", release="v1.2.3", trace_urls=False)

# or config object
from fastmcp_langfuse import LangfuseConfig

instrument(mcp, config=LangfuseConfig(environment="production"))
```

| Field              | Type   | Default                      | What it does                              |
|--------------------|--------|------------------------------|-------------------------------------------|
| `environment`      | `str`  | `""`                         | Tags `langfuse.environment` on every span |
| `release`          | `str`  | `""`                         | Tags `langfuse.release` on every span     |
| `session_tracking` | `bool` | `True`                       | Sets session/user IDs from FastMCP context|
| `trace_urls`       | `bool` | `True`                       | Prints Langfuse trace URLs to console     |
| `base_url`         | `str`  | `https://cloud.langfuse.com` | Your Langfuse instance URL                |

## Need more control?

Use `@langfuse_trace` on individual handlers instead of (or alongside) `instrument()`:

```python
from fastmcp_langfuse import langfuse_trace

@mcp.tool()
@langfuse_trace
def special_tool(x: str) -> str:
    return "traced with the decorator"
```

Same enrichment, just explicit.

## How it works under the hood

FastMCP auto-creates OTel spans for every tool/resource/prompt call. This library wraps your handlers to enrich those spans with Langfuse-specific attributes (`langfuse.session.id`, `langfuse.trace.input`, etc.) that the Langfuse SDK recognises when exporting.

The library only depends on `opentelemetry-api` and `fastmcp`. Lightweight, no opinions about your config management.

## Examples

Check out `examples/` for a full working demo with tools, resources, prompts, cost tracking, user feedback, and distributed tracing. It uses `pydantic-settings` to bridge env vars to `LangfuseConfig` -- but that's just one way to do it, bring your own config however you want.

```bash
cd examples && cp .env.example .env
# add your LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY

uv run python examples/server.py   # terminal 1
uv run python examples/client.py   # terminal 2
```

## License

MIT
