feat(contrib/mark3labs/mcp-go): skip telemetry injection for UI-only tools#4942
Conversation
🎉 All green!🧪 All tests passed 🎯 Code Coverage (details) 🔗 Commit SHA: 2ff4a7b | Docs | Datadog PR Page | Give us feedback! |
BenchmarksBenchmark execution time: 2026-07-01 16:38:29 Comparing candidate commit 2ff4a7b in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 321 metrics, 2 unstable metrics, 1 flaky benchmarks without significant changes.
|
0f308e2 to
301b597
Compare
d0e5464 to
0d1c005
Compare
0d1c005 to
a8261f9
Compare
301b597 to
ce524bf
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a8261f9663
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if meta == nil { | ||
| return true | ||
| } | ||
| uiMap, ok := meta.AdditionalFields["ui"].(map[string]any) |
There was a problem hiding this comment.
Handle typed UI metadata before defaulting callable
When a server constructs the same valid _meta.ui.visibility wire format with a typed Go value, such as a struct or map[string][]string, this assertion falls through and treats the tool as model-callable. mcp.Meta.AdditionalFields accepts arbitrary values and will marshal those typed values correctly, so app-only tools declared that way still get required telemetry injected in tools/list, leaving the UI calls rejected for the scenario this change is meant to fix.
Useful? React with 👍 / 👎.
ce524bf to
ec14544
Compare
a850ceb to
cb69328
Compare
ec14544 to
0c444d5
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files🚀 New features to boost your workflow:
|
cb69328 to
5f2c10d
Compare
2ec11b5
into
main
… time (#4943) This PR stack ports functionality added in the clone of this contrib in dd-source back to dd-trace-go. ## Summary Pass `llmobs.WithSessionID` at `StartToolSpan` time so LLMObs groups tool spans under their MCP session natively. The session is read from `server.ClientSessionFromContext(ctx)` if present. The existing `mcp_session_id` tag is unchanged — they are complementary: the tag exposes the session ID to span search/filtering, while `WithSessionID` populates the LLMObs session field used for trace grouping. ## Provenance No single dd-source PR — the change landed as part of a series of LLMObs-session changes. The current dd-source middleware lives at [`mcp-go/tracing.go#L36-L44`](https://github.com/DataDog/dd-source/blob/d10d624c374b69aa02168983be725532a38562b1/domains/mcp_services/libs/go/mcp/pre-release-external/dd-trace-go/contrib/mark3labs/mcp-go/tracing.go#L36-L44): ```go var toolHandlerMiddleware = func(next server.ToolHandlerFunc) server.ToolHandlerFunc { return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { // Note: WithIntegration is not yet in released llmobs package and cannot be used here var startOpts []llmobs.StartSpanOption if session := server.ClientSessionFromContext(ctx); session != nil { startOpts = append(startOpts, llmobs.WithSessionID(session.SessionID())) } toolSpan, ctx := llmobs.StartToolSpan(ctx, request.Params.Name, startOpts...) ``` This PR ports that exact pattern, additionally keeping the existing `llmobs.WithIntegration(...)` call since the released llmobs package now has it. ## Test plan - [x] Extended `TestIntegrationToolCallSuccess` to assert `toolSpan.SessionID` matches the MCP client session. - [x] Existing tracing tests still pass. Stacked on #4942. Co-authored-by: jboolean <julian.boilen@datadoghq.com>
…-only tools (#4949) This PR stack ports functionality added in the clone of this contrib in dd-source back to dd-trace-go (Rapid clone at `domains/api_platform/libs/go/mcp/tracing`). ## Summary Skips telemetry/intent injection for tools whose `_meta.ui.visibility` excludes `"model"`. UI-only tools cannot be invoked by the model, so a required `telemetry` parameter would be useless noise. `_meta.ui.visibility` is part of the MCP Apps extension (see https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/SEP-1865.md). Absent visibility list ⇒ model-callable (spec default). ## What this enables in dd-source Same behavior already shipping in the Rapid clone — UI-only tools registered through dd-source's MCP server are silently skipped today; this PR brings that to the public contrib. Source PR: DataDog/dd-source#420911 ## Test plan - [x] `TestIntentCaptureSkipsUIOnlyTools` — three tools (no meta, ui-only, dual-visibility): only the UI-only one is skipped. - [x] All existing intent-capture tests still pass. Stacked on #4948. --- Parallel port in the mark3labs/mcp-go contrib: #4942 Co-authored-by: jboolean <julian.boilen@datadoghq.com>

This PR stack ports functionality added in the clone of this contrib in dd-source back to dd-trace-go.
Summary
The intent-capture
ListToolshook unconditionally addstelemetryas a required field to every tool's input schema. That breaks tools annotated as UI-only (_meta.ui.visibilityexcluding"model"): they are invoked by the app UI, not by the model, so the UI caller has nointentto supply. OpenAI's MCP client strictly validates tool arguments against the advertised schema and rejects every UI-driven call as a result.This change adds an
isModelCallablehelper that inspects*mcp.Meta.AdditionalFields["ui"]["visibility"](handling both[]stringand[]anyshapes from JSON round-trips) and short-circuits the injection when the model is not in the visibility list. Tools with no_meta.ui.visibilitycontinue to default to model-callable per the MCP Apps spec.Ported from internal dd-source PR #420911.
Test plan
TestIntentCaptureSkipsUIOnlyTools— three tools (model-only, UI-only[app], dual[app, model]); assertstelemetryis injected for model and dual but not for UI-only.Stacked on #4941.
MCP standard reference
The
_meta.ui.visibilityfield is part of the official MCP Apps extension. Values:["model", "app"](default),["model"], or["app"]— they control which actor may invoke the tool. UI-only tools (["app"]) are invoked by the host shell, never by the model. This PR's skip logic respects that standard.Parallel port in the modelcontextprotocol/go-sdk contrib: #4949