backend-manager 5.6.3 → 5.7.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/CHANGELOG.md +43 -0
- package/CLAUDE.md +4 -3
- package/PROGRESS.md +34 -0
- package/docs/ai-library.md +62 -11
- package/docs/cdp-debugging.md +44 -0
- package/docs/cli-output.md +22 -10
- package/docs/mcp.md +166 -43
- package/docs/test-framework.md +2 -2
- package/package.json +1 -1
- package/plans/mcp2.md +247 -0
- package/src/cli/commands/mcp.js +8 -2
- package/src/cli/commands/serve.js +155 -29
- package/src/cli/commands/setup-tests/base-test.js +8 -0
- package/src/cli/commands/setup-tests/firebase-auth.js +26 -0
- package/src/cli/commands/setup-tests/firebase-cli.js +9 -13
- package/src/cli/commands/setup-tests/index.js +4 -0
- package/src/cli/commands/setup-tests/java-installed.js +26 -0
- package/src/cli/commands/setup.js +2 -1
- package/src/cli/commands/test.js +13 -0
- package/src/cli/index.js +14 -0
- package/src/cli/utils/ui.js +27 -5
- package/src/manager/index.js +8 -3
- package/src/manager/libraries/ai/index.js +45 -1
- package/src/manager/libraries/ai/providers/anthropic-format.js +234 -0
- package/src/manager/libraries/ai/providers/anthropic.js +28 -49
- package/src/manager/libraries/ai/providers/claude-code.js +21 -47
- package/src/manager/libraries/ai/providers/openai.js +154 -19
- package/src/manager/libraries/ai/providers/test.js +242 -0
- package/src/manager/libraries/email/data/disposable-domains.json +465 -0
- package/src/mcp/client.js +48 -13
- package/src/mcp/handler.js +222 -69
- package/src/mcp/index.js +48 -18
- package/src/mcp/tools.js +150 -0
- package/src/mcp/utils.js +108 -0
- package/src/test/fixtures/firebase-project/firebase.json +1 -1
- package/src/test/test-accounts.js +31 -0
- package/test/ai/tools-live.js +170 -0
- package/test/email/marketing-lifecycle.js +10 -5
- package/test/helpers/ai-test-provider.js +202 -0
- package/test/helpers/ai-tools-format.js +350 -0
- package/test/mcp/discovery.js +53 -0
- package/test/mcp/oauth.js +161 -0
- package/test/mcp/protocol.js +268 -0
- package/test/mcp/roles.js +168 -0
- package/test/mcp/utils.js +245 -0
- package/test/routes/marketing/webhook.js +37 -33
- package/.claude/settings.local.json +0 -12
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,49 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
|
14
14
|
- `Fixed` for any bug fixes.
|
|
15
15
|
- `Security` in case of vulnerabilities.
|
|
16
16
|
|
|
17
|
+
# [5.7.0] - 2026-06-17
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- **MCP role-based tool scoping.** 25 tools (was 19) with admin/user/public roles. Admin sees all, user sees `get_user` + `get_subscription` + `health_check`, unauthenticated gets 401 triggering OAuth. Defense-in-depth: route-level auth still validates.
|
|
21
|
+
- **MCP OAuth user authentication.** OAuth 2.1 with PKCE + dynamic client registration (RFC 7591). User sign-in via consumer website's `/token` page → Firebase ID token → exchanged for `api.privateKey`. Verified end-to-end in Claude Desktop.
|
|
22
|
+
- **MCP consumer tools.** Consumer projects define custom MCP tools in `functions/mcp.js` — route delegation (works on stdio + HTTP) or handler mode (HTTP only). Consumer tools override same-name built-ins.
|
|
23
|
+
- **MCP tool annotations.** `title`, `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint` on all tools. Claude Desktop shows read/write categorization and human-readable titles.
|
|
24
|
+
- **6 new MCP tools:** `update_post`, `update_campaign`, `delete_campaign`, `create_contact`, `delete_contact`, `get_payment_portal`.
|
|
25
|
+
- **HTTPS local dev.** `npx mgr serve` starts an HTTPS proxy on port 5002 (firebase serve on 5443 internally) with auto-generated mkcert certificates. Claude Desktop requires HTTPS for MCP connectors. Disable with `--no-https`.
|
|
26
|
+
- **MCP CLI `--token` flag.** `npx mgr mcp --token <api-key>` for user-level stdio connections.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
- **MCP discovery endpoints** use root-level issuer per RFC 8414 (was path-scoped, broke Claude Desktop's discovery chain).
|
|
30
|
+
- **`getApiUrl()`** returns `https://localhost:<port>` when `BEM_HTTPS_PORT` is set (serve command sets this automatically).
|
|
31
|
+
- **`cancel_subscription`, `refund_payment`, `generate_uuid`** moved from user/public to admin role (destructive operations and dev utilities shouldn't be in user-facing MCP).
|
|
32
|
+
- **`resolveConsumerAuthUrl()`** uses `Manager.getWebsiteUrl()` (auto-resolves localhost in dev) instead of `brand.url` (always production).
|
|
33
|
+
|
|
34
|
+
# [5.6.6] - 2026-06-15
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
- **`'warn'` return type for setup checks.** `run()` can now return the string `'warn'` for non-blocking failures — the check prints as `⚠` with detail lines from `getWarning()`, is counted in the summary (`36 passed, 1 warned, 0 failed`), but does **not** halt setup. `BaseTest` provides a default `getWarning()` returning `[]`. `Summary` gains a `warn(name, details)` method alongside `pass()` and `fail()`.
|
|
38
|
+
- **Java setup check** (`setup-tests/java-installed.js`). Checks whether Java is installed (required by the Firebase Firestore emulator for testing). Uses the `'warn'` return type — setup continues without Java, but the summary reports it.
|
|
39
|
+
- **Java pre-check in test runner.** `npx mgr test` now checks for Java before starting the emulator and fails fast with a clear message (`Java is required to run tests`) instead of the raw emulator crash.
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
- **Firebase CLI and auth setup checks no longer halt setup.** Both checks now use the `'warn'` return type instead of throwing from `fix()`. Missing Firebase CLI or unauthenticated state is reported in the summary but does not block the remaining checks.
|
|
43
|
+
|
|
44
|
+
# [5.6.5] - 2026-06-14
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
- **Cross-provider native tool calling in the AI library (agentic loops).** `ai.request()` now supports a unified tools interface on every text provider: `tools.list` accepts normalized function tools (`{ name, description, parameters }` — JSON Schema), `tools.choice` accepts `'auto' | 'required' | 'none' | { name }`, and the response adds `toolCalls: [{ id, name, arguments }]` (arguments parsed) plus `stopReason: 'tool_use' | 'end' | 'max_tokens'`. The Anthropic and claude-code providers gain native `tool_use` via a shared pure formatter (`providers/anthropic-format.js` — tool defs → `input_schema`, choice mapping incl. `required`→`any`, response extraction); the OpenAI provider normalizes function tools to the Responses API envelope (hosted tools like `{ type: 'web_search' }` still pass verbatim, and throw a clear error on Anthropic) and extracts `function_call` items. Multi-turn loop continuation is first-class through `options.messages`: `{ role: 'assistant', toolCalls }` and `{ role: 'tool', toolCallId, content }` turns map to each provider's wire format (consecutive tool results merge into one Anthropic user turn; OpenAI gets `function_call`/`function_call_output` items), raw Anthropic block arrays replay verbatim, and `normalizeOptions()` no longer string-flattens structured conversations (system-prompt injections still apply). OpenAI additionally gains a direct-messages mode: passing `messages[]` now sends ALL turns (previously middle turns were silently dropped in favor of prompt/message/history). JSON parsing (`response: 'json'`) is skipped on tool-call turns, where empty text is the normal intermediate state. Return shapes stay backward-compatible (`{ content, output, tokens, raw }` unchanged; OpenAI now also returns `raw`). Covered by `test/helpers/ai-tools-format.js` (pure, 22 cases) and `test/ai/tools-live.js` (extended-mode real 2-step tool loops on both providers; OpenAI live-validated).
|
|
48
|
+
- **Deterministic `test` AI provider** (`providers/test.js`, registered as `provider: 'test'`) — the AI analog of the `test` payment processor: a first-class provider that consumer suites drive with directives embedded in the last user message (`[[tool:name {json}]]`, `[[tools:[...]]]` for parallel calls, `[[reply:{json}]]`, `[[delay:ms]]`, `[[error:msg]]`), consumed sequentially across the turns of a tool loop. Refuses to run outside development/testing (Manager environment detection; falls back to `BEM_TESTING`/`FUNCTIONS_EMULATOR` signals when constructed without a Manager). Lets consumer chat/agent routes test the full loop — Firestore writes, usage, locks, tool executors — against the real emulator with zero paid API calls. Covered by `test/helpers/ai-test-provider.js`.
|
|
49
|
+
- **OpenAI provider constructor hardened** — `assistant.Manager` access now uses optional chaining (matching the Anthropic provider), so the provider can be constructed with a minimal assistant context (direct construction in tests/tools).
|
|
50
|
+
- **`docs/cdp-debugging.md` — launching a controllable browser (mirrored across UJM/BEM/BXM/EM).** The canonical Chrome launch for agents and humans: CDP port + REQUIRED dedicated `--user-data-dir` (Chrome 136+ silently ignores the debug port on the default profile — verified on 149), the persistent agent profile (`~/Library/Application Support/chrome-profiles/agent` — log in once, state survives relaunches, verified), the shared-instance model (CDP is multi-client — agents share the one logged-in Chrome on one port, one tab each; a second profile/port only for a second identity), safe quit by profile match, and driving via the `chrome-devtools` MCP (`CHROME_CDP_PORT` set before the session) or any CDP client. BEM flavor: aimed at verifying the frontend against your routes — watch network payloads and drive auth'd flows through the real UI. Indexed in CLAUDE.md.
|
|
51
|
+
|
|
52
|
+
# [5.6.4] - 2026-06-11
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
- **Consent side effects moved off shared test accounts (journey-account isolation).** The marketing webhook suite's revoke-event tests (`test/routes/marketing/webhook.js`) repeatedly write `consent.marketing.status = 'revoked'` to their target account — persistent side-effect data that previously landed on the shared `basic` account, leaving it revoked for the remainder of every run (and, since the v5.6.3 library consent gate, changing `sync()`/`add()` behavior for every later suite touching it). They now target a dedicated `journey-webhook-revoke` account. The extended-mode lifecycle suite (`test/email/marketing-lifecycle.js`) likewise now syncs a dedicated `journey-marketing-sync` account (`_test.allow_*` prefix) instead of the shared `consent-granted` sentinel (which the signup + consent-lifecycle suites rely on). Its cleanup step also now deletes the contact the suite actually created — previously it deleted the ADMIN account's contact, which (post-v5.6.3) revoked admin's doc consent mid-run AND left the synced contact behind in SendGrid/Beehiiv after every extended run. `docs/test-framework.md`'s journey-account rule now lists `consent.marketing` writes as a trigger. Validated: marketing route suites pass (46 passing / 10 env-gated skips / 0 failures).
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
- **Anonymous HMAC unsubscribe tests now actually run.** The self-test boot (`src/cli/commands/test.js`) injects a test-only `UNSUBSCRIBE_HMAC_KEY` into the process env (the emulated functions inherit it — same mechanism as the fixture webhook key), closing the fixture gap that left all 8 anon-HMAC tests in `test/routes/marketing/email-preferences.js` failing as "known env gap". Those tests are the route-level coverage for the v5.6.3 HMAC changes (signature validation, rate limiting, consent mirroring); marketing suites went from 38 passing + 8 failing to 46 passing + 0 failing.
|
|
59
|
+
|
|
17
60
|
# [5.6.3] - 2026-06-11
|
|
18
61
|
|
|
19
62
|
### Fixed
|
package/CLAUDE.md
CHANGED
|
@@ -70,7 +70,7 @@ Every feature ships with tests at EVERY surface it exposes — logic (`test/rout
|
|
|
70
70
|
| `watch` | Auto-reload functions on file change |
|
|
71
71
|
| `deploy` | Deploy Cloud Functions to Firebase |
|
|
72
72
|
| `test` | Run framework + project test suites against an emulator |
|
|
73
|
-
| `mcp` | Start the stdio MCP server (for Claude Code / Claude Desktop) |
|
|
73
|
+
| `mcp` | Start the stdio MCP server (for Claude Code / Claude Desktop). Supports `--token <key>` for user-level connections |
|
|
74
74
|
| `firestore:get/set/query/delete` | Direct Firestore reads/writes from the terminal |
|
|
75
75
|
| `auth:get/list/delete/set-claims` | Manage Auth users from the terminal |
|
|
76
76
|
| `logs:read` / `logs:tail` | Cloud Function logs from Google Cloud Logging |
|
|
@@ -136,8 +136,9 @@ Deep references live in `docs/`. **Whenever you make a behavioral change, update
|
|
|
136
136
|
- [docs/file-naming.md](docs/file-naming.md) — naming table for routes, schemas, API commands, events, cron jobs, hooks
|
|
137
137
|
- [docs/common-mistakes.md](docs/common-mistakes.md) — anti-pattern checklist (don't modify Manager internals, always await, increment-before-update, etc.)
|
|
138
138
|
- [docs/audit.md](docs/audit.md) — full-audit check catalog (U-xx universal / BEM-xx / F-xx IDs with severity + scope), protocol + fix loop
|
|
139
|
+
- [docs/cdp-debugging.md](docs/cdp-debugging.md) — launching a controllable Chrome (CDP) to verify the frontend against your routes (network payloads, auth'd flows via the persistent agent profile)
|
|
139
140
|
- [docs/key-files.md](docs/key-files.md) — quick lookup for the most-touched files (Manager, helpers, auth events, cron, payment processors, CLI commands)
|
|
140
|
-
- [docs/cli-output.md](docs/cli-output.md) — shared CLI styling module (`src/cli/utils/ui.js`): OMEGA-style banner/dividers/sections/status symbols + the `Summary` block; used by `setup`, adoptable by other commands
|
|
141
|
+
- [docs/cli-output.md](docs/cli-output.md) — shared CLI styling module (`src/cli/utils/ui.js`): OMEGA-style banner/dividers/sections/status symbols + the `Summary` block (pass/warn/fail); setup check return types (`true`/`false`/`Error`/`'warn'`); used by `setup`, adoptable by other commands
|
|
141
142
|
- [docs/environment-detection.md](docs/environment-detection.md) — `getEnvironment()` returns `'development' | 'testing' | 'production'` (mutually exclusive); gate side effects on the INTENTIONAL check (`isProduction()` for prod-only, `isDevelopment() || isTesting()` for local-or-test) — never `!isDevelopment()`. Plus the URL helper convention (always `Manager.getApiUrl()` — auto-resolves local in dev+test, never read `project.apiUrl`)
|
|
142
143
|
- [docs/response-headers.md](docs/response-headers.md) — automatic `bm-properties` header
|
|
143
144
|
|
|
@@ -157,7 +158,7 @@ Deep references live in `docs/`. **Whenever you make a behavioral change, update
|
|
|
157
158
|
- [docs/payment-system.md](docs/payment-system.md) — full payment pipeline: Intent → Webhook → On-Write → Transition; subscription model, statuses, `resolveSubscription()`, transition handlers, processor interface, product config, test processor
|
|
158
159
|
- [docs/marketing-campaigns.md](docs/marketing-campaigns.md) — campaign CRUD routes, recurring campaigns, generator pipeline (newsletter), newsletter-driven blog article (`content.article.enabled`), template-owned schemas, asset hosting, seed campaigns
|
|
159
160
|
- [docs/consent.md](docs/consent.md) — marketing consent capture: canonical `consent.{legal,marketing}` user-doc shape, signup-form capture, account-page toggle, HMAC unsub link (cross-provider unsub + re-add on resubscribe), admin contact-DELETE revoke mirror, SendGrid+Beehiiv webhook receivers, parent forwarder (`/marketing/webhook/forward`), library-level consent gate in `email.add()`/`email.sync()` (revoked-only skip), migration script template
|
|
160
|
-
- [docs/mcp.md](docs/mcp.md) — Model Context Protocol server:
|
|
161
|
+
- [docs/mcp.md](docs/mcp.md) — Model Context Protocol server: 25 tools with role-based scoping (22 admin / 2 user / 1 public), tool annotations (title, read/write hints), OAuth 2.1 with PKCE + dynamic client registration + consumer website sign-in, consumer MCP tools (`functions/mcp.js`), HTTPS local dev (mkcert), Claude Desktop/Chat/Code configuration
|
|
161
162
|
|
|
162
163
|
### Subsystems & Libraries
|
|
163
164
|
|
package/PROGRESS.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Project Progress Tracker
|
|
2
|
+
> Agents and maintainers should update this file regularly to reflect the current state of the project.
|
|
3
|
+
|
|
4
|
+
## 🎯 Current Focus
|
|
5
|
+
* **Goal:** MCP role-based tool scoping + consumer extensibility
|
|
6
|
+
* **Current Phase:** Complete — all phases done, 44 tests passing, docs finalized
|
|
7
|
+
* **Priority:** High
|
|
8
|
+
* **Last Updated:** 2026-06-17 3:42 AM PDT
|
|
9
|
+
* **Notes:** Ready to ship. Full OAuth flow verified in Claude Desktop. Role reassignment (16 admin / 2 user / 1 public), annotations, HTTPS serve, dynamic client registration all working.
|
|
10
|
+
|
|
11
|
+
## 📌 Active Task List
|
|
12
|
+
|
|
13
|
+
## ✅ Completed Task List
|
|
14
|
+
* [x] Phase 1: MCP role-based tool scoping + consumer extensibility
|
|
15
|
+
* [x] Foundation utilities (`src/mcp/utils.js`)
|
|
16
|
+
* [x] Add `role` to all 19 tools (`src/mcp/tools.js`)
|
|
17
|
+
* [x] User token support in HTTP client (`src/mcp/client.js`)
|
|
18
|
+
* [x] Stdio server role filtering + consumer tools (`src/mcp/index.js`)
|
|
19
|
+
* [x] CLI `--token` flag + `cwd` passthrough (`src/cli/commands/mcp.js`)
|
|
20
|
+
* [x] HTTP handler — role filtering, OAuth user flow, consumer tool execution (`src/mcp/handler.js`)
|
|
21
|
+
* [x] Test suite — 44 tests across 5 files
|
|
22
|
+
* [x] Documentation — `docs/mcp.md`, `CLAUDE.md`
|
|
23
|
+
* [x] UJM `/token` page update (separate repo)
|
|
24
|
+
* [x] Phase 2: HTTPS local dev + Claude Desktop MCP testing
|
|
25
|
+
* [x] HTTPS proxy in `npx mgr serve` (mkcert certs, port 5002 → 5443)
|
|
26
|
+
* [x] `getApiUrl()` returns `https://` when `BEM_HTTPS_PORT` is set
|
|
27
|
+
* [x] Fix OAuth discovery (root-level issuer per RFC 8414)
|
|
28
|
+
* [x] Add dynamic client registration (`POST /mcp/register`)
|
|
29
|
+
* [x] Fix 401 trigger for OAuth flow
|
|
30
|
+
* [x] Tool annotations (title, readOnlyHint, destructiveHint, etc.)
|
|
31
|
+
* [x] Role reassignment (cancel/refund/uuid → admin, user = read-only)
|
|
32
|
+
* [x] Consumer tool override bug fix (listing/execution sync)
|
|
33
|
+
* [x] Test MCP connection in Claude Desktop — verified end-to-end
|
|
34
|
+
* [x] Update tests (44 passing) + docs
|
package/docs/ai-library.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
| `anthropic` | `claude-sonnet-4-6` | Better at SVG illustrations and creative output |
|
|
9
9
|
| `claude-code` | `claude-opus-4-7` | Same Claude models as `anthropic`, but bills a Claude Pro/Max **subscription** instead of API credits |
|
|
10
10
|
|
|
11
|
-
Return shape (same for all providers): `{ content, output, tokens, raw }
|
|
11
|
+
Return shape (same for all providers): `{ content, output, tokens, raw }` — plus `toolCalls` and `stopReason` when tools are in play (see Tools below).
|
|
12
12
|
|
|
13
13
|
`options.response: 'json'` triggers JSON parsing — all providers strip fences and parse with JSON5 for robustness. `options.schema` enforces structure on OpenAI (real JSON schema) and is injected into the system prompt on Anthropic / claude-code.
|
|
14
14
|
|
|
@@ -36,14 +36,48 @@ Return shape (single image): `{ buffer, b64, mime, revisedPrompt, model, size, q
|
|
|
36
36
|
|
|
37
37
|
API key resolution is the same as `request()` — `BACKEND_MANAGER_OPENAI_API_KEY` / `OPENAI_API_KEY` (process.env or config).
|
|
38
38
|
|
|
39
|
-
## Tools
|
|
39
|
+
## Tools — cross-provider function calling (agentic loops)
|
|
40
40
|
|
|
41
|
-
Tools are nested under `options.tools` and opt-in — when omitted, no tools are sent and behavior is identical to a plain request
|
|
41
|
+
Tools are nested under `options.tools` and opt-in — when omitted, no tools are sent and behavior is identical to a plain request.
|
|
42
42
|
|
|
43
|
-
- `tools.list` — array of tool definitions
|
|
44
|
-
- `tools.choice` *(optional)* —
|
|
43
|
+
- `tools.list` — array of tool definitions. **Normalized function tools** (`{ name, description, parameters }` where `parameters` is a JSON Schema object — `type: 'function'` optional) work on EVERY provider. Provider-specific hosted tools (e.g. `{ type: 'web_search' }`, `{ type: 'code_interpreter' }`) pass verbatim on OpenAI and throw a clear error on Anthropic/claude-code.
|
|
44
|
+
- `tools.choice` *(optional)* — `'auto' | 'required' | 'none'`, or `{ name: 'tool_name' }` to force a specific tool. Mapped per provider (Anthropic: `auto`/`any`/`none`/`tool`).
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
When the model decides to call tools, the response carries them in normalized form:
|
|
47
|
+
|
|
48
|
+
- `r.toolCalls` — `[{ id, name, arguments }]`, `arguments` already parsed to an object.
|
|
49
|
+
- `r.stopReason` — `'tool_use' | 'end' | 'max_tokens'`.
|
|
50
|
+
|
|
51
|
+
A tool-call turn legitimately has `content: ''` — `response: 'json'` parsing is skipped on tool-call turns (the caller is expected to continue the loop, not consume a final answer).
|
|
52
|
+
|
|
53
|
+
### Loop continuation via `options.messages`
|
|
54
|
+
|
|
55
|
+
Structured conversations pass the full turn history through `options.messages` with two cross-provider conventions:
|
|
56
|
+
|
|
57
|
+
- Assistant tool-call turn: `{ role: 'assistant', content?, toolCalls: [{ id, name, arguments }] }` — or replay the provider's raw blocks (`{ role: 'assistant', content: r.raw.content }`) on Anthropic.
|
|
58
|
+
- Tool result turn: `{ role: 'tool', toolCallId, content }` — consecutive tool results merge into one Anthropic user turn of `tool_result` blocks; OpenAI gets `function_call_output` items.
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
const ai = Manager.AI(assistant);
|
|
62
|
+
const messages = [
|
|
63
|
+
{ role: 'system', content: 'Use tools to answer.' },
|
|
64
|
+
{ role: 'user', content: 'What is the weather in Paris?' },
|
|
65
|
+
];
|
|
66
|
+
const tools = { list: [{ name: 'get_weather', description: '...', parameters: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'] } }] };
|
|
67
|
+
|
|
68
|
+
const first = await ai.request({ provider: 'anthropic', messages, tools });
|
|
69
|
+
// first.stopReason === 'tool_use'; first.toolCalls = [{ id, name: 'get_weather', arguments: { city: 'Paris' } }]
|
|
70
|
+
|
|
71
|
+
messages.push({ role: 'assistant', content: first.raw.content }); // or { role: 'assistant', toolCalls: first.toolCalls }
|
|
72
|
+
messages.push({ role: 'tool', toolCallId: first.toolCalls[0].id, content: '{"temp":"21C"}' });
|
|
73
|
+
|
|
74
|
+
const second = await ai.request({ provider: 'anthropic', messages, tools });
|
|
75
|
+
// second.stopReason === 'end'; second.content is the final answer
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`normalizeOptions()` detects structured conversations (tool turns / toolCalls / raw blocks) and leaves them intact — only the system turn gets the universal prompt injections. Plain-text `messages[]` keep their legacy behavior, except OpenAI now sends ALL turns (previously middle turns were dropped in favor of prompt/message/history).
|
|
79
|
+
|
|
80
|
+
### Hosted web search (OpenAI only)
|
|
47
81
|
|
|
48
82
|
```js
|
|
49
83
|
const r = await ai.request({
|
|
@@ -56,7 +90,22 @@ const r = await ai.request({
|
|
|
56
90
|
});
|
|
57
91
|
```
|
|
58
92
|
|
|
59
|
-
|
|
93
|
+
URL citations live in the returned `output` (message content) as `annotations` of type `url_citation`.
|
|
94
|
+
|
|
95
|
+
## `test` provider — deterministic scripted AI for test suites
|
|
96
|
+
|
|
97
|
+
`provider: 'test'` is the AI analog of the `test` payment processor: a first-class provider that suites drive with directives in the LAST user message, so consumer routes exercise their full loop (Firestore writes, usage, locks, tool execution) against the real emulator with zero paid API calls. It **refuses to run outside development/testing**.
|
|
98
|
+
|
|
99
|
+
Directives form a sequence consumed across loop turns (call N executes directive N-1, indexed by assistant turns after the last user turn). Directive values must not contain `]]` internally (a trailing JSON `]` is fine):
|
|
100
|
+
|
|
101
|
+
| Directive | Behavior |
|
|
102
|
+
|---|---|
|
|
103
|
+
| `[[tool:name {json}]]` | Emit one tool call this step (`stopReason: 'tool_use'`) |
|
|
104
|
+
| `[[tools:[{"name":"a","arguments":{}}, ...]]]` | Emit parallel tool calls this step |
|
|
105
|
+
| `[[reply:{json}]]` | Final reply (parsed when `response: 'json'`) |
|
|
106
|
+
| `[[delay:ms]]` | Modifier — delay the NEXT step (max 30s) |
|
|
107
|
+
| `[[error:msg]]` | Throw at this step |
|
|
108
|
+
| *(none / exhausted)* | Echo reply: `Echo: <message>` (`{ message }` in json mode) |
|
|
60
109
|
|
|
61
110
|
## `claude-code` provider — subscription billing
|
|
62
111
|
|
|
@@ -74,8 +123,10 @@ The legacy `src/manager/libraries/openai.js` is a thin compatibility shim that r
|
|
|
74
123
|
|
|
75
124
|
| File | Purpose |
|
|
76
125
|
|---|---|
|
|
77
|
-
| `src/manager/libraries/ai/index.js` | Unified `AI` class (dispatches by provider) |
|
|
78
|
-
| `src/manager/libraries/ai/providers/openai.js` | OpenAI provider (
|
|
79
|
-
| `src/manager/libraries/ai/providers/anthropic.js` | Anthropic provider (Claude Messages API, x-api-key, API credits) |
|
|
80
|
-
| `src/manager/libraries/ai/providers/claude-code.js` | claude-code provider (Claude Messages API, OAuth Bearer, subscription billing) |
|
|
126
|
+
| `src/manager/libraries/ai/index.js` | Unified `AI` class (dispatches by provider; structured-messages detection) |
|
|
127
|
+
| `src/manager/libraries/ai/providers/openai.js` | OpenAI provider (Responses API; direct-messages mode + tool envelopes) |
|
|
128
|
+
| `src/manager/libraries/ai/providers/anthropic.js` | Anthropic provider (Claude Messages API, x-api-key, API credits, native tool_use) |
|
|
129
|
+
| `src/manager/libraries/ai/providers/claude-code.js` | claude-code provider (Claude Messages API, OAuth Bearer, subscription billing, native tool_use) |
|
|
130
|
+
| `src/manager/libraries/ai/providers/anthropic-format.js` | Shared pure formatters for both Claude providers (tool defs, message building, extraction) |
|
|
131
|
+
| `src/manager/libraries/ai/providers/test.js` | Deterministic `test` provider (scripted directives; dev/testing only) |
|
|
81
132
|
| `src/manager/libraries/openai.js` | Back-compat shim → providers/openai.js |
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# CDP Debugging (driving a live browser)
|
|
2
|
+
|
|
3
|
+
How to launch a browser you can CONTROL — see a site live, screenshot it, click, type, read console logs, inspect network requests — for agents (Claude via MCP/CDP) and humans. BEM has no UI of its own; reach for this when **verifying the frontend that consumes your backend** (the UJM site, a deployed app): drive the auth flow, watch the network panel for calls to your routes, read the actual request/response payloads.
|
|
4
|
+
|
|
5
|
+
> Mirrored across the four sister frameworks (UJM / BEM / BXM / EM) — same core section, framework-flavored. Edit all four together.
|
|
6
|
+
|
|
7
|
+
## Launching a controllable Chrome (the canonical command)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
open -gna "Google Chrome" --args \
|
|
11
|
+
--remote-debugging-port=9223 \
|
|
12
|
+
--user-data-dir="$HOME/Library/Application Support/chrome-profiles/agent" \
|
|
13
|
+
--no-first-run --no-default-browser-check \
|
|
14
|
+
--disable-background-timer-throttling \
|
|
15
|
+
--disable-backgrounding-occluded-windows \
|
|
16
|
+
--disable-renderer-backgrounding \
|
|
17
|
+
https://localhost:4000 # ← the frontend talking to your backend (or the IP from .temp/_config_browsersync.yml if localhost doesn't connect)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Verify it's up: `curl -s http://127.0.0.1:9223/json/version`
|
|
21
|
+
|
|
22
|
+
The rules that make this work (each one learned the hard way):
|
|
23
|
+
|
|
24
|
+
- **`open -gna` launches WITHOUT stealing focus.** `-g` = don't bring to foreground, `-n` = new instance (required — without it `open` just activates the already-running daily Chrome and the `--args` are ignored). Launching the Chrome binary directly ALWAYS activates the app and steals focus. Do NOT use `-j`/`--hide` — animations need a visible window; instead the three `--disable-*` flags keep timers/rAF/rendering at FULL speed while the window sits behind your work (verified: rAF at the display's native 120fps while backgrounded, focus never moved).
|
|
25
|
+
- **`--user-data-dir` is REQUIRED, not optional.** Chrome 136+ **silently ignores** `--remote-debugging-port` on the default profile — no error, no port, nothing (verified on Chrome 149). This is the #1 "why isn't CDP up" trap.
|
|
26
|
+
- **The profile dir IS the persistent login state.** Cookies + localStorage survive relaunches (verified by round-trip). **Log into sites once in the agent profile and every agent reuses the authenticated state** — auth'd flows against your routes work without re-login. Ecosystem convention: ONE shared profile at `~/Library/Application Support/chrome-profiles/agent` across all four frameworks, so logins are a one-time setup.
|
|
27
|
+
- **One Chrome instance per profile dir — but MANY agents per instance.** CDP is multi-client (verified: two concurrent clients driving different tabs of one instance): agents and sessions attach to the SAME port, each drives its own tab, and all share the profile's logins. One agent per tab is the only rule. A second launch with the same dir just opens a window in the existing instance and **ignores the new debug port** — attach to the running one instead. Reach for a second profile + port (`…/b` on 9224) only for a different IDENTITY (a different account = a different cookie jar) or hard isolation.
|
|
28
|
+
- It runs **side-by-side with the daily Chrome** — a different `--user-data-dir` is a fully separate instance.
|
|
29
|
+
- **Quit by profile match, never by app name**: `pkill -f "chrome-profiles/agent"`. (`osascript 'tell app "Google Chrome" to quit'` hits the daily browser too — same app name.)
|
|
30
|
+
|
|
31
|
+
## Driving it
|
|
32
|
+
|
|
33
|
+
| Client | Good for | Port handoff |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| `chrome-devtools` MCP | rich interaction — click, fill, type, screenshots, network requests, console messages, performance traces | `CHROME_CDP_PORT` env var, **expanded ONCE when the Claude session spawns its MCP — set it BEFORE launching `claude`** (mid-session changes do nothing) |
|
|
36
|
+
| Any CDP client — including EM's `npx mgr cdp` run from any EM project | quick JS eval, per-renderer screenshots | per invocation: `EM_CDP_PORT=9223 npx mgr cdp eval ":4000" 'document.title'` |
|
|
37
|
+
|
|
38
|
+
Port conventions: **9222** = Electron apps (EM), **9223+** = Chrome instances.
|
|
39
|
+
|
|
40
|
+
## BEM specifics
|
|
41
|
+
|
|
42
|
+
- **The UJM dev site is HTTPS.** BrowserSync serves over HTTPS (self-signed cert). Prefer `https://localhost:4000`; fall back to the machine's local network IP (e.g. `https://192.168.x.x:4000`) if localhost doesn't connect. Port 4000 by default, increments to 4001+ when multiple sites run. The exact URL is in `.temp/_config_browsersync.yml` at the root of the WEBSITE project (the UJM consumer — e.g. `<brand>-website/.temp/_config_browsersync.yml`, NOT this backend repo) — read that file first, every time, before navigating.
|
|
43
|
+
- The network tab is the payoff: `list_network_requests` shows every call the frontend makes to your routes — method, status, and payloads — while you click through the real UI.
|
|
44
|
+
- Backend-side observation stays where it always was: `npx mgr logs` (gcloud logs) and the emulator suite; this doc only covers the browser half of the loop.
|
package/docs/cli-output.md
CHANGED
|
@@ -67,18 +67,20 @@ ui.rule(); // a bare 70-char rule string (
|
|
|
67
67
|
|
|
68
68
|
### `ui.Summary`
|
|
69
69
|
|
|
70
|
-
Collects pass/fail outcomes and prints an OMEGA-style summary block (green `✅`
|
|
70
|
+
Collects pass/warn/fail outcomes and prints an OMEGA-style summary block (green `✅`
|
|
71
71
|
when all passed, yellow `⚠` otherwise).
|
|
72
72
|
|
|
73
73
|
```js
|
|
74
74
|
const summary = new ui.Summary().start();
|
|
75
|
-
summary.pass();
|
|
75
|
+
summary.pass(); // record a pass
|
|
76
|
+
summary.warn('check name', detailsArr);// record a warning (non-blocking)
|
|
76
77
|
summary.fail('check name', detailsArr);// record a fail with pre-formatted detail lines
|
|
77
78
|
summary.print({ hint: 'Fix the above, then run npx mgr setup again.' });
|
|
78
79
|
```
|
|
79
80
|
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
Results line: `36 passed, 1 warned, 0 failed` (the warned segment only appears
|
|
82
|
+
when > 0). Warnings are listed before failures in the summary block — yellow `⚠`
|
|
83
|
+
lines with their detail arrays indented beneath.
|
|
82
84
|
|
|
83
85
|
## How `setup` uses it
|
|
84
86
|
|
|
@@ -94,12 +96,22 @@ from these helpers:
|
|
|
94
96
|
|
|
95
97
|
### Test runner (`Main.prototype.test` in `src/cli/index.js`)
|
|
96
98
|
|
|
97
|
-
Each setup check prints ` [N] <symbol> <name>`. A check can:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
Each setup check prints ` [N] <symbol> <name>`. A check's `run()` can return:
|
|
100
|
+
|
|
101
|
+
| Return value | Behavior |
|
|
102
|
+
|---|---|
|
|
103
|
+
| `true` | `✓` pass (recorded via `setupSummary.pass()`) |
|
|
104
|
+
| `false` | Attempt `fix()` → `✓ fixed` on success, `✗ Could not fix` + halt on throw |
|
|
105
|
+
| `Error` | `✗` hard halt, no fix attempted |
|
|
106
|
+
| `'warn'` | `⚠` non-blocking warning — reported in summary, does **not** halt |
|
|
107
|
+
|
|
108
|
+
**`'warn'` return type:** When `run()` returns `'warn'`, the runner prints the
|
|
109
|
+
check as `⚠`, calls `getWarning()` on the test instance for detail lines (array
|
|
110
|
+
of strings), and records it via `setupSummary.warn()`. Setup continues. The
|
|
111
|
+
summary shows `36 passed, 1 warned, 0 failed` with the warning details listed
|
|
112
|
+
at the bottom. Use this for environment prerequisites that don't block dev/deploy
|
|
113
|
+
(e.g. Java, optional CLIs). `BaseTest` provides a default `getWarning()` returning
|
|
114
|
+
`[]`; override it with your detail lines.
|
|
103
115
|
|
|
104
116
|
A failing check's `fix()` may attach `error.summaryDetails` (an array of styled
|
|
105
117
|
lines) to surface a compact version in the summary block — see
|
package/docs/mcp.md
CHANGED
|
@@ -1,41 +1,163 @@
|
|
|
1
1
|
# Model Context Protocol (MCP)
|
|
2
2
|
|
|
3
|
-
BEM includes a built-in MCP server that exposes BEM routes as tools for Claude Chat, Claude Code, and other MCP clients.
|
|
3
|
+
BEM includes a built-in MCP server that exposes BEM routes as tools for Claude Chat, Claude Code, Claude Desktop, and other MCP clients. The MCP layer is a thin wrapper over the existing BEM API — every tool maps to a route, and authentication goes through the same middleware pipeline.
|
|
4
4
|
|
|
5
5
|
## Architecture
|
|
6
6
|
|
|
7
7
|
Two transport modes:
|
|
8
8
|
- **Stdio** (local): `npx mgr mcp` — for Claude Code / Claude Desktop
|
|
9
|
-
- **Streamable HTTP** (remote): `POST /backend-manager/mcp` — for Claude Chat (stateless, Firebase Functions compatible)
|
|
10
|
-
|
|
11
|
-
##
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
|
16
|
-
|
|
17
|
-
| `
|
|
18
|
-
| `
|
|
19
|
-
| `
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
|
26
|
-
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
9
|
+
- **Streamable HTTP** (remote): `POST /backend-manager/mcp` — for Claude Chat / Claude Desktop custom connectors (stateless, Firebase Functions compatible)
|
|
10
|
+
|
|
11
|
+
## Roles
|
|
12
|
+
|
|
13
|
+
Every tool has a `role` that controls who can see and call it:
|
|
14
|
+
|
|
15
|
+
| Role | Who sees it | Tool count | Examples |
|
|
16
|
+
|------|-------------|------------|---------|
|
|
17
|
+
| `admin` | Admin key connections only | 22 | `firestore_read`, `send_email`, `cancel_subscription` |
|
|
18
|
+
| `user` | Authenticated users + admins | 2 | `get_user`, `get_subscription` |
|
|
19
|
+
| `public` | Everyone (after OAuth) | 1 | `health_check` |
|
|
20
|
+
|
|
21
|
+
Admin sees ALL tools. User sees `user` + `public`. Unauthenticated connections get a 401 that triggers the OAuth flow — there is no unauthenticated tool access. Defense-in-depth: even if someone calls an admin tool by name, the underlying BEM route still rejects.
|
|
22
|
+
|
|
23
|
+
## Available Tools (25)
|
|
24
|
+
|
|
25
|
+
| Tool | Role | Route | Description |
|
|
26
|
+
|------|------|-------|-------------|
|
|
27
|
+
| `firestore_read` | admin | `GET /admin/firestore` | Read a Firestore document by path |
|
|
28
|
+
| `firestore_write` | admin | `POST /admin/firestore` | Write/merge a Firestore document |
|
|
29
|
+
| `firestore_query` | admin | `POST /admin/firestore/query` | Query a collection with where/orderBy/limit |
|
|
30
|
+
| `send_email` | admin | `POST /admin/email` | Send transactional email via SendGrid |
|
|
31
|
+
| `send_notification` | admin | `POST /admin/notification` | Send push notification via FCM |
|
|
32
|
+
| `get_user` | user | `GET /user` | Get authenticated user info |
|
|
33
|
+
| `get_subscription` | user | `GET /user/subscription` | Get subscription info for a user |
|
|
34
|
+
| `sync_users` | admin | `POST /admin/users/sync` | Sync user data across systems |
|
|
35
|
+
| `list_campaigns` | admin | `GET /marketing/campaign` | List marketing campaigns |
|
|
36
|
+
| `create_campaign` | admin | `POST /marketing/campaign` | Create a marketing campaign |
|
|
37
|
+
| `get_stats` | admin | `GET /admin/stats` | Get system statistics |
|
|
38
|
+
| `cancel_subscription` | admin | `POST /payments/cancel` | Cancel subscription at period end |
|
|
39
|
+
| `refund_payment` | admin | `POST /payments/refund` | Process a refund |
|
|
40
|
+
| `get_payment_portal` | admin | `POST /payments/portal` | Generate Stripe billing portal link |
|
|
41
|
+
| `update_campaign` | admin | `PUT /marketing/campaign` | Update a pending campaign |
|
|
42
|
+
| `delete_campaign` | admin | `DELETE /marketing/campaign` | Delete a pending campaign |
|
|
43
|
+
| `create_contact` | admin | `POST /marketing/contact` | Add a marketing contact |
|
|
44
|
+
| `delete_contact` | admin | `DELETE /marketing/contact` | Remove a marketing contact |
|
|
45
|
+
| `run_cron` | admin | `POST /admin/cron` | Trigger a cron job by ID |
|
|
46
|
+
| `create_post` | admin | `POST /admin/post` | Create a blog post |
|
|
47
|
+
| `update_post` | admin | `PUT /admin/post` | Update an existing blog post |
|
|
48
|
+
| `create_backup` | admin | `POST /admin/backup` | Create a Firestore backup |
|
|
49
|
+
| `run_hook` | admin | `POST /admin/hook` | Execute a custom hook |
|
|
50
|
+
| `generate_uuid` | admin | `POST /general/uuid` | Generate a UUID |
|
|
51
|
+
| `health_check` | public | `GET /test/health` | Check server health |
|
|
52
|
+
|
|
53
|
+
## Tool Annotations
|
|
54
|
+
|
|
55
|
+
Every tool has MCP annotations that control how Claude Desktop categorizes and displays it:
|
|
56
|
+
|
|
57
|
+
| Field | Purpose |
|
|
58
|
+
|-------|---------|
|
|
59
|
+
| `title` | Human-readable display name (e.g. "Get authenticated user info" instead of `get_user`) |
|
|
60
|
+
| `readOnlyHint` | `true` → "Read-only tools" category in Claude Desktop |
|
|
61
|
+
| `destructiveHint` | `true` → marked as destructive (cancel, refund) |
|
|
62
|
+
| `idempotentHint` | `true` → safe to retry (firestore_write with merge) |
|
|
63
|
+
| `openWorldHint` | `true` → touches external systems (email, notifications) |
|
|
64
|
+
|
|
65
|
+
Consumer tools can set all the same annotations — they're passed through automatically.
|
|
34
66
|
|
|
35
67
|
## Authentication
|
|
36
68
|
|
|
37
|
-
|
|
38
|
-
|
|
69
|
+
### OAuth Flow (HTTP transport — Claude Desktop / Claude Chat)
|
|
70
|
+
|
|
71
|
+
1. Client sends `POST /backend-manager/mcp` with no auth → 401 with `WWW-Authenticate` header
|
|
72
|
+
2. Client discovers `/.well-known/oauth-protected-resource` → finds authorization server
|
|
73
|
+
3. Client discovers `/.well-known/oauth-authorization-server` → gets endpoints
|
|
74
|
+
4. Client registers via `POST /backend-manager/mcp/register` (RFC 7591 Dynamic Client Registration)
|
|
75
|
+
5. Client opens browser to `/backend-manager/mcp/authorize`
|
|
76
|
+
- If `client_id` matches admin key → auto-redirects (admin access)
|
|
77
|
+
- Otherwise → redirects to consumer's website (`/token?redirect_uri=...&state=...&mcp=true`)
|
|
78
|
+
6. User signs in on their familiar site, gets a Firebase ID token
|
|
79
|
+
7. Consumer's `/token` page redirects back with `code={idToken}&state={state}`
|
|
80
|
+
8. Client exchanges code: `POST /backend-manager/mcp/token` → BEM verifies ID token, returns `api.privateKey` as `access_token`
|
|
81
|
+
9. Client uses the API key for all future MCP requests as `Authorization: Bearer {key}`
|
|
82
|
+
|
|
83
|
+
The consumer auth URL is resolved from `Manager.getWebsiteUrl()` (auto-resolves localhost in dev, production domain otherwise), or overridden via `mcp.authUrl` in `backend-manager-config.json`.
|
|
84
|
+
|
|
85
|
+
### Admin (Stdio)
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npx mgr mcp # Reads BACKEND_MANAGER_KEY from functions/.env — sees all 25 tools
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### User (Stdio)
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npx mgr mcp --token <api-key> # User-level — sees 3 tools (2 user + 1 public)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Consumer MCP Tools
|
|
98
|
+
|
|
99
|
+
Consumer projects expose custom MCP tools via a single `functions/mcp.js` file. Tools are automatically discovered and merged with the built-in tools.
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
// functions/mcp.js
|
|
103
|
+
module.exports = [
|
|
104
|
+
// Route delegation — points at an existing route (works on stdio + HTTP)
|
|
105
|
+
{
|
|
106
|
+
name: 'get_sponsorship',
|
|
107
|
+
description: 'Get sponsorship details by ID',
|
|
108
|
+
role: 'user',
|
|
109
|
+
method: 'GET',
|
|
110
|
+
path: 'sponsorship',
|
|
111
|
+
annotations: { title: 'Get sponsorship details', readOnlyHint: true },
|
|
112
|
+
inputSchema: {
|
|
113
|
+
type: 'object',
|
|
114
|
+
properties: {
|
|
115
|
+
id: { type: 'string', description: 'Sponsorship ID' },
|
|
116
|
+
},
|
|
117
|
+
required: ['id'],
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
// Handler mode — runs code directly (HTTP transport only)
|
|
122
|
+
{
|
|
123
|
+
name: 'newsletter_stats',
|
|
124
|
+
description: 'Get newsletter stats for the past N days',
|
|
125
|
+
role: 'admin',
|
|
126
|
+
annotations: { title: 'Get newsletter stats', readOnlyHint: true },
|
|
127
|
+
inputSchema: {
|
|
128
|
+
type: 'object',
|
|
129
|
+
properties: {
|
|
130
|
+
days: { type: 'number', description: 'Days to look back', default: 30 },
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
handler: async ({ Manager, assistant, user, params, libraries }) => {
|
|
134
|
+
const cutoff = Date.now() - (params.days || 30) * 86400000;
|
|
135
|
+
const snapshot = await libraries.admin.firestore()
|
|
136
|
+
.collection('newsletters')
|
|
137
|
+
.where('metadata.created.timestampUNIX', '>=', Math.floor(cutoff / 1000))
|
|
138
|
+
.get();
|
|
139
|
+
return { total: snapshot.docs.length };
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
];
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Rules:**
|
|
146
|
+
- Consumer tools with the same name as a built-in tool override it
|
|
147
|
+
- Every tool needs `name`, `description`, and either `path` (route delegation) or `handler` (direct execution)
|
|
148
|
+
- `role` defaults to `admin` if not specified
|
|
149
|
+
- Handler-based tools only work on the HTTP transport (they return an error on stdio)
|
|
150
|
+
- Handler-based tools bypass BEM route middleware — they execute directly with the Manager context
|
|
151
|
+
- All MCP-standard fields are passed through: `annotations`, `outputSchema`, `inputSchema`
|
|
152
|
+
|
|
153
|
+
## HTTPS Local Development
|
|
154
|
+
|
|
155
|
+
`npx mgr serve` starts an HTTPS proxy on port 5002 (firebase serve runs internally on 5443). This enables Claude Desktop to connect locally since it requires HTTPS.
|
|
156
|
+
|
|
157
|
+
- Certificates are auto-generated via mkcert into `.temp/certs/`
|
|
158
|
+
- `getApiUrl()` returns `https://localhost:5002` when the HTTPS proxy is active
|
|
159
|
+
- Disable with `--no-https` to fall back to plain HTTP
|
|
160
|
+
- Install mkcert: `brew install mkcert && mkcert -install`
|
|
39
161
|
|
|
40
162
|
## Hosting Rewrites
|
|
41
163
|
|
|
@@ -48,11 +170,12 @@ The `npx mgr setup` command automatically adds required Firebase Hosting rewrite
|
|
|
48
170
|
}
|
|
49
171
|
```
|
|
50
172
|
|
|
51
|
-
##
|
|
173
|
+
## Claude Desktop Configuration
|
|
52
174
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
175
|
+
1. Go to Settings → Integrations → Add Custom Integration
|
|
176
|
+
2. **URL:** `https://api.yourdomain.com/backend-manager/mcp` (production) or `https://localhost:5002/backend-manager/mcp` (local dev with HTTPS proxy)
|
|
177
|
+
3. For admin access: set **OAuth Client ID** to your `BACKEND_MANAGER_KEY`
|
|
178
|
+
4. For user access: leave Client ID empty — the OAuth flow redirects to the consumer's website for sign-in
|
|
56
179
|
|
|
57
180
|
## Claude Code Configuration
|
|
58
181
|
|
|
@@ -70,26 +193,26 @@ Add to `.claude/settings.json`:
|
|
|
70
193
|
}
|
|
71
194
|
```
|
|
72
195
|
|
|
73
|
-
## Claude Chat Configuration
|
|
74
|
-
|
|
75
|
-
1. Go to Settings → Custom Connectors → Add
|
|
76
|
-
2. **URL:** `https://api.yourdomain.com/backend-manager/mcp`
|
|
77
|
-
3. **OAuth Client ID:** your `BACKEND_MANAGER_KEY` (enables auto-approve)
|
|
78
|
-
4. **OAuth Client Secret:** your `BACKEND_MANAGER_KEY`
|
|
79
|
-
|
|
80
196
|
## Key Files
|
|
81
197
|
|
|
82
198
|
| Purpose | File |
|
|
83
199
|
|---------|------|
|
|
84
|
-
| Tool definitions | `src/mcp/tools.js` |
|
|
85
|
-
|
|
|
200
|
+
| Tool definitions (roles + annotations) | `src/mcp/tools.js` |
|
|
201
|
+
| Shared utilities (auth, filtering, consumer loading) | `src/mcp/utils.js` |
|
|
202
|
+
| HTTP handler (OAuth + roles + consumer tools) | `src/mcp/handler.js` |
|
|
86
203
|
| Stdio server | `src/mcp/index.js` |
|
|
87
204
|
| HTTP client | `src/mcp/client.js` |
|
|
88
205
|
| CLI command | `src/cli/commands/mcp.js` |
|
|
206
|
+
| HTTPS proxy for local dev | `src/cli/commands/serve.js` |
|
|
89
207
|
| MCP route interception | `src/manager/index.js` (`_handleMcp`, `resolveMcpRoutePath`) |
|
|
90
208
|
| Hosting rewrites setup | `src/cli/commands/setup-tests/hosting-rewrites.js` |
|
|
91
209
|
|
|
92
210
|
## Adding New Tools
|
|
93
211
|
|
|
94
|
-
|
|
95
|
-
|
|
212
|
+
### Built-in tools (in BEM itself)
|
|
213
|
+
|
|
214
|
+
Add a tool definition to `src/mcp/tools.js` with `name`, `description`, `role`, `method`, `path`, `annotations`, and `inputSchema`. The tool automatically maps to the corresponding BEM route via the HTTP client.
|
|
215
|
+
|
|
216
|
+
### Consumer tools (in a consumer project)
|
|
217
|
+
|
|
218
|
+
Add an entry to `functions/mcp.js`. Use `path` + `method` for route delegation (works on both transports), or `handler` for direct execution (HTTP only). All MCP fields (`annotations`, `outputSchema`, etc.) are passed through automatically.
|
package/docs/test-framework.md
CHANGED
|
@@ -390,7 +390,7 @@ Security-rules tests use the `rules` client (`src/test/utils/firestore-rules-cli
|
|
|
390
390
|
|
|
391
391
|
## Test Account Isolation (CRITICAL)
|
|
392
392
|
|
|
393
|
-
**NEVER use shared accounts (`basic`, `admin`, `premium-active`, …) with the `test` processor or any operation that creates side-effect data** (orders, webhooks, subscriptions). The test processor auto-fires webhooks that upgrade a user's subscription asynchronously — using `basic` for a payment-intent test upgrades `basic` to a paid subscription and breaks every subsequent test that depends on `basic` being a basic user.
|
|
393
|
+
**NEVER use shared accounts (`basic`, `admin`, `premium-active`, …) with the `test` processor or any operation that creates side-effect data** (orders, webhooks, subscriptions, consent revocations). The test processor auto-fires webhooks that upgrade a user's subscription asynchronously — using `basic` for a payment-intent test upgrades `basic` to a paid subscription and breaks every subsequent test that depends on `basic` being a basic user.
|
|
394
394
|
|
|
395
395
|
**Rule: any test that creates persistent side-effect data MUST use a dedicated `journey-*` account.**
|
|
396
396
|
|
|
@@ -402,7 +402,7 @@ const response = await http.as('basic').post('payments/intent', { processor: 'te
|
|
|
402
402
|
const response = await http.as('journey-payments-intent-discount').post('payments/intent', { processor: 'test', ... });
|
|
403
403
|
```
|
|
404
404
|
|
|
405
|
-
**When to create a journey account:** the test uses `processor: 'test'`, creates docs in `payments-orders` / `payments-intents` / `payments-webhooks`, modifies subscription state,
|
|
405
|
+
**When to create a journey account:** the test uses `processor: 'test'`, creates docs in `payments-orders` / `payments-intents` / `payments-webhooks`, modifies subscription state, sends webhooks that trigger Firestore onWrite handlers, or **writes `consent.marketing` (grant/revoke)** — e.g. marketing webhook revoke events, or `DELETE /marketing/contact` (which mirrors `revoked` to the user doc). Revoked consent persists for the rest of the run and trips the email library's consent gate (`{ blocked: 'consent' }`) on every later `sync()`/`add()` of that account. Existing examples: `journey-webhook-revoke` (webhook revoke events), `journey-marketing-sync` (extended-mode live-provider sync + cleanup; `_test.allow_*` prefix). Add new ones to `src/test/test-accounts.js` (framework tests) or your project's `test/_init.js` `accounts` array (consumer tests).
|
|
406
406
|
|
|
407
407
|
**Shared accounts are safe for:** validation-only tests (missing fields, invalid input, auth rejection, unknown processor), read-only operations, and tests with no async side effects.
|
|
408
408
|
|