Skip to content

Commit 5f50bc2

Browse files
authored
Merge pull request #495 from quarkiverse/issue-493_1-7-x
core: fix resource template completion
2 parents 83f5784 + 55c2526 commit 5f50bc2

File tree

12 files changed

+80
-25
lines changed

12 files changed

+80
-25
lines changed

core/deployment/src/main/java/io/quarkiverse/mcp/server/deployment/McpServerProcessor.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,25 @@ void collectFeatureMethods(BeanDiscoveryFinishedBuildItem beanDiscovery, Invoker
434434
}
435435
}
436436

437+
// Check duplicate uris for resource templates
438+
List<FeatureMethodBuildItem> resourceTemplates = found.get(RESOURCE_TEMPLATE);
439+
if (resourceTemplates != null) {
440+
Map<String, List<FeatureMethodBuildItem>> byUri = resourceTemplates.stream()
441+
.collect(Collectors.toMap(FeatureMethodBuildItem::getUri, List::of, (v1, v2) -> {
442+
List<FeatureMethodBuildItem> list = new ArrayList<>();
443+
list.addAll(v1);
444+
list.addAll(v2);
445+
return list;
446+
}));
447+
for (List<FeatureMethodBuildItem> list : byUri.values()) {
448+
if (list.size() > 1) {
449+
String message = "Duplicate resource template uri found:\n\t%s"
450+
.formatted(list.stream().map(Object::toString).collect(Collectors.joining("\n\t")));
451+
errors.produce(new ValidationErrorBuildItem(new IllegalStateException(message)));
452+
}
453+
}
454+
}
455+
437456
// Check existing prompts for completions
438457
List<FeatureMethodBuildItem> prompts = found.get(PROMPT);
439458
List<FeatureMethodBuildItem> promptCompletions = found.get(PROMPT_COMPLETE);
@@ -448,7 +467,6 @@ void collectFeatureMethods(BeanDiscoveryFinishedBuildItem beanDiscovery, Invoker
448467
}
449468

450469
// Check existing resource templates for completions
451-
List<FeatureMethodBuildItem> resourceTemplates = found.get(RESOURCE_TEMPLATE);
452470
List<FeatureMethodBuildItem> resourceTemplateCompletions = found.get(RESOURCE_TEMPLATE_COMPLETE);
453471
if (resourceTemplateCompletions != null) {
454472
for (FeatureMethodBuildItem completion : resourceTemplateCompletions) {

core/runtime/src/main/java/io/quarkiverse/mcp/server/runtime/CompletionManagerBase.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ && matches(completion, mcpRequest)) {
6767

6868
protected abstract void validateReference(String refName, String argumentName);
6969

70+
protected abstract String refName(String refName);
71+
7072
IllegalArgumentException completionAlreadyExists(String refName, String argName) {
7173
return new IllegalArgumentException("A completion for [" + refName + "] with agument [" + argName + "] already exits");
7274
}
@@ -131,7 +133,7 @@ public CompletionDefinition setArgumentName(String argumentName) {
131133
public CompletionInfo register() {
132134
validate();
133135
validateReference(name, argumentName);
134-
CompletionDefinitionInfo ret = new CompletionDefinitionInfo(name, description, serverName, fun, asyncFun,
136+
CompletionDefinitionInfo ret = new CompletionDefinitionInfo(refName(name), description, serverName, fun, asyncFun,
135137
runOnVirtualThread, argumentName);
136138
String key = ret.name() + "_" + ret.argumentName();
137139
CompletionInfo existing = completions.putIfAbsent(key, ret);

core/runtime/src/main/java/io/quarkiverse/mcp/server/runtime/CompletionMessageHandler.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,13 @@ protected CompletionMessageHandler(ResponseHandlers responseHandlers) {
2222
protected abstract Future<CompletionResponse> execute(String key, ArgumentProviders argProviders,
2323
McpRequest mcpRequest) throws McpException;
2424

25+
protected String referenceName(JsonObject ref) {
26+
return ref.getString("name");
27+
}
28+
2529
Future<Void> complete(JsonObject message, Object id, JsonObject ref, JsonObject argument, Sender sender,
2630
McpRequest mcpRequest) {
27-
String referenceName = ref.getString("name");
31+
String referenceName = referenceName(ref);
2832
String argumentName = argument.getString("name");
2933

3034
LOG.debugf("Complete %s for argument %s [id: %s]", referenceName, argumentName, id);

core/runtime/src/main/java/io/quarkiverse/mcp/server/runtime/McpMessageHandler.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -497,9 +497,9 @@ private Future<Void> complete(JsonObject message, McpRequest mcpRequest) {
497497
if (argument == null) {
498498
return mcpRequest.sender().sendError(id, JsonRpcErrorCodes.INVALID_REQUEST, "Argument not found");
499499
} else {
500-
if ("ref/prompt".equals(referenceType)) {
500+
if (Messages.isPromptRef(referenceType)) {
501501
return promptCompleteHandler.complete(message, id, ref, argument, mcpRequest.sender(), mcpRequest);
502-
} else if ("ref/resource".equals(referenceType)) {
502+
} else if (Messages.isResourceRef(referenceType)) {
503503
return resourceTemplateCompleteHandler.complete(message, id, ref, argument, mcpRequest.sender(),
504504
mcpRequest);
505505
} else {

core/runtime/src/main/java/io/quarkiverse/mcp/server/runtime/Messages.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,11 @@ public static JsonObject newLog(LogLevel level, String loggerName, Object data)
113113
public static final String PROMPT_REF = "ref/prompt";
114114
public static final String RESOURCE_REF = "ref/resource";
115115

116-
public boolean isPromptRef(String referenceType) {
116+
public static boolean isPromptRef(String referenceType) {
117117
return PROMPT_REF.equals(referenceType);
118118
}
119119

120-
public boolean isResourceRef(String referenceType) {
120+
public static boolean isResourceRef(String referenceType) {
121121
return RESOURCE_REF.equals(referenceType);
122122
}
123123

core/runtime/src/main/java/io/quarkiverse/mcp/server/runtime/PromptCompletionManagerImpl.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ protected Feature feature() {
4040
return Feature.PROMPT_COMPLETE;
4141
}
4242

43+
@Override
44+
protected String refName(String refName) {
45+
return refName;
46+
}
47+
4348
@Override
4449
protected void validateReference(String refName, String argumentName) {
4550
PromptManager.PromptInfo prompt = promptManager.getPrompt(refName);

core/runtime/src/main/java/io/quarkiverse/mcp/server/runtime/ResourceTemplateCompleteMessageHandler.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import io.quarkiverse.mcp.server.McpException;
77
import io.quarkiverse.mcp.server.runtime.FeatureManagerBase.FeatureExecutionContext;
88
import io.vertx.core.Future;
9+
import io.vertx.core.json.JsonObject;
910

1011
class ResourceTemplateCompleteMessageHandler extends CompletionMessageHandler {
1112

@@ -16,6 +17,11 @@ class ResourceTemplateCompleteMessageHandler extends CompletionMessageHandler {
1617
this.manager = Objects.requireNonNull(manager);
1718
}
1819

20+
@Override
21+
protected String referenceName(JsonObject ref) {
22+
return ref.getString("uri");
23+
}
24+
1925
@Override
2026
protected Future<CompletionResponse> execute(String key, ArgumentProviders argProviders, McpRequest mcpRequest)
2127
throws McpException {

core/runtime/src/main/java/io/quarkiverse/mcp/server/runtime/ResourceTemplateCompletionManagerImpl.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package io.quarkiverse.mcp.server.runtime;
22

3+
import java.util.Map;
4+
import java.util.stream.Collectors;
5+
36
import jakarta.enterprise.inject.Instance;
47
import jakarta.inject.Singleton;
58

@@ -18,13 +21,21 @@ public class ResourceTemplateCompletionManagerImpl extends CompletionManagerBase
1821

1922
private final ResourceTemplateManagerImpl resourceTemplateManager;
2023

24+
private final Map<String, String> nameToUriTemplate;
25+
2126
ResourceTemplateCompletionManagerImpl(McpMetadata metadata, Vertx vertx, ObjectMapper mapper,
2227
ConnectionManager connectionManager, ResourceTemplateManagerImpl resourceTemplateManager,
2328
Instance<CurrentIdentityAssociation> currentIdentityAssociation, ResponseHandlers responseHandlers) {
2429
super(vertx, mapper, connectionManager, currentIdentityAssociation, responseHandlers);
30+
this.nameToUriTemplate = metadata.resourceTemplates()
31+
.stream().map(FeatureMetadata::info).collect(Collectors.toMap(FeatureMethodInfo::name, FeatureMethodInfo::uri));
2532
for (FeatureMetadata<CompletionResponse> c : metadata.resourceTemplateCompletions()) {
26-
String key = c.info().name() + "_"
27-
+ c.info().arguments().stream().filter(FeatureArgument::isParam).findFirst().orElseThrow()
33+
// Find the corresponding resource template
34+
String key = nameToUriTemplate.get(c.info().name()) + "_"
35+
+ c.info().arguments().stream()
36+
.filter(FeatureArgument::isParam)
37+
.findFirst()
38+
.orElseThrow()
2839
.name();
2940
this.completions.put(key, new CompletionMethod(c));
3041
}
@@ -52,4 +63,9 @@ protected void validateReference(String refName, String argumentName) {
5263
}
5364
}
5465

66+
@Override
67+
protected String refName(String refName) {
68+
return nameToUriTemplate.get(refName);
69+
}
70+
5571
}

test/src/main/java/io/quarkiverse/mcp/server/test/McpAssured.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -656,25 +656,25 @@ default ASSERT resourcesTemplatesList(Consumer<ResourcesTemplatesPage> assertFun
656656
/**
657657
* Build a {@value McpAssured#COMPLETION_COMPLETE} message.
658658
*
659-
* @param promptName
659+
* @param uriTemplate
660660
* @return a new builder
661661
*/
662-
ResourceTemplateCompleteMessage<ASSERT> resourceTemplateComplete(String resourceTemplateName);
662+
ResourceTemplateCompleteMessage<ASSERT> resourceTemplateComplete(String uriTemplate);
663663

664664
/**
665665
* Send a {@value McpAssured#COMPLETION_COMPLETE} message to the server.
666666
* <p>
667667
* The assert function is not used until the {@link #thenAssertResults()} method is called.
668668
*
669-
* @param promptName
669+
* @param uriTemplate
670670
* @param argumentName
671671
* @param argumentValue
672672
* @param assertFunction
673673
* @return self
674674
*/
675-
default ASSERT resourceTemplateComplete(String resourceTemplateName, String argumentName, String argumentValue,
675+
default ASSERT resourceTemplateComplete(String uriTemplate, String argumentName, String argumentValue,
676676
Consumer<CompletionResponse> assertFunction) {
677-
return resourceTemplateComplete(resourceTemplateName)
677+
return resourceTemplateComplete(uriTemplate)
678678
.withArgument(argumentName, argumentValue)
679679
.withAssert(assertFunction)
680680
.send();

test/src/main/java/io/quarkiverse/mcp/server/test/McpTestClientBase.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,13 @@ protected JsonObject newPromptsGetMessage(String promptName, Map<String, String>
193193
protected JsonObject newCompleteMessage(String refType, String refName, String argumentName, String argumentValue,
194194
Map<String, String> contextArgs) {
195195
return newRequest(McpAssured.COMPLETION_COMPLETE, p -> {
196-
p.put("ref", new JsonObject()
197-
.put("type", refType)
198-
.put("name", refName));
196+
JsonObject ref = new JsonObject().put("type", refType);
197+
if (Messages.isPromptRef(refType)) {
198+
ref.put("name", refName);
199+
} else if (Messages.isResourceRef(refType)) {
200+
ref.put("uri", refName);
201+
}
202+
p.put("ref", ref);
199203
p.put("argument", new JsonObject()
200204
.put("name", argumentName)
201205
.put("value", argumentValue));
@@ -356,8 +360,8 @@ public ResourcesTemplatesListMessage<ASSERT> resourcesTemplatesList() {
356360
}
357361

358362
@Override
359-
public ResourceTemplateCompleteMessage<ASSERT> resourceTemplateComplete(String resourceTemplateName) {
360-
return new ResourceTemplateCompleteMessageImpl(resourceTemplateName);
363+
public ResourceTemplateCompleteMessage<ASSERT> resourceTemplateComplete(String uriTemplate) {
364+
return new ResourceTemplateCompleteMessageImpl(uriTemplate);
361365
}
362366

363367
@Override
@@ -639,8 +643,8 @@ abstract class CompleteMessageImpl {
639643
class ResourceTemplateCompleteMessageImpl extends CompleteMessageImpl
640644
implements ResourceTemplateCompleteMessage<ASSERT> {
641645

642-
ResourceTemplateCompleteMessageImpl(String resourceTemplateName) {
643-
super(resourceTemplateName);
646+
ResourceTemplateCompleteMessageImpl(String uriTemplate) {
647+
super(uriTemplate);
644648
}
645649

646650
@Override

0 commit comments

Comments
 (0)