feat: add size field and builder pattern to Resource record (#331)
The size field helps hosts display file sizes and estimate context window usage. - Add size parameter to Resource record for tracking raw content size in bytes - Implement Builder pattern for Resource construction with validation - Add deprecated constructor for backwards compatibility - Add tests for builder pattern and field validation Resolves #330 Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
This commit is contained in:
parent
0fe76ba993
commit
6c330db5ad
3 changed files with 130 additions and 7 deletions
|
|
@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory;
|
||||||
* Context Protocol Schema</a>.
|
* Context Protocol Schema</a>.
|
||||||
*
|
*
|
||||||
* @author Christian Tzolov
|
* @author Christian Tzolov
|
||||||
|
* @author Luca Chang
|
||||||
*/
|
*/
|
||||||
public final class McpSchema {
|
public final class McpSchema {
|
||||||
|
|
||||||
|
|
@ -482,6 +483,9 @@ public final class McpSchema {
|
||||||
* by clients to improve the LLM's understanding of available resources. It can be
|
* by clients to improve the LLM's understanding of available resources. It can be
|
||||||
* thought of like a "hint" to the model.
|
* thought of like a "hint" to the model.
|
||||||
* @param mimeType The MIME type of this resource, if known.
|
* @param mimeType The MIME type of this resource, if known.
|
||||||
|
* @param size The size of the raw resource content, in bytes (i.e., before base64
|
||||||
|
* encoding or any tokenization), if known. This can be used by Hosts to display file
|
||||||
|
* sizes and estimate context window usage.
|
||||||
* @param annotations Optional annotations for the client. The client can use
|
* @param annotations Optional annotations for the client. The client can use
|
||||||
* annotations to inform how objects are used or displayed.
|
* annotations to inform how objects are used or displayed.
|
||||||
*/
|
*/
|
||||||
|
|
@ -492,7 +496,67 @@ public final class McpSchema {
|
||||||
@JsonProperty("name") String name,
|
@JsonProperty("name") String name,
|
||||||
@JsonProperty("description") String description,
|
@JsonProperty("description") String description,
|
||||||
@JsonProperty("mimeType") String mimeType,
|
@JsonProperty("mimeType") String mimeType,
|
||||||
|
@JsonProperty("size") Long size,
|
||||||
@JsonProperty("annotations") Annotations annotations) implements Annotated {
|
@JsonProperty("annotations") Annotations annotations) implements Annotated {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Only exists for backwards-compatibility purposes. Use
|
||||||
|
* {@link Resource#builder()} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public Resource(String uri, String name, String description, String mimeType, Annotations annotations) {
|
||||||
|
this(uri, name, description, mimeType, null, annotations);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
private String uri;
|
||||||
|
private String name;
|
||||||
|
private String description;
|
||||||
|
private String mimeType;
|
||||||
|
private Long size;
|
||||||
|
private Annotations annotations;
|
||||||
|
|
||||||
|
public Builder uri(String uri) {
|
||||||
|
this.uri = uri;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder name(String name) {
|
||||||
|
this.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder description(String description) {
|
||||||
|
this.description = description;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder mimeType(String mimeType) {
|
||||||
|
this.mimeType = mimeType;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder size(Long size) {
|
||||||
|
this.size = size;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder annotations(Annotations annotations) {
|
||||||
|
this.annotations = annotations;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Resource build() {
|
||||||
|
Assert.hasText(uri, "uri must not be empty");
|
||||||
|
Assert.hasText(name, "name must not be empty");
|
||||||
|
|
||||||
|
return new Resource(uri, name, description, mimeType, size, annotations);
|
||||||
|
}
|
||||||
|
}
|
||||||
} // @formatter:on
|
} // @formatter:on
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package io.modelcontextprotocol.spec;
|
||||||
|
|
||||||
|
public class ArgumentException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -3,23 +3,24 @@
|
||||||
*/
|
*/
|
||||||
package io.modelcontextprotocol.spec;
|
package io.modelcontextprotocol.spec;
|
||||||
|
|
||||||
|
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
|
||||||
|
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
|
|
||||||
import io.modelcontextprotocol.spec.McpSchema.TextResourceContents;
|
|
||||||
import net.javacrumbs.jsonunit.core.Option;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json;
|
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
import io.modelcontextprotocol.spec.McpSchema.TextResourceContents;
|
||||||
|
import net.javacrumbs.jsonunit.core.Option;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Christian Tzolov
|
* @author Christian Tzolov
|
||||||
|
|
@ -295,6 +296,59 @@ public class McpSchemaTests {
|
||||||
{"uri":"resource://test","name":"Test Resource","description":"A test resource","mimeType":"text/plain","annotations":{"audience":["user","assistant"],"priority":0.8}}"""));
|
{"uri":"resource://test","name":"Test Resource","description":"A test resource","mimeType":"text/plain","annotations":{"audience":["user","assistant"],"priority":0.8}}"""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testResourceBuilder() throws Exception {
|
||||||
|
McpSchema.Annotations annotations = new McpSchema.Annotations(
|
||||||
|
Arrays.asList(McpSchema.Role.USER, McpSchema.Role.ASSISTANT), 0.8);
|
||||||
|
|
||||||
|
McpSchema.Resource resource = McpSchema.Resource.builder()
|
||||||
|
.uri("resource://test")
|
||||||
|
.name("Test Resource")
|
||||||
|
.description("A test resource")
|
||||||
|
.mimeType("text/plain")
|
||||||
|
.size(256L)
|
||||||
|
.annotations(annotations)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
String value = mapper.writeValueAsString(resource);
|
||||||
|
assertThatJson(value).when(Option.IGNORING_ARRAY_ORDER)
|
||||||
|
.when(Option.IGNORING_EXTRA_ARRAY_ITEMS)
|
||||||
|
.isObject()
|
||||||
|
.isEqualTo(
|
||||||
|
json("""
|
||||||
|
{"uri":"resource://test","name":"Test Resource","description":"A test resource","mimeType":"text/plain","size":256,"annotations":{"audience":["user","assistant"],"priority":0.8}}"""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testResourceBuilderUriRequired() {
|
||||||
|
McpSchema.Annotations annotations = new McpSchema.Annotations(
|
||||||
|
Arrays.asList(McpSchema.Role.USER, McpSchema.Role.ASSISTANT), 0.8);
|
||||||
|
|
||||||
|
McpSchema.Resource.Builder resourceBuilder = McpSchema.Resource.builder()
|
||||||
|
.name("Test Resource")
|
||||||
|
.description("A test resource")
|
||||||
|
.mimeType("text/plain")
|
||||||
|
.size(256L)
|
||||||
|
.annotations(annotations);
|
||||||
|
|
||||||
|
assertThatThrownBy(resourceBuilder::build).isInstanceOf(java.lang.IllegalArgumentException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testResourceBuilderNameRequired() {
|
||||||
|
McpSchema.Annotations annotations = new McpSchema.Annotations(
|
||||||
|
Arrays.asList(McpSchema.Role.USER, McpSchema.Role.ASSISTANT), 0.8);
|
||||||
|
|
||||||
|
McpSchema.Resource.Builder resourceBuilder = McpSchema.Resource.builder()
|
||||||
|
.uri("resource://test")
|
||||||
|
.description("A test resource")
|
||||||
|
.mimeType("text/plain")
|
||||||
|
.size(256L)
|
||||||
|
.annotations(annotations);
|
||||||
|
|
||||||
|
assertThatThrownBy(resourceBuilder::build).isInstanceOf(java.lang.IllegalArgumentException.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testResourceTemplate() throws Exception {
|
void testResourceTemplate() throws Exception {
|
||||||
McpSchema.Annotations annotations = new McpSchema.Annotations(Arrays.asList(McpSchema.Role.USER), 0.5);
|
McpSchema.Annotations annotations = new McpSchema.Annotations(Arrays.asList(McpSchema.Role.USER), 0.5);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue