@zackbart/connecta 0.22.1 → 0.22.3

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.
@@ -1,25 +1,44 @@
1
1
  import type { Connector, ConnectorCallAdmissionPolicy } from "../types.js";
2
2
  /** Vercel's public REST origin. Override only for a proxy or test double. */
3
3
  export declare const VERCEL_API_BASE_URL = "https://api.vercel.com";
4
- export interface VercelOptions {
5
- /** Human-readable display name; defaults to "Vercel". */
4
+ /** Vercel's official hosted MCP endpoint. */
5
+ export declare const VERCEL_MCP_ENDPOINT = "https://mcp.vercel.com";
6
+ interface VercelCommonOptions {
7
+ /** Human-readable display name; defaults identify the selected surface. */
6
8
  title?: string;
7
9
  /** Downstream auth ownership. Defaults to one shared deployment grant. */
8
10
  authScope?: "shared" | "personal";
9
11
  /** Which Vercel account or team this connection operates, and for whom. */
10
12
  purpose: string;
11
- /** Default team id for scoped calls. Omit to use the token's personal account. */
12
- teamId?: string;
13
13
  /** Account-specific conventions appended to the maintained provider guide. */
14
14
  instructions?: string;
15
- /** API base override for a proxy or test double. */
16
- baseUrl?: string;
17
- /** Default page size for list tools. Defaults to 20; Vercel's local cap is 100. */
18
- defaultPageSize?: number;
19
15
  /** Optional per-runtime downstream call-admission policy. */
20
16
  callAdmission?: ConnectorCallAdmissionPolicy;
21
17
  /** Connector-specific inline result limit; omit to inherit the deployment. */
22
18
  maxResultBytes?: number;
23
19
  }
24
- /** A maintained Vercel connection over the public REST API. */
25
- export declare function vercel(id: string, options: VercelOptions): Connector;
20
+ /** Connecta's maintained hand-written Vercel REST surface. */
21
+ export interface VercelApiOptions extends VercelCommonOptions {
22
+ /** Omit for backward compatibility; the hand-written API surface is the default. */
23
+ surface?: "api";
24
+ /** Default team id for scoped calls. Omit to use the token's personal account. */
25
+ teamId?: string;
26
+ /** API base override for a proxy or test double. */
27
+ baseUrl?: string;
28
+ /** Default page size for list tools. Defaults to 20; Vercel's local cap is 100. */
29
+ defaultPageSize?: number;
30
+ }
31
+ /** Vercel's official hosted MCP surface, authenticated through OAuth. */
32
+ export interface VercelMcpOptions extends VercelCommonOptions {
33
+ surface: "mcp";
34
+ }
35
+ /** Backward-compatible API options; existing consumers may extend this interface. */
36
+ export interface VercelOptions extends VercelApiOptions {
37
+ }
38
+ /** Select one Vercel surface when deployment configuration constructs it. */
39
+ export type VercelConnectionOptions = VercelOptions | VercelMcpOptions;
40
+ /** Release-reviewed Vercel MCP inventory and safety verdicts. */
41
+ export declare const VERCEL_MCP_VETTED_CATALOG: import("../catalog-drift.js").VettedCatalog;
42
+ /** A maintained Vercel connection using the selected provider surface. */
43
+ export declare function vercel(id: string, options: VercelConnectionOptions): Connector;
44
+ export {};
@@ -1,10 +1,14 @@
1
1
  /** See documentation/vercel.md#no-sdk-on-purpose. */
2
- import { api } from "../connectors/api.js";
2
+ import { api, defined } from "../connectors/api.js";
3
+ import { remoteMcp } from "../connectors/remote-mcp.js";
4
+ import { vettedCatalog, withVettedCatalog } from "../catalog-drift.js";
3
5
  import { guardedFetch, retryAfterMs, } from "../connectors/guarded-fetch.js";
4
6
  import { ConnectorCallError } from "../errors.js";
5
7
  import { withDeadline } from "../timeout.js";
6
8
  /** Vercel's public REST origin. Override only for a proxy or test double. */
7
9
  export const VERCEL_API_BASE_URL = "https://api.vercel.com";
10
+ /** Vercel's official hosted MCP endpoint. */
11
+ export const VERCEL_MCP_ENDPOINT = "https://mcp.vercel.com";
8
12
  const MAX_PAGE_SIZE = 100;
9
13
  const DEFAULT_PAGE_SIZE = 20;
10
14
  const VERCEL_MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
@@ -755,7 +759,7 @@ function tools(send, defaultPageSize, defaultTeamId) {
755
759
  type: { type: "string" }, createdAt: { type: "number" },
756
760
  message: { type: "string" }, payload: { type: "object" },
757
761
  },
758
- required: ["type", "createdAt"],
762
+ required: ["type"],
759
763
  },
