Skip to content
Open
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 .changesets/feat_imporove_tool_descriptions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### feat: Enhance tool descriptions - @DaleSeo PR #350

This PR enhances the descriptions of the introspect and search tools to offer clearer guidance for AI models on efficient GraphQL schema exploration patterns.
62 changes: 55 additions & 7 deletions crates/apollo-mcp-server/src/introspection/tools/introspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,9 @@ fn tool_description(
"Get GraphQL type information - T=type,I=input,E=enum,U=union,F=interface;s=String,i=Int,f=Float,b=Boolean,d=ID;!=required,[]=list,<>=implements;".to_string()
} else {
format!(
"Get detailed information about types from the GraphQL schema.{}{}",
root_query_type
.map(|t| format!(" Use the type name `{t}` to get root query fields."))
.unwrap_or_default(),
root_mutation_type
.map(|t| format!(" Use the type name `{t}` to get root mutation fields."))
.unwrap_or_default()
"Get information about a given GraphQL type defined in the schema. Instructions: Use this tool to explore the schema by providing specific type names. Start with the root query ({}) or mutation ({}) types to discover available fields. If the search tool is also available, use this tool first to get the fields, then use the search tool with relevant field return types and argument input types (ignore default GraphQL scalars) as search terms.",
root_query_type.as_deref().unwrap_or("Query"),
root_mutation_type.as_deref().unwrap_or("Mutation")
)
}
}
Expand All @@ -140,3 +136,55 @@ fn tool_description(
fn default_depth() -> usize {
1
}

#[cfg(test)]
mod tests {
use super::*;
use apollo_compiler::Schema;
use apollo_compiler::validation::Valid;
use rstest::{fixture, rstest};
use std::sync::Arc;
use tokio::sync::Mutex;

const TEST_SCHEMA: &str = include_str!("testdata/schema.graphql");

#[fixture]
fn schema() -> Valid<Schema> {
Schema::parse(TEST_SCHEMA, "schema.graphql")
.expect("Failed to parse test schema")
.validate()
.expect("Failed to validate test schema")
}

#[rstest]
#[tokio::test]
async fn test_introspect_tool_description_is_not_minified(schema: Valid<Schema>) {
let introspect = Introspect::new(Arc::new(Mutex::new(schema)), None, None, false);

let description = introspect.tool.description.unwrap();

assert!(
description
.contains("Get information about a given GraphQL type defined in the schema")
);
assert!(description.contains("Instructions: Use this tool to explore the schema"));
// Should not contain minification legend
assert!(!description.contains("T=type,I=input"));
// Should mention conditional search tool usage
assert!(description.contains("If the search tool is also available"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: you could argue that this last condition is testing something else entirely and not related to the minification stuff. You could technically split this in a separate test called like instrospect_description_mentions_search_tool_usage. I find it helpful to have small very directed unit tests. It helps quickly determining what is broken in tests in the future.

}

#[rstest]
#[tokio::test]
async fn test_introspect_tool_description_is_minified_with_an_appropriate_legend(
schema: Valid<Schema>,
) {
let introspect = Introspect::new(Arc::new(Mutex::new(schema)), None, None, true);

let description = introspect.tool.description.unwrap();

// Should contain minification legend
assert!(description.contains("T=type,I=input,E=enum,U=union,F=interface"));
assert!(description.contains("s=String,i=Int,f=Float,b=Boolean,d=ID"));
}
}
35 changes: 34 additions & 1 deletion crates/apollo-mcp-server/src/introspection/tools/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ impl Search {
tool: Tool::new(
SEARCH_TOOL_NAME,
format!(
"Search a GraphQL schema{}",
"Search a GraphQL schema for types matching the provided search terms. Returns complete type definitions including all related types needed to construct GraphQL operations. Instructions: If the introspect tool is also available, you can discover type names by using the introspect tool starting from the root Query or Mutation types. Avoid reusing previously searched terms for more efficient exploration.{}",
if minify {
" - T=type,I=input,E=enum,U=union,F=interface;s=String,i=Int,f=Float,b=Boolean,d=ID;!=required,[]=list,<>=implements"
} else {
Expand Down Expand Up @@ -246,4 +246,37 @@ mod tests {
"Expected to find the createUser mutation in search results"
);
}

#[rstest]
#[tokio::test]
async fn test_search_tool_description_is_not_minified(schema: Valid<Schema>) {
let schema = Arc::new(Mutex::new(schema));
let search = Search::new(schema.clone(), false, 1, 15_000_000, false)
.expect("Failed to create search tool");

let description = search.tool.description.unwrap();

assert!(
description
.contains("Search a GraphQL schema for types matching the provided search terms")
);
assert!(description.contains("Instructions: If the introspect tool is also available"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: these two asserts aren't related to minified stuff. You could split these two into a separate test like search _tool_description_contains_expected_instructions or something of that nature. That way if it breaks in the future we don't have debug to see if it's breaking due to the minification related asserts or these.

assert!(description.contains("Avoid reusing previously searched terms"));
// Should not contain minification legend
assert!(!description.contains("T=type,I=input"));
}

#[rstest]
#[tokio::test]
async fn test_tool_description_minified(schema: Valid<Schema>) {
let schema = Arc::new(Mutex::new(schema));
let search = Search::new(schema.clone(), false, 1, 15_000_000, true)
.expect("Failed to create search tool");

let description = search.tool.description.unwrap();

// Should contain minification legend
assert!(description.contains("T=type,I=input,E=enum,U=union,F=interface"));
assert!(description.contains("s=String,i=Int,f=Float,b=Boolean,d=ID"));
}
}