Skip to content

Docs / MCP client onboarding

MCP client onboarding

Connect Claude Code, Cursor, ChatGPT, Claude.ai, or Claude mobile to mcp.developerz.ai: PAT for local clients, OAuth for hosted ones, dispatch a task end to end.

The developerz.ai control plane is a plain MCP server at mcp.developerz.ai. It speaks two transports that any spec-compliant client can drive: HTTP with a bearer personal access token (PAT) for local agents that paste a token in their config, and OAuth 2.1 (RFC 7591 dynamic client registration + RFC 8414 authorization-server discovery) for hosted clients that have nowhere to paste one.

The clients substantiated in this repository today are listed below. Each is one of those two shapes: name a client only when you can point at the MCP surface it speaks, a transport already documented here, or a config example already committed.

Client Auth Substantiation
Claude Code PAT claude mcp add --transport http recipe below; root .mcp.json uses type: http against https://mcp.developerz.ai/v1/mcp
Claude.ai (web) OAuth apps/api/src/routes/oauth-metadata.ts explicitly names Claude.ai as a hosted client; the OAuth dance (/oauth/register/oauth/authorize/oauth/token) is wired
Claude mobile (iOS / Android) OAuth Same OAuth flow as Claude.ai, the same client code path, on mobile. No iOS/Android-specific config ships in this repo; the mobile app reads the same discovery URL the web app does
ChatGPT OAuth Same OAuth flow. The connector URL is https://mcp.developerz.ai/v1/mcp; OpenAI's Developer mode + plan gate is theirs, not ours
Cursor PAT ~/.cursor/mcp.json recipe below (user-level only, token inline)
Claude Desktop PAT via mcp-remote Bridged through mcp-remote. See the recipe below; the bridge is the supported path for any client that takes a stdio command but no header config
Generic MCP client (your own script, CI) PAT The HTTP transport is stateless JSON-RPC (POST /v1/mcp, one body in, one JSON-RPC response out); any client that can POST with a Bearer header drives it directly

A client not in the table is not substantiated here. The PAT recipe and the OAuth recipe below are the only two paths this server ships; if a client you use does not fit either, treat it as unverified until you have a config that worked end to end.

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


1. Authenticate to developerz.ai

There are two ways to connect your MCP client: hosted-client OAuth (ChatGPT, Claude.ai, Claude mobile) or personal access token (Claude Code, Cursor, Claude Desktop via mcp-remote, scripts, CI). Pick the one that matches your client.

Hosted clients (OAuth): ChatGPT, Claude.ai, Claude mobile

If you are using a client that has no place to paste a token (a chat host with custom connectors, or a mobile app), you do not mint a PAT. Add developerz.ai as a connector and let the OAuth dance run:

  • Name: developerz.ai
  • MCP server URL: https://mcp.developerz.ai/v1/mcp

The server publishes its OAuth 2.1 metadata at https://mcp.developerz.ai/.well-known/oauth-authorization-server (the authorization-server document, RFC 8414) and a per-resource twin at https://mcp.developerz.ai/.well-known/oauth-protected-resource/v1/mcp (RFC 9728). A POST /v1/mcp without a bearer gets a 401 carrying WWW-Authenticate: Bearer resource_metadata="…"; the client follows the challenge, dynamically registers (POST /oauth/register, RFC 7591), consents (GET /oauth/authorize), and exchanges the code for an ordinary PAT (POST /oauth/token). The PAT it mints lists on your dashboard tokens screen the same way a hand-minted one does, and revokes the same way.

ChatGPT

Custom connectors live behind Developer mode. Go to chatgpt.com/settingsApps (or Connectors) → Advanced settings, turn Developer mode on, then use Create / Add custom connector and enter the name and URL above. Pick OAuth when it asks how to authenticate, then follow the consent flow to sign in with GitHub and authorize developerz.ai.

Developer mode needs a paid plan, and dispatching a task is a write action. OpenAI gates those by plan: Business, Enterprise and Edu workspaces get write-capable connectors, while Pro is limited to read and fetch, which is enough to watch a run but not to start one. Inside a workspace it is an admin or owner who turns Developer mode on and creates the connector; members use what has been published to them. That gate is OpenAI's and it moves, so check their Developer mode and MCP apps guide for the current plan matrix before setting this up.

Claude.ai (web) and Claude mobile (iOS / Android)

Add the connector the same way. Name developerz.ai, URL https://mcp.developerz.ai/v1/mcp. Claude.ai and the Claude mobile app on iOS and Android run the same hosted-client path: no PAT, no header to paste; the discovery document does the work. There is no iOS/Android-specific configuration in this repo because the OAuth dance is identical across surfaces: the mobile app fetches the same /.well-known/oauth-authorization-server URL the web app does.

What is not yet verified here is the exact "add connector" UI in the Claude mobile app at the version you have installed today. The OAuth transport is substantiated (it is the same flow Claude.ai uses), but the in-app steps to reach it on mobile have not been performed and recorded. See Recorded end-to-end runs below.

Once authorized, any of these three hosted clients can dispatch tasks, watch runs, and manage your account, with exactly the scopes you approved. You can revoke access at any time from the developerz.ai dashboard: an OAuth grant mints an ordinary PAT, so it is listed and revocable on your /org/:org/account/tokens screen.

Self-hosted or automation: Personal access token

For local clients (Claude Code, Cursor, tesote.ai) or scripts running in your CI, mint a scoped personal access token instead.

Sign in to the dashboard (GitHub SSO). Navigate to your account settings at /org/:org/account/tokens (replace :org with your organization name or username). The tokens screen lets you mint, list, and revoke personal access tokens. The mint route requires write:account, which among dashboard roles only the account owner holds, so mint from the owner's login.

