CLI and MCP server setup
The FlurryPORT CLI (npm package flurryport) does three jobs: it forwards live captures from an endpoint to your local machine, it manages accounts and targets from the terminal, and it runs FlurryPORT as an MCP server so an AI agent in your editor can capture, inspect and replay webhooks. It is the only way to forward live traffic to a localhost dev server, because FlurryPORT runs in the cloud and cannot reach your machine on its own. Install it with npx and you can be forwarding in about a minute.
This page is the reference for all three jobs. If you set the tool up before September 2026, the MCP server section and the environment variable table are new and worth a fresh read.
Install
npx flurryport@latest --help
npx runs it without installing anything. Install it globally if you use it daily:
npm install -g flurryport
The CLI is built and tested on Node 22. Source is on GitHub and releases are on npm.
Sign in
Run login with no token and the CLI prints a link. Open it, approve the login in the browser, and the CLI stores the token it is handed. Nothing sensitive is typed into the terminal or copied through the clipboard, and the link expires after about ten minutes.
flurryport login --use
--use makes the new account the active one in a single step. --name <name> stores it under a name other than default, which is how you keep several accounts on one machine.
Pasting a personal access token still works and is the right choice for CI, where no browser exists. Mint one in the app under Settings, then:
flurryport login fp_your_token_here # stored as "default" flurryport login --name alice fp_your_token_here # stored as "alice"
First run
From a fresh install to a request arriving on your machine, with no backend of your own:
flurryport login --use # approve in the browser flurryport target create http://localhost:3000/webhooks flurryport echo 3000 # terminal 1: a receiver that prints what arrives flurryport listen # terminal 2: forward captures to it # now send a request to your capture URL, or replay one from the dashboard
target create picks the only project and endpoint when there is one and prompts when there are several. Once your own app is listening, point the target at it instead of echo.
Commands
Thirteen commands. Every command that talks to the API accepts --account <name> to use a stored account other than the active one for that run.
| Command | What it does | Key flags |
|---|---|---|
login [token] | Sign in by browser approval, or with a personal access token for CI. | --name, --use |
account | List, switch and remove stored accounts. | list, use <name>, remove <name> |
config show | Print the active environment, account and API URL. | |
target create [url] | Create a replay target without leaving the terminal. | --project, --endpoint, --name |
listen | Forward live captures to a replay target on your machine. | --interval |
echo [port] | Run a local server that returns 200 and mirrors what it received. | --host, --path |
post [body] | Post a signed intent to an endpoint from a script or a pipe. | --project, --endpoint, --file, --content-type |
keys | List or remove the signing keys stored on this machine. Values are never printed. | list, remove <ref> |
mcp | Run FlurryPORT as an MCP server for an AI editor or a hosted agent. | eight flags, see below |
join <token> | Accept an invite to an endpoint and store its contributor key. | --name, --interval, --window, --url |
seat <guest-name> | Mint a pairing code that seats another agent in a room. | --project, --endpoint, --hours, --code-minutes, --recipe |
seat-server | Run the seat connector a room guest connects to. | --host, --port, --allowed-hosts, --api-url, --idle-minutes |
console | Open the interactive room console with its colon commands. | --json |
listen
Discover the replay targets that point at your machine, attach to one, and forward executions until you press Ctrl+C.
flurryport listen flurryport listen --interval 5000 # poll interval in ms (default 3000) flurryport listen --account alice # use a stored account for this run
When the CLI starts, any executions that were already pending are marked stale and dead-lettered, so a restart never replays a backlog you did not ask for. Sequence runs started from the dashboard are grouped under a header in the output.
target create
# Interactive flurryport target create # Fully scripted flurryport target create http://localhost:3000/webhooks \ --project my-project \ --endpoint stripe-webhook \ --name "My laptop"
Targets created from the CLI always start with auto-forward off. Turn it on in the dashboard when you want every new capture delivered without asking. The plan limits on targets are on the plans and limits page.
post
Post an intent to an endpoint from the terminal, signed with the key stored on this machine (the owner key from the dashboard, or the contributor key join stored). The key never appears in output or arguments. The payload comes from --file first, then the argument, then piped stdin.
flurryport post --project <id> --endpoint <id> --file event.json
echo '{"type":"order.created","id":42}' | flurryport post --project <id> --endpoint <id>echo
Start a local HTTP server that returns 200 to every request, logs it, and mirrors it straight back. It is the fastest way to confirm forwarding is wired correctly with no backend of your own.

