backend-manager 5.6.4 → 5.7.1

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/CLAUDE.md +4 -3
  3. package/PROGRESS.md +41 -0
  4. package/docs/ai-library.md +62 -11
  5. package/docs/cdp-debugging.md +44 -0
  6. package/docs/cli-output.md +22 -10
  7. package/docs/mcp.md +166 -43
  8. package/package.json +1 -1
  9. package/plans/mcp2.md +247 -0
  10. package/src/cli/commands/mcp.js +8 -2
  11. package/src/cli/commands/serve.js +155 -29
  12. package/src/cli/commands/setup-tests/base-test.js +8 -0
  13. package/src/cli/commands/setup-tests/firebase-auth.js +26 -0
  14. package/src/cli/commands/setup-tests/firebase-cli.js +9 -13
  15. package/src/cli/commands/setup-tests/hosting-rewrites.js +1 -1
  16. package/src/cli/commands/setup-tests/index.js +4 -0
  17. package/src/cli/commands/setup-tests/java-installed.js +26 -0
  18. package/src/cli/commands/setup.js +2 -1
  19. package/src/cli/commands/test.js +8 -0
  20. package/src/cli/index.js +14 -0
  21. package/src/cli/utils/ui.js +27 -5
  22. package/src/manager/index.js +9 -4
  23. package/src/manager/libraries/ai/index.js +45 -1
  24. package/src/manager/libraries/ai/providers/anthropic-format.js +234 -0
  25. package/src/manager/libraries/ai/providers/anthropic.js +28 -49
  26. package/src/manager/libraries/ai/providers/claude-code.js +21 -47
  27. package/src/manager/libraries/ai/providers/openai.js +154 -19
  28. package/src/manager/libraries/ai/providers/test.js +242 -0
  29. package/src/manager/libraries/email/data/disposable-domains.json +465 -0
  30. package/src/manager/libraries/email/generators/newsletter.js +3 -3
  31. package/src/mcp/client.js +48 -13
  32. package/src/mcp/handler.js +222 -69
  33. package/src/mcp/index.js +48 -18
  34. package/src/mcp/tools.js +150 -0
  35. package/src/mcp/utils.js +108 -0
  36. package/src/test/fixtures/firebase-project/firebase.json +1 -1
  37. package/test/ai/tools-live.js +170 -0
  38. package/test/helpers/ai-test-provider.js +202 -0
  39. package/test/helpers/ai-tools-format.js +350 -0
  40. package/test/mcp/discovery.js +53 -0
  41. package/test/mcp/oauth.js +161 -0
  42. package/test/mcp/protocol.js +268 -0
  43. package/test/mcp/roles.js +168 -0
  44. package/test/mcp/utils.js +245 -0
  45. package/.claude/settings.local.json +0 -12
package/CHANGELOG.md CHANGED
@@ -14,6 +14,47 @@ 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.1] - 2026-06-17
18
+
19
+ ### Added
20
+ - **MCP shorthand URL.** MCP endpoint now accessible at `/mcp` in addition to `/backend-manager/mcp`. Hosting rewrites updated; consumer projects pick up on next `npx mgr setup`.
21
+ - **`/register` hosting rewrite.** Dynamic client registration endpoint now included in the default hosting rewrite pattern.
22
+
23
+ # [5.7.0] - 2026-06-17
24
+
25
+ ### Added
26
+ - **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.
27
+ - **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.
28
+ - **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.
29
+ - **MCP tool annotations.** `title`, `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint` on all tools. Claude Desktop shows read/write categorization and human-readable titles.
30
+ - **6 new MCP tools:** `update_post`, `update_campaign`, `delete_campaign`, `create_contact`, `delete_contact`, `get_payment_portal`.
31
+ - **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`.
32
+ - **MCP CLI `--token` flag.** `npx mgr mcp --token <api-key>` for user-level stdio connections.
33
+
34
+ ### Changed
35
+ - **MCP discovery endpoints** use root-level issuer per RFC 8414 (was path-scoped, broke Claude Desktop's discovery chain).
36
+ - **`getApiUrl()`** returns `https://localhost:<port>` when `BEM_HTTPS_PORT` is set (serve command sets this automatically).
37
+ - **`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).
38
+ - **`resolveConsumerAuthUrl()`** uses `Manager.getWebsiteUrl()` (auto-resolves localhost in dev) instead of `brand.url` (always production).
39
+
40
+ # [5.6.6] - 2026-06-15
41
+
42
+ ### Added
43
+ - **`'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()`.
44
+ - **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.
45
+ - **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.
46
+
47
+ ### Changed
48
+ - **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.
49
+
50
+ # [5.6.5] - 2026-06-14
51
+
52
+ ### Added
53
+ - **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).
54
+ - **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`.
55
+ - **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).
56
+ - **`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.
57
+
17
58
  # [5.6.4] - 2026-06-11
