@zackbart/connecta 0.24.3 → 0.24.4

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 (67) hide show
  1. package/AGENTS.md +18 -20
  2. package/CHANGELOG.md +64 -1
  3. package/README.md +5 -6
  4. package/dist/branding.d.ts +31 -2
  5. package/dist/branding.js +116 -8
  6. package/dist/connectors/api.d.ts +1 -1
  7. package/dist/connectors/api.js +10 -2
  8. package/dist/connectors/guarded-fetch.d.ts +5 -1
  9. package/dist/connectors/guarded-fetch.js +34 -4
  10. package/dist/connectors/remote-mcp.js +8 -4
  11. package/dist/errors.d.ts +11 -3
  12. package/dist/errors.js +2 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.js +12 -1
  15. package/dist/meta-tools.js +105 -29
  16. package/dist/operator-ui/generated.js +2 -2
  17. package/dist/operator-ui/view.d.ts +38 -1
  18. package/dist/operator-ui/view.js +71 -0
  19. package/dist/providers/cloudflare.d.ts +14 -2
  20. package/dist/providers/cloudflare.js +107 -16
  21. package/dist/providers/linear.d.ts +26 -4
  22. package/dist/providers/linear.js +19 -4
  23. package/dist/providers/mixpanel.d.ts +16 -3
  24. package/dist/providers/mixpanel.js +13 -2
  25. package/dist/providers/notion.d.ts +8 -1
  26. package/dist/providers/notion.js +83 -10
  27. package/dist/providers/revenuecat.d.ts +30 -4
  28. package/dist/providers/revenuecat.js +42 -4
  29. package/dist/providers/stripe.d.ts +7 -1
  30. package/dist/providers/stripe.js +30 -4
  31. package/dist/providers/vercel.js +11 -1
  32. package/dist/registry.d.ts +12 -4
  33. package/dist/registry.js +22 -8
  34. package/dist/types.d.ts +37 -0
  35. package/dist/ui.js +18 -10
  36. package/dist/version.d.ts +1 -1
  37. package/dist/version.js +1 -1
  38. package/documentation/architecture.md +193 -181
  39. package/documentation/auth.md +197 -176
  40. package/documentation/code-mode.md +426 -321
  41. package/documentation/meta-tools.md +356 -416
  42. package/examples/worker/AGENTS.md +2 -1
  43. package/examples/worker/README.md +12 -10
  44. package/examples/worker/src/index.ts +12 -15
  45. package/package.json +1 -2
  46. package/templates/node/.env.example +3 -3
  47. package/templates/node/AGENTS.md +5 -4
  48. package/templates/node/README.md +2 -1
  49. package/templates/node/package.json +1 -1
  50. package/templates/node/src/index.ts +23 -22
  51. package/documentation/call-admission.md +0 -158
  52. package/documentation/cloudflare.md +0 -471
  53. package/documentation/connector-guides.md +0 -176
  54. package/documentation/connectors.md +0 -431
  55. package/documentation/linear.md +0 -193
  56. package/documentation/mixpanel.md +0 -160
  57. package/documentation/notion.md +0 -308
  58. package/documentation/operations.md +0 -359
  59. package/documentation/operator-ui.md +0 -135
  60. package/documentation/optional-modules-upgrade.md +0 -243
  61. package/documentation/provider-conventions.md +0 -729
  62. package/documentation/request-admission.md +0 -204
  63. package/documentation/revenuecat.md +0 -305
  64. package/documentation/storage-and-credentials.md +0 -254
  65. package/documentation/stripe.md +0 -262
  66. package/documentation/upgrading.md +0 -768
  67. package/documentation/vercel.md +0 -241