flurryport echo [port] [--host <host>] [--path <path>]
| Argument | Default | Description |
|---|---|---|
[port] | 3000 | Port to listen on. |
--host <host> | localhost | Interface to bind. |
--path <path> | all paths | If set, only that exact path returns 200 and everything else returns 404. A leading slash is added for you and query strings are ignored when matching. |
Every incoming request header is echoed back prefixed with X-Echo-, and the JSON body carries the method, path, query, headers, body and a request counter for the run.
Worked example: secret substitution and redaction
# 1. Add a secret in the dashboard (Secrets > Add secret): STRIPE_KEY # 2. On a replay target, set a header: X-Api-Key: $secrets.STRIPE_KEY # 3. Start a local receiver: flurryport echo 3000 # 4. In another terminal, forward captures to it: flurryport listen # 5. Send a test webhook to your FlurryPORT endpoint.
FlurryPORT substitutes the real key into X-Api-Key on the way out. echo mirrors it back as X-Echo-X-Api-Key. Because the destination echoed a secret, FlurryPORT scrubs it before storing, so the recorded response shows [REDACTED:STRIPE_KEY]. The real secret reached the destination and the stored response never contains it. See secrets for the full workflow.
keys
keys list shows the signing keys stored on this machine by reference, and keys remove <ref> deletes one. Key values are never printed by any command.
join, seat, seat-server and console
These four belong to rooms. join <token> accepts an invite token (it starts with fpi_) and stores the contributor key for that endpoint. seat <guest-name> mints a pairing code that seats another agent in a room, valid for ten minutes by default, for a seat that lasts 24 hours by default. seat-server runs the connector a seated agent talks to, on 127.0.0.1:8791 by default, closing after 30 idle minutes. console opens the interactive room console, a short colon-command language for reading and posting to a room; --json switches its output to machine-readable form.
Environment variables
Seventeen variables. Most exist so the CLI can be pointed at a different instance; the first two are the ones a CI job sets.
| Variable | What it sets | Default |
|---|---|---|
FLURRYPORT_TOKEN | A personal access token for the MCP server to boot with. Must start with fp_. | |
FLURRYPORT_ACCOUNT | Stored account name for the MCP server to boot as. | active account |
FLURRYPORT_API_URL | API base URL. Beats --api-url. | https://api.flurryport.io |
FLURRYPORT_ANON_URL | Anonymous capture base URL. | https://flurryport.dev |
FLURRYPORT_REF | Attribution source for anonymous session start. --ref beats it. | |
FLURRYPORT_WEB_URL | Web base URL used in login and claim links. | https://flurryport.io |
FLURRYPORT_VIEWER_URL | Capture viewer base URL. | the anonymous base URL |
FLURRYPORT_BILLING_URL | Billing API base URL, used by get_upgrade_options. | the API URL |
FLURRYPORT_CATALOG_URL | Recipe catalog API base URL. | https://api.flurryport.io |
FLURRYPORT_ROOMS_URL | Full URL of the rooms MCP endpoint. | {API URL}/rooms/mcp |
FLURRYPORT_ROOMS_IDLE_MINUTES | Minutes of silence before the seat server closes an idle session. Beats --idle-minutes. | 30 |
FLURRYPORT_ROOMS_STANDING_IDLE_MINUTES | Idle bound for standing sessions. | none |
FLURRYPORT_MCP_ALLOWED_HOSTS | Extra Host header values accepted in HTTP mode, comma-separated. Adds to --allowed-hosts. | |
FLURRYPORT_MCP_ALLOWED_ORIGINS | Origin header values accepted in HTTP mode, comma-separated. | |
FLURRYPORT_PUBLIC_API_HOST | Marks the public API host for hosted rooms. | |
FLURRYPORT_ACCEPT_GRACE_MS | Grace window for an invite accept race, in milliseconds. | 30000 |
FLURRYPORT_LANDING_CHECK_MS | Poll interval while waiting for an invite to land, in milliseconds. | 30000 |
FLURRYPORT_API_URL beats --api-url, and FLURRYPORT_ROOMS_IDLE_MINUTES beats --idle-minutes, but --ref beats FLURRYPORT_REF. In two of the three the environment wins over the flag you just typed. If a command appears to ignore an argument, check the environment first.MCP server
npx flurryport mcp
That command is the whole install for most clients. It starts FlurryPORT as an MCP server over stdio, and an agent can ask for a capture URL in one tool call with no account.
Install per client
The block is identical for three of these clients and differs for two. Restart the editor after any of these edits; most clients read MCP configuration only at startup.
| Client | Config file | Shape |
|---|---|---|
| Claude Code | none | claude mcp add flurryport -- npx -y flurryport mcp |
| Claude Desktop | claude_desktop_config.json | standard mcpServers block |
| Cursor | ~/.cursor/mcp.json | identical block, different path |
| Windsurf | ~/.codeium/windsurf/mcp_config.json | identical block, different path |
| VS Code | .vscode/mcp.json | top-level key is servers, and needs "type": "stdio" |
| ChatGPT | none | not supported on this path, see below |
{
"mcpServers": {
"flurryport": {
"command": "npx",
"args": ["-y", "flurryport", "mcp"]
}
}
}VS Code differs twice, so it gets its own block:
{
"servers": {
"flurryport": {
"type": "stdio",
"command": "npx",
"args": ["-y", "flurryport", "mcp"]
}
}
}ChatGPT cannot connect to a server that runs locally over stdio, so no configuration makes the block above work there. A hosted client like that needs the HTTP mode below behind a public hostname, or a hosted MCP endpoint.
Flags
| Flag | Default | What it is for |
|---|---|---|
--allow-lan | off | Let forward_to_localhost deliver to private LAN addresses as well as loopback. |
--ref <source> | FLURRYPORT_REF | Attribution recorded when an anonymous session starts. Registry listings set this. It does not bind or accept an invite. |
--account <name> | FLURRYPORT_ACCOUNT | Boot as a specific stored account without changing the active one. |
--anon-url <url> | https://flurryport.dev | Override the anonymous capture base URL. |
--http | off | Serve streamable HTTP at /mcp instead of stdio, for remote MCP clients and hosted agents. Each MCP session gets its own server state. |
--port <port> | 8790 | HTTP port, with --http. |
--host <host> | 127.0.0.1 | HTTP bind address, with --http. Loopback by default so nothing is exposed without an explicit choice. |
--allowed-hosts <hosts> | Comma-separated public hostnames accepted in the Host header, with --http: the tunnel or reverse proxy in front of this server. |
HTTP mode and the transport guard
--http serves the same server over streamable HTTP at /mcp for remote MCP clients and hosted agents. It binds to 127.0.0.1 on port 8790 unless you say otherwise, so nothing is reachable from outside the machine without an explicit choice. To expose it, put a tunnel or reverse proxy in front and pass its hostname with --allowed-hosts.
Every request is checked against that list. The Host header must be a loopback name, the bind host, or a hostname you allowed; and if the request carries an Origin header at all, that origin must be on the allowed-origins list (FLURRYPORT_MCP_ALLOWED_ORIGINS). A request that fails either check is refused with a 403 whose body names the flag that would allow it. This is what stops a page in your browser, or a stray process on the same network, from driving the server through your credentials.
Which tools you get
The server registers one toolset of sixty tools at startup, and the list is the same whether or not you are signed in. Twenty of them work before a session is claimed: the capture loop (get_capture_url, list_captures, get_capture, get_capture_digest, forward_to_localhost), test traffic and pacing (send_test_event, capture_count, start_echo_server), standing watches, the recipe catalog (search_recipes, get_recipe, lint_recipe, verify_chain), invites, upgrade options, secret setup and get_server_info. The other forty answer a structured account_required error until you claim the session or sign in, at which point the same tools simply start succeeding. Nothing is added or removed, so a client that cached the tool list at startup is never holding a stale shape.
flurryport seat-server is a separate process with its own thirteen tools for a seated room guest: read, post, wait for posts, the roster, canon and sections, and the pairing and standing-credential ceremony.
Try it without an account
With no token configured the server starts in anonymous mode. Ask your agent for a capture URL, point a provider at it, and debug the payload in the editor. Anonymous sessions are honest about their limits: 250 captures of up to 32 KB each, 90 minutes of idle life, 24 hours maximum, and plaintext storage. Do not send real production data to one; claim the session, which is free, for encryption at rest. The session survives editor restarts.
Forwarding is loopback-only by default (--allow-lan opts in) and delivers the raw bytes with the original headers, so provider signatures verify on your app exactly as they would in production.
To claim, open the link the server hands your agent and sign up or sign in. Your captures move into a real project, re-encrypted, and your watches wake up on it. The CLI completes a device-code hand-off: a read-only token is minted for you and the toolset flips to authenticated live, with no restart and nothing to paste. Already have an account? Sign in with the login command above and restart the MCP server; token scope is read at startup, so the restart matters.
Permissions and tokens
This answers the question a careful reader has before installing anything: what can this thing do to my systems, and what does it do with my keys.
Replay is the only destructive operation. replay_to_target is the only tool on the server annotated as destructive, so a client that asks before running destructive tools will ask before anything is sent. Server-side replay works only against replay targets you registered in the dashboard; the agent can never supply an arbitrary URL.
Token scopes fail closed. A personal access token has two settings at mint time, both on by default, because the most common reason to mint one is handing it to an AI.
| Scope | Default | What it enforces |
|---|---|---|
| Read-only | On | Inspect and forward to your own machine only. No server-side replays, no changes. Enforced by the server, not a client hint. |
| Redact PII | On | Capture reads come back with emails, cards, SSNs, phones and IPs masked, best effort. Forwards to your own local target stay untouched so signatures keep verifying. |
Service credentials stay server side. When a recipe acts on Slack, GitHub or Azure DevOps, the credential lives on our side. The agent sends a typed intent and receives the receipt: what was sent, what the service answered, when. It does not receive the key, and the key does not enter the model’s context.
A personal access token is accepted by the Core API only, as Authorization: Bearer fp_.... The Users and Billing APIs refuse it entirely, so a token can never change account settings, billing or terms. Within Core it can:
So a teammate holding your token can forward your captures to their machine and create their own replay targets, but cannot delete your projects, change your billing or extend their own access. Lost or leaked tokens are revoked individually from Settings.
Use it in CI
No browser exists in CI, so this is the one place a pasted token is the right answer. Mint a token, store it as a secret, and pass it in the environment. This example posts a fixture event to an endpoint on every run, so the pipes bound to that endpoint fire and leave a receipt:
- name: Post the release event to FlurryPORT
env:
FLURRYPORT_TOKEN: ${{ secrets.FLURRYPORT_TOKEN }}
run: npx -y flurryport post --project ${{ vars.FLURRYPORT_PROJECT }} --endpoint ${{ vars.FLURRYPORT_ENDPOINT }} --file fixtures/release.jsonConfiguration file
Config lives at ~/.flurryport/config.json and is rewritten on every command. The directory is created with mode 0700 and the file with 0600 on macOS and Linux; Windows gets best-effort ACLs. Direct editing is supported.
{
"activeEnvironment": "prod",
"environments": {
"prod": {
"apiUrl": "https://api.flurryport.io",
"activeAccount": "me",
"accounts": {
"me": { "apiKey": "fp_..." },
"alice": { "apiKey": "fp_..." }
}
}
}
}How listen works
Limitations and notes
Version and uninstall
flurryport --version
Released versions and their notes are on GitHub. When a newer version is available the CLI says so once per session.
npm uninstall -g flurryport
npx leaves nothing installed, so if you only ever used npx there is nothing to remove. Either way, the tokens and signing keys the CLI stored live under ~/.flurryport/; delete that directory to remove them, and revoke the tokens from Settings if the machine is leaving your hands.
When something does not work
Open an issue on GitHub with the command, the version and what the provider’s own delivery log says. Those three things resolve most reports in one round trip, and the third is the one people leave out.