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>
|
<build>
|
||||||
<plugins>
|
<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>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-site-plugin</artifactId>
|
<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>
|
<parent>
|
||||||
<groupId>org.springframework.experimental</groupId>
|
<groupId>org.springframework.experimental</groupId>
|
||||||
<artifactId>spring-ai-mcp</artifactId>
|
<artifactId>spring-ai-mcp</artifactId>
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
<version>0.1.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>spring-ai-mcp-core</artifactId>
|
<artifactId>spring-ai-mcp-core</artifactId>
|
||||||
<packaging>jar</packaging>
|
<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;
|
package org.springframework.ai.mcp.client;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
@ -5,14 +20,15 @@ import java.time.Duration;
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import org.springframework.ai.mcp.spec.DefaultMcpSession;
|
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.McpError;
|
||||||
import org.springframework.ai.mcp.spec.McpSchema;
|
import org.springframework.ai.mcp.spec.McpSchema;
|
||||||
import org.springframework.ai.mcp.spec.McpSchema.GetPromptRequest;
|
import org.springframework.ai.mcp.spec.McpSchema.GetPromptRequest;
|
||||||
import org.springframework.ai.mcp.spec.McpSchema.GetPromptResult;
|
import org.springframework.ai.mcp.spec.McpSchema.GetPromptResult;
|
||||||
import org.springframework.ai.mcp.spec.McpSchema.ListPromptsResult;
|
import org.springframework.ai.mcp.spec.McpSchema.ListPromptsResult;
|
||||||
import org.springframework.ai.mcp.spec.McpSchema.PaginatedRequest;
|
import org.springframework.ai.mcp.spec.McpSchema.PaginatedRequest;
|
||||||
|
import org.springframework.ai.mcp.spec.McpTransport;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Dariusz Jędrzejczyk
|
* @author Dariusz Jędrzejczyk
|
||||||
|
|
@ -32,8 +48,7 @@ public class McpAsyncClient extends DefaultMcpSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The initialization phase MUST be the first interaction between client and
|
* The initialization phase MUST be the first interaction between client and server.
|
||||||
* server.
|
|
||||||
* During this phase, the client and server:
|
* During this phase, the client and server:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Establish protocol version compatibility</li>
|
* <li>Establish protocol version compatibility</li>
|
||||||
|
|
@ -41,8 +56,7 @@ public class McpAsyncClient extends DefaultMcpSession {
|
||||||
* <li>Share implementation details</li>
|
* <li>Share implementation details</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* <br/>
|
* <br/>
|
||||||
* The client MUST initiate this phase by sending an initialize request
|
* The client MUST initiate this phase by sending an initialize request containing:
|
||||||
* containing:
|
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>The protocol version the client supports</li>
|
* <li>The protocol version the client supports</li>
|
||||||
* <li>The client's capabilities</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:
|
* The server MUST respond with its own capabilities and information:
|
||||||
* {@link McpSchema.ServerCapabilities}. <br/>
|
* {@link McpSchema.ServerCapabilities}. <br/>
|
||||||
* After successful initialization, the client MUST send an initialized
|
* After successful initialization, the client MUST send an initialized notification
|
||||||
* notification
|
|
||||||
* to indicate it is ready to begin normal operations.
|
* to indicate it is ready to begin normal operations.
|
||||||
*
|
*
|
||||||
* <br/>
|
* <br/>
|
||||||
|
|
@ -60,7 +73,6 @@ public class McpAsyncClient extends DefaultMcpSession {
|
||||||
* <a href=
|
* <a href=
|
||||||
* "https://github.com/modelcontextprotocol/specification/blob/main/docs/specification/basic/lifecycle.md#initialization">Initialization
|
* "https://github.com/modelcontextprotocol/specification/blob/main/docs/specification/basic/lifecycle.md#initialization">Initialization
|
||||||
* Spec</a>
|
* Spec</a>
|
||||||
*
|
|
||||||
* @return the initialize result.
|
* @return the initialize result.
|
||||||
*/
|
*/
|
||||||
public Mono<McpSchema.InitializeResult> initialize() {
|
public Mono<McpSchema.InitializeResult> initialize() {
|
||||||
|
|
@ -75,12 +87,11 @@ public class McpAsyncClient extends DefaultMcpSession {
|
||||||
|
|
||||||
return result.flatMap(initializeResult -> {
|
return result.flatMap(initializeResult -> {
|
||||||
if (!McpSchema.LATEST_PROTOCOL_VERSION.equals(initializeResult.protocolVersion())) {
|
if (!McpSchema.LATEST_PROTOCOL_VERSION.equals(initializeResult.protocolVersion())) {
|
||||||
return Mono.error(
|
return Mono.error(new McpError(
|
||||||
new McpError("Unsupported protocol version from the server: "
|
"Unsupported protocol version from the server: " + initializeResult.protocolVersion()));
|
||||||
+ initializeResult.protocolVersion()));
|
}
|
||||||
} else {
|
else {
|
||||||
return this.sendNotification("notifications/initialized", null)
|
return this.sendNotification("notifications/initialized", null).thenReturn(initializeResult);
|
||||||
.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.CallToolResult> CALL_TOOL_RESULT_TYPE_REF = new TypeReference<McpSchema.CallToolResult>() {
|
||||||
};
|
};
|
||||||
|
|
||||||
private static TypeReference<McpSchema.ListToolsResult> LIST_TOOLS_RESULT_TYPE_REF = new TypeReference<McpSchema.ListToolsResult>() {
|
private static TypeReference<McpSchema.ListToolsResult> LIST_TOOLS_RESULT_TYPE_REF = new TypeReference<McpSchema.ListToolsResult>() {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a tools/call request.
|
* Send a tools/call request.
|
||||||
*
|
|
||||||
* @param callToolRequest the call tool request.
|
* @param callToolRequest the call tool request.
|
||||||
* @return the call tool result.
|
* @return the call tool result.
|
||||||
*/
|
*/
|
||||||
|
|
@ -119,7 +130,6 @@ public class McpAsyncClient extends DefaultMcpSession {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a tools/list request.
|
* Send a tools/list request.
|
||||||
*
|
|
||||||
* @return the list of tools result.
|
* @return the list of tools result.
|
||||||
*/
|
*/
|
||||||
public Mono<McpSchema.ListToolsResult> listTools(String cursor) {
|
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.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.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>() {
|
private static TypeReference<McpSchema.ListResourceTemplatesResult> LIST_RESOURCE_TEMPLATES_RESULT_TYPE_REF = new TypeReference<McpSchema.ListResourceTemplatesResult>() {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a resources/list request.
|
* Send a resources/list request.
|
||||||
*
|
|
||||||
* @param cursor the cursor
|
* @param cursor the cursor
|
||||||
* @return the list of resources result.
|
* @return the list of resources result.
|
||||||
*/
|
*/
|
||||||
|
|
@ -150,7 +161,6 @@ public class McpAsyncClient extends DefaultMcpSession {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a resources/read request.
|
* Send a resources/read request.
|
||||||
*
|
|
||||||
* @param resource the resource to read
|
* @param resource the resource to read
|
||||||
* @return the resource content.
|
* @return the resource content.
|
||||||
*/
|
*/
|
||||||
|
|
@ -160,7 +170,6 @@ public class McpAsyncClient extends DefaultMcpSession {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a resources/read request.
|
* Send a resources/read request.
|
||||||
*
|
|
||||||
* @param readResourceRequest the read resource request.
|
* @param readResourceRequest the read resource request.
|
||||||
* @return the resource content.
|
* @return the resource content.
|
||||||
*/
|
*/
|
||||||
|
|
@ -173,7 +182,6 @@ public class McpAsyncClient extends DefaultMcpSession {
|
||||||
* templates. Arguments may be auto-completed through the completion API.
|
* templates. Arguments may be auto-completed through the completion API.
|
||||||
*
|
*
|
||||||
* Request a list of resource templates the server has.
|
* Request a list of resource templates the server has.
|
||||||
*
|
|
||||||
* @param cursor the cursor
|
* @param cursor the cursor
|
||||||
* @return the list of resource templates result.
|
* @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,
|
* List Changed Notification. When the list of available resources changes, servers
|
||||||
* servers that declared the listChanged capability SHOULD send a notification:
|
* that declared the listChanged capability SHOULD send a notification:
|
||||||
*/
|
*/
|
||||||
public Mono<Void> sendResourcesListChanged() {
|
public Mono<Void> sendResourcesListChanged() {
|
||||||
return this.sendNotification("notifications/resources/list_changed");
|
return this.sendNotification("notifications/resources/list_changed");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscriptions. The protocol supports optional subscriptions to resource
|
* Subscriptions. The protocol supports optional subscriptions to resource changes.
|
||||||
* changes.
|
* Clients can subscribe to specific resources and receive notifications when they
|
||||||
* Clients can subscribe to specific resources and receive notifications when
|
* change.
|
||||||
* they change.
|
|
||||||
*
|
*
|
||||||
* Send a resources/subscribe request.
|
* Send a resources/subscribe request.
|
||||||
*
|
* @param subscribeRequest the subscribe request contains the uri of the resource to
|
||||||
* @param subscribeRequest the subscribe request contains the uri of the
|
* subscribe to.
|
||||||
* resource to subscribe to.
|
|
||||||
*/
|
*/
|
||||||
public Mono<Void> subscribeResource(McpSchema.SubscribeRequest subscribeRequest) {
|
public Mono<Void> subscribeResource(McpSchema.SubscribeRequest subscribeRequest) {
|
||||||
return this.sendRequest("resources/subscribe", subscribeRequest, VOID_TYPE_REFERENCE);
|
return this.sendRequest("resources/subscribe", subscribeRequest, VOID_TYPE_REFERENCE);
|
||||||
|
|
@ -207,9 +213,8 @@ public class McpAsyncClient extends DefaultMcpSession {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a resources/unsubscribe request.
|
* Send a resources/unsubscribe request.
|
||||||
*
|
* @param unsubscribeRequest the unsubscribe request contains the uri of the resource
|
||||||
* @param unsubscribeRequest the unsubscribe request contains the uri of the
|
* to unsubscribe from.
|
||||||
* resource to unsubscribe from.
|
|
||||||
*/
|
*/
|
||||||
public Mono<Void> unsubscribeResource(McpSchema.UnsubscribeRequest unsubscribeRequest) {
|
public Mono<Void> unsubscribeResource(McpSchema.UnsubscribeRequest unsubscribeRequest) {
|
||||||
return this.sendRequest("resources/unsubscribe", unsubscribeRequest, VOID_TYPE_REFERENCE);
|
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.ListPromptsResult> LIST_PROMPTS_RESULT_TYPE_REF = new TypeReference<McpSchema.ListPromptsResult>() {
|
||||||
};
|
};
|
||||||
|
|
||||||
private static TypeReference<McpSchema.GetPromptResult> GET_PROMPT_RESULT_TYPE_REF = new TypeReference<McpSchema.GetPromptResult>() {
|
private static TypeReference<McpSchema.GetPromptResult> GET_PROMPT_RESULT_TYPE_REF = new TypeReference<McpSchema.GetPromptResult>() {
|
||||||
};
|
};
|
||||||
|
|
||||||
public Mono<ListPromptsResult> listPrompts(String cursor) {
|
public Mono<ListPromptsResult> listPrompts(String cursor) {
|
||||||
return this
|
return this.sendRequest("prompts/list", new PaginatedRequest(cursor), LIST_PROMPTS_RESULT_TYPE_REF);
|
||||||
.sendRequest("prompts/list", new PaginatedRequest(cursor), LIST_PROMPTS_RESULT_TYPE_REF);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Mono<GetPromptResult> getPrompt(GetPromptRequest getPromptRequest) {
|
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
|
* (Server) An optional notification from the server to the client, informing it that
|
||||||
* that
|
* the list of prompts it offers has changed. This may be issued by servers without
|
||||||
* the list of prompts it offers has changed. This may be issued by servers
|
|
||||||
* without
|
|
||||||
* any previous subscription from the client.
|
* any previous subscription from the client.
|
||||||
*/
|
*/
|
||||||
public Mono<Void> promptListChangedNotification() {
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* 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
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* 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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.ai.mcp.client;
|
package org.springframework.ai.mcp.client;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import org.springframework.ai.mcp.spec.McpTransport;
|
import org.springframework.ai.mcp.spec.McpTransport;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory class providing static methods for creating Model Context Protocol (MCP) clients.
|
* Factory class providing static methods for creating Model Context Protocol (MCP)
|
||||||
* This class serves as the main entry point for establishing connections with MCP servers,
|
* clients. This class serves as the main entry point for establishing connections with
|
||||||
* offering both synchronous and asynchronous client implementations.
|
* MCP servers, offering both synchronous and asynchronous client implementations.
|
||||||
*
|
*
|
||||||
* <p>The class provides factory methods to create either:
|
* <p>
|
||||||
|
* The class provides factory methods to create either:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@link McpAsyncClient} for non-blocking operations
|
* <li>{@link McpAsyncClient} for non-blocking operations
|
||||||
* <li>{@link McpSyncClient} for blocking operations
|
* <li>{@link McpSyncClient} for blocking operations
|
||||||
* </ul>
|
* </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.
|
* 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 Christian Tzolov
|
||||||
* @author Dariusz Jędrzejczyk
|
* @author Dariusz Jędrzejczyk
|
||||||
*/
|
*/
|
||||||
public class McpClient {
|
public class McpClient {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Private constructor to prevent instantiation as this is a utility class
|
* Private constructor to prevent instantiation as this is a utility class containing
|
||||||
* containing only static factory methods.
|
* only static factory methods.
|
||||||
*/
|
*/
|
||||||
private McpClient() {
|
private McpClient() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: introduce a builder like:
|
// TODO: introduce a builder like:
|
||||||
// McpClient.using(transport)
|
// McpClient.using(transport)
|
||||||
// .withRequestTimeout(Duration.ofSeconds(5))
|
// .withRequestTimeout(Duration.ofSeconds(5))
|
||||||
// .withObjectMapper(objectMapper); <- or even a more sophisticated
|
// .withObjectMapper(objectMapper); <- or even a more sophisticated
|
||||||
// JSONtoPOJOCodec type
|
// JSONtoPOJOCodec type
|
||||||
// .sync();
|
// .sync();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an asynchronous MCP client with default configuration.
|
* Creates an asynchronous MCP client with default configuration.
|
||||||
*
|
* @param transport The transport layer implementation for MCP communication
|
||||||
* @param transport The transport layer implementation for MCP communication
|
* @return A new instance of {@link McpAsyncClient}
|
||||||
* @return A new instance of {@link McpAsyncClient}
|
*/
|
||||||
*/
|
public static McpAsyncClient async(McpTransport transport) {
|
||||||
public static McpAsyncClient async(McpTransport transport) {
|
return new McpAsyncClient(transport);
|
||||||
return new McpAsyncClient(transport);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an asynchronous MCP client with custom configuration.
|
* Creates an asynchronous MCP client with custom configuration.
|
||||||
*
|
* @param transport The transport layer implementation for MCP communication
|
||||||
* @param transport The transport layer implementation for MCP communication
|
* @param requestTimeout The duration to wait before timing out requests
|
||||||
* @param requestTimeout The duration to wait before timing out requests
|
* @param objectMapper The Jackson object mapper for JSON
|
||||||
* @param objectMapper The Jackson object mapper for JSON serialization/deserialization
|
* serialization/deserialization
|
||||||
* @return A new instance of {@link McpAsyncClient}
|
* @return A new instance of {@link McpAsyncClient}
|
||||||
*/
|
*/
|
||||||
public static McpAsyncClient async(McpTransport transport, Duration requestTimeout,
|
public static McpAsyncClient async(McpTransport transport, Duration requestTimeout, ObjectMapper objectMapper) {
|
||||||
ObjectMapper objectMapper) {
|
return new McpAsyncClient(transport, requestTimeout, objectMapper);
|
||||||
return new McpAsyncClient(transport, requestTimeout, objectMapper);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a synchronous MCP client with default configuration.
|
* Creates a synchronous MCP client with default configuration. This method wraps an
|
||||||
* This method wraps an asynchronous client to provide synchronous operations.
|
* asynchronous client to provide synchronous operations.
|
||||||
*
|
* @param transport The transport layer implementation for MCP communication
|
||||||
* @param transport The transport layer implementation for MCP communication
|
* @return A new instance of {@link McpSyncClient}
|
||||||
* @return A new instance of {@link McpSyncClient}
|
*/
|
||||||
*/
|
public static McpSyncClient sync(McpTransport transport) {
|
||||||
public static McpSyncClient sync(McpTransport transport) {
|
return new McpSyncClient(async(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;
|
package org.springframework.ai.mcp.client;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
@ -32,8 +48,7 @@ public class McpSyncClient implements AutoCloseable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The initialization phase MUST be the first interaction between client and
|
* The initialization phase MUST be the first interaction between client and server.
|
||||||
* server.
|
|
||||||
* During this phase, the client and server:
|
* During this phase, the client and server:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Establish protocol version compatibility</li>
|
* <li>Establish protocol version compatibility</li>
|
||||||
|
|
@ -41,8 +56,7 @@ public class McpSyncClient implements AutoCloseable {
|
||||||
* <li>Share implementation details</li>
|
* <li>Share implementation details</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* <br/>
|
* <br/>
|
||||||
* The client MUST initiate this phase by sending an initialize request
|
* The client MUST initiate this phase by sending an initialize request containing:
|
||||||
* containing:
|
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>The protocol version the client supports</li>
|
* <li>The protocol version the client supports</li>
|
||||||
* <li>The client's capabilities</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:
|
* The server MUST respond with its own capabilities and information:
|
||||||
* {@link McpSchema.ServerCapabilities}. <br/>
|
* {@link McpSchema.ServerCapabilities}. <br/>
|
||||||
* After successful initialization, the client MUST send an initialized
|
* After successful initialization, the client MUST send an initialized notification
|
||||||
* notification
|
|
||||||
* to indicate it is ready to begin normal operations.
|
* to indicate it is ready to begin normal operations.
|
||||||
*
|
*
|
||||||
* <br/>
|
* <br/>
|
||||||
|
|
@ -60,7 +73,6 @@ public class McpSyncClient implements AutoCloseable {
|
||||||
* <a href=
|
* <a href=
|
||||||
* "https://github.com/modelcontextprotocol/specification/blob/main/docs/specification/basic/lifecycle.md#initialization">Initialization
|
* "https://github.com/modelcontextprotocol/specification/blob/main/docs/specification/basic/lifecycle.md#initialization">Initialization
|
||||||
* Spec</a>
|
* Spec</a>
|
||||||
*
|
|
||||||
* @return the initialize result.
|
* @return the initialize result.
|
||||||
*/
|
*/
|
||||||
public McpSchema.InitializeResult initialize() {
|
public McpSchema.InitializeResult initialize() {
|
||||||
|
|
@ -88,7 +100,6 @@ public class McpSyncClient implements AutoCloseable {
|
||||||
// --------------------------
|
// --------------------------
|
||||||
/**
|
/**
|
||||||
* Send a tools/call request.
|
* Send a tools/call request.
|
||||||
*
|
|
||||||
* @param callToolRequest the call tool request.
|
* @param callToolRequest the call tool request.
|
||||||
* @return the call tool result.
|
* @return the call tool result.
|
||||||
*/
|
*/
|
||||||
|
|
@ -98,7 +109,6 @@ public class McpSyncClient implements AutoCloseable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a tools/list request.
|
* Send a tools/list request.
|
||||||
*
|
|
||||||
* @return the list of tools result.
|
* @return the list of tools result.
|
||||||
*/
|
*/
|
||||||
public McpSchema.ListToolsResult listTools(String cursor) {
|
public McpSchema.ListToolsResult listTools(String cursor) {
|
||||||
|
|
@ -111,7 +121,6 @@ public class McpSyncClient implements AutoCloseable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a resources/list request.
|
* Send a resources/list request.
|
||||||
*
|
|
||||||
* @param cursor the cursor
|
* @param cursor the cursor
|
||||||
* @return the list of resources result.
|
* @return the list of resources result.
|
||||||
*/
|
*/
|
||||||
|
|
@ -121,7 +130,6 @@ public class McpSyncClient implements AutoCloseable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a resources/read request.
|
* Send a resources/read request.
|
||||||
*
|
|
||||||
* @param resource the resource to read
|
* @param resource the resource to read
|
||||||
* @return the resource content.
|
* @return the resource content.
|
||||||
*/
|
*/
|
||||||
|
|
@ -131,7 +139,6 @@ public class McpSyncClient implements AutoCloseable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a resources/read request.
|
* Send a resources/read request.
|
||||||
*
|
|
||||||
* @param readResourceRequest the read resource request.
|
* @param readResourceRequest the read resource request.
|
||||||
* @return the resource content.
|
* @return the resource content.
|
||||||
*/
|
*/
|
||||||
|
|
@ -144,7 +151,6 @@ public class McpSyncClient implements AutoCloseable {
|
||||||
* templates. Arguments may be auto-completed through the completion API.
|
* templates. Arguments may be auto-completed through the completion API.
|
||||||
*
|
*
|
||||||
* Request a list of resource templates the server has.
|
* Request a list of resource templates the server has.
|
||||||
*
|
|
||||||
* @param cursor the cursor
|
* @param cursor the cursor
|
||||||
* @return the list of resource templates result.
|
* @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,
|
* List Changed Notification. When the list of available resources changes, servers
|
||||||
* servers
|
|
||||||
* that declared the listChanged capability SHOULD send a notification:
|
* that declared the listChanged capability SHOULD send a notification:
|
||||||
*/
|
*/
|
||||||
public void sendResourcesListChanged() {
|
public void sendResourcesListChanged() {
|
||||||
|
|
@ -162,14 +167,13 @@ public class McpSyncClient implements AutoCloseable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscriptions. The protocol supports optional subscriptions to resource
|
* Subscriptions. The protocol supports optional subscriptions to resource changes.
|
||||||
* changes. Clients can subscribe to specific resources and receive
|
* Clients can subscribe to specific resources and receive notifications when they
|
||||||
* notifications when they change.
|
* change.
|
||||||
*
|
*
|
||||||
* Send a resources/subscribe request.
|
* Send a resources/subscribe request.
|
||||||
*
|
* @param subscribeRequest the subscribe request contains the uri of the resource to
|
||||||
* @param subscribeRequest the subscribe request contains the uri of the
|
* subscribe to.
|
||||||
* resource to subscribe to.
|
|
||||||
*/
|
*/
|
||||||
public void subscribeResource(McpSchema.SubscribeRequest subscribeRequest) {
|
public void subscribeResource(McpSchema.SubscribeRequest subscribeRequest) {
|
||||||
this.delegate.subscribeResource(subscribeRequest).block();
|
this.delegate.subscribeResource(subscribeRequest).block();
|
||||||
|
|
@ -177,10 +181,8 @@ public class McpSyncClient implements AutoCloseable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a resources/unsubscribe request.
|
* Send a resources/unsubscribe request.
|
||||||
*
|
* @param unsubscribeRequest the unsubscribe request contains the uri of the resource
|
||||||
* @param unsubscribeRequest the unsubscribe request contains the uri of the
|
* to unsubscribe from.
|
||||||
* resource
|
|
||||||
* to unsubscribe from.
|
|
||||||
*/
|
*/
|
||||||
public void unsubscribeResource(McpSchema.UnsubscribeRequest unsubscribeRequest) {
|
public void unsubscribeResource(McpSchema.UnsubscribeRequest unsubscribeRequest) {
|
||||||
this.delegate.unsubscribeResource(unsubscribeRequest).block();
|
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
|
* (Server) An optional notification from the server to the client, informing it that
|
||||||
* that the list of prompts it offers has changed. This may be issued by servers
|
* the list of prompts it offers has changed. This may be issued by servers without
|
||||||
* without any previous subscription from the client.
|
* any previous subscription from the client.
|
||||||
*/
|
*/
|
||||||
public void promptListChangedNotification() {
|
public void promptListChangedNotification() {
|
||||||
this.delegate.promptListChangedNotification().block();
|
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;
|
package org.springframework.ai.mcp.client.stdio;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -9,6 +25,7 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
import org.springframework.ai.mcp.client.util.Assert;
|
import org.springframework.ai.mcp.client.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -22,11 +39,11 @@ public class ServerParameters {
|
||||||
|
|
||||||
// Environment variables to inherit by default
|
// Environment variables to inherit by default
|
||||||
private static final List<String> DEFAULT_INHERITED_ENV_VARS = System.getProperty("os.name")
|
private static final List<String> DEFAULT_INHERITED_ENV_VARS = System.getProperty("os.name")
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.contains("win")
|
.contains("win")
|
||||||
? Arrays.asList("APPDATA", "HOMEDRIVE", "HOMEPATH", "LOCALAPPDATA", "PATH",
|
? Arrays.asList("APPDATA", "HOMEDRIVE", "HOMEPATH", "LOCALAPPDATA", "PATH", "PROCESSOR_ARCHITECTURE",
|
||||||
"PROCESSOR_ARCHITECTURE", "SYSTEMDRIVE", "SYSTEMROOT", "TEMP", "USERNAME", "USERPROFILE")
|
"SYSTEMDRIVE", "SYSTEMROOT", "TEMP", "USERNAME", "USERPROFILE")
|
||||||
: Arrays.asList("HOME", "LOGNAME", "PATH", "SHELL", "TERM", "USER");
|
: Arrays.asList("HOME", "LOGNAME", "PATH", "SHELL", "TERM", "USER");
|
||||||
|
|
||||||
@JsonProperty("command")
|
@JsonProperty("command")
|
||||||
private String command;
|
private String command;
|
||||||
|
|
@ -117,17 +134,17 @@ public class ServerParameters {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a default environment object including only environment variables
|
* Returns a default environment object including only environment variables deemed
|
||||||
* deemed safe to inherit.
|
* safe to inherit.
|
||||||
*/
|
*/
|
||||||
private static Map<String, String> getDefaultEnvironment() {
|
private static Map<String, String> getDefaultEnvironment() {
|
||||||
return System.getenv()
|
return System.getenv()
|
||||||
.entrySet()
|
.entrySet()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(entry -> DEFAULT_INHERITED_ENV_VARS.contains(entry.getKey()))
|
.filter(entry -> DEFAULT_INHERITED_ENV_VARS.contains(entry.getKey()))
|
||||||
.filter(entry -> entry.getValue() != null)
|
.filter(entry -> entry.getValue() != null)
|
||||||
.filter(entry -> !entry.getValue().startsWith("()"))
|
.filter(entry -> !entry.getValue().startsWith("()"))
|
||||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,26 +1,28 @@
|
||||||
/*
|
/*
|
||||||
* 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* 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
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.ai.mcp.client.stdio;
|
package org.springframework.ai.mcp.client.stdio;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import org.springframework.ai.mcp.client.stdio.ServerParametersParser.McpServerConfigurations.McpServerConfiguration;
|
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;
|
package org.springframework.ai.mcp.client.stdio;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
|
|
@ -13,6 +29,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.core.scheduler.Scheduler;
|
import reactor.core.scheduler.Scheduler;
|
||||||
import reactor.core.scheduler.Schedulers;
|
import reactor.core.scheduler.Schedulers;
|
||||||
|
|
||||||
import org.springframework.ai.mcp.client.util.Assert;
|
import org.springframework.ai.mcp.client.util.Assert;
|
||||||
import org.springframework.ai.mcp.spec.AbstractMcpTransport;
|
import org.springframework.ai.mcp.spec.AbstractMcpTransport;
|
||||||
import org.springframework.ai.mcp.spec.McpSchema.JSONRPCMessage;
|
import org.springframework.ai.mcp.spec.McpSchema.JSONRPCMessage;
|
||||||
|
|
@ -34,11 +51,15 @@ public class StdioServerTransport extends AbstractMcpTransport {
|
||||||
private Process process;
|
private Process process;
|
||||||
|
|
||||||
private BufferedReader processErrorReader;
|
private BufferedReader processErrorReader;
|
||||||
|
|
||||||
private BufferedReader processReader;
|
private BufferedReader processReader;
|
||||||
|
|
||||||
private BufferedWriter processWriter;
|
private BufferedWriter processWriter;
|
||||||
|
|
||||||
private Scheduler inboundScheduler;
|
private Scheduler inboundScheduler;
|
||||||
|
|
||||||
private Scheduler outboundScheduler;
|
private Scheduler outboundScheduler;
|
||||||
|
|
||||||
private Scheduler errorScheduler;
|
private Scheduler errorScheduler;
|
||||||
|
|
||||||
volatile boolean isRunning;
|
volatile boolean isRunning;
|
||||||
|
|
@ -59,12 +80,9 @@ public class StdioServerTransport extends AbstractMcpTransport {
|
||||||
this.params = params;
|
this.params = params;
|
||||||
|
|
||||||
// Start threads
|
// Start threads
|
||||||
this.inboundScheduler = Schedulers.fromExecutorService(Executors.newSingleThreadExecutor(),
|
this.inboundScheduler = Schedulers.fromExecutorService(Executors.newSingleThreadExecutor(), "inbound");
|
||||||
"inbound");
|
this.outboundScheduler = Schedulers.fromExecutorService(Executors.newSingleThreadExecutor(), "outbound");
|
||||||
this.outboundScheduler = Schedulers.fromExecutorService(Executors.newSingleThreadExecutor(),
|
this.errorScheduler = Schedulers.fromExecutorService(Executors.newSingleThreadExecutor(), "error");
|
||||||
"outbound");
|
|
||||||
this.errorScheduler = Schedulers.fromExecutorService(Executors.newSingleThreadExecutor(),
|
|
||||||
"error");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -81,7 +99,8 @@ public class StdioServerTransport extends AbstractMcpTransport {
|
||||||
// Start the process
|
// Start the process
|
||||||
try {
|
try {
|
||||||
this.process = processBuilder.start();
|
this.process = processBuilder.start();
|
||||||
} catch (IOException e) {
|
}
|
||||||
|
catch (IOException e) {
|
||||||
throw new RuntimeException("Failed to start process with command: " + fullCommand, e);
|
throw new RuntimeException("Failed to start process with command: " + fullCommand, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -110,7 +129,8 @@ public class StdioServerTransport extends AbstractMcpTransport {
|
||||||
public void awaitForExit() {
|
public void awaitForExit() {
|
||||||
try {
|
try {
|
||||||
this.process.waitFor();
|
this.process.waitFor();
|
||||||
} catch (InterruptedException e) {
|
}
|
||||||
|
catch (InterruptedException e) {
|
||||||
throw new RuntimeException("Process interrupted", e);
|
throw new RuntimeException("Process interrupted", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -124,15 +144,18 @@ public class StdioServerTransport extends AbstractMcpTransport {
|
||||||
System.out.println("Received error line: " + line);
|
System.out.println("Received error line: " + line);
|
||||||
// TODO: handle errors, etc.
|
// TODO: handle errors, etc.
|
||||||
this.getErrorSink().tryEmitNext(line);
|
this.getErrorSink().tryEmitNext(line);
|
||||||
} catch (Exception e) {
|
}
|
||||||
|
catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
}
|
||||||
|
catch (IOException e) {
|
||||||
if (this.isRunning) {
|
if (this.isRunning) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
} finally {
|
}
|
||||||
|
finally {
|
||||||
this.isRunning = false;
|
this.isRunning = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -149,15 +172,18 @@ public class StdioServerTransport extends AbstractMcpTransport {
|
||||||
// TODO: Back off, reschedule, give up?
|
// TODO: Back off, reschedule, give up?
|
||||||
throw new RuntimeException("Failed to enqueue message");
|
throw new RuntimeException("Failed to enqueue message");
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
}
|
||||||
|
catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
}
|
||||||
|
catch (IOException e) {
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
} finally {
|
}
|
||||||
|
finally {
|
||||||
isRunning = false;
|
isRunning = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -170,9 +196,11 @@ public class StdioServerTransport extends AbstractMcpTransport {
|
||||||
// Determine message type based on specific JSON structure
|
// Determine message type based on specific JSON structure
|
||||||
if (map.containsKey("method") && map.containsKey("id")) {
|
if (map.containsKey("method") && map.containsKey("id")) {
|
||||||
return this.objectMapper.convertValue(map, JSONRPCRequest.class);
|
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);
|
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);
|
return this.objectMapper.convertValue(map, JSONRPCResponse.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -181,23 +209,24 @@ public class StdioServerTransport extends AbstractMcpTransport {
|
||||||
|
|
||||||
private void startOutboundProcessing() {
|
private void startOutboundProcessing() {
|
||||||
this.getOutboundSink()
|
this.getOutboundSink()
|
||||||
.asFlux()
|
.asFlux()
|
||||||
// this bit is important since writes come from user threads and we
|
// this bit is important since writes come from user threads and we
|
||||||
// want to ensure that the actual writing happens on a dedicated thread
|
// want to ensure that the actual writing happens on a dedicated thread
|
||||||
.publishOn(outboundScheduler)
|
.publishOn(outboundScheduler)
|
||||||
.handle((message, s) -> {
|
.handle((message, s) -> {
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
try {
|
try {
|
||||||
this.processWriter.write(objectMapper.writeValueAsString(message));
|
this.processWriter.write(objectMapper.writeValueAsString(message));
|
||||||
this.processWriter.newLine();
|
this.processWriter.newLine();
|
||||||
this.processWriter.flush();
|
this.processWriter.flush();
|
||||||
s.next(message);
|
s.next(message);
|
||||||
} catch (IOException e) {
|
|
||||||
s.error(new RuntimeException(e));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
catch (IOException e) {
|
||||||
.subscribe();
|
s.error(new RuntimeException(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -205,41 +234,40 @@ public class StdioServerTransport extends AbstractMcpTransport {
|
||||||
|
|
||||||
this.isRunning = false;
|
this.isRunning = false;
|
||||||
|
|
||||||
return Mono.whenDelayError(
|
return Mono.whenDelayError(Mono.fromRunnable(() -> {
|
||||||
Mono.fromRunnable(() -> {
|
try {
|
||||||
try {
|
this.processErrorReader.close();
|
||||||
this.processErrorReader.close();
|
}
|
||||||
} catch (IOException e) {
|
catch (IOException e) {
|
||||||
throw new RuntimeException(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 {
|
.then()
|
||||||
this.processReader.close();
|
.subscribeOn(Schedulers.boundedElastic());
|
||||||
} 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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,18 +1,19 @@
|
||||||
/*
|
/*
|
||||||
* 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* 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
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.ai.mcp.client.util;
|
package org.springframework.ai.mcp.client.util;
|
||||||
|
|
||||||
import reactor.util.annotation.Nullable;
|
import reactor.util.annotation.Nullable;
|
||||||
|
|
@ -23,26 +24,26 @@ import reactor.util.annotation.Nullable;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class Assert {
|
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
|
* Assert that an object is not {@code null}.
|
||||||
* be {@code null} and must contain at least one non-whitespace character.
|
*
|
||||||
|
* <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>
|
* <pre class="code">Assert.hasText(name, "'name' must not be empty");</pre>
|
||||||
* @param text the String to check
|
* @param text the String to check
|
||||||
* @param message the exception message to use if the assertion fails
|
* @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>.
|
* Check whether the given {@code String} contains actual <em>text</em>.
|
||||||
* <p>More specifically, this method returns {@code true} if the
|
* <p>
|
||||||
* {@code String} is not {@code null}, its length is greater than 0,
|
* More specifically, this method returns {@code true} if the {@code String} is not
|
||||||
* and it contains at least one non-whitespace character.
|
* {@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})
|
* @param str the {@code String} to check (may be {@code null})
|
||||||
* @return {@code true} if the {@code String} is not {@code null}, its
|
* @return {@code true} if the {@code String} is not {@code null}, its length is
|
||||||
* length is greater than 0, and it does not contain whitespace only
|
* greater than 0, and it does not contain whitespace only
|
||||||
* @see #hasText(CharSequence)
|
* @see #hasText(CharSequence)
|
||||||
* @see #hasLength(String)
|
* @see #hasLength(String)
|
||||||
* @see Character#isWhitespace
|
* @see Character#isWhitespace
|
||||||
|
|
@ -71,5 +73,4 @@ public class Assert {
|
||||||
return (str != null && !str.isBlank());
|
return (str != null && !str.isBlank());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,19 @@
|
||||||
/*
|
/*
|
||||||
* 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* 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
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.ai.mcp.spec;
|
package org.springframework.ai.mcp.spec;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
@ -20,6 +21,7 @@ import java.util.function.Consumer;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.core.publisher.Sinks;
|
import reactor.core.publisher.Sinks;
|
||||||
|
|
||||||
import org.springframework.ai.mcp.client.util.Assert;
|
import org.springframework.ai.mcp.client.util.Assert;
|
||||||
import org.springframework.ai.mcp.spec.McpSchema.JSONRPCMessage;
|
import org.springframework.ai.mcp.spec.McpSchema.JSONRPCMessage;
|
||||||
|
|
||||||
|
|
@ -32,11 +34,14 @@ public abstract class AbstractMcpTransport implements McpTransport {
|
||||||
protected final ObjectMapper objectMapper;
|
protected final ObjectMapper objectMapper;
|
||||||
|
|
||||||
private final Sinks.Many<String> errorSink;
|
private final Sinks.Many<String> errorSink;
|
||||||
|
|
||||||
private final Sinks.Many<JSONRPCMessage> inboundSink;
|
private final Sinks.Many<JSONRPCMessage> inboundSink;
|
||||||
|
|
||||||
private final Sinks.Many<JSONRPCMessage> outboundSink;
|
private final Sinks.Many<JSONRPCMessage> outboundSink;
|
||||||
|
|
||||||
private Consumer<JSONRPCMessage> inboundMessageHandler = message -> System.out
|
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);
|
private Consumer<String> errorHandler = error -> System.err.println("Received error: " + error);
|
||||||
|
|
||||||
public AbstractMcpTransport() {
|
public AbstractMcpTransport() {
|
||||||
|
|
@ -66,8 +71,7 @@ public abstract class AbstractMcpTransport implements McpTransport {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleIncomingMessages() {
|
private void handleIncomingMessages() {
|
||||||
this.inboundSink.asFlux()
|
this.inboundSink.asFlux().subscribe(message -> this.inboundMessageHandler.accept(message));
|
||||||
.subscribe(message -> this.inboundMessageHandler.accept(message));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleIncomingErrors() {
|
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
|
// In this approach we delegate the retry and the backpressure onto the
|
||||||
// caller. This might be enough for most cases.
|
// caller. This might be enough for most cases.
|
||||||
return Mono.empty();
|
return Mono.empty();
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
return Mono.error(new RuntimeException("Failed to enqueue message"));
|
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;
|
package org.springframework.ai.mcp.spec;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
@ -9,6 +25,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.core.publisher.MonoSink;
|
import reactor.core.publisher.MonoSink;
|
||||||
|
|
||||||
import org.springframework.ai.mcp.client.util.Assert;
|
import org.springframework.ai.mcp.client.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -39,16 +56,19 @@ public class DefaultMcpSession implements McpSession {
|
||||||
|
|
||||||
this.transport.setInboudMessageHandler(message -> {
|
this.transport.setInboudMessageHandler(message -> {
|
||||||
|
|
||||||
if ( message instanceof McpSchema.JSONRPCResponse response) {
|
if (message instanceof McpSchema.JSONRPCResponse response) {
|
||||||
var sink = pendingResponses.remove(response.id());
|
var sink = pendingResponses.remove(response.id());
|
||||||
if (sink == null) {
|
if (sink == null) {
|
||||||
System.out.println("Unexpected response for unkown id " + response.id());
|
System.out.println("Unexpected response for unkown id " + response.id());
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
sink.success(response);
|
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");
|
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");
|
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,
|
// TODO: This is non-blocking, but it's actually a synchronous call,
|
||||||
// perhaps there's no need to make it return Mono?
|
// perhaps there's no need to make it return Mono?
|
||||||
this.transport.sendMessage(jsonrpcRequest)
|
this.transport.sendMessage(jsonrpcRequest)
|
||||||
// TODO: It's most efficient to create a dedicated
|
// TODO: It's most efficient to create a dedicated
|
||||||
// Subscriber here
|
// Subscriber here
|
||||||
.subscribe(v -> {
|
.subscribe(v -> {
|
||||||
}, e -> {
|
}, e -> {
|
||||||
this.pendingResponses.remove(requestId);
|
this.pendingResponses.remove(requestId);
|
||||||
sink.error(e);
|
sink.error(e);
|
||||||
});
|
});
|
||||||
} catch (Exception e) {
|
}
|
||||||
|
catch (Exception e) {
|
||||||
sink.error(e);
|
sink.error(e);
|
||||||
}
|
}
|
||||||
}).timeout(this.requestTimeout).handle((jsonRpcResponse, s) -> {
|
}).timeout(this.requestTimeout).handle((jsonRpcResponse, s) -> {
|
||||||
if (jsonRpcResponse.error() != null) {
|
if (jsonRpcResponse.error() != null) {
|
||||||
s.error(new McpError(jsonRpcResponse.error()));
|
s.error(new McpError(jsonRpcResponse.error()));
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
if (typeRef.getType().getTypeName().equals("java.lang.Void")) {
|
if (typeRef.getType().getTypeName().equals("java.lang.Void")) {
|
||||||
s.complete();
|
s.complete();
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
s.next(this.objectMapper.convertValue(jsonRpcResponse.result(), typeRef));
|
s.next(this.objectMapper.convertValue(jsonRpcResponse.result(), typeRef));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -99,7 +122,8 @@ public class DefaultMcpSession implements McpSession {
|
||||||
method, params);
|
method, params);
|
||||||
try {
|
try {
|
||||||
this.transport.sendMessage(jsonrpcNotification);
|
this.transport.sendMessage(jsonrpcNotification);
|
||||||
} catch (Exception e) {
|
}
|
||||||
|
catch (Exception e) {
|
||||||
return Mono.error(new McpError(e));
|
return Mono.error(new McpError(e));
|
||||||
}
|
}
|
||||||
return Mono.empty();
|
return Mono.empty();
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,19 @@
|
||||||
/*
|
/*
|
||||||
* 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* 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
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.ai.mcp.spec;
|
package org.springframework.ai.mcp.spec;
|
||||||
|
|
||||||
import java.util.List;
|
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
|
// Resource Interfaces
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
/**
|
/**
|
||||||
* Base for objects that include optional annotations for the client. The client
|
* Base for objects that include optional annotations for the client. The client can
|
||||||
* can
|
|
||||||
* use annotations to inform how objects are used or displayed
|
* use annotations to inform how objects are used or displayed
|
||||||
*/
|
*/
|
||||||
public interface Annotated {
|
public interface Annotated {
|
||||||
|
|
@ -189,18 +190,13 @@ public class McpSchema {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param audience Describes who the intended customer of this object or data
|
* @param audience Describes who the intended customer of this object or data is. It
|
||||||
* is. It
|
* can include multiple entries to indicate content useful for multiple audiences
|
||||||
* can include multiple entries to indicate content useful for
|
* (e.g., `["user", "assistant"]`).
|
||||||
* multiple audiences
|
* @param priority Describes how important this data is for operating the server. A
|
||||||
* (e.g., `["user", "assistant"]`).
|
* value of 1 means "most important," and indicates that the data is effectively
|
||||||
* @param priority Describes how important this data is for operating the
|
* required, while 0 means "least important," and indicates that the data is entirely
|
||||||
* server. A
|
* optional. It is a number between 0 and 1.
|
||||||
* 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)
|
@JsonInclude(JsonInclude.Include.NON_ABSENT)
|
||||||
public record Annotations( // @formatter:off
|
public record Annotations( // @formatter:off
|
||||||
|
|
@ -211,16 +207,13 @@ public class McpSchema {
|
||||||
/**
|
/**
|
||||||
* A known resource that the server is capable of reading.
|
* A known resource that the server is capable of reading.
|
||||||
*
|
*
|
||||||
* @param uri the URI of the resource.
|
* @param uri the URI of the resource.
|
||||||
* @param name A human-readable name for this resource. This can be used
|
* @param name A human-readable name for this resource. This can be used by clients to
|
||||||
* by clients to
|
* populate UI elements.
|
||||||
* populate UI elements.
|
* @param description A description of what this resource represents. This can be used
|
||||||
* @param description A description of what this resource represents. This can
|
* by clients to improve the LLM's understanding of available resources. It can be
|
||||||
* be used
|
* thought of like a "hint" to the model.
|
||||||
* by clients to improve the LLM's understanding of available
|
* @param mimeType The MIME type of this resource, if known.
|
||||||
* 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)
|
@JsonInclude(JsonInclude.Include.NON_ABSENT)
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
|
@ -264,13 +257,11 @@ public class McpSchema {
|
||||||
} // @formatter:on
|
} // @formatter:on
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sent from the client to request resources/updated notifications from the
|
* Sent from the client to request resources/updated notifications from the server
|
||||||
* server
|
|
||||||
* whenever a particular resource changes.
|
* whenever a particular resource changes.
|
||||||
*
|
*
|
||||||
* @param uri the URI of the resource to subscribe to. The URI can use any
|
* @param uri the URI of the resource to subscribe to. The URI can use any protocol;
|
||||||
* protocol;
|
* it is up to the server how to interpret it.
|
||||||
* it is up to the server how to interpret it.
|
|
||||||
*/
|
*/
|
||||||
@JsonInclude(JsonInclude.Include.NON_ABSENT)
|
@JsonInclude(JsonInclude.Include.NON_ABSENT)
|
||||||
public record SubscribeRequest( // @formatter:off
|
public record SubscribeRequest( // @formatter:off
|
||||||
|
|
@ -292,14 +283,12 @@ public class McpSchema {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The URI of this resource.
|
* The URI of this resource.
|
||||||
*
|
|
||||||
* @return the URI of this resource.
|
* @return the URI of this resource.
|
||||||
*/
|
*/
|
||||||
String uri();
|
String uri();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The MIME type of this resource.
|
* The MIME type of this resource.
|
||||||
*
|
|
||||||
* @return the MIME type of this resource.
|
* @return the MIME type of this resource.
|
||||||
*/
|
*/
|
||||||
String mimeType();
|
String mimeType();
|
||||||
|
|
@ -309,11 +298,10 @@ public class McpSchema {
|
||||||
/**
|
/**
|
||||||
* Text contents of a resource.
|
* 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 mimeType the MIME type of this resource.
|
||||||
* @param text the text of the resource. This must only be set if the
|
* @param text the text of the resource. This must only be set if the resource can
|
||||||
* resource can
|
* actually be represented as text (not binary data).
|
||||||
* actually be represented as text (not binary data).
|
|
||||||
*/
|
*/
|
||||||
@JsonInclude(JsonInclude.Include.NON_ABSENT)
|
@JsonInclude(JsonInclude.Include.NON_ABSENT)
|
||||||
public record TextResourceContents( // @formatter:off
|
public record TextResourceContents( // @formatter:off
|
||||||
|
|
@ -325,13 +313,11 @@ public class McpSchema {
|
||||||
/**
|
/**
|
||||||
* Binary contents of a resource.
|
* 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 mimeType the MIME type of this resource.
|
||||||
* @param blob a base64-encoded string representing the binary data of the
|
* @param blob a base64-encoded string representing the binary data of the resource.
|
||||||
* resource.
|
* This must only be set if the resource can actually be represented as binary data
|
||||||
* This must only be set if the resource can actually be
|
* (not text).
|
||||||
* represented as binary data
|
|
||||||
* (not text).
|
|
||||||
*/
|
*/
|
||||||
@JsonInclude(JsonInclude.Include.NON_ABSENT)
|
@JsonInclude(JsonInclude.Include.NON_ABSENT)
|
||||||
public record BlobResourceContents( // @formatter:off
|
public record BlobResourceContents( // @formatter:off
|
||||||
|
|
@ -346,9 +332,9 @@ public class McpSchema {
|
||||||
/**
|
/**
|
||||||
* A prompt or prompt template that the server offers.
|
* 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 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)
|
@JsonInclude(JsonInclude.Include.NON_ABSENT)
|
||||||
public record Prompt( // @formatter:off
|
public record Prompt( // @formatter:off
|
||||||
|
|
@ -360,9 +346,9 @@ public class McpSchema {
|
||||||
/**
|
/**
|
||||||
* Describes an argument that a prompt can accept.
|
* 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 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)
|
@JsonInclude(JsonInclude.Include.NON_ABSENT)
|
||||||
public record PromptArgument( // @formatter:off
|
public record PromptArgument( // @formatter:off
|
||||||
|
|
@ -374,12 +360,10 @@ public class McpSchema {
|
||||||
/**
|
/**
|
||||||
* Describes a message returned as part of a prompt.
|
* Describes a message returned as part of a prompt.
|
||||||
*
|
*
|
||||||
* This is similar to `SamplingMessage`, but also supports the embedding of
|
* This is similar to `SamplingMessage`, but also supports the embedding of resources
|
||||||
* resources
|
|
||||||
* from the MCP server.
|
* from the MCP server.
|
||||||
*
|
*
|
||||||
* @param role The sender or recipient of messages and data in a
|
* @param role The sender or recipient of messages and data in a conversation.
|
||||||
* conversation.
|
|
||||||
* @param content The content of the message of type {@link Content}.
|
* @param content The content of the message of type {@link Content}.
|
||||||
*/
|
*/
|
||||||
@JsonInclude(JsonInclude.Include.NON_ABSENT)
|
@JsonInclude(JsonInclude.Include.NON_ABSENT)
|
||||||
|
|
@ -400,7 +384,7 @@ public class McpSchema {
|
||||||
/**
|
/**
|
||||||
* Used by the client to get a prompt provided by the server.
|
* 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.
|
* @param arguments Arguments to use for templating the prompt.
|
||||||
*/
|
*/
|
||||||
@JsonInclude(JsonInclude.Include.NON_ABSENT)
|
@JsonInclude(JsonInclude.Include.NON_ABSENT)
|
||||||
|
|
|
||||||
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* 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
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* 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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.ai.mcp.spec;
|
package org.springframework.ai.mcp.spec;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
@ -23,65 +24,72 @@ import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a Model Control Protocol (MCP) session that handles communication between
|
* Represents a Model Control Protocol (MCP) session that handles communication between
|
||||||
* clients and the server. This interface provides methods for sending requests
|
* clients and the server. This interface provides methods for sending requests and
|
||||||
* and notifications, as well as managing the session lifecycle.
|
* notifications, as well as managing the session lifecycle.
|
||||||
*
|
*
|
||||||
* <p>The session operates asynchronously using Project Reactor's {@link Mono} type
|
* <p>
|
||||||
* for non-blocking operations. It supports both request-response patterns and
|
* The session operates asynchronously using Project Reactor's {@link Mono} type for
|
||||||
* one-way notifications.</p>
|
* non-blocking operations. It supports both request-response patterns and one-way
|
||||||
|
* notifications.
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* @author Christian Tzolov
|
* @author Christian Tzolov
|
||||||
* @author Dariusz Jędrzejczyk
|
* @author Dariusz Jędrzejczyk
|
||||||
*/
|
*/
|
||||||
public interface McpSession {
|
public interface McpSession {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a request to the model server and expects a response of type T.
|
* 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
|
* <p>
|
||||||
* from the server. The response type is determined by the provided TypeReference.</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.
|
||||||
* @param <T> the type of the expected response
|
* </p>
|
||||||
* @param method the name of the method to be called on the server
|
* @param <T> the type of the expected response
|
||||||
* @param requestParams the parameters to be sent with the request
|
* @param method the name of the method to be called on the server
|
||||||
* @param typeRef the TypeReference describing the expected response type
|
* @param requestParams the parameters to be sent with the request
|
||||||
* @return a Mono that will emit the response when received
|
* @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);
|
*/
|
||||||
|
<T> Mono<T> sendRequest(String method, Object requestParams, TypeReference<T> typeRef);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a notification to the model server without parameters.
|
* Sends a notification to the model server without parameters.
|
||||||
*
|
*
|
||||||
* <p>This method implements the notification pattern where no response is expected
|
* <p>
|
||||||
* from the server. It's useful for fire-and-forget scenarios.</p>
|
* This method implements the notification pattern where no response is expected from
|
||||||
*
|
* the server. It's useful for fire-and-forget scenarios.
|
||||||
* @param method the name of the notification method to be called on the server
|
* </p>
|
||||||
* @return a Mono that completes when the notification has been sent
|
* @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) {
|
default Mono<Void> sendNotification(String method) {
|
||||||
return sendNotification(method, null);
|
return sendNotification(method, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a notification to the model server with parameters.
|
* Sends a notification to the model server with parameters.
|
||||||
*
|
*
|
||||||
* <p>Similar to {@link #sendNotification(String)} but allows sending additional
|
* <p>
|
||||||
* parameters with the notification.</p>
|
* Similar to {@link #sendNotification(String)} but allows sending additional
|
||||||
*
|
* parameters with the notification.
|
||||||
* @param method the name of the notification method to be called on the server
|
* </p>
|
||||||
* @param params a map of parameters to be sent with the notification
|
* @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
|
* @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);
|
*/
|
||||||
|
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,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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* 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
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
|
@ -13,23 +13,22 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.ai.mcp.spec;
|
package org.springframework.ai.mcp.spec;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import org.springframework.ai.mcp.spec.McpSchema.JSONRPCMessage;
|
import org.springframework.ai.mcp.spec.McpSchema.JSONRPCMessage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines the asynchronous transport layer for the Model Context Protocol
|
* Defines the asynchronous transport layer for the Model Context Protocol (MCP).
|
||||||
* (MCP).
|
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* The McpTransport interface provides the foundation for implementing custom
|
* The McpTransport interface provides the foundation for implementing custom transport
|
||||||
* transport
|
* mechanisms in the Model Context Protocol. It handles the bidirectional communication
|
||||||
* mechanisms in the Model Context Protocol. It handles the bidirectional
|
* between the client and server components, supporting asynchronous message exchange
|
||||||
* communication between
|
|
||||||
* the client and server components, supporting asynchronous message exchange
|
|
||||||
* using JSON-RPC format.
|
* using JSON-RPC format.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
|
|
@ -44,8 +43,7 @@ import org.springframework.ai.mcp.spec.McpSchema.JSONRPCMessage;
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* The transport layer is designed to be protocol-agnostic, allowing for various
|
* The transport layer is designed to be protocol-agnostic, allowing for various
|
||||||
* implementations
|
* implementations such as WebSocket, HTTP, or custom protocols.
|
||||||
* such as WebSocket, HTTP, or custom protocols.
|
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @author Christian Tzolov
|
* @author Christian Tzolov
|
||||||
|
|
@ -53,78 +51,69 @@ import org.springframework.ai.mcp.spec.McpSchema.JSONRPCMessage;
|
||||||
*/
|
*/
|
||||||
public interface McpTransport {
|
public interface McpTransport {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes and starts the transport connection.
|
* Initializes and starts the transport connection.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This method should be called before any message exchange can occur. It sets
|
* This method should be called before any message exchange can occur. It sets up the
|
||||||
* up
|
* necessary resources and establishes the connection to the server.
|
||||||
* the necessary resources and establishes the connection to the server.
|
* </p>
|
||||||
* </p>
|
*/
|
||||||
*/
|
void start();
|
||||||
void start();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes the transport connection and releases any associated resources.
|
* Closes the transport connection and releases any associated resources.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This method ensures proper cleanup of resources when the transport is no
|
* This method ensures proper cleanup of resources when the transport is no longer
|
||||||
* longer needed.
|
* needed. It should handle the graceful shutdown of any active connections.
|
||||||
* It should handle the graceful shutdown of any active connections.
|
* </p>
|
||||||
* </p>
|
*/
|
||||||
*/
|
default void close() {
|
||||||
default void close() {
|
this.closeGracefully().subscribe();
|
||||||
this.closeGracefully().subscribe();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes the transport connection and releases any associated resources
|
* Closes the transport connection and releases any associated resources
|
||||||
* asynchronously.
|
* asynchronously.
|
||||||
*
|
* @return a {@link Mono<Void>} that completes when the connection has been closed.
|
||||||
* @return a {@link Mono<Void>} that completes when the connection has been
|
*/
|
||||||
* closed.
|
Mono<Void> closeGracefully();
|
||||||
*/
|
|
||||||
Mono<Void> closeGracefully();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the handler for processing incoming messages from the server.
|
* Sets the handler for processing incoming messages from the server.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* The provided handler will be called whenever a new message is received from
|
* The provided handler will be called whenever a new message is received from the
|
||||||
* the server.
|
* server. Messages are expected to follow the JSON-RPC format as defined in the MCP
|
||||||
* Messages are expected to follow the JSON-RPC format as defined in the MCP
|
* specification.
|
||||||
* specification.
|
* </p>
|
||||||
* </p>
|
* @param inboundMessageHandler a consumer that processes incoming
|
||||||
*
|
* {@link JSONRPCMessage}s
|
||||||
* @param inboundMessageHandler a consumer that processes incoming
|
*/
|
||||||
* {@link JSONRPCMessage}s
|
void setInboudMessageHandler(Consumer<JSONRPCMessage> inboundMessageHandler);
|
||||||
*/
|
|
||||||
void setInboudMessageHandler(Consumer<JSONRPCMessage> inboundMessageHandler);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the handler for processing transport-level errors.
|
* Sets the handler for processing transport-level errors.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* The provided handler will be called when errors occur during transport
|
* The provided handler will be called when errors occur during transport operations,
|
||||||
* operations,
|
* such as connection failures or protocol violations.
|
||||||
* such as connection failures or protocol violations.
|
* </p>
|
||||||
* </p>
|
* @param inboundErrorHandler a consumer that processes error messages
|
||||||
*
|
*/
|
||||||
* @param inboundErrorHandler a consumer that processes error messages
|
void setInboundErrorHandler(Consumer<String> inboundErrorHandler);
|
||||||
*/
|
|
||||||
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.
|
* Copyright 2024-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* 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
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.ai.mcp.attic;
|
package org.springframework.ai.mcp.attic;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import org.springframework.ai.mcp.client.McpClient;
|
import org.springframework.ai.mcp.client.McpClient;
|
||||||
import org.springframework.ai.mcp.client.McpSyncClient;
|
import org.springframework.ai.mcp.client.McpSyncClient;
|
||||||
import org.springframework.ai.mcp.client.stdio.ServerParameters;
|
import org.springframework.ai.mcp.client.stdio.ServerParameters;
|
||||||
|
|
@ -35,13 +37,11 @@ public class ClientSessionTests2 {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|
||||||
var stdioParams = ServerParameters.builder("uv")
|
var stdioParams = ServerParameters.builder("uv")
|
||||||
.args("--directory", "dir", "run", "mcp-server-sqlite",
|
.args("--directory", "dir", "run", "mcp-server-sqlite", "--db-path", "~/test.db")
|
||||||
"--db-path", "~/test.db")
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
try (McpSyncClient clientSession =
|
try (McpSyncClient clientSession = McpClient.sync(new StdioServerTransport(stdioParams), Duration.ofSeconds(10),
|
||||||
McpClient.sync(new StdioServerTransport(stdioParams),
|
new ObjectMapper())) {
|
||||||
Duration.ofSeconds(10), new ObjectMapper())) {
|
|
||||||
|
|
||||||
clientSession.initialize();
|
clientSession.initialize();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,25 @@
|
||||||
/*
|
/*
|
||||||
* 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* 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
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.ai.mcp.attic;
|
package org.springframework.ai.mcp.attic;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import org.springframework.ai.mcp.client.McpClient;
|
import org.springframework.ai.mcp.client.McpClient;
|
||||||
import org.springframework.ai.mcp.client.McpSyncClient;
|
import org.springframework.ai.mcp.client.McpSyncClient;
|
||||||
import org.springframework.ai.mcp.client.stdio.ServerParameters;
|
import org.springframework.ai.mcp.client.stdio.ServerParameters;
|
||||||
|
|
@ -35,16 +37,14 @@ public class ClientSessionTests3 {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|
||||||
var stdioParams = ServerParameters.builder("uv")
|
var stdioParams = ServerParameters.builder("uv")
|
||||||
.args("--directory", "dir", "run", "mcp-server-sqlite",
|
.args("--directory", "dir", "run", "mcp-server-sqlite", "--db-path", "~/test.db")
|
||||||
"--db-path", "~/test.db")
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|
||||||
McpSyncClient clientSession = null;
|
McpSyncClient clientSession = null;
|
||||||
try {
|
try {
|
||||||
|
|
||||||
clientSession = McpClient.sync(new StdioServerTransport(stdioParams),
|
clientSession = McpClient.sync(new StdioServerTransport(stdioParams), Duration.ofSeconds(10),
|
||||||
Duration.ofSeconds(10), new ObjectMapper());
|
new ObjectMapper());
|
||||||
|
|
||||||
clientSession.initialize();
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* 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
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* 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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.ai.mcp.client;
|
package org.springframework.ai.mcp.client;
|
||||||
|
|
||||||
import java.time.Duration;
|
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.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.Timeout;
|
import org.junit.jupiter.api.Timeout;
|
||||||
|
|
||||||
import org.springframework.ai.mcp.client.stdio.ServerParameters;
|
import org.springframework.ai.mcp.client.stdio.ServerParameters;
|
||||||
import org.springframework.ai.mcp.client.stdio.StdioServerTransport;
|
import org.springframework.ai.mcp.client.stdio.StdioServerTransport;
|
||||||
import org.springframework.ai.mcp.spec.McpSchema.CallToolRequest;
|
import org.springframework.ai.mcp.spec.McpSchema.CallToolRequest;
|
||||||
|
|
@ -54,8 +56,8 @@ class McpAsyncClientTests {
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
stdioParams = ServerParameters.builder("npx")
|
stdioParams = ServerParameters.builder("npx")
|
||||||
.args("-y", "@modelcontextprotocol/server-everything", "dir")
|
.args("-y", "@modelcontextprotocol/server-everything", "dir")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
assertThatCode(() -> {
|
assertThatCode(() -> {
|
||||||
mcpAsyncClient = McpClient.async(new StdioServerTransport(stdioParams), TIMEOUT, new ObjectMapper());
|
mcpAsyncClient = McpClient.async(new StdioServerTransport(stdioParams), TIMEOUT, new ObjectMapper());
|
||||||
|
|
@ -73,13 +75,13 @@ class McpAsyncClientTests {
|
||||||
@Test
|
@Test
|
||||||
void testConstructorWithInvalidArguments() {
|
void testConstructorWithInvalidArguments() {
|
||||||
assertThatThrownBy(() -> McpClient.sync(null, TIMEOUT, new ObjectMapper()))
|
assertThatThrownBy(() -> McpClient.sync(null, TIMEOUT, new ObjectMapper()))
|
||||||
.isInstanceOf(IllegalArgumentException.class);
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
|
|
||||||
assertThatThrownBy(() -> McpClient.sync(new StdioServerTransport(stdioParams), null, new ObjectMapper()))
|
assertThatThrownBy(() -> McpClient.sync(new StdioServerTransport(stdioParams), null, new ObjectMapper()))
|
||||||
.isInstanceOf(IllegalArgumentException.class);
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
|
|
||||||
assertThatThrownBy(() -> McpClient.sync(new StdioServerTransport(stdioParams), TIMEOUT, null))
|
assertThatThrownBy(() -> McpClient.sync(new StdioServerTransport(stdioParams), TIMEOUT, null))
|
||||||
.isInstanceOf(IllegalArgumentException.class);
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -152,16 +154,15 @@ class McpAsyncClientTests {
|
||||||
@Timeout(15)
|
@Timeout(15)
|
||||||
void testListPrompts() {
|
void testListPrompts() {
|
||||||
mcpAsyncClient.listPrompts(null).subscribe(prompts -> {
|
mcpAsyncClient.listPrompts(null).subscribe(prompts -> {
|
||||||
assertThat(prompts).isNotNull()
|
assertThat(prompts).isNotNull().satisfies(result -> {
|
||||||
.satisfies(result -> {
|
assertThat(result.prompts()).isNotNull();
|
||||||
assertThat(result.prompts()).isNotNull();
|
|
||||||
|
|
||||||
if (!result.prompts().isEmpty()) {
|
if (!result.prompts().isEmpty()) {
|
||||||
Prompt firstPrompt = result.prompts().get(0);
|
Prompt firstPrompt = result.prompts().get(0);
|
||||||
assertThat(firstPrompt.name()).isNotNull();
|
assertThat(firstPrompt.name()).isNotNull();
|
||||||
assertThat(firstPrompt.description()).isNotNull();
|
assertThat(firstPrompt.description()).isNotNull();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,11 +170,10 @@ class McpAsyncClientTests {
|
||||||
@Timeout(15)
|
@Timeout(15)
|
||||||
void testGetPrompt() {
|
void testGetPrompt() {
|
||||||
mcpAsyncClient.getPrompt(new GetPromptRequest("simple_prompt", Map.of())).subscribe(prompt -> {
|
mcpAsyncClient.getPrompt(new GetPromptRequest("simple_prompt", Map.of())).subscribe(prompt -> {
|
||||||
assertThat(prompt).isNotNull()
|
assertThat(prompt).isNotNull().satisfies(result -> {
|
||||||
.satisfies(result -> {
|
assertThat(result.description()).isNotNull();
|
||||||
assertThat(result.description()).isNotNull();
|
assertThat(result.messages()).isNotEmpty();
|
||||||
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* 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
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* 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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.ai.mcp.client;
|
package org.springframework.ai.mcp.client;
|
||||||
|
|
||||||
import java.time.Duration;
|
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.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.Timeout;
|
import org.junit.jupiter.api.Timeout;
|
||||||
|
|
||||||
import org.springframework.ai.mcp.client.stdio.ServerParameters;
|
import org.springframework.ai.mcp.client.stdio.ServerParameters;
|
||||||
import org.springframework.ai.mcp.client.stdio.StdioServerTransport;
|
import org.springframework.ai.mcp.client.stdio.StdioServerTransport;
|
||||||
import org.springframework.ai.mcp.spec.McpSchema.CallToolRequest;
|
import org.springframework.ai.mcp.spec.McpSchema.CallToolRequest;
|
||||||
|
|
|
||||||
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* 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
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* 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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.ai.mcp.client.stdio;
|
package org.springframework.ai.mcp.client.stdio;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
|
|
@ -129,4 +130,5 @@ public class StdioServerTransportTests {
|
||||||
assertThat(exception.getMessage()).contains("Process input or output stream is null");
|
assertThat(exception.getMessage()).contains("Process input or output stream is null");
|
||||||
verify(mockProcess).destroy();
|
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