> ## Documentation Index
> Fetch the complete documentation index at: https://docs.flow.engineer/llms.txt
> Use this file to discover all available pages before exploring further.

# Build an MCP server

> Make your product's tools available in Claude, ChatGPT and other assistants.

Your product doesn't need to own the chat for someone to use it. An MCP server lets people
work with your product from the assistant they're already using, alongside the rest of their task.

Flow exposes your tools and skills over Streamable HTTP, with descriptors, authentication hooks
and structured results.

The host runs the conversation and calls your tools. You do not need a model key for the server.

## Try the starter

**1. Start the server**

From a [Flow project](https://docs.flow.engineer/framework/quickstart.md), run:

```sh Terminal theme={null}
npm run mcp
```

Keep it running. The starter prints its MCP URL and a Claude Code connection command.

**2. Connect a client**

**Claude Code**

Run the connection command printed by the starter. At its default port, it is:

```sh Terminal theme={null}
claude mcp add --transport http bean-and-leaf http://127.0.0.1:8790/mcp
```

Use the printed URL if the server chose a different port. Restart the client if it
does not discover the newly added connection.

**Claude & ChatGPT**

These remote hosts need an endpoint they can reach over **public HTTPS**. Deploy your
server, then add its `/mcp` URL through the host's MCP connection flow.

Use [Flow hosting](https://docs.flow.engineer/hosting.md) or your own Node hosting. Configure OAuth when the tools
access a user's account. The local `127.0.0.1` URL is for a client on your machine.

**Other MCP clients**

Add the URL as an HTTP MCP server in your client. Use a local URL for a local client
or HTTPS for a remote one.

The server uses Streamable HTTP. A client must support that transport and, for
account-protected tools, MCP authorization.

**3. Make a request**

Ask the connected assistant:

```text Try this theme={null}
What's on the café menu? Which drinks have oat milk?
```

**Check it worked:** the host calls `get_menu` and uses the returned menu to answer.
If you added the tool from [Write a tool](https://docs.flow.engineer/build-agent.md), ask about the opening hours too.

> [!NOTE]
>
> Connecting a server lets you test it in a host. Publishing to that host's public directory is
> a separate submission and review process. [Native plugin packages](https://docs.flow.engineer/plugins.md) are another way
> to deliver skills and an MCP connection.

## Serve your own tools

The starter already includes `src/mcp.ts`. In an existing Node project, install the optional
server SDK, then point `mcpServer` at your tools and agent folder.

```sh Terminal theme={null}
npm i @flow-engineer/flow @modelcontextprotocol/server@2.3.1
```

```ts src/mcp.ts theme={null}
import { createServer } from "node:http";
import { fileURLToPath } from "node:url";
import { mcpServer } from "@flow-engineer/flow/mcp";
import { tools } from "./tools.ts";

export const mcp = mcpServer({
  name: "my-product",
  version: "1.0.0",
  tools,
  dir: fileURLToPath(new URL("../agent", import.meta.url)),
});

createServer((req, res) => void mcp.node(req, res)).listen(8790);
```

Run it with `node src/mcp.ts`. It serves `/mcp` and the tools-only plugin endpoint
`/mcp/plugin`.

## What the host receives

| Your definition | MCP representation |
| - | - |
| `name`, `title`, `description` | The tool's identifier and how to use it |
| `input` | JSON Schema for its arguments |
| `output` | Validated results, also sent as structured content |
| `hints` | Read-only, destructive, idempotent and open-world annotations |
| `status` | Running and completion text for hosts that support it |
| `agent/skills/` | Skill descriptions and a `read_skill` tool |

## Defer less-used tools

Available in Framework 0.8.0. By default every tool is listed upfront. Mark an
individual tool `mcp: { deferred: true }` to let assistants discover its schema when needed.
This affects MCP only: WhatsApp and other chat agents still receive their normal tools, and the
CLI keeps its ordinary commands.

For a minimal example, use this tool in the `tools` array passed to `mcpServer` above:

```ts src/tools.ts theme={null}
import { tool, z } from "@flow-engineer/flow";

export const inspectRecord = tool({
  name: "inspect_record",
  description: "Return a record identifier for inspection.",
  input: z.object({ id: z.string() }),
  output: z.object({ id: z.string() }),
  signIn: "none",
  hints: { readOnly: true },
  mcp: { deferred: true },
  run: ({ id }) => ({ id }),
});

export const tools = [inspectRecord];
```

**Expected result:** an MCP client's `tools/list` contains `search_tools` and `execute_tool`, plus
`read_skill` if the server has skills. It does not contain `inspect_record` upfront. With no
deferred tools, neither generated wrapper is added.

The assistant discovers and invokes the operation in two calls:

```json search_tools arguments theme={null}
{ "query": "inspect record", "limit": 1 }
```

Search returns `{ "tools": [...], "total": 1 }` with matching deferred tools' full descriptors: name,
description, input and output schemas, annotations and metadata. It matches keywords in names,
titles and descriptions; the default limit is five and the maximum is twenty. A search for
something unrelated returns an empty list. `total` counts all matches, including those past the limit.

```json execute_tool arguments theme={null}
{ "name": "inspect_record", "arguments": { "id": "record-1" } }
```

This returns structured content `{ "id": "record-1" }`. Execute invokes one existing tool,
validates its arguments and output, and preserves its formatting, verified identity, backend
credentials, cancellation signal and idempotency key. Its target's sign-in and scope checks still
run before execution. It does not evaluate generated code.

> [!NOTE]
>
> Deferral manages context, not access. Known original tools remain directly callable. A generic
> executor may invoke writes, so its host-visible annotations conservatively allow destructive
> operations even when the selected target is read-only. Server authorization remains specific
> to the target.

### Choose exposure at deployment

An explicit server list replaces the tools' flags:

```ts theme={null}
const mcp = mcpServer({ name: "my-product", version: "1", tools, deferred: ["inspect_record"] });
```

Omit `deferred` to use the individual flags; use `deferred: []` to put everything upfront.
Unknown names are refused when constructing the server. A hosting service can choose this list
from usage data before constructing each server handle. Selection stays fixed for that handle;
automatic promotion and demotion are not part of this release. Executed operations emit traces
under their own names, so usage can count direct and indirect calls together. This works on Flow
hosting and on your own infrastructure.

Both `/mcp` and `/mcp/plugin` apply the policy; the plugin endpoint keeps excluding standalone
skills. The CLI and `mcpTools` request the complete original catalog automatically. Other clients
can request it using `tools/list` metadata:

```json theme={null}
{ "_meta": { "flow.engineer/full-tool-catalog": true } }
```

This returns the endpoint's original descriptors without the generated wrappers. Use `descriptor`
to customize the wrappers, including their names, or supply a tool named `search_tools` or
`execute_tool` to replace the corresponding generated implementation. Custom SDK handlers remain
available through `extend`.

## Add per-user sign-in

Configure `mcpServer({ auth: { issuer, scopes, verify } })` against your authorization server.
Flow challenges calls that need sign-in, and the host starts its OAuth flow.

| Tool setting | Use it for |
| - | - |
| `signIn: "required"` | Account data or actions; this is the default |
| `signIn: "optional"` | A tool that supports both anonymous and signed-in callers |
| `signIn: "none"` | Public information such as the example menu |

Authentication must be configured on the server to enforce those requirements. The café starter
has no accounts and runs without server authentication.

Use `context.key` for the verified account and `context.credentials.get("backend")` for backend
access supplied by your credentials provider. Sharing a tool with a messaging agent does not
import the assistant's private chat history.

> **[Connect your existing login](https://github.com/flow-engineer/flow/blob/main/coding-agents/auth-skill.md)**
>
> OAuth providers, verified identities and backend credentials.

> **[Use Flow's OAuth bridge](https://github.com/flow-engineer/flow/blob/main/coding-agents/oauth-bridge-skill.md)**
>
> Connect an existing login page when you do not have an OAuth server.

## Customize or extend it

Override instructions and tool descriptors, add your own SDK handlers through `extend`, and
keep custom routes beside the generated ones. The [CLI](https://docs.flow.engineer/cli.md) uses the same endpoint.

> **Use tools from another MCP server**
>
> `mcpTools({ name, url })` brings another server's tools into your agent. Pin the descriptors in
> `mcp.lock.json` so a remote change goes through your checks before reaching customers.
>
> ```sh Terminal theme={null}
> npx @flow-engineer/flow mcp update
> ```

> **[Package the same tools and skills as a plugin](https://docs.flow.engineer/plugins.md)**
>
> Generate native packages that connect to the server's tools-only endpoint.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.