Skip to the content.

Low-token tool router

src/hpe_networking_mcp/mcp_servers/tool_router.py is the recommended MCP entrypoint. Instead of exposing every backend tool to the client up front, it exposes a small discovery/dispatch surface and loads backend tools on demand.

Flowchart titled Router discovery and safety flow. A task request goes to find_tool, which classifies the selected tool's capability. Read capability dispatches through invoke_read_tool; diagnostic capability dispatches through invoke_tool; both go straight to backend dispatch. Write and destructive capability first require a dry-run preview, then a gate check asking whether the tool is enabled by the global and platform gates. A no answer returns a blocked response with the next action; a yes, reviewed answer dispatches to the backend.
Every call starts at find_tool. Read and diagnostic capabilities dispatch immediately; write and destructive capabilities must clear the global/platform write gate before the backend is ever reached.

Daily workflow

1
<p>Ask <code>find_tool</code> for the action you need. Each result names the exact backend tool plus its normalized <code>capability</code> and <code>recommended_dispatcher</code>.</p>
2
<p>If <code>capability</code> is <code>read</code>, call <code>invoke_read_tool</code>. If it is <code>diagnostic</code>, <code>write</code>, or <code>destructive</code>, call <code>invoke_tool</code>.</p>
3
<p>For a <code>write</code>/<code>destructive</code> tool, pass <code>dry_run=true</code> first when the schema supports it, and only re-run with a real write after explicit user intent.</p>
find_tool("show active critical alerts")
invoke_read_tool("list_active_alerts", {"severity": "CRITICAL"})

See example-prompts.md for complete scenario cards with router calls, expected result shapes, and safety classifications.

Router tools

read diagnostic write destructive are the four normalized capabilities find_tool reports, used throughout this page.

Tool Safety Use
find_tool read Search the enabled backend catalog
invoke_read_tool read Dispatch only backend tools annotated read-only
invoke_read_tool_batch read Bounded, ordered batch of read-only calls in one round trip (outside minimal mode)
invoke_tool destructive Generic dispatcher for diagnostic/write/destructive tools
Convenience wrappers mixed Available only outside minimal mode
plan_tool_workflow read Deterministic, catalog-backed dependency/order planner (outside minimal mode)
plan_reconciliation_schedule read Plan-only recurring reconciliation schedule builder (outside minimal mode)
evaluate_compliance_policy read Bounded, declarative compliance-policy evaluator over caller-supplied observations (outside minimal mode)

invoke_tool is annotated destructive as a router-dispatch safety label, not a claim that every tool behind it is destructive – see Why invoke_tool is destructive.

Discover with find_tool

find_tool(query, top_k=5, include_schema=False, platform=None, server=None, capability=None, origin=None, operation_id=None) combines semantic search with a tool-name keyword match and returns compact, deduplicated results. Set include_schema=true only when you need the full JSON schema for one selected tool – it is omitted by default to keep discovery responses small.

find_tool("list Aruba Central sites")
[
  {
    "name": "list_sites",
    "server": "central-monitoring",
    "platform": "central",
    "capability": "read",
    "recommended_dispatcher": "invoke_read_tool",
    "requires_write_enablement": false,
    "currently_enabled": true,
    "supports_dry_run": false,
    "supports_confirm": false,
    "requires_confirmation": false,
    "read_only": true,
    "destructive": false,
    "idempotent": true
  }
]

recommended_dispatcher is the field to branch on: invoke_read_tool for read, invoke_tool for everything else.

Filter discovery with platform, exact server, or normalized capability (read, diagnostic, write, destructive). Filters apply equally to keyword and semantic matches:

find_tool("configuration", platform="central", capability="write")
find_tool("health check", server="mist-core", capability="diagnostic")

Write/destructive results also carry an execution_contract – the same compact shape attached to router-dispatched write responses (see Safety gates):

{
  "platform": "central",
  "capability": "write",
  "gate": {"env_var": "HPE_MCP_CENTRAL_WRITES", "state": "enabled", "source": "platform_default"},
  "dry_run": {"supported": true, "state": "default_preview"},
  "confirm": {"supported": true, "required": true},
  "idempotent": true,
  "next_action": "Call invoke_tool with dry_run=true to preview the change."
}

