Flow
ServicesMCP Gateway

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
EnvironmentURL
devhttps://intelligence-mcp.dev.apps-and-services.com/mcp
localhttp://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:

WhereRead by
instructions on the initialize resultevery client, before the model's first action
The flow-intelligence://protocol resourcea client that drops or truncates instructions
The get_started toolan 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.

On this page