MCP Server
CoderFlow can act as a Model Context Protocol (MCP) server. Once an administrator enables it, any MCP-capable agent — Claude Code, Claude Desktop / Cowork, GitHub Copilot in VS Code, Cursor, and others — can hand work to CoderFlow directly instead of a user copying and pasting between tools.
Through the MCP server an external agent can:
- Discover the environments and task templates the user can use
- Create a task, optionally passing along a handoff (summary and transcript) of the session it is coming from
- Attach files for the CoderFlow agent to read
- Poll a task's status and read the agent's final summary
- Send follow-up instructions to a finished task
Every connection authenticates with a user's personal API key and runs under that user's roles and permissions. The MCP server never grants access beyond what the same user has in the Web UI or CLI.
Enable the MCP Server
- Go to Settings → Integrations → MCP Server
- Toggle Enabled on and click Save
- Copy the Endpoint URL (for example
https://coderflow.example.com/mcp)
The endpoint uses the Site URL from General settings when one is configured; otherwise it reflects the address you are browsing from. Make sure the URL you hand to clients is reachable from the machines where those clients run. MCP and OAuth URLs are always built from the origin (https://host[:port]): the routes and the OAuth discovery documents live at the root, so a Site URL path prefix is not carried into them, and the public base URL setting must be an origin with no path.
The setting can also be pinned with the MCP_SERVER_ENABLED environment variable (true or false) on the server. When the variable is set, the Settings toggle is read-only.
The MCP server is off by default. Requests to /mcp return 404 while it is disabled.
Enable OAuth for Hosted Clients
Some clients cannot attach a static API key to a remote MCP server. claude.ai and Cowork custom connectors, for example, expect the server to implement the MCP authorization flow (OAuth 2.1 with PKCE and dynamic client registration). CoderFlow ships that authorization server built in.
- Set an HTTPS Site URL in Settings → General. The OAuth issuer is derived from it, and the MCP SDK refuses non-HTTPS issuers other than localhost.
- In Settings → Integrations → MCP Server, toggle OAuth for hosted clients on and click Save.
- Copy the Endpoint URL (for example
https://coderflow.example.com/mcp) and add it as a custom connector in the client.
When a client first calls the endpoint without credentials, CoderFlow answers 401 with a WWW-Authenticate challenge that points at the discovery document. The client registers itself, opens the CoderFlow login page in the browser (local login or SSO), shows a consent page naming the client, and receives tokens once the user approves. No key is copied anywhere.
Endpoints, all under the Site URL:
| Endpoint | Purpose |
|---|---|
/.well-known/oauth-authorization-server | Authorization server metadata |
/.well-known/oauth-protected-resource/mcp | Protected resource metadata for /mcp |
/mcp/oauth/register | Dynamic client registration |
/mcp/oauth/authorize | Login and consent, issues authorization codes |
/mcp/oauth/token | Exchanges codes and refresh tokens |
/mcp/oauth/revoke | Token revocation |
Token lifetimes:
| Token | Lifetime |
|---|---|
| Authorization code | 10 minutes, single use |
| Access token | 1 hour |
| Refresh token | 30 days, rotated on every use |
Tokens are stored only as hashes in mcp-oauth.json in the server data directory. The setting can be pinned with the MCP_OAUTH_ENABLED environment variable, and it has no effect while the MCP server itself is disabled.
Hardening the authorization server
Dynamic client registration is open by design: anyone who can reach the registration endpoint can register a client with any name. Two settings on the MCP Server panel close the resulting phishing path. Both are off by default so existing connections keep working; turn them on for production servers.
- Allowed OAuth redirect hosts. One hostname per line, with
*.example.commatching subdomains. Registrations and authorizations that redirect anywhere else are refused, including clients registered before the list was set. Loopback addresses used by local clients such as Claude Code andmcp-remoteare always allowed. For Claude connectors, allowclaude.aiand*.anthropic.com, and check the redirect host shown on the consent page the first time you connect. - Require fresh sign-in for consent. A number of minutes. Approving a new client then requires a login newer than that; an older browser session is sent back through the login page first. Local users re-enter their password (and MFA code when enrolled); SSO users are re-prompted by the identity provider because CoderFlow requests
prompt=login. This stops a walk-up on an unlocked, signed-in browser from silently authorizing a connector. On development servers started withDISABLE_AUTH, there is no login to refresh, so the rule is treated as satisfied.
Managing authorized clients
Users can list and revoke the clients they have approved:
# List my authorized MCP clients
curl -H "Authorization: Bearer $CODER_API_KEY" "$CODERFLOW_URL/api/mcp-oauth/grants"
# Revoke one client, or everything
curl -X DELETE -H "Authorization: Bearer $CODER_API_KEY" "$CODERFLOW_URL/api/mcp-oauth/grants/<client_id>"
curl -X DELETE -H "Authorization: Bearer $CODER_API_KEY" "$CODERFLOW_URL/api/mcp-oauth/grants"
Revoking a refresh token also revokes the access tokens issued with it. Deleting a user revokes everything they authorized, because tokens only work while the user still exists.
Dedicated MCP Listener
By default the MCP endpoint and its OAuth routes are served by the main CoderFlow port. Deployments can instead start a second HTTP listener that serves only the machine-facing MCP routes and expose just that port, keeping the main application port private. This is the same pattern as the messaging-integrations and webhook listeners, and like them it is route and port isolation inside one Node process, not a separate process or privilege boundary.
Set the following before starting the server:
MCP_INGRESS_MODE=listener
MCP_INGRESS_PORT=3004
MCP_INGRESS_HOST=0.0.0.0
MCP_INGRESS_PORT by itself also enables listener mode. The listener uses the same TLS configuration as the main server.
The listener serves:
GET /healthPOST /mcp(plus theGET/DELETEmethod-not-allowed responses)POST /mcp/oauth/token,POST /mcp/oauth/register,POST /mcp/oauth/revokeGET /.well-known/oauth-authorization-serverGET /.well-known/oauth-protected-resource/mcp
It does not serve the web UI, the authenticated APIs, or /mcp/oauth/authorize. Login and consent always happen on the main application, in the user's browser. Only bearer credentials (OAuth access tokens or personal API keys) authenticate on the listener; browser sessions do not.
When using the listener:
- Set Site URL in General settings to the main application URL users open in a browser. The OAuth metadata publishes the authorization endpoint there.
- Set Public base URL for MCP clients on the MCP Server panel to the public HTTPS address of the listener, for example
https://mcp.example.com. The endpoint URL, discovery documents, and token endpoints are published there. TheMCP_PUBLIC_BASE_URLenvironment variable pins the same value. - Check the read-only Dedicated MCP listener field to confirm the server sees the expected mode and port.
Internal servers and hosted connectors
This split is what makes hosted connectors work against a CoderFlow that is not on the public internet. Claude connectors make their MCP, registration, and token requests from Anthropic's servers, but the login and consent steps run in the user's browser. With the listener:
- Expose only the listener port through the firewall, ideally restricted to Anthropic's published egress IP ranges, with a publicly trusted certificate.
- Keep the main application internal. Users on the internal network reach the login and consent pages there.
- Set the redirect allowlist to the Claude callback hosts so nothing else can register.
Connect a Client
Clients that support the MCP authorization flow (claude.ai and Cowork custom connectors, and recent versions of Claude Code, Claude Desktop, VS Code, and Cursor) can use OAuth as described above: add the endpoint URL and sign in when prompted.
Any client can instead use a personal API key sent as a Bearer token on every request. There is no Web UI page for creating one; use any of these routes:
- Run
coder login(username and password) orcoder login --sso(OIDC device flow). The server issues a key and the CLI stores it in the active profile in~/.coder/config.json. - Sign in through the VS Code extension's Profile Manager; it stores the returned key in the selected profile.
- Call
POST /api-keyswith{ "name": "MCP" }from an authenticated session; the secret is returned once.
See API Keys for rotation and revocation. Replace <API_KEY> in the examples below.
Claude Code
claude mcp add --transport http coderflow https://coderflow.example.com/mcp \
--header "Authorization: Bearer <API_KEY>"
Then, from any Claude Code session:
Hand this work off to CoderFlow in the
billingenvironment. Summarize what we did and what is left.
Claude Code calls create_task with a handoff object, and the resulting task appears in the CoderFlow dashboard.
Claude Desktop and Cowork
With OAuth enabled, add CoderFlow as a custom connector using the endpoint URL. Cowork opens the CoderFlow login page, the user approves the consent screen, and the connector is ready. Organization administrators can add the connector once for everyone.
Without OAuth, Claude Desktop can still launch local MCP servers from claude_desktop_config.json. Use the mcp-remote bridge to reach CoderFlow's HTTP endpoint with a Bearer token:
{
"mcpServers": {
"coderflow": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://coderflow.example.com/mcp",
"--header",
"Authorization: Bearer <API_KEY>"
]
}
}
}
Restart Claude Desktop after editing the file. The CoderFlow tools then appear in Cowork sessions.
VS Code and GitHub Copilot
Add CoderFlow to .vscode/mcp.json (workspace) or your user MCP settings:
{
"servers": {
"coderflow": {
"type": "http",
"url": "https://coderflow.example.com/mcp",
"headers": {
"Authorization": "Bearer <API_KEY>"
}
}
}
}
To keep the key out of the file, VS Code supports input variables:
{
"inputs": [
{ "id": "coderflow-key", "type": "promptString", "password": true, "description": "CoderFlow API key" }
],
"servers": {
"coderflow": {
"type": "http",
"url": "https://coderflow.example.com/mcp",
"headers": { "Authorization": "Bearer ${input:coderflow-key}" }
}
}
}
Other clients
Any client that supports the MCP Streamable HTTP transport with custom headers can connect. The server is stateless: it does not issue session IDs and does not open a standalone server-to-client event stream, so it works behind ordinary reverse proxies and load balancers without sticky sessions.
Tools
| Tool | What it does |
|---|---|
list_environments | Environments the user can see, with repositories, default agent, and whether the user may create tasks there. |
list_templates | Task templates in an environment, including parameter definitions. |
create_task | Create and start a task. Accepts instructions, an optional template and parameters, agent, branches, staged, file attachments, and a handoff object. |
get_task | Current status of a task. Includes the agent's summary once the task has finished. |
list_tasks | Tasks visible to the user, newest first, with optional environment, status, and ownership filters. |
follow_up_task | Continue a finished (or staged) task with more instructions and optional attachments. |
Session handoff
create_task accepts a handoff object:
{
"environment": "billing",
"instructions": "Finish the invoice totals refactor and make the tests pass.",
"handoff": {
"source": "Claude Code session",
"summary": "Refactored invoice.js to use Decimal. totals.test.js still fails on rounding. Do not touch the API layer.",
"transcript": "…full conversation…"
}
}
CoderFlow inlines the summary into the task instructions (bounded to roughly 24,000 characters) and writes the full summary and transcript to /task-output/attachments/handoff.md inside the task container, so the CoderFlow agent starts with the prior context instead of a cold prompt.
Attachments
attachments is an array of { "filename", "content", "encoding" } entries where encoding is utf8 (default) or base64. The same limits as Web UI uploads apply: 25 files per request and 50 MB per file. Files land in /task-output/attachments/ and are listed at the end of the task instructions.
Permissions
| Tool | Required permission |
|---|---|
list_environments, list_templates | Environment visibility (any binding in the environment) |
create_task | tasks:create in the target environment |
get_task, list_tasks | tasks:view (private tasks: owner, or tasks:view_any) |
follow_up_task | tasks:change (private tasks: owner, or tasks:change_any) |
Tasks created through MCP record mcp as their source and are attributed to the API key's user.
Troubleshooting
- 404 from
/mcp— The MCP server is disabled. Enable it in Settings, or checkMCP_SERVER_ENABLED. - 401 Invalid API key — The Bearer token is missing, revoked, or belongs to a deleted user. Create a new personal API key.
permission_deniedin a tool result — The user lacks the permission listed above in that environment. Adjust their role bindings.environment_not_foundfor an environment that exists — The user has no binding in that environment, so it is hidden from them. Grant access or pick another environment fromlist_environments.- Client cannot send headers — Enable OAuth for hosted clients so the client can sign in instead, or use the
mcp-remotebridge shown above, which runs locally and adds the header for you. 503from the OAuth endpoints — The Site URL is missing or not HTTPS. Set anhttps://Site URL in General settings.invalid_client_metadataorinvalid_requestmentioning the redirect — The client's callback host is not on the allowed redirect hosts list. Add it, or clear the list.- Consent keeps bouncing to the login page — The fresh sign-in rule is on and the session is older than the limit. Signing in again (or completing the identity provider's re-authentication) returns you to the consent page.
- Listener answers 404 for the authorization URL — Expected.
/mcp/oauth/authorizeis only served by the main application; check that Site URL points at it. - Consent page says the form expired — The browser session changed between loading and submitting the consent page. Start the connection again from the client.
- Token works, then stops after an hour — Expected: access tokens are short-lived and the client refreshes them automatically. If refresh fails, the grant was revoked or expired; reconnect from the client.