@zackbart/connecta 0.22.0 → 0.22.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.
package/dist/version.d.ts CHANGED
@@ -4,4 +4,4 @@
4
4
  * a bump that forgets this file fails the build rather than shipping a stale
5
5
  * version to `/health` and to downstream MCP handshakes.
6
6
  */
7
- export declare const CONNECTA_VERSION = "0.22.0";
7
+ export declare const CONNECTA_VERSION = "0.22.1";
package/dist/version.js CHANGED
@@ -4,4 +4,4 @@
4
4
  * a bump that forgets this file fails the build rather than shipping a stale
5
5
  * version to `/health` and to downstream MCP handshakes.
6
6
  */
7
- export const CONNECTA_VERSION = "0.22.0";
7
+ export const CONNECTA_VERSION = "0.22.1";
@@ -136,11 +136,11 @@ few things both shapes fully own:
136
136
 
137
137
  - [Hand-written HTTP providers](./provider-conventions.md#hand-written-http-providers)
138
138
  (H1–H14) — `api()` surfaces where Connecta owns every name, schema,
139
- projection, and error. Cloudflare and Notion.
139
+ projection, and error. Cloudflare, Notion, and Vercel.
140
140
  - [Hosted-MCP proxies](./provider-conventions.md#hosted-mcp-proxies) (P1–P13) —
141
141
  `remoteMcp()` wrappers where the downstream owns the catalog and Connecta
142
142
  owns the endpoint, credential, classification, guide, and budget. Linear,
143
- Stripe, and Mixpanel.
143
+ Stripe, Mixpanel, and RevenueCat.
144
144
 
145
145
  Both sets are judged by one measure: what the convention saves the model that
146
146
  interacts with connecta, priced in discovery tokens, wrong-tool selection,
@@ -137,6 +137,7 @@ Maintained provider guides:
137
137
  - [Notion](./notion.md)
138
138
  - [RevenueCat](./revenuecat.md)
139
139
  - [Stripe](./stripe.md)
140
+ - [Vercel](./vercel.md)
140
141
 
141
142
  ## The `api()` construction contract
142
143
 
@@ -226,12 +227,13 @@ into a result or a typed failure. That split is not fastidiousness. Notion's
226
227
  cannot fix it — while Cloudflare's means a token scope, and the two want
227
228
  opposite next moves. A helper that guessed would be wrong for one of them.
228
229
 
229
- Cloudflare and Notion both run on it. Their existing suites carried over
230
- unchanged, which proves the migration kept the behavior those suites cover
231
- not that nothing changed. Three things did, and the changelog names them: a
232
- 3xx is refused where both providers used to follow it, both now fail past
233
- their byte ceiling, and `cloudflare()`'s `baseUrl` is validated at
234
- construction. Each suite gained one test for the ceiling, because the one
230
+ Cloudflare, Notion, and Vercel run on it. The first two existing suites carried
231
+ over unchanged when the helper was extracted, which proves that migration kept
232
+ the behavior those suites cover — not that nothing changed. Three things did,
233
+ and the changelog names them: a 3xx is refused where both providers used to
234
+ follow it, both now fail past their byte ceiling, and `cloudflare()`'s
235
+ `baseUrl` is validated at construction. Each suite gained one test for the
236
+ ceiling, because the one
235
237
  guard the helper was written to add is the one a provider's own mapper can
236
238
  most easily disarm: a bare `catch` around `response.json()` swallows the
237
239
  transport's refusal along with a parse error, and turns a response nobody was
@@ -258,7 +258,7 @@ in.
258
258
  | `operator-boundary.test.ts` | the operator row of the decisions table, after every mutation route: authentication material managed without moving a declared structure, and the one honest exception — a credential write making a remote catalog appear, which is discovery arriving, not an operator editing the deployment |
259
259
  | `operator-store.test.ts` | `src/operator-ui/app/store.ts` against a fake browser: the Clerk listener, ambient Access requests without a browser-readable token, `gate()`, the generation fence, and the request path |
260
260
  | `provider-conventions.test.ts` | the conventions a test can hold: hand-written providers refusing schemas they cannot enforce (H5), their compact discovery schemas staying complete (H7), Cloudflare stating its second pagination convention in the schema (H10), and Notion saying it has no escape hatch (H14) |
261
- | `provider-registry.test.ts` | all six maintained providers inside real deployments: boot, description, address, catalog, storage, credential, admission, and activity isolation; plus provider-specific discovery and guide contracts |
261
+ | `provider-registry.test.ts` | all seven maintained providers inside real deployments: boot, description, address, catalog, storage, credential, admission, and activity isolation; plus provider-specific discovery and guide contracts |
262
262
  | `registry.test.ts` | construction and id validation, startup warnings, address resolution, version 2 catalog TTL/persistence/completeness, agent-only stale-while-revalidate with cross-request single-flight shared with blocking reads in both start orders, owned teardown, invalidation/fingerprint guards, blocking diagnostics, and broken-connector isolation |
263
263
  | `remote-mcp.test.ts` | `remoteMcp()` against an in-process server through the `_transportFactory` seam: passthrough, downstream `isError`, Workers-safe output-schema validation, request-scoped client reuse and at-most-once scope close; plus the real transport's manual redirect policy, destination guard, credential containment, and downstream session termination |
264
264
  | `remote-mcp-credential.test.ts` | `remoteMcp()` drawing a static key from `/credentials`: the declared slot and its refusal of named fields and bad header names, header framing (bearer, bare, and the two `Basic` forms) observed on the wire, an empty slot failing as `auth_required` rather than reaching the downstream, a value carrying a control character refused before framing and absent from every surface — `call_tool`, `status`, the Test result, the payload-free activity event, and the thrown error — rotation replacing the cached client and a connect already in flight while a wiped value fails the next call, the Test action's catalog probe and scope close, the cleartext-destination warning, and the vault and `authorize_connector` handoff end to end |
@@ -274,6 +274,7 @@ in.
274
274
  | `ui-credentials.test.ts` | credential-management routes: save, test, delete, validation, authentication, same-origin checks, and multi-field credential shapes |
275
275
  | `ui.test.ts` | the server shell and remaining `/ui/*` routes: gated `/ui/data` with broken-connector isolation and registry-owned catalog-observation containment, plus the URL safety gates |
276
276
  | `validate.test.ts` | `validateToolInput()` — a returned (not thrown) `invalid_args` naming the path, `additionalProperties: false` enforcement, per-schema validator caching, and an unusable schema passed through with one warning |
277
+ | `vercel-provider.test.ts` | `vercel()` construction, team scoping, project and deployment projections, finite build and runtime logs, value-safe environment variables, domains, lifecycle writes, REST hatches, typed failures, and credential test |
277
278
 
278
279
  ### Node-bound (`NODE_ONLY_SUITES`)
279
280
 
@@ -1,6 +1,6 @@
1
1
  # Provider conventions
2
2
 
3
- The six maintained prebuilt connections grew one at a time, and until now
3
+ The seven maintained prebuilt connections grew one at a time, and until now
4
4
  "excellent provider" meant whatever the last author thought. This document
5
5
  writes the judgment down so it can be argued with, audited, and reused.
6
6
 
@@ -8,7 +8,7 @@ There are two genuinely different provider shapes, and one convention set
8
8
  cannot honestly cover both:
9
9
 
10
10
  - **Hand-written HTTP providers** — `api()` surfaces where Connecta owns every
11
- tool name, schema, projection, and error. Today: Cloudflare, Notion.
11
+ tool name, schema, projection, and error. Today: Cloudflare, Notion, Vercel.
12
12
  - **Hosted-MCP proxies** — `remoteMcp()` wrappers around a server somebody else
13
13
  operates, where the names, schemas, results, and error prose arrive as they
14
14
  are. Today: Linear, Stripe, Mixpanel, RevenueCat.
@@ -85,7 +85,7 @@ other source with no description or no `inputSchema`.
85
85
  ## Hand-written HTTP providers
86
86
 
87
87
  Connecta owns the whole surface here, which means every miss is ours. These
88
- apply to `api()`-based prebuilt connections (Cloudflare, Notion) and are the
88
+ apply to `api()`-based prebuilt connections (Cloudflare, Notion, Vercel) and are the
89
89
  bar any future one is written to.
90
90
 
91
91
  None of them asks an author to re-derive transport safety. URL confinement,
@@ -646,11 +646,11 @@ evidence and nothing else: no tool is generated from one, which is the
646
646
  ## What the audit checks
647
647
 
648
648
  The provider audit ([#342](https://github.com/zackbart/connecta/issues/342))
649
- runs this document against each of the six providers and returns a verdict per
649
+ runs this document against each of the seven providers and returns a verdict per
650
650
  convention: **meets**, **misses** (with the fix), or **not applicable** (with
651
651
  the reason). A convention is never quietly skipped, and an accepted miss is
652
652
  recorded as a provider-specific exception with its argument, not left blank.
653
- Its six reports live in [provider-audit.md](https://github.com/zackbart/connecta/blob/main/records/provider-audit.md), and the
653
+ Its seven reports live in [provider-audit.md](https://github.com/zackbart/connecta/blob/main/records/provider-audit.md), and the
654
654
  mechanically checkable half of the hand-written bar runs on every test run in
655
655
  [`test/provider-conventions.test.ts`](https://github.com/zackbart/connecta/blob/main/test/provider-conventions.test.ts) —
656
656
  so a convention that was met once stays met, or fails loudly.
@@ -57,7 +57,7 @@ exist so far:
57
57
  | --- | --- | --- |
58
58
  | **pre-template** | before 0.10.2 | no `connecta init` existed; hand-written, or copied from the retired `examples/node` |
59
59
  | **A** | 0.10.2 – 0.15.1 | `.env.example`, `.gitignore`, `AGENTS.md`, `CLAUDE.md`, `README.md`, `package.json`, `src/index.ts`, `tsconfig.json` |
60
- | **B** | 0.16.0 – 0.22.0 | adds `.dockerignore`, `Dockerfile`, `docker-compose.yml`, and `src/file-activity.ts`; `src/index.ts` grows the four commented operator blocks; `.env.example` ships `CONNECTA_TOKEN=` empty |
60
+ | **B** | 0.16.0 – 0.22.1 | adds `.dockerignore`, `Dockerfile`, `docker-compose.yml`, and `src/file-activity.ts`; `src/index.ts` grows the four commented operator blocks; `.env.example` ships `CONNECTA_TOKEN=` empty |
61
61
 
62
62
  Generation A is a decade in template years and identifying it precisely does
63
63
  not matter, because you are about to reconstruct it exactly rather than guess
@@ -106,7 +106,7 @@ know what to preserve, once to know what to re-verify at the end.
106
106
  ### Bump the pin and install
107
107
 
108
108
  ```sh
109
- npm pkg set dependencies.@zackbart/connecta=0.22.0
109
+ npm pkg set dependencies.@zackbart/connecta=0.22.1
110
110
  npm install
111
111
  ```
112
112
 
@@ -130,7 +130,7 @@ Generate the *current* template beside the base you already made, into the same
130
130
  `$SCRATCH`:
131
131
 
132
132
  ```sh
133
- (cd "$SCRATCH" && npx @zackbart/connecta@0.22.0 init current)
133
+ (cd "$SCRATCH" && npx @zackbart/connecta@0.22.1 init current)
134
134
  ```
135
135
 
136
136
  You now have a three-way merge with a real base: `$SCRATCH/base` is what this
@@ -186,7 +186,7 @@ A deployment older than 0.10.2 has no base to diff against. Do not try to
186
186
  manufacture one. Instead:
187
187
 
188
188
  1. `SCRATCH=$(mktemp -d)`, then
189
- `(cd "$SCRATCH" && npx @zackbart/connecta@0.22.0 init current)` — there is no
189
+ `(cd "$SCRATCH" && npx @zackbart/connecta@0.22.1 init current)` — there is no
190
190
  `base` leg here, only the current template to read from.
191
191
  2. Copy `$SCRATCH/current` into the deployment file by file, **skipping
192
192
  `src/index.ts`**.
@@ -207,7 +207,7 @@ first, so cross them bottom-up: start at the oldest one still above this
207
207
  deployment's pin and work back up the page, because each boundary assumes the
208
208
  older ones are already done.
209
209
 
210
- ### 0.21.2 → 0.22.0
210
+ ### 0.21.2 → 0.22.1
211
211
 
212
212
  Connector and user policy remain config-as-code. If `identity.connectorAccess`
213
213
  is configured, every interactive human may now manage the authentication of
@@ -0,0 +1,194 @@
1
+ # Vercel
2
+
3
+ Import `vercel()` independently from `@zackbart/connecta/providers/vercel`.
4
+ It is a hand-written `api()` connection over Vercel's public REST API. The
5
+ connection owns 18 named operations and three provider-relative REST hatches.
6
+ It adds no provider dependency, imports no `node:` builtin, and is not reachable
7
+ from Connecta's root entry.
8
+
9
+ ```ts
10
+ import { vercel } from "@zackbart/connecta/providers/vercel";
11
+
12
+ const hosting = vercel("hosting", {
13
+ purpose: "Production web applications for the product team",
14
+ teamId: "team_1a2b3c4d5e6f7g8h9i0j1k2l",
15
+ });
16
+ ```
17
+
18
+ The deployment stores one Vercel access token in Connecta's credential vault.
19
+ Create the token in Vercel Account Settings under Tokens. Scope it to the
20
+ personal account or team this connection needs and give it an expiration date.
21
+ The operator UI's Test action calls `GET /v2/user` and reports the authenticated
22
+ username, email, name, or id. Connecta never probes it in the background.
23
+
24
+ ## Why this uses REST instead of Vercel MCP
25
+
26
+ Vercel MCP provides useful project, deployment, and log reads, but it does not
27
+ cover the public API. This connection keeps those common reads and adds project
28
+ domains, value-safe environment-variable management, deployment promotion and
29
+ deletion, and direct access to versioned REST endpoints. The three hatches mean
30
+ a newly published Vercel endpoint does not require a Connecta release before an
31
+ agent can use it.
32
+
33
+ This is still authored rather than generated. No OpenAPI document creates tools
34
+ at runtime. The named operations are reviewed, projected, classified, and
35
+ tested by hand. The published OpenAPI document is used only by
36
+ `npm run drift:check` to compare the 19 named endpoints this connection calls.
37
+
38
+ ## Configuration
39
+
40
+ ```ts
41
+ vercel("hosting", {
42
+ title: "Production hosting",
43
+ authScope: "shared",
44
+ purpose: "Customer-facing sites owned by Platform",
45
+ teamId: "team_...",
46
+ defaultPageSize: 20,
47
+ instructions: "Never promote the docs project from this connection.",
48
+ maxResultBytes: 512_000,
49
+ callAdmission: {
50
+ rules: [{ maxConcurrency: 6 }],
51
+ },
52
+ });
53
+ ```
54
+
55
+ `purpose` is required and blank text throws at construction. `teamId` is a
56
+ default, not a hidden lock. Named account-scoped tools accept a `teamId`
57
+ override; pass `null` to target the token owner's personal account explicitly.
58
+ Without a configured default or an override, Vercel uses that personal account.
59
+ `list_teams` returns the ids needed to reach a team.
60
+
61
+ `defaultPageSize` defaults to 20 and must be a whole number from 1 through 100.
62
+ Vercel meters endpoints separately, so the connection invents no global request
63
+ budget. A deployment may supply `callAdmission` when it has its own concurrency
64
+ or call-rate requirement.
65
+
66
+ `baseUrl` exists for a test double or an HTTPS proxy. The guarded transport
67
+ confines every path under that base, refuses redirects, prevents request headers
68
+ from replacing `Authorization`, passes `ctx.signal`, and stops reading at 8 MiB.
69
+ The runtime-log read also returns at most 500 rows and stops a stream that stays
70
+ open past 10 seconds. HTTP is accepted only for a loopback test double.
71
+
72
+ ## Named tools
73
+
74
+ | Tool | What it does |
75
+ | --- | --- |
76
+ | `list_teams` | Lists teams the token can reach. |
77
+ | `list_projects` | Searches or lists lean project summaries. |
78
+ | `get_project` | Reads build settings, Git identity, and the production deployment. |
79
+ | `list_deployments` | Filters deployments by project, target, state, branch, or SHA. |
80
+ | `get_deployment` | Reads one deployment by id or hostname. |
81
+ | `get_build_logs` | Reads at most 1,000 existing build events with live following disabled. |
82
+ | `get_runtime_logs` | Reads a 1–500 row runtime-log snapshot and stops a stream open past 10 seconds. |
83
+ | `list_project_domains` | Lists verification, redirect, branch, and custom-environment state. |
84
+ | `add_project_domain` | Adds a regular, redirect, branch, or custom-environment domain. |
85
+ | `verify_project_domain` | Rechecks a pending domain after its DNS challenge is complete. |
86
+ | `remove_project_domain` | Removes a project domain, optionally with domains redirecting to it. |
87
+ | `list_project_env_vars` | Lists metadata without asking Vercel to decrypt values. |
88
+ | `upsert_project_env_var` | Creates or replaces one variable. |
89
+ | `update_project_env_var` | Patches one variable by its id. |
90
+ | `delete_project_env_var` | Removes one variable from future deployments. |
91
+ | `promote_deployment` | Promotes an existing build to production without rebuilding. |
92
+ | `cancel_deployment` | Cancels work that is queued, initializing, or building. |
93
+ | `delete_deployment` | Permanently removes a deployment and its URL. |
94
+
95
+ Every read returns a lean projection by default. Projects drop security,
96
+ billing, and presentation settings. Deployments keep state, target, timestamps,
97
+ creator, and Git identity. Domain reads keep the verification challenge because
98
+ dropping it would make an unverified result unusable. The project, deployment,
99
+ domain, and build-log reads accept `raw: true` when a Vercel field omitted by
100
+ the projection matters.
101
+
102
+ `raw: true` preserves unprojected list items while keeping the named tool's
103
+ declared envelope: list calls still return their item key and `page`, and build
104
+ logs still return `{ events }`.
105
+
106
+ ## Environment values are write-only here
107
+
108
+ `list_project_env_vars` sends `decrypt=false`, then drops `value` even if
109
+ Vercel returns one anyway. It returns the key, id, storage type, visibility,
110
+ targets, branch, custom-environment ids, comment, and timestamps. The create and
111
+ update tools accept a value as input, but their result projection drops it too.
112
+
113
+ That boundary is deliberate. An agent can audit placement and make a requested
114
+ change without filling its context with database URLs or API keys. Sensitive
115
+ values cannot be read back from Vercel in any case. The generic
116
+ `vercel_api_get` hatch returns the endpoint's untouched response, so a caller
117
+ that deliberately requests an endpoint capable of decrypting a non-sensitive
118
+ value has asked to cross the named tool's safer boundary.
119
+
120
+ Environment changes affect future deployments. They do not rewrite a value
121
+ already embedded in an existing deployment, and none of the environment tools
122
+ triggers a deployment on its own.
123
+
124
+ ## REST hatches
125
+
126
+ Vercel's API is too large and changes too often for every operation to deserve
127
+ a permanent named tool.
128
+
129
+ - `vercel_api_get` accepts only GET and is explicitly read-only.
130
+ - `vercel_api_mutate` accepts JSON POST, PUT, PATCH, and DELETE. It always
131
+ crosses `call_destructive_tool`.
132
+ - `vercel_api_upload` accepts POST or PUT with exactly one explicit UTF-8 or
133
+ base64 body. It also crosses `call_destructive_tool`.
134
+
135
+ All three take a path beginning with `/` and including Vercel's version, such
136
+ as `/v1/edge-config`. Query parameters are name/value rows rather than a string
137
+ to parse. They use the configured default team unless the caller passes
138
+ `personalAccount: true`; that flag cannot be combined with a `teamId` or `slug`
139
+ query row. The upload hatch accepts endpoint-specific headers such as a digest,
140
+ but refuses credential, cookie, host, content-type, content-length, and
141
+ transfer-encoding headers. It reads no local file. The caller supplies the
142
+ bytes, content type, and any checksum the endpoint requires.
143
+
144
+ Use a named tool when one exists. A named tool wins on argument validation,
145
+ result size, or safety routing. The hatch is for products such as Edge Config,
146
+ feature flags, drains, checks, security, and team settings that are not worth a
147
+ large permanent catalog.
148
+
149
+ ## Pagination
150
+
151
+ The four list families expose one connector-wide contract:
152
+
153
+ ```ts
154
+ {
155
+ items: [],
156
+ page: { hasMore: true, nextCursor: "opaque" }
157
+ }
158
+ ```
159
+
160
+ The item key is `teams`, `projects`, `deployments`, or `domains`. Pass
161
+ `nextCursor` back as `cursor` unchanged. Vercel uses different parameter names
162
+ and cursor types behind the four endpoints. The connector owns that mapping so
163
+ programs do not parse timestamps or branch on provider-specific pagination.
164
+
165
+ Environment-variable listing has no pagination in Vercel's published contract
166
+ and returns `{ variables }` without a false page object.
167
+
168
+ ## Typed failures
169
+
170
+ - HTTP 401 and 403 become `auth_required`. Vercel uses 403 both for a bad token
171
+ and for a token outside the requested team or operation scope.
172
+ - HTTP 404 becomes `not_found`. Re-list the owning project, deployment, domain,
173
+ or environment variable before using the id again.
174
+ - HTTP 400, 409, and 422 become `invalid_args`.
175
+ - HTTP 429 becomes `rate_limited`. `Retry-After` wins; otherwise the connector
176
+ derives the delay from `X-RateLimit-Reset`.
177
+ - HTTP 5xx becomes `unavailable`.
178
+
179
+ The error text keeps Vercel's error code and message. It never parses prose to
180
+ invent a class.
181
+
182
+ ## No SDK on purpose
183
+
184
+ The connection imports only Connecta modules and Web APIs. `@vercel/sdk` is not
185
+ a dependency or optional peer. Direct fetch keeps the root Workers-safe, avoids
186
+ shipping the generated model graph, and lets the reviewed named operations and
187
+ the REST hatches share one guarded transport.
188
+
189
+ The trade is API drift, handled explicitly. `scripts/drift/vercel-endpoints.json`
190
+ records the method, versioned path, specification revision, and request/response
191
+ digest for every fixed endpoint. Before a release, `npm run drift:check` compares
192
+ those rows with Vercel's published OpenAPI document at
193
+ `https://openapi.vercel.sh/`. The hatches are intentionally absent from that
194
+ list because their endpoint is chosen by deployment code at call time.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zackbart/connecta",
3
- "version": "0.22.0",
3
+ "version": "0.22.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.",
@@ -90,6 +90,10 @@
90
90
  "./providers/stripe": {
91
91
  "types": "./dist/providers/stripe.d.ts",
92
92
  "import": "./dist/providers/stripe.js"
93
+ },
94
+ "./providers/vercel": {
95
+ "types": "./dist/providers/vercel.d.ts",
96
+ "import": "./dist/providers/vercel.js"
93
97
  }
94
98
  },
95
99
  "scripts": {
@@ -15,7 +15,7 @@
15
15
  "typecheck": "tsc --noEmit"
16
16
  },
17
17
  "dependencies": {
18
- "@zackbart/connecta": "0.22.0",
18
+ "@zackbart/connecta": "0.22.1",
19
19
  "quickjs-emscripten": "0.32.0"
20
20
  },
21
21
  "devDependencies": {