Flowly speaks MCP both ways:
- As a client β connect Flowly to external MCP servers (Context7, GitHub, Linear, Playwright, your own) and the agent calls their tools like any built-in.
- As a server β run
flowly mcp serveso other MCP clients (Claude Desktop, Cursor, Claude Code, another agent) can read your Flowly conversation history and, optionally, send messages and resolve approvals.
Everything is managed from the flowly mcp command group, the /mcp modal in the TUI, the desktop MCP tab, or by hand-editing the mcpServers block in ~/.flowly/config.json. Changes take effect at the next agent boot β restart the gateway (flowly restart) or start a new session.
Adding a server
Use flowly mcp add for stdio (local subprocess) or HTTP servers:
# stdio: local subprocess
flowly mcp add context7 --command npx --arg -y --arg @upstash/context7-mcp
# HTTP (StreamableHTTP): remote URL
flowly mcp add acme --url https://mcp.example.com/mcp --header "X-Api-Key: ..."
# HTTP + OAuth
flowly mcp add linear --url https://mcp.linear.app/mcp --auth oauth--command and --url are mutually exclusive; --auth oauth requires --url. Other flags: --env KEY=VALUE, --timeout (120s default), --connect-timeout (60s), --probe/--no-probe, --force.
Transports
| Transport | Config | Notes |
|---|---|---|
| stdio | command + args (+ env) | Local subprocess. Default for local servers. stderr β $FLOWLY_HOME/logs/mcp-stderr.log |
| HTTP (StreamableHTTP) | url (+ headers) | First-class. Default for remote servers |
| SSE | url + transport: sse | For older SSE-style servers |
The mcpServers config block
Servers live under the top-level mcpServers key in ~/.flowly/config.json. Keys are camelCase. A real stdio example with an injected secret:
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_PERSONAL_ACCESS_TOKEN}" }
}
}
}${VAR} interpolation works in env, args, and headers. Variables resolve at boot from $FLOWLY_HOME/.env (and the process environment, which wins on conflict).
.env (mode 0600) and reference them by ${VAR} rather than inlining them.
An HTTP server with OAuth:
{
"mcpServers": {
"linear": {
"url": "https://mcp.linear.app/mcp",
"auth": "oauth"
}
}
}For mTLS / a custom CA on HTTP/SSE servers, set clientCert, clientKey, and sslVerify (true | false | path to a CA bundle).
For stdio servers installed via npx/uvx/pipx, osvCheck (default true) queries the OSV API for known supply-chain malware advisories on the package before the server spawns. Set it to false to skip the check for a trusted or local server.
Per-server tool filtering
Each remote tool registers as mcp_{server}_{tool} (non-alphanumeric characters become _). For example, Context7's resolve-library-id becomes mcp_context7_resolve_library_id. On a name collision the existing tool wins and the MCP one is skipped β Flowly's native tools are never overwritten.
Limit which of a server's tools the agent sees via the tools block:
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"tools": {
"include": ["search_repositories", "get_issue"],
"exclude": [],
"resources": false,
"prompts": false
}
}
}
}tools.includeis a whitelist β if set, it wins and everything else is hidden.tools.excludeis a blacklist β used only whenincludeis empty.- Empty
include+ emptyexcludeexposes all tools. resources/promptsexpose the server's resource/prompt utility tools when it advertises those capabilities.
flowly mcp configure <name> connects to the server, lists its tools, and gives you an interactive checkbox picker that writes tools.include for you.
OAuth for remote servers
HTTP servers with auth: oauth use OAuth 2.1 + PKCE. Tokens are stored per-server at $FLOWLY_HOME/mcp-tokens/{server}.json (mode 0600) and auto-refreshed.
flowly mcp login linearflowly mcp login clears any cached token, then runs the interactive browser authorization flow. The callback is pinned to http://127.0.0.1:8765/callback, so only one interactive MCP OAuth flow can run at a time, and that redirect URI must match the authorization server's registration. At agent boot, stored tokens are used/refreshed non-interactively β if a server needs login and no browser is available, that server is skipped and boot is never blocked. flowly mcp remove also clears the server's tokens.
token exchange with the raw MCP SDK β flowly mcp login returns a 401/token-exchange error even though you authorized successfully. Wire those servers through mcp-remote, the ecosystem-standard OAuth bridge, instead:
flowly mcp add yargi --command npx \
--arg=-y --arg=mcp-remote@latest --arg=https://server.example.com/mcp \
--connect-timeout 300The first connect opens the browser, mcp-remote completes the OAuth and caches the token under ~/.mcp-auth, and the bot reuses it on every boot. To Flowly this is a plain stdio server (no auth: oauth), so it needs Node.js on the bot host. The desktop app's Requires OAuth sign-in checkbox does all of this for you (see Managing servers from the desktop app).
The curated catalog
Flowly ships a curated catalog of ready-to-install servers. Browse and install them:
flowly mcp catalog # table: Name / Auth / Transport / Description
flowly mcp install github # resolve manifest, prompt for secrets, write config, probe
flowly mcp picker # interactive catalog browser (TTY only)The catalog has exactly 8 entries:
| Name | Transport | Auth |
|---|---|---|
context7 | stdio (npx) | none |
fetch | stdio (uvx) | none |
time | stdio (uvx) | none |
filesystem | stdio (npx) | api_key (MCP_FILESYSTEM_ROOT) |
github | stdio (npx) | api_key (GITHUB_PERSONAL_ACCESS_TOKEN) |
notion | stdio (npx) | api_key (NOTION_API_KEY) |
playwright | stdio (npx) | none |
linear | HTTP | oauth |
flowly mcp install <name> resolves the manifest, prompts for any declared environment variables, saves them to $FLOWLY_HOME/.env, writes the mcpServers entry, and probes the server (probing is skipped for OAuth servers). It then prints the manifest's post_install note β for OAuth entries like linear, that note tells you to run flowly mcp login linear next. To see the current catalog at any time, run flowly mcp catalog.
Running Flowly as an MCP server
Expose your Flowly conversation history to any MCP client (Claude Desktop, Cursor, Claude Code) over stdio:
flowly mcp serve # read-only (default)
flowly mcp serve --allow-writes # also expose send + approvals (needs gateway)
flowly mcp serve --verboseRead tools (always available, no gateway required β they read JSONL sessions and the FTS index directly):
| Tool | Purpose |
|---|---|
conversations_list | List conversations (filter by platform / search) |
conversation_get | Metadata for one channel:chat_id |
messages_read | Recent user/assistant messages of a conversation |
messages_search | Full-text search across all conversations (FTS5) |
channels_list | Configured channels + enabled state |
Write tools (only with --allow-writes, and they require a running flowly gateway): messages_send, approvals_list, approvals_resolve (decision = allow-once / allow-always / deny). These reach the gateway over an authed localhost control endpoint ($FLOWLY_HOME/gateway-api.json); when the gateway is down they return a clear "gateway not running" message instead of failing.
serve is read-only by default, so it is safe to point at your real ~/.flowly.
Point a client at it the same way you would any stdio server:
{
"mcpServers": {
"flowly": { "command": "/path/to/flowly", "args": ["mcp", "serve"] }
}
}flowly mcp subcommands
| Command | What it does |
|---|---|
list | Table of configured servers: Name / Transport / Tools filter / Status |
add <name> | Add a server (--command/--url, --arg, --env, --header, --auth oauth, --timeout, --connect-timeout, --probe, --force) |
remove <name> | Remove a server (--yes); also clears its OAuth tokens |
enable <name> | Flip the server's enabled flag on |
disable <name> | Flip the server's enabled flag off |
configure <name> | Interactively pick enabled tools β writes tools.include |
serve | Run Flowly as an MCP server (--allow-writes, --verbose) |
catalog | List the curated catalog |
install <name> | Install a catalog entry (--force, --probe) |
picker | Interactive catalog browser (TTY only) |
test <name> | Connect + list tools β a health check |
login <name> | (Re)run the OAuth browser flow |
The /mcp slash command
In the TUI, /mcp opens a modal to manage MCP servers and install entries from the curated catalog β the same operations as the CLI, without leaving the chat.
Managing servers from the desktop app
Flowly Desktop has an MCP tab (Dashboard β MCP) for managing a bot's servers from a GUI β the same operations as the CLI and TUI, served over the bot's feature RPC. It works identically whether the selected bot is local, a relay bot, or a direct self-hosted gateway: there's one source of truth (the bot's mcpServers config), never a per-transport path.
The tab shows two groups:
- Configured β your servers, each with a status badge, an enable/disable toggle, Test (connect + list tools), and Remove.
- Available β installable curated-catalog entries. Install writes the entry, prompting first for any required secrets (which are saved to the bot's
.env).
Add server opens a dialog with two transports:
- Local (stdio) β command + space-separated arguments + environment variables.
- Remote (HTTP) β URL + headers, with an optional Requires OAuth sign-in.
A change restarts the bot's gateway so newly-registered tools load at the next boot; the panel refreshes automatically when the bot reconnects.
OAuth from the desktop
Checking Requires OAuth sign-in on a Remote (HTTP) server turns the dialog's button into Sign in & add, and Flowly wires the server through mcp-remote rather than the native HTTP+OAuth path:
- Click Sign in & add β a browser window opens for the provider's authorization (e.g. WorkOS).
- Approve;
mcp-remotecaches the token. - The server is saved + enabled, and the bot reconnects using the cached token β no further sign-in.
This avoids the token-exchange failures some providers' OAuth has with the raw MCP SDK (a direct auth: oauth HTTP server may 401 on token exchange where mcp-remote succeeds). It requires Node.js on the bot host (for npx).
For OAuth servers the status badge reflects real authorization state β sign-in needed (enabled but no token yet) vs signed in β and a configured OAuth server you haven't signed into yet exposes a prominent Sign in button on its row. A plain enabled badge means the server is on in config; it is not a connectivity guarantee, so use Test to confirm a server actually connects.
bot host. For a local/desktop bot that is your own machine, so sign-in is one click. For a remote/VPS bot, run the one-time npx -y mcp-remote@latest <url> on the host (over SSH) to cache the token there, then add the server as a Local (stdio) mcp-remote command from the tab.
Security
MCP servers run third-party code, so Flowly applies several guards:
- OSV malware gate β before an
npx/uvxserver spawns, Flowly queries the OSV database for known-malware advisories and blocks the spawn if any match. Fail-open (a network error allows the spawn); per-server opt-out viaosvCheck: false. - Filtered subprocess env β stdio servers get only a safe baseline (
PATH,HOME, β¦) plus theenvyou explicitly list. Flowly's own provider keys are never inherited. - Credential redaction β tokens and keys in error messages are replaced with
[REDACTED]before the model or the logs see them. - Prompt-injection scan β tool descriptions are scanned for override patterns and logged (not blocked) so a hostile server is detectable.
- Sandbox β under
FLOWLY_SANDBOX=1the whole agent (and its MCP subprocesses) runs insidesandbox-exec(macOS) /bwrap(Linux). See Sandbox & approvals. - Circuit breaker β a server that fails repeatedly is short-circuited for a cooldown (you'll see "unreachable, auto-retry in Ns") so the model stops hammering it; it recovers automatically.
Subprocess stderr is redirected to $FLOWLY_HOME/logs/mcp-stderr.log so a chatty server can't corrupt the TUI β check it first when debugging.
Full mcpServers config reference
Every key a server entry accepts (camelCase on disk; Flowly converts to snake internally β server names and env/headers keys are preserved verbatim):
{
"mcpServers": {
"example": {
"enabled": true,
"command": "npx", // stdio: command + args + env
"args": ["-y", "@scope/pkg"],
"env": { "TOKEN": "${TOKEN}" },
"url": "", // http/sse: url + headers instead
"headers": {},
"transport": "auto", // auto | stdio | http | sse
"timeout": 120, // per-tool-call seconds
"connectTimeout": 60, // initial connect seconds
"tools": { // optional filtering / utilities
"include": [], // whitelist (empty = all)
"exclude": [], // blacklist (ignored if include set)
"resources": false, // expose resources/* utility tools
"prompts": false // expose prompts/* utility tools
},
"auth": "", // "" | "oauth"
"scope": "", // optional OAuth scope
"sslVerify": true, // true | false | CA-bundle path
"clientCert": "", // mTLS cert (path or [cert, key])
"clientKey": "",
"osvCheck": true, // OSV malware gate
"reapOrphans": false, // force-kill orphaned stdio children (Linux)
"supportsParallelToolCalls": false,
"sampling": { // server-initiated LLM (off by default)
"enabled": false,
"model": "",
"maxRpm": 10,
"maxTokensCap": 4096,
"allowedModels": []
}
}
}
}Troubleshooting
| Symptom | Check |
|---|---|
| Server won't connect | flowly mcp test <name>; read $FLOWLY_HOME/logs/mcp-stderr.log |
npx/uvx not found | Ensure Node / uv is on PATH, or set an absolute command + env.PATH |
| Tools missing after add | Start a new session β MCP loads at agent boot (flowly restart) |
| OAuth stuck | flowly mcp login <name> to re-authorize; for WorkOS-style servers use the mcp-remote bridge above |
| "unreachable, auto-retry in Ns" | Circuit breaker is open after repeated failures β fix the server; it recovers automatically |