diff --git a/crates/impossible-voice-server/src/mcp.rs b/crates/impossible-voice-server/src/mcp.rs index 6ca6dba..78282b4 100644 --- a/crates/impossible-voice-server/src/mcp.rs +++ b/crates/impossible-voice-server/src/mcp.rs @@ -13,7 +13,7 @@ use axum::{ use base64::{Engine, engine::general_purpose::STANDARD}; use impossible_server_core::RequestContext; use impossible_voice_audio::{AudioLimits, MonoPcm}; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize, de::IgnoredAny}; use serde_json::{Value, json}; use crate::voice_api::{VoiceBackend, VoiceBackendError}; @@ -170,6 +170,9 @@ struct ToolCall { name: String, #[serde(default)] arguments: Value, + /// MCP reserves `params._meta` on every request (for example `progressToken`). + #[serde(default, rename = "_meta")] + _meta: Option, } async fn call_tool( @@ -313,6 +316,9 @@ fn resources() -> Value { #[serde(deny_unknown_fields)] struct ReadResource { uri: String, + /// MCP reserves `params._meta` on every request (for example `progressToken`). + #[serde(default, rename = "_meta")] + _meta: Option, } fn read_resource(params: Value) -> Result { @@ -602,6 +608,41 @@ mod tests { Ok(()) } + #[tokio::test] + async fn real_mcp_network_accepts_reserved_meta_params() -> Result<(), Box> { + let (endpoint, shutdown, task) = start_server().await?; + let client = Client::new(); + let with_meta = call( + &client, + &endpoint, + rpc( + 8, + "tools/call", + json!({ "name": "health", "arguments": {}, "_meta": { "progressToken": "p-1" } }), + ), + ) + .await?; + assert_eq!( + with_meta["result"]["content"][0]["text"], + "{\"status\":\"ready\"}" + ); + let resource_with_meta = call( + &client, + &endpoint, + rpc( + 9, + "resources/read", + json!({ "uri": "voice://status", "_meta": { "progressToken": 9 } }), + ), + ) + .await?; + assert!(resource_with_meta["error"].is_null()); + + let _ = shutdown.cancel(); + tokio::time::timeout(Duration::from_secs(2), task).await???; + Ok(()) + } + #[allow(clippy::needless_pass_by_value)] fn rpc(id: u64, method: &str, params: Value) -> Value { json!({ "jsonrpc": "2.0", "id": id, "method": method, "params": params })