Skip to content

Docs / MCP client onboarding

MCP client onboarding

Mint a personal access token (PAT) with write:tasks + read:tasks, point any MCP client — Claude Code, Cursor, tesote.ai — at mcp.developerz.ai, and dispatch a task end to end.

MCP client onboarding

The developerz.ai control plane is a plain MCP server at mcp.developerz.ai, authenticated by a personal access token (PAT). Any MCP-capable client — Claude Code, Cursor, tesote.ai, or your own script — can drive it: dispatch a task, watch it run, read the result.

This page is the full external-client walkthrough: mint a token, connect your client, dispatch a task, and check the work. For the full tool surface see the MCP verb reference.


1. Mint a PAT with write:tasks + read:tasks

Sign in to the dashboard (GitHub SSO). The login sets a dz_session cookie that the REST API accepts as a credential on every /v1/* route — so your first token is one POST /v1/tokens call away. The mint route requires write:account, which among dashboard roles only the account owner holds, so mint from the owner's login.

The dashboard has no tokens screen yet — until one ships, the self-serve path is the session call itself. From any page on app.developerz.ai, open the browser console and run:

fetch('https://api.developerz.ai/v1/tokens', {
  method: 'POST',
  credentials: 'include', // sends the dz_session cookie
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ scopes: ['write:tasks', 'read:tasks'] }),
}).then((r) => r.json());
// → { id: "…", token: "dev_pat_…", scopes: ["write:tasks", "read:tasks"] }

The API CORS-allowlists the dashboard origin for credentialed calls, so the cookie rides along. Prefer the shell? Copy the dz_session value from devtools → Application → Cookies — but treat it as what it is: a full-power dashboard credential at the owner-role ceiling, far stronger than the scoped PAT you're minting. Pasted inline it lands in your shell history and the process table, so keep it in an env var and use it once, in a throwaway shell:

read -s DZ_SESSION  # paste the cookie value — no echo, nothing in history
curl https://api.developerz.ai/v1/tokens \
  -H "Cookie: dz_session=$DZ_SESSION" \
  -H "Content-Type: application/json" \
  -d '{"scopes":["write:tasks","read:tasks"]}'
unset DZ_SESSION

If the value was ever exposed — shared screen, pasted into a chat or a file — rotate it: signing out of the dashboard (POST /auth/logout) revokes the session server-side, and the next sign-in mints a fresh cookie. The PAT you already minted is an independent credential and keeps working.

A dispatch token needs exactly two scopes:

Scope Why
write:tasks Dispatch work (task_create).
read:tasks Watch it (task.list, task.run_status, task.run_tail).

The plaintext (dev_pat_…) is returned once — copy it into your client's config immediately. Scopes are subset-of-caller: a PAT can never hold more than the credential that minted it, and asking for a scope the caller lacks fails the mint. Omit scopes and the token inherits everything the caller holds except admin:* and use:* — those land on a token only when explicitly requested, and only when the caller holds them itself.

Already hold a token with write:account? Mint the scoped one with that bearer instead of the session — read it into an env var first so it stays out of your shell history:

read -s DEVELOPERZ_MINT_TOKEN; export DEVELOPERZ_MINT_TOKEN  # paste the write:account token
curl https://api.developerz.ai/v1/tokens \
  -H "Authorization: Bearer $DEVELOPERZ_MINT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"scopes":["write:tasks","read:tasks"]}'
# → 201 { "id": "…", "token": "dev_pat_…", "scopes": ["write:tasks","read:tasks"] }

2. Point your client at mcp.developerz.ai

The endpoint is https://mcp.developerz.ai/v1/mcp with a single Authorization: Bearer header. The transport is stateless single-request JSON-RPC — one POST in, one JSON-RPC response out, no SSE stream — so streamable-HTTP clients interop through the plain JSON responses and the configs below work as written.

Claude Code

Paste the PAT at the read prompt so it stays out of your shell history, then register the server. Be clear about what read -s does not cover, though: claude mcp add has no env indirection — the header value is expanded into the process argv at exec and stored verbatim, in plaintext, in ~/.claude.json. Same exposure class as the dz_session cookie in step 1, except this credential is scoped (write:tasks + read:tasks) and revocable. Treat ~/.claude.json as a live credential file from then on:

read -s DEVELOPERZ_API_KEY; export DEVELOPERZ_API_KEY  # paste the dev_pat_… value
claude mcp add --transport http developerz \
  https://mcp.developerz.ai/v1/mcp \
  --header "Authorization: Bearer $DEVELOPERZ_API_KEY"

The committed .mcp.json route avoids the plaintext copy: it expands ${DEVELOPERZ_API_KEY} from each user's environment at launch, so the token never lands in the file:

{
  "mcpServers": {
    "developerz": {
      "type": "http",
      "url": "https://mcp.developerz.ai/v1/mcp",
      "headers": { "Authorization": "Bearer ${DEVELOPERZ_API_KEY}" }
    }
  }
}

Cursor

Cursor does not expand ${VAR} references in headers, so the token has to sit inline — which makes the file choice load-bearing. Add it to the user-level ~/.cursor/mcp.json only, and never commit that file. Do not use the project-level .cursor/mcp.json for this server: that file is routinely checked in, and an inline token there leaks to everyone with repo access.

{
  "mcpServers": {
    "developerz": {
      "url": "https://mcp.developerz.ai/v1/mcp",
      "headers": { "Authorization": "Bearer dev_pat_…" }
    }
  }
}

tesote.ai (or any generic MCP client)

Add a remote MCP server with:

  • URL: https://mcp.developerz.ai/v1/mcp
  • Auth header: Authorization: Bearer dev_pat_…

Clients without header support

Bridge through mcp-remote, which injects the bearer for you. It takes the full endpoint URL and passes auth via --header CLI args (there is no token env var it reads on its own). This block goes in the client's user-level config — e.g. Claude Desktop's claude_desktop_config.json — never a committed project file: the token sits inline in the env block, and committing it leaks the PAT to everyone with repo access.

{
  "mcpServers": {
    "developerz": {
      "command": "npx",
      "args": [
        "mcp-remote",
        "https://mcp.developerz.ai/v1/mcp",
        "--header",
        "Authorization: Bearer ${DEVELOPERZ_API_KEY}"
      ],
      "env": { "DEVELOPERZ_API_KEY": "dev_pat_…" }
    }
  }
}

On Windows, Cursor and Claude Desktop mangle spaces inside args — drop the space after the colon and put the whole value in the env var instead: "Authorization:${DEVELOPERZ_AUTH_HEADER}" with "env": { "DEVELOPERZ_AUTH_HEADER": "Bearer dev_pat_…" }. Same rule as above: the header value is inline in env, so the file stays user-level only and uncommitted.

Verify the connection from any client by calling whoami — it returns the account, MCP tier, and granted scopes. With curl, token read from the environment so it stays out of your shell history:

read -s DEVELOPERZ_API_KEY; export DEVELOPERZ_API_KEY  # paste the dev_pat_… value
curl https://mcp.developerz.ai/v1/mcp \
  -H "Authorization: Bearer $DEVELOPERZ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"tools/call","id":1,"params":{"name":"whoami","arguments":{}}}'

3. Dispatch a task

Call task_create with a scoped envelope. The server stamps the origin and the caller identity — you supply the work spec:

{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "id": 2,
  "params": {
    "name": "task_create",
    "arguments": {
      "repo_full_name": "owner/my-repo",
      "title": "Add pagination to the user listing endpoint",
      "problem": "GET /users returns the full table; it times out past 10k rows.",
      "acceptance_criteria": [
        "GET /users accepts page and per_page params",
        "Default per_page is 50, max 200",
        "Existing clients without params get page 1 unchanged"
      ],
      "files_touched": ["src/routes/users.ts"],
      "forbidden": ["src/routes/billing.ts"],
      "confidence": 0.9,
      "sink": "direct"
    }
  }
}

Field notes:

  • sinkdirect or queued runs the task on your BYOVM fleet (queued is the capacity-aware fan-out sink: a busy fleet queues until the org's max queue depth, then refuses the enqueue with queue_full); gh opens a GitHub issue; linear files to your Linear.
  • confidence below the repo policy's confidence_threshold becomes an escalation, not a task — a human is paged instead of the fleet.
  • files_touched is advisory; forbidden is a ban-wrong-fixes list rendered into the coding agent's brief as "Do not touch" guidance — prompt-level, not an enforced filesystem gate.

A direct dispatch returns {task_id, sink, sink_ref, url}. A queued dispatch returns {task_id, sink, queue_position, queue_depth} so you can watch the backlog drain — unless the org is already at its max queue depth, in which case the enqueue is refused: no task is created, and the result comes back as a tool error (isError: true) whose text names the depth and the cap:

queue full: <queue_depth> tasks waiting (max <max_depth>); retry once the backlog drains

Handle both shapes in an automation: success JSON carrying a task_id, or the queue_full refusal — back off and re-dispatch once task.list shows the backlog draining. In natural-language clients (Claude Code, Cursor, tesote.ai) you can simply ask: "use task_create to dispatch … to owner/my-repo".


4. Check the work

The same PAT reads the fleet back (read:tasks):

Tool Action Returns
task list The account's task queue, newest-first (filters.status, per_page). Each row carries its run_id once a runner claims it.
task run_status One-row pulse for a run — last event kind, seq, finished flag.
task run_tail Durable run events after since_seq, capped — feed next_since_seq back to keep polling.
curl https://mcp.developerz.ai/v1/mcp \
  -H "Authorization: Bearer $DEVELOPERZ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"tools/call","id":3,"params":{"name":"task","arguments":{"action":"run_status","run_id":"RUN_ID"}}}'

The run_id comes from task.list once the task is claimed. The dashboard's fleet view shows the same run live, and every step — the dispatch, the claim, each tool call, the PR — is in the audit log.


Hygiene

  • Least privilege. A dispatch PAT needs only write:tasks + read:tasks. Don't reuse a dashboard-grade token in an external client.
  • Never commit the token. Reference it from the environment (${DEVELOPERZ_API_KEY}) in committed configs — but only where the client genuinely expands it (a committed .mcp.json does; Cursor's headers do not). Where expansion isn't supported, the token sits inline and the config file is user-level only, never committed.
  • Revoke when done. A dispatch PAT can't revoke itself — revocation needs write:account, the same owner session that minted it. List tokens with GET /v1/tokens (read:account — any dashboard role), then DELETE /v1/tokens/:id with the owner session (the console-fetch pattern from step 1; re-revoking is idempotent). A credential that already holds write:account can also call the MCP token resource's revoke action.