Skip to content

feat: Support spec v1.11.0 — OpenTelemetry OTLP tracing configuration (§4.1.3.6)#3188

Merged
lpcox merged 7 commits intomainfrom
copilot/support-spec-v1-11-0-opentelemetry-config
Apr 5, 2026
Merged

feat: Support spec v1.11.0 — OpenTelemetry OTLP tracing configuration (§4.1.3.6)#3188
lpcox merged 7 commits intomainfrom
copilot/support-spec-v1-11-0-opentelemetry-config

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 4, 2026

Implements the opentelemetry gateway config section from MCP Gateway Spec v1.11.0 §4.1.3.6, bridging the gap between the existing TracingConfig skeleton and full spec compliance.

Config changes

  • New fields on TracingConfig: Headers map[string]string, TraceID string, SpanID string
  • New TOML key [gateway.opentelemetry] alongside legacy [gateway.tracing] (backward-compatible; opentelemetry takes precedence)
  • JSON stdin: Added StdinOpenTelemetryConfig wired into StdinGatewayConfig.opentelemetry
  • JSON schema: opentelemetry added to gatewayConfig definition with correct types and patterns
[gateway.opentelemetry]
endpoint    = "https://otel-collector.example.com"
trace_id    = "4bf92f3577b34da6a3ce929d0e0e4736"
span_id     = "00f067aa0ba902b7"
service_name = "mcp-gateway"

[gateway.opentelemetry.headers]
Authorization = "******"

Validation (enforced only for the opentelemetry section)

  • endpoint required and must be HTTPS
  • traceId must be 32-char lowercase hex (or ${VAR})
  • spanId must be 16-char lowercase hex (or ${VAR})
  • spanId without traceId → warning, not error

OTLP provider

  • Headers forwarded to exporter via otlptracehttp.WithHeaders()
  • W3C remote parent context constructed from traceId+spanId at startup; missing spanId generates a random one (T-OTEL-008)
  • tracing.ParentContext(ctx, cfg) exported for use at startup

Instrumentation (callBackendTool)

  • Span renamed gateway.tool_callmcp.tool_call
  • Attributes updated to spec names: mcp.server, mcp.method ("tools/call"), mcp.tool
  • http.status_code attribute added: 200 (success) / 403 (DIFC denied) / 500 (error)

Tests

