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
3 changes: 3 additions & 0 deletions src/command/dev/do_dev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,8 @@ impl Dev {
// the overrides for the temporary config we set for hot-reloading the router, but also as
// a message to the user for where to find their router
let router_address = *run_router.state.config.address();
// Extract the router's listen path from the config to construct the full endpoint URL for MCP
let router_url_path = run_router.state.config.listen_path();
let hot_reload_overrides = HotReloadConfigOverrides::builder()
.address(router_address)
.build();
Expand Down Expand Up @@ -313,6 +315,7 @@ impl Dev {
TokioSpawn::default(),
run_router.state.hot_reload_schema_path.clone(),
router_address,
router_url_path,
config.clone(),
run_router.state.env.clone(),
)
Expand Down
123 changes: 119 additions & 4 deletions src/command/dev/mcp/binary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ pub struct RunMcpServerBinary<Spawn: Send> {
supergraph_schema_path: Utf8PathBuf,
spawn: Spawn,
router_address: RouterAddress,
router_url_path: Option<String>,
mcp_config_path: Option<Utf8PathBuf>,
env: HashMap<String, String>,
}
Expand All @@ -87,6 +88,13 @@ impl<Spawn: Send> RunMcpServerBinary<Spawn> {
// understood by the MCP server.
// TODO: Magic strings are not fun to debug later.
fn opts_into_env(self) -> HashMap<String, String> {
// Build the endpoint URL with the optional path from router config
let endpoint = if let Some(path) = &self.router_url_path {
format!("{}{}", self.router_address.pretty_string(), path)
} else {
self.router_address.pretty_string()
};

let overlaid = HashMap::from([
// Configure the schema to be a local file
("APOLLO_MCP_SCHEMA__SOURCE".to_string(), "local".to_string()),
Expand All @@ -95,10 +103,7 @@ impl<Spawn: Send> RunMcpServerBinary<Spawn> {
self.supergraph_schema_path.to_string(),
),
// Configure the endpoint from the running router instance
(
"APOLLO_MCP_ENDPOINT".to_string(),
self.router_address.pretty_string(),
),
("APOLLO_MCP_ENDPOINT".to_string(), endpoint),
(
"APOLLO_MCP_TRANSPORT__TYPE".to_string(),
"streamable_http".to_string(),
Expand Down Expand Up @@ -248,3 +253,113 @@ where
});
}
}

#[cfg(test)]
mod tests {
use std::{collections::HashMap, net::IpAddr};

use camino::Utf8PathBuf;
use semver::Version;

use super::*;
use crate::command::dev::router::config::{RouterAddress, RouterHost, RouterPort};

struct MockSpawn;

#[test]
fn test_mcp_endpoint_without_router_url_path() {
let router_address = RouterAddress::new(
Some(RouterHost::Default(IpAddr::V4(std::net::Ipv4Addr::new(
127, 0, 0, 1,
)))),
Some(RouterPort::Default(4000)),
);

let binary = McpServerBinary::new(
Utf8PathBuf::from("/fake/path"),
Version::parse("1.0.0").unwrap(),
);

let router_url_path: Option<String> = None;
let mcp_config_path: Option<Utf8PathBuf> = None;

let runner = RunMcpServerBinary::<MockSpawn>::builder()
.mcp_server_binary(binary)
.supergraph_schema_path(Utf8PathBuf::from("/fake/schema.graphql"))
.spawn(MockSpawn)
.router_address(router_address)
.and_router_url_path(router_url_path)
.and_mcp_config_path(mcp_config_path)
.env(HashMap::new())
.build();

let env = runner.opts_into_env();
let endpoint = env.get("APOLLO_MCP_ENDPOINT").unwrap();

assert_eq!(endpoint, "http://localhost:4000");
}

#[test]
fn test_mcp_endpoint_with_router_url_path() {
let router_address = RouterAddress::new(
Some(RouterHost::Default(IpAddr::V4(std::net::Ipv4Addr::new(
127, 0, 0, 1,
)))),
Some(RouterPort::Default(4000)),
);

let binary = McpServerBinary::new(
Utf8PathBuf::from("/fake/path"),
Version::parse("1.0.0").unwrap(),
);

let mcp_config_path: Option<Utf8PathBuf> = None;

let runner = RunMcpServerBinary::<MockSpawn>::builder()
.mcp_server_binary(binary)
.supergraph_schema_path(Utf8PathBuf::from("/fake/schema.graphql"))
.spawn(MockSpawn)
.router_address(router_address)
.and_router_url_path(Some("/graphql".to_string()))
.and_mcp_config_path(mcp_config_path)
.env(HashMap::new())
.build();

let env = runner.opts_into_env();
let endpoint = env.get("APOLLO_MCP_ENDPOINT").unwrap();

assert_eq!(endpoint, "http://localhost:4000/graphql");
}

#[test]
fn test_mcp_endpoint_with_custom_path() {
let router_address = RouterAddress::new(
Some(RouterHost::Default(IpAddr::V4(std::net::Ipv4Addr::new(
127, 0, 0, 1,
)))),
Some(RouterPort::Default(4000)),
);

let binary = McpServerBinary::new(
Utf8PathBuf::from("/fake/path"),
Version::parse("1.0.0").unwrap(),
);

let mcp_config_path: Option<Utf8PathBuf> = None;

let runner = RunMcpServerBinary::<MockSpawn>::builder()
.mcp_server_binary(binary)
.supergraph_schema_path(Utf8PathBuf::from("/fake/schema.graphql"))
.spawn(MockSpawn)
.router_address(router_address)
.and_router_url_path(Some("/custom-path".to_string()))
.and_mcp_config_path(mcp_config_path)
.env(HashMap::new())
.build();

let env = runner.opts_into_env();
let endpoint = env.get("APOLLO_MCP_ENDPOINT").unwrap();

assert_eq!(endpoint, "http://localhost:4000/custom-path");
}
}
2 changes: 2 additions & 0 deletions src/command/dev/mcp/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ impl RunMcpServer<state::Run> {
spawn: Spawn,
supergraph_schema_path: Utf8PathBuf,
router_address: RouterAddress,
router_url_path: Option<String>,
mcp_config_path: Option<Utf8PathBuf>,
env: HashMap<String, String>,
) -> Result<RunMcpServer<state::Abort>, RunMcpServerBinaryError>
Expand All @@ -70,6 +71,7 @@ impl RunMcpServer<state::Run> {
.supergraph_schema_path(supergraph_schema_path.clone())
.spawn(spawn)
.router_address(router_address)
.and_router_url_path(router_url_path)
.and_mcp_config_path(mcp_config_path)
.env(env)
.build();
Expand Down