18
59
 
19
60
  ### Changed
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: 19 tools, stdio + HTTP transports, OAuth, Claude Chat/Code configuration
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,41 @@
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:** Fix newsletter generation ReferenceError (beehiivConfig → newsletterRoleConfig)
6
+ * **Current Phase:** Fix applied, pending deploy + Firestore sendAt reset
7
+ * **Priority:** High
8
+ * **Last Updated:** 2026-06-17 4:10 PM PDT
9
+ * **Notes:** v5.5.0 refactor missed renaming `beehiivConfig` at 3 sites in newsletter.js (lines 331, 336, 340). Fix applied to framework source. Consumer (somiibo-backend) needs deploy + Firestore `_recurring-newsletter.sendAt` advanced to 1782322200 (Jun 24 17:30 UTC). Separate issue: Beehiiv send API requires Enterprise plan — all generated newsletters fail at send step.
10
+
11
+ ## 📌 Active Task List
12
+ * [ ] Phase 3: Newsletter generation fix (beehiivConfig ReferenceError)
13
+ * [x] Diagnose: traced prod logs + Firestore to find generation crashes after AI completes
14
+ * [x] Root cause: v5.5.0 missed renaming `beehiivConfig` → `newsletterRoleConfig` at lines 331/336/340
15
+ * [x] Fix applied to framework source (`src/manager/libraries/email/generators/newsletter.js`)
16
+ * [ ] Commit + publish framework fix
17
+ * [ ] Deploy consumer (somiibo-backend): `cd functions && npx mgr deploy`
18
+ * [ ] Advance stuck sendAt: `npx mgr firestore:set marketing-campaigns/_recurring-newsletter --merge --data '{"sendAt": 1782322200}'`
19
+
20
+ ## ✅ Completed Task List
21
+ * [x] Phase 1: MCP role-based tool scoping + consumer extensibility
22
+ * [x] Foundation utilities (`src/mcp/utils.js`)
23
+ * [x] Add `role` to all 19 tools (`src/mcp/tools.js`)
24
+ * [x] User token support in HTTP client (`src/mcp/client.js`)
25
+ * [x] Stdio server role filtering + consumer tools (`src/mcp/index.js`)
26
+ * [x] CLI `--token` flag + `cwd` passthrough (`src/cli/commands/mcp.js`)
27
+ * [x] HTTP handler — role filtering, OAuth user flow, consumer tool execution (`src/mcp/handler.js`)
28
+ * [x] Test suite — 44 tests across 5 files
29
+ * [x] Documentation — `docs/mcp.md`, `CLAUDE.md`
30
+ * [x] UJM `/token` page update (separate repo)
31
+ * [x] Phase 2: HTTPS local dev + Claude Desktop MCP testing
32
+ * [x] HTTPS proxy in `npx mgr serve` (mkcert certs, port 5002 → 5443)
33
+ * [x] `getApiUrl()` returns `https://` when `BEM_HTTPS_PORT` is set
34
+ * [x] Fix OAuth discovery (root-level issuer per RFC 8414)
35
+ * [x] Add dynamic client registration (`POST /mcp/register`)
36
+ * [x] Fix 401 trigger for OAuth flow
37
+ * [x] Tool annotations (title, readOnlyHint, destructiveHint, etc.)
38
+ * [x] Role reassignment (cancel/refund/uuid → admin, user = read-only)
39
+ * [x] Consumer tool override bug fix (listing/execution sync)
40
+ * [x] Test MCP connection in Claude Desktop — verified end-to-end
41
+ * [x] Update tests (44 passing) + docs
@@ -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 / web search (OpenAI)
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 passed to the OpenAI Responses API verbatim. Built-in hosted tools (e.g. `{ type: 'web_search' }`, `{ type: 'code_interpreter' }`) OR custom function tools (`{ type: 'function', name, parameters }`).
44
- - `tools.choice` *(optional)* — maps to `tool_choice` (`'auto'` | `'required'` | `'none'`, or a specific tool). Omit to let OpenAI default to `auto`.
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
- The most common use is OpenAI's built-in **web search** so the model finds and cites real, currently-live URLs instead of hallucinating them:
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
- When tools are active, the response `output` array may contain tool-call items (e.g. `web_search_call`) alongside the `message`; the message-text extractor ignores non-message items, so `r.content` is unaffected. URL citations live in the returned `output` (message content) as `annotations` of type `url_citation`.
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 (original `openai.js` content) |
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.
@@ -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(); // record a 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
- `fail()`'s second arg is an array of already-styled lines shown indented under the
81
- failing check in the summary block.
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
- - **pass** → `✓` (recorded via `setupSummary.pass()`).
99
- - **fail then auto-fix** `⚠ … — fixing…` then `✓ fixed`.
100
- - **fail unfixably** → `✗ Could not fix: <message>`, recorded via
101
- `setupSummary.fail(name, details)`, then `haltSetup()` prints the summary and
102
- `process.exit(1)`.
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
- ## Available Tools (19)
12
-
13
- | Tool | Route | Description |
14
- |------|-------|-------------|
15
- | `firestore_read` | `GET /admin/firestore` | Read a Firestore document by path |
16
- | `firestore_write` | `POST /admin/firestore` | Write/merge a Firestore document |
17
- | `firestore_query` | `POST /admin/firestore/query` | Query a collection with where/orderBy/limit |
18
- | `send_email` | `POST /admin/email` | Send transactional email via SendGrid |
19
- | `send_notification` | `POST /admin/notification` | Send push notification via FCM |
20
- | `get_user` | `GET /user` | Get authenticated user info |
21
- | `get_subscription` | `GET /user/subscription` | Get subscription info for a user |
22
- | `sync_users` | `POST /admin/users/sync` | Sync user data across systems |
23
- | `list_campaigns` | `GET /marketing/campaign` | List marketing campaigns |
24
- | `create_campaign` | `POST /marketing/campaign` | Create a marketing campaign |
25
- | `get_stats` | `GET /admin/stats` | Get system statistics |
26
- | `cancel_subscription` | `POST /payments/cancel` | Cancel subscription at period end |
27
- | `refund_payment` | `POST /payments/refund` | Process a refund |
28
- | `run_cron` | `POST /admin/cron` | Trigger a cron job by ID |
29
- | `create_post` | `POST /admin/post` | Create a blog post |
30
- | `create_backup` | `POST /admin/backup` | Create a Firestore backup |
31
- | `run_hook` | `POST /admin/hook` | Execute a custom hook |
32
- | `generate_uuid` | `POST /general/uuid` | Generate a UUID |
33
- | `health_check` | `GET /test/health` | Check server health |
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
- - **Stdio (local):** Reads `BACKEND_MANAGER_KEY` from `functions/.env` automatically
38
- - **HTTP (remote):** OAuth 2.1 Authorization Code flow with PKCE. Claude Chat handles the flow — user pastes BEM key once on the authorize page. If `OAuth Client ID` is set to the BEM key in the connector config, the authorize step auto-approves.
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
- ## CLI Usage
173
+ ## Claude Desktop Configuration
52
174
 
53
- ```bash
54
- npx mgr mcp # Start stdio MCP server (for Claude Code)
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
- | HTTP handler (stateless + OAuth) | `src/mcp/handler.js` |
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
- 1. Add the tool definition to `src/mcp/tools.js` with `name`, `description`, `method`, `path`, and `inputSchema`
95
- 2. The tool automatically maps to the corresponding BEM route via the HTTP client — no handler code needed
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backend-manager",
3
- "version": "5.6.4",
3
+ "version": "5.7.1",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {