> ## 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.

# Host it yourself

> Run MCP servers and messaging agents on your own Node infrastructure.

Flow runs in your Node process. Host an MCP server behind HTTPS, or run a messaging worker with
a durable database. You choose the model, messaging provider and observability system.

## Before you start

Use Node 22.18 or later and a project from the [quickstart](https://docs.flow.engineer/framework/quickstart.md). Install its locked
dependencies with `npm ci`. Keep `agent/`, `src/` and the package files in the deployed application;
TypeScript source runs directly on Node.

## Start a production process

**MCP server**

The starter's `npm run mcp` binds to loopback for local development. Add this entry point to
listen on the container or server's network interface:

```ts src/serve-mcp.ts theme={null}
import { createServer } from "node:http";
import { mcp } from "./mcp.ts";

const port = Number(process.env.PORT ?? 8080);
createServer((req, res) => {
  if (req.url === "/health") {
    res.writeHead(200).end("ok");
    return;
  }
  void mcp.node(req, res);
}).listen(port, "0.0.0.0");
```

Start it with:

```sh Terminal theme={null}
node --env-file-if-exists=.env src/serve-mcp.ts
```

Put a TLS reverse proxy or load balancer in front of the process. Preserve the MCP paths and
OAuth discovery routes, and allow streaming responses. Connect remote hosts to
`https://your-domain.example/mcp`; plugins use `/mcp/plugin`.

Add the [MCP authentication configuration](https://docs.flow.engineer/framework/mcp.md#add-per-user-sign-in) before exposing
account data. A public listener is not an authentication boundary.

**Messaging agent**

This entry point reuses your `buildAgent` and receives messages through Flow Messaging's
outbound stream. It needs `FLOW_MESSAGING_KEY`, configured senders and a model key. It does
not need a public webhook URL.

```ts src/serve-agent.ts theme={null}
import { mkdirSync } from "node:fs";
import { createServer } from "node:http";
import { sqlite } from "@flow-engineer/flow";
import { flowMessaging } from "@flow-engineer/flow/flow-messaging";
import { buildAgent } from "./agent.ts";

mkdirSync("data", { recursive: true });
const fm = flowMessaging();
const agent = buildAgent({
  host: {
    database: sqlite("data/agent.db"),
    messaging: fm,
  },
});
await agent.resume();

let streamHealthy = true;
const live = fm.stream(agent, {
  cursorFile: "data/flow-messaging-cursor",
});
live.stopped.then((why) => {
  console.error("Messaging stream stopped:", why.code, why.type);
  streamHealthy = false;
});

createServer((req, res) => {
  if (req.url !== "/health") {
    res.writeHead(404).end();
    return;
  }
  res.writeHead(streamHealthy ? 200 : 503).end(streamHealthy ? "ok" : "stream stopped");
}).listen(Number(process.env.PORT ?? 8080), "0.0.0.0");
```

Start it with:

```sh Terminal theme={null}
node --env-file-if-exists=.env src/serve-agent.ts
```

Mount `data/` on persistent storage, including the database's WAL files and the stream cursor.
Keep one process using this SQLite database. The starter's `host.messaging` adds Telegram,
plus WhatsApp and iMessage when their sender variables are set. Add an explicit `fm.sms()`
channel if you need [SMS](https://docs.flow.engineer/messaging/sms.md).

To use your own messaging provider instead, supply its channel definitions to `buildAgent`.
Verify its webhooks before passing incoming messages to the agent.

**Check it worked:** `curl http://127.0.0.1:8080/health` returns `ok`. Then make a real tool request
or send a test message; a healthy listener alone does not prove the integration works.

## Persist conversations

Messaging state includes history, pending messages, background tools, scheduled messages and
the agent's own [records](https://docs.flow.engineer/records.md).
The default database is in memory and is lost on restart.

| Storage | Configuration |
| - | - |
| SQLite | `database: sqlite("data/agent.db")` on a persistent volume |
| Postgres | `database: postgres(pool)` with your node-postgres pool |
| Your backend | Implement `Database` or provide a `store` |

With a custom `buildAgent`, pass the database through `host: options.host` to `createAgent`,
as the starter does. Here is a Postgres host configuration fragment; `pool` is your application's
node-postgres pool:

```ts Postgres host configuration theme={null}
import { postgres } from "@flow-engineer/flow";

const agent = buildAgent({
  host: { database: postgres(pool) },
  channels,
});
await agent.resume();
```

Call `await agent.resume()` before accepting messages with a durable database. Cut-off turns run
again; a tool that was running is reported as interrupted rather than silently executed again.

> [!NOTE]
>
> Pass `context.idempotencyKey` through to your backend's idempotency mechanism for writes. Flow
> supplies the same key for the same tool input in a retried turn; your backend enforces one write.

## Handle retries and restarts

Pass the provider's message id as `messageId` to `agent.message()`. A retried webhook with that id
is handled once. Ids are kept for seven days by default (`keepMessageIdsDays`). Flow Messaging's
adapter supplies these ids for you.

Run one steady-state messaging instance per database, kept warm with CPU available for timers.
Conversation leases protect deployment overlap; a dead copy's lease expires after 15 seconds.
They are not a substitute for verifying multi-instance steady-state operation.

Keep the process under a supervisor, retain logs and alert when its messaging stream stops.
On release, stop routing new work to the old process and allow active work to finish before
termination. `agent.status()` reports activity and idle time.

## Production checklist

* Store secrets in your hosting platform's secret store; never bake `.env` into an image.
* Use live messaging senders for real customers, not shared trial numbers.
* Keep the local playground private; the entry points above do not serve it.
* Back up the database and test restoring it. Never share a SQLite file across machines.
* Exercise restart recovery, a retried webhook and a slow tool before sending customer traffic.

The MCP server and tool CLI need no Flow account. Using Flow's models, Messaging or scenario-test
cloud remains optional and uses the credentials for those services.

> **[Inspect traces](https://docs.flow.engineer/traces.md)**
>
> Connect runtime events to your observability system.

> **[Deploy on Flow instead](https://docs.flow.engineer/hosting.md)**
>
> Let Flow supply the host and durable store.


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