@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.
- package/AGENTS.md +18 -20
- package/CHANGELOG.md +64 -1
- package/README.md +5 -6
- package/dist/branding.d.ts +31 -2
- package/dist/branding.js +116 -8
- package/dist/connectors/api.d.ts +1 -1
- package/dist/connectors/api.js +10 -2
- package/dist/connectors/guarded-fetch.d.ts +5 -1
- package/dist/connectors/guarded-fetch.js +34 -4
- package/dist/connectors/remote-mcp.js +8 -4
- package/dist/errors.d.ts +11 -3
- package/dist/errors.js +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +12 -1
- package/dist/meta-tools.js +105 -29
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/view.d.ts +38 -1
- package/dist/operator-ui/view.js +71 -0
- package/dist/providers/cloudflare.d.ts +14 -2
- package/dist/providers/cloudflare.js +107 -16
- package/dist/providers/linear.d.ts +26 -4
- package/dist/providers/linear.js +19 -4
- package/dist/providers/mixpanel.d.ts +16 -3
- package/dist/providers/mixpanel.js +13 -2
- package/dist/providers/notion.d.ts +8 -1
- package/dist/providers/notion.js +83 -10
- package/dist/providers/revenuecat.d.ts +30 -4
- package/dist/providers/revenuecat.js +42 -4
- package/dist/providers/stripe.d.ts +7 -1
- package/dist/providers/stripe.js +30 -4
- package/dist/providers/vercel.js +11 -1
- package/dist/registry.d.ts +12 -4
- package/dist/registry.js +22 -8
- package/dist/types.d.ts +37 -0
- package/dist/ui.js +18 -10
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +193 -181
- package/documentation/auth.md +197 -176
- package/documentation/code-mode.md +426 -321
- package/documentation/meta-tools.md +356 -416
- package/examples/worker/AGENTS.md +2 -1
- package/examples/worker/README.md +12 -10
- package/examples/worker/src/index.ts +12 -15
- package/package.json +1 -2
- package/templates/node/.env.example +3 -3
- package/templates/node/AGENTS.md +5 -4
- package/templates/node/README.md +2 -1
- package/templates/node/package.json +1 -1
- package/templates/node/src/index.ts +23 -22
- package/documentation/call-admission.md +0 -158
- package/documentation/cloudflare.md +0 -471
- package/documentation/connector-guides.md +0 -176
- package/documentation/connectors.md +0 -431
- package/documentation/linear.md +0 -193
- package/documentation/mixpanel.md +0 -160
- package/documentation/notion.md +0 -308
- package/documentation/operations.md +0 -359
- package/documentation/operator-ui.md +0 -135
- package/documentation/optional-modules-upgrade.md +0 -243
- package/documentation/provider-conventions.md +0 -729
- package/documentation/request-admission.md +0 -204
- package/documentation/revenuecat.md +0 -305
- package/documentation/storage-and-credentials.md +0 -254
- package/documentation/stripe.md +0 -262
- package/documentation/upgrading.md +0 -768
- 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.
|
package/documentation/stripe.md
DELETED
|
@@ -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).
|