Nautobot Integration Walkthrough


Date: May 2026

FMCP Version: 0.6

Agent: Claude.ai (Web based)


Claim: A Nautobot instance with three installed plugins was fully accessible to Claude.ai, Claude Code, Cursor, and GPT via MCP — with zero changes to Nautobot's source code.


The Standard We Are Held To

Nautobot follows the Network to Code (NTC) contribution standards. The core rule relevant here: do not modify the framework. Extensions go in plugins, configuration goes in nautobot_config.py, and the framework code stays as shipped. Any integration that edits nautobot/core/, monkey-patches a view, or injects middleware without a sanctioned hook violates this standard.

The integration documented here does not do any of those things.


What Was Running

Server: AWS EC2 T3.medium (2 vCPU, 4 GB RAM)
Nautobot Version: 3.x
MCP Endpoint: https://<url>/api/mcp
frisian-mcp Branch: dispatcher_enhancements

Installed Nautobot Plugins (unmodified):

  • nautobot-bgp-models
  • nautobot-golden-config
  • nautobot-dns-models

None of these plugin packages were modified. None of Nautobot's core packages were modified.


The Complete Integration — Two Files

The entire integration lives in Nautobot's configuration file. No Nautobot source file was edited. No plugin source file was edited. No Django app was created inside the Nautobot repository.

Step 1: INSTALLED_APPS — Two Lines

# nautobot_config.py  (development)
# nautobot_config.prod.py  (production)

INSTALLED_APPS.append("frisian_mcp")
INSTALLED_APPS.append("frisian_mcp.contrib.oauth")

INSTALLED_APPS is a standard Django list. Nautobot exposes it for exactly this purpose — adding apps without touching the framework. frisian_mcp is the core package. frisian_mcp.contrib.oauth adds the OAuth 2.0 token store so agents (Claude.ai, GPT) can authenticate via the standard PKCE flow.

Step 2: Settings — Configuration, Not Code

# MCP endpoint path — mounted at /api/mcp alongside Nautobot's own /api/
FRISIAN_MCP_PATH = "api/mcp"

# Anonymous callers see read-tier tools; authenticated callers get read_write
FRISIAN_MCP_UNAUTHENTICATED_TIER = "read"

# OAuth issuer — must match the public URL clients use to reach this server
FRISIAN_MCP_OAUTH_ISSUER = "https://thehealthygeek.com"  # production
# FRISIAN_MCP_OAUTH_ISSUER = "http://localhost:8080"      # development

# Authentication stack:
# 1. OAuth bearer token (Claude.ai, GPT PKCE flow)
# 2. frisian-mcp static API key (testing, scripts)
# 3. Nautobot's own token (admin/curl access)
FRISIAN_MCP_AUTHENTICATION_CLASSES = [
    "frisian_mcp.contrib.oauth.authentication.OAuthTokenAuthentication",
    "frisian_mcp.contrib.tokens.authentication.FrisianMcpApiKeyAuthentication",
    "nautobot.core.api.authentication.TokenAuthentication",
]

# All callers must be authenticated at the MCP gateway layer
FRISIAN_MCP_PERMISSION_CLASSES = [
    "rest_framework.permissions.IsAuthenticated",
]

# PKCE flow for Claude.ai and GPT:
# - Auto-approve: skip the HTML consent screen (agents cannot render HTML)
# - Auto-register: accept agent-generated client_ids without pre-registration
FRISIAN_MCP_OAUTH_AUTO_APPROVE = True
FRISIAN_MCP_OAUTH_PKCE_AUTO_REGISTER = True
FRISIAN_MCP_OAUTH_PKCE_DEFAULT_PERMISSION = "read_write"

# Map Nautobot user roles to MCP tiers
FRISIAN_MCP_TOKEN_TIER_MAP = {
    "superuser": "read_write",
    "staff":     "read_write",
    "default":   "read",
}

Every line here is a setting assignment in nautobot_config.py. The Nautobot documentation explicitly describes this file as the place to configure installed apps. Nothing above touches a Nautobot class, view, serializer, or model.

Step 3: Dispatcher Groups — Tool Surface Configuration

