Skip to content

Commit a64a2d1

Browse files
committed
feat(completion): Add prompt and resource completion support with annotations and server components
1 parent f25dd38 commit a64a2d1

File tree

10 files changed

+600
-26
lines changed

10 files changed

+600
-26
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package com.github.codeboyzhou.mcp.declarative.annotation;
2+
3+
import com.github.codeboyzhou.mcp.declarative.server.component.McpCompleteCompletion;
4+
import com.github.codeboyzhou.mcp.declarative.util.StringHelper;
5+
import java.lang.annotation.ElementType;
6+
import java.lang.annotation.Retention;
7+
import java.lang.annotation.RetentionPolicy;
8+
import java.lang.annotation.Target;
9+
10+
/**
11+
* Annotation for marking methods that provide completion functionality for MCP prompts.
12+
*
13+
* <p>This annotation is used to identify methods that can handle completion requests for specific
14+
* prompts in the Model Context Protocol (MCP) server. When applied to a method, it indicates that
15+
* the method can provide auto-completion suggestions for the specified prompt.
16+
*
17+
* <p>Methods annotated with {@code @McpPromptCompletion} must:
18+
*
19+
* <ul>
20+
* <li>Return {@link McpCompleteCompletion}
21+
* <li>Accept exactly one parameter of type {@code McpSchema.CompleteRequest.CompleteArgument}
22+
* <li>Be properly configured with a prompt name
23+
* </ul>
24+
*
25+
* <p>The annotation is retained at runtime and can only be applied to methods.
26+
*
27+
* @author codeboyzhou
28+
*/
29+
@Target(ElementType.METHOD)
30+
@Retention(RetentionPolicy.RUNTIME)
31+
public @interface McpPromptCompletion {
32+
/**
33+
* Specifies the name of the prompt for which completion is provided.
34+
*
35+
* <p>This value must match the name of an existing prompt that has been registered with the MCP
36+
* server. The completion method will be invoked when completion requests are made for this
37+
* specific prompt.
38+
*
39+
* <p>The name should be a non-empty string that follows the MCP naming conventions for prompts.
40+
*
41+
* @return the name of the prompt for which completion is provided
42+
*/
43+
String name();
44+
45+
/**
46+
* Specifies an optional title or description for the prompt completion.
47+
*
48+
* <p>This field provides a human-readable title or description for the completion functionality.
49+
* It can be used in documentation, UI displays, or logging to provide more context about what the
50+
* completion does.
51+
*
52+
* <p>If not specified, defaults to an empty string using {@link StringHelper#EMPTY}. This allows
53+
* the title to be optional while maintaining consistency across the codebase.
54+
*
55+
* @return the title or description for the prompt completion, defaults to empty string if not
56+
* specified
57+
*/
58+
String title() default StringHelper.EMPTY;
59+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.github.codeboyzhou.mcp.declarative.annotation;
2+
3+
import com.github.codeboyzhou.mcp.declarative.server.component.McpCompleteCompletion;
4+
import java.lang.annotation.ElementType;
5+
import java.lang.annotation.Retention;
6+
import java.lang.annotation.RetentionPolicy;
7+
import java.lang.annotation.Target;
8+
9+
/**
10+
* Annotation for marking methods that provide completion functionality for MCP resources.
11+
*
12+
* <p>This annotation is used to identify methods that can handle completion requests for specific
13+
* resources in the Model Context Protocol (MCP) server. When applied to a method, it indicates that
14+
* the method can provide auto-completion suggestions for the specified resource URI.
15+
*
16+
* <p>Methods annotated with {@code @McpResourceCompletion} must:
17+
*
18+
* <ul>
19+
* <li>Return {@link McpCompleteCompletion}
20+
* <li>Accept exactly one parameter of type {@code McpSchema.CompleteRequest.CompleteArgument}
21+
* <li>Be properly configured with a resource URI
22+
* </ul>
23+
*
24+
* <p>The annotation is retained at runtime and can only be applied to methods. Resource completion
25+
* is typically used to provide suggestions for resource identifiers, file paths, or other
26+
* resource-specific parameters.
27+
*
28+
* @author codeboyzhou
29+
*/
30+
@Target(ElementType.METHOD)
31+
@Retention(RetentionPolicy.RUNTIME)
32+
public @interface McpResourceCompletion {
33+
/**
34+
* Specifies the URI of the resource for which completion is provided.
35+
*
36+
* <p>This value must match the URI of an existing resource that has been registered with the MCP
37+
* server. The completion method will be invoked when completion requests are made for this
38+
* specific resource.
39+
*
40+
* <p>The URI string must be non-null and non-empty. It should uniquely identify the resource
41+
* within the MCP server context.
42+
*
43+
* @return the URI of the resource for which completion is provided
44+
*/
45+
String uri();
46+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.github.codeboyzhou.mcp.declarative.exception;
2+
3+
/**
4+
* Exception thrown when an error occurs during MCP server component registration.
5+
*
6+
* <p>This exception is used to indicate failures that occur while attempting to register MCP server
7+
* components such as resources, prompts, tools, or completion handlers. Common scenarios where this
8+
* exception may be thrown include:
9+
*
10+
* <ul>
11+
* <li>Invalid method signatures for component handlers
12+
* <li>Missing or incorrect annotations on component methods
13+
* <li>Dependency injection failures during component creation
14+
* <li>Configuration errors in component metadata
15+
* <li>Reflection-related errors during component discovery
16+
* </ul>
17+
*
18+
* <p>This exception extends {@link McpServerException} and is part of the MCP declarative
19+
* framework's exception hierarchy, providing specific error information for component registration
20+
* failures while maintaining compatibility with the general MCP server exception handling.
21+
*
22+
* @author codeboyzhou
23+
*/
24+
public class McpServerComponentRegistrationException extends McpServerException {
25+
/**
26+
* Constructs a new {@code McpServerComponentRegistrationException} with the specified detail
27+
* message.
28+
*
29+
* <p>This constructor is typically used when the component registration failure can be described
30+
* by a single error message without an underlying cause. The message should provide clear
31+
* information about what went wrong during the registration process.
32+
*
33+
* @param message the detail message explaining the reason for the exception, may be null but
34+
* should provide meaningful error information
35+
*/
36+
public McpServerComponentRegistrationException(String message) {
37+
super(message);
38+
}
39+
40+
/**
41+
* Constructs a new {@code McpServerComponentRegistrationException} with the specified detail
42+
* message and cause.
43+
*
44+
* <p>This constructor is typically used when the component registration failure is caused by
45+
* another exception. The cause exception is preserved and can be accessed later for detailed
46+
* error analysis and debugging.
47+
*
48+
* <p>Common causes include reflection exceptions, dependency injection failures, or validation
49+
* errors during component creation. The message should provide context about the registration
50+
* operation that failed.
51+
*
52+
* @param message the detail message explaining the reason for the exception, may be null but
53+
* should provide meaningful error information
54+
* @param cause the underlying cause of the exception, may be null if the cause is unknown or not
55+
* applicable
56+
*/
57+
public McpServerComponentRegistrationException(String message, Throwable cause) {
58+
super(message, cause);
59+
}
60+
}

src/main/java/com/github/codeboyzhou/mcp/declarative/reflect/MethodCache.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.github.codeboyzhou.mcp.declarative.reflect;
22

33
import com.github.codeboyzhou.mcp.declarative.annotation.McpPrompt;
4+
import com.github.codeboyzhou.mcp.declarative.annotation.McpPromptCompletion;
45
import com.github.codeboyzhou.mcp.declarative.annotation.McpResource;
6+
import com.github.codeboyzhou.mcp.declarative.annotation.McpResourceCompletion;
57
import com.github.codeboyzhou.mcp.declarative.annotation.McpTool;
68
import com.github.codeboyzhou.mcp.declarative.common.Immutable;
79
import java.lang.reflect.Method;
@@ -43,6 +45,12 @@ public final class MethodCache {
4345
/** The annotation {@link McpTool} on the cached method. */
4446
private final McpTool mcpToolAnnotation;
4547

48+
/** The annotation {@link McpPromptCompletion} on the cached method. */
49+
private final McpPromptCompletion mcpPromptCompletionAnnotation;
50+
51+
/** The annotation {@link McpResourceCompletion} on the cached method. */
52+
private final McpResourceCompletion mcpResourceCompletionAnnotation;
53+
4654
/**
4755
* Creates a new instance of {@code MethodCache} with the specified method.
4856
*
@@ -58,6 +66,8 @@ public MethodCache(Method method) {
5866
this.mcpResourceAnnotation = method.getAnnotation(McpResource.class);
5967
this.mcpPromptAnnotation = method.getAnnotation(McpPrompt.class);
6068
this.mcpToolAnnotation = method.getAnnotation(McpTool.class);
69+
this.mcpPromptCompletionAnnotation = method.getAnnotation(McpPromptCompletion.class);
70+
this.mcpResourceCompletionAnnotation = method.getAnnotation(McpResourceCompletion.class);
6171
}
6272

6373
/**
@@ -151,6 +161,24 @@ public McpTool getMcpToolAnnotation() {
151161
return mcpToolAnnotation;
152162
}
153163

164+
/**
165+
* Returns the annotation {@link McpPromptCompletion} on the cached method.
166+
*
167+
* @return the annotation {@link McpPromptCompletion} on the cached method
168+
*/
169+
public McpPromptCompletion getMcpPromptCompletionAnnotation() {
170+
return mcpPromptCompletionAnnotation;
171+
}
172+
173+
/**
174+
* Returns the annotation {@link McpResourceCompletion} on the cached method.
175+
*
176+
* @return the annotation {@link McpResourceCompletion} on the cached method
177+
*/
178+
public McpResourceCompletion getMcpResourceCompletionAnnotation() {
179+
return mcpResourceCompletionAnnotation;
180+
}
181+
154182
@Override
155183
public boolean equals(Object obj) {
156184
if (this == obj) {

src/main/java/com/github/codeboyzhou/mcp/declarative/server/AbstractMcpServer.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,16 @@ public abstract class AbstractMcpServer<S extends McpServerInfo> implements McpS
1919
* @param serverInfo the server info
2020
*/
2121
public void start(S serverInfo) {
22+
McpServerComponentRegister register = new McpServerComponentRegister();
2223
McpSyncServer server =
2324
sync(serverInfo)
2425
.serverInfo(serverInfo.name(), serverInfo.version())
2526
.capabilities(serverCapabilities(serverInfo))
2627
.instructions(serverInfo.instructions())
2728
.requestTimeout(serverInfo.requestTimeout())
29+
.completions(register.registerCompletions())
2830
.build();
29-
McpServerComponentRegister.of(server).registerComponents();
31+
register.registerComponents(server);
3032
}
3133

3234
/**
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package com.github.codeboyzhou.mcp.declarative.server.component;
2+
3+
import java.util.Collection;
4+
import java.util.List;
5+
6+
/**
7+
* Represents a completion result for MCP (Model Context Protocol) server operations.
8+
*
9+
* <p>This record encapsulates the response data for completion requests, including the list of
10+
* completion values, total count, and whether more results are available. The class is designed to
11+
* be immutable and provides defensive copying to protect internal state from external modification.
12+
*
13+
* @param values the list of completion values, may be null
14+
* @param total the total number of available completions, may be null
15+
* @param hasMore true if more completions are available, false otherwise
16+
* @author codeboyzhou
17+
*/
18+
public record McpCompleteCompletion(List<String> values, Integer total, boolean hasMore) {
19+
/**
20+
* Compact constructor that creates a defensive copy of the values list.
21+
*
22+
* <p>This constructor ensures that the internal list cannot be modified from outside the record
23+
* by creating an immutable copy using {@link List#copyOf(Collection)}. If the input list is null,
24+
* it remains null to preserve the original intent.
25+
*
26+
* @param values the input list of completion values to be defensively copied
27+
*/
28+
public McpCompleteCompletion {
29+
values = values == null ? null : List.copyOf(values);
30+
}
31+
32+
/**
33+
* Creates a new {@link Builder} instance for constructing {@link McpCompleteCompletion} objects.
34+
*
35+
* <p>This factory method provides a convenient way to create a builder for step-by-step
36+
* construction of completion results using the builder pattern.
37+
*
38+
* @return a new {@link Builder} instance
39+
*/
40+
public static McpCompleteCompletion.Builder builder() {
41+
return new McpCompleteCompletion.Builder();
42+
}
43+
44+
/**
45+
* Creates an empty {@link McpCompleteCompletion} instance.
46+
*
47+
* <p>This factory method returns a completion result with no values, zero total count, and no
48+
* more results available. Useful as a default or placeholder completion.
49+
*
50+
* @return an empty {@link McpCompleteCompletion} instance
51+
*/
52+
public static McpCompleteCompletion empty() {
53+
return builder().values(List.of()).total(0).hasMore(false).build();
54+
}
55+
56+
/**
57+
* Builder class for constructing {@link McpCompleteCompletion} instances.
58+
*
59+
* <p>This builder provides a fluent API for creating completion results with defensive copying to
60+
* protect against external modification of mutable inputs. The builder maintains the same
61+
* immutability guarantees as the record itself.
62+
*
63+
* @author codeboyzhou
64+
*/
65+
public static class Builder {
66+
/** The list of completion values. */
67+
private List<String> values;
68+
69+
/** The total number of available completions. */
70+
private Integer total;
71+
72+
/** Whether more completions are available. */
73+
private boolean hasMore;
74+
75+
/**
76+
* Sets the completion values with defensive copying.
77+
*
78+
* <p>This method creates an immutable copy of the input list to prevent external modification
79+
* after the value has been set. If the input list is null, the internal values field is set to
80+
* null.
81+
*
82+
* @param values the list of completion values to set, may be null
83+
* @return this {@link Builder} instance for method chaining
84+
*/
85+
public Builder values(List<String> values) {
86+
// Create defensive copy to prevent external modification after setting
87+
this.values = values == null ? null : List.copyOf(values);
88+
return this;
89+
}
90+
91+
/**
92+
* Sets the total number of available completions.
93+
*
94+
* <p>This value represents the total count of completions that could be returned, which may be
95+
* greater than the number of values in the current completion result if pagination is
96+
* supported.
97+
*
98+
* @param total the total number of available completions, may be null
99+
* @return this {@link Builder} instance for method chaining
100+
*/
101+
public Builder total(Integer total) {
102+
this.total = total;
103+
return this;
104+
}
105+
106+
/**
107+
* Sets whether more completions are available.
108+
*
109+
* <p>This flag indicates whether additional completion results can be obtained through further
110+
* requests, typically used for pagination or incremental loading scenarios.
111+
*
112+
* @param hasMore true if more completions are available, false otherwise
113+
* @return this {@link Builder} instance for method chaining
114+
*/
115+
public Builder hasMore(boolean hasMore) {
116+
this.hasMore = hasMore;
117+
return this;
118+
}
119+
120+
/**
121+
* Builds a new {@link McpCompleteCompletion} instance with the configured values.
122+
*
123+
* <p>This method creates a new record instance using the current builder state. The record's
124+
* compact constructor will apply additional defensive copying to ensure the final object is
125+
* fully immutable.
126+
*
127+
* @return a new {@link McpCompleteCompletion} instance with the configured values
128+
*/
129+
public McpCompleteCompletion build() {
130+
return new McpCompleteCompletion(values, total, hasMore);
131+
}
132+
}
133+
}

0 commit comments

Comments
 (0)