@zackbart/connecta 0.13.0 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +225 -0
  2. package/dist/catalog-service.d.ts.map +1 -1
  3. package/dist/catalog-service.js +33 -6
  4. package/dist/catalog-service.js.map +1 -1
  5. package/dist/connectors/api.d.ts.map +1 -1
  6. package/dist/connectors/api.js +5 -1
  7. package/dist/connectors/api.js.map +1 -1
  8. package/dist/execute.js +1 -1
  9. package/dist/execute.js.map +1 -1
  10. package/dist/providers/cloudflare.d.ts +54 -0
  11. package/dist/providers/cloudflare.d.ts.map +1 -0
  12. package/dist/providers/cloudflare.js +3210 -0
  13. package/dist/providers/cloudflare.js.map +1 -0
  14. package/dist/providers/linear.d.ts +44 -0
  15. package/dist/providers/linear.d.ts.map +1 -0
  16. package/dist/providers/linear.js +243 -0
  17. package/dist/providers/linear.js.map +1 -0
  18. package/dist/providers/mixpanel.d.ts.map +1 -1
  19. package/dist/providers/mixpanel.js +15 -7
  20. package/dist/providers/mixpanel.js.map +1 -1
  21. package/dist/providers/notion.d.ts +39 -0
  22. package/dist/providers/notion.d.ts.map +1 -0
  23. package/dist/providers/notion.js +1625 -0
  24. package/dist/providers/notion.js.map +1 -0
  25. package/dist/providers/stripe.d.ts +37 -0
  26. package/dist/providers/stripe.d.ts.map +1 -0
  27. package/dist/providers/stripe.js +232 -0
  28. package/dist/providers/stripe.js.map +1 -0
  29. package/dist/version.d.ts +1 -1
  30. package/dist/version.js +1 -1
  31. package/documentation/cloudflare.md +313 -0
  32. package/documentation/connectors.md +13 -7
  33. package/documentation/linear.md +144 -0
  34. package/documentation/meta-tools.md +12 -1
  35. package/documentation/mixpanel.md +12 -7
  36. package/documentation/notion.md +233 -0
  37. package/documentation/stripe.md +202 -0
  38. package/ethos.md +1 -0
  39. package/package.json +17 -1
  40. package/src/catalog-service.ts +35 -6
  41. package/src/connectors/api.ts +5 -1
  42. package/src/execute.ts +1 -1
  43. package/src/providers/cloudflare.ts +3803 -0
  44. package/src/providers/linear.ts +301 -0
  45. package/src/providers/mixpanel.ts +15 -7
  46. package/src/providers/notion.ts +1879 -0
  47. package/src/providers/stripe.ts +306 -0
  48. package/src/version.ts +1 -1
  49. package/templates/node/package.json +1 -1
