diff --git a/mcp/.mvn/wrapper/maven-wrapper.properties b/mcp-core/.mvn/wrapper/maven-wrapper.properties similarity index 100% rename from mcp/.mvn/wrapper/maven-wrapper.properties rename to mcp-core/.mvn/wrapper/maven-wrapper.properties diff --git a/mcp/README.md b/mcp-core/README.md similarity index 100% rename from mcp/README.md rename to mcp-core/README.md diff --git a/mcp/mvnw b/mcp-core/mvnw similarity index 100% rename from mcp/mvnw rename to mcp-core/mvnw diff --git a/mcp/mvnw.cmd b/mcp-core/mvnw.cmd similarity index 100% rename from mcp/mvnw.cmd rename to mcp-core/mvnw.cmd diff --git a/mcp/pom.xml b/mcp-core/pom.xml similarity index 98% rename from mcp/pom.xml rename to mcp-core/pom.xml index 341090e..64deecb 100644 --- a/mcp/pom.xml +++ b/mcp-core/pom.xml @@ -4,9 +4,9 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 spring.ai.experimental - mcp + mcp-core 0.0.1-SNAPSHOT - mcp + mcp-core Demo project for Spring Boot diff --git a/mcp/src/main/java/spring/ai/experimental/mcp/client/McpAsyncClient.java b/mcp-core/src/main/java/spring/ai/experimental/mcp/client/McpAsyncClient.java similarity index 68% rename from mcp/src/main/java/spring/ai/experimental/mcp/client/McpAsyncClient.java rename to mcp-core/src/main/java/spring/ai/experimental/mcp/client/McpAsyncClient.java index 9dce57e..ad2c3b9 100644 --- a/mcp/src/main/java/spring/ai/experimental/mcp/client/McpAsyncClient.java +++ b/mcp-core/src/main/java/spring/ai/experimental/mcp/client/McpAsyncClient.java @@ -9,6 +9,10 @@ import spring.ai.experimental.mcp.spec.DefaultMcpSession; import spring.ai.experimental.mcp.spec.McpTransport; import spring.ai.experimental.mcp.spec.McpError; import spring.ai.experimental.mcp.spec.McpSchema; +import spring.ai.experimental.mcp.spec.McpSchema.GetPromptRequest; +import spring.ai.experimental.mcp.spec.McpSchema.GetPromptResult; +import spring.ai.experimental.mcp.spec.McpSchema.ListPromptsResult; +import spring.ai.experimental.mcp.spec.McpSchema.PaginatedRequest; /** * @author Dariusz Jędrzejczyk @@ -16,6 +20,9 @@ import spring.ai.experimental.mcp.spec.McpSchema; */ public class McpAsyncClient extends DefaultMcpSession { + private static TypeReference VOID_TYPE_REFERENCE = new TypeReference() { + }; + public McpAsyncClient(McpTransport transport) { this(transport, Duration.ofSeconds(10), new ObjectMapper()); } @@ -89,14 +96,17 @@ public class McpAsyncClient extends DefaultMcpSession { * Send a synchronous ping request. */ public Mono ping() { - return this.sendRequest("ping", null, - new TypeReference() { - }); + return this.sendRequest("ping", null, VOID_TYPE_REFERENCE); } // -------------------------- // Tools // -------------------------- + private static TypeReference CALL_TOOL_RESULT_TYPE_REF = new TypeReference() { + }; + private static TypeReference LIST_TOOLS_RESULT_TYPE_REF = new TypeReference() { + }; + /** * Send a tools/call request. * @@ -104,9 +114,7 @@ public class McpAsyncClient extends DefaultMcpSession { * @return the call tool result. */ public Mono callTool(McpSchema.CallToolRequest callToolRequest) { - return this.sendRequest("tools/call", callToolRequest, - new TypeReference() { - }); + return this.sendRequest("tools/call", callToolRequest, CALL_TOOL_RESULT_TYPE_REF); } /** @@ -115,15 +123,20 @@ public class McpAsyncClient extends DefaultMcpSession { * @return the list of tools result. */ public Mono listTools(String cursor) { - return this.sendRequest("tools/list", new McpSchema.PaginatedRequest(cursor), - new TypeReference() { - }); + return this.sendRequest("tools/list", new McpSchema.PaginatedRequest(cursor), LIST_TOOLS_RESULT_TYPE_REF); } // -------------------------- // Resources // -------------------------- + private static TypeReference LIST_RESOURCES_RESULT_TYPE_REF = new TypeReference() { + }; + private static TypeReference READ_RESOURCE_RESULT_TYPE_REF = new TypeReference() { + }; + private static TypeReference LIST_RESOURCE_TEMPLATES_RESULT_TYPE_REF = new TypeReference() { + }; + /** * Send a resources/list request. * @@ -132,8 +145,7 @@ public class McpAsyncClient extends DefaultMcpSession { */ public Mono listResources(String cursor) { return this.sendRequest("resources/list", new McpSchema.PaginatedRequest(cursor), - new TypeReference() { - }); + LIST_RESOURCES_RESULT_TYPE_REF); } /** @@ -153,9 +165,7 @@ public class McpAsyncClient extends DefaultMcpSession { * @return the resource content. */ public Mono readResource(McpSchema.ReadResourceRequest readResourceRequest) { - return this.sendRequest("resources/read", readResourceRequest, - new TypeReference() { - }); + return this.sendRequest("resources/read", readResourceRequest, READ_RESOURCE_RESULT_TYPE_REF); } /** @@ -169,14 +179,12 @@ public class McpAsyncClient extends DefaultMcpSession { */ public Mono listResourceTemplates(String cursor) { return this.sendRequest("resources/templates/list", new McpSchema.PaginatedRequest(cursor), - new TypeReference() { - }); + LIST_RESOURCE_TEMPLATES_RESULT_TYPE_REF); } /** * List Changed Notification. When the list of available resources changes, - * servers - * that declared the listChanged capability SHOULD send a notification: + * servers that declared the listChanged capability SHOULD send a notification: */ public Mono sendResourcesListChanged() { return this.sendNotification("notifications/resources/list_changed"); @@ -186,31 +194,53 @@ public class McpAsyncClient extends DefaultMcpSession { * Subscriptions. The protocol supports optional subscriptions to resource * changes. * Clients can subscribe to specific resources and receive notifications when - * they - * change. + * they change. * * Send a resources/subscribe request. * * @param subscribeRequest the subscribe request contains the uri of the - * resource to - * subscribe to. + * resource to subscribe to. */ public Mono subscribeResource(McpSchema.SubscribeRequest subscribeRequest) { - return this.sendRequest("resources/subscribe", subscribeRequest, - new TypeReference() { - }); + return this.sendRequest("resources/subscribe", subscribeRequest, VOID_TYPE_REFERENCE); } /** * Send a resources/unsubscribe request. * * @param unsubscribeRequest the unsubscribe request contains the uri of the - * resource - * to unsubscribe from. + * resource to unsubscribe from. */ public Mono unsubscribeResource(McpSchema.UnsubscribeRequest unsubscribeRequest) { - return this.sendRequest("resources/unsubscribe", unsubscribeRequest, - new TypeReference() { - }); + return this.sendRequest("resources/unsubscribe", unsubscribeRequest, VOID_TYPE_REFERENCE); } + + // -------------------------- + // Prompts + // -------------------------- + private static TypeReference LIST_PROMPTS_RESULT_TYPE_REF = new TypeReference() { + }; + private static TypeReference GET_PROMPT_RESULT_TYPE_REF = new TypeReference() { + }; + + public Mono listPrompts(String cursor) { + return this + .sendRequest("prompts/list", new PaginatedRequest(cursor), LIST_PROMPTS_RESULT_TYPE_REF); + } + + public Mono getPrompt(GetPromptRequest getPromptRequest) { + return this.sendRequest("prompts/get", getPromptRequest, GET_PROMPT_RESULT_TYPE_REF); + } + + /** + * (Server) An optional notification from the server to the client, informing it + * that + * the list of prompts it offers has changed. This may be issued by servers + * without + * any previous subscription from the client. + */ + public Mono promptListChangedNotification() { + return this.sendNotification("notifications/prompts/list_changed"); + } + } diff --git a/mcp/src/main/java/spring/ai/experimental/mcp/client/McpClient.java b/mcp-core/src/main/java/spring/ai/experimental/mcp/client/McpClient.java similarity index 100% rename from mcp/src/main/java/spring/ai/experimental/mcp/client/McpClient.java rename to mcp-core/src/main/java/spring/ai/experimental/mcp/client/McpClient.java diff --git a/mcp/src/main/java/spring/ai/experimental/mcp/client/McpSyncClient.java b/mcp-core/src/main/java/spring/ai/experimental/mcp/client/McpSyncClient.java similarity index 64% rename from mcp/src/main/java/spring/ai/experimental/mcp/client/McpSyncClient.java rename to mcp-core/src/main/java/spring/ai/experimental/mcp/client/McpSyncClient.java index 021a13c..9e2073b 100644 --- a/mcp/src/main/java/spring/ai/experimental/mcp/client/McpSyncClient.java +++ b/mcp-core/src/main/java/spring/ai/experimental/mcp/client/McpSyncClient.java @@ -4,6 +4,9 @@ import java.time.Duration; import spring.ai.experimental.mcp.client.util.Assert; import spring.ai.experimental.mcp.spec.McpSchema; +import spring.ai.experimental.mcp.spec.McpSchema.GetPromptRequest; +import spring.ai.experimental.mcp.spec.McpSchema.GetPromptResult; +import spring.ai.experimental.mcp.spec.McpSchema.ListPromptsResult; /** * @author Dariusz Jędrzejczyk @@ -12,11 +15,11 @@ import spring.ai.experimental.mcp.spec.McpSchema; public class McpSyncClient implements AutoCloseable { // TODO: Consider providing a client config to set this properly - // this is currently a concern only because AutoCloseable is used - perhaps it is - // not a requirement? + // this is currently a concern only because AutoCloseable is used - perhaps it + // is not a requirement? private static final long DEFAULT_CLOSE_TIMEOUT_MS = 10_000L; - McpAsyncClient delegate; + private final McpAsyncClient delegate; public McpSyncClient(McpAsyncClient delegate) { Assert.notNull(delegate, "The delegate can not be null"); @@ -25,11 +28,12 @@ public class McpSyncClient implements AutoCloseable { @Override public void close() { - delegate.closeGracefully(Duration.ofMillis(DEFAULT_CLOSE_TIMEOUT_MS)).block(); + this.delegate.closeGracefully(Duration.ofMillis(DEFAULT_CLOSE_TIMEOUT_MS)).block(); } /** - * The initialization phase MUST be the first interaction between client and server. + * The initialization phase MUST be the first interaction between client and + * server. * During this phase, the client and server: *
    *
  • Establish protocol version compatibility
  • @@ -37,7 +41,8 @@ public class McpSyncClient implements AutoCloseable { *
  • Share implementation details
  • *
*
- * The client MUST initiate this phase by sending an initialize request containing: + * The client MUST initiate this phase by sending an initialize request + * containing: *
    *
  • The protocol version the client supports
  • *
  • The client's capabilities
  • @@ -46,7 +51,8 @@ public class McpSyncClient implements AutoCloseable { * * The server MUST respond with its own capabilities and information: * {@link McpSchema.ServerCapabilities}.
    - * After successful initialization, the client MUST send an initialized notification + * After successful initialization, the client MUST send an initialized + * notification * to indicate it is ready to begin normal operations. * *
    @@ -54,29 +60,27 @@ public class McpSyncClient implements AutoCloseable { * Initialization * Spec + * * @return the initialize result. */ public McpSchema.InitializeResult initialize() { // TODO: block takes no argument here as we assume the async client is - // configured with a requestTimeout at all times - return delegate.initialize() - .block(); + // configured with a requestTimeout at all times + return this.delegate.initialize().block(); } /** * Send a roots/list_changed notification. */ public void sendRootsListChanged() { - delegate.sendRootsListChanged() - .block(); + this.delegate.sendRootsListChanged().block(); } /** * Send a synchronous ping request. */ public void ping() { - delegate.ping() - .block(); + this.delegate.ping().block(); } // -------------------------- @@ -84,21 +88,21 @@ public class McpSyncClient implements AutoCloseable { // -------------------------- /** * Send a tools/call request. + * * @param callToolRequest the call tool request. * @return the call tool result. */ public McpSchema.CallToolResult callTool(McpSchema.CallToolRequest callToolRequest) { - return delegate.callTool(callToolRequest) - .block(); + return this.delegate.callTool(callToolRequest).block(); } /** * Send a tools/list request. + * * @return the list of tools result. */ public McpSchema.ListToolsResult listTools(String cursor) { - return delegate.listTools(cursor) - .block(); + return this.delegate.listTools(cursor).block(); } // -------------------------- @@ -107,32 +111,32 @@ public class McpSyncClient implements AutoCloseable { /** * Send a resources/list request. + * * @param cursor the cursor * @return the list of resources result. */ public McpSchema.ListResourcesResult listResources(String cursor) { - return delegate.listResources(cursor) - .block(); + return this.delegate.listResources(cursor).block(); } /** * Send a resources/read request. + * * @param resource the resource to read * @return the resource content. */ public McpSchema.ReadResourceResult readResource(McpSchema.Resource resource) { - return delegate.readResource(resource) - .block(); + return this.delegate.readResource(resource).block(); } /** * Send a resources/read request. + * * @param readResourceRequest the read resource request. * @return the resource content. */ public McpSchema.ReadResourceResult readResource(McpSchema.ReadResourceRequest readResourceRequest) { - return delegate.readResource(readResourceRequest) - .block(); + return this.delegate.readResource(readResourceRequest).block(); } /** @@ -140,44 +144,65 @@ public class McpSyncClient implements AutoCloseable { * templates. Arguments may be auto-completed through the completion API. * * Request a list of resource templates the server has. + * * @param cursor the cursor * @return the list of resource templates result. */ public McpSchema.ListResourceTemplatesResult listResourceTemplates(String cursor) { - return delegate.listResourceTemplates(cursor) - .block(); + return this.delegate.listResourceTemplates(cursor).block(); } /** - * List Changed Notification. When the list of available resources changes, servers + * List Changed Notification. When the list of available resources changes, + * servers * that declared the listChanged capability SHOULD send a notification: */ public void sendResourcesListChanged() { - delegate.sendResourcesListChanged() - .block(); + this.delegate.sendResourcesListChanged().block(); } /** - * Subscriptions. The protocol supports optional subscriptions to resource changes. - * Clients can subscribe to specific resources and receive notifications when they - * change. + * Subscriptions. The protocol supports optional subscriptions to resource + * changes. Clients can subscribe to specific resources and receive + * notifications when they change. * * Send a resources/subscribe request. - * @param subscribeRequest the subscribe request contains the uri of the resource to - * subscribe to. + * + * @param subscribeRequest the subscribe request contains the uri of the + * resource to subscribe to. */ public void subscribeResource(McpSchema.SubscribeRequest subscribeRequest) { - delegate.subscribeResource(subscribeRequest) - .block(); + this.delegate.subscribeResource(subscribeRequest).block(); } /** * Send a resources/unsubscribe request. - * @param unsubscribeRequest the unsubscribe request contains the uri of the resource - * to unsubscribe from. + * + * @param unsubscribeRequest the unsubscribe request contains the uri of the + * resource + * to unsubscribe from. */ public void unsubscribeResource(McpSchema.UnsubscribeRequest unsubscribeRequest) { - delegate.unsubscribeResource(unsubscribeRequest) - .block(); + this.delegate.unsubscribeResource(unsubscribeRequest).block(); + } + + // -------------------------- + // Prompts + // -------------------------- + public ListPromptsResult listPrompts(String cursor) { + return this.delegate.listPrompts(cursor).block(); + } + + public GetPromptResult getPrompt(GetPromptRequest getPromptRequest) { + return this.delegate.getPrompt(getPromptRequest).block(); + } + + /** + * (Server) An optional notification from the server to the client, informing it + * that the list of prompts it offers has changed. This may be issued by servers + * without any previous subscription from the client. + */ + public void promptListChangedNotification() { + this.delegate.promptListChangedNotification().block(); } } diff --git a/mcp/src/main/java/spring/ai/experimental/mcp/client/stdio/ServerParameters.java b/mcp-core/src/main/java/spring/ai/experimental/mcp/client/stdio/ServerParameters.java similarity index 100% rename from mcp/src/main/java/spring/ai/experimental/mcp/client/stdio/ServerParameters.java rename to mcp-core/src/main/java/spring/ai/experimental/mcp/client/stdio/ServerParameters.java diff --git a/mcp/src/main/java/spring/ai/experimental/mcp/client/stdio/ServerParametersParser.java b/mcp-core/src/main/java/spring/ai/experimental/mcp/client/stdio/ServerParametersParser.java similarity index 100% rename from mcp/src/main/java/spring/ai/experimental/mcp/client/stdio/ServerParametersParser.java rename to mcp-core/src/main/java/spring/ai/experimental/mcp/client/stdio/ServerParametersParser.java diff --git a/mcp/src/main/java/spring/ai/experimental/mcp/client/stdio/StdioServerTransport.java b/mcp-core/src/main/java/spring/ai/experimental/mcp/client/stdio/StdioServerTransport.java similarity index 100% rename from mcp/src/main/java/spring/ai/experimental/mcp/client/stdio/StdioServerTransport.java rename to mcp-core/src/main/java/spring/ai/experimental/mcp/client/stdio/StdioServerTransport.java diff --git a/mcp/src/main/java/spring/ai/experimental/mcp/client/util/Assert.java b/mcp-core/src/main/java/spring/ai/experimental/mcp/client/util/Assert.java similarity index 100% rename from mcp/src/main/java/spring/ai/experimental/mcp/client/util/Assert.java rename to mcp-core/src/main/java/spring/ai/experimental/mcp/client/util/Assert.java diff --git a/mcp/src/main/java/spring/ai/experimental/mcp/spec/AbstractMcpTransport.java b/mcp-core/src/main/java/spring/ai/experimental/mcp/spec/AbstractMcpTransport.java similarity index 100% rename from mcp/src/main/java/spring/ai/experimental/mcp/spec/AbstractMcpTransport.java rename to mcp-core/src/main/java/spring/ai/experimental/mcp/spec/AbstractMcpTransport.java diff --git a/mcp/src/main/java/spring/ai/experimental/mcp/spec/DefaultMcpSession.java b/mcp-core/src/main/java/spring/ai/experimental/mcp/spec/DefaultMcpSession.java similarity index 100% rename from mcp/src/main/java/spring/ai/experimental/mcp/spec/DefaultMcpSession.java rename to mcp-core/src/main/java/spring/ai/experimental/mcp/spec/DefaultMcpSession.java diff --git a/mcp/src/main/java/spring/ai/experimental/mcp/spec/McpError.java b/mcp-core/src/main/java/spring/ai/experimental/mcp/spec/McpError.java similarity index 100% rename from mcp/src/main/java/spring/ai/experimental/mcp/spec/McpError.java rename to mcp-core/src/main/java/spring/ai/experimental/mcp/spec/McpError.java diff --git a/mcp/src/main/java/spring/ai/experimental/mcp/spec/McpSchema.java b/mcp-core/src/main/java/spring/ai/experimental/mcp/spec/McpSchema.java similarity index 77% rename from mcp/src/main/java/spring/ai/experimental/mcp/spec/McpSchema.java rename to mcp-core/src/main/java/spring/ai/experimental/mcp/spec/McpSchema.java index 45a5473..4516d4e 100644 --- a/mcp/src/main/java/spring/ai/experimental/mcp/spec/McpSchema.java +++ b/mcp-core/src/main/java/spring/ai/experimental/mcp/spec/McpSchema.java @@ -57,7 +57,7 @@ public class McpSchema { } - public sealed interface Request permits InitializeRequest, CallToolRequest, CreateMessageRequest, CompleteRequest { + public sealed interface Request permits InitializeRequest, CallToolRequest, CreateMessageRequest, CompleteRequest, GetPromptRequest { } @@ -178,7 +178,8 @@ public class McpSchema { // Resource Interfaces // --------------------------- /** - * Base for objects that include optional annotations for the client. The client can + * Base for objects that include optional annotations for the client. The client + * can * use annotations to inform how objects are used or displayed */ public interface Annotated { @@ -188,13 +189,18 @@ public class McpSchema { } /** - * @param audience Describes who the intended customer of this object or data is. It - * can include multiple entries to indicate content useful for multiple audiences - * (e.g., `["user", "assistant"]`). - * @param priority Describes how important this data is for operating the server. A - * value of 1 means "most important," and indicates that the data is effectively - * required, while 0 means "least important," and indicates that the data is entirely - * optional. It is a number between 0 and 1. + * @param audience Describes who the intended customer of this object or data + * is. It + * can include multiple entries to indicate content useful for + * multiple audiences + * (e.g., `["user", "assistant"]`). + * @param priority Describes how important this data is for operating the + * server. A + * value of 1 means "most important," and indicates that the + * data is effectively + * required, while 0 means "least important," and indicates that + * the data is entirely + * optional. It is a number between 0 and 1. */ @JsonInclude(JsonInclude.Include.NON_ABSENT) public record Annotations( // @formatter:off @@ -205,13 +211,16 @@ public class McpSchema { /** * A known resource that the server is capable of reading. * - * @param uri the URI of the resource. - * @param name A human-readable name for this resource. This can be used by clients to - * populate UI elements. - * @param description A description of what this resource represents. This can be used - * by clients to improve the LLM's understanding of available resources. It can be - * thought of like a "hint" to the model. - * @param mimeType The MIME type of this resource, if known. + * @param uri the URI of the resource. + * @param name A human-readable name for this resource. This can be used + * by clients to + * populate UI elements. + * @param description A description of what this resource represents. This can + * be used + * by clients to improve the LLM's understanding of available + * resources. It can be + * thought of like a "hint" to the model. + * @param mimeType The MIME type of this resource, if known. */ @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonIgnoreProperties(ignoreUnknown = true) @@ -255,11 +264,13 @@ public class McpSchema { } // @formatter:on /** - * Sent from the client to request resources/updated notifications from the server + * Sent from the client to request resources/updated notifications from the + * server * whenever a particular resource changes. * - * @param uri the URI of the resource to subscribe to. The URI can use any protocol; - * it is up to the server how to interpret it. + * @param uri the URI of the resource to subscribe to. The URI can use any + * protocol; + * it is up to the server how to interpret it. */ @JsonInclude(JsonInclude.Include.NON_ABSENT) public record SubscribeRequest( // @formatter:off @@ -281,12 +292,14 @@ public class McpSchema { /** * The URI of this resource. + * * @return the URI of this resource. */ String uri(); /** * The MIME type of this resource. + * * @return the MIME type of this resource. */ String mimeType(); @@ -296,10 +309,11 @@ public class McpSchema { /** * Text contents of a resource. * - * @param uri the URI of this resource. + * @param uri the URI of this resource. * @param mimeType the MIME type of this resource. - * @param text the text of the resource. This must only be set if the resource can - * actually be represented as text (not binary data). + * @param text the text of the resource. This must only be set if the + * resource can + * actually be represented as text (not binary data). */ @JsonInclude(JsonInclude.Include.NON_ABSENT) public record TextResourceContents( // @formatter:off @@ -311,11 +325,13 @@ public class McpSchema { /** * Binary contents of a resource. * - * @param uri the URI of this resource. + * @param uri the URI of this resource. * @param mimeType the MIME type of this resource. - * @param blob a base64-encoded string representing the binary data of the resource. - * This must only be set if the resource can actually be represented as binary data - * (not text). + * @param blob a base64-encoded string representing the binary data of the + * resource. + * This must only be set if the resource can actually be + * represented as binary data + * (not text). */ @JsonInclude(JsonInclude.Include.NON_ABSENT) public record BlobResourceContents( // @formatter:off @@ -327,14 +343,83 @@ public class McpSchema { // --------------------------- // Prompt Interfaces // --------------------------- - public record Prompt(String name, String description, List arguments) { - } + /** + * A prompt or prompt template that the server offers. + * + * @param name The name of the prompt or prompt template. + * @param description An optional description of what this prompt provides. + * @param arguments A list of arguments to use for templating the prompt. + */ + @JsonInclude(JsonInclude.Include.NON_ABSENT) + public record Prompt( // @formatter:off + @JsonProperty("name") String name, + @JsonProperty("description") String description, + @JsonProperty("arguments") List arguments) { + } // @formatter:on - public record PromptArgument(String name, String description, Boolean required) { - } + /** + * Describes an argument that a prompt can accept. + * + * @param name The name of the argument. + * @param description A human-readable description of the argument. + * @param required Whether this argument must be provided. + */ + @JsonInclude(JsonInclude.Include.NON_ABSENT) + public record PromptArgument( // @formatter:off + @JsonProperty("name") String name, + @JsonProperty("description") String description, + @JsonProperty("required") Boolean required) { + }// @formatter:on - public record PromptMessage(Role role, Content content) { - } + /** + * Describes a message returned as part of a prompt. + * + * This is similar to `SamplingMessage`, but also supports the embedding of + * resources + * from the MCP server. + * + * @param role The sender or recipient of messages and data in a + * conversation. + * @param content The content of the message of type {@link Content}. + */ + @JsonInclude(JsonInclude.Include.NON_ABSENT) + public record PromptMessage( // @formatter:off + @JsonProperty("role") Role role, + @JsonProperty("content") Content content) { + } // @formatter:on + + /** + * The server's response to a prompts/list request from the client. + */ + @JsonInclude(JsonInclude.Include.NON_ABSENT) + public record ListPromptsResult( // @formatter:off + @JsonProperty("prompts") List prompts, + @JsonProperty("nextCursor") String nextCursor) { + }// @formatter:on + + /** + * Used by the client to get a prompt provided by the server. + * + * @param name The name of the prompt or prompt template. + * @param arguments Arguments to use for templating the prompt. + */ + @JsonInclude(JsonInclude.Include.NON_ABSENT) + public record GetPromptRequest(// @formatter:off + @JsonProperty("name") String name, + @JsonProperty("arguments") Map arguments) implements Request { + }// @formatter:off + + /** + * The server's response to a prompts/get request from the client. + * + * @param description An optional description for the prompt. + * @param messages A list of messages to display as part of the prompt. + */ + @JsonInclude(JsonInclude.Include.NON_ABSENT) + public record GetPromptResult( // @formatter:off + @JsonProperty("description") String description, + @JsonProperty("messages") List messages) { + } // @formatter:on // --------------------------- // Tool Interfaces diff --git a/mcp/src/main/java/spring/ai/experimental/mcp/spec/McpSession.java b/mcp-core/src/main/java/spring/ai/experimental/mcp/spec/McpSession.java similarity index 100% rename from mcp/src/main/java/spring/ai/experimental/mcp/spec/McpSession.java rename to mcp-core/src/main/java/spring/ai/experimental/mcp/spec/McpSession.java diff --git a/mcp/src/main/java/spring/ai/experimental/mcp/spec/McpTransport.java b/mcp-core/src/main/java/spring/ai/experimental/mcp/spec/McpTransport.java similarity index 100% rename from mcp/src/main/java/spring/ai/experimental/mcp/spec/McpTransport.java rename to mcp-core/src/main/java/spring/ai/experimental/mcp/spec/McpTransport.java diff --git a/mcp/src/test/java/spring/ai/experimental/mcp/attic/ClientSessionTests2.java b/mcp-core/src/test/java/spring/ai/experimental/mcp/attic/ClientSessionTests2.java similarity index 100% rename from mcp/src/test/java/spring/ai/experimental/mcp/attic/ClientSessionTests2.java rename to mcp-core/src/test/java/spring/ai/experimental/mcp/attic/ClientSessionTests2.java diff --git a/mcp/src/test/java/spring/ai/experimental/mcp/attic/ClientSessionTests3.java b/mcp-core/src/test/java/spring/ai/experimental/mcp/attic/ClientSessionTests3.java similarity index 100% rename from mcp/src/test/java/spring/ai/experimental/mcp/attic/ClientSessionTests3.java rename to mcp-core/src/test/java/spring/ai/experimental/mcp/attic/ClientSessionTests3.java diff --git a/mcp/src/test/java/spring/ai/experimental/mcp/client/McpAsyncClientTests.java b/mcp-core/src/test/java/spring/ai/experimental/mcp/client/McpAsyncClientTests.java similarity index 79% rename from mcp/src/test/java/spring/ai/experimental/mcp/client/McpAsyncClientTests.java rename to mcp-core/src/test/java/spring/ai/experimental/mcp/client/McpAsyncClientTests.java index fe78c2d..a221a26 100644 --- a/mcp/src/test/java/spring/ai/experimental/mcp/client/McpAsyncClientTests.java +++ b/mcp-core/src/test/java/spring/ai/experimental/mcp/client/McpAsyncClientTests.java @@ -26,6 +26,8 @@ import org.junit.jupiter.api.Timeout; import spring.ai.experimental.mcp.client.stdio.ServerParameters; import spring.ai.experimental.mcp.client.stdio.StdioServerTransport; import spring.ai.experimental.mcp.spec.McpSchema.CallToolRequest; +import spring.ai.experimental.mcp.spec.McpSchema.GetPromptRequest; +import spring.ai.experimental.mcp.spec.McpSchema.Prompt; import spring.ai.experimental.mcp.spec.McpSchema.Resource; import spring.ai.experimental.mcp.spec.McpSchema.Tool; @@ -52,8 +54,8 @@ class McpAsyncClientTests { @BeforeEach void setUp() { stdioParams = ServerParameters.builder("npx") - .args("-y", "@modelcontextprotocol/server-everything", "dir") - .build(); + .args("-y", "@modelcontextprotocol/server-everything", "dir") + .build(); assertThatCode(() -> { mcpAsyncClient = McpClient.async(new StdioServerTransport(stdioParams), TIMEOUT, new ObjectMapper()); @@ -71,13 +73,13 @@ class McpAsyncClientTests { @Test void testConstructorWithInvalidArguments() { assertThatThrownBy(() -> McpClient.sync(null, TIMEOUT, new ObjectMapper())) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(IllegalArgumentException.class); assertThatThrownBy(() -> McpClient.sync(new StdioServerTransport(stdioParams), null, new ObjectMapper())) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(IllegalArgumentException.class); assertThatThrownBy(() -> McpClient.sync(new StdioServerTransport(stdioParams), TIMEOUT, null)) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(IllegalArgumentException.class); } @Test @@ -146,4 +148,33 @@ class McpAsyncClientTests { assertThat(mcpAsyncClient).isNotNull(); } + @Test + @Timeout(15) + void testListPrompts() { + mcpAsyncClient.listPrompts(null).subscribe(prompts -> { + assertThat(prompts).isNotNull() + .satisfies(result -> { + assertThat(result.prompts()).isNotNull(); + + if (!result.prompts().isEmpty()) { + Prompt firstPrompt = result.prompts().get(0); + assertThat(firstPrompt.name()).isNotNull(); + assertThat(firstPrompt.description()).isNotNull(); + } + }); + }); + } + + @Test + @Timeout(15) + void testGetPrompt() { + mcpAsyncClient.getPrompt(new GetPromptRequest("simple_prompt", Map.of())).subscribe(prompt -> { + assertThat(prompt).isNotNull() + .satisfies(result -> { + assertThat(result.description()).isNotNull(); + assertThat(result.messages()).isNotEmpty(); + }); + }); + } + } diff --git a/mcp/src/test/java/spring/ai/experimental/mcp/client/McpSyncClientTests.java b/mcp-core/src/test/java/spring/ai/experimental/mcp/client/McpSyncClientTests.java similarity index 100% rename from mcp/src/test/java/spring/ai/experimental/mcp/client/McpSyncClientTests.java rename to mcp-core/src/test/java/spring/ai/experimental/mcp/client/McpSyncClientTests.java diff --git a/mcp/src/test/java/spring/ai/experimental/mcp/client/stdio/StdioServerTransportTests.java b/mcp-core/src/test/java/spring/ai/experimental/mcp/client/stdio/StdioServerTransportTests.java similarity index 100% rename from mcp/src/test/java/spring/ai/experimental/mcp/client/stdio/StdioServerTransportTests.java rename to mcp-core/src/test/java/spring/ai/experimental/mcp/client/stdio/StdioServerTransportTests.java diff --git a/mcp/src/test/java/spring/ai/experimental/mcp/spec/DefaultMcpTransportTests.java b/mcp-core/src/test/java/spring/ai/experimental/mcp/spec/DefaultMcpTransportTests.java similarity index 99% rename from mcp/src/test/java/spring/ai/experimental/mcp/spec/DefaultMcpTransportTests.java rename to mcp-core/src/test/java/spring/ai/experimental/mcp/spec/DefaultMcpTransportTests.java index 0a2abac..a8f3ba0 100644 --- a/mcp/src/test/java/spring/ai/experimental/mcp/spec/DefaultMcpTransportTests.java +++ b/mcp-core/src/test/java/spring/ai/experimental/mcp/spec/DefaultMcpTransportTests.java @@ -31,6 +31,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; class DefaultMcpTransportTests { private AbstractMcpTransport transport; + private ObjectMapper objectMapper; @BeforeEach diff --git a/spring/.mvn/wrapper/maven-wrapper.properties b/mcp-spring/.mvn/wrapper/maven-wrapper.properties similarity index 100% rename from spring/.mvn/wrapper/maven-wrapper.properties rename to mcp-spring/.mvn/wrapper/maven-wrapper.properties diff --git a/spring/mvnw b/mcp-spring/mvnw similarity index 100% rename from spring/mvnw rename to mcp-spring/mvnw diff --git a/spring/mvnw.cmd b/mcp-spring/mvnw.cmd similarity index 100% rename from spring/mvnw.cmd rename to mcp-spring/mvnw.cmd diff --git a/spring/pom.xml b/mcp-spring/pom.xml similarity index 96% rename from spring/pom.xml rename to mcp-spring/pom.xml index 364a90d..94b1c19 100644 --- a/spring/pom.xml +++ b/mcp-spring/pom.xml @@ -10,9 +10,9 @@ spring.ai.experimental - mcp-spring-boot-demo + mcp-spring 0.0.1-SNAPSHOT - mcp-spring-boot-demo + mcp-spring Demo project for Spring Boot diff --git a/spring/src/main/java/spring/ai/mcp/McpApplication.java b/mcp-spring/src/main/java/spring/ai/mcp/McpApplication.java similarity index 100% rename from spring/src/main/java/spring/ai/mcp/McpApplication.java rename to mcp-spring/src/main/java/spring/ai/mcp/McpApplication.java diff --git a/spring/src/main/java/spring/ai/mcp/McpFunctionCallback.java b/mcp-spring/src/main/java/spring/ai/mcp/McpFunctionCallback.java similarity index 100% rename from spring/src/main/java/spring/ai/mcp/McpFunctionCallback.java rename to mcp-spring/src/main/java/spring/ai/mcp/McpFunctionCallback.java diff --git a/spring/src/main/resources/application.properties b/mcp-spring/src/main/resources/application.properties similarity index 100% rename from spring/src/main/resources/application.properties rename to mcp-spring/src/main/resources/application.properties diff --git a/pom.xml b/pom.xml index bf34957..648a8f5 100644 --- a/pom.xml +++ b/pom.xml @@ -28,8 +28,8 @@ 1.0.0-M4 - mcp - spring + mcp-core + mcp-spring