Repository navigation
Add Primitive repository APIs (ResourceRepository, ToolsRepository, ... ) #578
Description
Activity
I believe the repository-oriented design should be able to address the needs expressed in #593 (comment). If we can unify these ideas in this issue we could close #593 and make progress designing a comprehensive solution with this issue. @tzolov, @sdelamo WDYT?
@sdelamo @chemicL
hi, I think these problems are essentially the same type of problems that the org.springframework.ai.mcp.McpToolUtils.prefixedToolName(String prefix, String title, String toolName) method in the SpringAI project solves. They are all about how to manage tools by grouping them so that the MCP client can more intelligently heuristically call tools among a large number of tools, first calling the tool group and then calling the specific tool.
https://github.com/spring-projects/spring-ai/blob/main/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java- addedenhancementNew feature or requestNew feature or requestP3Nice to haves, rare edge casesNice to haves, rare edge cases
on Feb 18, 2026 - marked Support filtering tools depends on request data (http-headers, etc) #608 as a duplicate of this issue
on Feb 20, 2026 - marked Support ToolsRepository for Dynamic Tool Management & Cursor-based Pagination Status #746 as a duplicate of this issue
on Feb 20, 2026 More requirements are described in #746, which I closed as a duplicate but should keep in mind when implementing this.
Reacted by Taewoong Kim- marked How to filter list of tool based on JWT token? #740 as a duplicate of this issue
on Feb 20, 2026 As all the related tickets seem to have been closed, I want to throw in one requirement that we at our company need; we've implemented a dynamic tool approach depending on some header field in the
tools/listrequest. So a repository-based approach would be great, but also on a per-request basis.Reacted by Dariusz Jędrzejczyk and Lei Xiao@chemicL
Hi! After #746 was closed as a duplicate of this issue, I’d like to confirm the direction.
I put together PR #747 as an early attempt at the ToolsRepository portion (repository abstraction, per-request filtering, and cursor pagination).Does this approach align with the direction you’re aiming for here? If not, I’m happy to adjust it accordingly.
If it does, I’d be glad to help with the remaining work via follow-up PRs.Reacted by Björn, Tobias Metzke-Bernstein and Tarmo TerimaaAny updates on when this feature will be addressed and will be available?
Reacted by sandeepbhojwani, Chris Tarczon, lldacing, Taras Zubrei, abstiger, DanielVolovikUpwind, Vladimir, Tim Schneider, Tarmo Terimaa, Marvin L and 7 more6 remaining items
Now, that 2.0.0 is released, can this be prioritized? @chemicL
Reacted by Björn and KernevezWe want to introduce this feature in a non-breaking, 2.1 minor. Users may opt-in to the new feature, possibly replacing the old way of doing things with repositories (rather than having both at runtime); but they may stay on the current implementation as well.
With that said,
State of the project and upcoming changes
Here's our thinking right now:
We have 6 variants for the servers, [Sync | Async] * [SSE | Streamable | Stateless]. We can dial it down to 4 by deciding not to implement SSE. In our case, "Stateless" means "Streamable but only the request-response part, no sessions or streaming". They were all implemented to match the API of the STDIO client.
If we were to implement a full API, we'd need 4 interfaces for repositories for Tools, 4 for Resources, 4 for Prompts and possibly Completions.
On top of that, there is a new spec coming (tentatively) end of July, introducing a new definition of statelessness SEP-2575: Make MCP Stateless. It relies on SEP-2322: Multi Round-Trip Requests, which changes return types from e.g.
CallToolResulttoCallToolResult | InputRequiredResult. We haven't started on the implementation yet but this likely means we'll have to introduce a new abstraction, separate from the existing stuff, leading to more definitions for Tools and, possibly ratcheting up the number of repositories even further.In 3.0, we would like to move away from the Sync / Async dichotomy, and introduce a new, virtual-thread friendly, unified API (see #778).
So all these new repositories we discussed above, we'll have to maintain, even though we will be moving away from them in the mid term.
Implementation strategy
We do want this feature. We're planning on experimenting with it, releasing some milestones so users can start playing with it and we get feedback.
Here's what we are thinking thus far:
- Implement only the sync variant (no Mono<...>), as it matches our long-term roadmap
- This may be an issue, as the current architecture for sync is an async core, which must NOT perform blocking work. Typically achieved by defensively wrapping blocking calls with
.subscribeOn(Schedulers.boundedElastic())), but this means introducing some undesirable overhead. - Consider making the sync implementations pluggable into async servers using an adapter
- This may be an issue, as the current architecture for sync is an async core, which must NOT perform blocking work. Typically achieved by defensively wrapping blocking calls with
- Start with the stateless variant, i.e. a repository for
(CallToolRequest, McpTransportContext) -> CallToolResult- This means we would not support sampling, logging (going away in the next spec), as well as elicitation and progress.
- These can be plugged into streamable, but you'd lose the benefit
Reacted by Björn, Taewoong Kim and Kernevez- Implement only the sync variant (no Mono<...>), as it matches our long-term roadmap
Thanks for laying this out, @Kehrlann. This direction makes sense to me.
My understanding is that the first implementation should move toward a sync/stateless repository contract, without
Mono<...>in the public repository API. It should receive the MCP request together with request context, likely throughMcpTransportContext, and avoid exposing a server exchange object in the repository contract.That would keep the repository path focused on request-context based dynamic discovery and execution, while users who need exchange-dependent flows such as sampling, elicitation, logging, or progress can continue using the existing exchange-based handler API.
If this direction sounds right, I’d like to rework the repository proposal across tools, resources, prompts, and completions using a consistent sync/stateless repository shape.
I’d also be happy to help with follow-up work around exchange-dependent or multi-round-trip behavior once the Java SDK model for that settles.
Would you prefer me to rework #953 directly, or open a fresh smaller PR for that narrower shape?
I opened #1034 as a smaller proposal to make the sync/stateless repository direction more concrete and easier to review.
When you have a chance, I’d appreciate a check on whether this matches the intended direction.
@Kehrlann Any release date is planned for this feature?
Sorry for the long response time - summer time in Europe is not very active.
We have decided to NOT implement full repositories in the 2025-11-25 iteration of the spec (current
2.xline of the SDK), and instead focus on implementing from first principles in3.x(2026-07-28 sec).A full implementation in the current state (2.x, 2025-11-25) spec would be complex, as it'd need to target [sync | async] x [stateful | stateless] = 4 variants. We'd need to address the bridges between sync and async, make it coexist with current server's tool management (array list of tools, .addTool method). We have originally floated the idea of doing only stateless / sync ; but that would leave the 3 other variants stranded.
3.x will bring breaking changes and new APIs, and we will introduce repositories as a first class primitive in that version.In the meantime, to address the most pressing need, we'll introduce
tools/listfiltering in 2.1.0 (see #1108)Reacted by Taewoong KimA concrete case for the per-client half of this, from a server built on the SDK.
RESTHeart derives MCP resources from MongoDB collections and GraphQL applications. Two things we wanted to do turned out not to be expressible against a server-wide registry:
A schema that differs per caller. We added a
@visible(roles: [...])directive to our GraphQL apps: a field the caller's roles do not name is removed from the schema they see, so introspection does not reveal it exists. We wanted the resource description in the catalog to carry the same role-filtered schema. It cannot:resourcesis one registry for the whole server, so the description we register is the one every client gets. Publishing the unfiltered schema there would have revealed exactly what the directive removes, so we left the schema out of the catalog entirely and point clients at GraphQL introspection instead — which does answer per caller.A catalog that reflects what the caller may read. Reads are authorized per principal (a resource read is checked as the equivalent REST
GET), so a client can see catalog entries it will be refused. That is a deliberate trade on our side, not a complaint — but with a repository API it would be a choice rather than a constraint.Both would be covered by the interface proposed here, provided the lookup receives the exchange (i.e. the authenticated identity), not just a key.
One adjacent note, since it comes up in the same place: #746 was closed as a duplicate of this issue, but it also asked for cursor-based pagination, which this issue does not cover — the server-side list handlers ignore
cursorand never setnextCursor. Filed separately as #1128, so it does not get lost again.
Currently,
McpStatelessAsyncServerandMcpAsyncServersaveresources,tools,prompts, andcompletionswithCopyOnWriteArrayList`.https://github.com/modelcontextprotocol/java-sdk/blob/main/mcp-core/src/main/java/io/modelcontextprotocol/server/McpStatelessAsyncServer.java#L60-L68
I want to provide an API (for example,
ResourcesRepository) that, by default, uses the current in-memory strategy but can be replaced, for example, by a dynamic implementation that returns a different list of resources depending on the currently authenticated user.