Skip to content

feat: Added title to Implementation specification to match Python SDK #343

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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 @@ -66,7 +66,7 @@
* McpClient.async(transport)
* .requestTimeout(Duration.ofSeconds(10))
* .capabilities(new ClientCapabilities(...))
* .clientInfo(new Implementation("My Client", "1.0.0"))
* .clientInfo(new Implementation("My Client", "Title", "1.0.0"))
* .roots(new Root("file://workspace", "Workspace Files"))
* .toolsChangeConsumer(tools -> Mono.fromRunnable(() -> System.out.println("Tools updated: " + tools)))
* .resourcesChangeConsumer(resources -> Mono.fromRunnable(() -> System.out.println("Resources updated: " + resources)))
Expand Down Expand Up @@ -163,7 +163,7 @@ class SyncSpec {

private ClientCapabilities capabilities;

private Implementation clientInfo = new Implementation("Java SDK MCP Client", "1.0.0");
private Implementation clientInfo = new Implementation("Java SDK MCP Client", null, "1.0.0");

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

Expand Down Expand Up @@ -421,7 +421,7 @@ class AsyncSpec {

private ClientCapabilities capabilities;

private Implementation clientInfo = new Implementation("Spring AI MCP Client", "0.3.1");
private Implementation clientInfo = new Implementation("Spring AI MCP Client", null, "0.3.1");

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

Expand Down
14 changes: 8 additions & 6 deletions mcp/src/main/java/io/modelcontextprotocol/server/McpServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ static AsyncSpecification async(McpServerTransportProvider transportProvider) {
class AsyncSpecification {

private static final McpSchema.Implementation DEFAULT_SERVER_INFO = new McpSchema.Implementation("mcp-server",
"1.0.0");
null, "1.0.0");

private final McpServerTransportProvider transportProvider;

Expand Down Expand Up @@ -256,15 +256,16 @@ public AsyncSpecification serverInfo(McpSchema.Implementation serverInfo) {
* is a convenience method alternative to
* {@link #serverInfo(McpSchema.Implementation)}.
* @param name The server name. Must not be null or empty.
* @param title The server title. May be null or empty.
* @param version The server version. Must not be null or empty.
* @return This builder instance for method chaining
* @throws IllegalArgumentException if name or version is null or empty
* @see #serverInfo(McpSchema.Implementation)
*/
public AsyncSpecification serverInfo(String name, String version) {
public AsyncSpecification serverInfo(String name, String title, String version) {
Assert.hasText(name, "Name must not be null or empty");
Assert.hasText(version, "Version must not be null or empty");
this.serverInfo = new McpSchema.Implementation(name, version);
this.serverInfo = new McpSchema.Implementation(name, title, version);
return this;
}

Expand Down Expand Up @@ -646,7 +647,7 @@ public McpAsyncServer build() {
class SyncSpecification {

private static final McpSchema.Implementation DEFAULT_SERVER_INFO = new McpSchema.Implementation("mcp-server",
"1.0.0");
null, "1.0.0");

private McpUriTemplateManagerFactory uriTemplateManagerFactory = new DeafaultMcpUriTemplateManagerFactory();

Expand Down Expand Up @@ -748,15 +749,16 @@ public SyncSpecification serverInfo(McpSchema.Implementation serverInfo) {
* is a convenience method alternative to
* {@link #serverInfo(McpSchema.Implementation)}.
* @param name The server name. Must not be null or empty.
* @param title The server title. May be null or empty.
* @param version The server version. Must not be null or empty.
* @return This builder instance for method chaining
* @throws IllegalArgumentException if name or version is null or empty
* @see #serverInfo(McpSchema.Implementation)
*/
public SyncSpecification serverInfo(String name, String version) {
public SyncSpecification serverInfo(String name, String title, String version) {
Assert.hasText(name, "Name must not be null or empty");
Assert.hasText(version, "Version must not be null or empty");
this.serverInfo = new McpSchema.Implementation(name, version);
this.serverInfo = new McpSchema.Implementation(name, title, version);
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ public ServerCapabilities build() {
@JsonIgnoreProperties(ignoreUnknown = true)
public record Implementation(// @formatter:off
@JsonProperty("name") String name,
@JsonProperty("title") String title,
@JsonProperty("version") String version) {
} // @formatter:on

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@

class McpAsyncClientResponseHandlerTests {

private static final McpSchema.Implementation SERVER_INFO = new McpSchema.Implementation("test-server", "1.0.0");
private static final McpSchema.Implementation SERVER_INFO = new McpSchema.Implementation("test-server", null,
"1.0.0");

private static final McpSchema.ServerCapabilities SERVER_CAPABILITIES = McpSchema.ServerCapabilities.builder()
.tools(true)
Expand All @@ -57,7 +58,7 @@ private static MockMcpClientTransport initializationEnabledTransport(

@Test
void testSuccessfulInitialization() {
McpSchema.Implementation serverInfo = new McpSchema.Implementation("mcp-test-server", "0.0.1");
McpSchema.Implementation serverInfo = new McpSchema.Implementation("mcp-test-server", null, "0.0.1");
McpSchema.ServerCapabilities serverCapabilities = McpSchema.ServerCapabilities.builder()
.tools(false)
.resources(true, true) // Enable both resources and resource templates
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

class McpAsyncClientTests {

public static final McpSchema.Implementation MOCK_SERVER_INFO = new McpSchema.Implementation("test-server",
public static final McpSchema.Implementation MOCK_SERVER_INFO = new McpSchema.Implementation("test-server", null,
"1.0.0");

public static final McpSchema.ServerCapabilities MOCK_SERVER_CAPABILITIES = McpSchema.ServerCapabilities.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ class McpClientProtocolVersionTests {

private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(30);

private static final McpSchema.Implementation CLIENT_INFO = new McpSchema.Implementation("test-client", "1.0.0");
private static final McpSchema.Implementation CLIENT_INFO = new McpSchema.Implementation("test-client", null,
"1.0.0");

@Test
void shouldUseLatestVersionByDefault() {
Expand All @@ -45,7 +46,7 @@ void shouldUseLatestVersionByDefault() {

transport.simulateIncomingMessage(new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(),
new McpSchema.InitializeResult(McpSchema.LATEST_PROTOCOL_VERSION, null,
new McpSchema.Implementation("test-server", "1.0.0"), null),
new McpSchema.Implementation("test-server", null, "1.0.0"), null),
null));
}).assertNext(result -> {
assertThat(result.protocolVersion()).isEqualTo(McpSchema.LATEST_PROTOCOL_VERSION);
Expand Down Expand Up @@ -78,10 +79,12 @@ void shouldNegotiateSpecificVersion() {
McpSchema.InitializeRequest initRequest = (McpSchema.InitializeRequest) request.params();
assertThat(initRequest.protocolVersion()).isIn(List.of(oldVersion, McpSchema.LATEST_PROTOCOL_VERSION));

transport.simulateIncomingMessage(new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(),
new McpSchema.InitializeResult(oldVersion, null,
new McpSchema.Implementation("test-server", "1.0.0"), null),
null));
transport
.simulateIncomingMessage(
new McpSchema.JSONRPCResponse(
McpSchema.JSONRPC_VERSION, request.id(), new McpSchema.InitializeResult(oldVersion,
null, new McpSchema.Implementation("test-server", null, "1.0.0"), null),
null));
}).assertNext(result -> {
assertThat(result.protocolVersion()).isEqualTo(oldVersion);
}).verifyComplete();
Expand Down Expand Up @@ -109,7 +112,7 @@ void shouldFailForUnsupportedVersion() {

transport.simulateIncomingMessage(new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(),
new McpSchema.InitializeResult(unsupportedVersion, null,
new McpSchema.Implementation("test-server", "1.0.0"), null),
new McpSchema.Implementation("test-server", null, "1.0.0"), null),
null));
}).expectError(McpError.class).verify();
}
Expand Down Expand Up @@ -142,7 +145,7 @@ void shouldUseHighestVersionWhenMultipleSupported() {

transport.simulateIncomingMessage(new McpSchema.JSONRPCResponse(McpSchema.JSONRPC_VERSION, request.id(),
new McpSchema.InitializeResult(latestVersion, null,
new McpSchema.Implementation("test-server", "1.0.0"), null),
new McpSchema.Implementation("test-server", null, "1.0.0"), null),
null));
}).assertNext(result -> {
assertThat(result.protocolVersion()).isEqualTo(latestVersion);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,18 @@ void testConstructorWithInvalidArguments() {

@Test
void testGracefulShutdown() {
var mcpAsyncServer = McpServer.async(createMcpTransportProvider()).serverInfo("test-server", "1.0.0").build();
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
.serverInfo("test-server", null, "1.0.0")
.build();

StepVerifier.create(mcpAsyncServer.closeGracefully()).verifyComplete();
}

@Test
void testImmediateClose() {
var mcpAsyncServer = McpServer.async(createMcpTransportProvider()).serverInfo("test-server", "1.0.0").build();
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
.serverInfo("test-server", null, "1.0.0")
.build();

assertThatCode(() -> mcpAsyncServer.close()).doesNotThrowAnyException();
}
Expand All @@ -104,7 +108,7 @@ void testImmediateClose() {
void testAddTool() {
Tool newTool = new McpSchema.Tool("new-tool", "New test tool", emptyJsonSchema);
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
.serverInfo("test-server", "1.0.0")
.serverInfo("test-server", null, "1.0.0")
.capabilities(ServerCapabilities.builder().tools(true).build())
.build();

Expand All @@ -120,7 +124,7 @@ void testAddDuplicateTool() {
Tool duplicateTool = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);

var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
.serverInfo("test-server", "1.0.0")
.serverInfo("test-server", null, "1.0.0")
.capabilities(ServerCapabilities.builder().tools(true).build())
.tool(duplicateTool, (exchange, args) -> Mono.just(new CallToolResult(List.of(), false)))
.build();
Expand All @@ -141,7 +145,7 @@ void testRemoveTool() {
Tool too = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);

var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
.serverInfo("test-server", "1.0.0")
.serverInfo("test-server", null, "1.0.0")
.capabilities(ServerCapabilities.builder().tools(true).build())
.tool(too, (exchange, args) -> Mono.just(new CallToolResult(List.of(), false)))
.build();
Expand All @@ -154,7 +158,7 @@ void testRemoveTool() {
@Test
void testRemoveNonexistentTool() {
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
.serverInfo("test-server", "1.0.0")
.serverInfo("test-server", null, "1.0.0")
.capabilities(ServerCapabilities.builder().tools(true).build())
.build();

Expand All @@ -170,7 +174,7 @@ void testNotifyToolsListChanged() {
Tool too = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema);

var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
.serverInfo("test-server", "1.0.0")
.serverInfo("test-server", null, "1.0.0")
.capabilities(ServerCapabilities.builder().tools(true).build())
.tool(too, (exchange, args) -> Mono.just(new CallToolResult(List.of(), false)))
.build();
Expand All @@ -186,7 +190,9 @@ void testNotifyToolsListChanged() {

@Test
void testNotifyResourcesListChanged() {
var mcpAsyncServer = McpServer.async(createMcpTransportProvider()).serverInfo("test-server", "1.0.0").build();
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
.serverInfo("test-server", null, "1.0.0")
.build();

StepVerifier.create(mcpAsyncServer.notifyResourcesListChanged()).verifyComplete();

Expand All @@ -195,7 +201,9 @@ void testNotifyResourcesListChanged() {

@Test
void testNotifyResourcesUpdated() {
var mcpAsyncServer = McpServer.async(createMcpTransportProvider()).serverInfo("test-server", "1.0.0").build();
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
.serverInfo("test-server", null, "1.0.0")
.build();

StepVerifier
.create(mcpAsyncServer
Expand All @@ -208,7 +216,7 @@ void testNotifyResourcesUpdated() {
@Test
void testAddResource() {
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
.serverInfo("test-server", "1.0.0")
.serverInfo("test-server", null, "1.0.0")
.capabilities(ServerCapabilities.builder().resources(true, false).build())
.build();

Expand All @@ -225,7 +233,7 @@ void testAddResource() {
@Test
void testAddResourceWithNullSpecification() {
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
.serverInfo("test-server", "1.0.0")
.serverInfo("test-server", null, "1.0.0")
.capabilities(ServerCapabilities.builder().resources(true, false).build())
.build();

Expand All @@ -241,7 +249,7 @@ void testAddResourceWithNullSpecification() {
void testAddResourceWithoutCapability() {
// Create a server without resource capabilities
McpAsyncServer serverWithoutResources = McpServer.async(createMcpTransportProvider())
.serverInfo("test-server", "1.0.0")
.serverInfo("test-server", null, "1.0.0")
.build();

Resource resource = new Resource(TEST_RESOURCE_URI, "Test Resource", "text/plain", "Test resource description",
Expand All @@ -259,7 +267,7 @@ void testAddResourceWithoutCapability() {
void testRemoveResourceWithoutCapability() {
// Create a server without resource capabilities
McpAsyncServer serverWithoutResources = McpServer.async(createMcpTransportProvider())
.serverInfo("test-server", "1.0.0")
.serverInfo("test-server", null, "1.0.0")
.build();

StepVerifier.create(serverWithoutResources.removeResource(TEST_RESOURCE_URI)).verifyErrorSatisfies(error -> {
Expand All @@ -274,7 +282,9 @@ void testRemoveResourceWithoutCapability() {

@Test
void testNotifyPromptsListChanged() {
var mcpAsyncServer = McpServer.async(createMcpTransportProvider()).serverInfo("test-server", "1.0.0").build();
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
.serverInfo("test-server", null, "1.0.0")
.build();

StepVerifier.create(mcpAsyncServer.notifyPromptsListChanged()).verifyComplete();

Expand All @@ -284,7 +294,7 @@ void testNotifyPromptsListChanged() {
@Test
void testAddPromptWithNullSpecification() {
var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
.serverInfo("test-server", "1.0.0")
.serverInfo("test-server", null, "1.0.0")
.capabilities(ServerCapabilities.builder().prompts(false).build())
.build();

Expand All @@ -298,7 +308,7 @@ void testAddPromptWithNullSpecification() {
void testAddPromptWithoutCapability() {
// Create a server without prompt capabilities
McpAsyncServer serverWithoutPrompts = McpServer.async(createMcpTransportProvider())
.serverInfo("test-server", "1.0.0")
.serverInfo("test-server", null, "1.0.0")
.build();

Prompt prompt = new Prompt(TEST_PROMPT_NAME, "Test Prompt", List.of());
Expand All @@ -316,7 +326,7 @@ void testAddPromptWithoutCapability() {
void testRemovePromptWithoutCapability() {
// Create a server without prompt capabilities
McpAsyncServer serverWithoutPrompts = McpServer.async(createMcpTransportProvider())
.serverInfo("test-server", "1.0.0")
.serverInfo("test-server", null, "1.0.0")
.build();

StepVerifier.create(serverWithoutPrompts.removePrompt(TEST_PROMPT_NAME)).verifyErrorSatisfies(error -> {
Expand All @@ -335,7 +345,7 @@ void testRemovePrompt() {
.of(new PromptMessage(McpSchema.Role.ASSISTANT, new McpSchema.TextContent("Test content"))))));

var mcpAsyncServer = McpServer.async(createMcpTransportProvider())
.serverInfo("test-server", "1.0.0")
.serverInfo("test-server", null, "1.0.0")
.capabilities(ServerCapabilities.builder().prompts(true).build())
.prompts(specification)
.build();
Expand All @@ -348,7 +358,7 @@ void testRemovePrompt() {
@Test
void testRemoveNonexistentPrompt() {
var mcpAsyncServer2 = McpServer.async(createMcpTransportProvider())
.serverInfo("test-server", "1.0.0")
.serverInfo("test-server", null, "1.0.0")
.capabilities(ServerCapabilities.builder().prompts(true).build())
.build();

Expand All @@ -372,7 +382,7 @@ void testRootsChangeHandlers() {
var consumerCalled = new boolean[1];

var singleConsumerServer = McpServer.async(createMcpTransportProvider())
.serverInfo("test-server", "1.0.0")
.serverInfo("test-server", null, "1.0.0")
.rootsChangeHandlers(List.of((exchange, roots) -> Mono.fromRunnable(() -> {
consumerCalled[0] = true;
if (!roots.isEmpty()) {
Expand All @@ -392,7 +402,7 @@ void testRootsChangeHandlers() {
var rootsContent = new List[1];

var multipleConsumersServer = McpServer.async(createMcpTransportProvider())
.serverInfo("test-server", "1.0.0")
.serverInfo("test-server", null, "1.0.0")
.rootsChangeHandlers(List.of((exchange, roots) -> Mono.fromRunnable(() -> {
consumer1Called[0] = true;
rootsContent[0] = roots;
Expand All @@ -406,7 +416,7 @@ void testRootsChangeHandlers() {

// Test error handling
var errorHandlingServer = McpServer.async(createMcpTransportProvider())
.serverInfo("test-server", "1.0.0")
.serverInfo("test-server", null, "1.0.0")
.rootsChangeHandlers(List.of((exchange, roots) -> {
throw new RuntimeException("Test error");
}))
Expand All @@ -419,7 +429,7 @@ void testRootsChangeHandlers() {

// Test without consumers
var noConsumersServer = McpServer.async(createMcpTransportProvider())
.serverInfo("test-server", "1.0.0")
.serverInfo("test-server", null, "1.0.0")
.build();

assertThat(noConsumersServer).isNotNull();
Expand Down
Loading