001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.rest.server.mcp;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.util.*;
022
023import org.apache.juneau.commons.inject.*;
024
025/**
026 * Aggregate configuration consumed by the bound {@link McpRevision}.
027 *
028 * <p>
029 * Applications register a single {@link McpServerConfig} (typically as a bean in their {@code RestContext}
030 * bean store) listing the tools, prompts, resources, and resource templates to expose, plus optional server
031 * metadata and a pagination strategy.
032 *
033 * <p>
034 * This type is revision-neutral: it holds no field typed with any protocol revision's wire beans, and
035 * in particular holds no capabilities field in any form. The protocol version is owned by
036 * {@link McpRevision#protocolVersion()}, and an explicit capabilities advertisement is owned by a
037 * revision-specific hook on that revision's servlet or endpoint mixin.
038 */
039@SuppressWarnings({
040   "java:S135", // Handler-validation and completer-lookup loops (validatePrompts, validateResourceTemplates, resolveResourceTemplate, completer*) use independent continue guard clauses
041   "java:S3077", // The volatile fields hold immutable List/Map snapshots that are replaced wholesale when resource templates are published, so volatile only publishes a fully-built reference
042   "java:S3776" // validateResourceTemplates(), resolveResourceTemplate() and the completer lookups each match handlers against several URI/argument rules in one pass
043})
044public class McpServerConfig {
045
046   private String name;
047   private String version;
048   private String instructions;
049   private List<McpToolHandler> tools = l();
050   private List<McpPromptHandler> prompts = l();
051   private List<McpResourceHandler> resources = l();
052   private List<McpResourceTemplateHandler> resourceTemplates = l();
053
054   private volatile List<McpResourceTemplateHandler> validatedResourceTemplatesSnapshot = List.of();
055   private volatile Map<McpResourceTemplateHandler, McpUriTemplateMatcher> compiledResourceTemplateMatchers = Map.of();
056   private McpCursor cursor = McpCursor.SINGLE_PAGE;
057
058   /**
059    * Server name reported in {@code initialize}.
060    *
061    * <p>
062    * When both this and {@link #getVersion()} are {@code null}, the bound revision substitutes its
063    * own default server identity.
064    *
065    * @return The name, or {@code null} if not set.
066    */
067   public String getName() {
068      return name;
069   }
070
071   /**
072    * Sets the server name.
073    *
074    * @param value The new value. Can be <jk>null</jk> to unset the property.
075    * @return This object.
076    */
077   public McpServerConfig setName(String value) {
078      name = value;
079      return this;
080   }
081
082   /**
083    * Server version reported in {@code initialize}.
084    *
085    * @return The version, or {@code null} if not set.
086    */
087   public String getVersion() {
088      return version;
089   }
090
091   /**
092    * Sets the server version.
093    *
094    * @param value The new value. Can be <jk>null</jk> to unset the property.
095    * @return This object.
096    */
097   public McpServerConfig setVersion(String value) {
098      version = value;
099      return this;
100   }
101
102   /**
103    * Optional instructions surfaced via {@code initialize}.
104    *
105    * @return The instructions, or {@code null} if not set.
106    */
107   public String getInstructions() {
108      return instructions;
109   }
110
111   /**
112    * Sets initialize instructions.
113    *
114    * @param instructions The new value.
115    * @return This object (for method chaining).
116    */
117   public McpServerConfig setInstructions(String instructions) {
118      this.instructions = instructions;
119      return this;
120   }
121
122   /**
123    * Registered tool handlers.
124    *
125    * @return Mutable list of handlers. Never {@code null}.
126    */
127   public List<McpToolHandler> getTools() {
128      return tools;
129   }
130
131   /**
132    * Sets the tool handler list.
133    *
134    * @param tools The new value (or {@code null} to clear).
135    * @return This object (for method chaining).
136    */
137   public McpServerConfig setTools(List<McpToolHandler> tools) {
138      this.tools = tools == null ? l() : new ArrayList<>(tools);
139      return this;
140   }
141
142   /**
143    * Convenience: append one or more tool handlers.
144    *
145    * @param handlers Handlers to add.
146    * @return This object (for method chaining).
147    */
148   public McpServerConfig addTool(McpToolHandler... handlers) {
149      Collections.addAll(this.tools, handlers);
150      return this;
151   }
152
153   /**
154    * Convenience: adapt and append a single typed tool handler.
155    *
156    * <p>
157    * The handler is bridged into a raw {@link McpToolHandler} via
158    * {@link McpTypedHandlers#adaptTool(McpTypedToolHandler)} before it is stored. This overload is intentionally
159    * singular: a typed varargs overload would make {@code addTool()} ambiguous. To register multiple typed
160    * handlers, use chained {@code addTool(...)} calls, or adapt them explicitly with
161    * {@link McpTypedHandlers#adaptTool(McpTypedToolHandler)} and pass the results to
162    * {@link #addTool(McpToolHandler...)}.
163    *
164    * @param handler The typed handler to adapt and add.
165    * @return This object (for method chaining).
166    */
167   public McpServerConfig addTool(McpTypedToolHandler<?,?> handler) {
168      tools.add(McpTypedHandlers.adaptTool(handler));
169      return this;
170   }
171
172   /**
173    * Registered prompt handlers.
174    *
175    * @return Mutable list of handlers. Never {@code null}.
176    */
177   public List<McpPromptHandler> getPrompts() {
178      return prompts;
179   }
180
181   /**
182    * Sets the prompt handler list.
183    *
184    * <p>
185    * The complete resulting registry is validated before publication; on failure, the previously published
186    * registry is left unchanged. See {@link #addPrompt(McpPromptHandler...)} for the validated conditions.
187    *
188    * @param prompts The new value (or {@code null} to clear).
189    * @return This object (for method chaining).
190    * @throws IllegalArgumentException If the resulting registry would be invalid.
191    */
192   public McpServerConfig setPrompts(List<McpPromptHandler> prompts) {
193      var candidate = prompts == null ? new ArrayList<McpPromptHandler>() : new ArrayList<>(prompts);
194      validatePrompts(candidate);
195      this.prompts = candidate;
196      return this;
197   }
198
199   /**
200    * Convenience: append one or more prompt handlers.
201    *
202    * <p>
203    * The complete resulting registry - the handlers already registered plus {@code handlers} - is validated
204    * before publication; on failure, the previously published registry is left unchanged. Fails fast with
205    * {@link IllegalArgumentException} for a single prompt whose declared {@link McpPromptArgument} list either:
206    * <ul>
207    *    <li>repeats a non-{@code null} argument name; or
208    *    <li>attaches a non-{@code null} {@link McpPromptArgument#getCompleter() completer} to a {@code null} or
209    *       blank argument name.
210    * </ul>
211    *
212    * @param handlers Handlers to add.
213    * @return This object (for method chaining).
214    * @throws IllegalArgumentException If the resulting registry would be invalid.
215    */
216   public McpServerConfig addPrompt(McpPromptHandler... handlers) {
217      var candidate = new ArrayList<>(this.prompts);
218      Collections.addAll(candidate, handlers);
219      validatePrompts(candidate);
220      this.prompts = candidate;
221      return this;
222   }
223
224   /**
225    * Validates one candidate prompt registry in full, throwing {@link IllegalArgumentException} on the first
226    * invalid entry, so a misconfigured prompt argument is caught at registration time rather than during
227    * {@code completion/complete} dispatch. See {@link #addPrompt(McpPromptHandler...)} for the validated
228    * conditions.
229    *
230    * <p>
231    * A {@code null} handler, a handler whose {@link McpPromptHandler#descriptor()} returns {@code null}, or a
232    * descriptor with {@code null} {@link McpPromptSpec#getArguments() arguments} carries nothing to validate
233    * here and is silently skipped; those are not this validation's concern.
234    */
235   private static void validatePrompts(List<McpPromptHandler> candidate) {
236      for (var i = 0; i < candidate.size(); i++) {
237         var handler = candidate.get(i);
238         if (handler == null)
239            continue;
240         var descriptor = handler.descriptor();
241         if (descriptor == null)
242            continue;
243         var arguments = descriptor.getArguments();
244         if (arguments == null)
245            continue;
246         var seenNames = new HashSet<String>();
247         for (var j = 0; j < arguments.size(); j++) {
248            var argument = arguments.get(j);
249            if (argument == null)
250               continue;
251            var name = argument.getName();
252            if (argument.getCompleter() != null && ib(name))
253               throw iaex(
254                  "Invalid prompt registration at index %s (prompt '%s'), argument index %s: a completer must not be attached to a null or blank argument name",
255                  i, descriptor.getName(), j);
256            if (name != null && ! seenNames.add(name))
257               throw iaex("Invalid prompt registration at index %s (prompt '%s'): duplicate argument name '%s'",
258                  i, descriptor.getName(), name);
259         }
260      }
261   }
262
263   /**
264    * Registered resource handlers.
265    *
266    * @return Mutable list of handlers. Never {@code null}.
267    */
268   public List<McpResourceHandler> getResources() {
269      return resources;
270   }
271
272   /**
273    * Sets the resource handler list.
274    *
275    * @param resources The new value (or {@code null} to clear).
276    * @return This object (for method chaining).
277    */
278   public McpServerConfig setResources(List<McpResourceHandler> resources) {
279      this.resources = resources == null ? l() : new ArrayList<>(resources);
280      return this;
281   }
282
283   /**
284    * Convenience: append one or more resource handlers.
285    *
286    * @param handlers Handlers to add.
287    * @return This object (for method chaining).
288    */
289   public McpServerConfig addResource(McpResourceHandler... handlers) {
290      Collections.addAll(this.resources, handlers);
291      return this;
292   }
293
294   /**
295    * Registered resource-template handlers.
296    *
297    * <p>
298    * Every read of this property (re)validates the current registry if the list has been mutated directly
299    * since the last validated {@link #setResourceTemplates(List)}, {@link #addResourceTemplate(McpResourceTemplateHandler...)},
300    * {@link #addResourceTemplate(McpResourceTemplateSpec...)}, or {@link #setResourceTemplateSpecs(List)} call,
301    * so direct-list corruption is caught on first consumption rather than silently propagating to dispatch.
302    * A registry that has not been mutated since its last successful validation is returned without
303    * recompiling its templates.
304    *
305    * @return Mutable list of handlers. Never {@code null}.
306    * @throws IllegalArgumentException If the list was mutated directly into an invalid state (see
307    *    {@link #addResourceTemplate(McpResourceTemplateHandler...)} for the validated conditions).
308    */
309   public List<McpResourceTemplateHandler> getResourceTemplates() {
310      ensureResourceTemplatesValid();
311      return resourceTemplates;
312   }
313
314   /**
315    * Sets the resource-template handler list.
316    *
317    * <p>
318    * The complete resulting registry is validated before publication; on failure, the previously published
319    * registry is left unchanged. See {@link #addResourceTemplate(McpResourceTemplateHandler...)} for the
320    * validated conditions.
321    *
322    * @param value The new value (or {@code null} to clear).
323    * @return This object (for method chaining).
324    * @throws IllegalArgumentException If the resulting registry would be invalid.
325    */
326   public McpServerConfig setResourceTemplates(List<McpResourceTemplateHandler> value) {
327      var candidate = value == null ? new ArrayList<McpResourceTemplateHandler>() : new ArrayList<>(value);
328      publishResourceTemplates(candidate);
329      return this;
330   }
331
332   /**
333    * Sets the resource-template registry from bare descriptors, replacing each with a built-in listing-only
334    * handler equivalent to what {@link #addResourceTemplate(McpResourceTemplateSpec...)} wraps.
335    *
336    * <p>
337    * The complete resulting registry is validated before publication; on failure, the previously published
338    * registry is left unchanged.
339    *
340    * @param value The new descriptor list (or {@code null} to clear).
341    * @return This object (for method chaining).
342    * @throws IllegalArgumentException If the resulting registry would be invalid.
343    */
344   public McpServerConfig setResourceTemplateSpecs(List<McpResourceTemplateSpec> value) {
345      return setResourceTemplates(value == null ? null : value.stream().map(McpServerConfig::listingOnlyTemplate).toList());
346   }
347
348   /**
349    * Convenience: append one or more resource-template handlers.
350    *
351    * <p>
352    * The complete resulting registry - the handlers already registered plus {@code values} - is validated
353    * before publication; on failure, the previously published registry is left unchanged. Fails fast with
354    * {@link IllegalArgumentException} naming the offending registration index and template for:
355    * <ul>
356    *    <li>a {@code null} handler;
357    *    <li>a handler whose {@link McpResourceTemplateHandler#descriptor()} returns {@code null};
358    *    <li>a descriptor with a {@code null} or blank {@code uriTemplate};
359    *    <li>a malformed {@code uriTemplate} (delegated to {@link McpUriTemplateMatcher#compile(String)}); and
360    *    <li>an exact-duplicate {@code uriTemplate} string within the resulting registry.
361    * </ul>
362    * A syntactically valid {@code uriTemplate} outside {@link McpUriTemplateMatcher}'s reverse-matchable
363    * subset is a legal registration, not an error.
364    *
365    * @param values Handlers to add.
366    * @return This object (for method chaining).
367    * @throws IllegalArgumentException If the resulting registry would be invalid.
368    */
369   public McpServerConfig addResourceTemplate(McpResourceTemplateHandler... values) {
370      var candidate = new ArrayList<>(resourceTemplates);
371      Collections.addAll(candidate, values);
372      publishResourceTemplates(candidate);
373      return this;
374   }
375
376   /**
377    * Convenience: append one or more resource-template descriptors as built-in listing-only handlers.
378    *
379    * <p>
380    * Each descriptor is wrapped in a handler whose {@link McpResourceTemplateHandler#descriptor()} returns
381    * that same descriptor instance, whose {@link McpResourceTemplateHandler#read(String, Map, BeanStore) read}
382    * always returns {@code null}, and whose {@link McpResourceTemplateHandler#completer(String) completer}
383    * always returns {@code null}. The resulting registry is validated exactly as in
384    * {@link #addResourceTemplate(McpResourceTemplateHandler...)}.
385    *
386    * @param values Descriptors to add.
387    * @return This object (for method chaining).
388    * @throws IllegalArgumentException If the resulting registry would be invalid.
389    */
390   public McpServerConfig addResourceTemplate(McpResourceTemplateSpec... values) {
391      var wrapped = new McpResourceTemplateHandler[values.length];
392      for (var i = 0; i < values.length; i++)
393         wrapped[i] = listingOnlyTemplate(values[i]);
394      return addResourceTemplate(wrapped);
395   }
396
397   private static McpResourceTemplateHandler listingOnlyTemplate(McpResourceTemplateSpec spec) {
398      return new McpResourceTemplateHandler() {
399         @Override public McpResourceTemplateSpec descriptor() { return spec; }
400         @Override public McpResourceOutcome read(String uri, Map<String,String> variables, BeanStore ctx) { return null; }
401      };
402   }
403
404   /**
405    * Validates {@code candidate} and, on success, publishes it as the current registry and its already-valid
406    * snapshot (so the very next {@link #getResourceTemplates()} call does not repeat the validation just
407    * performed here). On failure the previously published registry is left untouched.
408    */
409   private void publishResourceTemplates(List<McpResourceTemplateHandler> candidate) {
410      var compiled = validateResourceTemplates(candidate);
411      resourceTemplates = candidate;
412      validatedResourceTemplatesSnapshot = List.copyOf(candidate);
413      compiledResourceTemplateMatchers = compiled;
414   }
415
416   /**
417    * Revalidates the live {@link #resourceTemplates} list if it differs from the last validated snapshot,
418    * catching mutation performed directly through the mutable list returned by {@link #getResourceTemplates()}.
419    * A registry that is unchanged since its last successful validation is left alone: templates are not
420    * recompiled on every call.
421    */
422   private void ensureResourceTemplatesValid() {
423      var current = resourceTemplates;
424      if (validatedResourceTemplatesSnapshot.equals(current))
425         return;
426      var compiled = validateResourceTemplates(current);
427      validatedResourceTemplatesSnapshot = List.copyOf(current);
428      compiledResourceTemplateMatchers = compiled;
429   }
430
431   /**
432    * Validates one candidate resource-template registry in full, throwing {@link IllegalArgumentException}
433    * on the first invalid entry. See {@link #addResourceTemplate(McpResourceTemplateHandler...)} for the
434    * validated conditions.
435    *
436    * <p>
437    * As a side benefit of validation already compiling every entry's {@link McpUriTemplateMatcher} once,
438    * this returns an identity-keyed map from each validated handler to its compiled matcher, so callers can
439    * publish it as {@link #compiledResourceTemplateMatchers} and reuse the compiled matcher on the
440    * per-request paths ({@link #resolveResourceTemplate(String)}, {@link #hasAnyCompleter()},
441    * {@link #templateCompleter(String, String)}) instead of recompiling from the {@code uriTemplate} string
442    * on every call.
443    */
444   private static Map<McpResourceTemplateHandler, McpUriTemplateMatcher> validateResourceTemplates(List<McpResourceTemplateHandler> candidate) {
445      var seenTemplates = new HashSet<String>();
446      var compiled = new IdentityHashMap<McpResourceTemplateHandler, McpUriTemplateMatcher>();
447      for (var i = 0; i < candidate.size(); i++) {
448         var handler = candidate.get(i);
449         if (handler == null)
450            throw iaex("Invalid resource-template registration at index %s: handler must not be null", i);
451         var descriptor = handler.descriptor();
452         if (descriptor == null)
453            throw iaex("Invalid resource-template registration at index %s: descriptor() must not return null", i);
454         var uriTemplate = descriptor.getUriTemplate();
455         if (ib(uriTemplate))
456            throw iaex("Invalid resource-template registration at index %s (template '%s'): uriTemplate must not be null or blank",
457               i, uriTemplate);
458         McpUriTemplateMatcher matcher;
459         try {
460            matcher = McpUriTemplateMatcher.compile(uriTemplate);
461         } catch (IllegalArgumentException e) {
462            throw iaex(e, "Invalid resource-template registration at index %s (template '%s'): %s", i, uriTemplate, e.getMessage());
463         }
464         if (! seenTemplates.add(uriTemplate))
465            throw iaex("Invalid resource-template registration at index %s (template '%s'): duplicate uriTemplate", i, uriTemplate);
466         compiled.put(handler, matcher);
467      }
468      return Collections.unmodifiableMap(compiled);
469   }
470
471   /**
472    * Resolves the completer for one declared variable of a registered resource-template handler, enforcing
473    * in one neutral place - rather than in each dated adapter - that an undeclared variable name never
474    * receives a completer even if the handler itself misbehaves and returns one.
475    *
476    * @param handler The resource-template handler. Must not be {@code null}, and its
477    *    {@link McpResourceTemplateHandler#descriptor()}.{@code uriTemplate} must be well-formed (as enforced by
478    *    registration validation).
479    * @param variableName The variable name from a {@code completion/complete} request.
480    * @return The handler's completer for that variable, or {@code null} if the variable is not declared by
481    *    the template or the handler supplies no completer for it.
482    */
483   static McpCompleter resourceTemplateCompleter(McpResourceTemplateHandler handler, String variableName) {
484      return resourceTemplateCompleter(handler, variableName, McpUriTemplateMatcher.compile(handler.descriptor().getUriTemplate()));
485   }
486
487   /**
488    * Same contract as {@link #resourceTemplateCompleter(McpResourceTemplateHandler, String)}, but takes an
489    * already-compiled {@code matcher} (typically the one cached in {@link #compiledResourceTemplateMatchers}
490    * for a registered handler) instead of recompiling the template from its {@code uriTemplate} string.
491    */
492   static McpCompleter resourceTemplateCompleter(McpResourceTemplateHandler handler, String variableName, McpUriTemplateMatcher matcher) {
493      var declared = matcher.variableNames();
494      return declared.contains(variableName) ? handler.completer(variableName) : null;
495   }
496
497   /**
498    * Resolves the concrete {@code resources/read} request URI {@code uri} against the registered
499    * resource-template registry, selecting the single winning template handler using the deterministic
500    * specificity ranking shared identically by both dated adapters.
501    *
502    * <p>
503    * A candidate is every reverse-matchable ({@link McpUriTemplateMatcher#isReverseMatchable()}) template
504    * handler whose {@link McpUriTemplateMatcher#match(String)} succeeds against {@code uri}; a
505    * non-reverse-matchable or non-matching template handler is never a candidate. Successful candidates are
506    * ranked, highest priority first, by exactly:
507    * <ol>
508    *    <li>greater {@link McpUriTemplateMatcher#literalOctetCount() literalOctetCount};
509    *    <li>then fewer {@link McpUriTemplateMatcher#variableCount() variableCount}; and
510    *    <li>then earlier registration index (candidates are scanned in registration order and a later
511    *       candidate only displaces the current winner on a strictly better key, so the first-registered
512    *       candidate wins a complete tie).
513    * </ol>
514    * No other tie-breaker - operator type, capture width, descriptor name/title, or match-time value
515    * length - is consulted.
516    *
517    * <p>
518    * This method does not consult exact {@link McpResourceHandler} registrations. Exact lookup is
519    * unconditionally higher precedence than any template match; callers must perform exact lookup first
520    * and invoke this method only on an exact miss.
521    *
522    * @param uri The concrete request URI. Can be {@code null}, in which case this method returns
523    *    {@code null}.
524    * @return The winning match (handler plus its immutable, insertion-ordered decoded variable map), or
525    *    {@code null} if no registered reverse-matchable template matches {@code uri}.
526    */
527   public ResourceTemplateMatch resolveResourceTemplate(String uri) {
528      if (uri == null)
529         return null;
530      McpResourceTemplateHandler winner = null;
531      Map<String,String> winnerVariables = null;
532      McpUriTemplateMatcher winnerMatcher = null;
533      var templates = getResourceTemplates();
534      var matchers = compiledResourceTemplateMatchers;
535      for (var handler : templates) {
536         if (handler == null)
537            continue;
538         var descriptor = handler.descriptor();
539         if (descriptor == null)
540            continue;
541         var matcher = matchers.get(handler);
542         if (matcher == null)
543            matcher = McpUriTemplateMatcher.compile(descriptor.getUriTemplate());
544         if (! matcher.isReverseMatchable())
545            continue;
546         var variables = matcher.match(uri);
547         if (variables == null)
548            continue;
549         if (winner == null
550               || matcher.literalOctetCount() > winnerMatcher.literalOctetCount()
551               || (matcher.literalOctetCount() == winnerMatcher.literalOctetCount()
552                  && matcher.variableCount() < winnerMatcher.variableCount())) {
553            winner = handler;
554            winnerVariables = variables;
555            winnerMatcher = matcher;
556         }
557      }
558      return winner == null ? null : new ResourceTemplateMatch(winner, winnerVariables);
559   }
560
561   /**
562    * The result of {@link #resolveResourceTemplate(String)}: the winning resource-template handler paired
563    * with the variables decoded from the concrete request URI.
564    *
565    * @param handler The winning handler. Never {@code null}.
566    * @param variables The immutable, insertion-ordered map of the handler's declared variables to their
567    *    decoded values, captured from the request URI. Never {@code null}.
568    */
569   public record ResourceTemplateMatch(McpResourceTemplateHandler handler, Map<String,String> variables) {}
570
571   /**
572    * Resolves the completer registered for one declared argument of a registered prompt, by exact prompt name
573    * and exact argument name.
574    *
575    * <p>
576    * This is one half of the neutral cross-target completion lookup shared identically by both dated
577    * adapters; see {@link #templateCompleter(String, String)} for the resource-template half and
578    * {@link #completer(McpCompletionRef, String)} for the unified entry point.
579    *
580    * @param promptName The exact prompt name to look up. Can be {@code null}, in which case this method
581    *    returns {@code null}.
582    * @param argumentName The exact argument name to look up. Can be {@code null}, in which case this method
583    *    returns {@code null}.
584    * @return The argument's completer, or {@code null} if the prompt is unknown, the argument is undeclared,
585    *    or the argument declares no completer. Never invokes the completer.
586    */
587   public McpCompleter promptCompleter(String promptName, String argumentName) {
588      if (promptName == null || argumentName == null)
589         return null;
590      for (var handler : getPrompts()) {
591         if (handler == null)
592            continue;
593         var descriptor = handler.descriptor();
594         if (descriptor == null || neq(promptName, descriptor.getName()))
595            continue;
596         var arguments = descriptor.getArguments();
597         return arguments == null ? null : completerForArgument(arguments, argumentName);
598      }
599      return null;
600   }
601
602   /**
603    * Scans one prompt's declared arguments for the exact-named argument's completer, extracted from
604    * {@link #promptCompleter(String, String)} to keep that method's cognitive complexity within the
605    * project's guard-clause-density threshold.
606    */
607   private static McpCompleter completerForArgument(List<McpPromptArgument> arguments, String argumentName) {
608      for (var argument : arguments)
609         if (argument != null && eq(argumentName, argument.getName()))
610            return argument.getCompleter();
611      return null;
612   }
613
614   /**
615    * Resolves the completer registered for one declared variable of a registered resource-template, by exact
616    * registered {@code uriTemplate} string and exact variable name.
617    *
618    * <p>
619    * Matching is against the exact registered template string, not a reverse-match against a concrete URI: a
620    * {@code completion/complete} reference names the declaration being completed, never a concrete/expanded
621    * URI. This is one half of the neutral cross-target completion lookup shared identically by both dated
622    * adapters; see {@link #promptCompleter(String, String)} for the prompt half and
623    * {@link #completer(McpCompletionRef, String)} for the unified entry point. Delegates the "variable must be
624    * declared by the template" check to {@link #resourceTemplateCompleter(McpResourceTemplateHandler, String)}
625    * rather than re-parsing.
626    *
627    * @param uriTemplate The exact registered {@code uriTemplate} to look up. Can be {@code null}, in which
628    *    case this method returns {@code null}.
629    * @param variableName The exact variable name to look up. Can be {@code null}, in which case this method
630    *    returns {@code null}.
631    * @return The variable's completer, or {@code null} if the template is unregistered, the variable is
632    *    undeclared, or the handler declares no completer for it. Never invokes the completer.
633    */
634   public McpCompleter templateCompleter(String uriTemplate, String variableName) {
635      if (uriTemplate == null || variableName == null)
636         return null;
637      var templates = getResourceTemplates();
638      var matchers = compiledResourceTemplateMatchers;
639      for (var handler : templates) {
640         if (handler == null)
641            continue;
642         var descriptor = handler.descriptor();
643         if (descriptor == null || ! uriTemplate.equals(descriptor.getUriTemplate()))
644            continue;
645         var matcher = matchers.get(handler);
646         return matcher != null ? resourceTemplateCompleter(handler, variableName, matcher) : resourceTemplateCompleter(handler, variableName);
647      }
648      return null;
649   }
650
651   /**
652    * Unified neutral completion lookup: resolves the completer for a {@code completion/complete} target,
653    * dispatching by {@link McpCompletionRef.Kind} to {@link #promptCompleter(String, String)} or
654    * {@link #templateCompleter(String, String)}.
655    *
656    * <p>
657    * Both dated adapters call this same method for both target kinds, so template/prompt resolution policy is
658    * defined exactly once, in this neutral module.
659    *
660    * @param ref The completion reference identifying the exact prompt name or registered template string.
661    *    Can be {@code null}, in which case this method returns {@code null}.
662    * @param argumentName The exact argument/variable name to look up. Can be {@code null}, in which case this
663    *    method returns {@code null}.
664    * @return The resolved completer, or {@code null} if {@code ref} is {@code null}/has no {@link
665    *    McpCompletionRef#getKind() kind}, or if the target-specific lookup finds none. Never invokes the
666    *    completer.
667    */
668   public McpCompleter completer(McpCompletionRef ref, String argumentName) {
669      if (ref == null || ref.getKind() == null)
670         return null;
671      if (ref.getKind() == McpCompletionRef.Kind.PROMPT)
672         return promptCompleter(ref.getTarget(), argumentName);
673      return templateCompleter(ref.getTarget(), argumentName);
674   }
675
676   /**
677    * Reports whether {@code completions} capability should be advertised: whether at least one registered
678    * prompt argument or resource-template variable currently declares a non-{@code null} completer.
679    *
680    * <p>
681    * This never invokes a completer; it only detects registration, so both dated adapters can use it for
682    * auto-derived capability advertisement without side effects.
683    *
684    * @return {@code true} if at least one prompt-argument completer or resource-template variable completer
685    *    is registered.
686    */
687   public boolean hasAnyCompleter() {
688      for (var handler : getPrompts()) {
689         if (handler == null)
690            continue;
691         var descriptor = handler.descriptor();
692         var arguments = descriptor == null ? null : descriptor.getArguments();
693         if (arguments == null)
694            continue;
695         for (var argument : arguments)
696            if (argument != null && argument.getCompleter() != null)
697               return true;
698      }
699      var templates = getResourceTemplates();
700      var matchers = compiledResourceTemplateMatchers;
701      for (var handler : templates) {
702         if (handler == null)
703            continue;
704         var descriptor = handler.descriptor();
705         var uriTemplate = descriptor == null ? null : descriptor.getUriTemplate();
706         if (uriTemplate == null)
707            continue;
708         var matcher = matchers.get(handler);
709         var variableNames = matcher != null ? matcher.variableNames() : McpUriTemplateMatcher.compile(uriTemplate).variableNames();
710         for (var variableName : variableNames)
711            if (handler.completer(variableName) != null)
712               return true;
713      }
714      return false;
715   }
716
717   /**
718    * Pagination strategy for {@code list} dispatchers (tools / prompts / resources / resource templates).
719    *
720    * @return The cursor. Never {@code null} (defaults to {@link McpCursor#SINGLE_PAGE}).
721    */
722   public McpCursor getCursor() {
723      return cursor;
724   }
725
726   /**
727    * Sets the pagination strategy.
728    *
729    * @param cursor The new value (or {@code null} to reset to {@link McpCursor#SINGLE_PAGE}).
730    * @return This object (for method chaining).
731    */
732   public McpServerConfig setCursor(McpCursor cursor) {
733      this.cursor = cursor == null ? McpCursor.SINGLE_PAGE : cursor;
734      return this;
735   }
736}