diff --git a/.changesets/feat_auth_token_passthrough_disable.md b/.changesets/feat_auth_token_passthrough_disable.md new file mode 100644 index 00000000..dd9ac487 --- /dev/null +++ b/.changesets/feat_auth_token_passthrough_disable.md @@ -0,0 +1,3 @@ +### feat: Configuration for disabling authorization token passthrough - @swcollard PR #336 + +A new optional new MCP Server configuration parameter, `transport.auth.disable_auth_token_passthrough`, which is `false` by default, that when true, will no longer pass through validated Auth tokens to the GraphQL API. \ No newline at end of file diff --git a/crates/apollo-mcp-server/src/auth.rs b/crates/apollo-mcp-server/src/auth.rs index fc1f4bd6..1c802828 100644 --- a/crates/apollo-mcp-server/src/auth.rs +++ b/crates/apollo-mcp-server/src/auth.rs @@ -46,6 +46,10 @@ pub struct Config { /// Supported OAuth scopes by this resource server pub scopes: Vec, + + /// Whether to disable the auth token passthrough to upstream API + #[serde(default)] + pub disable_auth_token_passthrough: bool, } impl Config { diff --git a/crates/apollo-mcp-server/src/main.rs b/crates/apollo-mcp-server/src/main.rs index ae5102e6..3f3d8738 100644 --- a/crates/apollo-mcp-server/src/main.rs +++ b/crates/apollo-mcp-server/src/main.rs @@ -109,6 +109,8 @@ async fn main() -> anyhow::Result<()> { .then(|| config.graphos.graph_ref()) .transpose()?; + let transport = config.transport.clone(); + Ok(Server::builder() .transport(config.transport) .schema_source(schema_source) @@ -125,6 +127,15 @@ async fn main() -> anyhow::Result<()> { .mutation_mode(config.overrides.mutation_mode) .disable_type_description(config.overrides.disable_type_description) .disable_schema_description(config.overrides.disable_schema_description) + .disable_auth_token_passthrough(match transport { + apollo_mcp_server::server::Transport::Stdio => false, + apollo_mcp_server::server::Transport::SSE { auth, .. } => auth + .map(|a| a.disable_auth_token_passthrough) + .unwrap_or(false), + apollo_mcp_server::server::Transport::StreamableHttp { auth, .. } => auth + .map(|a| a.disable_auth_token_passthrough) + .unwrap_or(false), + }) .custom_scalar_map( config .custom_scalars diff --git a/crates/apollo-mcp-server/src/server.rs b/crates/apollo-mcp-server/src/server.rs index 96c0d772..6ef0153c 100644 --- a/crates/apollo-mcp-server/src/server.rs +++ b/crates/apollo-mcp-server/src/server.rs @@ -36,6 +36,7 @@ pub struct Server { mutation_mode: MutationMode, disable_type_description: bool, disable_schema_description: bool, + disable_auth_token_passthrough: bool, search_leaf_depth: usize, index_memory_bytes: usize, health_check: HealthCheckConfig, @@ -112,6 +113,7 @@ impl Server { mutation_mode: MutationMode, disable_type_description: bool, disable_schema_description: bool, + disable_auth_token_passthrough: bool, search_leaf_depth: usize, index_memory_bytes: usize, health_check: HealthCheckConfig, @@ -138,6 +140,7 @@ impl Server { mutation_mode, disable_type_description, disable_schema_description, + disable_auth_token_passthrough, search_leaf_depth, index_memory_bytes, health_check, diff --git a/crates/apollo-mcp-server/src/server/states.rs b/crates/apollo-mcp-server/src/server/states.rs index 81211cda..2bf71147 100644 --- a/crates/apollo-mcp-server/src/server/states.rs +++ b/crates/apollo-mcp-server/src/server/states.rs @@ -44,6 +44,7 @@ struct Config { mutation_mode: MutationMode, disable_type_description: bool, disable_schema_description: bool, + disable_auth_token_passthrough: bool, search_leaf_depth: usize, index_memory_bytes: usize, health_check: HealthCheckConfig, @@ -76,6 +77,7 @@ impl StateMachine { mutation_mode: server.mutation_mode, disable_type_description: server.disable_type_description, disable_schema_description: server.disable_schema_description, + disable_auth_token_passthrough: server.disable_auth_token_passthrough, search_leaf_depth: server.search_leaf_depth, index_memory_bytes: server.index_memory_bytes, health_check: server.health_check, diff --git a/crates/apollo-mcp-server/src/server/states/running.rs b/crates/apollo-mcp-server/src/server/states/running.rs index b1b69495..92c060a5 100644 --- a/crates/apollo-mcp-server/src/server/states/running.rs +++ b/crates/apollo-mcp-server/src/server/states/running.rs @@ -52,6 +52,7 @@ pub(super) struct Running { pub(super) mutation_mode: MutationMode, pub(super) disable_type_description: bool, pub(super) disable_schema_description: bool, + pub(super) disable_auth_token_passthrough: bool, pub(super) health_check: Option, } @@ -211,7 +212,9 @@ impl ServerHandler for Running { let mut headers = self.headers.clone(); if let Some(axum_parts) = context.extensions.get::() { // Optionally extract the validated token and propagate it to upstream servers if present - if let Some(token) = axum_parts.extensions.get::() { + if !self.disable_auth_token_passthrough + && let Some(token) = axum_parts.extensions.get::() + { headers.typed_insert(token.deref().clone()); } @@ -242,7 +245,9 @@ impl ServerHandler for Running { let mut headers = self.headers.clone(); if let Some(axum_parts) = context.extensions.get::() { // Optionally extract the validated token and propagate it to upstream servers if present - if let Some(token) = axum_parts.extensions.get::() { + if !self.disable_auth_token_passthrough + && let Some(token) = axum_parts.extensions.get::() + { headers.typed_insert(token.deref().clone()); } @@ -355,6 +360,7 @@ mod tests { mutation_mode: MutationMode::None, disable_type_description: false, disable_schema_description: false, + disable_auth_token_passthrough: false, health_check: None, }; diff --git a/crates/apollo-mcp-server/src/server/states/starting.rs b/crates/apollo-mcp-server/src/server/states/starting.rs index a23b137b..4109faca 100644 --- a/crates/apollo-mcp-server/src/server/states/starting.rs +++ b/crates/apollo-mcp-server/src/server/states/starting.rs @@ -148,6 +148,7 @@ impl Starting { mutation_mode: self.config.mutation_mode, disable_type_description: self.config.disable_type_description, disable_schema_description: self.config.disable_schema_description, + disable_auth_token_passthrough: self.config.disable_auth_token_passthrough, health_check: health_check.clone(), }; diff --git a/docs/source/config-file.mdx b/docs/source/config-file.mdx index ba7c45dc..294bf697 100644 --- a/docs/source/config-file.mdx +++ b/docs/source/config-file.mdx @@ -165,13 +165,14 @@ The available fields depend on the value of the nested `type` key: These fields are under the top-level `transport` key, nested under the `auth` key. Learn more about [authorization and authentication](/apollo-mcp-server/auth). -| Option | Type | Default | Description | -| :----------------------- | :------------- | :------ | :------------------------------------------------------------------------------------------------- | -| `servers` | `List` | | List of upstream delegated OAuth servers (must support OIDC metadata discovery endpoint) | -| `audiences` | `List` | | List of accepted audiences from upstream signed JWTs | -| `resource` | `string` | | The externally available URL pointing to this MCP server. Can be `localhost` when testing locally. | -| `resource_documentation` | `string` | | Optional link to more documentation relating to this MCP server | -| `scopes` | `List` | | List of queryable OAuth scopes from the upstream OAuth servers | +| Option | Type | Default | Description | +| :-------------------------------- | :------------- | :------ | :------------------------------------------------------------------------------------------------- | +| `servers` | `List` | | List of upstream delegated OAuth servers (must support OIDC metadata discovery endpoint) | +| `audiences` | `List` | | List of accepted audiences from upstream signed JWTs | +| `resource` | `string` | | The externally available URL pointing to this MCP server. Can be `localhost` when testing locally. | +| `resource_documentation` | `string` | | Optional link to more documentation relating to this MCP server | +| `scopes` | `List` | | List of queryable OAuth scopes from the upstream OAuth servers | +| `disable_auth_token_passthrough` | `bool` | `false` | Optional flag to disable passing validated Authorization header to downstream API | Below is an example configuration using `StreamableHTTP` transport with authentication: