@@ -21,7 +21,11 @@ pub struct Explorer {
21
21
pub struct Input {
22
22
/// The GraphQL document
23
23
document : String ,
24
+
25
+ /// Any variables used in the document
24
26
variables : String ,
27
+
28
+ /// Headers to be sent with the operation
25
29
headers : String ,
26
30
}
27
31
@@ -36,70 +40,42 @@ impl Explorer {
36
40
variant,
37
41
tool : Tool :: new (
38
42
EXPLORER_TOOL_NAME ,
39
- "Open a GraphQL operation in Apollo Explorer. The input fields must be in the order document, variables, headers. All fields must be present, but if they are not set, set them to a string containing just `{}` " ,
43
+ "Open a GraphQL operation in Apollo Explorer" ,
40
44
schema_from_type ! ( Input ) ,
41
45
) ,
42
46
}
43
47
}
44
48
45
49
fn create_explorer_url ( & self , input : & Value ) -> String {
46
- let compressed = lz_str:: compress_to_encoded_uri_component ( input. to_string ( ) . as_str ( ) ) ;
47
- format ! (
48
- "https://studio.apollographql.com/graph/{graph_id}/variant/{variant}/explorer?explorerURLState={compressed}" ,
49
- graph_id = self . graph_id,
50
- variant = self . variant
51
- )
52
- }
53
-
54
- pub async fn execute ( & self , input : Value ) -> Result < CallToolResult , McpError > {
55
- // Validate field order: must be ["document", "variables", "headers"]
56
- if let Value :: Object ( map) = & input {
57
- let mut keys = map. keys ( ) ;
58
- if !( keys. next ( ) == Some ( & "document" . to_string ( ) )
59
- && keys. next ( ) == Some ( & "variables" . to_string ( ) )
60
- && keys. next ( ) == Some ( & "headers" . to_string ( ) )
61
- && keys. next ( ) . is_none ( ) )
62
- {
63
- return Err ( McpError :: new (
64
- ErrorCode :: INVALID_PARAMS ,
65
- "Input fields must be in order: document, variables, headers" ,
66
- None ,
67
- ) ) ;
68
- }
69
- } else {
70
- return Err ( McpError :: new (
71
- ErrorCode :: INVALID_PARAMS ,
72
- "Input must be a JSON object" ,
73
- None ,
74
- ) ) ;
75
- }
50
+ let mut input = input. clone ( ) ;
76
51
77
52
let document = input. get ( "document" ) . and_then ( |v| v. as_str ( ) ) ;
78
- let variables = input. get ( "variables" ) . and_then ( |v| v. as_str ( ) ) ;
79
- let headers = input. get ( "headers" ) . and_then ( |v| v. as_str ( ) ) ;
80
-
81
53
if document. is_none ( ) || document == Some ( "" ) {
82
- return Err ( McpError :: new (
83
- ErrorCode :: INVALID_PARAMS ,
84
- "Missing or empty 'document' field in input" ,
85
- None ,
86
- ) ) ;
54
+ if let Some ( obj) = input. as_object_mut ( ) {
55
+ obj. insert ( "document" . to_string ( ) , Value :: String ( "{}" . to_string ( ) ) ) ;
56
+ }
87
57
}
58
+ let variables = input. get ( "variables" ) . and_then ( |v| v. as_str ( ) ) ;
88
59
if variables. is_none ( ) || variables == Some ( "" ) {
89
- return Err ( McpError :: new (
90
- ErrorCode :: INVALID_PARAMS ,
91
- "Missing or empty 'variables' field in input" ,
92
- None ,
93
- ) ) ;
60
+ if let Some ( obj) = input. as_object_mut ( ) {
61
+ obj. insert ( "variables" . to_string ( ) , Value :: String ( "{}" . to_string ( ) ) ) ;
62
+ }
94
63
}
64
+ let headers = input. get ( "headers" ) . and_then ( |v| v. as_str ( ) ) ;
95
65
if headers. is_none ( ) || headers == Some ( "" ) {
96
- return Err ( McpError :: new (
97
- ErrorCode :: INVALID_PARAMS ,
98
- "Missing or empty 'headers' field in input" ,
99
- None ,
100
- ) ) ;
66
+ if let Some ( obj) = input. as_object_mut ( ) {
67
+ obj. insert ( "headers" . to_string ( ) , Value :: String ( "{}" . to_string ( ) ) ) ;
68
+ }
101
69
}
70
+ let compressed = lz_str:: compress_to_encoded_uri_component ( input. to_string ( ) . as_str ( ) ) ;
71
+ format ! (
72
+ "https://studio.apollographql.com/graph/{graph_id}/variant/{variant}/explorer?explorerURLState={compressed}" ,
73
+ graph_id = self . graph_id,
74
+ variant = self . variant
75
+ )
76
+ }
102
77
78
+ pub async fn execute ( & self , input : Value ) -> Result < CallToolResult , McpError > {
103
79
let url = self . create_explorer_url ( & input) ;
104
80
info ! (
105
81
"Opening Apollo Explorer URL: {} for input operation: {}" ,
@@ -120,6 +96,7 @@ mod tests {
120
96
use super :: * ;
121
97
use insta:: assert_snapshot;
122
98
use rmcp:: serde_json:: json;
99
+ use rstest:: rstest;
123
100
124
101
#[ test]
125
102
fn test_create_explorer_url ( ) {
@@ -136,4 +113,44 @@ mod tests {
136
113
@"https://studio.apollographql.com/graph/mcp-example/variant/mcp/explorer?explorerURLState=N4IgJg9gxgrgtgUwHYBcQC4QEcYIE4CeABAOIIoDqCAhigBb4CCANvigM4AUAJOyrQnREAyijwBLJAHMAhAEoiwADpIiRaqzwdOfAUN78UCBctVqi7BADd84lARXmiYBOygSADinEQkj85J8eDBQ3r7+AL4qESAANCAM1C547BggwDHxVtQS1ABGrKmYyiC6RkoYRBUAwowVMRFAA"
137
114
) ;
138
115
}
116
+
117
+ #[ tokio:: test]
118
+ #[ rstest]
119
+ #[ case( json!( {
120
+ "variables" : "{\" state\" : \" CA\" }" ,
121
+ "headers" : "{}"
122
+ } ) , "document" ) ]
123
+ #[ case( json!( {
124
+ "document" : "query GetWeatherAlerts($state: String!) {\n alerts(state: $state) {\n severity\n description\n instruction\n }\n }" ,
125
+ "headers" : "{}"
126
+ } ) , "variables" ) ]
127
+ #[ case( json!( {
128
+ "document" : "query GetWeatherAlerts($state: String!) {\n alerts(state: $state) {\n severity\n description\n instruction\n }\n }" ,
129
+ "variables" : "{\" state\" : \" CA\" }"
130
+ } ) , "headers" ) ]
131
+ async fn test_input_missing_fields ( #[ case] input : Value , #[ case] missing_field : & str ) {
132
+ let explorer = Explorer :: new ( String :: from ( "mcp-example@mcp" ) ) ;
133
+ let url = explorer. create_explorer_url ( & input) ;
134
+ let filled_input = {
135
+ let mut input = input;
136
+ if missing_field == "document" {
137
+ if let Some ( obj) = input. as_object_mut ( ) {
138
+ obj. insert ( "document" . to_string ( ) , Value :: String ( "{}" . to_string ( ) ) ) ;
139
+ }
140
+ }
141
+ if missing_field == "variables" {
142
+ if let Some ( obj) = input. as_object_mut ( ) {
143
+ obj. insert ( "variables" . to_string ( ) , Value :: String ( "{}" . to_string ( ) ) ) ;
144
+ }
145
+ }
146
+ if missing_field == "headers" {
147
+ if let Some ( obj) = input. as_object_mut ( ) {
148
+ obj. insert ( "headers" . to_string ( ) , Value :: String ( "{}" . to_string ( ) ) ) ;
149
+ }
150
+ }
151
+ input
152
+ } ;
153
+ let expected_url = explorer. create_explorer_url ( & filled_input) ;
154
+ assert_eq ! ( url, expected_url) ;
155
+ }
139
156
}
0 commit comments