Public MCP Server
The public MCP server is the endpoint noBGP hosts for you:
https://mcp.nobgp.com/mcp
It speaks for your account — every network and organization you belong to. Point an AI client at it and it can list your fleet, create networks, provision and register nodes, publish services, run commands, move files, and watch events, on machines behind NAT, CGNAT and firewalls that ssh, scp and curl cannot reach.
This page is the guide — what the endpoint is, how to connect to it, and how it authenticates. For the tools themselves, see the MCP Reference, which covers this surface and the local server side by side.
Connect a client
- Claude Desktop — add noBGP as a custom connector
- Claude Code — an
httpserver entry in.mcp.json - ChatGPT — the pre-configured Custom GPT, or noBGP as a Developer Mode connector
What only this surface can do
A node's local server exposes a deliberate subset of the same tools. The things it deliberately cannot do all live here:
- Create and destroy —
network_create,network_delete,provision_node,deprovision_node,register_node - Organizations and billing —
org_create,org_update,org_sso_setup,org_sso_set_enforced - Publish services —
service_publish,service_update,service_delete,service_share - Widen a node's reach —
node_label,node_grant,node_revoke - See across networks —
network_directory, and any call naming a network other than one node's own
A granted node is bounded to its own network and cannot grant or label, so it can never widen its own reach. That boundary is the reason both surfaces exist.
Authentication
All endpoints require a Bearer token.
- MCP (
https://mcp.nobgp.com/mcp): OAuth 2.0 Authorization Code Flow with PKCE. Sign in with any provider offered on the noBGP login page (Google, GitHub, and any SSO connection your organization has configured). Token refresh is automatic; session management is per-conversation. - REST / OpenAPI: supply the same Bearer token via the
Authorizationheader.
Discovery
A client that already knows how to sign in — Claude Desktop, Claude Code, ChatGPT — needs nothing from this section. A client you are writing yourself discovers where to sign in from the endpoint itself, and this is the contract it can rely on.
An unauthenticated request to a protected path answers 401 with a WWW-Authenticate header naming the metadata document for that endpoint:
WWW-Authenticate: Bearer realm="nobgp",
resource_metadata="https://mcp.nobgp.com/.well-known/oauth-protected-resource/mcp",
authorization_uri="https://signin.nobgp.com/oauth2/authorize"
Fetching that document returns RFC 9728 protected-resource metadata:
{
"resource": "https://mcp.nobgp.com/mcp",
"authorization_servers": ["https://signin.nobgp.com"],
"bearer_methods_supported": ["header"]
}
Four things are worth knowing about it:
authorization_serversnames exactly one server, and it is never the MCP host. noBGP's sign-in service is the authorization server; the router only accepts the tokens it issues. Client registration, authorization and token exchange all happen at the entry in this list — read it rather than assuming the endpoint you called also issues tokens. Router 0.4.49 removed a second entry that pointed back at the MCP host, where no such endpoints exist.- The router serves no
/.well-known/oauth-authorization-server. It is a resource server: it accepts tokens, it does not issue them. Any authorization-server metadata you need comes from the issuer named above, at that issuer's own address. resourceis the canonical URI, with no trailing slash. You are expected to compare it against the URL you are calling as a plain string, sohttps://mcp.nobgp.comandhttps://mcp.nobgp.com/are not interchangeable. The bare/.well-known/oauth-protected-resourceanswers with the former.- The document is per-endpoint.
/.well-known/oauth-protected-resource/mcpdescribeshttps://mcp.nobgp.com/mcp;/.well-known/oauth-protected-resource/api/v1/toolsdescribes the REST path. The401challenge always names the one that matches what you called, so following it is simpler than constructing it.
Scopes
Each tool advertises the scope family it belongs to via x-nobgp-auth-scopes in the OpenAPI schema and GET /api/v1/tools. Scopes are advisory classification for clients; authorization is enforced by organization role (see the role notes on each tool). A denied call returns forbidden (HTTP 403).
| Scope | Tools |
|---|---|
| (none) | whoami, node_label, node_grant, node_revoke, command_subscribe, the event tools (fs_subscribe, presence_subscribe, event_tail, event_unsubscribe, event_publish, event_subscriptions), and the SSO/billing tools |
network.read | network_directory |
network.write | network_create, network_delete |
org.write | org_create |
node.register | register_node |
provisioning.write | provision_node, deprovision_node |
service.write | service_publish, service_update, service_delete, service_share |
shell.exec | command |
fs.read | file (read/list/stat), fs_read, fs_list, fs_stat, fs_glob, fs_grep, fs_grep_subscribe |
fs.write | file (write/edit/delete/mkdir), fs_write, fs_edit, fs_delete, fs_mkdir |
net.read | net_peers, net_interfaces, net_metrics, net_routes, net_dns |
members.manage | org_update |
Transport adapters
The same canonical tools registry is reachable over three adapters. Parameters and response shapes are identical — only framing, auth carrier, and streaming delivery differ.
MCP — /mcp
JSON-RPC 2.0 over Streamable HTTP. Tools advertised via tools/list; invoked via tools/call. Streaming tools deliver chunks as notifications/progress messages; one-shot tools return their full response in the tools/call result.
REST — POST /api/v1/tools/{name}
Request struct as JSON body. Success envelope:
{ "data": { "...typed response..." }, "message": "human-readable summary" }
Error envelope:
{ "error": { "code": "not_found", "message": "..." } }
Streaming tools (fs_grep) negotiate via Accept: text/event-stream and respond with SSE events: chunk, progress, done, error.
OpenAPI — GET /api/v1/openapi.json
OpenAPI 3.1 spec served live from any router replica. Use it as a build input for SDK generation. Every tool appears under /api/v1/tools/{name} with full request/response schemas.
Tool index — GET /api/v1/tools
Returns a brief index (name, description, mode, capabilities) for quick introspection.
Execution modes
one_shot— request in, response out. Used by every read tool and most write tools.server_stream— long-running tool that emits incremental progress events (SSE on REST, MCP progress on MCP). Currently onlyfs_grep.
Whether fs_grep matches actually arrive incrementally depends on the node: a search that has to run as the node's configured user — the usual case, since registration sets user to the installing account — is performed by the agent's privilege-dropped file worker, which answers with one reply per request. The response shape and the SSE framing are the same either way; the matches simply land when the walk finishes instead of as they are found. On agent 0.4.34 that case was refused outright; upgrade to 0.4.35 if fs_grep fails on a node.
A few tools (command, file read/write) are technically one_shot but expose a session-based workflow: the first call returns a session id, subsequent calls pass that id to continue. See Cross-replica session forwarding.
fs_glob (doublestar pattern match) is available via the versioned REST surface at POST /api/v1/tools/fs_glob and is not registered over MCP. It is a one-shot call; nothing about the transport prevents it, so this may change.
Its matches are bounded by the node's allow-roots at every directory the walk enters, and a refused directory is pruned. Following symlinks does not widen that: agents before 0.4.37 could descend through a link and report entries whose real path lay outside the roots entirely, which was only ever a list of names fs_read refused on the next call.
fs_grep is on MCP — see fs_grep for how its results arrive, which depends on whether you send a progressToken.
org_update is likewise REST-only — it is intended for dashboard/management clients rather than the conversational MCP toolset. So are the remaining organization-membership, audit, and billing operations (org_members_list, org_invite_*, org_member_*, org_leave, org_transfer_ownership, org_audit_list, create_checkout, create_billing_portal, set_spend_cap); their behavior is described on the Organizations and Plans & Billing pages.
Rate limiting
Requests may return rate_limited with a Retry-After hint — back off and retry.
A rate_limited error includes details.retry_after_ms. Streaming tools (fs_grep) also count against a global in-flight slot pool (default 8 concurrent streams) — exhaustion surfaces as resource_exhausted (HTTP 429) rather than rate_limited. The pool frees up as in-flight streams finish, so this case is safe to retry shortly.
Audit logging
The router emits one structured audit event per tool invocation, capturing who called the tool, which tool ran, the target it acted on, when it ran, and the outcome. Tool arguments are not captured — they can contain secrets (tokens, keys, sensitive paths), so only that narrow summary is logged. Events are written to the per-organization audit log.
Versioning
The router and the agent are versioned independently. Tool schemas are forward-compatible: response struct field additions land without a major bump and are absorbed by the loose-output-schema in the MCP adapter, so cached clients don't break on new fields.
Field renames or type changes are breaking and require a coordinated agent + router release. CI diffs a committed OpenAPI snapshot on every change to catch unintended drift.
For the authoritative machine-readable schemas, fetch /api/v1/openapi.json live from any router replica.
When your client's tool list is stale
Nothing forces an MCP client to refetch tools/list. The endpoint is stateless, so there is no server→client channel to push a notifications/tools/list_changed on, and the protocol's cache hint is already as strong as it gets — every response is marked immediately stale — yet some clients cache regardless. A client holding a snapshot from an older router build then fails in two ways that both read as a bug in the tool:
- A parameter its schema never listed gets sent untyped, and Claude-family clients serialize an untyped value as a string.
admin: "true"was then rejected withtype: true has type "string", want "boolean"— an error that blames the type and never mentions the cache. - A parameter removed since the snapshot —
session.username, gone in router 0.4.33 — is rejected as an unknown property, with nothing saying the whole snapshot predates the removal.
Both are mitigated at the boundary now:
- Boolean strings are coerced.
"true","false","1"and"0"on a boolean-typed parameter become the boolean and the call proceeds. This is booleans only, and deliberately so: the string form of a boolean is unambiguous, while a stringified array or integer is not, so those still fail — the fix for them is a refreshed schema, not a lenient server. - Every argument-validation rejection names the actual fix, in a postscript carrying the router version you are talking to. So a call that coercion cannot save costs one failed call instead of a wrong mental model built around a field that appears not to exist.
Both only apply to calls the validator rejects; an accepted call is untouched. Ordinary mistakes — naming both node_id and node_name, say — stay unadorned, because those are not stale caches.
Reconnecting your client is the real fix. A router deploy disconnects MCP clients, and most refetch the tool list on reconnect.
Next Steps
- MCP Reference - Every tool on both surfaces, with parameters and response shapes
- Local MCP Server - The other surface: a node serving MCP on
127.0.0.1 - Use Cases & Examples - The tools in action
- Core Concepts - The architecture underneath
Additional Resources
- MCP Specification - Learn about the protocol
- noBGP Web Dashboard - Alternative management interface
- OpenAPI Schema - machine-readable schema for every tool