From the tokens screen, click Mint PAT, select the scopes write:tasks and read:tasks, and copy the token. It is shown ONCE and never displayed again.

dev_pat_…

Never carry the dz_session dashboard cookie out of the browser to do this. It is a full-power credential at the owner-role ceiling, far stronger than the scoped PAT you are minting, and a shell paste lands it in your history and the process table. The tokens screen mints from the browser, where the cookie already lives; for automation, use the bearer recipe below.

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 tokens screen, 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. Configure your PAT client at mcp.developerz.ai

If you minted a personal access token in step 1, use the recipes below to configure your client. 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.

If you authorized via hosted-client OAuth (ChatGPT, Claude.ai, Claude mobile), you are already authenticated - skip to step 3.

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_…" }
    }
  }
}

Any generic MCP client (your own script, CI)

The transport is stateless single-request JSON-RPC over HTTPS: one POST in, one JSON-RPC response out, no SSE. Any client that can POST with a Bearer header drives it directly. There is no client SDK required:

  • URL: https://mcp.developerz.ai/v1/mcp
  • Auth header: Authorization: Bearer dev_pat_…
  • Body: standard JSON-RPC 2.0 (see step 3 for the envelope)
  • Media type: application/json

This recipe is the one behind every PAT-authenticated entry in the table above that is not Claude Code, Cursor or Claude Desktop. It is what you point your own script or CI job at when you do not have a UI client.

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:

  • sink: direct 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, ChatGPT, Claude.ai, Claude mobile) 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. Add wait_ms (up to 25000) to WATCH instead of poll.
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.

Watch, do not poll. Your read budget is 60 a minute, so a once-a-second run_tail spends all of it on one run, and a run can last hours. Send wait_ms and the call is held open until the first new event lands:

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":4,"params":{"name":"task","arguments":{"action":"run_tail","run_id":"RUN_ID","since_seq":412,"wait_ms":25000}}}'

An empty events array after a wait is not an error, it means nothing happened in that window: send the same next_since_seq again. A run that has already ended is the exception worth handling: it answers at once rather than holding the call open, so an instant empty page is your cue to run run_status and stop rather than to re-send. Omit wait_ms and the call answers immediately, exactly as before. There is no server-push transport here and there will not be one, because a tool result is one message, so this long poll is the watch path over MCP rather than a placeholder for a subscription. If you can hold a connection open, the REST Server-Sent Events stream GET /v1/fleet/runs/:runId/events is the other shape. See rate limits.


Recorded end-to-end runs

Three runs are intended for this page: Claude Code, Claude mobile, and ChatGPT, so that every recipe on this page has at least one human-observed run behind it.

One of three recorded. Run 1 was performed and is cited below to its merged artifact. Runs 2 and 3 have not been performed: they are the runs that should land here, not runs that have landed, and a reader who came here for proof that those two recipes work end to end has no proof to read yet. Filling them is a human task: verifying a paid plan's entitlement on a third-party platform, then performing and recording those runs as named. Until that work has been done, this page will not claim either of them happened.

Run 1: Claude Code (recorded 2026-09-01)

Performed from Claude Code over MCP against https://mcp.developerz.ai/v1/mcp, under the conversation ref issue-2684-run1-claude-code. The direction given was to document the purpose of release-check.ts in the repository README.

The artifact is PR #2911, opened by the developerz-ai App on branch dz/task-tsk_48919914…, one file changed, merged 2026-09-01T14:09:28Z. Run timeline: run_7a72530754394f4a9756848e701cdbc8.

What this run evidences, and what it does not. It evidences the dispatch path end to end: a person directing Claude Code, a task minted over MCP on that person's account, and a bot-attributed PR merged on the repo with no human authoring the commit. It does not evidence the other two properties this section asks a run to show. The credential the dispatch used is not readable from the merged artifact, and "zero terminal interaction after the initial direction" is a property of the session rather than of the PR, so neither is claimed here. The run also did not exercise the missing-env-key blocker-link leg originally scoped into it; that leg remains unperformed.

Run 2: Claude mobile (not yet performed)

The run to record:

  1. Install the Claude mobile app on iOS or Android.
  2. Sign in; add developerz.ai as a connector via the OAuth dance (the same /.well-known/oauth-authorization-server URL the web app uses).
  3. Negotiate a new project through the architect: bootstrap plan, approve, first PR merged, rolled up.

What the recorded run must show: a per-customer token (the OAuth-grant PAT) in use, bot-attributed PRs, zero terminal interaction after the initial direction. The OAuth transport is substantiated by the repo today (same flow as Claude.ai on the web); what has not been performed is the in-app "add connector" UI on mobile at a real app version, and the resulting end-to-end run.

Run 3: ChatGPT (not yet performed)

The run to record:

  1. In ChatGPT (Developer mode enabled), add developerz.ai as a custom connector with the OAuth URL above.
  2. Direct ChatGPT to approve a pre-negotiated plan.
  3. Show a real injected failure surfacing loudly, then recovery.

What the recorded run must show: a per-customer token in use, bot-attributed PRs, zero terminal interaction after the initial direction.

Plan-entitlement caveat (must be verified before recording): OpenAI gates write-capable custom connectors by plan: Business, Enterprise and Edu get them, Pro is read/fetch only. The entitlement MUST be verified and recorded before the run is described as a write-capable flow; if the plan permits read-only, this run is watch-and-rollup rather than dispatch-and-approve, and the description above is rewritten to match. See the ChatGPT sub-section above for the gate that has to be checked.


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.