Skip to main content
Glama

nautobot-mcp

CI Python License

An MCP server for Nautobot, built for instances whose API is too large to enumerate: Nautobot 3.2 ships 1,673 REST operations across 477 paths, and every installed app adds more. This server exposes 15 schema-driven tools instead of one tool per endpoint, so the whole API — core and plugins alike — is reachable without flooding an agent's context.

What makes it work

A build step, not runtime parsing. Nautobot's OpenAPI document is 18 MB and its GraphQL introspection another 10 MB. A build script fuses them into a ~1.1 MB SQLite index with an FTS5 search table. The server opens it read-only and answers lookups in microseconds; start-up does not depend on the size of the API.

Foreign keys recovered from GraphQL. OpenAPI alone cannot describe Nautobot's relationships — every related field serialises as an identical opaque object:

// dcim.device: device_type, role, status and location are indistinguishable here
"device_type": { "id": {...}, "object_type": {"pattern": "^[a-z]+\\.[a-z]+$"}, "url": {...} }

GraphQL's type system names the targets outright (device_type → DeviceTypeType), so the two are joined on the OpenAPI component name to recover 441 typed FK edges. That graph is what makes dependency planning possible.

Filters compressed. dcim.device exposes 250 filter parameters, which are really ~74 base fields times a family of lookup suffixes (__ic, __n, __isnull, __gte, …). The index stores base fields plus their suffix sets and describes the vocabulary once.

Related MCP server: Advanced Hasura GraphQL MCP Server

Install

uv venv && uv pip install -e ".[dev]"
cp .env.example .env        # then set NAUTOBOT_URL and NAUTOBOT_TOKEN
cp .mcp.json.example .mcp.json   # optional: for stdio-based clients
python -m nautobot_mcp.schema.build --probe

Or skip the checkout entirely and run it in a container — see Docker.

The build step fetches the schemas and writes var/index.sqlite. Re-run it after installing or upgrading a Nautobot app — or call the nautobot_refresh_schema tool.

Configuration

Variable

Default

Purpose

NAUTOBOT_URL

Base URL, e.g. http://nautobot.example.com:8080

NAUTOBOT_TOKEN

API token

NAUTOBOT_ALLOW_WRITE

false

Master gate for create/update/delete

NAUTOBOT_VERIFY_SSL

true

TLS verification

NAUTOBOT_TIMEOUT

30

Per-request timeout (seconds)

NAUTOBOT_CACHE_DIR

./var

Where schema sources and the index live

NAUTOBOT_MAX_PAGE

1000

Ceiling on fetch_all pagination

Container-only knobs, read by the entrypoint rather than the server:

Variable

Default

Purpose

MCP_TRANSPORT

streamable-http

Transport the container serves (stdio for a client-spawned container)

MCP_HOST

0.0.0.0

Bind address for HTTP transports

MCP_PORT

8000

Bind port for HTTP transports

NAUTOBOT_AUTO_INDEX

true

Build a missing schema index on start instead of refusing to run

Register with a client

{
  "mcpServers": {
    "nautobot": {
      "command": "/path/to/nautobot-mcp/.venv/bin/python",
      "args": ["-m", "nautobot_mcp"],
      "env": {
        "NAUTOBOT_URL": "http://nautobot.example.com:8080",
        "NAUTOBOT_TOKEN": "...",
        "NAUTOBOT_CACHE_DIR": "/path/to/nautobot-mcp/var"
      }
    }
  }
}

HTTP transports are available too: python -m nautobot_mcp --transport streamable-http --port 8000.

Docker

cp .env.example .env        # then set NAUTOBOT_URL and NAUTOBOT_TOKEN
docker compose up -d        # or: make docker-up

The first start builds the schema index against your instance and stores it on the index volume; later starts reuse it. The server listens on 127.0.0.1:8000/mcp.

The index is not baked into the image, and cannot be: it is fused from the schemas of one specific Nautobot instance, including whatever apps that instance has installed. Rebuild it after installing or upgrading an app — make docker-index, or the nautobot_refresh_schema tool, which writes to the same volume.

