Skip to content

Propagate @McpResource(annotations = ...) to MCP wire objects - #6801

Open
a-yeyang wants to merge 1 commit into
spring-projects:mainfrom
a-yeyang:fix/mcp-resource-annotations-6749
Open

Propagate @McpResource(annotations = ...) to MCP wire objects#6801
a-yeyang wants to merge 1 commit into
spring-projects:mainfrom
a-yeyang:fix/mcp-resource-annotations-6749

Conversation

@a-yeyang

Copy link
Copy Markdown

Fixes #6749.

Problem

@McpResource declares a nested annotations = @McpAnnotations(audience, priority, lastModified) element. Because Java annotation elements can never be null, the compiler always materialises the declared default, so the mere presence of @McpResource on a method makes annotations() return a non-null value regardless of whether the user actually set one.

None of the four resource providers observed today take that value into account:

  • SyncMcpResourceProvider
  • AsyncMcpResourceProvider
  • SyncStatelessMcpResourceProvider
  • AsyncStatelessMcpResourceProvider

Each of them builds McpSchema.Resource / McpSchema.ResourceTemplate directly and never calls Resource.Builder#annotations(...) / ResourceTemplate.Builder#annotations(...), so user-supplied audience / priority / lastModified values never reach the MCP client.

ResourceAdapter (the sibling helper) did look at annotations(), but it was gated on !lastModified.isEmpty(), silently dropped the lastModified value even when it fired, and did not handle the template path. It is also not used by the providers today, so its behaviour did not offset the provider-side omission.

Change

  1. New mcp-annotations/common/ResourceAnnotationsUtils.toSchemaAnnotations(McpAnnotations):
    • resolves the declared default of McpResource#annotations() at class-load time via Method#getDefaultValue() — no default constants have to be duplicated;
    • returns null when the runtime value structurally equals that default (annotations implement structural equals), so the compiler-materialised default is not published to clients;
    • otherwise copies audience, priority and lastModified verbatim into an McpSchema.Annotations.
  2. Call the helper from all four providers, for both the direct-resource and the URI-template paths, and set the resulting value via Resource.Builder#annotations(...) / ResourceTemplate.Builder#annotations(...).
  3. Fix ResourceAdapter#asResource and #asResourceTemplate to use the same helper.
  4. Add regression coverage across the four provider test classes:
    • default annotations() is not propagated;
    • explicit annotations reach McpSchema.Resource;
    • explicit annotations reach McpSchema.ResourceTemplate;
    • audience-only changes propagate for the sync stateful provider (proves conversion is not gated on lastModified).

Scope kept intentionally narrow

  • The issue also mentions that lastModified cannot be dynamically computed per request; that is an API-shape change worth its own discussion and is not attempted here.
  • Title propagation on the four provider paths is being handled separately in Propagate @McpResource title to generated resources #6787 for ResourceAdapter; this PR does not touch title so the two changes stay orthogonal.

Local validation

  • mvn -pl mcp/mcp-annotations test1389 tests run, 0 failures, 0 errors, 2 skipped, including the new regression cases.
  • mvn -pl mcp/mcp-annotations -DskipTests install (checkstyle + disable.checks profile) → BUILD SUCCESS.
  • mvn -pl mcp/mcp-annotations spring-javaformat:validate -DskipTestsBUILD SUCCESS.

DCO

Signed-off-by.

Java annotation elements can never be `null`, so the compiler always
materialises the declared default of `McpResource#annotations()`.
Combined with the fact that none of the four Async/Sync[Stateless]
McpResourceProvider classes read that value when building the
`McpSchema.Resource` / `McpSchema.ResourceTemplate` objects they hand to
the SDK, user-supplied `audience` / `priority` / `lastModified` values
never reach the client. `ResourceAdapter` did look at the value but was
gated on a non-empty `lastModified`, dropped the `lastModified` string
itself, and never fired for URI templates — and, more importantly, was
not used by the providers.

Add a shared `ResourceAnnotationsUtils.toSchemaAnnotations` helper that
uses `Method#getDefaultValue()` to obtain the declared default of
`McpResource#annotations()` and returns `null` when the runtime value
equals it (annotations use structural equality), otherwise copies
`audience`, `priority` and `lastModified` verbatim into an
`McpSchema.Annotations`. Call the helper from all four resource
providers, for both direct-resource and URI-template paths, and fix
`ResourceAdapter` to use the same helper. Add regression coverage in the
four provider test classes for (a) the default-value case is not
propagated, (b) explicit annotations reach direct resources, (c)
explicit annotations reach resource templates, and (d) an audience-only
change propagates without requiring `lastModified` to be set.

Closes spring-projectsgh-6749

Signed-off-by: chenshiyang <88581400+a-yeyang@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug - MCP server] McpResource.annotations is ignored/does nothing

2 participants