Integration Guide

Connect MeshOptixIQ to AI assistants, SOAR platforms, Ansible, and NetBox. Covers license verification, the MCP server (134 tools, 6 resources, 6 prompts), SOAR webhook schema, RBAC policy, and Ansible dynamic inventory export.

1. License Key Setup

MeshOptixIQ resolves the license key from the following sources, in order:

  1. Environment variableMESHOPTIXIQ_LICENSE_KEY
  2. License file~/.meshoptixiq/license.key (one key per line; first non-empty line is used)

Environment Variable (Docker / CI)

export MESHOPTIXIQ_LICENSE_KEY="mq-pro-xxxxxxxx"

License File (Persistent Installations)

mkdir -p ~/.meshoptixiq
echo "mq-pro-xxxxxxxx" > ~/.meshoptixiq/license.key
chmod 600 ~/.meshoptixiq/license.key

Community Plan

The Community plan requires no license key. If neither the environment variable nor the license file is present, the application automatically operates in Community tier with a 1-device limit.

2. How Verification Works

License verification is multi-layered to ensure reliability in both online and offline environments.

  1. Key resolution — The verifier reads the license key from the environment variable or license file at startup.
  2. Server validation (24h cache) — On first use and every 24 hours, the verifier sends an HMAC-signed request to the licensing API. The server checks expiry, device binding, and plan status, then returns an RSA-signed response containing the plan and feature flags.
  3. Local state cache — The signed response is written to an encrypted local state file. On subsequent calls within the 24-hour window, the verifier reads from this cache without hitting the network.
  4. 72-hour grace period — If the server is unreachable during a scheduled check, the cached state remains valid for up to 72 hours. The application continues operating normally; a warning is printed on every command.
  5. Grace expiry — After 72 hours without a successful server contact, the application enters restricted mode. All Pro/Enterprise features are disabled until connectivity is restored. Community-tier features always remain available.

3. Reading Plan and Features

CLI

meshq license info
# Example output:
Plan:    Pro
Status:  Active
Expires: 2027-06-30 (491 days remaining)
Device:  a3f8c1d2e4b5... (1/5 registered)

Feature Flags:
  api_access          true
  firewall_queries    true
  whatif_simulation   true
  mcp_server          true
  redis_clustering    true
  rbac                true
  netbox_sync         true
  audit_logging       true
  oidc_sso            false
  soar_webhooks       false

Device Limits:
  max_managed_devices    5
  max_network_devices    750

Python API

The verifier module exposes two public functions:

from network_discovery.licensing import verifier

# Current plan name (str): "community" | "starter" | "pro" | "enterprise"
plan = verifier.get_plan()

# Feature flags and device limits (dict)
features = verifier.get_features()
print(features["max_network_devices"])  # e.g. 750 for Pro
print(features["api_access"])           # True / False

Note: verifier.py is compiled to a Cython .so module in production builds. Do not import it directly by path — use from network_discovery.licensing import verifier to ensure the compiled module takes precedence over the source file.

4. Feature Gating Pattern

Use check_feature() from gates.py to gate functionality at runtime. The function raises FeatureNotAvailableError when the current plan does not include the requested feature.

from network_discovery.licensing.gates import check_feature, FeatureNotAvailableError

plan = verifier.get_plan()

try:
    check_feature(plan, "api_access")
    # proceed with API operation
except FeatureNotAvailableError as exc:
    print(f"Upgrade required: {exc}")
    sys.exit(1)

Demo Mode Bypass

When MESHOPTIXIQ_DEMO_MODE=true is set, all feature gates return True regardless of plan. Always check the environment variable before calling check_feature() to avoid stale compiled-module behaviour:

import os

if not os.environ.get("MESHOPTIXIQ_DEMO_MODE"):
    check_feature(plan, "firewall_queries")

Feature Flag Reference

