MCP server
Connect Claude Code or another MCP client to the API, with one tool per operation.
WorkRoad runs an MCP server at https://workroad.app/api/mcp. It turns each REST API operation except webhook management into a tool, so an agent can read and change your notes, tasks and study sets without writing HTTP calls. A tool call runs the same checks as the REST request it stands for: scopes, rate limits, input validation and idempotency.
The server speaks MCP's Streamable HTTP transport without sessions. Every call is a single POST that answers with JSON, so there is no event stream to hold open.
Connect a client
Create an API key in Settings → API keys with the scopes the agent needs. Put it in an environment variable once, for example in your shell profile, so no config file holds the key itself:
export WORKROAD_API_KEY=wr_...Claude Code. Add this to .mcp.json in your project. Claude Code expands ${WORKROAD_API_KEY} from its environment when it uses the server:
{
"mcpServers": {
"workroad": {
"type": "http",
"url": "https://workroad.app/api/mcp",
"headers": { "Authorization": "Bearer ${WORKROAD_API_KEY}" }
}
}
}Or register it from the command line. claude mcp add-json takes the server object itself, without the mcpServers wrapper. Keep the single quotes so your shell leaves ${WORKROAD_API_KEY} alone: Claude Code then stores the reference and resolves it when it connects, so the key is not written to its config.
claude mcp add-json workroad '{"type":"http","url":"https://workroad.app/api/mcp","headers":{"Authorization":"Bearer ${WORKROAD_API_KEY}"}}'Codex. Run this, or add the table below to ~/.codex/config.toml. Codex reads the variable named in bearer_token_env_var and sends it as the bearer token:
codex mcp add workroad --url https://workroad.app/api/mcp --bearer-token-env-var WORKROAD_API_KEY[mcp_servers.workroad]
url = "https://workroad.app/api/mcp"
bearer_token_env_var = "WORKROAD_API_KEY"Cursor. Add this to .cursor/mcp.json in your project or ~/.cursor/mcp.json for every project. Cursor reads the variable from the environment it was started in, so launch it from a shell where the variable is set:
{
"mcpServers": {
"workroad": {
"url": "https://workroad.app/api/mcp",
"headers": { "Authorization": "Bearer ${env:WORKROAD_API_KEY}" }
}
}
}Use a key with only the scopes the agent needs, and revoke it in Settings if it leaks.
In Claude Code, run /mcp to check the connection. workroad should show as connected, with its tools listed. A 401 means the key is missing, wrong or revoked, or the variable was not set when the client started.
Other MCP clients connect the same way: point them at /api/mcp and send the key as Authorization: Bearer <key>. The server does not offer OAuth.
Test mode
https://workroad.app/api/test/mcp has the same tools, but each one returns the fixed example response from its operation page instead of running, as test mode does for REST. Register it under a second name, such as workroad-test, to try an agent without touching your data. In Claude Code:
claude mcp add-json workroad-test '{"type":"http","url":"https://workroad.app/api/test/mcp","headers":{"Authorization":"Bearer ${WORKROAD_API_KEY}"}}'For Codex and Cursor, use the snippets above with the /api/test/mcp URL.
Which tools appear
The key's scopes decide the tool list. Each tool is named after its operation's id, such as listTasks or createNote, and appears only when the key's scopes allow that operation. A key with only tasks:view sees listTasks, getTask, listTaskTypes and listSubjects, plus getMe and getAiAllowance, which any key may call. The scope table lists what each operation needs.
The server builds the list when the client asks for it. After you change a key's scopes, reconnect (in Claude Code, /mcp) so the client fetches the new list.
Tool calls count against the key's REST rate limits: a read tool uses the read bucket and a write tool the write bucket.
Arguments and results
A tool's arguments are the operation's path parameters, query parameters and body fields, all at the top level. getTask takes { "taskId": "..." }, and updateTask takes taskId next to the fields to change. Each tool's description repeats the operation's summary and required scopes, and its input schema has every field's type and limits.
Write tools that create something (the POST operations) also take an optional idempotencyKey. It works like the REST Idempotency-Key header: retry with the same key and arguments and you get the first result back, without creating anything twice. See Idempotency.
A successful call returns one text block containing the HTTP status, optional retry timing and REST response body, as { "status": 201, "body": { "data": { ... } } }. The server omits structuredContent, so clients that pass only text to the model still receive the full body. The status matters where an operation answers two ways, such as createReview, which returns 200 instead of 201 for a review it already recorded.
A call the API refuses comes back as a tool error, not a protocol error, so the agent can read it and correct itself. The text block holds the status and the REST error envelope:
{
"status": 403,
"error": { "code": "forbidden", "message": "This API key does not grant this operation." }
}Invalid arguments fail the way the matching REST request would. A bad body field returns 400 invalid_request with the failing fields in details, a bad query argument such as a limit of 500 returns 400 invalid_query, and a malformed path id returns 404 not_found. The error codes are the same as for REST.
When the REST response would carry a Retry-After header, the text block adds retryAfter in seconds, as in { "status": 429, "retryAfter": 3, "error": { ... } }. Wait that long before calling again. Successful calls carry it too: getJob on a pending or running job returns { "status": 200, "retryAfter": 5, "body": { "data": { ... } } }.
Uploading files
The agent uploads a file itself; the MCP server never handles file contents. createNoteFile returns the file's id, a signed upload.url and the upload.fields. The agent then POSTs the file to upload.url as multipart/form-data, with every field from upload.fields in order and the file last as file, and calls completeNoteFile inside the same 10 minutes. Uploading files has the details and limits.
Long-running work
Tools that start AI work or parse a file, such as generateNoteFromTopic, sendCopilotMessage or completeNoteFile, return a jobId straight away. Poll getJob with that id as jobId until status is succeeded, failed or cancelled, waiting the retryAfter seconds from the text block between calls. Jobs describes each kind's result.
Webhook management is REST-only. The webhook operations are not MCP tools, because a webhook sends events to a URL the caller picks, which would let an injected agent send data out. Create webhooks in the app or over REST.