@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.
- package/CONTRIBUTING.md +95 -0
- package/LICENSE +21 -0
- package/README.md +221 -0
- package/SKILL.md +214 -0
- package/bin/flowmcp +80 -0
- package/cases/flowmcp.md +79 -0
- package/commands/add.sh +46 -0
- package/commands/connect.sh +61 -0
- package/commands/debug.sh +122 -0
- package/commands/inspect.sh +58 -0
- package/commands/install.sh +67 -0
- package/commands/lang.sh +38 -0
- package/commands/list.sh +35 -0
- package/commands/remove.sh +82 -0
- package/commands/rename.sh +104 -0
- package/commands/rotate.sh +33 -0
- package/commands/run-mcp.sh +27 -0
- package/commands/schema.sh +131 -0
- package/commands/secret-set.sh +37 -0
- package/commands/test.sh +131 -0
- package/friction.md +67 -0
- package/lib/audit.sh +17 -0
- package/lib/bootstrap.sh +18 -0
- package/lib/clients.sh +107 -0
- package/lib/common.sh +66 -0
- package/lib/i18n.sh +248 -0
- package/lib/oauth.sh +22 -0
- package/lib/profiles.sh +58 -0
- package/lib/secrets.sh +89 -0
- package/lib/ui.sh +121 -0
- package/package.json +51 -0
package/CONTRIBUTING.md
ADDED
|
@@ -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
|