constellation MCP server
The constellation MCP (Model Context Protocol) server exposes the same operations as the pt CLI but as MCP tools — so MCP-native agent runtimes (Claude Desktop, Claude Code with .mcp.json, Cursor, Codex, ChatGPT custom connectors) can call them as part of a normal turn instead of as one-shot subprocesses.
Use the hosted connector instead — no checkout, no token, no local server. Point your client at https://mcp.planetb2b.com/api/mcp and sign in; mcp.planetb2b.com has the per-client steps (INF-349).
A one-command installer (npx @planetb2b/mcp-connect, INF-350) is written but not yet published to npm, so that command does not resolve today — follow the per-client steps on the connect page.
The rest of this page covers the local stdio server, which is what you want when you are developing the MCP server or working against a local stack.
The server identity was constellation-pt before the wiki tools were merged in. Update the server key in your config to constellation (examples below) — a one-time per-developer change. Setting the optional WIKI_BASE_URL env var additionally registers the 24 wiki tools (list_spaces, search_pages, get_page, traverse_dependencies, rebuild_space_index, …). The hosted Streamable-HTTP endpoint keeps its existing URL until a separate coordinated infra rename.
The wire protocol is stdio JSON-RPC for desktop / CLI agents and Streamable HTTP for ChatGPT. Backend: same REST API as the CLI.
The full source-of-truth setup notes live in tools/mcp-server/README.md. The summaries below are extracted for the most common setups.
Recommended: pt login (OAuth, no static token — INF-286)
For interactive developer machines, skip static API keys entirely:
export PT_BASE_URL=https://constellation.planetb2b.com/projects # required: keys the profile + derives the Directory origin
npx pt login # opens the browser to the Directory consent screen (loopback + PKCE)
Credentials land in a user-level store (~/.config/constellation/credentials.json, 0600) and auto-refresh for ~30 days per consent — a server-side secret rotation no longer breaks your setup, and fresh git worktrees need no token copy. pt whoami shows the authenticated user; pt logout revokes the grant server-side and clears the store.
Register the MCP server user-scoped (once per machine), running the command from your main checkout, so it survives the worktree-per-task workflow:
claude mcp add constellation -s user \
-e PT_BASE_URL=https://constellation.planetb2b.com/projects \
-e WIKI_BASE_URL=https://constellation.planetb2b.com/wiki \
-- npx -y tsx "$(pwd)/tools/mcp-server/src/index.ts"
⚠
"$(pwd)/…"expands at registration time, pinning your main checkout's absolute path into the stored command. A relative path would resolve against each session's cwd — broken in a fresh worktree beforenpm install, and broken outside the repo entirely. Pinned, the main checkout'snode_modulesserves every session and a fresh worktree needs no per-worktree step for the MCP.
⚠ Pass both env vars in the registration itself. A user-scoped server is launched outside any worktree's
.env.local, so withoutPT_BASE_URLthe server exits at startup, and withoutWIKI_BASE_URLit starts but silently registers no wiki tools. (Inside the repo these are usually picked up from the root.env/.env.local, which is why a first run can look fine and a fresh worktree then fail.)
Claude resolves MCP servers local → project → user. If your repo checkout still has a project-scoped constellation entry in .mcp.json, it shadows the user-scoped one and its static token wins — remove that entry after migrating. pt login warns when it detects this.
For Codex CLI, keep the ~/.codex/config.toml entry below but omit the PT_AUTH_TOKEN line — the credential store supplies auth. CI and headless environments keep using the static PT_AUTH_TOKEN env var (it always takes precedence over the store).
Legacy / CI: get an API key
Both stdio and HTTP modes also accept a static PT auth token (required for CI/headless). Generate one in the PT UI:
- Log into PT at
${PT_BASE_URL}/settings/api-keys(production:https://constellation.planetb2b.com/projects/settings/api-keys). - Provide a label (e.g. "Claude Desktop MCP"), pick an expiry, click Generate.
- Copy the token — it's shown once. The default lifetime is 90 days for ordinary users and 14 days for administrative ones, with maxima of 365 and 30 days respectively. Both maxima are deployment-tunable, so 30 is the administrative default rather than an absolute limit — see the MCP server README for which role names count as administrative and how the caps are configured.
Tokens are revocable from the same page — revocation propagates within ~30 seconds across Directory, Project Tracker, and Catalog (30-second TTL on the principal-liveness cache).
Ending an npx pt login session
The same page carries an Active CLI sessions card, one entry per active
npx pt login — that is, per refresh-token family. No machine identifier is
stored, so two logins from the same machine are two entries. Each shows when it
signed in, when it last refreshed, and when it expires (30 days from consent —
refreshing does not extend it).
End session revokes that session's whole refresh-token family, not just the
short-lived bridge token it happens to be holding. That distinction is the point:
revoking only the access token left the family alive, so the CLI minted a
replacement at its next refresh. Ending the session also revokes the outstanding
access token, so that machine normally stops working within the same ~30 seconds
rather than keeping access for the rest of that token's lifetime — one hour by
default, up to 24 hours where MCP_PT_TOKEN_TTL has been raised to its ceiling.
One exception, and the UI says so when it applies: a session whose access-token
rows predate migration 035 carries no cli_family_id, so there is nothing to link
them and only the family is revoked. The session cannot refresh again, but a token
it was already issued keeps working until it expires on its own. That population
drains within one token lifetime of the deploy.
Other sessions are unaffected — each npx pt login is its own family. The machine
whose session you ended must run npx pt login again; there is no way to resume a
revoked family.
The machine-minted pt-cli-oauth rows in the key list above are those sessions'
bridge tokens. Revoking one there does not end the session — use this card.
Claude Code (.mcp.json)
The user-scoped claude mcp add above is the preferred path. If you instead use a project-scoped .mcp.json at the monorepo root (gitignored — per-developer), omit any auth field — after npx pt login the stdio server reads the credential store:
{
"mcpServers": {
"constellation": {
"command": "npx",
"args": ["tsx", "tools/mcp-server/src/index.ts"],
"env": {
"PT_BASE_URL": "https://constellation.planetb2b.com/projects",
"WIKI_BASE_URL": "https://constellation.planetb2b.com/wiki"
}
}
}
}
Run from the repo, the server loads root .env/.env.local before resolving auth, and a PT_AUTH_TOKEN/PT_AUTH_COOKIE from there takes precedence over the credential store. So a leftover value in root .env/.env.local — or a project-scoped .mcp.json still carrying a static token (Claude resolves local → project → user) — silently keeps using the stale credential even with a token-free config. To rely on the store: clear PT_AUTH_TOKEN and PT_AUTH_COOKIE from root .env/.env.local (and any .mcp.json auth field), then confirm with npx pt whoami — it must report "via the pt login credential store". Set a static token only for CI/headless (see Legacy / CI).
For local dev against localhost:3002 against a logged-in browser / mock-auth session, add a PT_AUTH_COOKIE you grab from your browser dev tools:
{
"env": {
"PT_BASE_URL": "http://localhost:3002",
"PT_AUTH_COOKIE": "constellation-auth=<paste value here>"
}
}
Claude Code picks up the change next session.
Claude Desktop
Claude Desktop is not configured by claude mcp add (that writes Claude Code's config) — edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or the equivalent on your platform. Run npx pt login once, then use the same store-backed config, with no auth field (the stdio server reads the credential store):
{
"mcpServers": {
"constellation": {
"command": "npx",
"args": ["tsx", "/absolute/path/to/constellation/tools/mcp-server/src/index.ts"],
"env": {
"PT_BASE_URL": "https://constellation.planetb2b.com/projects",
"WIKI_BASE_URL": "https://constellation.planetb2b.com/wiki"
}
}
}
}
Note the absolute path — Claude Desktop runs from $HOME, not the repo root. Restart Claude Desktop after editing. (Add PT_AUTH_TOKEN only for a CI/headless-style setup — see Legacy / CI.)
OpenAI Codex CLI
Codex CLI reads MCP servers from ~/.codex/config.toml (TOML, not JSON):
[mcp_servers.constellation]
command = "npx"
args = ["tsx", "/absolute/path/to/constellation/tools/mcp-server/src/index.ts"]
env = { PT_BASE_URL = "https://constellation.planetb2b.com/projects", WIKI_BASE_URL = "https://constellation.planetb2b.com/wiki" }
No PT_AUTH_TOKEN line — after npx pt login the credential store supplies auth (add a token only for CI/headless). Restart any running Codex sessions after editing.
Smoke test. Ask Codex something like "use the constellation MCP to list my IN_PROGRESS tasks across all five projects" — it should call get_workspace_context and the per-project list_tasks tools without further prompting.
Cursor / Aider / other stdio MCP hosts
The same pattern applies — command + args + env, with whatever syntax that runtime's config uses (JSON for most, TOML for Codex). The repo-relative path works only for hosts that run from the repo root; otherwise use an absolute path. See tools/mcp-server/README.md for the canonical reference.
ChatGPT custom connector
The MCP server speaks Streamable HTTP in addition to stdio, so ChatGPT's Developer Mode custom connectors can talk to it directly — no local bridge required.
- Mint a PT API key as above.
- Add the connector: in ChatGPT, open Settings → Connectors → Advanced → Developer mode → Add custom MCP.
- Fill in the URL (
https://mcp.planetb2b.com/api/mcp— the hosted MCP service is its own Vercel deployment, separate fromPT_BASE_URL), authentication (Bearer token, paste the JWT), and a name. - Save and test in a new conversation.
Full ChatGPT setup walkthrough including the URL path and authentication mode is in tools/mcp-server/README.md.
OAuth connector scopes (read / write)
Connector sessions authenticated via the one-click OAuth flow (Claude.ai custom connectors, ChatGPT OAuth mode) are scoped by the OAuth grant, not by the API key:
- Default grant is
read write(INF-279): when the client omits the OAuthscopeparameter — Claude's connector client does in practice — the connector grants full read + write, so mutation tools (create_task,update_task,resolve_issue,update_page, …) work out of the box. The 401 challenge also advertisesscope="read write"so spec-following clients request it explicitly. - Write means every mutation tool, including destructive ones (
delete_project,delete_space, …). The Directory consent screen discloses this before you click Allow. - A client that explicitly requests
scope=readstays read-only: the per-token scope gate (INF-215) hides mutation tools fromtools/listand rejects them ontools/call. - Writes act as you: the connector forwards a Directory-issued token carrying your own PT roles, so every mutation is authorised by your real permissions and attributed to your user in the PT audit trail.
Manual-Bearer setups (everything above this section) are unaffected — an API key carries no OAuth scope and exposes all tools, gated by your PT roles.
What's exposed as MCP tools
The MCP server surfaces the same operations as the CLI subcommands, with MCP-style names (snake_case instead of kebab-case). Most rows map directly; exceptions are noted in the table (get_initiative_summary has two CLI spellings, pt release-kb-curate has no MCP equivalent), and MCP-only tools with no CLI subcommand at all are listed below the table.
| CLI subcommand | MCP tool name |
|---|---|
pt workspace | get_workspace_context |
pt find-project | find_project |
pt list-projects | list_projects |
pt get-project-overview | get_project_overview |
pt create-project | create_project |
pt update-project | update_project |
pt delete-project | delete_project |
pt initiative / pt get-initiative-summary | get_initiative_summary |
pt list-initiatives | list_initiatives |
pt create-initiative | create_initiative |
pt update-initiative | update_initiative |
pt delete-initiative | delete_initiative |
pt list-tasks | list_tasks |
pt list-epic-children | list_epic_children |
pt get-task | get_task |
pt create-task | create_task |
pt update-task | update_task |
pt mark-awaiting-decision | mark_awaiting_decision |
pt answer-decision | answer_decision_request |
pt cancel-decision | cancel_decision_request |
pt claim-next-ready | claim_next_ready_task |
pt create-task-link | create_task_link |
pt delete-task-link | delete_task_link |
pt list-issues | list_issues |
pt get-issue | get_issue |
pt create-issue | create_issue |
pt update-issue | update_issue |
pt assign-issue | (subsumed into update_issue) |
pt escalate-issue | (subsumed into update_issue) |
pt transition-issue | transition_issue |
pt resolve-issue | resolve_issue |
pt list-stages | list_stages |
pt consult | consult_coordinator |
pt query-kb | query_knowledge_base |
pt release-kb-curate | (no MCP equivalent — CLI-only convenience wrapper; see release-and-migrations) |
The MCP side additionally exposes create_stage, reorder_stages, find_user, and resolve_key with no CLI equivalent.
pt list-tasks's flow filters map to boolean list_tasks arguments of the same name: --ready → ready, --review → review, and --awaiting-decision (PT-905, the Decisions queue) → awaiting_decision. All three are mutually exclusive on both surfaces.
When WIKI_BASE_URL is set, 24 additional wiki tools are registered (no CLI equivalent). They talk to the wiki module over its REST API only — the same Directory-issued token works across zones:
- Spaces —
list_spaces,get_space,create_space,update_space,delete_space,decommission_space - Pages —
search_pages,get_page,list_child_pages,create_page,update_page - Links & dependencies —
list_page_links,link_pages,traverse_dependencies - Attachments —
get_attachment - Maintenance —
rebuild_space_index,append_space_log - Lint (honesty loop) —
lint_space,list_lint_findings,record_lint_finding,resolve_lint_finding - Source ingestion —
ingest_source,retract_source,revert_ingest_batch
Tool arguments are strict — a misnamed parameter is an error, not a no-op
Every tool rejects arguments it does not declare. Passing an undeclared key fails the call, names the offending key, and the handler does not run.
The failure arrives as an ordinary tool result carrying isError: true — not as a JSON-RPC error response — so a client sees it the same way it sees any other tool failure, in the result text:
MCP error -32602: Input validation error: Invalid arguments for tool create_page: Unrecognized key(s) in object: 'content'
The MCP error -32602 prefix is part of that text, not a transport-level error code: the server catches the InvalidParams exception and reports it as tool output.
This matches what each tool has always advertised in tools/list (additionalProperties: false) — the server previously stripped undeclared keys and returned success instead of enforcing it. The practical consequence of that leniency: create_page given content: where the schema declares bodyMd: returned a real page id for a page published with an empty body, and nothing in the response indicated anything was wrong.
So when a call fails this way, read the key name in the error rather than retrying — the parameter exists under a different name. The common ones on create_page / update_page are bodyMd (not content or body), parent (not parentId), and pageType (not type); frontmatter is an object, not a JSON string.
See also
- CLI overview — when to use the CLI vs the MCP server.
- Subcommand reference — every operation, with arguments and examples.
tools/mcp-server/README.md— canonical install + auth reference; this page is a summary.