MCP Server for Self-Hosted NetBird

Updated

The NetBird MCP server lets AI agents manage your NetBird account through the management API. On a self-hosted deployment it is off by default. Turning it on takes one environment variable, and with the embedded identity provider, OAuth sign-in for MCP clients works without further setup.

Before You Begin

  • A NetBird release that includes the MCP server. See Upgrade.
  • Your identity provider setup. The OAuth setup below differs between the two:
    • Embedded identity provider. You run the combined netbird-server container with config.yaml, or management.json has an EmbeddedIdP section with "Enabled": true. This includes Google, Microsoft Entra ID, Okta, Keycloak or other providers connected in Settings → Identity Providers or by the migration tool.
    • Standalone identity provider. management.json has no enabled EmbeddedIdP, and PKCEAuthorizationFlow → ProviderConfig → AuthorizationEndpoint points at your identity provider.
  • HTTPS on your NetBird domain. MCP clients connect to https://netbird.example.com/api/mcp, the same host your dashboard uses for the API.
  • Who must reach the server. Clients that run on users' computers, such as Claude Code, reach it from there. Claude Desktop and claude.ai reach it from Anthropic's cloud, so they work only if the server is reachable from the internet.
  • More than one management instance? Set up the shared Redis and keys in Multiple Management Instances before you enable MCP.

Enable the MCP Server

Set NB_MCP_ENABLED=true on the container that runs management. There is no config.yaml or management.json setting for it.

netbird-server:
  environment:
    - NB_MCP_ENABLED=true

Recreate the container to apply the change:

docker compose up -d netbird-server

On a multi-container deployment, run docker compose up -d management instead. docker compose restart keeps the old environment.

The value is read as a boolean, so true, 1 and TRUE all turn the server on. Unset, false or a value that is not a boolean leaves it off, and a value that is not a boolean logs a warning that starts with failed to parse NB_MCP_ENABLED.

Verify the Endpoint

Send a request without a credential:

curl -i -X POST https://netbird.example.com/api/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
ResponseMeaning
401 with WWW-Authenticate: Bearer resource_metadata="https://netbird.example.com/api/mcp/.well-known/oauth-protected-resource"The MCP server and OAuth sign-in are running.
401 without a WWW-Authenticate headerThe MCP server runs, but OAuth sign-in did not start. Clients can connect with a personal access token only. See Troubleshooting.
404The MCP server is off. Check NB_MCP_ENABLED and recreate the container.

When OAuth sign-in is running, the metadata document names your server:

curl -s https://netbird.example.com/api/mcp/.well-known/oauth-protected-resource

It returns JSON with a resource of https://netbird.example.com/api/mcp and an authorization_servers entry of https://netbird.example.com/api/mcp/auth. If the URLs start with http:// or name another host than the one your users connect to, see Reverse Proxy.

Each management instance decides at startup whether OAuth sign-in runs. Behind a load balancer, check the startup log of each instance for MCP OAuth disabled.

Set Up Sign-In

MCP clients sign users in through an OAuth authorization server that runs inside management. Management handles client registration, consent and tokens itself. It sends users to your identity provider only to log in, then validates that login like a dashboard login and issues its own tokens. The identity provider's token is discarded.

Embedded Identity Provider

Nothing to configure. Management adds its sign-in callback, https://netbird.example.com/api/mcp/auth/callback, to the embedded identity provider when it starts. External providers connected in Settings → Identity Providers log in through the embedded identity provider, so they need no change either.

Two conditions apply:

  • The issuer must have the form https://netbird.example.com/oauth2, with no trailing slash, as the setup script writes it. The issuer is server.auth.issuer in config.yaml, or EmbeddedIdP.Issuer in management.json. Otherwise OAuth sign-in does not start, and management logs the MCP base URL "..." must be a scheme and host, such as https://netbird.example.com.
  • MCP clients must connect on the issuer's host. Sign-ins are bound to https://<issuer host>/api/mcp, and the server's metadata names that host. A client that connects on another hostname or IP address cannot complete the sign-in.

Standalone Identity Provider (Advanced)

