Skip to main content
Glama

cnc-mcp

CI License: MIT

An MCP server that lets an AI agent operate Cisco Crosswork Network Controller (CNC) — the SDN controller for Cisco service-provider networks — through its REST APIs.

With this server connected, an agent can answer questions like "which devices are unreachable?", "what does the topology look like?", "which SR policies are down and what path do they take?", "is the Data Gateway collecting?", "is PE1 in sync with NSO?", "is the network ready for SRv6?", and, when writes are enabled, onboard devices, manage credential profiles and providers, map devices to gateways, drive NSO sync and connect actions, provision SR-TE policies through the SR-PCE, provision ODN templates, SR-TE policies and L3VPNs — over SR-MPLS or SRv6 — through NSO's T-SDN function packs (dry-run first), subscribe webhooks and external Kafka/gRPC feeds to alarm/inventory events, inspect collection jobs, manage device groups and their membership, the LCM / Circuit-Style managers, create and activate performance-monitoring policies and read the dashboards and NPM analytics, tune alarm settings (event-type severity, auto-clear, alarm-manager switches), run OAM trace routes, read SWIM state and manage the ZTP catalogue (config files, profiles, serial numbers, static routes, devices) — all through typed, documented tools with the platform's own error reasons surfaced verbatim.

285 tools (187 read, 98 write) over 24 API areas plus seven MCP prompts. Every tool was built from behaviour verified against a live CNC 7.2 instance, not from the documentation alone — see How it was verified.

Contents

Related MCP server: network-mcp

Quickstart

Requires uv (it fetches a Python 3.11+ on its own if none is installed) and a Crosswork user (the admin role covers everything; docs/RBAC.md lists the exact API rows a least-privilege account needs, and cnc_check_permissions verifies one).

Run it without cloning

uvx --from git+https://github.com/dr-stutters/cnc-mcp cnc-mcp

uvx fetches the repository, builds the package into its cache and starts the server on stdio. Append a ref to the URL to pin what you run — @v0.1.0 (a release tag, once that release exists) or @main. Settings are read from CNC_MCP_* environment variables or a .env file in the server's working directory — Configuration lists every variable and .env.example is a commented template. Run unconfigured, the server exits at once with Configuration error — check environment variables (CNC_MCP_BASE_URL), which is the quickest check that the install works.

A PyPI package (plain uvx cnc-mcp) is planned once the project is registered there; until then the git URL above is the install path.

Register it with an MCP client

Claude Code

claude mcp add cnc -e CNC_MCP_BASE_URL=https://cnc.example.com:30603 \
  -- uvx --from git+https://github.com/dr-stutters/cnc-mcp cnc-mcp

The credentials (CNC_MCP_USERNAME / CNC_MCP_PASSWORD, or CNC_MCP_API_TOKEN) reach the server either as variables exported in the shell that starts the client, or from a .env file in the server's working directory. To make that directory explicit — independent of where the client happens to start the server — pass uv's --directory flag:

claude mcp add cnc -- uvx --directory /home/me/cnc-config \
  --from git+https://github.com/dr-stutters/cnc-mcp cnc-mcp

with /home/me/cnc-config/.env holding the CNC_MCP_* settings (copy .env.example). Claude Code's default local scope and the user scope keep the registration in your own ~/.claude.json; the project scope writes a .mcp.json into the repository for everyone who checks it out. CNC_MCP_USERNAME / CNC_MCP_PASSWORD must never go into a shared client config — a .mcp.json in a repository, a team-distributed claude_desktop_config.json, a .cursor/mcp.json that gets committed. Use -e only for non-secret settings such as CNC_MCP_BASE_URL, and keep the secrets in a .env (git-ignored here) or in your own shell environment.

Claude Desktop — claude_desktop_config.json (Settings → Developer → Edit Config):

{
  "mcpServers": {
    "cnc": {
      "command": "uvx",
      "args": [
        "--directory", "/home/me/cnc-config",
        "--from", "git+https://github.com/dr-stutters/cnc-mcp", "cnc-mcp"
      ],
      "env": { "CNC_MCP_BASE_URL": "https://cnc.example.com:30603" }
    }
  }
}

Desktop clients do not start servers in a predictable working directory, so the --directory form is the reliable way to have the .env found. If the client reports that it cannot find uvx, use its absolute path (which uvx) as command.

Cursor — the same mcpServers block in ~/.cursor/mcp.json (per user) or .cursor/mcp.json (per project, shared if committed).

VS Code — .vscode/mcp.json (per project) or the user-level mcp.json (MCP: Open User Configuration); same entry, but the top-level key is servers and the entry carries "type": "stdio".

Write tools are not registered at all until CNC_MCP_ENABLE_WRITES=true, so a read-only registration cannot be talked into changing anything; Safety controls covers the area allowlist, the tool denylist and the dry-run mode that sit on top of that switch.

Clone and run (development)

git clone https://github.com/dr-stutters/cnc-mcp && cd cnc-mcp
make install                      # uv sync
cp .env.example .env              # set CNC_MCP_BASE_URL, USERNAME, PASSWORD
make test && make lint            # 2,800+ tests, all HTTP mocked — no CNC needed
make run                          # start the server on stdio
make inspect                      # MCP Inspector against it
make cli ARGS="list"              # scripts/mcp_cli.py: list | schema | call | prompts | prompt
make rbac-check                   # the packaged RBAC map still matches the tool source
make build                        # wheel + sdist into dist/

scripts/mcp_cli.py drives the server over the real MCP stdio protocol — instructions, list [--writes], schema <tool>, call <tool> '<json>', prompts, prompt <name> '<json>', each with --env NAME=VALUE to start the server under other settings — so what it prints is exactly what an agent sees. To register a checkout with a client, replace the uvx command above with "command": "uv", "args": ["--directory", "/path/to/cnc-mcp", "run", "cnc-mcp"]; the checkout's own .env is then the configuration.

Docker

docker run -i --rm --env-file .env ghcr.io/dr-stutters/cnc-mcp:latest

The image is published to GHCR by the release workflow on each tag. It contains no credentials (.dockerignore keeps .env out of the build context); they are passed at run time with --env-file or -e. To build locally instead: make docker-build, then docker run -i --rm --env-file .env cnc-mcp. In a client config the entry is "command": "docker" with "args": ["run", "-i", "--rm", "--env-file", "/path/to/.env", "ghcr.io/dr-stutters/cnc-mcp:latest"].

Every setting, its default and what it does is in Configuration below; .env.example is the same list as a commented template.

Tools

Read tools — registered in every mode:

Area

Tools

Devices

cnc_list_devices · cnc_get_device · cnc_get_device_collection_summary · cnc_wait_for_device_reachable

Credential profiles

cnc_list_credential_profiles · cnc_get_credential_profile

Providers (SR-PCE, NSO, …)

cnc_list_providers · cnc_get_provider

Topology (RESTCONF NBI)

cnc_get_topology_summary · cnc_list_topology_nodes · cnc_get_topology_node · cnc_list_node_interfaces · cnc_get_node_interface · cnc_list_topology_links · cnc_get_topology_link · cnc_list_srv6_locators

TE state (SR-PCE feed)

cnc_get_te_summary · cnc_list_sr_policies · cnc_get_sr_policy · cnc_list_p2mp_policies · cnc_get_p2mp_policy · cnc_list_rsvp_te_tunnels · cnc_get_rsvp_te_tunnel · cnc_get_link_performance_metrics · cnc_get_sr_policy_performance_metrics · cnc_get_rsvp_tunnel_performance_metrics

SR-TE operations (Optimization Engine)

cnc_list_sr_policies_on_nodes · cnc_list_sr_policies_on_interface · cnc_get_sr_policy_routes · cnc_get_sr_policy_metrics · cnc_preview_sr_policy_route · cnc_dryrun_sr_policy · cnc_get_sr_policy_path_notification_state · cnc_wait_for_sr_policy_oper_state

Platform

cnc_list_tags · cnc_list_users · cnc_list_applications · cnc_list_alarms · cnc_list_inventory_jobs · cnc_get_inventory_job · cnc_wait_for_inventory_job

Data Gateway

cnc_list_data_gateways · cnc_get_data_gateway · cnc_list_data_gateway_pools · cnc_get_data_gateway_load_metrics · cnc_list_data_gateway_outages · cnc_get_data_gateway_health · cnc_get_data_gateway_global_parameters · cnc_list_data_destinations · cnc_list_data_gateway_files

NSO

cnc_is_nso_configured · cnc_get_nso_policy · cnc_list_nso_devices · cnc_get_nso_device · cnc_check_device_nso_state · cnc_check_nso_device_sync · cnc_get_nso_device_config · cnc_wait_for_device_nso_state

Inventory extras

cnc_get_device_summary · cnc_get_inventory_config · cnc_get_collection_cadence · cnc_get_device_tags

Fault

cnc_get_alarm · cnc_search_alarms · cnc_list_events · cnc_list_device_alarms · cnc_get_alarm_settings · cnc_get_alarm_manager_settings · cnc_list_event_types · cnc_get_event_type_recommendation · cnc_list_alarm_suppression_policies

Device configuration

cnc_get_device_config_preferences · cnc_list_device_backups · cnc_get_device_backup · cnc_list_config_backup_jobs · cnc_get_config_backup_job · cnc_list_config_templates · cnc_get_config_template · cnc_list_template_deployments · cnc_get_template_deployment · cnc_wait_for_config_backup_job · cnc_wait_for_template_deployment

EMF inventory

cnc_list_ems_nodes · cnc_get_ems_node · cnc_list_ems_interfaces · cnc_get_ems_interface · cnc_get_ems_inventory_summary

Platform admin & RBAC

cnc_get_platform_version · cnc_get_cluster_health · cnc_list_cluster_nodes · cnc_get_cluster_node · cnc_list_microservices · cnc_list_application_status · cnc_list_app_manager_jobs · cnc_list_app_manager_events · cnc_get_maintenance_status · cnc_list_certificates · cnc_check_certificate_expiry · cnc_get_login_banner · cnc_get_session_config · cnc_list_active_sessions · cnc_get_user · cnc_list_roles · cnc_get_role_tasks · cnc_get_role_permissions · cnc_get_password_policy · cnc_list_secured_apis · cnc_check_permissions

Notifications

cnc_list_notification_streams · cnc_list_notification_subscriptions · cnc_get_notification_subscription · cnc_list_kafka_subscriptions

Collection service

cnc_get_collection_job_count · cnc_get_collection_job_summary · cnc_get_collection_job_state · cnc_list_export_collection_jobs · cnc_list_sensor_templates · cnc_get_collection_health

Device groups

cnc_list_group_rule_conditions · cnc_list_root_groups · cnc_get_group_hierarchy · cnc_get_group_details · cnc_list_group_devices · cnc_list_group_rules · cnc_list_group_ports

