feat(mcp): add MCP server with schema-driven GraphQL tools#624
Conversation
Add Model Context Protocol (MCP) server implementation that provides dynamic access to the Nais GraphQL API for LLMs and AI assistants. Key features: - Dynamic schema-driven approach using GraphQL introspection - Schema exploration tools for API discovery - GraphQL query execution and validation - Thread-safe schema caching with sync.Once - Rate limiting support - Multiple transport options (stdio, HTTP, SSE) - In-process testing without subprocess spawning Tools provided: - get_nais_context: User info, teams, and console URL patterns - execute_graphql: Execute GraphQL queries - validate_graphql: Validate queries before execution - schema_list_types, schema_get_type, schema_list_queries, etc. The server is read-only and uses the authenticated user's credentials. All queries are validated and depth-limited for security. Includes comprehensive documentation and agent prompts for optimal LLM integration with GitHub Copilot, Zed, Cline, and IntelliJ IDEA.
📝 Changelog previewBelow is a preview of the Changelog that will be added to the next release. Only commit messages that follow the Conventional Commits specification will be included in the Changelog. v3.25.0 - 2025-12-17Full Changelog: v3.24.1...v3.25.0 🚀 Features
|
The LLM would think that the graphql api didn't support secrets with the previous errors, and not that it was a limitation of the mcp
There was a problem hiding this comment.
Pull request overview
This PR adds a Model Context Protocol (MCP) server that enables LLMs and AI assistants to interact with the Nais GraphQL API. The implementation uses a dynamic, schema-driven approach with GraphQL introspection for API discovery.
Key changes:
- Schema-driven GraphQL tools with exploration capabilities (list types, get type details, search schema)
- GraphQL query execution and validation with security controls (read-only, depth limiting, secret filtering)
- Thread-safe schema caching, rate limiting, and multiple transport options (stdio, HTTP, SSE)
Reviewed changes
Copilot reviewed 22 out of 23 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/naisapi/auth/oidc.go | Updated error message with login command hint |
| internal/mcp/tools/tools.go | Core tool registration and context management with caching |
| internal/mcp/tools/schema.go | Schema exploration tools using gqlparser AST |
| internal/mcp/tools/graphql.go | GraphQL execution with security validation |
| internal/mcp/tools/schema_test.go | Comprehensive schema parsing tests |
| internal/mcp/tools/graphql_secrets_test.go | Security validation tests for secret filtering |
| internal/mcp/tools/cache_test.go | Thread-safe caching tests |
| internal/mcp/server.go | MCP server with transport support |
| internal/mcp/ratelimit.go | Token bucket rate limiter |
| internal/mcp/client/client.go | Client abstraction layer |
| internal/mcp/resources/resources.go | MCP resources (schema, best practices) |
| internal/mcp/command/mcp.go | CLI command integration |
| MCP.md | Comprehensive documentation with setup examples |
| go.mod/go.sum | Added mcp-go dependency |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| case *ast.FragmentSpread: | ||
| // Fragment spreads would need fragment definitions to be fully validated | ||
| // For now, we flag any fragment that has "secret" in its name as a heuristic | ||
| if strings.Contains(strings.ToLower(sel.Name), "secret") { | ||
| return true, fmt.Sprintf("MCP security policy: fragment '%s' may access sensitive data that cannot be accessed via this interface", sel.Name) | ||
| } | ||
| } |
There was a problem hiding this comment.
The fragment spread validation at lines 458-462 only checks if the fragment name contains "secret" as a heuristic, but doesn't validate the actual fragment definition. This means a fragment named "getTeamInfo" that queries secret fields would pass validation.
Consider either properly resolving fragment definitions from doc.Fragments and checking their selection sets, or documenting this limitation clearly. If fragment definitions aren't available in the validation context, at least add a comment explaining this is a known limitation and why it's acceptable.
| case *ast.FragmentSpread: | ||
| // Fragment spreads would need to be resolved against fragment definitions | ||
| // For simplicity, we count them as +1 depth | ||
| childDepth = currentDepth + 1 |
There was a problem hiding this comment.
The calculateQueryDepth function counts FragmentSpreads as +1 depth but doesn't actually traverse into the fragment definition's selection set. This could underestimate the actual query depth if fragments contain deeply nested selections. An attacker could bypass the maxQueryDepth limit by using multiple levels of fragments.
Consider tracking fragment definitions from the parsed document and recursively calculating their depth, or at minimum document this as a known limitation.
| if err != nil { | ||
| return err | ||
| } | ||
| defer f.Close() |
There was a problem hiding this comment.
File handle may be writable as a result of data flow from a call to OpenFile and closing it may result in data loss upon failure, which is not handled explicitly.
| defer f.Close() | |
| defer func() { | |
| if err := f.Close(); err != nil { | |
| slog.Error("failed to close log file", "error", err) | |
| } | |
| }() |
Add Model Context Protocol (MCP) server implementation that provides dynamic access to the Nais GraphQL API for LLMs and AI assistants.
Key features:
Tools provided:
The server is read-only and uses the authenticated user's credentials. All queries are validated and depth-limited for security.
Includes comprehensive documentation and agent prompts for optimal LLM integration with GitHub Copilot, Zed, IntelliJ IDEA and VS Code