diff --git a/README.md b/README.md index 7dd20519..e96ae0fe 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Apollo MCP Server for DoControl +# DoControl MCP Server This is a thin wrapper around [Apollo MCP Server](https://github.com/apollographql/apollo-mcp-server) configured specifically for DoControl's authentication flow. @@ -14,7 +14,7 @@ This wrapper handles DoControl's OAuth token refresh flow automatically: ### How It Works -1. **Token Refresh**: Uses `APOLLO_REFRESH_TOKEN` to obtain fresh access tokens from `APOLLO_REFRESH_URL` +1. **Token Refresh**: Uses `DC_REFRESH_TOKEN` to obtain fresh access tokens from `DC_REFRESH_URL` 2. **Auto-Refresh**: Tokens are automatically refreshed before expiration (5 minutes before) 3. **Config Update**: Fresh tokens are written back to the config file's auth section 4. **Background Task**: A background task continuously monitors and refreshes tokens @@ -26,15 +26,16 @@ The server requires these environment variables: ```bash # DoControl Token Refresh -APOLLO_REFRESH_TOKEN="your-refresh-token-from-docontrol" -APOLLO_REFRESH_URL="https://auth.prod.docontrol.io/refresh" -APOLLO_GRAPHQL_ENDPOINT="https://apollo-gateway-v4-api.prod.docontrol.io/graphql" +DC_TOKEN_REFRESH_ENABLED="true" +DC_REFRESH_TOKEN="your-refresh-token-from-docontrol" +DC_REFRESH_URL="https://auth.prod.docontrol.io/refresh" +DC_GRAPHQL_ENDPOINT="https://apollo-gateway-v4-api.prod.docontrol.io/graphql" # Apollo GraphOS API Key -APOLLO_KEY="service:docontrol-api:your-apollo-key" +DC_API_KEY="service:docontrol-api:your-apollo-key" # Optional: Override the hardcoded graph ref (defaults to "docontrol-api@current") -# APOLLO_GRAPH_REF="docontrol-api@current" +# DC_GRAPH_REF="docontrol-api@current" ``` ### Configuration File @@ -47,7 +48,7 @@ endpoint: "https://apollo-gateway-v4-api.prod.docontrol.io/graphql" # Apollo GraphOS configuration graphos: - apollo_key: "${APOLLO_KEY}" # From environment variable + apollo_key: "${DC_API_KEY}" # From environment variable # Use introspection to discover operations operations: introspect @@ -58,7 +59,7 @@ introspection: mutation: true ``` -**Note**: The `apollo_graph_ref` is hardcoded in the source code as `docontrol-api@current`. You can override it by setting the `APOLLO_GRAPH_REF` environment variable if needed. +**Note**: The `apollo_graph_ref` is hardcoded in the source code as `docontrol-api@current`. You can override it by setting the `DC_GRAPH_REF` environment variable if needed. **Note**: The `auth` section in the config file is automatically managed by the token refresh system. You don't need to manually specify it. @@ -76,14 +77,16 @@ Add this to your MCP configuration file: { "mcpServers": { "docontrol": { - "command": "/path/to/apollo-mcp-server", + "command": "/path/to/dc-mcp-server", "args": ["/path/to/config.yaml"], "env": { - "APOLLO_REFRESH_TOKEN": "your-refresh-token", - "APOLLO_REFRESH_URL": "https://auth.prod.docontrol.io/refresh", - "APOLLO_GRAPHQL_ENDPOINT": "https://apollo-gateway-v4-api.prod.docontrol.io/graphql", - "APOLLO_GRAPH_REF": "docontrol-api@current", - "APOLLO_KEY": "service:docontrol-api:your-key" + "DC_TOKEN_REFRESH_ENABLED": "true", + "DC_REFRESH_TOKEN": "your-refresh-token", + "DC_REFRESH_URL": "https://auth.prod.docontrol.io/refresh", + "DC_GRAPHQL_ENDPOINT": "https://apollo-gateway-v4-api.prod.docontrol.io/graphql", + "DC_GRAPH_REF": "docontrol-api@current", + "DC_API_KEY": "service:docontrol-api:your-key", + "RUST_LOG": "info" } } } @@ -95,13 +98,14 @@ Add this to your MCP configuration file: For testing and debugging: ```bash -export APOLLO_REFRESH_TOKEN="your-refresh-token" -export APOLLO_REFRESH_URL="https://auth.prod.docontrol.io/refresh" -export APOLLO_GRAPHQL_ENDPOINT="https://apollo-gateway-v4-api.prod.docontrol.io/graphql" -export APOLLO_GRAPH_REF="docontrol-api@current" -export APOLLO_KEY="service:docontrol-api:your-key" - -npx @modelcontextprotocol/inspector apollo-mcp-server config.yaml +export DC_TOKEN_REFRESH_ENABLED="true" +export DC_REFRESH_TOKEN="your-refresh-token" +export DC_REFRESH_URL="https://auth.prod.docontrol.io/refresh" +export DC_GRAPHQL_ENDPOINT="https://apollo-gateway-v4-api.prod.docontrol.io/graphql" +export DC_GRAPH_REF="docontrol-api@current" +export DC_API_KEY="service:docontrol-api:your-key" + +npx @modelcontextprotocol/inspector dc-mcp-server config.yaml ``` ## How Introspection Works @@ -119,15 +123,16 @@ All queries and mutations from the DoControl GraphQL API are automatically avail ### From Release -Download the latest release for your platform from the [releases page](https://github.com/yourusername/dc-mcp-server/releases): -- **Linux**: `apollo-mcp-server-linux-x86_64.tar.gz` -- **macOS**: `apollo-mcp-server-macos-aarch64.tar.gz` +Download the latest release for your platform from the [releases page](https://github.com/docontrol-io/dc-mcp-server/releases): +- **Linux**: `dc-mcp-server-linux-x86_64.tar.gz` +- **macOS**: `dc-mcp-server-macos-aarch64.tar.gz` +- **Windows**: `dc-mcp-server-windows-x86_64.tar.gz` ### From Source ```bash -cargo build --release -cp target/release/apollo-mcp-server /usr/local/bin/ +cargo build --release --package dc-mcp-server +cp target/release/dc-mcp-server /usr/local/bin/ ``` ## Example Setup @@ -163,13 +168,13 @@ The AI assistant will have access to all GraphQL queries and mutations from the - ✅ **Limit permissions** - use read-only tokens when possible **Secrets to protect:** -- `APOLLO_REFRESH_TOKEN` - DoControl OAuth refresh token -- `APOLLO_KEY` - Apollo Studio API key +- `DC_REFRESH_TOKEN` - DoControl OAuth refresh token +- `DC_API_KEY` - Apollo Studio API key - Config files containing tokens ## How Token Refresh Works -1. **Server Startup**: Reads `APOLLO_REFRESH_TOKEN` from environment +1. **Server Startup**: Reads `DC_REFRESH_TOKEN` from environment 2. **Initial Refresh**: Immediately refreshes to get a valid access token 3. **Config Update**: Writes access token to config file's `auth` section 4. **Token Verification**: Verifies token works with a test GraphQL request @@ -194,7 +199,7 @@ cargo build --release Enable debug logging: ```bash -RUST_LOG=debug apollo-mcp-server config.yaml +RUST_LOG=debug dc-mcp-server config.yaml ``` ## Upstream diff --git a/crates/dc-mcp-server/src/introspection/snapshots/apollo_mcp_server__introspection__minify__tests__minify_schema.snap b/crates/dc-mcp-server/src/introspection/snapshots/dc_mcp_server__introspection__minify__tests__minify_schema.snap similarity index 100% rename from crates/dc-mcp-server/src/introspection/snapshots/apollo_mcp_server__introspection__minify__tests__minify_schema.snap rename to crates/dc-mcp-server/src/introspection/snapshots/dc_mcp_server__introspection__minify__tests__minify_schema.snap diff --git a/crates/dc-mcp-server/src/introspection/tools/snapshots/apollo_mcp_server__introspection__tools__search__tests__search_tool.snap b/crates/dc-mcp-server/src/introspection/tools/snapshots/dc_mcp_server__introspection__tools__search__tests__search_tool.snap similarity index 100% rename from crates/dc-mcp-server/src/introspection/tools/snapshots/apollo_mcp_server__introspection__tools__search__tests__search_tool.snap rename to crates/dc-mcp-server/src/introspection/tools/snapshots/dc_mcp_server__introspection__tools__search__tests__search_tool.snap diff --git a/crates/dc-mcp-server/src/main.rs b/crates/dc-mcp-server/src/main.rs index 1a583c13..e887b3f8 100644 --- a/crates/dc-mcp-server/src/main.rs +++ b/crates/dc-mcp-server/src/main.rs @@ -3,14 +3,14 @@ use std::path::PathBuf; use apollo_mcp_registry::platform_api::operation_collections::collection_poller::CollectionSource; use apollo_mcp_registry::uplink::persisted_queries::ManifestSource; use apollo_mcp_registry::uplink::schema::SchemaSource; +use clap::Parser; +use clap::builder::Styles; +use clap::builder::styling::{AnsiColor, Effects}; use dc_mcp_server::custom_scalar_map::CustomScalarMap; use dc_mcp_server::errors::ServerError; use dc_mcp_server::operations::OperationSource; use dc_mcp_server::server::Server; use dc_mcp_server::startup; -use clap::Parser; -use clap::builder::Styles; -use clap::builder::styling::{AnsiColor, Effects}; use runtime::IdOrDefault; use tracing::{info, warn}; diff --git a/crates/dc-mcp-server/src/runtime.rs b/crates/dc-mcp-server/src/runtime.rs index d1d5ddb8..796b9346 100644 --- a/crates/dc-mcp-server/src/runtime.rs +++ b/crates/dc-mcp-server/src/runtime.rs @@ -143,7 +143,7 @@ mod test { jail.create_file(path, config)?; jail.set_env( - "APOLLO_UPLINK_ENDPOINTS", + "DC_UPLINK_ENDPOINTS", "http://from_env:4000/,http://from_env2:4000/", ); diff --git a/crates/dc-mcp-server/src/startup.rs b/crates/dc-mcp-server/src/startup.rs index 571f932b..9a844367 100644 --- a/crates/dc-mcp-server/src/startup.rs +++ b/crates/dc-mcp-server/src/startup.rs @@ -5,6 +5,7 @@ use crate::errors::McpError; use crate::token_manager::TokenManager; use rmcp::model::ErrorCode; use std::env; +use std::sync::Arc; use tracing::{debug, info, warn}; /// Initialize the Apollo MCP Server with token refresh and environment setup @@ -16,29 +17,24 @@ pub async fn initialize_with_token_refresh( ) -> Result<(), McpError> { info!("🎯 Apollo MCP Server initializing with token refresh..."); - // Step 1: Verify config file - let config_manager = ConfigManager::new(config_path.clone()); + // Step 1: Create shared config manager + let config_manager = Arc::new(ConfigManager::new(config_path.clone())); config_manager.verify_config().map_err(|e| { warn!("Config verification failed: {}", e); e })?; - // Step 2: Initialize token manager + // Step 2: Initialize token manager with injected config manager let mut token_manager = TokenManager::new(refresh_token, refresh_url)?; + token_manager.set_config_manager(Arc::clone(&config_manager)); - // Step 3: Get fresh token + // Step 3: Get fresh token (will automatically write to config) let new_token = token_manager.get_valid_token().await.map_err(|e| { warn!("Token refresh failed: {}", e); e })?; - // Step 4: Update config file with new token - config_manager.update_auth_token(&new_token).map_err(|e| { - warn!("Config update failed: {}", e); - e - })?; - - // Step 5: Verify the new token + // Step 4: Verify the new token if !token_manager .verify_token(&new_token, &graphql_endpoint) .await @@ -54,10 +50,10 @@ pub async fn initialize_with_token_refresh( )); } - // Step 6: Start background token refresh task + // Step 5: Start background token refresh task token_manager.start_refresh_task(graphql_endpoint).await; - // Step 7: Set up environment variables + // Step 6: Set up environment variables setup_environment_variables(); info!("✅ Apollo MCP Server initialization complete"); diff --git a/crates/dc-mcp-server/src/token_manager.rs b/crates/dc-mcp-server/src/token_manager.rs index c51aa9ef..18ba397c 100644 --- a/crates/dc-mcp-server/src/token_manager.rs +++ b/crates/dc-mcp-server/src/token_manager.rs @@ -1,9 +1,11 @@ //! Token refresh functionality for Apollo MCP Server +use crate::config_manager::ConfigManager; use crate::errors::McpError; use reqwest::Client; use rmcp::model::ErrorCode; use serde::{Deserialize, Serialize}; +use std::sync::Arc; use std::time::{Duration, Instant}; use tokio::time::sleep; use tracing::{debug, error, info, warn}; @@ -28,6 +30,7 @@ pub struct TokenManager { access_token: Option, token_expires_at: Option, client: Client, + config_manager: Option>, } impl TokenManager { @@ -70,9 +73,15 @@ impl TokenManager { access_token: None, token_expires_at: None, client, + config_manager: None, }) } + /// Inject the config manager for automatic token persistence + pub fn set_config_manager(&mut self, config_manager: Arc) { + self.config_manager = Some(config_manager); + } + /// Get a valid access token, refreshing if necessary pub async fn get_valid_token(&mut self) -> Result { // Check if we have a valid token @@ -157,6 +166,15 @@ impl TokenManager { info!("✅ Successfully refreshed access token (expires in 1h)"); } + // Write the token to config file if config manager is set + if let Some(config_manager) = &self.config_manager { + if let Err(e) = config_manager.update_auth_token(&token_response.access_token) { + warn!("Failed to write refreshed token to config file: {}", e); + } else { + info!("✅ Refreshed token written to config file"); + } + } + Ok(token_response.access_token) } @@ -226,6 +244,8 @@ impl TokenManager { if let Err(e) = token_manager.verify_token(&token, &graphql_endpoint).await { error!("Token verification failed in background task: {}", e); + } else { + info!("✅ Background task: token refreshed and verified"); } } Err(e) => { @@ -245,6 +265,7 @@ impl Clone for TokenManager { access_token: self.access_token.clone(), token_expires_at: self.token_expires_at, client: self.client.clone(), + config_manager: self.config_manager.clone(), } } }