On this page
Updated October 2026
Instrumenting an MCP server with OpenTelemetry means recording the server-side tool execution as part of a distributed trace, exporting only approved context, and verifying that the resulting telemetry explains both technical behavior and the client-visible result.
Because MCP SDKs and observability conventions change, use the maintained plugin for repository-specific implementation and this guide for the durable architecture and verification process.
Start with the instrumentation question
Do not instrument everything because it is available. Write the questions the telemetry must answer:
- Which server and tool received the call?
- Did the operation complete, fail, time out, or get cancelled?
- Which dependency explains a failure or latency change?
- Is this call part of a repeated or incomplete workflow?
- Which permitted user, account, agent, or client was affected?
- Did the broader job produce a meaningful outcome?
Each question belongs to a different layer. OpenTelemetry provides the technical evidence. A behavioral layer can organize that evidence into users, sessions, use cases, and outcomes.
The reference architecture
- An AI host or agent selects an MCP tool.
- The MCP client sends the request and standard trace context where supported.
- The MCP server records the tool execution boundary.
- Downstream service and database spans continue the trace.
- An OpenTelemetry pipeline processes and exports the approved telemetry.
- Operational and behavioral products analyze the data for their respective jobs.
The OpenTelemetry semantic conventions ↗ define common meanings across telemetry. Use the OpenTelemetry context propagation guide ↗ for distributed tracing, and verify propagation in your MCP SDK and client. A protocol revision alone does not prove that the full path is instrumented.
Instrument the tool boundary
A practical implementation sequence
- Inventory the runtime. Record the language, MCP SDK version, client, transport, deployment model, and any existing tracing. Choose a maintained integration that explicitly supports that combination.
- Set up the telemetry provider. Initialize instrumentation before the modules it instruments. The official OpenTelemetry Node.js guide ↗ demonstrates this load-order requirement; use the corresponding SDK guide for other runtimes.
- Configure the destination. Match the exporter's OTLP transport, endpoint, and authentication to the receiving backend. Keep credentials in deployment configuration, never source or tool arguments. Give test and production traffic distinct service and environment identities.
- Instrument once. Add the supported MCP integration at the tool boundary. Inspect one exported trace before adding dashboards. An HTTP request span is not proof that tool-level instrumentation works.
- Test both result paths. Exercise a normal result, domain failure, timeout, and recovery. Check timing, status, parent relationships, and redaction against the known synthetic request.
- Test delivery at shutdown. Let the SDK flush using its supported shutdown lifecycle. Short-lived processes and serverless runtimes need a deployment-specific check that buffered telemetry reaches the backend.
- Roll out with a coverage check. Compare eligible server activity with delivered telemetry, excluding duplicates and intentional sampling. Alert on delivery failures independently from application failures.
For stdio MCP servers, do not send telemetry or diagnostic console output to stdout. That stream carries protocol messages. Use stderr for diagnostics or a separate exporter. The official MCP PHP SDK logging guidance ↗ makes this transport distinction explicit.
What the tool span should represent
The most useful server span represents the MCP tool operation the client invoked. An HTTP middleware span alone can show transport behavior but may not explain which tool ran or what the client received. A downstream API span explains a dependency but not the MCP operation that triggered it.
Place instrumentation where the server has enough context to:
- identify the service and operation
- time the complete server-side execution
- distinguish normal completion from tool and protocol failures
- preserve parent and downstream trace relationships
- apply the approved content and identity policy
- record the final status after result serialization
Avoid tracing the same operation in both middleware and every handler unless the spans represent genuinely different work. Duplicate spans distort call counts and latency.
Use official and current conventions
MCP and generative AI conventions are evolving. Use the current official MCP SDK and OpenTelemetry documentation for your language. Pin versions, read migration notes, and test changes before production.
The 2026 MCP revision removed the earlier protocol handshake and protocol-level session identifier from the modern stateless core. Do not design new product analytics around a field that modern requests no longer carry. Standard distributed trace context still connects a request across services. A multi-request product journey needs a separate, permitted application-level relationship.
Add MCP observability to an existing OpenTelemetry setup
If your service already runs OpenTelemetry, add MCP spans and a duration metric to the providers it already configures instead of starting a second SDK, exporter or global provider.
- Check for existing MCP spans first. Send one synthetic tool call and inspect the export. If an installed integration already creates a tool span, keep it and check its attributes rather than adding a second span.
- Reuse the global providers. Your existing startup code, such as
opentelemetry-instrumentin Python or a Node.js SDK loaded before the server module, already owns the exporter, resource attributes and sampling. - Wrap the tool handler once. Name the span after the method and tool, set the MCP identifiers, record the duration in seconds and classify failures.
- Keep content capture off. The examples below record identifiers only, never arguments or results.
The attribute names follow the OTel MCP conventions ↗, which are still in Development; check them against your installed versions. Both snippets are minimal illustrations to review, not a supported package. The Flowlines plugin ↗ remains the source of truth for repository-specific instrumentation.
Python
import time
from mcp.server.fastmcp import FastMCP
from opentelemetry import metrics, trace
from opentelemetry.trace import SpanKind
mcp = FastMCP("sales-mcp")
# Reuse the providers your service already configures. Do not create new ones here.
tracer = trace.get_tracer("sales-mcp.tools")
duration = metrics.get_meter("sales-mcp.tools").create_histogram(
"mcp.server.operation.duration", unit="s"
)
@mcp.tool()
async def find_account(domain: str) -> dict:
attributes = {"mcp.method.name": "tools/call", "gen_ai.tool.name": "find_account"}
start = time.perf_counter()
with tracer.start_as_current_span(
"tools/call find_account", kind=SpanKind.SERVER, attributes=attributes
) as span:
try:
return await lookup_account(domain)
except Exception:
# FastMCP returns the exception to the client as an isError result.
attributes["error.type"] = "tool_error"
span.set_attribute("error.type", "tool_error")
raise
finally:
duration.record(time.perf_counter() - start, attributes)The span records the exception and sets an error status when it leaves the with block. Requests rejected before they reach the handler, such as malformed JSON-RPC, need monitoring at the protocol or transport boundary.
TypeScript
import { metrics, SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
const server = new McpServer({ name: "sales-mcp", version: "1.4.0" });
// Reuse the SDK your service already starts. Do not register a second provider here.
const tracer = trace.getTracer("sales-mcp.tools");
const duration = metrics.getMeter("sales-mcp.tools")
.createHistogram("mcp.server.operation.duration", { unit: "s" });
server.registerTool(
"find_account",
{ description: "Find an account by domain", inputSchema: { domain: z.string() } },
async ({ domain }) => {
const attributes: Record<string, string> = { "mcp.method.name": "tools/call", "gen_ai.tool.name": "find_account" };
const start = performance.now();
return tracer.startActiveSpan("tools/call find_account", { kind: SpanKind.SERVER, attributes }, async (span) => {
try {
const result = await lookupAccount(domain); // returns a CallToolResult
if (result.isError) attributes["error.type"] = "tool_error";
return result;
} catch (error) {
// McpServer returns a thrown error to the client as an isError result.
attributes["error.type"] = "tool_error";
span.recordException(error as Error);
throw error;
} finally {
if (attributes["error.type"]) span.setAttributes(attributes).setStatus({ code: SpanStatusCode.ERROR });
duration.record((performance.now() - start) / 1000, attributes);
span.end();
}
});
},
);Load your existing instrumentation before the server module, for example with node --import ./instrumentation.mjs server.js, so automatic instrumentation can patch the modules it supports. On stdio servers, never attach a console exporter that writes to stdout.
After deployment, run the acceptance cases below and compare the result with the production monitoring signals your team alerts on. To keep Grafana or Datadog dashboards and add usage analytics beside them, see MCP usage analytics for an OpenTelemetry, Grafana or Datadog stack.
Define the data boundary first
MCP tools can handle source code, personal data, customer records, credentials, financial data, or internal documents. The fact that content is technically available does not make it appropriate telemetry.
Before enabling export:
- Inventory the classes of content the tool can receive and return.
- Decide which operational questions require content rather than metadata.
- Exclude secrets, tokens, cookies, authorization headers, and credentials.
- Minimize personal and customer data.
- Prefer pseudonymous identifiers when the use case permits them.
- Apply allowlists or redaction before export.
- Confirm retention, regional, and access requirements.
- Test the result with synthetic sensitive values.
Treat client-reported identity as analytics context, not an authorization source. Authorization must remain in the server's trusted control path.
Propagate trace context
Use W3C Trace Context when the MCP client and server support it. Continue the incoming trace rather than creating an unrelated trace for the same operation, then propagate context to downstream services.
If an upstream client does not provide trace context, the server can still create a root trace for the operation. Be explicit about what remains uncorrelated. Do not fabricate a complete journey from coincidental timestamps or similar content.
Represent errors honestly
A successful transport response does not always mean the tool completed its domain job. Conversely, one failed dependency call can recover before the tool returns a useful result.
Verify that telemetry distinguishes:
- transport and authentication failure
- protocol validation failure
- tool or domain error
- downstream dependency error
- timeout or cancellation
- recovered execution
- successful result with unknown broader outcome
This separation helps engineering find the technical cause without teaching product teams that every green call is a successful user session.
Validate with a representative test matrix
Sampling changes what your analytics can claim
OpenTelemetry sampling ↗ reduces the telemetry retained for investigation. An error-biased sample is useful for debugging but cannot directly estimate the population's success rate. Independently sampled calls can also leave a partial multi-call journey.
Document what was eligible, what was sampled, what was delivered, and which journeys remain complete enough to analyze. Use unsampled counters for traffic totals where available. Scope behavioral rates to the observed population unless you have a justified estimation method, and preserve unknown outcomes when evidence is missing.
Acceptance cases
| Test | What it should prove |
|---|---|
| Normal success | One operation appears with accurate timing and status |
| Expected domain error | The tool-level failure is visible even if transport succeeds |
| Downstream timeout | The dependency and server operation connect correctly |
| Recovery | A transient error does not become a false failed outcome |
| Repeated call | Each invocation is distinct without duplicate instrumentation |
| Multi-tool workflow | Related work can be analyzed when permitted context exists |
| Missing outcome | The system reports unknown rather than inventing success |
| Sensitive input | Protected content is absent or correctly redacted |
Run the matrix in a safe environment before production. After deployment, verify a small number of real permitted sessions and monitor telemetry coverage.
Build views for different owners
Each owner needs a different view of the same telemetry; Flowlines provides the product and customer views (users, AI clients, use cases, outcomes and signals) while your operational backend keeps the platform view.
Platform and SRE
Availability, throughput, latency, saturation, protocol versions, client mix, authentication failures, dependency health, and exporter health.
MCP engineering
Calls by tool, tool failures, retries, result categories, unused tools, release changes, and representative traces.
Use MCP tool analytics to interpret tool volume alongside use cases and failures. A catalog and sufficient telemetry coverage are needed before calling a tool unused.
Product and AI teams
Unique users, attempted use cases, common paths, completion, outcome coverage, loops, corrections, unsupported requests, feedback, and unmet needs.
Use MCP server analytics for adoption and repeat usage. Use MCP session analytics to follow related calls and keep reported outcomes separate from unknown results.
Customer and commercial teams
Affected accounts, recurring adoption barriers, implementation questions, product demand, and reviewed high-value signals.
Do not send every raw failure to every team. Route a concise finding with the relevant scope and evidence.
OpenTelemetry does not remove the need for product semantics
Flowlines adds the product layer on top of OpenTelemetry: who called each tool, from which AI client, what they were trying to do, and whether the job finished.
OpenTelemetry can show that a tool ran, which service it called, how long it took, and where a technical failure occurred. It cannot decide your user's intended job or business outcome automatically. Those concepts must be defined by the product and validated against evidence.
To understand what to measure after the telemetry arrives, read how to monitor an MCP server in production. That broader MCP observability framework separates infrastructure, tools, sessions, behavior, and outcomes. MCP analytics then treats calls as evidence, not customers.
Instrument with the Flowlines plugin
Flowlines uses OpenTelemetry-compatible MCP instrumentation to capture available context and connect related tool activity into observed workflows. The official Flowlines plugin ↗ handles the MCP-specific instrumentation required for behavioral analysis and guides Claude Code or Codex through the scoped repository change.
Connect your assistant to Flowlines or use the manual plugin setup. Connecting the Flowlines MCP and instrumenting your own server are separate steps. If the newly installed plugin is unavailable, start a new Claude Code session or Codex task before asking it to instrument the server. If you do not have a namespace API key, ask your assistant to guide you to create one in Flowlines.
See the MCP product walkthrough for the guided setup and the views available after telemetry arrives.
Production acceptance checklist
- The server and SDK versions are documented.
- The tool boundary is instrumented exactly once.
- Incoming and downstream trace context are tested.
- Technical error categories are accurate.
- Content and identity follow an approved policy.
- Secrets and authorization material are excluded.
- Success, failure, timeout, recovery, and repeat paths pass.
- Missing relationships and outcomes remain explicit.
- Telemetry delivery and coverage are monitored.
- Each dashboard has an owner and decision.
- Representative source traces can be inspected.
- The integration has a rollback path.
Primary sources and further reading
- MCP 2026-07-28 release ↗
- OpenTelemetry semantic conventions ↗
- OpenTelemetry generative AI observability ↗
- Grafana MCP server observability documentation ↗
- OpenLIT MCP monitoring quickstart ↗
- Flowlines plugin source ↗
Worked example: a tool call missing from the trace
Consider a synthetic CRM MCP server with a tool that reads recent account activity. The tool returns a valid result, but the exported trace contains only the surrounding HTTP request and downstream database call. The MCP tool operation itself is missing.
Send one uniquely identifiable synthetic request. Confirm the server executes the tool once, then compare that execution with the exported trace. If no tool span appears, check whether instrumentation initialized before the MCP server module, whether the installed integration supports the runtime and transport, and whether the exporter flushed successfully.
Test the instrumentation boundary
Build a safe acceptance matrix with five cases:
| Case | Expected evidence |
|---|---|
| Successful tool result | One tool operation appears with accurate timing and status |
| Domain error | The tool failure is visible even when transport succeeds |
| Dependency timeout | The downstream call remains connected to the tool operation |
| Repeated request | Each execution appears once without duplicate spans |
| Process shutdown | Buffered telemetry reaches the destination before exit |
For each case, verify that the operational backend can inspect the call and its dependencies without exposing content outside the approved data boundary. Journey and outcome analysis happens after this technical evidence is complete enough to support it.
Keep implementation details maintained in the plugin
Public documentation should explain the standards, privacy decisions, and acceptance tests. The official Flowlines plugin should remain the source of truth for the current repository-specific implementation. This prevents a copied snippet from drifting away from the supported integration or encouraging teams to export more data than they intended.
Read the production monitoring framework or open the manual plugin setup.
Frequently asked questions
Can MCP servers use OpenTelemetry?
Yes. MCP servers can emit OpenTelemetry traces, metrics, and logs. Use a supported integration for your SDK and transport, and verify W3C Trace Context propagation across the clients and services involved.
How do I add MCP observability to an existing OpenTelemetry setup?
Get tracers and meters from the providers your service already configures, then wrap each tool handler in one span with the MCP method and tool name, record its duration in seconds and classify failures. Check first that no installed integration already creates the same span, and keep argument and result capture off.
Where should MCP tool calls be instrumented?
Instrument at the server's tool execution boundary so the telemetry represents the tool operation the client invoked, not only the surrounding HTTP route or downstream API call. Avoid duplicate instrumentation.
What should MCP telemetry contain?
Capture enough approved context to identify the service and operation, measure status and duration, connect related work, and investigate the client-visible outcome. The exact data boundary depends on the server, risk, and consent.
Should MCP tool arguments and results be captured?
Only when they are necessary, permitted, and protected. Minimize or omit sensitive content, exclude secrets and authorization material, apply redaction, and test the export boundary with synthetic requests.
How do I correlate an MCP call with the rest of a trace?
Use standard W3C trace context propagation where the client and server support it. For broader product journeys, use a permitted application-level relationship rather than relying on the removed modern protocol session identifier.
How do I test MCP instrumentation?
Run representative success, domain error, timeout, recovery, repeated-call, and incomplete workflow cases. Confirm each operation appears once, technical status is accurate, sensitive data is absent, and missing outcomes remain unknown.
Does Flowlines MCP instrumentation require a proprietary SDK?
No proprietary Flowlines runtime SDK is required, but the MCP server still needs compatible instrumentation. The Flowlines plugin guides the repository through an OpenTelemetry-based setup.
How do I verify MCP telemetry after instrumentation?
Compare eligible server activity with delivered telemetry, confirm every test operation appears once, check status and duration, follow trace context into dependencies, and verify that sensitive data is absent or redacted.