MCP Server¶
netbox_mcp provides a deliberately small Model Context Protocol surface over
the same SchemaIndex, request resolver, plugin discovery, pagination, client,
and profile configuration used by the SDK and CLI. It does not generate one
tool per OpenAPI operation, so its tool list stays stable when NetBox plugins
change the reachable resources.
Install and run¶
stdio is the default transport. For Streamable HTTP:
NETBOX_MCP_AUTH_TOKEN="$NETBOX_MCP_AUTH_TOKEN" nbx-mcp --transport streamable-http --host 127.0.0.1 --port 8000
The MCP endpoint is /mcp. Every Streamable HTTP bind requires a
shared-secret bearer token via --auth-token or NETBOX_MCP_AUTH_TOKEN; the
server raises RuntimeError and refuses to start without one, including on
loopback hosts (127.0.0.1, localhost, ::1). Binding to loopback only
restricts reachability to this machine — it does not authenticate other
local processes or users, who could otherwise reach the server's loaded
NetBox credential (and any active --allow-mutations window) unauthenticated
on a shared dev or bastion host. Put TLS termination in front of it when it
is exposed beyond the local host — the bearer token authenticates the
caller, it does not encrypt the transport. Prefer NETBOX_MCP_AUTH_TOKEN
over --auth-token on any shared host: a CLI argument's value is visible to
other local users through ps and /proc/<pid>/cmdline, while the
environment variable is not.
This gate cannot be bypassed by calling the server object directly instead
of going through run(): create_mcp_server() always shadows the returned
server's streamable_http_app and sse_app with wrappers that enforce
the same auth_token, including raising RuntimeError when no token was
configured at all. There is no code path — run("streamable-http"),
streamable_http_app(), run("sse"), sse_app(), or otherwise — that
yields an unauthenticated network app from a create_mcp_server()-produced
instance. The --transport CLI flag only exposes stdio and
streamable-http, but any embedder holding the returned FastMCP instance
directly (not just the nbx-mcp entrypoint) could otherwise reach the SSE
transport unauthenticated, since it shares the same instance-attribute
shadowing mechanism as Streamable HTTP.
Tool surface¶
| Tool | Behavior |
|---|---|
list_groups, list_resources, describe_operation |
Stable JSON schema introspection; live=true includes runtime resources |
list, get |
Schema-resolved reads; list supports pagination and repeated query keys; live=true dispatches against the same connected-instance schema describe_operation(live=true) reports, so a runtime-discovered resource can be listed or fetched, not just described |
filters |
Local filter introspection with no HTTP request |
create, update, patch, delete |
Detail mutations guarded off by default |
bulk_update, bulk_patch, bulk_delete |
List-path mutations guarded off by default |
plugin_discover |
Enrich the active schema through live plugin discovery |
plugin_list_tools |
Discover and validate semantic tools explicitly advertised by NetBox plugin API roots |
plugin_call_tool |
Invoke one advertised plugin operation through the configured SDK client; writes remain guarded off by default |
call |
Relative /api/ escape hatch; GET/HEAD only while the mutation gate is closed |
Every tool input is validated by an explicit Pydantic schema. Names, IDs,
methods, relative API paths, list sizes, unknown fields, and credential control
characters are rejected before dispatch. Raw call paths containing an encoded
path separator (%2F or %5C, case-insensitive) are rejected before cache or
network access because routers disagree on whether those octets split segments.
Plugin manifests are also treated as hostile input: origin, namespace, path,
method/effect consistency, schemas, payloads, nesting, and sizes are checked
before target dispatch. See NetBox Plugin Bridge for the
wire contract and plugin-author checklist.
Authentication¶
For stdio, the server loads the existing default profile from
netbox_sdk.config; it does not create a second credential store. Each tool
that contacts NetBox can instead receive a token bearer credential for that
call. Avoid placing tokens in logs, model-visible transcripts, or checked-in
server configuration.
This per-call NetBox token is separate from the Streamable HTTP transport's
own --auth-token/NETBOX_MCP_AUTH_TOKEN bearer gate described above: the
transport token authenticates the MCP caller to this server, while the
per-call token authenticates this server to NetBox.
Mutation safety¶
Live writes are denied by default. Preview the request first with dry_run=true,
then start a deliberately scoped execution window with either:
dry_run=true resolves the method, path, query, and body locally without
constructing a client. It is not server-side validation and does not prove that
the live NetBox call will succeed. The one exception is
plugin_call_tool: discovery of an advertised tool necessarily performs
read-only GET requests for the live plugin root and manifest. Its dry-run still
never dispatches the advertised target mutation.
The nbx process independently refuses dynamic CRUD/bulk writes, Proxbox
CRUD/sync or TUI launch, write-method raw/dev-HTTP calls, and mutating Branching
verbs unless the reviewed command includes --confirm or its environment
contains NETBOX_SDK_CONFIRM_WRITE=1.
Repository-local Claude Code and Codex hooks add a defense-in-depth early denial
for recognizable Bash source; decoded or generated shell input still reaches
the authoritative CLI-process gate.
Agent operating sequence¶
- Inspect
nbx capabilities --json, or calllist_groups,list_resources,describe_operation, andplugin_list_tools. - Preview every write with
--dry-runordry_run=true. - Explicitly enable/confirm only the reviewed operation and execute it.
- Verify the result with
getor a filteredlist.
The repository ships this procedure as the mirrored
netbox-sdk-operations Skill under .claude/skills/ and .codex/skills/.