feat(telemetry): optional OpenTelemetry instrumentation for MCP operations#129
Open
getlarge wants to merge 6 commits intoplatformatic:mainfrom
Open
feat(telemetry): optional OpenTelemetry instrumentation for MCP operations#129getlarge wants to merge 6 commits intoplatformatic:mainfrom
getlarge wants to merge 6 commits intoplatformatic:mainfrom
Conversation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- withSpan() helper wraps fn execution in an active OTel span, loading @opentelemetry/api dynamically on first use so users without the peer dep pay zero cost - MCP_ATTR constants and buildSpanAttributes() live in telemetry-constants.ts with no OTel dependency, so they can be imported statically by any module - Attribute keys are inlined rather than sourced from @opentelemetry/semantic-conventions: the JS semconv package only exports 4 of the 6 MCP attrs as of 1.40.0, under an explicitly unstable /experimental path. See telemetry-constants.ts for the full rationale. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TracerLike is a structural interface compatible with @opentelemetry/api's Tracer, defined locally so consumers don't need @opentelemetry/api installed just to import plugin types. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- handleRequest wraps each JSON-RPC dispatch in withSpan when a tracer is configured on HandlerDependencies - Span names match the JSON-RPC method; span attributes use MCP semconv keys (mcp.method.name, mcp.session.id, plus mcp.tool.name / mcp.resource.uri / mcp.prompt.name for method-specific dispatches) - The telemetry module is loaded lazily via dynamic import inside the per-request wrap, so users without a tracer configured never touch @opentelemetry/api - Routes thread opts.telemetry?.tracer into processMessage - HandlerDependencies is now exported for advanced consumers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Export MCP_ATTR, buildSpanAttributes, withSpan, TracerLike, and HandlerDependencies from the package root so advanced consumers can build their own span wiring on top of the plugin. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- test/telemetry.test.ts: unit tests for withSpan (success/error paths, no-tracer fallthrough) and buildSpanAttributes - test/telemetry-types.test.ts: type-level tests for MCPPluginOptions.telemetry - test/telemetry-integration.test.ts: end-to-end plugin -> tracer wiring, driving tools/call and tools/list through the HTTP surface and asserting span names, attributes, and per-method extras (mcp.tool.name) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #128.
Adds optional OpenTelemetry tracing for MCP operations. Zero cost when no tracer is configured —
@opentelemetry/apiis an optional peer dep and is only loaded via dynamicimport()the first time a span is created.What's included
telemetry?: { tracer: TracerLike }onMCPPluginOptions—TracerLikeis a structural interface so consumers don't need@opentelemetry/apiinstalled just to import plugin typeshandleRequestwraps each JSON-RPC dispatch in an active span when a tracer is configured, with MCP semconv attributes (mcp.method.name,mcp.session.id, plusmcp.tool.name/mcp.resource.uri/mcp.prompt.nameper method)withSpan,buildSpanAttributes,MCP_ATTR,TracerLike,HandlerDependenciesre-exported from the package root for advanced consumersUsage
Design note: why inlined attribute keys instead of
@opentelemetry/semantic-conventionssrc/telemetry-constants.tsinlines the six MCP semconv keys as string constants rather than importing from@opentelemetry/semantic-conventions. Reasoning:@opentelemetry/semantic-conventions@1.40.0, onlyATTR_MCP_METHOD_NAME,ATTR_MCP_PROTOCOL_VERSION,ATTR_MCP_RESOURCE_URI, andATTR_MCP_SESSION_IDare exported.mcp.tool.nameandmcp.prompt.nameare in the MCP spec but haven't landed in the JS semconv package yet — importing would force a partial-import + hardcoded-strings mix anyway.semantic-conventions/experimental, an explicitly unstable export path. Coupling to it trades six stable local strings for drift risk on every semconv release.There's a
// Revisit once all six attrs are exported from a stable semconv pathcomment intelemetry-constants.ts. Happy to switch to the semconv imports once coverage + path stability catch up, or earlier if you'd prefer to couple to the experimental path now.Commits
Split into logical chunks for review:
chore: add @opentelemetry/api as optional peer dependencyfeat(telemetry): add telemetry module with dynamic OTel loadingfeat(telemetry): add TracerLike and telemetry option to MCPPluginOptionsfeat(telemetry): instrument MCP handlers with OTel spansfeat(telemetry): export telemetry utilities from package indextest(telemetry): add unit and integration testsOpen questions
INTERNAL(current) orSERVER? LeaningINTERNALsince the Fastify HTTP span is alreadySERVER.Test plan
npm run typecheckcleannpm run lintcleantest/telemetry*.ts)