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:
- Environment variable —
MESHOPTIXIQ_LICENSE_KEY - 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.
- Key resolution — The verifier reads the license key from the environment variable or license file at startup.
- 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.
- 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.
- 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.
- 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:
- MAC address of the primary network interface
- OS hostname
- CPU architecture and OS platform
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:
- 0–72 hours offline: Application continues normally. A warning is printed on every
meshqcommand showing hours remaining in the grace period. - 72+ hours offline: The application enters restricted mode. Pro/Enterprise features are disabled. Community-tier functionality (basic discovery, topology) remains available.
- Reconnection: Validation resumes automatically on the next scheduled check (every 24 hours) or on application restart. Full functionality is restored immediately after a successful check.
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:
- Firewall rules blocking outbound HTTPS to
api.meshoptixiq.com(port 443) - DNS resolution failures in air-gapped or proxied environments
- System clock skew greater than 5 minutes (HMAC timestamp validation fails)
- Proxy requiring authentication that the verifier does not send
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 |
|---|---|---|---|
| Topology | device_neighbors, interface_neighbors, topology_edges, topology_neighborhood, lldp_neighbors | 5 | api_access (Pro+) |
| Endpoints | locate_endpoint_by_ip, locate_endpoint_by_mac, endpoints_on_interface | 3 | api_access (Pro+) |
| Blast Radius | blast_radius_interface, blast_radius_device, blast_radius_vlan, blast_radius_subnet | 4 | api_access (Pro+) |
| Addressing | ips_in_subnet, subnets_on_device, orphaned_ips | 3 | api_access (Pro+) |
| Hygiene | devices_without_neighbors, interfaces_without_ips, endpoints_without_location, devices_missing_os_version, devices_missing_hostname, interfaces_no_description, duplicate_ip_addresses | 7 | api_access (Pro+) |
| Inventory | all_devices, summary_stats, update_device_metadata | 3 | api_access / netbox_sync |
| Firewall | firewall_rules_by_device, firewall_rules_by_zone_pair, path_analysis, all_firewall_devices, deny_rules_summary | 5 | firewall_queries (Pro+) |
| Routing | bgp_peers, bgp_topology, bgp_peers_down | 3 | bgp_intelligence (Pro+) |
| InfiniBand / GPU | ib_topology, ib_ports_down, dcgm_gpu_health | 3 | nccl_visualization (Enterprise) |
| Metrics | interface_metrics, link_utilization | 2 | server_metrics (Pro+) |
| NCCL / Training | nccl_jobs, nccl_flows_by_job, nccl_top_talkers | 3 | nccl_visualization (Enterprise) |
| Alerts | meshq_alerts_list, meshq_alerts_acknowledge, meshq_alerts_rules_get, meshq_alerts_stream | 4 | alert_rules (Pro+) |
| Compliance | meshq_compliance_run, meshq_compliance_results, meshq_compliance_export | 3 | compliance_reporting (Enterprise) |
| Flows | meshq_flows_top_talkers, meshq_flows_conversations, meshq_flows_status, meshq_flows_interfaces | 4 | flow_analytics (Enterprise) |
| Kubernetes | meshq_k8s_pods, meshq_k8s_services, meshq_k8s_nodes | 3 | k8s_observability (Enterprise) |
| Synthetic | meshq_synthetic_probes, meshq_synthetic_results | 2 | synthetic_monitoring (Enterprise) |
| Vulnerabilities | meshq_vuln_scan, meshq_vuln_by_device | 2 | vulnerability_correlation (Enterprise) |
MCP Resources (6 resources)
| URI | Description |
|---|---|
| network://inventory/summary | Aggregate device/interface/endpoint counts |
| network://topology/edges | All device-to-device connection edges |
| network://health/platform | Platform health status (API, graph, Redis, license) |
| network://firewall/devices | All devices with collected firewall rules |
| network://alerts/active | Currently fired (unacknowledged) alerts |
| network://compliance/latest | Most recent compliance scan results |
MCP Prompts (6 prompts)
| Prompt Name | Use Case |
|---|---|
| network_incident_triage | Guide Claude through structured network incident response |
| network_change_impact_assessment | Assess blast radius before a planned maintenance window |
| network_endpoint_hunt | Locate a specific endpoint by IP or MAC across the network |
| network_addressing_audit | Audit IP addressing for orphaned IPs and subnet inconsistencies |
| network_hygiene_report | Generate a network hygiene report (missing IPs, orphaned devices) |
| network_firewall_audit | Audit 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
| Field | Direction | Type | Description |
|---|---|---|---|
scenario_type | request | string | device_failure | link_failure | segment_failure |
target | request | string | Device hostname, interface name, or subnet CIDR to simulate |
depth | request | int | BFS hop depth for blast radius traversal (default: 3, max: 6) |
blast_radius_pct | response | float | Percentage of total managed devices affected (0.0–100.0) |
affected_devices | response | array | Array of device hostnames in the blast radius |
severity | response | string | low | medium | high | critical |
impact_score | response | int | Weighted 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.