760
764
  },
761
765
  },
@@ -776,7 +780,8 @@ function tools(send, defaultPageSize, defaultTeamId) {
776
780
  const event = asRecord(value);
777
781
  const eventPayload = asRecord(event["payload"]);
778
782
  return compact({
779
- type: event["type"], createdAt: event["created"] ?? event["date"],
783
+ type: event["type"] ?? "unknown",
784
+ createdAt: event["created"] ?? event["date"],
780
785
  message: eventPayload["text"] ?? eventPayload["message"],
781
786
  payload: Object.keys(eventPayload).length === 0 ? undefined : eventPayload,
782
787
  });
@@ -1029,7 +1034,7 @@ function tools(send, defaultPageSize, defaultTeamId) {
1029
1034
  },
1030
1035
  ];
1031
1036
  }
1032
- function usageGuide(purpose, teamId, instructions) {
1037
+ function apiUsageGuide(purpose, teamId, instructions) {
1033
1038
  const accountInstructions = instructions?.trim();
1034
1039
  return `# Vercel usage
1035
1040
 
@@ -1079,12 +1084,104 @@ ${accountInstructions
1079
1084
  ? `\n## Account instructions\n\n${accountInstructions}\n`
1080
1085
  : ""}`;
1081
1086
  }
1082
- /** A maintained Vercel connection over the public REST API. */
1083
- export function vercel(id, options) {
1084
- const purpose = options.purpose.trim();
1085
- if (!purpose) {
1086
- throw new Error("vercel() requires a non-empty account purpose.");
1087
- }
1087
+ /** Reads reviewed against Vercel's official MCP tool reference. */
1088
+ const MCP_READ_ONLY_TOOLS = new Set([
1089
+ "search_vercel_documentation",
1090
+ "list_teams",
1091
+ "list_projects",
1092
+ "get_project",
1093
+ "list_deployments",
1094
+ "get_deployment",
1095
+ "get_deployment_build_logs",
1096
+ "get_runtime_logs",
1097
+ "get_runtime_errors",
1098
+ "get_web_analytics",
1099
+ "list_agent_run_projects",
1100
+ "list_agent_runs",
1101
+ "get_agent_run",
1102
+ "get_agent_run_trace",
1103
+ "check_domain_availability_and_price",
1104
+ "get_purchase_quote",
1105
+ "get_domain_order",
1106
+ "list_toolbar_threads",
1107
+ "get_toolbar_thread",
1108
+ // Returns CLI guidance. Any later CLI execution is outside this MCP call.
1109
+ "use_vercel_cli",
1110
+ ]);
1111
+ /** Writes reviewed against Vercel's official MCP tool reference. */
1112
+ const MCP_WRITE_TOOLS = new Map([
1113
+ // These only append state.
1114
+ ["reply_to_toolbar_thread", "additive"],
1115
+ ["add_toolbar_reaction", "additive"],
1116
+ // Deploying to production, billing, access grants, imports, and edits can
1117
+ // all change existing state, even where the provider uses a create verb.
1118
+ ["deploy_to_vercel", "destructive"],
1119
+ ["buy_pro", "destructive"],
1120
+ ["buy_credits", "destructive"],
1121
+ ["buy_addon", "destructive"],
1122
+ ["buy_domain", "destructive"],
1123
+ ["get_access_to_vercel_url", "destructive"],
1124
+ // A GET against application code is not guaranteed to be observational.
1125
+ ["web_fetch_vercel_url", "destructive"],
1126
+ ["import-claude-design-from-url", "destructive"],
1127
+ ["change_toolbar_thread_resolve_status", "destructive"],
1128
+ ["edit_toolbar_message", "destructive"],
1129
+ ]);
1130
+ /** Release-reviewed Vercel MCP inventory and safety verdicts. */
1131
+ export const VERCEL_MCP_VETTED_CATALOG = vettedCatalog({
1132
+ reads: MCP_READ_ONLY_TOOLS,
1133
+ writes: MCP_WRITE_TOOLS,
1134
+ });
1135
+ function mcpUsageGuide(purpose, instructions) {
1136
+ const accountInstructions = instructions?.trim();
1137
+ return `# Vercel MCP usage
1138
+
1139
+ Official MCP surface: tool names, descriptions, argument schemas, and result
1140
+ schemas come from Vercel's live server. Connecta preserves that catalog and
1141
+ only fills in release-reviewed safety annotations when Vercel leaves them out.
1142
+
1143
+ Account purpose: ${purpose}
1144
+
1145
+ - Discover the live catalog before assuming a tool exists. Vercel can change
1146
+ the surface independently of a Connecta release, and account features may
1147
+ affect what the authorization can reach.
1148
+ - Resolve team, project, deployment, run, thread, and order ids with the list
1149
+ and get tools. Do not guess opaque ids.
1150
+ - Diagnose deployments with \`get_deployment\`, then build logs, runtime error
1151
+ clusters, and runtime logs. Narrow time windows before raising result limits.
1152
+ - Purchase tools change billing. Read a quote first and carry its price,
1153
+ idempotency key, and requested term into the confirmed purchase unchanged.
1154
+ - \`get_access_to_vercel_url\` creates a temporary access grant. Treat the URL
1155
+ it returns as a credential and do not expose it outside the requested task.
1156
+ - \`deploy_to_vercel\` and \`import-claude-design-from-url\` can create or update
1157
+ live projects. Read the target and deployment mode before approving them.
1158
+ - An \`auth_required\` failure means this connector's OAuth grant is missing or
1159
+ expired. Run \`authorize_connector\` for this connector id, then retry.
1160
+ ${accountInstructions
1161
+ ? `\n## Account instructions\n\n${accountInstructions}\n`
1162
+ : ""}`;
1163
+ }
1164
+ function vercelMcp(id, purpose, options) {
1165
+ const connector = remoteMcp(id, {
1166
+ url: VERCEL_MCP_ENDPOINT,
1167
+ ...defined({
1168
+ authScope: options.authScope,
1169
+ callAdmission: options.callAdmission,
1170
+ maxResultBytes: options.maxResultBytes,
1171
+ }),
1172
+ title: options.title ?? "Vercel (MCP)",
1173
+ description: `Vercel's official hosted MCP surface: ${purpose}`,
1174
+ auth: { type: "oauth" },
1175
+ requireHttps: true,
1176
+ usageGuide: {
1177
+ content: mcpUsageGuide(purpose, options.instructions),
1178
+ summary: "Official MCP. Live Vercel schemas, id resolution, deployment diagnosis, purchases, and access grants.",
1179
+ required: true,
1180
+ },
1181
+ });
1182
+ return withVettedCatalog(connector, VERCEL_MCP_VETTED_CATALOG);
1183
+ }
1184
+ function vercelApi(id, purpose, options) {
1088
1185
  const defaultPageSize = options.defaultPageSize ?? DEFAULT_PAGE_SIZE;
1089
1186
  if (!Number.isInteger(defaultPageSize) ||
1090
1187
  defaultPageSize < 1 ||
@@ -1117,7 +1214,7 @@ export function vercel(id, options) {
1117
1214
  }
1118
1215
  },
1119
1216
  usageGuide: {
1120
- content: usageGuide(purpose, teamId, options.instructions),
1217
+ content: apiUsageGuide(purpose, teamId, options.instructions),
1121
1218
  summary: "Team scoping, deployment diagnosis, value-safe environment variables, REST hatches, and cursor pagination.",
1122
1219
  required: true,
1123
1220
  },
@@ -1130,3 +1227,13 @@ export function vercel(id, options) {
1130
1227
  : {}),
1131
1228
  });
