Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;

Expand Down Expand Up @@ -89,6 +91,8 @@ class LifecycleInitializer {

private final McpSchema.Implementation clientInfo;

private final Map<String, Object> initializeRequestMeta;

private List<String> protocolVersions;

private final AtomicReference<DefaultInitialization> initializationRef = new AtomicReference<>();
Expand All @@ -109,6 +113,15 @@ public LifecycleInitializer(McpSchema.ClientCapabilities clientCapabilities, Mcp
Function<ContextView, McpClientSession> sessionSupplier,
Function<Initialization, Mono<Void>> postInitializationHook) {

this(clientCapabilities, clientInfo, null, protocolVersions, initializationTimeout, sessionSupplier,
postInitializationHook);
}

public LifecycleInitializer(McpSchema.ClientCapabilities clientCapabilities, McpSchema.Implementation clientInfo,
Map<String, Object> initializeRequestMeta, List<String> protocolVersions, Duration initializationTimeout,
Function<ContextView, McpClientSession> sessionSupplier,
Function<Initialization, Mono<Void>> postInitializationHook) {

Assert.notNull(sessionSupplier, "Session supplier must not be null");
Assert.notNull(clientCapabilities, "Client capabilities must not be null");
Assert.notNull(clientInfo, "Client info must not be null");
Expand All @@ -119,11 +132,16 @@ public LifecycleInitializer(McpSchema.ClientCapabilities clientCapabilities, Mcp
this.sessionSupplier = sessionSupplier;
this.clientCapabilities = clientCapabilities;
this.clientInfo = clientInfo;
this.initializeRequestMeta = copyInitializeRequestMeta(initializeRequestMeta);
this.protocolVersions = Collections.unmodifiableList(new ArrayList<>(protocolVersions));
this.initializationTimeout = initializationTimeout;
this.postInitializationHook = postInitializationHook;
}

private static Map<String, Object> copyInitializeRequestMeta(Map<String, Object> initializeRequestMeta) {
return initializeRequestMeta != null ? Collections.unmodifiableMap(new HashMap<>(initializeRequestMeta)) : null;
}

/**
* This method is package-private and used for test only. Should not be called by user
* code.
Expand Down Expand Up @@ -301,9 +319,12 @@ private Mono<McpSchema.InitializeResult> doInitialize(DefaultInitialization init

String latestVersion = this.protocolVersions.get(this.protocolVersions.size() - 1);

McpSchema.InitializeRequest initializeRequest = McpSchema.InitializeRequest
.builder(latestVersion, this.clientCapabilities, this.clientInfo)
.build();
McpSchema.InitializeRequest.Builder initializeRequestBuilder = McpSchema.InitializeRequest
.builder(latestVersion, this.clientCapabilities, this.clientInfo);
if (this.initializeRequestMeta != null) {
initializeRequestBuilder.meta(this.initializeRequestMeta);
}
McpSchema.InitializeRequest initializeRequest = initializeRequestBuilder.build();

Mono<McpSchema.InitializeResult> result = mcpClientSession.sendRequest(McpSchema.METHOD_INITIALIZE,
initializeRequest, McpAsyncClient.INITIALIZE_RESULT_TYPE_REF);
Expand Down Expand Up @@ -355,4 +376,4 @@ public Mono<?> closeGracefully() {
});
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -351,9 +351,9 @@ public class McpAsyncClient {
}).then();
};

this.initializer = new LifecycleInitializer(clientCapabilities, clientInfo, transport.protocolVersions(),
initializationTimeout, ctx -> new McpClientSession(requestTimeout, transport, requestHandlers,
notificationHandlers, con -> con.contextWrite(ctx)),
this.initializer = new LifecycleInitializer(clientCapabilities, clientInfo, features.initializeRequestMeta(),
transport.protocolVersions(), initializationTimeout, ctx -> new McpClientSession(requestTimeout,
transport, requestHandlers, notificationHandlers, con -> con.contextWrite(ctx)),
postInitializationHook);

this.transport.setExceptionHandler(this.initializer::handleException);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -172,6 +173,8 @@ class SyncSpec {

private Implementation clientInfo = Implementation.builder("Java SDK MCP Client", "0.15.0").build();

private Map<String, Object> initializeRequestMeta;

private final Map<String, Root> roots = new HashMap<>();

private final List<Consumer<List<McpSchema.Tool>>> toolsChangeConsumers = new ArrayList<>();
Expand Down Expand Up @@ -263,6 +266,20 @@ public SyncSpec clientInfo(Implementation clientInfo) {
return this;
}

/**
* Sets optional metadata to include in the initialization request's {@code _meta}
* field.
* @param initializeRequestMeta Metadata to send with the initialize request. Must
* not be null.
* @return This builder instance for method chaining
* @throws IllegalArgumentException if initializeRequestMeta is null
*/
public SyncSpec initializeRequestMeta(Map<String, Object> initializeRequestMeta) {
Assert.notNull(initializeRequestMeta, "Initialize request metadata must not be null");
this.initializeRequestMeta = Collections.unmodifiableMap(new HashMap<>(initializeRequestMeta));
return this;
}

/**
* Sets the root URIs that this client can access. Roots define the base URIs for
* resources that the client can request from the server. For example, a root
Expand Down Expand Up @@ -551,10 +568,11 @@ public SyncSpec applyElicitationDefaults(boolean applyElicitationDefaults) {
*/
public McpSyncClient build() {
McpClientFeatures.Sync syncFeatures = new McpClientFeatures.Sync(this.clientInfo, this.capabilities,
this.roots, this.toolsChangeConsumers, this.resourcesChangeConsumers, this.resourcesUpdateConsumers,
this.promptsChangeConsumers, this.loggingConsumers, this.progressConsumers,
this.elicitationCompleteConsumers, this.samplingHandler, this.formElicitationHandler,
this.urlElicitationHandler, this.enableCallToolSchemaCaching, this.applyElicitationDefaults);
this.initializeRequestMeta, this.roots, this.toolsChangeConsumers, this.resourcesChangeConsumers,
this.resourcesUpdateConsumers, this.promptsChangeConsumers, this.loggingConsumers,
this.progressConsumers, this.elicitationCompleteConsumers, this.samplingHandler,
this.formElicitationHandler, this.urlElicitationHandler, this.enableCallToolSchemaCaching,
this.applyElicitationDefaults);

McpClientFeatures.Async asyncFeatures = McpClientFeatures.Async.fromSync(syncFeatures);

Expand Down Expand Up @@ -593,6 +611,8 @@ class AsyncSpec {

private Implementation clientInfo = Implementation.builder("Java SDK MCP Client", "0.15.0").build();

private Map<String, Object> initializeRequestMeta;

private final Map<String, Root> roots = new HashMap<>();

private final List<Function<List<McpSchema.Tool>, Mono<Void>>> toolsChangeConsumers = new ArrayList<>();
Expand Down Expand Up @@ -682,6 +702,20 @@ public AsyncSpec clientInfo(Implementation clientInfo) {
return this;
}

/**
* Sets optional metadata to include in the initialization request's {@code _meta}
* field.
* @param initializeRequestMeta Metadata to send with the initialize request. Must
* not be null.
* @return This builder instance for method chaining
* @throws IllegalArgumentException if initializeRequestMeta is null
*/
public AsyncSpec initializeRequestMeta(Map<String, Object> initializeRequestMeta) {
Assert.notNull(initializeRequestMeta, "Initialize request metadata must not be null");
this.initializeRequestMeta = Collections.unmodifiableMap(new HashMap<>(initializeRequestMeta));
return this;
}

/**
* Sets the root URIs that this client can access. Roots define the base URIs for
* resources that the client can request from the server. For example, a root
Expand Down Expand Up @@ -960,11 +994,11 @@ public McpAsyncClient build() {
: McpJsonDefaults.getSchemaValidator();
return new McpAsyncClient(this.transport, this.requestTimeout, this.initializationTimeout,
jsonSchemaValidator,
new McpClientFeatures.Async(this.clientInfo, this.capabilities, this.roots,
this.toolsChangeConsumers, this.resourcesChangeConsumers, this.resourcesUpdateConsumers,
this.promptsChangeConsumers, this.loggingConsumers, this.progressConsumers,
this.elicitationCompleteConsumers, this.samplingHandler, this.formElicitationHandler,
this.urlElicitationHandler, this.enableCallToolSchemaCaching,
new McpClientFeatures.Async(this.clientInfo, this.capabilities, this.initializeRequestMeta,
this.roots, this.toolsChangeConsumers, this.resourcesChangeConsumers,
this.resourcesUpdateConsumers, this.promptsChangeConsumers, this.loggingConsumers,
this.progressConsumers, this.elicitationCompleteConsumers, this.samplingHandler,
this.formElicitationHandler, this.urlElicitationHandler, this.enableCallToolSchemaCaching,
this.applyElicitationDefaults));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package io.modelcontextprotocol.client;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -54,6 +55,7 @@ class McpClientFeatures {
*
* @param clientInfo the client implementation information.
* @param clientCapabilities the client capabilities.
* @param initializeRequestMeta metadata to include in the initialize request.
* @param roots the roots.
* @param toolsChangeConsumers the tools change consumers.
* @param resourcesChangeConsumers the resources change consumers.
Expand All @@ -68,7 +70,8 @@ class McpClientFeatures {
* in the {@code requestedSchema}.
*/
record Async(McpSchema.Implementation clientInfo, McpSchema.ClientCapabilities clientCapabilities,
Map<String, McpSchema.Root> roots, List<Function<List<McpSchema.Tool>, Mono<Void>>> toolsChangeConsumers,
Map<String, Object> initializeRequestMeta, Map<String, McpSchema.Root> roots,
List<Function<List<McpSchema.Tool>, Mono<Void>>> toolsChangeConsumers,
List<Function<List<McpSchema.Resource>, Mono<Void>>> resourcesChangeConsumers,
List<Function<List<McpSchema.ResourceContents>, Mono<Void>>> resourcesUpdateConsumers,
List<Function<List<McpSchema.Prompt>, Mono<Void>>> promptsChangeConsumers,
Expand Down Expand Up @@ -110,13 +113,51 @@ public Async(McpSchema.Implementation clientInfo, McpSchema.ClientCapabilities c
Function<McpSchema.ElicitUrlRequest, Mono<McpSchema.ElicitResult>> urlElicitationHandler,
boolean enableCallToolSchemaCaching, boolean applyElicitationDefaults) {

this(clientInfo, clientCapabilities, null, roots, toolsChangeConsumers, resourcesChangeConsumers,
resourcesUpdateConsumers, promptsChangeConsumers, loggingConsumers, progressConsumers,
elicitationCompleteConsumers, samplingHandler, formElicitationHandler, urlElicitationHandler,
enableCallToolSchemaCaching, applyElicitationDefaults);
}

/**
* Create an instance and validate the arguments.
* @param clientCapabilities the client capabilities.
* @param initializeRequestMeta metadata to include in the initialize request.
* @param roots the roots.
* @param toolsChangeConsumers the tools change consumers.
* @param resourcesChangeConsumers the resources change consumers.
* @param promptsChangeConsumers the prompts change consumers.
* @param loggingConsumers the logging consumers.
* @param progressConsumers the progress consumers.
* @param samplingHandler the sampling handler.
* @param formElicitationHandler the elicitation handler.
* @param enableCallToolSchemaCaching whether to enable call tool schema caching.
* @param applyElicitationDefaults whether the client should fill in missing
* fields of an accepted {@code ElicitResult.content} with the {@code default}
* values declared in the {@code requestedSchema}.
*/
public Async(McpSchema.Implementation clientInfo, McpSchema.ClientCapabilities clientCapabilities,
Map<String, Object> initializeRequestMeta, Map<String, McpSchema.Root> roots,
List<Function<List<McpSchema.Tool>, Mono<Void>>> toolsChangeConsumers,
List<Function<List<McpSchema.Resource>, Mono<Void>>> resourcesChangeConsumers,
List<Function<List<McpSchema.ResourceContents>, Mono<Void>>> resourcesUpdateConsumers,
List<Function<List<McpSchema.Prompt>, Mono<Void>>> promptsChangeConsumers,
List<Function<McpSchema.LoggingMessageNotification, Mono<Void>>> loggingConsumers,
List<Function<McpSchema.ProgressNotification, Mono<Void>>> progressConsumers,
List<Function<McpSchema.ElicitationCompleteNotification, Mono<Void>>> elicitationCompleteConsumers,
Function<McpSchema.CreateMessageRequest, Mono<McpSchema.CreateMessageResult>> samplingHandler,
Function<McpSchema.ElicitFormRequest, Mono<McpSchema.ElicitResult>> formElicitationHandler,
Function<McpSchema.ElicitUrlRequest, Mono<McpSchema.ElicitResult>> urlElicitationHandler,
boolean enableCallToolSchemaCaching, boolean applyElicitationDefaults) {

Assert.notNull(clientInfo, "Client info must not be null");
this.clientInfo = clientInfo;
this.clientCapabilities = (clientCapabilities != null) ? clientCapabilities
: new McpSchema.ClientCapabilities(null,
!Utils.isEmpty(roots) ? new McpSchema.ClientCapabilities.RootCapabilities(false) : null,
samplingHandler != null ? new McpSchema.ClientCapabilities.Sampling() : null,
elicitationCapabilities(formElicitationHandler, urlElicitationHandler));
this.initializeRequestMeta = copyInitializeRequestMeta(initializeRequestMeta);
this.roots = roots != null ? new ConcurrentHashMap<>(roots) : new ConcurrentHashMap<>();

this.toolsChangeConsumers = toolsChangeConsumers != null ? toolsChangeConsumers : List.of();
Expand Down Expand Up @@ -219,11 +260,11 @@ public static Async fromSync(Sync syncSpec) {
.subscribeOn(Schedulers.boundedElastic())
: null;

return new Async(syncSpec.clientInfo(), syncSpec.clientCapabilities(), syncSpec.roots(),
toolsChangeConsumers, resourcesChangeConsumers, resourcesUpdateConsumers, promptsChangeConsumers,
loggingConsumers, progressConsumers, elicitationCompleteConsumers, samplingHandler,
formElicitationHandler, urlElicitationHandler, syncSpec.enableCallToolSchemaCaching,
syncSpec.applyElicitationDefaults);
return new Async(syncSpec.clientInfo(), syncSpec.clientCapabilities(), syncSpec.initializeRequestMeta(),
syncSpec.roots(), toolsChangeConsumers, resourcesChangeConsumers, resourcesUpdateConsumers,
promptsChangeConsumers, loggingConsumers, progressConsumers, elicitationCompleteConsumers,
samplingHandler, formElicitationHandler, urlElicitationHandler,
syncSpec.enableCallToolSchemaCaching, syncSpec.applyElicitationDefaults);
}

}
Expand All @@ -234,6 +275,7 @@ public static Async fromSync(Sync syncSpec) {
*
* @param clientInfo the client implementation information.
* @param clientCapabilities the client capabilities.
* @param initializeRequestMeta metadata to include in the initialize request.
* @param roots the roots.
* @param toolsChangeConsumers the tools change consumers.
* @param resourcesChangeConsumers the resources change consumers.
Expand All @@ -248,7 +290,8 @@ public static Async fromSync(Sync syncSpec) {
* in the {@code requestedSchema}.
*/
public record Sync(McpSchema.Implementation clientInfo, McpSchema.ClientCapabilities clientCapabilities,
Map<String, McpSchema.Root> roots, List<Consumer<List<McpSchema.Tool>>> toolsChangeConsumers,
Map<String, Object> initializeRequestMeta, Map<String, McpSchema.Root> roots,
List<Consumer<List<McpSchema.Tool>>> toolsChangeConsumers,
List<Consumer<List<McpSchema.Resource>>> resourcesChangeConsumers,
List<Consumer<List<McpSchema.ResourceContents>>> resourcesUpdateConsumers,
List<Consumer<List<McpSchema.Prompt>>> promptsChangeConsumers,
Expand Down Expand Up @@ -291,13 +334,53 @@ public Sync(McpSchema.Implementation clientInfo, McpSchema.ClientCapabilities cl
Function<McpSchema.ElicitUrlRequest, McpSchema.ElicitResult> urlElicitationHandler,
boolean enableCallToolSchemaCaching, boolean applyElicitationDefaults) {

this(clientInfo, clientCapabilities, null, roots, toolsChangeConsumers, resourcesChangeConsumers,
resourcesUpdateConsumers, promptsChangeConsumers, loggingConsumers, progressConsumers,
elicitationCompleteConsumers, samplingHandler, formElicitationHandler, urlElicitationHandler,
enableCallToolSchemaCaching, applyElicitationDefaults);
}

/**
* Create an instance and validate the arguments.
* @param clientInfo the client implementation information.
* @param clientCapabilities the client capabilities.
* @param initializeRequestMeta metadata to include in the initialize request.
* @param roots the roots.
* @param toolsChangeConsumers the tools change consumers.
* @param resourcesChangeConsumers the resources change consumers.
* @param resourcesUpdateConsumers the resource update consumers.
* @param promptsChangeConsumers the prompts change consumers.
* @param loggingConsumers the logging consumers.
* @param progressConsumers the progress consumers.
* @param samplingHandler the sampling handler.
* @param formElicitationHandler the elicitation handler.
* @param enableCallToolSchemaCaching whether to enable call tool schema caching.
* @param applyElicitationDefaults whether the client should fill in missing
* fields of an accepted {@code ElicitResult.content} with the {@code default}
* values declared in the {@code requestedSchema}.
*/
public Sync(McpSchema.Implementation clientInfo, McpSchema.ClientCapabilities clientCapabilities,
Map<String, Object> initializeRequestMeta, Map<String, McpSchema.Root> roots,
List<Consumer<List<McpSchema.Tool>>> toolsChangeConsumers,
List<Consumer<List<McpSchema.Resource>>> resourcesChangeConsumers,
List<Consumer<List<McpSchema.ResourceContents>>> resourcesUpdateConsumers,
List<Consumer<List<McpSchema.Prompt>>> promptsChangeConsumers,
List<Consumer<McpSchema.LoggingMessageNotification>> loggingConsumers,
List<Consumer<McpSchema.ProgressNotification>> progressConsumers,
List<Consumer<McpSchema.ElicitationCompleteNotification>> elicitationCompleteConsumers,
Function<McpSchema.CreateMessageRequest, McpSchema.CreateMessageResult> samplingHandler,
Function<McpSchema.ElicitFormRequest, McpSchema.ElicitResult> formElicitationHandler,
Function<McpSchema.ElicitUrlRequest, McpSchema.ElicitResult> urlElicitationHandler,
boolean enableCallToolSchemaCaching, boolean applyElicitationDefaults) {

Assert.notNull(clientInfo, "Client info must not be null");
this.clientInfo = clientInfo;
this.clientCapabilities = (clientCapabilities != null) ? clientCapabilities
: new McpSchema.ClientCapabilities(null,
!Utils.isEmpty(roots) ? new McpSchema.ClientCapabilities.RootCapabilities(false) : null,
samplingHandler != null ? new McpSchema.ClientCapabilities.Sampling() : null,
elicitationCapabilities(formElicitationHandler, urlElicitationHandler));
this.initializeRequestMeta = copyInitializeRequestMeta(initializeRequestMeta);
this.roots = roots != null ? new HashMap<>(roots) : new HashMap<>();

this.toolsChangeConsumers = toolsChangeConsumers != null ? toolsChangeConsumers : List.of();
Expand Down Expand Up @@ -333,6 +416,10 @@ public Sync(McpSchema.Implementation clientInfo, McpSchema.ClientCapabilities cl
}
}

private static Map<String, Object> copyInitializeRequestMeta(Map<String, Object> initializeRequestMeta) {
return initializeRequestMeta != null ? Collections.unmodifiableMap(new HashMap<>(initializeRequestMeta)) : null;
}

private static McpSchema.ClientCapabilities.Elicitation elicitationCapabilities(
Function<McpSchema.ElicitFormRequest, ?> formElicitationHandler,
Function<McpSchema.ElicitUrlRequest, ?> urlElicitationHandler) {
Expand Down
Loading