diff --git a/MCP.md b/MCP.md index d7e2b0df..3c9ea33e 100644 --- a/MCP.md +++ b/MCP.md @@ -121,7 +121,7 @@ nais alpha mcp serve [flags] | Flag | Default | Description | |------|---------|-------------| | `--transport`, `-t` | `stdio` | Transport: `stdio`, `http`, or `sse` | -| `--listen`, `-l` | `:8080` | Listen address (for http/sse) | +| `--listen`, `-l` | `127.0.0.1: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 | diff --git a/internal/mcp/command/flag/flag.go b/internal/mcp/command/flag/flag.go index b57b7bad..49eb3497 100644 --- a/internal/mcp/command/flag/flag.go +++ b/internal/mcp/command/flag/flag.go @@ -23,7 +23,7 @@ func (t *Transport) AutoComplete(context.Context, *naistrix.Arguments, string, a type Serve struct { *MCP Transport Transport `name:"transport" usage:"Transport type (stdio, http, sse)."` - ListenAddr string `name:"listen" short:"l" usage:"Address to listen on (for http/sse transports)."` + ListenAddr string `name:"listen" short:"l" usage:"Address to listen on (for http/sse transports). The server has no authentication; only bind to non-loopback addresses on trusted networks."` RateLimit int `name:"rate-limit" short:"r" usage:"Maximum requests per minute (0 = unlimited)."` LogFile string `name:"log-file" usage:"Write logs to file instead of stderr."` } diff --git a/internal/mcp/command/mcp.go b/internal/mcp/command/mcp.go index edff32f6..b1ad1e50 100644 --- a/internal/mcp/command/mcp.go +++ b/internal/mcp/command/mcp.go @@ -4,6 +4,7 @@ import ( "context" "io" "log/slog" + "net" "os" alpha "github.com/nais/cli/internal/alpha/command/flag" @@ -29,7 +30,7 @@ func serveCommand(parentFlags *flag.MCP) *naistrix.Command { flags := &flag.Serve{ MCP: parentFlags, Transport: "stdio", - ListenAddr: ":8080", + ListenAddr: "127.0.0.1:8080", RateLimit: 25, } @@ -43,10 +44,10 @@ Examples: nais alpha mcp serve # Start with HTTP transport on a specific port - nais alpha mcp serve --transport http --listen :8080 + nais alpha mcp serve --transport http --listen 127.0.0.1:8080 # Start with SSE transport - nais alpha mcp serve --transport sse --listen :8080 + nais alpha mcp serve --transport sse --listen 127.0.0.1:8080 # Restrict to specific teams nais alpha mcp serve --team my-team --team other-team @@ -106,8 +107,24 @@ func runServe(ctx context.Context, flags *flag.Serve, out *naistrix.OutputWriter out.Println("Starting MCP server with transport:", string(flags.Transport)) if flags.Transport == "http" || flags.Transport == "sse" { out.Println("Listening on:", flags.ListenAddr) + if !isLoopbackAddr(flags.ListenAddr) { + out.Println("WARNING: the MCP server has no authentication and uses your Nais credentials. Anyone who can reach this address can query the Nais API as you.") + } } } return server.Serve(ctx) } + +// isLoopbackAddr reports whether addr (host:port) only binds to a loopback interface. +func isLoopbackAddr(addr string) bool { + host, _, err := net.SplitHostPort(addr) + if err != nil { + return false + } + if host == "localhost" { + return true + } + ip := net.ParseIP(host) + return ip != nil && ip.IsLoopback() +} diff --git a/internal/mcp/command/mcp_test.go b/internal/mcp/command/mcp_test.go new file mode 100644 index 00000000..827d83e2 --- /dev/null +++ b/internal/mcp/command/mcp_test.go @@ -0,0 +1,20 @@ +package command + +import "testing" + +func TestIsLoopbackAddr(t *testing.T) { + for addr, want := range map[string]bool{ + "127.0.0.1:8080": true, + "localhost:8080": true, + "[::1]:8080": true, + ":8080": false, + "0.0.0.0:8080": false, + "10.0.0.1:8080": false, + "example.com:80": false, + "invalid": false, + } { + if got := isLoopbackAddr(addr); got != want { + t.Errorf("isLoopbackAddr(%q) = %v, want %v", addr, got, want) + } + } +} diff --git a/internal/mcp/options.go b/internal/mcp/options.go index d9eae494..e2bfcb79 100644 --- a/internal/mcp/options.go +++ b/internal/mcp/options.go @@ -48,7 +48,7 @@ type Option func(*Options) func DefaultOptions() *Options { return &Options{ Transport: TransportStdio, - ListenAddr: ":8080", + ListenAddr: "127.0.0.1:8080", RateLimit: 10, Logger: slog.Default(), }