cnc-mcp
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| CNC_MCP_BASE_URL | Yes | CNC UI/API URL with scheme, e.g. https://host:30603 | |
| CNC_MCP_PASSWORD | No | Crosswork password for two-leg CAS SSO authentication | |
| CNC_MCP_USERNAME | No | Crosswork username for two-leg CAS SSO authentication | |
| CNC_MCP_API_TOKEN | No | Pre-issued JWT token alternative to username/password (cannot be refreshed) | |
| CNC_MCP_VERIFY_TLS | No | Whether to verify TLS certificates; set to false for self-signed lab certificates | true |
| CNC_MCP_ENABLE_WRITES | No | Write tools are not registered until this is true | false |
| CNC_MCP_TIMEOUT_SECONDS | No | Timeout in seconds (see .env.example for additional timeout, retry, concurrency, and response-cap settings) |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": false
} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
| experimental | {} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| cnc_list_devicesA | 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). |
| cnc_get_deviceA | 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). |
| cnc_get_device_collection_summaryA | 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: ...". |
| cnc_wait_for_device_reachableA | 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. |
| cnc_list_credential_profilesA | 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). |
| cnc_get_credential_profileA | 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: ". |
| cnc_list_providersA | 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 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; 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}
|
| cnc_get_providerA | Get the full record of one provider, by UUID or by exact name. Read-only. Exactly one of Returns:
str: JSON object with every provider field as Crosswork returns it
(note |
| cnc_get_topology_summaryA | Summarize the CNC topology: node/link totals and state breakdowns. Read-only. Combines three topology-service calls ( 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": {: {: }, ...}}
|
| cnc_get_topologyA | Get the topology graph: one page of nodes plus one page of links (edges). Read-only. Crosswork returns the whole graph (up to 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 |
| cnc_list_topology_nodesA | 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
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 |
| cnc_list_topology_linksA | List the links (edges) on the topology map with status and utilisation. Read-only. Each link carries its type (display name, e.g. "L2 Ethernet" for LT_L2_ETHERNET, "L3 ISIS IPv4 L1", "L3 OSPF IPv4", "L3 BGP EPE IPv4"), status (Up/Degraded/Down), both endpoint node names/UUIDs and interface names, and per-direction utilisation labels such as "0.00015% (1.5Kbps/1Gbps)" with a HEALTHY/... severity. L2 links come from LLDP collection; L3 links need an SR-PCE provider (BGP-LS). Paging is a row window: startRow = page * page_size,
endRow = startRow + page_size. Past the end the platform returns
Args: page_size: rows per page (1-500). page: 0-based page number. response_format: markdown (default) or json. Returns:
str: Markdown "status linkType: srcNode:srcIf <-> dstNode:dstIf (util)"
per link, or JSON:
{"total": int, "count": int, "page": int, "page_size": int,
"items": [{"uuid": str, "name": str, "linkType": "L2 Ethernet", "status": "Up",
"sourceNode-name": str, "sourceNode-uuid": str,
"sourceConnector-name": str, "targetNode-name": str,
"targetNode-uuid": str, "targetConnector-name": str,
"targetConnector-uto-label": str,
"targetConnector-uto-severity": "HEALTHY", ...}],
"has_more": bool, "next_page": int|null, ...}
Each item is the element's |
| cnc_list_tagsA | 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: ". |
| cnc_list_usersA | 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). |
| cnc_list_applicationsA | 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: ". |
| cnc_list_alarmsA | 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: ". |
| cnc_list_inventory_jobsA | 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: ". |
| cnc_get_inventory_jobA | 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: ". |
| cnc_wait_for_inventory_jobA | 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: ...". |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
TDQS
Scored across 19 tools
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.
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.
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.
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.