Skip to main content
Glama
bilgili

MCP Flow

by bilgili

MCP Flow is one process that serves three things on one port. It serves the MCP gateway at /mcp. It serves an admin web user interface (UI). It serves a health endpoint at /health. The Python package and the console command keep the name mcpflow.

The gateway runs child MCP servers as subprocesses. It also proxies remote MCP servers. A failed child does not stop the gateway or another child. A change to a child takes effect without a process restart.

How it works

  agent ──┐                          ┌── uvx  <python package>   (namespace a)
  agent ──┼── Bearer token ──► /mcp ─┼── npx  <npm package>      (namespace b)
  agent ──┘        MCP Flow          ├── https://remote/mcp      (namespace c)
                      │              └── mcpflow_*  built-in admin tools
             /login  /  /marketplace  /tokens  /api
  • An agent connects to /mcp with one bearer token. The gateway publishes every visible tool of every running child as {namespace}_{tool}. A muted tool stays hidden.

  • The admin adds, edits, mutes, and restarts children in the web UI, over the Representational State Transfer (REST) application programming interface (API) at /api, or with the built-in mcpflow_* MCP tools.

  • The gateway keeps its persistent state under one data directory. See "Files under DATA_DIR".

Related MCP server: mcpstead

Screens

Select a picture to open it at full size.

Contents

Install

Install the package with pip. The package needs Python 3.12 or newer. The source is at https://github.com/bilgili/mcpflow.

pip install mcpflow

Set the admin password

The process needs an admin password. Create a password hash first.

mcpflow hash-password

The command prints one line that starts with pbkdf2_sha256$. Set the line as ADMIN_PASSWORD_HASH. You can also set the clear-text ADMIN_PASSWORD instead, but the hash is safer.

Run the server

Start the gateway with serve. The flags override the environment variables.

export ADMIN_PASSWORD_HASH='pbkdf2_sha256$...'
mcpflow serve --host 127.0.0.1 --port 8000 --data-dir ./data

Open http://127.0.0.1:8000/login and log in with the admin password.

Run with Docker

Copy the example environment file and set the values.

cp .env.example .env
# set ADMIN_PASSWORD_HASH and SECRET_KEY in .env
docker compose up -d

The compose file builds the image, maps port 8000, and mounts a named volume at /data. Open http://localhost:8000/login.

The named volume is mcpflow-data.

To build the image from a local wheel, run make docker-build-local. The command builds the wheel and passes it to Docker through a named build context.

Environment variables

Name

Default

Purpose

DATA_DIR

./data (Docker: /data)

registry, tokens, logs, caches

HOST

127.0.0.1 (Docker: 0.0.0.0)

bind address

PORT

8000

bind port

ADMIN_PASSWORD

unset

clear-text admin password; hashed in memory at start

ADMIN_PASSWORD_HASH

unset

output of mcpflow hash-password; wins over ADMIN_PASSWORD

SECRET_KEY

unset

cookie signing key; else DATA_DIR/secret_key

COOKIE_SECURE

0

set 1 behind HTTPS

SESSION_TTL_SECONDS

604800

session lifetime

CHILD_START_TIMEOUT

60

probe timeout in seconds

CHILD_CACHE_TTL

300

seconds a child component list serves lookups by name

LOG_LEVEL

INFO

process log level

PUBLIC_URL

unset

externally reachable base URL; else the request host

UV_CACHE_DIR

Docker: /data/cache/uv

uvx cache

npm_config_cache

Docker: /data/cache/npm

npx cache

A child's cache_ttl in servers.json or the form overrides it; 0 disables the cache for that child.

Files under DATA_DIR

The process keeps all state under one data directory.

  • servers.json: the persisted list of child servers.

  • tokens.json: the API tokens. The file holds only the SHA-256 digest of each token.

  • secret_key: the cookie signing key. The process creates it on first start with mode 0600.

  • logs/{namespace}.log: the stderr of one child. The process truncates it on each start. The process deletes it when the secrets of the child change and when the child is removed.

  • servers/{namespace}/: the inline source files of one child (see Inline source servers). Directories are mode 0700, files 0600.

  • creds/{namespace}/: client.json and token.json for an OAuth child. Files are mode 0600.

  • cache/uv and cache/npm: the package caches in Docker.

Add a server

