Merge branch 'wt/review-002-cov-deployment-knobs'

# Conflicts:
#	src/gateway/routes.rs
This commit is contained in:
2026-08-31 01:55:12 +00:00
6 changed files with 300 additions and 0 deletions
+103
View File
@@ -581,3 +581,106 @@ async fn gateway_endpoints_exist_with_bearer_enforcement() {
"unknown op is NOT_FOUND regardless of auth"
);
}
/// COV-11b: the rmcp-entered `ServerHandler::call_tool` routing shell is
/// what production runs; the dispatch-level tests below it bypass it.
/// One real rmcp-protocol `peer.call_tool` round-trip per gateway tool
/// through the served `/mcp` mount.
#[tokio::test]
async fn to_mcp_call_tool_production_dispatch_round_trips_all_tools() {
use rmcp::model::{CallToolRequestParams, ClientCapabilities, ClientInfo, Implementation};
use rmcp::service::RoleClient;
use rmcp::transport::streamable_http_client::{
StreamableHttpClientTransport, StreamableHttpClientTransportConfig,
};
use rmcp::{Peer, ServiceExt};
let registry = local_registry();
let provider = provider_with(vec![("tok-1", identity("alice", &["user"]))]);
let base = spawn_full_server(registry, provider).await;
let mut default_headers = reqwest::header::HeaderMap::new();
default_headers.insert(
reqwest::header::AUTHORIZATION,
reqwest::header::HeaderValue::from_static("Bearer tok-1"),
);
let http = reqwest::Client::builder()
.default_headers(default_headers)
.build()
.unwrap();
let url = format!("{base}/mcp");
let transport = StreamableHttpClientTransport::with_client(
http,
StreamableHttpClientTransportConfig::with_uri(url),
);
let client_info = ClientInfo::new(
ClientCapabilities::default(),
Implementation::new("integration-test", "0.1.0"),
);
let running = client_info.serve(transport).await.expect("initialize");
let peer: Peer<RoleClient> = running.peer().clone();
let schema_params = CallToolRequestParams::new("schema".to_string()).with_arguments(
serde_json::json!({ "name": "echo/run" })
.as_object()
.unwrap()
.clone(),
);
let schema = peer.call_tool(schema_params).await.expect("schema call");
assert_eq!(schema.is_error, Some(false));
let structured = schema.structured_content.expect("structured present");
assert_eq!(structured["name"], "echo/run");
assert!(structured.get("input_schema").is_some());
let call_params = CallToolRequestParams::new("call".to_string()).with_arguments(
serde_json::json!({ "operation": "echo/run", "input": { "v": 7 } })
.as_object()
.unwrap()
.clone(),
);
let call_result = peer.call_tool(call_params).await.expect("call call");
assert_eq!(call_result.is_error, Some(false));
assert_eq!(
call_result.structured_content,
Some(serde_json::json!({ "v": 7 }))
);
let batch_params = CallToolRequestParams::new("batch".to_string()).with_arguments(
serde_json::json!({ "calls": [
{ "operation": "echo/run", "input": { "n": 1 } },
{ "operation": "echo/run", "input": { "n": 2 } }
] })
.as_object()
.unwrap()
.clone(),
);
let batch = peer.call_tool(batch_params).await.expect("batch call");
assert_eq!(batch.is_error, Some(false));
let results = batch
.structured_content
.and_then(|v| v.get("results").cloned())
.expect("results array");
assert_eq!(
results,
serde_json::json!([
{ "isError": false, "output": { "n": 1 } },
{ "isError": false, "output": { "n": 2 } }
])
);
let unknown = peer
.call_tool(CallToolRequestParams::new("bogus".to_string()))
.await
.expect("unknown tool call resolves, not errors");
assert_eq!(unknown.is_error, Some(true));
let err = unknown
.structured_content
.expect("structured error present");
assert_eq!(err["code"], "NOT_FOUND");
assert!(err["message"]
.as_str()
.unwrap_or_default()
.contains("unknown gateway tool"));
let _ = running.cancel().await;
}