dry_run.state becomes preview or execution_requested at dispatch time, once the schema and call arguments make that state knowable. Invalid gate values fail closed. Read and diagnostic responses are never decorated with write metadata.

If the semantic tool index is unavailable and no keyword fallback matches, find_tool returns a compact error with a rebuild hint instead of an empty success-shaped result.

Dispatch reads with invoke_read_tool

invoke_read_tool(name, arguments=None, cursor=None) refuses any tool that is not annotated read-only, before the backend is ever called.

invoke_read_tool("list_active_alerts", {"severity": "CRITICAL"})

Scope discovery uses Central’s official v1 scope-management endpoints and returns normalized global, site, and device-group records:

invoke_read_tool("list_scopes", {"limit": 100, "offset": 0})

If one scope source is unavailable, the usable records are preserved with a bounded warnings list. If all three sources fail, the tool returns an explicit failed result rather than an empty success.

Calling a write tool through invoke_read_tool is refused, not silently downgraded:

{
  "error": "Tool 'set_site' is not read-only. Use invoke_tool only after explicit user intent for write/destructive actions.",
  "tool": "set_site",
  "status": "blocked"
}

Response budgets and continuation metadata

Every result dispatched through invoke_tool/invoke_read_tool passes through a deterministic response-bounding step. A response already inside budget is returned byte-for-byte unchanged. When clipping is required, the response gains a _pagination block plus a _response_bounds marker:

{
  "items": ["...bounded..."],
  "_pagination": {"limit": 25, "offset": 0, "truncated": true, "total": 400},
  "_response_bounds": {"truncated": true, "reason": "item_budget", "item_limit": 25, "byte_limit": 200000}
}

reason is item_budget, byte_budget, or item_budget+byte_budget. If a result has nothing sliceable and still exceeds the byte budget, the response falls back to a bounded text preview instead of an over-budget payload. Configure the two budgets with HPE_MCP_ROUTER_RESPONSE_MAX_ITEMS (default 200, range 1-200) and HPE_MCP_ROUTER_RESPONSE_MAX_BYTES (default 200,000, minimum 1024); invalid or missing values fall back to the defaults rather than raising.

Continuation cursors (invoke_read_tool only). When a clipped response has more data remaining, it also gains an opaque next_cursor string and a resumable: true flag inside _response_bounds. Pass that value back as the cursor argument on a repeated call to the same tool with the same arguments to fetch the next page:

{
  "items": ["...page 1..."],
  "_pagination": {"limit": 40, "offset": 0, "truncated": true, "total": 100},
  "_response_bounds": {"truncated": true, "reason": "item_budget", "item_limit": 40, "byte_limit": 200000, "resumable": true},
  "next_cursor": "eyJ2IjoxLCJl...",
  "cursor_expires_in_seconds": 900
}
invoke_read_tool("list_devices", {"site_id": "SITE_ID"}, cursor="eyJ2IjoxLCJl...")

Dispatch diagnostics and writes with invoke_tool

invoke_tool(name, arguments=None) dispatches through the owning backend’s MCPServer tool manager, so arguments get MCPServer validation/coercion and the router’s request Context is forwarded – this is what lets async, ctx-requiring destructive ops tools (reboot_device, port_bounce, poe_bounce, disconnect_client) reach their confirmation elicitation. Diagnostic tools also go through invoke_tool because they are intentionally not annotated read-only.

Diagnostic call:

invoke_tool("cx_ping", {"serial_number": "CN12ABC456", "destination": "10.0.0.1"})

Dry-run write preview – no state changes yet:

invoke_tool("build_underlay_ssid", {"ssid_name": "guest-wifi", "scope_id": "SCOPE_ID", "dry_run": true})

Only re-run with dry_run=false after the user has reviewed the preview and explicitly asked for the change. Some destructive ops tools instead confirm interactively through MCP elicitation on ctx and take no confirm argument at all (reboot_device, port_bounce, poe_bounce, disconnect_client); others, like aos8_apply_migration_run, take an explicit confirm: bool argument alongside dry_run. Check the tool’s own schema (find_tool(..., include_schema=true)) rather than assuming either shape.

