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/settings → Apps (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:directorqueuedruns the task on your BYOVM fleet (queuedis the capacity-aware fan-out sink: a busy fleet queues until the org's max queue depth, then refuses the enqueue withqueue_full);ghopens a GitHub issue;linearfiles to your Linear.confidencebelow the repo policy'sconfidence_thresholdbecomes an escalation, not a task. A human is paged instead of the fleet.files_touchedis advisory;forbiddenis 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:
- Install the Claude mobile app on iOS or Android.
- Sign in; add developerz.ai as a connector via the OAuth dance (the same
/.well-known/oauth-authorization-serverURL the web app uses). - 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:
- In ChatGPT (Developer mode enabled), add developerz.ai as a custom connector with the OAuth URL above.
- Direct ChatGPT to approve a pre-negotiated plan.
- 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.jsondoes; 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 withGET /v1/tokens(read:account, any dashboard role), thenDELETE /v1/tokens/:idwith the owner session (the console-fetch pattern from step 1; re-revoking is idempotent). A credential that already holdswrite:accountcan also call the MCPtokenresource'srevokeaction.