Skip to content
Draft
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
35 changes: 24 additions & 11 deletions crates/apollo-mcp-server/src/introspection/tools/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,25 @@ pub struct Input {
}

impl Execute {
pub fn new(mutation_mode: MutationMode) -> Self {
pub fn new(mutation_mode: MutationMode, hints: Option<String>) -> Self {
let base_description = "Execute a GraphQL operation. Use the `introspect` tool to get information about the GraphQL schema. Always use the schema to create operations - do not try arbitrary operations. If available, first use the `validate` tool to validate operations. DO NOT try to execute introspection queries.";

let description = match hints.as_ref() {
Some(hint_text) => {
tracing::info!("Execute tool created with custom hints: {}", hint_text);
format!("{}\n\n{}", base_description, hint_text)
},
None => {
tracing::info!("Execute tool created without custom hints");
base_description.to_string()
},
};

Self {
mutation_mode,
tool: Tool::new(
EXECUTE_TOOL_NAME,
"Execute a GraphQL operation. Use the `introspect` tool to get information about the GraphQL schema. Always use the schema to create operations - do not try arbitrary operations. If available, first use the `validate` tool to validate operations. DO NOT try to execute introspection queries.",
description,
schema_from_type!(Input),
),
}
Expand Down Expand Up @@ -106,7 +119,7 @@ mod tests {

#[test]
fn execute_query_with_variables_as_string() {
let execute = Execute::new(MutationMode::None);
let execute = Execute::new(MutationMode::None, None);

let query = "query GetUser($id: ID!) { user(id: $id) { id name } }";
let variables = json!({ "id": "123" });
Expand All @@ -128,7 +141,7 @@ mod tests {

#[test]
fn execute_query_with_variables_as_json() {
let execute = Execute::new(MutationMode::None);
let execute = Execute::new(MutationMode::None, None);

let query = "query GetUser($id: ID!) { user(id: $id) { id name } }";
let variables = json!({ "id": "123" });
Expand All @@ -150,7 +163,7 @@ mod tests {

#[test]
fn execute_query_without_variables() {
let execute = Execute::new(MutationMode::None);
let execute = Execute::new(MutationMode::None, None);

let query = "query GetUser($id: ID!) { user(id: $id) { id name } }";

Expand All @@ -170,7 +183,7 @@ mod tests {

#[test]
fn execute_query_anonymous_operation() {
let execute = Execute::new(MutationMode::None);
let execute = Execute::new(MutationMode::None, None);

let query = "{ user(id: \"123\") { id name } }";
let input = json!({
Expand All @@ -188,7 +201,7 @@ mod tests {

#[test]
fn execute_query_err_with_mutation_when_mutation_mode_is_none() {
let execute = Execute::new(MutationMode::None);
let execute = Execute::new(MutationMode::None, None);

let query = "mutation MutationName { id }".to_string();
let input = json!({
Expand All @@ -207,7 +220,7 @@ mod tests {

#[test]
fn execute_query_ok_with_mutation_when_mutation_mode_is_all() {
let execute = Execute::new(MutationMode::All);
let execute = Execute::new(MutationMode::All, None);

let query = "mutation MutationName { id }".to_string();
let input = json!({
Expand All @@ -230,7 +243,7 @@ mod tests {
MutationMode::Explicit,
MutationMode::All,
] {
let execute = Execute::new(mutation_mode);
let execute = Execute::new(mutation_mode, None);

let input = json!({
"query": "subscription SubscriptionName { id }",
Expand All @@ -249,7 +262,7 @@ mod tests {

#[test]
fn execute_query_invalid_input() {
let execute = Execute::new(MutationMode::None);
let execute = Execute::new(MutationMode::None, None);

let input = json!({
"nonsense": "whatever",
Expand All @@ -275,7 +288,7 @@ mod tests {

#[test]
fn execute_query_invalid_variables() {
let execute = Execute::new(MutationMode::None);
let execute = Execute::new(MutationMode::None, None);

let input = json!({
"query": "query GetUser($id: ID!) { user(id: $id) { id name } }",
Expand Down
8 changes: 7 additions & 1 deletion crates/apollo-mcp-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ struct Args {

#[tokio::main]
async fn main() -> anyhow::Result<()> {
let config: runtime::Config = match Args::parse().config {
let args = Args::parse();
let config: runtime::Config = match &args.config {
Some(config_path) => runtime::read_config(config_path)?,
None => runtime::read_config_from_env().unwrap_or_default(),
};
Expand Down Expand Up @@ -109,6 +110,10 @@ async fn main() -> anyhow::Result<()> {
.then(|| config.graphos.graph_ref())
.transpose()?;

// Resolve execute hints from either inline hints or hints_file
let config_dir = args.config.as_ref().and_then(|p| p.parent());
let resolved_execute_hints = config.introspection.execute.resolve_hints(config_dir)?;

let transport = config.transport.clone();

Ok(Server::builder()
Expand All @@ -119,6 +124,7 @@ async fn main() -> anyhow::Result<()> {
.maybe_explorer_graph_ref(explorer_graph_ref)
.headers(config.headers)
.execute_introspection(config.introspection.execute.enabled)
.maybe_execute_hints(resolved_execute_hints)
.validate_introspection(config.introspection.validate.enabled)
.introspect_introspection(config.introspection.introspect.enabled)
.introspect_minify(config.introspection.introspect.minify)
Expand Down
2 changes: 2 additions & 0 deletions crates/apollo-mcp-server/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,8 @@ mod test {
introspection: Introspection {
execute: ExecuteConfig {
enabled: false,
hints: None,
hints_file: None,
},
introspect: IntrospectConfig {
enabled: false,
Expand Down
35 changes: 35 additions & 0 deletions crates/apollo-mcp-server/src/runtime/introspection.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use schemars::JsonSchema;
use serde::Deserialize;
use std::path::Path;

/// Introspection configuration
#[derive(Debug, Default, Deserialize, JsonSchema)]
Expand All @@ -24,6 +25,40 @@ pub struct Introspection {
pub struct ExecuteConfig {
/// Enable introspection for execution
pub enabled: bool,

/// Additional hints to append to the execute tool description
pub hints: Option<String>,

/// Path to a file containing additional hints to append to the execute tool description.
/// If both hints and hints_file are provided, hints_file takes precedence.
/// Path is relative to the configuration file location.
pub hints_file: Option<String>,
}

impl ExecuteConfig {
/// Resolve hints from either inline hints or hints_file
/// If hints_file is provided, it takes precedence over inline hints
/// The file path is resolved relative to the config_dir if provided
pub fn resolve_hints(&self, config_dir: Option<&Path>) -> Result<Option<String>, std::io::Error> {
if let Some(hints_file) = &self.hints_file {
let hints_path = if let Some(config_dir) = config_dir {
config_dir.join(hints_file)
} else {
hints_file.into()
};

let hints_content = std::fs::read_to_string(&hints_path)
.map_err(|e| {
tracing::warn!("Failed to read hints file '{}': {}", hints_path.display(), e);
e
})?;

tracing::info!("Loaded hints from file: {}", hints_path.display());
Ok(Some(hints_content))
} else {
Ok(self.hints.clone())
}
}
}

/// Introspect-specific introspection configuration
Expand Down
3 changes: 3 additions & 0 deletions crates/apollo-mcp-server/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub struct Server {
endpoint: Url,
headers: HeaderMap,
execute_introspection: bool,
execute_hints: Option<String>,
validate_introspection: bool,
introspect_introspection: bool,
introspect_minify: bool,
Expand Down Expand Up @@ -103,6 +104,7 @@ impl Server {
endpoint: Url,
headers: HeaderMap,
execute_introspection: bool,
execute_hints: Option<String>,
validate_introspection: bool,
introspect_introspection: bool,
search_introspection: bool,
Expand Down Expand Up @@ -130,6 +132,7 @@ impl Server {
endpoint,
headers,
execute_introspection,
execute_hints,
validate_introspection,
introspect_introspection,
search_introspection,
Expand Down
2 changes: 2 additions & 0 deletions crates/apollo-mcp-server/src/server/states.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ struct Config {
endpoint: Url,
headers: HeaderMap,
execute_introspection: bool,
execute_hints: Option<String>,
validate_introspection: bool,
introspect_introspection: bool,
search_introspection: bool,
Expand Down Expand Up @@ -67,6 +68,7 @@ impl StateMachine {
endpoint: server.endpoint,
headers: server.headers,
execute_introspection: server.execute_introspection,
execute_hints: server.execute_hints,
validate_introspection: server.validate_introspection,
introspect_introspection: server.introspect_introspection,
search_introspection: server.search_introspection,
Expand Down
2 changes: 1 addition & 1 deletion crates/apollo-mcp-server/src/server/states/starting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ impl Starting {
let execute_tool = self
.config
.execute_introspection
.then(|| Execute::new(self.config.mutation_mode));
.then(|| Execute::new(self.config.mutation_mode, self.config.execute_hints.clone()));

let root_query_type = self
.config
Expand Down
2 changes: 2 additions & 0 deletions graphql/TheSpaceDevs/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ overrides:
introspection:
execute:
enabled: true
hints_file: execute_hints.txt
# hints: Some inline hints that could be used instead of hints_file
introspect:
enabled: true
search:
Expand Down
23 changes: 23 additions & 0 deletions graphql/TheSpaceDevs/execute_hints.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
🚀 TheSpaceDevs API Execution Hints:

• Popular queries to explore:
- Launch data: Use 'launches' field with filters like upcoming, status, or date ranges
- Spacecraft info: Query 'spacecraft' for technical details and mission history
- Astronaut data: Access 'astronauts' for crew information and flight records
- Agency details: Use 'agencies' to get space organization information

• Common filter patterns:
- Date filtering: Use 'net__gte' and 'net__lte' for launch time windows
- Status filtering: 'status__ids' accepts arrays like [1,3,4] for launch statuses
- Limit results: Always include 'limit' parameter to control response size

• Best practices:
- Start with small limit values (10-20) to avoid large responses
- Use 'ordering' parameter to sort results (e.g., 'net' for launch date)
- Check 'total_count' in responses to understand dataset size
- Combine multiple fields for richer data (launches + spacecraft + agencies)

• Example useful queries:
- Next SpaceX launches: filter by agency name and upcoming status
- ISS crew rotations: query astronauts with ISS mission filters
- Recent successful launches: filter by status and recent dates