---
title: 'Use AI to manage your job board with Cavuno MCP'
label: 'MCP'
description: 'Connect Claude, Cursor, Codex, and other MCP clients to your Cavuno job board.'
publishedAt: 2026-05-05
updatedAt: 2026-10-20
order: -2
status: 'published'
---

The Cavuno MCP server is a hosted Cloudflare Worker at `mcp.cavuno.com`. It lets AI agents like Claude, Cursor, and Codex drive the Cavuno API without any per-endpoint MCP tool registration — there is no npm package to install, just an HTTPS URL. The public [Cavuno MCP repository](https://github.com/wollemiahq/cavuno-mcp) contains connection examples and issue tracker details, plus registry metadata; the hosted server implementation is not published there.

Using Claude on the web, desktop or mobile? See [Claude](/docs/claude).

## Connect Claude to your job board

In your Cavuno dashboard, open **Settings → Integrations → Admin MCP**. This page has the server URL, a copy button, and an **Open the MCP setup guide** link.

Choose the steps for the Claude app you use:

- **Claude Desktop or Claude on the web, as a custom connector:** Add `https://mcp.cavuno.com/mcp` as a custom connector. See [Add the full MCP to Claude](/docs/claude#full-mcp) for the steps.
- **Claude Code:** Run `claude mcp add --transport http cavuno https://mcp.cavuno.com/mcp` in your terminal. In Claude Code, run `/mcp`, select Cavuno, and follow the authentication steps to sign in and approve access.

You do not need to create an API key for this sign-in flow. If you use another AI client, see [Connect a client](#connect-a-client).

## How Cavuno MCP works

The authenticated Operator MCP endpoint at `mcp.cavuno.com/mcp` reaches the whole Cavuno API through two tools, regardless of how many REST endpoints exist:

- `search` — runs JavaScript against the Cavuno OpenAPI spec for endpoint and schema discovery.
- `execute` — runs JavaScript that calls the Cavuno API via a sandboxed loopback binding. Multiple calls can be chained in one execution; the final return value and captured console output flow back to the model.

This follows [Cloudflare's “code mode” pattern](https://blog.cloudflare.com/code-mode/). In Cloudflare's implementation, code-based discovery and execution reduced the tool context needed for a large API. Cavuno applies the same pattern with a two-tool surface; results still depend on the client, model, and task.

A few helper tools sit alongside them, such as [`search_docs`](/docs/mcp/reference/docs-search), which searches Cavuno's documentation.

## Choose a job board resource

Use the resource references when you need exact discovery prompts, write patterns, and safety boundaries:

- [Jobs](/docs/mcp/reference/jobs) — create, update, publish, pause, expire, duplicate, delete, and review job listings.
- [Companies](/docs/mcp/reference/companies) — find, create, update, and delete company records.
- [Blog posts](/docs/mcp/reference/blog-posts) — audit, draft, update, and publish job board articles.
- [Blog authors and tags](/docs/mcp/reference/blog-authors-and-tags) — manage article ownership and organization.
- [Taxonomies](/docs/mcp/reference/taxonomies) — manage categories, skills, and markets, and read canonical remote permits and timezones.
- [Settings](/docs/mcp/reference/settings) — inspect and change supported job board configuration.
- [Domains](/docs/mcp/reference/domains) — inspect domain state and verification data.
- [Google Search Console](/docs/mcp/reference/search-console) — query finalized search performance and inspect Google's indexed version of Board URLs.
- [Media](/docs/mcp/reference/media) — understand media metadata and the upload boundary.
- [Operations](/docs/mcp/reference/operations) — follow asynchronous work to completion.
- [API keys](/docs/mcp/reference/api-keys) — choose key permissions, including Builder access, and keep secrets out of MCP.
- [Audit logs](/docs/mcp/reference/audit-logs) — verify who changed what and when.
- [Usage and limits](/docs/mcp/reference/usage-and-limits) — inspect quotas before bulk work.
- [Authentication and security](/docs/mcp/reference/authentication-and-security) — choose OAuth or API keys and protect credentials.
- [Limitations](/docs/mcp/reference/limitations) — understand outbound network, files, and execution constraints.

## See what you can do with it

The [MCP cookbook](/docs/mcp/cookbook) starts with the outcome rather than API paths. Use it to post a job, publish or refresh blog content, research a content strategy, sync jobs from a careers page, or build a custom job scraper. The agent discovers the relevant endpoints and record IDs from Cavuno for you.

When a task also needs a browser, scraper, spreadsheet, or an unsupported third-party platform, that access comes from your AI client or another connector. Google Search Console is the exception documented above: after it is connected in Cavuno, MCP can use its native Board-bound performance and URL Inspection reads.

## Connect a client

Add Cavuno to your MCP-aware editor. Clients that support remote MCP OAuth can open a browser so you can sign into Cavuno and approve access. Cavuno currently grants the single `full_access` scope. Configuration, token storage, and refresh behavior vary by client.

### Claude Desktop

Remote MCP servers cannot be added directly through `claude_desktop_config.json`. See [Claude](/docs/claude) to add Cavuno from Claude's connector directory, or [add the full MCP as a custom connector](/docs/claude#full-mcp).

### Cursor

Edit `~/.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "cavuno": {
      "url": "https://mcp.cavuno.com/mcp"
    }
  }
}
```

### Codex

Add the server to `~/.codex/config.toml` (or the project-level `.codex/config.toml` for a trusted project):

```toml
[mcp_servers.cavuno]
url = "https://mcp.cavuno.com/mcp"
```

### ChatGPT Developer mode

Turn on Developer mode in ChatGPT, then add `https://mcp.cavuno.com/mcp` as a custom MCP app. ChatGPT uses a Client ID Metadata Document as its `client_id` when Cavuno advertises `client_id_metadata_document_supported`. Cavuno fetches that HTTPS document and treats ChatGPT as a public PKCE client. You do not paste a client ID or secret.

### Claude Code

```bash
claude mcp add --transport http cavuno https://mcp.cavuno.com/mcp
```

Run `/mcp` in Claude Code, select Cavuno, and follow the authentication steps. See [Claude Code's MCP guide](https://code.claude.com/docs/en/mcp#authenticate-with-remote-mcp-servers) for details.

## Authentication

Two paths are supported. OAuth is the right choice for interactive use; an API key is the right choice for CI / automation that doesn't have a browser.

### OAuth 2.1 (default)

The MCP spec runs the standard OAuth authorization-code flow with PKCE. On first connect, the client:

1. Fetches `https://mcp.cavuno.com/.well-known/oauth-protected-resource/mcp` and discovers the authorization server (`api.cavuno.com/v1/oauth`).
2. Reads authorization-server metadata. Clients that support Client ID Metadata Documents send an HTTPS metadata URL as `client_id` when `client_id_metadata_document_supported` is true. Other clients use dynamic registration at `/v1/oauth/register`.
3. Opens a browser to `/v1/oauth/authorize` for user consent. The minted JWT is bound to the Cavuno MCP resource (`aud=https://mcp.cavuno.com/mcp`).
4. Sends every subsequent request with `Authorization: Bearer <jwt>`.

The worker verifies the JWT’s issuer, audience, and signature through the authorization server’s JWKS before invoking a tool.

Cavuno treats CIMD clients as public PKCE clients (`token_endpoint_auth_method: none`). Dynamic registration also accepts `client_secret_basic`, `client_secret_post`, and public clients using `none`. Cavuno does not implement `private_key_jwt`. If a metadata document offers both `none` and `private_key_jwt`, Cavuno uses `none`.

### API key (CI / scripts)

Mint a key in the Cavuno dashboard at **Settings → Developer → API keys**. Pass it as the bearer token — MCP clients that support custom request headers can attach it directly:

```json
{
  "mcpServers": {
    "cavuno": {
      "url": "https://mcp.cavuno.com/mcp",
      "headers": {
        "Authorization": "Bearer cavuno_live_xxxxxxxxxxxxxxxx_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
      }
    }
  }
}
```

API keys beginning with `cavuno_live_` skip the OAuth flow entirely—the worker passes them through unchanged.

## The two tools

Both tools take a single argument: `code`, the source of an `async () => …` arrow function. The function runs in a fresh Cloudflare Dynamic Worker isolate per call — there is no shared state across invocations.

### `search`

```javascript
// Tool: search
// Args: { code: string }   // an async arrow function as a string
//
// Globals available inside the sandbox:
//   spec — the full Cavuno OpenAPI document with $refs pre-resolved
//
// Network access: blocked. Use `spec` for everything.

async () => Object.keys(spec.paths).slice(0, 10)
```

### `execute`

```javascript
// Tool: execute
// Args: { code: string }   // an async arrow function as a string
//
// Globals available inside the sandbox:
//   cavuno.request({ method, path, body?, query?, headers? })
//     -> { status: number, ok: boolean, data: unknown }
//
// Network access: only the loopback binding above. The agent's bearer
// token is held by the parent worker and injected on every call —
// never include an Authorization header in the code you write.

async () => {
  const r = await cavuno.request({ method: 'GET', path: '/usage' });
  return r.data;
}
```

The `spec` object that `search` sees is the same OpenAPI document published at [api.cavuno.com/v1/openapi.json](https://api.cavuno.com/v1/openapi.json) — also visible in the [interactive API reference](/docs/api). `execute` can call authenticated Operator API endpoints that accept JSON or multipart form data. Endpoints that require a different authentication context remain outside this MCP surface.

## Sandbox & limits

- Outbound network from inside the sandbox is **blocked** (`globalOutbound: null`). The only way out is `cavuno.request(…)`; calling `fetch(…)` throws.
- No access to environment variables, file system, or persistent storage. Each invocation starts in a fresh isolate.
- `console.log/warn/error` is captured and returned alongside the function's return value, so multi-step scripts can print intermediate state for debugging.
- CPU and wall-clock budgets are bounded by the underlying Cloudflare Workers limits — keep individual `execute` calls under a few seconds.
- `cavuno.request` accepts `FormData` for multipart endpoints such as `POST /v1/media/upload`. File bytes must already be available inside the invocation because the sandbox cannot read the local filesystem. Do not set `Content-Type`; Cavuno generates the multipart boundary.

## Errors

When user code throws (or returns a rejected promise), the response is a structured error envelope rather than a tool failure. The captured `console` output is included so the agent can self-diagnose without re-running:

```json
{
  "ok": false,
  "error": "TypeError: Cannot read properties of null (reading 'id')",
  "stack": "TypeError: Cannot read properties of null...",
  "logs": [
    { "level": "log", "args": ["company:", null] }
  ]
}
```

API-level errors (4xx / 5xx) come back from `cavuno.request` as `{ status, ok: false, data: { error: { code, message, requestId, details?, upgrade? } } }` — the agent can branch on `data.error.code` rather than parsing strings.

### Plan upgrade hints

When a request is refused because of your board's Cavuno plan (for example the active job limit, team seats, removing Cavuno branding or webhooks), the error carries an `upgrade` object. It names the capability that blocked the request, the cheapest self-serve plan that unlocks it, and the billing endpoints that start the upgrade:

```json
{
  "error": {
    "code": "jobs_quota_exceeded",
    "message": "Active job limit reached. Upgrade your plan to publish more jobs.",
    "requestId": "req_...",
    "upgrade": {
      "capability": "activeJobsLimit",
      "requiredPlan": "basic",
      "checkout": { "method": "POST", "path": "/v1/billing/checkout" },
      "changePlan": { "method": "POST", "path": "/v1/billing/upgrade" }
    }
  }
}
```

To act on it, the agent:

1. Calls `GET /v1/billing/subscription`. With no active subscription (for example during a trial), it uses `checkout`; with one, it uses `changePlan`.
2. Sends `{ "planKey": "<requiredPlan>" }` to that endpoint.
3. Gives the returned `url` to you. Nothing is charged until a person confirms on that hosted page.

`requiredPlan` is left out when the cheapest plan can't be told from the error alone; the agent then picks a higher plan from the subscription response. Errors on your public job board (the Board API) never include upgrade hints. The CLI prints the hint as an `upgrade:` line under the error.

## Reporting problems

Every Cavuno MCP endpoint (`/mcp`, `/dev` and `/builder`) has a `submit_feedback` tool. Agents call it when something prevented or complicated a task, even with a workaround: a missing capability, unclear or wrong documentation, a surprising response, an unhelpful error, a bug or slow performance. It is safe to call at any time and needs no confirmation from the user. Reports go to the engineers who build Cavuno.

The tool takes an [Agent Feedback Protocol v0.1](https://trybackloop.com/spec/) report. `type`, `goal` and `message` are required; `type` is one of `missing_capability`, `bug`, `unclear_documentation`, `unexpected_response`, `unhelpful_error`, `performance` or `other`. Fill `outcome`, `expected`, `suggestion`, `agent.name` and `agent.model` when you know them.

```json
{
  "type": "unhelpful_error",
  "goal": "What you were trying to do",
  "message": "What got in the way",
  "endpoint": "execute",
  "outcome": "degraded",
  "agent": { "name": "my-agent", "model": "model-name" }
}
```

Outside MCP, send the same JSON to the API:

```bash
curl https://api.cavuno.com/v1/feedback \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{ "type": "bug", "goal": "What you were trying to do", "message": "What got in the way" }'
```

Each account or visitor can send 20 reports every 5 minutes. Never include credentials, personal data or your user's private content. Send the report, then continue with the task; a failed report never blocks it.
