spring-projects / spring-projects/spring-ai

[Bug - MCP server] `@McpResource(title = ...)` is dropped by the resource providers

Open
#6,786 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

status: waiting-for-triage
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 call toolBuilder.title(title), and even fall back to the tool name when it is blank.
  • @McpPromptPromptAdapter sets 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

  1. Annotate a method with @McpResource(uri = "...", name = "...", title = "My Resource Title").
  2. Register the bean so the annotation providers pick it up.
  3. Call resources/list from a client (or inspect the Resource the provider builds).
  4. The title field is absent; clients display the name instead.

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.