Skip to content

Commit 35da1a4

Browse files
committed
feat: Add titles
1 parent b328051 commit 35da1a4

File tree

11 files changed

+147
-52
lines changed

11 files changed

+147
-52
lines changed

mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxSseIntegrationTests.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -996,19 +996,20 @@ void testCompletionShouldReturnExpectedSuggestions(String clientType) {
996996
var mcpServer = McpServer.sync(mcpServerTransportProvider)
997997
.capabilities(ServerCapabilities.builder().completions().build())
998998
.prompts(new McpServerFeatures.SyncPromptSpecification(
999-
new Prompt("code_review", "this is code review prompt",
1000-
List.of(new PromptArgument("language", "string", false))),
999+
new Prompt("code_review", "Code review", "this is code review prompt",
1000+
List.of(new PromptArgument("language", "Language", "string", false))),
10011001
(mcpSyncServerExchange, getPromptRequest) -> null))
10021002
.completions(new McpServerFeatures.SyncCompletionSpecification(
1003-
new McpSchema.PromptReference("ref/prompt", "code_review"), completionHandler))
1003+
new McpSchema.PromptReference("ref/prompt", "code_review", "Code review"), completionHandler))
10041004
.build();
10051005

10061006
try (var mcpClient = clientBuilder.build()) {
10071007

10081008
InitializeResult initResult = mcpClient.initialize();
10091009
assertThat(initResult).isNotNull();
10101010

1011-
CompleteRequest request = new CompleteRequest(new PromptReference("ref/prompt", "code_review"),
1011+
CompleteRequest request = new CompleteRequest(
1012+
new PromptReference("ref/prompt", "code_review", "Code review"),
10121013
new CompleteRequest.CompleteArgument("language", "py"));
10131014

10141015
CompleteResult result = mcpClient.completeCompletion(request);

mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ void testListAllPromptsReturnsImmutableList() {
420420
.consumeNextWith(result -> {
421421
assertThat(result.prompts()).isNotNull();
422422
// Verify that the returned list is immutable
423-
assertThatThrownBy(() -> result.prompts().add(new Prompt("test", "test", null)))
423+
assertThatThrownBy(() -> result.prompts().add(new Prompt("test", "Test", "test", null)))
424424
.isInstanceOf(UnsupportedOperationException.class);
425425
})
426426
.verifyComplete();
@@ -604,7 +604,7 @@ void testListAllResourceTemplatesReturnsImmutableList() {
604604
assertThat(result.resourceTemplates()).isNotNull();
605605
// Verify that the returned list is immutable
606606
assertThatThrownBy(() -> result.resourceTemplates()
607-
.add(new McpSchema.ResourceTemplate("test://template", "test", null, null, null)))
607+
.add(new McpSchema.ResourceTemplate("test://template", "test", "test", null, null, null)))
608608
.isInstanceOf(UnsupportedOperationException.class);
609609
})
610610
.verifyComplete();

mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ void testAddPromptWithoutCapability() {
302302
.serverInfo("test-server", "1.0.0")
303303
.build();
304304

305-
Prompt prompt = new Prompt(TEST_PROMPT_NAME, "Test Prompt", List.of());
305+
Prompt prompt = new Prompt(TEST_PROMPT_NAME, "Test Prompt", "Test Prompt", List.of());
306306
McpServerFeatures.AsyncPromptSpecification specification = new McpServerFeatures.AsyncPromptSpecification(
307307
prompt, (exchange, req) -> Mono.just(new GetPromptResult("Test prompt description", List
308308
.of(new PromptMessage(McpSchema.Role.ASSISTANT, new McpSchema.TextContent("Test content"))))));
@@ -330,7 +330,7 @@ void testRemovePromptWithoutCapability() {
330330
void testRemovePrompt() {
331331
String TEST_PROMPT_NAME_TO_REMOVE = "TEST_PROMPT_NAME678";
332332

333-
Prompt prompt = new Prompt(TEST_PROMPT_NAME_TO_REMOVE, "Test Prompt", List.of());
333+
Prompt prompt = new Prompt(TEST_PROMPT_NAME_TO_REMOVE, "Test Prompt", "Test Prompt", List.of());
334334
McpServerFeatures.AsyncPromptSpecification specification = new McpServerFeatures.AsyncPromptSpecification(
335335
prompt, (exchange, req) -> Mono.just(new GetPromptResult("Test prompt description", List
336336
.of(new PromptMessage(McpSchema.Role.ASSISTANT, new McpSchema.TextContent("Test content"))))));

mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ void testAddPromptWithoutCapability() {
289289
.serverInfo("test-server", "1.0.0")
290290
.build();
291291

292-
Prompt prompt = new Prompt(TEST_PROMPT_NAME, "Test Prompt", List.of());
292+
Prompt prompt = new Prompt(TEST_PROMPT_NAME, "Test Prompt", "Test Prompt", List.of());
293293
McpServerFeatures.SyncPromptSpecification specification = new McpServerFeatures.SyncPromptSpecification(prompt,
294294
(exchange, req) -> new GetPromptResult("Test prompt description", List
295295
.of(new PromptMessage(McpSchema.Role.ASSISTANT, new McpSchema.TextContent("Test content")))));
@@ -310,7 +310,7 @@ void testRemovePromptWithoutCapability() {
310310

311311
@Test
312312
void testRemovePrompt() {
313-
Prompt prompt = new Prompt(TEST_PROMPT_NAME, "Test Prompt", List.of());
313+
Prompt prompt = new Prompt(TEST_PROMPT_NAME, "Test Prompt", "Test Prompt", List.of());
314314
McpServerFeatures.SyncPromptSpecification specification = new McpServerFeatures.SyncPromptSpecification(prompt,
315315
(exchange, req) -> new GetPromptResult("Test prompt description", List
316316
.of(new PromptMessage(McpSchema.Role.ASSISTANT, new McpSchema.TextContent("Test content")))));

mcp/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -462,8 +462,8 @@ private List<McpSchema.ResourceTemplate> getResourceTemplates() {
462462
.filter(uri -> uri.contains("{"))
463463
.map(uri -> {
464464
var resource = this.resources.get(uri).resource();
465-
var template = new McpSchema.ResourceTemplate(resource.uri(), resource.name(), resource.description(),
466-
resource.mimeType(), resource.annotations());
465+
var template = new McpSchema.ResourceTemplate(resource.uri(), resource.name(), resource.title(),
466+
resource.description(), resource.mimeType(), resource.annotations());
467467
return template;
468468
})
469469
.toList();
@@ -725,7 +725,8 @@ private McpSchema.CompleteRequest parseCompletionParams(Object object) {
725725
String refType = (String) refMap.get("type");
726726

727727
McpSchema.CompleteReference ref = switch (refType) {
728-
case "ref/prompt" -> new McpSchema.PromptReference(refType, (String) refMap.get("name"));
728+
case "ref/prompt" -> new McpSchema.PromptReference(refType, (String) refMap.get("name"),
729+
refMap.get("title") != null ? (String) refMap.get("title") : null);
729730
case "ref/resource" -> new McpSchema.ResourceReference(refType, (String) refMap.get("uri"));
730731
default -> throw new IllegalArgumentException("Invalid ref type: " + refType);
731732
};

mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java

Lines changed: 104 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
*
3333
* @author Christian Tzolov
3434
* @author Luca Chang
35+
* @author Surbhi Bansal
3536
*/
3637
public final class McpSchema {
3738

@@ -451,7 +452,12 @@ public ServerCapabilities build() {
451452
@JsonIgnoreProperties(ignoreUnknown = true)
452453
public record Implementation(// @formatter:off
453454
@JsonProperty("name") String name,
454-
@JsonProperty("version") String version) {
455+
@JsonProperty("title") String title,
456+
@JsonProperty("version") String version) implements BaseMetadata {
457+
458+
public Implementation(String name, String version) {
459+
this(name, null, version);
460+
}
455461
} // @formatter:on
456462

457463
// Existing Enums and Base Types (from previous implementation)
@@ -499,12 +505,10 @@ public record Annotations( // @formatter:off
499505
* interface is implemented by both {@link Resource} and {@link ResourceLink} to
500506
* provide a consistent way to access resource metadata.
501507
*/
502-
public interface ResourceContent {
508+
public interface ResourceContent extends BaseMetadata {
503509

504510
String uri();
505511

506-
String name();
507-
508512
String description();
509513

510514
String mimeType();
@@ -515,6 +519,28 @@ public interface ResourceContent {
515519

516520
}
517521

522+
/**
523+
* Base interface for metadata with name (identifier) and title (display name)
524+
* properties.
525+
*/
526+
public interface BaseMetadata {
527+
528+
/**
529+
* Intended for programmatic or logical use, but used as a display name in past
530+
* specs or fallback (if title isn't present).
531+
*/
532+
String name();
533+
534+
/**
535+
* Intended for UI and end-user contexts — optimized to be human-readable and
536+
* easily understood, even by those unfamiliar with domain-specific terminology.
537+
*
538+
* If not provided, the name should be used for display.
539+
*/
540+
String title();
541+
542+
}
543+
518544
/**
519545
* A known resource that the server is capable of reading.
520546
*
@@ -536,6 +562,7 @@ public interface ResourceContent {
536562
public record Resource( // @formatter:off
537563
@JsonProperty("uri") String uri,
538564
@JsonProperty("name") String name,
565+
@JsonProperty("title") String title,
539566
@JsonProperty("description") String description,
540567
@JsonProperty("mimeType") String mimeType,
541568
@JsonProperty("size") Long size,
@@ -547,7 +574,7 @@ public record Resource( // @formatter:off
547574
*/
548575
@Deprecated
549576
public Resource(String uri, String name, String description, String mimeType, Annotations annotations) {
550-
this(uri, name, description, mimeType, null, annotations);
577+
this(uri, name, null, description, mimeType, null, annotations);
551578
}
552579

553580
public static Builder builder() {
@@ -557,6 +584,7 @@ public static Builder builder() {
557584
public static class Builder {
558585
private String uri;
559586
private String name;
587+
private String title;
560588
private String description;
561589
private String mimeType;
562590
private Long size;
@@ -572,6 +600,11 @@ public Builder name(String name) {
572600
return this;
573601
}
574602

603+
public Builder title(String title) {
604+
this.title = title;
605+
return this;
606+
}
607+
575608
public Builder description(String description) {
576609
this.description = description;
577610
return this;
@@ -596,7 +629,7 @@ public Resource build() {
596629
Assert.hasText(uri, "uri must not be empty");
597630
Assert.hasText(name, "name must not be empty");
598631

599-
return new Resource(uri, name, description, mimeType, size, annotations);
632+
return new Resource(uri, name, title, description, mimeType, size, annotations);
600633
}
601634
}
602635
} // @formatter:on
@@ -622,9 +655,10 @@ public Resource build() {
622655
public record ResourceTemplate( // @formatter:off
623656
@JsonProperty("uriTemplate") String uriTemplate,
624657
@JsonProperty("name") String name,
658+
@JsonProperty("title") String title,
625659
@JsonProperty("description") String description,
626660
@JsonProperty("mimeType") String mimeType,
627-
@JsonProperty("annotations") Annotations annotations) implements Annotated {
661+
@JsonProperty("annotations") Annotations annotations) implements Annotated, BaseMetadata {
628662
} // @formatter:on
629663

630664
@JsonInclude(JsonInclude.Include.NON_ABSENT)
@@ -746,8 +780,9 @@ public record BlobResourceContents( // @formatter:off
746780
@JsonIgnoreProperties(ignoreUnknown = true)
747781
public record Prompt( // @formatter:off
748782
@JsonProperty("name") String name,
783+
@JsonProperty("title") String title,
749784
@JsonProperty("description") String description,
750-
@JsonProperty("arguments") List<PromptArgument> arguments) {
785+
@JsonProperty("arguments") List<PromptArgument> arguments) implements BaseMetadata {
751786
} // @formatter:on
752787

753788
/**
@@ -761,8 +796,9 @@ public record Prompt( // @formatter:off
761796
@JsonIgnoreProperties(ignoreUnknown = true)
762797
public record PromptArgument( // @formatter:off
763798
@JsonProperty("name") String name,
799+
@JsonProperty("title") String title,
764800
@JsonProperty("description") String description,
765-
@JsonProperty("required") Boolean required) {
801+
@JsonProperty("required") Boolean required) implements BaseMetadata {
766802
}// @formatter:on
767803

768804
/**
@@ -883,16 +919,60 @@ public record ToolAnnotations( // @formatter:off
883919
@JsonIgnoreProperties(ignoreUnknown = true)
884920
public record Tool( // @formatter:off
885921
@JsonProperty("name") String name,
922+
@JsonProperty("title") String title,
886923
@JsonProperty("description") String description,
887924
@JsonProperty("inputSchema") JsonSchema inputSchema,
888-
@JsonProperty("annotations") ToolAnnotations annotations) {
925+
@JsonProperty("annotations") ToolAnnotations annotations) implements BaseMetadata {
889926

890927
public Tool(String name, String description, String schema) {
891-
this(name, description, parseSchema(schema), null);
928+
this(name, null, description, parseSchema(schema), null);
892929
}
893930

894931
public Tool(String name, String description, String schema, ToolAnnotations annotations) {
895-
this(name, description, parseSchema(schema), annotations);
932+
this(name, null, description, parseSchema(schema), annotations);
933+
}
934+
935+
public static Builder builder() {
936+
return new Builder();
937+
}
938+
939+
public static class Builder {
940+
private String name;
941+
private String title;
942+
private String description;
943+
private JsonSchema inputSchema;
944+
private ToolAnnotations annotations;
945+
946+
public Builder name(String name) {
947+
this.name = name;
948+
return this;
949+
}
950+
951+
public Builder title(String title) {
952+
this.title = title;
953+
return this;
954+
}
955+
956+
public Builder description(String description) {
957+
this.description = description;
958+
return this;
959+
}
960+
961+
public Builder inputSchema(JsonSchema inputSchema) {
962+
this.inputSchema = inputSchema;
963+
return this;
964+
}
965+
966+
public Builder annotations(ToolAnnotations annotations) {
967+
this.annotations = annotations;
968+
return this;
969+
}
970+
971+
public Tool build() {
972+
Assert.hasText(name, "name must not be empty");
973+
974+
return new Tool(name, title, description, inputSchema, annotations);
975+
}
896976
}
897977

898978
} // @formatter:on
@@ -1579,10 +1659,11 @@ public sealed interface CompleteReference permits PromptReference, ResourceRefer
15791659
@JsonIgnoreProperties(ignoreUnknown = true)
15801660
public record PromptReference(// @formatter:off
15811661
@JsonProperty("type") String type,
1582-
@JsonProperty("name") String name) implements McpSchema.CompleteReference {
1662+
@JsonProperty("name") String name,
1663+
@JsonProperty("title") String title ) implements McpSchema.CompleteReference, BaseMetadata {
15831664

15841665
public PromptReference(String name) {
1585-
this("ref/prompt", name);
1666+
this("ref/prompt", name, null);
15861667
}
15871668

15881669
@Override
@@ -1794,6 +1875,7 @@ public Double priority() {
17941875
@JsonIgnoreProperties(ignoreUnknown = true)
17951876
public record ResourceLink( // @formatter:off
17961877
@JsonProperty("name") String name,
1878+
@JsonProperty("title") String title,
17971879
@JsonProperty("uri") String uri,
17981880
@JsonProperty("description") String description,
17991881
@JsonProperty("mimeType") String mimeType,
@@ -1808,6 +1890,8 @@ public static class Builder {
18081890

18091891
private String name;
18101892

1893+
private String title;
1894+
18111895
private String uri;
18121896

18131897
private String description;
@@ -1823,6 +1907,11 @@ public Builder name(String name) {
18231907
return this;
18241908
}
18251909

1910+
public Builder title(String title) {
1911+
this.title = title;
1912+
return this;
1913+
}
1914+
18261915
public Builder uri(String uri) {
18271916
this.uri = uri;
18281917
return this;
@@ -1852,7 +1941,7 @@ public ResourceLink build() {
18521941
Assert.hasText(uri, "uri must not be empty");
18531942
Assert.hasText(name, "name must not be empty");
18541943

1855-
return new ResourceLink(name, uri, description, mimeType, size, annotations);
1944+
return new ResourceLink(name, title, uri, description, mimeType, size, annotations);
18561945
}
18571946

18581947
}

mcp/src/test/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ void testListAllPromptsReturnsImmutableList() {
421421
.consumeNextWith(result -> {
422422
assertThat(result.prompts()).isNotNull();
423423
// Verify that the returned list is immutable
424-
assertThatThrownBy(() -> result.prompts().add(new Prompt("test", "test", null)))
424+
assertThatThrownBy(() -> result.prompts().add(new Prompt("test", "test", "test", null)))
425425
.isInstanceOf(UnsupportedOperationException.class);
426426
})
427427
.verifyComplete();
@@ -605,7 +605,7 @@ void testListAllResourceTemplatesReturnsImmutableList() {
605605
assertThat(result.resourceTemplates()).isNotNull();
606606
// Verify that the returned list is immutable
607607
assertThatThrownBy(() -> result.resourceTemplates()
608-
.add(new McpSchema.ResourceTemplate("test://template", "test", null, null, null)))
608+
.add(new McpSchema.ResourceTemplate("test://template", "test", "test", null, null, null)))
609609
.isInstanceOf(UnsupportedOperationException.class);
610610
})
611611
.verifyComplete();

mcp/src/test/java/io/modelcontextprotocol/client/McpAsyncClientResponseHandlerTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,8 +251,8 @@ void testPromptsChangeNotificationHandling() {
251251
assertThat(asyncMcpClient.initialize().block()).isNotNull();
252252

253253
// Create a mock prompts list that the server will return
254-
McpSchema.Prompt mockPrompt = new McpSchema.Prompt("test-prompt", "Test Prompt Description",
255-
List.of(new McpSchema.PromptArgument("arg1", "Test argument", true)));
254+
McpSchema.Prompt mockPrompt = new McpSchema.Prompt("test-prompt", "Test Prompt", "Test Prompt Description",
255+
List.of(new McpSchema.PromptArgument("arg1", "Test argument", "Test argument", true)));
256256
McpSchema.ListPromptsResult mockPromptsResult = new McpSchema.ListPromptsResult(List.of(mockPrompt), null);
257257

258258
// Simulate server sending prompts/list_changed notification

0 commit comments

Comments
 (0)