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
56 changes: 56 additions & 0 deletions mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java
Original file line number Diff line number Diff line change
Expand Up @@ -5205,6 +5205,40 @@ public record ResourceLink( // @formatter:off
@JsonProperty("annotations") Annotations annotations,
@JsonProperty("_meta") Map<String, Object> meta) implements Content, ResourceContent { // @formatter:on

public ResourceLink {
Assert.notNull(uri, "uri must not be null");
Assert.notNull(name, "name must not be null");
}

@JsonCreator
static ResourceLink fromJson(@JsonProperty("name") String name, @JsonProperty("title") String title,
@JsonProperty("uri") String uri, @JsonProperty("description") String description,
@JsonProperty("mimeType") String mimeType, @JsonProperty("size") Long size,
@JsonProperty("annotations") Annotations annotations, @JsonProperty("_meta") Map<String, Object> meta) {
if (name == null || uri == null) {
List<String> missing = new ArrayList<>();
if (name == null) {
missing.add("name -> ''");
name = "";
}
if (uri == null) {
missing.add("uri -> ''");
uri = "";
}
logger.warn("ResourceLink: missing required fields during deserialization: {}",
String.join(", ", missing));
}
return new ResourceLink(name, title, uri, description, mimeType, size, annotations, meta);
}

public static Builder builder(String uri, String name) {
return new Builder(uri, name);
}

/**
* @deprecated Use {@link #builder(String, String)} instead.
*/
@Deprecated
public static Builder builder() {
return new Builder();
}
Expand All @@ -5227,6 +5261,24 @@ public static class Builder {

private Map<String, Object> meta;

/**
* @deprecated Use {@link ResourceLink#builder(String, String)} instead.
*/
@Deprecated
public Builder() {
}

private Builder(String uri, String name) {
Assert.hasText(uri, "uri must not be empty");
Assert.hasText(name, "name must not be empty");
this.uri = uri;
this.name = name;
}

/**
* @deprecated Use {@link ResourceLink#builder(String, String)} instead.
*/
@Deprecated
public Builder name(String name) {
this.name = name;
return this;
Expand All @@ -5237,6 +5289,10 @@ public Builder title(String title) {
return this;
}

/**
* @deprecated Use {@link ResourceLink#builder(String, String)} instead.
*/
@Deprecated
public Builder uri(String uri) {
this.uri = uri;
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,10 +228,8 @@ void testEmbeddedResourceWithBlobContentsDeserialization() throws Exception {

@Test
void testResourceLink() throws Exception {
McpSchema.ResourceLink resourceLink = McpSchema.ResourceLink.builder()
.name("main.rs")
McpSchema.ResourceLink resourceLink = McpSchema.ResourceLink.builder("file:///project/src/main.rs", "main.rs")
.title("Main file")
.uri("file:///project/src/main.rs")
.description("Primary application entry point")
.mimeType("text/x-rust")
.meta(Map.of("metaKey", "metaValue"))
Expand Down Expand Up @@ -261,6 +259,44 @@ void testResourceLinkDeserialization() throws Exception {
assertThat(resourceLink.meta()).containsEntry("metaKey", "metaValue");
}

@Test
void testResourceLinkRejectsNullName() {
assertThatThrownBy(() -> new McpSchema.ResourceLink(null, null, "file:///project/src/main.rs", null, null, null,
null, null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("name must not be null");
}

@Test
void testResourceLinkRejectsNullUri() {
assertThatThrownBy(() -> new McpSchema.ResourceLink("main.rs", null, null, null, null, null, null, null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("uri must not be null");
}

@Test
void testResourceLinkDeserializationWithMissingRequiredFields() throws Exception {
McpSchema.ResourceLink resourceLink = JSON_MAPPER.readValue("""
{"type":"resource_link","description":"Primary application entry point"}""",
McpSchema.ResourceLink.class);

assertThat(resourceLink).isNotNull();
assertThat(resourceLink.name()).isEmpty();
assertThat(resourceLink.uri()).isEmpty();
assertThat(resourceLink.description()).isEqualTo("Primary application entry point");
}

@Test
void testResourceLinkUnknownFieldsIgnored() throws Exception {
McpSchema.ResourceLink resourceLink = JSON_MAPPER.readValue(
"""
{"type":"resource_link","name":"main.rs","uri":"file:///project/src/main.rs","futureField":"ignored"}""",
McpSchema.ResourceLink.class);

assertThat(resourceLink.name()).isEqualTo("main.rs");
assertThat(resourceLink.uri()).isEqualTo("file:///project/src/main.rs");
}

// JSON-RPC Message Types Tests

@Test
Expand Down