MCP server
An MCP server at https://crossfeed.m1kuorg.workers.dev/mcp gives an assistant the same operations as the API, as tools, for the network its API key belongs to. It serves Streamable HTTP, statelessly, and takes the key as a bearer token, with no OAuth.
Connect your assistant
Each assistant reads one of the network’s API keys from where you keep it, or asks you for it: no command, configuration or link here holds one. Signed in, Connect your AI, on your network’s Developers page, makes a key and gives the same steps.
Claude Code
Put the key in your shell as
CROSSFEED_API_KEY: in bash or zsh, run this, paste the key, and press Return. Nothing shows as you paste, and the key stays out of your shell’s history. On Windows, run these steps in WSL or Git Bash: in PowerShell,$CROSSFEED_API_KEYis empty, andclaude mcp addwould save the header without the key.Shell read -rs CROSSFEED_API_KEY && export CROSSFEED_API_KEYAdd Crossfeed’s MCP server to Claude Code, in the folder you’ll use it from. Claude Code keeps the key from then on, in plain text in its own config file,
~/.claude.json.Shell claude mcp add --transport http crossfeed \ https://crossfeed.m1kuorg.workers.dev/mcp \ --header "Authorization: Bearer $CROSSFEED_API_KEY"Check it:
claude mcp listshould show it connected.Shell claude mcp listOutput crossfeed: https://crossfeed.m1kuorg.workers.dev/mcp (HTTP) - ✔ Connected
The Crossfeed plugin installs two skills that teach Claude Code to plan launch promos and triage guest pitches, with the MCP server: see Skills and plugin.
Claude
Add Crossfeed as a custom connector, which Claude Desktop gets too: each link opens Claude’s Add custom connector dialog with Crossfeed’s name and address filled in, for you to check.
Add to ClaudeOpens claude.ai in a new tab, asking you to sign in first if you haven’t. On a Team or Enterprise plan, only an Owner, or a custom role allowed to, adds a connector, so ask an Owner.
Add for your organizationOpens your organization’s connectors in a new tab, for a Team or Enterprise Owner, who adds Crossfeed for everyone in the organization, who then share its key.
For Authentication, choose No sign-in. Then under Request headers, choose
authorization, enterBearer, a space and your key, mark it Required, and choose Add. Claude keeps the key and doesn’t show it again, and can’t change these settings later: for a mistyped key, remove the connector and add it again. Request headers are a beta that only some organizations have: if the dialog has none, connect Claude Desktop as below, or use Claude Code.In a chat, turn Crossfeed on under the + button’s Connectors.
Without request headers, Claude Desktop can reach Crossfeed through mcp-remote, a bridge it runs with Node.js 18 or later. In its Settings, under Developer, choose Edit Config, add Crossfeed to claude_desktop_config.json as here, and type your key in place of cf_live_…. Then quit Claude Desktop and open it again. It keeps the key in plain text in that file.
claude_desktop_config.json{
"mcpServers": {
"crossfeed": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://crossfeed.m1kuorg.workers.dev/mcp",
"--header",
"Authorization:${AUTH_HEADER}"
],
"env": {
"AUTH_HEADER": "Bearer cf_live_…"
}
}
}
}Cursor
Put the key in your shell as
CROSSFEED_API_KEY: in bash or zsh, run this, paste the key, and press Return. Nothing shows as you paste, and the key stays out of your shell’s history. On Windows, in PowerShell, the first two lines ask for the key without showing it and set it for that window, and the third keeps it for apps you start afterwards, in plain text in your user environment.Shell read -rs CROSSFEED_API_KEY && export CROSSFEED_API_KEYPowerShell $k = Read-Host 'Crossfeed API key' -AsSecureString $env:CROSSFEED_API_KEY = [System.Net.NetworkCredential]::new('', $k).Password [Environment]::SetEnvironmentVariable( 'CROSSFEED_API_KEY', $env:CROSSFEED_API_KEY, 'User')Quit Cursor, then start it again from that terminal with
cursor: Cursor reads the variable as it starts, and started from the Dock, it doesn’t see one set in a terminal. On a Mac, run Shell Command: Install ‘cursor’ command in PATH from Cursor’s Command Palette once first, so the terminal hascursor.Add Crossfeed’s MCP server: follow the link, and Cursor asks before it adds it, or paste this into
~/.cursor/mcp.json, for every project. Either way, Cursor sends the key from the variable.Add to CursorOpens Cursor if it’s installed. Start Cursor from the terminal where you set the variable first, or it has no key to send.
~/.cursor/mcp.json{ "mcpServers": { "crossfeed": { "url": "https://crossfeed.m1kuorg.workers.dev/mcp", "headers": { "Authorization": "Bearer ${env:CROSSFEED_API_KEY}" } } } }
VS Code
Put the key in your shell as
CROSSFEED_API_KEY: in bash or zsh, run this, paste the key, and press Return. Nothing shows as you paste, and the key stays out of your shell’s history. On Windows, in PowerShell, the first two lines ask for the key without showing it and set it for that window, and the third keeps it for apps you start afterwards, in plain text in your user environment.Shell read -rs CROSSFEED_API_KEY && export CROSSFEED_API_KEYPowerShell $k = Read-Host 'Crossfeed API key' -AsSecureString $env:CROSSFEED_API_KEY = [System.Net.NetworkCredential]::new('', $k).Password [Environment]::SetEnvironmentVariable( 'CROSSFEED_API_KEY', $env:CROSSFEED_API_KEY, 'User')Quit VS Code, then start it again from that terminal with
code: VS Code reads the variable as it starts, and started from the Dock, it doesn’t see one set in a terminal. On a Mac, run Shell Command: Install ‘code’ command in PATH from VS Code’s Command Palette once first, so the terminal hascode.Add Crossfeed’s MCP server: follow the link, and VS Code asks before it adds it, or run MCP: Open User Configuration from the Command Palette and paste this in. Either way, VS Code sends the key from the variable.
Add to VS CodeOpens VS Code if it’s installed. Start VS Code from the terminal where you set the variable first, or it has no key to send.
mcp.json{ "servers": { "crossfeed": { "type": "http", "url": "https://crossfeed.m1kuorg.workers.dev/mcp", "headers": { "Authorization": "Bearer ${env:CROSSFEED_API_KEY}" } } } }
Without the variable, VS Code can ask for the key as the server first starts, hide it as you type, and keep it: paste this into mcp.json instead.
mcp.json{
"inputs": [
{
"type": "promptString",
"id": "crossfeed-api-key",
"description": "Crossfeed API key",
"password": true
}
],
"servers": {
"crossfeed": {
"type": "http",
"url": "https://crossfeed.m1kuorg.workers.dev/mcp",
"headers": {
"Authorization": "Bearer ${input:crossfeed-api-key}"
}
}
}
}Codex
Put the key in your shell as
CROSSFEED_API_KEY: in bash or zsh, run this, paste the key, and press Return. Nothing shows as you paste, and the key stays out of your shell’s history. On Windows, run these steps, andcodex, in WSL or Git Bash.Shell read -rs CROSSFEED_API_KEY && export CROSSFEED_API_KEYAdd Crossfeed’s MCP server to Codex. Codex reads the key from the variable each time it starts, so it never writes the key down.
Shell codex mcp add crossfeed \ --url https://crossfeed.m1kuorg.workers.dev/mcp \ --bearer-token-env-var CROSSFEED_API_KEYStart
codexfrom a shell where the variable is set: its/mcpcommand lists Crossfeed among its servers.
Another MCP client
Give it the MCP server’s address, which serves Streamable HTTP.
URL https://crossfeed.m1kuorg.workers.dev/mcpHave it send the key in the
Authorizationheader, asBearer, a space and the key, with every request.HTTP Authorization: Bearer cf_live_…
ChatGPT can’t connect yet: its custom MCP servers sign in with OAuth or not at all, and Crossfeed’s MCP server takes an API key.
Tools
The 23 tools the server lists today. Each description ends with the same sentence: “When a run is waiting on a question, ask the human that question as it is and answer with their words; never answer it yourself.”
| Tool | What it does |
|---|---|
get_networkReads | Read the network: its name, ID, and CPM, which prices every promo slot. |
get_rosterReads | Read the network's roster: each show, how it matched in Qloo, its downloads per episode, and whether it's left out of launches. It's pending while any show is being looked up; read it again until it's settled. |
import_rosterChanges | Import the network's roster: a CSV with a name column and optional downloads, status, and description columns (format csv), or one show name per line (format lines), at most 50 shows. It returns at once with the new shows pending; read the roster until it's settled. Over a roster that has shows, set replace: the shows already on it keep their matches, comps, and exclusions, and only the figures the import brings change.Takes roster, format, replace (optional). |
update_showChanges | Settle or edit one show, named as on the roster: pick one of an ambiguous show's candidates (entityId), map a show Qloo has no match for to one of its comp search's results (compId), or change its downloads per episode, status, or exclusion from launches. A field left out stays as it is.Takes name, entityId (optional), compId (optional), downloads (optional), status (optional), excluded (optional). |
search_compsAdds | Search Qloo for a comparable show to stand in for one it has no match for. It returns at once with the search pending; read the roster until it's settled, then map the show to a result with update_show and compId.Takes query, name. |
get_overlapReads | Read the roster's overlap: which of its shows share listeners with each other, as Qloo measured it. Each show's closest shows by listener overlap, as ranks and bands within its own ranking of the rest of the roster, and the close pairs, two shows each among the other's closest. It's measuring while its calls are out; read it again until it isn't. |
measure_overlapAdds | Measure the roster's overlap: rank the rest of the roster against each show matched in Qloo, one Qloo call each, counting shows on the same Qloo show once. It needs at least 4 such shows and no show still being looked up. It returns at once with the overlap measuring; read it with get_overlap until it isn't. With the roster's matched shows unchanged, it asks only the shows whose ranking is missing or failed, and nothing once all have landed. Its calls count against the network's daily Qloo calls. |
get_listenersReads | Read the roster's listener profile: who its shows reach, as Qloo profiled them. Each show's leans by age band and gender, relative to the average in Qloo, which describe listeners, not people, and which age bands and gender most of the shows lean toward. It's profiling while its call is out; read it again until it isn't. |
profile_listenersAdds | Profile the roster's listeners: one Qloo call for every show matched in Qloo, counting shows on the same Qloo show once. It needs at least 4 such shows and no show still being looked up. It returns at once with the profile profiling; read it with get_listeners until it isn't. With the roster's matched shows unchanged since a profile that landed, it asks nothing. Its call counts against the network's daily Qloo calls. |
list_launchesReads | List the network's launches, newest first, as its Launches page does. |
create_launchAdds | Plan a new launch by its name, as Qloo lists a released show, or by up to three released shows as comps for an unreleased one. It joins the network's Launches page; start a run with start_launch_run to rank the roster for it. A network keeps a limited number of launches at once; past it, a member deletes one on the Launches page to make room.Takes name (optional), comps (optional). |
start_launch_runChanges | Start a run of a launch: it ranks the network's roster by fit with the launch's listeners in Qloo, asks the human what it needs, such as the budget, and drafts an insertion order priced at the network's CPM. With no name or comps it plans the launch as it stands. It returns at once; read the launch with get_launch until the run is done or waiting on a question. It counts as one of the network's model runs, and ends a run left waiting. Only a member of the network approves an insertion order, in the app on its launch's page, https://crossfeed.m1kuorg.workers.dev/app/<networkId>/launches/<launchId>.Takes launchId, name (optional), comps (optional). |
get_launchReads | Read a launch: its status, and its latest run as it stands: the question it waits on and how to answer it, the chart of roster shows by fit, and the draft insertion order. Only a member of the network approves an insertion order, in the app on its launch's page, https://crossfeed.m1kuorg.workers.dev/app/<networkId>/launches/<launchId>.Takes launchId. |
answer_launch_questionAdds | Answer the question a launch's latest run is waiting on, with the human's words, by its ID and with the fields its kind takes: choice or text for choice; choice (one of its options) or text (the name Qloo lists the launch under) for launch; slots or dollars for budget; exclude, from its options, for exclusions; comps or text for comps; and picks for confirm_comps. The run resumes; read the launch again with get_launch.Takes questionId, launchId, runId, choice (optional), text (optional), slots (optional), dollars (optional), exclude (optional), comps (optional), picks (optional). |
edit_orderChanges | Change the lines of a launch's current insertion order, once its run has ended: each finalist's promo slots and host-read angle, or drop a show by leaving its line out. Code prices it again at the network's CPM. Editing an approved order returns it to draft. Only a member of the network approves an insertion order, in the app on its launch's page, https://crossfeed.m1kuorg.workers.dev/app/<networkId>/launches/<launchId>.Takes lines, launchId, orderId. |
get_evidenceReads | Read a launch's evidence: the Qloo calls its runs made, each with what was sent and what came back, scores included. Name one call, such as the chart's, to read that call alone in full.Takes launchId, call (optional). |
get_inboxReads | Read the network's inbox: each pitch, the guest and public work the agent read in it, and how the work matched in Qloo. It's reading while any pitch is being read or its work looked up; read it again until it's done. |
load_pitchesChanges | Load a day's guest pitches into the inbox, at most 50: as text, with a line of three dashes between pitches, where a pitch's first lines may give its From: and Subject:; or as pitches, each with its body and any from and subject. Only each pitch's public work, its title and type, reaches Qloo, never a guest's name or a sender. It returns at once with the pitches reading; read the inbox until it's done. Reading them counts as one of the network's model runs. Over an inbox that has pitches, set replace: they go, with the last pitch run's lists and replies.Takes text (optional), pitches (optional), replace (optional). |
confirm_workChanges | Confirm which of an ambiguous work's same-title candidates a pitch is about, by the human's choice. Only a work matched in Qloo is ranked.Takes entityId, pitchId. |
start_pitch_runChanges | Start a pitch run: it ranks the inbox's pitched works against a roster show matched in Qloo, and against its best past guests' works too, then drafts a reply to every pitch. It returns at once; read it with get_pitch_run until it's done or waiting on a question. It counts as one of the network's model runs, and ends a run left waiting. Only a member of the network approves a reply, in the app on the inbox page, https://crossfeed.m1kuorg.workers.dev/app/<networkId>/inbox.Takes show, pastGuests (optional). |
list_pitch_runsReads | List the network's pitch runs, newest first: the first is the latest, whose replies the inbox holds. Read one, with the replies it drafted, with get_pitch_run. |
get_pitch_runReads | Read a pitch run: its status, the question it waits on and how to answer it, its ranked lists, one per work type, with each pitch's rank by what it says (Claude's reading, not Qloo data), and the reply it drafted to each pitch. Only a member of the network approves a reply, in the app on the inbox page, https://crossfeed.m1kuorg.workers.dev/app/<networkId>/inbox.Takes runId. |
answer_pitch_questionAdds | Answer the question a pitch run is waiting on, with the human's words, by its ID: one of its options as choice, or their own words as text. The run resumes; read it again with get_pitch_run.Takes questionId, runId, choice (optional), text (optional), slots (optional), dollars (optional), exclude (optional), comps (optional), picks (optional). |
Reads change nothing; Adds create something new, such as a launch or an answer; Changes replace or edit what the network has. There’s no approve tool, and no tool changes the network’s CPM. The inbox and pitch run tools are listed only while guest pitches are switched on.
What a tool answers
Each tool answers in short text for the assistant: where things stand and what to call next, such as the question a run waits on and the tool, IDs, and fields that answer it. Its structured content is the operation’s result as the REST API answers it, with the same text again as summary, since Claude Code gives its model the structured content alone.
A refusal is a tool result marked as an error, with { error: { code, message } } and the API’s codes. Only the checks before any tool runs answer with an HTTP status: 401 without a key or with an unknown or revoked one, 429 past 120 requests a minute, 403 to a browser whose Origin is neither Crossfeed’s nor a local one, and 404 while accounts aren’t open.
A launch, from the assistant’s side
Asked to plan promos for a launch, the assistant creates it, starts a run, and reads it until the run waits. Then it asks you the run’s question as it is, and answers with your words:
| Assistant | Calls create_launch with { "name": "Night Shift Cinema" }, then start_launch_run, then get_launch until the run waits. |
|---|---|
| Crossfeed | Run 5f0c… is waiting on a question for the human (budget): What is the slot budget for Night Shift Cinema’s promos, as a slot count or a dollar amount? Ask the human this question as it is, and answer with their words… |
| Assistant | Crossfeed asks: what is the slot budget for Night Shift Cinema’s promos, as a slot count or a dollar amount? |
| You | 20 slots. |
| Assistant | Calls answer_launch_question with the run’s IDs and { "slots": 20 }, then reads the launch until the run is done. |
Once the run is done, get_launch has the chart and the draft insertion order, and the launch’s page in the app, where a member approves the order.