Open / and select Add server. The add window opens over the dashboard and has three tabs.

  • Python: run a Python MCP server with uvx <package>. Enter the package and arguments.

  • npm: run a Node MCP server with npx -y <package>. Enter the package and arguments.

  • JSON: paste a raw MCP config block. The form reads a mcpServers object or a bare name map.

The add form supports two more kinds through import. A remote server proxies an HTTP or SSE URL. A custom server runs any command.

Source: run a package from a git repo or a local directory

The Python and npm tabs accept an optional Source. It names where to install the package from: a git URL, a github: shorthand, or an absolute directory path. It must start with git+https://, https://, github:, or /. Only the Python and npm kinds accept a source.

With a source, the gateway runs uvx --from <source> <package> or npx -y --package=<source> <bin>. On the npm tab, Package is then the bin name, not the npm package name.

# Python from a git repo
source:  git+https://github.com/owner/repo
package: my-tool

# npm from a github shorthand
source:  github:owner/repo
package: my-bin

A source may embed a credential for a private repo, for example git+https://oauth2:<token>@gitlab.example/owner/repo. The gateway stores the raw URL in servers.json (mode 0600). The dashboard, the server window, every REST API response, and every admin MCP tool result show the redacted form git+https://***@gitlab.example/owner/repo. See Secrets.

Import from mcpmarket

The mcpmarket tab posts a listing URL. The importer fetches the page and pre-fills the add form from the first server that it finds. The admin always confirms before the registry changes.

The importer accepts only https URLs on the allowed hosts mcpmarket.com, www.mcpmarket.com, github.com, and raw.githubusercontent.com. It does not follow redirects.

The import can fail. mcpmarket.com can answer 429. The page can carry no MCP config. On a failure the UI shows the error and a JSON paste field. Paste the server config and confirm.

API tokens

An MCP client authenticates to /mcp with a bearer token. Open /tokens to manage the tokens.

  • Enter a name and a scope, then select Create token. The page shows the token once. It starts with mcpflow_. Copy it now. The server stores only the SHA-256 digest.

  • Select revoke to delete a token. The next request with that token fails.

A token has one scope. An mcp token authenticates /mcp only. An admin token authenticates /api and /mcp. An admin session on /mcp also sees the built-in admin tools (see Admin MCP tools below). The scope selector defaults to mcp.

Send the token in the Authorization header.

Authorization: Bearer mcpflow_<token>

The gateway publishes every tool of a running child as {namespace}_{tool}.

Connect an agent

Every client needs the same two facts: the URL of the gateway and one bearer token. MCP Flow speaks streamable HTTP at /mcp. It is one server to the client, however many children it runs behind that address.

Create a token at /tokens first. That page prints the two blocks below with your real host and token already filled in, so copy from there rather than retyping.

URL     https://mcp.example/mcp
Header  Authorization: Bearer mcpflow_<token>

Claude Code

Run one command. It writes the server into the Claude Code config for you.

claude mcp add --transport http mcpflow https://mcp.example/mcp --header "Authorization: Bearer mcpflow_<token>"

Check it with claude mcp list. To share the server with a repository instead of your user account, add --scope project; Claude Code then writes .mcp.json beside your code, so use a token you are willing to commit, or none at all.

Any client that reads an mcpServers block

Most clients take the same JSON. Paste this into the client's MCP config file:

{
  "mcpServers": {
    "mcpflow": {
      "type": "http",
      "url": "https://mcp.example/mcp",
      "headers": {
        "Authorization": "Bearer mcpflow_<token>"
      }
    }
  }
}

Where that file lives differs by client:

Client

Config file

Claude Code

~/.claude.json, or .mcp.json in a project

Cursor

~/.cursor/mcp.json, or .cursor/mcp.json in a project

Windsurf

~/.codeium/windsurf/mcp_config.json

Claude Desktop

claude_desktop_config.json in the app's support directory

Two clients differ from the block above. VS Code names the top-level key servers, not mcpServers, and reads .vscode/mcp.json in a workspace. Codex CLI keeps its servers in ~/.codex/config.toml as [mcp_servers.mcpflow] rather than in JSON.

These formats move. If a client rejects the block, check that client's own documentation for the current key names; the URL and the Authorization header are the parts that come from MCP Flow.

Check the connection

