Observability becomes more important as an MCP server moves from a local development tool to a production service.
A local MCP server can often be diagnosed by reading console output:
Client
↓
MCP Server
↓
Tool
↓
Console
A production MCP server is different.
It may run behind:
Client
↓
CDN / Gateway
↓
Load Balancer
↓
MCP Server Instance A
MCP Server Instance B
MCP Server Instance C
↓
Database / APIs / Queues
With the MCP C# SDK 2.0, Streamable HTTP is stateless by default. The new protocol removes the session-based initialize handshake for the modern stateless flow and eliminates the Mcp-Session-Id requirement. Requests are designed to be self-contained so that any server instance can process them.
This is useful for horizontal scaling, but it creates an observability question:
If there is no protocol session to correlate requests, how do you reconstruct what happened during an MCP interaction?
The answer is not to recreate the old session model inside your application.
Instead, use standard distributed-systems observability:
Request
↓
Trace
↓
Span
↓
MCP Operation
↓
Tool
↓
Dependency
For multi-step operations, carry explicit application state or request-state handles when the workflow requires continuity.
Stateless Does Not Mean Untraceable
A common misconception is:
No session
=
No correlation
That is incorrect.
HTTP requests can still have:
Trace IDs
Span IDs
Request IDs
Authentication identities
Tool names
Parameters
Application correlation IDs
Explicit workflow identifiers
For example:
Trace ID: 7f3c...
Request ID: req-123
Tool: get_order_status
User: authenticated-subject
Region: eastus2
The server does not need a long-lived MCP session to record these attributes.
The important architectural distinction is:
Transport State
≠
Application State
≠
Observability Context
These should be designed independently.
What Changed in MCP C# SDK 2.0?
The MCP C# SDK 2.0 aligns with the July 28, 2026 MCP specification revision.
The HTTP transport is now stateless by default:
builder.Services
.AddMcpServer()
.WithHttpTransport()
.WithToolsFromAssembly();
The server can then expose the MCP endpoint through ASP.NET Core:
var app = builder.Build();
app.MapMcp();
app.Run();
The SDK documentation explicitly describes stateless Streamable HTTP as allowing requests to be handled by any server instance without session affinity.
That means a load balancer can route:
Request 1 → Server A
Request 2 → Server C
Request 3 → Server B
without requiring a shared MCP transport session.
Observability must therefore be request-oriented rather than session-oriented.
The Observability Model
A useful production model is:
Trace
|
+---------+---------+
| | |
Request Tool Dependency
| | |
HTTP MCP Database
Gateway Tool HTTP API
At minimum, capture:
| Layer | Recommended Data |
|---|
| HTTP | Method, route, status |
| MCP | MCP method, tool name |
| Request | Correlation/request ID |
| Trace | Trace ID, span ID |
| Identity | Authenticated subject or tenant ID |
| Performance | Duration |
| Result | Success/failure |
| Dependency | Database/API duration |
| Infrastructure | Instance/region |
| Error | Exception type and safe message |
Avoid logging sensitive tool arguments by default.
Use ASP.NET Core Request Telemetry
An MCP HTTP server is hosted on ASP.NET Core, so standard HTTP middleware and telemetry patterns remain useful.
A simple middleware can capture request timing:
public sealed class RequestTimingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestTimingMiddleware> _logger;
public RequestTimingMiddleware(
RequestDelegate next,
ILogger<RequestTimingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
var stopwatch = Stopwatch.StartNew();
try
{
await _next(context);
}
finally
{
stopwatch.Stop();
_logger.LogInformation(
"MCP HTTP request completed. " +
"Method={Method}, Path={Path}, " +
"StatusCode={StatusCode}, DurationMs={DurationMs}",
context.Request.Method,
context.Request.Path,
context.Response.StatusCode,
stopwatch.ElapsedMilliseconds);
}
}
}
Register it before mapping the MCP endpoint:
app.UseMiddleware<RequestTimingMiddleware>();
app.MapMcp();
This gives you a basic request-level diagnostic boundary.
For production systems, distributed tracing should generally be preferred over building an extensive custom telemetry system.
Use OpenTelemetry for Distributed Tracing
OpenTelemetry provides a useful model for connecting:
Gateway
↓
MCP Server
↓
Tool
↓
Database
↓
External API
Conceptually:
Trace
└── HTTP MCP request
├── Tool execution
├── Database query
└── External API call
The trace ID becomes the common correlation key.
A typical ASP.NET Core setup can look like:
builder.Services
.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation();
});
The exporter depends on your observability platform.
The important point is that the MCP request should participate in the same distributed trace as the downstream operations.
Add an MCP-Specific Span
ASP.NET Core instrumentation can tell you that an HTTP request occurred.
It does not necessarily provide all the semantic information you want about the MCP operation.
Create an application-level activity around tool execution when appropriate:
private static readonly ActivitySource ActivitySource =
new("MyCompany.McpServer");
Then:
using var activity =
ActivitySource.StartActivity(
"mcp.tool");
activity?.SetTag(
"mcp.tool.name",
"get_order_status");
activity?.SetTag(
"mcp.region",
region);
var result =
await orders.GetStatusAsync(
region,
orderId);
The exact semantic-convention names should be standardized within your organization rather than invented inconsistently across services.
The important design principle is to capture MCP-specific context without duplicating the entire HTTP telemetry layer.
Capture the MCP Method
The MCP 2.0 HTTP design exposes standardized headers that mirror important MCP request information.
For example, the official SDK documentation describes headers such as:
Mcp-Method
Mcp-Name
Mcp-Param-*
for Streamable HTTP requests. The purpose is to allow intermediaries such as load balancers, gateways, WAFs, and observability infrastructure to inspect MCP metadata without parsing the JSON-RPC body.
A request can therefore be conceptually observed as:
HTTP POST
Mcp-Method: tools/call
Mcp-Name: get_order_status
This is valuable for dashboards because infrastructure can identify MCP operations using ordinary HTTP metadata.
Do Not Trust Headers Over the Body
The standardized MCP headers are not a replacement for the MCP request body.
The official SDK documentation states that the JSON-RPC body remains authoritative. If the header disagrees with the body, the server rejects the request rather than choosing one value.
This is important for observability.
Do not design logging like:
Header says:
Tool = get_order_status
Therefore:
Tool = get_order_status
Instead, treat the validated MCP request as authoritative.
Headers should help infrastructure route and observe traffic, not become a second conflicting source of truth.
Add a Request Correlation Identifier
Distributed tracing should be the primary mechanism for cross-service correlation.
However, an application-level request identifier can still be useful.
For example:
var requestId =
context.TraceIdentifier;
Log it with the MCP operation:
_logger.LogInformation(
"MCP request started. RequestId={RequestId}",
context.TraceIdentifier);
This gives operators a simple value to search for in application logs.
A stronger production model is:
Trace ID
+
Request ID
+
MCP operation
The trace ID connects services, while the request ID provides a convenient local diagnostic key.
Propagate Trace Context to Downstream APIs
Suppose an MCP tool calls an orders API:
MCP Client
↓
MCP Server
↓
Orders API
↓
Database
The ideal trace looks like:
Trace abc123
│
├── MCP HTTP request
│
├── Tool: get_order_status
│
├── HTTP: Orders API
│
└── Database query
This lets an operator answer:
Why was this MCP tool slow?
without manually searching unrelated logs.
ASP.NET Core and HttpClient instrumentation can propagate distributed tracing context through outgoing HTTP requests when configured appropriately.
Include Server Instance Information
Stateless deployments make infrastructure topology dynamic.
A request can land on any instance:
Request A → pod-17
Request B → pod-04
Request C → pod-22
Log infrastructure identity:
InstanceId
Region
AvailabilityZone
ContainerId
DeploymentVersion
For example:
activity?.SetTag(
"service.instance.id",
Environment.MachineName);
In containerized environments, use your deployment platform's stable instance metadata where available.
This helps answer:
Did every instance behave the same?
or:
Did the problem occur only on one deployment instance?
Do Not Reintroduce Sticky Sessions for Logging
A common architectural mistake is:
No MCP session
↓
Need correlation
↓
Create server-side session
↓
Sticky load balancing
That defeats much of the operational simplicity gained by stateless HTTP.
Correlation should not require routing all requests from a client to the same server.
Instead:
Request
↓
Trace ID
↓
Central telemetry
allows requests to move between instances while remaining observable.
Model Application State Explicitly
Stateless MCP does not mean your business workflow must be stateless.
Suppose a shopping tool creates a basket:
create_basket
↓
basketId = B123
The next tool can receive:
get_basket(B123)
The identifier becomes explicit application state.
The official MCP SDK guidance gives examples such as a basketId or browserId as explicit handles passed between calls.
This is often preferable to hidden transport state because the model can reason about the identifier and pass it between tools.
Make State Handles Observable
If a tool uses an application-level state handle, record a safe reference to it.
For example:
Trace ID: abc123
Tool: get_basket
Basket ID: B123
But consider whether the identifier itself contains sensitive information.
Never assume that an application identifier is safe to expose in logs.
A better pattern is to use:
Internal state ID
+
Safe log representation
when necessary.
Do Not Log Full Tool Arguments
AI tools can receive sensitive information.
For example:
{
"customerName": "Example User",
"email": "[email protected]",
"accountNumber": "..."
}
Logging the entire request body can create a data-protection problem.
Instead of:
_logger.LogInformation(
"Arguments: {Arguments}",
JsonSerializer.Serialize(arguments));
prefer:
_logger.LogInformation(
"Tool {ToolName} invoked. " +
"ParameterCount={ParameterCount}",
toolName,
arguments.Count);
For selected parameters, explicitly classify them as safe before logging.
Observability should improve debugging without becoming a secondary data-leak channel.
Use Structured Logging
Avoid:
_logger.LogInformation(
$"Tool {toolName} took {duration}ms");
Prefer structured properties:
_logger.LogInformation(
"MCP tool completed. " +
"Tool={ToolName} DurationMs={DurationMs}",
toolName,
duration);
Structured logging makes it easier to query:
Tool = get_order_status
DurationMs > 1000
and build dashboards.
Useful fields include:
TraceId
RequestId
ToolName
McpMethod
DurationMs
Status
InstanceId
Region
DeploymentVersion
Record Success and Failure Separately
A useful tool metric should distinguish:
Success
Validation failure
Authorization failure
Timeout
Cancellation
Dependency failure
Unhandled exception
For example:
try
{
return await ExecuteToolAsync();
}
catch (OperationCanceledException)
{
activity?.SetTag(
"mcp.outcome",
"cancelled");
throw;
}
catch (Exception ex)
{
activity?.SetTag(
"mcp.outcome",
"error");
_logger.LogError(
ex,
"MCP tool execution failed. Tool={ToolName}",
toolName);
throw;
}
Do not treat every failure as a server error.
Client cancellation and downstream failures can represent different operational conditions.
Measure Tool Duration
A simple metric is:
MCP tool duration
But break it down when useful:
Total
├── Validation
├── Authorization
├── Business logic
├── Database
└── External API
For example:
Total tool duration = 850 ms
Database = 600 ms
Orders API = 180 ms
Application = 70 ms
The trace immediately shows where optimization effort belongs.
Add Metrics for Tool Calls
Tracing is excellent for individual requests.
Metrics are better for trends.
Track:
mcp.tool.calls
mcp.tool.errors
mcp.tool.duration
mcp.tool.cancellations
Break down carefully by low-cardinality dimensions such as:
ToolName
Outcome
Region
Avoid using:
CustomerId
OrderId
RequestId
as metric labels.
High-cardinality values can make metrics expensive and difficult to operate.
Put those identifiers in traces or logs instead.
Measure Request Rate
A production dashboard should answer:
How much MCP traffic are we receiving?
Track:
Requests/sec
Tool calls/sec
Errors/sec
Then break down by tool:
get_order_status
search_products
create_ticket
This helps identify traffic changes and unusual tool usage.
Measure Latency Percentiles
Do not use only averages.
Track:
p50
p95
p99
For example:
| Tool | p50 | p95 | p99 |
|---|
| get_order_status | Measure | Measure | Measure |
| search_products | Measure | Measure | Measure |
| create_ticket | Measure | Measure | Measure |
The values must come from your telemetry.
Tail latency is particularly important for MCP tools because a slow tool can delay an entire agent workflow.
Track Dependency Failures
Suppose:
search_products
↓
Catalog API
↓
Database
The MCP server may report a tool failure, but the root cause is the catalog API.
Distributed tracing lets you distinguish:
MCP failure
from:
Catalog API failure
and:
Database failure
This is one of the biggest reasons to integrate MCP observability with the rest of the application's telemetry rather than creating a separate MCP-only monitoring system.
Observe Load Balancing Behavior
Stateless MCP makes load balancing simpler because requests do not need protocol-level session affinity.
For example:
Client
|
v
Load Balancer
|
+---- Instance A
|
+---- Instance B
|
+---- Instance C
Record instance information in telemetry.
Then analyze:
Requests per instance
p95 per instance
Error rate per instance
CPU per instance
If one instance has significantly worse performance, investigate:
Resource pressure
Deployment differences
Network path
Configuration
Dependency connectivity
Test Instance Failure
Statelessness should also simplify failure recovery.
A useful test is:
Request 1 → Instance A
↓
Instance fails
Request 2 → Instance B
The second request should not depend on hidden transport session state in Instance A when operating in stateless mode.
The SDK documentation explicitly describes stateless HTTP as allowing horizontal scaling without session affinity.
Your application state, however, may still depend on external durable storage.
That distinction must be tested.
Use Durable Storage for Durable State
If a tool creates state that must survive:
Process restart
Instance failure
Deployment
Scaling
do not store it only in:
static Dictionary<string, object>
or:
IMemoryCache
Use an appropriate durable store.
For example:
MCP Server
↓
Database / Distributed Cache
↓
Application State
Then any instance can retrieve the state.
Stateless transport and durable application state can coexist cleanly.
Handle Multi-Round-Trip Requests
MCP 2.0 introduces Multi Round-Trip Requests for interactions that previously relied on server-initiated requests over a session. The server can return an input-required result with state that the client carries into a subsequent request.
This creates a new observability pattern:
Trace A
↓
Tool call
↓
Input required
↓
Client interaction
↓
Follow-up tool call
Do not assume that every multi-round interaction will have one server process handling every step.
Use an explicit correlation mechanism.
Where the MCP protocol provides request state, treat that state as workflow continuity rather than as a replacement for distributed tracing.
Correlate Multi-Round Workflows
For multi-round operations, consider an application-level workflow identifier:
WorkflowId = wf-123
Then log:
Trace A
Workflow wf-123
Tool close_support_ticket
Input required
and:
Trace B
Workflow wf-123
Tool close_support_ticket
Completed
This gives you:
Workflow
|
+── Trace A
|
+── Trace B
This is especially useful because separate HTTP requests may legitimately have separate traces.
Do not force them into a single artificially long-lived trace.
Distinguish Trace IDs From Workflow IDs
These identifiers solve different problems.
| Identifier | Purpose |
|---|
| Trace ID | Distributed request execution |
| Span ID | Individual operation |
| Request ID | One HTTP request |
| Workflow ID | Multiple requests forming one business operation |
| State handle | Application state reference |
| User/Tenant ID | Security and business identity |
A robust MCP platform can use several of these without turning any one identifier into a hidden session mechanism.
Build a Production Dashboard
A useful MCP dashboard should answer:
Is the service healthy?
How much traffic are we receiving?
Which tools are slow?
Which tools are failing?
Which dependency is responsible?
Is one instance unhealthy?
For example:
MCP Overview
---------------------------------
Requests/sec 125
Error rate 0.8%
p95 latency 420 ms
p99 latency 1.2 sec
Top tools
---------------------------------
search_products 55%
get_order_status 25%
create_ticket 12%
Top failures
---------------------------------
Catalog API 41%
Timeout 28%
Authorization 18%
Other 13%
The numbers above are illustrative only.
A real dashboard should use measured telemetry.
Add Health Checks
An MCP server should have operational health endpoints appropriate to its hosting environment.
Separate:
Liveness
from:
Readiness
A liveness check answers:
Is the process functioning?
A readiness check answers:
Should this instance receive traffic?
Do not make liveness depend on every downstream database or API unless that is explicitly required by your deployment model.
Otherwise, a temporary dependency outage can cause healthy application processes to restart unnecessarily.
Be Careful With Health-Check Dependencies
Suppose the MCP server depends on:
Database
Orders API
Identity provider
A health check that calls all three can create additional production traffic.
It can also produce misleading results:
Orders API down
↓
MCP marked unhealthy
↓
Load balancer removes all instances
This can amplify an external outage.
Use dependency-aware readiness checks carefully.
Add Timeouts
Every downstream operation should have a bounded timeout.
For example:
using var timeoutCts =
CancellationTokenSource.CreateLinkedTokenSource(
cancellationToken);
timeoutCts.CancelAfter(
TimeSpan.FromSeconds(5));
var result =
await ordersClient.GetStatusAsync(
orderId,
timeoutCts.Token);
The exact timeout should come from the service's SLO and dependency behavior.
A timeout is useful for observability because it converts an indefinite wait into a measurable failure category.
Record Cancellation
MCP clients can cancel requests.
Do not classify every cancellation as an application error.
For example:
catch (OperationCanceledException)
{
logger.LogInformation(
"MCP tool cancelled. Tool={ToolName}",
toolName);
throw;
}
Metrics can distinguish:
Cancelled
Failed
Succeeded
Timed out
This makes dashboards much more meaningful.
Avoid Logging Secrets
Never log:
Access tokens
Refresh tokens
API keys
Passwords
Authorization headers
Sensitive tool parameters
Be particularly careful with MCP because tool arguments can contain user-provided data.
Use structured redaction or explicit allowlists.
For example:
logger.LogInformation(
"Tool completed. Tool={ToolName}, " +
"Region={Region}",
toolName,
safeRegion);
Do not serialize the entire request object merely because it is convenient.
Use Sampling for High-Volume Traffic
Tracing every request at full detail may be unnecessary for very high-volume systems.
Use:
Metrics → all traffic
Logs → important events
Traces → sampled traffic
with increased trace retention for:
Errors
High latency
Specific tools
Important workflows
The exact sampling policy should be based on traffic volume and operational requirements.
Build a Failure-Injection Test
Observability should be tested, not just configured.
Inject failures such as:
Database timeout
External API 500
Network delay
Tool exception
Client cancellation
Instance termination
Then verify that the telemetry answers:
What failed?
Where did it fail?
How long did it take?
Which tool was running?
Which instance handled it?
Which dependency failed?
If the answer requires manually searching five systems, the observability design is incomplete.
Example Production Flow
Consider:
Client
↓
Load Balancer
↓
MCP Server
↓
get_order_status
↓
Orders API
↓
PostgreSQL
A trace might conceptually look like:
Trace: abc123
HTTP POST /mcp
|
+-- MCP tools/call
|
+-- Tool: get_order_status
|
+-- HTTP Orders API
|
+-- PostgreSQL query
If the database takes 900 ms, the trace should make that visible.
The operator should not need to guess whether the MCP runtime, tool logic, HTTP client, or database caused the latency.
Observability Architecture
A practical production architecture is:
+------------------+
| MCP Client |
+--------+---------+
|
v
+------------------+
| Load Balancer |
+--------+---------+
|
+---------------+---------------+
| | |
v v v
MCP Instance A MCP Instance B MCP Instance C
| | |
+---------------+---------------+
|
v
+------------------+
| OpenTelemetry |
+--------+---------+
|
+----------------+----------------+
| | |
v v v
Metrics Logs Traces
| | |
+----------------+----------------+
|
v
Dashboards
The key property is that telemetry is centralized even though request processing is distributed.
Common Mistakes
Recreating Server Sessions Just for Logging
Do not add sticky session state simply because the old MCP model made correlation convenient.
Use traces and explicit workflow identifiers.
Logging Complete Tool Arguments
This creates unnecessary privacy and security risk.
Log only what is operationally useful and approved.
Using Request IDs as Long-Lived Workflow IDs
A request ID represents one request.
A workflow may contain multiple requests.
Use separate identifiers.
Putting High-Cardinality IDs in Metrics
Do not label metrics with:
OrderId
CustomerId
RequestId
TraceId
Use traces and logs for those values.
Monitoring Only HTTP Status Codes
A successful HTTP response can still contain an MCP-level application failure or unsuccessful tool result.
Monitor the MCP operation outcome as well.
Ignoring Downstream Dependencies
MCP is often an orchestration layer.
A tool can be slow because the database or external API is slow.
Trace the complete dependency chain.
Troubleshooting
Requests Reach Different Instances
This is expected in a stateless deployment.
Use distributed tracing and centralized telemetry rather than requiring session affinity.
Logs Cannot Be Correlated
Check that your logging system captures:
TraceId
SpanId
RequestId
ToolName
and that those fields are searchable.
Tool Name Is Missing From Infrastructure Logs
Use the standardized MCP HTTP metadata available to your deployment and capture validated MCP operation information at the application layer. The MCP 2.0 design specifically introduces headers such as Mcp-Method and Mcp-Name so HTTP infrastructure can observe MCP traffic without parsing request bodies.
One Tool Is Slow but HTTP Latency Looks Normal
Inspect the tool's child spans and downstream dependencies.
The bottleneck may be:
Database
HTTP API
Queue
Serialization
Application logic
Multi-Step Workflow Cannot Be Reconstructed
Introduce an explicit workflow identifier and propagate it through the application-level operation.
Do not recreate a transport session solely for correlation.
State Is Lost After Scaling
The application may be storing state in process memory.
Move durable workflow state to an appropriate shared store.
Stateless MCP transport does not make in-memory business state magically distributed.
Best Practices
Treat MCP as a distributed HTTP workload.
Use OpenTelemetry for cross-service tracing.
Correlate requests with trace and request identifiers.
Record MCP method and tool name.
Capture server instance and deployment metadata.
Measure tool latency separately from total HTTP latency.
Trace downstream HTTP and database operations.
Use explicit workflow identifiers for multi-request business operations.
Store durable application state outside the process.
Avoid recreating MCP sessions solely for observability.
Do not log complete tool arguments by default.
Redact secrets and sensitive values.
Keep high-cardinality identifiers out of metrics.
Track cancellation separately from failures.
Test observability with injected failures.
Monitor p50, p95, and p99 latency.
Build dashboards around tools and operational outcomes.
Test load balancing and instance failure.
Keep liveness and readiness semantics separate.
Document the correlation model for operators and developers.
Frequently Asked Questions
Does stateless MCP mean there is no state anywhere?
No.
It means the transport does not require server-side session state for the stateless protocol flow.
Your application can still have business state stored in a database, cache, or another durable system. The MCP SDK documentation explicitly distinguishes stateless protocol behavior from application state.
How do I correlate requests without Mcp-Session-Id?
Use distributed tracing and request-level correlation.
A practical model is:
Trace ID
+
Request ID
+
Tool Name
+
Workflow ID when required
Should I create my own MCP session identifier?
Usually not just for observability.
If the application genuinely requires cross-request business state, use an explicit state handle such as a workflow, basket, or job identifier.
Can different MCP requests hit different servers?
Yes.
Stateless Streamable HTTP is designed so requests can be handled by different server instances without protocol-level session affinity.
How should I trace a multi-round MCP operation?
Treat each HTTP request as an independently traceable operation and use an application-level workflow identifier when multiple requests form one business operation.
Should MCP tool arguments be logged?
Not by default.
Tool arguments may contain sensitive user or business data. Prefer explicit allowlists for safe fields and rely on traces, metrics, and structured metadata for most operational diagnostics.
Should I use OpenTelemetry?
For production distributed systems, it is a strong fit because MCP traffic often crosses gateways, server instances, HTTP services, databases, and other dependencies. Npgsql, for example, provides tracing and metrics capabilities for PostgreSQL workloads, allowing database activity to participate in the broader application observability model.
Does stateless mode eliminate the need for a distributed cache?
No.
Stateless transport removes the requirement for transport session state, but your application may still need shared storage for durable business state, rate limits, caches, task state, or other cross-request data.
Conclusion
MCP 2.0's stateless-by-default HTTP architecture changes the way production teams should think about observability.
The old mental model was:
Client
↓
Session
↓
Server Instance
↓
Tool
The new model is closer to:
Request
↓
Trace
↓
Tool
↓
Dependencies
That is a significant architectural improvement for horizontally scaled systems because requests can be routed independently. The official MCP C# SDK documentation explicitly describes stateless HTTP as eliminating the need for session affinity and allowing ordinary HTTP infrastructure to route MCP traffic.
But removing protocol sessions does not remove the need for correlation.
A production MCP platform should instead combine:
Distributed Trace
+
Request ID
+
MCP Method
+
Tool Name
+
Workflow ID
+
Instance Metadata
+
Dependency Telemetry
Each identifier has a different purpose.
The result is an observability architecture that remains useful even when:
Request 1 → Instance A
Request 2 → Instance C
Request 3 → Instance B
and when a multi-step workflow spans several independent HTTP requests.
The most important principle is:
Do not recreate transport session state merely to make observability easier.
Use standard distributed tracing for execution context, explicit identifiers for business workflows, and durable storage for application state.
That gives MCP servers the scalability benefits of stateless HTTP without sacrificing the diagnostics required to operate them in production.