@forhuman/flowmcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,95 @@
1
+ # Contributing to flowmcp
2
+
3
+ Thanks for helping improve flowmcp.
4
+
5
+ ## Layout
6
+
7
+ ```
8
+ bin/flowmcp dispatcher — resolves symlinks, sources lib/bootstrap.sh, routes to commands/
9
+ lib/ shared functions: paths, secrets, profiles, client-config merge, audit log, UI
10
+ commands/ one script per verb, self-bootstrapping (works sourced or run standalone)
11
+ ```
12
+
13
+ Every `commands/*.sh` starts with the same line so it works whether it's
14
+ `source`d by the dispatcher or shelled out to directly (e.g. `debug.sh`
15
+ calling `test.sh`):
16
+
17
+ ```bash
18
+ source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../lib/bootstrap.sh"
19
+ ```
20
+
21
+ ## The one hard rule
22
+
23
+ **No command may accept a token as a CLI argument or print one, ever.**
24
+ `secret-set`/`rotate` are the only commands that touch a token, and both
25
+ refuse to run without a real interactive TTY (`read -s`, hidden input). If a
26
+ change makes a token cross that boundary — as a function argument that isn't
27
+ a `local`/`nameref` passed by name, in a log line, in an error message — it
28
+ doesn't get merged, no exceptions.
29
+
30
+ ## Development
31
+
32
+ Requires `bash`, `jq`, `curl`, and Node/`npx`.
33
+
34
+ ```bash
35
+ npm link # or: ln -s "$(pwd)/bin/flowmcp" /usr/local/bin/flowmcp
36
+ flowmcp --help
37
+ ```
38
+
39
+ Link it globally before calling anything done — running `bash bin/flowmcp`
40
+ directly skips the symlink-resolution path (`bin/flowmcp`'s
41
+ `while [ -h "$SOURCE" ]` loop) and the `bin` field a real install goes
42
+ through, so it verifies less than it looks like it does.
43
+
44
+ ## Testing changes
45
+
46
+ There is no test suite yet — verification is manual, against an isolated
47
+ environment:
48
+
49
+ ```bash
50
+ export WFW_HOME=/tmp/wfw-test-$$
51
+ export HOME=/tmp/wfw-test-home-$$
52
+ mkdir -p "$HOME"
53
+ flowmcp add zzz-test-1 --label "Test Co"
54
+ # ... exercise the command you changed ...
55
+ rm -rf "$WFW_HOME" "$HOME"
56
+ ```
57
+
58
+ **Always use an obviously-fake org name (`zzz-test-*`), never a name that
59
+ might collide with a real client.** `install`/`remove`/`rename` resolve
60
+ client config paths through the real `$HOME` by design (see
61
+ `lib/clients.sh:wfw_client_config_path`) unless `HOME` is overridden as
62
+ above — a name collision with a real org can silently repoint or delete a
63
+ real entry in someone's real Claude Code/Desktop/Cursor config. See
64
+ `friction.md` for the incident that made this rule non-optional.
65
+
66
+ Before opening a PR, run every changed `commands/*.sh` through `bash -n` at
67
+ minimum, and exercise the `--json` and (for `install`/`remove`/`rename`)
68
+ `--dry-run` paths, not just the human-output path — they're separate code
69
+ paths in every command that has them.
70
+
71
+ ## Adding a command
72
+
73
+ 1. Add `commands/<verb>.sh`, self-bootstrapping as above.
74
+ 2. Add it to the whitelist `case` in `bin/flowmcp` and to
75
+ `usage()`.
76
+ 3. Add it to `commands/schema.sh` — usage, whether it mutates, whether it
77
+ requires a TTY, whether it's destructive, and its JSON output shape.
78
+ `schema` is how an agent discovers the surface without parsing
79
+ `--help`; a command missing from it is invisible to that path.
80
+ 4. If it prints structured output, include `next_steps: [string]` when
81
+ there's an obvious follow-up command — that's the convention every other
82
+ command's JSON output follows.
83
+ 5. If it's read-only, no gate is needed. If it mutates a client config file
84
+ or a stored secret, give it `--dry-run`; if it's irreversible without a
85
+ backup, require an explicit `--yes` with no non-interactive default.
86
+
87
+ ## Fold friction back in
88
+
89
+ If something in this doc, or an existing pattern in the code, turns out
90
+ wrong for a case you're working, write it down in `friction.md` before you
91
+ move past it. It's what keeps this file honest.
92
+
93
+ ## License
94
+
95
+ MIT — contributions are accepted under the same license as the project.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 webflow-workspaces contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,221 @@
1
+ # flowmcp
2
+
3
+ Connect every client's Webflow account to your AI agent — one at a time,
4
+ safely, without a token ever touching the conversation.
5
+
6
+ If you run an agency, you likely need a separate Webflow connection per
7
+ client. `flowmcp` is a CLI (+ a Claude Code skill) that manages all of them
8
+ side by side: add a client in seconds, the token lives only in your OS
9
+ keychain, and your agent can add/list/test/install connections on your
10
+ behalf — but can never see, hold, or print the token itself.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install -g @forhuman/flowmcp
16
+ ```
17
+
18
+ Installs both `flowmcp` and the short alias `fmcp`. Requires
19
+ `bash`, `jq`, `curl`, and Node/`npx`. On macOS the `security` CLI (ships with
20
+ the OS) is used for keychain access; on Linux install `libsecret-tools`
21
+ (Debian/Ubuntu: `apt install libsecret-tools`) for keychain support, or
22
+ accept the `chmod 600` file fallback.
23
+
24
+ ## Quick start
25
+
26
+ ```bash
27
+ fmcp connect acme --label "Acme Corp"
28
+ # opens your browser -> client approves access -> Ctrl+C once connected -> done
29
+
30
+ fmcp test acme
31
+ fmcp install acme claude-code --scope project
32
+ ```
33
+
34
+ No OAuth App to create, no client ID/secret to manage — `connect` shells out
35
+ to [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) against Webflow's
36
+ own hosted MCP server. No browser available (headless environment)? Use a
37
+ manually-pasted token instead:
38
+
39
+ ```bash
40
+ fmcp add acme --label "Acme Corp"
41
+ fmcp secret-set acme # run this yourself, in your own terminal — TTY only
42
+ fmcp test acme
43
+ fmcp install acme claude-code --scope project
44
+ # restart the target app to pick up the new MCP server
45
+ ```
46
+
47
+ The first time a human runs `flowmcp` interactively, it asks once whether to
48
+ show help text in English or Español, and remembers it
49
+ (`flowmcp lang es` changes it later; JSON output is unaffected either way).
50
+
51
+ ## What it does
52
+
53
+ - Registers one **profile** per client (label, metadata — no secret).
54
+ - Stores the actual token in your **OS keychain**, never in a client config
55
+ file, never in an agent's context.
56
+ - Lets an AI agent drive the whole workflow (add, list, test, install,
57
+ debug) except the two commands that touch a token — those require you,
58
+ in your own terminal, by design.
59
+
60
+ ## Commands
61
+
62
+ | Command | What it does |
63
+ |---|---|
64
+ | `add <org> [--label NAME]` | Register org metadata (no secret) |
65
+ | `secret-set <org>` | Interactively paste a token (TTY only) |
66
+ | `rotate <org>` | Interactively replace a stored token (TTY only) |
67
+ | `connect <org> [--label NAME]` | Add/reconnect an org via browser OAuth — no setup, needs the user present |
68
+ | `list [--json]` | List orgs + last test status (no secrets) |
69
+ | `inspect <org> [--live] [--json]` | Show profile detail; `--live` re-runs `test` first |
70
+ | `test <org> [--json]` | Validate the stored credentials |
71
+ | `install <org> <client> [--scope user\|project] [--force] [--dry-run] [--json]` | Merge an `mcpServers` entry into a client config |
72
+ | `remove <org> --yes [--from client:scope]... [--dry-run] [--json]` | Delete profile + credentials, optionally strip client entries |
73
+ | `rename <old-org> <new-org> [--dry-run] [--json]` | Rename an org — no re-login needed |
74
+ | `debug <org> [--json]` | Diagnose profile/credential/network/config issues |
75
+ | `schema` | Machine-readable reference of every command's usage and JSON output shape |
76
+
77
+ Supported clients for `install`: `claude-code`, `claude-desktop`, `cursor`.
78
+
79
+ ## For agents
80
+
81
+ Symlink or copy this repo into `.claude/skills/flowmcp/` (project-level) or
82
+ `~/.claude/skills/flowmcp/` (user-level) — [`SKILL.md`](SKILL.md) at the repo
83
+ root is the skill definition an agent reads. In short: run `fmcp schema`
84
+ once per session instead of parsing `--help`, everything already prints JSON
85
+ when stdout isn't a TTY, and `install`/`remove`/`rename` support `--dry-run`
86
+ for anything you're not certain about.
87
+
88
+ ---
89
+
90
+ ## Technical details
91
+
92
+ The sections below are for anyone integrating, auditing, or extending
93
+ `flowmcp` — skip them if you just want to use the CLI.
94
+
95
+ ### How credentials stay out of client configs
96
+
97
+ For `connect` (`mcp-remote`) orgs, `install` writes:
98
+
99
+ ```json
100
+ {
101
+ "mcpServers": {
102
+ "webflow-acme": {
103
+ "command": "npx",
104
+ "args": ["-y", "mcp-remote", "https://mcp.webflow.com/mcp", "--resource", "https://mcp.webflow.com/mcp"],
105
+ "env": { "MCP_REMOTE_CONFIG_DIR": "/Users/you/.flowmcp/mcp-remote/acme" }
106
+ }
107
+ }
108
+ }
109
+ ```
110
+
111
+ No credential of any kind is in that file — just a directory path.
112
+ `mcp-remote` reads its own isolated session from that directory (and
113
+ refreshes it) when the MCP client actually launches the server.
114
+
115
+ For `secret-set` (PAT) orgs, `install` instead writes:
116
+
117
+ ```json
118
+ {
119
+ "mcpServers": {
120
+ "webflow-acme": {
121
+ "command": "/path/to/flowmcp/commands/run-mcp.sh",
122
+ "args": ["acme"]
123
+ }
124
+ }
125
+ }
126
+ ```
127
+
128
+ `run-mcp.sh` is invoked by the MCP client itself (Claude Code / Claude
129
+ Desktop / Cursor) when it starts the server — not by you or by an agent. At
130
+ that point it looks up the token for `acme` from the keychain, exports it as
131
+ `WEBFLOW_TOKEN`, and `exec`s `npx -y webflow-mcp-server@latest`. The token
132
+ exists only in that child process's environment, for the lifetime of the
133
+ MCP session.
134
+
135
+ ### JSON contract
136
+
137
+ `list`/`inspect`/`test`/`debug`/`install`/`remove`/`rename` print JSON
138
+ automatically whenever stdout isn't a real TTY (piped, redirected, or
139
+ invoked by an agent's tool call) — `--json` only matters when you want
140
+ machine output in your own terminal. JSON responses include a `next_steps`
141
+ array naming the follow-up command, when there is an obvious one.
142
+
143
+ `install`, `remove`, and `rename` are the only commands that touch a real,
144
+ shared client config file (Claude Code/Desktop/Cursor) — the one place here
145
+ where a wrong call has a real blast radius — so all three support `--dry-run`
146
+ to preview the change before it's written, and `remove` requires an explicit
147
+ `--yes` with no interactive fallback.
148
+
149
+ ### Storage layout
150
+
151
+ ```
152
+ ~/.flowmcp/
153
+ profiles/<org>.json # metadata only: label, created_at, auth_method, last test result
154
+ mcp-remote/<org>/ # mcp-remote orgs: isolated session storage, owned by mcp-remote itself
155
+ secrets/<org>.token # pat orgs: chmod 600, ONLY used when no OS keychain is available
156
+ audit/<YYYY-MM>.jsonl # append-only action log — no credential values
157
+ ```
158
+
159
+ Metadata and secrets are physically separate files/stores, so profile data
160
+ can be freely read, printed, or committed to a private dotfiles repo without
161
+ any risk of exposing a token — never put `secrets/` under version control
162
+ (see `.gitignore`).
163
+
164
+ ### Security model — what this does and doesn't protect against
165
+
166
+ - Protects against: tokens leaking into an AI agent's conversation
167
+ transcript/logs, tokens sitting in plaintext in versioned config files,
168
+ one client's token being usable to access another client's workspace.
169
+ - Does not protect against: a compromised local machine (keychain access
170
+ is scoped to your OS user, not sandboxed further), a user manually
171
+ pasting a token into chat despite the tool telling them not to, or
172
+ `ps`-level visibility of `security add-generic-password -w <token>`'s
173
+ argument for the brief instant `secret-set` invokes it on macOS (an
174
+ inherent limitation of that CLI, not of this tool — it never happens
175
+ through anything that logs).
176
+ - `connect` specifics: verified directly against `mcp.webflow.com`'s OAuth
177
+ metadata (`.well-known/oauth-authorization-server`) that it supports PKCE
178
+ (`S256`) and Dynamic Client Registration (`/oauth/register`, no
179
+ approval required) — confirmed with a real registration + full PKCE
180
+ token exchange. That's *specific to Webflow's hosted MCP server*, not
181
+ Webflow's classic Data API OAuth (`api.webflow.com/oauth`), which still
182
+ requires a `client_secret` with no PKCE alternative — which is why the
183
+ PAT path exists rather than a client_secret-based OAuth flow for that API.
184
+ The actual OAuth flow (PKCE, browser, token storage, refresh) is handled
185
+ by the third-party [`mcp-remote`](https://www.npmjs.com/package/mcp-remote)
186
+ tool, not by code in this repo — we only isolate each org's session into
187
+ its own `MCP_REMOTE_CONFIG_DIR` and check for the presence of a
188
+ `*_tokens.json` file as a "connected" signal, never reading its contents.
189
+
190
+ ### Extending to other MCP servers
191
+
192
+ The design isn't Webflow-specific by construction: `lib/secrets.sh`,
193
+ `lib/profiles.sh`, and `lib/clients.sh` generalize to any provider. The
194
+ `connect`/`mcp-remote` path generalizes to any remote MCP server with a
195
+ spec-compliant OAuth server (PKCE + Dynamic Client Registration); the PAT
196
+ path (`run-mcp.sh`) generalizes to any local MCP server that authenticates
197
+ via a single bearer-token env var. `$WFW_MCP_URL` in `lib/common.sh` and
198
+ the `webflow-mcp-server` reference in `run-mcp.sh` are the two places with
199
+ Webflow hardcoded today.
200
+
201
+ ## Links
202
+
203
+ - [SKILL.md](SKILL.md) — the agent-facing manual this CLI ships with
204
+ - [CONTRIBUTING.md](CONTRIBUTING.md) — layout, the one hard rule, how to test safely
205
+ - [cases/flowmcp.md](cases/flowmcp.md) — how this CLI was built, what broke, what got rejected
206
+ - Webflow MCP server: https://www.npmjs.com/package/webflow-mcp-server
207
+ - mcp-remote: https://www.npmjs.com/package/mcp-remote
208
+
209
+ ## Contributing
210
+
211
+ See [CONTRIBUTING.md](CONTRIBUTING.md) — layout, the one non-negotiable rule
212
+ about tokens, and how to test against an isolated environment without ever
213
+ touching a real client's config.
214
+
215
+ ## License
216
+
217
+ MIT — see [LICENSE](LICENSE).
218
+
219
+ ---
220
+
221
+ Built by [forhuman](https://www.forhuman.studio/).
package/SKILL.md ADDED
@@ -0,0 +1,214 @@
1
+ ---
2
+ name: flowmcp
3
+ description: Manage multiple Webflow MCP server connections (one per client/org — e.g. an agency's own workspace plus each customer's) without ever letting an API token pass through agent context. Use when the user wants to add, list, inspect, test, install, rotate, remove, or debug a Webflow MCP connection, or asks to set up webflow-mcp-server for a new client, or reports an MCP connection to Webflow that isn't working.
4
+ license: MIT
5
+ ---
6
+
7
+ # flowmcp
8
+
9
+ A CLI (`bin/flowmcp`) for managing one `webflow-mcp-server` connection
10
+ per client/org, so an agency can hold separate Webflow API tokens for its own
11
+ workspace and for each customer's workspace at once — installed into Claude
12
+ Code, Claude Desktop, and/or Cursor.
13
+
14
+ ## Start here: `flowmcp schema`
15
+
16
+ Run this once per session instead of parsing `--help`. It returns
17
+ machine-readable JSON: every command's usage, whether it mutates state,
18
+ whether it requires a real TTY, whether it's destructive, and the shape of
19
+ its JSON output. Use it to confirm a command's flags exist before calling
20
+ it, rather than guessing from this doc if the two ever drift.
21
+
22
+ ## The one rule that matters
23
+
24
+ **You (the agent) must never see, hold, echo, or transmit a real Webflow API
25
+ token.** Not as a Bash argument, not in a variable you print, not by asking
26
+ the user to paste it into chat. The tool is built so you never need to:
27
+
28
+ - `secret-set` and `rotate` are the *only* commands that touch a token, and
29
+ both refuse to run unless invoked from a real interactive TTY (`read -s`
30
+ hidden prompt). They will hard-fail if piped or run non-interactively —
31
+ that's intentional, not a bug to work around.
32
+ - Every other command (`add`, `list`, `inspect`, `test`, `install`, `remove`,
33
+ `debug`) only ever touches metadata or a keychain *reference*. Run these
34
+ freely via your Bash tool.
35
+ - `list`, `inspect`, `test`, `debug`, `install`, `remove`, and `rename` print
36
+ structured JSON automatically whenever stdout isn't a real TTY — which
37
+ includes every call you make through your Bash tool. You don't need to
38
+ pass `--json` yourself; parse the output directly instead of scraping the
39
+ colored human-readable text (that text only appears when a human is
40
+ watching a real terminal). Where there's an obvious follow-up action, the
41
+ JSON includes `next_steps: [string]` — prefer that over inferring the next
42
+ command yourself.
43
+ - `install`, `remove`, and `rename` are the only commands that write to a
44
+ real, shared client config file. Pass `--dry-run` first when you're not
45
+ certain what a call will change, and show the user the diff before
46
+ re-running for real — this matters more than it sounds: a test-org name
47
+ that collides with a real org's name can silently repoint or delete a real
48
+ entry (see `friction.md` in the repo for the incident that made this a
49
+ standing rule, not a suggestion).
50
+ - `install` never writes a literal token into a client's config file. It
51
+ points the `command` field at `commands/run-mcp.sh <org>`, which looks the
52
+ token up from the OS keychain (or the chmod-600 file fallback) at the
53
+ moment the MCP client actually launches the server — long after your
54
+ session has ended.
55
+
56
+ If a user asks you to "just paste the token in" or run `secret-set` for
57
+ them, don't. Tell them to run it themselves, in their own terminal, and
58
+ give them the exact command. Do not offer to run it "on their behalf" via
59
+ your Bash tool even if they insist — the TTY guard will reject it anyway,
60
+ but the point is to not try.
61
+
62
+ ## Two ways to connect an org — prefer `connect`
63
+
64
+ There are two paths to get a client's Webflow credentials registered.
65
+ **Prefer `connect` whenever a browser is available** — it's the one
66
+ non-technical users can do themselves with zero copy/paste and zero setup.
67
+ Fall back to the PAT path (`add` + `secret-set`) only for headless/remote
68
+ environments where no browser can be opened.
69
+
70
+ ### `connect` (preferred, no setup required)
71
+
72
+ ```
73
+ flowmcp connect <org> --label "Acme Corp"
74
+ ```
75
+
76
+ You can run this yourself, but it needs the user physically present: it
77
+ runs `npx mcp-remote` against Webflow's own hosted MCP server
78
+ (`mcp.webflow.com`), which opens a real browser, the client logs into
79
+ their own Webflow account and clicks approve, and the session is saved to
80
+ disk automatically. There is **no one-time app-registration step anymore**
81
+ — Webflow's hosted OAuth server supports Dynamic Client Registration, so
82
+ `mcp-remote` registers itself on the fly. No token ever touches your Bash
83
+ tool calls, stdout, or the conversation — you only see "connected" or
84
+ "failed", never a credential.
85
+
86
+ Because it needs a live Ctrl+C from the user once they see "connected" in
87
+ the output, tell them to run it themselves in their own terminal if you're
88
+ not driving an interactive session with them watching.
89
+
90
+ ### PAT path (fallback): `add` + `secret-set`
91
+
92
+ 1. `flowmcp add <org> --label "Acme Corp"` — you can run this.
93
+ Registers profile metadata only.
94
+ 2. Tell the user, verbatim, to run in their own terminal:
95
+ `flowmcp secret-set <org>`
96
+ Do not run this yourself. Do not ask them to paste the token into chat.
97
+
98
+ ### Then, either path:
99
+
100
+ 3. Verify with `flowmcp test <org>` — you can run this; it hits
101
+ the real Webflow API and reports back scopes/site count without ever
102
+ printing the token.
103
+ 4. Install into whichever client(s) the user wants:
104
+ `flowmcp install <org> claude-code --scope project`
105
+ (or `claude-desktop` / `cursor`, `--scope user`). This merges into the
106
+ existing config — it will not clobber other MCP servers already there,
107
+ and refuses to overwrite an existing `webflow-<org>` entry unless you
108
+ pass `--force` (confirm with the user before using `--force`).
109
+ 5. Tell the user to restart the target client to pick up the new server.
110
+
111
+ ## Workflow: something's not connecting
112
+
113
+ Run `flowmcp debug <org>`. It branches on the org's
114
+ `auth_method` (`mcp-remote` or `pat`) and checks the right things for each:
115
+ profile exists, credentials exist (session file or keychain), the relevant
116
+ npm package (`mcp-remote` or `webflow-mcp-server`) is resolvable, a
117
+ credential check (reusing `test`), and whether known client config files
118
+ are valid JSON and contain the `webflow-<org>` entry. Read its output back
119
+ to the user — it never prints a token, so its full output is always safe
120
+ to relay verbatim.
121
+
122
+ Common causes:
123
+ - `mcp-remote` orgs: no saved session → tell the user to run
124
+ `flowmcp connect <org>` themselves. `test`/`debug` only check
125
+ that a session was *saved*, not that it's still valid — an expired
126
+ session refreshes or re-prompts automatically the next time a real
127
+ client connects, so don't over-interpret an "ok" here as a guarantee.
128
+ - `pat` orgs: HTTP 401 → token invalid/revoked/expired → tell user to run
129
+ `flowmcp rotate <org>` themselves. HTTP 403 → token valid but
130
+ missing scopes. HTTP 000 → network/DNS/proxy issue, not a token problem.
131
+ - client config exists but has no `webflow-<org>` entry → `install` wasn't
132
+ run yet, or was run against a different scope/client than the user
133
+ expects (check `--scope user` vs `--scope project`, and which client).
134
+
135
+ ## Workflow: removing or rotating a client
136
+
137
+ - Rotate: PAT orgs — tell the user to run `flowmcp rotate <org>`
138
+ themselves, same TTY-only rule as `secret-set`. `mcp-remote` orgs don't
139
+ need manual rotation — just re-run `connect <org>`, which overwrites the
140
+ saved session.
141
+ - Remove: `flowmcp remove <org> --yes [--from client:scope]...`
142
+ is destructive (deletes the profile and the stored credentials — token
143
+ or saved session, whichever applies) — confirm with the user before
144
+ running it, and pass `--from claude-code:project` etc. for each client
145
+ config you should also strip the entry from.
146
+
147
+ ## Command reference
148
+
149
+ ```
150
+ flowmcp add <org> [--label "Name"]
151
+ flowmcp secret-set <org> # human-only, TTY required
152
+ flowmcp rotate <org> # human-only, TTY required
153
+ flowmcp connect <org> [--label "Name"] # opens a browser, needs the user present
154
+ flowmcp list [--json]
155
+ flowmcp inspect <org> [--live] [--json]
156
+ flowmcp test <org> [--json]
157
+ flowmcp install <org> <client> [--scope user|project] [--force] [--dry-run] [--json]
158
+ # client: claude-code | claude-desktop | cursor
159
+ flowmcp remove <org> --yes [--from client:scope]... [--dry-run] [--json]
160
+ flowmcp rename <old-org> <new-org> [--dry-run] [--json]
161
+ flowmcp debug <org> [--json]
162
+ flowmcp schema # always JSON — run this first
163
+ flowmcp lang [en|es] [--json] # view/change human --help language, never affects JSON
164
+ ```
165
+
166
+ `--json` is implicit whenever stdout isn't a TTY, so you (the agent) get it by
167
+ default — the flag exists for a human who wants machine output in their own
168
+ terminal. `schema` is always JSON regardless of TTY, since it's meant to be
169
+ run by an agent at session start.
170
+
171
+ ## Storage layout (for context, not something you normally touch directly)
172
+
173
+ - `~/.flowmcp/profiles/<org>.json` — non-sensitive metadata only
174
+ (label, created_at, `auth_method`, secret backend, last test
175
+ result/scopes/error). Safe to `jq`/`cat`/show the user in full.
176
+ - `mcp-remote` orgs: `~/.flowmcp/mcp-remote/<org>/` — an
177
+ isolated `MCP_REMOTE_CONFIG_DIR` per org, owned and read entirely by the
178
+ third-party `mcp-remote` tool. We only ever check whether a `*_tokens.json`
179
+ file exists in there (`wfw_mcp_remote_connected`) — never read its
180
+ contents.
181
+ - `pat` orgs: OS keychain first (`security` on macOS, `secret-tool`/libsecret
182
+ on Linux), falling back to `~/.flowmcp/secrets/<org>.token` at
183
+ `chmod 600` only when no keychain is available (e.g. a headless
184
+ container). Never read this file directly — always go through `test`,
185
+ `inspect`, or `debug`, which are designed not to leak it.
186
+ - `~/.flowmcp/audit/<YYYY-MM>.jsonl` — append-only log of actions
187
+ with timestamps and results, never credential values. Useful if the user
188
+ asks "when did we last touch the Acme connection."
189
+
190
+ ## Why there's no more manual OAuth App setup
191
+
192
+ Earlier versions of this tool required creating a Webflow OAuth App by hand
193
+ (client_id/client_secret) because the classic Data API OAuth
194
+ (`api.webflow.com/oauth`) has no PKCE support — a shared secret couldn't be
195
+ shipped safely in an open-source package. That's no longer needed: Webflow
196
+ now hosts its own MCP server at `mcp.webflow.com` with a modern OAuth
197
+ server (PKCE + Dynamic Client Registration + refresh tokens), verified
198
+ directly against the real endpoint. `connect` uses that via `mcp-remote`
199
+ instead, which eliminates the manual setup step entirely. The tradeoff:
200
+ `mcp-remote` orgs only work with Webflow's *hosted* MCP server (its access
201
+ tokens don't work against `api.webflow.com` directly) and tokens expire
202
+ hourly (refreshed transparently by `mcp-remote`/the MCP client, not by us).
203
+ The PAT path still exists for headless environments or if raw Data API
204
+ access is ever needed outside of `mcp.webflow.com`'s tool surface.
205
+
206
+ ## Extending to other MCP servers
207
+
208
+ The design isn't hardcoded to Webflow: `lib/secrets.sh`, `lib/profiles.sh`,
209
+ `lib/clients.sh` generalize to any provider. The `mcp-remote` path
210
+ generalizes to any remote MCP server with a spec-compliant OAuth server
211
+ (PKCE + DCR); the PAT path (`run-mcp.sh`) generalizes to any local MCP
212
+ server taking a single bearer-token env var. `$WFW_MCP_URL` in
213
+ `lib/common.sh` and the `webflow-mcp-server` reference in `run-mcp.sh` are
214
+ the two places with Webflow hardcoded today.
package/bin/flowmcp ADDED
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env bash
2
+ # flowmcp — manage multiple Webflow MCP server connections
3
+ # (one per client/org) safely, keeping tokens out of agent context.
4
+ set -euo pipefail
5
+
6
+ # Resolve through symlinks (e.g. npm link / global npm bin) to the real file.
7
+ SOURCE="${BASH_SOURCE[0]}"
8
+ while [ -h "$SOURCE" ]; do
9
+ DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
10
+ SOURCE="$(readlink "$SOURCE")"
11
+ [[ "$SOURCE" != /* ]] && SOURCE="$DIR/$SOURCE"
12
+ done
13
+ SCRIPT_DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
14
+ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
15
+ export WFW_LIB_DIR="$REPO_ROOT/lib"
16
+ export WFW_COMMANDS_DIR="$REPO_ROOT/commands"
17
+
18
+ # shellcheck source=../lib/bootstrap.sh
19
+ source "$WFW_LIB_DIR/bootstrap.sh"
20
+
21
+ # wfw_cmd_line <name> <description> — pads <name> to a fixed width BEFORE
22
+ # adding color codes, so visible columns line up regardless of ANSI escapes.
23
+ wfw_cmd_line() {
24
+ local name="$1" desc="$2" padded
25
+ padded="$(printf "%-11s" "$name")"
26
+ printf " %s%s%s %s\n" "$WFW_C_BOLD" "$padded" "$WFW_C_RESET" "$desc"
27
+ }
28
+
29
+ usage() {
30
+ wfw_banner
31
+ echo
32
+ echo "${WFW_C_DIM}$(wfw_t usage)${WFW_C_RESET}"
33
+ echo " flowmcp <command> [args]"
34
+ echo
35
+ echo "${WFW_C_DIM}$(wfw_t onboarding)${WFW_C_RESET}"
36
+ wfw_cmd_line "connect" "$(wfw_t cmd_connect)"
37
+ wfw_cmd_line "add" "$(wfw_t cmd_add)"
38
+ wfw_cmd_line "secret-set" "$(wfw_t cmd_secret_set)"
39
+ wfw_cmd_line "rotate" "$(wfw_t cmd_rotate)"
40
+ echo
41
+ echo "${WFW_C_DIM}$(wfw_t daily_use)${WFW_C_RESET}"
42
+ wfw_cmd_line "list" "$(wfw_t cmd_list)"
43
+ wfw_cmd_line "inspect" "$(wfw_t cmd_inspect)"
44
+ wfw_cmd_line "test" "$(wfw_t cmd_test)"
45
+ wfw_cmd_line "install" "$(wfw_t cmd_install)"
46
+ printf " %-11s %s\n" "" "$(wfw_t cmd_install_clients)"
47
+ wfw_cmd_line "remove" "$(wfw_t cmd_remove)"
48
+ wfw_cmd_line "debug" "$(wfw_t cmd_debug)"
49
+ wfw_cmd_line "rename" "$(wfw_t cmd_rename)"
50
+ wfw_cmd_line "schema" "$(wfw_t cmd_schema)"
51
+ wfw_cmd_line "lang" "$(wfw_t cmd_lang)"
52
+ echo
53
+ echo "${WFW_C_DIM}$(wfw_t security_1)${WFW_C_RESET}"
54
+ echo "${WFW_C_DIM}$(wfw_t security_2)${WFW_C_RESET}"
55
+ echo "${WFW_C_DIM}$(wfw_t security_3)${WFW_C_RESET}"
56
+ }
57
+
58
+ wfw_prompt_lang_if_needed
59
+
60
+ cmd="${1:-}"
61
+ [[ -n "$cmd" ]] || { usage; exit 1; }
62
+ shift || true
63
+
64
+ case "$cmd" in
65
+ add|list|inspect|test|install|remove|rotate|debug|secret-set|connect|rename|schema|lang)
66
+ # shellcheck disable=SC1090
67
+ source "$WFW_COMMANDS_DIR/$cmd.sh" "$@"
68
+ ;;
69
+ run-mcp)
70
+ exec "$WFW_COMMANDS_DIR/run-mcp.sh" "$@"
71
+ ;;
72
+ -h|--help|help)
73
+ usage
74
+ ;;
75
+ *)
76
+ echo "error: unknown command '$cmd'" >&2
77
+ usage
78
+ exit 1
79
+ ;;
80
+ esac