From e28b2aa5e65fea01c837c994de62131117a5efbf Mon Sep 17 00:00:00 2001 From: cafalchio Date: Tue, 18 Aug 2026 14:14:24 +0100 Subject: [PATCH 01/17] Removed call_tool from expected failures Signed-off-by: cafalchio --- tests/conformance/expected-failures.yml | 43 ++++++++++--------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/tests/conformance/expected-failures.yml b/tests/conformance/expected-failures.yml index 5f21562..f39c373 100644 --- a/tests/conformance/expected-failures.yml +++ b/tests/conformance/expected-failures.yml @@ -1,37 +1,28 @@ +# Generated by `make conformance-bless` from scored dataplane findings. +# Pinned fixture findings are excluded; see upstream-fixture-failures.yml. server: - - server-stateless:sep-2575-server-declares-prompts-in-discover - - server-stateless:sep-2575-discover-capabilities-match-handlers - - server-stateless:sep-2575-http-server-no-independent-requests-on-stream - - completion-complete:completion-complete - - tools-list:tools-list - # Dataplane-owned on main. The fixture passes these behavior checks itself; - # its separate wire-schema failures live in upstream-fixture-failures.yml. - - tools-call-with-progress:tools-call-with-progress - - resources-list:resources-list - - resources-read-text:resources-read-text - - resources-read-binary:resources-read-binary - - resources-templates-read:resources-templates-read - - sep-2164-resource-not-found:sep-2164-error-code - - sep-2164-resource-not-found:sep-2164-data-uri - - prompts-list:prompts-list - - prompts-get-simple:prompts-get-simple - - prompts-get-with-args:prompts-get-with-args - - prompts-get-embedded-resource:prompts-get-embedded-resource - - prompts-get-with-image:prompts-get-with-image - - caching:sep-2549-tools-list-caching-hints + - caching:sep-2549-cache-scope-valid - caching:sep-2549-prompts-list-caching-hints - caching:sep-2549-resources-list-caching-hints - caching:sep-2549-resources-templates-list-caching-hints + - caching:sep-2549-tools-list-caching-hints - caching:sep-2549-ttl-non-negative - - caching:sep-2549-cache-scope-valid + - completion-complete:completion-complete - input-required-result-basic-elicitation:sep-2322-elicitation-incomplete - - input-required-result-basic-sampling:sep-2322-sampling-incomplete - input-required-result-basic-list-roots:sep-2322-list-roots-incomplete - - input-required-result-request-state:sep-2322-request-state-incomplete - - input-required-result-multiple-input-requests:sep-2322-multiple-inputs-incomplete - - input-required-result-multi-round:sep-2322-multi-round-r1 + - input-required-result-basic-sampling:sep-2322-sampling-incomplete + - input-required-result-capability-check:sep-2322-respect-client-capabilities - input-required-result-missing-input-response:sep-2322-missing-response-rerequests + - input-required-result-multi-round:sep-2322-multi-round-r1 + - input-required-result-multiple-input-requests:sep-2322-multiple-inputs-incomplete - input-required-result-non-tool-request:sep-2322-non-tool-incomplete + - input-required-result-request-state:sep-2322-request-state-incomplete - input-required-result-result-type:sep-2322-result-type-included - input-required-result-tampered-state:sep-2322-reject-tampered-state - - input-required-result-capability-check:sep-2322-respect-client-capabilities + - prompts-list:prompts-list + - resources-list:resources-list + - server-stateless:sep-2575-discover-capabilities-match-handlers + - server-stateless:sep-2575-http-server-no-independent-requests-on-stream + - server-stateless:sep-2575-server-declares-prompts-in-discover + - tools-call-with-progress:tools-call-with-progress + - tools-list:tools-list From 0f63bfde149e593977d5116a5825968c67f683ce Mon Sep 17 00:00:00 2001 From: cafalchio Date: Tue, 18 Aug 2026 14:35:59 +0100 Subject: [PATCH 02/17] removed passing tests from ignore Signed-off-by: cafalchio --- tests/conformance/expected-failures.yml | 29 ++++++++++++++++++------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/tests/conformance/expected-failures.yml b/tests/conformance/expected-failures.yml index f39c373..415b7f7 100644 --- a/tests/conformance/expected-failures.yml +++ b/tests/conformance/expected-failures.yml @@ -1,7 +1,26 @@ # Generated by `make conformance-bless` from scored dataplane findings. # Pinned fixture findings are excluded; see upstream-fixture-failures.yml. server: - - caching:sep-2549-cache-scope-valid + - server-stateless:sep-2575-server-declares-prompts-in-discover + - server-stateless:sep-2575-discover-capabilities-match-handlers + - server-stateless:sep-2575-http-server-no-independent-requests-on-stream + - completion-complete:completion-complete + - tools-list:tools-list + # Dataplane-owned on main. The fixture passes these behavior checks itself; + # its separate wire-schema failures live in upstream-fixture-failures.yml. + - tools-call-with-progress:tools-call-with-progress + - resources-list:resources-list + - resources-read-text:resources-read-text + - resources-read-binary:resources-read-binary + - resources-templates-read:resources-templates-read + - sep-2164-resource-not-found:sep-2164-error-code + - sep-2164-resource-not-found:sep-2164-data-uri + - prompts-list:prompts-list + - prompts-get-simple:prompts-get-simple + - prompts-get-with-args:prompts-get-with-args + - prompts-get-embedded-resource:prompts-get-embedded-resource + - prompts-get-with-image:prompts-get-with-image + - caching:sep-2549-tools-list-caching-hints - caching:sep-2549-prompts-list-caching-hints - caching:sep-2549-resources-list-caching-hints - caching:sep-2549-resources-templates-list-caching-hints @@ -19,10 +38,4 @@ server: - input-required-result-request-state:sep-2322-request-state-incomplete - input-required-result-result-type:sep-2322-result-type-included - input-required-result-tampered-state:sep-2322-reject-tampered-state - - prompts-list:prompts-list - - resources-list:resources-list - - server-stateless:sep-2575-discover-capabilities-match-handlers - - server-stateless:sep-2575-http-server-no-independent-requests-on-stream - - server-stateless:sep-2575-server-declares-prompts-in-discover - - tools-call-with-progress:tools-call-with-progress - - tools-list:tools-list + - input-required-result-capability-check:sep-2322-respect-client-capabilities From 0a577c87407555fa0f1bca6437ec4788e7eeba24 Mon Sep 17 00:00:00 2001 From: cafalchio Date: Tue, 18 Aug 2026 15:12:10 +0100 Subject: [PATCH 03/17] Added prompt and resources stateless Signed-off-by: cafalchio --- .../src/gateway/mcp_service/prompts.rs | 37 ++++++++++++------ .../src/gateway/mcp_service/resources.rs | 38 +++++++++++++------ 2 files changed, 51 insertions(+), 24 deletions(-) diff --git a/crates/contextforge-data-plane-lib/src/gateway/mcp_service/prompts.rs b/crates/contextforge-data-plane-lib/src/gateway/mcp_service/prompts.rs index 4962441..499c02a 100644 --- a/crates/contextforge-data-plane-lib/src/gateway/mcp_service/prompts.rs +++ b/crates/contextforge-data-plane-lib/src/gateway/mcp_service/prompts.rs @@ -1,16 +1,17 @@ use contextforge_data_plane_cpex::PromptPreFetchResult; use rmcp::{ ErrorData, RoleServer, - model::{GetPromptRequestParams, GetPromptResponse, ListPromptsResult, PaginatedRequestParams}, + model::{ErrorCode, GetPromptRequestParams, GetPromptResponse, ListPromptsResult, PaginatedRequestParams}, service::RequestContext, }; use tracing::info; use super::McpService; use crate::gateway::{ - identifier_routing::{backend_forward_error, route_identifier_to_backend}, + identifier_routing::{backend_forward_error, resolve_tool_route}, list_aggregation::{decode_gateway_cursor, fan_out_list, merge_prompts}, mcp_call_validator::AuthorizedCallValidator, + mcp_service::initialization::connect_backend_for_request, session_manager::SessionManager, session_store::UserSessionStore, }; @@ -74,16 +75,28 @@ where T: UserSessionStore + Send + Sync + 'static, { let mcp_call_validator = AuthorizedCallValidator::new("get_prompt", &cx); - let (virtual_host, session_id, claims) = mcp_call_validator.validate()?; - let session_manager = SessionManager::new(virtual_host, session_id, claims.sub.as_str(), &mcp_service.transports); + let (virtual_host, _claims) = mcp_call_validator.validate_stateless()?; + let backend_names: Vec<&str> = virtual_host.backends.keys().map(String::as_str).collect(); + let Some((backend_name, prompt_name)) = resolve_tool_route(virtual_host, &request.name, &backend_names) else { + return Err(ErrorData { + code: ErrorCode::INVALID_PARAMS, + message: "Routing problem... promtp not found".into(), + data: None, + }); + }; - let (service_name, service, prompt_name) = route_identifier_to_backend( - &session_manager, - "get_prompt", - &request.name, - "Routing problem... invalid prompt name", - ) - .await?; + let backend_name = backend_name.to_owned(); + let prompt_name = prompt_name.to_owned(); + + let backend = virtual_host.backends.get(&backend_name).ok_or_else(|| ErrorData { + code: ErrorCode::INVALID_PARAMS, + message: "Routing problem... backend not found".into(), + data: None, + })?; + + let service_name = backend_name.clone(); + let backend_service = + connect_backend_for_request(mcp_service, &backend_name, backend, virtual_host.backends.len() > 1, &cx).await?; let pre_result = if let Some(plugin_runtime) = &mcp_service.plugin_runtime { plugin_runtime.before_get_prompt(&request, &prompt_name, &service_name).await? @@ -92,7 +105,7 @@ where }; let mut routed_request = request; pre_result.arguments.apply_to_request(&mut routed_request, &prompt_name); - let response = service + let response = backend_service .get_prompt(routed_request) .await .map_err(|error| backend_forward_error("get_prompt", &service_name, &error))?; diff --git a/crates/contextforge-data-plane-lib/src/gateway/mcp_service/resources.rs b/crates/contextforge-data-plane-lib/src/gateway/mcp_service/resources.rs index d75dea0..8de861a 100644 --- a/crates/contextforge-data-plane-lib/src/gateway/mcp_service/resources.rs +++ b/crates/contextforge-data-plane-lib/src/gateway/mcp_service/resources.rs @@ -1,7 +1,7 @@ use rmcp::{ ErrorData, RoleServer, model::{ - ListResourceTemplatesResult, ListResourcesResult, PaginatedRequestParams, ReadResourceRequestParams, + ErrorCode, ListResourceTemplatesResult, ListResourcesResult, PaginatedRequestParams, ReadResourceRequestParams, ReadResourceResponse, SubscribeRequestParams, UnsubscribeRequestParams, }, service::RequestContext, @@ -10,9 +10,10 @@ use tracing::info; use super::McpService; use crate::gateway::{ - identifier_routing::{backend_forward_error, route_identifier_to_backend}, + identifier_routing::{backend_forward_error, resolve_tool_route, route_identifier_to_backend}, list_aggregation::{decode_gateway_cursor, fan_out_list, merge_resource_templates, merge_resources}, mcp_call_validator::AuthorizedCallValidator, + mcp_service::initialization::connect_backend_for_request, session_manager::SessionManager, session_store::UserSessionStore, }; @@ -76,24 +77,37 @@ where T: UserSessionStore + Send + Sync + 'static, { let mcp_call_validator = AuthorizedCallValidator::new("read_resource", &cx); - let (virtual_host, session_id, claims) = mcp_call_validator.validate()?; - let session_manager = SessionManager::new(virtual_host, session_id, claims.sub.as_str(), &mcp_service.transports); + let (virtual_host, _claims) = mcp_call_validator.validate_stateless()?; + let backend_names: Vec<&str> = virtual_host.backends.keys().map(String::as_str).collect(); + let Some((backend_name, resource_uri)) = resolve_tool_route(virtual_host, &request.uri, &backend_names) else { + return Err(ErrorData { + code: ErrorCode::INVALID_PARAMS, + message: "Routing problem... resource not found".into(), + data: None, + }); + }; + let backend_name = backend_name.to_owned(); + let resource_uri = resource_uri.to_owned(); - let (service_name, service, resource_uri) = route_identifier_to_backend( - &session_manager, - "read_resource", - &request.uri, - "Routing problem... wrong resource name", - ) - .await?; + let backend = virtual_host.backends.get(&backend_name).ok_or_else(|| ErrorData { + code: ErrorCode::INVALID_PARAMS, + message: "Routing problem... backend not found".into(), + data: None, + })?; + + let service_name = backend_name.clone(); + let backend_service = + connect_backend_for_request(mcp_service, &backend_name, backend, virtual_host.backends.len() > 1, &cx).await?; let mut routed_request = request; routed_request.uri = resource_uri; - let response = service + + let response = backend_service .read_resource(routed_request) .await .map_err(|error| backend_forward_error("read_resource", &service_name, &error))?; info!("read_resource: backend {service_name} returned {} contents", response.contents.len()); + Ok(response.into()) } From 43965ce73087b1e5a1838d3a8e1c7b5cd7010b2f Mon Sep 17 00:00:00 2001 From: cafalchio Date: Tue, 18 Aug 2026 16:14:25 +0100 Subject: [PATCH 04/17] connect to backend after plugin pre_promtp Signed-off-by: cafalchio --- .../src/gateway/mcp_service/prompts.rs | 6 ++-- tests/conformance/expected-failures.yml | 29 +++++-------------- 2 files changed, 10 insertions(+), 25 deletions(-) diff --git a/crates/contextforge-data-plane-lib/src/gateway/mcp_service/prompts.rs b/crates/contextforge-data-plane-lib/src/gateway/mcp_service/prompts.rs index 499c02a..0c28e43 100644 --- a/crates/contextforge-data-plane-lib/src/gateway/mcp_service/prompts.rs +++ b/crates/contextforge-data-plane-lib/src/gateway/mcp_service/prompts.rs @@ -93,16 +93,14 @@ where message: "Routing problem... backend not found".into(), data: None, })?; - let service_name = backend_name.clone(); - let backend_service = - connect_backend_for_request(mcp_service, &backend_name, backend, virtual_host.backends.len() > 1, &cx).await?; - let pre_result = if let Some(plugin_runtime) = &mcp_service.plugin_runtime { plugin_runtime.before_get_prompt(&request, &prompt_name, &service_name).await? } else { PromptPreFetchResult::unchanged() }; + let backend_service = + connect_backend_for_request(mcp_service, &backend_name, backend, virtual_host.backends.len() > 1, &cx).await?; let mut routed_request = request; pre_result.arguments.apply_to_request(&mut routed_request, &prompt_name); let response = backend_service diff --git a/tests/conformance/expected-failures.yml b/tests/conformance/expected-failures.yml index 415b7f7..f39c373 100644 --- a/tests/conformance/expected-failures.yml +++ b/tests/conformance/expected-failures.yml @@ -1,26 +1,7 @@ # Generated by `make conformance-bless` from scored dataplane findings. # Pinned fixture findings are excluded; see upstream-fixture-failures.yml. server: - - server-stateless:sep-2575-server-declares-prompts-in-discover - - server-stateless:sep-2575-discover-capabilities-match-handlers - - server-stateless:sep-2575-http-server-no-independent-requests-on-stream - - completion-complete:completion-complete - - tools-list:tools-list - # Dataplane-owned on main. The fixture passes these behavior checks itself; - # its separate wire-schema failures live in upstream-fixture-failures.yml. - - tools-call-with-progress:tools-call-with-progress - - resources-list:resources-list - - resources-read-text:resources-read-text - - resources-read-binary:resources-read-binary - - resources-templates-read:resources-templates-read - - sep-2164-resource-not-found:sep-2164-error-code - - sep-2164-resource-not-found:sep-2164-data-uri - - prompts-list:prompts-list - - prompts-get-simple:prompts-get-simple - - prompts-get-with-args:prompts-get-with-args - - prompts-get-embedded-resource:prompts-get-embedded-resource - - prompts-get-with-image:prompts-get-with-image - - caching:sep-2549-tools-list-caching-hints + - caching:sep-2549-cache-scope-valid - caching:sep-2549-prompts-list-caching-hints - caching:sep-2549-resources-list-caching-hints - caching:sep-2549-resources-templates-list-caching-hints @@ -38,4 +19,10 @@ server: - input-required-result-request-state:sep-2322-request-state-incomplete - input-required-result-result-type:sep-2322-result-type-included - input-required-result-tampered-state:sep-2322-reject-tampered-state - - input-required-result-capability-check:sep-2322-respect-client-capabilities + - prompts-list:prompts-list + - resources-list:resources-list + - server-stateless:sep-2575-discover-capabilities-match-handlers + - server-stateless:sep-2575-http-server-no-independent-requests-on-stream + - server-stateless:sep-2575-server-declares-prompts-in-discover + - tools-call-with-progress:tools-call-with-progress + - tools-list:tools-list From de54380540f704b6a90cf4f79284a032b679b6c0 Mon Sep 17 00:00:00 2001 From: cafalchio Date: Wed, 19 Aug 2026 09:46:21 +0100 Subject: [PATCH 05/17] removed ignore for passing test Signed-off-by: cafalchio --- crates/contextforge-data-plane-lib/tests/gateway_completions.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/contextforge-data-plane-lib/tests/gateway_completions.rs b/crates/contextforge-data-plane-lib/tests/gateway_completions.rs index db37558..4cd2f68 100644 --- a/crates/contextforge-data-plane-lib/tests/gateway_completions.rs +++ b/crates/contextforge-data-plane-lib/tests/gateway_completions.rs @@ -10,7 +10,6 @@ use support::{ #[tokio::test(flavor = "multi_thread", worker_threads = 1)] #[test_log::test] -#[ignore = "2026-07-28 protocol transition"] async fn plaintext_completes_prompt_argument_through_prefixed_backend() -> Result<()> { let gateway_port = create_ports(1)[0]; let user = TEST_USER_ID; @@ -29,7 +28,6 @@ async fn plaintext_completes_prompt_argument_through_prefixed_backend() -> Resul #[tokio::test(flavor = "multi_thread", worker_threads = 1)] #[test_log::test] -#[ignore = "2026-07-28 protocol transition"] async fn plaintext_completes_resource_argument_through_prefixed_backend() -> Result<()> { let gateway_port = create_ports(1)[0]; let user = TEST_USER_ID; From 4debd57592dd7b8afeec1f059e1fd1f76804fa23 Mon Sep 17 00:00:00 2001 From: cafalchio Date: Wed, 19 Aug 2026 09:48:49 +0100 Subject: [PATCH 06/17] Replace test to use new rmcp client Signed-off-by: cafalchio --- .../tests/gateway_plugins.rs | 41 ++----------------- 1 file changed, 3 insertions(+), 38 deletions(-) diff --git a/crates/contextforge-data-plane-lib/tests/gateway_plugins.rs b/crates/contextforge-data-plane-lib/tests/gateway_plugins.rs index 4dce7a8..72822d1 100644 --- a/crates/contextforge-data-plane-lib/tests/gateway_plugins.rs +++ b/crates/contextforge-data-plane-lib/tests/gateway_plugins.rs @@ -377,44 +377,9 @@ async fn disabled_runtime_does_not_invoke_registered_plugin() { #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn stateless_tool_call_reaches_backend_without_session() { let gateway = start_gateway(TEST_USER_ID, false, Arc::new(CpexRuntimeRegistry::default())).await; - let response = reqwest::Client::new() - .post(gateway.gateway_url()) - .bearer_auth(token(TEST_USER_ID)) - .header(http::header::CONTENT_TYPE, "application/json") - .header(http::header::ACCEPT, "application/json, text/event-stream") - .header("MCP-Protocol-Version", "2026-07-28") - .header("MCP-Method", "tools/call") - .header("MCP-Name", "sum") - .json(&json!({ - "jsonrpc": "2.0", - "id": 1, - "method": "tools/call", - "params": { - "name": "sum", - "arguments": { "a": 1, "b": 2 }, - "_meta": { - "io.modelcontextprotocol/protocolVersion": "2026-07-28", - "io.modelcontextprotocol/clientInfo": { - "name": "stateless-test-client", - "version": "0.1.0" - }, - "io.modelcontextprotocol/clientCapabilities": {} - } - } - })) - .send() - .await - .expect("stateless tool call is sent"); - - let status = response.status(); - let body = response.text().await.expect("stateless tool response body is read"); - assert!(status.is_success(), "stateless tool call failed with status {status}: {body}"); - let messages = sse_data_values(&body); - let result = messages - .iter() - .find(|message| message.get("id").and_then(Value::as_i64) == Some(1)) - .unwrap_or_else(|| panic!("missing response id 1 in body: {body}")); - assert_eq!(Some("3"), result.pointer("/result/content/0/text").and_then(Value::as_str)); + let service = gateway.connect(TEST_USER_ID).await; + let result = service.call_tool(sum_request("sum", 1, 2)).await.unwrap(); + assert_eq!("3", text(&result)); } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] From 2936d9d71c1153f36d0b4418e10cc9abb72891da Mon Sep 17 00:00:00 2001 From: cafalchio Date: Wed, 19 Aug 2026 10:09:37 +0100 Subject: [PATCH 07/17] Added luca suggestions Signed-off-by: cafalchio --- .../tests/gateway_plugins.rs | 78 ++++++++++++++++++- .../tests/support/client.rs | 44 +++++++++++ .../tests/support/mod.rs | 4 +- 3 files changed, 122 insertions(+), 4 deletions(-) diff --git a/crates/contextforge-data-plane-lib/tests/gateway_plugins.rs b/crates/contextforge-data-plane-lib/tests/gateway_plugins.rs index 72822d1..26c6506 100644 --- a/crates/contextforge-data-plane-lib/tests/gateway_plugins.rs +++ b/crates/contextforge-data-plane-lib/tests/gateway_plugins.rs @@ -377,11 +377,85 @@ async fn disabled_runtime_does_not_invoke_registered_plugin() { #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn stateless_tool_call_reaches_backend_without_session() { let gateway = start_gateway(TEST_USER_ID, false, Arc::new(CpexRuntimeRegistry::default())).await; - let service = gateway.connect(TEST_USER_ID).await; - let result = service.call_tool(sum_request("sum", 1, 2)).await.unwrap(); + let service = support::connect_modern_client( + gateway.gateway_url(), + support::create_client(TEST_USER_ID), + support::modern_client_info(), + ) + .await; + let result = service.call_tool(sum_request("sum", 1, 2)).await.expect("stateless tool call succeeds"); assert_eq!("3", text(&result)); } +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn stateless_tool_error_round_trips() { + let gateway = start_gateway(TEST_USER_ID, false, Arc::new(CpexRuntimeRegistry::default())).await; + let service = support::connect_modern_client( + gateway.gateway_url(), + support::create_client(TEST_USER_ID), + support::modern_client_info(), + ) + .await; + let error = service.call_tool(CallToolRequestParams::new("missing_tool")).await.unwrap_err(); + let rmcp::service::ServiceError::McpError(error) = error else { + panic!("expected backend MCP error, got {error:?}"); + }; + assert_eq!(ErrorCode::METHOD_NOT_FOUND, error.code); +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn stateless_alias_and_namespaced_tool_names_route() { + let gateway_port = support::create_ports(1)[0]; + let support::ListToolsGatewaySettings { handle, gateway_url, expected_tool_names, .. } = + support::create_gateway_with_four_counters(TEST_USER_ID, support::plaintext_config(gateway_port)) + .await + .expect("gateway starts"); + let service = support::connect_modern_client(&gateway_url, support::create_client(TEST_USER_ID), support::modern_client_info()).await; + let alias = expected_tool_names.iter().find(|name| name.ends_with(".sum")).expect("sum alias is advertised"); + let backend_port = alias + .strip_prefix("backend-") + .and_then(|name| name.strip_suffix(".sum")) + .expect("alias contains the backend port"); + let namespaced_name = format!("00000000-0000-0000-0000-{backend_port:0>12}-sum"); + let alias_result = service.call_tool(sum_request(alias, 1, 2)).await.expect("alias routes"); + let namespaced_result = service.call_tool(sum_request(&namespaced_name, 3, 4)).await.expect("namespace routes"); + assert_eq!("3", text(&alias_result)); + assert_eq!("7", text(&namespaced_result)); + handle.abort(); +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn stateless_concurrent_progress_calls_remain_request_scoped() { + let gateway = start_gateway(TEST_USER_ID, false, Arc::new(CpexRuntimeRegistry::default())).await; + let client = RecordingClient::default(); + let progress = Arc::clone(&client.progress); + let service = support::connect_modern_client(gateway.gateway_url(), support::create_client(TEST_USER_ID), client).await; + let first = send_progress_call(&service, "progress_sum").await; + let first_progress_token = first.progress_token.clone(); + let second = send_progress_call(&service, "progress_sum").await; + let second_progress_token = second.progress_token.clone(); + assert_ne!(first_progress_token, second_progress_token); + let (first, second) = tokio::time::timeout(std::time::Duration::from_secs(3), async { + tokio::join!(first.await_response(), second.await_response()) + }) + .await + .expect("both stateless calls complete"); + let ServerResult::CallToolResult(first) = first.expect("first call succeeds") else { + panic!("expected first tool result"); + }; + let ServerResult::CallToolResult(second) = second.expect("second call succeeds") else { + panic!("expected second tool result"); + }; + assert_eq!("completed 4 packages", text(&first)); + assert_eq!("completed 4 packages", text(&second)); + wait_for_event_count(&progress, 8).await; + let progress = progress.lock().expect("progress lock poisoned"); + assert_eq!(4, progress.iter().filter(|event| event.progress_token == first_progress_token).count()); + assert_eq!(4, progress.iter().filter(|event| event.progress_token == second_progress_token).count()); +} + + + #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn secrets_detection_pre_hook_redacts_tool_arguments_before_backend_call() { let runtime = runtime_with_secrets_detection( diff --git a/crates/contextforge-data-plane-lib/tests/support/client.rs b/crates/contextforge-data-plane-lib/tests/support/client.rs index d0d7e59..cb5a9ac 100644 --- a/crates/contextforge-data-plane-lib/tests/support/client.rs +++ b/crates/contextforge-data-plane-lib/tests/support/client.rs @@ -71,3 +71,47 @@ where } } } + + +pub(crate) fn modern_client_info() -> InitializeRequestParams { + use rmcp::model::{ClientCapabilities, Implementation, ProtocolVersion}; + InitializeRequestParams::new( + ClientCapabilities::default(), + Implementation::new("stateless-test-client", "0.1.0"), + ) + .with_protocol_version(ProtocolVersion::V_2026_07_28) +} + +pub(crate) async fn connect_modern_client( + gateway_url: &str, + client: reqwest::Client, + handler: H, +) -> rmcp::service::RunningService +where + H: rmcp::ClientHandler + Clone, +{ + let deadline = Instant::now() + CLIENT_CONNECT_TIMEOUT; + loop { + let transport = StreamableHttpClientTransport::with_client( + client.clone(), + StreamableHttpClientTransportConfig::with_uri(gateway_url.to_owned()), + ); + match rmcp::service::serve_client_with_lifecycle( + handler.clone(), + transport, + rmcp::ClientLifecycleMode::Discover { + preferred_versions: vec![rmcp::model::ProtocolVersion::V_2026_07_28], + }, + ) + .await + { + Ok(service) => return service, + Err(error) if Instant::now() < deadline => { + warn!("No Service {error:?}"); + tokio::time::sleep(TEST_POLL_INTERVAL).await; + }, + Err(error) => panic!("modern stateless client connects: {error:?}"), + } + } +} + diff --git a/crates/contextforge-data-plane-lib/tests/support/mod.rs b/crates/contextforge-data-plane-lib/tests/support/mod.rs index a1ce24b..ffb1ae2 100644 --- a/crates/contextforge-data-plane-lib/tests/support/mod.rs +++ b/crates/contextforge-data-plane-lib/tests/support/mod.rs @@ -16,8 +16,8 @@ pub(crate) const TEST_USER_EMAIL: &str = "admin@example.com"; pub(crate) use auth::token; pub(crate) use client::{ - CLIENT_CONNECT_TIMEOUT, TEST_POLL_INTERVAL, connect_client, connect_client_with_handler, create_client, - create_tls_client, + CLIENT_CONNECT_TIMEOUT, TEST_POLL_INTERVAL, connect_client, connect_client_with_handler, connect_modern_client, + create_client, create_tls_client, modern_client_info, }; pub(crate) use list_tools_gateway::{ ListToolsGatewaySettings, create_gateway_with_four_counters, create_ports, From feb26872051c54d57cc10517ea56897cdeb1ebdd Mon Sep 17 00:00:00 2001 From: cafalchio Date: Wed, 19 Aug 2026 10:18:49 +0100 Subject: [PATCH 08/17] fixed clippy and fmt Signed-off-by: cafalchio --- .../tests/gateway_plugins.rs | 12 ++++++++---- .../tests/support/client.rs | 9 ++------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/crates/contextforge-data-plane-lib/tests/gateway_plugins.rs b/crates/contextforge-data-plane-lib/tests/gateway_plugins.rs index 26c6506..efbea39 100644 --- a/crates/contextforge-data-plane-lib/tests/gateway_plugins.rs +++ b/crates/contextforge-data-plane-lib/tests/gateway_plugins.rs @@ -410,7 +410,12 @@ async fn stateless_alias_and_namespaced_tool_names_route() { support::create_gateway_with_four_counters(TEST_USER_ID, support::plaintext_config(gateway_port)) .await .expect("gateway starts"); - let service = support::connect_modern_client(&gateway_url, support::create_client(TEST_USER_ID), support::modern_client_info()).await; + let service = support::connect_modern_client( + &gateway_url, + support::create_client(TEST_USER_ID), + support::modern_client_info(), + ) + .await; let alias = expected_tool_names.iter().find(|name| name.ends_with(".sum")).expect("sum alias is advertised"); let backend_port = alias .strip_prefix("backend-") @@ -429,7 +434,8 @@ async fn stateless_concurrent_progress_calls_remain_request_scoped() { let gateway = start_gateway(TEST_USER_ID, false, Arc::new(CpexRuntimeRegistry::default())).await; let client = RecordingClient::default(); let progress = Arc::clone(&client.progress); - let service = support::connect_modern_client(gateway.gateway_url(), support::create_client(TEST_USER_ID), client).await; + let service = + support::connect_modern_client(gateway.gateway_url(), support::create_client(TEST_USER_ID), client).await; let first = send_progress_call(&service, "progress_sum").await; let first_progress_token = first.progress_token.clone(); let second = send_progress_call(&service, "progress_sum").await; @@ -454,8 +460,6 @@ async fn stateless_concurrent_progress_calls_remain_request_scoped() { assert_eq!(4, progress.iter().filter(|event| event.progress_token == second_progress_token).count()); } - - #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn secrets_detection_pre_hook_redacts_tool_arguments_before_backend_call() { let runtime = runtime_with_secrets_detection( diff --git a/crates/contextforge-data-plane-lib/tests/support/client.rs b/crates/contextforge-data-plane-lib/tests/support/client.rs index cb5a9ac..dfebe34 100644 --- a/crates/contextforge-data-plane-lib/tests/support/client.rs +++ b/crates/contextforge-data-plane-lib/tests/support/client.rs @@ -72,14 +72,10 @@ where } } - pub(crate) fn modern_client_info() -> InitializeRequestParams { use rmcp::model::{ClientCapabilities, Implementation, ProtocolVersion}; - InitializeRequestParams::new( - ClientCapabilities::default(), - Implementation::new("stateless-test-client", "0.1.0"), - ) - .with_protocol_version(ProtocolVersion::V_2026_07_28) + InitializeRequestParams::new(ClientCapabilities::default(), Implementation::new("stateless-test-client", "0.1.0")) + .with_protocol_version(ProtocolVersion::V_2026_07_28) } pub(crate) async fn connect_modern_client( @@ -114,4 +110,3 @@ where } } } - From 317c56c31252df0720b8caa137bf790e5dff847c Mon Sep 17 00:00:00 2001 From: cafalchio Date: Wed, 19 Aug 2026 11:24:49 +0100 Subject: [PATCH 09/17] fixed clippy Signed-off-by: cafalchio --- .secrets.baseline | 98 ------------------- .../tests/gateway_plugins.rs | 5 +- 2 files changed, 4 insertions(+), 99 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index 8f4c5ab..38b0975 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -213,104 +213,6 @@ "verified_result": null } ], - "crates/plugins/cpex-secrets-detection/src/scanner.rs": [ - { - "hashed_secret": "9249e2590f5d19742260cb5296cb76fe0677f147", - "is_secret": false, - "is_verified": false, - "line_number": 238, - "type": "Secret Keyword", - "verified_result": null - }, - { - "hashed_secret": "199da8f71b7dced64f82cf6e96483134cace9b14", - "is_secret": false, - "is_verified": false, - "line_number": 239, - "type": "Secret Keyword", - "verified_result": null - }, - { - "hashed_secret": "c0026c4c848882618c987859077ffbae92130625", - "is_secret": false, - "is_verified": false, - "line_number": 242, - "type": "Secret Keyword", - "verified_result": null - }, - { - "hashed_secret": "078553dc10635837abb80f404302c70cba91b879", - "is_secret": false, - "is_verified": false, - "line_number": 278, - "type": "Base64 High Entropy String", - "verified_result": null - }, - { - "hashed_secret": "e175c6f5f2a92e8623bd9a4820edb4e8c1b0fd10", - "is_secret": false, - "is_verified": false, - "line_number": 278, - "type": "GitHub Token", - "verified_result": null - }, - { - "hashed_secret": "97d99a51e5ac827bb36fe6273facfda35245917a", - "is_secret": false, - "is_verified": false, - "line_number": 279, - "type": "Base64 High Entropy String", - "verified_result": null - }, - { - "hashed_secret": "be4fc4886bd949b369d5e092eb87494f12e57e5b", - "is_secret": false, - "is_verified": false, - "line_number": 282, - "type": "Private Key", - "verified_result": null - }, - { - "hashed_secret": "b1775a785f09a6ebaf2dc33d6eaeb98974d9cdb8", - "is_secret": false, - "is_verified": false, - "line_number": 284, - "type": "Hex High Entropy String", - "verified_result": null - }, - { - "hashed_secret": "eae9124e42e2ef05ba727bd1a1c0c6fa61a05b9e", - "is_secret": false, - "is_verified": false, - "line_number": 302, - "type": "Secret Keyword", - "verified_result": null - }, - { - "hashed_secret": "86de8c52637ec530fe39b0a8471da9b8764d5242", - "is_secret": false, - "is_verified": false, - "line_number": 401, - "type": "AWS Access Key", - "verified_result": null - }, - { - "hashed_secret": "9d7235fe33b6612ed7ebca4b63afd00d4adf5d66", - "is_secret": false, - "is_verified": false, - "line_number": 410, - "type": "Secret Keyword", - "verified_result": null - }, - { - "hashed_secret": "27a39044bff80a4c196689dfa8dcf129cb27fef8", - "is_secret": false, - "is_verified": false, - "line_number": 431, - "type": "Base64 High Entropy String", - "verified_result": null - } - ], "crates/plugins/cpex-secrets-detection/tests/plugin_manager.rs": [ { "hashed_secret": "436da7d4d22c39c0165ab0d5b40073d0f2fc11c5", diff --git a/crates/contextforge-data-plane-lib/tests/gateway_plugins.rs b/crates/contextforge-data-plane-lib/tests/gateway_plugins.rs index efbea39..51560f6 100644 --- a/crates/contextforge-data-plane-lib/tests/gateway_plugins.rs +++ b/crates/contextforge-data-plane-lib/tests/gateway_plugins.rs @@ -416,7 +416,10 @@ async fn stateless_alias_and_namespaced_tool_names_route() { support::modern_client_info(), ) .await; - let alias = expected_tool_names.iter().find(|name| name.ends_with(".sum")).expect("sum alias is advertised"); + let alias = expected_tool_names + .iter() + .find(|name| std::path::Path::new(name).extension().is_some_and(|ext| ext.eq_ignore_ascii_case("sum"))) + .expect("sum alias is advertised"); let backend_port = alias .strip_prefix("backend-") .and_then(|name| name.strip_suffix(".sum")) From 3ab9fe3b9064447b35b9aaa0612e660c7b1024d6 Mon Sep 17 00:00:00 2001 From: cafalchio Date: Fri, 14 Aug 2026 14:27:33 +0100 Subject: [PATCH 10/17] Added stateless tool call Signed-off-by: cafalchio --- .../src/gateway/mcp_service/initialization.rs | 61 ------------------- .../tests/gateway_completions.rs | 2 + 2 files changed, 2 insertions(+), 61 deletions(-) diff --git a/crates/contextforge-data-plane-lib/src/gateway/mcp_service/initialization.rs b/crates/contextforge-data-plane-lib/src/gateway/mcp_service/initialization.rs index 4b86148..be9d619 100644 --- a/crates/contextforge-data-plane-lib/src/gateway/mcp_service/initialization.rs +++ b/crates/contextforge-data-plane-lib/src/gateway/mcp_service/initialization.rs @@ -197,67 +197,6 @@ fn merge_and_build_capabilities(server_capabilities: Vec<(String, Option( - mcp_service: &McpService, - backend_name: &str, - backend: &BackendMCPGateway, - namespace_identifiers: bool, - cx: &RequestContext, -) -> Result, ErrorData> -where - T: UserSessionStore + Send + Sync + 'static, -{ - let mut headers = HashMap::new(); - let downstream_headers = cx.extensions.get::().map(|parts| &parts.headers); - - if let Some(host) = backend.url.host_str() - && backend.url.scheme() == "https" - { - let authority = if let Some(port) = backend.url.port() { format!("{host}:{port}") } else { host.to_owned() }; - if let Ok(value) = http::HeaderValue::from_str(&authority) { - headers.insert(http::header::HOST, value); - } else { - warn!("connect_backend_for_request - invalid backend host backend_name = {backend_name}"); - } - } - - apply_header_config(&mut headers, backend, downstream_headers); - crate::telemetry::inject_current_context(&mut headers); - - let config = StreamableHttpClientTransportConfig::with_uri(backend.url.to_string()).custom_headers(headers); - let transport = StreamableHttpClientTransport::with_client(mcp_service.http_client.clone(), config); - let client_info = InitializeRequestParams::new( - ClientCapabilities::default(), - Implementation::new("contextforge-data-plane", env!("CARGO_PKG_VERSION")), - ) - .with_protocol_version(ProtocolVersion::V_2026_07_28); - - let backend_client = GatewayBackendClient::new( - backend_name.to_owned(), - namespace_identifiers, - client_info, - mcp_service.plugin_runtime.clone(), - ); - - serve_client_with_lifecycle_and_ct( - backend_client, - transport, - ClientLifecycleMode::Discover { preferred_versions: vec![ProtocolVersion::V_2026_07_28] }, - cx.ct.clone(), - ) - .await - .map_err(|error| { - warn!( - "connect_backend_for_request - backend connection failed backend_name = {backend_name} error = {error:?}" - ); - ErrorData { - code: ErrorCode::INTERNAL_ERROR, - message: "Routing problem... backend unavailable".into(), - data: None, - } - }) -} - /// Apply a backend's header config to the upstream header map. fn apply_header_config( headers: &mut HashMap, diff --git a/crates/contextforge-data-plane-lib/tests/gateway_completions.rs b/crates/contextforge-data-plane-lib/tests/gateway_completions.rs index 4cd2f68..db37558 100644 --- a/crates/contextforge-data-plane-lib/tests/gateway_completions.rs +++ b/crates/contextforge-data-plane-lib/tests/gateway_completions.rs @@ -10,6 +10,7 @@ use support::{ #[tokio::test(flavor = "multi_thread", worker_threads = 1)] #[test_log::test] +#[ignore = "2026-07-28 protocol transition"] async fn plaintext_completes_prompt_argument_through_prefixed_backend() -> Result<()> { let gateway_port = create_ports(1)[0]; let user = TEST_USER_ID; @@ -28,6 +29,7 @@ async fn plaintext_completes_prompt_argument_through_prefixed_backend() -> Resul #[tokio::test(flavor = "multi_thread", worker_threads = 1)] #[test_log::test] +#[ignore = "2026-07-28 protocol transition"] async fn plaintext_completes_resource_argument_through_prefixed_backend() -> Result<()> { let gateway_port = create_ports(1)[0]; let user = TEST_USER_ID; From ef45793d58f639693bef76d0b5d9b9e4b9208c77 Mon Sep 17 00:00:00 2001 From: cafalchio Date: Tue, 18 Aug 2026 13:10:03 +0100 Subject: [PATCH 11/17] Added cancellation token Signed-off-by: cafalchio --- .../src/gateway/mcp_service/initialization.rs | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/crates/contextforge-data-plane-lib/src/gateway/mcp_service/initialization.rs b/crates/contextforge-data-plane-lib/src/gateway/mcp_service/initialization.rs index be9d619..6f16551 100644 --- a/crates/contextforge-data-plane-lib/src/gateway/mcp_service/initialization.rs +++ b/crates/contextforge-data-plane-lib/src/gateway/mcp_service/initialization.rs @@ -3,12 +3,14 @@ use std::{collections::HashMap, sync::Arc}; use contextforge_data_plane_apis::user_store::BackendMCPGateway; use http::request::Parts; use rmcp::{ + ClientLifecycleMode, ErrorData, RoleClient, RoleServer, ServiceExt, ClientLifecycleMode, ErrorData, RoleClient, RoleServer, ServiceExt, model::{ ClientCapabilities, ErrorCode, Implementation, InitializeRequestParams, InitializeResult, ProtocolVersion, ServerCapabilities, }, service::serve_client_with_lifecycle_and_ct, + service::serve_client_with_lifecycle_and_ct, service::{RequestContext, RunningService}, transport::{StreamableHttpClientTransport, streamable_http_client::StreamableHttpClientTransportConfig}, }; @@ -197,6 +199,67 @@ fn merge_and_build_capabilities(server_capabilities: Vec<(String, Option( + mcp_service: &McpService, + backend_name: &str, + backend: &BackendMCPGateway, + namespace_identifiers: bool, + cx: &RequestContext, +) -> Result, ErrorData> +where + T: UserSessionStore + Send + Sync + 'static, +{ + let mut headers = HashMap::new(); + let downstream_headers = cx.extensions.get::().map(|parts| &parts.headers); + + if let Some(host) = backend.url.host_str() + && backend.url.scheme() == "https" + { + let authority = if let Some(port) = backend.url.port() { format!("{host}:{port}") } else { host.to_owned() }; + if let Ok(value) = http::HeaderValue::from_str(&authority) { + headers.insert(http::header::HOST, value); + } else { + warn!("connect_backend_for_request - invalid backend host backend_name = {backend_name}"); + } + } + + apply_header_config(&mut headers, backend, downstream_headers); + crate::telemetry::inject_current_context(&mut headers); + + let config = StreamableHttpClientTransportConfig::with_uri(backend.url.to_string()).custom_headers(headers); + let transport = StreamableHttpClientTransport::with_client(mcp_service.http_client.clone(), config); + let client_info = InitializeRequestParams::new( + ClientCapabilities::default(), + Implementation::new("contextforge-data-plane", env!("CARGO_PKG_VERSION")), + ) + .with_protocol_version(ProtocolVersion::V_2026_07_28); + + let backend_client = GatewayBackendClient::new( + backend_name.to_owned(), + namespace_identifiers, + client_info, + mcp_service.plugin_runtime.clone(), + ); + + serve_client_with_lifecycle_and_ct( + backend_client, + transport, + ClientLifecycleMode::Discover { preferred_versions: vec![ProtocolVersion::V_2026_07_28] }, + cx.ct.clone(), + ) + .await + .map_err(|error| { + warn!( + "connect_backend_for_request - backend connection failed backend_name = {backend_name} error = {error:?}" + ); + ErrorData { + code: ErrorCode::INTERNAL_ERROR, + message: "Routing problem... backend unavailable".into(), + data: None, + } + }) +} + /// Apply a backend's header config to the upstream header map. fn apply_header_config( headers: &mut HashMap, From b73a977549430e7060d6c7eeba7ba9fc9909e80e Mon Sep 17 00:00:00 2001 From: cafalchio Date: Tue, 18 Aug 2026 14:35:59 +0100 Subject: [PATCH 12/17] removed passing tests from ignore Signed-off-by: cafalchio --- tests/conformance/expected-failures.yml | 29 ++++++++++++++++++------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/tests/conformance/expected-failures.yml b/tests/conformance/expected-failures.yml index f39c373..415b7f7 100644 --- a/tests/conformance/expected-failures.yml +++ b/tests/conformance/expected-failures.yml @@ -1,7 +1,26 @@ # Generated by `make conformance-bless` from scored dataplane findings. # Pinned fixture findings are excluded; see upstream-fixture-failures.yml. server: - - caching:sep-2549-cache-scope-valid + - server-stateless:sep-2575-server-declares-prompts-in-discover + - server-stateless:sep-2575-discover-capabilities-match-handlers + - server-stateless:sep-2575-http-server-no-independent-requests-on-stream + - completion-complete:completion-complete + - tools-list:tools-list + # Dataplane-owned on main. The fixture passes these behavior checks itself; + # its separate wire-schema failures live in upstream-fixture-failures.yml. + - tools-call-with-progress:tools-call-with-progress + - resources-list:resources-list + - resources-read-text:resources-read-text + - resources-read-binary:resources-read-binary + - resources-templates-read:resources-templates-read + - sep-2164-resource-not-found:sep-2164-error-code + - sep-2164-resource-not-found:sep-2164-data-uri + - prompts-list:prompts-list + - prompts-get-simple:prompts-get-simple + - prompts-get-with-args:prompts-get-with-args + - prompts-get-embedded-resource:prompts-get-embedded-resource + - prompts-get-with-image:prompts-get-with-image + - caching:sep-2549-tools-list-caching-hints - caching:sep-2549-prompts-list-caching-hints - caching:sep-2549-resources-list-caching-hints - caching:sep-2549-resources-templates-list-caching-hints @@ -19,10 +38,4 @@ server: - input-required-result-request-state:sep-2322-request-state-incomplete - input-required-result-result-type:sep-2322-result-type-included - input-required-result-tampered-state:sep-2322-reject-tampered-state - - prompts-list:prompts-list - - resources-list:resources-list - - server-stateless:sep-2575-discover-capabilities-match-handlers - - server-stateless:sep-2575-http-server-no-independent-requests-on-stream - - server-stateless:sep-2575-server-declares-prompts-in-discover - - tools-call-with-progress:tools-call-with-progress - - tools-list:tools-list + - input-required-result-capability-check:sep-2322-respect-client-capabilities From 97b5c9f9391b3440f95b8571a716373f4b62e253 Mon Sep 17 00:00:00 2001 From: cafalchio Date: Wed, 19 Aug 2026 11:46:48 +0100 Subject: [PATCH 13/17] updated expected failures Signed-off-by: cafalchio --- tests/conformance/expected-failures.yml | 29 +++++++------------------ 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/tests/conformance/expected-failures.yml b/tests/conformance/expected-failures.yml index 415b7f7..f39c373 100644 --- a/tests/conformance/expected-failures.yml +++ b/tests/conformance/expected-failures.yml @@ -1,26 +1,7 @@ # Generated by `make conformance-bless` from scored dataplane findings. # Pinned fixture findings are excluded; see upstream-fixture-failures.yml. server: - - server-stateless:sep-2575-server-declares-prompts-in-discover - - server-stateless:sep-2575-discover-capabilities-match-handlers - - server-stateless:sep-2575-http-server-no-independent-requests-on-stream - - completion-complete:completion-complete - - tools-list:tools-list - # Dataplane-owned on main. The fixture passes these behavior checks itself; - # its separate wire-schema failures live in upstream-fixture-failures.yml. - - tools-call-with-progress:tools-call-with-progress - - resources-list:resources-list - - resources-read-text:resources-read-text - - resources-read-binary:resources-read-binary - - resources-templates-read:resources-templates-read - - sep-2164-resource-not-found:sep-2164-error-code - - sep-2164-resource-not-found:sep-2164-data-uri - - prompts-list:prompts-list - - prompts-get-simple:prompts-get-simple - - prompts-get-with-args:prompts-get-with-args - - prompts-get-embedded-resource:prompts-get-embedded-resource - - prompts-get-with-image:prompts-get-with-image - - caching:sep-2549-tools-list-caching-hints + - caching:sep-2549-cache-scope-valid - caching:sep-2549-prompts-list-caching-hints - caching:sep-2549-resources-list-caching-hints - caching:sep-2549-resources-templates-list-caching-hints @@ -38,4 +19,10 @@ server: - input-required-result-request-state:sep-2322-request-state-incomplete - input-required-result-result-type:sep-2322-result-type-included - input-required-result-tampered-state:sep-2322-reject-tampered-state - - input-required-result-capability-check:sep-2322-respect-client-capabilities + - prompts-list:prompts-list + - resources-list:resources-list + - server-stateless:sep-2575-discover-capabilities-match-handlers + - server-stateless:sep-2575-http-server-no-independent-requests-on-stream + - server-stateless:sep-2575-server-declares-prompts-in-discover + - tools-call-with-progress:tools-call-with-progress + - tools-list:tools-list From dbbd3698baa8f83e85a6774f8c316a65eded96bd Mon Sep 17 00:00:00 2001 From: cafalchio Date: Wed, 19 Aug 2026 12:02:13 +0100 Subject: [PATCH 14/17] Fix secrets Signed-off-by: cafalchio --- .secrets.baseline | 120 +++++++++++++++++++++++++++++++++------------- 1 file changed, 88 insertions(+), 32 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index 38b0975..497ff6e 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -1,9 +1,9 @@ { "exclude": { - "files": "Cargo\\.lock$|\\.lock$|target/|^.secrets.baseline$", + "files": "(?x)(Cargo\\.lock$|\\.lock$)|^\\.secrets\\.baseline$", "lines": null }, - "generated_at": "2026-08-19T10:02:56Z", + "generated_at": "2026-08-19T10:59:12Z", "plugins_used": [ { "name": "AWSKeyDetector" @@ -80,7 +80,6 @@ "assets/contextforgeCA/contextforge-client.key.pem": [ { "hashed_secret": "1348b145fa1a555461c1b790a2f66614781091e9", - "is_secret": false, "is_verified": false, "line_number": 1, "type": "Private Key", @@ -90,7 +89,6 @@ "assets/contextforgeCA/contextforge-server.key.pem": [ { "hashed_secret": "1348b145fa1a555461c1b790a2f66614781091e9", - "is_secret": false, "is_verified": false, "line_number": 1, "type": "Private Key", @@ -100,7 +98,6 @@ "assets/contextforgeCA/contextforge.ca.key.pem": [ { "hashed_secret": "1348b145fa1a555461c1b790a2f66614781091e9", - "is_secret": false, "is_verified": false, "line_number": 1, "type": "Private Key", @@ -110,7 +107,6 @@ "assets/contextforgeCA/contextforge.intermediate.key.pem": [ { "hashed_secret": "1348b145fa1a555461c1b790a2f66614781091e9", - "is_secret": false, "is_verified": false, "line_number": 1, "type": "Private Key", @@ -120,7 +116,6 @@ "assets/jwt.key": [ { "hashed_secret": "be4fc4886bd949b369d5e092eb87494f12e57e5b", - "is_secret": false, "is_verified": false, "line_number": 1, "type": "Private Key", @@ -130,7 +125,6 @@ "assets/tls_key.pem": [ { "hashed_secret": "1348b145fa1a555461c1b790a2f66614781091e9", - "is_secret": false, "is_verified": false, "line_number": 1, "type": "Private Key", @@ -140,7 +134,6 @@ "crates/contextforge-data-plane-lib/src/common.rs": [ { "hashed_secret": "4a4645604f0b9e29503be96a87f6f47a6e4a7890", - "is_secret": false, "is_verified": false, "line_number": 154, "type": "Secret Keyword", @@ -148,7 +141,6 @@ }, { "hashed_secret": "427f5e1b530d4a544883308d876a11d724060c86", - "is_secret": false, "is_verified": false, "line_number": 157, "type": "Secret Keyword", @@ -156,7 +148,6 @@ }, { "hashed_secret": "bfc6000db1195a9522813fc405c666dd4ce669ad", - "is_secret": false, "is_verified": false, "line_number": 263, "type": "Secret Keyword", @@ -166,7 +157,6 @@ "crates/contextforge-data-plane-lib/src/telemetry.rs": [ { "hashed_secret": "0a24796d4c71ce722a92f450f69dc36c60b21de4", - "is_secret": false, "is_verified": false, "line_number": 87, "type": "Hex High Entropy String", @@ -176,7 +166,6 @@ "crates/contextforge-data-plane-lib/tests/support/client.rs": [ { "hashed_secret": "a453c8b2640819a451ce875ac1e04d0dbab7b403", - "is_secret": false, "is_verified": false, "line_number": 12, "type": "Secret Keyword", @@ -186,7 +175,6 @@ "crates/contextforge-data-plane-lib/tests/support/mod.rs": [ { "hashed_secret": "a453c8b2640819a451ce875ac1e04d0dbab7b403", - "is_secret": false, "is_verified": false, "line_number": 17, "type": "Secret Keyword", @@ -196,7 +184,6 @@ "crates/contextforge-data-plane/Cargo.toml": [ { "hashed_secret": "58e7dc38ba3a7d4a720006d2f3cc4cda774d89dc", - "is_secret": false, "is_verified": false, "line_number": 19, "type": "Hex High Entropy String", @@ -206,17 +193,101 @@ "crates/plugins/cpex-secrets-detection/src/lib.rs": [ { "hashed_secret": "86de8c52637ec530fe39b0a8471da9b8764d5242", - "is_secret": false, "is_verified": false, "line_number": 609, "type": "AWS Access Key", "verified_result": null } ], + "crates/plugins/cpex-secrets-detection/src/scanner.rs": [ + { + "hashed_secret": "9249e2590f5d19742260cb5296cb76fe0677f147", + "is_verified": false, + "line_number": 238, + "type": "Secret Keyword", + "verified_result": null + }, + { + "hashed_secret": "199da8f71b7dced64f82cf6e96483134cace9b14", + "is_verified": false, + "line_number": 239, + "type": "Secret Keyword", + "verified_result": null + }, + { + "hashed_secret": "c0026c4c848882618c987859077ffbae92130625", + "is_verified": false, + "line_number": 242, + "type": "Secret Keyword", + "verified_result": null + }, + { + "hashed_secret": "078553dc10635837abb80f404302c70cba91b879", + "is_verified": false, + "line_number": 278, + "type": "Base64 High Entropy String", + "verified_result": null + }, + { + "hashed_secret": "e175c6f5f2a92e8623bd9a4820edb4e8c1b0fd10", + "is_verified": false, + "line_number": 278, + "type": "GitHub Token", + "verified_result": null + }, + { + "hashed_secret": "97d99a51e5ac827bb36fe6273facfda35245917a", + "is_verified": false, + "line_number": 279, + "type": "Base64 High Entropy String", + "verified_result": null + }, + { + "hashed_secret": "be4fc4886bd949b369d5e092eb87494f12e57e5b", + "is_verified": false, + "line_number": 282, + "type": "Private Key", + "verified_result": null + }, + { + "hashed_secret": "b1775a785f09a6ebaf2dc33d6eaeb98974d9cdb8", + "is_verified": false, + "line_number": 284, + "type": "Hex High Entropy String", + "verified_result": null + }, + { + "hashed_secret": "eae9124e42e2ef05ba727bd1a1c0c6fa61a05b9e", + "is_verified": false, + "line_number": 302, + "type": "Secret Keyword", + "verified_result": null + }, + { + "hashed_secret": "86de8c52637ec530fe39b0a8471da9b8764d5242", + "is_verified": false, + "line_number": 401, + "type": "AWS Access Key", + "verified_result": null + }, + { + "hashed_secret": "9d7235fe33b6612ed7ebca4b63afd00d4adf5d66", + "is_verified": false, + "line_number": 410, + "type": "Secret Keyword", + "verified_result": null + }, + { + "hashed_secret": "27a39044bff80a4c196689dfa8dcf129cb27fef8", + "is_verified": false, + "line_number": 431, + "type": "Base64 High Entropy String", + "verified_result": null + } + ], "crates/plugins/cpex-secrets-detection/tests/plugin_manager.rs": [ { "hashed_secret": "436da7d4d22c39c0165ab0d5b40073d0f2fc11c5", - "is_secret": false, "is_verified": false, "line_number": 197, "type": "AWS Access Key", @@ -224,7 +295,6 @@ }, { "hashed_secret": "8b4510a576d82f38bd2730436bf5e20c4e15b30e", - "is_secret": false, "is_verified": false, "line_number": 198, "type": "AWS Access Key", @@ -232,7 +302,6 @@ }, { "hashed_secret": "e4ea017859bcad962c8ab551fe29da9147877eee", - "is_secret": false, "is_verified": false, "line_number": 199, "type": "AWS Access Key", @@ -240,7 +309,6 @@ }, { "hashed_secret": "86de8c52637ec530fe39b0a8471da9b8764d5242", - "is_secret": false, "is_verified": false, "line_number": 268, "type": "AWS Access Key", @@ -250,7 +318,6 @@ "docker/docker-compose-langfuse.yaml": [ { "hashed_secret": "cb1fde0682fbd1ac0faf2a9f297167ac9d06434b", - "is_secret": false, "is_verified": false, "line_number": 16, "type": "Secret Keyword", @@ -258,7 +325,6 @@ }, { "hashed_secret": "cb58df830a45cc33df1a313e616ecad78cd796c5", - "is_secret": false, "is_verified": false, "line_number": 77, "type": "Secret Keyword", @@ -266,7 +332,6 @@ }, { "hashed_secret": "2e0c522bfe4e7885492862df2e0b987c0ca02623", - "is_secret": false, "is_verified": false, "line_number": 100, "type": "Secret Keyword", @@ -274,7 +339,6 @@ }, { "hashed_secret": "d9d007c8de197b3f36a3a0ba4f13c0f7df175d5a", - "is_secret": false, "is_verified": false, "line_number": 255, "type": "Secret Keyword", @@ -284,7 +348,6 @@ "docker/docker-compose.yml": [ { "hashed_secret": "2a8bfc0ce436d55ca907d0162989481bcb7677b4", - "is_secret": false, "is_verified": false, "line_number": 189, "type": "Secret Keyword", @@ -292,7 +355,6 @@ }, { "hashed_secret": "fdda45b7f6d2ead95d9991fc4678640c3bab0d84", - "is_secret": false, "is_verified": false, "line_number": 363, "type": "Secret Keyword", @@ -300,7 +362,6 @@ }, { "hashed_secret": "093d378410a5cfa4bd5088f3fef62fbdb8a95665", - "is_secret": false, "is_verified": false, "line_number": 369, "type": "Secret Keyword", @@ -308,7 +369,6 @@ }, { "hashed_secret": "c3de40d5e3fc71ed62771c2127a8e42585026c97", - "is_secret": false, "is_verified": false, "line_number": 371, "type": "Secret Keyword", @@ -316,7 +376,6 @@ }, { "hashed_secret": "4d4acd9b084d13f5fdb23807d857e1c48a1cfd0f", - "is_secret": false, "is_verified": false, "line_number": 460, "type": "Secret Keyword", @@ -324,7 +383,6 @@ }, { "hashed_secret": "bd0160c2cf35d950843c88f3be2b9412ed71f485", - "is_secret": false, "is_verified": false, "line_number": 495, "type": "Secret Keyword", @@ -332,7 +390,6 @@ }, { "hashed_secret": "293324f6824bb3a6db5c4dc42a60ddd4a9851c99", - "is_secret": false, "is_verified": false, "line_number": 658, "type": "Hex High Entropy String", @@ -342,7 +399,6 @@ "scripts/git/resolve-secrets-baseline-conflict.sh": [ { "hashed_secret": "44ffd1bfb94772d5f91d528e7aca703990edbbd7", - "is_secret": false, "is_verified": false, "line_number": 31, "type": "Secret Keyword", From 0f87d3040b22366d16e0213d726e95fd58aed34f Mon Sep 17 00:00:00 2001 From: cafalchio Date: Wed, 19 Aug 2026 13:37:49 +0100 Subject: [PATCH 15/17] fixed secrets Signed-off-by: cafalchio --- .secrets.baseline | 210 +++++++++++++++++++++++++++------------------- 1 file changed, 126 insertions(+), 84 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index 497ff6e..01d7538 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -80,329 +80,371 @@ "assets/contextforgeCA/contextforge-client.key.pem": [ { "hashed_secret": "1348b145fa1a555461c1b790a2f66614781091e9", - "is_verified": false, + "is_verified": true, "line_number": 1, "type": "Private Key", - "verified_result": null + "verified_result": true, + "is_secret": false } ], "assets/contextforgeCA/contextforge-server.key.pem": [ { "hashed_secret": "1348b145fa1a555461c1b790a2f66614781091e9", - "is_verified": false, + "is_verified": true, "line_number": 1, "type": "Private Key", - "verified_result": null + "verified_result": true, + "is_secret": false } ], "assets/contextforgeCA/contextforge.ca.key.pem": [ { "hashed_secret": "1348b145fa1a555461c1b790a2f66614781091e9", - "is_verified": false, + "is_verified": true, "line_number": 1, "type": "Private Key", - "verified_result": null + "verified_result": true, + "is_secret": false } ], "assets/contextforgeCA/contextforge.intermediate.key.pem": [ { "hashed_secret": "1348b145fa1a555461c1b790a2f66614781091e9", - "is_verified": false, + "is_verified": true, "line_number": 1, "type": "Private Key", - "verified_result": null + "verified_result": true, + "is_secret": false } ], "assets/jwt.key": [ { "hashed_secret": "be4fc4886bd949b369d5e092eb87494f12e57e5b", - "is_verified": false, + "is_verified": true, "line_number": 1, "type": "Private Key", - "verified_result": null + "verified_result": true, + "is_secret": false } ], "assets/tls_key.pem": [ { "hashed_secret": "1348b145fa1a555461c1b790a2f66614781091e9", - "is_verified": false, + "is_verified": true, "line_number": 1, "type": "Private Key", - "verified_result": null + "verified_result": true, + "is_secret": false } ], "crates/contextforge-data-plane-lib/src/common.rs": [ { "hashed_secret": "4a4645604f0b9e29503be96a87f6f47a6e4a7890", - "is_verified": false, + "is_verified": true, "line_number": 154, "type": "Secret Keyword", - "verified_result": null + "verified_result": true, + "is_secret": false }, { "hashed_secret": "427f5e1b530d4a544883308d876a11d724060c86", - "is_verified": false, + "is_verified": true, "line_number": 157, "type": "Secret Keyword", - "verified_result": null + "verified_result": true, + "is_secret": false }, { "hashed_secret": "bfc6000db1195a9522813fc405c666dd4ce669ad", - "is_verified": false, + "is_verified": true, "line_number": 263, "type": "Secret Keyword", - "verified_result": null + "verified_result": true, + "is_secret": false } ], "crates/contextforge-data-plane-lib/src/telemetry.rs": [ { "hashed_secret": "0a24796d4c71ce722a92f450f69dc36c60b21de4", - "is_verified": false, + "is_verified": true, "line_number": 87, "type": "Hex High Entropy String", - "verified_result": null + "verified_result": true, + "is_secret": false } ], "crates/contextforge-data-plane-lib/tests/support/client.rs": [ { "hashed_secret": "a453c8b2640819a451ce875ac1e04d0dbab7b403", - "is_verified": false, + "is_verified": true, "line_number": 12, "type": "Secret Keyword", - "verified_result": null + "verified_result": true, + "is_secret": false } ], "crates/contextforge-data-plane-lib/tests/support/mod.rs": [ { "hashed_secret": "a453c8b2640819a451ce875ac1e04d0dbab7b403", - "is_verified": false, + "is_verified": true, "line_number": 17, "type": "Secret Keyword", - "verified_result": null + "verified_result": true, + "is_secret": false } ], "crates/contextforge-data-plane/Cargo.toml": [ { "hashed_secret": "58e7dc38ba3a7d4a720006d2f3cc4cda774d89dc", - "is_verified": false, + "is_verified": true, "line_number": 19, "type": "Hex High Entropy String", - "verified_result": null + "verified_result": true, + "is_secret": false } ], "crates/plugins/cpex-secrets-detection/src/lib.rs": [ { "hashed_secret": "86de8c52637ec530fe39b0a8471da9b8764d5242", - "is_verified": false, + "is_verified": true, "line_number": 609, "type": "AWS Access Key", - "verified_result": null + "verified_result": true, + "is_secret": false } ], "crates/plugins/cpex-secrets-detection/src/scanner.rs": [ { "hashed_secret": "9249e2590f5d19742260cb5296cb76fe0677f147", - "is_verified": false, + "is_verified": true, "line_number": 238, "type": "Secret Keyword", - "verified_result": null + "verified_result": true, + "is_secret": false }, { "hashed_secret": "199da8f71b7dced64f82cf6e96483134cace9b14", - "is_verified": false, + "is_verified": true, "line_number": 239, "type": "Secret Keyword", - "verified_result": null + "verified_result": true, + "is_secret": false }, { "hashed_secret": "c0026c4c848882618c987859077ffbae92130625", - "is_verified": false, + "is_verified": true, "line_number": 242, "type": "Secret Keyword", - "verified_result": null + "verified_result": true, + "is_secret": false }, { "hashed_secret": "078553dc10635837abb80f404302c70cba91b879", - "is_verified": false, + "is_verified": true, "line_number": 278, "type": "Base64 High Entropy String", - "verified_result": null + "verified_result": true, + "is_secret": false }, { "hashed_secret": "e175c6f5f2a92e8623bd9a4820edb4e8c1b0fd10", - "is_verified": false, + "is_verified": true, "line_number": 278, "type": "GitHub Token", - "verified_result": null + "verified_result": true, + "is_secret": false }, { "hashed_secret": "97d99a51e5ac827bb36fe6273facfda35245917a", - "is_verified": false, + "is_verified": true, "line_number": 279, "type": "Base64 High Entropy String", - "verified_result": null + "verified_result": true, + "is_secret": false }, { "hashed_secret": "be4fc4886bd949b369d5e092eb87494f12e57e5b", - "is_verified": false, + "is_verified": true, "line_number": 282, "type": "Private Key", - "verified_result": null + "verified_result": true, + "is_secret": false }, { "hashed_secret": "b1775a785f09a6ebaf2dc33d6eaeb98974d9cdb8", - "is_verified": false, + "is_verified": true, "line_number": 284, "type": "Hex High Entropy String", - "verified_result": null + "verified_result": true, + "is_secret": false }, { "hashed_secret": "eae9124e42e2ef05ba727bd1a1c0c6fa61a05b9e", - "is_verified": false, + "is_verified": true, "line_number": 302, "type": "Secret Keyword", - "verified_result": null + "verified_result": true, + "is_secret": false }, { "hashed_secret": "86de8c52637ec530fe39b0a8471da9b8764d5242", - "is_verified": false, + "is_verified": true, "line_number": 401, "type": "AWS Access Key", - "verified_result": null + "verified_result": true, + "is_secret": false }, { "hashed_secret": "9d7235fe33b6612ed7ebca4b63afd00d4adf5d66", - "is_verified": false, + "is_verified": true, "line_number": 410, "type": "Secret Keyword", - "verified_result": null + "verified_result": true, + "is_secret": false }, { "hashed_secret": "27a39044bff80a4c196689dfa8dcf129cb27fef8", - "is_verified": false, + "is_verified": true, "line_number": 431, "type": "Base64 High Entropy String", - "verified_result": null + "verified_result": true, + "is_secret": false } ], "crates/plugins/cpex-secrets-detection/tests/plugin_manager.rs": [ { "hashed_secret": "436da7d4d22c39c0165ab0d5b40073d0f2fc11c5", - "is_verified": false, + "is_verified": true, "line_number": 197, "type": "AWS Access Key", - "verified_result": null + "verified_result": true, + "is_secret": false }, { "hashed_secret": "8b4510a576d82f38bd2730436bf5e20c4e15b30e", - "is_verified": false, + "is_verified": true, "line_number": 198, "type": "AWS Access Key", - "verified_result": null + "verified_result": true, + "is_secret": false }, { "hashed_secret": "e4ea017859bcad962c8ab551fe29da9147877eee", - "is_verified": false, + "is_verified": true, "line_number": 199, "type": "AWS Access Key", - "verified_result": null + "verified_result": true, + "is_secret": false }, { "hashed_secret": "86de8c52637ec530fe39b0a8471da9b8764d5242", - "is_verified": false, + "is_verified": true, "line_number": 268, "type": "AWS Access Key", - "verified_result": null + "verified_result": true, + "is_secret": false } ], "docker/docker-compose-langfuse.yaml": [ { "hashed_secret": "cb1fde0682fbd1ac0faf2a9f297167ac9d06434b", - "is_verified": false, + "is_verified": true, "line_number": 16, "type": "Secret Keyword", - "verified_result": null + "verified_result": true, + "is_secret": false }, { "hashed_secret": "cb58df830a45cc33df1a313e616ecad78cd796c5", - "is_verified": false, + "is_verified": true, "line_number": 77, "type": "Secret Keyword", - "verified_result": null + "verified_result": true, + "is_secret": false }, { "hashed_secret": "2e0c522bfe4e7885492862df2e0b987c0ca02623", - "is_verified": false, + "is_verified": true, "line_number": 100, "type": "Secret Keyword", - "verified_result": null + "verified_result": true, + "is_secret": false }, { "hashed_secret": "d9d007c8de197b3f36a3a0ba4f13c0f7df175d5a", - "is_verified": false, + "is_verified": true, "line_number": 255, "type": "Secret Keyword", - "verified_result": null + "verified_result": true, + "is_secret": false } ], "docker/docker-compose.yml": [ { "hashed_secret": "2a8bfc0ce436d55ca907d0162989481bcb7677b4", - "is_verified": false, + "is_verified": true, "line_number": 189, "type": "Secret Keyword", - "verified_result": null + "verified_result": true, + "is_secret": false }, { "hashed_secret": "fdda45b7f6d2ead95d9991fc4678640c3bab0d84", - "is_verified": false, + "is_verified": true, "line_number": 363, "type": "Secret Keyword", - "verified_result": null + "verified_result": true, + "is_secret": false }, { "hashed_secret": "093d378410a5cfa4bd5088f3fef62fbdb8a95665", - "is_verified": false, + "is_verified": true, "line_number": 369, "type": "Secret Keyword", - "verified_result": null + "verified_result": true, + "is_secret": false }, { "hashed_secret": "c3de40d5e3fc71ed62771c2127a8e42585026c97", - "is_verified": false, + "is_verified": true, "line_number": 371, "type": "Secret Keyword", - "verified_result": null + "verified_result": true, + "is_secret": false }, { "hashed_secret": "4d4acd9b084d13f5fdb23807d857e1c48a1cfd0f", - "is_verified": false, + "is_verified": true, "line_number": 460, "type": "Secret Keyword", - "verified_result": null + "verified_result": true, + "is_secret": false }, { "hashed_secret": "bd0160c2cf35d950843c88f3be2b9412ed71f485", - "is_verified": false, + "is_verified": true, "line_number": 495, "type": "Secret Keyword", - "verified_result": null + "verified_result": true, + "is_secret": false }, { "hashed_secret": "293324f6824bb3a6db5c4dc42a60ddd4a9851c99", - "is_verified": false, + "is_verified": true, "line_number": 658, "type": "Hex High Entropy String", - "verified_result": null + "verified_result": true, + "is_secret": false } ], "scripts/git/resolve-secrets-baseline-conflict.sh": [ { "hashed_secret": "44ffd1bfb94772d5f91d528e7aca703990edbbd7", - "is_verified": false, + "is_verified": true, "line_number": 31, "type": "Secret Keyword", - "verified_result": null + "verified_result": true, + "is_secret": false } ] }, From 9ff324f0f74db6ccae07197e5ada84128cec6208 Mon Sep 17 00:00:00 2001 From: cafalchio Date: Wed, 19 Aug 2026 16:16:18 +0100 Subject: [PATCH 16/17] Added deterministic close Signed-off-by: cafalchio --- .../src/gateway/mcp_service/prompts.rs | 11 ++++++----- .../src/gateway/mcp_service/resources.rs | 12 +++++++----- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/crates/contextforge-data-plane-lib/src/gateway/mcp_service/prompts.rs b/crates/contextforge-data-plane-lib/src/gateway/mcp_service/prompts.rs index 0c28e43..1c3d0a6 100644 --- a/crates/contextforge-data-plane-lib/src/gateway/mcp_service/prompts.rs +++ b/crates/contextforge-data-plane-lib/src/gateway/mcp_service/prompts.rs @@ -99,14 +99,15 @@ where } else { PromptPreFetchResult::unchanged() }; - let backend_service = + let mut backend_service = connect_backend_for_request(mcp_service, &backend_name, backend, virtual_host.backends.len() > 1, &cx).await?; let mut routed_request = request; pre_result.arguments.apply_to_request(&mut routed_request, &prompt_name); - let response = backend_service - .get_prompt(routed_request) - .await - .map_err(|error| backend_forward_error("get_prompt", &service_name, &error))?; + let response = backend_service.get_prompt(routed_request).await; + if let Err(error) = backend_service.close().await { + tracing::warn!("get_prompt: backend cleanup failed backend_name = {service_name} error = {error:?}"); + } + let response = response.map_err(|error| backend_forward_error("get_prompt", &service_name, &error))?; info!("get_prompt: backend {service_name} returned {} messages", response.messages.len()); let response = if let Some(plugin_runtime) = &mcp_service.plugin_runtime { plugin_runtime.after_get_prompt(&prompt_name, response, pre_result.state).await? diff --git a/crates/contextforge-data-plane-lib/src/gateway/mcp_service/resources.rs b/crates/contextforge-data-plane-lib/src/gateway/mcp_service/resources.rs index 8de861a..504cad6 100644 --- a/crates/contextforge-data-plane-lib/src/gateway/mcp_service/resources.rs +++ b/crates/contextforge-data-plane-lib/src/gateway/mcp_service/resources.rs @@ -96,16 +96,18 @@ where })?; let service_name = backend_name.clone(); - let backend_service = + let mut backend_service = connect_backend_for_request(mcp_service, &backend_name, backend, virtual_host.backends.len() > 1, &cx).await?; let mut routed_request = request; routed_request.uri = resource_uri; - let response = backend_service - .read_resource(routed_request) - .await - .map_err(|error| backend_forward_error("read_resource", &service_name, &error))?; + let response = backend_service.read_resource(routed_request).await; + if let Err(error) = backend_service.close().await { + tracing::warn!("read_resource: backend cleanup failed backend_name = {service_name} error = {error:?}"); + } + let response = response.map_err(|error| backend_forward_error("read_resource", &service_name, &error))?; + info!("read_resource: backend {service_name} returned {} contents", response.contents.len()); Ok(response.into()) From 9204ea99f17e03c7f67faa63008e42f395133ee1 Mon Sep 17 00:00:00 2001 From: cafalchio Date: Thu, 20 Aug 2026 10:31:11 +0100 Subject: [PATCH 17/17] merge rebase Signed-off-by: cafalchio --- .../src/gateway/mcp_service/initialization.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/contextforge-data-plane-lib/src/gateway/mcp_service/initialization.rs b/crates/contextforge-data-plane-lib/src/gateway/mcp_service/initialization.rs index 6f16551..4b86148 100644 --- a/crates/contextforge-data-plane-lib/src/gateway/mcp_service/initialization.rs +++ b/crates/contextforge-data-plane-lib/src/gateway/mcp_service/initialization.rs @@ -3,14 +3,12 @@ use std::{collections::HashMap, sync::Arc}; use contextforge_data_plane_apis::user_store::BackendMCPGateway; use http::request::Parts; use rmcp::{ - ClientLifecycleMode, ErrorData, RoleClient, RoleServer, ServiceExt, ClientLifecycleMode, ErrorData, RoleClient, RoleServer, ServiceExt, model::{ ClientCapabilities, ErrorCode, Implementation, InitializeRequestParams, InitializeResult, ProtocolVersion, ServerCapabilities, }, service::serve_client_with_lifecycle_and_ct, - service::serve_client_with_lifecycle_and_ct, service::{RequestContext, RunningService}, transport::{StreamableHttpClientTransport, streamable_http_client::StreamableHttpClientTransportConfig}, };