Skip to content

Commit eeec73d

Browse files
Validate ResourceLink required fields
1 parent c09ee67 commit eeec73d

2 files changed

Lines changed: 95 additions & 3 deletions

File tree

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

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5019,6 +5019,40 @@ public record ResourceLink( // @formatter:off
50195019
@JsonProperty("annotations") Annotations annotations,
50205020
@JsonProperty("_meta") Map<String, Object> meta) implements Content, ResourceContent { // @formatter:on
50215021

5022+
public ResourceLink {
5023+
Assert.notNull(uri, "uri must not be null");
5024+
Assert.notNull(name, "name must not be null");
5025+
}
5026+
5027+
@JsonCreator
5028+
static ResourceLink fromJson(@JsonProperty("name") String name, @JsonProperty("title") String title,
5029+
@JsonProperty("uri") String uri, @JsonProperty("description") String description,
5030+
@JsonProperty("mimeType") String mimeType, @JsonProperty("size") Long size,
5031+
@JsonProperty("annotations") Annotations annotations, @JsonProperty("_meta") Map<String, Object> meta) {
5032+
if (name == null || uri == null) {
5033+
List<String> missing = new ArrayList<>();
5034+
if (name == null) {
5035+
missing.add("name -> ''");
5036+
name = "";
5037+
}
5038+
if (uri == null) {
5039+
missing.add("uri -> ''");
5040+
uri = "";
5041+
}
5042+
logger.warn("ResourceLink: missing required fields during deserialization: {}",
5043+
String.join(", ", missing));
5044+
}
5045+
return new ResourceLink(name, title, uri, description, mimeType, size, annotations, meta);
5046+
}
5047+
5048+
public static Builder builder(String uri, String name) {
5049+
return new Builder(uri, name);
5050+
}
5051+
5052+
/**
5053+
* @deprecated Use {@link #builder(String, String)} instead.
5054+
*/
5055+
@Deprecated
50225056
public static Builder builder() {
50235057
return new Builder();
50245058
}
@@ -5041,6 +5075,24 @@ public static class Builder {
50415075

50425076
private Map<String, Object> meta;
50435077

5078+
/**
5079+
* @deprecated Use {@link ResourceLink#builder(String, String)} instead.
5080+
*/
5081+
@Deprecated
5082+
public Builder() {
5083+
}
5084+
5085+
private Builder(String uri, String name) {
5086+
Assert.hasText(uri, "uri must not be empty");
5087+
Assert.hasText(name, "name must not be empty");
5088+
this.uri = uri;
5089+
this.name = name;
5090+
}
5091+
5092+
/**
5093+
* @deprecated Use {@link ResourceLink#builder(String, String)} instead.
5094+
*/
5095+
@Deprecated
50445096
public Builder name(String name) {
50455097
this.name = name;
50465098
return this;
@@ -5051,6 +5103,10 @@ public Builder title(String title) {
50515103
return this;
50525104
}
50535105

5106+
/**
5107+
* @deprecated Use {@link ResourceLink#builder(String, String)} instead.
5108+
*/
5109+
@Deprecated
50545110
public Builder uri(String uri) {
50555111
this.uri = uri;
50565112
return this;

mcp-test/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,10 +228,8 @@ void testEmbeddedResourceWithBlobContentsDeserialization() throws Exception {
228228

229229
@Test
230230
void testResourceLink() throws Exception {
231-
McpSchema.ResourceLink resourceLink = McpSchema.ResourceLink.builder()
232-
.name("main.rs")
231+
McpSchema.ResourceLink resourceLink = McpSchema.ResourceLink.builder("file:///project/src/main.rs", "main.rs")
233232
.title("Main file")
234-
.uri("file:///project/src/main.rs")
235233
.description("Primary application entry point")
236234
.mimeType("text/x-rust")
237235
.meta(Map.of("metaKey", "metaValue"))
@@ -261,6 +259,44 @@ void testResourceLinkDeserialization() throws Exception {
261259
assertThat(resourceLink.meta()).containsEntry("metaKey", "metaValue");
262260
}
263261

262+
@Test
263+
void testResourceLinkRejectsNullName() {
264+
assertThatThrownBy(() -> new McpSchema.ResourceLink(null, null, "file:///project/src/main.rs", null, null, null,
265+
null, null))
266+
.isInstanceOf(IllegalArgumentException.class)
267+
.hasMessage("name must not be null");
268+
}
269+
270+
@Test
271+
void testResourceLinkRejectsNullUri() {
272+
assertThatThrownBy(() -> new McpSchema.ResourceLink("main.rs", null, null, null, null, null, null, null))
273+
.isInstanceOf(IllegalArgumentException.class)
274+
.hasMessage("uri must not be null");
275+
}
276+
277+
@Test
278+
void testResourceLinkDeserializationWithMissingRequiredFields() throws Exception {
279+
McpSchema.ResourceLink resourceLink = JSON_MAPPER.readValue("""
280+
{"type":"resource_link","description":"Primary application entry point"}""",
281+
McpSchema.ResourceLink.class);
282+
283+
assertThat(resourceLink).isNotNull();
284+
assertThat(resourceLink.name()).isEmpty();
285+
assertThat(resourceLink.uri()).isEmpty();
286+
assertThat(resourceLink.description()).isEqualTo("Primary application entry point");
287+
}
288+
289+
@Test
290+
void testResourceLinkUnknownFieldsIgnored() throws Exception {
291+
McpSchema.ResourceLink resourceLink = JSON_MAPPER.readValue(
292+
"""
293+
{"type":"resource_link","name":"main.rs","uri":"file:///project/src/main.rs","futureField":"ignored"}""",
294+
McpSchema.ResourceLink.class);
295+
296+
assertThat(resourceLink.name()).isEqualTo("main.rs");
297+
assertThat(resourceLink.uri()).isEqualTo("file:///project/src/main.rs");
298+
}
299+
264300
// JSON-RPC Message Types Tests
265301

266302
@Test

0 commit comments

Comments
 (0)