spring-projects / spring-projects/spring-ai
[Bug - MCP server] `@McpResource(title = ...)` is dropped by the resource providers
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 9.5k
- Forks
- 2.9k
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 6
Description
Bug description
@McpResource(title = "...") is silently dropped. The title never reaches the client, so MCP clients fall back to name (which defaults to the Java method name) as the display name.
This shares a root cause with #6749 (McpResource.annotations is ignored) — both stem from ResourceAdapter having no production callers. Filing separately because title is a distinct, user-visible symptom that #6749 does not mention, so a fix scoped to that issue's title could easily miss it. Feel free to close as a duplicate if you'd rather track both under #6749.
The four resource providers build the Resource / ResourceTemplate without ever calling .title(...):
SyncMcpResourceProvider(lines 70, 113)AsyncMcpResourceProvider(lines 96, 149)SyncStatelessMcpResourceProvider(lines 95, 148)AsyncStatelessMcpResourceProvider(lines 96, 149)
var mcpResource = McpSchema.Resource.builder(uri, name)
.description(description)
.mimeType(mimeType)
.meta(meta)
.build(); // no .title(...)
These are the runtime path — SyncMcpAnnotationProviders (lines 96, 106) and AsyncMcpAnnotationProviders delegate to them. ResourceAdapter#asResource does set the title correctly (line 50), but it has no production callers.
For contrast, the other two annotation families do propagate the title:
@McpTool— all four tool providers calltoolBuilder.title(title), and even fall back to the tool name when it is blank.@McpPrompt—PromptAdaptersets it (lines 59, 76), and that adapter is used in production.
Resources are the only family where it is dropped. McpSchema.Resource.Builder#title defaults to null with no fallback from name, so nothing downstream fills it in.
Environment
- Spring AI: 2.0.0-SNAPSHOT (
main, commit as of 2026-08-12) - MCP Java SDK: 2.0.0
- Java: 17
Steps to reproduce
- Annotate a method with
@McpResource(uri = "...", name = "...", title = "My Resource Title"). - Register the bean so the annotation providers pick it up.
- Call
resources/listfrom a client (or inspect theResourcethe provider builds). - The
titlefield is absent; clients display thenameinstead.
Expected behavior
title from @McpResource is propagated to the generated Resource and ResourceTemplate, matching the behavior of @McpTool and @McpPrompt.
Minimal Complete Reproducible example
Failing test, dropped into mcp/mcp-annotations/src/test/java/org/springframework/ai/mcp/annotation/provider/resource/:
public class ReproTitleDroppedTests {
@Test
void resourceTitleIsPropagated() {
List<SyncResourceSpecification> specs = new SyncMcpResourceProvider(List.of(new TitledResources()))
.getResourceSpecifications();
assertThat(specs).hasSize(1);
assertThat(specs.get(0).resource().title()).isEqualTo("My Resource Title");
}
@Test
void resourceTemplateTitleIsPropagated() {
List<SyncResourceTemplateSpecification> specs = new SyncMcpResourceProvider(List.of(new TitledResources()))
.getResourceTemplateSpecifications();
assertThat(specs).hasSize(1);
assertThat(specs.get(0).resourceTemplate().title()).isEqualTo("My Template Title");
}
static class TitledResources {
@McpResource(uri = "test://resource", name = "test-resource", title = "My Resource Title",
description = "A test resource")
public String fixedResource() {
return "content";
}
@McpResource(uri = "test://resource/{id}", name = "test-template", title = "My Template Title",
description = "A test resource template")
public String templatedResource(String id) {
return "content-" + id;
}
}
}
Both fail on main:
[ERROR] Tests run: 2, Failures: 2, Errors: 0, Skipped: 0
[ERROR] ReproTitleDroppedTests.resourceTitleIsPropagated:40
expected: "My Resource Title"
but was: null
[ERROR] ReproTitleDroppedTests.resourceTemplateTitleIsPropagated:49
expected: "My Template Title"
but was: null
I only ran this against SyncMcpResourceProvider. I did not run it against the other three, but they contain the same builder block with no .title(...) call at the line numbers listed above, so I expect them to behave the same.
I'm happy to open a PR. The obvious fix is to route the providers through ResourceAdapter so title, annotations and future attributes stay in one place — that would close #6749 as well. If you'd prefer a narrower change that only adds .title(...) to the four providers and leaves the ResourceAdapter question (including the lastModified design point raised in #6749) for later, I can do that instead. Let me know which direction you want.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the failing ReproTitleDroppedTests in mcp/mcp-annotations/src/test/java/org/springframework/ai/mcp/annotation/provider/resource/ and inspect the builder blocks in SyncMcpResourceProvider, AsyncMcpResourceProvider, SyncStatelessMcpResourceProvider, and AsyncStatelessMcpResourceProvider. Compare them with ResourceAdapter#asResource and the title handling in the tool and prompt providers. Done means both resource and resource-template titles are present for all four providers, with the tests passing.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, spring
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100