Attempting to structure the client API with sync and async abstractions

This commit is contained in:
Dariusz Jędrzejczyk 2024-12-06 22:59:19 +01:00
commit 4b6201a8ef
No known key found for this signature in database
GPG key ID: FD786A4E2335C448
9 changed files with 521 additions and 230 deletions

View file

@ -5,6 +5,7 @@ import java.util.List;
import com.fasterxml.jackson.databind.ObjectMapper;
import spring.ai.mcp.client.McpClient;
import spring.ai.mcp.client.McpSyncClient;
import spring.ai.mcp.client.stdio.StdioServerParameters;
import spring.ai.mcp.client.stdio.StdioServerTransport;
@ -41,7 +42,7 @@ public class McpApplication {
}
@Bean
public List<McpFunctionCallback> functionCallbacks(McpClient mcpClient) {
public List<McpFunctionCallback> functionCallbacks(McpSyncClient mcpClient) {
return mcpClient.listTools(null)
.tools()
@ -51,16 +52,17 @@ public class McpApplication {
}
@Bean(destroyMethod = "close")
public McpClient clientSession() {
public McpSyncClient clientSession() {
var stdioParams = StdioServerParameters.builder("npx")
.args("-y", "@modelcontextprotocol/server-filesystem",
"/Users/christiantzolov/Dev/projects/demo/mcp-server/dir")
"dir")
.build();
McpClient mcpClient = null;
McpSyncClient mcpClient = null;
try {
mcpClient = new McpClient(new StdioServerTransport(stdioParams, Duration.ofMillis(100)),
mcpClient = McpClient.sync(new StdioServerTransport(stdioParams,
Duration.ofMillis(100)),
Duration.ofSeconds(10), new ObjectMapper());
var init = mcpClient.initialize();

View file

@ -18,6 +18,7 @@ package spring.ai.mcp;
import java.util.Map;
import spring.ai.mcp.client.McpClient;
import spring.ai.mcp.client.McpSyncClient;
import spring.ai.mcp.spec.McpSchema.CallToolRequest;
import spring.ai.mcp.spec.McpSchema.CallToolResult;
import spring.ai.mcp.spec.McpSchema.Tool;
@ -32,11 +33,12 @@ import org.springframework.ai.model.function.FunctionCallback;
public class McpFunctionCallback implements FunctionCallback {
private final McpClient mcpClient;
// TODO: revisit function calling as well to handle the async case
private final McpSyncClient mcpClient;
private final Tool tool;
public McpFunctionCallback(McpClient clientSession, Tool tool) {
public McpFunctionCallback(McpSyncClient clientSession, Tool tool) {
this.mcpClient = clientSession;
this.tool = tool;
}

View file

@ -0,0 +1,187 @@
package spring.ai.mcp.client;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import reactor.core.publisher.Mono;
import spring.ai.mcp.spec.McpSchema;
import spring.ai.mcp.spec.McpSession;
import spring.ai.mcp.spec.McpTransport;
public class McpAsyncClient extends McpAsyncSession {
public McpAsyncClient(McpTransport transport) {
this(transport, Duration.ofSeconds(10), new ObjectMapper());
}
public McpAsyncClient(McpTransport transport, Duration requestTimeout, ObjectMapper objectMapper) {
super(requestTimeout, objectMapper, transport);
}
/**
* The initialization phase MUST be the first interaction between client and server.
* During this phase, the client and server:
* <ul>
* <li>Establish protocol version compatibility</li>
* <li>Exchange and negotiate capabilities</li>
* <li>Share implementation details</li>
* </ul>
* <br/>
* The client MUST initiate this phase by sending an initialize request containing:
* <ul>
* <li>The protocol version the client supports</li>
* <li>The client's capabilities</li>
* <li>Client implementation information</li>
* </ul>
*
* The server MUST respond with its own capabilities and information:
* {@link McpSchema.ServerCapabilities}. <br/>
* After successful initialization, the client MUST send an initialized notification
* to indicate it is ready to begin normal operations.
*
* <br/>
*
* <a href=
* "https://github.com/modelcontextprotocol/specification/blob/main/docs/specification/basic/lifecycle.md#initialization">Initialization
* Spec</a>
* @return the initialize result.
*/
public Mono<McpSchema.InitializeResult> initialize() {
McpSchema.InitializeRequest initializeRequest = new McpSchema.InitializeRequest(// @formatter:off
McpSchema.LATEST_PROTOCOL_VERSION,
new McpSchema.ClientCapabilities(null, new McpSchema.ClientCapabilities.RootCapabilities(true), null),
new McpSchema.Implementation("mcp-java-client", "0.0.1")); // @formatter:on
Mono<McpSchema.InitializeResult> result =
this.sendRequest("initialize", initializeRequest, new TypeReference<McpSchema.InitializeResult>() {});
return result.flatMap(initializeResult -> {
if (initializeResult.protocolVersion() != McpSchema.LATEST_PROTOCOL_VERSION) {
return Mono.error(
new McpError("Unsupported protocol version from the server: "
+ initializeResult.protocolVersion()));
} else {
return this.sendNotification("notifications/initialized", null)
.thenReturn(initializeResult);
}
});
}
/**
* Send a roots/list_changed notification.
*/
public Mono<Void> sendRootsListChanged() {
return this.sendNotification("notifications/roots/list_changed");
}
/**
* Send a synchronous ping request.
*/
public Mono<Void> ping() {
return this.sendRequest("ping", null,
new TypeReference<Void>() {});
}
// --------------------------
// Tools
// --------------------------
/**
* Send a tools/call request.
* @param callToolRequest the call tool request.
* @return the call tool result.
*/
public Mono<McpSchema.CallToolResult> callTool(McpSchema.CallToolRequest callToolRequest) {
return this.sendRequest("tools/call", callToolRequest,
new TypeReference<McpSchema.CallToolResult>() {});
}
/**
* Send a tools/list request.
* @return the list of tools result.
*/
public Mono<McpSchema.ListToolsResult> listTools(String cursor) {
return this.sendRequest("tools/list", new McpSchema.PaginatedRequest(cursor),
new TypeReference<McpSchema.ListToolsResult>() {});
}
// --------------------------
// Resources
// --------------------------
/**
* Send a resources/list request.
* @param cursor the cursor
* @return the list of resources result.
*/
public Mono<McpSchema.ListResourcesResult> listResources(String cursor) {
return this.sendRequest("resources/list", new McpSchema.PaginatedRequest(cursor),
new TypeReference<McpSchema.ListResourcesResult>() {});
}
/**
* Send a resources/read request.
* @param resource the resource to read
* @return the resource content.
*/
public Mono<McpSchema.ReadResourceResult> readResource(McpSchema.Resource resource) {
return this.readResource(new McpSchema.ReadResourceRequest(resource.uri()));
}
/**
* Send a resources/read request.
* @param readResourceRequest the read resource request.
* @return the resource content.
*/
public Mono<McpSchema.ReadResourceResult> readResource(McpSchema.ReadResourceRequest readResourceRequest) {
return this.sendRequest("resources/read", readResourceRequest,
new TypeReference<McpSchema.ReadResourceResult>() {});
}
/**
* Resource templates allow servers to expose parameterized resources using URI
* templates. Arguments may be auto-completed through the completion API.
*
* Request a list of resource templates the server has.
* @param cursor the cursor
* @return the list of resource templates result.
*/
public Mono<McpSchema.ListResourceTemplatesResult> listResourceTemplates(String cursor) {
return this.sendRequest("resources/templates/list", new McpSchema.PaginatedRequest(cursor),
new TypeReference<McpSchema.ListResourceTemplatesResult>() {});
}
/**
* List Changed Notification. When the list of available resources changes, servers
* that declared the listChanged capability SHOULD send a notification:
*/
public Mono<Void> sendResourcesListChanged() {
return this.sendNotification("notifications/resources/list_changed");
}
/**
* Subscriptions. The protocol supports optional subscriptions to resource changes.
* Clients can subscribe to specific resources and receive notifications when they
* change.
*
* Send a resources/subscribe request.
* @param subscribeRequest the subscribe request contains the uri of the resource to
* subscribe to.
*/
public Mono<Void> subscribeResource(McpSchema.SubscribeRequest subscribeRequest) {
return this.sendRequest("resources/subscribe", subscribeRequest,
new TypeReference<Void>() {});
}
/**
* Send a resources/unsubscribe request.
* @param unsubscribeRequest the unsubscribe request contains the uri of the resource
* to unsubscribe from.
*/
public Mono<Void> unsubscribeResource(McpSchema.UnsubscribeRequest unsubscribeRequest) {
return this.sendRequest("resources/unsubscribe", unsubscribeRequest,
new TypeReference<Void>() {});
}
}

View file

@ -0,0 +1,112 @@
package spring.ai.mcp.client;
import java.time.Duration;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoSink;
import reactor.core.publisher.Sinks;
import spring.ai.mcp.spec.McpSchema;
import spring.ai.mcp.spec.McpSession;
import spring.ai.mcp.spec.McpTransport;
public class McpAsyncSession {
private final ConcurrentHashMap<Object, MonoSink<McpSchema.JSONRPCResponse>> pendingResponses
= new ConcurrentHashMap<>();
private final Duration requestTimeout;
private final ObjectMapper objectMapper;
private final McpTransport transport;
public McpAsyncSession(Duration requestTimeout,
ObjectMapper objectMapper,
McpTransport transport) {
this.requestTimeout = requestTimeout;
this.objectMapper = objectMapper;
this.transport = transport;
this.transport.setMessageHandler(message -> {
switch (message) {
case McpSchema.JSONRPCResponse response -> {
var sink = pendingResponses.remove(response.id());
if (sink == null) {
System.out.println("Unexpected response for unkown id " + response.id());
} else {
sink.success(response);
}
}
case McpSchema.JSONRPCRequest request -> {
System.out.println("Client does not yet support server requests");
}
case McpSchema.JSONRPCNotification notification -> {
System.out.println("Notifications not yet supported");
}
}
});
this.transport.setErrorHandler(error -> System.out.println("Error received " + error));
this.transport.start();
}
public <T> Mono<T> sendRequest(String method, Object requestParams, TypeReference<T> typeRef) {
String requestId = UUID.randomUUID().toString();
return Mono.<McpSchema.JSONRPCResponse>create(sink -> {
this.pendingResponses.put(requestId, sink);
McpSchema.JSONRPCRequest jsonrpcRequest = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, method, requestId, requestParams);
try {
// TODO convert the transport to non-blocking as well
this.transport.sendMessage(jsonrpcRequest);
} catch (Exception e) {
sink.error(e);
}
})
.timeout(this.requestTimeout)
.handle((jsonRpcResponse, s) -> {
if (jsonRpcResponse.error() != null) {
s.error(new McpError(jsonRpcResponse.error()));
} else {
s.next(this.objectMapper.convertValue(jsonRpcResponse.result(), typeRef));
}
});
}
public static class McpError extends RuntimeException {
public McpError(Object error) {
super(error.toString());
}
}
public Mono<Void> sendNotification(String method) {
return sendNotification(method, null);
}
public Mono<Void> sendNotification(String method, Map<String, Object> params) {
McpSchema.JSONRPCNotification
jsonrpcNotification = new McpSchema.JSONRPCNotification(McpSchema.JSONRPC_VERSION, method, params);
try {
// TODO: make it non-blocking
this.transport.sendMessage(jsonrpcNotification);
}
catch (Exception e) {
return Mono.error(new McpError(e));
}
return Mono.empty();
}
public Mono<Void> closeGracefully(Duration timeout) {
// TODO handle the timeout in transport
return Mono.fromRunnable(this.transport::close);
}
}

View file

@ -47,232 +47,35 @@ import spring.ai.mcp.spec.McpSchema.ClientCapabilities.RootCapabilities;
* @author Christian Tzolov
* @since 1.0.0
*/
public class McpClient extends McpSession {
public class McpClient {
public McpClient(McpTransport transport) {
this(transport, Duration.ofSeconds(10), new ObjectMapper());
private McpClient() {
}
public McpClient(McpTransport transport, Duration readTimeout, ObjectMapper objectMapper) {
super(transport, readTimeout, objectMapper);
// TODO: introduce a builder like:
// McpClient.using(transport)
// .withRequestTimeout(Duration.ofSeconds(5))
// .withObjectMapper(objectMapper); <- or even a more sophisticated
// JSONtoPOJOCodec type
// .sync();
public static McpAsyncClient async(McpTransport transport) {
return new McpAsyncClient(transport);
}
/**
* The initialization phase MUST be the first interaction between client and server.
* During this phase, the client and server:
* <ul>
* <li>Establish protocol version compatibility</li>
* <li>Exchange and negotiate capabilities</li>
* <li>Share implementation details</li>
* </ul>
* <br/>
* The client MUST initiate this phase by sending an initialize request containing:
* <ul>
* <li>The protocol version the client supports</li>
* <li>The client's capabilities</li>
* <li>Client implementation information</li>
* </ul>
*
* The server MUST respond with its own capabilities and information:
* {@link McpSchema.ServerCapabilities}. <br/>
* After successful initialization, the client MUST send an initialized notification
* to indicate it is ready to begin normal operations.
*
* <br/>
*
* <a href=
* "https://github.com/modelcontextprotocol/specification/blob/main/docs/specification/basic/lifecycle.md#initialization">Initialization
* Spec</a>
* @return the initialize result.
*/
public InitializeResult initialize() {
InitializeRequest initializeRequest = new InitializeRequest(// @formatter:off
McpSchema.LATEST_PROTOCOL_VERSION,
new ClientCapabilities(null, new RootCapabilities(true), null),
new Implementation("mcp-java-client", "0.0.1")); // @formatter:on
CompletableFuture<InitializeResult> initializeResultFuture = this.sendRequest("initialize", initializeRequest,
new TypeReference<InitializeResult>() {
});
try {
InitializeResult initializeResult = initializeResultFuture.get();
if (initializeRequest.protocolVersion() != McpSchema.LATEST_PROTOCOL_VERSION) {
throw new McpError(
"Unsupported protocol version from the server: " + initializeRequest.protocolVersion());
}
this.sendNotification("notifications/initialized", null);
return initializeResult;
}
catch (Exception e) {
throw new McpError(e);
}
public static McpAsyncClient async(McpTransport transport, Duration requestTimeout,
ObjectMapper objectMapper) {
return new McpAsyncClient(transport, requestTimeout, objectMapper);
}
/**
* Send a roots/list_changed notification.
*/
public void sendRootsListChanged() {
this.sendNotification("notifications/roots/list_changed");
public static McpSyncClient sync(McpTransport transport) {
return new McpSyncClient(async(transport));
}
/**
* Send a synchronous ping request.
*/
public void ping() {
try {
this.sendRequest("ping", null, new TypeReference<Void>() {
}).get();
}
catch (Exception e) {
throw new McpError(e);
}
public static McpSyncClient sync(McpTransport transport, Duration requestTimeout,
ObjectMapper objectMapper) {
return new McpSyncClient(async(transport, requestTimeout, objectMapper));
}
// --------------------------
// Tools
// --------------------------
/**
* Send a tools/call request.
* @param callToolRequest the call tool request.
* @return the call tool result.
*/
public CallToolResult callTool(CallToolRequest callToolRequest) {
try {
return this.sendRequest("tools/call", callToolRequest, new TypeReference<CallToolResult>() {
}).get();
}
catch (Exception e) {
throw new McpError(e);
}
}
/**
* Send a tools/list request.
* @return the list of tools result.
*/
public ListToolsResult listTools(String cursor) {
try {
return this.sendRequest("tools/list", new PaginatedRequest(cursor), new TypeReference<ListToolsResult>() {
}).get();
}
catch (Exception e) {
throw new McpError(e);
}
}
// --------------------------
// Resources
// --------------------------
/**
* Send a resources/list request.
* @param cursor the cursor
* @return the list of resources result.
*/
public ListResourcesResult listResources(String cursor) {
try {
return this
.sendRequest("resources/list", new PaginatedRequest(cursor), new TypeReference<ListResourcesResult>() {
})
.get();
}
catch (Exception e) {
throw new McpError(e);
}
}
/**
* Send a resources/read request.
* @param resource the resource to read
* @return the resource content.
*/
public McpSchema.ReadResourceResult readResource(Resource resource) {
return this.readResource(new ReadResourceRequest(resource.uri()));
}
/**
* Send a resources/read request.
* @param readResourceRequest the read resource request.
* @return the resource content.
*/
public McpSchema.ReadResourceResult readResource(ReadResourceRequest readResourceRequest) {
try {
return this
.sendRequest("resources/read", readResourceRequest, new TypeReference<McpSchema.ReadResourceResult>() {
})
.get();
}
catch (Exception e) {
e.printStackTrace();
throw new McpError(e);
}
}
/**
* Resource templates allow servers to expose parameterized resources using URI
* templates. Arguments may be auto-completed through the completion API.
*
* Request a list of resource templates the server has.
* @param cursor the cursor
* @return the list of resource templates result.
*/
public ListResourceTemplatesResult listResourceTemplates(String cursor) {
try {
return this.sendRequest("resources/templates/list", new PaginatedRequest(cursor),
new TypeReference<ListResourceTemplatesResult>() {
})
.get();
}
catch (Exception e) {
throw new McpError(e);
}
}
/**
* List Changed Notification. When the list of available resources changes, servers
* that declared the listChanged capability SHOULD send a notification:
*/
public void sendResourcesListChanged() {
this.sendNotification("notifications/resources/list_changed");
}
/**
* Subscriptions. The protocol supports optional subscriptions to resource changes.
* Clients can subscribe to specific resources and receive notifications when they
* change.
*
* Send a resources/subscribe request.
* @param subscribeRequest the subscribe request contains the uri of the resource to
* subscribe to.
*/
public void subscribeResource(SubscribeRequest subscribeRequest) {
try {
this.sendRequest("resources/subscribe", subscribeRequest, new TypeReference<Void>() {
}).get();
}
catch (Exception e) {
throw new McpError(e);
}
}
/**
* Send a resources/unsubscribe request.
* @param unsubscribeRequest the unsubscribe request contains the uri of the resource
* to unsubscribe from.
*/
public void unsubscribeResource(UnsubscribeRequest unsubscribeRequest) {
try {
this.sendRequest("resources/unsubscribe", unsubscribeRequest, new TypeReference<Void>() {
}).get();
}
catch (Exception e) {
throw new McpError(e);
}
}
}

View file

@ -0,0 +1,177 @@
package spring.ai.mcp.client;
import java.time.Duration;
import spring.ai.mcp.spec.McpSchema;
public class McpSyncClient implements AutoCloseable {
// TODO: Consider providing a client config to set this properly
// this is currently a concern only because AutoCloseable is used - perhaps it is
// not a requirement?
private static final long DEFAULT_CLOSE_TIMEOUT_MS = 10_000L;
McpAsyncClient delegate;
public McpSyncClient(McpAsyncClient delegate) {
this.delegate = delegate;
}
@Override
public void close() {
delegate.closeGracefully(Duration.ofMillis(DEFAULT_CLOSE_TIMEOUT_MS)).block();
}
/**
* The initialization phase MUST be the first interaction between client and server.
* During this phase, the client and server:
* <ul>
* <li>Establish protocol version compatibility</li>
* <li>Exchange and negotiate capabilities</li>
* <li>Share implementation details</li>
* </ul>
* <br/>
* The client MUST initiate this phase by sending an initialize request containing:
* <ul>
* <li>The protocol version the client supports</li>
* <li>The client's capabilities</li>
* <li>Client implementation information</li>
* </ul>
*
* The server MUST respond with its own capabilities and information:
* {@link McpSchema.ServerCapabilities}. <br/>
* After successful initialization, the client MUST send an initialized notification
* to indicate it is ready to begin normal operations.
*
* <br/>
*
* <a href=
* "https://github.com/modelcontextprotocol/specification/blob/main/docs/specification/basic/lifecycle.md#initialization">Initialization
* Spec</a>
* @return the initialize result.
*/
public McpSchema.InitializeResult initialize() {
// TODO: block takes no argument here as we assume the async client is
// configured with a requestTimeout at all times
return delegate.initialize()
.block();
}
/**
* Send a roots/list_changed notification.
*/
public void sendRootsListChanged() {
delegate.sendRootsListChanged()
.block();
}
/**
* Send a synchronous ping request.
*/
public void ping() {
delegate.ping()
.block();
}
// --------------------------
// Tools
// --------------------------
/**
* Send a tools/call request.
* @param callToolRequest the call tool request.
* @return the call tool result.
*/
public McpSchema.CallToolResult callTool(McpSchema.CallToolRequest callToolRequest) {
return delegate.callTool(callToolRequest)
.block();
}
/**
* Send a tools/list request.
* @return the list of tools result.
*/
public McpSchema.ListToolsResult listTools(String cursor) {
return delegate.listTools(cursor)
.block();
}
// --------------------------
// Resources
// --------------------------
/**
* Send a resources/list request.
* @param cursor the cursor
* @return the list of resources result.
*/
public McpSchema.ListResourcesResult listResources(String cursor) {
return delegate.listResources(cursor)
.block();
}
/**
* Send a resources/read request.
* @param resource the resource to read
* @return the resource content.
*/
public McpSchema.ReadResourceResult readResource(McpSchema.Resource resource) {
return delegate.readResource(resource)
.block();
}
/**
* Send a resources/read request.
* @param readResourceRequest the read resource request.
* @return the resource content.
*/
public McpSchema.ReadResourceResult readResource(McpSchema.ReadResourceRequest readResourceRequest) {
return delegate.readResource(readResourceRequest)
.block();
}
/**
* Resource templates allow servers to expose parameterized resources using URI
* templates. Arguments may be auto-completed through the completion API.
*
* Request a list of resource templates the server has.
* @param cursor the cursor
* @return the list of resource templates result.
*/
public McpSchema.ListResourceTemplatesResult listResourceTemplates(String cursor) {
return delegate.listResourceTemplates(cursor)
.block();
}
/**
* List Changed Notification. When the list of available resources changes, servers
* that declared the listChanged capability SHOULD send a notification:
*/
public void sendResourcesListChanged() {
delegate.sendResourcesListChanged()
.block();
}
/**
* Subscriptions. The protocol supports optional subscriptions to resource changes.
* Clients can subscribe to specific resources and receive notifications when they
* change.
*
* Send a resources/subscribe request.
* @param subscribeRequest the subscribe request contains the uri of the resource to
* subscribe to.
*/
public void subscribeResource(McpSchema.SubscribeRequest subscribeRequest) {
delegate.subscribeResource(subscribeRequest)
.block();
}
/**
* Send a resources/unsubscribe request.
* @param unsubscribeRequest the unsubscribe request contains the uri of the resource
* to unsubscribe from.
*/
public void unsubscribeResource(McpSchema.UnsubscribeRequest unsubscribeRequest) {
delegate.unsubscribeResource(unsubscribeRequest)
.block();
}
}

View file

@ -19,6 +19,7 @@ import java.time.Duration;
import com.fasterxml.jackson.databind.ObjectMapper;
import spring.ai.mcp.client.McpClient;
import spring.ai.mcp.client.McpSyncClient;
import spring.ai.mcp.client.stdio.StdioServerParameters;
import spring.ai.mcp.client.stdio.StdioServerTransport;
import spring.ai.mcp.spec.McpSchema.ListResourcesResult;
@ -33,10 +34,12 @@ public class ClientSessionTests {
var stdioParams = StdioServerParameters.builder("npx")
.args("-y", "@modelcontextprotocol/server-everything",
"/Users/christiantzolov/Dev/projects/demo/mcp-server/dir")
"dir")
.build();
try (McpClient clientSession = new McpClient(new StdioServerTransport(stdioParams), Duration.ofSeconds(10),
try (McpSyncClient clientSession =
McpClient.sync(new StdioServerTransport(stdioParams),
Duration.ofSeconds(10),
new ObjectMapper())) {
// clientSession.handleIncomingMessages();
clientSession.initialize();

View file

@ -19,6 +19,7 @@ import java.time.Duration;
import com.fasterxml.jackson.databind.ObjectMapper;
import spring.ai.mcp.client.McpClient;
import spring.ai.mcp.client.McpSyncClient;
import spring.ai.mcp.client.stdio.StdioServerParameters;
import spring.ai.mcp.client.stdio.StdioServerTransport;
import spring.ai.mcp.spec.McpSchema.ListResourcesResult;
@ -34,11 +35,13 @@ public class ClientSessionTests2 {
public static void main(String[] args) {
var stdioParams = StdioServerParameters.builder("uv")
.args("--directory", "/Users/christiantzolov/Dev/projects/demo/mcp-server/dir", "run", "mcp-server-sqlite",
.args("--directory", "dir", "run", "mcp-server-sqlite",
"--db-path", "~/test.db")
.build();
try (McpClient clientSession = new McpClient(new StdioServerTransport(stdioParams), Duration.ofSeconds(10), new ObjectMapper())) {
try (McpSyncClient clientSession =
McpClient.sync(new StdioServerTransport(stdioParams),
Duration.ofSeconds(10), new ObjectMapper())) {
clientSession.initialize();

View file

@ -19,6 +19,7 @@ import java.time.Duration;
import com.fasterxml.jackson.databind.ObjectMapper;
import spring.ai.mcp.client.McpClient;
import spring.ai.mcp.client.McpSyncClient;
import spring.ai.mcp.client.stdio.StdioServerParameters;
import spring.ai.mcp.client.stdio.StdioServerTransport;
import spring.ai.mcp.spec.McpSchema.ListResourcesResult;
@ -34,15 +35,16 @@ public class ClientSessionTests3 {
public static void main(String[] args) {
var stdioParams = StdioServerParameters.builder("uv")
.args("--directory", "/Users/christiantzolov/Dev/projects/demo/mcp-server/dir", "run", "mcp-server-sqlite",
.args("--directory", "dir", "run", "mcp-server-sqlite",
"--db-path", "~/test.db")
.build();
McpClient clientSession = null;
McpSyncClient clientSession = null;
try {
clientSession = new McpClient(new StdioServerTransport(stdioParams), Duration.ofSeconds(10), new ObjectMapper());
clientSession = McpClient.sync(new StdioServerTransport(stdioParams),
Duration.ofSeconds(10), new ObjectMapper());
clientSession.initialize();