1132
1229
  }
1230
+ /** A maintained Vercel connection using the selected provider surface. */
1231
+ export function vercel(id, options) {
1232
+ const purpose = options.purpose.trim();
1233
+ if (!purpose) {
1234
+ throw new Error("vercel() requires a non-empty account purpose.");
1235
+ }
1236
+ return options.surface === "mcp"
1237
+ ? vercelMcp(id, purpose, options)
1238
+ : vercelApi(id, purpose, options);
1239
+ }
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.1";
7
+ export declare const CONNECTA_VERSION = "0.22.3";
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.1";
7
+ export const CONNECTA_VERSION = "0.22.3";
@@ -1,20 +1,20 @@
1
1
  # Cloudflare prebuilt connection
2
2
 
3
3
  Import `cloudflare()` independently from
4
- `@zackbart/connecta/providers/cloudflare`. It is a deliberate, hand-written
5
- surface over Cloudflare's v4 REST API. Fifty-one tools combine ergonomic,
6
- fully described operations for common work with three guarded escape hatches
7
- for the rest of Cloudflare's fast-moving control plane. Reads, JSON mutations,
8
- and raw/multipart uploads remain separate so safety routing does not depend on
9
- an agent-supplied HTTP method. The connection keeps lean projections, typed
10
- failures, and a rate-limit budget matching the documented one. It adds no
11
- provider dependency, imports nothing outside Connecta, and is not reachable
12
- from Connecta's root entry.
4
+ `@zackbart/connecta/providers/cloudflare`. The deployment chooses one of two
5
+ interfaces. The default is a deliberate, hand-written surface over
6
+ Cloudflare's v4 REST API. Its fifty-one tools combine ergonomic, fully
7
+ described operations for common work with three guarded escape hatches for the
8
+ rest of Cloudflare's fast-moving control plane. The other choice is
9
+ Cloudflare's official whole-API hosted MCP, which exposes `search` and
10
+ `execute` with live provider-owned schemas. Both are ordinary connectors and
11
+ neither is reachable from Connecta's root entry.
13
12
 