make docker-index                 # rebuild the index in place
make docker-logs                  # follow the server log
make docker-down                  # stop; VOLUMES=1 also drops the index
docker compose run --rm server index --offline   # rebuild from cached sources only

Registering the container with a client

Over HTTP, point the client at the published port:

{
  "mcpServers": {
    "nautobot": { "url": "http://127.0.0.1:8000/mcp" }
  }
}

Or let the client spawn a container per session over stdio, reusing the same index volume:

{
  "mcpServers": {
    "nautobot": {
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "--env-file", "/path/to/nautobot-mcp/.env",
        "-e", "MCP_TRANSPORT=stdio",
        "-v", "nautobot-mcp_index:/data",
        "nautobot-mcp:latest"
      ]
    }
  }
}

Anything passed after the image name goes straight to python -m nautobot_mcp, so docker run ... nautobot-mcp:latest --transport sse --host 0.0.0.0 --port 8000 works too.

What the compose file assumes

  • The port is published on loopback only. Section Security applies in full: this is an unauthenticated proxy holding a token with your permissions, so reaching it from another host means putting authentication in front of it, not widening the port mapping.

  • Writes stay off unless NAUTOBOT_ALLOW_WRITE=true is in your .env.

  • The container is hardened by default — non-root (uid 1000), read-only root filesystem, all capabilities dropped, no-new-privileges. The only writable path is the /data volume, which is where the index and its cached sources belong.

  • Health is a TCP connect, not an MCP request: an unsessioned request to /mcp makes the session manager allocate a transport that nothing reaps, so probing the protocol every 30s would leak a session per probe.

  • .env is read verbatim by compose. Keep comments on their own line; a trailing # comment is not reliably stripped from a value.

Tools

Tool

Purpose

nautobot_search_schema

Find models by name, description or field name

nautobot_describe_model

Fields, required fields, FK targets, filters, actions

nautobot_list_apps

App namespaces (core and plugin), versions, index state

nautobot_plan_create

Ordered prerequisites for creating an object

nautobot_resolve

Human name → UUID, scoped to the referring model

nautobot_list / nautobot_get

Read any model, slimmed or projected

nautobot_create / nautobot_update / nautobot_delete

Gated writes

nautobot_graphql

Arbitrary GraphQL queries

nautobot_graphql_schema

Introspection, a type at a time

nautobot_model_actions

Non-CRUD endpoints (trace, napalm, notes, …)

nautobot_call

Any REST endpoint — plugins, bulk ops, custom actions

nautobot_refresh_schema

Re-fetch schemas and rebuild the index

Model references are forgiving: dcim.device, device, devices, Device, /dcim/devices/ and DeviceType all resolve, and typos get suggestions (dvice → "Did you mean: dcim.device?").

Dependency planning

Creating a Device on an empty instance means creating four other objects first. nautobot_plan_create("dcim.device") walks the FK graph, checks the live instance for what already exists, and returns them in order:

dcim.manufacturer → dcim.devicetype → dcim.locationtype → dcim.location → extras.role → dcim.device

It also handles Nautobot's content-type scoping. Role, Status and Tag are only assignable to models listed in their content_types. A global count is the wrong question — an instance can hold 20 Roles while none apply to a Device:

{
  "model": "extras.role",
  "action": "create",                      // not "use_existing", despite 20 existing
  "content_type_scoped": true,
  "by_referrer": { "dcim.device": { "valid_count": 0 } },
  "note": "No extras.role is assignable to dcim.device yet. Create one with
           content_types including ['dcim.device'] ..."
}

Which models scope this way is discovered, not hardcoded: content_types means "what may live here" on LocationType and "who may reference me" on Role. The planner tries the scoped query and treats a 400 as proof that scoping does not apply — so plugin models behave correctly with no extra code.

Writes

