MeshOptixIQ Network Discovery
Vendor-agnostic network discovery and graph ingestion pipeline. Collects CLI output from network devices via Netmiko over SSH, parses it into canonical Pydantic models, normalizes and deduplicates the data, and ingests it into Neo4j or PostgreSQL.
┌─────────────┐ ┌──────────────┐ ┌────────────┐ ┌───────────────┐ ┌──────────┐
│ Inventory │────▶│ Netmiko │────▶│ Vendor │────▶│ Normalization │────▶│ Neo4j / │
│ (YAML) │ │ Collector │ │ Parsers │ │ & Dedup │ │ Postgres │
└─────────────┘ └──────────────┘ └────────────┘ └───────────────┘ └──────────┘
│ │ │
▼ ▼ ▼
Raw CLI Output Canonical Models Validated Facts
(JSON files) (Pydantic v2) (Schema v1)
Table of Contents
- Installation
- Configuration
- CLI Usage
- API Server
- Query Library
- Docker Deployment
- Cluster Mode
- Kubernetes / Helm
- Database Backends
- Supported Vendors
- Data Model
- Enterprise Features
- MCP Server
- Development
- Licensing
Installation
From PyPI (CLI + MCP server)
Wheels are published for CPython 3.11–3.14 on Linux (x86_64, aarch64), macOS (x86_64, arm64) and Windows (x86_64).
# Isolated CLI install (recommended); pick a 3.11-3.14 interpreter if your default is newer
pipx install --python python3.12 "meshoptixiq-network-discovery==0.27.0"
# CLI + bundled MCP server (meshq-mcp)
pipx install --python python3.12 "meshoptixiq-network-discovery[mcp]==0.27.0"
# Or with pip, inside a Python 3.11-3.14 virtualenv (add extras as needed:
# postgres, cluster, mcp, enterprise, ai, grpc, graphql, ...)
pip install "meshoptixiq-network-discovery==0.27.0"
The CLI and MCP server take their license plan from your MeshOptixIQ API server — point them at it with MESHOPTIXIQ_API_URL (and MESHOPTIXIQ_API_KEY). Run the API itself from the container image meshoptixiq/meshoptixiq:0.27.0 (see Docker Deployment) or the Helm chart (see Kubernetes / Helm).
Local (development, from a source checkout)
cd network_discovery
# Core install (Neo4j backend)
pip install -e ".[dev]"
# With PostgreSQL support
pip install -e ".[dev,postgres]"
# With cluster (Redis) support
pip install -e ".[dev,cluster]"
# With MCP server support
pip install -e ".[dev,mcp]"
# All enterprise features
pip install -e ".[dev,enterprise]"
After installation the meshq CLI is available on your PATH.
Docker
Published images: meshoptixiq/meshoptixiq:<version> and meshoptixiq/meshoptixiq:enterprise-<version> on Docker Hub. To build locally from a source checkout:
# From the repo root
docker build --target runtime -t meshoptixiq/meshoptixiq:local .
docker build --target enterprise -t meshoptixiq/meshoptixiq:enterprise-local .
Configuration
Core Environment Variables
| Variable | Default | Description |
|---|---|---|
API_KEY |
(required) | API key — server refuses to start without it |
GRAPH_BACKEND |
neo4j |
neo4j or postgres |
MESHOPTIXIQ_LICENSE_KEY |
(required by API server) | License key; CLI and MCP no longer need this — they query the local API |
MESHOPTIXIQ_API_URL |
http://localhost:8000 |
Local API server URL used by CLI + MCP for plan resolution |
MESHQ_INSTALL_ID |
(unset) | Stable licensed-device identity shared by all replicas of one install; if unset a UUID is persisted in <data dir>/install_id |
MESHQ_DATA_DIR |
./data |
Directory holding install_id (falls back to ~/.meshoptixiq when ./data is absent) |
MESHQ_LICENSE_REVALIDATE_SECONDS |
3600 |
Periodic license re-validation interval (min 300, 0 disables) |
DEVICE_PASSWORD |
(none) | Default SSH password for collection |
CORS_ORIGINS |
(empty — denied) | Comma-separated allowed CORS origins |
Neo4j
| Variable | Default | Description |
|---|---|---|
NEO4J_URI |
bolt://localhost:7687 |
Bolt connection URI |
NEO4J_USER |
neo4j |
Username |
NEO4J_PASSWORD |
(empty) | Password (required in production) |
PostgreSQL
| Variable | Default | Description |
|---|---|---|
POSTGRES_DSN |
postgresql://postgres:postgres@localhost:5432/network_discovery |
Connection string |
POSTGRES_POOL_MIN |
2 |
Minimum pool connections |
POSTGRES_POOL_MAX |
10 |
Maximum pool connections |
Transport Security (v0.26 — see network_discovery/docs/security.md)
| Variable | Default | Description |
|---|---|---|
MESHQ_SSH_HOST_KEY_POLICY |
tofu |
SSH/NETCONF host keys: tofu pins on first connect and refuses changed keys, strict only contacts hosts in the known_hosts file, insecure disables checks (warns). Manage pins with meshq hostkeys list|forget <host> |
MESHQ_SSH_KNOWN_HOSTS |
<data dir>/ssh_known_hosts |
Pinned host-key file (OpenSSH format) |
GNMI_INSECURE |
false |
gNMI uses TLS; true opts out (warns). GNMI_TLS_CA / GNMI_TLS_CERT / GNMI_TLS_KEY / GNMI_TLS_SERVER_NAME configure verification and mTLS |
MESHQ_GRPC_TLS_CERT / MESHQ_GRPC_TLS_KEY |
(unset → plaintext) | Serve the gRPC QueryService over TLS (MESHQ_GRPC_TLS_CLIENT_CA for mTLS; MESHQ_GRPC_HOST bind address) |
Clustering (Redis)
| Variable | Default | Description |
|---|---|---|
REDIS_URL |
(none) | Activates cluster mode when set |
Authentication & RBAC
| Variable | Default | Description |
|---|---|---|
AUTH_MODE |
api_key |
api_key, oidc, or both |
OIDC_DISCOVERY_URL |
(none) | OIDC provider discovery URL |
OIDC_CLIENT_ID |
(none) | OIDC client ID |
RBAC_POLICY_FILE |
(none) | Path to YAML RBAC policy file (hot-reloaded every 30 s) |
RBAC_POLICY |
(none) | Inline YAML RBAC policy string |
RBAC_RELOAD_INTERVAL |
30 |
Seconds between RBAC file mtime checks |
Inventory File
# inventory.yaml
devices:
- hostname: core-sw-01
host: 192.168.1.1
vendor: cisco_ios
username: admin
password_env: DEVICE_PASSWORD # reads from environment variable
- hostname: fw-01
host: 10.0.0.1
vendor: paloalto_panos
username: netops
key_file: ~/.ssh/id_rsa # SSH key auth
CLI Usage
meshq ingest — Full pipeline
meshq ingest --source inventory.yaml [--output-dir ./output] [--dry-run] [--workers N] [-v]
| Flag | Description |
|---|---|
--source PATH |
Inventory YAML file (required) |
--output-dir DIR |
Directory for raw JSON output (default: output/) |
--dry-run |
Collect, parse, normalize but skip database writes |
--workers N |
Concurrent SSH threads (default: 10) |
-v |
Debug logging |
Pipeline stages: API license check → collect → parse → normalize → ingest.
meshq collect — Collection modes
# Standard: collect all devices in inventory
meshq collect --source inventory.yaml
# Dispatch: push device list to Redis queue, then exit
meshq collect --source inventory.yaml --dispatch
# Worker: pop devices from queue, collect, ingest, repeat
meshq collect --worker [--poll-interval 5]
| Flag | Description |
|---|---|
--source PATH |
Inventory YAML (required for standard + dispatch) |
--dispatch |
Push all devices to Redis queue and exit |
--worker |
Pop-collect-ingest loop (requires REDIS_URL) |
--poll-interval N |
Idle seconds between pops in --worker mode (default: 5) |
meshq parse — Parse existing raw files
meshq parse --raw-dir output/raw/
meshq export — Export Ansible inventory
meshq export --format ansible [--output inventory.json]
meshq export --format ansible --output inventory.ini # legacy INI format
meshq sync — NetBox sync
meshq sync --target netbox [--direction push|pull|both] [--dry-run]
meshq status / meshq version
meshq status # Check backend connectivity
meshq version # Print version (no license required)
API Server
Starting the server
# Local (loopback; front with a TLS reverse proxy to serve other machines)
uvicorn network_discovery.api.main:app --host 127.0.0.1 --port 8000
# Docker
docker run -d -p 127.0.0.1:8000:8000 \
-e NEO4J_URI=bolt://host.docker.internal:7687 \
-e NEO4J_PASSWORD=secret \
-e API_KEY=changeme \
meshoptixiq/meshoptixiq:0.27.0
Interactive Swagger docs: http://localhost:8000/docs
Endpoints
Health
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/health |
No | Process alive check |
GET |
/health/ready |
No | Backend reachability; includes pool_available for postgres |
GET |
/health/redis |
No | Redis cluster connectivity |
GET |
/health/license |
No | License plan tier, expiry date, and days remaining |
Queries
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/queries/ |
Yes | List all 25 queries with metadata |
GET |
/queries/{name} |
Yes | Query details |
POST |
/queries/{name}/execute |
Yes | Execute with parameters |
Collection Queue (cluster mode)
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/collect/status |
Yes | Queue depth + in-flight count |
POST |
/collect/dispatch |
Yes | Push device list to queue |
POST |
/collect/recover |
Yes | Re-queue stale in-flight devices |
Admin
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/admin/config |
Yes | Sanitized runtime config (admin role) |
POST |
/admin/rbac/reload |
Yes | Force RBAC policy reload + broadcast |
Other
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/auth/me |
Yes | Current user context |
GET |
/events |
Yes | SSE stream (30 s keepalive) |
GET |
/history/snapshots |
Yes | Historical metric snapshots |
GET |
/history/diff |
Yes | Compare two snapshots by timestamp (?from_ts=…&to_ts=…) |
POST |
/graph/whatif |
Yes | Simulate proposed NetworkFacts against current state (pro+ only) |
GET |
/inventory/ansible |
Yes | Ansible dynamic inventory (?format=ini) |
Authentication
API_KEY is required. Pass via X-API-Key header or ?api_key= query parameter (for SSE EventSource):
curl -H "X-API-Key: $API_KEY" http://localhost:8000/queries/
Query Library
25 queries across 7 categories, each with Cypher (Neo4j) and SQL (PostgreSQL) implementations.
Topology
| Query | Parameters | Description |
|---|---|---|
device_neighbors |
device_name |
Devices directly connected to a given device |
interface_neighbors |
device_a, device_b |
Interfaces connecting two devices |
topology_edges |
(none) | All edges in the topology graph |
topology_summary |
(none) | Device and link count summary |
all_devices |
(none) | All known devices |
topology_neighborhood |
device, depth |
N-hop neighborhood subgraph around a focal device |
Endpoints
| Query | Parameters | Description |
|---|---|---|
locate_endpoint_by_ip |
ip, vrf (optional) |
Find endpoint by IP address; filter by VRF |
locate_endpoint_by_mac |
mac |
Find endpoint by MAC address |
endpoints_on_interface |
device, interface |
Endpoints on a specific interface |
Blast Radius
| Query | Parameters | Description |
|---|---|---|
blast_radius_interface |
device, interface |
Endpoints impacted by interface failure |
blast_radius_device |
device |
Endpoints impacted by device failure |
blast_radius_vlan |
vlan |
Endpoints in a VLAN |
blast_radius_subnet |
cidr |
Endpoints in a subnet |
Addressing
| Query | Parameters | Description |
|---|---|---|
ips_in_subnet |
cidr, vrf (optional) |
IPs allocated in a subnet; filter by VRF |
subnets_on_device |
device |
Subnets present on a device |
orphaned_ips |
(none) | IPs with no known subnet |
Hygiene
| Query | Parameters | Description |
|---|---|---|
devices_without_neighbors |
(none) | Isolated devices |
interfaces_without_ips |
(none) | Interfaces missing IP assignments |
endpoints_without_location |
(none) | Endpoints with no physical port |
Firewall
| Query | Parameters | Description |
|---|---|---|
firewall_rules_by_device |
device_name |
All rules for a firewall, ordered by precedence |
firewall_rules_by_zone_pair |
src_zone, dst_zone |
Rules between a source/destination zone pair |
path_analysis |
src_ip, dst_ip |
Rules matching traffic between two IPs |
all_firewall_devices |
(none) | All devices with collected firewall rules |
deny_rules_summary |
(none) | All deny/drop/reject rules across every firewall |
Inventory
| Query | Parameters | Description |
|---|---|---|
update_device_metadata |
(none) | Pull nb_site/tenant/rack from NetBox onto Device nodes |
Example:
curl -X POST http://localhost:8000/queries/blast_radius_device/execute \
-H "Content-Type: application/json" \
-H "X-API-Key: changeme" \
-d '{"parameters": {"device": "core-sw-01"}}'
Docker Deployment
# API + Neo4j
docker run -d -p 127.0.0.1:8000:8000 \
-e NEO4J_URI=bolt://host.docker.internal:7687 \
-e NEO4J_PASSWORD=secret \
-e API_KEY=changeme \
-e MESHQ_TOKEN_SALT=$(python3 -c "import secrets; print(secrets.token_hex(32))") \
-e MESHOPTIXIQ_LICENSE_KEY=your-key \
-v meshq_data:/app/data \
meshoptixiq/meshoptixiq:0.27.0
# Collection job (API server must be running; CLI resolves plan from it)
docker run --rm \
-e NEO4J_URI=bolt://host.docker.internal:7687 \
-e NEO4J_PASSWORD=secret \
-e MESHOPTIXIQ_API_URL=http://host.docker.internal:8000 \
-e MESHOPTIXIQ_API_KEY=changeme \
-v $(pwd)/configs:/app/configs \
meshoptixiq/meshoptixiq:0.27.0 \
meshq ingest --source /app/configs/inventory.yaml
The image contains every entry point — override the command to run one, e.g. docker run --rm meshoptixiq/meshoptixiq:0.27.0 meshq --help or docker run --rm -i ... meshq-mcp.
Cluster Mode
Multiple API pods share state via Redis. Set REDIS_URL to enable:
export API_KEY=secret
docker compose -f docker-compose.cluster.yml up --build
The cluster stack runs 3 API instances, 2 collection workers, a dispatcher, Redis, Neo4j, and nginx.
How device distribution works
meshq collect --dispatch → meshq:collect_queue (Redis List)
│ LPOP (atomic)
┌───────────────┼───────────────┐
worker-1 worker-2 worker-N
(SSH → parse → ingest)
Each device is claimed by exactly one worker per collection cycle via LPOP. Stale in-flight items are re-queued automatically via POST /collect/recover.
Queue Redis keys
| Key | Type | Purpose |
|---|---|---|
meshq:collect_queue |
List | Pending device JSON blobs |
meshq:collect_processing |
Hash | hostname → {device_json, started_at} |
meshq:collect_results |
Sorted Set | hostname → unix-ms of last success |
meshq:collect_lock |
String (NX TTL=60s) | One dispatcher at a time |
Kubernetes / Helm
helm install meshoptixiq oci://registry-1.docker.io/meshoptixiq/meshoptixiq-helm \
--version 0.27.0 \
--set api.key=secret \
--set license.key=your-key \
--set neo4j.uri=bolt://neo4j:7687 \
--set neo4j.password=secret \
--set redis.url=redis://redis:6379 \
--set collector.inventoryConfigMap=my-inventory
Chart version = application version; every pod runs meshoptixiq/meshoptixiq:<chart version> unless image.tag is set. The release shares one generated MESHQ_INSTALL_ID, so it counts as one licensed device (set license.installId and api.tokenSalt explicitly with helm template / GitOps).
The chart deploys:
- API Deployment + HPA (CPU 70%, min 2 / max 10)
- ingest / graph / collect (and optional AI) service Deployments — same image, license-validated at startup
- Collector worker Deployment (2 replicas,
meshq collect --worker) - Dispatcher CronJob (
concurrencyPolicy: Forbid) - SSE-safe nginx Ingress
- ConfigMap + Secret (no inline secrets in Deployment)
Database Backends
Neo4j (default)
Set GRAPH_BACKEND=neo4j. Uses MERGE for idempotent ingestion and native graph relationships.
PostgreSQL
Set GRAPH_BACKEND=postgres. Install the extra:
pip install "meshoptixiq-network-discovery[postgres]==0.27.0" # source checkout: pip install -e ".[postgres]"
The provider uses a ConnectionPool (psycopg-pool) with configurable min/max size:
POSTGRES_POOL_MIN=2 # default
POSTGRES_POOL_MAX=10 # default
Tables are auto-created from graph/postgres/schema.sql on first connection. All writes use INSERT ... ON CONFLICT upserts inside explicit transactions.
Supported Vendors
| Vendor | Key | Topology | Firewall Policies |
|---|---|---|---|
| Cisco IOS | cisco_ios |
Interfaces, ARP, CDP, VLANs, MACs | — |
| Juniper JunOS | juniper_junos |
Interfaces, ARP, LLDP | Security policies, address book |
| Arista EOS | arista_eos |
Interfaces, ARP, LLDP | — |
| Aruba OS | aruba_os |
Interfaces, ARP | — |
| Palo Alto PAN-OS | paloalto_panos |
— | Security policies, address objects, service objects |
| Fortinet FortiGate | fortinet |
— | Firewall policies, address objects |
| Cisco ASA | cisco_asa |
— | ACLs, object groups |
| Cisco FTD | cisco_ftd |
— | Policies |
| CheckPoint GAIA | checkpoint_gaia |
— | Policies |
Adding a New Vendor
- Create
vendors/<vendor_key>/commands.yamlwith CLI commands. - Create parser classes under
parsers/<vendor_key>/inheriting fromBaseParser. - Decorate with
@registerfor auto-registration. - Import in
parsers/<vendor_key>/__init__.py.
Data Model
Core Entities
| Model | Key Fields | Description |
|---|---|---|
DeviceModel |
hostname |
Network device |
InterfaceModel |
device_hostname + name |
Physical or logical interface |
IPAddressModel |
address + vrf |
IPv4/IPv6 address on an interface; vrf=None treated as global routing table |
SubnetModel |
network_address + vrf |
IP subnet; supports M&A multi-tenant isolation via vrf + tenant fields |
VLANModel |
device_hostname + vlan_id |
VLAN membership |
MACModel |
address |
Layer-2 MAC address |
EndpointModel |
mac_address + ip_address |
Host inferred from ARP + MAC tables |
Firewall Entities
| Model | Key Fields | Description |
|---|---|---|
FirewallRuleModel |
device_hostname + rule_id |
Security policy rule |
AddressObjectModel |
device_hostname + name |
Named address object |
ServiceObjectModel |
device_hostname + name |
Named service/port object |
Graph Relationships
| Relationship | From → To | Description |
|---|---|---|
HAS_INTERFACE |
Device → Interface | Device owns this interface |
HAS_IP |
Interface → IPAddress | IP assigned to interface |
IN_SUBNET |
IPAddress → Subnet | IP belongs to subnet |
TAGGED_IN |
Interface → VLAN | Interface participates in VLAN |
CONNECTED_TO |
Interface → Interface | Physical link (CDP/LLDP) |
LEARNED_ON |
MAC → Interface | MAC seen on this port |
USES_IP |
Endpoint → IPAddress | Endpoint uses this IP |
USES_MAC |
Endpoint → MAC | Endpoint uses this MAC |
HAS_FIREWALL_RULE |
Device → FirewallRule | Device owns this rule |
HAS_ADDRESS_OBJECT |
Device → AddressObject | Device owns this address object |
HAS_SERVICE_OBJECT |
Device → ServiceObject | Device owns this service object |
Enterprise Features
Install the enterprise extras:
pip install "meshoptixiq-network-discovery[enterprise]==0.27.0"
| Feature | Config |
|---|---|
| OIDC authentication | AUTH_MODE=oidc, OIDC_DISCOVERY_URL, OIDC_CLIENT_ID |
| RBAC policy | RBAC_POLICY_FILE or RBAC_POLICY (hot-reloaded every 30 s; instant cross-pod via Redis Pub/Sub) |
| Audit logging | AUDIT_LOG_ENABLED=true → Splunk, Elasticsearch, OpenSearch, webhook |
| Secrets management | SECRETS_PROVIDER=vault|aws|azure|gcp |
| Distributed tracing | Auto-detected: Datadog → New Relic → OTLP |
| NetBox sync | meshq sync --target netbox |
| SOAR webhooks | SOAR_RULES JSON + SOAR_WEBHOOK_URL |
| Ansible inventory | GET /inventory/ansible or meshq export --format ansible |
MCP Server
Exposes the network graph to AI assistants (Claude Desktop, Claude Code, any MCP client) over stdio. The server is bundled with this package as meshq-mcp:
pipx install --python python3.12 "meshoptixiq-network-discovery[mcp]==0.27.0"
It needs a running MeshOptixIQ API (Pro+ plan, resolved via GET /health/license) and refuses to start under the root/admin key — create a scoped Personal Access Token for it:
curl -X POST http://localhost:8000/admin/users -H "X-API-Key: $API_KEY" \
-H 'Content-Type: application/json' -d '{"username": "mcp", "role": "network"}'
curl -X POST http://localhost:8000/admin/users/mcp/tokens -H "X-API-Key: $API_KEY" \
-H 'Content-Type: application/json' -d '{"description": "Claude Desktop", "expires_in_days": 90}'
Claude Desktop config (claude_desktop_config.json; use the absolute path printed by which meshq-mcp):
{
"mcpServers": {
"meshoptixiq": {
"command": "/home/you/.local/bin/meshq-mcp",
"env": {
"MESHOPTIXIQ_API_URL": "http://localhost:8000",
"MESHOPTIXIQ_API_KEY": "mq_live_...",
"GRAPH_BACKEND": "neo4j",
"NEO4J_URI": "bolt://localhost:7687",
"NEO4J_USER": "neo4j",
"NEO4J_PASSWORD": "your-password"
}
}
}
}
Claude Code: claude mcp add meshoptixiq -e MESHOPTIXIQ_API_URL=http://localhost:8000 -e MESHOPTIXIQ_API_KEY=mq_live_... -e GRAPH_BACKEND=neo4j -e NEO4J_URI=bolt://localhost:7687 -e NEO4J_PASSWORD=your-password -- meshq-mcp
No local Python? The container image runs the same server: docker run --rm -i -e MESHOPTIXIQ_API_URL -e MESHOPTIXIQ_API_KEY -e GRAPH_BACKEND -e NEO4J_URI -e NEO4J_PASSWORD meshoptixiq/meshoptixiq:0.27.0 meshq-mcp.
A standalone, hosted-connector edition of the MeshOptixIQ MCP server is coming soon and will be listed in the Claude connectors directory; until then, use the bundled meshq-mcp.
Development
Running Tests
cd network_discovery
pytest tests/ -v
# 2,500+ tests — unit, integration, e2e
Neither psycopg/psycopg_pool nor a real database are required for the test suite — all database interactions are mocked.
Backend parity tests (real Neo4j / PostgreSQL)
The unit suite only exercises the InMemory backend. tests/integration/test_backend_parity.py
ingests the demo fixtures into live backends and runs every registry query through
provider.execute_query with the query file text (the same path as POST /queries/{name}/execute),
asserting that every query executes, that core queries (topology, blast radius, BGP, inventory,
firewall, hygiene) return rows with non-null key columns, and that row counts match InMemory.
It is skipped unless the backends are configured, and wipes the target databases:
docker run -d --rm --name parity-neo4j -p 127.0.0.1:17687:7687 -e NEO4J_AUTH=neo4j/paritypass123 neo4j:5
docker run -d --rm --name parity-pg -p 127.0.0.1:15432:5432 -e POSTGRES_PASSWORD=paritypass123 postgres:16
pip install 'psycopg[binary]' psycopg_pool
NEO4J_TEST_URI=bolt://localhost:17687 NEO4J_TEST_PASSWORD=paritypass123 \
POSTGRES_TEST_DSN=postgresql://postgres:paritypass123@localhost:15432/postgres \
pytest tests/integration -m integration -v
CI runs it in the backend-parity job of .github/workflows/docker-publish.yml with
neo4j:5 and postgres:16 service containers.
Project Structure
network_discovery/
├── network_discovery/
│ ├── api/
│ │ ├── main.py # FastAPI app, lifespan, middleware
│ │ ├── auth.py # API key + OIDC authentication
│ │ ├── cluster.py # Redis client factory
│ │ ├── collect_queue.py # Redis work queue (dispatch/pop/complete)
│ │ ├── dependencies.py # DB provider injection
│ │ ├── snapshot_store.py # Metric ring buffer (local + Redis)
│ │ └── routes/
│ │ ├── queries.py # /queries/* endpoints
│ │ ├── health.py # /health/* endpoints
│ │ ├── admin.py # /admin/* endpoints
│ │ ├── collect.py # /collect/* endpoints
│ │ ├── events.py # SSE /events endpoint
│ │ ├── history.py # /history/snapshots + /history/diff endpoints
│ │ ├── whatif.py # /graph/whatif endpoint (what-if impact analysis)
│ │ └── inventory.py # /inventory/ansible endpoint
│ ├── collectors/
│ │ └── netmiko_collector.py # SSH collection + --worker loop
│ ├── enterprise/
│ │ ├── auth/
│ │ │ ├── oidc.py # OIDC JWT validation
│ │ │ └── rbac.py # RBAC policy loader (mtime + pub/sub)
│ │ ├── audit/logger.py # Splunk/ES/webhook audit
│ │ ├── integrations/
│ │ │ ├── netbox.py # NetBox sync
│ │ │ └── soar.py # SOAR webhook dispatcher
│ │ ├── observability/tracing.py # DD/NR/OTLP tracing
│ │ └── secrets/resolver.py # Vault/AWS/Azure/GCP secrets
│ ├── graph/
│ │ ├── provider.py # Abstract GraphProvider interface
│ │ ├── neo4j_provider.py # Neo4j MERGE-based ingestion
│ │ └── postgres_provider.py # PostgreSQL pool + ON CONFLICT
│ ├── mcp/
│ │ └── server.py # MCP server (23 tools)
│ ├── parsers/
│ │ ├── base.py # BaseParser ABC
│ │ ├── registry.py # @register decorator + dispatch
│ │ ├── cisco_ios/
│ │ ├── juniper_junos/
│ │ ├── arista_eos/
│ │ ├── paloalto_panos/ # security policies, address/service objects
│ │ ├── fortinet/ # firewall policies, address objects
│ │ ├── cisco_asa/ # ACLs, object groups
│ │ ├── cisco_ftd/
│ │ └── checkpoint_gaia/
│ ├── normalization/
│ │ ├── models.py # Canonical Pydantic models (incl. firewall)
│ │ └── normalize.py # Dedup, subnet derivation, integrity
│ ├── queries/
│ │ ├── registry.yaml # 25 query definitions
│ │ └── v1/ # Cypher + SQL per query
│ ├── licensing/
│ │ ├── verifier.py # License client (Cython-compiled)
│ │ └── gates.py # Plan feature gating (Cython-compiled)
│ ├── local_api_client.py # get_license_status() — CLI + MCP license helper
│ ├── cli.py # meshq entry point
│ ├── ingest.py # Ingestion orchestrator
│ └── scanner.py # CIDR TCP scanner
├── tests/ # 2,500+ tests (unit + integration + e2e)
├── pyproject.toml
└── README.md
Code Quality
ruff check .
mypy network_discovery/
Licensing
Only the server side needs the license key: the API and, in split deployments, the ingest / graph / collect / AI services (network_discovery.*_app), which validate it at startup and re-validate periodically exactly like the API. Give every server process the same key and the same MESHQ_INSTALL_ID. The CLI and MCP resolve their plan tier by calling GET /health/license on the API (root key or a PAT), so a single key covers all tools.
# Set on the API server
export MESHOPTIXIQ_LICENSE_KEY=your-key
# Persistent file on the API server host
mkdir -p ~/.meshoptixiq
echo "your-key" > ~/.meshoptixiq/license.key
chmod 600 ~/.meshoptixiq/license.key
"Devices" counts API installs, not pods. Each install is identified by MESHQ_INSTALL_ID (set it to the same value on every replica) or a UUID persisted in <data dir>/install_id (MESHQ_DATA_DIR, default ./data — /app/data in the Docker image; keep it on a persistent volume). Plan changes made in the customer portal apply within MESHQ_LICENSE_REVALIDATE_SECONDS (default 3600) without a restart.
Point the CLI and MCP at the API server if it is not running locally:
export MESHOPTIXIQ_API_URL=http://api-server:8000
| Tier | Devices | Network Devices | API Queries | Firewall Queries | MCP Server |
|---|---|---|---|---|---|
| Community | 1 | 5 | Core (REST / web UI) | — | — |
| Starter | 1 | 100 | Core (REST, gRPC, GraphQL) | — | — |
| Pro / Trial | 5 | 750 | ✅ | ✅ | ✅ |
| Enterprise | Unlimited | Unlimited | ✅ | ✅ | ✅ |
Check the current plan and expiry at any time:
meshq license # shows plan, expiry, feature flags
curl http://localhost:8000/health/license
Metadata
Release files for meshoptixiq-network-discovery 0.27.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
Total release size: 29.3 MB
Release files / meshoptixiq_network_discovery-0.27.0-cp314-cp314-win_amd64.whl
| Download URL | meshoptixiq_network_discovery-0.27.0-cp314-cp314-win_amd64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.14 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
f2d7bdfadef21a6e704da1c8e817fc3b4b76ca0d4c57dd3d0a3655c06e1e61e2
|
|
BLAKE2b-256 checksum How to use checksums |
24cb8d29055984773ad20592e35464b4b4a23261df74aac37ed9ca0c7be8b432
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / meshoptixiq_network_discovery-0.27.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
| Download URL | meshoptixiq_network_discovery-0.27.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 2.0 MB |
| Tags | CPython 3.14 Linux glibc 2.17+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
1071de85fff831d9c4c16624ed9891b0fd58f7406ea5df315b38f058fe7866d2
|
|
BLAKE2b-256 checksum How to use checksums |
1afe6af18d06a6a510d803d1b2767bde9456288f84f4aae298b2b446d4cf9b9a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / meshoptixiq_network_discovery-0.27.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
| Download URL | meshoptixiq_network_discovery-0.27.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl |
|---|---|
| Size | 2.0 MB |
| Tags | CPython 3.14 Linux glibc 2.17+ ARM64 Linux glibc 2.28+ ARM64 |
|
SHA-256 checksum How to use checksums |
0022f349740906d8c039bf8631062966588ebc59aee542def26fa45b5b6a7d2b
|
|
BLAKE2b-256 checksum How to use checksums |
c67c4a041cfc07ac44f02c793bcc476dd42f9368da87e3732d984e96f9a80619
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / meshoptixiq_network_discovery-0.27.0-cp314-cp314-macosx_11_0_arm64.whl
| Download URL | meshoptixiq_network_discovery-0.27.0-cp314-cp314-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.14 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
e81fa9f7f3ed7ee4c1b62713dd616b75d76a4c9d23d06a2a4434b8c20332d44c
|
|
BLAKE2b-256 checksum How to use checksums |
56517bcbd0a0584de8ed5320ece846926f908955b2bba79aca6f05e821f9251e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / meshoptixiq_network_discovery-0.27.0-cp314-cp314-macosx_10_15_x86_64.whl
| Download URL | meshoptixiq_network_discovery-0.27.0-cp314-cp314-macosx_10_15_x86_64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.14 macOS 10.15+ x86-64 |
|
SHA-256 checksum How to use checksums |
1658954c4593a3ef3717c1e6e8c03441cc92b1b821fe7d18db7b40b71b0668a8
|
|
BLAKE2b-256 checksum How to use checksums |
befb3f2039ef6e47c18081aa3ad540d67683119e1809b6e26a1f993653311e86
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / meshoptixiq_network_discovery-0.27.0-cp313-cp313-win_amd64.whl
| Download URL | meshoptixiq_network_discovery-0.27.0-cp313-cp313-win_amd64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.13 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
ce09f7f6511c747689b5fcda8f1ffabc9fa67f53988e1473c3e34caf6952cb40
|
|
BLAKE2b-256 checksum How to use checksums |
987a05523e0fc72140db20fa0c690d74fab2cdd000f25ebcd0b5625cd40e6e54
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / meshoptixiq_network_discovery-0.27.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
| Download URL | meshoptixiq_network_discovery-0.27.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 2.0 MB |
| Tags | CPython 3.13 Linux glibc 2.17+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
caa93545f7c776fa66ec3b9e57a3f5b2c68ab703d9b9c02e797f37d6ddc68478
|
|
BLAKE2b-256 checksum How to use checksums |
93822e08a3f13844843d231ec9a71156883aa6672d9d2040333c721e88f24a12
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / meshoptixiq_network_discovery-0.27.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
| Download URL | meshoptixiq_network_discovery-0.27.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl |
|---|---|
| Size | 2.0 MB |
| Tags | CPython 3.13 Linux glibc 2.17+ ARM64 Linux glibc 2.28+ ARM64 |
|
SHA-256 checksum How to use checksums |
d0d6f1e08c1b47ef6bf0acea312ad6d7cc1946c3696cb3b4e28d0a5f8b77c64f
|
|
BLAKE2b-256 checksum How to use checksums |
4cba39c08569eb4281465594de14d003d679ff6702a0a092c6fc62620116c65c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / meshoptixiq_network_discovery-0.27.0-cp313-cp313-macosx_11_0_arm64.whl
| Download URL | meshoptixiq_network_discovery-0.27.0-cp313-cp313-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.13 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
a0b659e0d95ebedc08bbde6734229d58624ac2b5d4c3ab6707280f64e2e0a2a1
|
|
BLAKE2b-256 checksum How to use checksums |
55f202a7187a024ac66d30384562b4170cb26a0c9b9054ad1421ca408b6e04f9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / meshoptixiq_network_discovery-0.27.0-cp313-cp313-macosx_10_13_x86_64.whl
| Download URL | meshoptixiq_network_discovery-0.27.0-cp313-cp313-macosx_10_13_x86_64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.13 macOS 10.13+ x86-64 |
|
SHA-256 checksum How to use checksums |
4e4bd470b292105aab020aa76c710f15086adad5f1caf9f9711a75b3934316e6
|
|
BLAKE2b-256 checksum How to use checksums |
8abf2017d15e8f979948767696a9250d1d3c22c922701f4aee9001d36ac4d3ae
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / meshoptixiq_network_discovery-0.27.0-cp312-cp312-win_amd64.whl
| Download URL | meshoptixiq_network_discovery-0.27.0-cp312-cp312-win_amd64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.12 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
0548cfc82f6db168ba0a2d081aa759423226461fdfd816a0cd10013522d7c2a9
|
|
BLAKE2b-256 checksum How to use checksums |
d73e8d83ed797c593f5e676f54104e65b63f2d57483e0fae80fd37bf8d3a4636
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / meshoptixiq_network_discovery-0.27.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
| Download URL | meshoptixiq_network_discovery-0.27.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 2.0 MB |
| Tags | CPython 3.12 Linux glibc 2.17+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
af0b1e25980cb5fd861ad8bb997b4b57b6146c2f4c1d374f1260c76b1310d641
|
|
BLAKE2b-256 checksum How to use checksums |
1d653695d14a71d01b085e6ea188623f9c63be04fcbd0dda76df46ea8d6bfe78
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / meshoptixiq_network_discovery-0.27.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
| Download URL | meshoptixiq_network_discovery-0.27.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl |
|---|---|
| Size | 2.0 MB |
| Tags | CPython 3.12 Linux glibc 2.17+ ARM64 Linux glibc 2.28+ ARM64 |
|
SHA-256 checksum How to use checksums |
86cf6cab1ca7d76072a05280111326859b128019a13125e80e25d91276d5f4c4
|
|
BLAKE2b-256 checksum How to use checksums |
480943007fcfeb657044be12fcb2de9b82248665565bc876ab17f2837e3bd6eb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / meshoptixiq_network_discovery-0.27.0-cp312-cp312-macosx_11_0_arm64.whl
| Download URL | meshoptixiq_network_discovery-0.27.0-cp312-cp312-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.12 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
479464c010f3f9dc89980d7af21433020c2a7228bfcc05493262fd4bba69a4df
|
|
BLAKE2b-256 checksum How to use checksums |
784bb37bb1e57611dd30f4547b7b99147f50e0d543d4de9bb73b7023a38ce6a9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / meshoptixiq_network_discovery-0.27.0-cp312-cp312-macosx_10_13_x86_64.whl
| Download URL | meshoptixiq_network_discovery-0.27.0-cp312-cp312-macosx_10_13_x86_64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.12 macOS 10.13+ x86-64 |
|
SHA-256 checksum How to use checksums |
f653ecbb8c2040232f055f7d49481543d5058a7d4f9662cb09ac074818334245
|
|
BLAKE2b-256 checksum How to use checksums |
1bb6789396d69ceae80f8b7151cab84814a3c1b52139b5c30e6776fd0d4040f3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / meshoptixiq_network_discovery-0.27.0-cp311-cp311-win_amd64.whl
| Download URL | meshoptixiq_network_discovery-0.27.0-cp311-cp311-win_amd64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.11 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
1b96934e936ecb41043fa247d6f24ff2d483fe09e8929f2975f57d790590d528
|
|
BLAKE2b-256 checksum How to use checksums |
6572ada01284213953f09c9ace214c7a3b65ad78e38661da5a12d17c3f4c359a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / meshoptixiq_network_discovery-0.27.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
| Download URL | meshoptixiq_network_discovery-0.27.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 2.1 MB |
| Tags | CPython 3.11 Linux glibc 2.17+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
e98bdda559c184186e5ca33b4de75301259b9aa8b07739474ae1d313c950fcc2
|
|
BLAKE2b-256 checksum How to use checksums |
b5af32a7a14a244b99102dcb9f57f82adccd4d12634fcbe5dda450a33443165c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / meshoptixiq_network_discovery-0.27.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
| Download URL | meshoptixiq_network_discovery-0.27.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl |
|---|---|
| Size | 2.1 MB |
| Tags | CPython 3.11 Linux glibc 2.17+ ARM64 Linux glibc 2.28+ ARM64 |
|
SHA-256 checksum How to use checksums |
8e5a75d6092df0393c2b695f6efa057f64e9afcd5ddd726dea1e2920681d7527
|
|
BLAKE2b-256 checksum How to use checksums |
bed3b383c1438a3e61644c062b6e58427db0b939cfb98c84857af231edff7300
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / meshoptixiq_network_discovery-0.27.0-cp311-cp311-macosx_11_0_arm64.whl
| Download URL | meshoptixiq_network_discovery-0.27.0-cp311-cp311-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.11 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
cabbac66dda4575220b5a00f23abbd9416d1ef23e9884239ad53b93f65c238c1
|
|
BLAKE2b-256 checksum How to use checksums |
2828005c03c12a231d3a8de20e63672c97fd13ad81078d0a2db997496625dc23
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / meshoptixiq_network_discovery-0.27.0-cp311-cp311-macosx_10_9_x86_64.whl
| Download URL | meshoptixiq_network_discovery-0.27.0-cp311-cp311-macosx_10_9_x86_64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.11 macOS 10.9+ x86-64 |
|
SHA-256 checksum How to use checksums |
dc61cc1228a8725bc0807b9ac16732ec399a75b9680e868c48b77c5ff1459ce4
|
|
BLAKE2b-256 checksum How to use checksums |
63d090cfb7de1cffd0b3e7b30201db8afe3936c105b102e28b5302e46b7f5190
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|