From 8a910d5e2646fcdcb5af7d48e29f435df0867f67 Mon Sep 17 00:00:00 2001 From: cafalchio Date: Sat, 29 Aug 2026 20:04:38 +0100 Subject: [PATCH 01/20] Added VirtualHost mapping Signed-off-by: cafalchio --- crates/contextforge-data-plane-apis/src/user_store.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/contextforge-data-plane-apis/src/user_store.rs b/crates/contextforge-data-plane-apis/src/user_store.rs index 3712765..e9d1921 100644 --- a/crates/contextforge-data-plane-apis/src/user_store.rs +++ b/crates/contextforge-data-plane-apis/src/user_store.rs @@ -74,6 +74,9 @@ pub struct BackendMCPGateway { #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] pub struct VirtualHost { pub backends: HashMap, + pub tools: HashMap, + pub resources: HashMap, + pub prompts: HashMap, } #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] From 8227d1df21c34e640440f828456de09274128930 Mon Sep 17 00:00:00 2001 From: cafalchio Date: Sat, 29 Aug 2026 20:07:28 +0100 Subject: [PATCH 02/20] Changed call_tool to use mapping Signed-off-by: cafalchio --- .../src/gateway/mcp_service/tools.rs | 39 +++++++------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/crates/contextforge-data-plane-lib/src/gateway/mcp_service/tools.rs b/crates/contextforge-data-plane-lib/src/gateway/mcp_service/tools.rs index d820a33..2935785 100644 --- a/crates/contextforge-data-plane-lib/src/gateway/mcp_service/tools.rs +++ b/crates/contextforge-data-plane-lib/src/gateway/mcp_service/tools.rs @@ -9,10 +9,8 @@ use tracing::{info, warn}; use super::McpService; use crate::gateway::{ - backend_client::call_backend_tool, - identifier_routing::{backend_forward_error, resolve_tool_route}, - mcp_call_validator::AuthorizedCallValidator, - mcp_service::initialization::connect_backend_for_request, + backend_client::call_backend_tool, identifier_routing::backend_forward_error, + mcp_call_validator::AuthorizedCallValidator, mcp_service::initialization::connect_backend_for_request, }; use crate::mcp_standard_headers; @@ -23,14 +21,9 @@ pub(super) async fn call_tool( ) -> Result { let mcp_call_validator = AuthorizedCallValidator::new("call_tool", &cx); 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, tool_name)) = - resolve_tool_route(virtual_host, &request.name, &backend_names).map_err(|e| ErrorData { - code: ErrorCode::INVALID_PARAMS, - message: format!("Routing problem... {e}").into(), - data: None, - })? - else { + + let dowstream_name = request.name.to_string(); + let Some((backend_name, tool_name)) = virtual_host.tools.get(&dowstream_name) else { return Err(ErrorData { code: ErrorCode::INVALID_PARAMS, message: "Routing problem... tool not found".into(), @@ -38,9 +31,7 @@ pub(super) async fn call_tool( }); }; - let backend_name = backend_name.to_owned(); - let tool_name = tool_name.to_owned(); - let backend = virtual_host.backends.get(&backend_name).ok_or_else(|| ErrorData { + 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, @@ -52,23 +43,23 @@ pub(super) async fn call_tool( .get::() .map(|parts| &parts.headers) .ok_or_else(|| ErrorData::internal_error("Routing problem... request headers not found", None))?; - let tool_schema = backend.tool_schemas.get(&tool_name).ok_or_else(|| { + let tool_schema = backend.tool_schemas.get(tool_name).ok_or_else(|| { ErrorData::header_mismatch(format!("Missing published schema for tool '{tool_name}'"), None) })?; mcp_standard_headers::validate_tool_params(downstream_headers, request.arguments.as_ref(), tool_schema) .map_err(|message| ErrorData::header_mismatch(message, None))?; } - let service_name = backend_name.clone(); + let backend_name = backend_name.clone(); let pre_result = if let Some(plugin_runtime) = &mcp_service.plugin_runtime { - plugin_runtime.before_tool_call(&request, &tool_name, &service_name).await? + plugin_runtime.before_tool_call(&request, tool_name, &backend_name).await? } else { ToolPreCallResult::unchanged() }; let mut backend_service = connect_backend_for_request(mcp_service, &backend_name, backend, &cx).await?; let post_state = pre_result.state; let mut routed_request = request; - pre_result.arguments.apply_to_request(&mut routed_request, &tool_name); + pre_result.arguments.apply_to_request(&mut routed_request, tool_name); let progress_token = cx.meta.get_progress_token(); let handle = backend_service @@ -82,21 +73,21 @@ pub(super) async fn call_tool( post_state.clone(), ) .await - .map_err(|error| backend_forward_error("call_tool", &service_name, &error))?; + .map_err(|error| backend_forward_error("call_tool", &backend_name, &error))?; let backend_progress_token = handle.progress_token.clone(); let response = call_backend_tool(handle, cx.ct.clone()).await; backend_service.service().stop_tracking_tool_call(&backend_progress_token).await; if let Err(error) = backend_service.close().await { - warn!("call_tool: backend cleanup failed backend_name = {service_name} error = {error:?}"); + warn!("call_tool: backend cleanup failed backend_name = {backend_name} error = {error:?}"); } - let response = response.map_err(|error| backend_forward_error("call_tool", &service_name, &error))?; + let response = response.map_err(|error| backend_forward_error("call_tool", &backend_name, &error))?; let response = match (&mcp_service.plugin_runtime, post_state) { (Some(plugin_runtime), Some(post_state)) => { - plugin_runtime.after_tool_call(&tool_name, response, Some(post_state)).await? + plugin_runtime.after_tool_call(tool_name, response, Some(post_state)).await? }, _ => response, }; - info!("call_tool: backend {service_name} completed"); + info!("call_tool: backend {backend_name} completed"); Ok(response.into()) } From 1477bc6830f8d7f139663355d997273441b10fd5 Mon Sep 17 00:00:00 2001 From: cafalchio Date: Sat, 29 Aug 2026 20:07:57 +0100 Subject: [PATCH 03/20] Changed prompts and resources to use mapping Signed-off-by: cafalchio --- .../src/gateway/mcp_service/prompts.rs | 21 ++++--------- .../src/gateway/mcp_service/resources.rs | 31 ++++++------------- 2 files changed, 16 insertions(+), 36 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 9e7219f..4edc492 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 @@ -8,8 +8,7 @@ use tracing::info; use super::McpService; use crate::gateway::{ - identifier_routing::{backend_forward_error, resolve_prompt_route}, - mcp_call_validator::AuthorizedCallValidator, + identifier_routing::backend_forward_error, mcp_call_validator::AuthorizedCallValidator, mcp_service::initialization::connect_backend_for_request, }; @@ -20,14 +19,7 @@ pub(super) async fn get_prompt( ) -> Result { let mcp_call_validator = AuthorizedCallValidator::new("get_prompt", &cx); 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_prompt_route(virtual_host, &request.name, &backend_names).map_err(|e| ErrorData { - code: ErrorCode::INVALID_PARAMS, - message: format!("Routing problem... {e}").into(), - data: None, - })? - else { + let Some((backend_name, prompt_name)) = virtual_host.prompts.get(&request.name) else { return Err(ErrorData { code: ErrorCode::INVALID_PARAMS, message: "Routing problem... prompt not found".into(), @@ -43,9 +35,8 @@ pub(super) async fn get_prompt( message: "Routing problem... backend not found".into(), data: None, })?; - let service_name = backend_name.clone(); let pre_result = if let Some(plugin_runtime) = &mcp_service.plugin_runtime { - plugin_runtime.before_get_prompt(&request, &prompt_name, &service_name).await? + plugin_runtime.before_get_prompt(&request, &prompt_name, &backend_name).await? } else { PromptPreFetchResult::unchanged() }; @@ -54,10 +45,10 @@ pub(super) async fn get_prompt( pre_result.arguments.apply_to_request(&mut routed_request, &prompt_name); 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:?}"); + tracing::warn!("get_prompt: backend cleanup failed backend_name = {backend_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 = response.map_err(|error| backend_forward_error("get_prompt", &backend_name, &error))?; + info!("get_prompt: backend {backend_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? } else { 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 b4e0279..3924d17 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 @@ -9,8 +9,7 @@ use tracing::info; use super::McpService; use crate::gateway::{ - identifier_routing::{backend_forward_error, resolve_resources_route}, - mcp_call_validator::AuthorizedCallValidator, + identifier_routing::backend_forward_error, mcp_call_validator::AuthorizedCallValidator, mcp_service::initialization::connect_backend_for_request, }; @@ -21,42 +20,32 @@ pub(super) async fn read_resource( ) -> Result { let mcp_call_validator = AuthorizedCallValidator::new("read_resource", &cx); 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_resources_route(virtual_host, &request.uri, &backend_names) - .map_err(|e| ErrorData { - code: ErrorCode::INVALID_PARAMS, - message: format!("Routing problem... {e}").into(), - data: None, - })? - else { + let dowstream_name = request.uri.clone(); + + let Some((backend_name, resource_uri)) = virtual_host.resources.get(&dowstream_name) 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 backend = virtual_host.backends.get(&backend_name).ok_or_else(|| ErrorData { + 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 mut backend_service = connect_backend_for_request(mcp_service, &backend_name, backend, &cx).await?; - + let mut backend_service = connect_backend_for_request(mcp_service, backend_name, backend, &cx).await?; let mut routed_request = request; - routed_request.uri = resource_uri; - + routed_request.uri = resource_uri.clone(); 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:?}"); + tracing::warn!("read_resource: backend cleanup failed backend_name = {backend_name} error = {error:?}"); } - let response = response.map_err(|error| backend_forward_error("read_resource", &service_name, &error))?; + let response = response.map_err(|error| backend_forward_error("read_resource", backend_name, &error))?; - info!("read_resource: backend {service_name} returned {} contents", response.contents.len()); + info!("read_resource: backend {backend_name} returned {} contents", response.contents.len()); Ok(response.into()) } From 94f4411ebbb16e6327eb600d8a3de6ed4da0093f Mon Sep 17 00:00:00 2001 From: cafalchio Date: Sat, 29 Aug 2026 20:29:56 +0100 Subject: [PATCH 04/20] Removed unused code, renamed file, fmt and clippy Signed-off-by: cafalchio --- .../src/gateway/identifier_routing.rs | 219 ------------------ .../src/gateway/mcp_service/prompts.rs | 4 +- .../src/gateway/mcp_service/resources.rs | 5 +- .../src/gateway/mcp_service/tools.rs | 4 +- .../src/gateway/mod.rs | 2 +- .../src/gateway/routing_error.rs | 15 ++ 6 files changed, 23 insertions(+), 226 deletions(-) delete mode 100644 crates/contextforge-data-plane-lib/src/gateway/identifier_routing.rs create mode 100644 crates/contextforge-data-plane-lib/src/gateway/routing_error.rs diff --git a/crates/contextforge-data-plane-lib/src/gateway/identifier_routing.rs b/crates/contextforge-data-plane-lib/src/gateway/identifier_routing.rs deleted file mode 100644 index b5e8b47..0000000 --- a/crates/contextforge-data-plane-lib/src/gateway/identifier_routing.rs +++ /dev/null @@ -1,219 +0,0 @@ -use contextforge_data_plane_apis::user_store::{BackendMCPGateway, NameAlias, VirtualHost}; -use rmcp::{ErrorData, model::ErrorCode, service::ServiceError}; -use tracing::{debug, warn}; - -fn resolve_route<'a, N: AsRef + std::fmt::Debug>( - virtual_host: &'a VirtualHost, - name: &'a str, - backend_names: &'a [N], - name_extractor: impl Fn(&'a str, &'a BackendMCPGateway) -> Option<&'a str>, -) -> Result, Box> { - debug!("resolve_route: vh {virtual_host:#?}, name {name}, backend_names {backend_names:?}"); - let mut aliases = backend_names.iter().filter_map(|backend_name| { - let backend_name = backend_name.as_ref(); - let backend = virtual_host.backends.get(backend_name)?; - let upstream_name = name_extractor(name, backend)?; - Some((backend_name, upstream_name)) - }); - let alias = aliases.next(); - if aliases.next().is_some() { - return Err(format!("Multiple backends found for {name}").into()); - } - Ok(alias) -} - -/// Resolves an exact control-plane alias to its backend and upstream name. Without an alias, -/// single-backend hosts preserve the upstream name and multi-backend hosts use the legacy prefix. -pub(crate) fn resolve_tool_route<'a, N: AsRef + std::fmt::Debug>( - virtual_host: &'a VirtualHost, - name: &'a str, - backend_names: &'a [N], -) -> Result, Box> { - resolve_route(virtual_host, name, backend_names, |name: &'a str, backend: &'a BackendMCPGateway| { - backend - .tool_name_aliases - .get(&NameAlias::with_downstream_prefixed_name(name.to_owned())) - .map(NameAlias::get_upstream_name) - }) -} - -pub(super) fn resolve_resources_route<'a, N: AsRef + std::fmt::Debug>( - virtual_host: &'a VirtualHost, - name: &'a str, - backend_names: &'a [N], -) -> Result, Box> { - resolve_route(virtual_host, name, backend_names, |name: &'a str, backend: &'a BackendMCPGateway| { - backend - .resource_uri_aliases - .get(&NameAlias::with_downstream_prefixed_name(name.to_owned())) - .map(NameAlias::get_upstream_name) - }) -} - -pub(super) fn resolve_prompt_route<'a, N: AsRef + std::fmt::Debug>( - virtual_host: &'a VirtualHost, - name: &'a str, - backend_names: &'a [N], -) -> Result, Box> { - resolve_route(virtual_host, name, backend_names, |name: &'a str, backend: &'a BackendMCPGateway| { - backend - .prompt_name_aliases - .get(&NameAlias::with_downstream_prefixed_name(name.to_owned())) - .map(NameAlias::get_upstream_name) - }) -} - -pub(super) fn backend_forward_error(op: &str, backend_name: &str, error: &ServiceError) -> ErrorData { - warn!("{op}: backend {backend_name} error = {error:?}"); - - match error { - ServiceError::McpError(mcp_error) => mcp_error.to_owned(), - _ => ErrorData { - code: ErrorCode::INTERNAL_ERROR, - message: "Routing problem... got no responses from backends".into(), - data: None, - }, - } -} - -#[cfg(test)] -mod tests { - use super::*; - - /// Preserves identifiers for a single backend. For multiple backends, splits a - /// `{backend}-{identifier}` namespace so duplicate identifiers remain routable. - fn route_identifier<'a, N: AsRef>(identifier: &'a str, backend_names: &'a [N]) -> Option<(&'a str, &'a str)> { - if let [backend] = backend_names { - return Some((backend.as_ref(), identifier)); - } - - backend_names.iter().find_map(|backend| { - let backend = backend.as_ref(); - identifier.strip_prefix(backend)?.strip_prefix('-').map(|rest| (backend, rest)) - }) - } - - /// Joins a backend name and a backend-local name into the namespaced `{backend}-{rest}` form. - fn prefixed_name(backend_name: &str, rest: &str) -> String { - format!("{backend_name}-{rest}") - } - - /// Returns the control-plane alias for an upstream tool when configured. Without an alias, - /// single-backend hosts preserve the upstream name and multi-backend hosts use the legacy prefix. - fn exposed_tool_name(virtual_host: &VirtualHost, backend_name: &str, original_name: &str) -> String { - virtual_host - .backends - .get(backend_name) - .and_then(|backend| { - backend - .tool_name_aliases - .iter() - .find_map(|alias| (alias.get_upstream_name() == original_name).then(|| alias.clone())) - }) - .map_or_else( - || { - if virtual_host.backends.len() == 1 { - original_name.to_owned() - } else { - prefixed_name(backend_name, original_name) - } - }, - |a| a.get_downstream_prefixed_name().to_owned(), - ) - } - - #[test] - fn multi_backend_route_requires_exact_backend_prefix() { - let backend_names = vec!["counter-on", "counter-oneee", "counter-one"]; - assert_eq!(Some(("counter-one", "increment")), route_identifier("counter-one-increment", &backend_names)); - assert_eq!(None, route_identifier("counter-oneincrement", &backend_names)); - assert_eq!(None, route_identifier("counteroneincrement", &backend_names)); - assert_eq!(Some(("counter-one", "get-value")), route_identifier("counter-one-get-value", &backend_names)); - - // Tool, resource, and prompt routing all share this splitter. - assert_eq!( - Some(("counter-one", "example-prompt")), - route_identifier("counter-one-example-prompt", &backend_names) - ); - assert_eq!(None, route_identifier("counter-oneexample-prompt", &backend_names)); - - let backend_names = vec!["counter_on", "counter_oneee", "counter_one"]; - assert_eq!(Some(("counter_one", "get-value")), route_identifier("counter_one-get-value", &backend_names)); - } - - #[test] - fn single_backend_routes_unprefixed_identifier_unchanged() { - let backend_names = vec!["backend-id"]; - - assert_eq!(Some(("backend-id", "test_simple_text")), route_identifier("test_simple_text", &backend_names)); - assert_eq!(Some(("backend-id", "backend-id-tool")), route_identifier("backend-id-tool", &backend_names)); - assert_eq!( - Some(("backend-id", "test://template/123/data")), - route_identifier("test://template/123/data", &backend_names) - ); - } - - #[test] - fn control_plane_alias_is_advertised_and_routes_to_original_name() { - let config_json = serde_json::json!({ - "backends": { - "79fabb70-2188-4de8-95ed-dc1e976e14d4": { - "name": "compliance_reference", - "url": "http://upstream:9000/mcp", - "mcp_protocol_version": "2026_07_28", - "passthrough_headers": [], - "tool_name_aliases": [ - {"downstream_prefixed_name":"Public.Tool", "upstream_name":"get_stats"}, - {"downstream_prefixed_name":"Echo_Tool", "upstream_name":"echo"} - ], - "tool_schemas": {} - } - } - }); - let virtual_host: VirtualHost = serde_json::from_value(config_json).expect("valid virtual host"); - let backend_ids = vec!["79fabb70-2188-4de8-95ed-dc1e976e14d4"]; - - assert_eq!( - "Public.Tool", - exposed_tool_name(&virtual_host, "79fabb70-2188-4de8-95ed-dc1e976e14d4", "get_stats") - ); - assert_eq!( - Some(("79fabb70-2188-4de8-95ed-dc1e976e14d4", "get_stats")), - resolve_tool_route(&virtual_host, "Public.Tool", &backend_ids).expect("this should work") - ); - } - - #[test] - fn multi_backend_tool_routing_falls_back_to_legacy_prefixed_names() { - let config_json = serde_json::json!({ - "backends": { - "compliance-reference": { - "name": "compliance_reference", - "url": "http://upstream:9000/mcp", - "mcp_protocol_version": "2026_07_28", - "passthrough_headers": [], - "tool_schemas": {} - }, - "other": { - "name": "other", - "url": "http://other:9000/mcp", - "mcp_protocol_version": "2026_07_28", - "passthrough_headers": [], - "tool_schemas": {} - } - } - }); - let virtual_host: VirtualHost = serde_json::from_value(config_json).expect("valid virtual host"); - let backend_names = vec!["compliance-reference", "other"]; - - assert_eq!( - "compliance-reference-get_stats", - exposed_tool_name(&virtual_host, "compliance-reference", "get_stats") - ); - assert_ne!( - Some(("compliance-reference", "get_stats")), - resolve_tool_route(&virtual_host, "compliance-reference-get_stats", &backend_names) - .expect("this should work") - ); - } -} 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 4edc492..5743632 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 @@ -8,8 +8,8 @@ use tracing::info; use super::McpService; use crate::gateway::{ - identifier_routing::backend_forward_error, mcp_call_validator::AuthorizedCallValidator, - mcp_service::initialization::connect_backend_for_request, + mcp_call_validator::AuthorizedCallValidator, mcp_service::initialization::connect_backend_for_request, + routing_error::backend_forward_error, }; pub(super) async fn get_prompt( 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 3924d17..0ef0a94 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 @@ -9,8 +9,8 @@ use tracing::info; use super::McpService; use crate::gateway::{ - identifier_routing::backend_forward_error, mcp_call_validator::AuthorizedCallValidator, - mcp_service::initialization::connect_backend_for_request, + mcp_call_validator::AuthorizedCallValidator, mcp_service::initialization::connect_backend_for_request, + routing_error::backend_forward_error, }; pub(super) async fn read_resource( @@ -38,6 +38,7 @@ pub(super) async fn read_resource( let mut backend_service = connect_backend_for_request(mcp_service, backend_name, backend, &cx).await?; let mut routed_request = request; + routed_request.uri = resource_uri.clone(); let response = backend_service.read_resource(routed_request).await; if let Err(error) = backend_service.close().await { diff --git a/crates/contextforge-data-plane-lib/src/gateway/mcp_service/tools.rs b/crates/contextforge-data-plane-lib/src/gateway/mcp_service/tools.rs index 2935785..4ae8860 100644 --- a/crates/contextforge-data-plane-lib/src/gateway/mcp_service/tools.rs +++ b/crates/contextforge-data-plane-lib/src/gateway/mcp_service/tools.rs @@ -9,8 +9,8 @@ use tracing::{info, warn}; use super::McpService; use crate::gateway::{ - backend_client::call_backend_tool, identifier_routing::backend_forward_error, - mcp_call_validator::AuthorizedCallValidator, mcp_service::initialization::connect_backend_for_request, + backend_client::call_backend_tool, mcp_call_validator::AuthorizedCallValidator, + mcp_service::initialization::connect_backend_for_request, routing_error::backend_forward_error, }; use crate::mcp_standard_headers; diff --git a/crates/contextforge-data-plane-lib/src/gateway/mod.rs b/crates/contextforge-data-plane-lib/src/gateway/mod.rs index d9a754b..113d752 100644 --- a/crates/contextforge-data-plane-lib/src/gateway/mod.rs +++ b/crates/contextforge-data-plane-lib/src/gateway/mod.rs @@ -1,6 +1,6 @@ mod backend_client; -mod identifier_routing; +mod routing_error; mod mcp_call_validator; mod mcp_service; diff --git a/crates/contextforge-data-plane-lib/src/gateway/routing_error.rs b/crates/contextforge-data-plane-lib/src/gateway/routing_error.rs new file mode 100644 index 0000000..a4a3d0f --- /dev/null +++ b/crates/contextforge-data-plane-lib/src/gateway/routing_error.rs @@ -0,0 +1,15 @@ +use rmcp::{ErrorData, model::ErrorCode, service::ServiceError}; +use tracing::warn; + +pub(super) fn backend_forward_error(op: &str, backend_name: &str, error: &ServiceError) -> ErrorData { + warn!("{op}: backend {backend_name} error = {error:?}"); + + match error { + ServiceError::McpError(mcp_error) => mcp_error.to_owned(), + _ => ErrorData { + code: ErrorCode::INTERNAL_ERROR, + message: "Routing problem... got no responses from backends".into(), + data: None, + }, + } +} From 1c8fcef20971f2e5e98f0ef4aa393560d56de7b2 Mon Sep 17 00:00:00 2001 From: cafalchio Date: Tue, 1 Sep 2026 09:31:38 +0100 Subject: [PATCH 05/20] Fixed tests Signed-off-by: cafalchio --- .../tests/support/test_gateways.rs | 60 ++++++++++++++++++- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/crates/contextforge-data-plane-lib/tests/support/test_gateways.rs b/crates/contextforge-data-plane-lib/tests/support/test_gateways.rs index dd43c1e..5369677 100644 --- a/crates/contextforge-data-plane-lib/tests/support/test_gateways.rs +++ b/crates/contextforge-data-plane-lib/tests/support/test_gateways.rs @@ -1,4 +1,7 @@ -use std::{collections::HashMap, sync::Arc}; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; use contextforge_data_plane_apis::{ User, @@ -54,6 +57,37 @@ pub(crate) fn create_ports(ports: usize) -> Vec { selected } +pub fn construct_services(backend_name: &str, service_names: &[&str]) -> HashMap { + let aliases: HashSet = + service_names.iter().map(|n| NameAlias::new(n.to_string(), n.to_string())).collect(); + let mut result: HashMap = HashMap::new(); + + for alias in aliases { + result.insert( + alias.get_downstream_prefixed_name().to_string(), + (backend_name.to_string(), alias.get_upstream_name().to_string()), + ); + } + result +} + +fn create_services_from_ports(ports: &[u16], service_names: &[&str]) -> HashMap { + let mut services = HashMap::new(); + + for &port in ports { + let backend_id = backend_id(port); + + for &service_name in service_names { + let key = format!("{backend_id}-{service_name}"); + let value = (backend_id.clone(), service_name.to_string()); + + services.insert(key, value); + } + } + + services +} + async fn create_gateway_with_four_counters_and_custom_config( user: &str, config: Config, @@ -91,14 +125,34 @@ async fn create_gateway_with_four_counters_and_custom_config( let mut virtual_host_one_resource_uris = create_resource_uris(&gateway_one_ports); virtual_host_one_resource_uris.sort(); + let gateway_one_tools = create_services_from_ports(&gateway_one_ports, MOCK_COUNTER_TOOL_NAMES); + let gateway_one_resources = create_services_from_ports(&gateway_one_ports, MOCK_COUNTER_RESOURCE_URIS); + let gateway_one_prompts = create_services_from_ports(&gateway_one_ports, MOCK_COUNTER_PROMPT_NAMES); + let user_key = User::new(user); let virtual_host_one_id = uuid::Uuid::new_v4().to_string(); let virtual_host_two_id = uuid::Uuid::new_v4().to_string(); let virtual_hosts = HashMap::from([ - (virtual_host_one_id.clone(), VirtualHost { backends: gateway_one_backends }), - (virtual_host_two_id, VirtualHost { backends: gateway_two_backends }), + ( + virtual_host_one_id.clone(), + VirtualHost { + backends: gateway_one_backends, + tools: gateway_one_tools, + resources: gateway_one_resources, + prompts: gateway_one_prompts, + }, + ), + ( + virtual_host_two_id, + VirtualHost { + backends: gateway_two_backends, + tools: HashMap::new(), + resources: HashMap::new(), + prompts: HashMap::new(), + }, + ), ]); let user_config = UserConfig { virtual_hosts }; From f09e69a85dd1286e5eba26e5b60b1242e298e7b5 Mon Sep 17 00:00:00 2001 From: cafalchio Date: Tue, 1 Sep 2026 09:32:00 +0100 Subject: [PATCH 06/20] Added empty defaul values, clippy fmt Signed-off-by: cafalchio --- .../src/user_store.rs | 3 +++ .../src/layers/virtual_host_config.rs | 10 +++++++++- .../tests/gateway_pagination.rs | 16 ++++++++++++---- .../tests/support/plugin_gateway.rs | 5 +++++ 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/crates/contextforge-data-plane-apis/src/user_store.rs b/crates/contextforge-data-plane-apis/src/user_store.rs index e9d1921..5ae1bd5 100644 --- a/crates/contextforge-data-plane-apis/src/user_store.rs +++ b/crates/contextforge-data-plane-apis/src/user_store.rs @@ -74,8 +74,11 @@ pub struct BackendMCPGateway { #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] pub struct VirtualHost { pub backends: HashMap, + #[serde(default)] pub tools: HashMap, + #[serde(default)] pub resources: HashMap, + #[serde(default)] pub prompts: HashMap, } diff --git a/crates/contextforge-data-plane-lib/src/layers/virtual_host_config.rs b/crates/contextforge-data-plane-lib/src/layers/virtual_host_config.rs index 0d6101c..459a1d2 100644 --- a/crates/contextforge-data-plane-lib/src/layers/virtual_host_config.rs +++ b/crates/contextforge-data-plane-lib/src/layers/virtual_host_config.rs @@ -54,7 +54,15 @@ mod tests { fn user_config_with_virtual_host(virtual_host_id: &str) -> UserConfig { UserConfig { - virtual_hosts: HashMap::from([(virtual_host_id.to_owned(), VirtualHost { backends: HashMap::new() })]), + virtual_hosts: HashMap::from([( + virtual_host_id.to_owned(), + VirtualHost { + backends: HashMap::new(), + tools: HashMap::new(), + resources: HashMap::new(), + prompts: HashMap::new(), + }, + )]), } } diff --git a/crates/contextforge-data-plane-lib/tests/gateway_pagination.rs b/crates/contextforge-data-plane-lib/tests/gateway_pagination.rs index 4588183..adbd91e 100644 --- a/crates/contextforge-data-plane-lib/tests/gateway_pagination.rs +++ b/crates/contextforge-data-plane-lib/tests/gateway_pagination.rs @@ -94,8 +94,12 @@ async fn single_backend_pagination_all_tools_reachable() -> Result<()> { let virtual_host_id = "22222222-2222-2222-2222-222222222222"; let backends = HashMap::from([(backend_id(backend_port), paginating_backend(backend_port))]); - let user_config = - UserConfig { virtual_hosts: HashMap::from([(virtual_host_id.to_owned(), VirtualHost { backends })]) }; + let user_config = UserConfig { + virtual_hosts: HashMap::from([( + virtual_host_id.to_owned(), + VirtualHost { backends, tools: HashMap::new(), resources: HashMap::new(), prompts: HashMap::new() }, + )]), + }; let backend_listener = bind_backend_port(backend_port).await; tokio::spawn(serve_paginating_backend(backend_listener)); @@ -148,8 +152,12 @@ async fn multi_backend_exhausted_backend_not_requeried() -> Result<()> { (backend_id(port_a), paginating_backend(port_a)), (backend_id(port_b), paginating_backend(port_b)), ]); - let user_config = - UserConfig { virtual_hosts: HashMap::from([(virtual_host_id.to_owned(), VirtualHost { backends })]) }; + let user_config = UserConfig { + virtual_hosts: HashMap::from([( + virtual_host_id.to_owned(), + VirtualHost { backends, tools: HashMap::new(), resources: HashMap::new(), prompts: HashMap::new() }, + )]), + }; let listener_a = bind_backend_port(port_a).await; let listener_b = bind_backend_port(port_b).await; diff --git a/crates/contextforge-data-plane-lib/tests/support/plugin_gateway.rs b/crates/contextforge-data-plane-lib/tests/support/plugin_gateway.rs index 65667e9..13e0bd4 100644 --- a/crates/contextforge-data-plane-lib/tests/support/plugin_gateway.rs +++ b/crates/contextforge-data-plane-lib/tests/support/plugin_gateway.rs @@ -29,6 +29,8 @@ use rmcp::{ use serde_json::{Map, Value, json}; use tokio::sync::Mutex as TokioMutex; +use crate::support::test_gateways::construct_services; + use super::{MemoryUserConfigStore, token}; pub(crate) const BACKEND_PROMPT_RESOURCE: &str = "token=secret"; @@ -413,6 +415,9 @@ async fn start_gateway_with_state( completion: HashMap::new(), }, )]), + tools: construct_services(&backend_name, TOOL_NAMES), + resources: construct_services(&backend_name, RESOURCE_URIS), + prompts: construct_services(&backend_name, PROMPT_NAMES), }, )]), }, From a14a8ebf2d3a84970f25fef6807d1507e1543ca6 Mon Sep 17 00:00:00 2001 From: cafalchio Date: Tue, 1 Sep 2026 10:19:19 +0100 Subject: [PATCH 07/20] fixed clippy and e2e test Signed-off-by: cafalchio --- .../tests/support/test_gateways.rs | 6 +++--- .../contextforge-data-plane/tests/secrets_detection_e2e.rs | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/contextforge-data-plane-lib/tests/support/test_gateways.rs b/crates/contextforge-data-plane-lib/tests/support/test_gateways.rs index 5369677..45098fe 100644 --- a/crates/contextforge-data-plane-lib/tests/support/test_gateways.rs +++ b/crates/contextforge-data-plane-lib/tests/support/test_gateways.rs @@ -64,8 +64,8 @@ pub fn construct_services(backend_name: &str, service_names: &[&str]) -> HashMap for alias in aliases { result.insert( - alias.get_downstream_prefixed_name().to_string(), - (backend_name.to_string(), alias.get_upstream_name().to_string()), + alias.get_downstream_prefixed_name().to_owned(), + (backend_name.to_owned(), alias.get_upstream_name().to_owned()), ); } result @@ -79,7 +79,7 @@ fn create_services_from_ports(ports: &[u16], service_names: &[&str]) -> HashMap< for &service_name in service_names { let key = format!("{backend_id}-{service_name}"); - let value = (backend_id.clone(), service_name.to_string()); + let value = (backend_id.clone(), service_name.to_owned()); services.insert(key, value); } diff --git a/crates/contextforge-data-plane/tests/secrets_detection_e2e.rs b/crates/contextforge-data-plane/tests/secrets_detection_e2e.rs index 28ff56f..99604dc 100644 --- a/crates/contextforge-data-plane/tests/secrets_detection_e2e.rs +++ b/crates/contextforge-data-plane/tests/secrets_detection_e2e.rs @@ -391,6 +391,9 @@ async fn write_redis_config(redis_port: u16, backend: &RunningBackend) { completion: HashMap::new(), }, )]), + tools: HashMap::new(), + resources: HashMap::new(), + prompts: HashMap::new(), }, )]), }; From d745ed41e56b642ca403ab860220d7d116497f24 Mon Sep 17 00:00:00 2001 From: cafalchio Date: Tue, 1 Sep 2026 15:36:03 +0100 Subject: [PATCH 08/20] Created ServiceRoute struct Signed-off-by: cafalchio --- .../src/user_store.rs | 14 +++++++++++--- .../src/gateway/mcp_service/prompts.rs | 6 +++--- .../src/gateway/mcp_service/resources.rs | 11 +++++++---- .../src/gateway/mcp_service/tools.rs | 16 +++++++++------- .../src/layers/virtual_host_config.rs | 1 + .../tests/gateway_pagination.rs | 16 ++++++++++++++-- .../tests/support/plugin_gateway.rs | 1 + .../tests/support/test_gateways.rs | 15 ++++++++------- 8 files changed, 54 insertions(+), 26 deletions(-) diff --git a/crates/contextforge-data-plane-apis/src/user_store.rs b/crates/contextforge-data-plane-apis/src/user_store.rs index 5ae1bd5..46186cb 100644 --- a/crates/contextforge-data-plane-apis/src/user_store.rs +++ b/crates/contextforge-data-plane-apis/src/user_store.rs @@ -71,15 +71,23 @@ pub struct BackendMCPGateway { pub tool_schemas: HashMap>, } +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] +pub struct ServiceRoute { + pub backend_name: String, + pub upstream_name: String, +} + #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] pub struct VirtualHost { pub backends: HashMap, #[serde(default)] - pub tools: HashMap, + pub tools: HashMap, + #[serde(default)] + pub resources: HashMap, #[serde(default)] - pub resources: HashMap, + pub resources_templates: HashMap, #[serde(default)] - pub prompts: HashMap, + pub prompts: HashMap, } #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] 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 5743632..f092bfb 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 @@ -19,7 +19,7 @@ pub(super) async fn get_prompt( ) -> Result { let mcp_call_validator = AuthorizedCallValidator::new("get_prompt", &cx); let (virtual_host, _claims) = mcp_call_validator.validate_stateless()?; - let Some((backend_name, prompt_name)) = virtual_host.prompts.get(&request.name) else { + let Some(route) = virtual_host.prompts.get(&request.name) else { return Err(ErrorData { code: ErrorCode::INVALID_PARAMS, message: "Routing problem... prompt not found".into(), @@ -27,8 +27,8 @@ pub(super) async fn get_prompt( }); }; - let backend_name = backend_name.to_owned(); - let prompt_name = prompt_name.to_owned(); + let backend_name = route.backend_name.clone(); + let prompt_name = route.upstream_name.clone(); let backend = virtual_host.backends.get(&backend_name).ok_or_else(|| ErrorData { code: ErrorCode::INVALID_PARAMS, 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 0ef0a94..bf538aa 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 @@ -22,7 +22,7 @@ pub(super) async fn read_resource( let (virtual_host, _claims) = mcp_call_validator.validate_stateless()?; let dowstream_name = request.uri.clone(); - let Some((backend_name, resource_uri)) = virtual_host.resources.get(&dowstream_name) else { + let Some(route) = virtual_host.resources.get(&dowstream_name) else { return Err(ErrorData { code: ErrorCode::INVALID_PARAMS, message: "Routing problem... resource not found".into(), @@ -30,13 +30,16 @@ pub(super) async fn read_resource( }); }; - let backend = virtual_host.backends.get(backend_name).ok_or_else(|| ErrorData { + let backend_name = route.backend_name.clone(); + let resource_uri = route.upstream_name.clone(); + + 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 mut backend_service = connect_backend_for_request(mcp_service, backend_name, backend, &cx).await?; + let mut backend_service = connect_backend_for_request(mcp_service, &backend_name, backend, &cx).await?; let mut routed_request = request; routed_request.uri = resource_uri.clone(); @@ -44,7 +47,7 @@ pub(super) async fn read_resource( if let Err(error) = backend_service.close().await { tracing::warn!("read_resource: backend cleanup failed backend_name = {backend_name} error = {error:?}"); } - let response = response.map_err(|error| backend_forward_error("read_resource", backend_name, &error))?; + let response = response.map_err(|error| backend_forward_error("read_resource", &backend_name, &error))?; info!("read_resource: backend {backend_name} returned {} contents", response.contents.len()); diff --git a/crates/contextforge-data-plane-lib/src/gateway/mcp_service/tools.rs b/crates/contextforge-data-plane-lib/src/gateway/mcp_service/tools.rs index 4ae8860..b0cb1da 100644 --- a/crates/contextforge-data-plane-lib/src/gateway/mcp_service/tools.rs +++ b/crates/contextforge-data-plane-lib/src/gateway/mcp_service/tools.rs @@ -23,7 +23,7 @@ pub(super) async fn call_tool( let (virtual_host, _claims) = mcp_call_validator.validate_stateless()?; let dowstream_name = request.name.to_string(); - let Some((backend_name, tool_name)) = virtual_host.tools.get(&dowstream_name) else { + let Some(route) = virtual_host.tools.get(&dowstream_name) else { return Err(ErrorData { code: ErrorCode::INVALID_PARAMS, message: "Routing problem... tool not found".into(), @@ -31,7 +31,10 @@ pub(super) async fn call_tool( }); }; - let backend = virtual_host.backends.get(backend_name).ok_or_else(|| ErrorData { + let backend_name = route.backend_name.clone(); + let tool_name = route.upstream_name.clone(); + + 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, @@ -43,23 +46,22 @@ pub(super) async fn call_tool( .get::() .map(|parts| &parts.headers) .ok_or_else(|| ErrorData::internal_error("Routing problem... request headers not found", None))?; - let tool_schema = backend.tool_schemas.get(tool_name).ok_or_else(|| { + let tool_schema = backend.tool_schemas.get(&tool_name).ok_or_else(|| { ErrorData::header_mismatch(format!("Missing published schema for tool '{tool_name}'"), None) })?; mcp_standard_headers::validate_tool_params(downstream_headers, request.arguments.as_ref(), tool_schema) .map_err(|message| ErrorData::header_mismatch(message, None))?; } - let backend_name = backend_name.clone(); let pre_result = if let Some(plugin_runtime) = &mcp_service.plugin_runtime { - plugin_runtime.before_tool_call(&request, tool_name, &backend_name).await? + plugin_runtime.before_tool_call(&request, &tool_name, &backend_name).await? } else { ToolPreCallResult::unchanged() }; let mut backend_service = connect_backend_for_request(mcp_service, &backend_name, backend, &cx).await?; let post_state = pre_result.state; let mut routed_request = request; - pre_result.arguments.apply_to_request(&mut routed_request, tool_name); + pre_result.arguments.apply_to_request(&mut routed_request, &tool_name); let progress_token = cx.meta.get_progress_token(); let handle = backend_service @@ -84,7 +86,7 @@ pub(super) async fn call_tool( let response = response.map_err(|error| backend_forward_error("call_tool", &backend_name, &error))?; let response = match (&mcp_service.plugin_runtime, post_state) { (Some(plugin_runtime), Some(post_state)) => { - plugin_runtime.after_tool_call(tool_name, response, Some(post_state)).await? + plugin_runtime.after_tool_call(&tool_name, response, Some(post_state)).await? }, _ => response, }; diff --git a/crates/contextforge-data-plane-lib/src/layers/virtual_host_config.rs b/crates/contextforge-data-plane-lib/src/layers/virtual_host_config.rs index 459a1d2..17041f7 100644 --- a/crates/contextforge-data-plane-lib/src/layers/virtual_host_config.rs +++ b/crates/contextforge-data-plane-lib/src/layers/virtual_host_config.rs @@ -60,6 +60,7 @@ mod tests { backends: HashMap::new(), tools: HashMap::new(), resources: HashMap::new(), + resources_templates: HashMap::new(), prompts: HashMap::new(), }, )]), diff --git a/crates/contextforge-data-plane-lib/tests/gateway_pagination.rs b/crates/contextforge-data-plane-lib/tests/gateway_pagination.rs index adbd91e..dc3d443 100644 --- a/crates/contextforge-data-plane-lib/tests/gateway_pagination.rs +++ b/crates/contextforge-data-plane-lib/tests/gateway_pagination.rs @@ -97,7 +97,13 @@ async fn single_backend_pagination_all_tools_reachable() -> Result<()> { let user_config = UserConfig { virtual_hosts: HashMap::from([( virtual_host_id.to_owned(), - VirtualHost { backends, tools: HashMap::new(), resources: HashMap::new(), prompts: HashMap::new() }, + VirtualHost { + backends, + tools: HashMap::new(), + resources: HashMap::new(), + resources_templates: HashMap::new(), + prompts: HashMap::new(), + }, )]), }; @@ -155,7 +161,13 @@ async fn multi_backend_exhausted_backend_not_requeried() -> Result<()> { let user_config = UserConfig { virtual_hosts: HashMap::from([( virtual_host_id.to_owned(), - VirtualHost { backends, tools: HashMap::new(), resources: HashMap::new(), prompts: HashMap::new() }, + VirtualHost { + backends, + tools: HashMap::new(), + resources: HashMap::new(), + resources_templates: HashMap::new(), + prompts: HashMap::new(), + }, )]), }; diff --git a/crates/contextforge-data-plane-lib/tests/support/plugin_gateway.rs b/crates/contextforge-data-plane-lib/tests/support/plugin_gateway.rs index 13e0bd4..8c7d206 100644 --- a/crates/contextforge-data-plane-lib/tests/support/plugin_gateway.rs +++ b/crates/contextforge-data-plane-lib/tests/support/plugin_gateway.rs @@ -417,6 +417,7 @@ async fn start_gateway_with_state( )]), tools: construct_services(&backend_name, TOOL_NAMES), resources: construct_services(&backend_name, RESOURCE_URIS), + resources_templates: HashMap::new(), prompts: construct_services(&backend_name, PROMPT_NAMES), }, )]), diff --git a/crates/contextforge-data-plane-lib/tests/support/test_gateways.rs b/crates/contextforge-data-plane-lib/tests/support/test_gateways.rs index 45098fe..499bf21 100644 --- a/crates/contextforge-data-plane-lib/tests/support/test_gateways.rs +++ b/crates/contextforge-data-plane-lib/tests/support/test_gateways.rs @@ -5,7 +5,7 @@ use std::{ use contextforge_data_plane_apis::{ User, - user_store::{BackendMCPGateway, NameAlias, UserConfig, VirtualHost}, + user_store::{BackendMCPGateway, NameAlias, ServiceRoute, UserConfig, VirtualHost}, }; use contextforge_data_plane_lib::{ Config, Gateway, Result, UpstreamConnectionMode, UserConfigStore, UserConfigStoreType, @@ -57,21 +57,21 @@ pub(crate) fn create_ports(ports: usize) -> Vec { selected } -pub fn construct_services(backend_name: &str, service_names: &[&str]) -> HashMap { +pub fn construct_services(backend_name: &str, service_names: &[&str]) -> HashMap { let aliases: HashSet = service_names.iter().map(|n| NameAlias::new(n.to_string(), n.to_string())).collect(); - let mut result: HashMap = HashMap::new(); + let mut result: HashMap = HashMap::new(); for alias in aliases { result.insert( alias.get_downstream_prefixed_name().to_owned(), - (backend_name.to_owned(), alias.get_upstream_name().to_owned()), + ServiceRoute { backend_name: backend_name.to_owned(), upstream_name: alias.get_upstream_name().to_owned() }, ); } result } -fn create_services_from_ports(ports: &[u16], service_names: &[&str]) -> HashMap { +fn create_services_from_ports(ports: &[u16], service_names: &[&str]) -> HashMap { let mut services = HashMap::new(); for &port in ports { @@ -79,12 +79,11 @@ fn create_services_from_ports(ports: &[u16], service_names: &[&str]) -> HashMap< for &service_name in service_names { let key = format!("{backend_id}-{service_name}"); - let value = (backend_id.clone(), service_name.to_owned()); + let value = ServiceRoute { backend_name: backend_id.clone(), upstream_name: service_name.to_owned() }; services.insert(key, value); } } - services } @@ -141,6 +140,7 @@ async fn create_gateway_with_four_counters_and_custom_config( backends: gateway_one_backends, tools: gateway_one_tools, resources: gateway_one_resources, + resources_templates: HashMap::new(), prompts: gateway_one_prompts, }, ), @@ -150,6 +150,7 @@ async fn create_gateway_with_four_counters_and_custom_config( backends: gateway_two_backends, tools: HashMap::new(), resources: HashMap::new(), + resources_templates: HashMap::new(), prompts: HashMap::new(), }, ), From 6aece995302b321b03bb0ec52031cb1192c1d819 Mon Sep 17 00:00:00 2001 From: cafalchio Date: Tue, 1 Sep 2026 16:00:58 +0100 Subject: [PATCH 09/20] Added missing resource_tempolates Signed-off-by: cafalchio --- crates/contextforge-data-plane/tests/secrets_detection_e2e.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/contextforge-data-plane/tests/secrets_detection_e2e.rs b/crates/contextforge-data-plane/tests/secrets_detection_e2e.rs index 99604dc..d832ffb 100644 --- a/crates/contextforge-data-plane/tests/secrets_detection_e2e.rs +++ b/crates/contextforge-data-plane/tests/secrets_detection_e2e.rs @@ -393,6 +393,7 @@ async fn write_redis_config(redis_port: u16, backend: &RunningBackend) { )]), tools: HashMap::new(), resources: HashMap::new(), + resources_templates: HashMap::new(), prompts: HashMap::new(), }, )]), From 7194f259bb45e17930532178c313040bc48c8108 Mon Sep 17 00:00:00 2001 From: cafalchio Date: Tue, 1 Sep 2026 16:14:20 +0100 Subject: [PATCH 10/20] fixed typo Signed-off-by: cafalchio --- crates/contextforge-data-plane-apis/src/user_store.rs | 2 +- .../src/layers/virtual_host_config.rs | 2 +- .../contextforge-data-plane-lib/tests/gateway_pagination.rs | 4 ++-- .../tests/support/plugin_gateway.rs | 2 +- .../tests/support/test_gateways.rs | 4 ++-- crates/contextforge-data-plane/tests/secrets_detection_e2e.rs | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/contextforge-data-plane-apis/src/user_store.rs b/crates/contextforge-data-plane-apis/src/user_store.rs index 46186cb..b2d639f 100644 --- a/crates/contextforge-data-plane-apis/src/user_store.rs +++ b/crates/contextforge-data-plane-apis/src/user_store.rs @@ -85,7 +85,7 @@ pub struct VirtualHost { #[serde(default)] pub resources: HashMap, #[serde(default)] - pub resources_templates: HashMap, + pub resource_templates: HashMap, #[serde(default)] pub prompts: HashMap, } diff --git a/crates/contextforge-data-plane-lib/src/layers/virtual_host_config.rs b/crates/contextforge-data-plane-lib/src/layers/virtual_host_config.rs index 17041f7..a0e0262 100644 --- a/crates/contextforge-data-plane-lib/src/layers/virtual_host_config.rs +++ b/crates/contextforge-data-plane-lib/src/layers/virtual_host_config.rs @@ -60,7 +60,7 @@ mod tests { backends: HashMap::new(), tools: HashMap::new(), resources: HashMap::new(), - resources_templates: HashMap::new(), + resource_templates: HashMap::new(), prompts: HashMap::new(), }, )]), diff --git a/crates/contextforge-data-plane-lib/tests/gateway_pagination.rs b/crates/contextforge-data-plane-lib/tests/gateway_pagination.rs index dc3d443..ad2ebbf 100644 --- a/crates/contextforge-data-plane-lib/tests/gateway_pagination.rs +++ b/crates/contextforge-data-plane-lib/tests/gateway_pagination.rs @@ -101,7 +101,7 @@ async fn single_backend_pagination_all_tools_reachable() -> Result<()> { backends, tools: HashMap::new(), resources: HashMap::new(), - resources_templates: HashMap::new(), + resource_templates: HashMap::new(), prompts: HashMap::new(), }, )]), @@ -165,7 +165,7 @@ async fn multi_backend_exhausted_backend_not_requeried() -> Result<()> { backends, tools: HashMap::new(), resources: HashMap::new(), - resources_templates: HashMap::new(), + resource_templates: HashMap::new(), prompts: HashMap::new(), }, )]), diff --git a/crates/contextforge-data-plane-lib/tests/support/plugin_gateway.rs b/crates/contextforge-data-plane-lib/tests/support/plugin_gateway.rs index 8c7d206..dbb50f1 100644 --- a/crates/contextforge-data-plane-lib/tests/support/plugin_gateway.rs +++ b/crates/contextforge-data-plane-lib/tests/support/plugin_gateway.rs @@ -417,7 +417,7 @@ async fn start_gateway_with_state( )]), tools: construct_services(&backend_name, TOOL_NAMES), resources: construct_services(&backend_name, RESOURCE_URIS), - resources_templates: HashMap::new(), + resource_templates: HashMap::new(), prompts: construct_services(&backend_name, PROMPT_NAMES), }, )]), diff --git a/crates/contextforge-data-plane-lib/tests/support/test_gateways.rs b/crates/contextforge-data-plane-lib/tests/support/test_gateways.rs index 499bf21..e00e5a9 100644 --- a/crates/contextforge-data-plane-lib/tests/support/test_gateways.rs +++ b/crates/contextforge-data-plane-lib/tests/support/test_gateways.rs @@ -140,7 +140,7 @@ async fn create_gateway_with_four_counters_and_custom_config( backends: gateway_one_backends, tools: gateway_one_tools, resources: gateway_one_resources, - resources_templates: HashMap::new(), + resource_templates: HashMap::new(), prompts: gateway_one_prompts, }, ), @@ -150,7 +150,7 @@ async fn create_gateway_with_four_counters_and_custom_config( backends: gateway_two_backends, tools: HashMap::new(), resources: HashMap::new(), - resources_templates: HashMap::new(), + resource_templates: HashMap::new(), prompts: HashMap::new(), }, ), diff --git a/crates/contextforge-data-plane/tests/secrets_detection_e2e.rs b/crates/contextforge-data-plane/tests/secrets_detection_e2e.rs index d832ffb..9fc6e27 100644 --- a/crates/contextforge-data-plane/tests/secrets_detection_e2e.rs +++ b/crates/contextforge-data-plane/tests/secrets_detection_e2e.rs @@ -393,7 +393,7 @@ async fn write_redis_config(redis_port: u16, backend: &RunningBackend) { )]), tools: HashMap::new(), resources: HashMap::new(), - resources_templates: HashMap::new(), + resource_templates: HashMap::new(), prompts: HashMap::new(), }, )]), From 8021d6e3ff171388ed316137ffcd85473ba76fa0 Mon Sep 17 00:00:00 2001 From: cafalchio Date: Tue, 1 Sep 2026 16:16:28 +0100 Subject: [PATCH 11/20] Updated wiki Signed-off-by: cafalchio --- _context/wiki/index.md | 2 +- _context/wiki/routing.md | 157 +++++++++------------------------------ 2 files changed, 35 insertions(+), 124 deletions(-) diff --git a/_context/wiki/index.md b/_context/wiki/index.md index 049785c..abbed89 100644 --- a/_context/wiki/index.md +++ b/_context/wiki/index.md @@ -12,7 +12,7 @@ then follow only the links that are relevant. | [project.md](project.md) | What the project is, goals, stakeholders, key modules, crate ownership, active work | | [preferences.md](preferences.md) | Working standards, code style, logging rules, branch naming, AI interaction preferences | | [architecture.md](architecture.md) | Current middleware stack order, pipeline shape, module boundaries, state ownership, executor shapes | -| [routing.md](routing.md) | Current backend prefix contract, list/routed ops, federated pagination, session state, capability merge | +| [routing.md](routing.md) | Stateless routing model: VirtualHost routing tables, per-request backend lifecycle, method quick reference, header forwarding, plugin hooks | | [mcp-capability-allocation.md](mcp-capability-allocation.md) | Tentative ContextForge 2.0 target topology, ownership, state model, Phase 1-4 roadmap, and Phase 3 flows | | [failure-modes.md](failure-modes.md) | HTTP/MCP/routing/backend/plugin failure table — exact HTTP codes and JSON-RPC errors | | [config.md](config.md) | Key CLI flags, JWT claims, UserConfig shape, plugin config, telemetry debugging, startup validation, local observability stack | diff --git a/_context/wiki/routing.md b/_context/wiki/routing.md index 1013311..9f30038 100644 --- a/_context/wiki/routing.md +++ b/_context/wiki/routing.md @@ -1,141 +1,52 @@ # MCP Routing Semantics -> This page describes the **current transitional routing behavior**. Its live -> upstream fan-out and durable-session assumptions are not the Phase 3 target. -> See [ContextForge 2.0 Target Architecture and Roadmap](mcp-capability-allocation.md) -> for the proposed ownership boundary and migration. +The external dataplane is a **pure stateless router**. No session state, no `BackendTransports`, no sticky-routing requirement. -## Backend Prefix Contract +## How a request is routed -Backend map keys become public identifiers only for **multi-backend virtual hosts without an explicit tool alias**: +1. `validate_stateless` extracts `VirtualHost` from request extensions (set by `virtual_host_config` layer from the JWT virtual-host ID). +2. Downstream name is looked up in `VirtualHost::tools`, `::resources`, or `::prompts` — an O(1) table lookup. +3. `connect_backend_for_request` opens a fresh `StreamableHttpClientTransport`, runs the call, closes the connection. -```text -backend tool "increment" on backend "gateway-one" → "gateway-one-increment" -backend resource "counter" on backend "gateway-one" → "gateway-one-counter" -``` - -Single-backend virtual hosts: identifiers pass through **unchanged**. - -> **Breaking change rule:** changing a backend map key changes downstream identifiers for multi-backend virtual hosts. Do not rename without updating merge logic, split logic, and tests. - -## Tool Aliases - -`BackendMCPGateway.tool_name_aliases` maps `{downstream_alias: upstream_original}`. Aliases take precedence over prefix fallback. They are advertised and routed exactly as published (case, dots, underscores preserved). - -## List Operations (fan-out) - -All four list methods fan out to all connected backends concurrently and merge results: - -```text -list_tools / list_resources / list_prompts / list_resource_templates - → all connected backends → merged sorted output -``` +The control plane builds and publishes the routing tables to Redis; the dataplane never derives names at call time. -Failed/unavailable backends are logged and skipped. Single-backend: identifiers unchanged. Multi-backend: prefixed with backend map key. +## Routing table shape -## Routed Operations (single backend) +```rust +VirtualHost { backends: HashMap, + tools: HashMap, + resources: HashMap, + resource_templates: HashMap, + prompts: HashMap } -Calls targeting one object use the inverse rule. The name splitter walks configured backend names and requires a `-` immediately after the backend name: - -```text -gateway-one-increment → backend: gateway-one, tool: increment -gateway-oneincrement → rejected (no - separator) +ServiceRoute { backend_name: String, // key into VirtualHost::backends + upstream_name: String } // name/URI forwarded to the backend ``` -`call_tool` resolves explicit alias first, then falls back to single/multi-backend logic. - -Methods using the same conditional routing: `read_resource`, `subscribe`, `unsubscribe`, `get_prompt`, `complete`. +Source: [`user_store.rs`](../../crates/contextforge-data-plane-apis/src/user_store.rs) -## Federated Pagination +## Method quick reference -The gateway wraps per-backend cursors inside its own opaque token (JSON, treated as opaque by MCP clients). First request: all backends queried. Resume: cursor decoded, exhausted backends skipped. New cursor emitted when any backend has more pages. +| Method | Behavior | +| --- | --- | +| `initialize` (`2026-07-28`) | `INVALID_REQUEST` — not supported by this dataplane. | +| `initialize` (legacy) | Stub `InitializeResult`; no backend fanout. Supports older clients during migration. | +| `list_tools`, `list_resources`, `list_resource_templates`, `list_prompts` | `INVALID_REQUEST` — delegated to control plane. | +| `call_tool` | Lookup in `tools` map → pre-hook → fresh connection → call → post-hook → close. Forwards cancellation; tracks progress tokens. | +| `read_resource` | Lookup in `resources` map → fresh connection → call with upstream URI → close. | +| `get_prompt` | Lookup in `prompts` map → pre-hook → fresh connection → call → post-hook → close. | +| `subscribe`, `unsubscribe`, `complete` | `INVALID_REQUEST` — delegated to control plane. | +| `ping` | Local success; no backend fanout. | +| `DELETE` | RMCP handles; `session_id_layer` removes the `LocalUserSessionStore` entry. No backend state to clean up. | -**Known limitation:** if backend set changes between pages, removed backend's cursor is silently dropped. +## Header forwarding -## Session State (local process) +Applied in order per upstream call: Host (from backend URL, HTTPS only) → passthrough (`BackendMCPGateway::passthrough_headers`) → `Mcp-Param-*` auto-forward → trace context → add (`add_headers`, overrides passthrough) → remove (`remove_headers`, applied last). -Backend RMCP services are stored in `BackendTransports` keyed by: -```text -principal (claims.sub) + backend_name (map key) + downstream_session_id -``` - -This is **local process state only**. Implications: -- After `initialize`, later requests must reach the same process. -- Sticky routing required for load-balanced deployments. -- Gateway restart → all sessions lost → clients must re-run `initialize`. -- Multi-runtime mode (`--single-runtime false`): each runtime thread has its own `BackendTransports` with no cross-thread affinity. - -**Exception: `call_tool` uses per-request backend lifecycle.** Each tool call creates a fresh backend connection, executes the call with plugin hooks, then closes the connection. This bypasses `BackendTransports` entirely and does not require session affinity for tool calls specifically (though other MCP methods still do). - - -```mermaid -sequenceDiagram - participant C as MCP Client - participant GW as Gateway (RMCP) - participant BT as BackendTransports
(local process state) - participant LU as LocalUserSessionStore
(LRU 50k / 1h) - participant BA as Backend A - participant BB as Backend B - - C->>GW: POST initialize (Mcp-Session-Id: S) - GW->>BA: initialize (concurrent) - GW->>BB: initialize (concurrent) - BA-->>GW: InitializeResult - BB-->>GW: InitializeResult - GW->>BT: store RunningService keyed by sub+backend+S - GW->>LU: store session entry for sub+S - GW-->>C: merged InitializeResult - - C->>GW: POST call_tool (Mcp-Session-Id: S) - GW->>BT: lookup sub+backend+S → Arc - BT-->>GW: RunningService handle - GW->>BA: call_tool (routed by name prefix) - BA-->>GW: ToolResult - GW-->>C: ToolResult - - C->>GW: DELETE (Mcp-Session-Id: S) - GW->>GW: RMCP handles DELETE - GW->>LU: remove sub+S entry - GW->>BT: remove all sub+*+S entries - GW-->>C: 200 OK -``` +Protected headers that config can never touch: `Host`, `Content-Length`, `Content-Type`, all RFC 7230 hop-by-hop headers, `Mcp-Session-Id`, `Accept`, `Last-Event-Id`, and all computed MCP standard headers (`Mcp-Method`, `Mcp-Name`, `Mcp-Protocol-Version`, `Mcp-Param-*`). -## Capability Merge +For clients on `≥ 2026-07-28`, `call_tool` validates `Mcp-Param-*` headers against `BackendMCPGateway::tool_schemas` before contacting the backend. -On `initialize`, the gateway builds one downstream `InitializeResult` — not a passthrough of any one backend. The source of truth is each backend's `InitializeResult`; the gateway reads `peer_info().capabilities` from each running service and stores them with the backend transport state. +## Plugin hooks -The merge rule (gateway-aware, not a raw union): -- Enable a top-level capability when ≥1 backend supports it **and** the gateway has a routing story for it. -- `resources.subscribe` preserved if any backend advertises it (the gateway routes subscribe/unsubscribe and forwards resource-update notifications). -- `listChanged` not yet advertised (gateway doesn't emit downstream list-changed notifications when upstream lists change). -- Single-backend passthrough is not a stable contract (`HashMap` iteration order). -- If no backend reports supported capabilities, returns `ServerCapabilities::default()`. - -**Do not** initialize the downstream capability from just one backend entry — the gateway fronts multiple backends, `HashMap` iteration is non-deterministic, and list methods already merge across all backends. - -## Cleanup - -`DELETE` with `Mcp-session-id`: -```text -→ RMCP handles request -→ on success: remove LocalUserSessionStore entry + BackendTransports entries for principal+session -``` -If RMCP rejects the delete, local state is untouched. - - -## MCP Method Quick Reference - -| Method | Group | Behavior | -| --- | --- | --- | -| `initialize` | Session | Concurrent fanout to all backends; failure of one backend is non-fatal (stored with no service). Returns merged capability set. Requires `DownstreamSessionId`, `UserConfig`, `VirtualHostId`, `ContextForgeClaims`. | -| `list_tools` | List | Fan-out all connected backends → merged sorted result. Cursor-based pagination across backends. | -| `list_resources` | List | Same as list_tools. | -| `list_prompts` | List | Same as list_tools. | -| `list_resource_templates` | List | Same — both name and URI template get prefixed for multi-backend. | -| `call_tool` | Targeted | **Per-request backend lifecycle:** creates fresh connection via `connect_backend_for_request`, runs pre-hook, executes call, runs post-hook, closes connection. Resolves alias → single/multi-backend fallback. Forwards downstream cancellation to backend. Tracks backend progress tokens: RMCP assigns a new token per backend request; the gateway maps each backend token to the downstream token. Request enqueue and mapping publication are serialized against progress lookup so an immediate backend notification cannot overtake registration. When the notification matches an in-flight token, the gateway restores the downstream token and forwards it to the client. Does not use session-backed `BackendTransports`. | -| `read_resource` | Targeted | Single-backend: URI unchanged. Multi-backend: strips prefix. | -| `subscribe` / `unsubscribe` | Targeted | Same resource-URI routing; forwards/stops resource-update notifications. | -| `get_prompt` | Targeted | Single-backend: name unchanged. Multi-backend: strips prefix. Runs pre/post prompt hooks around the backend call: the pre hook may rewrite arguments or deny, the post hook may rewrite or reject the rendered messages. | -| `complete` | Targeted | Routes on prompt name or resource URI inside `ref`. | -| `ping` | Local | Returns success; no backend fanout. | -| `DELETE` | Session | RMCP handles first; on success `session_id_layer` removes local session + backend transports. | +`call_tool` and `get_prompt` run `before_*/after_*` hooks when a `GatewayPluginRuntimeHandle` is configured. Pre-hook may rewrite arguments or deny; post-hook may rewrite or reject the response. Pre-hook state is passed to the post-hook. From 0eedda858c17097103bc1402f9ab255bffa3b833 Mon Sep 17 00:00:00 2001 From: cafalchio Date: Tue, 1 Sep 2026 16:23:21 +0100 Subject: [PATCH 12/20] Removed NameAlias not needed Signed-off-by: cafalchio --- .../src/user_store.rs | 42 +---------------- .../src/gateway/mcp_service/initialization.rs | 5 -- .../tests/gateway_pagination.rs | 8 +--- .../tests/support/plugin_gateway.rs | 16 +------ .../tests/support/test_gateways.rs | 46 ++++--------------- schemas/user_config.json | 41 +---------------- 6 files changed, 13 insertions(+), 145 deletions(-) diff --git a/crates/contextforge-data-plane-apis/src/user_store.rs b/crates/contextforge-data-plane-apis/src/user_store.rs index b2d639f..e9346bf 100644 --- a/crates/contextforge-data-plane-apis/src/user_store.rs +++ b/crates/contextforge-data-plane-apis/src/user_store.rs @@ -1,4 +1,4 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -12,40 +12,6 @@ pub enum IntegrationType { Mcp, } -#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, Default, Eq)] -pub struct NameAlias { - downstream_prefixed_name: String, - upstream_name: String, -} - -impl PartialEq for NameAlias { - fn eq(&self, other: &Self) -> bool { - self.downstream_prefixed_name == other.downstream_prefixed_name - } -} - -impl std::hash::Hash for NameAlias { - fn hash(&self, state: &mut H) { - self.downstream_prefixed_name.hash(state); - } -} - -impl NameAlias { - pub fn new(downstream_prefixed_name: String, upstream_name: String) -> Self { - Self { downstream_prefixed_name, upstream_name } - } - pub fn with_downstream_prefixed_name(downstream_prefixed_name: String) -> Self { - NameAlias { downstream_prefixed_name, upstream_name: String::new() } - } - pub fn get_upstream_name(&self) -> &str { - &self.upstream_name - } - - pub fn get_downstream_prefixed_name(&self) -> &str { - &self.downstream_prefixed_name - } -} - #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] pub struct BackendMCPGateway { pub name: String, @@ -60,12 +26,6 @@ pub struct BackendMCPGateway { #[serde(default)] pub remove_headers: Vec, #[serde(default)] - pub tool_name_aliases: HashSet, - #[serde(default)] - pub resource_uri_aliases: HashSet, - #[serde(default)] - pub prompt_name_aliases: HashSet, - #[serde(default)] pub completion: HashMap, /// Input schemas keyed by the original upstream tool name. pub tool_schemas: HashMap>, 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 53b68c9..9096475 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 @@ -167,8 +167,6 @@ fn is_protected_header(name: &http::HeaderName) -> bool { #[cfg(test)] mod tests { - use std::collections::HashSet; - use super::*; fn backend(passthrough: &[&str], add: &[(&str, &str)], remove: &[&str]) -> BackendMCPGateway { @@ -180,9 +178,6 @@ mod tests { add_headers: add.iter().map(|(k, v)| ((*k).to_owned(), (*v).to_owned())).collect(), remove_headers: remove.iter().map(|s| (*s).to_owned()).collect(), tool_schemas: HashMap::new(), - tool_name_aliases: HashSet::new(), - resource_uri_aliases: HashSet::new(), - prompt_name_aliases: HashSet::new(), completion: HashMap::new(), } } diff --git a/crates/contextforge-data-plane-lib/tests/gateway_pagination.rs b/crates/contextforge-data-plane-lib/tests/gateway_pagination.rs index ad2ebbf..246ce10 100644 --- a/crates/contextforge-data-plane-lib/tests/gateway_pagination.rs +++ b/crates/contextforge-data-plane-lib/tests/gateway_pagination.rs @@ -1,9 +1,6 @@ mod support; -use std::{ - collections::{HashMap, HashSet}, - sync::Arc, -}; +use std::{collections::HashMap, sync::Arc}; use contextforge_data_plane_apis::{ User, @@ -32,9 +29,6 @@ fn paginating_backend(port: u16) -> BackendMCPGateway { add_headers: HashMap::new(), remove_headers: Vec::new(), tool_schemas: HashMap::new(), - tool_name_aliases: HashSet::new(), - resource_uri_aliases: HashSet::new(), - prompt_name_aliases: HashSet::new(), completion: HashMap::new(), } } diff --git a/crates/contextforge-data-plane-lib/tests/support/plugin_gateway.rs b/crates/contextforge-data-plane-lib/tests/support/plugin_gateway.rs index dbb50f1..14677d9 100644 --- a/crates/contextforge-data-plane-lib/tests/support/plugin_gateway.rs +++ b/crates/contextforge-data-plane-lib/tests/support/plugin_gateway.rs @@ -1,12 +1,12 @@ use std::{ - collections::{HashMap, HashSet}, + collections::HashMap, sync::{Arc, Mutex as StdMutex, OnceLock}, time::{Duration, Instant}, }; use contextforge_data_plane_apis::{ User, - user_store::{BackendMCPGateway, NameAlias, UserConfig, VirtualHost}, + user_store::{BackendMCPGateway, UserConfig, VirtualHost}, }; use contextforge_data_plane_cpex::CpexRuntimeRegistry; use contextforge_data_plane_lib::{Config, Gateway, UpstreamConnectionMode, UserConfigStore, UserConfigStoreType}; @@ -400,18 +400,6 @@ async fn start_gateway_with_state( add_headers: HashMap::default(), remove_headers: Vec::new(), tool_schemas: published_tool_schemas(parameter_headers), - tool_name_aliases: TOOL_NAMES - .iter() - .map(|n| NameAlias::new(n.to_string(), n.to_string())) - .collect(), - resource_uri_aliases: RESOURCE_URIS - .iter() - .map(|n| NameAlias::new(n.to_string(), n.to_string())) - .collect(), - prompt_name_aliases: PROMPT_NAMES - .iter() - .map(|n| NameAlias::new(n.to_string(), n.to_string())) - .collect(), completion: HashMap::new(), }, )]), diff --git a/crates/contextforge-data-plane-lib/tests/support/test_gateways.rs b/crates/contextforge-data-plane-lib/tests/support/test_gateways.rs index e00e5a9..64fabaa 100644 --- a/crates/contextforge-data-plane-lib/tests/support/test_gateways.rs +++ b/crates/contextforge-data-plane-lib/tests/support/test_gateways.rs @@ -1,11 +1,8 @@ -use std::{ - collections::{HashMap, HashSet}, - sync::Arc, -}; +use std::{collections::HashMap, sync::Arc}; use contextforge_data_plane_apis::{ User, - user_store::{BackendMCPGateway, NameAlias, ServiceRoute, UserConfig, VirtualHost}, + user_store::{BackendMCPGateway, ServiceRoute, UserConfig, VirtualHost}, }; use contextforge_data_plane_lib::{ Config, Gateway, Result, UpstreamConnectionMode, UserConfigStore, UserConfigStoreType, @@ -58,17 +55,12 @@ pub(crate) fn create_ports(ports: usize) -> Vec { } pub fn construct_services(backend_name: &str, service_names: &[&str]) -> HashMap { - let aliases: HashSet = - service_names.iter().map(|n| NameAlias::new(n.to_string(), n.to_string())).collect(); - let mut result: HashMap = HashMap::new(); - - for alias in aliases { - result.insert( - alias.get_downstream_prefixed_name().to_owned(), - ServiceRoute { backend_name: backend_name.to_owned(), upstream_name: alias.get_upstream_name().to_owned() }, - ); - } - result + service_names + .iter() + .map(|&name| { + (name.to_owned(), ServiceRoute { backend_name: backend_name.to_owned(), upstream_name: name.to_owned() }) + }) + .collect() } fn create_services_from_ports(ports: &[u16], service_names: &[&str]) -> HashMap { @@ -236,28 +228,6 @@ fn create_backends( .iter() .map(|name| ((*name).to_owned(), serde_json::Map::new())) .collect(), - tool_name_aliases: MOCK_COUNTER_TOOL_NAMES - .iter() - .map(|tool_name| { - let backend_id = backend_id.clone(); - NameAlias::new(format!("{backend_id}-{tool_name}"), tool_name.to_string()) - }) - .collect(), - - resource_uri_aliases: MOCK_COUNTER_RESOURCE_URIS - .iter() - .map(|resource_uri| { - let backend_id = backend_id.clone(); - NameAlias::new(format!("{backend_id}-{resource_uri}"), resource_uri.to_string()) - }) - .collect(), - prompt_name_aliases: MOCK_COUNTER_PROMPT_NAMES - .iter() - .map(|prompt_name| { - let backend_id = backend_id.clone(); - NameAlias::new(format!("{backend_id}-{prompt_name}"), prompt_name.to_string()) - }) - .collect(), completion: HashMap::new(), }, diff --git a/schemas/user_config.json b/schemas/user_config.json index 38869f2..4f793b0 100644 --- a/schemas/user_config.json +++ b/schemas/user_config.json @@ -64,30 +64,6 @@ }, "default": [] }, - "tool_name_aliases": { - "type": "array", - "uniqueItems": true, - "items": { - "$ref": "#/$defs/NameAlias" - }, - "default": [] - }, - "resource_uri_aliases": { - "type": "array", - "uniqueItems": true, - "items": { - "$ref": "#/$defs/NameAlias" - }, - "default": [] - }, - "prompt_name_aliases": { - "type": "array", - "uniqueItems": true, - "items": { - "$ref": "#/$defs/NameAlias" - }, - "default": [] - }, "completion": { "type": "object", "additionalProperties": { @@ -115,21 +91,6 @@ "ProtocolVersion": { "description": "Represents the MCP protocol version used for communication.\n\nThis ensures compatibility between clients and servers by specifying\nwhich version of the Model Context Protocol is being used.", "type": "string" - }, - "NameAlias": { - "type": "object", - "properties": { - "downstream_prefixed_name": { - "type": "string" - }, - "upstream_name": { - "type": "string" - } - }, - "required": [ - "downstream_prefixed_name", - "upstream_name" - ] - } + } } } From d2396c78e8ec9f8487d3984b992d29089a5e933e Mon Sep 17 00:00:00 2001 From: cafalchio Date: Tue, 1 Sep 2026 16:48:46 +0100 Subject: [PATCH 13/20] removed unused code Signed-off-by: cafalchio --- .../contextforge-data-plane/tests/secrets_detection_e2e.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/contextforge-data-plane/tests/secrets_detection_e2e.rs b/crates/contextforge-data-plane/tests/secrets_detection_e2e.rs index 9fc6e27..b19bf23 100644 --- a/crates/contextforge-data-plane/tests/secrets_detection_e2e.rs +++ b/crates/contextforge-data-plane/tests/secrets_detection_e2e.rs @@ -4,7 +4,7 @@ #![cfg(feature = "plugins")] use std::{ - collections::{HashMap, HashSet}, + collections::HashMap, fs, net::TcpStream as StdTcpStream, path::PathBuf, @@ -385,9 +385,6 @@ async fn write_redis_config(redis_port: u16, backend: &RunningBackend) { ("sum".to_owned(), Map::new()), ("reflect_text".to_owned(), Map::new()), ]), - tool_name_aliases: HashSet::new(), - resource_uri_aliases: HashSet::new(), - prompt_name_aliases: HashSet::new(), completion: HashMap::new(), }, )]), From 2ea8795e1b9f872ea8a165604e9adc91f976a98b Mon Sep 17 00:00:00 2001 From: cafalchio Date: Wed, 2 Sep 2026 11:24:01 +0100 Subject: [PATCH 14/20] Added type alias for clarity Signed-off-by: cafalchio --- .../contextforge-data-plane-apis/src/user_store.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/crates/contextforge-data-plane-apis/src/user_store.rs b/crates/contextforge-data-plane-apis/src/user_store.rs index e9346bf..a3e52a8 100644 --- a/crates/contextforge-data-plane-apis/src/user_store.rs +++ b/crates/contextforge-data-plane-apis/src/user_store.rs @@ -37,17 +37,22 @@ pub struct ServiceRoute { pub upstream_name: String, } +pub type DownstreamToolName = String; +pub type DownstreamResourceName = String; +pub type DownstreamResourceTemplateName = String; +pub type DownstreamPromptName = String; + #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] pub struct VirtualHost { pub backends: HashMap, #[serde(default)] - pub tools: HashMap, + pub tools: HashMap, #[serde(default)] - pub resources: HashMap, + pub resources: HashMap, #[serde(default)] - pub resource_templates: HashMap, + pub resource_templates: HashMap, #[serde(default)] - pub prompts: HashMap, + pub prompts: HashMap, } #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] From 02ac728ba5e40079a0a9b9d81d4700cbfecdbffe Mon Sep 17 00:00:00 2001 From: cafalchio Date: Wed, 2 Sep 2026 14:17:27 +0100 Subject: [PATCH 15/20] Added missing type alias for backend Signed-off-by: cafalchio --- crates/contextforge-data-plane-apis/src/user_store.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/contextforge-data-plane-apis/src/user_store.rs b/crates/contextforge-data-plane-apis/src/user_store.rs index a3e52a8..2de946a 100644 --- a/crates/contextforge-data-plane-apis/src/user_store.rs +++ b/crates/contextforge-data-plane-apis/src/user_store.rs @@ -37,6 +37,7 @@ pub struct ServiceRoute { pub upstream_name: String, } +pub type DownstreamBackendName = String; pub type DownstreamToolName = String; pub type DownstreamResourceName = String; pub type DownstreamResourceTemplateName = String; @@ -44,7 +45,7 @@ pub type DownstreamPromptName = String; #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] pub struct VirtualHost { - pub backends: HashMap, + pub backends: HashMap, #[serde(default)] pub tools: HashMap, #[serde(default)] From d327d15f4cabb70fbbe42c78cb513dc52326df9d Mon Sep 17 00:00:00 2001 From: cafalchio Date: Wed, 2 Sep 2026 14:22:15 +0100 Subject: [PATCH 16/20] Added VirtualHostId Signed-off-by: cafalchio --- .../src/user_store.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/contextforge-data-plane-apis/src/user_store.rs b/crates/contextforge-data-plane-apis/src/user_store.rs index 2de946a..3c5cc69 100644 --- a/crates/contextforge-data-plane-apis/src/user_store.rs +++ b/crates/contextforge-data-plane-apis/src/user_store.rs @@ -3,6 +3,13 @@ use std::collections::HashMap; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +pub type DownstreamBackendName = String; +pub type DownstreamToolName = String; +pub type DownstreamResourceName = String; +pub type DownstreamResourceTemplateName = String; +pub type DownstreamPromptName = String; +pub type VirtualHostId = String; + #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, Default)] pub enum IntegrationType { #[serde(rename = "REST")] @@ -37,12 +44,6 @@ pub struct ServiceRoute { pub upstream_name: String, } -pub type DownstreamBackendName = String; -pub type DownstreamToolName = String; -pub type DownstreamResourceName = String; -pub type DownstreamResourceTemplateName = String; -pub type DownstreamPromptName = String; - #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] pub struct VirtualHost { pub backends: HashMap, @@ -58,5 +59,5 @@ pub struct VirtualHost { #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] pub struct UserConfig { - pub virtual_hosts: HashMap, + pub virtual_hosts: HashMap, } From 3f04483bcae34c7fbb0b627cbe6eda535cf7e3c6 Mon Sep 17 00:00:00 2001 From: cafalchio Date: Wed, 2 Sep 2026 14:40:19 +0100 Subject: [PATCH 17/20] Fixed typo downstream Signed-off-by: cafalchio --- .../src/gateway/mcp_service/resources.rs | 4 ++-- .../src/gateway/mcp_service/tools.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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 bf538aa..3633da6 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 @@ -20,9 +20,9 @@ pub(super) async fn read_resource( ) -> Result { let mcp_call_validator = AuthorizedCallValidator::new("read_resource", &cx); let (virtual_host, _claims) = mcp_call_validator.validate_stateless()?; - let dowstream_name = request.uri.clone(); + let downstream_name = request.uri.clone(); - let Some(route) = virtual_host.resources.get(&dowstream_name) else { + let Some(route) = virtual_host.resources.get(&downstream_name) else { return Err(ErrorData { code: ErrorCode::INVALID_PARAMS, message: "Routing problem... resource not found".into(), diff --git a/crates/contextforge-data-plane-lib/src/gateway/mcp_service/tools.rs b/crates/contextforge-data-plane-lib/src/gateway/mcp_service/tools.rs index b0cb1da..4ef618f 100644 --- a/crates/contextforge-data-plane-lib/src/gateway/mcp_service/tools.rs +++ b/crates/contextforge-data-plane-lib/src/gateway/mcp_service/tools.rs @@ -22,8 +22,8 @@ pub(super) async fn call_tool( let mcp_call_validator = AuthorizedCallValidator::new("call_tool", &cx); let (virtual_host, _claims) = mcp_call_validator.validate_stateless()?; - let dowstream_name = request.name.to_string(); - let Some(route) = virtual_host.tools.get(&dowstream_name) else { + let downstream_name = request.name.to_string(); + let Some(route) = virtual_host.tools.get(&downstream_name) else { return Err(ErrorData { code: ErrorCode::INVALID_PARAMS, message: "Routing problem... tool not found".into(), From 806153cd97ea95cbf4b74b7d330b76d6aaf34f50 Mon Sep 17 00:00:00 2001 From: cafalchio Date: Wed, 2 Sep 2026 16:27:08 +0100 Subject: [PATCH 18/20] added DownstreamBackendName UpstreamName to ServiceRoute Signed-off-by: cafalchio --- crates/contextforge-data-plane-apis/src/user_store.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/contextforge-data-plane-apis/src/user_store.rs b/crates/contextforge-data-plane-apis/src/user_store.rs index 3c5cc69..fec80f1 100644 --- a/crates/contextforge-data-plane-apis/src/user_store.rs +++ b/crates/contextforge-data-plane-apis/src/user_store.rs @@ -8,6 +8,7 @@ pub type DownstreamToolName = String; pub type DownstreamResourceName = String; pub type DownstreamResourceTemplateName = String; pub type DownstreamPromptName = String; +pub type UpstreamName = String; pub type VirtualHostId = String; #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, Default)] @@ -40,8 +41,8 @@ pub struct BackendMCPGateway { #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] pub struct ServiceRoute { - pub backend_name: String, - pub upstream_name: String, + pub backend_name: DownstreamBackendName, + pub upstream_name: UpstreamName, } #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] From 484e5fbe651a1f0284e901caf9e38ae04cbd15aa Mon Sep 17 00:00:00 2001 From: cafalchio Date: Wed, 2 Sep 2026 16:39:49 +0100 Subject: [PATCH 19/20] Ran secrets Signed-off-by: cafalchio --- .secrets.baseline | 46 ++-------------------------------------------- schemas/user.json | 2 +- 2 files changed, 3 insertions(+), 45 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index 6eeb6e1..acce4a0 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -1,9 +1,9 @@ { "exclude": { - "files": "(?x)(Cargo\\.lock$|\\.lock$)|^\\.secrets\\.baseline$|^.secrets.baseline$", + "files": "(?x)(Cargo\\.lock$|\\.lock$)|^\\.secrets\\.baseline$", "lines": null }, - "generated_at": "2026-08-26T14:21:23Z", + "generated_at": "2026-09-02T15:38:21Z", "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": 20, "type": "Hex High Entropy String", @@ -206,7 +193,6 @@ "crates/plugins/cpex-secrets-detection/src/lib.rs": [ { "hashed_secret": "86de8c52637ec530fe39b0a8471da9b8764d5242", - "is_secret": false, "is_verified": false, "line_number": 610, "type": "AWS Access Key", @@ -216,7 +202,6 @@ "crates/plugins/cpex-secrets-detection/src/scanner.rs": [ { "hashed_secret": "9249e2590f5d19742260cb5296cb76fe0677f147", - "is_secret": false, "is_verified": false, "line_number": 238, "type": "Secret Keyword", @@ -224,7 +209,6 @@ }, { "hashed_secret": "199da8f71b7dced64f82cf6e96483134cace9b14", - "is_secret": false, "is_verified": false, "line_number": 239, "type": "Secret Keyword", @@ -232,7 +216,6 @@ }, { "hashed_secret": "c0026c4c848882618c987859077ffbae92130625", - "is_secret": false, "is_verified": false, "line_number": 242, "type": "Secret Keyword", @@ -240,7 +223,6 @@ }, { "hashed_secret": "078553dc10635837abb80f404302c70cba91b879", - "is_secret": false, "is_verified": false, "line_number": 278, "type": "Base64 High Entropy String", @@ -248,7 +230,6 @@ }, { "hashed_secret": "e175c6f5f2a92e8623bd9a4820edb4e8c1b0fd10", - "is_secret": false, "is_verified": false, "line_number": 278, "type": "GitHub Token", @@ -256,7 +237,6 @@ }, { "hashed_secret": "97d99a51e5ac827bb36fe6273facfda35245917a", - "is_secret": false, "is_verified": false, "line_number": 279, "type": "Base64 High Entropy String", @@ -264,7 +244,6 @@ }, { "hashed_secret": "be4fc4886bd949b369d5e092eb87494f12e57e5b", - "is_secret": false, "is_verified": false, "line_number": 282, "type": "Private Key", @@ -272,7 +251,6 @@ }, { "hashed_secret": "b1775a785f09a6ebaf2dc33d6eaeb98974d9cdb8", - "is_secret": false, "is_verified": false, "line_number": 284, "type": "Hex High Entropy String", @@ -280,7 +258,6 @@ }, { "hashed_secret": "eae9124e42e2ef05ba727bd1a1c0c6fa61a05b9e", - "is_secret": false, "is_verified": false, "line_number": 302, "type": "Secret Keyword", @@ -288,7 +265,6 @@ }, { "hashed_secret": "86de8c52637ec530fe39b0a8471da9b8764d5242", - "is_secret": false, "is_verified": false, "line_number": 401, "type": "AWS Access Key", @@ -296,7 +272,6 @@ }, { "hashed_secret": "9d7235fe33b6612ed7ebca4b63afd00d4adf5d66", - "is_secret": false, "is_verified": false, "line_number": 410, "type": "Secret Keyword", @@ -304,7 +279,6 @@ }, { "hashed_secret": "27a39044bff80a4c196689dfa8dcf129cb27fef8", - "is_secret": false, "is_verified": false, "line_number": 431, "type": "Base64 High Entropy String", @@ -314,7 +288,6 @@ "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", @@ -322,7 +295,6 @@ }, { "hashed_secret": "8b4510a576d82f38bd2730436bf5e20c4e15b30e", - "is_secret": false, "is_verified": false, "line_number": 198, "type": "AWS Access Key", @@ -330,7 +302,6 @@ }, { "hashed_secret": "e4ea017859bcad962c8ab551fe29da9147877eee", - "is_secret": false, "is_verified": false, "line_number": 199, "type": "AWS Access Key", @@ -338,7 +309,6 @@ }, { "hashed_secret": "86de8c52637ec530fe39b0a8471da9b8764d5242", - "is_secret": false, "is_verified": false, "line_number": 268, "type": "AWS Access Key", @@ -348,7 +318,6 @@ "docker/docker-compose-langfuse.yaml": [ { "hashed_secret": "cb1fde0682fbd1ac0faf2a9f297167ac9d06434b", - "is_secret": false, "is_verified": false, "line_number": 16, "type": "Secret Keyword", @@ -356,7 +325,6 @@ }, { "hashed_secret": "cb58df830a45cc33df1a313e616ecad78cd796c5", - "is_secret": false, "is_verified": false, "line_number": 77, "type": "Secret Keyword", @@ -364,7 +332,6 @@ }, { "hashed_secret": "2e0c522bfe4e7885492862df2e0b987c0ca02623", - "is_secret": false, "is_verified": false, "line_number": 100, "type": "Secret Keyword", @@ -372,7 +339,6 @@ }, { "hashed_secret": "d9d007c8de197b3f36a3a0ba4f13c0f7df175d5a", - "is_secret": false, "is_verified": false, "line_number": 255, "type": "Secret Keyword", @@ -382,7 +348,6 @@ "docker/docker-compose.yml": [ { "hashed_secret": "2a8bfc0ce436d55ca907d0162989481bcb7677b4", - "is_secret": false, "is_verified": false, "line_number": 189, "type": "Secret Keyword", @@ -390,7 +355,6 @@ }, { "hashed_secret": "fdda45b7f6d2ead95d9991fc4678640c3bab0d84", - "is_secret": false, "is_verified": false, "line_number": 363, "type": "Secret Keyword", @@ -398,7 +362,6 @@ }, { "hashed_secret": "093d378410a5cfa4bd5088f3fef62fbdb8a95665", - "is_secret": false, "is_verified": false, "line_number": 369, "type": "Secret Keyword", @@ -406,7 +369,6 @@ }, { "hashed_secret": "c3de40d5e3fc71ed62771c2127a8e42585026c97", - "is_secret": false, "is_verified": false, "line_number": 371, "type": "Secret Keyword", @@ -414,7 +376,6 @@ }, { "hashed_secret": "4d4acd9b084d13f5fdb23807d857e1c48a1cfd0f", - "is_secret": false, "is_verified": false, "line_number": 460, "type": "Secret Keyword", @@ -422,7 +383,6 @@ }, { "hashed_secret": "bd0160c2cf35d950843c88f3be2b9412ed71f485", - "is_secret": false, "is_verified": false, "line_number": 495, "type": "Secret Keyword", @@ -430,7 +390,6 @@ }, { "hashed_secret": "293324f6824bb3a6db5c4dc42a60ddd4a9851c99", - "is_secret": false, "is_verified": false, "line_number": 658, "type": "Hex High Entropy String", @@ -440,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", diff --git a/schemas/user.json b/schemas/user.json index 0144f02..78176e5 100644 --- a/schemas/user.json +++ b/schemas/user.json @@ -22,4 +22,4 @@ ] } } -} \ No newline at end of file +} From d2a12e9478c5af49a9e8f15bec1d3e9686816da8 Mon Sep 17 00:00:00 2001 From: cafalchio Date: Wed, 2 Sep 2026 16:46:43 +0100 Subject: [PATCH 20/20] secrets Signed-off-by: cafalchio --- .secrets.baseline | 130 ++++++++++++++++++++++++++++++---------------- 1 file changed, 86 insertions(+), 44 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index acce4a0..4d8c4e2 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "(?x)(Cargo\\.lock$|\\.lock$)|^\\.secrets\\.baseline$", "lines": null }, - "generated_at": "2026-09-02T15:38:21Z", + "generated_at": "2026-09-02T15:42:38Z", "plugins_used": [ { "name": "AWSKeyDetector" @@ -83,7 +83,8 @@ "is_verified": false, "line_number": 1, "type": "Private Key", - "verified_result": null + "verified_result": null, + "is_secret": false } ], "assets/contextforgeCA/contextforge-server.key.pem": [ @@ -92,7 +93,8 @@ "is_verified": false, "line_number": 1, "type": "Private Key", - "verified_result": null + "verified_result": null, + "is_secret": false } ], "assets/contextforgeCA/contextforge.ca.key.pem": [ @@ -101,7 +103,8 @@ "is_verified": false, "line_number": 1, "type": "Private Key", - "verified_result": null + "verified_result": null, + "is_secret": false } ], "assets/contextforgeCA/contextforge.intermediate.key.pem": [ @@ -110,7 +113,8 @@ "is_verified": false, "line_number": 1, "type": "Private Key", - "verified_result": null + "verified_result": null, + "is_secret": false } ], "assets/jwt.key": [ @@ -119,7 +123,8 @@ "is_verified": false, "line_number": 1, "type": "Private Key", - "verified_result": null + "verified_result": null, + "is_secret": false } ], "assets/tls_key.pem": [ @@ -128,7 +133,8 @@ "is_verified": false, "line_number": 1, "type": "Private Key", - "verified_result": null + "verified_result": null, + "is_secret": false } ], "crates/contextforge-data-plane-lib/src/common.rs": [ @@ -137,21 +143,24 @@ "is_verified": false, "line_number": 154, "type": "Secret Keyword", - "verified_result": null + "verified_result": null, + "is_secret": false }, { "hashed_secret": "427f5e1b530d4a544883308d876a11d724060c86", "is_verified": false, "line_number": 157, "type": "Secret Keyword", - "verified_result": null + "verified_result": null, + "is_secret": false }, { "hashed_secret": "bfc6000db1195a9522813fc405c666dd4ce669ad", "is_verified": false, "line_number": 263, "type": "Secret Keyword", - "verified_result": null + "verified_result": null, + "is_secret": false } ], "crates/contextforge-data-plane-lib/src/telemetry.rs": [ @@ -160,7 +169,8 @@ "is_verified": false, "line_number": 87, "type": "Hex High Entropy String", - "verified_result": null + "verified_result": null, + "is_secret": false } ], "crates/contextforge-data-plane-lib/tests/support/client.rs": [ @@ -169,7 +179,8 @@ "is_verified": false, "line_number": 12, "type": "Secret Keyword", - "verified_result": null + "verified_result": null, + "is_secret": false } ], "crates/contextforge-data-plane-lib/tests/support/mod.rs": [ @@ -178,7 +189,8 @@ "is_verified": false, "line_number": 17, "type": "Secret Keyword", - "verified_result": null + "verified_result": null, + "is_secret": false } ], "crates/contextforge-data-plane/Cargo.toml": [ @@ -187,7 +199,8 @@ "is_verified": false, "line_number": 20, "type": "Hex High Entropy String", - "verified_result": null + "verified_result": null, + "is_secret": false } ], "crates/plugins/cpex-secrets-detection/src/lib.rs": [ @@ -196,7 +209,8 @@ "is_verified": false, "line_number": 610, "type": "AWS Access Key", - "verified_result": null + "verified_result": null, + "is_secret": false } ], "crates/plugins/cpex-secrets-detection/src/scanner.rs": [ @@ -205,84 +219,96 @@ "is_verified": false, "line_number": 238, "type": "Secret Keyword", - "verified_result": null + "verified_result": null, + "is_secret": false }, { "hashed_secret": "199da8f71b7dced64f82cf6e96483134cace9b14", "is_verified": false, "line_number": 239, "type": "Secret Keyword", - "verified_result": null + "verified_result": null, + "is_secret": false }, { "hashed_secret": "c0026c4c848882618c987859077ffbae92130625", "is_verified": false, "line_number": 242, "type": "Secret Keyword", - "verified_result": null + "verified_result": null, + "is_secret": false }, { "hashed_secret": "078553dc10635837abb80f404302c70cba91b879", "is_verified": false, "line_number": 278, "type": "Base64 High Entropy String", - "verified_result": null + "verified_result": null, + "is_secret": false }, { "hashed_secret": "e175c6f5f2a92e8623bd9a4820edb4e8c1b0fd10", "is_verified": false, "line_number": 278, "type": "GitHub Token", - "verified_result": null + "verified_result": null, + "is_secret": false }, { "hashed_secret": "97d99a51e5ac827bb36fe6273facfda35245917a", "is_verified": false, "line_number": 279, "type": "Base64 High Entropy String", - "verified_result": null + "verified_result": null, + "is_secret": false }, { "hashed_secret": "be4fc4886bd949b369d5e092eb87494f12e57e5b", "is_verified": false, "line_number": 282, "type": "Private Key", - "verified_result": null + "verified_result": null, + "is_secret": false }, { "hashed_secret": "b1775a785f09a6ebaf2dc33d6eaeb98974d9cdb8", "is_verified": false, "line_number": 284, "type": "Hex High Entropy String", - "verified_result": null + "verified_result": null, + "is_secret": false }, { "hashed_secret": "eae9124e42e2ef05ba727bd1a1c0c6fa61a05b9e", "is_verified": false, "line_number": 302, "type": "Secret Keyword", - "verified_result": null + "verified_result": null, + "is_secret": false }, { "hashed_secret": "86de8c52637ec530fe39b0a8471da9b8764d5242", "is_verified": false, "line_number": 401, "type": "AWS Access Key", - "verified_result": null + "verified_result": null, + "is_secret": false }, { "hashed_secret": "9d7235fe33b6612ed7ebca4b63afd00d4adf5d66", "is_verified": false, "line_number": 410, "type": "Secret Keyword", - "verified_result": null + "verified_result": null, + "is_secret": false }, { "hashed_secret": "27a39044bff80a4c196689dfa8dcf129cb27fef8", "is_verified": false, "line_number": 431, "type": "Base64 High Entropy String", - "verified_result": null + "verified_result": null, + "is_secret": false } ], "crates/plugins/cpex-secrets-detection/tests/plugin_manager.rs": [ @@ -291,28 +317,32 @@ "is_verified": false, "line_number": 197, "type": "AWS Access Key", - "verified_result": null + "verified_result": null, + "is_secret": false }, { "hashed_secret": "8b4510a576d82f38bd2730436bf5e20c4e15b30e", "is_verified": false, "line_number": 198, "type": "AWS Access Key", - "verified_result": null + "verified_result": null, + "is_secret": false }, { "hashed_secret": "e4ea017859bcad962c8ab551fe29da9147877eee", "is_verified": false, "line_number": 199, "type": "AWS Access Key", - "verified_result": null + "verified_result": null, + "is_secret": false }, { "hashed_secret": "86de8c52637ec530fe39b0a8471da9b8764d5242", "is_verified": false, "line_number": 268, "type": "AWS Access Key", - "verified_result": null + "verified_result": null, + "is_secret": false } ], "docker/docker-compose-langfuse.yaml": [ @@ -321,28 +351,32 @@ "is_verified": false, "line_number": 16, "type": "Secret Keyword", - "verified_result": null + "verified_result": null, + "is_secret": false }, { "hashed_secret": "cb58df830a45cc33df1a313e616ecad78cd796c5", "is_verified": false, "line_number": 77, "type": "Secret Keyword", - "verified_result": null + "verified_result": null, + "is_secret": false }, { "hashed_secret": "2e0c522bfe4e7885492862df2e0b987c0ca02623", "is_verified": false, "line_number": 100, "type": "Secret Keyword", - "verified_result": null + "verified_result": null, + "is_secret": false }, { "hashed_secret": "d9d007c8de197b3f36a3a0ba4f13c0f7df175d5a", "is_verified": false, "line_number": 255, "type": "Secret Keyword", - "verified_result": null + "verified_result": null, + "is_secret": false } ], "docker/docker-compose.yml": [ @@ -351,49 +385,56 @@ "is_verified": false, "line_number": 189, "type": "Secret Keyword", - "verified_result": null + "verified_result": null, + "is_secret": false }, { "hashed_secret": "fdda45b7f6d2ead95d9991fc4678640c3bab0d84", "is_verified": false, "line_number": 363, "type": "Secret Keyword", - "verified_result": null + "verified_result": null, + "is_secret": false }, { "hashed_secret": "093d378410a5cfa4bd5088f3fef62fbdb8a95665", "is_verified": false, "line_number": 369, "type": "Secret Keyword", - "verified_result": null + "verified_result": null, + "is_secret": false }, { "hashed_secret": "c3de40d5e3fc71ed62771c2127a8e42585026c97", "is_verified": false, "line_number": 371, "type": "Secret Keyword", - "verified_result": null + "verified_result": null, + "is_secret": false }, { "hashed_secret": "4d4acd9b084d13f5fdb23807d857e1c48a1cfd0f", "is_verified": false, "line_number": 460, "type": "Secret Keyword", - "verified_result": null + "verified_result": null, + "is_secret": false }, { "hashed_secret": "bd0160c2cf35d950843c88f3be2b9412ed71f485", "is_verified": false, "line_number": 495, "type": "Secret Keyword", - "verified_result": null + "verified_result": null, + "is_secret": false }, { "hashed_secret": "293324f6824bb3a6db5c4dc42a60ddd4a9851c99", "is_verified": false, "line_number": 658, "type": "Hex High Entropy String", - "verified_result": null + "verified_result": null, + "is_secret": false } ], "scripts/git/resolve-secrets-baseline-conflict.sh": [ @@ -402,7 +443,8 @@ "is_verified": false, "line_number": 31, "type": "Secret Keyword", - "verified_result": null + "verified_result": null, + "is_secret": false } ] }, @@ -411,4 +453,4 @@ "file": null, "hash": null } -} +} \ No newline at end of file