AI & MCP Tools

Wiring Simple Facts to MCP: A Technical Deep Dive

Milan Zapletal
May 12, 2026

Table Of Contents

In our announcement post we explained why adding an MCP connector to Simple Facts matters — AI agents need a structured, on-demand interface into your network automation toolkit, and the Model Context Protocol provides exactly that. This article is the follow-up for the engineers who want to know how it actually works: which tools we’re exposing, what real code they map to, and the design decisions that shaped the connector. This is how we wired Simple Facts to MCP: A Technical Deep Dive

The Gap MCP Is Closing

Before the MCP connector, Simple Facts had two consumption surfaces. The CLI handled one-shot, operator-driven runs. Prefect handled scheduled pipelines, retries, artifact history, and the operational visibility that comes with a proper workflow UI. Both are the right tools for their contexts.

But an AI agent reasoning in real time has a different shape of need. It doesn’t want to submit a Prefect flow run and wait for a webhook. It wants to call a tool, get a structured response, incorporate that response into its reasoning, and decide what to call next. That’s a synchronous, on-demand interaction model — and neither the CLI nor Prefect is designed for it.

MCP fills that gap. It gives agents a discoverable, callable surface into Simple Facts without changing anything about how Prefect or the CLI work. The three surfaces coexist, each optimised for a different operator mode.

Three Layers, One Protocol

The connector follows a strict three-layer architecture. Nothing lives in the wrong layer.


┌─────────────────────────────────────────────────┐
          AI Agent / Claude Desktop              
     (any MCP-compatible client or agent)        │
└────────────────────┬────────────────────────────┘
                       MCP protocol (stdio / SSE)
                     
┌─────────────────────────────────────────────────┐
             sfacts MCP Server                   
  ┌─────────────────────────────────────────┐    
    Tool definitions (JSON schema)         │    │
    Thin parameter wrappers                    
    Credential injection (env / keyring)   │    │
  └─────────────────────────────────────────┘    
└────────────────────┬────────────────────────────┘
                       Direct Python calls
                     
┌─────────────────────────────────────────────────┐
             sfacts Core Layer                   
  ┌──────────────────────────────────────────┐   
    NetworkDiscovery   (scanner + discovery)│   │
    BasicFactCollector (collector)          │   │
    NetBoxSyncManager  (core/netbox/)       │   │
    NautobotSyncManager(core/nautobot/)        
  └──────────────────────────────────────────┘   
└─────────────────────────────────────────────────┘

The MCP server owns the protocol surface and nothing else. All business logic — scanning, fact collection, IPAM synchronisation — lives in the sfacts/core/ layer, exactly where it lived before. The connector adds the protocol adapter; it doesn’t rewrite the automation.

One design decision worth calling out explicitly: the MCP tools wrap the core classes directly, not the Prefect task wrappers. Prefect tasks are designed for async, long-running pipeline execution. The core classes are synchronous Python — they return clean dicts and raise clean exceptions. That’s the right shape for an MCP tool call.

The Seven Tools

The connector exposes seven tools across three functional groups: discovery, fact collection, and IPAM management. Here’s how each one maps to the real sfacts codebase.

1. discover_subnet

The entry point for any network inventory workflow. An agent calls this with a CIDR range and gets back a structured list of discovered devices — host, device type, and credentials embedded for the next step.


Agent
  
    discover_subnet(subnet="192.168.1.0/24")
  
MCP Tool
  
    NetworkDiscovery.discover_subnet()
  
SubnetScanner.scan_ports()          ← parallel TCP/22 probe across all hosts

  │  responsive IPs

Netmiko SSHDetect                   ← device type fingerprinting per host

  │  device_type identified

List[DeviceDict]                    → returned to agent

Underneath, Netmiko’s SSHDetect does the heavy lifting on device type fingerprinting. The agent gets back a clean list it can pass directly to collect_facts.

2. discover_tunnel

The lab-mode counterpart to discover_subnet. Instead of scanning a CIDR range, it connects through SSH tunnels — the pattern used with containerlab and remote lab environments. An agent passes a host and port range; the discovery engine maps each port to a virtual device.


Agent
  
    discover_tunnel(host="localhost", port_range="2101-2116")
  
MCP Tool
  
    NetworkDiscovery.discover_tunnel()
  
Port range expansion                 2101, 2102, ... 2116
  
  
Netmiko SSHDetect (per port)         each port = one virtual device
  
  
List[DeviceDict]                     returned to agent

3. collect_facts

The most data-rich tool in the set. It takes the device list from discovery and opens parallel NAPALM connections to each device, running a standard set of getters. The result is the central facts dict that every downstream tool operates on.


Agent
  
    collect_facts(devices=[...])
  
