chore: import upstream snapshot with attribution
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Waiting to run
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Waiting to run
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
# OpenTelemetry
|
||||
|
||||
Your server is already traced. You don't have to add anything.
|
||||
|
||||
Every server you create emits an [OpenTelemetry](https://opentelemetry.io/) span for every
|
||||
message it handles. You didn't write that, and you don't import it. It is there the moment you
|
||||
call `MCPServer(...)`.
|
||||
|
||||
```python title="server.py"
|
||||
--8<-- "docs_src/opentelemetry/tutorial001.py"
|
||||
```
|
||||
|
||||
That is a complete, traced server. Call `search_books` and a span is created for it. The same is
|
||||
true for the low-level `Server`: the tracing lives on both.
|
||||
|
||||
## What you get
|
||||
|
||||
Every inbound message becomes a `SERVER` span named after the method and its target. So a
|
||||
`tools/call` for `search_books` is the span `tools/call search_books`, and a bare `tools/list`
|
||||
is just `tools/list`.
|
||||
|
||||
Each span carries a few attributes:
|
||||
|
||||
* `mcp.method.name` and `mcp.protocol.version`, on every span.
|
||||
* `jsonrpc.request.id`, on a request (a notification has none).
|
||||
* A handler that raises sets the span status to error. So does a tool result with `is_error=True`.
|
||||
|
||||
And because tracing a tool call is such a common thing to want, `tools/call` spans speak
|
||||
OpenTelemetry's [GenAI semantic conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/):
|
||||
|
||||
* `gen_ai.operation.name`, set to `"execute_tool"`.
|
||||
* `gen_ai.tool.name`, set to the tool being called.
|
||||
|
||||
A `prompts/get` span gets `gen_ai.prompt.name` in the same spirit. The list methods carry no
|
||||
`gen_ai.*` keys, because there is nothing to name.
|
||||
|
||||
!!! tip
|
||||
Those GenAI attributes are the reason a tracing UI groups your tool calls the way it groups
|
||||
any other agent's. You get that grouping for free, with no extra code.
|
||||
|
||||
## It costs nothing until you want it
|
||||
|
||||
Here is the part that makes "on by default" a comfortable default.
|
||||
|
||||
The SDK depends only on `opentelemetry-api`, the lightweight half of OpenTelemetry. With no SDK
|
||||
and no exporter installed, creating a span is a no-op. So the spans your server is emitting right
|
||||
now cost you almost nothing, and nobody is collecting them.
|
||||
|
||||
The day you want to *see* them, you install the other half and point it somewhere:
|
||||
|
||||
```console
|
||||
uv add opentelemetry-sdk opentelemetry-exporter-otlp
|
||||
```
|
||||
|
||||
Configure an exporter the usual OpenTelemetry way, and every span the SDK has been quietly
|
||||
creating lights up. Your server code does not change. Not one line.
|
||||
|
||||
!!! info
|
||||
[Pydantic Logfire](https://logfire.pydantic.dev/) is one such backend, and it does the
|
||||
configuration for you: `pip install logfire`, `logfire.configure()`, and your MCP spans show
|
||||
up in the live view. It is built on OpenTelemetry, so anything below applies to it too.
|
||||
|
||||
## Traces that cross the wire
|
||||
|
||||
A trace is most useful when it follows a request from the client into the server, in one
|
||||
connected picture.
|
||||
|
||||
When the client and the server both run the SDK, that connection is automatic. The client injects
|
||||
the [W3C trace context](https://www.w3.org/TR/trace-context/) into the request, and the server
|
||||
reads it back out, so the server span nests under the client span in the same trace. This is
|
||||
[SEP-414](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/414), and you get it without
|
||||
asking.
|
||||
|
||||
If the inbound message carries no trace context, for example a request from a client that is not
|
||||
the SDK, the server span simply parents to whatever span is already current on the server, rather
|
||||
than starting a brand-new orphan trace.
|
||||
|
||||
## Turning it off
|
||||
|
||||
Tracing is a middleware, the first one on your server's list. If you really want a server that
|
||||
emits no spans, take it off:
|
||||
|
||||
```python
|
||||
from mcp.server._otel import OpenTelemetryMiddleware
|
||||
|
||||
mcp._lowlevel_server.middleware[:] = [
|
||||
m for m in mcp._lowlevel_server.middleware if not isinstance(m, OpenTelemetryMiddleware)
|
||||
]
|
||||
```
|
||||
|
||||
!!! warning
|
||||
That import has a leading underscore, and that is on purpose. The class is provisional, the
|
||||
same way [`Server.middleware`](../advanced/middleware.md) is provisional, so the import path is something
|
||||
you should expect to change. You almost never need this: with no exporter installed the spans
|
||||
are free, so the usual answer is to leave them on and not install an exporter.
|
||||
|
||||
## Recap
|
||||
|
||||
* Every `MCPServer` and every low-level `Server` emits one `SERVER` span per inbound message, out
|
||||
of the box. You write nothing.
|
||||
* Spans carry `mcp.method.name` and `mcp.protocol.version`; `tools/call` and `prompts/get` also
|
||||
carry GenAI attributes so your tool calls group like any other agent's.
|
||||
* It costs nothing until you install an OpenTelemetry SDK and an exporter, and then it lights up
|
||||
with no change to your server.
|
||||
* Client-to-server trace context propagates automatically when both sides run the SDK.
|
||||
|
||||
The thing that decides whether a request runs at all is **[Authorization](authorization.md)**.
|
||||
Reference in New Issue
Block a user