Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 36 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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.

Expand All @@ -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"
}
}
}
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
6 changes: 3 additions & 3 deletions crates/dc-mcp-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down
2 changes: 1 addition & 1 deletion crates/dc-mcp-server/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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/",
);

Expand Down
22 changes: 9 additions & 13 deletions crates/dc-mcp-server/src/startup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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");
Expand Down
21 changes: 21 additions & 0 deletions crates/dc-mcp-server/src/token_manager.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -28,6 +30,7 @@ pub struct TokenManager {
access_token: Option<String>,
token_expires_at: Option<Instant>,
client: Client,
config_manager: Option<Arc<ConfigManager>>,
}

impl TokenManager {
Expand Down Expand Up @@ -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<ConfigManager>) {
self.config_manager = Some(config_manager);
}

/// Get a valid access token, refreshing if necessary
pub async fn get_valid_token(&mut self) -> Result<String, McpError> {
// Check if we have a valid token
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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) => {
Expand All @@ -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(),
}
}
}
Expand Down