Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
65 changes: 65 additions & 0 deletions router-tests/mcp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
1 change: 1 addition & 0 deletions router/core/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}

Expand Down
1 change: 1 addition & 0 deletions router/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}

Expand Down
5 changes: 5 additions & 0 deletions router/pkg/config/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}
}
},
Expand Down
1 change: 1 addition & 0 deletions router/pkg/config/fixtures/full.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions router/pkg/config/testdata/config_defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@
"ExcludeMutations": false,
"EnableArbitraryOperations": false,
"ExposeSchema": false,
"OmitOperationPrefix": false,
"RouterURL": ""
},
"DemoMode": false,
Expand Down
1 change: 1 addition & 0 deletions router/pkg/config/testdata/config_full.json
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@
"ExcludeMutations": false,
"EnableArbitraryOperations": false,
"ExposeSchema": false,
"OmitOperationPrefix": false,
"RouterURL": "https://cosmo-router.wundergraph.com"
},
"DemoMode": true,
Expand Down
18 changes: 17 additions & 1 deletion router/pkg/mcpserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -110,6 +112,7 @@ type GraphQLSchemaServer struct {
excludeMutations bool
enableArbitraryOperations bool
exposeSchema bool
omitOperationPrefix bool
stateless bool
operationsManager *OperationsManager
schemaCompiler *SchemaCompiler
Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down