Nautobot's REST API exposes over 1,500 resources across 15+ app boundaries. Sending all of them to an AI client's context window as individual tools would consume more tokens than the context window holds before a single tool call is made. The dispatcher configuration collapses this surface into named groups. This is also a setting — no code:

FRISIAN_MCP_DISPATCH_GROUPS = {
    "dcim": [
        "device", "rack", "rackgroup", "rackreservation",
        "interface", "interfacetemplate",
        "cable", "location", "locationtype",
        "manufacturer", "devicetype", "devicefamily",
        "platform", "inventoryitem",
        "consoleport", "powerport", "powerpanel",
        "module", "modulebay", "modulefamily", "moduletype",
        "controller", "virtualchassis",
        "softwareimagefile", "softwareversion",
        # ... 30+ additional DCIM resources
    ],
    "ipam": [
        "ipaddress", "prefix", "vlan", "vlangroup",
        "vrf", "routetarget", "iprange", "namespace",
        "rir", "service",
        # ... additional IPAM resources
    ],
    "circuits":        ["circuit", "circuittype", "circuittermination", "provider", "providernetwork"],
    "tenancy":         ["tenant", "tenantgroup", "contact", "contactassociation", "team"],
    "virtualization":  ["cluster", "clustergroup", "virtualmachine", "vminterface"],
    "wireless":        ["wirelessnetwork", "radioprofile", "supporteddatarate"],
    "cloud":           ["cloudaccount", "cloudnetwork", "cloudresourcetype", "cloudservice"],
    "vpn":             ["vpn", "vpnphase1policy", "vpnphase2policy", "vpntunnel"],
    "load_balancers":  ["loadbalancerpool", "virtualserver", "certificateprofile"],
    "users":           ["user", "group", "token", "objectpermission"],
    "approvalworkflow":["approvalworkflow", "approvalworkflowdefinition", "approvalworkflowstage"],
    "data_validation": ["minmaxvalidationrule", "regularexpressionvalidationrule", "uniquevalidationrule"],
    "extras":          ["tag", "customfield", "relationship", "dynamicgroup", "job", "webhook",
                        "role", "status", "secret", "note", "objectchange"],
    # Plugin resources — configured the same way as core resources
    "golden_config":   ["goldenconfig", "goldenconfigsetting", "compliancefeature",
                        "compliancerule", "configcompliance", "configremove", "configreplace",
                        "remediationsetting", "configplan"],
    "bgp":             ["autonomoussystem", "autonomoussystemrange", "peergroup",
                        "peerendpoint", "peering", "bgproutinginstance"],
    "dns":             ["dns_views", "dns_zones", "ns_records", "a_records",
                        "aaaa_records", "cname_records", "mx_records", "txt_records"],
}

The plugin groups (golden_config, bgp, dns) are configured identically to the core groups. The plugin packages were installed by Nautobot's normal plugin mechanism. frisian-mcp reads their OpenAPI schemas at startup the same way it reads Nautobot core's schemas.


What Agents Could Do After Configuration

The following results were produced on 2026-05-06, against the live server, with no human writing or guiding the operations.

Dispatchers Exposed

Group Resources Tools Available
Nautobot:dcim 53 530+
Nautobot:bgp 10 100
Nautobot:golden_config 9 89
Nautobot:approvalworkflow 7 53
Nautobot:circuits 5 51
Total 84+ 634+

634 flat Nautobot API resources, accessible as 5 logical dispatcher groups (plus all other configured groups above). Every CRUD operation available via Nautobot's REST API is available via MCP.

GPT: Infrastructure Build From Scratch

GPT was given the following task (verbatim from nautobot_gpt_build_instructions.md):

"You have access to an MCP service called Nautobot MCP Tool. Your task is to use this tool to construct a complete integrated environment from scratch. You start with nothing."

No API keys were given to GPT. No Nautobot topology was pre-loaded. GPT used the dispatcher's help action to discover what resources existed, then planned and executed a full build. Result (validated by a second agent):

Metric Result
Geographic locations 3 (NYC Metro Core, Chicago Regional DR, Denver Edge Campus)
Total devices 65
Device types Routers, core switches, aggregation switches, access switches
WAN circuits 2 (NYC↔Chicago, Chicago↔Denver)
Build errors 0
Build completeness 100% specification compliance