If your deployment uses a standalone identity provider configured in management.json, MCP sign-in uses the same application as the netbird up SSO login: the one in the PKCEAuthorizationFlow section. Allow the MCP callback on that application:

  1. In management.json, note the ClientID under PKCEAuthorizationFlow → ProviderConfig.
  2. In your identity provider, open the application with that client ID and add this redirect URI, next to http://localhost:53000:
    https://netbird.example.com/api/mcp/auth/callback
    
    If MCP clients reach management on more than one hostname, add the callback for each one.
  3. Make sure your reverse proxy sets X-Forwarded-Proto and X-Forwarded-Host on the requests it sends to /api/. With nginx, use the location snippet in Reverse Proxy.
  4. Reload your proxy if you changed it, then verify the endpoint. The redirect URI and proxy changes need no restart of management.
ProviderChange needed if you followed the NetBird guide
KeycloakNone. The https://YOUR_DOMAIN/* redirect URI of the netbird-client client already matches.
AuthentikNone. The https://<domain>/.* redirect URI pattern already matches.
ZitadelAdd the callback to the application's redirect URIs.
PocketIDAdd the callback to the client's callback URLs.
OktaAdd the callback to the sign-in redirect URIs of the single-page application.
Microsoft Entra IDAdd the callback in Authentication → Mobile and desktop applications, next to http://localhost:53000.
Google WorkspaceAdd the callback to the authorized redirect URIs of the OAuth client.
JumpCloudAdd the callback to the application's redirect URIs.
Auth0Add the callback to the allowed callback URLs.

From PKCEAuthorizationFlow → ProviderConfig, management uses:

  • ClientID, AuthorizationEndpoint and TokenEndpoint, which are required.
  • Audience, sent to the identity provider as the audience parameter.
  • Scope, the scopes to request.
  • UseIDToken, to validate the ID token instead of the access token.
  • ClientSecret, for a confidential client.

RedirectURLs, LoginFlag and DisablePromptLogin do not apply to MCP sign-in. The token the identity provider returns must carry an audience management accepts, as it does for netbird up. Management presents no client certificate to the identity provider.

A flow that works for netbird up works for MCP clients once the callback is allowed.

Without OAuth Sign-In

Without a PKCEAuthorizationFlow and without the embedded identity provider, OAuth sign-in does not start. Management logs MCP OAuth disabled: no PKCE authorization flow is configured, MCP clients must use a personal access token, and users connect with personal access tokens.

Limit the Available Tools

By default, the server exposes every tool. To expose only some, list them in NB_MCP_TOOLS, separated by commas. For example, a read-only server for peers, groups and policies:

netbird-server:
  environment:
    - NB_MCP_ENABLED=true
    - NB_MCP_TOOLS=list_peers,get_peer,list_groups,get_group,list_policies,get_policy

Tools outside the list are neither listed to agents nor callable, whatever the user's role or credential. The tool names are listed in What Agents Can Do.

Keys and Restarts

Management seals OAuth codes and tokens with a key, and stores active sign-ins in a session store. Both decide whether users stay signed in across restarts.

Encryption Keys

  • Keep the datastore encryption key persistent. It is server.store.encryptionKey in config.yaml, or DataStoreEncryptionKey in management.json. The setup script writes it. If it is missing, the management command of a multi-container deployment generates one and saves it to management.json, so keep that file on a persistent volume. The combined netbird-server container instead generates a new key at every start and logs DataStoreEncryptionKey generated (...); add it to your config file under 'server.store.encryptionKey' to persist across restarts. Every MCP client must then register and sign in again after each restart, and other data encrypted with the key cannot be read.

  • Optionally set NB_MCP_OAUTH_KEY, a separate secret for OAuth codes and tokens. It lets you sign every MCP client out without touching the datastore key. It must be at least 32 bytes. Generate one with:

    openssl rand -base64 32
    

    A shorter value keeps OAuth sign-in from starting, with the log message NB_MCP_OAUTH_KEY must be at least 32 bytes, generate one with openssl rand -base64 32.

