@wevo-ai/pulse-mcp 0.0.0-stage → 0.1.0-test.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/README.md CHANGED
@@ -1,3 +1,458 @@
1
- # Temporary Holding Version
1
+ # Local Pulse public API MCP server
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Local stdio MCP server → WEVO Pulse public API (`/api/public/v1`), **prod**
4
+ by default (`https://platform.wevo.ai`); WEVO developers opt into dev or
5
+ staging explicitly (see [Environments](#environments)). Claude Code, Claude Desktop,
6
+ Cursor, and VS Code launch it as a child process. No local Pulse backend,
7
+ Docker, or deployment is required; the only listener is the short-lived
8
+ `127.0.0.1` callback used while you sign in.
9
+
10
+ ## Prior design documents
11
+
12
+ - [MCP Auth, Credits & Distribution Plan (Oct 8 draft)](https://app.notion.com/p/3f3bf908bd6881ea8f19fc34d3f0010f)
13
+ - [Pulse Public API MVP spec](https://app.notion.com/p/3edbf908bd68819bb7afe2ee9a66a333)
14
+ - [WEV-4086: private MCP server](https://linear.app/wevo/issue/WEV-4086)
15
+
16
+ This implements the plan's **local development** option: public v1 tools
17
+ acting as one signed-in user. Sign-in runs Authorization Code + PKCE in your
18
+ browser, started by the server itself on the first tool call (or by the
19
+ `login` CLI command), and stores a refreshable token; the server refreshes it
20
+ without re-approval. The older ticket proposes Device Code, but WEVO's Auth0 tenant
21
+ does not offer Device Code to third-party (developer portal) apps.
22
+
23
+ ## Install and sign in
24
+
25
+ Requires **Node.js 22 or newer** on `PATH`. The package `@wevo-ai/pulse-mcp`
26
+ is one bundled file with no runtime dependencies; its command is
27
+ `wevo-pulse-public-mcp`. MCP clients launch it with `npx`. No clone, build,
28
+ or CodeArtifact access is needed.
29
+
30
+ > **Restricted package.** `@wevo-ai/pulse-mcp` is planned as a **restricted**
31
+ > (private) npm package for now; see [Package and publish](#package-and-publish).
32
+ > `npx -y @wevo-ai/pulse-mcp` then works only for npm users who belong to the
33
+ > `@wevo-ai` npm organization, sit on a team with read access to the package,
34
+ > and are authenticated to `registry.npmjs.org` (`npm login`, or an `.npmrc`
35
+ > line `//registry.npmjs.org/:_authToken=${NPM_TOKEN}`). Everyone else gets a 404. **Customers should use the [Claude Desktop bundle](#connect-clients)
36
+ > (`wevo-pulse.mcpb`)**: it is self-contained and needs no npm account.
37
+ >
38
+ > **Not published yet.** Until it is, run `npm pack` in this package and
39
+ > replace `-y @wevo-ai/pulse-mcp` below with
40
+ > `-y --package /ABS/PATH/TO/wevo-ai-pulse-mcp-0.1.0.tgz wevo-pulse-public-mcp`.
41
+
42
+ ### Environments
43
+
44
+ With no `WEVO_PULSE_BASE_URL`, the server and `login` use **prod**. Each
45
+ environment has its own Auth0 tenant; `login` picks the matching defaults:
46
+
47
+ | Environment | `WEVO_PULSE_BASE_URL` | Auth0 domain | Audience |
48
+ | -------------- | -------------------------------- | ---------------------- | ------------- |
49
+ | prod (default) | `https://platform.wevo.ai` | `auth.wevo.ai` | `persona-api` |
50
+ | staging | `https://platform-stage.wevo.ai` | `auth-staging.wevo.ai` | `persona-api` |
51
+ | dev | `https://platform-dev.wevo.ai` | `auth-dev.wevo.ai` | `persona-api` |
52
+
53
+ WEVO developers set `WEVO_PULSE_BASE_URL=https://platform-dev.wevo.ai` (or
54
+ the staging origin) themselves, in `mcp.env` or the client's `env`. Logins are
55
+ stored per origin, so switching environments needs one `login` per origin.
56
+
57
+ > **Prod launch pending.** `https://platform.wevo.ai` serves
58
+ > `/api/public/v1` only after the prod public API host launches (WEV-4085);
59
+ > until then prod calls fail there. `https://personas-api.wevo.ai` reaches the
60
+ > same prod API and uses the same prod Auth0 defaults in the meantime.
61
+
62
+ ### Sign in
63
+
64
+ 1. **Client ID.** **Dev** (`https://platform-dev.wevo.ai`) and **staging**
65
+ (`https://platform-stage.wevo.ai`) have WEVO's own MCP login client built
66
+ in: skip to step 3 (WEVO developers only need `WEVO_PULSE_BASE_URL` from
67
+ step 2). Prod has no default login client yet, so `login` there needs
68
+ `WEVO_PULSE_CLIENT_ID`. Open the developer portal (`<origin>/developer`)
69
+ and create a **Public** app in your own organization (an app registered in
70
+ another organization returns `403 api_client_org_mismatch`; Public apps
71
+ created before October 7, 2026 have no refresh tokens, so create a new
72
+ one). Callback URL, exactly: `http://127.0.0.1:47615/callback`. Select the
73
+ scopes below. Setting `WEVO_PULSE_CLIENT_ID` on dev or staging overrides
74
+ the built-in client.
75
+ 2. Save settings in the per-user config file, which every run loads
76
+ automatically:
77
+
78
+ ```bash
79
+ mkdir -p ~/.config/wevo-pulse-mcp
80
+ (umask 077 && cat > ~/.config/wevo-pulse-mcp/mcp.env <<'EOF'
81
+ # Prod only, until WEVO's own login client ships there:
82
+ # WEVO_PULSE_CLIENT_ID=your_public_app_client_id
83
+ # WEVO developers only (default is prod):
84
+ # WEVO_PULSE_BASE_URL=https://platform-dev.wevo.ai
85
+ EOF
86
+ )
87
+ ```
88
+
89
+ 3. Sign in. **No terminal needed:** in your MCP client, call any WEVO tool or
90
+ just say "log in to WEVO". When nobody is signed in, the server opens your
91
+ browser at WEVO sign-in and the tool returns `login_started` at once with
92
+ the sign-in link (`authorize_url`) as a fallback; it does not wait for you.
93
+ Sign in, then retry the request: the next call uses the new login. A
94
+ sign-in stays open for 5 minutes; a later call after that starts a fresh
95
+ one. Clients that support MCP URL elicitation are asked to show the
96
+ sign-in page themselves instead of the server opening a browser.
97
+
98
+ From a terminal instead (or on a machine without a desktop browser):
99
+
100
+ ```bash
101
+ npx -y @wevo-ai/pulse-mcp login
102
+ ```
103
+
104
+ On your **first** login a one-time **consent screen** lists the requested
105
+ scopes: approve it. Auth0 always asks once for an app with a loopback
106
+ (`127.0.0.1`) callback; silent refreshes never prompt, and later logins ask
107
+ again only if the requested scopes change. `--no-browser` (or
108
+ `WEVO_PULSE_NO_BROWSER=1`, which also applies to server-started sign-in)
109
+ prints the URL instead of opening a browser. The command confirms the
110
+ signed-in user, org, app, and scopes. `npx -y @wevo-ai/pulse-mcp status`
111
+ shows the API origin and active credential later; `logout` revokes and
112
+ deletes it; `help` lists commands and settings. The MCP tools `login`,
113
+ `auth_status`, and `logout` do the same from inside a client.
114
+
115
+ For all tools: `pulse:read pulse:write personas:read personas:write
116
+ personas:outputs:read assets:read assets:write`. Read-only use needs only the
117
+ corresponding read scopes. The API still checks user permissions, app grants,
118
+ org membership, resource ownership/sharing, and credits. Call `whoami` first
119
+ and confirm user, org, app, scopes, and expiry.
120
+
121
+ ### Stored login
122
+
123
+ The token is stored per user at `~/.config/wevo-pulse-mcp/credentials.json`
124
+ (`$XDG_CONFIG_HOME/wevo-pulse-mcp`, or `%APPDATA%\wevo-pulse-mcp` on Windows;
125
+ `WEVO_PULSE_CONFIG_DIR` overrides), owner-only `0600`, keyed by API origin.
126
+ Every client on the machine shares it, and running servers pick it up on their
127
+ next call; no restart is needed. They refresh the access token shortly before
128
+ expiry and once after a `401`, persisting Auth0's rotated refresh token
129
+ atomically under a lock file so several MCP clients can share one login. When
130
+ the refresh token expires or is revoked, the next tool call starts browser
131
+ sign-in again (`login_started`). Tokens are never printed or logged.
132
+
133
+ Sign-in uses the fixed callback port `47615`. One sign-in runs per server
134
+ process; while it is open, further calls reuse it and return the same link.
135
+ If another process (a second MCP client or a terminal `login`) holds the port
136
+ mid-sign-in, tools return `login_port_busy`: finish that sign-in and retry.
137
+ On prod without a client ID (see step 1) they return `login_unavailable`,
138
+ naming `WEVO_PULSE_CLIENT_ID`. Stopping the server
139
+ cancels its open sign-in.
140
+
141
+ ### Configuration files
142
+
143
+ Settings come from process variables plus at most one env file, chosen in
144
+ this order:
145
+
146
+ 1. `WEVO_PULSE_ENV_FILE`, if set. Use an absolute path; a missing file fails
147
+ startup.
148
+ 2. The package-root `.env` (monorepo checkouts only; installed packages have
149
+ none).
150
+ 3. `mcp.env` in the config directory above (`~/.config/wevo-pulse-mcp/mcp.env`
151
+ by default).
152
+
153
+ Missing implicit files are ignored. Process variables win over file values.
154
+ `WEVO_PULSE_BASE_URL` is an **origin only**, without `/api` or
155
+ `/api/public/v1`, and defaults to prod (`https://platform.wevo.ai`); HTTPS is required except for loopback
156
+ test APIs. Timeout (`WEVO_PULSE_TIMEOUT_MS`): 1–300000 ms. See
157
+ [`.env.example`](.env.example) for every setting.
158
+
159
+ Login options: `WEVO_PULSE_CALLBACK_PORT` (must match a registered callback),
160
+ `WEVO_PULSE_CLIENT_ID` (built in for dev and staging; required on prod until
161
+ WEVO's own login client ships there),
162
+ `WEVO_PULSE_AUTH0_DOMAIN` / `WEVO_PULSE_AUTH0_AUDIENCE` (required only for
163
+ origins outside [Environments](#environments)),
164
+ `WEVO_PULSE_AUTH0_ORGANIZATION` (users in several orgs), and
165
+ `WEVO_PULSE_SCOPES` (subset of the app's scopes; `offline_access` is always
166
+ added), and `WEVO_PULSE_NO_BROWSER=1` (return the sign-in link without opening
167
+ a browser).
168
+
169
+ ### Fixed token override (CI/tests)
170
+
171
+ `WEVO_PULSE_TOKEN` (process env or env file) overrides the stored login and is
172
+ never refreshed; while it is set the server never starts browser sign-in. Use a user-delegated **access token**, without `Bearer `, from
173
+ a developer portal app (for example via the
174
+ [API docs test client](https://github.com/wevo-conversion/pulse-api-docs-test-client)).
175
+ Do not use an ID token, client secret, or client-credentials/M2M token. A
176
+ normal Pulse web-app token is insufficient unless its client is registered for
177
+ public API use. Keep tokens out of client JSON, shell history, and commits.
178
+
179
+ ## Connect clients
180
+
181
+ Clients need only the command: the server reads
182
+ `~/.config/wevo-pulse-mcp/mcp.env` and the stored login itself, and starts
183
+ browser sign-in on the first tool call when nobody is signed in. MCP reserves
184
+ stdout for JSON-RPC; `npx -y` installs quietly, and the server logs only to
185
+ stderr.
186
+
187
+ **Claude Code** — register once:
188
+
189
+ ```bash
190
+ claude mcp add --transport stdio --scope user \
191
+ wevo-pulse-public -- npx -y @wevo-ai/pulse-mcp
192
+ ```
193
+
194
+ Run `/mcp`, then ask: “Use wevo-pulse-public to check my identity and list my
195
+ Personas.”
196
+
197
+ **Cursor** — merge into `~/.cursor/mcp.json` (user) or `.cursor/mcp.json` (project):
198
+
199
+ ```json
200
+ {
201
+ "mcpServers": {
202
+ "wevo-pulse-public": {
203
+ "command": "npx",
204
+ "args": ["-y", "@wevo-ai/pulse-mcp"]
205
+ }
206
+ }
207
+ }
208
+ ```
209
+
210
+ Enable it in Cursor's MCP settings; refresh/restart after configuration changes.
211
+
212
+ **Claude Desktop** — either:
213
+
214
+ - **One-click bundle** (recommended for customers while the npm package is
215
+ restricted): open `wevo-pulse.mcpb` (Settings → Extensions → Install
216
+ Extension). The bundle embeds the server, so it needs no npm account,
217
+ registry access, Node install, or env file; Desktop runs it on its built-in
218
+ Node.js. It prompts for the API origin (prefilled with prod,
219
+ `https://platform.wevo.ai`) plus two optional fields. Leave the **access
220
+ token** empty, then ask Claude anything that uses WEVO (or "log in to
221
+ WEVO"): a browser window opens to sign in, and the next request works. No
222
+ terminal is needed. On prod, until WEVO's own login client ships there,
223
+ fill in the **client ID** field (`WEVO_PULSE_CLIENT_ID`, step 1 above);
224
+ dev and staging need none. The login lives in the same credentials file as a
225
+ terminal `login`, so both share it and its refresh. Pasting an access token instead stores it in the OS keychain and
226
+ disables browser sign-in; it is never refreshed, so replace it when it
227
+ expires. If you set `XDG_CONFIG_HOME` or `WEVO_PULSE_CONFIG_DIR` only in
228
+ your shell, Desktop will not see a terminal login. Build the bundle with `npm run mcpb`
229
+ (output: `dist/wevo-pulse.mcpb`) and share the file directly.
230
+ - **JSON config**: the Cursor `mcpServers` JSON in
231
+ `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS
232
+ (Settings → Developer → Edit Config), then restart Claude Desktop.
233
+
234
+ Neither makes the server reachable from the claude.ai website.
235
+
236
+ **VS Code / GitHub Copilot** — `.vscode/mcp.json` uses `servers`:
237
+
238
+ ```json
239
+ {
240
+ "servers": {
241
+ "wevo-pulse-public": {
242
+ "type": "stdio",
243
+ "command": "npx",
244
+ "args": ["-y", "@wevo-ai/pulse-mcp"]
245
+ }
246
+ }
247
+ }
248
+ ```
249
+
250
+ Other stdio clients use the same command and arguments. To keep settings
251
+ elsewhere, add `"env": { "WEVO_PULSE_ENV_FILE": "/Users/YOU/path/mcp.env" }`
252
+ (JSON configs do not expand `~`). GUI apps may not inherit a shell-managed
253
+ `PATH` (nvm, fnm, Volta): if the client cannot find `npx`, use its absolute
254
+ path from `which npx`. On Windows clients that cannot launch `npx` directly,
255
+ use `"command": "cmd"` with `"args": ["/c", "npx", "-y", "@wevo-ai/pulse-mcp"]`.
256
+
257
+ ### From a monorepo checkout (WEVO developers)
258
+
259
+ From the `wevo-pulse` repository root, using Node 25 from `.nvmrc`:
260
+
261
+ ```bash
262
+ make install-api-client install-mcp
263
+ make build-api-client build-mcp
264
+ cp -n packages/wevo-pulse-mcp/.env.example packages/wevo-pulse-mcp/.env
265
+ chmod 600 packages/wevo-pulse-mcp/.env # then set WEVO_PULSE_BASE_URL (dev and staging need no WEVO_PULSE_CLIENT_ID)
266
+ ./scripts/with-node.sh node packages/wevo-pulse-mcp/dist/public-index.js login
267
+ claude mcp add --transport stdio --scope user wevo-pulse-public -- \
268
+ /ABS/PATH/TO/wevo-pulse/scripts/with-node.sh node \
269
+ /ABS/PATH/TO/wevo-pulse/packages/wevo-pulse-mcp/dist/public-index.js
270
+ ```
271
+
272
+ The gitignored package `.env` loads automatically, regardless of the client's
273
+ working directory, and takes the place of `mcp.env`. Login uses the same
274
+ per-user credentials file as the npm package. Other clients use
275
+ `/ABS/PATH/TO/wevo-pulse/scripts/with-node.sh` as `command` and
276
+ `["node", "/ABS/PATH/TO/wevo-pulse/packages/wevo-pulse-mcp/dist/public-index.js"]`
277
+ as `args`. The wrapper selects Node 25 without writing to stdout.
278
+
279
+ ## Run and develop
280
+
281
+ No shared daemon needs starting first: each MCP client starts its own process.
282
+ To run manually from the repository root:
283
+
284
+ ```bash
285
+ ./scripts/with-node.sh node packages/wevo-pulse-mcp/dist/public-index.js
286
+ ```
287
+
288
+ A quiet process waiting on stdin is expected. Ctrl-C stops it. For interactive
289
+ protocol inspection after signing in:
290
+
291
+ ```bash
292
+ ./scripts/with-node.sh npx @modelcontextprotocol/inspector \
293
+ ./scripts/with-node.sh node packages/wevo-pulse-mcp/dist/public-index.js
294
+ ```
295
+
296
+ For source iteration, run `npm run dev:public` inside this package. Reconnect
297
+ after watch-process restarts. Compiled clients need `make build-mcp` followed
298
+ by restart/reconnect. `npm start` also runs the compiled public server.
299
+
300
+ ```bash
301
+ make test-mcp typecheck-mcp lint-mcp
302
+ make code-quality
303
+ ```
304
+
305
+ Tests cover MCP discovery/calls, all route mappings and request schemas,
306
+ configuration validation, auth/error propagation, 204 deletes, idempotency,
307
+ timeouts, redirects, and a real stdio child process against a loopback fixture
308
+ API. Login tests cover PKCE, callback state handling, credential file
309
+ permissions/locking, refresh-token rotation, single 401 retry, env-token
310
+ precedence, and in-server sign-in (auto-start, reuse, success, timeout,
311
+ busy port, URL elicitation, logout, and a stdio child whose stdout carries only
312
+ JSON-RPC) against a loopback fake Auth0; they never contact real Auth0. Tests
313
+ never launch real billable Pulses.
314
+
315
+ ## Package and publish
316
+
317
+ `npm run build` uses esbuild to bundle `src/public-index.ts` and the legacy
318
+ `src/index.ts` into self-contained ESM files in `dist/` (local sourcemaps),
319
+ plus `dist/THIRD_PARTY_NOTICES.txt` for the inlined dependencies. Runtime
320
+ libraries, including `@wevo/pulse-api-client`, are `devDependencies` on
321
+ purpose: they are inlined, so the published package installs nothing else. Add
322
+ new runtime libraries the same way.
323
+
324
+ `npm pack` builds first, then ships only the `wevo-pulse-public-mcp` bin,
325
+ notices, README, and `package.json` (about 150 kB). The legacy
326
+ `wevo-pulse-mcp` server targets internal Persona/Audience APIs, so it stays
327
+ monorepo-only (`node dist/index.js`). `npm run mcpb` writes
328
+ `dist/wevo-pulse.mcpb` for Claude Desktop with a pinned `@anthropic-ai/mcpb`.
329
+
330
+ The package publishes as `@wevo-ai/pulse-mcp` in WEVO's `@wevo-ai` npm org,
331
+ with **restricted** access for now (public later).
332
+ The `@wevo` scope on public npm belongs to an unrelated owner: never publish
333
+ or document install commands under it. Internal packages such as
334
+ `@wevo/pulse-api-client` stay on private CodeArtifact and are only inlined
335
+ into the bundle.
336
+
337
+ Publishing is gated by `"private": true`. `publishConfig` pins
338
+ `registry.npmjs.org` with `"access": "restricted"`, so a CodeArtifact login
339
+ that rewrites the default registry cannot capture the publish and the first
340
+ publish cannot go public by accident.
341
+
342
+ Restricted access means (per npm docs:
343
+ [private packages](https://docs.npmjs.com/about-private-packages),
344
+ [paid organization plan](https://docs.npmjs.com/upgrading-to-a-paid-organization-plan),
345
+ [visibility](https://docs.npmjs.com/package-scope-access-level-and-visibility),
346
+ [CI auth](https://docs.npmjs.com/using-private-packages-in-a-ci-cd-workflow)):
347
+
348
+ - The `@wevo-ai` org must be on the paid **npm Teams** plan, billed per org
349
+ member (every member, not just publishers).
350
+ - Only org members on a team granted read access can view or install it.
351
+ Each installer needs their own npm account in the org and must authenticate
352
+ to `registry.npmjs.org` (`npm login`, or a read-only granular token via
353
+ `.npmrc`). Anonymous `npx` gets a 404.
354
+ - The [MCP Registry](https://modelcontextprotocol.io/registry/package-types)
355
+ lists npm servers only from the public npm registry and verifies `mcpName`
356
+ from the published `package.json`, so no MCP Registry listing is possible
357
+ while restricted.
358
+ - Customers outside the org use the `.mcpb` bundle, which embeds the server.
359
+
360
+ To publish:
361
+
362
+ 1. Confirm the `@wevo-ai` org is on npm Teams, then remove `"private": true`.
363
+ 2. Publish from CI, not a laptop: a `workflow_dispatch` job modeled on
364
+ `.github/workflows/api-client.yml` (Node from `.nvmrc`,
365
+ `make install-api-client install-mcp test-mcp`, `npm version`,
366
+ `npm publish` with `id-token: write`, using npm trusted publishing
367
+ configured for `@wevo-ai/pulse-mcp`). Do not pass `--provenance`: npm
368
+ generates [provenance](https://docs.npmjs.com/trusted-publishers) only for
369
+ public packages published from public repositories, and both are private
370
+ here. Attach `dist/wevo-pulse.mcpb` to the GitHub release.
371
+ 3. Going public later: set `publishConfig.access` to `"public"` (or
372
+ `npm access set status=public @wevo-ai/pulse-mcp`), then add `mcpName` and
373
+ a `server.json` for an MCP Registry listing.
374
+
375
+ The server version reported at MCP `initialize` comes from `package.json`:
376
+ `scripts/build.mjs` injects it with esbuild `define` (unbundled `tsx` runs
377
+ report `0.0.0-dev`).
378
+
379
+ ## Tools and workflow
380
+
381
+ Routes below are relative to `/api/public/v1`.
382
+
383
+ | Tools | API | Scope |
384
+ | ----------------------------------------------------------------- | ------------------------------------------------------------------------------- | ---------------------------------------- |
385
+ | `login`, `auth_status`, `logout` | Browser sign-in; local credential status; sign out (no `/api` call) | None |
386
+ | `whoami` | `GET /me` | Valid user token |
387
+ | `list_personas`, `get_persona` | `GET /personas`, `GET /personas/{id}` | `personas:read` |
388
+ | `get_persona_research_profile`, `get_persona_personality_profile` | `GET /personas/{id}/research-profile`, `GET /personas/{id}/personality-profile` | `personas:outputs:read` |
389
+ | `create_persona`, `update_persona`, `delete_persona` | `POST /personas`, `PATCH/DELETE /personas/{id}` | `personas:write` |
390
+ | `generate_persona`, `get_persona_generation` | `POST /personas/generations`, `GET /personas/generations/{id}` | write / read |
391
+ | `create_url_asset`, `list_assets`, `get_asset`, `delete_asset` | `POST/GET /assets`, `GET/DELETE /assets/{id}` | `assets:write` / `assets:read` |
392
+ | `list_pulses`, `get_pulse`, `get_pulse_results` | `GET /pulses`, `GET /pulses/{id}`, `GET /pulses/{id}/results` | `pulse:read` |
393
+ | `launch_pulse` | `POST /pulses` | `pulse:write` + Pulse product permission |
394
+ | `launch_compare`, `get_compare`, `get_compare_results` | `POST /compares`, `GET /compares/{id}`, `GET /compares/{id}/results` | write + Compare permission / read |
395
+
396
+ `create_persona` (structured fields) and `generate_persona` (description) both
397
+ return a full generation payload: `generation_id`, overall `status`, optional
398
+ `persona_id`, and states for `core`, `research_profile`, `personality`,
399
+ `demographics`, `image`, and `grounding`. Keep the generation ID. Poll
400
+ `get_persona_generation` no more often than every **10 seconds**, narrating
401
+ component progress to the user. Report completion only when every component is
402
+ `completed` or `skipped`; if any component or overall generation is `failed`,
403
+ name the component and stop. Read research/personality output only after their
404
+ components finish.
405
+
406
+ Flow: `whoami` → select/create and fully complete Persona → `create_url_asset`
407
+ → poll `get_asset` until `ready` → `launch_pulse` → poll `get_pulse` →
408
+ `get_pulse_results` after `completed` or `partial`. Partial Pulses expose
409
+ successful sections. Stop on failure. Compare 2–6 completed Pulses, then
410
+ retrieve status/results. Tool callers should send an arguments object (`{}` for
411
+ no filters).
412
+
413
+ Generation, Pulse launch, and Compare launch require `idempotency_key`. Use a
414
+ unique key per intended operation; retain it and identical inputs when retrying
415
+ an uncertain response. API retention is 24 hours; failed Persona generations
416
+ can restart with the same key under the API's documented semantics. The server
417
+ never retries writes automatically. Asset/manual Persona creation lack public
418
+ API idempotency: inspect existing resources before repeating uncertain creates.
419
+ Writes can spend credits. Effect annotations do not replace client approval
420
+ or API authorization.
421
+
422
+ Errors set MCP `isError: true`. HTTP failures preserve `{status, body,
423
+ retry_after}`; `body` is the public error envelope, including request ID.
424
+ `401`: invalid/expired token; `login_started` (sign-in opened: finish it at
425
+ `authorize_url`, then retry); `login_port_busy` or `login_unavailable` (see
426
+ [Stored login](#stored-login)). `403`:
427
+ scopes, app registration, membership, or permissions. `402`: insufficient credits. `429`: follow retry guidance when
428
+ provided. Network errors report an unknown outcome without raw transport errors.
429
+
430
+ ## Boundaries and hosted migration
431
+
432
+ `public-server.ts` owns tools and turns `login_required` into a background
433
+ sign-in; `public-client.ts` owns HTTP transport and the 401 retry; `src/auth/`
434
+ owns login, the per-process sign-in session (`session.ts`), the credential
435
+ file, and refresh;
436
+ `public-index.ts` dispatches CLI subcommands or connects stdio. Typed requests use
437
+ `createPublicPulseClient` in `@wevo/pulse-api-client`, generated from
438
+ `contracts/openapi-public-v1.json`. Regenerate with `make api-contract`; verify
439
+ with `make api-contract-check`. The broad internal contract remains separate
440
+ because it accepts launch fields absent from public v1.
441
+
442
+ The planned hosted endpoint uses **Streamable HTTP**. Before exposing it,
443
+ implement per-user OAuth authorization code + PKCE, resource metadata,
444
+ audience validation, refresh/revocation, rate limits, and the agreed downstream
445
+ authorization design. Never expose this single-user local credential setup
446
+ through a shared network listener. Credit attribution and launch billing readiness remain
447
+ prerequisites from the design plan. No Auth0, AWS, Terraform, or deployment
448
+ changes are included here.
449
+
450
+ Image upload and SSE-to-MCP progress are deferred. URL
451
+ Assets, full tracked Persona generation, output reads, polling, and public
452
+ results support the local end-to-end workflow.
453
+
454
+ The existing `dist/index.js` and `npm run dev` remain available in the
455
+ monorepo for internal Persona/Audience integrations; see
456
+ [legacy setup](docs/internal-server.md). The published package omits them.
457
+ Use **`dist/public-index.js`**, **`wevo-pulse-public-mcp`**, or **`npm start`** for
458
+ this public API server. It registers no internal Audience/chat tools.