Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
133 changes: 133 additions & 0 deletions MCP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Nais MCP Server

The Nais MCP (Model Context Protocol) server allows LLMs and AI assistants to interact with the Nais platform. It provides dynamic access to the Nais GraphQL API through schema exploration and query execution tools.

## Quick Start

### Installation

Ensure you're authenticated with the Nais CLI:

```bash
nais auth login -n
```

### Configuration

Add to your MCP settings file:

**GitHub Copilot CLI** (`~/.mcp/config.json`):
```json
{
"mcpServers": {
"nais": {
"command": "nais",
"args": ["alpha", "mcp", "serve"],
"tools": ["*"]
}
}
}
```

**Zed** (`~/.config/zed/settings.json`):
```json
{
"context_servers": {
"nais": {
"enabled": true,
"command": "nais",
"args": ["alpha", "mcp", "serve"]
}
}
}
```

**VS Code** (with Cline extension):

1. Open the command palette
2. Select "MCP: Add Server..."
3. Select "Command (stdio)"
4. Insert `nais` in the command input and press Enter
5. Insert `alpha mcp serve` in the args input and press Enter
6. When prompted for a name, insert `nais`
7. Select if you want to add it as a Global or Workspace MCP server

**IntelliJ IDEA** (with GitHub Copilot):

