|
12 | 12 | import io.modelcontextprotocol.spec.McpError; |
13 | 13 | import io.modelcontextprotocol.spec.McpSchema; |
14 | 14 | import io.modelcontextprotocol.spec.McpSchema.CallToolResult; |
| 15 | +import io.modelcontextprotocol.spec.McpSchema.CompleteResult.CompleteCompletion; |
| 16 | +import io.modelcontextprotocol.spec.McpSchema.ErrorCodes; |
15 | 17 | import io.modelcontextprotocol.spec.McpSchema.JSONRPCResponse; |
16 | 18 | import io.modelcontextprotocol.spec.McpSchema.PromptReference; |
17 | 19 | import io.modelcontextprotocol.spec.McpSchema.ResourceReference; |
@@ -667,55 +669,105 @@ private McpStatelessRequestHandler<McpSchema.GetPromptResult> promptsGetRequestH |
667 | 669 | }; |
668 | 670 | } |
669 | 671 |
|
| 672 | + private static final Mono<McpSchema.CompleteResult> EMPTY_COMPLETION_RESULT = Mono |
| 673 | + .just(new McpSchema.CompleteResult(new CompleteCompletion(List.of(), 0, false))); |
| 674 | + |
670 | 675 | private McpStatelessRequestHandler<McpSchema.CompleteResult> completionCompleteRequestHandler() { |
671 | 676 | return (ctx, params) -> { |
672 | 677 | McpSchema.CompleteRequest request = parseCompletionParams(params); |
673 | 678 |
|
674 | 679 | if (request.ref() == null) { |
675 | | - return Mono.error(new McpError("ref must not be null")); |
| 680 | + return Mono.error( |
| 681 | + McpError.builder(ErrorCodes.INVALID_PARAMS).message("Completion ref must not be null").build()); |
676 | 682 | } |
677 | 683 |
|
678 | 684 | if (request.ref().type() == null) { |
679 | | - return Mono.error(new McpError("type must not be null")); |
| 685 | + return Mono.error(McpError.builder(ErrorCodes.INVALID_PARAMS) |
| 686 | + .message("Completion ref type must not be null") |
| 687 | + .build()); |
680 | 688 | } |
681 | 689 |
|
682 | 690 | String type = request.ref().type(); |
683 | 691 |
|
684 | 692 | String argumentName = request.argument().name(); |
685 | 693 |
|
686 | | - // check if the referenced resource exists |
| 694 | + // Check if valid a Prompt exists for this completion request |
687 | 695 | if (type.equals(PromptReference.TYPE) |
688 | 696 | && request.ref() instanceof McpSchema.PromptReference promptReference) { |
| 697 | + |
689 | 698 | McpStatelessServerFeatures.AsyncPromptSpecification promptSpec = this.prompts |
690 | 699 | .get(promptReference.name()); |
691 | 700 | if (promptSpec == null) { |
692 | | - return Mono.error(new McpError("Prompt not found: " + promptReference.name())); |
| 701 | + return Mono.error(McpError.builder(ErrorCodes.INVALID_PARAMS) |
| 702 | + .message("Prompt not found: " + promptReference.name()) |
| 703 | + .build()); |
693 | 704 | } |
694 | | - if (promptSpec.prompt().arguments().stream().noneMatch(arg -> arg.name().equals(argumentName))) { |
| 705 | + if (!promptSpec.prompt() |
| 706 | + .arguments() |
| 707 | + .stream() |
| 708 | + .filter(arg -> arg.name().equals(argumentName)) |
| 709 | + .findFirst() |
| 710 | + .isPresent()) { |
| 711 | + |
| 712 | + logger.warn("Argument not found: {} in prompt: {}", argumentName, promptReference.name()); |
695 | 713 |
|
696 | | - return Mono.error(new McpError("Argument not found: " + argumentName)); |
| 714 | + return EMPTY_COMPLETION_RESULT; |
697 | 715 | } |
698 | 716 | } |
699 | 717 |
|
| 718 | + // Check if valid Resource or ResourceTemplate exists for this completion |
| 719 | + // request |
700 | 720 | if (type.equals(ResourceReference.TYPE) |
701 | 721 | && request.ref() instanceof McpSchema.ResourceReference resourceReference) { |
702 | | - McpStatelessServerFeatures.AsyncResourceSpecification resourceSpec = resources |
703 | | - .get(resourceReference.uri()); |
704 | | - if (resourceSpec == null) { |
705 | | - return Mono.error(RESOURCE_NOT_FOUND.apply(resourceReference.uri())); |
706 | | - } |
707 | | - if (!uriTemplateManagerFactory.create(resourceSpec.resource().uri()) |
708 | | - .getVariableNames() |
709 | | - .contains(argumentName)) { |
710 | | - return Mono.error(new McpError("Argument not found: " + argumentName)); |
| 722 | + |
| 723 | + var uriTemplateManager = uriTemplateManagerFactory.create(resourceReference.uri()); |
| 724 | + |
| 725 | + if (!uriTemplateManager.isUriTemplate(resourceReference.uri())) { |
| 726 | + // Attempting to autocomplete a fixed resource URI is not an error in |
| 727 | + // the spec (but probably should be). |
| 728 | + return EMPTY_COMPLETION_RESULT; |
711 | 729 | } |
712 | 730 |
|
| 731 | + McpStatelessServerFeatures.AsyncResourceSpecification resourceSpec = this |
| 732 | + .findResourceSpecification(resourceReference.uri()) |
| 733 | + .orElse(null); |
| 734 | + |
| 735 | + if (resourceSpec != null) { |
| 736 | + if (!uriTemplateManagerFactory.create(resourceSpec.resource().uri()) |
| 737 | + .getVariableNames() |
| 738 | + .contains(argumentName)) { |
| 739 | + |
| 740 | + return Mono.error(McpError.builder(ErrorCodes.INVALID_PARAMS) |
| 741 | + .message("Argument not found: " + argumentName + " in resource: " + resourceReference.uri()) |
| 742 | + .build()); |
| 743 | + } |
| 744 | + } |
| 745 | + else { |
| 746 | + var templateSpec = this.findResourceTemplateSpecification(resourceReference.uri()).orElse(null); |
| 747 | + if (templateSpec != null) { |
| 748 | + |
| 749 | + if (!uriTemplateManagerFactory.create(templateSpec.resourceTemplate().uriTemplate()) |
| 750 | + .getVariableNames() |
| 751 | + .contains(argumentName)) { |
| 752 | + |
| 753 | + return Mono.error(McpError.builder(ErrorCodes.INVALID_PARAMS) |
| 754 | + .message("Argument not found: " + argumentName + " in resource template: " |
| 755 | + + resourceReference.uri()) |
| 756 | + .build()); |
| 757 | + } |
| 758 | + } |
| 759 | + else { |
| 760 | + return Mono.error(RESOURCE_NOT_FOUND.apply(resourceReference.uri())); |
| 761 | + } |
| 762 | + } |
713 | 763 | } |
714 | 764 |
|
715 | 765 | McpStatelessServerFeatures.AsyncCompletionSpecification specification = this.completions.get(request.ref()); |
716 | 766 |
|
717 | 767 | if (specification == null) { |
718 | | - return Mono.error(new McpError("AsyncCompletionSpecification not found: " + request.ref())); |
| 768 | + return Mono.error(McpError.builder(ErrorCodes.INVALID_PARAMS) |
| 769 | + .message("AsyncCompletionSpecification not found: " + request.ref()) |
| 770 | + .build()); |
719 | 771 | } |
720 | 772 |
|
721 | 773 | return specification.completionHandler().apply(ctx, request); |
|
0 commit comments