Ask the agent to list its tools. Every tool MCP Flow publishes carries its namespace as a prefix, so a time server appears as time_get_current_time. If the agent sees nothing, confirm the child reports running on the dashboard and that the namespace is not muted.

An admin token additionally exposes the built-in mcpflow_* tools, so the agent can add and restart servers itself. See "Admin MCP tools" below.

Admin API

The gateway serves a JSON REST API under /api. A script manages the same servers, tools, and tokens as the HTML pages. Every route needs an admin token in the Authorization header. The session cookie does not authenticate /api. An mcp token authenticates /mcp only; an admin token authenticates /api and /mcp. Create an admin token on /tokens.

Method

Path

Body

Result

GET

/api/servers

—

list the children

POST

/api/servers

ServerSpec fields

add a child, 201

GET

/api/servers/{ns}

—

one child

PUT

/api/servers/{ns}

ServerSpec fields

replace a child

DELETE

/api/servers/{ns}

—

remove a child, 204

POST

/api/servers/{ns}/enable

—

enable a child

POST

/api/servers/{ns}/disable

—

disable a child

POST

/api/servers/{ns}/restart

—

restart a child

GET

/api/servers/{ns}/log?lines=N

—

the log tail as text

GET

/api/tools

—

the tool list

PUT

/api/visibility/root

{"muted": bool}

mute or show all tools

PUT

/api/visibility/namespaces/{ns}

{"muted": bool}

mute one namespace

PUT

/api/visibility/namespaces/{ns}/tools

{"tool": str, "muted": bool}

mute one tool

GET

/api/tokens

—

list the tokens

POST

/api/tokens

{"name": str, "scope"?: "mcp"|"admin"}

create a token, 201

DELETE

/api/tokens/{id}

—

revoke a token, 204

A PUT /api/servers/{ns} is a full replacement. An omitted field takes the ServerSpec default, so an omitted enabled starts a stopped child. Send "enabled": false to keep it stopped.

An error answers with a JSON body {"error": "<message>"}. A bad request is 400. An unknown namespace or token is 404. A missing or wrong-scope token is 401.

List the servers:

curl -H "Authorization: Bearer mcpflow_<admin token>" http://localhost:8000/api/servers

Admin MCP tools

The gateway mounts one built-in MCP server under the namespace mcpflow. Its tools manage the gateway over /mcp, so an agent that speaks only MCP adds a child, reads a log, or unmutes a tool without the REST API. The tools publish as mcpflow_<tool>. An admin token is required on /mcp to see and call them. An mcp session never lists or calls a mcpflow_* tool. A muted root hides the child tools but never a mcpflow_* tool, so an admin session can always unmute.

The namespace mcpflow and every namespace that starts with mcpflow_ are reserved. A child cannot take one, so no child tool name shadows an admin tool.

Tool

Purpose

mcpflow_list_servers

list the children

mcpflow_get_server

one child

mcpflow_add_server

add a child from a spec and start it

mcpflow_update_server

replace a child spec; the argument names the child

mcpflow_remove_server

remove a child

mcpflow_enable_server

enable a child

mcpflow_disable_server

disable a child

mcpflow_restart_server

restart a child

mcpflow_server_log

the log tail as text, capped at 1000 lines

mcpflow_list_tools

the tool list with visibility

mcpflow_set_root_muted

mute or show all child tools

mcpflow_set_namespace_muted

mute one namespace

mcpflow_set_tool_muted

mute one child tool

mcpflow_import_from_url

fetch candidate specs from a URL; adds nothing

mcpflow_write_source

write inline source files for a child (see Inline source servers)

mcpflow_import_from_url returns candidates only. The agent confirms each one with mcpflow_add_server. No tool mints a token; tokens stay on the REST API and the tokens page.

Call a tool with an MCP client that sends the admin token. For example, with the FastMCP client:

from fastmcp import Client
from fastmcp.client.transports import StreamableHttpTransport

transport = StreamableHttpTransport(
    "http://localhost:8000/mcp",
    headers={"Authorization": "Bearer mcpflow_<admin token>"},
)
async with Client(transport) as c:
    servers = await c.call_tool("mcpflow_list_servers", {})

Secrets

A child can hold a secret in four fields: env, headers, url, and source. The gateway keeps the raw values in servers.json (mode 0600) and starts each child with the raw values. No read surface shows a raw value.

What a read shows