Claude.ai: Dispatcher Validation and Plugin Object Creation

Claude.ai (claude-sonnet-4-6) ran a separate validation session on the same server. Session duration: 30 minutes. Error count: zero.

Objects created via MCP (BGP Models plugin):

ASN Description UUID
65001 NYC Enterprise b0e5b9c9-6e88-4c76-8897-676b5ea6ab0b
65002 NYC Transit e71837d5-7d7e-45e3-9761-14be762b37c5
65003 Chicago Enterprise 0b2722b5-a030-4ca4-93b7-33153ba31aa1
65004 Chicago Transit 4470ca72-d068-449e-b28c-19022dcd8948
65005 Denver Enterprise 11dd192d-8f11-4f48-8675-7a273d04e7df
65006 Denver Transit 42c113bd-beb6-4f44-9c81-74ca91e24a96

Objects created via MCP (Golden Config plugin):

  • Compliance Feature: "NTP Configuration Check" (750ad595-c24d-4f24-92c8-11f25645706d)

All 8 objects created on first attempt. The plugin objects were created exactly the same way as core Nautobot objects — same dispatcher pattern, same tool call format, same authentication flow.

Token efficiency measured:

Dataset Without pagination With @mcp_heavy Savings
65 devices (tested) ~40,300 tokens ~31,000 tokens 23%
500 devices (projected) ~310,000 tokens ~31,000 tokens 90%
2,000 devices (projected) ~1,240,000 tokens ~31,000 tokens 97%

At 500+ devices, an unpaginated response would consume the entire context window before a single operation could be performed. The @mcp_heavy decorator — configured, not coded — applies automatic pagination to prevent this.


What Was Not Touched

The following directories contain only unmodified, as-shipped code:

nautobot/nautobot/           — Nautobot core framework
nautobot/nautobot/core/      — Core views, models, authentication, API
nautobot/nautobot/dcim/      — DCIM app
nautobot/nautobot/ipam/      — IPAM app
nautobot/nautobot/circuits/  — Circuits app
nautobot/nautobot/extras/    — Extras app

No file in any of these directories was opened for editing during integration. The git history of the Nautobot repository is clean — no frisian-mcp related commits in the core packages.

Plugin packages (nautobot-bgp-models, nautobot-golden-config, nautobot-dns-models) were also unmodified. They were installed via the standard Nautobot plugin mechanism and registered in PLUGINS in nautobot_config.py, as their own documentation requires.


Why This Matters

Nautobot is a shared platform. NTC and enterprise operators run it across teams. An integration that modifies core code creates an upgrade dependency — every Nautobot release requires the integration patch to be rebased, tested, and re-applied. A misconfigured patch breaks everyone on that instance.

An integration that only touches nautobot_config.py carries no upgrade risk. When Nautobot releases a new version, pip install --upgrade nautobot runs clean. The frisian-mcp settings in nautobot_config.py remain in place. The MCP endpoint re-registers at startup by reading the new version's OpenAPI schema.

This is the difference between a plugin and a patch. The integration demonstrated here is a plugin.


Summary

What How Where
Package install pip install frisian-mcp Standard Python packaging
App registration INSTALLED_APPS.append(...) nautobot_config.py only
MCP endpoint FRISIAN_MCP_PATH = "api/mcp" nautobot_config.py only
Authentication FRISIAN_MCP_AUTHENTICATION_CLASSES nautobot_config.py only
Tool surface FRISIAN_MCP_DISPATCH_GROUPS nautobot_config.py only
Nautobot core changes None
Plugin source changes None

Claude.ai, Claude Code, Cursor, and GPT connected to the same endpoint using the same OAuth PKCE flow. All four clients used the tools/listtools/call protocol without any client-specific configuration on the server side. The server does not know or care which client is calling — it authenticates the token, checks the tier, and dispatches the tool.


Source data: — session reports, validation logs, and build task outputs from live server runs on 2026-05-06.
Configuration source: nautobot/development/nautobot_config.py and nautobot/development/nautobot_config.prod.py — the only files modified during integration.