backend-manager 5.9.4 → 5.9.6

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 (251) hide show
  1. package/CLAUDE.md +6 -0
  2. package/package.json +8 -1
  3. package/src/cli/commands/install.js +4 -3
  4. package/src/cli/commands/setup-tests/backend-manager.js +2 -1
  5. package/src/cli/commands/setup-tests/firebase-admin.js +2 -1
  6. package/src/cli/commands/setup-tests/firebase-functions.js +2 -1
  7. package/src/cli/utils/safe-install.js +13 -0
  8. package/src/manager/routes/payments/cancel/post.js +22 -2
  9. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/metadata.json +14 -0
  10. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/newsletter.html +486 -0
  11. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/newsletter.md +41 -0
  12. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/newsletter.mjml +97 -0
  13. package/{test/email/fixtures/clean.json → src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/structure.json} +16 -4
  14. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/summary.md +1 -0
  15. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/metadata.json +14 -0
  16. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/newsletter.html +486 -0
  17. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/newsletter.md +41 -0
  18. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/newsletter.mjml +97 -0
  19. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/structure.json +42 -0
  20. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/summary.md +1 -0
  21. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/metadata.json +14 -0
  22. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/newsletter.html +486 -0
  23. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/newsletter.md +41 -0
  24. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/newsletter.mjml +97 -0
  25. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/structure.json +42 -0
  26. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/summary.md +1 -0
  27. package/src/test/fixtures/firebase-project/.temp/test-mode.json +6 -0
  28. package/src/test/fixtures/firebase-project/database-debug.log +12 -0
  29. package/src/test/fixtures/firebase-project/firestore-debug.log +136 -0
  30. package/src/test/fixtures/firebase-project/pubsub-debug.log +17 -0
  31. package/src/test/test-accounts.js +9 -0
  32. package/.codeclimate.yml +0 -32
  33. package/.nvmrc +0 -1
  34. package/CHANGELOG.md +0 -1432
  35. package/PROGRESS.md +0 -86
  36. package/TODO-2.md +0 -121
  37. package/TODO-CHARGEBLAST.md +0 -32
  38. package/TODO-WEBHOOK-KEY-LEGACY-REMOVAL.md +0 -15
  39. package/TODO-WEBHOOK-KEY-UPGRADE.md +0 -138
  40. package/TODO-email-auth.md +0 -14
  41. package/TODO.md +0 -187
  42. package/plans/mcp2.md +0 -247
  43. package/scripts/test-helper-providers.js +0 -162
  44. package/scripts/update-disposable-domains.js +0 -44
  45. package/templates/_.env +0 -42
  46. package/templates/_.gitignore +0 -60
  47. package/templates/backend-manager-config.json +0 -249
  48. package/templates/database.rules.json +0 -83
  49. package/templates/firebase.json +0 -66
  50. package/templates/firestore.indexes.json +0 -4
  51. package/templates/firestore.rules +0 -112
  52. package/templates/public/404.html +0 -26
  53. package/templates/public/index.html +0 -24
  54. package/templates/remoteconfig.template.json +0 -1
  55. package/templates/storage-lifecycle-config-1-day.json +0 -9
  56. package/templates/storage-lifecycle-config-30-days.json +0 -9
  57. package/templates/storage.rules +0 -8
  58. package/test/_init/accounts-validation.js +0 -58
  59. package/test/_legacy/ai/index.js +0 -231
  60. package/test/_legacy/ai/test.jpg +0 -0
  61. package/test/_legacy/ai/test.pdf +0 -0
  62. package/test/_legacy/index.js +0 -31
  63. package/test/_legacy/payment-resolver/chargebee/orders/refunded.json +0 -0
  64. package/test/_legacy/payment-resolver/chargebee/orders/unpaid.json +0 -98
  65. package/test/_legacy/payment-resolver/chargebee/subscriptions/active.json +0 -578
  66. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-in-trial.json +0 -38
  67. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-skipped-to-active.json +0 -135
  68. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-active-to-cancelled.json +0 -42
  69. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-active.json +0 -44
  70. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-cancelled.json +0 -39
  71. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-refund.json +0 -143
  72. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-suspended.json +0 -48
  73. package/test/_legacy/payment-resolver/coinbase/orders/regular.json +0 -204
  74. package/test/_legacy/payment-resolver/coinbase/subscriptions/cancelled.json +0 -204
  75. package/test/_legacy/payment-resolver/coinbase/subscriptions/paid-2.json +0 -125
  76. package/test/_legacy/payment-resolver/index.js +0 -1558
  77. package/test/_legacy/payment-resolver/paypal/orders/refunded.json +0 -0
  78. package/test/_legacy/payment-resolver/paypal/orders/regular.json +0 -120
  79. package/test/_legacy/payment-resolver/paypal/subscriptions/active-refund-previous-stmnt.json +0 -323
  80. package/test/_legacy/payment-resolver/paypal/subscriptions/active.json +0 -192
  81. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-in-trial.json +0 -125
  82. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-payment-not-complete.json +0 -76
  83. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-payment-overdue-2.json +0 -114
  84. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-payment-overdue.json +0 -114
  85. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-active-to-cancelled.json +0 -111
  86. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-active.json +0 -132
  87. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-cancelled.json +0 -107
  88. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-expired.json +0 -91
  89. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-refund.json +0 -147
  90. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-suspended.json +0 -120
  91. package/test/_legacy/payment-resolver/stripe/orders/refunded.json +0 -0
  92. package/test/_legacy/payment-resolver/stripe/orders/regular.json +0 -91
  93. package/test/_legacy/payment-resolver/stripe/subscriptions/active.json +0 -421
  94. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-in-trial.json +0 -349
  95. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-active-failed-authorization.json +0 -430
  96. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-active.json +0 -319
  97. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-cancelled.json +0 -319
  98. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-refund.json +0 -414
  99. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-suspended.json +0 -319
  100. package/test/_legacy/payment-resolver/stripe/subscriptions/unsure.json +0 -339
  101. package/test/_legacy/test-new +0 -1178
  102. package/test/_legacy/test.md +0 -89
  103. package/test/_legacy/usage.js +0 -175
  104. package/test/_legacy/user.js +0 -31
  105. package/test/ai/tools-live.js +0 -170
  106. package/test/boot/emulator-boots.js +0 -37
  107. package/test/content/blog-generate.js +0 -160
  108. package/test/email/campaign-send.js +0 -45
  109. package/test/email/consent-lifecycle.js +0 -257
  110. package/test/email/feedback-and-plain-send.js +0 -52
  111. package/test/email/fixtures/editorial.json +0 -30
  112. package/test/email/fixtures/field-report.json +0 -53
  113. package/test/email/marketing-lifecycle.js +0 -132
  114. package/test/email/newsletter-generate.js +0 -819
  115. package/test/email/newsletter-templates.js +0 -491
  116. package/test/email/templates.js +0 -207
  117. package/test/email/transactional-send.js +0 -34
  118. package/test/email/transactional.js +0 -560
  119. package/test/email/validation.js +0 -710
  120. package/test/events/auth-delete-race.js +0 -201
  121. package/test/events/payments/journey-payments-cancel-endpoint.js +0 -100
  122. package/test/events/payments/journey-payments-cancel.js +0 -182
  123. package/test/events/payments/journey-payments-discount.js +0 -89
  124. package/test/events/payments/journey-payments-failure.js +0 -146
  125. package/test/events/payments/journey-payments-legacy-product.js +0 -149
  126. package/test/events/payments/journey-payments-one-time-failure.js +0 -111
  127. package/test/events/payments/journey-payments-one-time.js +0 -136
  128. package/test/events/payments/journey-payments-plan-change.js +0 -144
  129. package/test/events/payments/journey-payments-refund-webhook.js +0 -180
  130. package/test/events/payments/journey-payments-suspend.js +0 -181
  131. package/test/events/payments/journey-payments-trial-cancel.js +0 -130
  132. package/test/events/payments/journey-payments-trial.js +0 -171
  133. package/test/events/payments/journey-payments-uid-resolution.js +0 -132
  134. package/test/events/payments/journey-payments-upgrade.js +0 -135
  135. package/test/fixtures/chargebee/invoice-one-time.json +0 -27
  136. package/test/fixtures/chargebee/subscription-active.json +0 -44
  137. package/test/fixtures/chargebee/subscription-cancelled.json +0 -42
  138. package/test/fixtures/chargebee/subscription-in-trial.json +0 -41
  139. package/test/fixtures/chargebee/subscription-legacy-plan.json +0 -41
  140. package/test/fixtures/chargebee/subscription-non-renewing.json +0 -41
  141. package/test/fixtures/chargebee/subscription-paused.json +0 -42
  142. package/test/fixtures/chargebee/webhook-payment-failed.json +0 -51
  143. package/test/fixtures/chargebee/webhook-subscription-created.json +0 -47
  144. package/test/fixtures/paypal/order-approved.json +0 -62
  145. package/test/fixtures/paypal/order-completed.json +0 -110
  146. package/test/fixtures/paypal/subscription-active.json +0 -76
  147. package/test/fixtures/paypal/subscription-cancelled.json +0 -50
  148. package/test/fixtures/paypal/subscription-suspended.json +0 -65
  149. package/test/fixtures/stripe/checkout-session-completed.json +0 -130
  150. package/test/fixtures/stripe/invoice-payment-failed.json +0 -148
  151. package/test/fixtures/stripe/invoice-subscription-payment-failed.json +0 -28
  152. package/test/fixtures/stripe/subscription-active.json +0 -161
  153. package/test/fixtures/stripe/subscription-canceled.json +0 -161
  154. package/test/fixtures/stripe/subscription-trialing.json +0 -161
  155. package/test/functions/admin/database-read.js +0 -134
  156. package/test/functions/admin/database-write.js +0 -159
  157. package/test/functions/admin/edit-post.js +0 -366
  158. package/test/functions/admin/firestore-query.js +0 -207
  159. package/test/functions/admin/firestore-read.js +0 -129
  160. package/test/functions/admin/firestore-write.js +0 -105
  161. package/test/functions/admin/get-stats.js +0 -115
  162. package/test/functions/admin/send-email.js +0 -119
  163. package/test/functions/admin/send-notification.js +0 -208
  164. package/test/functions/admin/write-repo-content.js +0 -223
  165. package/test/functions/general/add-marketing-contact.js +0 -335
  166. package/test/functions/general/fetch-post.js +0 -54
  167. package/test/functions/general/generate-uuid.js +0 -114
  168. package/test/functions/test/authenticate.js +0 -64
  169. package/test/functions/user/create-custom-token.js +0 -73
  170. package/test/functions/user/delete.js +0 -135
  171. package/test/functions/user/get-active-sessions.js +0 -111
  172. package/test/functions/user/get-subscription-info.js +0 -99
  173. package/test/functions/user/regenerate-api-keys.js +0 -156
  174. package/test/functions/user/resolve.js +0 -52
  175. package/test/functions/user/sign-out-all-sessions.js +0 -94
  176. package/test/functions/user/sign-up.js +0 -252
  177. package/test/functions/user/submit-feedback.js +0 -104
  178. package/test/functions/user/validate-settings.js +0 -82
  179. package/test/helpers/ai-request-payload.js +0 -300
  180. package/test/helpers/ai-test-provider.js +0 -202
  181. package/test/helpers/ai-tools-format.js +0 -383
  182. package/test/helpers/content/blog-auto-publisher.js +0 -484
  183. package/test/helpers/content/feed-parser.js +0 -528
  184. package/test/helpers/content/ghostii-blocks.js +0 -134
  185. package/test/helpers/content/ghostii-feed-integration.js +0 -404
  186. package/test/helpers/content/ghostii-write-article.js +0 -243
  187. package/test/helpers/environment.js +0 -230
  188. package/test/helpers/infer-contact.js +0 -113
  189. package/test/helpers/merge-line-files.js +0 -271
  190. package/test/helpers/payment/chargebee/parse-webhook.js +0 -413
  191. package/test/helpers/payment/chargebee/to-unified-one-time.js +0 -147
  192. package/test/helpers/payment/chargebee/to-unified-subscription.js +0 -648
  193. package/test/helpers/payment/paypal/parse-webhook.js +0 -539
  194. package/test/helpers/payment/paypal/to-unified-one-time.js +0 -382
  195. package/test/helpers/payment/paypal/to-unified-subscription.js +0 -820
  196. package/test/helpers/payment/stripe/parse-webhook.js +0 -447
  197. package/test/helpers/payment/stripe/to-unified-one-time.js +0 -306
  198. package/test/helpers/payment/stripe/to-unified-subscription.js +0 -708
  199. package/test/helpers/sanitize.js +0 -222
  200. package/test/helpers/slugify.js +0 -394
  201. package/test/helpers/storage.js +0 -194
  202. package/test/helpers/user.js +0 -755
  203. package/test/helpers/webhook-forward.js +0 -418
  204. package/test/mcp/discovery.js +0 -53
  205. package/test/mcp/oauth.js +0 -161
  206. package/test/mcp/protocol.js +0 -268
  207. package/test/mcp/roles.js +0 -188
  208. package/test/mcp/utils.js +0 -245
  209. package/test/routes/admin/create-post.js +0 -364
  210. package/test/routes/admin/database.js +0 -131
  211. package/test/routes/admin/deduplicate-image-alts.js +0 -190
  212. package/test/routes/admin/email.js +0 -114
  213. package/test/routes/admin/firestore-query.js +0 -204
  214. package/test/routes/admin/firestore.js +0 -127
  215. package/test/routes/admin/infer-contact.js +0 -218
  216. package/test/routes/admin/notification.js +0 -198
  217. package/test/routes/admin/post-resize-image.js +0 -184
  218. package/test/routes/admin/post.js +0 -368
  219. package/test/routes/admin/repo-content.js +0 -223
  220. package/test/routes/admin/stats.js +0 -112
  221. package/test/routes/content/post.js +0 -54
  222. package/test/routes/general/uuid.js +0 -115
  223. package/test/routes/marketing/campaign.js +0 -125
  224. package/test/routes/marketing/contact.js +0 -370
  225. package/test/routes/marketing/email-preferences.js +0 -292
  226. package/test/routes/marketing/push-send.js +0 -32
  227. package/test/routes/marketing/webhook-forward.js +0 -61
  228. package/test/routes/marketing/webhook.js +0 -639
  229. package/test/routes/payments/cancel.js +0 -140
  230. package/test/routes/payments/discount.js +0 -80
  231. package/test/routes/payments/dispute-alert.js +0 -320
  232. package/test/routes/payments/intent.js +0 -336
  233. package/test/routes/payments/portal.js +0 -92
  234. package/test/routes/payments/refund.js +0 -179
  235. package/test/routes/payments/trial-eligibility.js +0 -71
  236. package/test/routes/payments/webhook.js +0 -107
  237. package/test/routes/test/authenticate.js +0 -59
  238. package/test/routes/test/schema.js +0 -554
  239. package/test/routes/test/usage.js +0 -342
  240. package/test/routes/user/api-keys.js +0 -156
  241. package/test/routes/user/delete.js +0 -136
  242. package/test/routes/user/feedback.js +0 -104
  243. package/test/routes/user/sessions.js +0 -199
  244. package/test/routes/user/settings-validate.js +0 -82
  245. package/test/routes/user/signup.js +0 -542
  246. package/test/routes/user/subscription.js +0 -99
  247. package/test/routes/user/token.js +0 -73
  248. package/test/routes/user/user.js +0 -157
  249. package/test/rules/notifications.js +0 -410
  250. package/test/rules/payments-carts.js +0 -371
  251. package/test/rules/user.js +0 -396
package/plans/mcp2.md DELETED
@@ -1,247 +0,0 @@
1
- # MCP Role-Based Tool Scoping + Consumer Extensibility
2
-
3
- ## Context
4
-
5
- The BEM MCP server currently works only for admins — a single `BACKEND_MANAGER_KEY` grants access to all 19 tools. Regular users can't connect at all, and consumer BEM projects have no way to add custom MCP tools. This plan adds:
6
-
7
- 1. **Role-based tool scoping** — tools tagged `admin`, `user`, or `public`; connections only see tools matching their role
8
- 2. **User authentication** — seamless OAuth sign-in flow + API key as long-lived credential
9
- 3. **Consumer MCP tools** — a single `functions/mcp.js` file in consumer projects
10
-
11
- ## Architecture
12
-
13
- ### Role Model
14
-
15
- Every tool gets a `role` field:
16
-
17
- | Role | Who sees it | Examples |
18
- |------|-------------|---------|
19
- | `admin` | Admin key connections only | firestore_read, send_email, run_cron |
20
- | `user` | Authenticated users + admins | get_user, get_subscription, cancel_subscription |
21
- | `public` | Everyone (including unauthenticated) | generate_uuid, health_check |
22
-
23
- Admin sees ALL tools. User sees `user` + `public`. Unauthenticated sees only `public`. Defense-in-depth — even if someone calls an admin tool by name, the route still rejects with 403.
24
-
25
- ### Auth: How Users Connect (OAuth + Consumer's Website)
26
-
27
- **The key insight:** users already have an API key (`api.privateKey`) on their Firestore account doc. It's long-lived, already works with `assistant.authenticate()`, and already encodes identity + access level. This is the credential.
28
-
29
- **The UX:** Claude Code/Desktop opens a browser → user signs in on their familiar site → redirected back to Claude. Done. No copy-pasting tokens.
30
-
31
- **The OAuth flow (step by step):**
32
-
33
- ```
34
- 1. Claude discovers endpoints:
35
- GET /.well-known/oauth-authorization-server
36
- → { authorization_endpoint, token_endpoint, ... }
37
-
38
- 2. Claude opens browser to BEM authorize endpoint:
39
- GET /backend-manager/mcp/authorize?redirect_uri=CLAUDE_CALLBACK&state=STATE
40
-
41
- 3. BEM authorize checks the request:
42
- - If client_id === BACKEND_MANAGER_KEY → auto-redirect (admin, unchanged)
43
- - Otherwise → redirect to consumer's /token page:
44
- https://app.example.com/token?redirect_uri=CLAUDE_CALLBACK&state=STATE
45
-
46
- 4. User lands on their familiar website sign-in page.
47
- Signs in with Google / email+password / whatever.
48
- After sign-in, page gets Firebase ID token via webManager.auth().getIdToken()
49
-
50
- 5. Consumer's /token page redirects to CLAUDE_CALLBACK:
51
- CLAUDE_CALLBACK?code=FIREBASE_ID_TOKEN&state=STATE
52
-
53
- 6. Claude exchanges code for access token:
54
- POST /backend-manager/mcp/token
55
- { code: FIREBASE_ID_TOKEN }
56
-
57
- 7. BEM token endpoint:
58
- - If code === BACKEND_MANAGER_KEY → return it as access_token (admin, unchanged)
59
- - Otherwise → verify Firebase ID token with admin.auth().verifyIdToken()
60
- → look up user doc → return user's api.privateKey as access_token
61
-
62
- 8. Claude uses the API key for all future MCP requests:
63
- Authorization: Bearer {api.privateKey}
64
- ```
65
-
66
- **Why API key, not JWT:**
67
- - Firebase ID tokens expire in 1 hour — MCP connection would break constantly
68
- - The API key never expires (until regenerated)
69
- - `assistant.authenticate()` already handles API key → user lookup
70
- - User can revoke by regenerating from account settings
71
-
72
- **Why redirect to consumer's website instead of embedding Firebase Auth:**
73
- - User sees their familiar sign-in page (branded, trusted)
74
- - No need to embed Firebase Auth SDK in BEM's authorize HTML
75
- - UJM already has a `/token` page — just needs a small update to support `redirect_uri` param
76
- - Works with any auth provider the consumer has configured
77
-
78
- ### Auth Classification (Fast, No DB Call)
79
-
80
- `resolveAuthInfo(token)` classifies the Bearer token for tool filtering:
81
-
82
- - Token matches `BACKEND_MANAGER_KEY` → `role: 'admin'`
83
- - Any other non-empty token → `role: 'user'` (API key from the OAuth flow)
84
- - No token → `role: 'public'`
85
-
86
- No DB call needed — actual validation happens at the route level when a tool is called.
87
-
88
- ### Consumer MCP Tools
89
-
90
- Consumer tools live in a **single file**: `functions/mcp.js`. Keeps things simple since most consumer MCP tools are just route delegations pointing at routes already defined in `functions/routes/`.
91
-
92
- ```js
93
- // functions/mcp.js
94
- module.exports = [
95
- // Route delegation — points at an existing route (works on stdio + HTTP)
96
- {
97
- name: 'get_sponsorship',
98
- description: 'Get sponsorship details by ID',
99
- role: 'user',
100
- method: 'GET',
101
- path: 'sponsorship',
102
- inputSchema: {
103
- type: 'object',
104
- properties: {
105
- id: { type: 'string', description: 'Sponsorship ID' },
106
- },
107
- required: ['id'],
108
- },
109
- },
110
-
111
- // Handler mode — runs code directly (HTTP transport only, has full Manager context)
112
- {
113
- name: 'newsletter_stats',
114
- description: 'Get newsletter stats for the past N days',
115
- role: 'admin',
116
- inputSchema: {
117
- type: 'object',
118
- properties: {
119
- days: { type: 'number', description: 'Days to look back', default: 30 },
120
- },
121
- },
122
- handler: async ({ Manager, assistant, user, params, libraries }) => {
123
- const cutoff = Date.now() - (params.days || 30) * 86400000;
124
- const snapshot = await libraries.admin.firestore()
125
- .collection('newsletters')
126
- .where('metadata.created.timestampUNIX', '>=', Math.floor(cutoff / 1000))
127
- .get();
128
- return { total: snapshot.docs.length };
129
- },
130
- },
131
- ];
132
- ```
133
-
134
- Consumer tools with the same name as a built-in tool override it (same precedence as consumer routes).
135
-
136
- ## File Changes
137
-
138
- ### 1. `src/mcp/tools.js` — Add `role` to every tool
139
-
140
- | Tool | Role |
141
- |------|------|
142
- | `firestore_read/write/query` | `admin` |
143
- | `send_email`, `send_notification` | `admin` |
144
- | `sync_users`, `list_campaigns`, `create_campaign` | `admin` |
145
- | `get_stats`, `run_cron`, `create_post`, `create_backup`, `run_hook` | `admin` |
146
- | `get_user`, `get_subscription` | `user` |
147
- | `cancel_subscription`, `refund_payment` | `user` |
148
- | `generate_uuid`, `health_check` | `public` |
149
-
150
- ### 2. NEW: `src/mcp/utils.js` — Shared utilities
151
-
152
- - **`resolveAuthInfo(token)`** — classifies token → `{ role, authType, token }`. Admin key check is instant; everything else = user.
153
- - **`filterToolsByRole(tools, role)`** — admin→all, user→user+public, public→public only.
154
- - **`loadConsumerTools(cwd)`** — checks for `${cwd}/mcp.js`, `require()`s it if it exists, validates shape, returns array.
155
- - **`buildToolMap(builtinTools, consumerTools)`** — merges into a Map; consumer tools override same-name built-ins.
156
-
157
- ### 3. `src/mcp/client.js` — Support user auth tokens
158
-
159
- Extend constructor to accept `{ baseUrl, backendManagerKey, userToken }`.
160
-
161
- In `call()`:
162
- - Has `backendManagerKey` → current behavior (key in query/body)
163
- - Has `userToken` (no backendManagerKey) → put token in `Authorization: Bearer` header + `authenticationToken` query param for GET
164
- - Neither → unauthenticated request
165
-
166
- ### 4. `src/mcp/index.js` — Stdio server with role filtering
167
-
168
- - Import utils, call `resolveAuthInfo()` to determine role
169
- - Call `loadConsumerTools(options.cwd)` if `cwd` is provided
170
- - Merge + filter tools by role in `ListToolsRequestSchema` handler
171
- - In `CallToolRequestSchema`: `path`-based tools via BEMClient. Handler-only tools return error explaining they require HTTP transport.
172
- - Accept new `options.token` for user connections
173
-
174
- ### 5. `src/mcp/handler.js` — HTTP handler with OAuth user flow
175
-
176
- **`handleAuthorize()`** — the big change:
177
- - If `client_id` matches admin key → auto-redirect (current behavior, unchanged)
178
- - Otherwise → **redirect to consumer's website** for sign-in:
179
- - Build consumer auth URL from `Manager.config.brand.url` (or a configurable `mcp.authUrl` in backend-manager-config.json)
180
- - Redirect to: `{consumerUrl}/token?redirect_uri={originalRedirectUri}&state={state}`
181
- - The consumer's `/token` page handles sign-in, gets Firebase ID token, redirects back to Claude's callback
182
-
183
- **`handleToken()`** — exchanges Firebase ID token for API key:
184
- - If code matches admin key → return as `access_token` (unchanged)
185
- - Otherwise → treat code as Firebase ID token:
186
- 1. `admin.auth().verifyIdToken(code)` to get UID
187
- 2. `admin.firestore().doc('users/{uid}').get()` to get user doc
188
- 3. Return `user.api.privateKey` as the `access_token`
189
- 4. If user has no API key, generate one and save it
190
-
191
- **`handleMcpProtocol()`** — role-based tool filtering:
192
- - Extract Bearer token, call `resolveAuthInfo()`
193
- - Allow public connections (no 401 for empty tokens — just show public tools)
194
- - Discover + merge consumer tools (cached at module scope)
195
- - Filter tools by role in `ListToolsRequestSchema`
196
- - In `CallToolRequestSchema`:
197
- - `path`-based tools: BEMClient HTTP call (current behavior)
198
- - `handler`-based tools: execute directly with `{ Manager, assistant, user, params, libraries }`
199
-
200
- **Rename `isValidKey()` → `isAdminKey()`** for clarity.
201
-
202
- ### 6. `src/cli/commands/mcp.js` — New CLI flags
203
-
204
- - `--token` (`-t`): user's API key (for user-level connections)
205
- - Pass `cwd: functionsDir` to `startServer()` for consumer tool discovery
206
- - When `--token` is provided, create BEMClient with userToken instead of backendManagerKey
207
-
208
- ### 7. Consumer's UJM `/token` page — Small update (separate repo)
209
-
210
- The UJM `/token` page needs to support the MCP OAuth redirect flow:
211
- - Detect `redirect_uri` and `state` query params
212
- - After sign-in, get Firebase ID token via `webManager.auth().getIdToken()`
213
- - Redirect to `redirect_uri?code={idToken}&state={state}`
214
-
215
- This is a small addition to the existing `/token` page layout. Files:
216
- - `ultimate-jekyll-manager/src/defaults/dist/_layouts/blueprint/auth/token.html`
217
- - `ultimate-jekyll-manager/src/defaults/dist/_layouts/themes/classy/frontend/pages/auth/token.html`
218
-
219
- ### 8. `docs/mcp.md` — Documentation
220
-
221
- - Add "Roles" section explaining admin/user/public scoping
222
- - Add "User Authentication" section with the OAuth flow diagram
223
- - Add "Consumer MCP Tools" section with `functions/mcp.js` format + examples
224
- - Add CLI examples for user connections
225
- - Add guide for configuring the consumer auth URL
226
-
227
- ## Implementation Order
228
-
229
- 1. `src/mcp/utils.js` (new) — foundation utilities
230
- 2. `src/mcp/tools.js` — add `role` to all 19 tools
231
- 3. `src/mcp/client.js` — user token support
232
- 4. `src/mcp/index.js` — stdio role filtering + consumer tools
233
- 5. `src/cli/commands/mcp.js` — new CLI flags
234
- 6. `src/mcp/handler.js` — HTTP role filtering + OAuth user flow + consumer tool handlers
235
- 7. UJM `/token` page update (separate repo)
236
- 8. `docs/mcp.md` — documentation
237
-
238
- ## Verification
239
-
240
- 1. **Existing admin flow**: `npx mgr mcp` with `BACKEND_MANAGER_KEY` → all 19 tools listed, all callable
241
- 2. **User flow (stdio)**: `npx mgr mcp --token <api-key>` → only user+public tools listed
242
- 3. **Public flow**: `npx mgr mcp` (no key, no token) → only public tools listed
243
- 4. **Consumer tools**: Create `functions/mcp.js` in a consumer project → tools appear in listing
244
- 5. **HTTP OAuth flow**: Connect from Claude Code/Desktop → redirected to consumer site → sign in → redirected back → user tools available
245
- 6. **Token exchange**: POST to `/mcp/token` with Firebase ID token → returns API key as access_token
246
- 7. **Role enforcement**: User calling an admin tool by name → unknown tool error (tool not in filtered list)
247
- 8. **Run `npx mgr test`** to verify no regressions
@@ -1,162 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Test Helper — direct provider lookups + removals via API
5
- *
6
- * Lets the live-test checklist verify SendGrid + Beehiiv state without
7
- * clicking around in dashboards. Reuses the same provider helpers that
8
- * BEM uses in production (findContact / removeContact).
9
- *
10
- * Usage (run from a consumer project's functions/ dir):
11
- *
12
- * node ../../../backend-manager/scripts/test-helper-providers.js find user@example.com
13
- * node ../../../backend-manager/scripts/test-helper-providers.js purge user@example.com
14
- *
15
- * Or symlink: `ln -s ../../../backend-manager/scripts/test-helper-providers.js ./prov`
16
- * then: `node ./prov find user@example.com`
17
- *
18
- * - find: prints whether the contact exists in SendGrid + Beehiiv (and the data shape).
19
- * - purge: removes the contact from BOTH providers (idempotent; safe if absent).
20
- *
21
- * The script picks up:
22
- * - SENDGRID_API_KEY, BEEHIIV_API_KEY from <cwd>/.env (functions/.env)
23
- * - marketing.campaigns.listId, marketing.newsletter.publicationId from <cwd>/backend-manager-config.json
24
- * - service-account from <cwd>/service-account.json
25
- *
26
- * Exit codes: 0 ok, 1 usage error, 2 provider error.
27
- */
28
-
29
- const path = require('path');
30
- const fs = require('fs');
31
-
32
- const cwd = process.cwd();
33
- const envPath = path.join(cwd, '.env');
34
- const configPath = path.join(cwd, 'backend-manager-config.json');
35
- const serviceAccountPath = path.join(cwd, 'service-account.json');
36
-
37
- // --- sanity checks ---
38
- if (!fs.existsSync(envPath)) {
39
- console.error(`✗ Missing ${envPath} — run from the consumer's functions/ dir`);
40
- process.exit(1);
41
- }
42
- if (!fs.existsSync(configPath)) {
43
- console.error(`✗ Missing ${configPath} — run from the consumer's functions/ dir`);
44
- process.exit(1);
45
- }
46
- if (!fs.existsSync(serviceAccountPath)) {
47
- console.error(`✗ Missing ${serviceAccountPath} — run from the consumer's functions/ dir`);
48
- process.exit(1);
49
- }
50
-
51
- // --- parse args ---
52
- const [, , cmd, email] = process.argv;
53
- const VALID_CMDS = ['find', 'purge'];
54
-
55
- if (!cmd || !VALID_CMDS.includes(cmd) || !email) {
56
- console.error('Usage:');
57
- console.error(' node test-helper-providers.js find <email>');
58
- console.error(' node test-helper-providers.js purge <email>');
59
- process.exit(1);
60
- }
61
-
62
- // --- bootstrap env (dotenv style, minimal) ---
63
- require('dotenv').config({ path: envPath });
64
-
65
- if (!process.env.SENDGRID_API_KEY) {
66
- console.error('✗ SENDGRID_API_KEY not set in .env');
67
- process.exit(1);
68
- }
69
- if (!process.env.BEEHIIV_API_KEY) {
70
- console.error('✗ BEEHIIV_API_KEY not set in .env');
71
- process.exit(1);
72
- }
73
-
74
- // --- bootstrap Manager so providers can read Manager.config.marketing.* ---
75
- // The providers require '../../../index.js' which is the Manager singleton.
76
- // We need to load BEM from the consumer's node_modules (not the BEM repo's own src)
77
- // so it picks up the consumer's config + service account.
78
- let Manager;
79
- try {
80
- const BackendManager = require(path.join(cwd, 'node_modules', 'backend-manager'));
81
- Manager = (new BackendManager()).init({}, { setupFunctionsLegacy: false, log: false });
82
- } catch (e) {
83
- console.error('✗ Failed to bootstrap Manager from consumer node_modules:', e.message);
84
- process.exit(2);
85
- }
86
-
87
- // --- load providers via the BEM Manager.libraries surface (preferred) or direct path ---
88
- const sendgridProviderPath = path.join(cwd, 'node_modules', 'backend-manager', 'src', 'manager', 'libraries', 'email', 'providers', 'sendgrid.js');
89
- const beehiivProviderPath = path.join(cwd, 'node_modules', 'backend-manager', 'src', 'manager', 'libraries', 'email', 'providers', 'beehiiv.js');
90
-
91
- const sendgrid = require(sendgridProviderPath);
92
- const beehiiv = require(beehiivProviderPath);
93
-
94
- // --- commands ---
95
- async function find(email) {
96
- console.log(`Looking up ${email} on both providers...`);
97
-
98
- const [sgContact, bhContact] = await Promise.all([
99
- sendgrid.findContact(email).catch(e => ({ _error: e.message })),
100
- beehiiv.findContact(email).catch(e => ({ _error: e.message })),
101
- ]);
102
-
103
- console.log('\n── SendGrid ──');
104
- if (sgContact?._error) {
105
- console.log(` ✗ error: ${sgContact._error}`);
106
- } else if (!sgContact) {
107
- console.log(' ⊘ not found');
108
- } else {
109
- console.log(` ✓ found — id=${sgContact.id || '?'}, email=${sgContact.email || '?'}`);
110
- }
111
-
112
- console.log('\n── Beehiiv ──');
113
- if (bhContact?._error) {
114
- console.log(` ✗ error: ${bhContact._error}`);
115
- } else if (!bhContact) {
116
- console.log(' ⊘ not found');
117
- } else {
118
- console.log(` ✓ found — id=${bhContact.id || '?'}, email=${bhContact.email || '?'}, status=${bhContact.status || '?'}`);
119
- }
120
-
121
- return { sgContact, bhContact };
122
- }
123
-
124
- async function purge(email) {
125
- console.log(`Removing ${email} from both providers...`);
126
-
127
- const [sgResult, bhResult] = await Promise.all([
128
- sendgrid.removeContact(email).catch(e => ({ _error: e.message })),
129
- beehiiv.removeContact(email).catch(e => ({ _error: e.message })),
130
- ]);
131
-
132
- console.log('\n── SendGrid ──');
133
- if (sgResult?._error) {
134
- console.log(` ✗ error: ${sgResult._error}`);
135
- } else {
136
- console.log(` ✓ ${JSON.stringify(sgResult)}`);
137
- }
138
-
139
- console.log('\n── Beehiiv ──');
140
- if (bhResult?._error) {
141
- console.log(` ✗ error: ${bhResult._error}`);
142
- } else {
143
- console.log(` ✓ ${JSON.stringify(bhResult)}`);
144
- }
145
-
146
- return { sgResult, bhResult };
147
- }
148
-
149
- // --- main ---
150
- (async () => {
151
- try {
152
- if (cmd === 'find') {
153
- await find(email);
154
- } else if (cmd === 'purge') {
155
- await purge(email);
156
- }
157
- process.exit(0);
158
- } catch (e) {
159
- console.error('✗ Unexpected error:', e);
160
- process.exit(2);
161
- }
162
- })();
@@ -1,44 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Fetches the latest disposable email domain list from GitHub and saves it as JSON.
5
- *
6
- * Source: https://github.com/disposable-email-domains/disposable-email-domains
7
- * (curated, ~5k domains, low false-positive rate)
8
- *
9
- * Run manually: node scripts/update-disposable-domains.js
10
- * Runs automatically on: npm prepublishOnly
11
- */
12
- const fs = require('fs');
13
- const path = require('path');
14
-
15
- const SOURCE_URL = 'https://raw.githubusercontent.com/disposable-email-domains/disposable-email-domains/main/disposable_email_blocklist.conf';
16
- const OUTPUT_PATH = path.join(__dirname, '..', 'src', 'manager', 'libraries', 'email', 'data', 'disposable-domains.json');
17
-
18
- async function main() {
19
- console.log('Fetching disposable domain list...');
20
-
21
- const response = await fetch(SOURCE_URL);
22
-
23
- if (!response.ok) {
24
- throw new Error(`Failed to fetch: ${response.status} ${response.statusText}`);
25
- }
26
-
27
- const text = await response.text();
28
- const domains = text
29
- .trim()
30
- .split('\n')
31
- .map(d => d.trim().toLowerCase())
32
- .filter(Boolean);
33
-
34
- const unique = [...new Set(domains)].sort();
35
-
36
- fs.writeFileSync(OUTPUT_PATH, JSON.stringify(unique, null, 2) + '\n');
37
-
38
- console.log(`Updated disposable-domains.json: ${unique.length} domains`);
39
- }
40
-
41
- main().catch((e) => {
42
- console.warn('Warning: Failed to update disposable domains:', e.message);
43
- console.warn('Using existing list. Run manually later: node scripts/update-disposable-domains.js');
44
- });
package/templates/_.env DELETED
@@ -1,42 +0,0 @@
1
- # ========== Default Values ==========
2
- # GitHub
3
- GH_TOKEN=""
4
-
5
- # Backend Manager
6
- BACKEND_MANAGER_KEY=""
7
- BACKEND_MANAGER_WEBHOOK_KEY=""
8
- BACKEND_MANAGER_NAMESPACE=""
9
- BACKEND_MANAGER_OPENAI_API_KEY=""
10
- BACKEND_MANAGER_ANTHROPIC_API_KEY=""
11
-
12
- # AI
13
- OPENAI_API_KEY=""
14
- ANTHROPIC_API_KEY=""
15
- CLAUDE_CODE_OAUTH_TOKEN=""
16
-
17
- # Payment Processors
18
- PAYPAL_CLIENT_SECRET=""
19
- STRIPE_SECRET_KEY=""
20
- CHARGEBEE_API_KEY=""
21
- COINBASE_API_KEY=""
22
-
23
- # Analytics
24
- GOOGLE_ANALYTICS_SECRET=""
25
- META_ACCESS_TOKEN=""
26
- TIKTOK_ACCESS_TOKEN=""
27
-
28
- # Security
29
- CLOUDFLARE_TOKEN=""
30
- RECAPTCHA_SECRET_KEY=""
31
-
32
- # Email & Newsletter
33
- SENDGRID_API_KEY=""
34
- BEEHIIV_API_KEY=""
35
- NEVERBOUNCE_API_KEY=""
36
- ZEROBOUNCE_API_KEY=""
37
- UNSUBSCRIBE_HMAC_KEY=""
38
- APOLLO_API_KEY=""
39
-
40
- # ========== Custom Values ==========
41
- # Add your custom environment variables below this line
42
- # ...
@@ -1,60 +0,0 @@
1
- # ========== Default Values ==========
2
- # macOS
3
- .DS_Store
4
-
5
- # Logs
6
- logs
7
- *.log
8
- npm-debug.log*
9
- yarn-debug.log*
10
- yarn-error.log*
11
- firebase-debug.log*
12
-
13
- # Firebase cache
14
- .firebase/
15
-
16
- # Firebase config
17
- .firebaserc
18
-
19
- # Runtime data
20
- pids
21
- *.pid
22
- *.seed
23
- *.pid.lock
24
-
25
- # Coverage directory
26
- coverage
27
- .nyc_output
28
-
29
- # Dependency directories
30
- node_modules/
31
-
32
- # Optional npm cache directory
33
- .npm
34
-
35
- # Optional eslint cache
36
- .eslintcache
37
-
38
- # Output of 'npm pack'
39
- *.tgz
40
-
41
- # Yarn Integrity file
42
- .yarn-integrity
43
-
44
- # dotenv environment variables file
45
- .env*
46
-
47
- # BEM specific
48
- .runtimeconfig.json
49
- service-account*.json
50
- bem-reload-trigger.js
51
- .temp/
52
- _legacy
53
-
54
- # Root proxy (generated by setup)
55
- /package.json
56
- /package-lock.json
57
-
58
- # ========== Custom Values ==========
59
- # Add your custom ignore patterns below this line
60
- # ...