Writes are off until NAUTOBOT_ALLOW_WRITE=true. Even then, mutations are two-step: the first call returns a preview and a confirm_token, and the call is repeated with that token to apply it. Tokens are derived from the payload, so one issued for one body cannot be replayed against another. nautobot_update previews a field-level diff; nautobot_delete previews the object and everything that references it.

Security

This server is an unauthenticated privileged proxy to Nautobot. It holds an API token and performs no authentication of its own: any client that can reach it acts with that token's full permissions, without ever possessing the token.

Defaults are deliberately safe — --host binds 127.0.0.1 and NAUTOBOT_ALLOW_WRITE is false. The risky configuration is combining a non-loopback bind with writes enabled, which grants unauthenticated create/update/delete over your source of truth to anything that can route to the port.

The confirm-token flow is an accident guard, not an access control — any client can read the token from the preview response and confirm immediately.

If the server must be reachable by other hosts, put authentication in front of it (a reverse proxy with mTLS, an OAuth-aware gateway, or an SSH tunnel) and give it a Nautobot token scoped to only what the agent needs. See SECURITY.md.

Responsiveness

  • One pooled HTTP/2 client is shared across tools; the planner fans out existence checks concurrently.

  • Responses are slimmed before reaching the agent. Nautobot has no sparse-fieldset support (?fields= is rejected as an unknown filter), so url, natural_slug, notes_url, timestamps and empty custom-field blocks are dropped client-side, and nested related objects are reduced to identity. Pass fields=[...] to project, or full=true to opt out.

Extending

Each toolset is a module exposing register(server, ctx), listed in tools/__init__.py::TOOLSETS. Registration is wrapped so every tool returns a structured error instead of raising — an uncaught exception would reach the agent as an opaque "Error executing tool X".

Plugin endpoints need no code: they appear in /api/swagger.json, so rebuilding the index makes them available to every tool.

Tests

pytest

82 tests run against fixtures carved from the live schema, with HTTP mocked via respx. They pin the traps found while building this: the slug collision that maps virtualization.vminterface onto DCIM's InterfaceType, the content_types scoping that silently produces unusable plans, and the FK heuristic that resolves DynamicGroupMembership.group to Django's auth.Group instead of extras.DynamicGroup.

Agent configuration

AGENT.md contains a ready-to-use system prompt and registry description for an agent driving this server, including the write protocol and the content-type scoping rule that most commonly causes a create to fail.

Contributing

Issues and pull requests are welcome. pytest must pass and ruff check / ruff format --check must be clean; CI enforces both across Python 3.11-3.13. The suite needs no Nautobot instance and no network — it runs against schema fixtures in tests/fixtures with HTTP mocked by respx.

License

Apache 2.0 - see LICENSE.

Layout

src/nautobot_mcp/
  schema/build.py     fuses OpenAPI + GraphQL + content types into the index
  schema/index.py     read-only query layer (lookup, FTS search, graph)
  client.py           pooled async HTTP, slimming, error normalisation
  depgraph.py         creation planning and reference resolution
  safety.py           write gate, confirm tokens, diffs
  tools/              one module per toolset, registered through a guard
  server.py           MCP server assembly
A
license - permissive license
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    Enables comprehensive interaction with NetBox infrastructure management through both read and write operations. Supports full CRUD operations for devices, IP addresses, sites, racks, and other NetBox objects through natural language commands.
    9
    16
    Apache 2.0
  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables AI agents to interact with Hasura GraphQL endpoints to discover schema structures and execute queries or mutations. It provides specialized tools for table introspection, data previewing, and performing data aggregations through natural language.
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI agents to interact with any GraphQL API by introspecting the schema and exposing queries and mutations as MCP tools, with built-in pagination, semantic search, and framework adapters.
    13
    MIT

View all related MCP servers

Related MCP Connectors

  • Gateway between LLM agents and world data through eight tools and a bundled endpoint catalog.

  • SaaS intelligence for AI agents. 5 unified tools cover 1,000+ services with 91-96% token savings.

  • Provides cloud browser automation capabilities using Stagehand and Browserbase, enabling LLMs to i…

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/shamalawy/nautobot-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server