ChangeEffect on MCP clients
Set or change NB_MCP_OAUTH_KEYEvery OAuth client is signed out. Client registrations stay valid when a datastore key is set, so users only sign in again.
Change the datastore encryption keyEvery client registration becomes invalid, and without NB_MCP_OAUTH_KEY every client is also signed out. Users remove the server from their client and add it again.
Restart the combined container without server.store.encryptionKeySame as changing the datastore key.

Personal access tokens are not affected by NB_MCP_OAUTH_KEY. The datastore key also encrypts other data, so do not change it only to sign MCP clients out; see Store Settings.

To sign every client out, change NB_MCP_OAUTH_KEY rather than removing it. Without it, management falls back to the datastore key, and unexpired sessions that were sealed with that key can work again.

Session Store

Active sign-ins live in memory unless Redis is configured. With the in-memory store, a restart of management signs every MCP client out. Users then sign in again from their client.

To keep sign-ins across restarts, point management at Redis with NB_CACHE_REDIS_ADDRESS, a Redis URL:

netbird-server:
  environment:
    - NB_MCP_ENABLED=true
    - NB_CACHE_REDIS_ADDRESS=redis://redis:6379

Setting NB_CACHE_REDIS_ADDRESS also moves management's other caches to Redis. Management connects to Redis when it starts. If Redis cannot be reached or the URL does not parse, management does not start and logs failed to create shared cache store. Use a rediss:// URL for TLS.

Session keys in Redis start with mcp-oauth:. Deleting the mcp-oauth:session:* keys signs every MCP client out.

Multiple Management Instances

Every instance that serves the same domain needs:

  • The same Redis in NB_CACHE_REDIS_ADDRESS. The in-memory store works for a single instance only.
  • The same NB_MCP_OAUTH_KEY, if you set one.
  • The same datastore encryption key.

In a high availability deployment, management stores MCP sign-ins in the Redis set in server.ha.redisAddr, so you need no extra variable. Give every replica the same NB_MCP_OAUTH_KEY, if you set one.

