# CP-MCP documentation --- --- > MCP server (Claude connector) for the Cloudpath Enrollment System REST API. Google sign-in; discovers and calls any Core, Tenant Portal, or Property Management endpoint. --- --- # What is CP-MCP? Source: https://docs.neuralconfig.com/cp-mcp/ CP-MCP is a hosted MCP server that connects Claude to **your** Cloudpath Enrollment System. You sign in with Google, save your Cloudpath admin credentials, and add one URL to Claude as a custom connector. From then on you can ask Claude about your Cloudpath deployment in plain language, and it works against the live Cloudpath REST API on your behalf. It covers all three Cloudpath API surfaces — Core (admin), Tenant Portal, and Property Management — plus two extra utilities on the Core surface for minting word-list passphrases and generating branded portal images. ## What can you ask Claude to do? Day-to-day Cloudpath admin work, asked the way you'd ask a colleague: inventory and audits, bulk changes, DPSK and passphrase seeding, and portal branding. Claude can reach anything the Cloudpath REST API exposes to your admin account. For example: - *"List the authentication servers configured on this Cloudpath."* - *"How many registration lists exist, and how many entries does each hold?"* - *"Create ten memorable three-word passphrases and seed them into DPSK pool X."* - *"Generate a branded logo and favicon for this property portal and upload them."* - *"Show me the DPSK pools and which policies they're attached to."* ## Three surfaces from one connector | Object | Cloudpath surface | Served under | |---|---|---| | `core` | Core API (admin) | `/admin/publicApi` | | `tenant` | Tenant Portal API | `/admin/rest/tenant` | | `property` | Property Management API | `/admin/rest/propertyMgmtPortals/{portalGuid}` | Each object exposes the same six primitives for discovering and calling endpoints. `core` adds two more: [`generate_passphrase`](https://docs.neuralconfig.com/cp-mcp/generate-passphrase/) for word-list PSKs and [`generate_image`](https://docs.neuralconfig.com/cp-mcp/generate-image/) for branded portal logos and favicons. See [The three API surfaces](https://docs.neuralconfig.com/cp-mcp/surfaces/). ## How does Claude reach the API? Claude works against the live API through CP-MCP's one MCP tool. It searches an index of the 175 Cloudpath operations, reads the schema for the endpoint it needs, and calls it. CP-MCP attaches your credentials server-side, and you never pick endpoints yourself. For the mechanics, see [How code mode works](https://docs.neuralconfig.com/cp-mcp/how-code-mode-works/). ## Getting started 1. [Sign in and add your Cloudpath credentials](https://docs.neuralconfig.com/cp-mcp/getting-started/) 2. [Connect it to Claude](https://docs.neuralconfig.com/cp-mcp/connecting-claude/) 3. Try an [example](https://docs.neuralconfig.com/cp-mcp/examples/) ## How your credentials are handled Your Cloudpath password is encrypted at rest, decrypted only when minting a Cloudpath JWT, and never sent to Claude or included in any model context. See the [Security model](https://docs.neuralconfig.com/cp-mcp/security/). ## Explore the docs - [Getting started](https://docs.neuralconfig.com/cp-mcp/getting-started/): Sign in with Google, save your Cloudpath FQDN and admin credentials, and get your MCP URL. - [Connecting to Claude](https://docs.neuralconfig.com/cp-mcp/connecting-claude/): Add CP-MCP as a custom connector — the MCP URL and the OAuth consent. - [How code mode works](https://docs.neuralconfig.com/cp-mcp/how-code-mode-works/): Why CP-MCP exposes a single tool, and how Claude's TypeScript runs against Cloudpath. - [The three API surfaces](https://docs.neuralconfig.com/cp-mcp/surfaces/): How the core, tenant, and property objects map onto the Cloudpath APIs. - [Calling conventions](https://docs.neuralconfig.com/cp-mcp/calling-conventions/): How the call primitive behaves — its return shape, and how Cloudpath errors surface. - [Limits and quotas](https://docs.neuralconfig.com/cp-mcp/limits/): Timeouts, code size, Cloudpath rate limits, and the caps on each core utility. - [Generating passphrases](https://docs.neuralconfig.com/cp-mcp/generate-passphrase/): Mint memorable word-list DPSK passphrases instead of random strings. - [Generating portal images](https://docs.neuralconfig.com/cp-mcp/generate-image/): Render branded logos and favicons for a Cloudpath enrollment portal. - [Examples](https://docs.neuralconfig.com/cp-mcp/examples/): Worked examples of asking Claude to do real Cloudpath work end to end. - [Security model](https://docs.neuralconfig.com/cp-mcp/security/): Encryption at rest, server-side JWT minting, and what Claude never sees. - [Troubleshooting](https://docs.neuralconfig.com/cp-mcp/troubleshooting/): Failed credential verification, self-signed certificates, and connector problems. --- # Getting started Source: https://docs.neuralconfig.com/cp-mcp/getting-started/ Sign in with Google, save your Cloudpath admin credentials, verify them, and paste one URL into Claude. There is nothing to install. ## 1. Sign in Go to [cp-mcp.lanpulse.com](https://cp-mcp.lanpulse.com) and sign in with Google. That Google account is your CP-MCP identity — the same account backs both the web dashboard and the Claude connector. ## 2. Save your Cloudpath credentials In the dashboard, enter the admin credentials for your Cloudpath deployment: | Field | What it is | |---|---| | **FQDN** | The bare hostname of your Cloudpath server, e.g. `jeff245.cloudpath.net` — no `https://`, no path | | **Username** | Your Cloudpath admin username | | **Password** | The password for that admin account. It is write-only — once saved it is never shown again | | **Label** | Optional. Your own name for this deployment | :::caution The **FQDN must be a bare hostname** — no scheme and no path. Entering `https://jeff245.cloudpath.net` or a URL with a path is rejected with `fqdn must be a bare hostname (no scheme, no path)`. ::: It must also be **publicly routable**. Private addresses (`10.x`, `192.168.x`, `172.16–31.x`), loopback, link-local, CGNAT ranges, and internal-only names ending `.local`, `.internal`, or `.localhost` are rejected with `forbidden_host` — CP-MCP runs on Cloudflare, so it can only reach your Cloudpath across the public internet. See [Security model](https://docs.neuralconfig.com/cp-mcp/security/#the-fqdn-must-be-publicly-routable). The credentials you save are the ceiling on what Claude can do. CP-MCP does not layer its own role model on top of Cloudpath — access is governed entirely by the admin account you save here. Use Cloudpath's role-based access controls to scope that account. See [Security model](https://docs.neuralconfig.com/cp-mcp/security/#access-control). ## 3. Save & verify Click **Save & verify**. CP-MCP logs in to your Cloudpath by minting a real JWT against the Core surface, so a pass means the whole chain works: FQDN, username, and password. The dashboard confirms with *"Saved and verified."* and records a **Last verified** timestamp. :::caution Verification only runs when you **type a password**. If you leave the password field blank to keep the stored one — while changing the FQDN, say — the dashboard saves without checking anything and says *"Saved."* rather than *"Saved and verified."* Use **Test connection** to verify what is already stored. ::: If it fails, see [Troubleshooting](https://docs.neuralconfig.com/cp-mcp/troubleshooting/#save--verify-fails). :::note Your Cloudpath server must present a **publicly trusted TLS certificate**. Cloudflare Workers reject self-signed certificates on outbound HTTPS. Cloud-hosted Cloudpath at `*.cloudpath.net` already does; on-premises installs need a certificate from a public CA. See [Security model](https://docs.neuralconfig.com/cp-mcp/security/#tls-to-cloudpath). ::: ## 4. Copy your MCP URL and connect Claude Copy the MCP URL from the dashboard — `https://cp-mcp.lanpulse.com/mcp/` — then follow [Connecting to Claude](https://docs.neuralconfig.com/cp-mcp/connecting-claude/). The URL is the same for every user. What identifies *you* is the OAuth token Claude obtains when it connects, which is bound to your account. ## 5. Try it Ask Claude something concrete: > Using CP-MCP, list the authentication servers on my Cloudpath. Then something that needs discovery: > How many registration lists are configured, and how many entries are in each? ## The dashboard controls Three buttons sit under the credentials form. They do different things, and the difference matters when you are debugging: | Control | What it does | |---|---| | **Save & verify** | Writes the form to storage. Mints a test JWT **only if you typed a password** — otherwise it saves without verifying | | **Test connection** | Flushes the cached tokens and re-mints with the **stored** password, without you retyping it. Reports the new token's expiry on success, or the error code on failure | | **Delete** | Removes the stored credentials and flushes every cached token. Confirms first, because it stops the connector working | **Last verified** under the form shows the UTC timestamp of the most recent successful mint, from either **Save & verify** or **Test connection**. ## Changing deployments or rotating the password The MCP URL is static and tied to your account, not to a specific Cloudpath deployment. To point at a different Cloudpath, or to rotate the password, update the fields in the dashboard and save — no change is needed in Claude. Saving new credentials flushes the cached Cloudpath tokens, so the next call mints a fresh one. See [Security model](https://docs.neuralconfig.com/cp-mcp/security/#rotating-the-password). If you change the FQDN and leave the password blank, run **Test connection** afterwards — the save itself will not have checked that the new host accepts the stored credentials. ## Next steps - [Connecting to Claude](https://docs.neuralconfig.com/cp-mcp/connecting-claude/): Add the custom connector and approve the OAuth consent. - [Examples](https://docs.neuralconfig.com/cp-mcp/examples/): Worked prompts that produce real Cloudpath work end to end. - [Security model](https://docs.neuralconfig.com/cp-mcp/security/): How your password is encrypted, what is recorded, and how to revoke access. - [Troubleshooting](https://docs.neuralconfig.com/cp-mcp/troubleshooting/): What each verification failure means and how to clear it. --- # Connecting to Claude Source: https://docs.neuralconfig.com/cp-mcp/connecting-claude/ CP-MCP is a **remote** MCP server. There is nothing to install locally — you add one URL as a custom connector and authorize it. ## Add the connector 1. Copy your MCP URL from the [CP-MCP dashboard](https://cp-mcp.lanpulse.com) — `https://cp-mcp.lanpulse.com/mcp/`. 2. In Claude, go to **Settings → Connectors → Add custom connector**. 3. Paste the URL and add it. 4. Claude opens a consent page. Approve it. 5. The connector shows as connected, and `code_mode` appears in Claude's tools. :::note `code_mode` is the **only** tool the connector exposes, and CP-MCP publishes no MCP prompts. Everything you do runs through that one tool — see [How code mode works](https://docs.neuralconfig.com/cp-mcp/how-code-mode-works/). ::: ## What the consent flow is doing CP-MCP uses OAuth 2.1 with Dynamic Client Registration (RFC 7591) and PKCE: 1. Claude registers itself as an OAuth client automatically. 2. You are sent to CP-MCP to authorize, which requires your **Google session** — if you aren't signed in, you'll be asked to sign in first. 3. CP-MCP issues Claude an access token bound to your user account. That token is what makes the single shared MCP URL personal: every request Claude makes carries it, and CP-MCP resolves it to your account and your saved Cloudpath credentials. Tokens are stored hashed — CP-MCP never holds a raw copy of a token it issued. ## Where it works Any Claude surface that supports custom connectors — Claude Desktop, Claude on the web, and Claude Code. ## Verifying it's working Ask Claude: > Using CP-MCP, list the authentication servers on my Cloudpath. If the connector is live but your Cloudpath credentials are missing or wrong, Claude gets a structured error telling it to save credentials in the dashboard, rather than a silent empty result — that distinction is useful when debugging. ## When authorization loops or fails - **Redirect loop / "too many redirects"** — usually a browser cookie problem during the Google sign-in hop. Try a normal (non-private) window, allow cookies for `cp-mcp.lanpulse.com`, and re-add the connector. - **"Not authenticated" on every call** — the token was revoked, or the connector was added before you finished signing in. Remove the connector and add it again. - **Connected, but every call errors about credentials** — the MCP side is fine and the *Cloudpath* side isn't. Go back to the dashboard and click **Test connection**, which re-mints against the stored password and names the exact failure. See [Troubleshooting](https://docs.neuralconfig.com/cp-mcp/troubleshooting/). ## Revoking access Remove the connector in Claude to stop it making requests. To cut off Cloudpath access entirely, delete your credentials in the dashboard — that takes effect immediately, with no window in which a cached token still works. See [Security model](https://docs.neuralconfig.com/cp-mcp/security/#stopping-claudes-access). Removing the connector does **not** delete your saved Cloudpath credentials. ## Next steps - [Examples](https://docs.neuralconfig.com/cp-mcp/examples/): Prompts that produce real Cloudpath work once the connector is live. - [How code mode works](https://docs.neuralconfig.com/cp-mcp/how-code-mode-works/): Why there is only one tool, and what Claude does with it. - [Troubleshooting](https://docs.neuralconfig.com/cp-mcp/troubleshooting/): Authorization loops, credential errors, and every error code. --- # How code mode works Source: https://docs.neuralconfig.com/cp-mcp/how-code-mode-works/ CP-MCP publishes exactly one MCP tool, `code_mode`. Claude passes it the body of an async arrow function; the Worker runs that code in a sandboxed V8 isolate, and the value the function returns becomes the MCP response. ## Why one tool Cloudpath's REST API spans three surfaces and **175 operations** in the indexed spec. Registering each as its own MCP tool would overwhelm Claude's tool list and waste context on metadata the model rarely needs. Instead, the single `code_mode` tool gives Claude three namespaced objects it drives from code: - `core` — Cloudpath Core API (admin) - `tenant` — Tenant Portal API - `property` — Property Management API Because the code runs in an isolate, Claude can plan, search, loop, and post-process across many calls without a tool-call round-trip per step. See [The three API surfaces](https://docs.neuralconfig.com/cp-mcp/surfaces/) for what each object reaches. ```mermaid flowchart LR claude["Claude
writes the program"] subgraph cf["Cloudflare (CP-MCP)"] isolate["Sandboxed V8 isolate
runs the code_mode program"] end subgraph cloudpath["Cloudpath REST API"] core["core surface"] tenant["tenant surface"] property["property surface"] end claude -->|"code_mode(program)"| isolate isolate -->|"core.call()"| core isolate -->|"tenant.call()"| tenant isolate -->|"property.call()"| property isolate -->|"return value → MCP response"| claude ``` ## The six primitives Each of the three objects exposes the same six primitives: | Primitive | What it does | |---|---| | `list_tag_groups` | The tag groups the surface's index defines | | `list_tags` | Endpoint categories, optionally scoped to a group, each with an operation count | | `search_endpoints` | Keyword search over the spec index, ranked by relevance | | `list_endpoints_by_tag` | Enumerate operations under a known tag | | `get_endpoint_details` | Full parameters, request body, and response schema for one operation | | `call` | Execute an authenticated call against an endpoint on that surface | `core` carries two extra utilities on top of these six: [`generate_passphrase`](https://docs.neuralconfig.com/cp-mcp/generate-passphrase/) and [`generate_image`](https://docs.neuralconfig.com/cp-mcp/generate-image/). :::note The Cloudpath REST API User Guide has no group hierarchy above its endpoint categories, so the extracted index gives each surface a **single tag group named `All`**. `list_tag_groups` is therefore a thin call — [`list_tags`](https://docs.neuralconfig.com/cp-mcp/surfaces/#what-each-surface-holds) and `search_endpoints` are the useful places to start. ::: ## The workflow A typical `code_mode` program follows the same shape you would with the docs open: 1. **Orient** with `core.list_tag_groups()` / `core.list_tags()` (or the `tenant` / `property` equivalents) when you don't know where to look. 2. **Discover** with `search_endpoints({ query, tag?, method? })` or `list_endpoints_by_tag({ tag })`. 3. **Inspect** with `get_endpoint_details({ method, path })` when you need the property table or examples. 4. **Execute** with `call({ method, path, pathParams?, query?, body? })`. For the `property` surface, also pass `propertyMgmtPortalGuid`. Search results usually carry enough detail that Claude calls `call` directly without a separate `get_endpoint_details` fetch. ## Where the index comes from Cloudpath publishes no OpenAPI document — the REST API is specified only in the **Cloudpath Enrollment System REST API User Guide** for release 6.0. CP-MCP extracts that PDF once and ships the result in two pieces: | Piece | Where it lives | Used by | |---|---|---| | Slim index — method, path, tags, summary, parameter hints, return codes | Bundled into the Worker | `list_tag_groups`, `list_tags`, `search_endpoints`, `list_endpoints_by_tag` | | Full spec — per-endpoint property tables and descriptions | Object storage (Cloudflare R2) | `get_endpoint_details` | The slim index runs in memory with no I/O. The full spec is lazy-loaded the first time `get_endpoint_details` needs it in a given isolate, which adds a few hundred milliseconds to that one call and nothing afterwards. Because the index is extracted from a release-pinned document, it describes Cloudpath 6.0. An endpoint your deployment has but the guide doesn't will not appear in `search_endpoints` — you can still `call` it by path. ## Limits - The code you pass must be **under 20,000 characters**. - Each `code_mode` invocation has a **20-second wall-clock budget**; a program that exceeds it is aborted. - Each individual Cloudpath request inside it times out after **15 seconds**. See [Limits and quotas](https://docs.neuralconfig.com/cp-mcp/limits/) for the full set, including Cloudpath's own rate limits. ## Next steps - [The three API surfaces](https://docs.neuralconfig.com/cp-mcp/surfaces/): What core, tenant, and property each reach, and how big each one is. - [Calling conventions](https://docs.neuralconfig.com/cp-mcp/calling-conventions/): Return shape, pagination, filtering, and how errors surface. - [Limits and quotas](https://docs.neuralconfig.com/cp-mcp/limits/): Every timeout, size cap, and rate limit in one place. - [Examples](https://docs.neuralconfig.com/cp-mcp/examples/): What a good request looks like when Claude is writing the program. --- # The three API surfaces Source: https://docs.neuralconfig.com/cp-mcp/surfaces/ CP-MCP's single `code_mode` tool exposes three namespaced objects, one per Cloudpath API surface. You pick the object that matches the API you need; each handles its own base path and authentication. | Object | Cloudpath surface | Path prefix (added for you) | |---|---|---| | `core` | Core API (admin) | `/admin/publicApi` | | `tenant` | Tenant Portal API | `/admin/rest/tenant` | | `property` | Property Management API | `/admin/rest/propertyMgmtPortals/{portalGuid}` | ## What each surface holds The three surfaces are nothing like the same size. `core` is the Cloudpath admin API and carries the overwhelming majority of the operations; `tenant` and `property` are small, focused portals. | Object | Operations | Tags | Covers | |---|---|---|---| | `core` | 143 | 26 | Authentication servers, certificates and templates, DPSK pools and devices, enrollment, policies, RADIUS clients and attribute groups, registration lists, network segmentation groups, users and groups, management portals | | `tenant` | 13 | 2 | Tenant Portal properties and units | | `property` | 19 | 2 | Management portal and units, scoped to one property-management portal | Counts are from the indexed Cloudpath 6.0 REST API guide — 175 operations in total. If you don't know which surface owns a resource, search `core` first. ## Paths are relative to the surface root You pass the **raw spec path** relative to each surface's root — CP-MCP adds the prefix. So on the `core` surface you call `/authServers`, not `/admin/publicApi/authServers`. ```ts // Core surface — the /admin/publicApi prefix is added automatically. async () => { const r = await core.call({ method: 'GET', path: '/authServers' }); return { status: r.status, total: r.body?.page?.totalCount }; } ``` ## The property surface needs a portal GUID Every Property Management call is scoped to a specific property-management portal, whose GUID is part of the base URL. On the `property` surface you must pass `propertyMgmtPortalGuid` — it is substituted into the path prefix. ```ts // Property surface — propertyMgmtPortalGuid is required. async () => { const r = await property.call({ method: 'GET', path: '/properties', propertyMgmtPortalGuid: 'pmp-guid-here', }); return r.body; } ``` :::caution Omitting `propertyMgmtPortalGuid` on a `property` call returns a structured error (`missing_property_portal`) — the call cannot be scoped without it. The parameter is ignored on the `core` and `tenant` surfaces. ::: ## Extra utilities on core The `core` object carries two utilities beyond the six shared primitives that do **not** proxy a Cloudpath REST call: - [`generate_passphrase`](https://docs.neuralconfig.com/cp-mcp/generate-passphrase/) — mint memorable word-list passphrases for PSKs and registration passwords. - [`generate_image`](https://docs.neuralconfig.com/cp-mcp/generate-image/) — render branded logos and favicons for property and management portals. Both are only on `core`; `tenant` and `property` expose the six primitives only. Neither utility touches Cloudpath, so both work even before your credentials verify. ## Next steps - [Calling conventions](https://docs.neuralconfig.com/cp-mcp/calling-conventions/): How call behaves across all three surfaces — return shape, pagination, filters, errors. - [How code mode works](https://docs.neuralconfig.com/cp-mcp/how-code-mode-works/): The six primitives in detail, and where the endpoint index comes from. - [Examples](https://docs.neuralconfig.com/cp-mcp/examples/): Prompts that exercise each surface, including a property-scoped call. --- # Calling conventions Source: https://docs.neuralconfig.com/cp-mcp/calling-conventions/ The `call` primitive is the workhorse on every surface. It substitutes path placeholders, builds the query string, attaches Cloudpath authentication, and returns the response. This page covers the conventions that apply to every `call`. ## The return shape `call` always resolves to the same object: ```ts { ok, status, statusText, headers, body, durationMs, url, retried } ``` :::caution **HTTP errors are never thrown.** A `404` or `500` from Cloudpath comes back as a resolved result with `ok: false` and the status code — always inspect `ok` and `status` rather than relying on a thrown exception. ::: ```ts async () => { const r = await core.call({ method: 'GET', path: '/authServers' }); if (!r.ok) return { error: r.status, body: r.body }; return { total: r.body?.page?.totalCount }; } ``` ## Path parameters Path templates use `{paramName}` placeholders. Pass values via `pathParams`: ```ts await core.call({ method: 'GET', path: '/dpskPools/{guid}/dpsks', pathParams: { guid: 'pool-guid-here' }, }); ``` ## Pagination Cloudpath list responses use a pagination envelope: ```ts { page: { page, pageSize, totalCount }, contents: [ /* ... */ ] } ``` Request a page with `?page=N` via the `query` parameter. `pageSize` is server-controlled (up to 1000 records), so read it back from the response rather than setting it. ```ts async () => { const all = []; for (let page = 1; page <= 10; page++) { const r = await core.call({ method: 'GET', path: '/registrationLists', query: { page } }); if (!r.ok) return { error: r.status, body: r.body }; const contents = r.body?.contents ?? []; all.push(...contents); const info = r.body?.page ?? {}; const pageSize = info.pageSize ?? contents.length; const totalPages = pageSize > 0 ? Math.ceil((info.totalCount ?? 0) / pageSize) : 1; if (page >= totalPages || contents.length === 0) break; } return { count: all.length }; } ``` ## Filtering and sorting Both are query-string conventions on Cloudpath list endpoints: | | Syntax | Notes | |---|---|---| | **Filter** | `?filter=prop(op:val),prop2(op:val2)` | Operators: `like`, `eq`, `in`. For `in`, the value is underscore-separated: `val1_val2_val3` | | **Sort** | `?orderBy=prop:desc,prop2:asc` | Comma-separated `prop:direction` pairs | Filtering server-side is worth the effort on large lists: it costs one request instead of a pagination loop, and a loop is the usual way to hit Cloudpath's rate limit or the 20-second budget. ## Nested resources hide their parent GUID Some Cloudpath responses — DPSK entries are the common case — don't repeat the parent's GUID as a field. It appears only inside the entry's `links[].href` URL. If you need the pool GUID for a follow-up call, parse it out of that URL rather than expecting a `poolGuid` property. ## Rate limits Cloud-hosted Cloudpath enforces its own limits, independent of anything CP-MCP does: | Limit | Value | |---|---| | API requests | 180 per minute, per tenant | | Token mints | 10 per minute | The token cache keeps CP-MCP well clear of the mint cap — a normal session mints once per surface every few minutes. The request cap is the one a wide-ranging program can reach, so prefer filters over pagination loops. See [Limits and quotas](https://docs.neuralconfig.com/cp-mcp/limits/). ## Authentication and the automatic retry Authentication is handled for you. CP-MCP mints a short-lived Cloudpath JWT from your saved credentials, caches it, and attaches it to every request as the raw `Authorization` header (Cloudpath does **not** accept a `Bearer` prefix). If Cloudpath returns `401` or `406`, CP-MCP mints a fresh token once and retries the call exactly one time. The `retried` field in the response is `1` when that happened. Persistent auth failures surface to the model as `ok: false`. See the full lifecycle in [Security model](https://docs.neuralconfig.com/cp-mcp/security/#the-cloudpath-jwt). Failures *before* Cloudpath is reached — no saved credentials, an unroutable FQDN, a malformed request — also come back as a resolved result, but with `status: 0` and `statusText: "cp_auth_error"`, and the reason in `body.error`. Treat `status: 0` as "the call never left CP-MCP". The codes are listed in [Troubleshooting](https://docs.neuralconfig.com/cp-mcp/troubleshooting/#every-error-code-and-what-clears-it). ## Sending a body For JSON, pass a plain object/array/string/number/boolean as `body`. For **binary uploads** (multipart image uploads), you must base64-encode the raw request bytes and pass them as `bodyBase64` instead — the isolate marshals tool arguments through `JSON.stringify`, which silently destroys `Uint8Array`, `FormData`, and `Blob`. Passing both `body` and `bodyBase64` returns an `invalid_body` error. See [Generating portal images](https://docs.neuralconfig.com/cp-mcp/generate-image/#uploading-the-image) for the worked multipart pattern. Both `body` and `bodyBase64` are ignored on `GET` and `DELETE` — if a Cloudpath endpoint needs data on one of those verbs, put it in `query`. ## Next steps - [The three API surfaces](https://docs.neuralconfig.com/cp-mcp/surfaces/): Which object owns which Cloudpath API, and how big each surface is. - [Limits and quotas](https://docs.neuralconfig.com/cp-mcp/limits/): Timeouts, code size, rate limits, and per-utility caps. - [Generating portal images](https://docs.neuralconfig.com/cp-mcp/generate-image/): The worked multipart upload, including the field name that silently blanks an image. - [Troubleshooting](https://docs.neuralconfig.com/cp-mcp/troubleshooting/): Every error code, what it means, and what clears it. --- # Limits and quotas Source: https://docs.neuralconfig.com/cp-mcp/limits/ Every limit that can end a CP-MCP request, in one place. Most of them only matter when you ask Claude for something broad — "audit every property", "list every enrollment" — which is the case worth knowing about in advance. ## The code_mode invocation | Limit | Value | What happens when you hit it | |---|---|---| | Wall-clock budget | **20 seconds** | The invocation is aborted with `code_mode exceeded 20000ms wallclock budget`. Work already done is lost | | Code size | **20,000 characters** | Rejected before execution with *"code must be under 20,000 characters"* | | Single Cloudpath request | **15 seconds** | That one `call` resolves with `ok: false`; the rest of the program keeps running | The two timeouts compose badly on purpose: three slow Cloudpath calls in sequence can exhaust the 20-second budget on their own. A program that has to touch many resources should be split across several turns rather than looped inside one invocation. :::tip Ask for one scope at a time — one DPSK pool, one property, one page of a list. Claude will happily write a program that walks everything, and that program is the one that times out. ::: ## Cloudpath's own rate limits These are enforced by Cloudpath on cloud-hosted deployments, not by CP-MCP. Exceeding them produces an error from Cloudpath, which reaches you as a normal `ok: false` result with Cloudpath's status. | Limit | Value | Notes | |---|---|---| | API requests | **180 per minute, per tenant** | Shared with everything else hitting that Cloudpath — other integrations count | | Token mints | **10 per minute** | CP-MCP caches tokens for up to 240 seconds, so a normal session mints once per surface every few minutes | The mint cap is effectively unreachable through CP-MCP. The request cap is reachable: a pagination loop over a large list can issue dozens of requests in a few seconds. Prefer a server-side `filter` over fetching every page — see [Calling conventions](https://docs.neuralconfig.com/cp-mcp/calling-conventions/#filtering-and-sorting). ## Pagination `pageSize` is chosen by Cloudpath, not by you, and tops out at **1,000 records** per page. Read it back from the response envelope rather than assuming a value. ## Token cache | Property | Value | |---|---| | Maximum cache lifetime | 240 seconds | | Upstream JWT lifetime | ~5 minutes | | Safety margin | 60 seconds before the upstream expiry | | Automatic retries on `401`/`406` | Exactly 1 | The margin is why a cached token is never used within a minute of expiring, and the single retry is why a token that expires mid-request costs you one extra round trip rather than an error. See [Security model](https://docs.neuralconfig.com/cp-mcp/security/#the-cloudpath-jwt). ## generate_passphrase | Parameter | Range | Default | |---|---|---| | `count` | 1–100 passphrases per call | 1 | | `wordCount` | 2–8 words | 3 | | `separator` | up to 3 characters | `-` | Need more than 100? Call it again in the same program — it is a local computation with no upstream request, so repeated calls cost nothing but time. See [Generating passphrases](https://docs.neuralconfig.com/cp-mcp/generate-passphrase/). ## generate_image Image dimensions are fixed per target and cannot be overridden. | Target | Output | AI call | |---|---|---| | `tenant_logo`, `mgmt_logo` | 1024×256 PNG | Yes — one Workers AI generation per call | | `tenant_favicon`, `mgmt_favicon` | 128×128 PNG | No | Logo generation is the slowest thing CP-MCP does and the only part that bills AI compute, so it counts against the 20-second budget more than anything else. Generating a logo and a favicon and uploading both in one invocation is routine; generating a dozen logos in one is not. See [Generating portal images](https://docs.neuralconfig.com/cp-mcp/generate-image/). ## Next steps - [How code mode works](https://docs.neuralconfig.com/cp-mcp/how-code-mode-works/): What runs inside the isolate, and where the endpoint index comes from. - [Calling conventions](https://docs.neuralconfig.com/cp-mcp/calling-conventions/): Pagination, filtering, and the structured error shape. - [Troubleshooting](https://docs.neuralconfig.com/cp-mcp/troubleshooting/): Telling a budget timeout apart from a single slow call. --- # Generating passphrases Source: https://docs.neuralconfig.com/cp-mcp/generate-passphrase/ `core.generate_passphrase` is a utility on the `core` object that mints memorable word-list passphrases such as `apple-red-truck`. It does **not** proxy a Cloudpath REST call — it draws words from a bundled word list and hands the strings back to your code, where you can feed them straight into a `core.call(...)` in the same `code_mode` invocation. The intended use is seeding Cloudpath **DPSK pools**, **MPSK entries**, and **registration-list passwords**: mint the secret and store it in one call, so the generated value never round-trips the MCP transport before Cloudpath has it. ## Parameters | Parameter | Range | Default | Meaning | |---|---|---|---| | `count` | 1–100 | `1` | How many passphrases to generate | | `wordCount` | 2–8 | `3` | Words per passphrase | | `separator` | up to 3 chars | `"-"` | String joining the words | Words are drawn uniformly at random from the **EFF Short Word List** using `crypto.getRandomValues`. The bundled list holds **1,294 words of three to five letters** — the published EFF list with internal-hyphen entries removed, so a word can never be mistaken for the `-` separator. ## Entropy Three words drawn from 1,294 is roughly **31 bits of entropy** — comfortably above the 8-character Wi-Fi PSK floor — while staying memorable enough to read aloud. Increase `wordCount` if you need more: each extra word adds about 10.3 bits, so the 8-word maximum is roughly 83 bits. With the defaults, a passphrase is **11 to 17 characters** long (three words of three to five letters, joined by two separators) — always past the 8-character minimum, and short enough to type on a phone keypad. ## Return shape ```ts { passphrases: string[], wordListSize: number } ``` `wordListSize` reports the size of the bundled list — currently `1294`. It is surfaced so the model can reason about entropy without hard-coding the size. ## Example: seed a DPSK pool ```ts async () => { const { passphrases } = await core.generate_passphrase({ count: 10 }); // ...then core.call({ method: 'POST', path: '/dpskPools/{guid}/dpsks', ... }) for each. return { generated: passphrases.length, sample: passphrases.slice(0, 3) }; } ``` The defaults (`count` 1, `wordCount` 3, separator `-`) match the standalone cp-word-list enrollment webhook exactly, so passphrases minted here look the same as those generated during Cloudpath enrollment. ## Next steps - [Calling conventions](https://docs.neuralconfig.com/cp-mcp/calling-conventions/): How to POST the minted passphrases into a DPSK pool in the same invocation. - [Limits and quotas](https://docs.neuralconfig.com/cp-mcp/limits/): The 100-per-call cap, and why repeated calls are cheap. - [CP Word List](https://docs.neuralconfig.com/cp-word-list/): The standalone service that swaps Cloudpath's random DPSK for a passphrase at enrollment time. --- # Generating portal images Source: https://docs.neuralconfig.com/cp-mcp/generate-image/ `core.generate_image` is a utility on the `core` object that renders a branded PNG for a Cloudpath tenant-property portal or management portal. It runs a small image pipeline inside the Worker and hands the PNG bytes back to your code as base64, so you can upload them through `core.call` in the same `code_mode` invocation — the bytes never round-trip the MCP transport. ## Four targets, two upload paths Each target maps 1:1 to a Cloudpath upload endpoint. Logos are 4:1 banners; favicons are square monogram tiles. | Target | Size | Uploads to | |---|---|---| | `tenant_logo` | 1024×256 | `POST /properties/{guid}/uiConfiguration/logoFile` | | `tenant_favicon` | 128×128 | `POST /properties/{guid}/uiConfiguration/favicon` | | `mgmt_logo` | 1024×256 | `POST /mgmtPortals/{guid}/uiConfiguration/logoFile` | | `mgmt_favicon` | 128×128 | `POST /mgmtPortals/{guid}/uiConfiguration/favicon` | Logos combine an AI-generated background (Workers AI, `@cf/black-forest-labs/flux-1-schnell`) with a typography overlay. Favicons skip the AI step entirely and render a pure monogram tile. ## Parameters | Parameter | Required for | Notes | |---|---|---| | `target` | all | One of the four targets above | | `primaryText` | logo targets | The main line of text | | `backgroundPrompt` | logo targets | Subject and mood for the AI background | | `monogram` | favicon targets | 1–2 characters | | `primaryColor` | all | Hex, e.g. `#0D2B45` | | `accentColor` | all | Hex | | `secondaryText` | optional | Second line on logos | | `textColor` | optional | Hex, default `#FFFFFF` | | `font` | optional | `inter` (default), `playfair`, `montserrat`, `dm-serif` | | `layout` | optional, logos only | `left-gradient` (default), `center-vignette`, `bottom-bar` | :::note `layout` has no effect on favicon targets — a favicon is always a centred monogram on a radial tile. `font`, `primaryColor`, `accentColor`, and `textColor` do apply to favicons. ::: :::tip For logo targets, a banner-composition aspect hint is auto-prepended to your `backgroundPrompt` so the AI output survives the 4:1 cover-crop. Focus your prompt on subject and mood — the aspect guidance is handled for you. ::: ## Return shape ```ts { imageBase64, mimeType, width, height, suggestedFilename, model, aiGenerationMs, overlayMs } ``` `model` is the Workers AI model used, or `null` for favicons (which make no AI call). `suggestedFilename` is the value to use in the multipart upload's text field — see below. Like `call`, `generate_image` **never throws**. A failure — an invalid parameter combination, or Workers AI returning no image — comes back as a resolved object instead: ```ts { ok: false, error: 'generate_image_failed', message: '...' } ``` Check for `imageBase64` (or for `ok === false`) before using the result, or the upload that follows will send an empty body. ## Uploading the image The four `uiConfiguration` upload endpoints expect **multipart/form-data**, and uploading them has two gotchas that will silently corrupt the result if missed: :::danger The text field that names the stored file is **`filename` for logoFile endpoints** and **`iconFilename` for favicon endpoints**. Cloudpath reads that form field — **not** the `Content-Disposition: filename=` attribute. A wrong or missing field name returns `200 OK` and **silently clears the stored image**. ::: Second, because the isolate marshals tool arguments through `JSON.stringify`, you cannot pass a `Uint8Array`, `FormData`, or `Blob` as `body` — assemble the multipart body yourself, base64-encode it, and pass it as `bodyBase64` with the `Content-Type` (including the boundary) set in `headers`. See [Calling conventions](https://docs.neuralconfig.com/cp-mcp/calling-conventions/#sending-a-body). ```ts // Generate + upload a property portal logo in a single code_mode call. async () => { const img = await core.generate_image({ target: 'tenant_logo', primaryText: 'HARBORVIEW RESIDENCES', backgroundPrompt: 'aerial view of a luxury waterfront community at golden hour', primaryColor: '#0D2B45', accentColor: '#C8960C', font: 'playfair', }); // Decode the returned PNG bytes. const bin = atob(img.imageBase64); const png = new Uint8Array(bin.length); for (let i = 0; i < bin.length; i++) png[i] = bin.charCodeAt(i); // Build the multipart body: a `filename` text part + the `file` image part. const boundary = '----cp-mcp-' + Math.random().toString(36).slice(2); const enc = new TextEncoder(); const head = enc.encode( `--${boundary}\r\nContent-Disposition: form-data; name="filename"\r\n\r\n${img.suggestedFilename}\r\n` + `--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${img.suggestedFilename}"\r\nContent-Type: image/png\r\n\r\n` ); const tail = enc.encode(`\r\n--${boundary}--\r\n`); const body = new Uint8Array(head.length + png.length + tail.length); body.set(head, 0); body.set(png, head.length); body.set(tail, head.length + png.length); // Base64-encode in chunks (spread would overflow the call stack), one btoa at the end. let s = ''; for (let i = 0; i < body.length; i += 0x8000) { const sub = body.subarray(i, i + 0x8000); for (let j = 0; j < sub.length; j++) s += String.fromCharCode(sub[j]); } const r = await core.call({ method: 'POST', path: '/properties/{guid}/uiConfiguration/logoFile', pathParams: { guid: 'your-property-guid' }, headers: { 'Content-Type': `multipart/form-data; boundary=${boundary}` }, bodyBase64: btoa(s), }); return { uploaded: r.ok, status: r.status, model: img.model }; } ``` :::note Image generation uses paid Workers AI compute for logo targets (favicons do not). If you only need a favicon, its target makes no AI call. ::: ## Next steps - [Calling conventions](https://docs.neuralconfig.com/cp-mcp/calling-conventions/): Why binary uploads go through bodyBase64, and what call returns. - [Limits and quotas](https://docs.neuralconfig.com/cp-mcp/limits/): Fixed output sizes, and why logo generation dominates the 20-second budget. - [Troubleshooting](https://docs.neuralconfig.com/cp-mcp/troubleshooting/): What to check when an upload succeeds but the portal image is blank. --- # Examples Source: https://docs.neuralconfig.com/cp-mcp/examples/ CP-MCP works best when you ask for an *outcome* and let Claude work out the calls. These are patterns that reliably produce good results. ## Inventory and orientation > List the authentication servers on my Cloudpath, and show the type of each. Claude searches the API index for the relevant endpoint, calls it, and returns a table. You get the answer, not raw JSON. > How many registration lists are configured, and how many entries does each > hold? This needs pagination — Claude loops over `?page=N` inside the sandbox and joins the results. See [Calling conventions](https://docs.neuralconfig.com/cp-mcp/calling-conventions/#pagination). ## DPSK and passphrases > Create ten memorable three-word passphrases and add them to DPSK pool > `pool-guid`. Claude mints the passphrases with [`core.generate_passphrase`](https://docs.neuralconfig.com/cp-mcp/generate-passphrase/) and POSTs them into the pool in the same call, so the secrets never leave the isolate before Cloudpath has them. > Show me the DPSK pools and which policies they're attached to. ## Branded portal images > Generate a logo and favicon for the Harborview property portal — navy and > gold, an aerial waterfront background — and upload both. Claude renders the images with [`core.generate_image`](https://docs.neuralconfig.com/cp-mcp/generate-image/) and uploads them through `core.call` in one invocation. Name your colors and the mood you want; the 4:1 banner crop is handled for you. ## Property Management surface > Using property portal `pmp-guid`, list the properties and their management > status. Property Management calls are scoped to a portal GUID — name it (or the GUID) and Claude passes it as `propertyMgmtPortalGuid`. See [The three API surfaces](https://docs.neuralconfig.com/cp-mcp/surfaces/#the-property-surface-needs-a-portal-guid). ## Tips - **Say which surface.** "On the tenant portal…" or "on the property portal `pmp-guid`…" removes ambiguity when the same resource name exists on more than one surface. - **Ask for the evidence.** "Show me the raw response" is always reasonable, and useful when a write didn't take. - **Scope narrowly.** A single `code_mode` invocation has a 20-second budget — one pool or one property at a time is faster than "audit everything". See [Limits and quotas](https://docs.neuralconfig.com/cp-mcp/limits/). ## Next steps - [Calling conventions](https://docs.neuralconfig.com/cp-mcp/calling-conventions/): The mechanics behind these prompts — pagination, filters, and error handling. - [Generating passphrases](https://docs.neuralconfig.com/cp-mcp/generate-passphrase/): Parameters and entropy for the DPSK seeding example. - [Generating portal images](https://docs.neuralconfig.com/cp-mcp/generate-image/): Targets, parameters, and the multipart upload behind the branding example. - [Limits and quotas](https://docs.neuralconfig.com/cp-mcp/limits/): What to keep in mind before asking for something broad. --- # Security model Source: https://docs.neuralconfig.com/cp-mcp/security/ CP-MCP is a thin adapter that holds your Cloudpath admin credentials on your behalf, authenticates every request to a single user, and never exposes secrets to Claude. This page covers the posture in detail. ## Two sign-in flows, one identity Two independent OAuth flows authenticate the two things that reach the server: your browser (to manage credentials) and Claude (to make MCP requests). - **Web dashboard** — Google OAuth (Authorization Code + PKCE) issues a session cookie `cpmcp_session`. Only the SHA-256 hash of the cookie is stored. - **MCP connector** — Claude registers via OAuth 2.1 Dynamic Client Registration (RFC 7591) + PKCE. The access token (prefix `cpmk_`) is stored hashed and bound to your user id; the hash is checked before any Cloudpath call. Both flows resolve to the same user record, so your connector identity and your dashboard identity are one and the same. ## Credentials at rest Your Cloudpath password is stored in Cloudflare D1, encrypted with **AES-256-GCM** using a per-record key derived via **HKDF-SHA256** from a root `ENCRYPTION_KEY` that lives only in Cloudflare's encrypted secret store. The plaintext password is decrypted in memory only when a new Cloudpath JWT needs to be minted — it is never returned to Claude, written to a log line, or placed in any tool response. ## The Cloudpath JWT Cloudpath authenticates API callers with a short-lived JWT (~5 minute lifetime) minted by posting your username and password to `/token` on the relevant surface root. CP-MCP manages that lifecycle server-side: 1. On every `call`, CP-MCP first loads your credential record from D1. If there isn't one, the call stops there with `no_credentials` — the token cache is never consulted. 2. It then reads a cached JWT from a per-user, per-surface key-value entry (Cloudflare KV, `cp_token::`). Property-surface entries add the portal GUID — `cp_token::property:` — because each portal authenticates separately. 3. On a miss, it decrypts your password just long enough to mint a fresh JWT, then caches it — capped at 240 seconds, a safety margin below the upstream's ~5-minute expiry. 4. The JWT is attached to every upstream request as the raw `Authorization` header — Cloudpath does not accept a `Bearer` prefix. 5. A `401` or `406` from Cloudpath triggers exactly one automatic re-mint and retry. Repeated auth failures surface to the model as `ok: false`. Claude never handles the JWT directly. ```mermaid sequenceDiagram participant I as code_mode isolate participant W as CP-MCP Worker participant D as D1 (credentials) participant K as KV (token cache) participant CP as Your Cloudpath I->>W: core.call({ method, path }) W->>D: Load credential record D-->>W: Encrypted password (or nothing → no_credentials) W->>K: Read cp_token:user:surface alt Cached token still valid K-->>W: JWT else Miss or expired W->>W: Decrypt password in memory W->>CP: POST /token (username + password) CP-->>W: JWT + expireDateTime W->>K: Cache JWT (max 240s) end W->>CP: Request with raw Authorization: JWT opt Cloudpath answers 401 or 406 W->>W: Flush the cached token, re-mint once W->>CP: Retry the request exactly once end CP-->>W: Response W-->>I: { ok, status, body, retried } ``` ## TLS to Cloudpath Cloudflare Workers enforce strict TLS on outbound `fetch` and do not expose a skip-verify option, so **your Cloudpath deployment must present a publicly trusted TLS certificate**. - Cloud-hosted Cloudpath at `*.cloudpath.net` already presents a valid certificate — nothing to do. - On-premises installs with a **self-signed certificate will not work**. Install a certificate from a public CA. ## The FQDN must be publicly routable CP-MCP refuses to connect to hosts that are not on the public internet, both when you save credentials and on every subsequent call. This stops the Worker from being used as a confused deputy to probe private networks from Cloudflare's egress. Rejected with a `forbidden_host` error: | Rejected | Examples | |---|---| | Private IPv4 ranges | `10.x`, `172.16–31.x`, `192.168.x` | | Loopback and unspecified | `127.x`, `0.x`, `::1` | | Link-local and CGNAT | `169.254.x`, `100.64–127.x`, `fe80::` | | Multicast and reserved IPv4 | `224.x` through `255.x` | | Unique-local and multicast IPv6 | `fc00::`/`fd00::`, `ff00::` | | IPv4-mapped IPv6 of any range above | `::ffff:10.0.0.1` | | IPv4 written in anything but dotted decimal, public or not | `2130706433`, `0x7f.0.0.1`, `0177.0.0.1`, `127.1` | | Internal hostnames | `localhost`, `broadcasthost`, and anything ending `.local`, `.internal`, or `.localhost` | A single trailing dot is stripped before these checks, so `127.0.0.1.` is rejected like `127.0.0.1`. So an on-premises Cloudpath must be reachable at a **public FQDN with a public IP**, not at an internal address or a split-horizon internal-only name. A publicly resolvable name that happens to resolve to a private address is not blocked by this check — it will simply fail to connect. ## Access control CP-MCP deliberately does **not** layer its own role model on top of Cloudpath. Claude can reach any endpoint the saved admin account is permitted to call, for any HTTP method — there is no separate read-only mode or confirm-before-write gate in CP-MCP. Access is governed entirely by the Cloudpath account you save. Use Cloudpath's role-based access controls to scope that account by role and permission if you want to limit what Claude can do. ## Stopping Claude's access To cut Claude off from your Cloudpath — offboarding, responding to a suspected leak, or just pausing while you investigate — **delete your saved credentials in the dashboard**. Deletion is immediate and complete: - The encrypted password row is deleted from D1. - Every cached JWT is flushed from KV at the same time. - Crucially, the credential record is checked **before** the token cache on every call, so even a token that survived the flush can never be used. There is no window in which a cached JWT keeps working. You do not need to rotate the Cloudpath password to make the cutover take effect. Rotate it if the password itself may have been exposed — that is a separate concern from revoking CP-MCP's access. :::note Removing the connector in Claude stops Claude from making requests, but it does **not** delete your saved Cloudpath credentials. Deleting the credentials is what revokes access to Cloudpath. ::: ## Rotating the password - **Rotate** — paste the new password in the dashboard and save. The old ciphertext is replaced, and the next call mints a fresh JWT with the new password. No change is needed in Claude. - **Saving new credentials flushes the cached JWTs**, so switching deployments takes effect on the next call. ## What CP-MCP records Every `tools/call` writes one usage row, and each `generate_image` writes one cost row. Both are metadata only: | Recorded | Fields | |---|---| | MCP usage | Your user id, timestamp, tool name (`code_mode`), `ok`/`error`, duration in milliseconds | | Image generation | Your user id, timestamp, target, model, neuron cost, AI and overlay durations, `ok`/`error` | | Terms acceptance | Your user id, the terms and privacy versions accepted, timestamp, IP address, and user agent | Nothing about the *content* of a request is stored: not the TypeScript Claude wrote, not the arguments it passed, not the Cloudpath endpoints it hit, and not a single byte of a Cloudpath response. Your password and the minted JWTs are never written to a log line either. You accept the terms and privacy policy when you first sign in; a version bump re-prompts you, and each acceptance is logged separately. ## Next steps - [Getting started](https://docs.neuralconfig.com/cp-mcp/getting-started/): Save and verify your Cloudpath credentials, and find the Test connection and Delete controls. - [Calling conventions](https://docs.neuralconfig.com/cp-mcp/calling-conventions/): How call behaves — the return shape, the automatic retry, and structured errors. - [Limits and quotas](https://docs.neuralconfig.com/cp-mcp/limits/): Timeouts, code size, Cloudpath rate limits, and the caps on each utility. - [Troubleshooting](https://docs.neuralconfig.com/cp-mcp/troubleshooting/): What forbidden_host, invalid_host, and login_failed mean, and how to clear each. --- # Troubleshooting Source: https://docs.neuralconfig.com/cp-mcp/troubleshooting/ ## Save & verify fails **Save & verify** mints a real Cloudpath JWT against the Core surface, so a failure is always in the credentials chain. In order of likelihood: 1. **Wrong password or username.** Re-enter the password — it is write-only, so you can't inspect what's stored. A bad login surfaces as `login_failed`. 2. **FQDN not a bare hostname.** The field must be a hostname only — no `https://` and no path. `https://host/foo` is rejected with *"fqdn must be a bare hostname (no scheme, no path)"*. 3. **Self-signed or untrusted TLS certificate.** See below. 4. **Host unreachable.** A DNS or network failure to the FQDN surfaces as `invalid_host`. 5. **Host not publicly routable.** A private, loopback, or link-local address, or an internal-only name, is rejected as `forbidden_host`. See below. :::tip If the save reported *"Saved."* rather than *"Saved and verified."*, nothing was checked — verification only runs when you type a password. Click **Test connection** to verify the stored credentials without retyping them. ::: ## Every error code, and what clears it Failures come back as a stable code, from the dashboard and from Claude alike. | Code | Meaning | Fix | |---|---|---| | `no_credentials` | No Cloudpath credentials saved for this account, or they were deleted | Save them in the dashboard | | `login_failed` | Cloudpath rejected the username or password | Re-enter the password; confirm the account is not locked or expired in Cloudpath | | `invalid_host` | The FQDN could not be reached — DNS failure, connection refused, TLS rejected, or a timeout | Check the hostname and that it is reachable from the public internet with a trusted certificate | | `forbidden_host` | The FQDN is a private, loopback, link-local, CGNAT, or multicast address, an IPv4 address not written in dotted decimal (such as `2130706433` or `127.1`), or a name ending `.local`, `.internal`, or `.localhost` | Give CP-MCP a publicly routable FQDN | | `decryption_failed` | The stored ciphertext could not be decrypted | Re-save the password; if it recurs, contact support | | `missing_property_portal` | A `property.call` was made without `propertyMgmtPortalGuid` | Pass the portal GUID — see below | | `invalid_body` | Both `body` and `bodyBase64` were passed to one `call` | Pass exactly one | These arrive as a structured result, not a thrown error: `ok: false`, `status: 0`, and `statusText: "cp_auth_error"`, with the code in `body.error`. A `status` of `0` is the tell that the call never reached Cloudpath. ## My Cloudpath is on a private network CP-MCP runs on Cloudflare and reaches your Cloudpath across the public internet, so an internal-only deployment cannot work. Saving an address in a private range — or a name ending `.local`, `.internal`, or `.localhost` — is rejected outright as `forbidden_host`; the check exists so the Worker cannot be pointed at someone else's internal network. Publish the Cloudpath server at a public FQDN with a publicly trusted certificate. A public name that resolves to a private address passes the `forbidden_host` check but then fails to connect, surfacing as `invalid_host`. See [Security model](https://docs.neuralconfig.com/cp-mcp/security/#the-fqdn-must-be-publicly-routable). ## My Cloudpath uses a self-signed certificate It won't work. Cloudflare Workers reject self-signed certificates on outbound HTTPS and offer no skip-verify option. Install a **publicly trusted** certificate on your Cloudpath server. Cloud-hosted Cloudpath at `*.cloudpath.net` already presents a valid certificate. See [Security model](https://docs.neuralconfig.com/cp-mcp/security/#tls-to-cloudpath). ## The connector won't authorize - **Redirect loop or "too many redirects"** — a cookie problem during the Google sign-in hop. Use a normal (non-private) window, allow cookies for `cp-mcp.lanpulse.com`, remove the connector, and add it again. - **"Not authenticated" on every call** — the token was revoked, or the connector was added before sign-in completed. Remove and re-add it. - **Connector connects, but calls fail on credentials** — MCP auth is fine and Cloudpath auth isn't. Click **Test connection** in the dashboard; it re-mints against the stored password and names the failure. See [Connecting to Claude](https://docs.neuralconfig.com/cp-mcp/connecting-claude/). ## "No Cloudpath credentials configured" The connector is authorized but you haven't saved Cloudpath credentials for this account — or you deleted them. Sign in to [the dashboard](https://cp-mcp.lanpulse.com) and save your FQDN, username, and password. This surfaces to Claude as the `no_credentials` error. ## Property-surface calls fail - **`missing_property_portal`** — a `property.call` was made without `propertyMgmtPortalGuid`. Every Property Management call must be scoped to a portal GUID. See [The three API surfaces](https://docs.neuralconfig.com/cp-mcp/surfaces/#the-property-surface-needs-a-portal-guid). ## A call returns `ok: false` but nothing threw That's by design — `call` never throws on an HTTP error; it returns a result with `ok: false` and the `status`. Inspect `status` and `body` to see what Cloudpath said. See [Calling conventions](https://docs.neuralconfig.com/cp-mcp/calling-conventions/#the-return-shape). ## A portal logo or favicon uploaded but appears blank The multipart text field name was wrong or missing. Cloudpath reads the `filename` field for logos and `iconFilename` for favicons — the wrong name returns `200 OK` but silently clears the stored image. See [Generating portal images](https://docs.neuralconfig.com/cp-mcp/generate-image/#uploading-the-image). ## The request timed out or died part-way Two separate clocks can end a `code_mode` program: - **`code_mode exceeded 20000ms wallclock budget`** — the whole program ran too long. Narrow the ask: one pool, one property, one page at a time. - **A single `call` returning `ok: false` with `status: 0` and a timeout message** — that one Cloudpath request passed its 15-second limit while the rest of the program was still fine. A long pagination loop can also trip Cloudpath's own rate limit (180 requests per minute on cloud-hosted deployments). That is a rejection from Cloudpath, not a CP-MCP error, so it arrives as an ordinary `ok: false` carrying whatever status Cloudpath returned. See [Limits and quotas](https://docs.neuralconfig.com/cp-mcp/limits/). ## Still stuck? Contact [support](https://support.neuralconfig.com/cp-mcp?from=docs) with the FQDN, the surface you were calling, and the `status` and `body` from the failing `call`. - [Getting started](https://docs.neuralconfig.com/cp-mcp/getting-started/): The credentials form, the dashboard controls, and what each one verifies. - [Calling conventions](https://docs.neuralconfig.com/cp-mcp/calling-conventions/): The return shape, the automatic token retry, and how to read a failed call. - [Limits and quotas](https://docs.neuralconfig.com/cp-mcp/limits/): Every timeout, size cap, and rate limit in one place. - [Security model](https://docs.neuralconfig.com/cp-mcp/security/): TLS and routability requirements, and how to revoke access.