Skip to content

feat(mcp): add MCP server with schema-driven GraphQL tools#624

Merged
jhrv merged 5 commits intomainfrom
mcp_server
Dec 18, 2025
Merged

feat(mcp): add MCP server with schema-driven GraphQL tools#624
jhrv merged 5 commits intomainfrom
mcp_server

Conversation

@thokra-nav
Copy link
Contributor

@thokra-nav thokra-nav commented Dec 16, 2025

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, IntelliJ IDEA and VS Code

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.
@github-actions
Copy link
Contributor

github-actions bot commented Dec 16, 2025

📝 Changelog preview

Below 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-17

Full Changelog: v3.24.1...v3.25.0

🚀 Features

  • (mcp) Add MCP server with schema-driven GraphQL tools (5bda0df)

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
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.

Comment on lines +457 to +463
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)
}
}
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +555 to +558
case *ast.FragmentSpread:
// Fragment spreads would need to be resolved against fragment definitions
// For simplicity, we count them as +1 depth
childDepth = currentDepth + 1
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
if err != nil {
return err
}
defer f.Close()
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

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.

Suggested change
defer f.Close()
defer func() {
if err := f.Close(); err != nil {
slog.Error("failed to close log file", "error", err)
}
}()

Copilot uses AI. Check for mistakes.
@jhrv jhrv merged commit 046522f into main Dec 18, 2025
25 checks passed
@jhrv jhrv deleted the mcp_server branch December 18, 2025 14:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants