Update copyright headers and add javaformat plugin
This commit is contained in:
parent
7bec6efd37
commit
586f132f46
23 changed files with 708 additions and 642 deletions
14
pom.xml
14
pom.xml
|
|
@ -97,6 +97,20 @@
|
|||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>io.spring.javaformat</groupId>
|
||||
<artifactId>spring-javaformat-maven-plugin</artifactId>
|
||||
<version>${spring-javaformat-maven-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>validate</phase>
|
||||
<inherited>true</inherited>
|
||||
<goals>
|
||||
<goal>validate</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-site-plugin</artifactId>
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>org.springframework.experimental</groupId>
|
||||
<artifactId>spring-ai-mcp</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>spring-ai-mcp-core</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,18 @@
|
|||
/*
|
||||
* Copyright 2024-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.ai.mcp.client;
|
||||
|
||||
import java.time.Duration;
|
||||
|
|
@ -5,14 +20,15 @@ import java.time.Duration;
|
|||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.ai.mcp.spec.DefaultMcpSession;
|
||||
import org.springframework.ai.mcp.spec.McpTransport;
|
||||
import org.springframework.ai.mcp.spec.McpError;
|
||||
import org.springframework.ai.mcp.spec.McpSchema;
|
||||
import org.springframework.ai.mcp.spec.McpSchema.GetPromptRequest;
|
||||
import org.springframework.ai.mcp.spec.McpSchema.GetPromptResult;
|
||||
import org.springframework.ai.mcp.spec.McpSchema.ListPromptsResult;
|
||||
import org.springframework.ai.mcp.spec.McpSchema.PaginatedRequest;
|
||||
import org.springframework.ai.mcp.spec.McpTransport;
|
||||
|
||||
/**
|
||||
* @author Dariusz Jędrzejczyk
|
||||
|
|
@ -32,8 +48,7 @@ public class McpAsyncClient extends DefaultMcpSession {
|
|||
}
|
||||
|
||||
/**
|
||||
* 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:
|
||||
* <ul>
|
||||
* <li>Establish protocol version compatibility</li>
|
||||
|
|
@ -41,8 +56,7 @@ public class McpAsyncClient extends DefaultMcpSession {
|
|||
* <li>Share implementation details</li>
|
||||
* </ul>
|
||||
* <br/>
|
||||
* The client MUST initiate this phase by sending an initialize request
|
||||
* containing:
|
||||
* The client MUST initiate this phase by sending an initialize request containing:
|
||||
* <ul>
|
||||
* <li>The protocol version the client supports</li>
|
||||
* <li>The client's capabilities</li>
|
||||
|
|
@ -51,8 +65,7 @@ public class McpAsyncClient extends DefaultMcpSession {
|
|||
*
|
||||
* The server MUST respond with its own capabilities and information:
|
||||
* {@link McpSchema.ServerCapabilities}. <br/>
|
||||
* 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.
|
||||
*
|
||||
* <br/>
|
||||
|
|
@ -60,7 +73,6 @@ public class McpAsyncClient extends DefaultMcpSession {
|
|||
* <a href=
|
||||
* "https://github.com/modelcontextprotocol/specification/blob/main/docs/specification/basic/lifecycle.md#initialization">Initialization
|
||||
* Spec</a>
|
||||
*
|
||||
* @return the initialize result.
|
||||
*/
|
||||
public Mono<McpSchema.InitializeResult> initialize() {
|
||||
|
|
@ -75,12 +87,11 @@ public class McpAsyncClient extends DefaultMcpSession {
|
|||
|
||||
return result.flatMap(initializeResult -> {
|
||||
if (!McpSchema.LATEST_PROTOCOL_VERSION.equals(initializeResult.protocolVersion())) {
|
||||
return Mono.error(
|
||||
new McpError("Unsupported protocol version from the server: "
|
||||
+ initializeResult.protocolVersion()));
|
||||
} else {
|
||||
return this.sendNotification("notifications/initialized", null)
|
||||
.thenReturn(initializeResult);
|
||||
return Mono.error(new McpError(
|
||||
"Unsupported protocol version from the server: " + initializeResult.protocolVersion()));
|
||||
}
|
||||
else {
|
||||
return this.sendNotification("notifications/initialized", null).thenReturn(initializeResult);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -104,12 +115,12 @@ public class McpAsyncClient extends DefaultMcpSession {
|
|||
// --------------------------
|
||||
private static TypeReference<McpSchema.CallToolResult> CALL_TOOL_RESULT_TYPE_REF = new TypeReference<McpSchema.CallToolResult>() {
|
||||
};
|
||||
|
||||
private static TypeReference<McpSchema.ListToolsResult> LIST_TOOLS_RESULT_TYPE_REF = new TypeReference<McpSchema.ListToolsResult>() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a tools/call request.
|
||||
*
|
||||
* @param callToolRequest the call tool request.
|
||||
* @return the call tool result.
|
||||
*/
|
||||
|
|
@ -119,7 +130,6 @@ public class McpAsyncClient extends DefaultMcpSession {
|
|||
|
||||
/**
|
||||
* Send a tools/list request.
|
||||
*
|
||||
* @return the list of tools result.
|
||||
*/
|
||||
public Mono<McpSchema.ListToolsResult> listTools(String cursor) {
|
||||
|
|
@ -132,14 +142,15 @@ public class McpAsyncClient extends DefaultMcpSession {
|
|||
|
||||
private static TypeReference<McpSchema.ListResourcesResult> LIST_RESOURCES_RESULT_TYPE_REF = new TypeReference<McpSchema.ListResourcesResult>() {
|
||||
};
|
||||
|
||||
private static TypeReference<McpSchema.ReadResourceResult> READ_RESOURCE_RESULT_TYPE_REF = new TypeReference<McpSchema.ReadResourceResult>() {
|
||||
};
|
||||
|
||||
private static TypeReference<McpSchema.ListResourceTemplatesResult> LIST_RESOURCE_TEMPLATES_RESULT_TYPE_REF = new TypeReference<McpSchema.ListResourceTemplatesResult>() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a resources/list request.
|
||||
*
|
||||
* @param cursor the cursor
|
||||
* @return the list of resources result.
|
||||
*/
|
||||
|
|
@ -150,7 +161,6 @@ public class McpAsyncClient extends DefaultMcpSession {
|
|||
|
||||
/**
|
||||
* Send a resources/read request.
|
||||
*
|
||||
* @param resource the resource to read
|
||||
* @return the resource content.
|
||||
*/
|
||||
|
|
@ -160,7 +170,6 @@ public class McpAsyncClient extends DefaultMcpSession {
|
|||
|
||||
/**
|
||||
* Send a resources/read request.
|
||||
*
|
||||
* @param readResourceRequest the read resource request.
|
||||
* @return the resource content.
|
||||
*/
|
||||
|
|
@ -173,7 +182,6 @@ public class McpAsyncClient extends DefaultMcpSession {
|
|||
* 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.
|
||||
*/
|
||||
|
|
@ -183,23 +191,21 @@ public class McpAsyncClient extends DefaultMcpSession {
|
|||
}
|
||||
|
||||
/**
|
||||
* List Changed Notification. When the list of available resources changes,
|
||||
* servers that declared the listChanged capability SHOULD send a notification:
|
||||
* List Changed Notification. When the list of available resources changes, servers
|
||||
* that declared the listChanged capability SHOULD send a notification:
|
||||
*/
|
||||
public Mono<Void> sendResourcesListChanged() {
|
||||
return this.sendNotification("notifications/resources/list_changed");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Mono<Void> subscribeResource(McpSchema.SubscribeRequest subscribeRequest) {
|
||||
return this.sendRequest("resources/subscribe", subscribeRequest, VOID_TYPE_REFERENCE);
|
||||
|
|
@ -207,9 +213,8 @@ public class McpAsyncClient extends DefaultMcpSession {
|
|||
|
||||
/**
|
||||
* 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 Mono<Void> unsubscribeResource(McpSchema.UnsubscribeRequest unsubscribeRequest) {
|
||||
return this.sendRequest("resources/unsubscribe", unsubscribeRequest, VOID_TYPE_REFERENCE);
|
||||
|
|
@ -220,12 +225,12 @@ public class McpAsyncClient extends DefaultMcpSession {
|
|||
// --------------------------
|
||||
private static TypeReference<McpSchema.ListPromptsResult> LIST_PROMPTS_RESULT_TYPE_REF = new TypeReference<McpSchema.ListPromptsResult>() {
|
||||
};
|
||||
|
||||
private static TypeReference<McpSchema.GetPromptResult> GET_PROMPT_RESULT_TYPE_REF = new TypeReference<McpSchema.GetPromptResult>() {
|
||||
};
|
||||
|
||||
public Mono<ListPromptsResult> listPrompts(String cursor) {
|
||||
return this
|
||||
.sendRequest("prompts/list", new PaginatedRequest(cursor), LIST_PROMPTS_RESULT_TYPE_REF);
|
||||
return this.sendRequest("prompts/list", new PaginatedRequest(cursor), LIST_PROMPTS_RESULT_TYPE_REF);
|
||||
}
|
||||
|
||||
public Mono<GetPromptResult> getPrompt(GetPromptRequest getPromptRequest) {
|
||||
|
|
@ -233,10 +238,8 @@ public class McpAsyncClient extends DefaultMcpSession {
|
|||
}
|
||||
|
||||
/**
|
||||
* (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
|
||||
* (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<Void> promptListChangedNotification() {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
/*
|
||||
* Copyright 2024 - 2024 the original author or authors.
|
||||
* Copyright 2024-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
|
@ -13,93 +13,96 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.mcp.client;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.springframework.ai.mcp.spec.McpTransport;
|
||||
|
||||
/**
|
||||
* Factory class providing static methods for creating Model Context Protocol (MCP) clients.
|
||||
* This class serves as the main entry point for establishing connections with MCP servers,
|
||||
* offering both synchronous and asynchronous client implementations.
|
||||
*
|
||||
* <p>The class provides factory methods to create either:
|
||||
* Factory class providing static methods for creating Model Context Protocol (MCP)
|
||||
* clients. This class serves as the main entry point for establishing connections with
|
||||
* MCP servers, offering both synchronous and asynchronous client implementations.
|
||||
*
|
||||
* <p>
|
||||
* The class provides factory methods to create either:
|
||||
* <ul>
|
||||
* <li>{@link McpAsyncClient} for non-blocking operations
|
||||
* <li>{@link McpSyncClient} for blocking operations
|
||||
* <li>{@link McpAsyncClient} for non-blocking operations
|
||||
* <li>{@link McpSyncClient} for blocking operations
|
||||
* </ul>
|
||||
*
|
||||
* <p>Each client type can be instantiated with default settings or with custom configuration
|
||||
*
|
||||
* <p>
|
||||
* Each client type can be instantiated with default settings or with custom configuration
|
||||
* including request timeout and JSON object mapping.
|
||||
*
|
||||
* <p>Future implementations will introduce a builder pattern for more flexible client configuration.
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* Future implementations will introduce a builder pattern for more flexible client
|
||||
* configuration.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Dariusz Jędrzejczyk
|
||||
*/
|
||||
public class McpClient {
|
||||
|
||||
/**
|
||||
* Private constructor to prevent instantiation as this is a utility class
|
||||
* containing only static factory methods.
|
||||
*/
|
||||
private McpClient() {
|
||||
}
|
||||
/**
|
||||
* Private constructor to prevent instantiation as this is a utility class containing
|
||||
* only static factory methods.
|
||||
*/
|
||||
private McpClient() {
|
||||
}
|
||||
|
||||
// TODO: introduce a builder like:
|
||||
// McpClient.using(transport)
|
||||
// .withRequestTimeout(Duration.ofSeconds(5))
|
||||
// .withObjectMapper(objectMapper); <- or even a more sophisticated
|
||||
// JSONtoPOJOCodec type
|
||||
// .sync();
|
||||
// TODO: introduce a builder like:
|
||||
// McpClient.using(transport)
|
||||
// .withRequestTimeout(Duration.ofSeconds(5))
|
||||
// .withObjectMapper(objectMapper); <- or even a more sophisticated
|
||||
// JSONtoPOJOCodec type
|
||||
// .sync();
|
||||
|
||||
/**
|
||||
* Creates an asynchronous MCP client with default configuration.
|
||||
*
|
||||
* @param transport The transport layer implementation for MCP communication
|
||||
* @return A new instance of {@link McpAsyncClient}
|
||||
*/
|
||||
public static McpAsyncClient async(McpTransport transport) {
|
||||
return new McpAsyncClient(transport);
|
||||
}
|
||||
/**
|
||||
* Creates an asynchronous MCP client with default configuration.
|
||||
* @param transport The transport layer implementation for MCP communication
|
||||
* @return A new instance of {@link McpAsyncClient}
|
||||
*/
|
||||
public static McpAsyncClient async(McpTransport transport) {
|
||||
return new McpAsyncClient(transport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an asynchronous MCP client with custom configuration.
|
||||
*
|
||||
* @param transport The transport layer implementation for MCP communication
|
||||
* @param requestTimeout The duration to wait before timing out requests
|
||||
* @param objectMapper The Jackson object mapper for JSON serialization/deserialization
|
||||
* @return A new instance of {@link McpAsyncClient}
|
||||
*/
|
||||
public static McpAsyncClient async(McpTransport transport, Duration requestTimeout,
|
||||
ObjectMapper objectMapper) {
|
||||
return new McpAsyncClient(transport, requestTimeout, objectMapper);
|
||||
}
|
||||
/**
|
||||
* Creates an asynchronous MCP client with custom configuration.
|
||||
* @param transport The transport layer implementation for MCP communication
|
||||
* @param requestTimeout The duration to wait before timing out requests
|
||||
* @param objectMapper The Jackson object mapper for JSON
|
||||
* serialization/deserialization
|
||||
* @return A new instance of {@link McpAsyncClient}
|
||||
*/
|
||||
public static McpAsyncClient async(McpTransport transport, Duration requestTimeout, ObjectMapper objectMapper) {
|
||||
return new McpAsyncClient(transport, requestTimeout, objectMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a synchronous MCP client with default configuration.
|
||||
* This method wraps an asynchronous client to provide synchronous operations.
|
||||
*
|
||||
* @param transport The transport layer implementation for MCP communication
|
||||
* @return A new instance of {@link McpSyncClient}
|
||||
*/
|
||||
public static McpSyncClient sync(McpTransport transport) {
|
||||
return new McpSyncClient(async(transport));
|
||||
}
|
||||
/**
|
||||
* Creates a synchronous MCP client with default configuration. This method wraps an
|
||||
* asynchronous client to provide synchronous operations.
|
||||
* @param transport The transport layer implementation for MCP communication
|
||||
* @return A new instance of {@link McpSyncClient}
|
||||
*/
|
||||
public static McpSyncClient sync(McpTransport transport) {
|
||||
return new McpSyncClient(async(transport));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a synchronous MCP client with custom configuration. This method wraps an
|
||||
* asynchronous client to provide synchronous operations.
|
||||
* @param transport The transport layer implementation for MCP communication
|
||||
* @param requestTimeout The duration to wait before timing out requests
|
||||
* @param objectMapper The Jackson object mapper for JSON
|
||||
* serialization/deserialization
|
||||
* @return A new instance of {@link McpSyncClient}
|
||||
*/
|
||||
public static McpSyncClient sync(McpTransport transport, Duration requestTimeout, ObjectMapper objectMapper) {
|
||||
return new McpSyncClient(async(transport, requestTimeout, objectMapper));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a synchronous MCP client with custom configuration.
|
||||
* This method wraps an asynchronous client to provide synchronous operations.
|
||||
*
|
||||
* @param transport The transport layer implementation for MCP communication
|
||||
* @param requestTimeout The duration to wait before timing out requests
|
||||
* @param objectMapper The Jackson object mapper for JSON serialization/deserialization
|
||||
* @return A new instance of {@link McpSyncClient}
|
||||
*/
|
||||
public static McpSyncClient sync(McpTransport transport, Duration requestTimeout,
|
||||
ObjectMapper objectMapper) {
|
||||
return new McpSyncClient(async(transport, requestTimeout, objectMapper));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
* Copyright 2024-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.mcp.client;
|
||||
|
||||
import java.time.Duration;
|
||||
|
|
@ -32,8 +48,7 @@ public class McpSyncClient implements AutoCloseable {
|
|||
}
|
||||
|
||||
/**
|
||||
* 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:
|
||||
* <ul>
|
||||
* <li>Establish protocol version compatibility</li>
|
||||
|
|
@ -41,8 +56,7 @@ public class McpSyncClient implements AutoCloseable {
|
|||
* <li>Share implementation details</li>
|
||||
* </ul>
|
||||
* <br/>
|
||||
* The client MUST initiate this phase by sending an initialize request
|
||||
* containing:
|
||||
* The client MUST initiate this phase by sending an initialize request containing:
|
||||
* <ul>
|
||||
* <li>The protocol version the client supports</li>
|
||||
* <li>The client's capabilities</li>
|
||||
|
|
@ -51,8 +65,7 @@ public class McpSyncClient implements AutoCloseable {
|
|||
*
|
||||
* The server MUST respond with its own capabilities and information:
|
||||
* {@link McpSchema.ServerCapabilities}. <br/>
|
||||
* 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.
|
||||
*
|
||||
* <br/>
|
||||
|
|
@ -60,7 +73,6 @@ public class McpSyncClient implements AutoCloseable {
|
|||
* <a href=
|
||||
* "https://github.com/modelcontextprotocol/specification/blob/main/docs/specification/basic/lifecycle.md#initialization">Initialization
|
||||
* Spec</a>
|
||||
*
|
||||
* @return the initialize result.
|
||||
*/
|
||||
public McpSchema.InitializeResult initialize() {
|
||||
|
|
@ -88,7 +100,6 @@ public class McpSyncClient implements AutoCloseable {
|
|||
// --------------------------
|
||||
/**
|
||||
* Send a tools/call request.
|
||||
*
|
||||
* @param callToolRequest the call tool request.
|
||||
* @return the call tool result.
|
||||
*/
|
||||
|
|
@ -98,7 +109,6 @@ public class McpSyncClient implements AutoCloseable {
|
|||
|
||||
/**
|
||||
* Send a tools/list request.
|
||||
*
|
||||
* @return the list of tools result.
|
||||
*/
|
||||
public McpSchema.ListToolsResult listTools(String cursor) {
|
||||
|
|
@ -111,7 +121,6 @@ public class McpSyncClient implements AutoCloseable {
|
|||
|
||||
/**
|
||||
* Send a resources/list request.
|
||||
*
|
||||
* @param cursor the cursor
|
||||
* @return the list of resources result.
|
||||
*/
|
||||
|
|
@ -121,7 +130,6 @@ public class McpSyncClient implements AutoCloseable {
|
|||
|
||||
/**
|
||||
* Send a resources/read request.
|
||||
*
|
||||
* @param resource the resource to read
|
||||
* @return the resource content.
|
||||
*/
|
||||
|
|
@ -131,7 +139,6 @@ public class McpSyncClient implements AutoCloseable {
|
|||
|
||||
/**
|
||||
* Send a resources/read request.
|
||||
*
|
||||
* @param readResourceRequest the read resource request.
|
||||
* @return the resource content.
|
||||
*/
|
||||
|
|
@ -144,7 +151,6 @@ 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.
|
||||
*/
|
||||
|
|
@ -153,8 +159,7 @@ public class McpSyncClient implements AutoCloseable {
|
|||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
|
|
@ -162,14 +167,13 @@ public class McpSyncClient implements AutoCloseable {
|
|||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
this.delegate.subscribeResource(subscribeRequest).block();
|
||||
|
|
@ -177,10 +181,8 @@ public class McpSyncClient implements AutoCloseable {
|
|||
|
||||
/**
|
||||
* 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) {
|
||||
this.delegate.unsubscribeResource(unsubscribeRequest).block();
|
||||
|
|
@ -198,11 +200,12 @@ public class McpSyncClient implements AutoCloseable {
|
|||
}
|
||||
|
||||
/**
|
||||
* (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.
|
||||
* (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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
* Copyright 2024-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.mcp.client.stdio;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -9,11 +25,12 @@ import java.util.stream.Collectors;
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.springframework.ai.mcp.client.util.Assert;
|
||||
|
||||
/**
|
||||
* Server parameters for stdio client.
|
||||
*
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Dariusz Jędrzejczyk
|
||||
*/
|
||||
|
|
@ -22,11 +39,11 @@ public class ServerParameters {
|
|||
|
||||
// Environment variables to inherit by default
|
||||
private static final List<String> DEFAULT_INHERITED_ENV_VARS = System.getProperty("os.name")
|
||||
.toLowerCase()
|
||||
.contains("win")
|
||||
? Arrays.asList("APPDATA", "HOMEDRIVE", "HOMEPATH", "LOCALAPPDATA", "PATH",
|
||||
"PROCESSOR_ARCHITECTURE", "SYSTEMDRIVE", "SYSTEMROOT", "TEMP", "USERNAME", "USERPROFILE")
|
||||
: Arrays.asList("HOME", "LOGNAME", "PATH", "SHELL", "TERM", "USER");
|
||||
.toLowerCase()
|
||||
.contains("win")
|
||||
? Arrays.asList("APPDATA", "HOMEDRIVE", "HOMEPATH", "LOCALAPPDATA", "PATH", "PROCESSOR_ARCHITECTURE",
|
||||
"SYSTEMDRIVE", "SYSTEMROOT", "TEMP", "USERNAME", "USERPROFILE")
|
||||
: Arrays.asList("HOME", "LOGNAME", "PATH", "SHELL", "TERM", "USER");
|
||||
|
||||
@JsonProperty("command")
|
||||
private String command;
|
||||
|
|
@ -117,17 +134,17 @@ public class ServerParameters {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns a default environment object including only environment variables
|
||||
* deemed safe to inherit.
|
||||
* Returns a default environment object including only environment variables deemed
|
||||
* safe to inherit.
|
||||
*/
|
||||
private static Map<String, String> getDefaultEnvironment() {
|
||||
return System.getenv()
|
||||
.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> DEFAULT_INHERITED_ENV_VARS.contains(entry.getKey()))
|
||||
.filter(entry -> entry.getValue() != null)
|
||||
.filter(entry -> !entry.getValue().startsWith("()"))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> DEFAULT_INHERITED_ENV_VARS.contains(entry.getKey()))
|
||||
.filter(entry -> entry.getValue() != null)
|
||||
.filter(entry -> !entry.getValue().startsWith("()"))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,26 +1,28 @@
|
|||
/*
|
||||
* Copyright 2024 - 2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
* Copyright 2024-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.mcp.client.stdio;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.springframework.ai.mcp.client.stdio.ServerParametersParser.McpServerConfigurations.McpServerConfiguration;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
* Copyright 2024-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.mcp.client.stdio;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
|
|
@ -13,6 +29,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Scheduler;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import org.springframework.ai.mcp.client.util.Assert;
|
||||
import org.springframework.ai.mcp.spec.AbstractMcpTransport;
|
||||
import org.springframework.ai.mcp.spec.McpSchema.JSONRPCMessage;
|
||||
|
|
@ -22,7 +39,7 @@ import org.springframework.ai.mcp.spec.McpSchema.JSONRPCResponse;
|
|||
|
||||
/**
|
||||
* Stdio client for communicating with a server process.
|
||||
*
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Dariusz Jędrzejczyk
|
||||
*/
|
||||
|
|
@ -34,11 +51,15 @@ public class StdioServerTransport extends AbstractMcpTransport {
|
|||
private Process process;
|
||||
|
||||
private BufferedReader processErrorReader;
|
||||
|
||||
private BufferedReader processReader;
|
||||
|
||||
private BufferedWriter processWriter;
|
||||
|
||||
private Scheduler inboundScheduler;
|
||||
|
||||
private Scheduler outboundScheduler;
|
||||
|
||||
private Scheduler errorScheduler;
|
||||
|
||||
volatile boolean isRunning;
|
||||
|
|
@ -59,12 +80,9 @@ public class StdioServerTransport extends AbstractMcpTransport {
|
|||
this.params = params;
|
||||
|
||||
// Start threads
|
||||
this.inboundScheduler = Schedulers.fromExecutorService(Executors.newSingleThreadExecutor(),
|
||||
"inbound");
|
||||
this.outboundScheduler = Schedulers.fromExecutorService(Executors.newSingleThreadExecutor(),
|
||||
"outbound");
|
||||
this.errorScheduler = Schedulers.fromExecutorService(Executors.newSingleThreadExecutor(),
|
||||
"error");
|
||||
this.inboundScheduler = Schedulers.fromExecutorService(Executors.newSingleThreadExecutor(), "inbound");
|
||||
this.outboundScheduler = Schedulers.fromExecutorService(Executors.newSingleThreadExecutor(), "outbound");
|
||||
this.errorScheduler = Schedulers.fromExecutorService(Executors.newSingleThreadExecutor(), "error");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -81,7 +99,8 @@ public class StdioServerTransport extends AbstractMcpTransport {
|
|||
// Start the process
|
||||
try {
|
||||
this.process = processBuilder.start();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException("Failed to start process with command: " + fullCommand, e);
|
||||
}
|
||||
|
||||
|
|
@ -110,7 +129,8 @@ public class StdioServerTransport extends AbstractMcpTransport {
|
|||
public void awaitForExit() {
|
||||
try {
|
||||
this.process.waitFor();
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new RuntimeException("Process interrupted", e);
|
||||
}
|
||||
}
|
||||
|
|
@ -124,15 +144,18 @@ public class StdioServerTransport extends AbstractMcpTransport {
|
|||
System.out.println("Received error line: " + line);
|
||||
// TODO: handle errors, etc.
|
||||
this.getErrorSink().tryEmitNext(line);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
}
|
||||
catch (IOException e) {
|
||||
if (this.isRunning) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} finally {
|
||||
}
|
||||
finally {
|
||||
this.isRunning = false;
|
||||
}
|
||||
});
|
||||
|
|
@ -149,15 +172,18 @@ public class StdioServerTransport extends AbstractMcpTransport {
|
|||
// TODO: Back off, reschedule, give up?
|
||||
throw new RuntimeException("Failed to enqueue message");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
}
|
||||
catch (IOException e) {
|
||||
if (isRunning) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} finally {
|
||||
}
|
||||
finally {
|
||||
isRunning = false;
|
||||
}
|
||||
});
|
||||
|
|
@ -170,9 +196,11 @@ public class StdioServerTransport extends AbstractMcpTransport {
|
|||
// Determine message type based on specific JSON structure
|
||||
if (map.containsKey("method") && map.containsKey("id")) {
|
||||
return this.objectMapper.convertValue(map, JSONRPCRequest.class);
|
||||
} else if (map.containsKey("method") && !map.containsKey("id")) {
|
||||
}
|
||||
else if (map.containsKey("method") && !map.containsKey("id")) {
|
||||
return this.objectMapper.convertValue(map, JSONRPCNotification.class);
|
||||
} else if (map.containsKey("result") || map.containsKey("error")) {
|
||||
}
|
||||
else if (map.containsKey("result") || map.containsKey("error")) {
|
||||
return this.objectMapper.convertValue(map, JSONRPCResponse.class);
|
||||
}
|
||||
|
||||
|
|
@ -181,23 +209,24 @@ public class StdioServerTransport extends AbstractMcpTransport {
|
|||
|
||||
private void startOutboundProcessing() {
|
||||
this.getOutboundSink()
|
||||
.asFlux()
|
||||
// this bit is important since writes come from user threads and we
|
||||
// want to ensure that the actual writing happens on a dedicated thread
|
||||
.publishOn(outboundScheduler)
|
||||
.handle((message, s) -> {
|
||||
if (message != null) {
|
||||
try {
|
||||
this.processWriter.write(objectMapper.writeValueAsString(message));
|
||||
this.processWriter.newLine();
|
||||
this.processWriter.flush();
|
||||
s.next(message);
|
||||
} catch (IOException e) {
|
||||
s.error(new RuntimeException(e));
|
||||
}
|
||||
.asFlux()
|
||||
// this bit is important since writes come from user threads and we
|
||||
// want to ensure that the actual writing happens on a dedicated thread
|
||||
.publishOn(outboundScheduler)
|
||||
.handle((message, s) -> {
|
||||
if (message != null) {
|
||||
try {
|
||||
this.processWriter.write(objectMapper.writeValueAsString(message));
|
||||
this.processWriter.newLine();
|
||||
this.processWriter.flush();
|
||||
s.next(message);
|
||||
}
|
||||
})
|
||||
.subscribe();
|
||||
catch (IOException e) {
|
||||
s.error(new RuntimeException(e));
|
||||
}
|
||||
}
|
||||
})
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -205,41 +234,40 @@ public class StdioServerTransport extends AbstractMcpTransport {
|
|||
|
||||
this.isRunning = false;
|
||||
|
||||
return Mono.whenDelayError(
|
||||
Mono.fromRunnable(() -> {
|
||||
try {
|
||||
this.processErrorReader.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
return Mono.whenDelayError(Mono.fromRunnable(() -> {
|
||||
try {
|
||||
this.processErrorReader.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}).subscribeOn(errorScheduler), Mono.fromRunnable(() -> {
|
||||
try {
|
||||
this.processReader.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}).subscribeOn(inboundScheduler), Mono.fromRunnable(() -> {
|
||||
try {
|
||||
this.processWriter.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}).subscribeOn(outboundScheduler))
|
||||
.then(Mono.whenDelayError(inboundScheduler.disposeGracefully(), outboundScheduler.disposeGracefully(),
|
||||
errorScheduler.disposeGracefully()))
|
||||
.then(Mono.fromRunnable(() -> {
|
||||
if (this.process != null) {
|
||||
var process = this.process.destroyForcibly();
|
||||
if (process.exitValue() != 0) {
|
||||
throw new RuntimeException("Failed to destroy process");
|
||||
}
|
||||
}).subscribeOn(errorScheduler),
|
||||
Mono.fromRunnable(() -> {
|
||||
try {
|
||||
this.processReader.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}).subscribeOn(inboundScheduler),
|
||||
Mono.fromRunnable(() -> {
|
||||
try {
|
||||
this.processWriter.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}).subscribeOn(outboundScheduler))
|
||||
.then(Mono.whenDelayError(inboundScheduler.disposeGracefully(),
|
||||
outboundScheduler.disposeGracefully(),
|
||||
errorScheduler.disposeGracefully()))
|
||||
.then(Mono.fromRunnable(() -> {
|
||||
if (this.process != null) {
|
||||
var process = this.process.destroyForcibly();
|
||||
if (process.exitValue() != 0) {
|
||||
throw new RuntimeException("Failed to destroy process");
|
||||
}
|
||||
}
|
||||
}))
|
||||
.then()
|
||||
.subscribeOn(Schedulers.boundedElastic());
|
||||
}
|
||||
}))
|
||||
.then()
|
||||
.subscribeOn(Schedulers.boundedElastic());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,18 +1,19 @@
|
|||
/*
|
||||
* Copyright 2024 - 2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2024-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.mcp.client.util;
|
||||
|
||||
import reactor.util.annotation.Nullable;
|
||||
|
|
@ -23,26 +24,26 @@ import reactor.util.annotation.Nullable;
|
|||
*/
|
||||
|
||||
public class Assert {
|
||||
/**
|
||||
* Assert that an object is not {@code null}.
|
||||
*
|
||||
* <pre class="code">
|
||||
* Assert.notNull(clazz, "The class must not be null");
|
||||
* </pre>
|
||||
*
|
||||
* @param object the object to check
|
||||
* @param message the exception message to use if the assertion fails
|
||||
* @throws IllegalArgumentException if the object is {@code null}
|
||||
*/
|
||||
public static void notNull(@Nullable Object object, String message) {
|
||||
if (object == null) {
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given String contains valid text content; that is, it must not
|
||||
* be {@code null} and must contain at least one non-whitespace character.
|
||||
/**
|
||||
* Assert that an object is not {@code null}.
|
||||
*
|
||||
* <pre class="code">
|
||||
* Assert.notNull(clazz, "The class must not be null");
|
||||
* </pre>
|
||||
* @param object the object to check
|
||||
* @param message the exception message to use if the assertion fails
|
||||
* @throws IllegalArgumentException if the object is {@code null}
|
||||
*/
|
||||
public static void notNull(@Nullable Object object, String message) {
|
||||
if (object == null) {
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given String contains valid text content; that is, it must not be
|
||||
* {@code null} and must contain at least one non-whitespace character.
|
||||
* <pre class="code">Assert.hasText(name, "'name' must not be empty");</pre>
|
||||
* @param text the String to check
|
||||
* @param message the exception message to use if the assertion fails
|
||||
|
|
@ -57,12 +58,13 @@ public class Assert {
|
|||
|
||||
/**
|
||||
* Check whether the given {@code String} contains actual <em>text</em>.
|
||||
* <p>More specifically, this method returns {@code true} if the
|
||||
* {@code String} is not {@code null}, its length is greater than 0,
|
||||
* and it contains at least one non-whitespace character.
|
||||
* <p>
|
||||
* More specifically, this method returns {@code true} if the {@code String} is not
|
||||
* {@code null}, its length is greater than 0, and it contains at least one
|
||||
* non-whitespace character.
|
||||
* @param str the {@code String} to check (may be {@code null})
|
||||
* @return {@code true} if the {@code String} is not {@code null}, its
|
||||
* length is greater than 0, and it does not contain whitespace only
|
||||
* @return {@code true} if the {@code String} is not {@code null}, its length is
|
||||
* greater than 0, and it does not contain whitespace only
|
||||
* @see #hasText(CharSequence)
|
||||
* @see #hasLength(String)
|
||||
* @see Character#isWhitespace
|
||||
|
|
@ -71,5 +73,4 @@ public class Assert {
|
|||
return (str != null && !str.isBlank());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
/*
|
||||
* Copyright 2024 - 2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
* Copyright 2024-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.mcp.spec;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
|
@ -20,6 +21,7 @@ import java.util.function.Consumer;
|
|||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.Sinks;
|
||||
|
||||
import org.springframework.ai.mcp.client.util.Assert;
|
||||
import org.springframework.ai.mcp.spec.McpSchema.JSONRPCMessage;
|
||||
|
||||
|
|
@ -32,14 +34,17 @@ public abstract class AbstractMcpTransport implements McpTransport {
|
|||
protected final ObjectMapper objectMapper;
|
||||
|
||||
private final Sinks.Many<String> errorSink;
|
||||
|
||||
private final Sinks.Many<JSONRPCMessage> inboundSink;
|
||||
|
||||
private final Sinks.Many<JSONRPCMessage> outboundSink;
|
||||
|
||||
private Consumer<JSONRPCMessage> inboundMessageHandler = message -> System.out
|
||||
.println("Received message: " + message);
|
||||
.println("Received message: " + message);
|
||||
|
||||
private Consumer<String> errorHandler = error -> System.err.println("Received error: " + error);
|
||||
|
||||
public AbstractMcpTransport() {
|
||||
public AbstractMcpTransport() {
|
||||
this(new ObjectMapper());
|
||||
}
|
||||
|
||||
|
|
@ -66,8 +71,7 @@ public abstract class AbstractMcpTransport implements McpTransport {
|
|||
}
|
||||
|
||||
private void handleIncomingMessages() {
|
||||
this.inboundSink.asFlux()
|
||||
.subscribe(message -> this.inboundMessageHandler.accept(message));
|
||||
this.inboundSink.asFlux().subscribe(message -> this.inboundMessageHandler.accept(message));
|
||||
}
|
||||
|
||||
private void handleIncomingErrors() {
|
||||
|
|
@ -112,8 +116,10 @@ public abstract class AbstractMcpTransport implements McpTransport {
|
|||
// In this approach we delegate the retry and the backpressure onto the
|
||||
// caller. This might be enough for most cases.
|
||||
return Mono.empty();
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return Mono.error(new RuntimeException("Failed to enqueue message"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
* Copyright 2024-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.mcp.spec;
|
||||
|
||||
import java.time.Duration;
|
||||
|
|
@ -9,11 +25,12 @@ import com.fasterxml.jackson.core.type.TypeReference;
|
|||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.MonoSink;
|
||||
|
||||
import org.springframework.ai.mcp.client.util.Assert;
|
||||
|
||||
/**
|
||||
* Implementation of the MCP client session.
|
||||
*
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Dariusz Jędrzejczyk
|
||||
*/
|
||||
|
|
@ -39,16 +56,19 @@ public class DefaultMcpSession implements McpSession {
|
|||
|
||||
this.transport.setInboudMessageHandler(message -> {
|
||||
|
||||
if ( message instanceof McpSchema.JSONRPCResponse response) {
|
||||
if (message instanceof McpSchema.JSONRPCResponse response) {
|
||||
var sink = pendingResponses.remove(response.id());
|
||||
if (sink == null) {
|
||||
System.out.println("Unexpected response for unkown id " + response.id());
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
sink.success(response);
|
||||
}
|
||||
} else if ( message instanceof McpSchema.JSONRPCRequest request) {
|
||||
}
|
||||
else if (message instanceof McpSchema.JSONRPCRequest request) {
|
||||
System.out.println("Client does not yet support server requests");
|
||||
} else if ( message instanceof McpSchema.JSONRPCNotification notification) {
|
||||
}
|
||||
else if (message instanceof McpSchema.JSONRPCNotification notification) {
|
||||
System.out.println("Notifications not yet supported");
|
||||
}
|
||||
});
|
||||
|
|
@ -70,23 +90,26 @@ public class DefaultMcpSession implements McpSession {
|
|||
// TODO: This is non-blocking, but it's actually a synchronous call,
|
||||
// perhaps there's no need to make it return Mono?
|
||||
this.transport.sendMessage(jsonrpcRequest)
|
||||
// TODO: It's most efficient to create a dedicated
|
||||
// Subscriber here
|
||||
.subscribe(v -> {
|
||||
}, e -> {
|
||||
this.pendingResponses.remove(requestId);
|
||||
sink.error(e);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
// TODO: It's most efficient to create a dedicated
|
||||
// Subscriber here
|
||||
.subscribe(v -> {
|
||||
}, e -> {
|
||||
this.pendingResponses.remove(requestId);
|
||||
sink.error(e);
|
||||
});
|
||||
}
|
||||
catch (Exception e) {
|
||||
sink.error(e);
|
||||
}
|
||||
}).timeout(this.requestTimeout).handle((jsonRpcResponse, s) -> {
|
||||
if (jsonRpcResponse.error() != null) {
|
||||
s.error(new McpError(jsonRpcResponse.error()));
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (typeRef.getType().getTypeName().equals("java.lang.Void")) {
|
||||
s.complete();
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
s.next(this.objectMapper.convertValue(jsonRpcResponse.result(), typeRef));
|
||||
}
|
||||
}
|
||||
|
|
@ -99,7 +122,8 @@ public class DefaultMcpSession implements McpSession {
|
|||
method, params);
|
||||
try {
|
||||
this.transport.sendMessage(jsonrpcNotification);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
return Mono.error(new McpError(e));
|
||||
}
|
||||
return Mono.empty();
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
/*
|
||||
* Copyright 2024 - 2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
* Copyright 2024-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.mcp.spec;
|
||||
|
||||
import java.util.List;
|
||||
|
|
@ -57,7 +58,8 @@ public class McpSchema {
|
|||
|
||||
}
|
||||
|
||||
public sealed interface Request permits InitializeRequest, CallToolRequest, CreateMessageRequest, CompleteRequest, GetPromptRequest {
|
||||
public sealed interface Request
|
||||
permits InitializeRequest, CallToolRequest, CreateMessageRequest, CompleteRequest, GetPromptRequest {
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -178,8 +180,7 @@ 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 {
|
||||
|
|
@ -189,18 +190,13 @@ 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
|
||||
|
|
@ -211,16 +207,13 @@ 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)
|
||||
|
|
@ -264,13 +257,11 @@ 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
|
||||
|
|
@ -292,14 +283,12 @@ 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();
|
||||
|
|
@ -309,11 +298,10 @@ 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
|
||||
|
|
@ -325,13 +313,11 @@ 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
|
||||
|
|
@ -346,9 +332,9 @@ public class McpSchema {
|
|||
/**
|
||||
* A prompt or prompt template that the server offers.
|
||||
*
|
||||
* @param name The name of the prompt or prompt template.
|
||||
* @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.
|
||||
* @param arguments A list of arguments to use for templating the prompt.
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_ABSENT)
|
||||
public record Prompt( // @formatter:off
|
||||
|
|
@ -360,9 +346,9 @@ public class McpSchema {
|
|||
/**
|
||||
* Describes an argument that a prompt can accept.
|
||||
*
|
||||
* @param name The name of the argument.
|
||||
* @param name The name of the argument.
|
||||
* @param description A human-readable description of the argument.
|
||||
* @param required Whether this argument must be provided.
|
||||
* @param required Whether this argument must be provided.
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_ABSENT)
|
||||
public record PromptArgument( // @formatter:off
|
||||
|
|
@ -374,12 +360,10 @@ public class McpSchema {
|
|||
/**
|
||||
* Describes a message returned as part of a prompt.
|
||||
*
|
||||
* This is similar to `SamplingMessage`, but also supports the embedding of
|
||||
* resources
|
||||
* 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 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)
|
||||
|
|
@ -399,8 +383,8 @@ public class McpSchema {
|
|||
|
||||
/**
|
||||
* Used by the client to get a prompt provided by the server.
|
||||
*
|
||||
* @param name The name of the prompt or prompt template.
|
||||
*
|
||||
* @param name The name of the prompt or prompt template.
|
||||
* @param arguments Arguments to use for templating the prompt.
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_ABSENT)
|
||||
|
|
@ -411,7 +395,7 @@ public class McpSchema {
|
|||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
/*
|
||||
* Copyright 2024 - 2024 the original author or authors.
|
||||
* Copyright 2024-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.mcp.spec;
|
||||
|
||||
import java.time.Duration;
|
||||
|
|
@ -23,65 +24,72 @@ import reactor.core.publisher.Mono;
|
|||
|
||||
/**
|
||||
* Represents a Model Control Protocol (MCP) session that handles communication between
|
||||
* clients and the server. This interface provides methods for sending requests
|
||||
* and notifications, as well as managing the session lifecycle.
|
||||
*
|
||||
* <p>The session operates asynchronously using Project Reactor's {@link Mono} type
|
||||
* for non-blocking operations. It supports both request-response patterns and
|
||||
* one-way notifications.</p>
|
||||
* clients and the server. This interface provides methods for sending requests and
|
||||
* notifications, as well as managing the session lifecycle.
|
||||
*
|
||||
* <p>
|
||||
* The session operates asynchronously using Project Reactor's {@link Mono} type for
|
||||
* non-blocking operations. It supports both request-response patterns and one-way
|
||||
* notifications.
|
||||
* </p>
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Dariusz Jędrzejczyk
|
||||
*/
|
||||
public interface McpSession {
|
||||
|
||||
/**
|
||||
* Sends a request to the model server and expects a response of type T.
|
||||
*
|
||||
* <p>This method handles the request-response pattern where a response is expected
|
||||
* from the server. The response type is determined by the provided TypeReference.</p>
|
||||
*
|
||||
* @param <T> the type of the expected response
|
||||
* @param method the name of the method to be called on the server
|
||||
* @param requestParams the parameters to be sent with the request
|
||||
* @param typeRef the TypeReference describing the expected response type
|
||||
* @return a Mono that will emit the response when received
|
||||
*/
|
||||
<T> Mono<T> sendRequest(String method, Object requestParams, TypeReference<T> typeRef);
|
||||
/**
|
||||
* Sends a request to the model server and expects a response of type T.
|
||||
*
|
||||
* <p>
|
||||
* This method handles the request-response pattern where a response is expected from
|
||||
* the server. The response type is determined by the provided TypeReference.
|
||||
* </p>
|
||||
* @param <T> the type of the expected response
|
||||
* @param method the name of the method to be called on the server
|
||||
* @param requestParams the parameters to be sent with the request
|
||||
* @param typeRef the TypeReference describing the expected response type
|
||||
* @return a Mono that will emit the response when received
|
||||
*/
|
||||
<T> Mono<T> sendRequest(String method, Object requestParams, TypeReference<T> typeRef);
|
||||
|
||||
/**
|
||||
* Sends a notification to the model server without parameters.
|
||||
*
|
||||
* <p>This method implements the notification pattern where no response is expected
|
||||
* from the server. It's useful for fire-and-forget scenarios.</p>
|
||||
*
|
||||
* @param method the name of the notification method to be called on the server
|
||||
* @return a Mono that completes when the notification has been sent
|
||||
*/
|
||||
/**
|
||||
* Sends a notification to the model server without parameters.
|
||||
*
|
||||
* <p>
|
||||
* This method implements the notification pattern where no response is expected from
|
||||
* the server. It's useful for fire-and-forget scenarios.
|
||||
* </p>
|
||||
* @param method the name of the notification method to be called on the server
|
||||
* @return a Mono that completes when the notification has been sent
|
||||
*/
|
||||
default Mono<Void> sendNotification(String method) {
|
||||
return sendNotification(method, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a notification to the model server with parameters.
|
||||
*
|
||||
* <p>Similar to {@link #sendNotification(String)} but allows sending additional
|
||||
* parameters with the notification.</p>
|
||||
*
|
||||
* @param method the name of the notification method to be called on the server
|
||||
* @param params a map of parameters to be sent with the notification
|
||||
* @return a Mono that completes when the notification has been sent
|
||||
*/
|
||||
Mono<Void> sendNotification(String method, Map<String, Object> params);
|
||||
/**
|
||||
* Sends a notification to the model server with parameters.
|
||||
*
|
||||
* <p>
|
||||
* Similar to {@link #sendNotification(String)} but allows sending additional
|
||||
* parameters with the notification.
|
||||
* </p>
|
||||
* @param method the name of the notification method to be called on the server
|
||||
* @param params a map of parameters to be sent with the notification
|
||||
* @return a Mono that completes when the notification has been sent
|
||||
*/
|
||||
Mono<Void> sendNotification(String method, Map<String, Object> params);
|
||||
|
||||
/**
|
||||
* Gracefully closes the session with a specified timeout.
|
||||
*
|
||||
* <p>
|
||||
* This method attempts to close the session cleanly, waiting for any pending
|
||||
* operations to complete up to the specified timeout duration.
|
||||
* </p>
|
||||
* @param timeout the maximum duration to wait for the session to close
|
||||
* @return a Mono that completes when the session has been closed
|
||||
*/
|
||||
Mono<Void> closeGracefully(Duration timeout);
|
||||
|
||||
/**
|
||||
* Gracefully closes the session with a specified timeout.
|
||||
*
|
||||
* <p>This method attempts to close the session cleanly, waiting for any pending
|
||||
* operations to complete up to the specified timeout duration.</p>
|
||||
*
|
||||
* @param timeout the maximum duration to wait for the session to close
|
||||
* @return a Mono that completes when the session has been closed
|
||||
*/
|
||||
Mono<Void> closeGracefully(Duration timeout);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +1,37 @@
|
|||
/*
|
||||
* Copyright 2024 - 2024 the original author or authors.
|
||||
*
|
||||
/*
|
||||
* Copyright 2024-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.mcp.spec;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.ai.mcp.spec.McpSchema.JSONRPCMessage;
|
||||
|
||||
/**
|
||||
* Defines the asynchronous transport layer for the Model Context Protocol
|
||||
* (MCP).
|
||||
*
|
||||
* Defines the asynchronous transport layer for the Model Context Protocol (MCP).
|
||||
*
|
||||
* <p>
|
||||
* The McpTransport interface provides the foundation for implementing custom
|
||||
* transport
|
||||
* mechanisms in the Model Context Protocol. It handles the bidirectional
|
||||
* communication between
|
||||
* the client and server components, supporting asynchronous message exchange
|
||||
* The McpTransport interface provides the foundation for implementing custom transport
|
||||
* mechanisms in the Model Context Protocol. It handles the bidirectional communication
|
||||
* between the client and server components, supporting asynchronous message exchange
|
||||
* using JSON-RPC format.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* Implementations of this interface are responsible for:
|
||||
* </p>
|
||||
|
|
@ -41,11 +40,10 @@ import org.springframework.ai.mcp.spec.McpSchema.JSONRPCMessage;
|
|||
* <li>Handling incoming messages and errors from the server</li>
|
||||
* <li>Sending outbound messages to the server</li>
|
||||
* </ul>
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* The transport layer is designed to be protocol-agnostic, allowing for various
|
||||
* implementations
|
||||
* such as WebSocket, HTTP, or custom protocols.
|
||||
* implementations such as WebSocket, HTTP, or custom protocols.
|
||||
* </p>
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
|
|
@ -53,78 +51,69 @@ import org.springframework.ai.mcp.spec.McpSchema.JSONRPCMessage;
|
|||
*/
|
||||
public interface McpTransport {
|
||||
|
||||
/**
|
||||
* Initializes and starts the transport connection.
|
||||
*
|
||||
* <p>
|
||||
* This method should be called before any message exchange can occur. It sets
|
||||
* up
|
||||
* the necessary resources and establishes the connection to the server.
|
||||
* </p>
|
||||
*/
|
||||
void start();
|
||||
/**
|
||||
* Initializes and starts the transport connection.
|
||||
*
|
||||
* <p>
|
||||
* This method should be called before any message exchange can occur. It sets up the
|
||||
* necessary resources and establishes the connection to the server.
|
||||
* </p>
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* Closes the transport connection and releases any associated resources.
|
||||
*
|
||||
* <p>
|
||||
* This method ensures proper cleanup of resources when the transport is no
|
||||
* longer needed.
|
||||
* It should handle the graceful shutdown of any active connections.
|
||||
* </p>
|
||||
*/
|
||||
default void close() {
|
||||
this.closeGracefully().subscribe();
|
||||
}
|
||||
/**
|
||||
* Closes the transport connection and releases any associated resources.
|
||||
*
|
||||
* <p>
|
||||
* This method ensures proper cleanup of resources when the transport is no longer
|
||||
* needed. It should handle the graceful shutdown of any active connections.
|
||||
* </p>
|
||||
*/
|
||||
default void close() {
|
||||
this.closeGracefully().subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the transport connection and releases any associated resources
|
||||
* asynchronously.
|
||||
*
|
||||
* @return a {@link Mono<Void>} that completes when the connection has been
|
||||
* closed.
|
||||
*/
|
||||
Mono<Void> closeGracefully();
|
||||
/**
|
||||
* Closes the transport connection and releases any associated resources
|
||||
* asynchronously.
|
||||
* @return a {@link Mono<Void>} that completes when the connection has been closed.
|
||||
*/
|
||||
Mono<Void> closeGracefully();
|
||||
|
||||
/**
|
||||
* Sets the handler for processing incoming messages from the server.
|
||||
*
|
||||
* <p>
|
||||
* The provided handler will be called whenever a new message is received from
|
||||
* the server.
|
||||
* Messages are expected to follow the JSON-RPC format as defined in the MCP
|
||||
* specification.
|
||||
* </p>
|
||||
*
|
||||
* @param inboundMessageHandler a consumer that processes incoming
|
||||
* {@link JSONRPCMessage}s
|
||||
*/
|
||||
void setInboudMessageHandler(Consumer<JSONRPCMessage> inboundMessageHandler);
|
||||
/**
|
||||
* Sets the handler for processing incoming messages from the server.
|
||||
*
|
||||
* <p>
|
||||
* The provided handler will be called whenever a new message is received from the
|
||||
* server. Messages are expected to follow the JSON-RPC format as defined in the MCP
|
||||
* specification.
|
||||
* </p>
|
||||
* @param inboundMessageHandler a consumer that processes incoming
|
||||
* {@link JSONRPCMessage}s
|
||||
*/
|
||||
void setInboudMessageHandler(Consumer<JSONRPCMessage> inboundMessageHandler);
|
||||
|
||||
/**
|
||||
* Sets the handler for processing transport-level errors.
|
||||
*
|
||||
* <p>
|
||||
* The provided handler will be called when errors occur during transport
|
||||
* operations,
|
||||
* such as connection failures or protocol violations.
|
||||
* </p>
|
||||
*
|
||||
* @param inboundErrorHandler a consumer that processes error messages
|
||||
*/
|
||||
void setInboundErrorHandler(Consumer<String> inboundErrorHandler);
|
||||
/**
|
||||
* Sets the handler for processing transport-level errors.
|
||||
*
|
||||
* <p>
|
||||
* The provided handler will be called when errors occur during transport operations,
|
||||
* such as connection failures or protocol violations.
|
||||
* </p>
|
||||
* @param inboundErrorHandler a consumer that processes error messages
|
||||
*/
|
||||
void setInboundErrorHandler(Consumer<String> inboundErrorHandler);
|
||||
|
||||
/**
|
||||
* Sends a message to the server asynchronously.
|
||||
*
|
||||
* <p>
|
||||
* This method handles the transmission of messages to the server in an asynchronous
|
||||
* manner. Messages are sent in JSON-RPC format as specified by the MCP protocol.
|
||||
* </p>
|
||||
* @param message the {@link JSONRPCMessage} to be sent to the server
|
||||
* @return a {@link Mono<Void>} that completes when the message has been sent
|
||||
*/
|
||||
Mono<Void> sendMessage(JSONRPCMessage message);
|
||||
|
||||
/**
|
||||
* Sends a message to the server asynchronously.
|
||||
*
|
||||
* <p>
|
||||
* This method handles the transmission of messages to the server in an
|
||||
* asynchronous manner.
|
||||
* Messages are sent in JSON-RPC format as specified by the MCP protocol.
|
||||
* </p>
|
||||
*
|
||||
* @param message the {@link JSONRPCMessage} to be sent to the server
|
||||
* @return a {@link Mono<Void>} that completes when the message has been sent
|
||||
*/
|
||||
Mono<Void> sendMessage(JSONRPCMessage message);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,25 @@
|
|||
/*
|
||||
* Copyright 2024 - 2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
* Copyright 2024-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.mcp.attic;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.springframework.ai.mcp.client.McpClient;
|
||||
import org.springframework.ai.mcp.client.McpSyncClient;
|
||||
import org.springframework.ai.mcp.client.stdio.ServerParameters;
|
||||
|
|
@ -35,13 +37,11 @@ public class ClientSessionTests2 {
|
|||
public static void main(String[] args) {
|
||||
|
||||
var stdioParams = ServerParameters.builder("uv")
|
||||
.args("--directory", "dir", "run", "mcp-server-sqlite",
|
||||
"--db-path", "~/test.db")
|
||||
.args("--directory", "dir", "run", "mcp-server-sqlite", "--db-path", "~/test.db")
|
||||
.build();
|
||||
|
||||
try (McpSyncClient clientSession =
|
||||
McpClient.sync(new StdioServerTransport(stdioParams),
|
||||
Duration.ofSeconds(10), new ObjectMapper())) {
|
||||
try (McpSyncClient clientSession = McpClient.sync(new StdioServerTransport(stdioParams), Duration.ofSeconds(10),
|
||||
new ObjectMapper())) {
|
||||
|
||||
clientSession.initialize();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,25 @@
|
|||
/*
|
||||
* Copyright 2024 - 2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
* Copyright 2024-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.mcp.attic;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.springframework.ai.mcp.client.McpClient;
|
||||
import org.springframework.ai.mcp.client.McpSyncClient;
|
||||
import org.springframework.ai.mcp.client.stdio.ServerParameters;
|
||||
|
|
@ -35,16 +37,14 @@ public class ClientSessionTests3 {
|
|||
public static void main(String[] args) {
|
||||
|
||||
var stdioParams = ServerParameters.builder("uv")
|
||||
.args("--directory", "dir", "run", "mcp-server-sqlite",
|
||||
"--db-path", "~/test.db")
|
||||
.args("--directory", "dir", "run", "mcp-server-sqlite", "--db-path", "~/test.db")
|
||||
.build();
|
||||
|
||||
|
||||
McpSyncClient clientSession = null;
|
||||
try {
|
||||
|
||||
clientSession = McpClient.sync(new StdioServerTransport(stdioParams),
|
||||
Duration.ofSeconds(10), new ObjectMapper());
|
||||
clientSession = McpClient.sync(new StdioServerTransport(stdioParams), Duration.ofSeconds(10),
|
||||
new ObjectMapper());
|
||||
|
||||
clientSession.initialize();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
/*
|
||||
* Copyright 2024 - 2024 the original author or authors.
|
||||
* Copyright 2024-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.mcp.client;
|
||||
|
||||
import java.time.Duration;
|
||||
|
|
@ -23,6 +24,7 @@ import org.junit.jupiter.api.AfterEach;
|
|||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.Timeout;
|
||||
|
||||
import org.springframework.ai.mcp.client.stdio.ServerParameters;
|
||||
import org.springframework.ai.mcp.client.stdio.StdioServerTransport;
|
||||
import org.springframework.ai.mcp.spec.McpSchema.CallToolRequest;
|
||||
|
|
@ -54,8 +56,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());
|
||||
|
|
@ -73,13 +75,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
|
||||
|
|
@ -152,16 +154,15 @@ class McpAsyncClientTests {
|
|||
@Timeout(15)
|
||||
void testListPrompts() {
|
||||
mcpAsyncClient.listPrompts(null).subscribe(prompts -> {
|
||||
assertThat(prompts).isNotNull()
|
||||
.satisfies(result -> {
|
||||
assertThat(result.prompts()).isNotNull();
|
||||
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();
|
||||
}
|
||||
});
|
||||
if (!result.prompts().isEmpty()) {
|
||||
Prompt firstPrompt = result.prompts().get(0);
|
||||
assertThat(firstPrompt.name()).isNotNull();
|
||||
assertThat(firstPrompt.description()).isNotNull();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -169,11 +170,10 @@ class McpAsyncClientTests {
|
|||
@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();
|
||||
});
|
||||
assertThat(prompt).isNotNull().satisfies(result -> {
|
||||
assertThat(result.description()).isNotNull();
|
||||
assertThat(result.messages()).isNotEmpty();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
/*
|
||||
* Copyright 2024 - 2024 the original author or authors.
|
||||
* Copyright 2024-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.mcp.client;
|
||||
|
||||
import java.time.Duration;
|
||||
|
|
@ -23,6 +24,7 @@ import org.junit.jupiter.api.AfterEach;
|
|||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.Timeout;
|
||||
|
||||
import org.springframework.ai.mcp.client.stdio.ServerParameters;
|
||||
import org.springframework.ai.mcp.client.stdio.StdioServerTransport;
|
||||
import org.springframework.ai.mcp.spec.McpSchema.CallToolRequest;
|
||||
|
|
@ -104,7 +106,7 @@ class McpSyncClientTests {
|
|||
CallToolResult toolResult = mcpSyncClient.callTool(new CallToolRequest("add", Map.of("a", 3, "b", 4)));
|
||||
|
||||
assertThat(toolResult).isNotNull().satisfies(result -> {
|
||||
|
||||
|
||||
assertThat(result.content()).hasSize(1);
|
||||
|
||||
TextContent content = (TextContent) result.content().get(0);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
/*
|
||||
* Copyright 2024 - 2024 the original author or authors.
|
||||
* Copyright 2024-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.mcp.client.stdio;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
|
|
@ -129,4 +130,5 @@ public class StdioServerTransportTests {
|
|||
assertThat(exception.getMessage()).contains("Process input or output stream is null");
|
||||
verify(mockProcess).destroy();
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue