11/*
2- * Copyright 2024-2024 the original author or authors.
2+ * Copyright 2024-2026 the original author or authors.
33 */
44
55package io .modelcontextprotocol .server ;
1111import java .util .Optional ;
1212import java .util .UUID ;
1313import java .util .concurrent .ConcurrentHashMap ;
14- import java .util .concurrent .CopyOnWriteArrayList ;
1514import java .util .function .BiFunction ;
1615
1716import io .modelcontextprotocol .json .McpJsonMapper ;
@@ -103,7 +102,7 @@ public class McpAsyncServer {
103102
104103 private final String instructions ;
105104
106- private final CopyOnWriteArrayList < McpServerFeatures . AsyncToolSpecification > tools = new CopyOnWriteArrayList <>() ;
105+ private final ToolsRepository toolsRepository ;
107106
108107 private final ConcurrentHashMap <String , McpServerFeatures .AsyncResourceSpecification > resources = new ConcurrentHashMap <>();
109108
@@ -136,7 +135,8 @@ public class McpAsyncServer {
136135 this .serverInfo = features .serverInfo ();
137136 this .serverCapabilities = features .serverCapabilities ().mutate ().logging ().build ();
138137 this .instructions = features .instructions ();
139- this .tools .addAll (withStructuredOutputHandling (jsonSchemaValidator , features .tools ()));
138+ this .toolsRepository = initializeToolsRepository (features .toolsRepository (), jsonSchemaValidator ,
139+ features .tools ());
140140 this .resources .putAll (features .resources ());
141141 this .resourceTemplates .putAll (features .resourceTemplates ());
142142 this .prompts .putAll (features .prompts ());
@@ -153,6 +153,27 @@ public class McpAsyncServer {
153153 requestTimeout , transport , this ::asyncInitializeRequestHandler , requestHandlers , notificationHandlers ));
154154 }
155155
156+ /**
157+ * Initialize the tools repository, wrapping tools with structured output handling.
158+ */
159+ private ToolsRepository initializeToolsRepository (ToolsRepository providedRepository ,
160+ JsonSchemaValidator jsonSchemaValidator , List <McpServerFeatures .AsyncToolSpecification > initialTools ) {
161+ if (providedRepository != null ) {
162+ // Add initial tools to the provided repository with structured output
163+ // handling
164+ if (initialTools != null ) {
165+ for (McpServerFeatures .AsyncToolSpecification tool : initialTools ) {
166+ providedRepository .addTool (withStructuredOutputHandling (jsonSchemaValidator , tool ));
167+ }
168+ }
169+ return providedRepository ;
170+ }
171+ // Create default in-memory repository with wrapped tools
172+ List <McpServerFeatures .AsyncToolSpecification > wrappedTools = withStructuredOutputHandling (jsonSchemaValidator ,
173+ initialTools );
174+ return new InMemoryToolsRepository (wrappedTools );
175+ }
176+
156177 McpAsyncServer (McpStreamableServerTransportProvider mcpTransportProvider , McpJsonMapper jsonMapper ,
157178 McpServerFeatures .Async features , Duration requestTimeout ,
158179 McpUriTemplateManagerFactory uriTemplateManagerFactory , JsonSchemaValidator jsonSchemaValidator ) {
@@ -161,7 +182,8 @@ public class McpAsyncServer {
161182 this .serverInfo = features .serverInfo ();
162183 this .serverCapabilities = features .serverCapabilities ().mutate ().logging ().build ();
163184 this .instructions = features .instructions ();
164- this .tools .addAll (withStructuredOutputHandling (jsonSchemaValidator , features .tools ()));
185+ this .toolsRepository = initializeToolsRepository (features .toolsRepository (), jsonSchemaValidator ,
186+ features .tools ());
165187 this .resources .putAll (features .resources ());
166188 this .resourceTemplates .putAll (features .resourceTemplates ());
167189 this .prompts .putAll (features .prompts ());
@@ -336,12 +358,7 @@ public Mono<Void> addTool(McpServerFeatures.AsyncToolSpecification toolSpecifica
336358 var wrappedToolSpecification = withStructuredOutputHandling (this .jsonSchemaValidator , toolSpecification );
337359
338360 return Mono .defer (() -> {
339- // Remove tools with duplicate tool names first
340- if (this .tools .removeIf (th -> th .tool ().name ().equals (wrappedToolSpecification .tool ().name ()))) {
341- logger .warn ("Replace existing Tool with name '{}'" , wrappedToolSpecification .tool ().name ());
342- }
343-
344- this .tools .add (wrappedToolSpecification );
361+ this .toolsRepository .addTool (wrappedToolSpecification );
345362 logger .debug ("Added tool handler: {}" , wrappedToolSpecification .tool ().name ());
346363
347364 if (this .serverCapabilities .tools ().listChanged ()) {
@@ -471,7 +488,11 @@ private static McpServerFeatures.AsyncToolSpecification withStructuredOutputHand
471488 * @return A Flux stream of all registered tools
472489 */
473490 public Flux <Tool > listTools () {
474- return Flux .fromIterable (this .tools ).map (McpServerFeatures .AsyncToolSpecification ::tool );
491+ // Note: This method returns all tools without exchange context.
492+ // For context-aware listing, use toolsRepository.listTools(exchange, cursor)
493+ // directly.
494+ return Flux .defer (
495+ () -> toolsRepository .listTools (null , null ).flatMapMany (result -> Flux .fromIterable (result .tools ())));
475496 }
476497
477498 /**
@@ -488,17 +509,11 @@ public Mono<Void> removeTool(String toolName) {
488509 }
489510
490511 return Mono .defer (() -> {
491- if (this .tools .removeIf (toolSpecification -> toolSpecification .tool ().name ().equals (toolName ))) {
492-
493- logger .debug ("Removed tool handler: {}" , toolName );
494- if (this .serverCapabilities .tools ().listChanged ()) {
495- return notifyToolsListChanged ();
496- }
497- }
498- else {
499- logger .warn ("Ignore as a Tool with name '{}' not found" , toolName );
512+ this .toolsRepository .removeTool (toolName );
513+ logger .debug ("Requested tool removal: {}" , toolName );
514+ if (this .serverCapabilities .tools ().listChanged ()) {
515+ return notifyToolsListChanged ();
500516 }
501-
502517 return Mono .empty ();
503518 });
504519 }
@@ -513,9 +528,16 @@ public Mono<Void> notifyToolsListChanged() {
513528
514529 private McpRequestHandler <McpSchema .ListToolsResult > toolsListRequestHandler () {
515530 return (exchange , params ) -> {
516- List <Tool > tools = this .tools .stream ().map (McpServerFeatures .AsyncToolSpecification ::tool ).toList ();
517-
518- return Mono .just (new McpSchema .ListToolsResult (tools , null ));
531+ // Extract cursor from params if present
532+ String cursor = null ;
533+ if (params != null ) {
534+ McpSchema .PaginatedRequest paginatedRequest = jsonMapper .convertValue (params ,
535+ new TypeRef <McpSchema .PaginatedRequest >() {
536+ });
537+ cursor = paginatedRequest .cursor ();
538+ }
539+ return this .toolsRepository .listTools (exchange , cursor )
540+ .map (result -> new McpSchema .ListToolsResult (result .tools (), result .nextCursor ()));
519541 };
520542 }
521543
@@ -525,18 +547,10 @@ private McpRequestHandler<CallToolResult> toolsCallRequestHandler() {
525547 new TypeRef <McpSchema .CallToolRequest >() {
526548 });
527549
528- Optional <McpServerFeatures .AsyncToolSpecification > toolSpecification = this .tools .stream ()
529- .filter (tr -> callToolRequest .name ().equals (tr .tool ().name ()))
530- .findAny ();
531-
532- if (toolSpecification .isEmpty ()) {
533- return Mono .error (McpError .builder (McpSchema .ErrorCodes .INVALID_PARAMS )
534- .message ("Unknown tool: invalid_tool_name" )
535- .data ("Tool not found: " + callToolRequest .name ())
536- .build ());
537- }
538-
539- return toolSpecification .get ().callHandler ().apply (exchange , callToolRequest );
550+ return this .toolsRepository .resolveToolForCall (callToolRequest .name (), exchange )
551+ .switchIfEmpty (Mono
552+ .error (McpError .builder (McpSchema .ErrorCodes .METHOD_NOT_FOUND ).message ("Tool not found" ).build ()))
553+ .flatMap (spec -> spec .callHandler ().apply (exchange , callToolRequest ));
540554 };
541555 }
542556
0 commit comments