14
13
  ```ts
15
14
  import { cloudflare } from "@zackbart/connecta/providers/cloudflare";
16
15
 
17
16
  const edge = cloudflare("cloudflare_prod", {
17
+ surface: "api", // optional; this is the backward-compatible default
18
18
  title: "Production edge",
19
19
  purpose: "DNS and cache administration for the production estate",
20
20
  zoneId: "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
@@ -23,6 +23,15 @@ const edge = cloudflare("cloudflare_prod", {
23
23
  });
24
24
  ```
25
25
 
26
+ Use Cloudflare's hosted code-mode interface instead:
27
+
28
+ ```ts
29
+ const wholeApi = cloudflare("cloudflare_mcp", {
30
+ surface: "mcp",
31
+ purpose: "Cloudflare administration outside the curated REST workflows",
32
+ });
33
+ ```
34
+
26
35
  Use the legacy user-scoped Global API Key when an existing deployment needs it:
27
36
 
28
37
  ```ts
@@ -41,6 +50,29 @@ between a production and a staging instance needs to know which one answers the
41
50
  question. Account `instructions` are appended to the maintained guide and
42
51
  cannot change the connector's safety classification.
43
52
 
53
+ ## Choosing an interface
54
+
55
+ Use the API interface when its projected named tools cover the work. Connecta
56
+ owns those schemas, projections, typed errors, pagination, and the split
57
+ between read-only and mutating escape hatches.
58
+
59
+ Use `surface: "mcp"` when broad product coverage matters more than projected
60
+ results. Cloudflare's official server covers more than 2,500 API endpoints
61
+ through two code-mode tools. `search` reads the OpenAPI document and is
62
+ read-only. `execute` can run a program containing any authorized HTTP method,
63
+ so Connecta always routes it through approval. A program that happens to use
64
+ only GET cannot be proven observational from the tool schema.
65
+
66
+ The MCP catalog and schemas come from the live server. The release manifest
67
+ classifies the two known names but does not replace their schemas. OAuth is the
68
+ default. A headless deployment may instead pass `auth` with a scoped API token.
69
+ The credential remains the provider-side permission boundary either way. The
70
+ MCP interface accepts `callAdmission` when the deployment has a concurrency or
71
+ call-rate requirement; it does not reuse the API interface's REST-wide budget.
72
+
73
+ The remaining sections document the hand-written API interface. MCP tool
74
+ arguments and results are intentionally read from the live server instead.
75
+
44
76
  ## No SDK, on purpose
45
77
 
46
78
  Cloudflare publishes an official `cloudflare` npm SDK, and this connection does
@@ -61,7 +93,7 @@ claim: the `cloudflare` package must not appear in `dependencies`,
61
93
  `peerDependencies`, or `devDependencies`, and every import in the provider
62
94
  must be relative.
63
95
 
64
- ## Credentials
96
+ ## API credentials
65
97
 
66
98
  The default credential is a scoped Cloudflare API token, sent as
67
99
  `Authorization: Bearer <token>`. Create it under My Profile → API Tokens →
@@ -133,7 +165,7 @@ an empty `accountId` would fall back to the default again. A deployment that
133
165
  wants zones from one account passes `accountId` explicitly, and the property
134
166
  says so.
135
167
 
136
- ## Tools
168
+ ## API tools
137
169
 
138
170
  The named surface covers workflows that benefit most from concise schemas and
139
171
  projections:
@@ -423,6 +455,14 @@ not by Connecta. `maxConcurrency` is the bound that actually protects a shared
423
455
  credential, because a single `execute_code` program can fan out far faster than
424
456
  the window notices.
425
457
 
458
+ ## Contract checks
459
+
460
+ `npm run providers:check` compares the 49 fixed REST endpoints with
461
+ Cloudflare's published OpenAPI document and the two MCP names, endpoint, and
462
+ OAuth support with Cloudflare's official MCP page. It needs no Cloudflare
463
+ credential. The MCP schemas are not vendored or reconstructed: the live
464
+ `tools/list` response remains the contract agents receive.
465
+
426
466
  ## Conventions
427
467
 
428
468
  This connection is audited against
@@ -54,11 +54,29 @@ const analytics = mixpanel("product_analytics", {
54
54
  });
55
55
  ```
