From 959c62b5cd4c799bef3c9c3cac7ca36086b3bc97 Mon Sep 17 00:00:00 2001 From: Jonathan Springer Date: Wed, 22 Jul 2026 08:08:54 +0100 Subject: [PATCH] Emit SDK-required fields in modern MCP responses from fast-time-server mcp_types 2.0.0b2 (v2026_07_28 wire models) marks serverInfo, cacheScope, and ttlMs as required top-level DiscoverResult fields, and cacheScope/ttlMs as required on every CacheableResult list response. The modern server/discover response now carries top-level serverInfo and cacheScope/ttlMs (the _meta serverInfo entry is kept per the spec text), and modern tools/list results include cacheScope "private" and ttlMs 0, matching the SDK's conservative defaults. Verified end-to-end with mcp==2.0.0b2: auto and pinned 2026-07-28 modes now complete list_tools and tools/call against a strict server; legacy mode fails cleanly with the intended strict -32602 rejection. Fixes #11. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus Signed-off-by: Jonathan Springer --- mcp-servers/rust/fast-time-server/src/main.rs | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/mcp-servers/rust/fast-time-server/src/main.rs b/mcp-servers/rust/fast-time-server/src/main.rs index d7a49df..aa61922 100644 --- a/mcp-servers/rust/fast-time-server/src/main.rs +++ b/mcp-servers/rust/fast-time-server/src/main.rs @@ -522,6 +522,9 @@ fn mcp_discover_response(id: Option<&serde_json::Value>, config: &ServerConfig) "resultType": "complete", "supportedVersions": config.supported_versions(), "capabilities": { "tools": {} }, + "serverInfo": { "name": APP_NAME, "version": APP_VERSION }, + "cacheScope": "private", + "ttlMs": 0, "_meta": { SERVER_INFO_META_KEY: { "name": APP_NAME, "version": APP_VERSION } }, @@ -631,11 +634,21 @@ fn result_type_field(modern: bool) -> &'static str { } } +// CacheableResult responses (tools/list, server/discover) additionally +// require cacheScope and ttlMs on the 2026-07-28 wire. +fn cacheable_result_fields(modern: bool) -> &'static str { + if modern { + r#""resultType":"complete","cacheScope":"private","ttlMs":0,"# + } else { + "" + } +} + fn mcp_tools_list_response(id: Option<&serde_json::Value>, modern: bool) -> Response { mcp_json_response(format!( r#"{{"jsonrpc":"2.0","id":{},"result":{{{}"tools":[{{"name":"echo","description":"Echo back the provided message.","inputSchema":{{"type":"object","properties":{{"message":{{"type":"string"}},"delay":{{"type":"integer","minimum":0,"maximum":60000}},"delay_stddev":{{"type":"number","minimum":0}}}},"required":["message"]}}}},{{"name":"flaky","description":"Return isError=true for the first fail_times calls per key, then succeed (retry testing).","inputSchema":{{"type":"object","properties":{{"key":{{"type":"string","description":"Unique key to track attempt count across retries"}},"fail_times":{{"type":"integer","minimum":0,"description":"Number of times to return isError=true before succeeding (default 0)"}}}},"required":["key"]}}}},{{"name":"get_system_time","description":"Get current system time in the specified IANA timezone.","inputSchema":{{"type":"object","properties":{{"timezone":{{"type":"string"}}}}}}}},{{"name":"convert_time","description":"Convert a time value from a source IANA timezone to a target IANA timezone.","inputSchema":{{"type":"object","properties":{{"time":{{"type":"string"}},"source_timezone":{{"type":"string"}},"target_timezone":{{"type":"string"}}}},"required":["time","source_timezone","target_timezone"]}}}},{{"name":"schema_error","description":"Always returns isError=true.","inputSchema":{{"type":"object","properties":{{}}}},"outputSchema":{{"type":"object","properties":{{"recognitionId":{{"type":"string"}},"message":{{"type":"string"}}}},"required":["recognitionId"]}}}},{{"name":"schema_success","description":"Returns a JSON payload that conforms to the declared outputSchema.","inputSchema":{{"type":"object","properties":{{}}}},"outputSchema":{{"type":"object","properties":{{"recognitionId":{{"type":"string"}},"message":{{"type":"string"}}}},"required":["recognitionId"]}}}},{{"name":"get_stats","description":"Get server statistics including request count and uptime.","inputSchema":{{"type":"object","properties":{{}}}}}}]}}}}"#, mcp_id_json(id), - result_type_field(modern) + cacheable_result_fields(modern) )) } @@ -1606,6 +1619,11 @@ mod tests { result["supportedVersions"], json!(["2026-07-28", "2025-11-25"]) ); + // mcp_types 2.0.0b2 (v2026_07_28 wire models) requires serverInfo, + // cacheScope, and ttlMs as top-level DiscoverResult fields. + assert_eq!(result["serverInfo"]["name"], APP_NAME); + assert_eq!(result["cacheScope"], "private"); + assert_eq!(result["ttlMs"], 0); assert_eq!( result["_meta"]["io.modelcontextprotocol/serverInfo"]["name"], APP_NAME @@ -1613,6 +1631,24 @@ mod tests { assert!(result["capabilities"]["tools"].is_object()); } + #[tokio::test] + async fn test_modern_tools_list_includes_cacheable_result_fields() { + let response = mcp_handler( + modern_state(), + HeaderMap::new(), + axum::Json(modern_request("tools/list", "2026-07-28", 1)), + ) + .await; + + assert_eq!(response.status(), StatusCode::OK); + let body = response_json(response).await; + let result = &body["result"]; + assert_eq!(result["resultType"], "complete"); + assert_eq!(result["cacheScope"], "private"); + assert_eq!(result["ttlMs"], 0); + assert!(result["tools"].as_array().unwrap().len() >= 7); + } + #[tokio::test] async fn test_modern_tools_call_needs_no_session() { let mut request = modern_request("tools/call", "2026-07-28", 7);