# 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.