Intelligence MCP Gateway
How Cursor, Claude, and Codex reach Flow's knowledge through the MCP server, and how to connect one in a few lines of configuration.
This MCP gateway lets a chat client search what Flow knows before it answers. A well-behaved
client opens one conversation at the start of each chat with create_conversation and passes
that id on every later call. search, get_insight, get_pack, and list_projects write a
lookup stub before they return results — provenance, not the answer. The spoken answer is
appended with append_turn. save_output keeps a versioned artifact on a project.
save_file stores a documents blob on a conversation, a project, or both. A
filed conversation's file is linked to the project and the conversation; pass
conversation_id and the server uses the conversation's project. A file the
user uploaded is not in Flow until the client calls it, and it is not on the
conversation unless conversation_id is the id create_conversation returned.
Omitting conversation_id opens a new conversation rather than joining an earlier one.
It is a protocol adapter over endpoints that already exist: search and insights on intelligence;
projects, conversations, sessions, saved outputs, and files on workspace. MCP never writes to
intelligence. Foundry's Agent Harness V1 reimplements the same tools natively against those
HTTP APIs; it does not call this gateway.
It is not a service. It has no database, no migrations, and no domain of its own, which is why it
sits under mcp/ at the repository root rather than under services/. mcp/ holds one directory
per gateway, and this one is named for the knowledge it fronts rather than for a service it
belongs to. The reasoning is in
docs/decisions/0036-mcp-gateway-at-the-repository-root.md, and the code is
mcp/intelligence.
- Domain
- Services
- Kind
- MCP server
- Lifecycle
- Active
- flowctl project
- None
- Stacks
- Runs on its own
- Source
- mcp/intelligence
| Environment | URL |
|---|---|
| dev | https://intelligence-mcp.dev.apps-and-services.com/mcp |
| local | http://localhost:8117/mcp |
Connecting
There is no token to paste. The server answers an unauthenticated request with a 401 that
names its own authorization server; the client reads that, registers itself, opens a browser
to Cloudflare Access, and keeps the result. Configuration is a URL and nothing else.
In ~/.cursor/mcp.json, or .cursor/mcp.json for one repository:
{
"mcpServers": {
"flow": { "url": "https://intelligence-mcp.dev.apps-and-services.com/mcp" }
}
}Restart the client and the tools appear.
A conversation does not need a project
create_conversation takes project_id, but leaving it out is normal and is not a lesser
outcome. An unfiled conversation is recorded, only its author can read the transcript, and
the knowledge layer reads it like any other. Filing it later with move_conversation opens
the transcript to a project's members and puts it in that project's briefing pack; it is not
what makes the conversation count. create_project exists for work whose home does not
exist yet.
The one thing that keeps a recorded conversation out of the knowledge layer is the person
asking for it. set_conversation_privacy with private: true is that request, and an agent
should call it when asked rather than deciding on the user's behalf either way. A project can
also be marked as not contributing; neither setting overrides the other, and either one is
enough.
Pull requests
search matches words in the text. A pull request about a lease does not contain the phrase
"pull request", so "what are recent pull requests" and "what are engineers working on" come
back empty from search.
list_evidence reads the notes the caller is allowed to see, newest first. With no source
it reads GitHub and Azure DevOps. Each item names who opened the pull request, the title, when
it happened, and the start of the description. get_evidence reads one note in full. A question
about a topic in the code is still search, then get_evidence on the note it returns.
How the call order reaches the model
The rule that matters most governs the first turn of a chat: open one conversation, pass its id on every later call, and append both halves of every exchange. A tool description is the wrong place to say that, because hosts load descriptions lazily. A client can list every tool name with no schemas, so the rule first becomes readable several turns after the point it applied — which is how a session once answered two questions and recorded neither.
The call order is therefore stated in three places, all from one constant in mcp/intelligence/instructions.go:
| Where | Read by |
|---|---|
instructions on the initialize result | every client, before the model's first action |
The flow-intelligence://protocol resource | a client that drops or truncates instructions |
The get_started tool | an agent that has realized it is off-protocol |
Hosts truncate the instruction block, so CI checks it stays under 1800 bytes.
Two responses correct an agent that is already off-protocol. A call with no
conversation_id still opens one — refusing would cost a round trip and teach agents to
avoid the tool — but the response now carries conversation_created: true and a directive
naming the id, so the split transcript is visible rather than silent. And while a
conversation holds questions with no recorded answer, every read tool returns the same
warning that append_turn already did.
Install the skill too
The skill goes further than the instructions have room to: reading a claim in full rather
than quoting a snippet, attributing an answer, and filing a session onto a project. Search
records its query as an agent lookup, not as something the user said. Omitting
conversation_id opens a new conversation; it does not resume an earlier one.
Copy mcp/intelligence/skill/flow-knowledge into
~/.cursor/skills/ or ~/.claude/skills/. This repository already links it, so an agent
working here picks it up with no setup.
Recording without the agent's help
Everything above still depends on the agent choosing to record. On Claude Code and Cowork it
does not have to:
mcp/intelligence/plugin/flow-intelligence-capture is a plugin whose
UserPromptSubmit and Stop hooks call append_turn themselves and open the session with
capture: "hook". It is optional, and nothing in the protocol depends on it — Cursor and
Codex have no equivalent and are served by the instructions instead.
A hook fires once per turn and remembers nothing in between, so it cannot hold a
conversation_id. It passes the client's own session id as client_session_key, and the
gateway resolves that to one conversation per chat, opening it on first use under a stable
idempotency key so later turns join it rather than opening another.
Operations
Deploying intelligence — four releases from one image, what scales and what must not, scheduled work, spend caps, and the cost of changing the embedding model.
Operations
How a caller is identified, how the Cloudflare login works, what has to agree across three deployments, and how to run and deploy the gateway.