@@ -1,254 +0,0 @@
1
- # Storage and credentials
2
-
3
- The core `KVStorage` contract supports `get`, `set`, and `delete`; adapters may
4
- also implement `list(prefix)`. Core uses storage for connector state, catalogs,
5
- and result paging. The vault is an explicit independent option:
6
-
7
- ```ts
8
- import { encryptedCredentialVault } from "@zackbart/connecta/credentials";
9
-
10
- createConnecta({
11
- connectors,
12
- executor,
13
- storage,
14
- vault: encryptedCredentialVault(storage, encryptionKey),
15
- });
16
- ```
17
-
18
- `encryptionKey` is a base64 32-byte AES key. The factory implements the
19
- root-exported `CredentialVault` contract. Core depends on the contract without
20
- importing the encrypted implementation; deployments may supply their own.
21
- A replacement must isolate both connector id and optional owner, preserve
22
- metadata-only reads for the UI, and keep plaintext confined to host credential
23
- resolution. The built-in AES-GCM implementation binds connector and owner into
24
- authenticated encryption context. Sharing a backend is not permission to share
25
- a principal's credentials.
26
-
27
- Connectors declare a human-managed `credential` slot in code. The vault exposes
28
- read access only through that connector's `ctx.credential`. Values, masked
29
- values, call arguments, and raw errors never enter model recovery or activity.
30
- Omitting the vault leaves literal deployment-provided secrets and downstream
31
- OAuth available, but declared vault slots cannot be managed.
32
-
33
- Credentials fail at use. Proactive liveness probing remains removed by
34
- [the 0.9 decision](../ethos.md). Operator-triggered tests and local credential
35
- shape checks remain. `authorize_connector` returns a human credential handoff
36
- only when both a vault and the UI are configured. Without either, recovery is
37
- `unavailable` and explains the missing configuration.
38
-
39
- A static bearer may invoke a connector but never mutate its auth as a browser
40
- user. Interactive users need visibility plus `credentialAdministration` for
41
- shared auth or `personalConnection` for their own personal auth. Both management
42
- permissions default to none. Saving, testing, replacing, or removing a value
43
- never returns it. The vault is read for each call, so a saved replacement takes
44
- effect without restarting the deployment.
45
-
46
- ## Result storage
47
-
48
- Direct-call result paging uses the same KV interface with a 15-minute TTL.
49
- `results.maxStashBytes` defaults to 8 MiB of stored paging envelopes, including
50
- base64 overhead; `results.maxStashEntries` defaults to 64. Both are
51
- non-negative safe integers, and zero disables stashing. One registry accounts
52
- for all subjects and reserves capacity for pending writes. A full stash keeps
53
- the successful call's preview and returns a paging-unavailable notice, without
54
- a result id. Expired entries are deleted on later stash attempts before their
55
- capacity is reused, even when the backend only expires entries on read.
56
- A deletion failure keeps the reservation. Limits apply to writes by one
57
- runtime; they do not coordinate other processes or Worker isolates, or count
58
- entries left by a previous runtime.
59
-
60
- The memory store also checks up to 16 existing keys on each `set`, rotating
61
- through live keys so expired entries that nobody reads are eventually removed.
62
- There are no timers or background sweeps. Paging values use an ASCII base64
63
- envelope so only the requested bytes need decoding after the KV read. The
64
- storage adapter's format and interface stay unchanged.
65
-
66
- ## File storage
67
-
68
- `fileStorage` is a single-process development store. It loads one snapshot and
69
- rewrites the whole state synchronously on each mutation, including result
70
- stashes. It acquires an exclusive `<path>.lock` before loading, so a second
71
- instance or process opening the same path fails with the holder's pid instead
72
- of overwriting a stale snapshot. An unref'd timer refreshes the lock's mtime
73
- every 15 seconds. A heartbeat older than 60 seconds expires regardless of pid,
74
- so a container restart cannot leave a reused pid holding the file forever.
75
- The lock records the host/PID namespace as well as the pid. Within that same
76
- namespace, a dead pid permits immediate recovery; a matching current pid is
77
- live only when the in-process registry owns that lock. Other namespaces and
78
- older locks without namespace metadata rely on heartbeat expiry.
79
-
80
- Recovery uses a serialized `.lock.reclaim` guard, which also expires after
81
- 60 seconds if its process crashes or pauses. An incomplete lock likewise
82
- becomes recoverable after 60 seconds. A holder paused long enough to lose its
83
- lock fails subsequent writes with "lock was lost". Writes check ownership
84
- before changing state and again before rename; reads use the loaded snapshot
85
- without filesystem lock checks. This remains an advisory development store
86
- on a shared local filesystem, not a distributed storage adapter.
87
-
88
- The returned store's `close()` releases the lock and refuses further operations.
89
- Process exit also releases it, including Node `listen()`'s SIGTERM/SIGINT
90
- shutdown. Each write uses a unique, exclusively created temp file, and the
91
- state file's JSON format is unchanged.
92
-
93
- Expired entries are removed before each write. Large direct-call results therefore increase both
94
- retained state and write cost. Use `execute_code` to reduce read-only results
95
- before returning them, and choose a storage adapter suited to the deployment.
96
-
97
- ## Storage continuity
98
-
99
- This module extraction changes no encrypted record keys, owner partitions, or
100
- OAuth storage format. Reuse the existing storage and encryption key when
101
- constructing `encryptedCredentialVault`. Keep connector ids, principal ids, and
102
- identity namespaces unchanged. Losing the encryption key makes old vault
103
- values unreadable; changing identity context selects another personal partition.
104
-
105
- Removed Connecta-issued token records remain inert in storage. This upgrade
106
- does not delete them, convert them to another credential, or migrate their
107
- clients automatically.
108
-
109
- ## Shared and personal auth
110
-
111
- Connector auth defaults to `authScope: "shared"`. Its credential, OAuth state,
112
- tokens, catalog cache, and connector storage belong to the deployment. Set
113
- `authScope: "personal"` when every human principal needs a separate downstream
114
- account:
115
-
116
- ```ts
117
- remoteMcp("linear", {
118
- url: "https://mcp.linear.app/mcp",
119
- authScope: "personal",
120
- auth: { type: "oauth" },
121
- });
122
- ```
123
-
124
- Personal connectors disappear from a request that has no stable human
125
- principal. For a principal that can see one, connecta partitions connector
126
- storage, encrypted vault records, catalog caches, OAuth generations, and
127
- observed result shapes under an opaque SHA-256 identity key. Results used by
128
- `get_result` are partitioned by the authenticated subject independently of
129
- activity configuration. Open deployments and providers that supply no identity
130
- share one results partition. See [the partition and paging contract](./meta-tools.md#result-representation).
131
-
132
- Literal `auth: { type: "headers" }` cannot be personal because its secret lives
133
- in deployment code. `remoteMcp()` refuses that combination at construction.
134
- Use operator-managed credential auth or OAuth instead.
135
-
136
- ## A remote MCP connector's static credential
137
-
138
- `remoteMcp()` accepts a third auth shape beside OAuth and literal headers:
139
-
140
- ```ts
141
- remoteMcp("revenuecat_bepresent", {
142
- url: "https://mcp.revenuecat.ai/mcp",
143
- auth: { type: "credential", credential: { label: "API v2 secret key" } },
144
- });
145
- ```
146
-
147
- The connector, its endpoint, and the credential *slot* stay declared in code;
148
- only the secret arrives through the connection UI at `/`. That is the same boundary
149
- `api()` has always had, and the reason a project-wide key no longer has to be a
150
- Worker secret or an environment variable
151
- ([#439](https://github.com/zackbart/connecta/issues/439)).
152
-
153
- `header` defaults to `Authorization` and `scheme` to `Bearer`. `scheme: null`
154
- sends the stored value verbatim, which is what Linear's personal API keys
155
- expect. A scheme whose last token is `Basic` declares HTTP Basic credentials, so
156
- the stored `user:secret` is base64-encoded first — `"Basic"` produces
157
- `Basic <base64>`, and Mixpanel's documented `"Bearer Basic"` produces
158
- `Bearer Basic <base64>`. There is one reserved `value` field and no multi-field
159
- header composition: named `credential.fields` are refused at construction.
160
-
161
- A stored value is checked before anything frames it: a line break or other
162
- control character — what a key pasted across two lines leaves behind — is
163
- refused as `auth_required` with a message naming the problem and never the
164
- value. That check exists because the runtime that rejects such a header quotes
165
- the whole offending value back in its `TypeError`, and that message would
166
- otherwise reach the agent, the operator page, and the activity log. Behind it,
167
- any error whose message quotes the credential or the header it became is
168
- discarded whole and replaced; nothing is masked or truncated, because a
169
- redaction that keeps part of a secret is still a leak.
170
-
171
- An empty slot is not a boot failure and not a silently absent connector. The
172
- connector is present, its status reads `auth_required`, calls fail with the same
173
- typed error a missing OAuth grant produces, and `authorize_connector` returns
174
- a connection UI handoff at `/` when UI and vault are configured. Without either,
175
- it returns `unavailable` and explains what is missing.
176
-
177
- The vault is read before any cached downstream client is trusted, so a rotation
178
- lands on the next call rather than the next deploy. Connecta compares a SHA-256
179
- digest of the value the cached client connected with; a different digest closes
180
- that client and reconnects. The plaintext lives in the connect attempt's local
181
- scope, never on connector state, never in a log, and never in a status or error
182
- message. A cleartext `http://` destination warns at construction here exactly as
183
- it does for literal headers — who owns the secret changed, not what the wire
184
- carries.
185
-
186
- The connection UI Test action connects with the stored value and reports how many
187
- tools the downstream served. That is the whole honest check for a proxy: which
188
- account, project, or mode the key reaches is the provider's answer, not
189
- Connecta's.
190
-
191
- ## Downstream OAuth
192
-
193
- `remoteMcp()` stores dynamic client registration, tokens, PKCE material, state,
194
- and the pending authorization URL in the connector's storage namespace.
195
- Registration and token envelopes are bound to the validated authorization
196
- server `issuer`. An unbound pre-0.9 envelope is upgraded in place on its first
197
- issuer-aware read, preserving the existing grant.
198
-
199
- For personal OAuth, the authorization handoff also stores a 15-minute mapping
200
- from a SHA-256 digest of `state` to the principal partition. The public callback
201
- uses that mapping before it verifies state or exchanges the code. Neither the
202
- browser nor a callback parameter can select a principal. The callback deletes
203
- the mapping before it exchanges the code, so a second callback cannot replay
204
- the principal handoff in strongly consistent storage. Cloudflare KV deletion
205
- is eventually consistent, so handoff consumption there is best-effort across
206
- PoPs; the downstream authorization code remains single-use. If the callback
207
- request also carries an interactive identity, Connecta refuses it when that
208
- principal did not start the flow.
209
-
210
- If later discovery resolves a different issuer, Connecta does not send the old
211
- client identifier or tokens to it. The provider publishes a new generation
212
- epoch, makes every older credential namespace unreadable, cleans up the retired
213
- values, and lets the SDK begin registration and authorization again. The same
214
- epoch fence prevents an older isolate or late token exchange from resurrecting
215
- the retired grant.
216
-
217
- The OAuth callback verifies the one-shot `state` first, then hands the complete
218
- query string—including RFC 9207 `iss`—to the SDK transport. One-shot state,
219
- verifier, and pending URL are cleared only after a successful exchange.
220
-
221
- Within one `remoteMcp()` runtime, one request scope owns refresh-token
222
- redemption for an OAuth generation. Concurrent scopes wait for the owner's
223
- token save or bounded failure, then either read storage again or receive that
224
- failure. A scope that had already read the retired refresh token reuses the
225
- newly stored rotating token locally instead of sending the retired value
226
- upstream. Force reauthorization retires the old generation's gate, and a
227
- failed flow releases ownership for a later attempt. The coordinator retains
228
- only a completion signal and one temporary owner-abort listener until that
229
- exact flight settles, never the token response or downstream transport. A
230
- follower may stop waiting when its own request is cancelled without cancelling
231
- the owner or poisoning the generation for later callers. If the owner's
232
- credential mutation fails, joined callers receive that same bounded failure
233
- instead of waking to redeem the unchanged token; a later independent call may
234
- retry. Non-success and malformed token responses settle current waiters at the
235
- fetch boundary, before any later authorization callback can itself fail.
236
- Cancelling the owner aborts its fetch and fails current joiners rather than
237
- promoting one: once a request reaches the authorization server, repeating its
238
- old refresh token is not known to be safe. If that cancellation lands while
239
- the valid response's credential write is already running, a same-generation
240
- attempt receives `temporarily_unavailable` until the exact write succeeds or
241
- fails. This mutation marker contains no retained promise; force
242
- reauthorization removes it when the old generation becomes unreadable.
243
- An additional opaque success identity lets a request recognize a refresh that
244
- completed after its issuer-aware token read even when the authorization server
245
- returned byte-identical credentials. The identity is generation-scoped and is
246
- discarded with the retired generation. Every authoritative storage-generation
247
- read also retires coordinator state from other epochs, so an externally
248
- advanced generation cannot be overwritten in runtime state by late old work.
249
-
250
- This guarantee is runtime-local. `KVStorage` has no atomic lock or
251
- compare-and-set operation, so separate processes or Worker isolates can still
252
- redeem the same refresh token concurrently. Generation envelopes continue to
253
- fence their writes, but Connecta does not claim cross-isolate exactly-once
254
- refresh.
@@ -1,262 +0,0 @@
1
- # Stripe prebuilt connection
2
-
3
- Import `stripe()` independently from `@zackbart/connecta/providers/stripe`. It
4
- wraps [Stripe's hosted MCP server](https://docs.stripe.com/mcp) with OAuth by
5
- default, account-scoped mode guidance, a conservative admission policy, a
6
- task-oriented usage guide, and a vetted safety classification. It adds no
7
- provider dependency and is not reachable from Connecta's root entry.
8
-
9
- ```ts
10
- import { stripe } from "@zackbart/connecta/providers/stripe";
11
-
12
- const billing = stripe("stripe", {
13
- purpose: "Revenue, disputes, and refunds across our Stripe organization",
14
- instructions: "Never refund above $500 without a human in the loop.",
15
- });
16
- ```
17
-
18
- The `id` owns the ordinary connector namespaces. Choose a connector boundary
19
- for its credential or OAuth session and business purpose — not automatically
20
- for each Stripe account. One OAuth session may cover live and sandbox accounts
21
- in the same Stripe organization. Use separate connectors when the credential
22
- or business purpose differs.
23
-
24
- `purpose` is required because it tells an agent where the deployment intends
25
- to route a question. The connector id, title, and purpose are configuration,
26
- not proof of which account the authenticated Stripe session will use. Account
27
- `instructions` are appended to the maintained guide and cannot change the
28
- connector's safety classification.
29
-
30
- ## OAuth mode belongs to the selected account
31
-
32
- Do not pass `mode` for OAuth. Stripe's `list_available_accounts_or_orgs` returns
33
- each available account with its `stripe_context` and `livemode`. The same OAuth
34
- session can return both `livemode: true` and `livemode: false` results.
35
-
36
- The served guide tells agents to call that tool before each account-scoped
37
- operation. They select the intended result and carry its exact `stripe_context`
38
- and `livemode` unchanged. Connector id, title, purpose, and OAuth identity are
39
- routing hints. They never prove the account or mode. Ambiguity stops the call.
40
-
41
- OAuth metadata therefore stays neutral:
42
-
43
- - the default title is `Stripe`;
44
- - the description says `live and sandbox accounts`;
45
- - the guide warns that live writes move real money and sandbox writes change
46
- test data;
47
- - admission uses the stricter sandbox ceiling, because Connecta cannot select
48
- a different connector policy after the account-scoped call begins.
49
-
50
- `mode` remains required for `headers` auth. A restricted key has one fixed live
51
- or sandbox scope. Its title, description, guide, and admission policy keep the
52
- fixed-mode behavior. Construction still throws when a recognizable key prefix
53
- contradicts its declared mode.
54
-
55
- For OAuth, deploy one connector for the session:
56
-
57
- ```ts
58
- connectors: [
59
- stripe("stripe", {
60
- purpose: "Live and sandbox billing for our Stripe organization",
61
- }),
62
- ]
63
- ```
64
-
65
- ## Authentication
66
-
67
- OAuth is the default and the option Stripe recommends: it supports dynamic
68
- client registration and PKCE, and each connector instance keeps its own flow
69
- and tokens in connector-scoped storage. Stripe's current
70
- [session-management documentation](https://docs.stripe.com/mcp#manage-mcp-client-sessions)
71
- says one OAuth session can be tied to more than one account in the same Stripe
72
- organization. It does not say every session has multiple accounts.
73
-
74
- That scope changes what an agent must prove before an account-scoped call. It
75
- calls `list_available_accounts_or_orgs`, resolves the intended result, and
76
- carries its exact `stripe_context` and `livemode`. If more than one account
77
- fits, the agent stops and asks.
78
-
79
- Stripe also accepts a
80
- [restricted API key](https://docs.stripe.com/keys#create-restricted-api-key) as
81
- a bearer token for headless agents:
82
-
83
- ```ts
84
- stripe("stripe_sandbox", {
85
- mode: "sandbox",
86
- purpose: "Automated billing rehearsal",
87
- auth: {
88
- type: "headers",
89
- headers: { Authorization: `Bearer ${env.STRIPE_RESTRICTED_KEY}` },
90
- },
91
- });
92
- ```
93
-
94
- Use a restricted key, not a secret key, and scope it to the operations the
95
- agent actually needs; Stripe's own guidance is to "limit your agent's access to
96
- exactly the functionality it requires". Keep it in the runtime's secret store.
97
-
98
- The same key can come from the connection UI at `/` instead:
99
-
100
- ```ts
101
- stripe("stripe_sandbox", {
102
- mode: "sandbox",
103
- purpose: "Automated billing rehearsal",
104
- auth: { type: "credential" },
105
- });
106
- ```
107
-
108
- `mode` is required either way — a static key answers for exactly one
109
- environment and cannot report which. The literal-header form is checked against
110
- the key's `_live_`/`_test_` prefix at construction; an operator-managed key is
111
- not in the deployment file to read, so the declared mode stands alone and a key
112
- pointed at the other environment fails at Stripe. Declare the mode carefully:
113
- that check is the one guard Connecta can offer, and this shape does not get it.
114
- See
115
- [storage and credentials](./storage-and-credentials.md#a-remote-mcp-connectors-static-credential).
116
-
117
- Organization accounts in one OAuth session are not Stripe Connect connected
118
- accounts. Connect platforms can act as a connected account with
119
- `connectedAccount`, which adds Stripe's documented `Stripe-Account` header at
120
- connector construction. Stripe does not support OAuth for connected-account
121
- calls, and `Stripe-Account` is a second header beside the credential's own,
122
- which the operator-managed shape does not assemble — so this requires a
123
- restricted key through `headers` auth and throws otherwise:
124
-
125
- ```ts
126
- stripe("merchant_42", {
127
- mode: "production",
128
- purpose: "Billing questions for the merchant on account 42",
129
- connectedAccount: "acct_1234567890",
130
- auth: {
131
- type: "headers",
132
- headers: { Authorization: `Bearer ${env.STRIPE_PLATFORM_KEY}` },
133
- },
134
- });
135
- ```
136
-
137
- Administrators must enable MCP access in the Stripe Dashboard. A connector that
138
- boots but cannot list tools is usually a dashboard toggle, not a bad key.
139
-
140
- ## The eleven tools, and what they are classified as
141
-
142
- Stripe currently serves eleven tools. Seven are reads:
143
-
144
- `stripe_api_search`, `stripe_api_details`, `stripe_api_read`,
145
- `get_balance_summary`, `list_available_accounts_or_orgs`,
146
- `manage_stripe_accounts`, and `search_stripe_documentation`.
147
-
148
- Four are writes:
149
-
150
- `stripe_api_write` is destructive. `stripe_implementation_planner`,
151
- `stripe_analytics`, and `send_stripe_mcp_feedback` are additive.
152
-
153
- Two of those deserve a sentence. `stripe_api_read` is a read because Stripe
154
- documents it as the `GET` half of a generic pair — the tool is the read
155
- boundary, not whichever endpoint an agent names inside it, and its sibling
156
- `stripe_api_write` carries every `POST`, `PATCH`, `PUT`, and `DELETE`.
157
- `stripe_implementation_planner` creates and continues provider-side guide
158
- state. `stripe_analytics` combines retrieval with query-run creation behind one
159
- tool. Stripe marks both not read-only and not destructive, so Connecta records
160
- them as additive writes. They leave `destructiveHint` unset. Their explicit
161
- `readOnlyHint: false` already routes them through `call_destructive_tool`, and
162
- asserting destruction would misstate their effect.
163
-
164
- That classification fills in downstream silence and otherwise preserves
165
- explicit annotations. The current server annotates all eleven tools, while the
166
- manifest records what this release reviewed and exposes later contradictions.
167
- A tool on the read allowlist arriving with
168
- `destructiveHint: true` or `readOnlyHint: false` keeps exactly what the
169
- downstream said and stays behind `call_destructive_tool`. A tool on neither
170
- maintained list arriving with `readOnlyHint: true` keeps that too, and stays
171
- callable from `execute_code`. Both are the downstream telling you this release's
172
- allowlist is stale, and on a name no release has reviewed its word is the only
173
- evidence there is. One narrow fail-closed exception applies to a name this
174
- release reviewed and filed destructive: `stripe_api_write` claiming
175
- `readOnlyHint: true` is a downstream bug rather than news, and stays on the
176
- approval path.
177
-
178
- An unfamiliar tool that annotates nothing fails closed onto
179
- `call_destructive_tool` until a Connecta release reviews it. That is not
180
- hypothetical here: Stripe's own MCP page still carries a `create_customer`
181
- example that its tool table no longer lists. Whatever the server actually
182
- serves, an unclassified and unannotated `create_customer` lands on the approval
183
- path. Expect the undocumented Treasury tools Stripe alludes to to arrive
184
- unclassified as well — annotated ones will be taken at their word.
185
-
186
- The upshot is that this connection's tool list is not a fixed set, and the usage
187
- guide tells the agent so: search this connector for what it actually exposes
188
- rather than assuming a documented tool is present. The guide also names the id
189
- discipline the downstream schemas cannot enforce — Stripe ids are typed
190
- prefixes (`cus_`, `sub_`, `ch_`, `pi_`, `in_`, `acct_`), a plausible-looking one
191
- belongs to a different object or to nobody, and the id a write takes comes from
192
- `stripe_api_search` or a list read rather than from a guess.
193
-
194
- Account selection comes before that object-id rule. The served guide names
195
- `list_available_accounts_or_orgs`, `stripe_context`, and `livemode`, and tells
196
- the agent to stop when the account, mode, or selector is ambiguous. It keeps
197
- organization-account selection separate from the restricted-key-only Connect
198
- path, so an agent cannot repair uncertainty by fabricating `Stripe-Account` as
199
- a tool argument.
200
-
201
- The guide also carries the reduction advice the generic schemas cannot (P7):
202
- a list or search read that returns full objects belongs inside `execute_code`,
203
- projected to the fields the question needs before `return`, because an
204
- unprojected list truncates and a projected one keeps customer PII out of the
205
- transcript. It names Stripe search's per-resource field set — charges search
206
- has no `payment_intent` field, so the path is the PaymentIntent's
207
- `latest_charge` — and the account → search → details → read sequence as one
208
- program rather than four turns, and it names `outcome`, `failure_code`, and
209
- `failure_message` on the charge as the answer to "why did this payment fail".
210
-
211
- Stripe publishes no stability or deprecation policy for this tool set and
212
- invites tool requests by email, so treat the list as unversioned. `get_balance_summary`
213
- is Treasury, which Stripe labels public preview and gates behind an access
214
- request — expect it to be absent unless the account is allowlisted, and expect
215
- the other Treasury tools Stripe alludes to but does not document to arrive
216
- unclassified.
217
-
218
- ## Rate limits
219
-
220
- Stripe documents no rate limit specific to the MCP server. The connection
221
- therefore transcribes the account limit that MCP traffic spends
222
- ([rate limits](https://docs.stripe.com/rate-limits)): **100 requests per second
223
- in live mode, 25 in a sandbox**, and any single endpoint is capped at 25 per
224
- second regardless of mode. OAuth uses 25 calls per second and concurrency 4,
225
- the safe bound for a session that can reach either mode. Fixed live credentials
226
- use 100 calls per second and concurrency 8. Fixed sandbox credentials use 25
227
- and concurrency 4. Stripe
228
- documents that per-account and per-endpoint concurrency limits exist, and
229
- surface as `429` with a `Stripe-Rate-Limited-Reason` of `global-concurrency` or
230
- `endpoint-concurrency`, but publishes no number.
231
-
232
- As with every connector policy this is a **best-effort approximation** of the
233
- provider's limit, not an enforcement of it. Each runtime keeps its own counter,
234
- so N Worker isolates or Node processes serving one deployment can each admit up
235
- to the stated rate, and the same Stripe account may be spending its budget on
236
- traffic Connecta never sees. Discovery traffic is outside connector call
237
- admission and still needs restrained use.
238
-
239
- ## What is not verified
240
-
241
- Stripe's MCP documentation is silent on one thing this connection had to reason
242
- about rather than read:
243
-
244
- - **Whether pagination cursors and `Idempotency-Key` are passable through
245
- `stripe_api_read` / `stripe_api_write`.** The conventions in the usage guide
246
- are Stripe's documented API conventions; how they thread through the generic
247
- tools' arguments is not documented. The guide states them because an agent
248
- that ignores them is wrong either way.
249
-
250
- `npm run drift:check -- --docs --provider stripe` compares Stripe's official
251
- 16-tool table with the vetted manifest and checks the hosted endpoint plus OAuth
252
- support. `list_available_accounts_or_orgs` and `manage_stripe_accounts` remain
253
- classified from an authenticated review even though the current public table
254
- does not list them. The command prints that discrepancy without treating docs
255
- silence as proof that a live account no longer serves them.
256
-
257
- ## Conventions
258
-
259
- This connection is audited against
260
- [the provider conventions](./provider-conventions.md). Its verdict per
261
- convention, including every recorded exception, is the Stripe section of
262
- [the provider audit](https://github.com/zackbart/connecta/blob/main/records/provider-audit.md).