From 94be6c06e1e28bc71ff92c588c4552458f79392a Mon Sep 17 00:00:00 2001 From: Mike Gabriel Date: Mon, 5 Jan 2026 09:50:09 -0500 Subject: [PATCH 01/11] feat(router): add omit_operation_prefix config for MCP tool names Add a boolean configuration option to omit the 'execute_operation_' prefix from MCP tool names. When enabled, an operation named 'GetUser' generates tool name 'get_user' instead of 'execute_operation_get_user'. Closes #2308 --- router/core/router.go | 1 + router/pkg/config/config.go | 1 + router/pkg/config/config.schema.json | 5 +++++ router/pkg/config/fixtures/full.yaml | 1 + .../pkg/config/testdata/config_defaults.json | 1 + router/pkg/config/testdata/config_full.json | 1 + router/pkg/mcpserver/server.go | 18 +++++++++++++++++- 7 files changed, 27 insertions(+), 1 deletion(-) diff --git a/router/core/router.go b/router/core/router.go index 8bdc557b29..190a6ac15e 100644 --- a/router/core/router.go +++ b/router/core/router.go @@ -939,6 +939,7 @@ func (r *Router) bootstrap(ctx context.Context) error { mcpserver.WithExcludeMutations(r.mcp.ExcludeMutations), mcpserver.WithEnableArbitraryOperations(r.mcp.EnableArbitraryOperations), mcpserver.WithExposeSchema(r.mcp.ExposeSchema), + mcpserver.WithOmitOperationPrefix(r.mcp.OmitOperationPrefix), mcpserver.WithStateless(r.mcp.Session.Stateless), } diff --git a/router/pkg/config/config.go b/router/pkg/config/config.go index 4e430627c0..479eb2339d 100644 --- a/router/pkg/config/config.go +++ b/router/pkg/config/config.go @@ -997,6 +997,7 @@ type MCPConfiguration struct { ExcludeMutations bool `yaml:"exclude_mutations" envDefault:"false" env:"MCP_EXCLUDE_MUTATIONS"` EnableArbitraryOperations bool `yaml:"enable_arbitrary_operations" envDefault:"false" env:"MCP_ENABLE_ARBITRARY_OPERATIONS"` ExposeSchema bool `yaml:"expose_schema" envDefault:"false" env:"MCP_EXPOSE_SCHEMA"` + OmitOperationPrefix bool `yaml:"omit_operation_prefix" envDefault:"false" env:"MCP_OMIT_OPERATION_PREFIX"` RouterURL string `yaml:"router_url,omitempty" env:"MCP_ROUTER_URL"` } diff --git a/router/pkg/config/config.schema.json b/router/pkg/config/config.schema.json index 0b5fc698c3..3d66a46f69 100644 --- a/router/pkg/config/config.schema.json +++ b/router/pkg/config/config.schema.json @@ -2131,6 +2131,11 @@ "type": "boolean", "default": false, "description": "Expose the full GraphQL schema through MCP. When enabled, AI models can request the complete schema of your API." + }, + "omit_operation_prefix": { + "type": "boolean", + "default": false, + "description": "When true, MCP tool names for GraphQL operations will be generated without the 'execute_operation_' prefix. For example, an operation named 'GetUser' will become 'get_user' instead of 'execute_operation_get_user'. This provides cleaner tool names for AI models but may break existing integrations." } } }, diff --git a/router/pkg/config/fixtures/full.yaml b/router/pkg/config/fixtures/full.yaml index 515d08f45d..113f1de8d3 100644 --- a/router/pkg/config/fixtures/full.yaml +++ b/router/pkg/config/fixtures/full.yaml @@ -45,6 +45,7 @@ mcp: expose_schema: false enable_arbitrary_operations: false exclude_mutations: false + omit_operation_prefix: false graph_name: cosmo router_url: https://cosmo-router.wundergraph.com server: diff --git a/router/pkg/config/testdata/config_defaults.json b/router/pkg/config/testdata/config_defaults.json index ce91d417e7..12d02cac89 100644 --- a/router/pkg/config/testdata/config_defaults.json +++ b/router/pkg/config/testdata/config_defaults.json @@ -137,6 +137,7 @@ "ExcludeMutations": false, "EnableArbitraryOperations": false, "ExposeSchema": false, + "OmitOperationPrefix": false, "RouterURL": "" }, "DemoMode": false, diff --git a/router/pkg/config/testdata/config_full.json b/router/pkg/config/testdata/config_full.json index 02064e09d5..64d55d81e4 100644 --- a/router/pkg/config/testdata/config_full.json +++ b/router/pkg/config/testdata/config_full.json @@ -172,6 +172,7 @@ "ExcludeMutations": false, "EnableArbitraryOperations": false, "ExposeSchema": false, + "OmitOperationPrefix": false, "RouterURL": "https://cosmo-router.wundergraph.com" }, "DemoMode": true, diff --git a/router/pkg/mcpserver/server.go b/router/pkg/mcpserver/server.go index 173e2bf9d2..d776c61123 100644 --- a/router/pkg/mcpserver/server.go +++ b/router/pkg/mcpserver/server.go @@ -90,6 +90,8 @@ type Options struct { EnableArbitraryOperations bool // ExposeSchema determines whether the GraphQL schema is exposed ExposeSchema bool + // OmitOperationPrefix determines whether to omit the "execute_operation_" prefix from tool names + OmitOperationPrefix bool // Stateless determines whether the MCP server should be stateless Stateless bool // CorsConfig is the CORS configuration for the MCP server @@ -110,6 +112,7 @@ type GraphQLSchemaServer struct { excludeMutations bool enableArbitraryOperations bool exposeSchema bool + omitOperationPrefix bool stateless bool operationsManager *OperationsManager schemaCompiler *SchemaCompiler @@ -240,6 +243,7 @@ func NewGraphQLSchemaServer(routerGraphQLEndpoint string, opts ...func(*Options) excludeMutations: options.ExcludeMutations, enableArbitraryOperations: options.EnableArbitraryOperations, exposeSchema: options.ExposeSchema, + omitOperationPrefix: options.OmitOperationPrefix, stateless: options.Stateless, corsConfig: options.CorsConfig, } @@ -307,6 +311,13 @@ func WithStateless(stateless bool) func(*Options) { } } +// WithOmitOperationPrefix sets the omit operation prefix option +func WithOmitOperationPrefix(omitOperationPrefix bool) func(*Options) { + return func(o *Options) { + o.OmitOperationPrefix = omitOperationPrefix + } +} + func WithCORS(corsCfg cors.Config) func(*Options) { return func(o *Options) { // Force specific CORS settings for MCP server @@ -547,7 +558,12 @@ func (s *GraphQLSchemaServer) registerTools() error { toolDescription = fmt.Sprintf("Executes the GraphQL operation '%s' of type %s.", op.Name, op.OperationType) } - toolName := fmt.Sprintf("execute_operation_%s", operationToolName) + var toolName string + if s.omitOperationPrefix { + toolName = operationToolName + } else { + toolName = fmt.Sprintf("execute_operation_%s", operationToolName) + } tool := mcp.NewToolWithRawSchema( toolName, toolDescription, From 1c7397a1f83ff6971fc055ec105a11d829800aa8 Mon Sep 17 00:00:00 2001 From: Mike Gabriel Date: Mon, 5 Jan 2026 10:07:51 -0500 Subject: [PATCH 02/11] test(router): add integration tests for omit_operation_prefix config Add tests verifying: - Tool names are generated without 'execute_operation_' prefix when enabled - Tools can be executed using the short names --- router-tests/mcp_test.go | 65 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/router-tests/mcp_test.go b/router-tests/mcp_test.go index e89a2c0388..2a37c5fc91 100644 --- a/router-tests/mcp_test.go +++ b/router-tests/mcp_test.go @@ -161,6 +161,71 @@ func TestMCP(t *testing.T) { }) }) + t.Run("List user Operations / Tool names omit prefix when OmitOperationPrefix is enabled", func(t *testing.T) { + testenv.Run(t, &testenv.Config{ + MCP: config.MCPConfiguration{ + Enabled: true, + OmitOperationPrefix: true, + }, + }, func(t *testing.T, xEnv *testenv.Environment) { + + toolsRequest := mcp.ListToolsRequest{} + resp, err := xEnv.MCPClient.ListTools(xEnv.Context, toolsRequest) + require.NoError(t, err) + require.NotNil(t, resp) + + var foundMyEmployees, foundUpdateMood bool + var foundPrefixedMyEmployees, foundPrefixedUpdateMood bool + + for _, tool := range resp.Tools { + switch tool.Name { + case "my_employees": + foundMyEmployees = true + case "update_mood": + foundUpdateMood = true + case "execute_operation_my_employees": + foundPrefixedMyEmployees = true + case "execute_operation_update_mood": + foundPrefixedUpdateMood = true + } + } + + require.True(t, foundMyEmployees, "Tool 'my_employees' should be registered when OmitOperationPrefix is true") + require.True(t, foundUpdateMood, "Tool 'update_mood' should be registered when OmitOperationPrefix is true") + + require.False(t, foundPrefixedMyEmployees, "Tool 'execute_operation_my_employees' should NOT be registered when OmitOperationPrefix is true") + require.False(t, foundPrefixedUpdateMood, "Tool 'execute_operation_update_mood' should NOT be registered when OmitOperationPrefix is true") + }) + }) + + t.Run("Execute operation using short tool name when OmitOperationPrefix is enabled", func(t *testing.T) { + testenv.Run(t, &testenv.Config{ + MCP: config.MCPConfiguration{ + Enabled: true, + OmitOperationPrefix: true, + }, + }, func(t *testing.T, xEnv *testenv.Environment) { + + req := mcp.CallToolRequest{} + req.Params.Name = "my_employees" + req.Params.Arguments = map[string]interface{}{ + "criteria": map[string]interface{}{}, + } + + resp, err := xEnv.MCPClient.CallTool(xEnv.Context, req) + assert.NoError(t, err) + assert.NotNil(t, resp) + + assert.Len(t, resp.Content, 1) + + content, ok := resp.Content[0].(mcp.TextContent) + assert.True(t, ok) + + assert.Equal(t, content.Type, "text") + assert.Contains(t, content.Text, "findEmployees") + }) + }) + t.Run("List user Operations / Static operations of type mutation aren't exposed when excludeMutations is set", func(t *testing.T) { testenv.Run(t, &testenv.Config{ MCP: config.MCPConfiguration{ From 141bcdf23559f6d25b82dd01a75b3d742b4c7499 Mon Sep 17 00:00:00 2001 From: Mike Gabriel Date: Mon, 5 Jan 2026 10:30:02 -0500 Subject: [PATCH 03/11] docs(router): add comment for OmitOperationPrefix config field --- router/pkg/config/config.go | 38 +++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/router/pkg/config/config.go b/router/pkg/config/config.go index 479eb2339d..04f7fa2bbd 100644 --- a/router/pkg/config/config.go +++ b/router/pkg/config/config.go @@ -360,25 +360,25 @@ func (r *ResponseHeaderRule) GetMatching() string { } type EngineDebugConfiguration struct { - PrintOperationTransformations bool `envDefault:"false" env:"ENGINE_DEBUG_PRINT_OPERATION_TRANSFORMATIONS" yaml:"print_operation_transformations"` - PrintOperationEnableASTRefs bool `envDefault:"false" env:"ENGINE_DEBUG_PRINT_OPERATION_ENABLE_AST_REFS" yaml:"print_operation_enable_ast_refs"` - PrintPlanningPaths bool `envDefault:"false" env:"ENGINE_DEBUG_PRINT_PLANNING_PATHS" yaml:"print_planning_paths"` - PrintQueryPlans bool `envDefault:"false" env:"ENGINE_DEBUG_PRINT_QUERY_PLANS" yaml:"print_query_plans"` - PrintIntermediateQueryPlans bool `envDefault:"false" env:"ENGINE_DEBUG_PRINT_INTERMEDIATE_QUERY_PLANS" yaml:"print_intermediate_query_plans"` - PrintNodeSuggestions bool `envDefault:"false" env:"ENGINE_DEBUG_PRINT_NODE_SUGGESTIONS" yaml:"print_node_suggestions"` - ConfigurationVisitor bool `envDefault:"false" env:"ENGINE_DEBUG_CONFIGURATION_VISITOR" yaml:"configuration_visitor"` - PlanningVisitor bool `envDefault:"false" env:"ENGINE_DEBUG_PLANNING_VISITOR" yaml:"planning_visitor"` - DatasourceVisitor bool `envDefault:"false" env:"ENGINE_DEBUG_DATASOURCE_VISITOR" yaml:"datasource_visitor"` - ReportWebSocketConnections bool `envDefault:"false" env:"ENGINE_DEBUG_REPORT_WEBSOCKET_CONNECTIONS" yaml:"report_websocket_connections"` - ReportMemoryUsage bool `envDefault:"false" env:"ENGINE_DEBUG_REPORT_MEMORY_USAGE" yaml:"report_memory_usage"` - EnableResolverDebugging bool `envDefault:"false" env:"ENGINE_DEBUG_ENABLE_RESOLVER_DEBUGGING" yaml:"enable_resolver_debugging"` + PrintOperationTransformations bool `envDefault:"false" env:"ENGINE_DEBUG_PRINT_OPERATION_TRANSFORMATIONS" yaml:"print_operation_transformations"` + PrintOperationEnableASTRefs bool `envDefault:"false" env:"ENGINE_DEBUG_PRINT_OPERATION_ENABLE_AST_REFS" yaml:"print_operation_enable_ast_refs"` + PrintPlanningPaths bool `envDefault:"false" env:"ENGINE_DEBUG_PRINT_PLANNING_PATHS" yaml:"print_planning_paths"` + PrintQueryPlans bool `envDefault:"false" env:"ENGINE_DEBUG_PRINT_QUERY_PLANS" yaml:"print_query_plans"` + PrintIntermediateQueryPlans bool `envDefault:"false" env:"ENGINE_DEBUG_PRINT_INTERMEDIATE_QUERY_PLANS" yaml:"print_intermediate_query_plans"` + PrintNodeSuggestions bool `envDefault:"false" env:"ENGINE_DEBUG_PRINT_NODE_SUGGESTIONS" yaml:"print_node_suggestions"` + ConfigurationVisitor bool `envDefault:"false" env:"ENGINE_DEBUG_CONFIGURATION_VISITOR" yaml:"configuration_visitor"` + PlanningVisitor bool `envDefault:"false" env:"ENGINE_DEBUG_PLANNING_VISITOR" yaml:"planning_visitor"` + DatasourceVisitor bool `envDefault:"false" env:"ENGINE_DEBUG_DATASOURCE_VISITOR" yaml:"datasource_visitor"` + ReportWebSocketConnections bool `envDefault:"false" env:"ENGINE_DEBUG_REPORT_WEBSOCKET_CONNECTIONS" yaml:"report_websocket_connections"` + ReportMemoryUsage bool `envDefault:"false" env:"ENGINE_DEBUG_REPORT_MEMORY_USAGE" yaml:"report_memory_usage"` + EnableResolverDebugging bool `envDefault:"false" env:"ENGINE_DEBUG_ENABLE_RESOLVER_DEBUGGING" yaml:"enable_resolver_debugging"` // EnablePersistedOperationsCacheResponseHeader is deprecated, use EnableCacheResponseHeaders instead. EnablePersistedOperationsCacheResponseHeader bool `envDefault:"false" env:"ENGINE_DEBUG_ENABLE_PERSISTED_OPERATIONS_CACHE_RESPONSE_HEADER" yaml:"enable_persisted_operations_cache_response_header"` // EnableNormalizationCacheResponseHeader is deprecated, use EnableCacheResponseHeaders instead. - EnableNormalizationCacheResponseHeader bool `envDefault:"false" env:"ENGINE_DEBUG_ENABLE_NORMALIZATION_CACHE_RESPONSE_HEADER" yaml:"enable_normalization_cache_response_header"` - EnableCacheResponseHeaders bool `envDefault:"false" env:"ENGINE_DEBUG_ENABLE_CACHE_RESPONSE_HEADERS" yaml:"enable_cache_response_headers"` - AlwaysIncludeQueryPlan bool `envDefault:"false" env:"ENGINE_DEBUG_ALWAYS_INCLUDE_QUERY_PLAN" yaml:"always_include_query_plan"` - AlwaysSkipLoader bool `envDefault:"false" env:"ENGINE_DEBUG_ALWAYS_SKIP_LOADER" yaml:"always_skip_loader"` + EnableNormalizationCacheResponseHeader bool `envDefault:"false" env:"ENGINE_DEBUG_ENABLE_NORMALIZATION_CACHE_RESPONSE_HEADER" yaml:"enable_normalization_cache_response_header"` + EnableCacheResponseHeaders bool `envDefault:"false" env:"ENGINE_DEBUG_ENABLE_CACHE_RESPONSE_HEADERS" yaml:"enable_cache_response_headers"` + AlwaysIncludeQueryPlan bool `envDefault:"false" env:"ENGINE_DEBUG_ALWAYS_INCLUDE_QUERY_PLAN" yaml:"always_include_query_plan"` + AlwaysSkipLoader bool `envDefault:"false" env:"ENGINE_DEBUG_ALWAYS_SKIP_LOADER" yaml:"always_skip_loader"` } type EngineExecutionConfiguration struct { @@ -997,8 +997,10 @@ type MCPConfiguration struct { ExcludeMutations bool `yaml:"exclude_mutations" envDefault:"false" env:"MCP_EXCLUDE_MUTATIONS"` EnableArbitraryOperations bool `yaml:"enable_arbitrary_operations" envDefault:"false" env:"MCP_ENABLE_ARBITRARY_OPERATIONS"` ExposeSchema bool `yaml:"expose_schema" envDefault:"false" env:"MCP_EXPOSE_SCHEMA"` - OmitOperationPrefix bool `yaml:"omit_operation_prefix" envDefault:"false" env:"MCP_OMIT_OPERATION_PREFIX"` - RouterURL string `yaml:"router_url,omitempty" env:"MCP_ROUTER_URL"` + // OmitOperationPrefix removes the "execute_operation_" prefix from MCP tool names. + // When true, GetUser becomes get_user. When false (default), GetUser becomes execute_operation_get_user. + OmitOperationPrefix bool `yaml:"omit_operation_prefix" envDefault:"false" env:"MCP_OMIT_OPERATION_PREFIX"` + RouterURL string `yaml:"router_url,omitempty" env:"MCP_ROUTER_URL"` } type MCPSessionConfig struct { From 2c5e31535363552dc4b85ecada8206a95138bb45 Mon Sep 17 00:00:00 2001 From: Mike Gabriel Date: Mon, 5 Jan 2026 12:26:26 -0500 Subject: [PATCH 04/11] refactor(router): rename omit_operation_prefix to strip_tool_name_prefix Rename MCP config option per maintainer feedback: - omit_operation_prefix -> strip_tool_name_prefix - OmitOperationPrefix -> StripToolNamePrefix - MCP_OMIT_OPERATION_PREFIX -> MCP_STRIP_TOOL_NAME_PREFIX Updated description to clarify the option removes the 'execute_operation_' prefix from MCP tool names. --- router-tests/mcp_test.go | 16 ++-- router/core/router.go | 2 +- router/pkg/config/config.go | 6 +- router/pkg/config/config.schema.json | 4 +- router/pkg/config/fixtures/full.yaml | 4 +- .../pkg/config/testdata/config_defaults.json | 36 +++------ router/pkg/config/testdata/config_full.json | 75 +++++-------------- router/pkg/mcpserver/server.go | 16 ++-- 8 files changed, 50 insertions(+), 109 deletions(-) diff --git a/router-tests/mcp_test.go b/router-tests/mcp_test.go index 2a37c5fc91..dce7d40a06 100644 --- a/router-tests/mcp_test.go +++ b/router-tests/mcp_test.go @@ -161,11 +161,11 @@ func TestMCP(t *testing.T) { }) }) - t.Run("List user Operations / Tool names omit prefix when OmitOperationPrefix is enabled", func(t *testing.T) { + t.Run("List user Operations / Tool names omit prefix when StripToolNamePrefix is enabled", func(t *testing.T) { testenv.Run(t, &testenv.Config{ MCP: config.MCPConfiguration{ Enabled: true, - OmitOperationPrefix: true, + StripToolNamePrefix: true, }, }, func(t *testing.T, xEnv *testenv.Environment) { @@ -190,19 +190,19 @@ func TestMCP(t *testing.T) { } } - require.True(t, foundMyEmployees, "Tool 'my_employees' should be registered when OmitOperationPrefix is true") - require.True(t, foundUpdateMood, "Tool 'update_mood' should be registered when OmitOperationPrefix is true") + require.True(t, foundMyEmployees, "Tool 'my_employees' should be registered when StripToolNamePrefix is true") + require.True(t, foundUpdateMood, "Tool 'update_mood' should be registered when StripToolNamePrefix is true") - require.False(t, foundPrefixedMyEmployees, "Tool 'execute_operation_my_employees' should NOT be registered when OmitOperationPrefix is true") - require.False(t, foundPrefixedUpdateMood, "Tool 'execute_operation_update_mood' should NOT be registered when OmitOperationPrefix is true") + require.False(t, foundPrefixedMyEmployees, "Tool 'execute_operation_my_employees' should NOT be registered when StripToolNamePrefix is true") + require.False(t, foundPrefixedUpdateMood, "Tool 'execute_operation_update_mood' should NOT be registered when StripToolNamePrefix is true") }) }) - t.Run("Execute operation using short tool name when OmitOperationPrefix is enabled", func(t *testing.T) { + t.Run("Execute operation using short tool name when StripToolNamePrefix is enabled", func(t *testing.T) { testenv.Run(t, &testenv.Config{ MCP: config.MCPConfiguration{ Enabled: true, - OmitOperationPrefix: true, + StripToolNamePrefix: true, }, }, func(t *testing.T, xEnv *testenv.Environment) { diff --git a/router/core/router.go b/router/core/router.go index 190a6ac15e..c0711750ed 100644 --- a/router/core/router.go +++ b/router/core/router.go @@ -939,7 +939,7 @@ func (r *Router) bootstrap(ctx context.Context) error { mcpserver.WithExcludeMutations(r.mcp.ExcludeMutations), mcpserver.WithEnableArbitraryOperations(r.mcp.EnableArbitraryOperations), mcpserver.WithExposeSchema(r.mcp.ExposeSchema), - mcpserver.WithOmitOperationPrefix(r.mcp.OmitOperationPrefix), + mcpserver.WithStripToolNamePrefix(r.mcp.StripToolNamePrefix), mcpserver.WithStateless(r.mcp.Session.Stateless), } diff --git a/router/pkg/config/config.go b/router/pkg/config/config.go index 04f7fa2bbd..f54d9b62a1 100644 --- a/router/pkg/config/config.go +++ b/router/pkg/config/config.go @@ -997,9 +997,9 @@ type MCPConfiguration struct { ExcludeMutations bool `yaml:"exclude_mutations" envDefault:"false" env:"MCP_EXCLUDE_MUTATIONS"` EnableArbitraryOperations bool `yaml:"enable_arbitrary_operations" envDefault:"false" env:"MCP_ENABLE_ARBITRARY_OPERATIONS"` ExposeSchema bool `yaml:"expose_schema" envDefault:"false" env:"MCP_EXPOSE_SCHEMA"` - // OmitOperationPrefix removes the "execute_operation_" prefix from MCP tool names. - // When true, GetUser becomes get_user. When false (default), GetUser becomes execute_operation_get_user. - OmitOperationPrefix bool `yaml:"omit_operation_prefix" envDefault:"false" env:"MCP_OMIT_OPERATION_PREFIX"` + // StripToolNamePrefix removes the "execute_operation_" prefix from MCP tool names. + // When enabled, GetUser becomes get_user. When disabled (default), GetUser becomes execute_operation_get_user. + StripToolNamePrefix bool `yaml:"strip_tool_name_prefix" envDefault:"false" env:"MCP_STRIP_TOOL_NAME_PREFIX"` RouterURL string `yaml:"router_url,omitempty" env:"MCP_ROUTER_URL"` } diff --git a/router/pkg/config/config.schema.json b/router/pkg/config/config.schema.json index 3d66a46f69..57e6d887c0 100644 --- a/router/pkg/config/config.schema.json +++ b/router/pkg/config/config.schema.json @@ -2132,10 +2132,10 @@ "default": false, "description": "Expose the full GraphQL schema through MCP. When enabled, AI models can request the complete schema of your API." }, - "omit_operation_prefix": { + "strip_tool_name_prefix": { "type": "boolean", "default": false, - "description": "When true, MCP tool names for GraphQL operations will be generated without the 'execute_operation_' prefix. For example, an operation named 'GetUser' will become 'get_user' instead of 'execute_operation_get_user'. This provides cleaner tool names for AI models but may break existing integrations." + "description": "When enabled, MCP tool names generated from GraphQL operations omit the 'execute_operation_' prefix. For example, the GraphQL operation 'GetUser' results in a tool named 'get_user' instead of 'execute_operation_get_user'. This produces cleaner tool names for AI models." } } }, diff --git a/router/pkg/config/fixtures/full.yaml b/router/pkg/config/fixtures/full.yaml index 113f1de8d3..04badbdb9f 100644 --- a/router/pkg/config/fixtures/full.yaml +++ b/router/pkg/config/fixtures/full.yaml @@ -45,7 +45,7 @@ mcp: expose_schema: false enable_arbitrary_operations: false exclude_mutations: false - omit_operation_prefix: false + strip_tool_name_prefix: false graph_name: cosmo router_url: https://cosmo-router.wundergraph.com server: @@ -178,7 +178,7 @@ telemetry: schema_usage: enabled: true include_operation_sha: true - sample_rate: 1.0 # Supports any rate: 1.0, 0.8, 0.5, 0.1, 0.01, etc. + sample_rate: 1.0 # Supports any rate: 1.0, 0.8, 0.5, 0.1, 0.01, etc. cache_control_policy: enabled: true diff --git a/router/pkg/config/testdata/config_defaults.json b/router/pkg/config/testdata/config_defaults.json index 12d02cac89..2d87b65fb5 100644 --- a/router/pkg/config/testdata/config_defaults.json +++ b/router/pkg/config/testdata/config_defaults.json @@ -80,19 +80,9 @@ }, "CORS": { "Enabled": true, - "AllowOrigins": [ - "*" - ], - "AllowMethods": [ - "HEAD", - "GET", - "POST" - ], - "AllowHeaders": [ - "Origin", - "Content-Length", - "Content-Type" - ], + "AllowOrigins": ["*"], + "AllowMethods": ["HEAD", "GET", "POST"], + "AllowHeaders": ["Origin", "Content-Length", "Content-Type"], "AllowCredentials": true, "MaxAge": 300000000000 }, @@ -137,7 +127,7 @@ "ExcludeMutations": false, "EnableArbitraryOperations": false, "ExposeSchema": false, - "OmitOperationPrefix": false, + "StripToolNamePrefix": false, "RouterURL": "" }, "DemoMode": false, @@ -214,9 +204,7 @@ }, "Router": { "Fields": null, - "IgnoreQueryParamsList": [ - "variables" - ] + "IgnoreQueryParamsList": ["variables"] }, "Subgraphs": { "Enabled": false, @@ -409,15 +397,11 @@ }, "ForwardUpgradeHeaders": { "Enabled": true, - "AllowList": [ - "Authorization" - ] + "AllowList": ["Authorization"] }, "ForwardUpgradeQueryParams": { "Enabled": true, - "AllowList": [ - "Authorization" - ] + "AllowList": ["Authorization"] }, "ForwardInitialPayload": true, "Authentication": { @@ -451,9 +435,7 @@ "AttachServiceName": true, "DefaultExtensionCode": "DOWNSTREAM_SERVICE_ERROR", "AllowAllExtensionFields": false, - "AllowedExtensionFields": [ - "code" - ], + "AllowedExtensionFields": ["code"], "AllowedFields": null }, "StorageProviders": { @@ -557,4 +539,4 @@ "Maximum": 10000000000 } } -} \ No newline at end of file +} diff --git a/router/pkg/config/testdata/config_full.json b/router/pkg/config/testdata/config_full.json index 64d55d81e4..b8ed532ea7 100644 --- a/router/pkg/config/testdata/config_full.json +++ b/router/pkg/config/testdata/config_full.json @@ -110,19 +110,9 @@ }, "CORS": { "Enabled": true, - "AllowOrigins": [ - "*" - ], - "AllowMethods": [ - "HEAD", - "GET", - "POST" - ], - "AllowHeaders": [ - "Origin", - "Content-Length", - "Content-Type" - ], + "AllowOrigins": ["*"], + "AllowMethods": ["HEAD", "GET", "POST"], + "AllowHeaders": ["Origin", "Content-Length", "Content-Type"], "AllowCredentials": true, "MaxAge": 300000000000 }, @@ -172,7 +162,7 @@ "ExcludeMutations": false, "EnableArbitraryOperations": false, "ExposeSchema": false, - "OmitOperationPrefix": false, + "StripToolNamePrefix": false, "RouterURL": "https://cosmo-router.wundergraph.com" }, "DemoMode": true, @@ -307,10 +297,7 @@ ] } }, - "CookieWhitelist": [ - "cookie1", - "cookie2" - ] + "CookieWhitelist": ["cookie1", "cookie2"] }, "TrafficShaping": { "All": { @@ -433,10 +420,7 @@ } } ], - "IgnoreQueryParamsList": [ - "variables", - "anothervalue" - ] + "IgnoreQueryParamsList": ["variables", "anothervalue"] }, "Subgraphs": { "Enabled": true, @@ -490,9 +474,7 @@ "JWKS": [ { "URL": "https://example.com/.well-known/jwks.json", - "Algorithms": [ - "RS256" - ], + "Algorithms": ["RS256"], "RefreshInterval": 60000000000, "RefreshUnknownKID": { "Enabled": false, @@ -507,10 +489,7 @@ }, { "URL": "https://example.com/.well-known/jwks2.json", - "Algorithms": [ - "RS256", - "ES256" - ], + "Algorithms": ["RS256", "ES256"], "RefreshInterval": 120000000000, "RefreshUnknownKID": { "Enabled": true, @@ -545,10 +524,7 @@ { "Type": "header", "Name": "X-Authorization", - "ValuePrefixes": [ - "Bearer", - "Token" - ] + "ValuePrefixes": ["Bearer", "Token"] }, { "Type": "header", @@ -575,10 +551,7 @@ "HideStatsFromResponseExtension": false }, "Storage": { - "URLs": [ - "test@localhost:8000", - "test2@localhost:8001" - ], + "URLs": ["test@localhost:8000", "test2@localhost:8001"], "ClusterEnabled": true, "KeyPrefix": "cosmo_rate_limit" }, @@ -618,9 +591,7 @@ "Kafka": [ { "ID": "my-kafka", - "Brokers": [ - "localhost:9092" - ], + "Brokers": ["localhost:9092"], "Authentication": { "SASLPlain": { "Password": "admin", @@ -641,9 +612,7 @@ "Redis": [ { "ID": "my-redis", - "URLs": [ - "redis://localhost:6379/11" - ], + "URLs": ["redis://localhost:6379/11"], "ClusterEnabled": true } ] @@ -789,15 +758,11 @@ }, "ForwardUpgradeHeaders": { "Enabled": true, - "AllowList": [ - "Authorization" - ] + "AllowList": ["Authorization"] }, "ForwardUpgradeQueryParams": { "Enabled": true, - "AllowList": [ - "Authorization" - ] + "AllowList": ["Authorization"] }, "ForwardInitialPayload": true, "Authentication": { @@ -831,10 +796,7 @@ "AttachServiceName": true, "DefaultExtensionCode": "DOWNSTREAM_SERVICE_ERROR", "AllowAllExtensionFields": true, - "AllowedExtensionFields": [ - "field1", - "field2" - ], + "AllowedExtensionFields": ["field1", "field2"], "AllowedFields": null }, "StorageProviders": { @@ -853,10 +815,7 @@ "Redis": [ { "ID": "my_redis", - "URLs": [ - "test@localhost:8000", - "test2@localhost:8001" - ], + "URLs": ["test@localhost:8000", "test2@localhost:8001"], "ClusterEnabled": false } ], @@ -962,4 +921,4 @@ "Maximum": 10000000000 } } -} \ No newline at end of file +} diff --git a/router/pkg/mcpserver/server.go b/router/pkg/mcpserver/server.go index d776c61123..a7e79b5b15 100644 --- a/router/pkg/mcpserver/server.go +++ b/router/pkg/mcpserver/server.go @@ -90,8 +90,8 @@ type Options struct { EnableArbitraryOperations bool // ExposeSchema determines whether the GraphQL schema is exposed ExposeSchema bool - // OmitOperationPrefix determines whether to omit the "execute_operation_" prefix from tool names - OmitOperationPrefix bool + // StripToolNamePrefix removes the "execute_operation_" prefix from MCP tool names + StripToolNamePrefix bool // Stateless determines whether the MCP server should be stateless Stateless bool // CorsConfig is the CORS configuration for the MCP server @@ -112,7 +112,7 @@ type GraphQLSchemaServer struct { excludeMutations bool enableArbitraryOperations bool exposeSchema bool - omitOperationPrefix bool + stripToolNamePrefix bool stateless bool operationsManager *OperationsManager schemaCompiler *SchemaCompiler @@ -243,7 +243,7 @@ func NewGraphQLSchemaServer(routerGraphQLEndpoint string, opts ...func(*Options) excludeMutations: options.ExcludeMutations, enableArbitraryOperations: options.EnableArbitraryOperations, exposeSchema: options.ExposeSchema, - omitOperationPrefix: options.OmitOperationPrefix, + stripToolNamePrefix: options.StripToolNamePrefix, stateless: options.Stateless, corsConfig: options.CorsConfig, } @@ -311,10 +311,10 @@ func WithStateless(stateless bool) func(*Options) { } } -// WithOmitOperationPrefix sets the omit operation prefix option -func WithOmitOperationPrefix(omitOperationPrefix bool) func(*Options) { +// WithStripToolNamePrefix sets the strip tool name prefix option +func WithStripToolNamePrefix(stripToolNamePrefix bool) func(*Options) { return func(o *Options) { - o.OmitOperationPrefix = omitOperationPrefix + o.StripToolNamePrefix = stripToolNamePrefix } } @@ -559,7 +559,7 @@ func (s *GraphQLSchemaServer) registerTools() error { } var toolName string - if s.omitOperationPrefix { + if s.stripToolNamePrefix { toolName = operationToolName } else { toolName = fmt.Sprintf("execute_operation_%s", operationToolName) From 522a44aa9592ac1d25e8a84b01f3484a2b3fde4b Mon Sep 17 00:00:00 2001 From: Mike Gabriel <35150423+shamashel@users.noreply.github.com> Date: Wed, 7 Jan 2026 11:23:09 -0500 Subject: [PATCH 05/11] Update router/pkg/mcpserver/server.go Co-authored-by: Ahmet Soormally --- router/pkg/mcpserver/server.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/router/pkg/mcpserver/server.go b/router/pkg/mcpserver/server.go index a7e79b5b15..8bc87b6dee 100644 --- a/router/pkg/mcpserver/server.go +++ b/router/pkg/mcpserver/server.go @@ -558,10 +558,8 @@ func (s *GraphQLSchemaServer) registerTools() error { toolDescription = fmt.Sprintf("Executes the GraphQL operation '%s' of type %s.", op.Name, op.OperationType) } - var toolName string - if s.stripToolNamePrefix { - toolName = operationToolName - } else { + toolName := operationToolName + if !s.stripToolNamePrefix { toolName = fmt.Sprintf("execute_operation_%s", operationToolName) } tool := mcp.NewToolWithRawSchema( From 4f43b855cf8070c3e055373f102b38d9a7a6e9ea Mon Sep 17 00:00:00 2001 From: Mike Gabriel Date: Thu, 8 Jan 2026 09:46:50 -0500 Subject: [PATCH 06/11] refactor(router): rename strip_tool_name_prefix to omit_tool_name_prefix --- router-tests/mcp_test.go | 20 +++++++++---------- router/core/router.go | 2 +- router/pkg/config/config.go | 6 +++--- router/pkg/config/config.schema.json | 2 +- router/pkg/config/fixtures/full.yaml | 2 +- .../pkg/config/testdata/config_defaults.json | 2 +- router/pkg/config/testdata/config_full.json | 2 +- router/pkg/mcpserver/server.go | 16 +++++++-------- 8 files changed, 26 insertions(+), 26 deletions(-) diff --git a/router-tests/mcp_test.go b/router-tests/mcp_test.go index dce7d40a06..b275b2ed7a 100644 --- a/router-tests/mcp_test.go +++ b/router-tests/mcp_test.go @@ -161,11 +161,11 @@ func TestMCP(t *testing.T) { }) }) - t.Run("List user Operations / Tool names omit prefix when StripToolNamePrefix is enabled", func(t *testing.T) { + t.Run("List user Operations / Tool names omit prefix when OmitToolNamePrefix is enabled", func(t *testing.T) { testenv.Run(t, &testenv.Config{ MCP: config.MCPConfiguration{ - Enabled: true, - StripToolNamePrefix: true, + Enabled: true, + OmitToolNamePrefix: true, }, }, func(t *testing.T, xEnv *testenv.Environment) { @@ -190,19 +190,19 @@ func TestMCP(t *testing.T) { } } - require.True(t, foundMyEmployees, "Tool 'my_employees' should be registered when StripToolNamePrefix is true") - require.True(t, foundUpdateMood, "Tool 'update_mood' should be registered when StripToolNamePrefix is true") + require.True(t, foundMyEmployees, "Tool 'my_employees' should be registered when OmitToolNamePrefix is true") + require.True(t, foundUpdateMood, "Tool 'update_mood' should be registered when OmitToolNamePrefix is true") - require.False(t, foundPrefixedMyEmployees, "Tool 'execute_operation_my_employees' should NOT be registered when StripToolNamePrefix is true") - require.False(t, foundPrefixedUpdateMood, "Tool 'execute_operation_update_mood' should NOT be registered when StripToolNamePrefix is true") + require.False(t, foundPrefixedMyEmployees, "Tool 'execute_operation_my_employees' should NOT be registered when OmitToolNamePrefix is true") + require.False(t, foundPrefixedUpdateMood, "Tool 'execute_operation_update_mood' should NOT be registered when OmitToolNamePrefix is true") }) }) - t.Run("Execute operation using short tool name when StripToolNamePrefix is enabled", func(t *testing.T) { + t.Run("Execute operation using short tool name when OmitToolNamePrefix is enabled", func(t *testing.T) { testenv.Run(t, &testenv.Config{ MCP: config.MCPConfiguration{ - Enabled: true, - StripToolNamePrefix: true, + Enabled: true, + OmitToolNamePrefix: true, }, }, func(t *testing.T, xEnv *testenv.Environment) { diff --git a/router/core/router.go b/router/core/router.go index c0711750ed..ad4b77cc33 100644 --- a/router/core/router.go +++ b/router/core/router.go @@ -939,7 +939,7 @@ func (r *Router) bootstrap(ctx context.Context) error { mcpserver.WithExcludeMutations(r.mcp.ExcludeMutations), mcpserver.WithEnableArbitraryOperations(r.mcp.EnableArbitraryOperations), mcpserver.WithExposeSchema(r.mcp.ExposeSchema), - mcpserver.WithStripToolNamePrefix(r.mcp.StripToolNamePrefix), + mcpserver.WithOmitToolNamePrefix(r.mcp.OmitToolNamePrefix), mcpserver.WithStateless(r.mcp.Session.Stateless), } diff --git a/router/pkg/config/config.go b/router/pkg/config/config.go index f54d9b62a1..1755d413da 100644 --- a/router/pkg/config/config.go +++ b/router/pkg/config/config.go @@ -997,10 +997,10 @@ type MCPConfiguration struct { ExcludeMutations bool `yaml:"exclude_mutations" envDefault:"false" env:"MCP_EXCLUDE_MUTATIONS"` EnableArbitraryOperations bool `yaml:"enable_arbitrary_operations" envDefault:"false" env:"MCP_ENABLE_ARBITRARY_OPERATIONS"` ExposeSchema bool `yaml:"expose_schema" envDefault:"false" env:"MCP_EXPOSE_SCHEMA"` - // StripToolNamePrefix removes the "execute_operation_" prefix from MCP tool names. + // OmitToolNamePrefix removes the "execute_operation_" prefix from MCP tool names. // When enabled, GetUser becomes get_user. When disabled (default), GetUser becomes execute_operation_get_user. - StripToolNamePrefix bool `yaml:"strip_tool_name_prefix" envDefault:"false" env:"MCP_STRIP_TOOL_NAME_PREFIX"` - RouterURL string `yaml:"router_url,omitempty" env:"MCP_ROUTER_URL"` + OmitToolNamePrefix bool `yaml:"omit_tool_name_prefix" envDefault:"false" env:"MCP_OMIT_TOOL_NAME_PREFIX"` + RouterURL string `yaml:"router_url,omitempty" env:"MCP_ROUTER_URL"` } type MCPSessionConfig struct { diff --git a/router/pkg/config/config.schema.json b/router/pkg/config/config.schema.json index 57e6d887c0..a7fb9438ef 100644 --- a/router/pkg/config/config.schema.json +++ b/router/pkg/config/config.schema.json @@ -2132,7 +2132,7 @@ "default": false, "description": "Expose the full GraphQL schema through MCP. When enabled, AI models can request the complete schema of your API." }, - "strip_tool_name_prefix": { + "omit_tool_name_prefix": { "type": "boolean", "default": false, "description": "When enabled, MCP tool names generated from GraphQL operations omit the 'execute_operation_' prefix. For example, the GraphQL operation 'GetUser' results in a tool named 'get_user' instead of 'execute_operation_get_user'. This produces cleaner tool names for AI models." diff --git a/router/pkg/config/fixtures/full.yaml b/router/pkg/config/fixtures/full.yaml index 04badbdb9f..c4c2f29966 100644 --- a/router/pkg/config/fixtures/full.yaml +++ b/router/pkg/config/fixtures/full.yaml @@ -45,7 +45,7 @@ mcp: expose_schema: false enable_arbitrary_operations: false exclude_mutations: false - strip_tool_name_prefix: false + omit_tool_name_prefix: false graph_name: cosmo router_url: https://cosmo-router.wundergraph.com server: diff --git a/router/pkg/config/testdata/config_defaults.json b/router/pkg/config/testdata/config_defaults.json index 2d87b65fb5..9a35c9fec2 100644 --- a/router/pkg/config/testdata/config_defaults.json +++ b/router/pkg/config/testdata/config_defaults.json @@ -127,7 +127,7 @@ "ExcludeMutations": false, "EnableArbitraryOperations": false, "ExposeSchema": false, - "StripToolNamePrefix": false, + "OmitToolNamePrefix": false, "RouterURL": "" }, "DemoMode": false, diff --git a/router/pkg/config/testdata/config_full.json b/router/pkg/config/testdata/config_full.json index b8ed532ea7..640ad9a125 100644 --- a/router/pkg/config/testdata/config_full.json +++ b/router/pkg/config/testdata/config_full.json @@ -162,7 +162,7 @@ "ExcludeMutations": false, "EnableArbitraryOperations": false, "ExposeSchema": false, - "StripToolNamePrefix": false, + "OmitToolNamePrefix": false, "RouterURL": "https://cosmo-router.wundergraph.com" }, "DemoMode": true, diff --git a/router/pkg/mcpserver/server.go b/router/pkg/mcpserver/server.go index 8bc87b6dee..fee69d1555 100644 --- a/router/pkg/mcpserver/server.go +++ b/router/pkg/mcpserver/server.go @@ -90,8 +90,8 @@ type Options struct { EnableArbitraryOperations bool // ExposeSchema determines whether the GraphQL schema is exposed ExposeSchema bool - // StripToolNamePrefix removes the "execute_operation_" prefix from MCP tool names - StripToolNamePrefix bool + // OmitToolNamePrefix removes the "execute_operation_" prefix from MCP tool names + OmitToolNamePrefix bool // Stateless determines whether the MCP server should be stateless Stateless bool // CorsConfig is the CORS configuration for the MCP server @@ -112,7 +112,7 @@ type GraphQLSchemaServer struct { excludeMutations bool enableArbitraryOperations bool exposeSchema bool - stripToolNamePrefix bool + omitToolNamePrefix bool stateless bool operationsManager *OperationsManager schemaCompiler *SchemaCompiler @@ -243,7 +243,7 @@ func NewGraphQLSchemaServer(routerGraphQLEndpoint string, opts ...func(*Options) excludeMutations: options.ExcludeMutations, enableArbitraryOperations: options.EnableArbitraryOperations, exposeSchema: options.ExposeSchema, - stripToolNamePrefix: options.StripToolNamePrefix, + omitToolNamePrefix: options.OmitToolNamePrefix, stateless: options.Stateless, corsConfig: options.CorsConfig, } @@ -311,10 +311,10 @@ func WithStateless(stateless bool) func(*Options) { } } -// WithStripToolNamePrefix sets the strip tool name prefix option -func WithStripToolNamePrefix(stripToolNamePrefix bool) func(*Options) { +// WithOmitToolNamePrefix sets the omit tool name prefix option +func WithOmitToolNamePrefix(omitToolNamePrefix bool) func(*Options) { return func(o *Options) { - o.StripToolNamePrefix = stripToolNamePrefix + o.OmitToolNamePrefix = omitToolNamePrefix } } @@ -559,7 +559,7 @@ func (s *GraphQLSchemaServer) registerTools() error { } toolName := operationToolName - if !s.stripToolNamePrefix { + if !s.omitToolNamePrefix { toolName = fmt.Sprintf("execute_operation_%s", operationToolName) } tool := mcp.NewToolWithRawSchema( From 8a828de91ddfd507dd9f3d539c01bae4ddc1047f Mon Sep 17 00:00:00 2001 From: Mike Gabriel Date: Thu, 8 Jan 2026 10:40:01 -0500 Subject: [PATCH 07/11] fix(router): auto-prefix tool names that collide with built-in MCP tools --- router-tests/mcp_test.go | 30 +++++++++++++++++++ .../GetSchema.graphql | 5 ++++ .../MyMutation.graphql | 9 ++++++ .../mcp_operations_collision/MyQuery.graphql | 12 ++++++++ router-tests/testenv/testenv.go | 8 +++-- router/pkg/mcpserver/server.go | 8 +++++ 6 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 router-tests/testdata/mcp_operations_collision/GetSchema.graphql create mode 100644 router-tests/testdata/mcp_operations_collision/MyMutation.graphql create mode 100644 router-tests/testdata/mcp_operations_collision/MyQuery.graphql diff --git a/router-tests/mcp_test.go b/router-tests/mcp_test.go index b275b2ed7a..4c442e6670 100644 --- a/router-tests/mcp_test.go +++ b/router-tests/mcp_test.go @@ -226,6 +226,36 @@ func TestMCP(t *testing.T) { }) }) + t.Run("Tool name collision with built-in tool uses prefixed name when OmitToolNamePrefix is enabled", func(t *testing.T) { + testenv.Run(t, &testenv.Config{ + MCPOperationsPath: "testdata/mcp_operations_collision", + MCP: config.MCPConfiguration{ + Enabled: true, + OmitToolNamePrefix: true, + ExposeSchema: true, + }, + }, func(t *testing.T, xEnv *testenv.Environment) { + toolsRequest := mcp.ListToolsRequest{} + resp, err := xEnv.MCPClient.ListTools(xEnv.Context, toolsRequest) + require.NoError(t, err) + require.NotNil(t, resp) + + var foundBuiltInGetSchema, foundPrefixedGetSchema bool + + for _, tool := range resp.Tools { + switch tool.Name { + case "get_schema": + foundBuiltInGetSchema = true + case "execute_operation_get_schema": + foundPrefixedGetSchema = true + } + } + + require.True(t, foundBuiltInGetSchema, "Built-in 'get_schema' tool should be registered") + require.True(t, foundPrefixedGetSchema, "Conflicting operation should use prefixed name 'execute_operation_get_schema'") + }) + }) + t.Run("List user Operations / Static operations of type mutation aren't exposed when excludeMutations is set", func(t *testing.T) { testenv.Run(t, &testenv.Config{ MCP: config.MCPConfiguration{ diff --git a/router-tests/testdata/mcp_operations_collision/GetSchema.graphql b/router-tests/testdata/mcp_operations_collision/GetSchema.graphql new file mode 100644 index 0000000000..101a0f67f2 --- /dev/null +++ b/router-tests/testdata/mcp_operations_collision/GetSchema.graphql @@ -0,0 +1,5 @@ +query GetSchema { + employees { + id + } +} diff --git a/router-tests/testdata/mcp_operations_collision/MyMutation.graphql b/router-tests/testdata/mcp_operations_collision/MyMutation.graphql new file mode 100644 index 0000000000..48aa6d2f34 --- /dev/null +++ b/router-tests/testdata/mcp_operations_collision/MyMutation.graphql @@ -0,0 +1,9 @@ +mutation UpdateMood($employeeID: Int! $mood: Mood!) { + updateMood(employeeID: $employeeID mood: $mood) { + id + details { + forename + } + currentMood + } +} \ No newline at end of file diff --git a/router-tests/testdata/mcp_operations_collision/MyQuery.graphql b/router-tests/testdata/mcp_operations_collision/MyQuery.graphql new file mode 100644 index 0000000000..f592461a53 --- /dev/null +++ b/router-tests/testdata/mcp_operations_collision/MyQuery.graphql @@ -0,0 +1,12 @@ +query MyEmployees($criteria: SearchInput) { + findEmployees(criteria: $criteria) { + id + isAvailable + currentMood + products + details { + forename + nationality + } + } +} \ No newline at end of file diff --git a/router-tests/testenv/testenv.go b/router-tests/testenv/testenv.go index 2d1e5f64de..bc99b90a1a 100644 --- a/router-tests/testenv/testenv.go +++ b/router-tests/testenv/testenv.go @@ -341,6 +341,7 @@ type Config struct { UseVersionedGraph bool NoShutdownTestServer bool MCP config.MCPConfiguration + MCPOperationsPath string EnableRedis bool EnableRedisCluster bool Plugins PluginConfig @@ -1438,12 +1439,15 @@ func configureRouter(listenerAddr string, testConfig *Config, routerConfig *node } if testConfig.MCP.Enabled { - // Add Storage provider + mcpOperationsPath := "testdata/mcp_operations" + if testConfig.MCPOperationsPath != "" { + mcpOperationsPath = testConfig.MCPOperationsPath + } routerOpts = append(routerOpts, core.WithStorageProviders(config.StorageProviders{ FileSystem: []config.FileSystemStorageProvider{ { ID: "test", - Path: "testdata/mcp_operations", + Path: mcpOperationsPath, }, }, })) diff --git a/router/pkg/mcpserver/server.go b/router/pkg/mcpserver/server.go index fee69d1555..075ba20c42 100644 --- a/router/pkg/mcpserver/server.go +++ b/router/pkg/mcpserver/server.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "net/http" + "slices" "strings" "time" @@ -561,6 +562,13 @@ func (s *GraphQLSchemaServer) registerTools() error { toolName := operationToolName if !s.omitToolNamePrefix { toolName = fmt.Sprintf("execute_operation_%s", operationToolName) + } else if slices.Contains(s.registeredTools, operationToolName) { + s.logger.Warn("Operation name collides with built-in MCP tool, using prefixed name", + zap.String("operation", op.Name), + zap.String("conflicting_tool", operationToolName), + zap.String("using_name", fmt.Sprintf("execute_operation_%s", operationToolName)), + ) + toolName = fmt.Sprintf("execute_operation_%s", operationToolName) } tool := mcp.NewToolWithRawSchema( toolName, From a2188c53ad05ab9586ada76f1481bb132bd1efd8 Mon Sep 17 00:00:00 2001 From: Mike Gabriel <35150423+shamashel@users.noreply.github.com> Date: Fri, 9 Jan 2026 05:05:55 -0500 Subject: [PATCH 08/11] Update router/pkg/config/config.schema.json Co-authored-by: Jesse --- router/pkg/config/config.schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router/pkg/config/config.schema.json b/router/pkg/config/config.schema.json index a7fb9438ef..3a32c67f7d 100644 --- a/router/pkg/config/config.schema.json +++ b/router/pkg/config/config.schema.json @@ -2135,7 +2135,7 @@ "omit_tool_name_prefix": { "type": "boolean", "default": false, - "description": "When enabled, MCP tool names generated from GraphQL operations omit the 'execute_operation_' prefix. For example, the GraphQL operation 'GetUser' results in a tool named 'get_user' instead of 'execute_operation_get_user'. This produces cleaner tool names for AI models." + "description": "When enabled, MCP tool names generated from GraphQL operations omit the 'execute_operation_' prefix. For example, the GraphQL operation 'GetUser' results in a tool named 'get_user' instead of 'execute_operation_get_user'." } } }, From bdb9497ad56f4d1fd7cc6e23fcf04e9d65b5e5b1 Mon Sep 17 00:00:00 2001 From: Mike Gabriel Date: Fri, 9 Jan 2026 09:27:53 -0500 Subject: [PATCH 09/11] fix(router): address PR review feedback from endigma - Fix GraphQL syntax errors in MyMutation.graphql (add commas) - Use assert.ElementsMatch for cleaner test assertions - Embed struct values directly in CallToolRequest initialization - Reorder MCPConfiguration fields (RouterURL before OmitToolNamePrefix) - Trim description in config.schema.json --- router-tests/mcp_test.go | 35 +++------ .../MyMutation.graphql | 4 +- router/pkg/config/config.go | 36 ++++----- router/pkg/config/fixtures/full.yaml | 2 +- .../pkg/config/testdata/config_defaults.json | 38 ++++++--- router/pkg/config/testdata/config_full.json | 77 ++++++++++++++----- 6 files changed, 120 insertions(+), 72 deletions(-) diff --git a/router-tests/mcp_test.go b/router-tests/mcp_test.go index 4c442e6670..f040a93fac 100644 --- a/router-tests/mcp_test.go +++ b/router-tests/mcp_test.go @@ -174,27 +174,13 @@ func TestMCP(t *testing.T) { require.NoError(t, err) require.NotNil(t, resp) - var foundMyEmployees, foundUpdateMood bool - var foundPrefixedMyEmployees, foundPrefixedUpdateMood bool - - for _, tool := range resp.Tools { - switch tool.Name { - case "my_employees": - foundMyEmployees = true - case "update_mood": - foundUpdateMood = true - case "execute_operation_my_employees": - foundPrefixedMyEmployees = true - case "execute_operation_update_mood": - foundPrefixedUpdateMood = true - } + toolNames := make([]string, len(resp.Tools)) + for i, tool := range resp.Tools { + toolNames[i] = tool.Name } - require.True(t, foundMyEmployees, "Tool 'my_employees' should be registered when OmitToolNamePrefix is true") - require.True(t, foundUpdateMood, "Tool 'update_mood' should be registered when OmitToolNamePrefix is true") - - require.False(t, foundPrefixedMyEmployees, "Tool 'execute_operation_my_employees' should NOT be registered when OmitToolNamePrefix is true") - require.False(t, foundPrefixedUpdateMood, "Tool 'execute_operation_update_mood' should NOT be registered when OmitToolNamePrefix is true") + expectedToolNames := []string{"get_operation_info", "my_employees", "update_mood"} + assert.ElementsMatch(t, expectedToolNames, toolNames) }) }) @@ -206,10 +192,13 @@ func TestMCP(t *testing.T) { }, }, func(t *testing.T, xEnv *testenv.Environment) { - req := mcp.CallToolRequest{} - req.Params.Name = "my_employees" - req.Params.Arguments = map[string]interface{}{ - "criteria": map[string]interface{}{}, + req := mcp.CallToolRequest{ + Params: mcp.CallToolParams{ + Name: "my_employees", + Arguments: map[string]interface{}{ + "criteria": map[string]interface{}{}, + }, + }, } resp, err := xEnv.MCPClient.CallTool(xEnv.Context, req) diff --git a/router-tests/testdata/mcp_operations_collision/MyMutation.graphql b/router-tests/testdata/mcp_operations_collision/MyMutation.graphql index 48aa6d2f34..944f9e85b6 100644 --- a/router-tests/testdata/mcp_operations_collision/MyMutation.graphql +++ b/router-tests/testdata/mcp_operations_collision/MyMutation.graphql @@ -1,5 +1,5 @@ -mutation UpdateMood($employeeID: Int! $mood: Mood!) { - updateMood(employeeID: $employeeID mood: $mood) { +mutation UpdateMood($employeeID: Int!, $mood: Mood!) { + updateMood(employeeID: $employeeID, mood: $mood) { id details { forename diff --git a/router/pkg/config/config.go b/router/pkg/config/config.go index 1755d413da..e725348612 100644 --- a/router/pkg/config/config.go +++ b/router/pkg/config/config.go @@ -360,25 +360,25 @@ func (r *ResponseHeaderRule) GetMatching() string { } type EngineDebugConfiguration struct { - PrintOperationTransformations bool `envDefault:"false" env:"ENGINE_DEBUG_PRINT_OPERATION_TRANSFORMATIONS" yaml:"print_operation_transformations"` - PrintOperationEnableASTRefs bool `envDefault:"false" env:"ENGINE_DEBUG_PRINT_OPERATION_ENABLE_AST_REFS" yaml:"print_operation_enable_ast_refs"` - PrintPlanningPaths bool `envDefault:"false" env:"ENGINE_DEBUG_PRINT_PLANNING_PATHS" yaml:"print_planning_paths"` - PrintQueryPlans bool `envDefault:"false" env:"ENGINE_DEBUG_PRINT_QUERY_PLANS" yaml:"print_query_plans"` - PrintIntermediateQueryPlans bool `envDefault:"false" env:"ENGINE_DEBUG_PRINT_INTERMEDIATE_QUERY_PLANS" yaml:"print_intermediate_query_plans"` - PrintNodeSuggestions bool `envDefault:"false" env:"ENGINE_DEBUG_PRINT_NODE_SUGGESTIONS" yaml:"print_node_suggestions"` - ConfigurationVisitor bool `envDefault:"false" env:"ENGINE_DEBUG_CONFIGURATION_VISITOR" yaml:"configuration_visitor"` - PlanningVisitor bool `envDefault:"false" env:"ENGINE_DEBUG_PLANNING_VISITOR" yaml:"planning_visitor"` - DatasourceVisitor bool `envDefault:"false" env:"ENGINE_DEBUG_DATASOURCE_VISITOR" yaml:"datasource_visitor"` - ReportWebSocketConnections bool `envDefault:"false" env:"ENGINE_DEBUG_REPORT_WEBSOCKET_CONNECTIONS" yaml:"report_websocket_connections"` - ReportMemoryUsage bool `envDefault:"false" env:"ENGINE_DEBUG_REPORT_MEMORY_USAGE" yaml:"report_memory_usage"` - EnableResolverDebugging bool `envDefault:"false" env:"ENGINE_DEBUG_ENABLE_RESOLVER_DEBUGGING" yaml:"enable_resolver_debugging"` + PrintOperationTransformations bool `envDefault:"false" env:"ENGINE_DEBUG_PRINT_OPERATION_TRANSFORMATIONS" yaml:"print_operation_transformations"` + PrintOperationEnableASTRefs bool `envDefault:"false" env:"ENGINE_DEBUG_PRINT_OPERATION_ENABLE_AST_REFS" yaml:"print_operation_enable_ast_refs"` + PrintPlanningPaths bool `envDefault:"false" env:"ENGINE_DEBUG_PRINT_PLANNING_PATHS" yaml:"print_planning_paths"` + PrintQueryPlans bool `envDefault:"false" env:"ENGINE_DEBUG_PRINT_QUERY_PLANS" yaml:"print_query_plans"` + PrintIntermediateQueryPlans bool `envDefault:"false" env:"ENGINE_DEBUG_PRINT_INTERMEDIATE_QUERY_PLANS" yaml:"print_intermediate_query_plans"` + PrintNodeSuggestions bool `envDefault:"false" env:"ENGINE_DEBUG_PRINT_NODE_SUGGESTIONS" yaml:"print_node_suggestions"` + ConfigurationVisitor bool `envDefault:"false" env:"ENGINE_DEBUG_CONFIGURATION_VISITOR" yaml:"configuration_visitor"` + PlanningVisitor bool `envDefault:"false" env:"ENGINE_DEBUG_PLANNING_VISITOR" yaml:"planning_visitor"` + DatasourceVisitor bool `envDefault:"false" env:"ENGINE_DEBUG_DATASOURCE_VISITOR" yaml:"datasource_visitor"` + ReportWebSocketConnections bool `envDefault:"false" env:"ENGINE_DEBUG_REPORT_WEBSOCKET_CONNECTIONS" yaml:"report_websocket_connections"` + ReportMemoryUsage bool `envDefault:"false" env:"ENGINE_DEBUG_REPORT_MEMORY_USAGE" yaml:"report_memory_usage"` + EnableResolverDebugging bool `envDefault:"false" env:"ENGINE_DEBUG_ENABLE_RESOLVER_DEBUGGING" yaml:"enable_resolver_debugging"` // EnablePersistedOperationsCacheResponseHeader is deprecated, use EnableCacheResponseHeaders instead. EnablePersistedOperationsCacheResponseHeader bool `envDefault:"false" env:"ENGINE_DEBUG_ENABLE_PERSISTED_OPERATIONS_CACHE_RESPONSE_HEADER" yaml:"enable_persisted_operations_cache_response_header"` // EnableNormalizationCacheResponseHeader is deprecated, use EnableCacheResponseHeaders instead. - EnableNormalizationCacheResponseHeader bool `envDefault:"false" env:"ENGINE_DEBUG_ENABLE_NORMALIZATION_CACHE_RESPONSE_HEADER" yaml:"enable_normalization_cache_response_header"` - EnableCacheResponseHeaders bool `envDefault:"false" env:"ENGINE_DEBUG_ENABLE_CACHE_RESPONSE_HEADERS" yaml:"enable_cache_response_headers"` - AlwaysIncludeQueryPlan bool `envDefault:"false" env:"ENGINE_DEBUG_ALWAYS_INCLUDE_QUERY_PLAN" yaml:"always_include_query_plan"` - AlwaysSkipLoader bool `envDefault:"false" env:"ENGINE_DEBUG_ALWAYS_SKIP_LOADER" yaml:"always_skip_loader"` + EnableNormalizationCacheResponseHeader bool `envDefault:"false" env:"ENGINE_DEBUG_ENABLE_NORMALIZATION_CACHE_RESPONSE_HEADER" yaml:"enable_normalization_cache_response_header"` + EnableCacheResponseHeaders bool `envDefault:"false" env:"ENGINE_DEBUG_ENABLE_CACHE_RESPONSE_HEADERS" yaml:"enable_cache_response_headers"` + AlwaysIncludeQueryPlan bool `envDefault:"false" env:"ENGINE_DEBUG_ALWAYS_INCLUDE_QUERY_PLAN" yaml:"always_include_query_plan"` + AlwaysSkipLoader bool `envDefault:"false" env:"ENGINE_DEBUG_ALWAYS_SKIP_LOADER" yaml:"always_skip_loader"` } type EngineExecutionConfiguration struct { @@ -997,10 +997,10 @@ type MCPConfiguration struct { ExcludeMutations bool `yaml:"exclude_mutations" envDefault:"false" env:"MCP_EXCLUDE_MUTATIONS"` EnableArbitraryOperations bool `yaml:"enable_arbitrary_operations" envDefault:"false" env:"MCP_ENABLE_ARBITRARY_OPERATIONS"` ExposeSchema bool `yaml:"expose_schema" envDefault:"false" env:"MCP_EXPOSE_SCHEMA"` + RouterURL string `yaml:"router_url,omitempty" env:"MCP_ROUTER_URL"` // OmitToolNamePrefix removes the "execute_operation_" prefix from MCP tool names. // When enabled, GetUser becomes get_user. When disabled (default), GetUser becomes execute_operation_get_user. - OmitToolNamePrefix bool `yaml:"omit_tool_name_prefix" envDefault:"false" env:"MCP_OMIT_TOOL_NAME_PREFIX"` - RouterURL string `yaml:"router_url,omitempty" env:"MCP_ROUTER_URL"` + OmitToolNamePrefix bool `yaml:"omit_tool_name_prefix" envDefault:"false" env:"MCP_OMIT_TOOL_NAME_PREFIX"` } type MCPSessionConfig struct { diff --git a/router/pkg/config/fixtures/full.yaml b/router/pkg/config/fixtures/full.yaml index c4c2f29966..13628a925a 100644 --- a/router/pkg/config/fixtures/full.yaml +++ b/router/pkg/config/fixtures/full.yaml @@ -178,7 +178,7 @@ telemetry: schema_usage: enabled: true include_operation_sha: true - sample_rate: 1.0 # Supports any rate: 1.0, 0.8, 0.5, 0.1, 0.01, etc. + sample_rate: 1.0 # Supports any rate: 1.0, 0.8, 0.5, 0.1, 0.01, etc. cache_control_policy: enabled: true diff --git a/router/pkg/config/testdata/config_defaults.json b/router/pkg/config/testdata/config_defaults.json index 9a35c9fec2..b4ddad685e 100644 --- a/router/pkg/config/testdata/config_defaults.json +++ b/router/pkg/config/testdata/config_defaults.json @@ -80,9 +80,19 @@ }, "CORS": { "Enabled": true, - "AllowOrigins": ["*"], - "AllowMethods": ["HEAD", "GET", "POST"], - "AllowHeaders": ["Origin", "Content-Length", "Content-Type"], + "AllowOrigins": [ + "*" + ], + "AllowMethods": [ + "HEAD", + "GET", + "POST" + ], + "AllowHeaders": [ + "Origin", + "Content-Length", + "Content-Type" + ], "AllowCredentials": true, "MaxAge": 300000000000 }, @@ -127,8 +137,8 @@ "ExcludeMutations": false, "EnableArbitraryOperations": false, "ExposeSchema": false, - "OmitToolNamePrefix": false, - "RouterURL": "" + "RouterURL": "", + "OmitToolNamePrefix": false }, "DemoMode": false, "Modules": null, @@ -204,7 +214,9 @@ }, "Router": { "Fields": null, - "IgnoreQueryParamsList": ["variables"] + "IgnoreQueryParamsList": [ + "variables" + ] }, "Subgraphs": { "Enabled": false, @@ -397,11 +409,15 @@ }, "ForwardUpgradeHeaders": { "Enabled": true, - "AllowList": ["Authorization"] + "AllowList": [ + "Authorization" + ] }, "ForwardUpgradeQueryParams": { "Enabled": true, - "AllowList": ["Authorization"] + "AllowList": [ + "Authorization" + ] }, "ForwardInitialPayload": true, "Authentication": { @@ -435,7 +451,9 @@ "AttachServiceName": true, "DefaultExtensionCode": "DOWNSTREAM_SERVICE_ERROR", "AllowAllExtensionFields": false, - "AllowedExtensionFields": ["code"], + "AllowedExtensionFields": [ + "code" + ], "AllowedFields": null }, "StorageProviders": { @@ -539,4 +557,4 @@ "Maximum": 10000000000 } } -} +} \ No newline at end of file diff --git a/router/pkg/config/testdata/config_full.json b/router/pkg/config/testdata/config_full.json index 640ad9a125..c8b026e601 100644 --- a/router/pkg/config/testdata/config_full.json +++ b/router/pkg/config/testdata/config_full.json @@ -110,9 +110,19 @@ }, "CORS": { "Enabled": true, - "AllowOrigins": ["*"], - "AllowMethods": ["HEAD", "GET", "POST"], - "AllowHeaders": ["Origin", "Content-Length", "Content-Type"], + "AllowOrigins": [ + "*" + ], + "AllowMethods": [ + "HEAD", + "GET", + "POST" + ], + "AllowHeaders": [ + "Origin", + "Content-Length", + "Content-Type" + ], "AllowCredentials": true, "MaxAge": 300000000000 }, @@ -162,8 +172,8 @@ "ExcludeMutations": false, "EnableArbitraryOperations": false, "ExposeSchema": false, - "OmitToolNamePrefix": false, - "RouterURL": "https://cosmo-router.wundergraph.com" + "RouterURL": "https://cosmo-router.wundergraph.com", + "OmitToolNamePrefix": false }, "DemoMode": true, "Modules": { @@ -297,7 +307,10 @@ ] } }, - "CookieWhitelist": ["cookie1", "cookie2"] + "CookieWhitelist": [ + "cookie1", + "cookie2" + ] }, "TrafficShaping": { "All": { @@ -420,7 +433,10 @@ } } ], - "IgnoreQueryParamsList": ["variables", "anothervalue"] + "IgnoreQueryParamsList": [ + "variables", + "anothervalue" + ] }, "Subgraphs": { "Enabled": true, @@ -474,7 +490,9 @@ "JWKS": [ { "URL": "https://example.com/.well-known/jwks.json", - "Algorithms": ["RS256"], + "Algorithms": [ + "RS256" + ], "RefreshInterval": 60000000000, "RefreshUnknownKID": { "Enabled": false, @@ -489,7 +507,10 @@ }, { "URL": "https://example.com/.well-known/jwks2.json", - "Algorithms": ["RS256", "ES256"], + "Algorithms": [ + "RS256", + "ES256" + ], "RefreshInterval": 120000000000, "RefreshUnknownKID": { "Enabled": true, @@ -524,7 +545,10 @@ { "Type": "header", "Name": "X-Authorization", - "ValuePrefixes": ["Bearer", "Token"] + "ValuePrefixes": [ + "Bearer", + "Token" + ] }, { "Type": "header", @@ -551,7 +575,10 @@ "HideStatsFromResponseExtension": false }, "Storage": { - "URLs": ["test@localhost:8000", "test2@localhost:8001"], + "URLs": [ + "test@localhost:8000", + "test2@localhost:8001" + ], "ClusterEnabled": true, "KeyPrefix": "cosmo_rate_limit" }, @@ -591,7 +618,9 @@ "Kafka": [ { "ID": "my-kafka", - "Brokers": ["localhost:9092"], + "Brokers": [ + "localhost:9092" + ], "Authentication": { "SASLPlain": { "Password": "admin", @@ -612,7 +641,9 @@ "Redis": [ { "ID": "my-redis", - "URLs": ["redis://localhost:6379/11"], + "URLs": [ + "redis://localhost:6379/11" + ], "ClusterEnabled": true } ] @@ -758,11 +789,15 @@ }, "ForwardUpgradeHeaders": { "Enabled": true, - "AllowList": ["Authorization"] + "AllowList": [ + "Authorization" + ] }, "ForwardUpgradeQueryParams": { "Enabled": true, - "AllowList": ["Authorization"] + "AllowList": [ + "Authorization" + ] }, "ForwardInitialPayload": true, "Authentication": { @@ -796,7 +831,10 @@ "AttachServiceName": true, "DefaultExtensionCode": "DOWNSTREAM_SERVICE_ERROR", "AllowAllExtensionFields": true, - "AllowedExtensionFields": ["field1", "field2"], + "AllowedExtensionFields": [ + "field1", + "field2" + ], "AllowedFields": null }, "StorageProviders": { @@ -815,7 +853,10 @@ "Redis": [ { "ID": "my_redis", - "URLs": ["test@localhost:8000", "test2@localhost:8001"], + "URLs": [ + "test@localhost:8000", + "test2@localhost:8001" + ], "ClusterEnabled": false } ], @@ -921,4 +962,4 @@ "Maximum": 10000000000 } } -} +} \ No newline at end of file From 20d5a0f2dcd0a4e0048c733fb60a059a5842256e Mon Sep 17 00:00:00 2001 From: Mike Gabriel Date: Fri, 9 Jan 2026 09:36:32 -0500 Subject: [PATCH 10/11] refactor(router-tests): simplify collision test assertions --- router-tests/mcp_test.go | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/router-tests/mcp_test.go b/router-tests/mcp_test.go index f040a93fac..880178e0d7 100644 --- a/router-tests/mcp_test.go +++ b/router-tests/mcp_test.go @@ -229,19 +229,13 @@ func TestMCP(t *testing.T) { require.NoError(t, err) require.NotNil(t, resp) - var foundBuiltInGetSchema, foundPrefixedGetSchema bool - - for _, tool := range resp.Tools { - switch tool.Name { - case "get_schema": - foundBuiltInGetSchema = true - case "execute_operation_get_schema": - foundPrefixedGetSchema = true - } + toolNames := make([]string, len(resp.Tools)) + for i, tool := range resp.Tools { + toolNames[i] = tool.Name } - require.True(t, foundBuiltInGetSchema, "Built-in 'get_schema' tool should be registered") - require.True(t, foundPrefixedGetSchema, "Conflicting operation should use prefixed name 'execute_operation_get_schema'") + assert.Contains(t, toolNames, "get_schema") // built-in tool (ExposeSchema=true) + assert.Contains(t, toolNames, "execute_operation_get_schema") // collision uses prefix }) }) From ab918f0c865c67ea5a48a89006108a2e7be6ca54 Mon Sep 17 00:00:00 2001 From: Mike Gabriel <35150423+shamashel@users.noreply.github.com> Date: Fri, 9 Jan 2026 10:40:00 -0500 Subject: [PATCH 11/11] Update router-tests/mcp_test.go Co-authored-by: Jesse --- router-tests/mcp_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/router-tests/mcp_test.go b/router-tests/mcp_test.go index 880178e0d7..39de7ffbe7 100644 --- a/router-tests/mcp_test.go +++ b/router-tests/mcp_test.go @@ -195,8 +195,8 @@ func TestMCP(t *testing.T) { req := mcp.CallToolRequest{ Params: mcp.CallToolParams{ Name: "my_employees", - Arguments: map[string]interface{}{ - "criteria": map[string]interface{}{}, + Arguments: map[string]any{ + "criteria": map[string]any{}, }, }, }