Flag Available on Description
api_access Pro, Enterprise REST Query API access (/queries/*)
firewall_queries Pro, Enterprise Firewall policy query set (5 queries)
whatif_simulation Pro, Enterprise What-if change simulation (POST /graph/whatif)
mcp_server Pro, Enterprise MCP server for AI assistant integration
redis_clustering Pro, Enterprise Horizontal Redis clustering support
rbac Pro, Enterprise Role-based access control
netbox_sync Pro, Enterprise Bidirectional NetBox synchronisation
audit_logging Pro, Enterprise Tamper-evident audit log to SIEM
oidc_sso Enterprise OIDC/SSO identity provider integration
soar_webhooks Enterprise Automated SOAR webhook dispatch

5. Device Fingerprinting

Each paid plan has a maximum number of agent installations (distinct MeshOptixIQ agent installations registered against this license key). The verifier generates a hardware fingerprint from a combination of:

This fingerprint is sent with every validation request. If the device is new and the plan limit has not been reached, it is registered automatically. If the limit is already reached, the validation returns a 409 Device Limit Exceeded error.

Device Limit Behaviour

The device count is visible in meshq license info (e.g., 1/5 registered). Administrators can deregister devices via the customer portal or by contacting support.

Container environments (Docker, Kubernetes) each count as a separate device if they have distinct MAC addresses. Use the MESHOPTIXIQ_LICENSE_KEY environment variable to share one key across replicas that share the same underlying host.

6. Offline / Grace Period

The verifier's behaviour when it cannot reach the licensing server:

Diagnosing Grace Period Issues

meshq license info

# If in grace period:
# Status:  WARNING — Offline grace period active (48h 12m remaining)
# Last validated: 2026-02-22 09:14 UTC (23h 48m ago)

# Trigger an immediate validation check:
meshq license info --refresh

Common causes of unexpected grace period activation:

7. Troubleshooting

Error / Symptom Cause Resolution
LicenseNotFoundError The license key does not exist in the database, or has been deleted after revocation TTL expiry. Verify the key is correct. Log in to the portal to confirm the license is active. Contact support if needed.
LicenseExpiredError The expiration_date has passed and the key has not been renewed. Renew the license via the portal. The application will pick up the new expiry on the next validation cycle.
Device limit exceeded (409) The maximum number of devices for this plan is already registered. Deregister unused devices in the portal under License → Devices, or upgrade to a higher plan.
Clock skew / HMAC failure The system clock is more than 5 minutes from UTC. The server rejects the signed request. Synchronise the system clock with an NTP server: timedatectl set-ntp true (Linux).
Grace period expires unexpectedly Network outage, firewall, DNS, or proxy issue preventing server contact. Test connectivity: curl -I https://api.meshoptixiq.com/health. Check outbound firewall rules for port 443.
FeatureNotAvailableError in code Calling a feature that is not included in the current plan. Check the feature flag table above. Upgrade to Pro or Enterprise, or guard the call with a plan check before invoking check_feature().
LicenseInvalidError: "invalid" Key was revoked or the server returned a 403. Often seen with test keys that were removed from the database. Issue a new key from the admin portal. Do not reuse revoked keys.

8. MCP Server — AI Assistant Integration Pro+

The MCP server exposes 134 tools (from 32 modules), 6 resources, and 6 prompts to any MCP-compatible AI client (Claude Desktop, Cursor, Copilot, etc.). The license is read from the local API — the MCP process itself does not need a license key.

# Install with MCP extras
pip install -e "network_discovery[mcp]"

# Start the MCP server
meshq-mcp   # or: python -m network_discovery.mcp.server

# Configure in Claude Desktop / Cursor / any MCP-compatible client:
# Command: meshq-mcp
# Env:     MESHOPTIXIQ_API_URL=http://localhost:8000
#          MESHOPTIXIQ_API_KEY=your-api-key

Tool Categories (134 tools total)

Category Tools Count Gate
Topologydevice_neighbors, interface_neighbors, topology_edges, topology_neighborhood, lldp_neighbors5api_access (Pro+)
Endpointslocate_endpoint_by_ip, locate_endpoint_by_mac, endpoints_on_interface3api_access (Pro+)
Blast Radiusblast_radius_interface, blast_radius_device, blast_radius_vlan, blast_radius_subnet4api_access (Pro+)
Addressingips_in_subnet, subnets_on_device, orphaned_ips3api_access (Pro+)
Hygienedevices_without_neighbors, interfaces_without_ips, endpoints_without_location, devices_missing_os_version, devices_missing_hostname, interfaces_no_description, duplicate_ip_addresses7api_access (Pro+)
Inventoryall_devices, summary_stats, update_device_metadata3api_access / netbox_sync
Firewallfirewall_rules_by_device, firewall_rules_by_zone_pair, path_analysis, all_firewall_devices, deny_rules_summary5firewall_queries (Pro+)
Routingbgp_peers, bgp_topology, bgp_peers_down3bgp_intelligence (Pro+)
InfiniBand / GPUib_topology, ib_ports_down, dcgm_gpu_health3nccl_visualization (Enterprise)
Metricsinterface_metrics, link_utilization2server_metrics (Pro+)
NCCL / Trainingnccl_jobs, nccl_flows_by_job, nccl_top_talkers3nccl_visualization (Enterprise)
Alertsmeshq_alerts_list, meshq_alerts_acknowledge, meshq_alerts_rules_get, meshq_alerts_stream4alert_rules (Pro+)
Compliancemeshq_compliance_run, meshq_compliance_results, meshq_compliance_export3compliance_reporting (Enterprise)
Flowsmeshq_flows_top_talkers, meshq_flows_conversations, meshq_flows_status, meshq_flows_interfaces4flow_analytics (Enterprise)
Kubernetesmeshq_k8s_pods, meshq_k8s_services, meshq_k8s_nodes3k8s_observability (Enterprise)
Syntheticmeshq_synthetic_probes, meshq_synthetic_results2synthetic_monitoring (Enterprise)
Vulnerabilitiesmeshq_vuln_scan, meshq_vuln_by_device2vulnerability_correlation (Enterprise)

MCP Resources (6 resources)

URIDescription
network://inventory/summaryAggregate device/interface/endpoint counts
network://topology/edgesAll device-to-device connection edges
network://health/platformPlatform health status (API, graph, Redis, license)
network://firewall/devicesAll devices with collected firewall rules
network://alerts/activeCurrently fired (unacknowledged) alerts
network://compliance/latestMost recent compliance scan results

MCP Prompts (6 prompts)

Prompt NameUse Case
network_incident_triageGuide Claude through structured network incident response
network_change_impact_assessmentAssess blast radius before a planned maintenance window
network_endpoint_huntLocate a specific endpoint by IP or MAC across the network
network_addressing_auditAudit IP addressing for orphaned IPs and subnet inconsistencies
network_hygiene_reportGenerate a network hygiene report (missing IPs, orphaned devices)
network_firewall_auditAudit firewall rules for deny-heavy policies and zone exposure

Example: Acknowledge Alert from Claude Desktop

# In Claude Desktop, after connecting meshq-mcp:
# "Acknowledge alert ID alert-42"
# Claude calls: meshq_alerts_acknowledge(alert_id="alert-42")
# Returns: {"status": "acknowledged", "alert_id": "alert-42"}

Plan Requirement

Requires the mcp_server plan flag (Pro or Enterprise). The MCP server starts but returns license errors on tool calls if the flag is absent.

9. NetBox Bidirectional Sync Pro+

Sync device metadata between MeshOptixIQ and NetBox in either direction. Push enriches NetBox with discovered device attributes; pull stamps Device graph nodes with NetBox site, tenant, and rack fields.

# Environment variables
NETBOX_URL=https://netbox.corp.local
NETBOX_TOKEN=your-netbox-api-token
NETBOX_SYNC_DIRECTION=both   # push | pull | both

# Trigger via CLI
meshq sync --target netbox --direction pull --dry-run

# Install integrations extra (adds httpx)
pip install 'meshoptixiq-network-discovery[integrations]'

10. Ansible Dynamic Inventory Export Pro+

Expose the discovered device graph as an Ansible dynamic inventory — devices are grouped by vendor, role, and firewall presence.

# HTTP endpoint
curl -H "X-API-Key: your-key" http://localhost:8000/inventory/ansible

# INI format (legacy Ansible)
curl -H "X-API-Key: your-key" "http://localhost:8000/inventory/ansible?format=ini"

# CLI export to file
meshq export --format ansible --output /etc/ansible/meshoptixiq.json

11. SOAR Webhook Dispatch Enterprise

Automatically dispatch webhook events to your SOAR platform after qualifying query audit events. Conditions are evaluated without eval() — safe for production use.

# Environment variables
SOAR_WEBHOOK_URL=https://soar.corp.local/api/events
SOAR_WEBHOOK_TOKEN=your-bearer-token
SOAR_RULES='[{"condition":"row_count > 0","query":"deny_rules_summary","label":"deny-rules-alert"}]'

# Supported conditions: row_count > N  |  status >= N  |  elapsed_ms > N
# Webhooks fire automatically after qualifying query audit events — no polling required

SOAR Webhook Payload Format

When a SOAR rule triggers, the webhook receives a POST with this JSON payload:

{
  "rule_name": "deny-rules-alert",
  "query": "deny_rules_summary",
  "severity": "warning",
  "triggered_at": "2026-03-06T14:22:00Z",
  "matched_rows": 12,
  "sample_data": [
    {
      "hostname": "fw-corp-01",
      "rule_name": "deny-all-outbound",
      "action": "deny",
      "src_zone": "trust",
      "dst_zone": "untrust"
    }
  ]
}

12. Microsoft Entra ID SSO + RBAC Enterprise

Configure OIDC-based SSO from Microsoft Entra ID (formerly Azure AD). The JWKS cache is refreshed every hour and automatically rotates on unknown kid. Use AUTH_MODE=both to accept both OIDC JWTs and the API_KEY simultaneously.

# Environment variables
ENTRA_TENANT_ID=your-tenant-id
ENTRA_CLIENT_ID=your-client-id
AUTH_MODE=both   # accept both OIDC JWTs and API_KEY

# RBAC policy file (hot-reloaded every 30 seconds)
RBAC_POLICY_FILE=/etc/meshoptixiq/rbac.yaml

# Policy format: roles → glob patterns over query names
# roles:
#   network:
#     - "topology_*"
#     - "blast_radius_*"
#   security:
#     - "firewall_*"
#     - "path_analysis"

# Force reload without restart (publishes to all pods via Redis Pub/Sub)
curl -X POST -H "X-API-Key: your-key" http://localhost:8000/admin/rbac/reload

RBAC Policy File Format

# /etc/meshoptixiq/rbac.yaml
# Hot-reloaded every 30 seconds — no restart required
roles:
  network:
    resources:
      - "topology_*"
      - "blast_radius_*"
      - "endpoints_*"
      - "locate_endpoint_*"
    verbs: ["execute"]
  security:
    resources:
      - "firewall_*"
      - "path_analysis"
      - "deny_rules_summary"
    verbs: ["execute"]
  readonly:
    resources: ["*"]
    verbs: ["list"]

groups:
  "Network-Ops":
    roles: [network]
  "Security-Team":
    roles: [security]
  "Management":
    roles: [readonly]

13. GitOps: Blast Radius Pre-Merge Checks

Problem: A Terraform pull request that removes a core aggregation switch silently breaks 200 downstream endpoints. Your CI pipeline runs unit tests and Terraform plan — neither knows about physical network dependencies. Engineers merge confidently, then spend two hours tracing the outage.

Solution: Call the MeshOptixIQ Blast Radius API as a blocking step in your CI pipeline. If the simulated impact exceeds your threshold, the merge is blocked before it reaches production.

GitHub Actions Example

Add this job to your Terraform PR workflow. It posts a chaos simulation to MeshOptixIQ and fails the build if blast_radius_pct exceeds 25%:

name: Blast Radius Gate

on:
  pull_request:
    paths:
      - 'terraform/**'

jobs:
  blast-radius-check:
    runs-on: ubuntu-latest
    steps:
      - name: Run blast radius simulation
        id: blast
        run: |
          RESPONSE=$(curl -s -X POST \
            -H "X-API-Key: ${{ secrets.MESHQ_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d '{
              "scenario_type": "device_failure",
              "target": "${{ github.event.pull_request.title }}",
              "depth": 4
            }' \
            "${{ secrets.MESHQ_URL }}/graph/chaos-simulate")

          PCT=$(echo "$RESPONSE" | jq -r '.blast_radius_pct')
          SEVERITY=$(echo "$RESPONSE" | jq -r '.severity')
          AFFECTED=$(echo "$RESPONSE" | jq -r '.affected_devices | length')

          echo "blast_radius_pct=$PCT" >> "$GITHUB_OUTPUT"
          echo "severity=$SEVERITY" >> "$GITHUB_OUTPUT"
          echo "affected_devices=$AFFECTED" >> "$GITHUB_OUTPUT"
          echo "::notice::Blast radius: $PCT% ($AFFECTED devices affected, severity=$SEVERITY)"

      - name: Gate on blast radius
        if: ${{ steps.blast.outputs.blast_radius_pct | float > 25 }}
        run: |
          echo "::error::Blast radius ${{ steps.blast.outputs.blast_radius_pct }}% exceeds 25% threshold."
          echo "Severity: ${{ steps.blast.outputs.severity }}"
          echo "Affected devices: ${{ steps.blast.outputs.affected_devices }}"
          exit 1

POST /graph/chaos-simulate — Request & Response Fields

FieldDirectionTypeDescription
scenario_typerequeststringdevice_failure | link_failure | segment_failure
targetrequeststringDevice hostname, interface name, or subnet CIDR to simulate
depthrequestintBFS hop depth for blast radius traversal (default: 3, max: 6)
blast_radius_pctresponsefloatPercentage of total managed devices affected (0.0–100.0)
affected_devicesresponsearrayArray of device hostnames in the blast radius
severityresponsestringlow | medium | high | critical
impact_scoreresponseintWeighted impact (0–100) accounting for device criticality

Rate limit: POST /graph/chaos-simulate is limited to 5 requests per minute. In monorepos with many parallel Terraform plan jobs, cache the simulation result in a build artifact and share it across jobs rather than calling the API once per job.

Ansible Pre-Task Example

Gate any Ansible playbook that modifies network devices:

- name: Blast radius pre-check
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Simulate device failure
      ansible.builtin.uri:
        url: "{{ meshq_url }}/graph/chaos-simulate"
        method: POST
        headers:
          X-API-Key: "{{ meshq_api_key }}"
          Content-Type: "application/json"
        body_format: json
        body:
          scenario_type: device_failure
          target: "{{ target_device }}"
          depth: 4
      register: blast_result

    - name: Abort if blast radius too high
      ansible.builtin.fail:
        msg: >
          Blast radius {{ blast_result.json.blast_radius_pct }}% exceeds 30% threshold.
          Affected: {{ blast_result.json.affected_devices | length }} devices.
      when: blast_result.json.blast_radius_pct | float > 30

Using severity as a gate

The severity field (low / medium / high / critical) provides a qualitative threshold that works without setting a numeric percentage. Gate on severity != "low" to block any change with medium or higher blast radius, regardless of the raw percentage.