SSID, role, and profile scope-map workflows validate every scope ID before their first write. Invalid, nonnumeric, or overlong IDs fail the preflight instead of leaving a partially created resource.

Safety gates

Aggregate access profiles

HPE_MCP_ACCESS_PROFILE provides one end-to-end switch for the router, direct-mode registration, standalone backends, stdio, and streamable HTTP:

Profile Behavior
custom Compatibility default; preserves the existing Central, GLP, optional-product, and per-platform gates
safe-read-only Hides writes from router discovery/direct registration and blocks every write/destructive dispatch while leaving reads and diagnostics available
full-read-write Enables ordinary writes for every loaded platform

Full read/write mode changes availability only. It never bypasses tool-level dry_run, confirm, MCP elicitation, capability annotations, or dedicated guards such as HPE_MCP_AOS8_ROLLBACK_WRITES. Invalid profile names and contradictory settings refuse server startup; use custom for intentionally mixed platform access.

Global read-only kill switch

Set HPE_MCP_READONLY=1 for a server-wide write kill switch under custom or safe-read-only (full-read-write rejects that contradictory setting). Every write/destructive tool on every backend is hidden from find_tool, skipped in direct-mode registration, and refused at dispatch – before the backend is ever reached:

{
  "error": "Tool 'build_underlay_ssid' is disabled because HPE_MCP_READONLY is set. Unset HPE_MCP_READONLY to allow write/destructive tools.",
  "tool": "build_underlay_ssid",
  "status": "blocked"
}

read and diagnostic tools are unaffected, so troubleshooting flows keep working. The switch is enforced identically for a backend run standalone (python -m hpe_networking_mcp.mcp_servers.<x>). A platform whose own gate is enabled is still fully read-only while HPE_MCP_READONLY is set.

Per-platform write gates

Under custom, Central defaults to writes enabled (HPE_MCP_CENTRAL_WRITES=0 opts out), while GLP defaults to writes disabled (HPE_MCP_GLP_V2BETA1_WRITES=1 opts in). A blocked GLP write looks like this:

{
  "error": "Tool 'invite_glp_user' is disabled because glp writes are not enabled. Set HPE_MCP_GLP_V2BETA1_WRITES=1 to allow glp write workflows.",
  "tool": "invite_glp_user",
  "status": "blocked",
  "platform": "glp",
  "execution_contract": {
    "platform": "glp",
    "capability": "write",
    "gate": {"env_var": "HPE_MCP_GLP_V2BETA1_WRITES", "state": "disabled", "source": "platform_default"},
    "dry_run": {"supported": false, "state": "unsupported"},
    "confirm": {"supported": false, "required": false},
    "idempotent": true,
    "next_action": "Set HPE_MCP_GLP_V2BETA1_WRITES=1, then retry only after explicit user approval."
  }
}

Unrecognized or contradictory manual gate values fail closed and refuse server startup.

Optional product write access

The optional starters (clearpass, mist, apstra, aos8, edgeconnect, uxi, axis, design) share HPE_MCP_PRODUCT_ACCESS, which defaults to read-only. That hides optional write tools from find_tool and blocks direct dispatch through invoke_tool. Set HPE_MCP_ACCESS_PROFILE=full-read-write to open every loaded platform, or keep custom and set HPE_MCP_PRODUCT_ACCESS=read-write for optional-product lab workflows. Those write tools still default to dry_run=True. Use HPE_MCP_<PLATFORM>_WRITES=1 (e.g. HPE_MCP_AXIS_WRITES=1) for a narrower per-platform override instead of opening every optional write at once. See optional-products.md for the full matrix.

HPE_MCP_ROUTER_MODE=minimal
HPE_MCP_TOOLSETS=central,glp,rag

