Borker MCP
Connect any MCP client — Claude Code, Claude Desktop, and more — to your workspace with one URL and an agent key.
The Borker MCP is a hosted Model Context Protocol endpoint. Point any MCP client at it with an agent key and your AI assistant immediately knows how to drive your workspace — no SDK, no glue code, nothing to install.
https://borker.xyz/api/v1/mcpConnect from Claude Code
claude mcp add --transport http borker https://borker.xyz/api/v1/mcp \
--header "Authorization: Bearer <your agent key>"Then just ask: "List my Borker channels and draft a post about our launch for X."
Connect from Claude Cowork or Claude Desktop
These two share one MCP configuration, so the setup is identical: Borker is
added as a local mcp-remote bridge in the Claude config file, and the bridge
forwards every call to https://borker.xyz/api/v1/mcp with your agent key in
the header. There is nothing to install beyond Node (npx fetches the bridge).
Add custom connector does not work for Borker yet. That dialog only
connects servers that speak OAuth, and Borker authenticates with a static
agent key, which the dialog has no field for. It fails in a way that looks
like success: the connector reports healthy and lists every tool, because
catalog discovery is unauthenticated, and then every action answers
missing_authorization. Use the config file below instead. OAuth sign-in for
that dialog is planned (BORK-459); until it ships, the bridge is the way in.
First, confirm your key works:
curl -s https://borker.xyz/api/v1/channels \
-H "Authorization: Bearer <your agent key>"You should get your channel list back. If you get a key error instead, issue a new key in Settings → Keys before going further, because a bad key surfaces as a confusing client error rather than a clear message.
Then add Borker to the config file:
{
"mcpServers": {
"borker": {
"command": "npx",
"args": [
"-y", "mcp-remote",
"https://borker.xyz/api/v1/mcp",
"--header", "Authorization:${AUTH_HEADER}"
],
"env": { "AUTH_HEADER": "Bearer <your agent key>" }
}
}
}| Platform | Config file |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
Restart the app and Borker's tools appear.
The missing space after Authorization: is deliberate. Claude Desktop mangles
spaces inside args, so the space lives in the environment variable instead.
mcp-remote substitutes ${AUTH_HEADER} itself, so this works whether or not
the client expands variables.
Connect from another client
Any client that speaks MCP over streamable HTTP and lets you set a request header works directly:
- URL:
https://borker.xyz/api/v1/mcp - Header:
Authorization: Bearer <your agent key>
If the client cannot set a header, use the mcp-remote bridge shown above.
What your agent gets
Fourteen tools mirroring the REST API:
| Tool | What it does | Credits |
|---|---|---|
list_channels | Connected channels + ids to target, each with its voice (personal or brand) | — |
get_brand | Brand voice profile for in-character writing | — |
get_schedule | Scheduling config, per-channel cadence, and the planned window (the coming 14 days of plan items) | — |
get_stats | Channel balance, pipeline depth, quota usage | — |
list_content / get_content | Browse the pipeline | — |
create_content_draft | Add a draft the agent wrote itself, for the channel in channelId (pass title for Paragraph; scheduledAt to pick its time, else it takes the channel's next open time; media to attach images; idempotencyKey to make a retry safe) | Free. A call that names no channel fails with 400 naming the connected channels, and no draft is created; a failing image fails the call the same way |
supply_entry | Send a build-log entry (source handle, title, body, happenedAt; media for at most one image; externalId to make a retry safe; kind and context to tell Borker's writer what the beat is and why it mattered; skillVersion to name the sending skill's version; destinations for the channel ids from list_channels it goes to, one by default). The entry arrives not yet planned, and Borker's night run places it on your channels unless you switched that off; the answer's paces says which | Free. No AI runs until the entry's day |
generate_content | Borker generates in your brand voice | 1 credit per channel |
update_content | Approve (optionally at a scheduledAt you pick), un-approve, reject, edit, move to another channel (channelId), replace the images (media, [] clears) | Approving queues the post at your scheduledAt, else its planned instant, else the plan's next open time, and the result's scheduledAt says which; on a scheduled post, status: "pending" unschedules it. Approving text with AI-ism matches needs acknowledgeAiIsms: true, else 409 with the match list. A draft with no channel, or on a disconnected one, cannot be approved (400): give it a connected channel with channelId. Giving an item the channel its plan slot waited for can approve a held draft and queue an approved item at its planned time; the returned item's status and scheduledAt say so, and plannedTimePassed: true means it needs a new time |
schedule_content | Schedule or publish-now | Same acknowledgeAiIsms contract as update_content. An item with no channel, or on a disconnected one, fails with 400 naming the cause |
delete_content | Remove non-published items | — |
request_media_upload | Get an image into Borker: a signed single-use upload link (15 minutes, curl -T), or inline base64 up to 1MB | — |
list_media | The uploads no post has taken yet; their ids go into media | — |
Autopilot approves and publishes only the posts Borker plans itself. A post an agent creates stays a draft until a person approves it or the agent schedules it through the API, within the workspace's checks: the sensitivity hold, the length limit and URL credits.
An agent with no shell and no way to send bytes (a chat-only client) attaches
images by URL: media: [{ "url": "https://…/photo.png" }] on
create_content_draft, and Borker fetches the picture itself. There is no
human drop page for an agent's images yet (BORK-658).
A call that timed out may still have created its draft. Pass the same
idempotencyKey (any string up to 255 characters you choose, a UUID works)
when you retry create_content_draft: within 24 hours the retry returns the
draft the first call created, with the same id, and creates nothing. A
different key creates a new draft, and keys never match across workspaces.
Over REST the same thing is the Idempotency-Key header on POST /content
with body; a prompt generation takes no key and is refused with 400 if it
carries one.
The agent's power is exactly the key's power: a Read-scope key makes
the write tools fail with scope_required, and every action is
confined to the key's workspace. Key management is deliberately not
exposed as MCP tools.
Everything an agent does lands in your normal review pipeline with the API badge — sensitivity holds, AI-ism detection, and your approval mode all still apply. Connecting an agent doesn't bypass your safety net.
Scoping the agent down
Issue a dedicated key per agent (e.g. "Claude Code — laptop", "Research agent — read only") so you can see per-key usage and revoke one integration without touching the others. Read-scope keys are great for agents that should analyze but never post.
How failures arrive
Two different things can go wrong, and they arrive differently on purpose.
A tool ran and failed — bad arguments, quota exhausted, item not found.
That is a normal MCP tool error: HTTP 200, result.isError: true, and the
v1 error envelope as the result text.
Your agent should read the code, act on it, and carry on. A call that
leaves out a required argument (get_content with no id, say) is refused
the same way before anything runs, as validation_error naming the argument.
The request itself was malformed — a body that is not JSON answers
JSON-RPC's -32700 Parse error; a JSON body that is not a request answers
-32600. initialize answers with a protocol version the server supports
(2025-06-18, 2025-03-26 or 2024-11-05): yours when it is one of those,
the newest otherwise.
Your credentials are the problem — no key, an invalid or revoked key, or a
key without the scope the tool needs. Those carry a real HTTP status, because
a 200 gives your client nothing to react to and hides auth-failure spikes
from anything sitting in front of the endpoint:
| Situation | Status | error.data.code |
|---|---|---|
No Authorization header | 401 | missing_authorization |
| Unknown, malformed or revoked key | 401 | invalid_api_key |
| Key lacks the required scope | 403 | scope_required |
| Key owner's role is too low | 403 | forbidden |
| Too many requests | 429 | rate_limited_key / rate_limited_ip |
The message stays readable in every case — the status is added, nothing is
taken away. No WWW-Authenticate header is sent: Borker uses static bearer
keys, so there is no authorization server for a client to discover.
initialize, tools/list and ping stay reachable without a key, so a client
can show the catalogue before you paste one in.
On a 429, error.data.retryAfterSeconds says how long to wait, and
rate_limited_key vs rate_limited_ip says whether it was your key or the
address you're calling from. create_content_draft and generate_content are
limited more tightly than the read tools — see
Rate limits.