MCP Tool
  
    BasicFactCollector.collect_facts_batch()
  
ThreadPoolExecutor                   parallel connections, configurable workers
  
  ├── NAPALM get_facts               hostname, model, serial, OS version
  ├── NAPALM get_interfaces          interface list, speed, MTU, admin state
  ├── NAPALM get_interfaces_ip       IP/prefix assignments per interface
  ├── NAPALM get_vlans               VLAN table
  ├── NAPALM get_lldp_neighbors      neighbour topology
  ├── NAPALM get_network_instances   VRF table (optional, platform-dependent)
  └── NAPALM get_environment         CPU, memory, temperature (optional)
  
  
FactsDict {                          returned to agent
  "devices": { hostname: {...} },
  "collection_summary": { successful, failed }
}

The optional getters — VRF and environment data — are attempted on every device but silently skipped on platforms that don’t support them. The agent always gets a clean response regardless of platform mix.

4. sync_to_netbox / sync_to_nautobot

The write tools. Both take the facts dict from collect_facts and synchronise it to NetBox or Nautobot respectively. The surfaces are intentionally identical — an agent that knows how to call one knows how to call the other. For a full picture of what gets synchronised, see our NetBox IPAM integration guide.


Agent
  
    sync_to_netbox(facts_data={...}, site_name="Production", dry_run=True)
  
MCP Tool
  
  ├── dry_run=True   count objects, return diff preview, no writes
  
  └── dry_run=False
        
          NetBoxSyncManager / NautobotSyncManager
        
        ├── bulk=True    batch API operations (10-50x faster)
        └── bulk=False   standard sequential API calls
              
              
        Sites  Manufacturers  Device Types  Device Roles
              Devices  Interfaces  VLANs  Prefixes
              VRFs  IP Addresses  MAC Addresses
              
              
        SyncResultDict { devices_created, interfaces_created, ... }
         returned to agent

The dry_run parameter is a first-class tool parameter, not an afterthought. An agent can always call with dry_run=True to get a preview of what would change before committing — exactly the kind of safety check you want in an autonomous workflow.

5. get_netbox_status / get_nautobot_status

Read-only status tools. They test connectivity and return object counts across all IPAM and DCIM categories — sites, devices, interfaces, IP addresses, VLANs, prefixes, VRFs, and more. Safe to call at any point in an agent workflow, with no side effects.


Agent
  
    get_netbox_status()
  
MCP Tool
  
    NetBoxSyncManager.test_connection()
  
Connection check                     returns early with error if unreachable
  
  
Object count queries                 dcim.sites, dcim.devices, ipam.* etc.
  
  
StatusDict {
  "status": "success",
  "connection": { url, version },
  "statistics": { sites, devices, interfaces, ip_addresses, ... }
}                                   → returned to agent

These tools carry over the preflight/postflight pattern from the Prefect flows — where every mutating deployment checks IPAM state before and after. In an MCP context, an agent can replicate that pattern explicitly: call status, call sync, call status again, diff the counts.

6. purge_netbox / purge_nautobot

The destructive tools. They delete IPAM and DCIM objects in the correct dependency order — IP addresses first, then interfaces, devices, VLANs, prefixes, and finally the structural objects like device types and manufacturers. Scoped to a specific site or full instance-wide, depending on parameters.


Agent
  
    purge_netbox(site_name="Lab", dry_run=True)    always True by default
  
MCP Tool
  
  ├── dry_run=True   count objects only, return counts, no deletes
  
  └── dry_run=False (explicit agent decision required)
        
        
        Deletion order (dependency-safe):
        IP Addresses  MAC Addresses  Interfaces  Devices
         VLANs  Prefixes  VRFs*  Device Types*
         Manufacturers*  Device Roles*  Sites*
        (* full purge only — skipped for scoped site purge)
        
        
        PurgeResultDict { status, counts, deleted }
         returned to agent

dry_run=True is the default at the tool definition level. An agent has to explicitly pass dry_run=False to trigger actual deletion — making it impossible to accidentally purge data through a casual tool call.

Wiring the Server: Transport, Credentials, and Parameter Flow

Transport: stdio First

MCP supports two transport modes. stdio runs the server as a local process — the agent spawns it, communicates over stdin/stdout, and the process exits when done. SSE (Server-Sent Events) runs as a persistent HTTP service accessible over the network.

We’re starting with stdio. The primary use case is a single network operator running Simple Facts locally — connecting Claude Desktop or a local agent directly to their network toolkit. stdio is zero-configuration for that scenario: no server to run, no ports to open, no auth layer to manage at the MCP transport level. SSE is a natural next step for multi-user or server-hosted deployments.

Credentials: Out of the Agent’s Hands