This keeps the tool list small while still covering the common Central, GLP, and RAG workflows. If HPE_MCP_ROUTER_MODE is omitted, the router uses default mode and includes convenience wrappers – keep minimal in MCP client configs when token surface matters. Each convenience wrapper (list_sites, find_device, ask_docs, …) fans into exactly one backend call and draws exactly one rate-limit token for it – the same token a direct invoke_read_tool would draw, never a second token for the wrapper’s own MCP hop.

Profile Client-visible / indexed tools
Minimal router 3 client-visible tools
Default router 18 client-visible tools1
Platform API backend index 6,703 tools
Complete backend index (platform APIs + design-core + interop-core) 6,715 tools
Direct-all router 6,722 client-visible tools

The complete catalog spans nine platform surfaces plus RAG, design-core, and interop-core: nine generated manifests contain 6,144 reproducible operations (6,127 register as active generated tools; 576 platform curated tools bring the platform API backend total to 6,703). Adding the two credential-free local backends (design-core: 7, interop-core: 5) yields the complete 6,715-tool registered backend catalog. Minimal mode does not expose that schema surface to the MCP client – it searches the catalog on demand.

Generated requests preserve OpenAPI query-array serialization metadata: explicit style: form plus explode: false arrays are sent as comma-separated values, while default and exploded arrays retain repeated-key encoding. Generated schemas also hide authentication, content negotiation, HTTP framing, host/routing, and proxy-derived identity headers. Executors and HTTP clients own those values; API-level business headers such as If-Match, Idempotency-Key, Tenant-Acid, and Hpe-workspace-id remain available. Generated safe-method and idempotent PUT retries parse both numeric and HTTP-date Retry-After hints. Hints within the five-second retry budget are honored; longer hints return the rate-limit/transient response without retrying early or blocking an MCP call for the full server window.

Toolset Enables
central Config, monitoring, NAC, ops
central-generated Complete generated Central API surface
config Central configuration tools
monitoring Health, alerts, events, clients, devices
nac MAC registration, MPSK, visitors, auth policy tools
ops Troubleshooting and operational tools
glp GreenLake Platform devices and documented attribute grouping, subscriptions, users, Audit Logs v2beta1, workspaces, reporting, service catalog, and guarded writes
rag ask_docs, search_docs, lookup_api
interop interop-core: Central <-> Mist WLAN/site concept translation and bounded trend normalization. Credential-free and read-only-local, so it is loaded on every profile – name it only when you want just these tools.
clearpass, mist, apstra, aos8, edgeconnect, uxi, axis, design Optional product backends
all All core and optional backends

Optional products can be enabled either by HPE_MCP_TOOLSETS or by HPE_MCP_PRODUCTS; see optional-products.md for the per-product workflow matrix. Generic optional GET responses are paginated with limit and offset when the response contains a list.

HPE_MCP_PRODUCTS=clearpass,mist,apstra,aos8,edgeconnect,uxi,axis,design

Set HPE_MCP_TOKENIZE_SECRETS=1 to install the optional session-scoped secret-tokenization middleware. Plaintext values remain in bounded TTL vaults instead of being repeated through model-visible tool arguments and results.

Observability: audit log and metrics

Both are opt-in and disabled by default – installing them changes no existing tool behavior, and stdio mode never gains unsolicited output.

Audit log. Set HPE_MCP_AUDIT_LOG=1 to append one redacted JSONL record per completed or failed router call to state/tool-audit.jsonl (or set the variable to an explicit path). Each record contains:

Argument and result values are never written – only key names and a digest.

Metrics. Set HPE_MCP_METRICS=1 to enable bounded, in-process request/latency/outcome counters (no external dependency, no network call). Counters are bucketed by a capped set of allow-listed labels – tool, backend, capability, outcome – with a hard ceiling (max_series, default 512 distinct (tool, backend) pairs; anything beyond folds into one overflow bucket). Metrics never read argument values, result values, or exception messages.

Outcome classification treats a non-empty top-level errors list as a failed call even when a backend also reports a neutral status such as COMPLETED or HTTP 200; an empty errors list remains successful. Client-facing envelopes use 403 for write-gate blocked/forbidden policy refusals and 409 for confirmation or cancellation conflicts.