56
56
 
57
- The constructor may use `remoteMcp()` or `api()` internally. Callers should not
58
- need to care which transport gives the better agent-facing surface, and the
59
- choice does not grant the connection different runtime privileges. Two
60
- instances of the same provider are isolated in exactly the same way as two
61
- hand-written connectors with different ids.
57
+ The constructor may use `remoteMcp()` or `api()` internally. When a provider's
58
+ official MCP and HTTP API expose materially different capabilities or schema
59
+ ownership, the constructor may offer an explicit deployment-time surface
60
+ choice. It must document the difference, keep a backward-compatible default,
61
+ and never let an agent switch surfaces during a run. The choice grants no
62
+ different runtime privileges. Two instances of the same provider are isolated
63
+ in exactly the same way as two hand-written connectors with different ids.
64
+
65
+ That choice exists only when the two interfaces are genuinely different:
66
+
67
+ | Provider | Maintained interfaces | Why |
68
+ | --- | --- | --- |
69
+ | Cloudflare | API and MCP | The API interface has 48 projected named tools plus three safety-split hatches. The official MCP compresses more than 2,500 endpoints into `search` and approval-gated `execute`. |
70
+ | Notion | API and MCP | The API interface has stable lean projections. The official MCP adds Notion-owned live schemas, workspace search, files, views, agents, and sessions. |
71
+ | Vercel | API and MCP | The API interface has projected deployment operations. The official MCP owns a broader, independently changing catalog. |
72
+ | Linear | MCP | Vendoring its GraphQL API would create a second schema system rather than a distinct maintained interface. |
73
+ | Mixpanel | MCP | Its hosted service already joins several Mixpanel APIs; flattening those APIs would recreate the catalog problem. |
74
+ | RevenueCat | MCP | Its official server is generated from API v2, so a second wrapper would duplicate the same contract. |
75
+ | Stripe | MCP | Its official server already exposes both named workflows and supported API methods. A second raw API interface would duplicate it. |
76
+
77
+ This is not a requirement that every provider have two labels. A second choice
78
+ must change capability, result shape, or schema ownership enough to justify a
79
+ second contract. Otherwise it only gives agents two names for the same thing.
62
80
 
63
81
  A prebuilt connection's vetted annotations fill in downstream silence and
64
82
  otherwise preserve explicit annotations. This includes an explicit
@@ -176,6 +176,15 @@ A budget-only rule needs no queue. If you add `maxConcurrency` you are asking
176
176
  for a queue, and the admission controller then requires the rest of the queue
177
177
  settings at construction.
178
178
 
179
+ ## Public contract check
180
+
181
+ `npm run drift:check -- --docs --provider linear` checks Linear's official MCP
182
+ setup page for the read-write endpoint and OAuth support without using a
183
+ credential. Linear does not publish an exact tool inventory there, so the
184
+ command says `setup metadata only` and makes no claim about names or schemas.
185
+ At runtime the live `tools/list` response remains the schema authority and is
186
+ passed through without a vendored replacement.
187
+
179
188
  ## Conventions
180
189
 
181
190
  This connection is audited against
@@ -85,9 +85,11 @@ A read-only live audit on 2026-08-13 confirmed all three refusals against the
85
85
  US hosted endpoint. They are reported upstream as
