Skip to content

Commit 1c25b7c

Browse files
author
Mohammed Aboullaite
committed
Add ttlMs and cacheScope to cacheable result records (SEP-2549)
The draft spec introduces TTL caching hints on list and read results. This adds the optional ttlMs (Integer) and cacheScope (CacheScope enum) fields to ListToolsResult, ListPromptsResult, ListResourcesResult, ListResourceTemplatesResult, and ReadResourceResult, following the wire-record evolution rules in CONTRIBUTING.md. Purely additive, schema-layer only: no server or client behavior changes. Server-side TTL configuration hooks are deferred to #578.
1 parent fd00498 commit 1c25b7c

2 files changed

Lines changed: 421 additions & 20 deletions

File tree

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

Lines changed: 161 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,21 @@ public interface Result extends Meta {
190190

191191
}
192192

193+
/**
194+
* Indicates the intended scope of a cached response, analogous to HTTP
195+
* {@code Cache-Control: public} vs {@code Cache-Control: private}.
196+
*
197+
* @see <a href=
198+
* "https://spec.modelcontextprotocol.io/specification/draft/server/utilities/caching/">Specification:
199+
* Caching</a>
200+
*/
201+
public enum CacheScope {
202+
203+
// @formatter:off
204+
@JsonProperty("private") PRIVATE,
205+
@JsonProperty("public") PUBLIC
206+
} // @formatter:on
207+
193208
public interface Notification extends Meta {
194209

195210
}
@@ -1604,27 +1619,38 @@ public ResourceTemplate build() {
16041619
* @param nextCursor An opaque token representing the pagination position after the
16051620
* last returned result. If present, there may be more results available
16061621
* @param meta See specification for notes on _meta usage
1622+
* @param ttlMs A hint from the server indicating how long (in milliseconds) the
1623+
* client may cache this response before re-fetching
1624+
* @param cacheScope Indicates the intended scope of the cached response
16071625
*/
16081626
@JsonInclude(JsonInclude.Include.NON_ABSENT)
16091627
@JsonIgnoreProperties(ignoreUnknown = true)
16101628
public record ListResourcesResult( // @formatter:off
16111629
@JsonProperty("resources") List<Resource> resources,
16121630
@JsonProperty("nextCursor") String nextCursor,
1613-
@JsonProperty("_meta") Map<String, Object> meta) implements Result { // @formatter:on
1631+
@JsonProperty("_meta") Map<String, Object> meta,
1632+
@JsonProperty("ttlMs") Integer ttlMs,
1633+
@JsonProperty("cacheScope") CacheScope cacheScope) implements Result { // @formatter:on
16141634

16151635
public ListResourcesResult {
16161636
Assert.notNull(resources, "resources must not be null");
16171637
}
16181638

16191639
@JsonCreator
16201640
static ListResourcesResult fromJson(@JsonProperty("resources") List<Resource> resources,
1621-
@JsonProperty("nextCursor") String nextCursor, @JsonProperty("_meta") Map<String, Object> meta) {
1641+
@JsonProperty("nextCursor") String nextCursor, @JsonProperty("_meta") Map<String, Object> meta,
1642+
@JsonProperty("ttlMs") Integer ttlMs, @JsonProperty("cacheScope") CacheScope cacheScope) {
16221643
if (resources == null) {
16231644
logger.warn(
16241645
"ListResourcesResult: missing required field 'resources' during deserialization, using default []");
16251646
resources = List.of();
16261647
}
1627-
return new ListResourcesResult(resources, nextCursor, meta);
1648+
return new ListResourcesResult(resources, nextCursor, meta, ttlMs, cacheScope);
1649+
}
1650+
1651+
@Deprecated
1652+
public ListResourcesResult(List<Resource> resources, String nextCursor, Map<String, Object> meta) {
1653+
this(resources, nextCursor, meta, null, null);
16281654
}
16291655

16301656
@Deprecated
@@ -1644,6 +1670,10 @@ public static class Builder {
16441670

16451671
private Map<String, Object> meta;
16461672

1673+
private Integer ttlMs;
1674+
1675+
private CacheScope cacheScope;
1676+
16471677
private Builder(List<Resource> resources) {
16481678
Assert.notNull(resources, "resources must not be null");
16491679
this.resources = resources;
@@ -1659,8 +1689,18 @@ public Builder meta(Map<String, Object> meta) {
16591689
return this;
16601690
}
16611691

1692+
public Builder ttlMs(Integer ttlMs) {
1693+
this.ttlMs = ttlMs;
1694+
return this;
1695+
}
1696+
1697+
public Builder cacheScope(CacheScope cacheScope) {
1698+
this.cacheScope = cacheScope;
1699+
return this;
1700+
}
1701+
16621702
public ListResourcesResult build() {
1663-
return new ListResourcesResult(resources, nextCursor, meta);
1703+
return new ListResourcesResult(resources, nextCursor, meta, ttlMs, cacheScope);
16641704
}
16651705

16661706
}
@@ -1673,13 +1713,18 @@ public ListResourcesResult build() {
16731713
* @param nextCursor An opaque token representing the pagination position after the
16741714
* last returned result. If present, there may be more results available
16751715
* @param meta See specification for notes on _meta usage
1716+
* @param ttlMs A hint from the server indicating how long (in milliseconds) the
1717+
* client may cache this response before re-fetching
1718+
* @param cacheScope Indicates the intended scope of the cached response
16761719
*/
16771720
@JsonInclude(JsonInclude.Include.NON_ABSENT)
16781721
@JsonIgnoreProperties(ignoreUnknown = true)
16791722
public record ListResourceTemplatesResult( // @formatter:off
16801723
@JsonProperty("resourceTemplates") List<ResourceTemplate> resourceTemplates,
16811724
@JsonProperty("nextCursor") String nextCursor,
1682-
@JsonProperty("_meta") Map<String, Object> meta) implements Result { // @formatter:on
1725+
@JsonProperty("_meta") Map<String, Object> meta,
1726+
@JsonProperty("ttlMs") Integer ttlMs,
1727+
@JsonProperty("cacheScope") CacheScope cacheScope) implements Result { // @formatter:on
16831728

16841729
public ListResourceTemplatesResult {
16851730
Assert.notNull(resourceTemplates, "resourceTemplates must not be null");
@@ -1688,13 +1733,20 @@ public record ListResourceTemplatesResult( // @formatter:off
16881733
@JsonCreator
16891734
static ListResourceTemplatesResult fromJson(
16901735
@JsonProperty("resourceTemplates") List<ResourceTemplate> resourceTemplates,
1691-
@JsonProperty("nextCursor") String nextCursor, @JsonProperty("_meta") Map<String, Object> meta) {
1736+
@JsonProperty("nextCursor") String nextCursor, @JsonProperty("_meta") Map<String, Object> meta,
1737+
@JsonProperty("ttlMs") Integer ttlMs, @JsonProperty("cacheScope") CacheScope cacheScope) {
16921738
if (resourceTemplates == null) {
16931739
logger.warn(
16941740
"ListResourceTemplatesResult: missing required field 'resourceTemplates' during deserialization, using default []");
16951741
resourceTemplates = List.of();
16961742
}
1697-
return new ListResourceTemplatesResult(resourceTemplates, nextCursor, meta);
1743+
return new ListResourceTemplatesResult(resourceTemplates, nextCursor, meta, ttlMs, cacheScope);
1744+
}
1745+
1746+
@Deprecated
1747+
public ListResourceTemplatesResult(List<ResourceTemplate> resourceTemplates, String nextCursor,
1748+
Map<String, Object> meta) {
1749+
this(resourceTemplates, nextCursor, meta, null, null);
16981750
}
16991751

17001752
@Deprecated
@@ -1714,6 +1766,10 @@ public static class Builder {
17141766

17151767
private Map<String, Object> meta;
17161768

1769+
private Integer ttlMs;
1770+
1771+
private CacheScope cacheScope;
1772+
17171773
private Builder(List<ResourceTemplate> resourceTemplates) {
17181774
Assert.notNull(resourceTemplates, "resourceTemplates must not be null");
17191775
this.resourceTemplates = resourceTemplates;
@@ -1729,8 +1785,18 @@ public Builder meta(Map<String, Object> meta) {
17291785
return this;
17301786
}
17311787

1788+
public Builder ttlMs(Integer ttlMs) {
1789+
this.ttlMs = ttlMs;
1790+
return this;
1791+
}
1792+
1793+
public Builder cacheScope(CacheScope cacheScope) {
1794+
this.cacheScope = cacheScope;
1795+
return this;
1796+
}
1797+
17321798
public ListResourceTemplatesResult build() {
1733-
return new ListResourceTemplatesResult(resourceTemplates, nextCursor, meta);
1799+
return new ListResourceTemplatesResult(resourceTemplates, nextCursor, meta, ttlMs, cacheScope);
17341800
}
17351801

17361802
}
@@ -1801,26 +1867,37 @@ public ReadResourceRequest build() {
18011867
*
18021868
* @param contents The contents of the resource
18031869
* @param meta See specification for notes on _meta usage
1870+
* @param ttlMs A hint from the server indicating how long (in milliseconds) the
1871+
* client may cache this response before re-fetching
1872+
* @param cacheScope Indicates the intended scope of the cached response
18041873
*/
18051874
@JsonInclude(JsonInclude.Include.NON_ABSENT)
18061875
@JsonIgnoreProperties(ignoreUnknown = true)
18071876
public record ReadResourceResult( // @formatter:off
18081877
@JsonProperty("contents") List<ResourceContents> contents,
1809-
@JsonProperty("_meta") Map<String, Object> meta) implements Result { // @formatter:on
1878+
@JsonProperty("_meta") Map<String, Object> meta,
1879+
@JsonProperty("ttlMs") Integer ttlMs,
1880+
@JsonProperty("cacheScope") CacheScope cacheScope) implements Result { // @formatter:on
18101881

18111882
public ReadResourceResult {
18121883
Assert.notNull(contents, "contents must not be null");
18131884
}
18141885

18151886
@JsonCreator
18161887
static ReadResourceResult fromJson(@JsonProperty("contents") List<ResourceContents> contents,
1817-
@JsonProperty("_meta") Map<String, Object> meta) {
1888+
@JsonProperty("_meta") Map<String, Object> meta, @JsonProperty("ttlMs") Integer ttlMs,
1889+
@JsonProperty("cacheScope") CacheScope cacheScope) {
18181890
if (contents == null) {
18191891
logger.warn(
18201892
"ReadResourceResult: missing required field 'contents' during deserialization, using default []");
18211893
contents = List.of();
18221894
}
1823-
return new ReadResourceResult(contents, meta);
1895+
return new ReadResourceResult(contents, meta, ttlMs, cacheScope);
1896+
}
1897+
1898+
@Deprecated
1899+
public ReadResourceResult(List<ResourceContents> contents, Map<String, Object> meta) {
1900+
this(contents, meta, null, null);
18241901
}
18251902

18261903
@Deprecated
@@ -1838,6 +1915,10 @@ public static class Builder {
18381915

18391916
private Map<String, Object> meta;
18401917

1918+
private Integer ttlMs;
1919+
1920+
private CacheScope cacheScope;
1921+
18411922
private Builder(List<ResourceContents> contents) {
18421923
Assert.notNull(contents, "contents must not be null");
18431924
this.contents = contents;
@@ -1848,8 +1929,18 @@ public Builder meta(Map<String, Object> meta) {
18481929
return this;
18491930
}
18501931

1932+
public Builder ttlMs(Integer ttlMs) {
1933+
this.ttlMs = ttlMs;
1934+
return this;
1935+
}
1936+
1937+
public Builder cacheScope(CacheScope cacheScope) {
1938+
this.cacheScope = cacheScope;
1939+
return this;
1940+
}
1941+
18511942
public ReadResourceResult build() {
1852-
return new ReadResourceResult(contents, meta);
1943+
return new ReadResourceResult(contents, meta, ttlMs, cacheScope);
18531944
}
18541945

18551946
}
@@ -2411,27 +2502,38 @@ public PromptMessage build() {
24112502
* @param nextCursor An optional cursor for pagination. If present, indicates there
24122503
* are more prompts available.
24132504
* @param meta See specification for notes on _meta usage
2505+
* @param ttlMs A hint from the server indicating how long (in milliseconds) the
2506+
* client may cache this response before re-fetching
2507+
* @param cacheScope Indicates the intended scope of the cached response
24142508
*/
24152509
@JsonInclude(JsonInclude.Include.NON_ABSENT)
24162510
@JsonIgnoreProperties(ignoreUnknown = true)
24172511
public record ListPromptsResult( // @formatter:off
24182512
@JsonProperty("prompts") List<Prompt> prompts,
24192513
@JsonProperty("nextCursor") String nextCursor,
2420-
@JsonProperty("_meta") Map<String, Object> meta) implements Result { // @formatter:on
2514+
@JsonProperty("_meta") Map<String, Object> meta,
2515+
@JsonProperty("ttlMs") Integer ttlMs,
2516+
@JsonProperty("cacheScope") CacheScope cacheScope) implements Result { // @formatter:on
24212517

24222518
public ListPromptsResult {
24232519
Assert.notNull(prompts, "prompts must not be null");
24242520
}
24252521

24262522
@JsonCreator
24272523
static ListPromptsResult fromJson(@JsonProperty("prompts") List<Prompt> prompts,
2428-
@JsonProperty("nextCursor") String nextCursor, @JsonProperty("_meta") Map<String, Object> meta) {
2524+
@JsonProperty("nextCursor") String nextCursor, @JsonProperty("_meta") Map<String, Object> meta,
2525+
@JsonProperty("ttlMs") Integer ttlMs, @JsonProperty("cacheScope") CacheScope cacheScope) {
24292526
if (prompts == null) {
24302527
logger.warn(
24312528
"ListPromptsResult: missing required field 'prompts' during deserialization, using default []");
24322529
prompts = List.of();
24332530
}
2434-
return new ListPromptsResult(prompts, nextCursor, meta);
2531+
return new ListPromptsResult(prompts, nextCursor, meta, ttlMs, cacheScope);
2532+
}
2533+
2534+
@Deprecated
2535+
public ListPromptsResult(List<Prompt> prompts, String nextCursor, Map<String, Object> meta) {
2536+
this(prompts, nextCursor, meta, null, null);
24352537
}
24362538

24372539
@Deprecated
@@ -2451,6 +2553,10 @@ public static class Builder {
24512553

24522554
private Map<String, Object> meta;
24532555

2556+
private Integer ttlMs;
2557+
2558+
private CacheScope cacheScope;
2559+
24542560
private Builder(List<Prompt> prompts) {
24552561
Assert.notNull(prompts, "prompts must not be null");
24562562
this.prompts = prompts;
@@ -2466,8 +2572,18 @@ public Builder meta(Map<String, Object> meta) {
24662572
return this;
24672573
}
24682574

2575+
public Builder ttlMs(Integer ttlMs) {
2576+
this.ttlMs = ttlMs;
2577+
return this;
2578+
}
2579+
2580+
public Builder cacheScope(CacheScope cacheScope) {
2581+
this.cacheScope = cacheScope;
2582+
return this;
2583+
}
2584+
24692585
public ListPromptsResult build() {
2470-
return new ListPromptsResult(prompts, nextCursor, meta);
2586+
return new ListPromptsResult(prompts, nextCursor, meta, ttlMs, cacheScope);
24712587
}
24722588

24732589
}
@@ -2620,26 +2736,37 @@ public GetPromptResult build() {
26202736
* @param nextCursor An optional cursor for pagination. If present, indicates there
26212737
* are more tools available.
26222738
* @param meta See specification for notes on _meta usage
2739+
* @param ttlMs A hint from the server indicating how long (in milliseconds) the
2740+
* client may cache this response before re-fetching
2741+
* @param cacheScope Indicates the intended scope of the cached response
26232742
*/
26242743
@JsonInclude(JsonInclude.Include.NON_ABSENT)
26252744
@JsonIgnoreProperties(ignoreUnknown = true)
26262745
public record ListToolsResult( // @formatter:off
26272746
@JsonProperty("tools") List<Tool> tools,
26282747
@JsonProperty("nextCursor") String nextCursor,
2629-
@JsonProperty("_meta") Map<String, Object> meta) implements Result { // @formatter:on
2748+
@JsonProperty("_meta") Map<String, Object> meta,
2749+
@JsonProperty("ttlMs") Integer ttlMs,
2750+
@JsonProperty("cacheScope") CacheScope cacheScope) implements Result { // @formatter:on
26302751

26312752
public ListToolsResult {
26322753
Assert.notNull(tools, "tools must not be null");
26332754
}
26342755

26352756
@JsonCreator
26362757
static ListToolsResult fromJson(@JsonProperty("tools") List<Tool> tools,
2637-
@JsonProperty("nextCursor") String nextCursor, @JsonProperty("_meta") Map<String, Object> meta) {
2758+
@JsonProperty("nextCursor") String nextCursor, @JsonProperty("_meta") Map<String, Object> meta,
2759+
@JsonProperty("ttlMs") Integer ttlMs, @JsonProperty("cacheScope") CacheScope cacheScope) {
26382760
if (tools == null) {
26392761
logger.warn("ListToolsResult: missing required field 'tools' during deserialization, using default []");
26402762
tools = List.of();
26412763
}
2642-
return new ListToolsResult(tools, nextCursor, meta);
2764+
return new ListToolsResult(tools, nextCursor, meta, ttlMs, cacheScope);
2765+
}
2766+
2767+
@Deprecated
2768+
public ListToolsResult(List<Tool> tools, String nextCursor, Map<String, Object> meta) {
2769+
this(tools, nextCursor, meta, null, null);
26432770
}
26442771

26452772
@Deprecated
@@ -2659,6 +2786,10 @@ public static class Builder {
26592786

26602787
private Map<String, Object> meta;
26612788

2789+
private Integer ttlMs;
2790+
2791+
private CacheScope cacheScope;
2792+
26622793
private Builder(List<Tool> tools) {
26632794
Assert.notNull(tools, "tools must not be null");
26642795
this.tools = tools;
@@ -2674,8 +2805,18 @@ public Builder meta(Map<String, Object> meta) {
26742805
return this;
26752806
}
26762807

2808+
public Builder ttlMs(Integer ttlMs) {
2809+
this.ttlMs = ttlMs;
2810+
return this;
2811+
}
2812+
2813+
public Builder cacheScope(CacheScope cacheScope) {
2814+
this.cacheScope = cacheScope;
2815+
return this;
2816+
}
2817+
26772818
public ListToolsResult build() {
2678-
return new ListToolsResult(tools, nextCursor, meta);
2819+
return new ListToolsResult(tools, nextCursor, meta, ttlMs, cacheScope);
26792820
}
26802821

26812822
}

0 commit comments

Comments
 (0)