Reverse Proxy

  • Routes. MCP uses /api/*, and /oauth2/* for the embedded identity provider. The standard reverse proxy configurations already route both to management.
  • Root discovery documents. MCP clients first look for discovery documents at the root of the domain, then fall back to the copies under /api. The NetBird dashboard answers the root paths with 404, so clients fall back. If your proxy or another frontend answers unknown paths with 200 and an HTML page, route these three paths to management:
    • /.well-known/oauth-protected-resource/api/mcp
    • /.well-known/oauth-authorization-server/api/mcp/auth
    • /.well-known/openid-configuration/api/mcp/auth
  • Forwarded headers without the embedded identity provider. Management builds its sign-in URLs from the request: the scheme from X-Forwarded-Proto and the host from X-Forwarded-Host, or Host. Management accepts these headers from any caller, so make your proxy set or overwrite both, and do not expose management directly. Without X-Forwarded-Proto: https, the callback becomes an http:// URL that the identity provider rejects. With the embedded identity provider, management uses the issuer's host and ignores these headers.
  • Trusted proxies. The sign-in callback is limited to 30 requests per minute for each client IP address. Management takes the client address from X-Forwarded-For only when the request comes from a trusted proxy. List your proxy in the trusted proxies, as shown below, so that each user gets their own limit. Otherwise all users behind the proxy share one. The setup script sets it for the built-in Traefik. TrustedHTTPProxiesCount is not used for this limit.
location ~ ^/(api|oauth2)/ {
    proxy_pass http://netbird_server;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Real-IP $remote_addr;
}

To trust your proxy, list its address as management sees it, as a CIDR prefix such as 10.0.0.5/32. List only your proxies: management trusts the X-Forwarded-For header of every address in the list.

server:
  reverseProxy:
    trustedHTTPProxies:
      - "10.0.0.5/32"

Rate Limits

The API rate limiting settings apply to MCP when NB_API_RATE_LIMITING_ENABLED is exactly true:

  • A personal access token is limited per token, as on the rest of the API.
  • OAuth calls are limited per user. All MCP clients of one user share the limit.

Calls over the limit get 429. The sign-in callback limit described in Reverse Proxy applies even when API rate limiting is off.

Revoke Access

To cut offDo thisTakes effect
One user on every clientRemove the user, or block the user while they stay blockedNext call
Every OAuth client of every userSet or change NB_MCP_OAUTH_KEY and recreate every management containerWhen management restarts
A user disabled in the identity provider or removed from the JWT allow groupsBlock the user in NetBird for immediate effectOtherwise at their next sign-in, at most 30 days later

Signing clients out on the server, by changing NB_MCP_OAUTH_KEY, deleting the session keys in Redis or restarting management with the in-memory store, leaves no entry in the audit log. Blocking or removing a user records User blocked or User deleted.

See Revoke Access for what users can revoke themselves, and Audit Log for the entries MCP sign-ins record.

Anonymous Metrics

When anonymous metrics are on, management adds these properties to its usage report:

PropertyMeaning
mcp_enabledNB_MCP_ENABLED is on.
mcp_oauth_enabledOAuth sign-in is running.
mcp_tool_calls_last_dayTool calls in the last 24 hours, including calls that returned an error.
mcp_active_users_last_dayNumber of distinct users who made those calls.

Counts are kept in memory by each management instance and reset on restart. User IDs are used only to count users and are never reported. To turn metrics off, set server.disableAnonymousMetrics: true in config.yaml, or pass --disable-anonymous-metrics to the management command.

Troubleshooting

401 without a WWW-Authenticate header

The MCP server runs, but OAuth sign-in did not start. Management logs the reason at startup in a line that starts with MCP OAuth disabled:

Reason in the logFix
no PKCE authorization flow is configuredAdd a PKCEAuthorizationFlow, or let users connect with personal access tokens.
the PKCE flow needs a client id, an authorization endpoint and a token endpointComplete PKCEAuthorizationFlow → ProviderConfig.
NB_MCP_OAUTH_KEY must be at least 32 bytesGenerate a longer key with openssl rand -base64 32.
the MCP base URL "..." must be a scheme and hostSet the embedded identity provider's issuer to https://netbird.example.com/oauth2.

Management does not start

Message in the logFix
failed to register MCP endpointsA name in NB_MCP_TOOLS is not a tool. Correct the list.
failed to create shared cache storeManagement cannot reach Redis, or NB_CACHE_REDIS_ADDRESS does not parse. Check the URL and that management can reach Redis.

The identity provider rejects the redirect URI

The identity provider shows an error such as Keycloak's Invalid parameter: redirect_uri. On a standalone identity provider, add https://netbird.example.com/api/mcp/auth/callback to the application's redirect URIs. If the error shows an http:// callback or the wrong host, fix the X-Forwarded-Proto and X-Forwarded-Host headers on your proxy, as in Reverse Proxy.

Users see the login could not be verified

Management could not validate the identity provider's login. The user may be blocked, pending approval, outside the JWT allow groups, or the token's audience may not be one management accepts. Management may also have failed to redeem the code at TokenEndpoint. Management calls that endpoint itself, so it must be reachable from the management container, and a confidential client also needs ClientSecret. Set the log level to debug and look for MCP login failed for the reason.

Users see the login was not completed

The identity provider refused the login, or the user cancelled it. The identity provider's log has the reason.

Every MCP client is signed out after a restart

Management uses the in-memory session store, or the encryption keys are not persistent. A combined container that logs DataStoreEncryptionKey generated at startup has no persistent datastore key. See Keys and Restarts.

Clients are asked to sign in again minutes after signing in

Several management instances do not share Redis in NB_CACHE_REDIS_ADDRESS, or do not have the same NB_MCP_OAUTH_KEY. See Multiple Management Instances.

Users get 503 with temporarily unavailable, try again shortly

Management cannot reach Redis or its database. MCP sign-ins resume when the store is back. During a Redis outage, personal access tokens keep working. While the database is down, they fail too.

Users get 429 with rate limit exceeded, please try again later while signing in

All users behind your proxy share the callback limit. List the proxy in the trusted proxies, as in Reverse Proxy.