@@ -0,0 +1,233 @@
1
+ # Notion prebuilt connection
2
+
3
+ Import `notion()` independently from `@zackbart/connecta/providers/notion`. It
4
+ is a hand-written `api()` surface over Notion's public REST API — fifteen
5
+ deliberate tools, lean projections of Notion's famously bloated payloads, typed
6
+ failures, a rate budget matched to the documented limit, and a required usage
7
+ guide. It adds no provider dependency, imports no `node:` builtin, and is not
8
+ reachable from Connecta's root entry.
9
+
10
+ ```ts
11
+ import { notion } from "@zackbart/connecta/providers/notion";
12
+
13
+ const wiki = notion("engineering_wiki", {
14
+ title: "Engineering wiki",
15
+ purpose: "Runbooks, specs, and on-call notes for the platform team",
16
+ instructions: "Prefer the Runbooks database; specs live under Projects.",
17
+ });
18
+ ```
19
+
20
+ The `id` owns the ordinary connector namespaces; use a different id for every
21
+ Notion workspace. `purpose` is required because an agent choosing between two
22
+ instances needs to know which workspace answers the question. Workspace
23
+ `instructions` are appended to the maintained guide and cannot change the
24
+ connector's safety classification.
25
+
26
+ ## Why this one is `api()` and not `remoteMcp()`
27
+
28
+ Notion publishes an MCP server, but the interesting problem here is not
29
+ transport — it is shape. A single Notion page returns every property as a
30
+ discriminated wrapper object, every string as an array of rich-text runs each
31
+ carrying its own annotations block, and every user reference as a nested
32
+ object. A twenty-five row database query is tens of kilobytes of structure
33
+ around a few hundred bytes of meaning. Hand-writing the surface is what makes
34
+ the projections possible, and the projections are the point.
35
+
36
+ ## Authentication
37
+
38
+ One operator-managed credential: an internal integration token from
39
+ [notion.so/profile/integrations](https://www.notion.so/profile/integrations).
40
+ The deployment needs `credentials.encryptionKey` configured, or the token
41
+ cannot be stored and every call fails `auth_required` at use.
42
+
43
+ Two Notion-specific facts decide whether a working token is enough:
44
+
45
+ - **Sharing is per object.** A token reaches only what has been explicitly
46
+ shared with its integration. An unshared page returns 404, not 403.
47
+ - **Capabilities are per integration**, and the comment capabilities are off by
48
+ default. `list_comments` and `add_comment` fail with 403 until an operator
49
+ turns them on in Notion.
50
+
51
+ `/credentials` offers a Test action, which calls `GET /v1/users/me` — the
52
+ cheapest call that proves a token is live — and reports the workspace it
53
+ authenticated into.
54
+
55
+ ## The pinned API version
56
+
57
+ The connection pins `Notion-Version: 2026-03-11` and offers no override. That
58
+ is deliberate rather than lazy. Notion's versions are date-named and old ones
59
+ keep working indefinitely, so an override would look harmless; it is not.
60
+ `2026-03-11` is the version in which databases split into data sources,
61
+ `archived` became `in_trash`, and block append took a `position` object instead
62
+ of an `after` string. Every projection and write body here assumes those
63
+ shapes, so a deployment that pinned an older version would get quietly wrong
64
+ results instead of a loud failure.
65
+
66
+ Notion also ships *additive* changes to every version simultaneously, which is
67
+ why the property and block projections fall back to unwrapping an unknown
68
+ type's payload rather than switching exhaustively. A property type that ships
69
+ after this release degrades to its raw value, and a block type that does keeps
70
+ its payload under `raw`; neither vanishes.
71
+
72
+ ## Tools
73
+
74
+ Ten reads, all annotated `readOnlyHint: true`:
75
+
76
+ | Tool | What it is for |
77
+ | --- | --- |
78
+ | `search` | Find pages and data sources by **title**. Never searches content. |
79
+ | `get_page` | One page's metadata and flattened property values. |
80
+ | `get_page_content` | A page's body as flat blocks reduced to plain text. |
81
+ | `get_page_property` | One property in full, past the 25-entry truncation. |
82
+ | `get_database` | A database container and the data sources inside it. |
83
+ | `get_data_source_schema` | Property ids, types, and select/status options. |
84
+ | `query_data_source` | Filtered, sorted rows with properties already flattened. |
85
+ | `list_users` | Workspace users and bots with their ids. |
86
+ | `get_self` | Which integration and workspace this connector authenticates as. |
87
+ | `list_comments` | Unresolved comments as plain text with discussion ids. |
88
+
89
+ Five writes, none read-only, so all of them route through
90
+ `call_destructive_tool`:
91
+
92
+ | Tool | Classification |
93
+ | --- | --- |
94
+ | `create_page` | additive |
95
+ | `append_blocks` | additive |
96
+ | `add_comment` | additive |
97
+ | `update_page_properties` | `destructiveHint: true` — replaces existing values |
98
+ | `trash_page` | `destructiveHint: true` — removes a page from reads |
99
+
100
+ The additive three leave `destructiveHint` unset: `readOnlyHint: false` already
101
+ routes them through the approval path, and claiming a create destroys something
102
+ only inflates the copy a host shows a human. `update_page_properties`
103
+ deliberately has no `in_trash` argument, so an update can never trash a page by
104
+ accident; trashing is its own named, reversible tool.
105
+
106
+ ## Lean projections, and the raw escape hatch
107
+
108
+ Every read projects. A page becomes ids, plain text, and flattened values:
109
+ `title` and `rich_text` collapse to strings, `select` and `status` to their
110
+ option name, `multi_select` to an array of names, `relation` to an array of
111
+ page ids, `people` to `{ id, name }`, `unique_id` to `"RL-12"`, and
112
+ `formula`/`rollup` to their computed value. No `plain_text` runs, no
113
+ `annotations` blocks, no property wrappers survive.
114
+
115
+ Two projections are opinionated enough to call out:
116
+
117
+ - **`search` returns identity fields only** — no properties at all. A
118
+ twenty-five result search across a populated database would otherwise drag
119
+ back several hundred flattened values for results the agent is about to
120
+ discard. `get_page` fetches properties for the one that matched.
121
+ - **`get_page` reports what Notion hid.** Notion paginates four property types
122
+ — `title`, `rich_text`, `relation`, and `people` — cutting each off at 25
123
+ entries and signalling it only with a `has_more` flag on the property itself.
124
+ The projection surfaces those in `truncated_properties` as `{ name, id }`,
125
+ which is what stops an agent from confidently reasoning about 25 of 300
126
+ relations. The `id` is there because the handoff needs it: `get_page_property`
127
+ addresses a property by id, not by name.
128
+
129
+ Where the dropped detail can matter — `search`, `get_page`, `get_page_content`,
130
+ `get_page_property`, `get_data_source_schema`, `query_data_source`,
131
+ `list_comments` — `raw: true` returns Notion's untouched response instead. It is
132
+ much larger; it exists so a missing field is never a dead end. When the goal is
133
+ *fewer* fields rather than more, `get_page` and `query_data_source` also take a
134
+ `properties` array to project only the named ones.
135
+
136
+ One caveat with `get_page_content`: `raw: true` returns the requested level
137
+ exactly as Notion sent it and does not walk nested children, so `depth` is
138
+ ignored alongside it. A raw read of a deep page yields one level, not three.
139
+ Unmodelled *block* types are covered without it — a block whose type this
140
+ projection does not know, and whose payload is not plain rich text, keeps that
141
+ payload verbatim under `raw` on the block, so nothing collapses to an empty
142
+ string.
143
+
144
+ ## Databases contain data sources
145
+
146
+ This is the trap the guide is marked `required` for. A Notion database is a
147
+ container; the rows and the schema live in a *data source* inside it, and the
148
+ two ids are not interchangeable. The id in a database's URL is a **database
149
+ id**, and passing it to `query_data_source` fails. The sequence is
150
+ `get_database` → `get_data_source_schema` → `query_data_source`; `search`
151
+ returns data sources directly and skips the first step.
152
+
153
+ `create_page` needs the same distinction: a row is created under
154
+ `parent_data_source_id`, never a database id. Its title also needs
155
+ `title_property` from the schema, because a database's title column is rarely
156
+ called "title".
157
+
158
+ ## Typed failures
159
+
160
+ The mapping is deliberately not one-to-one with Notion's error codes. Notion's
161
+ `code` says what its API thinks happened; Connecta's says what the caller
162
+ should do next, and two of Notion's are easy to mistranslate.
163
+
164
+ | Notion | Connecta | Why |
165
+ | --- | --- | --- |
166
+ | 400 (`validation_error`, `invalid_json`, `invalid_request`, `missing_version`, …) | `invalid_args` | every documented 400 is a malformed request |
167
+ | 401 `unauthorized` | `auth_required` | the token is missing or invalid |
168
+ | 403 `restricted_resource` | `connector_call_failed`, non-retryable | **not** `auth_required` |
169
+ | 404 `object_not_found` | `connector_call_failed`, non-retryable | overloaded; see below |
170
+ | 409 `conflict_error` | `unavailable`, retryable | Notion says to retry |
171
+ | 429 `rate_limited` | `rate_limited` + `retryAfterMs` | `Retry-After` seconds → ms |
172
+ | 529 `service_overload` | `unavailable` + `retryAfterMs` | back off like a 429 |
173
+ | 5xx | `unavailable`, retryable | upstream failure |
174
+
175
+ The two that matter:
176
+
177
+ **403 is not an authentication failure.** The token is fine; the integration
178
+ lacks a capability or was never shared the object. Routing it to
179
+ `auth_required` would send an agent to `authorize_connector`, which cannot
180
+ grant a Notion capability or share a page. It is a non-retryable call failure
181
+ whose message says an operator must change it in Notion.
182
+
183
+ **404 does not prove absence.** Notion returns `object_not_found` both for an
184
+ object that does not exist and for one that exists but has not been shared with
185
+ the integration, and it will not say which. The message says both, because
186
+ treating it as deletion is exactly how an agent concludes a page is gone when
187
+ it was simply never shared.
188
+
189
+ ## Rate limiting
190
+
191
+ Notion documents "an average of three requests per second, with some bursts
192
+ beyond the average allowed" per connection, plus a separate per-workspace limit
193
+ scaled to the plan. The connection declares a rolling budget of 180 calls per
194
+ minute — the same average expressed over a window short bursts pass and a
195
+ sustained loop does not — **paired with `maxConcurrency: 3`**.
196
+
197
+ The concurrency cap is the load-bearing half. A budget alone is an average, and
198
+ an averaged budget cannot stop a program from firing forty calls in the same
199
+ tick; the cap keeps a burst shaped roughly like the one Notion documents.
200
+ Neither half is a guarantee, because admission meters *tool calls* rather than
201
+ requests — a single admitted `get_page_content` can spend twenty fetches, so
202
+ 180 calls per minute is a floor on the real request rate, not a ceiling.
203
+ Declaring the cap is also what makes the queue settings legal — the admission
204
+ controller refuses queue settings without a queue at construction.
205
+
206
+ Like every connector budget this is per-runtime: N Worker isolates or Node
207
+ processes serving one deployment each keep their own counter. It approximates
208
+ the provider's limit; it does not enforce it.
209
+
210
+ One tool can turn a single agent call into several downstream requests:
211
+ `get_page_content` with `depth > 0` walks nested blocks, and call admission
212
+ meters tool calls, not the fetches inside them. That walk stops at an internal
213
+ ceiling of twenty requests and reports `truncated: true` rather than spending
214
+ the whole budget invisibly.
215
+
216
+ ## Pagination
217
+
218
+ List-shaped tools take `page_size` (1–100) and `start_cursor`, and return
219
+ `has_more` with `next_cursor`. The default page size is 25 rather than Notion's
220
+ 100, because a first read should be cheap; `defaultPageSize` raises it for a
221
+ deployment that pages a lot.
222
+
223
+ Cursors are opaque. Notion's own versioning page is explicit that they may
224
+ change in length, format, and structure at any time and must be passed back
225
+ verbatim — never parsed, validated, or constructed.
226
+
227
+ ## What this connection does not do
228
+
229
+ No file uploads, no database or data-source creation, no schema editing, no
230
+ block updates or deletes, no page moves. Those are all real Notion endpoints
231
+ and all deliberately absent: this is a deliberate tool surface, not a mirror of
232
+ the API. Anything missing is reachable through a custom `api()` connector
233
+ beside this one, which remains a first-class path.
@@ -0,0 +1,202 @@
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 a required
5
+ production/sandbox mode, OAuth by default, a mode-scaled 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_live", {
13
+ mode: "production",
14
+ title: "Stripe (production)",
15
+ purpose: "Revenue, disputes, and refunds for the real business",
16
+ instructions: "Never refund above $500 without a human in the loop.",
17
+ });
18
+ ```
19
+
20
+ The `id` owns the ordinary connector namespaces; use a different id for every
21
+ Stripe account. `purpose` is required because an agent choosing between two
22
+ instances needs to know which account answers the question. Account
23
+ `instructions` are appended to the maintained guide and cannot change the
24
+ connector's safety classification.
25
+
26
+ ## Mode is required, and it is the whole point
27
+
28
+ `mode` accepts `"production"` or `"sandbox"` and has **no default**. There is no
29
+ safe guess between an account that moves real money and one that does not, so
30
+ the deployment has to say which it configured.
31
+
32
+ Stripe publishes exactly one endpoint — `https://mcp.stripe.com/` — and the
33
+ environment is selected by the credential, not the URL. Connecta therefore
34
+ cannot *route* by mode; what it can do is make the mode impossible for an agent
35
+ to miss, and refuse a deployment whose declaration and credential disagree.
36
+
37
+ `mode` shows up in four places an agent actually reads:
38
+
39
+ - the default `title` (`Stripe (production)` / `Stripe (sandbox)`);
40
+ - the `description`, which is what `search_tools` ranks and returns — production
41
+ reads `Stripe payments (production — live money and real customers) — …`,
42
+ sandbox reads `Stripe payments (sandbox — test data, no real money) — …`;
43
+ - the first two lines of the usage guide, which state the mode and then say
44
+ either "every write moves real money … a refund cannot be undone" or "never
45
+ answer a question about live revenue, payouts, or a named customer from this
46
+ connector";
47
+ - the admission policy, below.
48
+
49
+ And one place a deployment author reads: if `auth` is a `headers` credential
50
+ carrying a recognizable Stripe key prefix (`sk_`, `rk_`, or `pk_` with `_live_`
51
+ or `_test_`), construction throws when the key's mode contradicts the declared
52
+ one. That check reads nothing it cannot classify — an OAuth connector, or a
53
+ credential shape this release does not recognize, is left alone rather than
54
+ guessed at — and the error names only the two modes, never the key.
55
+
56
+ Deploy both side by side. Two instances are isolated exactly like two
57
+ hand-written connectors with different ids: separate addresses, catalogs,
58
+ credentials, storage, admission counters, and health.
59
+
60
+ ```ts
61
+ connectors: [
62
+ stripe("stripe_live", {
63
+ mode: "production",
64
+ purpose: "Revenue, disputes, and refunds for the real business",
65
+ }),
66
+ stripe("stripe_sandbox", {
67
+ mode: "sandbox",
68
+ purpose: "Rehearsing billing changes before they touch production",
69
+ }),
70
+ ]
71
+ ```
72
+
73
+ ## Authentication
74
+
75
+ OAuth is the default and the option Stripe recommends: it supports dynamic
76
+ client registration and PKCE, and each connector instance keeps its own flow
77
+ and tokens in connector-scoped storage. Stripe also accepts a
78
+ [restricted API key](https://docs.stripe.com/keys#create-restricted-api-key) as
79
+ a bearer token for headless agents:
80
+
81
+ ```ts
82
+ stripe("stripe_sandbox", {
83
+ mode: "sandbox",
84
+ purpose: "Automated billing rehearsal",
85
+ auth: {
86
+ type: "headers",
87
+ headers: { Authorization: `Bearer ${env.STRIPE_RESTRICTED_KEY}` },
88
+ },
89
+ });
90
+ ```
91
+
92
+ Use a restricted key, not a secret key, and scope it to the operations the
93
+ agent actually needs; Stripe's own guidance is to "limit your agent's access to
94
+ exactly the functionality it requires". Keep it in the runtime's secret store.
95
+
96
+ Connect platforms can act as a connected account with `connectedAccount`, which
97
+ adds Stripe's `Stripe-Account` header. Stripe does not support OAuth for
98
+ connected-account calls, so this requires `headers` auth and throws otherwise:
99
+
100
+ ```ts
101
+ stripe("merchant_42", {
102
+ mode: "production",
103
+ purpose: "Billing questions for the merchant on account 42",
104
+ connectedAccount: "acct_1234567890",
105
+ auth: {
106
+ type: "headers",
107
+ headers: { Authorization: `Bearer ${env.STRIPE_PLATFORM_KEY}` },
108
+ },
109
+ });
110
+ ```
111
+
112
+ Administrators must enable MCP access in the Stripe Dashboard, and Stripe
113
+ manages that setting **separately for sandbox and live mode**. A connector that
114
+ boots but cannot list tools is usually a dashboard toggle, not a bad key.
115
+
116
+ ## The eleven tools, and what they are classified as
117
+
118
+ Stripe documents eleven tools on the hosted server. Seven are reads:
119
+
120
+ `stripe_api_search`, `stripe_api_details`, `stripe_api_read`,
121
+ `get_stripe_account_info`, `get_balance_summary`,
122
+ `search_stripe_documentation`, `stripe_implementation_planner`.
123
+
124
+ Four are writes:
125
+
126
+ `stripe_api_write` and `create_refund` are classified destructive;
127
+ `stripe_report` and `send_stripe_mcp_feedback` are additive.
128
+
129
+ Two of those deserve a sentence. `stripe_api_read` is a read because Stripe
130
+ documents it as the `GET` half of a generic pair — the tool is the read
131
+ boundary, not whichever endpoint an agent names inside it, and its sibling
132
+ `stripe_api_write` carries every `POST`, `PATCH`, `PUT`, and `DELETE`.
133
+ `create_refund` is filed destructive despite its name: it reverses a settled
134
+ charge and moves money back out, which is a mutation of something that already
135
+ exists rather than a fresh object appearing beside it. Additive writes
136
+ (`stripe_report`, `send_stripe_mcp_feedback`) leave `destructiveHint` unset;
137
+ `readOnlyHint: false` already routes them through `call_destructive_tool`, and
138
+ asserting destruction only inflates the approval copy the host shows a human.
139
+
140
+ That classification is **fill-in only**, and unconditionally so: it supplies
141
+ the annotations Stripe leaves unset — Stripe documents no MCP annotations at
142
+ all — and contradicts an explicit downstream annotation in neither direction. A
143
+ tool on the read allowlist arriving with `destructiveHint: true` or
144
+ `readOnlyHint: false` keeps exactly what the downstream said and stays behind
145
+ `call_destructive_tool`. A tool on neither maintained list arriving with
146
+ `readOnlyHint: true` keeps that too, and stays callable from `execute_code`.
147
+ Both are the downstream telling you this release's allowlist is stale, and on a
148
+ name no release has reviewed its word is the only evidence there is. The one
149
+ classification that still outranks the downstream is a name this release
150
+ reviewed and filed destructive: a `create_refund` claiming `readOnlyHint: true`
151
+ is a downstream bug rather than news, and stays on the approval path.
152
+
153
+ An unfamiliar tool that annotates nothing fails closed onto
154
+ `call_destructive_tool` until a Connecta release reviews it. That is not
155
+ hypothetical here: Stripe's own MCP page still carries a `create_customer`
156
+ example that its tool table no longer lists. Whatever the server actually
157
+ serves, an unclassified and unannotated `create_customer` lands on the approval
158
+ path. Expect the undocumented Treasury tools Stripe alludes to to arrive
159
+ unclassified as well — annotated ones will be taken at their word.
160
+
161
+ Stripe publishes no stability or deprecation policy for this tool set and
162
+ invites tool requests by email, so treat the list as unversioned. `get_balance_summary`
163
+ is Treasury, which Stripe labels public preview and gates behind an access
164
+ request — expect it to be absent unless the account is allowlisted, and expect
165
+ the other Treasury tools Stripe alludes to but does not document to arrive
166
+ unclassified.
167
+
168
+ ## Rate limits
169
+
170
+ Stripe documents no rate limit specific to the MCP server. The connection
171
+ therefore transcribes the account limit that MCP traffic spends
172
+ ([rate limits](https://docs.stripe.com/rate-limits)): **100 requests per second
173
+ in live mode, 25 in a sandbox**, and any single endpoint is capped at 25 per
174
+ second regardless of mode, so paging one list is the real constraint. The
175
+ `maxConcurrency` beside it — 8 for
176
+ production, 4 for sandbox — is Connecta's own conservative choice: Stripe
177
+ documents that per-account and per-endpoint concurrency limits exist, and
178
+ surface as `429` with a `Stripe-Rate-Limited-Reason` of `global-concurrency` or
179
+ `endpoint-concurrency`, but publishes no number.
180
+
181
+ As with every connector policy this is a **best-effort approximation** of the
182
+ provider's limit, not an enforcement of it. Each runtime keeps its own counter,
183
+ so N Worker isolates or Node processes serving one deployment can each admit up
184
+ to the stated rate, and the same Stripe account may be spending its budget on
185
+ traffic Connecta never sees. Discovery traffic is outside connector call
186
+ admission and still needs restrained use.
187
+
188
+ ## What is not verified
189
+
190
+ Stripe's MCP documentation is silent on two things this connection had to reason
191
+ about rather than read:
192
+
193
+ - **How an OAuth session resolves to live versus sandbox at call time.** Stripe
194
+ says sessions are "scoped to … the current environment (live mode or a
195
+ sandbox)" and that dashboard access is managed separately per environment, but
196
+ never states the mechanism. The key-prefix check covers `headers` auth only;
197
+ for OAuth, `mode` is a declaration Connecta surfaces and cannot verify.
198
+ - **Whether pagination cursors and `Idempotency-Key` are passable through
199
+ `stripe_api_read` / `stripe_api_write`.** The conventions in the usage guide
200
+ are Stripe's documented API conventions; how they thread through the generic
201
+ tools' arguments is not documented. The guide states them because an agent
202
+ that ignores them is wrong either way.
package/ethos.md CHANGED
@@ -70,6 +70,7 @@ proposing one without a new argument is not.
70
70
  | Policy engine, approvals, pauses | refused | the host asks the human; connecta only annotates |
71
71
  | Runtime connector registration | refused | config-as-code is the security model |
72
72
  | Prebuilt connections as the preferred authoring path | accepted | an a-la-carte provider constructor, imported and constructed in the deployment file, encodes maintained defaults for providers connecta actually uses — preferred *when maintained*, with no promise of one per provider; it returns exactly one ordinary `Connector` with no extra privileges — never a bundle, a group, a preset, or a registry — its tools are hand-written or proxied from a downstream MCP catalog, never generated from a schema document; its vetted annotations classify what the downstream leaves unannotated and never overrule an explicit one; `remoteMcp()` and `api()` stay first-class ([#297](https://github.com/zackbart/connecta/issues/297)) |
73
+ | Guarded raw REST escape hatches in a prebuilt connection | accepted | a large, fast-moving provider cannot be honestly represented by a small frozen list: a GET-only tool may expose provider-relative reads, while JSON mutations and explicit-content uploads stay separate and always cross the destructive boundary; the connector owns authentication, rate limits, error mapping, URL confinement, and safe method classification, while the provider token remains the capability boundary — this is not schema ingestion, runtime connector registration, or permission widening |
73
74
  | Provider registry / integration marketplace | refused | prebuilt connections are imports, not listings; discovery happens in documentation, never at runtime ([#297](https://github.com/zackbart/connecta/issues/297)) |
74
75
  | Protocol sessions & server push | refused | stateless per request |
75
76
  | Resources & prompts aggregation | refused | tools only; connecta's own Apps shell is the one `resources/read` carve-out ([#266](https://github.com/zackbart/connecta/issues/266)) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zackbart/connecta",
3
- "version": "0.13.0",
3
+ "version": "0.14.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.",
@@ -65,9 +65,25 @@
65
65
  "types": "./dist/auth/clerk.d.ts",
66
66
  "import": "./dist/auth/clerk.js"
67
67
  },
68
+ "./providers/cloudflare": {
69
+ "types": "./dist/providers/cloudflare.d.ts",
70
+ "import": "./dist/providers/cloudflare.js"
71
+ },
72
+ "./providers/linear": {
73
+ "types": "./dist/providers/linear.d.ts",
74
+ "import": "./dist/providers/linear.js"
75
+ },
68
76
  "./providers/mixpanel": {
69
77
  "types": "./dist/providers/mixpanel.d.ts",
70
78
  "import": "./dist/providers/mixpanel.js"
79
+ },
80
+ "./providers/notion": {
81
+ "types": "./dist/providers/notion.d.ts",
82
+ "import": "./dist/providers/notion.js"
83
+ },
84
+ "./providers/stripe": {
85
+ "types": "./dist/providers/stripe.d.ts",
86
+ "import": "./dist/providers/stripe.js"
71
87
  }
72
88
  },
73
89
  "scripts": {
@@ -834,13 +834,42 @@ export class CatalogService {
834
834
  required: connectorGuideRequired(scopedConnector),
835
835
  }
836
836
  : undefined;
837
+ // A scope that resolved to nothing is the same silence one step earlier in
838
+ // the lookup: no connector resolved, so no catalog was even attempted, so
839
+ // no catalog failed and the unavailable path below never fires. Echo only
840
+ // the ID the caller already supplied — naming what else is configured
841
+ // would answer a question they did not ask, past a filter they may not
842
+ // pass.
843
+ const unknownConnectorGuidance =
844
+ args.connector && !scopedConnector
845
+ ? `Connector "${args.connector}" is not configured in this deployment. Omit connector to search all configured tools.`
846
+ : undefined;
847
+ // Term-bearing searches report analysis only when the scorer had to
848
+ // degrade; a browse has no terms to analyse and normally reports none at
849
+ // all. But neither "this catalog is unavailable" nor "there is no such
850
+ // connector" is a statement about terms, and answering either browse with
851
+ // an empty entry list alone is indistinguishable from a connector that
852
+ // simply exposes no tools. The term partitions stay empty on those paths
853
+ // because there were no terms — the scope fields carry the whole message.
854
+ const reportsQueryAnalysis =
855
+ queryTerms.length > 0
856
+ ? matchMode === "partial"
857
+ : unknownConnectorGuidance !== undefined || unavailableCatalogs > 0;
837
858
  const guidance =
838
859
  queryTerms.length === 0
839
- ? undefined
840
- : matches.length === 0
841
- ? args.connector && !scopedConnector
842
- ? `Connector "${args.connector}" is not configured in this deployment. Omit connector to search all configured tools.`
860
+ ? // A browse has no terms to advise about, so it stays silent unless
861
+ // the scope itself failed: the guidance on a scoped miss recommends
862
+ // browsing with an empty query, and that advice must not lead into a
863
+ // dead end that looks like a connector with no tools.
864
+ (unknownConnectorGuidance ??
865
+ (unavailableCatalogs === 0
866
+ ? undefined
843
867
  : scopedConnector
868
+ ? `Connector "${scopedConnector.id}" could not be browsed because its catalog was unavailable. Inspect catalogError for the typed reason and recovery detail.`
869
+ : `${unavailableCatalogs} connector catalog${unavailableCatalogs === 1 ? " was" : "s were"} unavailable, so this browse is incomplete. Scope by connector to see the typed reason.`))
870
+ : matches.length === 0
871
+ ? (unknownConnectorGuidance ??
872
+ (scopedConnector
844
873
  ? unavailableCatalogs > 0
845
874
  ? `Connector "${scopedConnector.id}" could not be searched because its catalog was unavailable. Inspect catalogError for the typed reason and recovery detail.`
846
875
  : scopedGuide?.required
@@ -848,7 +877,7 @@ export class CatalogService {
848
877
  : `No matching ${safetyLabel}capability was found on connector "${scopedConnector.id}". Refine terms or browse it with an empty query.${filterRecovery}`
849
878
  : unavailableCatalogs === 0
850
879
  ? `No matching ${safetyLabel}capability is configured in this deployment. Refine terms, scope by connector, or browse with an empty query.${filterRecovery}`
851
- : `No matching ${safetyLabel}capability was found in the catalogs that answered; ${unavailableCatalogs} connector catalog${unavailableCatalogs === 1 ? " was" : "s were"} unavailable. Refine terms, scope by connector, or browse with an empty query.${filterRecovery}`
880
+ : `No matching ${safetyLabel}capability was found in the catalogs that answered; ${unavailableCatalogs} connector catalog${unavailableCatalogs === 1 ? " was" : "s were"} unavailable. Refine terms, scope by connector, or browse with an empty query.${filterRecovery}`))
852
881
  : matchMode === "partial"
853
882
  ? scopedConnector
854
883
  ? `No single tool on connector "${scopedConnector.id}" matched every term. Split distinct intents into separate searches.`
@@ -866,7 +895,7 @@ export class CatalogService {
866
895
  ...(matchMode === "partial" && matches.length > 0
867
896
  ? { matchMode }
868
897
  : {}),
869
- ...(queryTerms.length > 0 && matchMode === "partial"
898
+ ...(reportsQueryAnalysis
870
899
  ? {
871
900
  queryAnalysis: {
872
901
  representedTerms,
@@ -149,7 +149,11 @@ export function api(id: string, opts: ApiOptions): Connector {
149
149
  });
150
150
  if (invalid) throw invalid;
151
151
  }
152
- return tool.handler(input, ctx);
152
+ // `await` (not a bare promise return) so a handler that throws before
153
+ // its first await never sits handler-less for the thenable-adoption
154
+ // microtask — workerd and vitest both report that gap as an unhandled
155
+ // rejection even though the caller catches the failure.
156
+ return await tool.handler(input, ctx);
153
157
  },
154
158
  };
155
159
  }
package/src/execute.ts CHANGED
@@ -1351,7 +1351,7 @@ export function registerExecuteTool(
1351
1351
  code: z
1352
1352
  .string()
1353
1353
  .describe(
1354
- "One complete JavaScript async arrow function. Consume search/describe results and finish the task inside it; returning catalog data for a later call spends a round trip and buys nothing.",
1354
+ "One complete JavaScript async arrow function. Consume search/describe results and finish the task inside it; returning catalog data for a later call spends a round trip and buys nothing. So does aborting on a missing tool match or result key — re-search, describe, or read the result's actual keys here instead.",
1355
1355
  ),
1356
1356
  diagnostics: z
1357
1357
  .boolean()