See [GitHub Copilot MCP documentation](https://docs.github.com/en/copilot/how-tos/provide-context/use-mcp/extend-copilot-chat-with-mcp?tool=jetbrains) for setup instructions.

Local server configuration:
```json
{
"servers": {
"nais": {
"command": "nais",
"args": [
"alpha",
"mcp",
"serve"
]
}
}
}
```

## Available Tools

### Context & Execution
- `get_nais_context` - Get current user, teams, and console URL patterns
- `execute_graphql` - Execute GraphQL queries against the Nais API
- `validate_graphql` - Validate a GraphQL query without executing it

### Schema Exploration
- `schema_list_types` - List all types in the API schema
- `schema_get_type` - Get details about a specific type
- `schema_list_queries` - List all available query operations
- `schema_list_mutations` - List all mutation operations (read-only server)
- `schema_get_field` - Get details about a specific field
- `schema_get_enum` - Get enum values and descriptions
- `schema_search` - Search the schema by name or description
- `schema_get_implementors` - Get types implementing an interface
- `schema_get_union_types` - Get member types of a union

## Recommended Agent Prompt

Add this to your `AGENTS.md` or system prompt to help the LLM use the Nais MCP effectively:

```markdown
You have access to the Nais MCP server for interacting with the Nais platform.

**Initial Setup:**
1. Always start with `get_nais_context` to understand the user, their teams, and available console URLs
2. Use schema exploration tools (`schema_list_queries`, `schema_get_type`) to discover available data
3. Construct GraphQL queries based on the schema
4. Execute queries with `execute_graphql`

**Query Guidelines:**
- Use pagination with reasonable page sizes (20-50 items, max 100)
- Filter queries when possible (by team, environment, name)
- Use `__typename` for union/interface types
- Include `pageInfo { hasNextPage endCursor }` for paginated results

All operations are read-only and use the user's authenticated identity.
```

## Command Reference

```bash
nais alpha mcp serve [flags]
```

| Flag | Default | Description |
|------|---------|-------------|
| `--transport`, `-t` | `stdio` | Transport: `stdio`, `http`, or `sse` |
| `--listen`, `-l` | `:8080` | Listen address (for http/sse) |
| `--rate-limit`, `-r` | `10` | Max requests per minute (0 = unlimited) |
| `--log-file` | - | Write logs to file instead of stderr |

## Resources

The server exposes these resources:

- `nais://schema` - Complete Nais GraphQL API schema
- `nais://api-best-practices` - API usage guidelines (pagination, optimization, rate limiting)
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ require (
github.com/lestrrat-go/jwx/v3 v3.0.8
github.com/lib/pq v1.10.9
github.com/mailgun/raymond/v2 v2.0.48
github.com/mark3labs/mcp-go v0.43.2
github.com/mitchellh/go-ps v1.0.0
github.com/nais/device v1.7.3
github.com/nais/krakend/pkg/migration v0.0.0-20251112211103-82b954ae962b
Expand Down Expand Up @@ -210,6 +211,7 @@ require (
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
github.com/zitadel/logging v0.6.2 // indirect
github.com/zitadel/schema v1.3.1 // indirect
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,8 @@ github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqA
github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mark3labs/mcp-go v0.43.2 h1:21PUSlWWiSbUPQwXIJ5WKlETixpFpq+WBpbMGDSVy/I=
github.com/mark3labs/mcp-go v0.43.2/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw=
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
Expand Down Expand Up @@ -563,6 +565,8 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMx
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
Expand Down
2 changes: 2 additions & 0 deletions internal/alpha/command/alpha.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/nais/cli/internal/flags"
krakend "github.com/nais/cli/internal/krakend/command"
log "github.com/nais/cli/internal/log/command"
mcpcmd "github.com/nais/cli/internal/mcp/command"
naisapi "github.com/nais/cli/internal/naisapi/command"
opensearch "github.com/nais/cli/internal/opensearch/command"
valkey "github.com/nais/cli/internal/valkey/command"
Expand All @@ -26,6 +27,7 @@ func Alpha(parentFlags *flags.GlobalFlags) *naistrix.Command {
opensearch.OpenSearch(flags),
log.Log(flags),
krakend.Krakend(flags),
mcpcmd.MCP(flags),
},
}
}
29 changes: 29 additions & 0 deletions internal/mcp/client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Package client provides the GraphQL client interface for MCP operations.
package client

import (
"context"

"github.com/nais/cli/internal/naisapi/gql"
)

// Client defines the interface for GraphQL operations used by MCP tools.
// This interface allows for both live API clients and mock clients for testing.
type Client interface {
// User operations
GetCurrentUser(ctx context.Context) (*User, error)
GetUserTeams(ctx context.Context) ([]gql.UserTeamsMeUserTeamsTeamMemberConnectionNodesTeamMember, error)

// Schema operations
GetSchema(ctx context.Context) (string, error)

// Console URL operations
GetConsoleURL(ctx context.Context) (string, error)
}

// User represents the authenticated user.
type User struct {
Name string
Email string
IsAdmin bool
}
55 changes: 55 additions & 0 deletions internal/mcp/client/live.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//go:build !mock

// Package client provides the GraphQL client interface for MCP operations.
package client

import (
"context"
"fmt"

"github.com/nais/cli/internal/naisapi"
"github.com/nais/cli/internal/naisapi/gql"
)

// LiveClient implements the Client interface using the real Nais API.
type LiveClient struct{}

// NewLiveClient creates a new live client.
func NewLiveClient() *LiveClient {
return &LiveClient{}
}

// GetCurrentUser returns the current authenticated user.
func (c *LiveClient) GetCurrentUser(ctx context.Context) (*User, error) {
user, err := naisapi.GetAuthenticatedUser(ctx)
if err != nil {
return nil, err
}

isAdmin := naisapi.IsConsoleAdmin(ctx)

return &User{
Name: user.Email(), // AuthenticatedUser interface only has Email(), not Name()
Email: user.Email(),
IsAdmin: isAdmin,
}, nil
}

// GetUserTeams returns the teams the current user is a member of.
func (c *LiveClient) GetUserTeams(ctx context.Context) ([]gql.UserTeamsMeUserTeamsTeamMemberConnectionNodesTeamMember, error) {
return naisapi.GetUserTeams(ctx)
}

// GetSchema returns the GraphQL schema.
func (c *LiveClient) GetSchema(ctx context.Context) (string, error) {
return naisapi.PullSchema(ctx, nil)
}

// GetConsoleURL returns the base console URL for the current tenant.
func (c *LiveClient) GetConsoleURL(ctx context.Context) (string, error) {
user, err := naisapi.GetAuthenticatedUser(ctx)
if err != nil {
return "", err
}
return fmt.Sprintf("https://%s", user.ConsoleHost()), nil
}
Loading