Skip to content

Preserve content-level annotations through vMCP relay#4336

Merged
JAORMX merged 3 commits intomainfrom
fix-content-annotations
Mar 25, 2026
Merged

Preserve content-level annotations through vMCP relay#4336
JAORMX merged 3 commits intomainfrom
fix-content-annotations

Conversation

@JAORMX
Copy link
Collaborator

@JAORMX JAORMX commented Mar 24, 2026

Summary

The vMCP relay was silently dropping per-content Annotations (audience, priority, lastModified) during the mcp→vmcp→mcp conversion. The MCP spec defines these annotations on every content type (text, image, audio, embedded resource, resource link), but the conversion layer was not carrying them through.

This adds ContentAnnotations to the vmcp domain model and threads annotation preservation through all content type conversions, ensuring annotations survive the full relay round-trip.

  • Add ContentAnnotations type (Audience, Priority, LastModified) to vmcp.types
  • Add Annotations *ContentAnnotations field to vmcp.Content
  • Add ConvertMCPAnnotations / ToMCPAnnotations converters for content-level annotations
  • Update ConvertMCPContent and ToMCPContent to preserve annotations on all content types (text, image, audio, embedded resource, resource link)
  • Add comprehensive tests including round-trip verification for all content types

Type of change

  • Bug fix

Test plan

  • Unit tests (task test)
  • Linting (task lint-fix)

Does this introduce a user-facing change?

Content annotations (audience hints, priority, timestamps) from backend MCP servers are now preserved through virtual MCP server responses, enabling AI clients to respect content targeting and ordering hints.

Special notes for reviewers

This is one of three related PRs fixing vMCP relay fidelity:

  • fix-prompt-messages: prompt message structure
  • fix-resource-relay: resource text/blob distinction and per-item metadata
  • This PR: per-content annotations (audience, priority, lastModified)

The ContentAnnotations type follows the existing Anti-Corruption Layer pattern — vmcp types use []string for audience rather than []mcp.Role, keeping the domain model decoupled from mcp-go.

Generated with Claude Code

@github-actions github-actions bot added the size/M Medium PR: 300-599 lines changed label Mar 24, 2026
@codecov
Copy link

codecov bot commented Mar 24, 2026

Codecov Report

❌ Patch coverage is 91.46341% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.02%. Comparing base (df1a5cd) to head (bd5eea1).
⚠️ Report is 10 commits behind head on main.

Files with missing lines Patch % Lines
pkg/vmcp/conversion/content.go 91.46% 1 Missing and 6 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4336      +/-   ##
==========================================
- Coverage   69.08%   69.02%   -0.06%     
==========================================
  Files         478      478              
  Lines       48432    48552     +120     
==========================================
+ Hits        33457    33513      +56     
- Misses      12314    12319       +5     
- Partials     2661     2720      +59     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions github-actions bot added size/M Medium PR: 300-599 lines changed and removed size/M Medium PR: 300-599 lines changed labels Mar 24, 2026
Copy link
Contributor

@yrobla yrobla left a comment

Choose a reason for hiding this comment

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

Nice fix overall — the ACL pattern is correctly applied and all five content type branches are updated. A couple of things worth addressing before merge.

Important: Two related issues in the annotation converters (pointer aliasing + asymmetric nil-elision). Test gaps: blob embedded resource path is untested, and TestToMCPAnnotations is thin.

@yrobla
Copy link
Contributor

yrobla commented Mar 24, 2026

Review priority summary (following up on the inline comments above):

Fix in this PR:

  • ToMCPAnnotations asymmetric nil-elision (comment) — 3-line guard, prevents "annotations":{} on the wire
  • Blob embedded resource test gap (comment) — a few lines, covers an existing untested branch

Fine as follow-ups:

  • Priority *float64 pointer aliasing — consistent with the existing *bool pattern for ToolAnnotations in the same file; unlikely to cause issues in a relay context
  • TestToMCPAnnotations empty non-nil case — documents the asymmetry above; can follow the nil-elision fix
  • Doc comment nits on Priority range and Audience valid values

JAORMX and others added 2 commits March 24, 2026 12:47
MCP content items (text, image, audio, embedded resource, resource link)
can carry per-content annotations (audience, priority, lastModified) that
inform how clients display results. The vMCP relay layer was silently
dropping these annotations during the mcp->vmcp->mcp conversion round
trip.

Add ContentAnnotations domain type following the existing Anti-Corruption
Layer pattern, and update ConvertMCPContent/ToMCPContent to preserve
annotations for all five content types.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Deep-copy Priority pointer in ConvertMCPAnnotations and ToMCPAnnotations
  to prevent pointer aliasing across the ACL boundary
- Add zero-value guard in ToMCPAnnotations for symmetry with
  ConvertMCPAnnotations (empty non-nil input now returns nil)
- Document Priority range constraint [0.0, 1.0] and valid Audience
  values per MCP spec
- Add test case for empty non-nil ContentAnnotations
- Add blob embedded resource test case for round-trip verification

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@JAORMX JAORMX force-pushed the fix-content-annotations branch from 2646a7b to 0fa964a Compare March 24, 2026 12:47
@github-actions github-actions bot added size/M Medium PR: 300-599 lines changed and removed size/M Medium PR: 300-599 lines changed labels Mar 24, 2026
Break long lines exceeding 130-char limit in ConvertMCPContent and
fix gci formatting alignment in content_test.go.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions bot added size/M Medium PR: 300-599 lines changed and removed size/M Medium PR: 300-599 lines changed labels Mar 24, 2026
Copy link
Contributor

@jerm-dro jerm-dro left a comment

Choose a reason for hiding this comment

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

The implementation is correct and well-tested. Round-trip coverage for all content types, priority-zero edge case handled, nil coalescing is sound.

question: Stepping back — this is the third consecutive PR fixing silent data loss in the vmcp conversion layer (#4334 prompts, #4335 resources, #4336 annotations). Each time mcp-go gains a field, the relay silently drops it until someone adds the conversion + round-trip tests in 4 places (vmcp type, ConvertMCP*, ToMCP*, tests).

The vmcp types (Content, ContentAnnotations, ResourceContent, PromptMessage) are 1:1 mirrors of their mcp-go counterparts, and 38 files in pkg/vmcp/ already import mcp-go/mcp directly — so the ACL boundary the conversion layer is meant to provide doesn't really exist in practice.

Would it be simpler to use mcp-go types directly for protocol pass-through data (content, annotations, resource contents, prompt messages) and reserve vmcp-specific types for things that genuinely have no mcp-go equivalent (BackendTarget, RoutingTable, etc.)? That would eliminate the conversion code for these types and — more importantly — make silent data loss structurally impossible for pass-through fields.

Not blocking this PR, but curious what @JAORMX and @yrobla think about whether the conversion layer is pulling its weight vs. producing bugs.

Generated with Claude Code

@JAORMX JAORMX merged commit 3a3287b into main Mar 25, 2026
54 of 56 checks passed
@JAORMX JAORMX deleted the fix-content-annotations branch March 25, 2026 05:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/M Medium PR: 300-599 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants