@zackbart/connecta 0.18.0 → 0.18.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.
@@ -0,0 +1,279 @@
1
+ # RevenueCat prebuilt connection
2
+
3
+ Import `revenuecat()` independently from
4
+ `@zackbart/connecta/providers/revenuecat`. It wraps
5
+ [RevenueCat's hosted MCP server](https://www.revenuecat.com/docs/tools/mcp/setup)
6
+ with OAuth by default, project-scoping guidance that differs by credential
7
+ shape, a task-oriented usage guide, and a vetted safety classification. It adds
8
+ no provider dependency and is not reachable from Connecta's root entry.
9
+
10
+ ```ts
11
+ import { revenuecat } from "@zackbart/connecta/providers/revenuecat";
12
+
13
+ const subscriptions = revenuecat("revenuecat", {
14
+ purpose: "Subscription state, entitlements, and revenue across our projects",
15
+ instructions: "Never grant a promotional entitlement without a support ticket.",
16
+ });
17
+ ```
18
+
19
+ The endpoint is `https://mcp.revenuecat.ai/mcp` over streamable HTTP.
20
+
21
+ `purpose` is required, and it does more work here than in any other maintained
22
+ connection. RevenueCat's own tools do not report which project a static key
23
+ reaches until you call one, and Connecta runs no credential test at construction
24
+ (P10), so `purpose` is the only place the deployment's intent is written down.
25
+ It opens the guide and it *is* the guide summary, which is the field search
26
+ returns. Project `instructions` are appended to the maintained guide and cannot
27
+ change the connector's safety classification.
28
+
29
+ ## The scoping fact this connection exists to get right
30
+
31
+ RevenueCat has two credential shapes with two different scopes, and the guide
32
+ you get depends on which one you configured.
33
+
34
+ **A secret API key is project-wide.** RevenueCat's own words:
35
+ "Secret API keys are project-wide and can be created and revoked by project
36
+ Admins" ([authentication](https://www.revenuecat.com/docs/projects/authentication)).
37
+ `list-projects` "lists all RevenueCat projects accessible with the provided API
38
+ key" — with an `sk_` key that is exactly one project. So a `headers`-auth
39
+ connector reaches one project and nothing outside it. Its title is
40
+ `RevenueCat (single project)` and its guide opens by naming the project the
41
+ operator said the key is for.
42
+
43
+ **OAuth is account-scoped.** One session reaches every project the account can
44
+ see, and each project-scoped tool takes a `project_id`. Its title is
45
+ `RevenueCat` and its guide opens with the resolution discipline: call
46
+ `list-projects` first, carry the exact `project_id` it returned into every
47
+ project-scoped call, and stop and ask when more than one project fits.
48
+ Connecta does not pick a project, and the connector id, title, and purpose are
49
+ routing hints rather than proof of where a call will land.
50
+
51
+ The constructor deliberately has no `project` option. Declaring a project that
52
+ Connecta then checked against `list-projects` at construction would be a
53
+ credential test, which P10 forbids — a proxy makes no unasked-for downstream
54
+ call. The operator's stated purpose carries the claim; the agent confirms it
55
+ with `list-projects` on first use.
56
+
57
+ ## Several projects
58
+
59
+ One key, one project, one connector. A deployment that needs two projects
60
+ declares two connectors, each with its own key and its own id:
61
+
62
+ ```ts
63
+ import { revenuecat } from "@zackbart/connecta/providers/revenuecat";
64
+
65
+ connectors: [
66
+ revenuecat("bepresent_ios", {
67
+ purpose: "Subscription state for the BePresent iOS project",
68
+ auth: {
69
+ type: "headers",
70
+ headers: { Authorization: `Bearer ${env.REVENUECAT_BEPRESENT_KEY}` },
71
+ },
72
+ }),
73
+ revenuecat("biblescroll", {
74
+ purpose: "Subscription state for the BibleScroll project",
75
+ auth: {
76
+ type: "headers",
77
+ headers: { Authorization: `Bearer ${env.REVENUECAT_BIBLESCROLL_KEY}` },
78
+ },
79
+ }),
80
+ ]
81
+ ```
82
+
83
+ That is config-as-code doing what an account model would otherwise do: one
84
+ credential per connector, each with its own catalog, storage namespace, health,
85
+ and admission counters. The two share a title, because Connecta cannot know
86
+ which project a key opens — so the guide summary is what tells them apart, and
87
+ it is built from `purpose`. Write a purpose that names the project, not one
88
+ that names RevenueCat.
89
+
90
+ If the deployment genuinely needs to move between projects in one session, use
91
+ OAuth instead and let the agent resolve `project_id`. Do not point a
92
+ project-scoped key's `project_id` argument at a project it cannot reach; the
93
+ call fails at RevenueCat, which is the correct outcome but a wasted round trip.
94
+
95
+ ## Authentication
96
+
97
+ OAuth is the default and the option RevenueCat recommends: "OAuth provides a
98
+ seamless authentication experience: log in to your RevenueCat account and grant
99
+ access to the MCP server, with no API keys to manage." Each connector instance
100
+ keeps its own flow and tokens in connector-scoped storage.
101
+
102
+ RevenueCat also accepts an API v2 secret key as a bearer token for headless
103
+ agents:
104
+
105
+ ```ts
106
+ revenuecat("bepresent_ios", {
107
+ purpose: "Subscription state for the BePresent iOS project",
108
+ auth: {
109
+ type: "headers",
110
+ headers: { Authorization: `Bearer ${env.REVENUECAT_KEY}` },
111
+ },
112
+ });
113
+ ```
114
+
115
+ Keys are prefixed `sk_`, are issued read-only or write-enabled, and can be
116
+ revoked at any time by a project Admin. RevenueCat's setup guidance is to "use
117
+ a write-enabled key if you plan to create/modify resources"; "a read-only key
118
+ works if you only need to view data". Keep the key in the runtime's secret
119
+ store, never in the deployment file.
120
+
121
+ **Connecta does not filter writes for a read-only key.** It has no way to tell
122
+ which kind a key is without spending a call, so every write in the catalog is
123
+ offered, reaches RevenueCat, and fails there in RevenueCat's own words. The
124
+ guide says so, so an agent reads that refusal as "this key cannot write" rather
125
+ than as a bad argument and repairs it by routing to a write-enabled connector
126
+ instead of retrying.
127
+
128
+ An expired or revoked credential surfaces as `auth_required`, and the guide
129
+ names the `authorize_connector` recovery. A permission gap, a plan restriction,
130
+ or a rejected argument arrives as RevenueCat wrote it and is not an
131
+ authorization problem.
132
+
133
+ ## The ninety-five tools, and what they are classified as
134
+
135
+ RevenueCat's
136
+ [tool reference](https://www.revenuecat.com/docs/tools/mcp/tools-reference),
137
+ read on **2026-08-18**, documents ninety-five tools in a Read/Write table.
138
+ Ninety-four carry an access column and are classified here: **50 read-only, 15
139
+ additive writes, 29 destructive writes.**
140
+
141
+ Reads are every `Read` row, verbatim — the nine project and app reads, the four
142
+ product reads, the entitlement, offering, targeting, paywall, customer, virtual
143
+ currency, chart, webhook, and SDK reads, and `get-paywall-ai-task`.
144
+
145
+ Writes follow the verb where the verb is honest: `archive-*` and `unarchive-*`
146
+ flip an existing object's active state, `update-*`, `delete-*`, `publish-*`,
147
+ `unpublish-*`, and `detach-*` change or remove something that already exists,
148
+ and a plain `create-*` brings a new object into being beside the old ones.
149
+ `set-product-store-state` is an upsert and `submit-products-to-store` sends
150
+ products to Apple for review, so both are destructive.
151
+ `assign-customer-offering` and `grant-customer-entitlement` change a real
152
+ customer's access, so both are destructive too.
153
+
154
+ Nine verdicts are not decided by the verb, and each is argued in the source
155
+ beside the row:
156
+
157
+ | Tool | Verdict | Why |
158
+ | --- | --- | --- |
159
+ | `create-product-prices` | destructive | named `create-`, described "Configure prices for a product". The price set already exists and configuring it replaces what is there. Money-facing and overwriting |
160
+ | `equalize-subscription-prices` | additive | "Fills **missing** App Store subscription territory prices" — by RevenueCat's own word it writes only where nothing is set |
161
+ | `validate-app-credentials` | additive | RevenueCat files it `Write`, so it does not reach the read path, but it leaves the saved credentials alone and only records the outcome of a check |
162
+ | `upload-product-store-state-screenshot` | additive | "Reserves an App Store Connect review screenshot slot" — a new slot appears; nothing existing is replaced |
163
+ | `attach-products-to-entitlement` | additive | attach adds membership and removes nothing; `detach-products-from-entitlement` is the destructive half. Filing both destructive would make the pair read identically in the approval copy a human sees |
164
+ | `attach-products-to-package` | additive | the same argument one level down |
165
+ | `duplicate-paywall` | additive | "Duplicates an existing paywall's current draft" — the original is untouched |
166
+ | `create-paywall-ai` | additive | starts an async task that creates a paywall; every existing one is left alone |
167
+ | `edit-paywall-ai` | destructive | starts an async task that rewrites a draft that already exists |
168
+
169
+ `create-webhook-integration` deserves a sentence too. No existing integration
170
+ changes, so the verb reads additive — but with filters omitted the new one
171
+ "starts delivering" every customer event in the project to a URL the caller
172
+ typed. Customer data leaving the account is the `create_refund` argument again:
173
+ filed destructive on consequence, so the approval copy says what is at stake.
174
+
175
+ **`render-paywall-screenshot` is deliberately unclassified.** RevenueCat's
176
+ reference gives it no access column at all, so no release has reviewed what it
177
+ does. It fails closed onto `call_destructive_tool` rather than being guessed
178
+ into the read path because its name sounds harmless (P5). The guide names it,
179
+ so an agent does not read the approval prompt as a bug.
180
+
181
+ That classification fills in downstream silence and otherwise preserves explicit
182
+ annotations. A tool on the read allowlist arriving with `destructiveHint: true`
183
+ or `readOnlyHint: false` keeps exactly what the downstream said and stays behind
184
+ `call_destructive_tool`. A tool on neither maintained list arriving with
185
+ `readOnlyHint: true` keeps that too. Both are the downstream telling you this
186
+ release's allowlist is stale. The one fail-closed exception applies to a name
187
+ this release reviewed and filed destructive: a `grant-customer-entitlement`
188
+ claiming `readOnlyHint: true` is a downstream bug rather than news, and stays on
189
+ the approval path.
190
+
191
+ The tool list is not a fixed set, and the guide says so. RevenueCat gates parts
192
+ of its catalog by plan, platform, and beta enrollment — paywall AI editing,
193
+ benchmarks, experiments, virtual currencies, and the account-billing tools are
194
+ the usual absentees — so search this connector for what it actually exposes
195
+ rather than assuming a documented tool is here.
196
+
197
+ **No schema digests are recorded.** No release has read RevenueCat's live
198
+ schemas and written them down; that needs a live project and a maintainer's own
199
+ key. The manifest therefore ships names and verdicts only, and the drift check
200
+ honestly counts zero schema changes rather than reporting an invented one.
201
+ `npm run drift:check -- --record` reads them from a live catalog and prints the
202
+ block a release pastes in
203
+ ([#351](https://github.com/zackbart/connecta/issues/351)).
204
+
205
+ ## Rate limits
206
+
207
+ RevenueCat documents numbers, and this connection still declares no budget.
208
+
209
+ API v2 meters per minute and **per domain**
210
+ ([rate limits](https://www.revenuecat.com/docs/api-v2#tag/Rate-Limit), read
211
+ 2026-08-18):
212
+
213
+ | Domain | Requests per minute |
214
+ | --- | --- |
215
+ | Customer Information | 480 |
216
+ | Virtual Currencies | 480 |
217
+ | Subscription Transactions Refunds | 480 |
218
+ | Audiences | 60 |
219
+ | Project Configuration | 60 |
220
+ | Charts & Metrics | 25 |
221
+
222
+ A `ConnectorCallAdmissionPolicy` carries exactly one rule, so a connector-wide
223
+ budget has to pick one of those six numbers for all ninety-five tools.
224
+ Transcribing 25 would throttle a customer read loop to a nineteenth of its
225
+ documented allowance; transcribing 480 would leave a chart sweep unprotected.
226
+ Neither is the provider's limit, and both would look like RevenueCat being
227
+ flaky. The metering scope says the same thing again: the limit applies per API
228
+ key for app-level keys and **per developer** for developer-level keys, so an
229
+ OAuth session shares one budget with everything else that developer does, which
230
+ a per-runtime counter cannot approximate in either direction.
231
+
232
+ So the number stays with the operator who knows the account (P12), and the
233
+ guide states RevenueCat's own limits instead, along with the `429`,
234
+ `Retry-After`, and `backoff_ms` signals to back off on. Supply one like this:
235
+
236
+ ```ts
237
+ revenuecat("revenuecat", {
238
+ purpose: "Revenue charts and cohort reporting",
239
+ callAdmission: {
240
+ rules: [
241
+ {
242
+ maxConcurrency: 4,
243
+ queueTimeoutMs: 5_000,
244
+ retryAfterMs: 2_000,
245
+ // The Charts & Metrics ceiling, because this connector is used for
246
+ // charts. A customer-lookup connector would declare 480.
247
+ budget: { kind: "rolling-window", maxCalls: 25, windowMs: 60_000 },
248
+ },
249
+ ],
250
+ },
251
+ });
252
+ ```
253
+
254
+ As with every connector policy this is a **best-effort approximation** of the
255
+ provider's limit, not an enforcement of it. Each runtime keeps its own counter,
256
+ so N Worker isolates or Node processes serving one deployment can each admit up
257
+ to the stated rate. Discovery traffic is outside connector call admission and
258
+ still needs restrained use.
259
+
260
+ ## What is not verified
261
+
262
+ - **Every tool name is transcribed from RevenueCat's published reference on
263
+ 2026-08-18, not read from a live catalog.** No maintainer ran this against a
264
+ project with a real key before it shipped. A name the reference lists and the
265
+ server does not serve costs nothing; a tool the server serves and the
266
+ reference omits fails closed. The maintainer-run drift check with
267
+ `CONNECTA_DRIFT_REVENUECAT_KEY` set is what turns that into a finding with a
268
+ name attached.
269
+ - **No input or output schema has been read**, which is why the manifest
270
+ carries no digests.
271
+ - **Whether `render-paywall-screenshot` mutates anything.** It has no access
272
+ column, and guessing is exactly what P5 exists to prevent.
273
+
274
+ ## Conventions
275
+
276
+ This connection is audited against
277
+ [the provider conventions](./provider-conventions.md). Its verdict per
278
+ convention is the RevenueCat section of
279
+ [the provider audit](./provider-audit.md).
@@ -177,6 +177,16 @@ organization-account selection separate from the restricted-key-only Connect
177
177
  path, so an agent cannot repair uncertainty by fabricating `Stripe-Account` as
178
178
  a tool argument.
179
179
 
180
+ The guide also carries the reduction advice the generic schemas cannot (P7):
181
+ a list or search read that returns full objects belongs inside `execute_code`,
182
+ projected to the fields the question needs before `return`, because an
183
+ unprojected list truncates and a projected one keeps customer PII out of the
184
+ transcript. It names Stripe search's per-resource field set — charges search
185
+ has no `payment_intent` field, so the path is the PaymentIntent's
186
+ `latest_charge` — and the account → search → details → read sequence as one
187
+ program rather than four turns, and it names `outcome`, `failure_code`, and
188
+ `failure_message` on the charge as the answer to "why did this payment fail".
189
+
180
190
  Stripe publishes no stability or deprecation policy for this tool set and
181
191
  invites tool requests by email, so treat the list as unversioned. `get_balance_summary`
182
192
  is Treasury, which Stripe labels public preview and gates behind an access
@@ -57,7 +57,7 @@ exist so far:
57
57
  | --- | --- | --- |
58
58
  | **pre-template** | before 0.10.2 | no `connecta init` existed; hand-written, or copied from the retired `examples/node` |
59
59
  | **A** | 0.10.2 – 0.15.1 | `.env.example`, `.gitignore`, `AGENTS.md`, `CLAUDE.md`, `README.md`, `package.json`, `src/index.ts`, `tsconfig.json` |
60
- | **B** | 0.16.0 – 0.18.0 | adds `.dockerignore`, `Dockerfile`, `docker-compose.yml`, and `src/file-activity.ts`; `src/index.ts` grows the four commented operator blocks; `.env.example` ships `CONNECTA_TOKEN=` empty |
60
+ | **B** | 0.16.0 – 0.18.1 | adds `.dockerignore`, `Dockerfile`, `docker-compose.yml`, and `src/file-activity.ts`; `src/index.ts` grows the four commented operator blocks; `.env.example` ships `CONNECTA_TOKEN=` empty |
61
61
 
62
62
  Generation A is a decade in template years and identifying it precisely does
63
63
  not matter, because you are about to reconstruct it exactly rather than guess
@@ -106,7 +106,7 @@ know what to preserve, once to know what to re-verify at the end.
106
106
  ### Bump the pin and install
107
107
 
108
108
  ```sh
109
- npm pkg set dependencies.@zackbart/connecta=0.18.0
109
+ npm pkg set dependencies.@zackbart/connecta=0.18.1
110
110
  npm install
111
111
  ```
112
112
 
@@ -130,7 +130,7 @@ Generate the *current* template beside the base you already made, into the same
130
130
  `$SCRATCH`:
131
131
 
132
132
  ```sh
133
- (cd "$SCRATCH" && npx @zackbart/connecta@0.18.0 init current)
133
+ (cd "$SCRATCH" && npx @zackbart/connecta@0.18.1 init current)
134
134
  ```
135
135
 
136
136
  You now have a three-way merge with a real base: `$SCRATCH/base` is what this
@@ -186,7 +186,7 @@ A deployment older than 0.10.2 has no base to diff against. Do not try to
186
186
  manufacture one. Instead:
187
187
 
188
188
  1. `SCRATCH=$(mktemp -d)`, then
189
- `(cd "$SCRATCH" && npx @zackbart/connecta@0.18.0 init current)` — there is no
189
+ `(cd "$SCRATCH" && npx @zackbart/connecta@0.18.1 init current)` — there is no
190
190
  `base` leg here, only the current template to read from.
191
191
  2. Copy `$SCRATCH/current` into the deployment file by file, **skipping
192
192
  `src/index.ts`**.
@@ -207,6 +207,16 @@ first, so cross them bottom-up: start at the oldest one still above this
207
207
  deployment's pin and work back up the page, because each boundary assumes the
208
208
  older ones are already done.
209
209
 
210
+ ### 0.18.0 → 0.18.1
211
+
212
+ Nothing throws, no option moves, and every deployment crosses this on the
213
+ version bump alone. The release adds one provider subpath,
214
+ `@zackbart/connecta/providers/revenuecat`, and rewrites guide text inside the
215
+ `mixpanel()` and `stripe()` connections; a deployment that constructs neither
216
+ sees no change, and one that does gets better first-line advice for the same
217
+ constructor calls. Clients that cache connector guides should refresh them
218
+ after upgrading.
219
+
210
220
  ### 0.17.0 → 0.18.0
211
221
 
212
222
  One floor moves and one always-loaded surface shrinks; neither changes a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zackbart/connecta",
3
- "version": "0.18.0",
3
+ "version": "0.18.1",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "One MCP to rule them all — a single MCP endpoint aggregating many downstream connectors behind a code-first surface of seven meta-tools.",
@@ -79,6 +79,10 @@
79
79
  "types": "./dist/providers/notion.d.ts",
80
80
  "import": "./dist/providers/notion.js"
81
81
  },
82
+ "./providers/revenuecat": {
83
+ "types": "./dist/providers/revenuecat.d.ts",
84
+ "import": "./dist/providers/revenuecat.js"
85
+ },
82
86
  "./providers/stripe": {
83
87
  "types": "./dist/providers/stripe.d.ts",
84
88
  "import": "./dist/providers/stripe.js"
@@ -15,7 +15,7 @@
15
15
  "typecheck": "tsc --noEmit"
16
16
  },
17
17
  "dependencies": {
18
- "@zackbart/connecta": "0.18.0",
18
+ "@zackbart/connecta": "0.18.1",
19
19
  "quickjs-emscripten": "0.32.0"
20
20
  },
21
21
  "devDependencies": {