LCM & Circuit-Style (Optimization Engine)

cnc_list_lcm_domains · cnc_get_lcm_config · cnc_list_lcm_managed_interfaces · cnc_get_lcm_recommendation · cnc_get_lcm_recommendation_preview · cnc_list_csm_bandwidth_pools · cnc_list_cs_policy_paths · cnc_list_cs_policies_on_nodes · cnc_list_cs_policies_on_interface

Services (CAT inventory, T-SDN)

cnc_list_service_types · cnc_get_service_counts · cnc_list_services · cnc_get_service · cnc_get_service_plan · cnc_wait_for_service_plan · cnc_list_vpn_services · cnc_get_vpn_service · cnc_get_vpn_service_health · cnc_get_vpn_underlay_transport · cnc_list_sub_services · cnc_find_services_on_transport · cnc_list_function_packs

Performance monitoring (PM policies, dashboards, NPM)

cnc_list_performance_policies · cnc_get_performance_policy · cnc_get_performance_policy_history · cnc_list_performance_policy_devices · cnc_list_performance_policy_templates · cnc_get_performance_retention · cnc_get_performance_health_settings · cnc_get_performance_statistics · cnc_get_performance_top_n · cnc_list_performance_top_n_columns · cnc_get_performance_summary · cnc_get_lsp_utilization · cnc_get_lsp_delay · cnc_get_interface_delay · cnc_get_srv6_locator_statistics

OAM & probes

cnc_get_oam_settings · cnc_list_oam_trace_routes · cnc_get_oam_trace_route · cnc_wait_for_oam_trace_route · cnc_get_probe_status

SWIM & ZTP

cnc_get_swim_preferences · cnc_list_software_images · cnc_get_device_running_images · cnc_get_swim_job · cnc_list_ztp_profiles · cnc_list_ztp_devices · cnc_list_ztp_serial_numbers · cnc_list_ztp_static_routes · cnc_get_ztp_device_policy · cnc_list_ztp_config_files · cnc_list_ztp_images

EMS inventory scheduler

cnc_list_inventory_scheduler_jobs · cnc_get_inventory_scheduler_job · cnc_wait_for_inventory_scheduler_job

Playbooks (one call, composed from the tools above)

cnc_investigate_device · cnc_network_health_report · cnc_explain_sr_policy · cnc_alarm_triage · cnc_explain_service · cnc_srv6_readiness

Write tools — registered only with CNC_MCP_ENABLE_WRITES=true (and, with CNC_MCP_WRITE_AREAS, only for the listed areas); deletes carry the MCP destructive annotation:

Area

Tools

Devices

cnc_create_device · cnc_update_device · cnc_delete_device · cnc_enable_device_gnmi

Credential profiles

cnc_create_credential_profile · cnc_update_credential_profile · cnc_delete_credential_profile

Providers

cnc_create_provider · cnc_update_provider · cnc_delete_provider

Data Gateway

cnc_map_devices_to_data_gateway

NSO

cnc_nso_device_action (check-sync / sync-from / connect / compare-config …) · cnc_nso_sync_to_device · cnc_sync_inventory_with_nso

SR-TE operations

cnc_create_sr_policy · cnc_update_sr_policy · cnc_delete_sr_policy · cnc_set_sr_policy_path_notifications

Platform admin

cnc_set_login_banner · cnc_set_maintenance_mode · cnc_restart_microservice

Inventory extras

cnc_create_tag · cnc_delete_tag · cnc_assign_tags · cnc_unassign_tags · cnc_set_device_location · cnc_clear_device_location · cnc_lock_device · cnc_unlock_device

Fault

cnc_acknowledge_alarm · cnc_annotate_alarm · cnc_clear_alarm · cnc_create_alarm_suppression_policy · cnc_update_alarm_suppression_policy · cnc_delete_alarm_suppression_policy · cnc_set_event_type_severity · cnc_set_event_type_autoclear · cnc_revert_event_type_autoclear · cnc_set_event_type_recommendation · cnc_update_alarm_manager_settings · cnc_update_gnmi_alarm_settings

Device configuration

cnc_backup_device_config · cnc_delete_config_backup_job · cnc_delete_device_backup · cnc_create_config_template · cnc_delete_config_template · cnc_deploy_config_template · cnc_delete_template_deployment

Notifications

cnc_create_webhook_subscription · cnc_delete_notification_subscription · cnc_create_external_subscription (Kafka / gRPC) · cnc_delete_external_subscription · cnc_clear_notification_subscriptions_by_topic

Device groups

cnc_create_device_group · cnc_update_device_group · cnc_delete_device_group · cnc_set_device_group_members · cnc_move_group_members

LCM

cnc_pause_lcm_recommendations

Service provisioning (NSO proxy, T-SDN CFPs)

cnc_create_odn_template · cnc_delete_odn_template · cnc_create_sr_policy_service · cnc_update_sr_policy_service · cnc_delete_sr_policy_service · cnc_create_sid_list · cnc_delete_sid_list · cnc_create_l3vpn_service · cnc_delete_vpn_service · cnc_provision_service · cnc_delete_service · cnc_resync_service_inventory — the three creates take srv6_locator for SRv6 transport (see SRv6)

Performance monitoring (PM policies, retention)

cnc_create_performance_policy · cnc_update_performance_policy · cnc_activate_performance_policy · cnc_deactivate_performance_policy · cnc_delete_performance_policy · cnc_update_performance_retention · cnc_reset_performance_retention

OAM & probes

cnc_start_oam_trace_route · cnc_reactivate_probe

SWIM & ZTP (the ZTP catalogue)

cnc_upload_ztp_config_file · cnc_update_ztp_config_file · cnc_delete_ztp_config_file · cnc_create_ztp_profile · cnc_update_ztp_profile · cnc_delete_ztp_profile · cnc_add_ztp_serial_numbers · cnc_delete_ztp_serial_numbers · cnc_create_ztp_static_route · cnc_delete_ztp_static_route · cnc_create_ztp_device · cnc_update_ztp_device · cnc_delete_ztp_device

EMS inventory scheduler

cnc_run_inventory_scheduler_job · cnc_suspend_inventory_scheduler_job · cnc_resume_inventory_scheduler_job

Playbooks

cnc_provision_l3vpn_e2e (dry-run → commit → plan → CAT status → OAM trace; srv6_locator for an SRv6 VPN, which skips the MPLS-only trace) · cnc_create_sr_policy_e2e (dry-run → create → wait UP → routes) — both take dry_run=true to stop after the preview

The playbook tools compose the others server-side: each answers with a verdict (healthy / degraded / red / deployed …, with the reasons), one section per underlying tool, and an audit list of the calls it made, so an agent can drill into any section with the individual tool. A section whose call fails is reported as unavailable rather than failing the whole answer. Blind-agent measurements: a "device looks degraded" investigation dropped from 44 tool calls to a handful, a network health overview from 25.

Seven MCP prompts package the operator workflows for clients that expose them as slash commands: troubleshoot_device, network_health_check, explain_sr_policy, provision_l3vpn (with an optional srv6_locator), alarm_triage, explain_service, srv6_readiness. Each tells the assistant which playbook to start from, where to drill in, and what to do when the write tools are absent.

Every tool has flat, typed parameters with examples and constraints (unknown argument names are rejected with a "did you mean" hint), a docstring that states when to use it, what it returns, and what each error means, and a response_format of markdown (curated summary, the default) or json (complete data). The wait_for_* tools poll server-side so an agent never has to loop on a status check.

Conventions the server also tells agents about at connect time:

  • Paging is page_size / page (0-based); list tools return {total, count, page, page_size, has_more, next_page, items} where total counts matches for the filter and collection_total the whole collection.

  • Filters are exact-match, case-insensitive, with * as a wildcard.

  • Enums accept friendly values (admin_state="up", family="sr_pce", protocol="ssh") or the platform's wire values.

  • Writes return Crosswork's job envelope (job_id, state, impacted). A job the platform rejected comes back as Error: … with the platform's reason, never as a silent success.

  • Ordering: credential profile → provider → device. A device's te_router_id must match its router-id in the SR-PCE topology for the two to correlate. ZTP: config file → profile → serial numbers → device, torn down in reverse (an in-use serial cannot be deleted).

  • Network-impacting writes are named as such: activating a PM policy starts SNMP/telemetry collection on its devices within seconds; a device group is populated by moving devices out of the leaf that holds them (Unassigned Devices for a device never placed), because the platform's "set members" call removes; reverting an event type's auto-clear deletes the interval rather than restoring a default.

SRv6

CNC 7.2 exposes SRv6 through the same NBIs as SR-MPLS, with no locator object anywhere in the topology model: a node advertises SRv6 node SIDs per IGP instance and a link End.X adjacency SIDs, so cnc_list_srv6_locators derives each locator from a node SID and its block / node lengths (fc00:0:1::/48 from lb 32 + ln 16, labelled uSID F3216 when the function length is 16 too). cnc_get_topology_node / cnc_get_topology_link render the SIDs, their structure and the node's Flex-Algos, cnc_get_topology_summary counts them, and cnc_list_topology_nodes / cnc_list_sr_policies take a dataplane filter (sr-mpls | srv6). SR-policy state carries no dataplane leaf either: a policy is reported as srv6 when it carries an srv6-binding-sid, IPv6 hops or IPv6 router-id keys (cnc_get_sr_policy accepts them; cnc_get_te_summary counts by dataplane; cnc_explain_sr_policy says which dataplane it found). cnc_get_srv6_locator_statistics reads the Performance dashboard's per-locator outBitRate series (it needs an SRV6LOCATOR monitoring policy on the device), and cnc_srv6_readiness folds all of it into a READY / PARTIAL / NONE verdict that names the nodes without a locator and the adjacencies without an End.X SID.

Provisioning goes through the T-SDN function packs only: srv6_locator on cnc_create_sr_policy_service, cnc_create_odn_template, cnc_create_l3vpn_service (service-wide or per endpoint) and cnc_provision_l3vpn_e2e. The function pack's rules are checked before anything is sent: an SRv6 policy needs an IPv6 tail-end and a dynamic path — explicit SID lists, bandwidth and a binding-SID are refused — and NSO validates neither the locator name nor the tail-end against the routers, so dry-run first and confirm the locator on the router with cnc_get_nso_device_config(subtree='segment-routing/srv6'). Not in 7.2, and the tools say so rather than pretend: PCE-initiated SRv6 policies (the Optimization Engine RPCs cnc_create_sr_policy / cnc_dryrun_sr_policy are SR-MPLS only), SRv6 OAM trace routes (cnc_start_oam_trace_route is MPLS LSP-ping only, so the L3VPN playbook skips the trace for an SRv6 VPN), explicit SRv6 SID lists, and L2VPN with SRv6-TE.