Set HPE_MCP_METRICS_HTTP=1 (in addition to HPE_MCP_METRICS=1, and only on the streamable-HTTP transport – see credential-free HTTP quickstart) to also expose a compact JSON snapshot at GET /metrics, protected by the same loopback/allow-list rules and MCP_HTTP_BEARER_TOKEN gate as every other HTTP path here. With only HPE_MCP_METRICS_HTTP=1 set, the route responds {"enabled": false}.

Beyond minimal mode

Outside minimal mode, four additional read-only tools support batching and planning without ever calling a live backend write.

invoke_read_tool_batch

Dispatches an ordered list of read-only calls in one MCP round trip, through the same annotation gate, cursor verification, and response-bounding path invoke_read_tool uses. Max 25 entries; one failed entry never aborts the batch. Each entry accepts name (required), arguments, an optional correlation id, and an optional cursor.

plan_tool_workflow

Builds a deterministic dependency/order plan across the enabled catalog. Steps reference an exact tool name or a free-text hint resolved through the same bounded keyword search find_tool uses -- never guessed. It never calls invoke_tool/invoke_read_tool itself.

plan_reconciliation_schedule

Builds a bounded, read-only recurring-check specification: a validated cadence plus a bounded set of currently enabled read/diagnostic tools. Write and destructive tools are always excluded. dry_run is always true.

evaluate_compliance_policy

A bounded, read-only, declarative compliance-policy evaluator (src/hpe_networking_mcp/pipeline/compliance.py) over caller-supplied observations -- it never fetches data itself. Fetch state first with invoke_read_tool, then pass the results as observations alongside a declarative policy.

invoke_read_tool_batch({
  "calls": [
    {"id": "alerts", "name": "list_active_alerts", "arguments": {"severity": "CRITICAL"}},
    {"id": "sites",  "name": "list_sites",         "arguments": {"limit": 25}}
  ]
})
plan_tool_workflow([
  {"id": "discover", "hint": "list devices"},
  {"id": "inspect", "hint": "find a specific device", "depends_on": ["discover"]}
])
plan_reconciliation_schedule("daily", platforms=["central"], max_entries=25)
evaluate_compliance_policy(
  observations=[{"hostname": "sw1", "firmware": {"version": "8.10.0"}}],
  policy=[{"field": "firmware.version", "operator": "version_gte", "expected": "8.9.0"}]
)

Each rule has a field (a restricted dotted/indexed path), an operator (one of eq, ne, lt, le, gt, ge, contains, in, regex_fullmatch, version_gte, version_range, exists, not_exists), and an expected value (required except for exists/not_exists). regex_fullmatch is restricted to a fail-closed safe-regex subset: at most one quantifier opcode anywhere in the whole pattern, no backreferences, no lookaround. A structurally invalid policy is rejected with "ok": false before any observation is evaluated; every per-rule result’s "actual" value is bounded and recursively redacted for credential/secret/tenant-shaped field paths.

Both planners and the evaluator produce a bounded, redacted "artifact" payload (router_dependency_plan, router_reconciliation_plan, or compliance_report – see artifact-contracts.md) ready for hpe_networking_mcp.pipeline.artifact_contracts.write_artifact; none of the three write to disk themselves.

Why invoke_tool is destructive

The backend catalog contains both read-only tools and tools that can change state. Since invoke_tool can dispatch any enabled backend tool, it is conservatively annotated as destructive. Use invoke_read_tool for normal investigations, and reserve invoke_tool for diagnostics, and for writes/destructive actions taken after explicit user intent.

Next

  1. v0.7 added plan_tool_workflow and plan_reconciliation_schedule; the post-v0.7 compliance expansion adds evaluate_compliance_policy and the batch dispatcher invoke_read_tool_batch, raising the default-mode count to 18. minimal mode remains the same three-tool surface. This count is identical whether every toolset/product is loaded or only the documented recommended profile (HPE_MCP_TOOLSETS=central,glp,rag) is – both are measured independently in docs/project-facts.json’s router_modes section (tools.default and tools.default_recommended_profile).