Compliance tests T-OTEL-001 through T-OTEL-010 covering config validation, header propagation, W3C traceparent construction, random span ID generation, and service name defaulting.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • example.com
    • Triggering command: /tmp/go-build344646749/b510/launcher.test /tmp/go-build344646749/b510/launcher.test -test.testlogfile=/tmp/go-build344646749/b510/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true 0/clients.go 0/doc.go x_amd64/compile OUTPUT -d 168.63.129.16 x_amd64/compile o_.o�� ache/go/1.25.8/x-errorsas oZMU/gIMg3r8eCE3-ifaceassert de/node/bin/as -p sh -lang=go1.25 646749/b171/_x00-buildtags (dns block)
    • Triggering command: /tmp/go-build952104743/b510/launcher.test /tmp/go-build952104743/b510/launcher.test -test.testlogfile=/tmp/go-build952104743/b510/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true cmCl2WHYI 646749/b318/ 64/pkg/tool/linux_amd64/vet --gdwarf-5 --64 -o 64/pkg/tool/linux_amd64/vet 6467�� olang.org/grpc@v1.80.0/internal/pretty/pretty.go-p pkg/mod/github.com/go-logr/logr@v1.4.3/context_smain cfg -b 646749/b318/_cgo/usr/bin/runc p/bin/git ache/go/1.25.8/x64/pkg/tool/linux_amd64/compile (dns block)
    • Triggering command: `/tmp/go-build3330052160/b514/launcher.test /tmp/go-build3330052160/b514/launcher.test -test.testlogfile=/tmp/go-build3330052160/b514/testlog.txt -test.paniconexit0 -test.timeout=10m0s e=/t�� t0 m0s x_amd64/vet pkg/mod/google.ggit ��4.1.3.6) alongside legacy tracing key
  • Add StdinOpenTelemetryConfig + wire into �� ache/go/1.25.8/x--exclude-standard x_amd64/vet --ve�� -o ache/go/1.25.8/x64/pkg/tool/linux_amd64/vet x_amd64/vet /tmp/go-build344bash -trimpath 646749/b371/vet.--noprofile x_amd64/vet` (dns block)
  • invalid-host-that-does-not-exist-12345.com
    • Triggering command: /tmp/go-build344646749/b492/config.test /tmp/go-build344646749/b492/config.test -test.testlogfile=/tmp/go-build344646749/b492/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true go t.go x_amd64/compile (dns block)
    • Triggering command: /tmp/go-build952104743/b492/config.test /tmp/go-build952104743/b492/config.test -test.testlogfile=/tmp/go-build952104743/b492/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true _.a 646749/b197/vet.cfg 64/pkg/tool/linux_amd64/vet . ateway/v2/utilitls-files --64 64/pkg/tool/linu--others -I 2mEnnrRfF 646749/b318/ 64/pkg/tool/linux_amd64/vet --gdwarf-5 --64 -o 64/pkg/tool/linusecurity (dns block)
    • Triggering command: /tmp/go-build1778975665/b222/config.test /tmp/go-build1778975665/b222/config.test -test.testlogfile=/tmp/go-build1778975665/b222/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true pkg/mod/google.golang.org/grpc@v1.80.0/internal/grpclog/prefix_l-errorsas -fPIC docker-buildx -pthread -Wl,--no-gc-sectinspect -fmessage-length--format docker-buildx -o /tmp/go-build344646749/b370/_pkg_.a -trimpath x_amd64/link -p google.golang.org/grpc/internal/--version -lang=go1.24 x_amd64/link (dns block)
  • nonexistent.local
    • Triggering command: /tmp/go-build344646749/b510/launcher.test /tmp/go-build344646749/b510/launcher.test -test.testlogfile=/tmp/go-build344646749/b510/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true 0/clients.go 0/doc.go x_amd64/compile OUTPUT -d 168.63.129.16 x_amd64/compile o_.o�� ache/go/1.25.8/x-errorsas oZMU/gIMg3r8eCE3-ifaceassert de/node/bin/as -p sh -lang=go1.25 646749/b171/_x00-buildtags (dns block)
    • Triggering command: /tmp/go-build952104743/b510/launcher.test /tmp/go-build952104743/b510/launcher.test -test.testlogfile=/tmp/go-build952104743/b510/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true cmCl2WHYI 646749/b318/ 64/pkg/tool/linux_amd64/vet --gdwarf-5 --64 -o 64/pkg/tool/linux_amd64/vet 6467�� olang.org/grpc@v1.80.0/internal/pretty/pretty.go-p pkg/mod/github.com/go-logr/logr@v1.4.3/context_smain cfg -b 646749/b318/_cgo/usr/bin/runc p/bin/git ache/go/1.25.8/x64/pkg/tool/linux_amd64/compile (dns block)
    • Triggering command: `/tmp/go-build3330052160/b514/launcher.test /tmp/go-build3330052160/b514/launcher.test -test.testlogfile=/tmp/go-build3330052160/b514/testlog.txt -test.paniconexit0 -test.timeout=10m0s e=/t�� t0 m0s x_amd64/vet pkg/mod/google.ggit ��4.1.3.6) alongside legacy tracing key
  • Add StdinOpenTelemetryConfig + wire into �� ache/go/1.25.8/x--exclude-standard x_amd64/vet --ve�� -o ache/go/1.25.8/x64/pkg/tool/linux_amd64/vet x_amd64/vet /tmp/go-build344bash -trimpath 646749/b371/vet.--noprofile x_amd64/vet` (dns block)
  • slow.example.com
    • Triggering command: /tmp/go-build344646749/b510/launcher.test /tmp/go-build344646749/b510/launcher.test -test.testlogfile=/tmp/go-build344646749/b510/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true 0/clients.go 0/doc.go x_amd64/compile OUTPUT -d 168.63.129.16 x_amd64/compile o_.o�� ache/go/1.25.8/x-errorsas oZMU/gIMg3r8eCE3-ifaceassert de/node/bin/as -p sh -lang=go1.25 646749/b171/_x00-buildtags (dns block)
    • Triggering command: /tmp/go-build952104743/b510/launcher.test /tmp/go-build952104743/b510/launcher.test -test.testlogfile=/tmp/go-build952104743/b510/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true cmCl2WHYI 646749/b318/ 64/pkg/tool/linux_amd64/vet --gdwarf-5 --64 -o 64/pkg/tool/linux_amd64/vet 6467�� olang.org/grpc@v1.80.0/internal/pretty/pretty.go-p pkg/mod/github.com/go-logr/logr@v1.4.3/context_smain cfg -b 646749/b318/_cgo/usr/bin/runc p/bin/git ache/go/1.25.8/x64/pkg/tool/linux_amd64/compile (dns block)
    • Triggering command: `/tmp/go-build3330052160/b514/launcher.test /tmp/go-build3330052160/b514/launcher.test -test.testlogfile=/tmp/go-build3330052160/b514/testlog.txt -test.paniconexit0 -test.timeout=10m0s e=/t�� t0 m0s x_amd64/vet pkg/mod/google.ggit ��4.1.3.6) alongside legacy tracing key
  • Add StdinOpenTelemetryConfig + wire into �� ache/go/1.25.8/x--exclude-standard x_amd64/vet --ve�� -o ache/go/1.25.8/x64/pkg/tool/linux_amd64/vet x_amd64/vet /tmp/go-build344bash -trimpath 646749/b371/vet.--noprofile x_amd64/vet` (dns block)
  • this-host-does-not-exist-12345.com
    • Triggering command: /tmp/go-build344646749/b519/mcp.test /tmp/go-build344646749/b519/mcp.test -test.testlogfile=/tmp/go-build344646749/b519/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true 64/src/net -trimpath 64/pkg/tool/linu-nilfunc -p math/rand/v2 -lang=go1.25 64/pkg/tool/linu-buildtags lid_�� 1.10.2/active_he-errorsas 1.10.2/args.go x_amd64/compile 646749/b165/ crypto/rsa ctor x_amd64/compile (dns block)
    • Triggering command: /tmp/go-build952104743/b519/mcp.test /tmp/go-build952104743/b519/mcp.test -test.testlogfile=/tmp/go-build952104743/b519/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true 646749/b440/_pkg-s /tmp/go-build344-w cfg go g/grpc/internal//usr/bin/runc 64/pkg/tool/linu--version ache/go/1.25.8/x-extld=gcc -W 646749/b458/_pkg_.a /tmp/go-build344646749/b318/ docker-buildx . b/gh-aw-mcpg/int/usr/bin/runc --64 docker-buildx (dns block)
    • Triggering command: /tmp/go-build3330052160/b523/mcp.test /tmp/go-build3330052160/b523/mcp.test -test.testlogfile=/tmp/go-build3330052160/b523/testlog.txt -test.paniconexit0 -test.timeout=10m0s /usr�� --version ache/go/1.25.8/x64/pkg/tool/linux_amd64/vet x_amd64/vet g pkg/mod/google.gls-files ache/go/1.25.8/x--exclude-standard x_amd64/vet --ve�� -o ache/go/1.25.8/x64/pkg/tool/linux_amd64/vet x_amd64/vet /tmp/go-build344bash -trimpath docker-buildx x_amd64/vet (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI and others added 2 commits April 4, 2026 23:33
- Add Headers, TraceID, SpanID fields to TracingConfig
- Add opentelemetry TOML key (spec §4.1.3.6) alongside legacy tracing key
- Add StdinOpenTelemetryConfig + wire into StdinGatewayConfig + stdin converter
- Update JSON schema to include opentelemetry in gatewayConfig
- Add validation: HTTPS endpoint, traceId (32-char hex), spanId (16-char hex)
- Update InitProvider to pass headers to OTLP exporter
- Add W3C parent context support from traceId/spanId
- Update callBackendTool to use spec attribute names (mcp.server, mcp.method, mcp.tool, http.status_code)
- Add compliance tests T-OTEL-001 through T-OTEL-010

Agent-Logs-Url: https://github.com/github/gh-aw-mcpg/sessions/68c6c224-9436-4b1d-9289-79e3af4c4d53

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Copilot AI changed the title [WIP] Support spec v1.11.0 OpenTelemetry OTLP tracing configuration feat: Support spec v1.11.0 — OpenTelemetry OTLP tracing configuration (§4.1.3.6) Apr 4, 2026
Copilot finished work on behalf of lpcox April 4, 2026 23:37
Copilot AI requested a review from lpcox April 4, 2026 23:37
@lpcox lpcox marked this pull request as ready for review April 5, 2026 00:49
Copilot AI review requested due to automatic review settings April 5, 2026 00:49
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the gateway’s tracing support to align with MCP Gateway Spec v1.11.0 §4.1.3.6 by introducing an opentelemetry config section, adding W3C parent context support (traceId/spanId), and updating tool-call span naming/attributes to the spec’s conventions.

Changes:

  • Add OpenTelemetry OTLP options: export headers + W3C remote parent context construction from traceId/spanId.
  • Add/validate [gateway.opentelemetry] config (TOML + stdin JSON + schema) with precedence over legacy [gateway.tracing].
  • Update callBackendTool span name/attributes to spec keys and add http.status_code attribute.
Show a summary per file
File Description
internal/tracing/provider.go Adds OTLP export headers and W3C remote parent context creation logic.
internal/tracing/provider_test.go Adds tests for headers and ParentContext behavior.
internal/server/unified.go Renames tool-call span and updates attributes + sets http.status_code.
internal/config/validation.go Adds spec validation for opentelemetry endpoint/traceId/spanId.
internal/config/schema/mcp-gateway-config.schema.json Adds gateway.opentelemetry to schema and reformats schema document.
internal/config/config_tracing.go Extends TracingConfig with Headers/TraceID/SpanID; adds stdin converter for opentelemetry.
internal/config/config_tracing_test.go Adds compliance tests for §4.1.3.6 validation/defaulting.
internal/config/config_stdin.go Adds stdin JSON struct for gateway.opentelemetry.
internal/config/config_core.go Adds GatewayConfig.Opentelemetry and merges it into legacy tracing with precedence + validation.
internal/cmd/root.go Applies configured W3C parent context at startup.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 10/10 changed files
  • Comments generated: 6

Comment on lines +517 to +526
// Validate traceId: must be 32-char lowercase hex (or a ${VAR} expression)
if cfg.TraceID != "" && !varExprPatternSimple.MatchString(cfg.TraceID) {
if !traceIDPattern.MatchString(cfg.TraceID) {
logValidation.Printf("Invalid traceId format: %s", cfg.TraceID)
return rules.InvalidValue("traceId",
fmt.Sprintf("traceId must be a 32-character lowercase hexadecimal string, got '%s'", cfg.TraceID),
"gateway.opentelemetry.traceId",
"Provide a valid W3C trace ID (32 lowercase hex chars, e.g., \"4bf92f3577b34da6a3ce929d0e0e4736\")")
}
}
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

W3C Trace Context forbids an all-zero traceId; the current regex check would accept it, and ParentContext will later drop it as an invalid SpanContext. Consider explicitly rejecting the all-zero traceId (and optionally all-zero spanId) during validation so misconfiguration is caught early and consistently.

Copilot uses AI. Check for mistakes.
Comment on lines +349 to +354
// The test server may or may not have received the export within the timeout.
// If it did, verify the Authorization header was forwarded correctly.
if capturedAuth != "" {
assert.Equal(t, "Bearer test-token", capturedAuth,
"Authorization header must be forwarded to the OTLP collector")
}
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is effectively non-asserting: it only checks the forwarded header if capturedAuth is non-empty, so it can pass even when headers are not applied. Since Shutdown should flush the batcher, consider making the test deterministic by waiting for a request (e.g., via a channel) and failing if no export arrives within a short timeout, then asserting the Authorization header.

Copilot uses AI. Check for mistakes.
Comment on lines +34 to +46
// Headers are HTTP headers sent with every OTLP export request (e.g. auth tokens).
// Values support ${VAR} variable expansion.
Headers map[string]string `toml:"headers" json:"headers,omitempty"`

// TraceID is an optional W3C trace ID (32-char lowercase hex) used to construct the
// parent traceparent header, linking gateway spans into a pre-existing trace.
// Supports ${VAR} variable expansion. Must be 32 lowercase hex characters.
TraceID string `toml:"trace_id" json:"traceId,omitempty"`

// SpanID is an optional W3C span ID (16-char lowercase hex) paired with TraceID
// to construct the parent traceparent header. Ignored when TraceID is absent.
// Supports ${VAR} variable expansion. Must be 16 lowercase hex characters.
SpanID string `toml:"span_id" json:"spanId,omitempty"`
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments state tracing Headers/TraceID/SpanID “support ${VAR} variable expansion”, but TOML config loading doesn’t appear to perform ${VAR} expansion for these fields (stdin JSON does via ExpandRawJSONVariables). Consider clarifying the comment to match actual behavior, or implementing variable expansion for TOML-loaded tracing config so the documented behavior is true.

Copilot uses AI. Check for mistakes.
lpcox and others added 4 commits April 4, 2026 17:55
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
- Add TOML variable expansion for tracing fields (endpoint, traceId,
  spanId, headers) via expandTracingVariables(), matching stdin JSON
  path behavior. Comments now accurately reflect expansion support.
- Reject all-zero traceId/spanId per W3C Trace Context specification.
- Make TestInitProvider_WithHeaders deterministic using a channel
  instead of conditional assertion on captured auth header.
- Remove unused varExprPatternSimple regex.
- Add tests: variable expansion, undefined var error, all-zero
  traceId/spanId rejection, unexpanded var expression rejection.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@lpcox lpcox merged commit a05ccb5 into main Apr 5, 2026
17 checks passed
@lpcox lpcox deleted the copilot/support-spec-v1-11-0-opentelemetry-config branch April 5, 2026 04:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support spec v1.11.0: OpenTelemetry OTLP tracing configuration

3 participants