Verification status: the lab has no SRv6 underlay yet, so the populated SRv6 renderings are built from the 7.2 YANG / OpenAPI shapes and exercised on fixtures only; what is verified live is that every reader answers "no SRv6" on the SR-MPLS lab with its SR-MPLS content unchanged, that cnc_srv6_readiness answers NONE with the underlay hint, that the srv6locator PM endpoints answer empty, and — through NSO dry-run, nothing committed — that the three creates render the expected CLI (srv6 / locator LOC1 binding-sid dynamic behavior ub6-insert-reduced with an IPv6 end-point for the policy and the ODN template; segment-routing srv6 / locator LOC1 / alloc mode per-vrf under the VRF's address-family for the L3VPN).

How it works

MCP client ──stdio──▶ cnc-mcp ──HTTPS──▶ Tyk gateway (:30603) ──▶ Crosswork services
                        │
                        ├─ auth.py       CAS SSO: ticket-granting ticket → service ticket (an 8 h JWT)
                        ├─ client.py     retries, concurrency cap, one transparent re-auth
                        ├─ errors.py     platform status/body → actionable "Error: …" hints
                        ├─ crosswork.py  JSON-over-POST dialect: query grammar, envelopes, job checks
                        ├─ restconf.py   RESTCONF NBI dialect (topology, optimization engine, NSO proxy)
                        ├─ emf.py        EMF RESTCONF dialect (fault / inventory / performance)
                        ├─ probe.py      "is this API even present on this deployment?"
                        └─ tools/        one module per API area, registered through safety.py

Authentication. Crosswork's two-leg CAS flow is implemented in CrossworkCasAuth: POST /crosswork/sso/v1/tickets yields a ticket-granting ticket, exchanged for a service ticket that is an 8-hour JWT sent as Authorization: Bearer. Crosswork never answers 401 — an expired token is a 403 "Unauthorized request", a malformed one a 500 "Middleware error" — so the auth strategy decides what "re-authenticate" looks like and the client retries once. httpx's request logging is capped at WARNING because the second leg's URL contains the ticket.

One gateway, several API dialects. Behind the single NodePort, CNC's services speak differently: JSON-over-POST …/query bodies with per-service grammars (inventory, dg-manager, collection, alarms), RESTCONF NBIs under /crosswork/nbi/* and the NSO proxy, and an EMF RESTCONF that only returns JSON for exactly Accept: application/json. Each dialect's verified quirks live in one helper module, so tool modules stay thin.

Safety. safety.register_tool() is the only way a tool is registered: it forces a read-only/destructive/idempotent decision, refuses to register write tools unless writes are enabled (and their area allowed), never registers a disabled tool, and in dry-run mode swaps each write for its preview — see Safety controls. POSTs are not auto-retried on 5xx (a lost response might mean the write happened) unless a tool explicitly marks the call safe to re-send. Tools never raise: every failure is returned as an Error: … string with the platform's reason, and secrets never appear in logs, errors, or output beyond what the platform itself masks.

Safety controls

Four layers, each an environment variable, each applied when the tools are registered: a tool a layer excludes is absent from the tool list the agent sees, not merely refused. (Dry-run mode is the exception by design — the write tools stay visible, but harmless.)

  1. Writes are off by default. CNC_MCP_ENABLE_WRITES=true registers the 98 write tools; without it the server is read-only, and the connect-time instructions say so.

  2. CNC_MCP_WRITE_AREAS — a comma-separated allowlist of the areas whose write tools are registered when writes are on (empty, the default, means every area). An area is a module in src/cnc_mcp/tools/; the ones with write tools are devices, credentials, providers, sr_te_operations, data_gateway, nso, admin, inventory_extras, fault, device_config, notifications, grouping, lcm_csm, service_provisioning, performance, oam, swim_ztp, ems_jobs and composite. Read tools are never affected. The write playbooks in composite need the sibling that commits for them: cnc_provision_l3vpn_e2e needs service_provisioning (and oam for its optional trace step), cnc_create_sr_policy_e2e needs sr_te_operations — CNC_MCP_WRITE_AREAS=composite on its own registers neither, and the startup log says which sibling each one lacks.

  3. CNC_MCP_DISABLED_TOOLS — a comma-separated denylist of tool names, read or write, that are never registered whatever the other settings say (cnc_delete_device,cnc_restart_microservice).

  4. CNC_MCP_DRY_RUN=true — the write tools stay registered but nothing changes on the platform. A write tool that takes dry_run runs with it forced to true and answers the preview (the device CLI NSO would push, the path the PCE would compute); every other write tool is not executed and answers NOT EXECUTED with the arguments it would have sent (secret-looking values redacted); the two write playbooks stop after their preview stage with a dry-run verdict. Each write tool's description ends with which of the two applies to it.

An unknown area or tool name is a configuration error at startup, with a "did you mean" hint, never a silent no-op; an allowlisted area whose tools are all read-only is logged as a warning. The startup log summarises the result (Registered 199 of 285 tools (187 read, 12 write); writes on for areas fault; disabled tools: none; dry-run off), the connect-time instructions tell the agent which mode it is in, and cnc_check_permissions repeats it next to the account's role.

scripts/mcp_cli.py --env NAME=VALUE (repeatable) starts the server with extra variables, to try a mode without editing .env:

uv run python scripts/mcp_cli.py --writes --env CNC_MCP_WRITE_AREAS=fault list
uv run python scripts/mcp_cli.py --writes --env CNC_MCP_DRY_RUN=true \
    call cnc_create_tag '{"name": "site-a"}'

Least-privilege account. The Crosswork gateway checks every request against the account's role, per API and HTTP method, so the server can run under a role that grants only what its registered tools send. docs/RBAC.md — generated from the tool source by scripts/rbac_map.py, checked in CI against the packaged map — lists the exact API rows a read-only account needs and what each write area adds, with ready-made role bodies in docs/rbac/. cnc_check_permissions reads the running account's role and reports which registered tools it would refuse and the rows to grant; a 403 from any tool points at it. The role bodies are the shape the Crosswork role editor submits (verified against a role built in the UI and read back; minus the empty _id/id the editor also sends, and with the read-back's limit/allowance_scope row fields), except that they grant single API ids where a UI tick grants a whole display-name group — so manage such a role through the API, not the editor (docs/RBAC.md says what is verified and what is not).

Configuration

Environment variables (or a .env file), prefix CNC_MCP_:

Variable

Default

Purpose

CNC_MCP_BASE_URL

(required)

CNC UI/API URL with scheme, e.g. https://host:30603

CNC_MCP_USERNAME / CNC_MCP_PASSWORD

—

Crosswork user; CAS SSO → JWT, refreshed automatically

CNC_MCP_API_TOKEN

—

Alternative: a pre-issued JWT (cannot be refreshed; expires in ~8 h)

CNC_MCP_VERIFY_TLS

true

false for self-signed lab certificates

CNC_MCP_ENABLE_WRITES

false

Write tools are not registered until true

CNC_MCP_WRITE_AREAS

(all)

Comma-separated areas whose write tools are registered when writes are on, e.g. fault,service_provisioning

CNC_MCP_DISABLED_TOOLS

—

Comma-separated tool names never registered, read or write

CNC_MCP_DRY_RUN

false

Write tools registered but not executed: forced preview where the tool has dry_run, recorded otherwise

CNC_MCP_TIMEOUT_SECONDS

30

Per-request read timeout, seconds (>= 1)

CNC_MCP_CONNECT_TIMEOUT_SECONDS

10

TCP connect timeout, seconds (>= 1)

CNC_MCP_MAX_RETRIES

3

Retries for 429 / 5xx / transport errors (idempotent calls; 0-10)

CNC_MCP_RETRY_BACKOFF_SECONDS

1.0

Base delay for the exponential backoff between retries, seconds (>= 0)

CNC_MCP_MAX_CONCURRENT_REQUESTS

5

Cap on in-flight requests to the platform

CNC_MCP_MAX_RESPONSE_CHARS

40000

Tool responses longer than this are truncated with a note

CNC_MCP_LOG_LEVEL

INFO

Python logging level (stderr only — stdout is the MCP transport)

How it was verified

Unit tests prove the code; only a live run proves the integration. Four layers were used:

  1. Mocked unit tests (make test): every tool has a happy-path test through the MCP server (which validates the input schema) asserting the exact request body sent, and an error-path test. All HTTP is mocked with respx; CI runs them on Python 3.11 and 3.12.

  2. Live smoke (scripts/live_smoke.py): a plan of real tool calls against a running instance. The read phase is side-effect free; the write phase creates smoke-* objects and removes them again, chaining created UUIDs into later steps, and must leave the platform exactly as it found it. scripts/smoke_plan.example.json is a sanitised copy of the plan used.

  3. Agent scenarios (scripts/mcp_cli.py): the server driven over the real MCP stdio protocol by assistants that see only the tool list, schemas and instructions — ten operator tasks (health overview, traffic ranking, policy explanation, service audit, a "degraded device" investigation, and five provisioning/operations tasks with full cleanup) run against the lab; every point of friction they reported became a fix (alarm triage rendering and sorting, unknown arguments rejected by name, host names accepted wherever a router-id is a key, modelled-vs-measured PM caveats, parseable truncation, ...). mcp_cli.py doubles as a manual test client.

  4. Live plumbing check (scripts/live_plumbing_check.py): exercises every dialect helper against the instance — the real 409, the real error-inside-200, the real XML fallback, the real routing signatures — so a platform change that breaks a verified assumption shows up before it breaks a tool.

The instance was a single-VM CNC 7.2.0 deployment with embedded NSO and Data Gateway, fed by a Cisco Modeling Labs fabric of five IOS-XRd routers running IS-IS + SR-MPLS (no SRv6 underlay yet — see SRv6 for what that leaves unverified), one of them acting as SR-PCE (BGP-LS + PCEP, feeding CNC over gRPC) with two PCE-delegated SR policies between the PEs, gNMI onboarded on every router, mpls oam and a vpnv4 iBGP pair on the PEs (so an L3VPN can be committed through the T-SDN function pack and traced end to end). Each module was also adversarially reviewed against the recorded facts before it was merged; that review caught bugs the tests had enshrined (a "not found" check that would have hidden an absent API, an NSO failure that would have read as success).

Platform facts that shaped the design

The published OpenAPI documents describe a platform that is not quite the one on the wire, so the client is built around the differences, each verified live: there are no 401s (an expired token is 403, garbage is 500); failed writes are HTTP 200 with state: JOB_FAILED in a job envelope; a missing RESTCONF entry is 409 data-missing while a 404 means "no such route"; the Optimization Engine answers bad input with a bare, empty 500; NSO device actions are fire-and-forget. The full list of 21 verified facts is in docs/platform-facts.md.

Roadmap

The published CNC 7.2 API has 948 operations across 103 OpenAPI documents; this server covers the inventory (incl. tags, locks, locations), the EMF inventory, topology, TE state, SR-TE operations, fault management, device configuration (backups, templates, deployments), platform administration and RBAC, Data Gateway, NSO, notifications (webhook and external Kafka / gRPC subscriptions), the collection service, device grouping (user groups, membership and rules), the LCM / Circuit-Style managers, the CAT service inventory and T-SDN service provisioning through the NSO proxy, performance monitoring (policy lifecycle, retention, dashboards) and NPM analytics, OAM trace routes and Service Health probes, SWIM reads, the ZTP catalogue (config files, profiles, serial numbers, static routes, devices) and the EMS inventory scheduler. docs/COVERAGE.md is the full picture: every documented operation, whether a tool sends it, and if not why (it is generated by scripts/api_coverage.py from the OpenAPI set, so its numbers are computed, not claimed). Planned modules, in the order they become exercisable on a lab:

Module

Scope

SRv6 on a live underlay

the SRv6 readers and renderings (SRv6) verified against a fabric that runs locators, IS-IS IPv6 and SRv6 policies — which members the SR-PCE feed populates, the endpoint-behaviour strings, a populated srv6locator series — then the dry-run-only provisioning steps committed and reverted

writes not yet exposed

collection job create, SWIM collect/distribute/activate, ZTP image upload / ownership vouchers / device status patch, config restore, LCM/CSM configuration and RSVP-TE / P2MP policy operations — unverified bodies with real network impact

not on this build

change automation, health insights, path analytics, service health (unrouted on a single-VM 7.2 deployment — a 404 from the home application; the error text names the missing application)

Project layout

src/cnc_mcp/
  server.py       assembly, auth selection, connect-time instructions
  auth.py         auth strategies incl. CrossworkCasAuth
  client.py       ApiClient: retries, re-auth, concurrency, raw bodies
  errors.py       PlatformError and the status/body → hint mapping
  config.py       Settings (env / .env)
  safety.py       register_tool(): annotations, write gating (areas, denylist), dry-run wrapper
  formatting.py   markdown/json response formats, pagination envelope, size cap
  polling.py      wait_until() for the wait_for_* tools
  crosswork.py    inventory query grammar, envelopes, job checks, enums, dg/collection/alarm helpers
  restconf.py     RESTCONF NBI helpers
  emf.py          EMF RESTCONF helpers
  probe.py        routing classification and availability probing
  tools/          devices, credentials, providers, inventory_extras, physical_inventory,
                  topology, te_state, sr_te_operations, platform, fault, device_config,
                  data_gateway, nso, admin, notifications, collection, grouping, lcm_csm,
                  services, service_provisioning, performance, oam, swim_ztp, ems_jobs,
                  composite (the playbooks)
  data/rbac_map.json        which gateway API each tool needs (generated; read by cnc_check_permissions)
scripts/
  live_smoke.py             live tool-call plan runner (read / write phases, $var chaining)
  live_plumbing_check.py    live verification of the dialect helpers
  mcp_cli.py                call the server over the real MCP stdio protocol (list/schema/call/prompts)
  api_coverage.py           maps the published OpenAPI operations onto the tools -> docs/COVERAGE.md
  rbac_map.py               tool -> gateway API map -> data/rbac_map.json, docs/RBAC.md, docs/rbac/
  smoke_plan.example.json   sanitised smoke plan
docs/
  platform-facts.md         the verified platform behaviours the client is built around
  COVERAGE.md               every published CNC 7.2 operation against the tools (generated)
  RBAC.md, rbac/            the API rows a least-privilege role needs, ready-made role bodies (generated)
tests/                      one test module per source module; respx-mocked

Development

make test          # pytest (respx-mocked HTTP)
make lint          # ruff check
make fmt           # ruff format + autofix
make rbac          # regenerate the RBAC map and docs/RBAC.md from the tool source (offline)
make rbac-check    # exit 1 when they are stale (CI runs this)
make docker-build  # stdio server image; run with: docker run -i --rm --env-file .env cnc-mcp

To add a tool module: read CLAUDE.md (the conventions are non-negotiable), copy the pattern of an existing module in src/cnc_mcp/tools/, register it in tools/__init__.py, give every tool a happy-path and an error-path test, add its calls to the smoke plan, run make rbac (a new tool without a regenerated map fails CI), and run the live smoke before merging.

License

MIT © 2026 Mitchell McInnes

Available Tools

19 tools
cnc_get_credential_profileGet Credential ProfileA
Read-onlyIdempotent

Get the full record of one credential profile by name.

Read-only. Profiles have no UUID: the name is the identifier everywhere (devices and providers reference it in their "profile" field). Find names with cnc_list_credential_profiles.

Args: profile: exact profile name (case-insensitive; surrounding whitespace is stripped). A '*' wildcard is accepted by the platform but this tool needs a single exact match.

Returns: str: JSON object {"profile": str, "user_pass": [{"user_name", "password": "******", "type": "ROBOT_USERPASS_SSH|HTTP|HTTPS|...", ...}], "v2_info": {...}?, "v3_info": {...}?}. Secrets are masked by the API. On failure: "Error: Credential profile '' not found ..." when nothing matches, "Error: ... matches several profiles ..." when a wildcard was used, "Error: profile must not be empty ..." for a blank name, or "Error: ".

ParametersJSON Schema
NameRequiredDescriptionDefault
profileYesExact profile name, case-insensitive (e.g. 'nso', 'cml-xrd').

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare the read-only/idempotent/non-destructive profile, but the description goes well beyond them: profiles have no UUID so the name is the identifier everywhere, secrets are masked by the API, wildcards are accepted by the platform but not by this tool, and it enumerates the exact error strings returned on failure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose, then Read-only note, then Args and Returns sections. The enumeration of four distinct error strings is slightly verbose but each is actionable for an agent and the rest is free of filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers identifier model, masking behavior, wildcard caveat, and failure modes. With an output schema present, the description does not need to explain return values, and its added behavioral context leaves an agent fully equipped to call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the schema already documents the single profile parameter, so the baseline is 3. The description adds real value beyond it: matching is case-insensitive, surrounding whitespace is stripped, and a '*' wildcard is accepted by the platform but rejected here – detail the schema does not convey.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (get) and resource (credential profile) with scope 'one ... by name'. It also distinguishes itself from the sibling cnc_list_credential_profiles, so an agent can tell the two apart without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes discovery to 'Find names with cnc_list_credential_profiles' and states the condition that selects this tool: a single exact match is required. Nothing about when to use it over the list sibling is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

cnc_get_deviceGet Device DetailsA
Read-onlyIdempotent

Get the full inventory record of one device by uuid or host name.

Read-only. Pass exactly one selector. Returns every field Crosswork holds for the node: uuid, host_name, node_ip, admin_state, reachability_state, operational_state, reachability_check, profile, connectivity_info, product_info, routing_info, tag_names, dg_name/dg_uuid, nso_state, errors, creation_time, last_upd_time, ...

Note the read/write asymmetry: node_ip.inet_af reads as a string ('ROBOT_INET_ADDR_TYPE_v4') but is the integer 0 in write bodies, so do not feed this object straight back into a write.

Returns: str: JSON object of the node, or "Error: ..." (not found -> no device matched the selector; ambiguous -> a wildcard host_name matched several devices, use the uuid).

ParametersJSON Schema
NameRequiredDescriptionDefault
uuidNoDevice uuid (e.g. '2a9b7c1e-0f3d-4b8a-9c6e-1d2f3a4b5c6d').
host_nameNoDevice host name, exact match, case-insensitive (e.g. 'PE1').

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive, yet the description adds real behavioral context: the read/write type asymmetry on node_ip.inet_af that makes round-tripping into a write unsafe, plus precise error-string semantics. That is value well beyond the annotation set.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the purpose, then constraints, then error semantics — a sensible order. Slightly long: enumerating every returned field is redundant given an output schema exists, so a few lines do not fully earn their place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers selection rules, read-only nature, notable data pitfalls, and both error shapes. With an output schema present it does not need to describe the return payload, so nothing an agent needs to call this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so uuid/host_name are already documented. The description still adds meaning: exactly one selector must be supplied, and the two selectors behave differently on ambiguity since only uuid guarantees a unique match. Minor tension: it mentions a 'wildcard host_name' while the schema declares exact match.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ('Get'), resource ('full inventory record of one device'), and both accepted selectors (uuid or host name). The singular 'one device' cleanly separates it from the sibling cnc_list_devices.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly instructs 'Pass exactly one selector' and explains the two failure modes and their remedy (not found vs. ambiguous wildcard -> use the uuid). It does not name cnc_list_devices as the alternative for enumeration, but the singular scope makes the choice inferable.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

cnc_get_device_collection_summaryGet Device Collection Status SummaryA
Read-onlyIdempotent

Count devices by collection status across the whole inventory.

Read-only. This is the "Collection status" widget of the Network Devices page: how many devices are in progress, completed, warning, failed or in maintenance for inventory collection. Use it as a quick health check before drilling into individual devices with cnc_list_devices.

Returns: str: JSON with flat integer counts: {"inprogress": int, "warning": int, "failed": int, "completed": int, "maintenance": int} On failure: "Error: ...".

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly, idempotent, non-destructive, so the description's 'Read-only' line is redundant. However, it adds genuine behavioral context beyond structured fields: the exact status categories counted and the failure surface ('On failure: "Error: ..."'). This is solid added value, short of exceptional.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the purpose, then usage, then return shape. Every block is relevant, though the 'Read-only' line repeats annotations and the explicit Returns block is somewhat redundant given an output schema exists. Minor padding, no real waste.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter aggregate tool whose safety profile is covered by annotations and whose return shape is backed by an output schema, the description is complete: purpose, use case, alternative, and count semantics are all present.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so per the rubric the baseline is 4. There is nothing for the description to disambiguate on inputs, and it correctly spends no words on parameters.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (count) and resource (devices by collection status) with an explicit scope (across the whole inventory). It further disambiguates from the sibling cnc_list_devices by contrasting the aggregate widget against drilling into individual devices.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly says when to use it ('quick health check before drilling into individual devices') and names the alternative tool (cnc_list_devices) for a different granularity. The routing condition is clear enough that an agent need not infer it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

cnc_get_inventory_jobGet Inventory JobA
Read-onlyIdempotent

Get one inventory job by id: its state, type, timestamps, the objects it touched and the error text when it failed.

Read-only. Use it to check the outcome of a device/credential/provider write. For a job that may still be running prefer cnc_wait_for_inventory_job, which polls until it finishes. The lookup scans the newest few hundred jobs (newest first), so a very old job may not be found even though cnc_list_inventory_jobs can still page to it.

Returns: str: JSON of the job: {"job_id", "state", "type", "creation_time", "completion_time", "created_by", "impacted": [" []"], "impacted_objects": [{"uuid", "name", "ip"}], "error"}. A state of JOB_COMPLETED_WITH_WARNING is a success whose advisory is in "error". "Error: No inventory job with id ..." when the id matches nothing; other failures: "Error: ".

ParametersJSON Schema
NameRequiredDescriptionDefault
job_idYesInventory job id as returned by a write tool or cnc_list_inventory_jobs (e.g. '0f6c1a2e-...').

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations cover the safety profile (readOnly, idempotent, non-destructive), so the bar is lower, yet the description still adds real context: the scan window limitation (newest few hundred jobs, newest first, so very old jobs may not be found), the success-with-warning semantics of JOB_COMPLETED_WITH_WARNING, and the exact error-string behavior. These are behavioral traits not encoded in annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the one-line purpose, then usage, then a structured returns block. Every sentence earns its place: the scan-window caveat and warning-state note are non-obvious and actionable, and nothing is redundant.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single-param read tool with an output schema and full annotations, this covers purpose, alternatives, lookup limitations, and return shape including edge cases (warning state, not-found error). Nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and the single job_id parameter is already documented with its source and an example, so the schema carries this burden. The description adds little parameter-level detail beyond confirming the id is the lookup key. Baseline 3 is appropriate when the schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Get one inventory job by id') and enumerates exactly what is returned: state, type, timestamps, impacted objects, and error text. It explicitly distinguishes itself from siblings cnc_wait_for_inventory_job and cnc_list_inventory_jobs by name, so an agent can route without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-use ('check the outcome of a device/credential/provider write') and a when-to-prefer-alternative rule ('For a job that may still be running prefer cnc_wait_for_inventory_job, which polls until it finishes'). It also names the fallback for old jobs (cnc_list_inventory_jobs can page to it). This is the full when/when-not/alternative set.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

cnc_get_providerGet Provider DetailsA
Read-onlyIdempotent

Get the full record of one provider, by UUID or by exact name.

Read-only. Exactly one of uuid / name must be given. Use it to inspect a provider's endpoints (connectivity_info), credential profile (profile), family, reachability and properties (for SR-PCE: auto-onboard, outgoing-interface, device-profile, preferred-stack) — for example before cnc_update_provider or cnc_delete_provider. A uuid lookup scans the (small) provider collection and matches client-side, since uuid is not a verified filter field on providers/query.

Returns: str: JSON object with every provider field as Crosswork returns it (note connectivity_info[].ipaddrs[].inet_af reads as 'ROBOT_INET_ADDR_TYPE_v4'; write bodies use 0 — don't round-trip a read object into a write). "Error: ..." when neither or both selectors are given, when no provider matches (verify with cnc_list_providers), or on an API failure.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameNoExact provider name, case-insensitive (e.g. 'cml-pce'). No wildcards; use cnc_list_providers to search. Give either uuid or name, not both.
uuidNoProvider UUID, exactly as returned by cnc_list_providers (e.g. '4f1c2d3e-...'). Give either uuid or name, not both.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover read-only/idempotent safety, so the bar is lower, yet the description adds non-obvious behavior: the uuid lookup is a client-side scan because uuid is 'not a verified filter field', and read objects must not be round-tripped into write bodies (inet_af differs). It also enumerates error conditions. This is real operational context beyond the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the one-line purpose, then uses a Returns block and parenthetical asides to carry the quirks. Structure is good, but the Returns section is somewhat verbose given an output schema exists, and some phrasing ('as Crosswork returns it') repeats what structured output already implies.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With annotations (safety), a full input schema, and an output schema already present, the description fills the remaining gaps: selector exclusivity, uuid scan behavior, error cases, and the read/write field-format mismatch. Nothing an agent needs to call this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description still adds value by reinforcing the mutual-exclusivity of uuid/name and by explaining that uuid matching happens client-side because it is not a verified filter field, which affects how the agent should source the value. It stops short of adding format details beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (get) and resource (one provider) with scope qualifiers (by UUID or exact name), and distinguishes itself from the sibling cnc_list_providers by returning 'the full record of one provider'. An agent can tell it apart from list/get_collection tools without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit rule ('Exactly one of uuid/name must be given'), names the scenarios where it applies (before cnc_update_provider or cnc_delete_provider), and routes to the alternative (verify with cnc_list_providers) when no match occurs. Both selection constraints and the fallback path are spelled out.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

cnc_get_topologyGet Topology GraphA
Read-onlyIdempotent

Get the topology graph: one page of nodes plus one page of links (edges).

Read-only. Crosswork returns the whole graph (up to maxLogicalNodes, 5000 on the lab instance) in one response with no server-side paging, so this tool re-downloads the graph on every call and pages BOTH lists client-side: page/page_size window the links, node_page/ node_page_size window the nodes. Each link carries both endpoints resolved (node UUID, node name, interface), so a links page is self-contained — you do not need the nodes page to read adjacency. Use it for node-to-node adjacency (e.g. "what is PE1 connected to?"); for tabular attributes (IPs, reachability, link status/utilisation) prefer cnc_list_topology_nodes / cnc_list_topology_links. Keep page sizes modest: the response is capped at the configured size limit.

Args: map_type: 'logical' only ('geo' is rejected with an explanation). page_size: links per page (1-500). page: 0-based page of links. node_page_size: nodes per page (1-1000). node_page: 0-based page of nodes. response_format: markdown (default) or json.

Returns: str: Markdown listing nodes then "A:ifA <-> B:ifB" per link, or JSON: {"map_type": "LOGICAL", "attributes": {"totalNodes": int, ...}, "nodes": {"total": int, "count": int, "page": int, "page_size": int, "items": [{"uuid": str, "name": str}], "has_more": bool, "next_page": int|null, ...}, "links": {"total": int, "count": int, "page": int, "page_size": int, "items": [{"uuid": str, "name": "-", "source": {"node_uuid": str, "node_name": str, "interface": str}, "target": {"node_uuid": str, "node_name": str, "interface": str}}], "has_more": bool, "next_page": int|null, ...}} Node icon/checksum attributes and edge decoration attributes (the only attributes the platform returns on /data) are dropped. On failure: "Error: ". An HTTP 500 "Internal Server Error" from /v1/topology-service/... means the service rejected the request body (mapType/viewId/params) — deterministic, do NOT retry. (It is not the inventory's "NATS request failed" signal.)

ParametersJSON Schema
NameRequiredDescriptionDefault
pageNo0-based page of links.
map_typeNoMap to read. Only 'logical' (default; wire value 'LOGICAL') is supported — 'geo' is rejected because the platform answers HTTP 500 for it.logical
node_pageNo0-based page of nodes.
page_sizeNoLinks per page (client-side paging).
node_page_sizeNoNodes per page (client-side paging).
response_formatNo'markdown' for human-readable output, 'json' for complete data.markdown

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Far beyond the annotations: it discloses that Crosswork returns the whole graph with no server-side paging, so every call re-downloads and pages client-side, that link endpoints are pre-resolved so a links page is self-contained, that icon/checksum and edge-decoration attributes are dropped, and that an HTTP 500 is deterministic and must not be retried.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core fact and cleanly sectioned into Args/Returns/error handling. The verbose JSON return-shape listing is somewhat redundant against the existing output schema, which costs it a point, but little of the prose is filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 6-parameter read tool with annotations and an output schema, the description still supplies the non-obvious operational context: client-side paging rationale, endpoint resolution, dropped attributes, page-size limits, and the deterministic-500 error contract.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is already 100%, so the baseline is 3, but the description adds the cross-parameter model — that page/page_size window links while node_page/node_page_size window nodes, and that map_type accepts only 'logical'. This explains the interaction between parameters rather than just restating them.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening line gives a precise verb and resource plus the exact shape of the payload ('one page of nodes plus one page of links'). It explicitly distinguishes itself from the sibling tools cnc_list_topology_nodes / cnc_list_topology_links by naming them and the attribute category each covers.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It states the use case with a concrete example ('what is PE1 connected to?') and gives an explicit when-not with the alternative tools for tabular attributes. It also adds a sizing caveat ('keep page sizes modest') that shapes invocation.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

cnc_get_topology_summaryGet Topology SummaryA
Read-onlyIdempotent

Summarize the CNC topology: node/link totals and state breakdowns.

Read-only. Combines three topology-service calls (init, nodes/summary, edges/summary) into one answer. Use it first to learn whether the topology is populated at all (an empty topology with a populated inventory usually means no reachable SR-PCE provider), then drill in with cnc_get_topology, cnc_list_topology_nodes or cnc_list_topology_links.

Returns: str: JSON: {"total_nodes": int, "unmapped_nodes": int, "max_logical_nodes": int, "reachability": {"CONN_STATE_REACHABLE": 5, ...}, "link_state": {"Up": 1, "Degraded": 0, "Down": 0}, "node_breakdowns": {: {: }, ...}, "link_breakdowns": {: {: }, ...}} unmapped_nodes counts nodes with no geographic location (they render on the logical map only). node_breakdowns carries every section the platform returned (e.g. device family) keyed by type. Counts are null and breakdowns empty when the platform returns an empty body (fresh, unpopulated topology). On failure: "Error: ". An HTTP 500 "Internal Server Error" from /v1/topology-service/... means the service rejected the request body (mapType/viewId/params) — deterministic, do NOT retry. (It is not the inventory's "NATS request failed" signal.)

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover read-only/idempotent, but the description adds substantial context beyond them: it discloses that three topology-service calls are combined, explains empty-body semantics (null counts, empty breakdowns), the failure format, and a critical non-retry rule for HTTP 500 body rejections, distinguishing it from the inventory's NATS failure signal.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the one-line purpose, then usage guidance, then a structured Returns block. The return-shape transcription borders on redundant given an output schema exists, but the semantic annotations attached to it (unmapped_nodes meaning, empty-body behavior) justify most of the length.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers purpose, ordering guidance, alternative tools, return semantics, empty-topology interpretation, and error handling with a non-retry directive. Nothing an agent needs to call this zero-parameter tool correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so there is nothing to disambiguate; baseline for 0 params is 4. The description correctly implies no filtering input is required.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource ('Summarize the CNC topology') plus the exact scope (node/link totals and state breakdowns), and explicitly names the sibling tools used for drilling in (cnc_get_topology, cnc_list_topology_nodes, cnc_list_topology_links). An agent can distinguish this from the drill-in siblings without reading any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit ordering guidance: 'Use it first to learn whether the topology is populated at all,' with a concrete diagnostic rule (empty topology + populated inventory ⇒ no reachable SR-PCE provider), then routes to the appropriate drill-in alternatives. Both when-to-use and when-to-use-something-else are covered.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

cnc_list_alarmsList AlarmsA
Read-onlyIdempotent

List Crosswork platform alarms (device reachability, collection, application health, ...), newest first as the platform orders them.

Read-only. Use it to find out why something is unhealthy before digging into devices or providers. Alarms are paged with a SQL-like criteria string ('select * from alarm limit N page M'); no other filtering is exposed. The platform reports no total, so 'has_more' means the page came back full — request the next page to check.

Args: open_only: True for open alarms only (default), False for all. limit / page: page size and 0-based page number.

Returns: str: Markdown with one line per alarm (category, description, created time, id, acknowledged flag, event count; the Events detail is omitted), or JSON: {"total": null, "count": int, "page": int, "page_size": int, "items": [{"AlarmId": str, "AlarmCategory": str, "Description": str, "Created": str, "Updated": str, "Acknowledge": bool, "object_id": str, "origin_app_id": str, "events_count": int, "Events": [...]}, ...], "has_more": bool, "next_page": int|null} On failure: "Error: ".

ParametersJSON Schema
NameRequiredDescriptionDefault
pageNo0-based page number (e.g. 0).
limitNoAlarms per page (e.g. 20).
open_onlyNoTrue (default) for open alarms only; False to include cleared.
response_formatNo'markdown' for human-readable output, 'json' for complete data.markdown

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover read-only/idempotent/non-destructive, but the description adds genuine behavioral detail beyond them: paging is done via a SQL-like criteria string, no total is reported, and 'has_more' just means the page came back full. These quirks materially affect correct invocation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The purpose is front-loaded and easy to scan, but the description is bloated by an Args block that duplicates the schema and a very long Returns block that restates a full JSON shape despite an output schema existing. Those sections do not fully earn their place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a list tool with annotations and an output schema present, the description is complete enough: it covers purpose, paging, filtering limits, and error format. The only excess is redundant return-value detail, not a gap in coverage.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all four parameters. The Args block restates open_only, limit, and page without adding syntax or edge-case meaning beyond what the schema provides. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('List Crosswork platform alarms') and even enumerates what the alarms cover (device reachability, collection, application health) plus ordering ('newest first'). It also implicitly distinguishes itself from siblings (devices, providers) by positioning itself as the diagnostic entry point.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives clear when-to-use guidance: 'find out why something is unhealthy before digging into devices or providers,' which routes the agent to deeper sibling tools. It also notes that 'no other filtering is exposed' beyond the criteria string, an implicit constraint. No explicit when-not-to-use, so not a full 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

cnc_list_applicationsList Installed ApplicationsA
Read-onlyIdempotent

List the applications installed on the Crosswork platform with their versions (e.g. Crosswork Optimization Engine, Service Health, ...).

Read-only. Use it to check what is installed and at which version before assuming a feature (topology, SR-TE, VPN services) is available on this instance.

Returns: str: Markdown "name (application_id) version — description" lines, or JSON: {"count": int, "items": [{"application_id": str, "application_data": {"version": str, "summary": {"name": str, "description": str}, "category": str, "build_information": {"date_time": str, "publisher": str}}}, ...]} On failure: "Error: ".

ParametersJSON Schema
NameRequiredDescriptionDefault
response_formatNo'markdown' for human-readable output, 'json' for complete data.markdown

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.8/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, openWorldHint, and destructiveHint=false, so 'Read-only' mostly restates structured data. The one genuinely additive behavioral detail is the failure mode ('Error: <actionable message>'), but nothing is said about latency, pagination, or freshness of the installed-application data.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The purpose and usage sentences are front-loaded and efficient, but the large 'Returns' block unpacks a nested JSON payload field-by-field, which is substantial duplication given an output schema exists. Roughly half the text is redundant structure rather than guidance.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool is inherently simple (one optional param, read-only), and the description covers purpose, usage context, and failure behavior. Since an output schema exists, the detailed return enumeration is unnecessary rather than missing, so completeness is high.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% with a single optional response_format parameter whose enum and per-value meaning are documented in the schema. The description never mentions response_format, so it adds no meaning beyond the schema; baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('List the applications installed on the Crosswork platform') and concretely grounds it with examples of the applications returned. It is clearly distinguishable from the device, credential, provider, and topology siblings, which all operate on different resources.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit situational guidance: use it to check what is installed and at which version before assuming a feature (topology, SR-TE, VPN services) is available. It doesn't name a competing sibling or a when-not-to-use case, but for a simple unfiltered list tool the usage context is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

cnc_list_credential_profilesList Credential ProfilesA
Read-onlyIdempotent

List credential profiles known to Crosswork, with optional name filter and paging.

Read-only. Use it to find the profile name to reference when adding devices or providers, or to check which protocols (SSH/HTTP/HTTPS/SNMPv2/...) a profile covers. For one profile's full record use cnc_get_credential_profile.

Secrets are masked by the API ("******"); usernames are returned in clear.

Args: profile: name filter (exact, case-insensitive, '*' wildcard; surrounding whitespace is stripped and a blank filter means no filter). page_size / page: filterData paging (0-based page). response_format: 'markdown' (default) or 'json'.

Returns: str: Markdown "- profile — types: SSH (user), HTTP (user), SNMPv2" lines, or JSON: {"total": int|null, "count": int, "page": int, "page_size": int, "items": [{"profile": str, "user_pass": [{"user_name": str, "password": "", "type": "ROBOT_USERPASS_SSH|HTTP|HTTPS|..."}], "v2_info": {"read_community": "", ...}?}, ...], "has_more": bool, "next_page": int|null, "collection_total": int|null} "total" is the number of profiles matching the filter (null when the platform omits it, i.e. zero matches); "collection_total" is the size of the whole collection regardless of filter. On failure: "Error: " (500 "NATS request failed" -> the query body was rejected; 403 -> token rejected or missing privilege).

ParametersJSON Schema
NameRequiredDescriptionDefault
pageNo0-based page number (e.g. 0).
profileNoFilter by profile name: exact match, case-insensitive, '*' is a wildcard (e.g. 'nso', 'cml-*', '*xrd'). No filter lists every profile.
page_sizeNoProfiles per page (e.g. 20).
response_formatNo'markdown' for human-readable output, 'json' for complete data.markdown

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the annotations (readOnly/idempotent/openWorld), the description discloses that secrets are masked as '******' while usernames are clear, and maps failure modes (500 NATS request failed, 403 token rejected or missing privilege). That is auth/behavioral context the annotations cannot convey.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose and routing, then cleanly sectioned into Args and Returns. The Returns block is verbose and largely duplicates the existing output schema, which is the main place text is not fully earning its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 4-param read-only list tool, the definition covers purpose, alternatives, filters, paging, output shape, and error semantics. An agent has everything needed to call it correctly and interpret results.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% so the baseline is 3, but the description adds semantics the schema lacks: surrounding whitespace is stripped, a blank filter means no filter, and paging is explicitly 0-based with the response_format default restated. It goes modestly beyond the schema rather than merely echoing it.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

First sentence states a specific verb (List) and resource (credential profiles) plus scope modifiers (name filter, paging). It explicitly contrasts itself with the sibling cnc_get_credential_profile, so the agent can route without inspecting either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives concrete when-to-use reasons ('find the profile name to reference when adding devices or providers', 'check which protocols a profile covers') and names the alternative for the single-record case. Nothing is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

cnc_list_devicesList DevicesA
Read-onlyIdempotent

List network devices (inventory nodes) with optional filters and paging.

Read-only. Use it to discover device uuids/host names before calling cnc_get_device, cnc_update_device or cnc_delete_device. Filters AND together. Unmanaged devices are hidden in the CNC UI's default table but are returned here.

Args: host_name, reachability, admin_state, credential_profile: exact-match filters (case-insensitive, '*' wildcard). Enum filters accept friendly or wire values. page_size, page: paging (page is 0-based). response_format: 'markdown' (default) or 'json'.

Returns: str: Markdown, one line per device: "host_name (uuid) ip=... reach=... oper=... admin=... profile=... dg=..." plus "More available: page=N." when another page exists. Or JSON: {"total": int|null, "count": int, "page": int, "page_size": int, "has_more": bool, "next_page": int|null, "collection_total": int|null, "items": []} 'total' is the number of matches for the filter (absent when zero matched); 'collection_total' is the size of the whole inventory. On failure: "Error: " (unknown enum value -> the accepted values are listed; 500 'NATS request failed' -> the platform could not parse the request).

ParametersJSON Schema
NameRequiredDescriptionDefault
pageNo0-based page number.
host_nameNoFilter by host name: exact match, case-insensitive, '*' wildcard (e.g. 'PE1' or 'PE*'). No substring match without '*'.
page_sizeNoDevices per page (e.g. 20).
admin_stateNoFilter by admin state: 'up', 'down' or 'unmanaged' (or the wire value, e.g. 'ROBOT_ADMIN_STATE_UP').
reachabilityNoFilter by reachability: 'reachable', 'unreachable', 'degraded' or 'unknown' (or the wire value, e.g. 'CONN_STATE_REACHABLE').
response_formatNo'markdown' for a one-line-per-device summary, 'json' for all fields.markdown
credential_profileNoFilter by credential profile name (e.g. 'cml-xrd'); '*' wildcard.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnly/idempotent annotations, it discloses that filters AND together, that unmanaged devices are included unlike the UI, and documents failure modes with actionable error semantics ('unknown enum value -> accepted values listed', 500 NATS failure meaning). This is unusually rich behavioral context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose, usage, then Args/Returns. Well-organized, though the Returns block is long and partially redundant with the existing output schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 7-param, filter-heavy read tool with an output schema, this covers discovery intent, filter combination, visibility caveats, paging, and error behavior comprehensively. Nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the parameter descriptions already carry most semantics. The description adds the cross-parameter rule that filters are AND-ed together and repeats the case-insensitive/'*' wildcard and 0-based paging contract, which is useful but largely mirrored in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States the specific verb+resource ('List network devices (inventory nodes)') plus scope ('optional filters and paging'). It even clarifies that these devices are inventory nodes, distinguishing it from the topology-oriented siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes the agent: use this to discover uuids/host names before calling cnc_get_device, cnc_update_device or cnc_delete_device. It also warns that unmanaged devices are hidden in the CNC UI default table but returned here, preventing a wrong inference about coverage.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

cnc_list_inventory_jobsList Inventory JobsA
Read-onlyIdempotent

List inventory jobs — the audit trail of every device, credential, provider and tag write on Crosswork (each write returns one job).

Read-only. Use it to review recent changes or to find the job_id of a write whose result was lost, then inspect one with cnc_get_inventory_job or wait for it with cnc_wait_for_inventory_job. Paged with the inventory filterData.PageSize/PageNum grammar (assumed for this endpoint; 'total' is null when the platform reports no result_count, and 'has_more' then means the page came back full).

Args: page_size / page: page size and 0-based page number.

Returns: str: Markdown listing, or JSON: {"total": int|null, "count": int, "page": int, "page_size": int, "items": [{"job_id": str, "state": str, "type": str, "creation_time": str, "completion_time": str, "created_by": str, "impacted": [str, ...], "error": str}, ...], "has_more": bool, "next_page": int|null, "collection_total": int|null} States: JOB_COMPLETED and JOB_COMPLETED_WITH_WARNING (a success with an advisory in "error", e.g. a no-op or partially applied write), JOB_FAILED / JOB_CANCELLED / JOB_ABORTED (unsuccessful), JOB_RUNNING and other in-progress states. On failure: "Error: ".

ParametersJSON Schema
NameRequiredDescriptionDefault
pageNo0-based page number (e.g. 0).
page_sizeNoJobs per page (e.g. 20).
response_formatNo'markdown' for human-readable output, 'json' for complete data.markdown

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint/idempotentHint and no destruction, but the description adds real behavioral context: pagination grammar, the fact that 'total' is null when the platform reports no result_count, and that 'has_more' then means the page came back full. It also interprets job states (e.g. COMPLETED_WITH_WARNING is a success with an advisory in 'error') and failure format. The bulk of the return-value detail, however, overlaps the output schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose, scope, and routing in the first two paragraphs, and the hedging on the pagination grammar ('assumed for this endpoint') is honest. It is somewhat over-long, however: the full Returns JSON shape duplicates information already present in the output schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a paged, read-only list endpoint, the definition covers purpose, routing alternatives, pagination caveats, item fields, state interpretation, and failure output. An output schema exists, so the return-shape narration is bonus rather than a gap, and nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents page, page_size and response_format. The description's Args line ('page_size / page: page size and 0-based page number') merely restates the schema, adding no new syntax or constraints — baseline 3.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource plus its exact scope: 'the audit trail of every device, credential, provider and tag write on Crosswork (each write returns one job).' This clearly separates it from the singular cnc_get_inventory_job and the blocking cnc_wait_for_inventory_job.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly names two use cases ('review recent changes' and 'find the job_id of a write whose result was lost') and routes the agent to the correct follow-up tools (cnc_get_inventory_job, cnc_wait_for_inventory_job) with the condition for each.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

cnc_list_providersList ProvidersA
Read-onlyIdempotent

List the providers configured in Crosswork (SR-PCE, NSO, WAE, ...).

Read-only. Providers are the external systems CNC integrates with; the SR-PCE provider is what feeds the L3/SR-TE topology (via BGP-LS), and the NSO provider is used for service provisioning. Use this to discover provider UUIDs and check their reachability before touching devices, topology, or services; use cnc_get_provider for the full record.

Filters AND together. Only name and family are supported filters on this endpoint. Page with page/page_size (0-based).

Args: name: exact/wildcard name filter (case-insensitive). family: friendly family name (sr_pce, nso, wae, syslog_storage, alert, proxy, onc, accedian_proxy) or wire value. page_size, page: paging; has_more/next_page say whether to fetch another page. response_format: markdown (one line per provider: name, uuid, family, reachability, endpoints, credential profile) or json.

Returns: str: Markdown listing, or JSON: {"total": int|null, "count": int, "page": int, "page_size": int, "items": [{"uuid", "name", "family", "profile", "reachability_state", "connectivity_info": [...], "properties": {...}, ...}], "has_more": bool, "next_page": int|null, "collection_total": int|null, "offset": int, "next_offset": int|null} total is the number of providers matching the filter (absent / null when Crosswork omits it, which it does for zero matches); collection_total is the size of the whole provider collection. On failure: "Error: " (unknown family value -> the list of accepted values; 500 "NATS request failed" -> malformed request rather than an outage).

ParametersJSON Schema
NameRequiredDescriptionDefault
nameNoProvider name filter: exact match, case-insensitive, '*' is a wildcard (e.g. 'cml-pce' or '*pce*'). No substring match without '*'.
pageNo0-based page number (e.g. 0).
familyNoProvider family filter, one of: accedian_proxy, alert, nso, onc, proxy, sr_pce, syslog_storage, wae (e.g. 'sr_pce'); wire values such as 'ROBOT_PROVIDER_SR_PCE' are accepted too.
page_sizeNoProviders per page (e.g. 20).
response_formatNo'markdown' for human-readable output, 'json' for complete data.markdown

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the safety profile (readOnly/idempotent/non-destructive/openWorld), and 'Read-only' restates them. The description does add genuinely non-annotated behavior: response_format output contents, 0-based paging with has_more/next_page, the null/absent semantics of total vs collection_total, and concrete error semantics (unknown family returns accepted values; 500 NATS request failed means malformed request, not an outage).

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded and organized with clear sections, but verbose: the Args block largely restates the 100%-covered schema and the Returns block reproduces the full JSON shape even though an output schema exists. The one valuable non-redundant detail is the total-vs-collection_total distinction on zero matches.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read-only list tool it covers everything an agent needs: what it returns, how to page, how filters combine, the sibling to use for detail, and how to interpret failure messages. Missing values for providers/reachability are explained via the field semantics.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description still adds cross-parameter meaning not in the schema, notably that filters AND together, that only name and family are honored on this endpoint, the accepted family values, and what each response_format yields. The name wildcard rule is repeated from the schema rather than extended.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Names a specific verb and resource ('List the providers configured in Crosswork'), enumerates the concrete provider types (SR-PCE, NSO, WAE), and explains what a provider is and which one feeds the topology. It is trivially distinguishable from cnc_get_provider, which it names.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states when to use it ('discover provider UUIDs and check their reachability before touching devices, topology, or services') and names the alternative for the other case ('use cnc_get_provider for the full record'). It also states the filter composition rule (filters AND together) and which filters this endpoint supports.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

cnc_list_tagsList TagsA
Read-onlyIdempotent

List the tags defined on Crosswork (system tags such as 'mdt' plus any user-defined ones), with optional name/category filtering and paging.

Read-only. Use it to learn the exact tag names before filtering devices by tag or attaching tags to devices. The tool sends an empty body and applies the name/category filters and the paging itself after the fetch. Whether tags/query honours a filter body or pages server-side is not verified; if the response carries result_count/total_count larger than the rows returned, the collection was truncated by the server and the output says so. 'total' is the number of fetched tags that matched the filters; 'collection_total' is the number of tags on the platform (from the server's counts when present, else the number fetched).

Args: name: case-insensitive substring of the tag name. category: exact category (case-insensitive), e.g. 'default'. page_size / page: client-side paging over the filtered tags.

Returns: str: Markdown listing, or JSON: {"total": int, "count": int, "page": int, "page_size": int, "items": [{"name": str, "category": str, "created_by": str, "creation_time": str, "tag_type": str}, ...], "has_more": bool, "next_page": int|null, "collection_total": int} On failure: "Error: ".

ParametersJSON Schema
NameRequiredDescriptionDefault
nameNoCase-insensitive substring to match against tag names (e.g. 'mdt'). Applied client-side.
pageNo0-based page number (e.g. 0).
categoryNoExact tag category to keep, case-insensitive (e.g. 'default'). Applied client-side.
page_sizeNoTags per page (e.g. 50).
response_formatNo'markdown' for human-readable output, 'json' for complete data.markdown

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive, but the description adds non-obvious implementation detail: the tool sends an empty body, filters and pages client-side, and warns the server-side behavior is unverified. It also defines 'total' vs 'collection_total' and the truncation signal, which no annotation or schema conveys.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The two opening sentences are front-loaded and efficient, but the implementation caveats ('Whether tags/query honours a filter body or pages server-side is not verified...') and the full inline return-shape block are verbose and partly duplicate the output schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers purpose, trigger, parameter behavior, the truncation edge case, and the total/collection_total distinction despite an output schema existing. The main gap is a slightly bloated return-value restatement, but overall the agent has what it needs to call and interpret the tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and each param is described there, so baseline is 3. The description adds value by clarifying that name/category are applied client-side after the fetch and by explaining paging behavior, going beyond the schema's field-level descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence states a specific verb and resource ('List the tags defined on Crosswork') plus the scope (system tags like 'mdt' plus user-defined). The following sentence gives the downstream use case, and no sibling tool overlaps this purpose.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states when to use it: 'Use it to learn the exact tag names before filtering devices by tag or attaching tags to devices.' This gives an unambiguous trigger, which is rare in the sibling set.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

cnc_list_topology_nodesList Topology NodesA
Read-onlyIdempotent

List the nodes on the topology map as a sorted, paged table.

Read-only. Topology nodes are the devices CNC has placed on the map (fed by inventory + SR-PCE); their UUIDs match the inventory node UUIDs. Use this for per-node attributes (management IP, TE router-id, reachability, family); use cnc_get_topology for adjacency.

Paging is a row window: startRow = page * page_size, endRow = startRow + page_size. Past the end the platform returns totalCount with no rows (reported as an empty page, not an error). No filtering is available on this endpoint — filter client-side or use the inventory tools. The platform silently accepts an unknown sort column (undefined order), so sort_by is validated here first.

Args: page_size: rows per page (1-500). page: 0-based page number. sort_by: column to sort on (see the parameter description). sort_ascending: sort direction. response_format: markdown (default) or json.

Returns: str: Markdown listing, or JSON: {"total": int, "count": int, "page": int, "page_size": int, "items": [{"uuid": str, "name": str, "nodeIp": str, "teRouterId": str, "reachabilityState": "CONN_STATE_REACHABLE"|..., "productType": str, "deviceFamily": str, "lastUpdateTime": str, ...}], "has_more": bool, "next_page": int|null, ...} Each item is the element's uuid merged with its attributes. On failure: "Error: " (unknown sort column -> "Error: Unknown sort column ..."). An HTTP 500 "Internal Server Error" from /v1/topology-service/... means the service rejected the request body (viewId/params) — deterministic, do NOT retry. (It is not the inventory's "NATS request failed" signal.)

ParametersJSON Schema
NameRequiredDescriptionDefault
pageNo0-based page number.
sort_byNoColumn to sort on: 'name' (default), 'nodeIp', 'lastUpdateTime' (all three verified live), 'teRouterId', 'reachabilityState', 'productType' or 'deviceFamily'.name
page_sizeNoRows per page (endRow - startRow).
sort_ascendingNoSort ascending (true, default) or descending.
response_formatNo'markdown' for human-readable output, 'json' for complete data.markdown

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Despite annotations already declaring read-only/idempotent behavior, the description adds substantial behavioral context: paging is a row window, past-the-end returns totalCount with no rows rather than an error, unknown sort columns are silently accepted by the platform but validated here, and an HTTP 500 means a deterministic rejection that should not be retried.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is structured with Args and Returns sections and front-loads the primary purpose and sibling routing. It is detailed but most sentences carry operational value; some return-format detail is redundant given the output schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read-only, paged, filterless list tool with annotations and an output schema, the description covers the key edge cases: paging behavior, lack of server-side filtering, sort validation, and non-retryable HTTP 500 failures. Nothing material is missing for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline would be 3. The description adds paging semantics beyond the schema, including the formulas startRow = page * page_size and endRow = startRow + page_size, and notes that sort_by is locally validated against unknown columns.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: 'List the nodes on the topology map as a sorted, paged table.' It also distinguishes the tool from its sibling cnc_get_topology, which is for adjacency rather than per-node attributes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly says to use this for per-node attributes and directs adjacency queries to cnc_get_topology. It also states there is no filtering on this endpoint and recommends client-side filtering or inventory tools as alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

cnc_list_usersList UsersA
Read-onlyIdempotent

List the Crosswork user accounts with their role, status and device access groups.

Read-only. Use it to confirm an account exists (Crosswork answers the same 'Invalid credentials' for an unknown username and a wrong password) and to see which role (PolicyId, e.g. 'admin') and device access groups (e.g. 'ALL-ACCESS') an account carries. The platform returns a dict keyed by username with PascalCase fields; this tool flattens it to a list and never returns the Password field.

Returns: str: Markdown listing, or JSON: {"count": int, "items": [{"username": str, "role": str, "first_name": str, "last_name": str, "status": str, "device_access_groups": [str, ...]}, ...]} On failure: "Error: " (403 -> the configured account lacks the user-administration privilege).

ParametersJSON Schema
NameRequiredDescriptionDefault
response_formatNo'markdown' for human-readable output, 'json' for complete data.markdown

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare read-only/idempotent/non-destructive, and the description reinforces that while adding non-obvious traits: the Password field is never returned, the platform's PascalCase dict is flattened to a list, and a 403 indicates the configured account lacks user-administration privilege. These are genuinely useful behaviors not present in structured fields.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads purpose, then usage, then returns and failure modes in labeled blocks. The Returns section is fairly long, but every element (count/items field names, error prefix) carries information an agent would otherwise have to guess.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Even though an output schema exists, the description fully covers return shape, field names, failure string format, and the 403 case, so an agent can call and interpret results without further inference.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Only one optional parameter (response_format) and the schema already documents it at 100% coverage. The description nevertheless earns credit by tying each format to a concrete return shape in the Returns block, going beyond the schema's terse enum description.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (List) plus resource (Crosswork user accounts) and enumerates the fields returned (role, status, device access groups). No sibling tool competes for this resource, so it is unambiguously distinguishable from the device/topology/provider listings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly gives two use cases: confirming an account exists (with a useful rationale about identical 'Invalid credentials' responses) and inspecting role/device access groups. It does not name alternatives or exclusions, but none exist among the siblings, so context is clear without them.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

cnc_wait_for_device_reachableWait for Device to Become ReachableA
Read-onlyIdempotent

Poll a device until its reachability_state is CONN_STATE_REACHABLE.

Read-only convergence wait. Call it right after cnc_create_device (or after fixing credentials / admin state) instead of polling cnc_get_device in a loop. Pass exactly one of uuid / host_name. A new device typically moves UNKNOWN/CHECKING -> REACHABLE within a minute or two once it is attached to a Data Gateway.

Returns: str: On success: "Device () is reachable after Ns." plus a JSON summary (reachability_state, operational_state, dg_name, errors). On timeout (NOT an error): "Not reachable yet after Ns; current reachability_state=..., operational_state=..." plus the same summary — call again to keep waiting, or inspect 'errors' / dg_name. "Error: ..." only for API failures or when no device matches.

ParametersJSON Schema
NameRequiredDescriptionDefault
uuidNoDevice uuid (from cnc_create_device's impacted_objects).
host_nameNoDevice host name, exact match (e.g. 'PE1').
timeout_secondsNoHow long to wait in total (e.g. 180).
interval_secondsNoSeconds between polls (e.g. 10).

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive, but the description adds critical behavior beyond them: a timeout is explicitly NOT an error and the caller should re-invoke, the typical UNKNOWN/CHECKING -> REACHABLE timeline, and the shape of both success and timeout responses. This is exactly the kind of convergence-wait context an agent needs.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the core action, then usage, then return semantics in a logical order. Slightly long, and the Returns block partially restates the output schema, but every line (notably timeout-is-not-an-error) carries information. Minor redundancy is the only deduction.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema and full annotation coverage present, the description still supplies the missing semantic layer: mutual-exclusive selector params, timeout semantics, and expected device-state transitions. Nothing an agent needs to invoke and interpret the result is absent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3, but the description adds a real constraint the schema cannot express: pass exactly one of uuid / host_name. It also ties uuid back to cnc_create_device's impacted_objects. The timeout/interval parameters are left to the schema, which is adequate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific action and target: 'Poll a device until its reachability_state is CONN_STATE_REACHABLE.' It names the exact terminal state and, via the usage line, distinguishes itself from plain polling with cnc_get_device. An agent can select it without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-use ('right after cnc_create_device', 'after fixing credentials / admin state') and names the alternative it replaces ('instead of polling cnc_get_device in a loop'). It also states the mutual-exclusion rule ('Pass exactly one of uuid / host_name').

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

cnc_wait_for_inventory_jobWait For Inventory JobA
Read-onlyIdempotent

Poll an inventory job until it reaches a terminal state or the timeout elapses.

Read-only. Use it right after a write that came back JOB_RUNNING instead of calling cnc_get_inventory_job in a loop. Terminal states are the verified ones: JOB_COMPLETED and JOB_COMPLETED_WITH_WARNING (both successes; the latter carries an advisory, e.g. a no-op or partially applied write), and JOB_FAILED / JOB_CANCELLED / JOB_ABORTED (failures). Any other state (JOB_RUNNING or an in-progress state not seen before) keeps the tool polling until the timeout.

Returns: str: "Inventory job completed after s." followed by the job JSON (with "impacted_objects" parsed from "impacted") on success; for JOB_COMPLETED_WITH_WARNING the line also carries "Warning: " and the JSON gains a "warning" key. A timeout is NOT an error: "Inventory job not finished after s; current state: JOB_RUNNING. ..." followed by the job JSON — call again to keep waiting. "Error: Inventory job failed (job , state JOB_FAILED): " when the job ended unsuccessfully; "Error: No inventory job with id ..." when the id matches nothing; other API failures: "Error: ...".

ParametersJSON Schema
NameRequiredDescriptionDefault
job_idYesInventory job id to wait for (e.g. '0f6c1a2e-...').
timeout_secondsNoGive up after this many seconds (e.g. 120).
interval_secondsNoSeconds between polls (e.g. 5).

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already establish read-only, idempotent, non-destructive behavior, but the description adds substantial behavioral detail: terminal states, warning semantics, timeout-as-non-error handling, and error return shapes. Nothing contradicts the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The first sentence is front-loaded and the usage guidance is clear. The returns block is lengthy and partly overlaps the output schema, but it earns its place by explaining timeout and warning behavior.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For an asynchronous polling tool with annotations, a full input schema, and an output schema, the description is complete enough to call correctly. It covers usage context, terminal states, timeout behavior, and error semantics.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so job_id, timeout_seconds, and interval_seconds are already documented in the schema. The description alludes to timeout behavior but does not add syntax, defaults, or constraints beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: poll an inventory job until terminal state or timeout. It distinguishes itself from the sibling cnc_get_inventory_job by explicitly saying to use this instead of calling that tool in a loop.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-use guidance: right after a write returns JOB_RUNNING, rather than polling cnc_get_inventory_job manually. It also clarifies that timeout is not an error and that the caller should call again to keep waiting.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 19 tool updatesv0.1.0
    • First observedcnc_get_credential_profile
    • First observedcnc_get_device
    • First observedcnc_get_device_collection_summary
    • First observedcnc_get_inventory_job
    • First observedcnc_get_provider
    • First observedcnc_get_topology
    • First observedcnc_get_topology_summary
    • First observedcnc_list_alarms
    • First observedcnc_list_applications
    • First observedcnc_list_credential_profiles
    • First observedcnc_list_devices
    • First observedcnc_list_inventory_jobs
    • First observedcnc_list_providers
    • First observedcnc_list_tags
    • First observedcnc_list_topology_links
    • First observedcnc_list_topology_nodes
    • First observedcnc_list_users
    • First observedcnc_wait_for_device_reachable
    • First observedcnc_wait_for_inventory_job

TDQS

A4.3/5.0

Scored across 19 tools

Disambiguation4/5

Each tool targets a distinct resource and action, and descriptions explicitly partition the topology tools (cnc_get_topology for adjacency vs cnc_list_topology_nodes/cnc_list_topology_links for tabular attributes). The only mild overlap is between the general topology getter and the two specialized topology listers, but the descriptions give clear guidance on when to use each.

Naming Consistency5/5

Every tool follows the same cnc_<verb>_<noun> convention (list_*, get_*, wait_for_*), with no camelCase or mixed-style deviations. The pattern is fully predictable across all 19 tools.

Tool Count4/5

19 tools is on the heavier side but each covers a genuinely distinct domain (devices, credentials, providers, topology, tags, users, applications, alarms, jobs) with read variants plus helpful summary/wait helpers. It feels slightly large but well-scoped rather than padded.

Completeness4/5

Read coverage is broad and coherent: devices, credentials, providers, topology, tags, users, applications, alarms and inventory jobs all have list/get surfaces, plus convergence-wait helpers. The notable gap is the complete absence of write operations (create/update/delete) even though descriptions reference them, but the surface appears intentionally read-only.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    D
    maintenance
    Enables AI-powered network automation through natural language interactions with Cisco NSO, providing access to device management, configuration retrieval, sync operations, and service orchestration via the RESTCONF API.
    9
    4
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Provides AI assistants with direct access to multi-vendor network devices for tasks like configuration management, health checks, and topology discovery through 35 specialized tools. It enables natural language control over platforms including Cisco, Juniper, and Nokia using SSH, NETCONF, and SNMP protocols.
    11
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to interact with the Netdisco network management platform through its complete REST API, supporting device inspection, port queries, VLAN searches, and job management.
    MIT