86
86
  [`mixpanel/mixpanel-headless#202`](https://github.com/mixpanel/mixpanel-headless/issues/202).
87
87
  The vetted catalog records current schema digests for all 64 tools,
88
- so a later schema correction or regression appears by tool name in the
89
- maintainer drift check. The guide can then shrink when the downstream schema
90
- becomes complete; Connecta does not absorb the defect permanently.
88
+ so a later schema correction or regression increments runtime drift when an
89
+ ordinary catalog refresh observes it. The live definition is still served
90
+ unchanged. The credential-free provider check does not depend on those digests.
91
+ The guide can shrink when the downstream schema becomes complete; Connecta does
92
+ not absorb the defect permanently.
91
93
 
92
94
  The wrapper classifies the documented observational tools as reads and the
93
95
  documented create, update, edit, merge, dismiss, duplicate, and delete tools as
@@ -142,6 +144,14 @@ for a queue, and the admission controller then requires the rest of the queue
142
144
  settings at construction. Discovery traffic is outside connector call admission
143
145
  either way and still needs restrained use.
144
146
 
147
+ ## Public contract check
148
+
149
+ `npm run drift:check -- --docs --provider mixpanel` compares Mixpanel's
150
+ official Available Tools table with the vetted manifest and checks all three
151
+ regional endpoints plus OAuth support. The current table lists 63 tools. It
152
+ omits `Fill-Event-Metadata`, which remains classified from the last
153
+ authenticated review and is reported as `not documented`, not silently removed.
154
+
145
155
  ## Conventions
146
156
 
147
157
  This connection is audited against
@@ -1,39 +1,60 @@
1
1
  # Notion prebuilt connection
2
2
 
3
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
4
+ offers two deployment-time interfaces. The default is a hand-written `api()`
5
+ surface over Notion's public REST API: fifteen deliberate tools, lean
6
+ projections of Notion's famously bloated payloads, typed failures, a rate
7
+ budget matched to the documented limit, and a required usage guide. The other
8
+ choice is Notion's official hosted MCP with live provider-owned schemas and a
9
+ broader workspace, files, views, agents, and sessions catalog. Neither is
8
10
  reachable from Connecta's root entry.
9
11
 
10
12
  ```ts
11
13
  import { notion } from "@zackbart/connecta/providers/notion";
12
14
 
13
15
  const wiki = notion("engineering_wiki", {
16
+ surface: "api", // optional; this is the backward-compatible default
14
17
  title: "Engineering wiki",
15
18
  purpose: "Runbooks, specs, and on-call notes for the platform team",
16
19
  instructions: "Prefer the Runbooks database; specs live under Projects.",
17
20
  });
18
21
  ```
19
22
 
23
+ Use Notion's hosted MCP instead:
24
+
25
+ ```ts
26
+ const workspace = notion("notion_mcp", {
27
+ surface: "mcp",
28
+ purpose: "Workspace search, files, views, and asynchronous agent sessions",
29
+ });
30
+ ```
31
+
20
32
  The `id` owns the ordinary connector namespaces; use a different id for every
21
33
  Notion workspace. `purpose` is required because an agent choosing between two
22
34
  instances needs to know which workspace answers the question. Workspace
23
35
  `instructions` are appended to the maintained guide and cannot change the
24
36
  connector's safety classification.
25
37
 
26
- ## Why this one is `api()` and not `remoteMcp()`
38
+ ## Choosing an interface
27
39
 
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
40
+ Use the API interface for its compact, stable projections. A single Notion
41
+ page returns every property as a
30
42
  discriminated wrapper object, every string as an array of rich-text runs each
31
43
  carrying its own annotations block, and every user reference as a nested
32
44
  object. A twenty-five row database query is tens of kilobytes of structure
33
45
  around a few hundred bytes of meaning. Hand-writing the surface is what makes
34
46
  the projections possible, and the projections are the point.
35
47
 
36
- ## Authentication
48
+ Use `surface: "mcp"` for Notion's wider official capabilities, including
49
+ connected-source search, attachments, saved views, Notion Skills, agents, and
50
+ asynchronous sessions. Tool names and schemas come from the live server.
51
+ Connecta preserves them and only fills in release-reviewed safety annotations
52
+ when Notion is silent. OAuth is the hosted server's authentication contract.
53
+ Do not apply the hand-written REST schemas to similarly named MCP tools. The
54
+ MCP interface accepts `callAdmission` for an operator-supplied runtime policy;
55
+ it does not assume the REST interface's endpoint budget describes MCP traffic.
56
+
57
+ ## API authentication
37
58
 
38
59
  One operator-managed credential: an internal integration token from
39
60
  [notion.so/profile/integrations](https://www.notion.so/profile/integrations).
@@ -52,6 +73,10 @@ Two Notion-specific facts decide whether a working token is enough:
52
73
  cheapest call that proves a token is live — and reports the workspace it
53
74
  authenticated into.
54
75
 
76
+ The MCP interface uses Notion OAuth instead of the integration-token form. An
77
+ `auth_required` failure means the grant is absent or expired and must be
78
+ completed again through `authorize_connector`.
79
+
55
80
  ## The pinned API version
56
81
 
57
82
  The connection pins `Notion-Version: 2026-03-11` and offers no override. That
@@ -69,7 +94,10 @@ type's payload rather than switching exhaustively. A property type that ships
69
94
  after this release degrades to its raw value, and a block type that does keeps
70
95
  its payload under `raw`; neither vanishes.
71
96
 
72
- ## Tools
97
+ The remaining sections document the hand-written API interface. MCP tool
98
+ arguments and results are intentionally read from the live server instead.
99
+
100
+ ## API tools
73
101
 
74
102
  Ten reads, all annotated `readOnlyHint: true`:
75
103
 
@@ -229,13 +257,14 @@ Cursors are opaque. Notion's own versioning page is explicit that they may
229
257
  change in length, format, and structure at any time and must be passed back
230
258
  verbatim — never parsed, validated, or constructed.
231
259
 
232
- ## What this connection does not do
260
+ ## What the API interface does not do
233
261
 
234
262
  No file uploads, no database or data-source creation, no schema editing, no
235
263
  block updates or deletes, no page moves. Those are all real Notion endpoints
236
264
  and all deliberately absent: this is a deliberate tool surface, not a mirror of
237
- the API. Anything missing is reachable through a custom `api()` connector
238
- beside this one, which remains a first-class path.
265
+ the API. Some are present on Notion's hosted MCP interface. Anything still
266
+ missing is reachable through a custom `api()` connector beside this one, which
267
+ remains a first-class path.
239
268
 
240
269
  The 2026-03-11 contract also offers more fields on create and update. They were
241
270
  reviewed after the 0.17.0 drift check and remain deliberately absent:
@@ -263,6 +292,14 @@ The usage guide says it too, because an agent that assumes a hatch exists
263
292
  spends a search proving it does not: absent from the tool list means absent
264
293
  from this connection, not hidden behind a generic call.
265
294
 
295
+ ## Contract checks
296
+
297
+ `npm run providers:check` compares the 14 fixed REST endpoints with Notion's
298
+ published OpenAPI document and the 34 MCP names, endpoint, and OAuth support
299
+ with Notion's official pages. It needs no Notion credential. The MCP schemas
300
+ are not vendored or reconstructed: the live `tools/list` response remains the
301
+ contract agents receive.
302
+
266
303
  ## Conventions
267
304
 
268
305
  This connection is audited against
@@ -194,10 +194,15 @@ Two more runners are deliberately outside `check`:
194
194
  - `npm run test:browser` — Playwright against a real headless Chromium
195
195
  (`npm run test:browser:install` once). It covers the embedded bundle without
196
196
  adding a browser download to the CI release check.
197
- - `npm run drift:check` — the maintainer-run provider drift check, with local
198
- provider credentials exported. No credential goes near CI and nothing files
199
- itself; findings are read by a human and become issues
197
+ - `npm run drift:check` — the lower-level maintainer provider contract check.
198
+ It reads public MCP references and OpenAPI documents only. No provider
199
+ credential is read and nothing files itself; findings are read by a human
200
+ and become issues
200
201
  ([provider conventions](./provider-conventions.md#the-maintainer-run-drift-check)).
202
+ - `npm run providers:check` — the normal provider check across every maintained
203
+ provider: official MCP documentation plus the OpenAPI contracts for
204
+ hand-written HTTP connections. It uses the network, so it stays outside the
205
+ deterministic `check` chain.
201
206
  - `npm run load:admission` — the opt-in capacity matrix and soak
202
207
  ([request admission](./request-admission.md#measuring-capacity)).
203
208
 
@@ -234,7 +239,7 @@ in.
234
239
  | `catalog.test.ts` | lexical ranking and the compact schema renderer — `const`, `allOf` beside siblings, `$ref`, the depth limit, per-schema caching, and 2020-12 keyword compatibility |
235
240
  | `clerk.test.ts` | protected-resource metadata, the browser sign-in config, OAuth and session tokens, cached best-effort activity labels with their caps, the hand-applied `azp` rejection, and the `allowedDomains` allowlist including every lookalike that must not be repaired into a match |
236
241
  | `cloudflare-access-auth.test.ts` | trusted `ctx.access` human and service identities, absent/error fail-closed behavior, service-token MCP admission without operator mutation, human same-origin mutation, and the Clerk-to-ambient shell switch |
237
- | `cloudflare-provider.test.ts` | `cloudflare()` construction, tool surface, current R2 and KV jurisdictions, useful output declarations, request building, projections including additive provider fields, typed failures, and credential test |
242
+ | `cloudflare-provider.test.ts` | `cloudflare()` API and MCP construction, the code-mode safety manifest, API tool surface, current R2 and KV jurisdictions, useful output declarations, request building, projections including additive provider fields, typed failures, and credential test |
238
243
  | `code-first-surface.test.ts` | the seven-tool surface itself — an executor required, every removed option and top-level tool refused, compact always-loaded routing pinned below 1,000 characters, complete on-demand usage served, and `connecta.ui` findable before connector search |
239
244
  | `codemode-compat.test.ts` | the `Executor` seam staying structurally compatible with `@cloudflare/codemode`'s `DynamicWorkerExecutor`, enforced by `tsc` |
240
245
  | `config.test.ts` | the grouped `ConnectaConfig` boundary — each group forwarding to its internals, malformed admission bounds failing construction, and unknown own-properties rejected by their complete path before construction does work |
@@ -254,7 +259,7 @@ in.
254
259
  | `meta-tools-search.test.ts` | registry-backed discovery: bounded search with page and address maxima, compact and JSON schemas with constraints, typed describe recovery and suggestions, and structured-result compatibility |
255
260
  | `meta-tools.test.ts` | the remaining registry-backed meta-tools: the complete on-demand usage skill, connector-guide selection and summary bounds, stored-credential drift, catalog health, authorization, probe timeouts, and unavailable or unknown browse recovery |
256
261
  | `mixpanel-provider.test.ts` | the Mixpanel proxy, its conditional-input guide, destructive metadata fill, and complete 64-tool schema-digest manifest |
257
- | `notion-provider.test.ts` | Notion's deliberate tool surface, including declined expanded page inputs, request construction, lean projections, both pagination conventions, error mapping, and writes |
262
+ | `notion-provider.test.ts` | Notion's API and MCP construction, the hosted safety manifest and drift behavior, the deliberate REST surface including declined expanded page inputs, request construction, lean projections, both pagination conventions, error mapping, and writes |
258
263
  | `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
264
  | `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
265
  | `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) |
@@ -274,7 +279,7 @@ in.
274
279
  | `ui-credentials.test.ts` | credential-management routes: save, test, delete, validation, authentication, same-origin checks, and multi-field credential shapes |
275
280
  | `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
281
  | `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 |
282
+ | `vercel-provider.test.ts` | `vercel()` API and MCP construction, MCP inventory classification, 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 |
278
283
 
279
284
  ### Node-bound (`NODE_ONLY_SUITES`)
280
285
 
@@ -286,7 +291,7 @@ justification for *not* re-running it in workerd, so "it was easier" is not one.
286
291
  | `deployment-shapes.test.ts` | the Worker as the only example with a loader-only sandbox, its agent instructions and setup guide pinning Claude and both ChatGPT Managed OAuth callback forms, one Node template that is also its own container, the same source running locally and in the container, the Node template's pinned esbuild install-script approval, the full operator surface in both, a template that cannot start on its own `.env.example`, a Worker README naming every optional peer its entrypoint imports, and the initializer's `.gitignore` staying in step | walks the template and example trees with Node filesystem APIs |
287
292
  | `doc-links.test.ts` | the documentation checker itself — local file and fragment resolution, repository URLs resolved back to the checkout, duplicate heading slugs, fenced-code exclusion, and useful failures | spawns the Node checker against filesystem fixtures |
288
293
  | `doctor-cli.test.ts` | `connecta doctor`'s executor line and credentials end to end — the sandbox the deployment reports is the one named, an unidentifiable executor gets an executor-neutral line, a hostile name is bounded, and a complete Cloudflare Access service-token pair is accepted while a partial pair is refused | spawns the CLI against a Node HTTP deployment over real sockets |
289
- | `drift-check.test.ts` | the maintainer drift checker hosted-provider credential framing, recorded touched endpoints, a quiet revision bump, clear failures for an unavailable spec/manifest/credential, `$ref` traversal, and one well-formed row per endpoint | spawns the Node checker against filesystem fixtures |
294
+ | `drift-check.test.ts` | the credential-free maintainer drift checker: recorded touched endpoints, heading, table, and inline MCP inventories, setup-only providers, live-schema ownership, a quiet revision bump, clear failures for unavailable inputs, `$ref` traversal, and one well-formed row per endpoint | spawns the checker against filesystem fixtures |
290
295
  | `file-storage.test.ts` | `fileStorage()` across instances, logical TTL plus physical pruning without clobbering a newer value, and corrupt-file quarantine | exercises the Node filesystem storage adapter |
291
296
  | `guest-api-contract-quickjs.test.ts` | the shared guest-contract cases on the real QuickJS executor, including identical caught failure codes and inline describe recovery, its exact absent globals, and blocked runtime imports | runs the contract cases on the Node QuickJS executor |
292
297
  | `node.test.ts` | the `listen()` adapter propagating an HTTP client disconnect through the Web `Request` and the MCP handler into a program's connector call, releasing both admission permits | exercises the Node HTTP adapter over real TCP sockets |