forked from 1b-agent/opencadstudio-upstream
feat(mcp): emit SEP-2549 TTL on every protocol version
List results (tools/list, resources/list, resources/read) carry ttlMs and cacheScope on the legacy 2025-11-25 path too; legacy clients ignore unknown fields per the SEP's backward-compat rule. The modern envelope (resultType, _meta.serverInfo) stays negotiated. Legacy-shape pin updated deliberately to assert presence.
This commit is contained in:
1 parent
d31b9d1a35
commit
71de9f844c
1 file changed
+34
-5
+34
-5
@@ -2110,6 +2110,15 @@ fn poll_task(
|
||||
}
|
||||
|
||||
fn protocol_result(mut result: Value, modern: bool, cacheable: bool) -> Value {
|
||||
// SEP-2549 freshness hints are version-independent: legacy clients
|
||||
// ignore unknown fields, so list results always carry them. The modern
|
||||
// envelope (resultType/_meta) stays negotiated.
|
||||
if cacheable {
|
||||
if let Some(object) = result.as_object_mut() {
|
||||
object.insert("ttlMs".into(), Value::from(CACHE_TTL_MS));
|
||||
object.insert("cacheScope".into(), Value::String("public".into()));
|
||||
}
|
||||
}
|
||||
if modern {
|
||||
let object = result
|
||||
.as_object_mut()
|
||||
@@ -2121,10 +2130,6 @@ fn protocol_result(mut result: Value, modern: bool, cacheable: bool) -> Value {
|
||||
"_meta".into(),
|
||||
json!({"io.modelcontextprotocol/serverInfo":server_info()}),
|
||||
);
|
||||
if cacheable {
|
||||
object.insert("ttlMs".into(), Value::from(CACHE_TTL_MS));
|
||||
object.insert("cacheScope".into(), Value::String("public".into()));
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
@@ -2550,7 +2555,11 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(listed["result"]["tools"].as_array().unwrap().len(), 4);
|
||||
assert!(listed["result"].get("ttlMs").is_none());
|
||||
// SEP-2549: freshness hints ship on every protocol version; only
|
||||
// the modern envelope stays negotiated (see
|
||||
// legacy_list_results_carry_sep2549_ttl).
|
||||
assert_eq!(listed["result"]["ttlMs"], CACHE_TTL_MS);
|
||||
assert_eq!(listed["result"]["cacheScope"], "public");
|
||||
assert!(listed["result"].get("resultType").is_none());
|
||||
}
|
||||
|
||||
@@ -2590,6 +2599,26 @@ mod tests {
|
||||
assert_eq!(listed["result"]["cacheScope"], "public");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_list_results_carry_sep2549_ttl() {
|
||||
// SEP-2549 makes ttlMs mandatory on list results; legacy clients
|
||||
// ignore unknown fields, so the stamp is version-independent while
|
||||
// the modern envelope (resultType/_meta) stays negotiated.
|
||||
for method in ["tools/list", "resources/list"] {
|
||||
let listed = handle_message(
|
||||
json!({"jsonrpc":"2.0","id":method,"method":method}),
|
||||
&mut HashMap::new(),
|
||||
&mut TaskStore::default(),
|
||||
&mut ResourceStore::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(listed["result"]["ttlMs"], CACHE_TTL_MS, "{method}");
|
||||
assert_eq!(listed["result"]["cacheScope"], "public", "{method}");
|
||||
assert!(listed["result"].get("resultType").is_none(), "{method}");
|
||||
assert!(listed["result"].get("_meta").is_none(), "{method}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_tool_rejects_mutations_before_connecting() {
|
||||
let mut clients = HashMap::new();
|
||||
|
||||
Reference in new issue
Block a user