One of the most important design decisions: credentials never travel through the MCP tool call. An agent doesn’t pass a NetBox token or SSH password as a tool parameter. The MCP server reads credentials the same way the CLI and Prefect do — from environment variables or the OS keyring.


Agent tool call params:
  { "subnet": "192.168.1.0/24", "timeout": 10 }
                
                
MCP Server
  
  ├── Tool params  Python kwargs (subnet, timeout)
  
  └── Credentials  injected from env / keyring
        NETOPS_USERNAME, NETOPS_PASSWORD   SSH credentials
        NETBOX_URL, NETBOX_TOKEN           NetBox API
        NAUTOBOT_URL, NAUTOBOT_TOKEN       Nautobot API
                
                
        Core class constructor
        NetworkDiscovery(credentials={...}, timeout=10)

This keeps sensitive values out of the MCP message stream entirely. The agent gets tool results — it never sees a token or password. This is the same credential boundary the CLI and Prefect already enforce, so the MCP connector doesn’t introduce any new attack surface.

What Stays in Prefect, What Lives in MCP

A clear responsibility split prevents the two surfaces from conflicting or duplicating each other.

ConcernPrefectMCP
Scheduled / recurring runs
Retry logic and failure handling
Artifact history and audit trail
Preflight / postflight status diffsAgent-driven (explicit calls)
Real-time agent tool calls
On-demand dry-run previews
Long-running pipeline orchestration
Conversational inventory queries

The key insight is that both surfaces call the same sfacts/core/ classes. There is no logic duplication. Prefect tasks wrap the core layer for pipeline execution. MCP tools wrap the same core layer for agent execution. The core layer is unaware of either.


                    sfacts/core/
                         
           ┌─────────────┴─────────────┐
                                      
    sfacts/tasks/               sfacts/mcp/
    (Prefect wrappers)          (MCP tool wrappers)
                                      
                                      
    Prefect flows               MCP Server
    Scheduled pipelines         Agent tool calls
    Artifact history            Real-time responses
    Retry / observability       Dry-run previews

What This Unlocks — and What’s Coming Next

With seven tools across the full inventory lifecycle — discovery, fact collection, IPAM sync, status checks, and cleanup — Simple Facts now has three distinct consumption surfaces: CLI for operator one-shots, Prefect for scheduled pipelines, and MCP for agent-driven workflows. Each is the right tool for its context. None of them required changes to the core library to add the next one.

The connector is in active development ahead of a public release. On the roadmap for potential MCP tools: a drift detection tool that diffs live facts against current IPAM state without triggering a sync, a topology tool that reshapes the LLDP neighbour data already collected by NAPALM into a structured peer map, and a device search tool for filtered queries against NetBox or Nautobot inventory. These are the tools that make agent-driven troubleshooting workflows genuinely powerful — not just automation triggers, but reasoning aids.

Follow the Simple Facts repository for the release, and open an issue if there’s a tool or use case you’d like to see covered.

NETWORK AUTOMATION INSIGHTS
Stay informed about the latest in network automation:
Technical deep dives - Implementation guides -
Industry best practices
Netodata official logo featuring a stylized green geometric icon and the brand name "NETODATA" in white and green typography on a transparent background.
From initial consulting to seamless implementation, we manage your network automation journey every step of the way. Our comprehensive suite of professional services caters to diverse enterprises, ranging from startups to established players.
Contact
1-234-1234
info@netodata.io
Address
Nové sady 988/2
602 00, Brno
Czech Republic
ICO: 23213035
GET IN TOUCH
Address
Netodata Labs, s.r.o. © 2026 All Rights Reserved
Nautobot icon

Nautobot

The central Source of Truth for network infrastructure data. Nautobot serves as:
Authoritative inventory database
IP address components tracking
Configuration template repository
Automation platform

Nornir

A Python automation framework specifically designed for network automation. Nornir provides:
High-performance concurrent task execution
Deep Python integration
Flexible inventory management
Fine-grained control over network operations
CI/CD

Orchestration & CD/CI

We integrate industry-standard orchestration tools to ensure reliable automation delivery:
Git-based version control
Automated pipelines
Controlled deployment workflows
Continuous integration practices

Ansible

An industry-standard automation platform that excels at network configuration management. We utilize Ansible for:
Network device configuration deployment
State validation and compliance checking
Integration with custom Python modules
Standardized workflow automation

Netbox

The central Source of Truth for network infrastructure data. NetBox serves as:
Authoritative inventory database
IP address components tracking
Configuration template repository
REST API provider for automation workflows

Python

The foundation of our automation framework, Python enables us to create modular, maintainable, and efficient network automation solutions. We leverage Python's extensive standard library and carefully selected packages to build:
Reusable automation components
Custom network management tools
API integrations
Data processing pipelines