The dashboard, the server window, the REST API, and the admin MCP tools show a masked copy of each child.

Field

Masked form

env, headers

Each key stays. Each non-empty value becomes •••. An empty value stays empty.

url

The user information and each non-empty query value become •••. The scheme, host, path, query keys, and fragment stay.

source

The user information becomes ***.

The gateway masks every value, not only the values of keys that look secret. Thus LOG_LEVEL=debug also reads as LOG_LEVEL=•••. Read a non-secret value in servers.json, or write a new value.

The JSON import preview shows the pasted values unchanged, because they are not stored yet.

Write a masked record back

A client can read a child, change one field, and send the whole record back.

  • Send ••• for a key that the child already has. The gateway keeps the stored value.

  • Send a new value. The gateway replaces the stored value.

  • Send the url exactly as a read shows it. The gateway keeps the stored url.

  • Send the source exactly as a read shows it. The gateway keeps the stored source.

The gateway refuses a write that still holds a mask after this merge. The answer is 400 on /api and a tool error on /mcp. The message names the field and the key, never a value. These writes are refused:

  • ••• under a key that the child does not have, for example after a rename.

  • A value that contains the mask, for example Bearer •••.

  • A url with a changed host that keeps a masked query value.

  • A new child copied from a read record (mcpflow_add_server or POST /api/servers).

  • A percent-encoded mask in a url or a source.

Send the real value in each of these cases.

Logs and errors

  • last_error and the child log (/servers/{ns}/log, /api/servers/{ns}/log, mcpflow_server_log) mask every secret of the child with •••. This includes the decoded forms of a url or source credential.

  • The process log masks the secrets of every child that the process started.

  • The access log masks the code and state query values of the OAuth callback.

Limits

  • args is not masked. Put a secret in env or headers, not in args.

  • The path of a url is not masked. Put a credential in a header when the provider accepts one.

  • A secret shorter than 4 characters is not masked in logs and errors.

Inline source servers

An agent can write a small MCP server as source files and run it, with no git host and no shell access. The flow is three steps over /mcp with an admin token:

  1. mcpflow_write_source(namespace, files) writes files under DATA_DIR/servers/<namespace>/ and returns path.

  2. mcpflow_add_server with kind python or npm, a package, and source set to the returned path. The gateway runs uvx --from <path> <package> or npx -y --package=<path> <bin>.

  3. The child's tools appear on /mcp.

files maps a relative POSIX path to file text. Limits: at most 200 files, 512 KiB per file, 1 MiB total. A key may not be absolute, hold a . or .. segment, a backslash, or a NUL, and no key may be a directory prefix of another. A bad key returns a ToolError that names the key.

A write replaces the whole tree at once: a file absent from the new files is gone. restarted in the result is true when the write bounced an existing running or failed child onto the new files. It is false for a starting child (call mcpflow_restart_server once it settles), a stopped child (enabled owns the process), and a namespace with no child yet (call mcpflow_add_server next). If a call fails at the restart step, the files are already written; call mcpflow_restart_server.

A child's source must point at its own directory. mcpflow_remove_server deletes the directory; mcpflow_update_server to a git source and mcpflow_disable_server keep it.

Development

The project uses uv and pytest. Run the tests from the repository root.

uv sync --extra dev
make test

The project designs each change with OpenSpec. The development branch ci holds the specs under openspec/; the main branch holds the code and the documentation only.

The logo lives at src/mcpflow/static/logo.svg. The web UI serves the same file at /static/logo.svg.

License

MIT. See LICENSE.

MCP Flow runs third-party MCP servers as child processes and proxies remote ones. Each of those carries its own license; this one covers MCP Flow itself.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables centralized management and unified interface for multiple child MCP servers (filesystem, sqlite, etc.), allowing users to discover, launch, and execute tools across different MCP servers through a single gateway.
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    MCP Gateway that aggregates multiple upstream MCP servers into a single endpoint with persistent connections, tool registry, and authentication.
    21 npm
    2
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    A gateway that aggregates multiple MCP servers into a single endpoint, namespacing their tools and forwarding calls, so an agent connects to one MCP to access the entire stack.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    An agent gateway that aggregates multiple local MCP servers into a single MCP endpoint with runtime control, hot-reloadable configuration, and meta tools like bridge__execute for calling any child server tool without restart.
    8 npm
    MIT