Skip to content

Commit 53a1b4e

Browse files
committed
Fix issues with PromptReference backwards incompatibility
Signed-off-by: Dariusz Jędrzejczyk <2554306+chemicL@users.noreply.github.com>
1 parent 1632be8 commit 53a1b4e

4 files changed

Lines changed: 81 additions & 19 deletions

File tree

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

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4234,12 +4234,12 @@ static SetLevelRequest fromJson(@JsonProperty("level") LoggingLevel level) {
42344234
* requests. Implementations are identified by a {@code "type"} discriminator field
42354235
* whose value maps to a concrete subtype via {@code @JsonSubTypes}.
42364236
*/
4237-
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
4237+
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type",
4238+
visible = true)
42384239
@JsonSubTypes({ @JsonSubTypes.Type(value = PromptReference.class, name = PromptReference.TYPE),
42394240
@JsonSubTypes.Type(value = ResourceReference.class, name = ResourceReference.TYPE) })
42404241
public interface CompleteReference {
42414242

4242-
@JsonIgnore
42434243
default String type() {
42444244
if (this instanceof PromptReference) {
42454245
return PromptReference.TYPE;
@@ -4262,21 +4262,46 @@ default String identifier() {
42624262
*
42634263
* @param name The name of the prompt
42644264
* @param title An optional title for the prompt
4265+
* @param type Always {@value #TYPE}; present for compatibility and read by the parent
4266+
* {@code @JsonTypeInfo} as the polymorphic discriminator
42654267
*/
42664268
@JsonInclude(JsonInclude.Include.NON_ABSENT)
42674269
@JsonIgnoreProperties(ignoreUnknown = true)
42684270
public record PromptReference( // @formatter:off
42694271
@JsonProperty("name") String name,
4270-
@JsonProperty("title") String title) implements McpSchema.CompleteReference, Identifier { // @formatter:on
4272+
@JsonProperty("title") String title,
4273+
@JsonProperty("type") String type) implements McpSchema.CompleteReference, Identifier { // @formatter:on
42714274

42724275
public static final String TYPE = "ref/prompt";
42734276

42744277
public PromptReference {
42754278
Assert.hasText(name, "name must not be null or empty");
4279+
type = TYPE;
4280+
}
4281+
4282+
@JsonCreator
4283+
static PromptReference fromJson(@JsonProperty("name") String name, @JsonProperty("title") String title,
4284+
@JsonProperty("type") String type) {
4285+
return new PromptReference(name, title, type);
4286+
}
4287+
4288+
/**
4289+
* @deprecated The {@code type} argument is ignored — the type discriminator is
4290+
* always {@value #TYPE}. Use {@link #PromptReference(String)} or the
4291+
* {@link #builder(String)} instead. In the future the builder might be removed
4292+
* and the two-arg {@code (String, String)} constructor would mean {@link #name()}
4293+
* and {@link #title()}.
4294+
*/
4295+
@Deprecated
4296+
public PromptReference(String type, String name) {
4297+
this(name, null, null);
4298+
logger.warn(
4299+
"PromptReference(String, String): the 'type' argument '{}' is ignored, type is always '{}'. Use PromptReference(String) or the builder.",
4300+
type, TYPE);
42764301
}
42774302

42784303
public PromptReference(String name) {
4279-
this(name, null);
4304+
this(name, null, null);
42804305
}
42814306

42824307
@Override
@@ -4298,6 +4323,32 @@ public boolean equals(Object obj) {
42984323
public int hashCode() {
42994324
return java.util.Objects.hash(name);
43004325
}
4326+
4327+
public static Builder builder(String name) {
4328+
return new Builder(name);
4329+
}
4330+
4331+
public static final class Builder {
4332+
4333+
private final String name;
4334+
4335+
private String title;
4336+
4337+
private Builder(String name) {
4338+
this.name = name;
4339+
}
4340+
4341+
public Builder title(String title) {
4342+
this.title = title;
4343+
return this;
4344+
}
4345+
4346+
public PromptReference build() {
4347+
return new PromptReference(name, title, null);
4348+
}
4349+
4350+
}
4351+
43014352
}
43024353

43034354
// TODO: this should actually be a ResourceTemplateReference
@@ -4317,6 +4368,17 @@ public record ResourceReference( // @formatter:off
43174368
Assert.notNull(uri, "uri must not be null");
43184369
}
43194370

4371+
@JsonProperty("type")
4372+
@Override
4373+
public String type() {
4374+
return CompleteReference.super.type();
4375+
}
4376+
4377+
@JsonCreator
4378+
static ResourceReference fromJson(@JsonProperty("uri") String uri, @JsonProperty("type") String type) {
4379+
return new ResourceReference(uri);
4380+
}
4381+
43204382
@Deprecated
43214383
public ResourceReference(String type, String uri) {
43224384
this(uri);

mcp-core/src/test/java/io/modelcontextprotocol/spec/PromptReferenceEqualsTest.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,47 +18,47 @@ class PromptReferenceEqualsTest {
1818

1919
@Test
2020
void testEqualsWithSameIdentifierAndType() {
21-
McpSchema.PromptReference ref1 = new PromptReference("test-prompt", "Test Title");
22-
McpSchema.PromptReference ref2 = new PromptReference("test-prompt", "Different Title");
21+
McpSchema.PromptReference ref1 = PromptReference.builder("test-prompt").title("Test Title").build();
22+
McpSchema.PromptReference ref2 = PromptReference.builder("test-prompt").title("Different Title").build();
2323

2424
assertTrue(ref1.equals(ref2), "PromptReferences with same identifier and type should be equal");
2525
assertEquals(ref1.hashCode(), ref2.hashCode(), "Equal objects should have same hash code");
2626
}
2727

2828
@Test
2929
void testEqualsWithDifferentIdentifier() {
30-
McpSchema.PromptReference ref1 = new PromptReference("test-prompt-1", "Test Title");
31-
McpSchema.PromptReference ref2 = new PromptReference("test-prompt-2", "Test Title");
30+
McpSchema.PromptReference ref1 = PromptReference.builder("test-prompt-1").title("Test Title").build();
31+
McpSchema.PromptReference ref2 = PromptReference.builder("test-prompt-2").title("Test Title").build();
3232

3333
assertFalse(ref1.equals(ref2), "PromptReferences with different identifiers should not be equal");
3434
}
3535

3636
@Test
3737
void testEqualsWithNull() {
38-
McpSchema.PromptReference ref1 = new PromptReference("test-prompt", "Test Title");
38+
McpSchema.PromptReference ref1 = PromptReference.builder("test-prompt").title("Test Title").build();
3939

4040
assertFalse(ref1.equals(null), "PromptReference should not be equal to null");
4141
}
4242

4343
@Test
4444
void testEqualsWithDifferentClass() {
45-
McpSchema.PromptReference ref1 = new PromptReference("test-prompt", "Test Title");
45+
McpSchema.PromptReference ref1 = PromptReference.builder("test-prompt").title("Test Title").build();
4646
String other = "not a PromptReference";
4747

4848
assertFalse(ref1.equals(other), "PromptReference should not be equal to different class");
4949
}
5050

5151
@Test
5252
void testEqualsWithSameInstance() {
53-
McpSchema.PromptReference ref1 = new PromptReference("test-prompt", "Test Title");
53+
McpSchema.PromptReference ref1 = PromptReference.builder("test-prompt").title("Test Title").build();
5454

5555
assertTrue(ref1.equals(ref1), "PromptReference should be equal to itself");
5656
}
5757

5858
@Test
5959
void testEqualsIgnoresTitle() {
60-
McpSchema.PromptReference ref1 = new PromptReference("test-prompt", "Title 1");
61-
McpSchema.PromptReference ref2 = new PromptReference("test-prompt", "Title 2");
60+
McpSchema.PromptReference ref1 = PromptReference.builder("test-prompt").title("Title 1").build();
61+
McpSchema.PromptReference ref2 = PromptReference.builder("test-prompt").title("Title 2").build();
6262
McpSchema.PromptReference ref3 = new PromptReference("test-prompt");
6363

6464
assertTrue(ref1.equals(ref2), "PromptReferences should be equal regardless of title");
@@ -68,8 +68,8 @@ void testEqualsIgnoresTitle() {
6868

6969
@Test
7070
void testHashCodeConsistency() {
71-
McpSchema.PromptReference ref1 = new PromptReference("test-prompt", "Test Title");
72-
McpSchema.PromptReference ref2 = new PromptReference("test-prompt", "Different Title");
71+
McpSchema.PromptReference ref1 = PromptReference.builder("test-prompt").title("Test Title").build();
72+
McpSchema.PromptReference ref2 = PromptReference.builder("test-prompt").title("Different Title").build();
7373

7474
assertEquals(ref1.hashCode(), ref2.hashCode(), "Objects that are equal should have the same hash code");
7575

mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1356,7 +1356,7 @@ void testCompletionShouldReturnExpectedSuggestions(String clientType) {
13561356
.build()))
13571357
.build(), (mcpSyncServerExchange, getPromptRequest) -> null))
13581358
.completions(new McpServerFeatures.SyncCompletionSpecification(
1359-
new McpSchema.PromptReference("code_review", "Code review"), completionHandler))
1359+
McpSchema.PromptReference.builder("code_review").title("Code review").build(), completionHandler))
13601360
.build();
13611361

13621362
try (var mcpClient = clientBuilder.build()) {
@@ -1365,7 +1365,7 @@ void testCompletionShouldReturnExpectedSuggestions(String clientType) {
13651365
assertThat(initResult).isNotNull();
13661366

13671367
CompleteRequest request = CompleteRequest
1368-
.builder(new PromptReference("code_review", "Code review"),
1368+
.builder(PromptReference.builder("code_review").title("Code review").build(),
13691369
new CompleteRequest.CompleteArgument("language", "py"))
13701370
.build();
13711371

mcp-test/src/test/java/io/modelcontextprotocol/server/HttpServletStatelessIntegrationTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ void testCompletionShouldReturnExpectedSuggestions(String clientType) {
204204
.build()))
205205
.build(), (transportContext, getPromptRequest) -> null))
206206
.completions(new McpStatelessServerFeatures.SyncCompletionSpecification(
207-
new PromptReference("code_review", "Code review"), completionHandler))
207+
PromptReference.builder("code_review").title("Code review").build(), completionHandler))
208208
.build();
209209

210210
try (var mcpClient = clientBuilder.build()) {
@@ -213,7 +213,7 @@ void testCompletionShouldReturnExpectedSuggestions(String clientType) {
213213
assertThat(initResult).isNotNull();
214214

215215
CompleteRequest request = CompleteRequest
216-
.builder(new PromptReference("code_review", "Code review"),
216+
.builder(PromptReference.builder("code_review").title("Code review").build(),
217217
new CompleteRequest.CompleteArgument("language", "py"))
218218
.build();
219219

0 commit comments

Comments
 (0)