# Ask Your Network in Plain English

**Level:** 1 hour · point an AI agent at your fleet

**APIs used:** GET /api/v2/routers/, GET /api/v2/groups/, GET /api/v2/alerts/, GET /api/v2/net_devices/, GET /api/v2/net_device_signal_samples/, GET /api/v3/subscriptions

---
Read https://cradlepoint.ericsson.com/llms.txt and the OpenAPI document at
https://cradlepoint.ericsson.com/developer/api/openapi.json so you know the
Ericsson NetCloud Manager (NCM) API. For project layout, you can look at the
reference servers in https://github.com/cradlepoint/api-samples under
ncm_mcp_servers. Those include write tools (router management, config
patching, reboot); do NOT copy any of them.

Build me a read-only MCP server in Python 3.10+ using the official `mcp` SDK
with the stdio transport. Pin the `mcp` version you install and use the server
API documented for that version, because module paths changed between 1.x and
2.x.

Expose these tools:
- list_routers(state?, group_name?, name?) -> GET /api/v2/routers/. state is
  online, offline or initialized. The API filters groups by ID, so resolve
  group_name with GET /api/v2/groups/?name=<name> first (exact,
  case-sensitive match). Say so clearly if no group matches; if several do,
  include them all with group__in.
- recent_alerts(hours=24, type?) -> GET /api/v2/alerts/ with
  created_at__gt=<ISO 8601 UTC timestamp>.
- wan_interfaces(router) -> GET /api/v2/net_devices/?router=<id>&mode=wan
- signal_summary(router, days=7) -> list the router's modems with
  GET /api/v2/net_devices/?router=<id>&is_asset=true, then page through
  GET /api/v2/net_device_signal_samples/?net_device__in=<ids>&created_at__gt=<ts>
  for the whole window. Return per modem: the sample count, plus average and
  minimum rsrp and sinr. Report the 5G fields (rsrp5g, sinr5g and their
  _highband variants) the same way, as separate figures computed over the
  samples where they are non-null; omit any that are null in every sample.
- subscriptions() -> GET /api/v3/subscriptions

For any `router` argument: if it is all digits, treat it as a router ID and
confirm it exists with GET /api/v2/routers/<id>/. Otherwise look it up with
GET /api/v2/routers/?name=<value>, which is an exact, case-sensitive match.
Return a clear error if no router matches or several do.

Requirements:
- Read-only. GET requests only. Don't implement any POST, PUT, PATCH or DELETE,
  and don't expose a generic "call any endpoint" tool.
- The two API versions authenticate differently, and mixing them up is the
  most common cause of 401s:
  - v2: base https://www.cradlepointecm.com/api/v2/, with a trailing slash on
    every path. Four headers, X-CP-API-ID, X-CP-API-KEY, X-ECM-API-ID and
    X-ECM-API-KEY, from the environment variables X_CP_API_ID, X_CP_API_KEY,
    X_ECM_API_ID and X_ECM_API_KEY.
  - v3: base https://api.cradlepointecm.com/api/v3/, with no trailing slash.
    The header Authorization: Bearer <token>, from the environment variable
    NCM_API_TOKEN.
  - Send a descriptive User-Agent header on both.
- Return compact JSON. On v2 list calls, pass fields=<comma-separated names>,
  so the API returns only the few fields needed to answer a question. `fields`
  works on the live v2 API but isn't in the OpenAPI document yet.
- Handle paging inside each tool:
  - On v2, follow meta.next exactly as returned. It may point at a regional
    host such as www.us0.cradlepointecm.com. Send the same headers there, but
    only ever send credentials to https:// URLs on cradlepointecm.com or its
    subdomains.
  - Don't follow redirects automatically; treat any 3xx response as an error
    (a 301 usually means a missing trailing slash). HTTP clients re-send
    custom headers to wherever a redirect points, which would leak your keys.
  - On v3, follow links.next (cursor page[after]).
  - list_routers, recent_alerts, wan_interfaces and subscriptions return at
    most 200 items each, and say so in the response when you truncate, so the
    model doesn't report a partial list as complete. signal_summary aggregates
    every sample in the window instead of capping them.
- On HTTP 429, wait until X-RateLimit-Reset (a Unix epoch) if that header is
  present, otherwise back off exponentially (1s, 2s, 4s, 8s). Make at most
  5 attempts per request in total, and never wait more than 60s at a time.
  Return other HTTP errors to the model as a clear error message. Use a 30s
  request timeout.
- Write clear tool descriptions and parameter docs. The assistant picks tools
  based on those.

Then show me the exact command to register this server with Claude Code
(`claude mcp add ...`), passing the credentials as environment variables.
