@zackbart/connecta 0.24.3 → 0.25.0

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 (145) hide show
  1. package/AGENTS.md +58 -30
  2. package/CHANGELOG.md +317 -1
  3. package/README.md +5 -6
  4. package/dist/advertised-schema.js +53 -0
  5. package/dist/auth/downstream-oauth.js +490 -106
  6. package/dist/branding.d.ts +31 -2
  7. package/dist/branding.js +116 -8
  8. package/dist/call-admission.js +139 -113
  9. package/dist/catalog-service.js +141 -105
  10. package/dist/catalog.js +258 -33
  11. package/dist/concurrency.js +24 -23
  12. package/dist/connector-scope.js +6 -37
  13. package/dist/connectors/api.d.ts +3 -3
  14. package/dist/connectors/api.js +10 -2
  15. package/dist/connectors/guarded-fetch.js +34 -4
  16. package/dist/connectors/remote-mcp.d.ts +6 -5
  17. package/dist/connectors/remote-mcp.js +325 -245
  18. package/dist/credential-contract.d.ts +9 -0
  19. package/dist/credential-rules.js +2 -2
  20. package/dist/credentials.d.ts +6 -1
  21. package/dist/credentials.js +45 -3
  22. package/dist/errors.d.ts +11 -3
  23. package/dist/errors.js +2 -1
  24. package/dist/execute.js +363 -257
  25. package/dist/executor-admission.js +67 -50
  26. package/dist/executors/quickjs-runtime.js +4 -2
  27. package/dist/executors/quickjs.js +251 -210
  28. package/dist/fix-prompt.js +37 -0
  29. package/dist/index.d.ts +11 -2
  30. package/dist/index.js +63 -21
  31. package/dist/invocation.js +324 -285
  32. package/dist/meta-tools.js +304 -114
  33. package/dist/oauth-callback-outcome.js +94 -0
  34. package/dist/oauth-sealing.js +29 -0
  35. package/dist/operator-ui/fix-prompts.js +87 -0
  36. package/dist/operator-ui/generated.js +2 -2
  37. package/dist/operator-ui/model.d.ts +46 -1
  38. package/dist/operator-ui/setup-commands.js +53 -0
  39. package/dist/operator-ui/view.js +109 -2
  40. package/dist/program-source.js +205 -0
  41. package/dist/providers/cloudflare.d.ts +14 -2
  42. package/dist/providers/cloudflare.js +107 -16
  43. package/dist/providers/linear.d.ts +26 -4
  44. package/dist/providers/linear.js +19 -4
  45. package/dist/providers/mixpanel.d.ts +16 -3
  46. package/dist/providers/mixpanel.js +13 -2
  47. package/dist/providers/notion.d.ts +8 -1
  48. package/dist/providers/notion.js +85 -12
  49. package/dist/providers/revenuecat.d.ts +30 -4
  50. package/dist/providers/revenuecat.js +70 -5
  51. package/dist/providers/stripe.d.ts +10 -3
  52. package/dist/providers/stripe.js +30 -4
  53. package/dist/providers/vercel.js +12 -2
  54. package/dist/registry.d.ts +13 -5
  55. package/dist/registry.js +317 -258
  56. package/dist/routes/activity.js +120 -137
  57. package/dist/routes/credentials.js +174 -163
  58. package/dist/routes/mcp.js +129 -70
  59. package/dist/routes/oauth-management.js +60 -71
  60. package/dist/routes/oauth.js +44 -16
  61. package/dist/routes/operator.js +82 -0
  62. package/dist/routes/shared.d.ts +2 -0
  63. package/dist/routes/ui-data.js +210 -0
  64. package/dist/routes/ui.js +64 -32
  65. package/dist/runtime/admission.js +43 -0
  66. package/dist/runtime/call-admission.js +67 -0
  67. package/dist/runtime/connector-scope.js +72 -0
  68. package/dist/runtime/run.js +174 -0
  69. package/dist/runtime/services.js +130 -0
  70. package/dist/runtime/storage.js +66 -0
  71. package/dist/server.js +111 -89
  72. package/dist/skills.js +1 -1
  73. package/dist/storage/file.js +39 -2
  74. package/dist/storage/memory.js +28 -16
  75. package/dist/timeout.js +17 -47
  76. package/dist/types.d.ts +64 -7
  77. package/dist/ui.d.ts +16 -2
  78. package/dist/ui.js +68 -176
  79. package/dist/url-safety.js +157 -0
  80. package/dist/version.d.ts +1 -1
  81. package/dist/version.js +1 -1
  82. package/documentation/architecture.md +368 -182
  83. package/documentation/auth.md +277 -176
  84. package/documentation/code-mode.md +479 -329
  85. package/documentation/meta-tools.md +494 -417
  86. package/ethos.md +44 -47
  87. package/examples/worker/AGENTS.md +2 -1
  88. package/examples/worker/README.md +62 -14
  89. package/examples/worker/src/d1-storage.ts +116 -0
  90. package/examples/worker/src/index.ts +18 -21
  91. package/examples/worker/wrangler.jsonc +16 -4
  92. package/package.json +11 -4
  93. package/templates/node/.env.example +3 -3
  94. package/templates/node/AGENTS.md +5 -4
  95. package/templates/node/README.md +2 -1
  96. package/templates/node/package.json +1 -1
  97. package/templates/node/src/index.ts +23 -22
  98. package/dist/auth/downstream-oauth.d.ts +0 -200
  99. package/dist/catalog-fingerprint.d.ts +0 -19
  100. package/dist/catalog-limits.d.ts +0 -9
  101. package/dist/catalog-service.d.ts +0 -263
  102. package/dist/catalog.d.ts +0 -87
  103. package/dist/concurrency.d.ts +0 -3
  104. package/dist/connectors/guarded-fetch.d.ts +0 -92
  105. package/dist/credential-rules.d.ts +0 -85
  106. package/dist/execute.d.ts +0 -154
  107. package/dist/executor-result.d.ts +0 -10
  108. package/dist/executors/quickjs-child.d.ts +0 -1
  109. package/dist/executors/quickjs-protocol.d.ts +0 -67
  110. package/dist/favicon.d.ts +0 -1
  111. package/dist/identity.d.ts +0 -4
  112. package/dist/invocation.d.ts +0 -74
  113. package/dist/mcp-result.d.ts +0 -8
  114. package/dist/meta-tools.d.ts +0 -129
  115. package/dist/operator-ui/generated.d.ts +0 -2
  116. package/dist/operator-ui/view.d.ts +0 -151
  117. package/dist/result-shapes.d.ts +0 -13
  118. package/dist/retired-toolkits.d.ts +0 -2
  119. package/dist/routes/activity.d.ts +0 -2
  120. package/dist/routes/credentials.d.ts +0 -2
  121. package/dist/routes/mcp.d.ts +0 -10
  122. package/dist/routes/oauth-management.d.ts +0 -2
  123. package/dist/routes/oauth.d.ts +0 -2
  124. package/dist/routes/ui.d.ts +0 -2
  125. package/dist/server.d.ts +0 -9
  126. package/dist/skills.d.ts +0 -44
  127. package/dist/timeout.d.ts +0 -21
  128. package/dist/tool-safety.d.ts +0 -9
  129. package/documentation/call-admission.md +0 -158
  130. package/documentation/cloudflare.md +0 -471
  131. package/documentation/connector-guides.md +0 -176
  132. package/documentation/connectors.md +0 -431
  133. package/documentation/linear.md +0 -193
  134. package/documentation/mixpanel.md +0 -160
  135. package/documentation/notion.md +0 -308
  136. package/documentation/operations.md +0 -359
  137. package/documentation/operator-ui.md +0 -135
  138. package/documentation/optional-modules-upgrade.md +0 -243
  139. package/documentation/provider-conventions.md +0 -729
  140. package/documentation/request-admission.md +0 -204
  141. package/documentation/revenuecat.md +0 -305
  142. package/documentation/storage-and-credentials.md +0 -254
  143. package/documentation/stripe.md +0 -262
  144. package/documentation/upgrading.md +0 -768
  145. package/documentation/vercel.md +0 -241
@@ -11,7 +11,11 @@ export const LINEAR_MCP_ENDPOINTS = {
11
11
  "read-write": "https://mcp.linear.app/mcp",
12
12
  "read-only": "https://mcp.linear.app/mcp/readonly",
13
13
  };
14
- /** Reviewed reads; see `documentation/linear.md` and provider convention P5. */
14
+ /**
15
+ * Reviewed reads, listed by name (P5). The list is a superset: Linear's hosted
16
+ * `tools/list` varies by plan and enabled features, so a name this workspace
17
+ * never returns costs nothing while an unlisted new one fails closed.
18
+ */
15
19
  const READ_ONLY_TOOLS = new Set([
16
20
  // Issues
17
21
  "list_issues",
@@ -73,7 +77,13 @@ const READ_ONLY_TOOLS = new Set([
73
77
  // than argues.
74
78
  "extract_images",
75
79
  ]);
76
- /** Reviewed writes; `save_*` upsert rationale lives in `documentation/linear.md`. */
80
+ /**
81
+ * Reviewed writes with their destructive verdict. Every `save_*` is destructive
82
+ * because Linear's `save_*` tools are upserts: omitting a record id creates,
83
+ * supplying one overwrites in place, and the schema cannot tell them apart. The
84
+ * genuine creates — the `create_*_label` and attachment tools — stay additive,
85
+ * since `readOnlyHint: false` already routes them through the approval path.
86
+ */
77
87
  const WRITE_TOOLS = new Map([
78
88
  // Issues
79
89
  ["save_issue", "destructive"],
@@ -107,7 +117,7 @@ const WRITE_TOOLS = new Map([
107
117
  ["create_attachment_from_upload", "additive"],
108
118
  ["create_attachment", "additive"],
109
119
  ["delete_attachment", "destructive"],
110
- // Explicit issue access
120
+ // Explicit issue access. Both halves change an existing issue's audience.
111
121
  ["share_issue", "destructive"],
112
122
  ["unshare_issue", "destructive"],
113
123
  // Customer requests (plan-gated)
@@ -116,7 +126,12 @@ const WRITE_TOOLS = new Map([
116
126
  ["save_customer_need", "destructive"],
117
127
  ["delete_customer_need", "destructive"],
118
128
  ]);
119
- /** Release-reviewed manifest; see provider conventions P5 and P13. */
129
+ /**
130
+ * One release-reviewed manifest, used both to classify a live tool and as the
131
+ * baseline the drift check compares against, so the annotation a caller gets
132
+ * and the verdict a check reads can never disagree. Drift must surface as an
133
+ * unclassified tool on the approval path (P5), never a quiet re-guess.
134
+ */
120
135
  export const LINEAR_VETTED_CATALOG = vettedCatalog({
121
136
  reads: READ_ONLY_TOOLS,
122
137
  writes: WRITE_TOOLS,
@@ -13,7 +13,10 @@ export interface MixpanelOptions {
13
13
  authScope?: "shared" | "personal";
14
14
  /** Who should use this account and for what decisions. */
15
15
  purpose: string;
16
- /** Data residency region; see `documentation/mixpanel.md`. */
16
+ /**
17
+ * Data residency region, selecting the matching official endpoint. A wrong
18
+ * region answers empty rather than wrong, which reads as an empty project.
19
+ */
17
20
  region?: MixpanelRegion;
18
21
  /**
19
22
  * OAuth by default; static headers support Mixpanel service accounts, and
@@ -25,10 +28,20 @@ export interface MixpanelOptions {
25
28
  instructions?: string;
26
29
  /** Connector-specific inline result limit; omit to inherit the deployment. */
27
30
  maxResultBytes?: number;
28
- /** Optional per-runtime policy; see `documentation/mixpanel.md#rate-limits`. */
31
+ /**
32
+ * Optional per-runtime policy. There is no default: Mixpanel meters its MCP
33
+ * server per user per hour, and a per-runtime counter over-counts one runtime
34
+ * serving many users and under-counts many runtimes sharing one credential.
35
+ * Only an operator who knows the account can pick a number worth enforcing.
36
+ */
29
37
  callAdmission?: ConnectorCallAdmissionPolicy;
30
38
  }
31
- /** Release-reviewed manifest; see provider conventions P5 and P13. */
39
+ /**
40
+ * One release-reviewed manifest, used both to classify a live tool and as the
41
+ * baseline the drift check compares against, so the annotation a caller gets
42
+ * and the verdict a check reads can never disagree. Drift must surface as an
43
+ * unclassified tool on the approval path (P5), never a quiet re-guess.
44
+ */
32
45
  export declare const MIXPANEL_VETTED_CATALOG: import("../catalog-drift.js").VettedCatalog;
33
46
  /** A maintained Mixpanel hosted-MCP connection. */
34
47
  export declare function mixpanel(id: string, options: MixpanelOptions): Connector;
@@ -44,7 +44,12 @@ const READ_ONLY_TOOLS = new Set([
44
44
  "Get-Feature-Flag-Setup-Guidance",
45
45
  "Get-Feature-Flag-Lifecycle-Guidance",
46
46
  ]);
47
- /** Reviewed writes; see provider convention P5. */
47
+ /**
48
+ * Reviewed writes, each with its destructive verdict (P5). Additive writes stay
49
+ * `additive` because `readOnlyHint: false` already routes them through the
50
+ * approval path, and claiming destruction only inflates the copy a host shows a
51
+ * human. Anything unlisted is not read-only, so a new name fails closed.
52
+ */
48
53
  const WRITE_TOOLS = new Map([
49
54
  ["Create-Dashboard", "additive"],
50
55
  ["Update-Dashboard", "destructive"],
@@ -74,6 +79,7 @@ const WRITE_TOOLS = new Map([
74
79
  ["Update-Experiment", "destructive"],
75
80
  ["Create-Feature-Flag", "additive"],
76
81
  ["Update-Feature-Flag", "destructive"],
82
+ // Applies generated names and descriptions to existing Lexicon events.
77
83
  ["Fill-Event-Metadata", "destructive"],
78
84
  ]);
79
85
  /** Live US hosted-MCP schemas reviewed read-only on 2026-08-30 (#395, #512). */
@@ -143,7 +149,12 @@ const MIXPANEL_SCHEMA_DIGESTS = {
143
149
  "Update-Lookup-Table": "sha256:ef28f1ec9c9484a7a53b5e2b6659e70f79a8e71e55bda17025aa4ab3c23b6a63",
144
150
  "Update-Metric": "sha256:739bb6abdab19282afd4a6644a96183daabe9098a5322c44a77b840608a5ee9d",
145
151
  };
146
- /** Release-reviewed manifest; see provider conventions P5 and P13. */
152
+ /**
153
+ * One release-reviewed manifest, used both to classify a live tool and as the
154
+ * baseline the drift check compares against, so the annotation a caller gets
155
+ * and the verdict a check reads can never disagree. Drift must surface as an
156
+ * unclassified tool on the approval path (P5), never a quiet re-guess.
157
+ */
147
158
  export const MIXPANEL_VETTED_CATALOG = vettedCatalog({
148
159
  reads: READ_ONLY_TOOLS,
149
160
  writes: WRITE_TOOLS,
@@ -3,7 +3,14 @@ import type { Connector, ConnectorCallAdmissionPolicy } from "../types.js";
3
3
  export declare const NOTION_API_BASE_URL = "https://api.notion.com";
4
4
  /** Notion's official hosted MCP endpoint. */
5
5
  export declare const NOTION_MCP_ENDPOINT = "https://mcp.notion.com/mcp";
6
- /** See documentation/notion.md#the-pinned-api-version. */
6
+ /**
7
+ * Pinned with no override. Notion's date-named versions keep working
8
+ * indefinitely, which makes an override look harmless; it is not. `2026-03-11`
9
+ * is where databases split into data sources, `archived` became `in_trash`, and
10
+ * block append took a `position` object instead of an `after` string. Every
11
+ * projection and write body here assumes those shapes, so an older version would
12
+ * return quietly wrong results rather than fail loudly.
13
+ */
7
14
  export declare const NOTION_API_VERSION = "2026-03-11";
8
15
  interface NotionCommonOptions {
9
16
  /** Human-readable display name; defaults identify the selected interface. */
@@ -7,7 +7,14 @@ import { ConnectorCallError } from "../errors.js";
7
7
  export const NOTION_API_BASE_URL = "https://api.notion.com";
8
8
  /** Notion's official hosted MCP endpoint. */
9
9
  export const NOTION_MCP_ENDPOINT = "https://mcp.notion.com/mcp";
10
- /** See documentation/notion.md#the-pinned-api-version. */
10
+ /**
11
+ * Pinned with no override. Notion's date-named versions keep working
12
+ * indefinitely, which makes an override look harmless; it is not. `2026-03-11`
13
+ * is where databases split into data sources, `archived` became `in_trash`, and
14
+ * block append took a `position` object instead of an `after` string. Every
15
+ * projection and write body here assumes those shapes, so an older version would
16
+ * return quietly wrong results rather than fail loudly.
17
+ */
11
18
  export const NOTION_API_VERSION = "2026-03-11";
12
19
  /** Notion's hard cap on `page_size` for every paginated endpoint. */
13
20
  const MAX_PAGE_SIZE = 100;
@@ -15,7 +22,12 @@ const MAX_PAGE_SIZE = 100;
15
22
  const DEFAULT_PAGE_SIZE = 25;
16
23
  /** Notion's cap on `children` per append, and on blocks per page create. */
17
24
  const MAX_CHILDREN_PER_REQUEST = 100;
18
- /** See documentation/notion.md#rate-limiting. */
25
+ /**
26
+ * Ceiling on the fetches one `get_page_content` walk may spend. Call admission
27
+ * meters tool calls, not the requests inside them, so a deep `depth` would
28
+ * otherwise drain the budget invisibly; the walk stops here and reports
29
+ * `truncated: true` instead.
30
+ */
19
31
  const MAX_CONTENT_REQUESTS = 20;
20
32
  /**
21
33
  * The largest response this connection will read.
@@ -26,7 +38,15 @@ const MAX_CONTENT_REQUESTS = 20;
26
38
  * a budget any real read has to think about.
27
39
  */
28
40
  const NOTION_MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
29
- /** See documentation/notion.md#rate-limiting. */
41
+ /**
42
+ * Notion documents "an average of three requests per second, with some bursts
43
+ * beyond the average allowed" per connection. 180 calls per minute is that
44
+ * average over a window short bursts pass and a sustained loop does not, and
45
+ * `maxConcurrency: 3` is the load-bearing half — an averaged budget cannot stop
46
+ * forty calls in one tick, and declaring a queue is also what makes the queue
47
+ * settings legal at construction. Per runtime, and a floor on the real request
48
+ * rate rather than a ceiling, since one admitted call can spend many fetches.
49
+ */
30
50
  const NOTION_ADMISSION = {
31
51
  rules: [
32
52
  {
@@ -38,7 +58,18 @@ const NOTION_ADMISSION = {
38
58
  },
39
59
  ],
40
60
  };
41
- /** See documentation/notion.md#typed-failures. */
61
+ /**
62
+ * Map to what the caller should do next, not to what Notion's `code` says
63
+ * happened. The two that are easy to mistranslate:
64
+ *
65
+ * - **403 is not an authentication failure.** The token is fine; the integration
66
+ * lacks a capability or was never shared the object, and neither
67
+ * `authorize_connector` nor a new token can fix it. Non-retryable call failure.
68
+ * - **404 does not prove absence.** Notion returns `object_not_found` both for an
69
+ * object that does not exist and for one never shared with the integration, and
70
+ * will not say which — so not `not_found`, which exists to assert absence
71
+ * (H11). The message names both possibilities.
72
+ */
42
73
  function notionFailure(status, body, headers) {
43
74
  const code = typeof body?.["code"] === "string" ? body["code"] : undefined;
44
75
  const detail = typeof body?.["message"] === "string" && body["message"].trim()
@@ -58,7 +89,7 @@ function notionFailure(status, body, headers) {
58
89
  return new ConnectorCallError("unavailable", `${labelled} Notion is overloaded; retry after the reported window.`, { retryAfterMs: retryAfter ?? 5_000 });
59
90
  }
60
91
  if (status === 401) {
61
- return new ConnectorCallError("auth_required", `${labelled} The Notion integration token is missing or invalid — an operator must set a valid token on /credentials.`);
92
+ return new ConnectorCallError("auth_required", `${labelled} The Notion integration token is missing or invalid. Call authorize_connector for recovery options. When available, an operator can set a valid token in this connection in the operator UI.`);
62
93
  }
63
94
  if (status === 403) {
64
95
  return new ConnectorCallError("connector_call_failed", `${labelled} The token is valid but this integration is not allowed to perform this operation. An operator must enable the matching capability on the Notion integration (comment capabilities are off by default) or share the object with it. Re-authorizing will not help.`, { retryable: false });
@@ -82,7 +113,18 @@ function notionFailure(status, body, headers) {
82
113
  retryable: false,
83
114
  });
84
115
  }
85
- /** See documentation/connectors.md#the-guarded-fetch-transport. */
116
+ /**
117
+ * The shared transport owns the mechanical, provider-independent guards: path
118
+ * confinement re-checked after URL normalization, encoded query construction,
119
+ * credential headers applied last and unshadowable, a refused 3xx (a redirect is
120
+ * an instruction to re-send the credential to another origin), and a body
121
+ * abandoned at `maxResponseBytes`. It deliberately owns no meaning — it never
122
+ * reads a status code, because Notion's 403 is an ungranted capability while
123
+ * another provider's is a token scope, and those want opposite next moves. The
124
+ * mapper below supplies that. Note it must re-throw `ConnectorCallError`: a bare
125
+ * catch around a body read swallows the transport's own refusal and turns a
126
+ * response nobody was allowed to read into an empty success.
127
+ */
86
128
  const send = guardedFetch({
87
129
  provider: "Notion",
88
130
  baseUrl: NOTION_API_BASE_URL,
@@ -91,7 +133,7 @@ const send = guardedFetch({
91
133
  authenticate: async (ctx) => {
92
134
  const token = (await ctx.credential?.get())?.trim();
93
135
  if (!token) {
94
- throw new ConnectorCallError("auth_required", "No Notion integration token is configured for this connector — an operator must add one on /credentials before any Notion call can run.");
136
+ throw new ConnectorCallError("auth_required", "No Notion integration token is configured for this connector. Call authorize_connector for recovery options. When available, an operator can add the token in this connection in the operator UI.");
95
137
  }
96
138
  return { Authorization: `Bearer ${token}` };
97
139
  },
@@ -108,7 +150,16 @@ async function notionRequest(ctx, request) {
108
150
  return payload ?? {};
109
151
  });
110
152
  }
111
- // Projections: documentation/notion.md#lean-projections-and-the-raw-escape-hatch.
153
+ // Projections. Notion wraps every property in a discriminated object, every
154
+ // string in an array of annotated rich-text runs, and every user in a nested
155
+ // object, so a small query is tens of kilobytes of structure around a few
156
+ // hundred bytes of meaning. Every read collapses that to ids, plain text, and
157
+ // flattened values. Nothing is lost for good: the reads that can drop something
158
+ // take `raw: true` and return Notion's untouched response instead.
159
+ //
160
+ // Unknown types unwrap rather than switch exhaustively, because Notion ships
161
+ // additive changes to every pinned version at once — a property type newer than
162
+ // this release degrades to its raw value instead of vanishing.
112
163
  /** Concatenate a rich-text array to its plain text. Safe for every variant. */
113
164
  function plainText(value) {
114
165
  if (!Array.isArray(value))
@@ -292,7 +343,11 @@ function projectPage(page, select) {
292
343
  ...(truncated.length ? { truncated_properties: truncated } : {}),
293
344
  };
294
345
  }
295
- /** See documentation/notion.md#lean-projections-and-the-raw-escape-hatch. */
346
+ /**
347
+ * Identity fields only, no properties. A 25-result search across a populated
348
+ * database would otherwise drag back hundreds of flattened values for results
349
+ * the agent is about to discard; `get_page` fetches them for the one that hit.
350
+ */
296
351
  function projectSearchHit(hit) {
297
352
  if (hit?.object === "data_source") {
298
353
  return {
@@ -359,7 +414,9 @@ function projectBlock(block, depth) {
359
414
  projected["icon"] = iconRef(payload?.icon);
360
415
  break;
361
416
  default:
362
- // Rationale: documentation/notion.md#lean-projections-and-the-raw-escape-hatch.
417
+ // A block type this projection does not model keeps its payload verbatim
418
+ // rather than collapsing to an empty string, so a block type newer than
419
+ // this release still carries its content.
363
420
  if (carriesUnprojectedContent(payload))
364
421
  projected["raw"] = payload;
365
422
  break;
@@ -705,6 +762,9 @@ function buildTools(defaultPageSize) {
705
762
  },
706
763
  {
707
764
  name: "get_page_content",
765
+ // `raw: true` returns the requested level exactly as Notion sent it and
766
+ // does not walk nested children, so `depth` is ignored alongside it: a
767
+ // raw read of a deep page yields one level.
708
768
  description: "Read a page's body as a flat list of blocks reduced to plain text. Each block keeps its id, type, and depth so it can be quoted, appended after, or drilled into. Nested content requires depth > 0.",
709
769
  annotations: { readOnlyHint: true },
710
770
  inputSchema: {
@@ -1107,7 +1167,11 @@ function buildTools(defaultPageSize) {
1107
1167
  description: "Create a page, either as a child of another page or as a row in a data source. Notion has no idempotency key: a retried create makes a second page, so confirm with search before repeating one.",
1108
1168
  annotations: { readOnlyHint: false },
1109
1169
  inputSchema: {
1110
- // See documentation/notion.md#what-this-connection-does-not-do.
1170
+ // A deliberate subset of the 2026-03-11 create contract, reviewed after
1171
+ // the 0.17.0 drift check (#408). Workspace-private pages, templates,
1172
+ // page placement, and the expanded icon/cover forms stay out: they
1173
+ // change ownership, start asynchronous content work, control ordering,
1174
+ // or depend on file surfaces, none of which extend page/row authoring.
1111
1175
  required: [],
1112
1176
  type: "object",
1113
1177
  properties: {
@@ -1263,6 +1327,10 @@ function buildTools(defaultPageSize) {
1263
1327
  },
1264
1328
  {
1265
1329
  name: "update_page_properties",
1330
+ // Deliberately no locking, templates, or `erase_content` (#409): locking
1331
+ // is coordination state, templates finish asynchronously, and
1332
+ // `erase_content` permanently deletes every child block. None belongs
1333
+ // under an approval named for property replacement.
1266
1334
  description: "Overwrite property values on an existing page. Every named property is replaced, not merged, so send a multi_select or relation's complete intended value. Cannot move a page and cannot trash one.",
1267
1335
  // Replaces values that already exist: the host should say so out loud.
1268
1336
  annotations: { readOnlyHint: false, destructiveHint: true },
@@ -1396,7 +1464,12 @@ function buildTools(defaultPageSize) {
1396
1464
  // ---------------------------------------------------------------------------
1397
1465
  // Guide and constructor
1398
1466
  // ---------------------------------------------------------------------------
1399
- /** See documentation/notion.md#databases-contain-data-sources. */
1467
+ /**
1468
+ * Marked `required` for one reason: a Notion database is a container, and the
1469
+ * rows and schema live in a data source inside it. The id in a database URL is a
1470
+ * database id, and passing it to `query_data_source` or `create_page` fails —
1471
+ * a trap no input schema can teach, so the guide has to.
1472
+ */
1400
1473
  function apiUsageGuide(purpose, instructions) {
1401
1474
  const accountInstructions = instructions?.trim();
1402
1475
  return `# Notion usage
@@ -3,7 +3,12 @@ import type { Connector, ConnectorCallAdmissionPolicy } from "../types.js";
3
3
  /** RevenueCat publishes one hosted MCP endpoint, streamable HTTP. */
4
4
  export declare const REVENUECAT_MCP_ENDPOINT = "https://mcp.revenuecat.ai/mcp";
5
5
  export interface RevenueCatOptions {
6
- /** Display name; scope defaults are in `documentation/revenuecat.md`. */
6
+ /**
7
+ * Display name. It defaults to the credential's scope, because that is what
8
+ * decides how an agent must address the connector: `RevenueCat (single
9
+ * project)` for a project-wide `sk_` key, plain `RevenueCat` for an
10
+ * account-scoped OAuth session that must resolve a `project_id` first.
11
+ */
7
12
  title?: string;
8
13
  /** Downstream auth ownership. Defaults to one shared deployment grant. */
9
14
  authScope?: "shared" | "personal";
@@ -13,16 +18,37 @@ export interface RevenueCatOptions {
13
18
  * so it goes in the guide's first line and its summary.
14
19
  */
15
20
  purpose: string;
16
- /** OAuth or a single-project API v2 key; see `documentation/revenuecat.md`. */
21
+ /**
22
+ * OAuth (account-scoped, reaches every project the account can see) or an
23
+ * API v2 `sk_` secret key, which RevenueCat scopes to exactly one project —
24
+ * hence one key, one connector, and no `project` option: checking a declared
25
+ * project against `list-projects` at construction would be the unasked-for
26
+ * credential test P10 forbids. Nor can Connecta tell a read-only key from a
27
+ * write-enabled one without spending a call, so every write is offered and a
28
+ * read-only key's refusal arrives from RevenueCat.
29
+ */
17
30
  auth?: RemoteMcpAuth;
18
31
  /** Project-specific conventions appended to the maintained provider guide. */
19
32
  instructions?: string;
20
33
  /** Connector-specific inline result limit; omit to inherit the deployment. */
21
34
  maxResultBytes?: number;
22
- /** Optional per-runtime policy; see `documentation/revenuecat.md#rate-limits`. */
35
+ /**
36
+ * Optional per-runtime policy. There is no default even though RevenueCat
37
+ * publishes numbers (API v2, read 2026-08-18): the limit is per minute per
38
+ * domain — 480 for customer information, virtual currencies, and refunds; 60
39
+ * for audiences and project configuration; 25 for charts and metrics — and a
40
+ * policy carries one rule, so any single number is wrong for most tools. The
41
+ * metering scope is per developer for developer-level keys, which a
42
+ * per-runtime counter cannot approximate either. The operator picks (P12).
43
+ */
23
44
  callAdmission?: ConnectorCallAdmissionPolicy;
24
45
  }
25
- /** Release-reviewed manifest; see provider conventions P5 and P13. */
46
+ /**
47
+ * One release-reviewed manifest, used both to classify a live tool and as the
48
+ * baseline the drift check compares against, so the annotation a caller gets
49
+ * and the verdict a check reads can never disagree. Names and verdicts only —
50
+ * no schemas are vendored; the live `tools/list` response stays authoritative.
51
+ */
26
52
  export declare const REVENUECAT_VETTED_CATALOG: import("../catalog-drift.js").VettedCatalog;
27
53
  /** A maintained RevenueCat hosted-MCP connection. */
28
54
  export declare function revenuecat(id: string, options: RevenueCatOptions): Connector;
@@ -3,7 +3,12 @@ import { vettedCatalog, withVettedCatalog } from "../catalog-drift.js";
3
3
  import { defined } from "../connectors/api.js";
4
4
  /** RevenueCat publishes one hosted MCP endpoint, streamable HTTP. */
5
5
  export const REVENUECAT_MCP_ENDPOINT = "https://mcp.revenuecat.ai/mcp";
6
- /** Reviewed reads; see `documentation/revenuecat.md` and convention P5. */
6
+ /**
7
+ * Reviewed reads, every `Read` row of RevenueCat's tool reference as read on
8
+ * 2026-09-21 (P5). The list is a superset: plan, platform, and beta enrollment
9
+ * gate parts of the catalog, so a name this account never returns costs
10
+ * nothing while an unlisted new one fails closed.
11
+ */
7
12
  const READ_ONLY_TOOLS = new Set([
8
13
  // Projects and apps
9
14
  "get-account-billing",
@@ -18,7 +23,11 @@ const READ_ONLY_TOOLS = new Set([
18
23
  // Products and prices
19
24
  "get-product",
20
25
  "get-product-store-state",
26
+ // Deprecated in RevenueCat's reference — superseded by the plan family's
27
+ // own read-back below — but still served, so the classification stands.
21
28
  "get-product-store-state-operation",
29
+ "get-product-store-state-plan",
30
+ "list-product-store-state-plans",
22
31
  "list-products",
23
32
  // Entitlements
24
33
  "get-entitlement",
@@ -70,26 +79,65 @@ const READ_ONLY_TOOLS = new Set([
70
79
  // *start* one are writes and sit below.
71
80
  "get-paywall-ai-task",
72
81
  ]);
73
- /** Reviewed writes and verb exceptions: `documentation/revenuecat.md`. */
82
+ /**
83
+ * Reviewed writes with their destructive verdict. Most follow the verb, and
84
+ * additive writes stay additive because `readOnlyHint: false` already routes
85
+ * them through the approval path — asserting a destruction that does not happen
86
+ * only inflates the copy a human reads. The verdicts the verb does not decide
87
+ * carry their reason on the row.
88
+ *
89
+ * `render-paywall-screenshot` is deliberately in neither map: RevenueCat's
90
+ * reference gives it no access column, so the live annotation stands and a
91
+ * catalog that omits one fails closed. Guessing from a harmless-sounding name
92
+ * is what P5 exists to prevent.
93
+ */
74
94
  const WRITE_TOOLS = new Map([
75
95
  // Projects and apps
76
96
  ["create-app", "additive"],
77
97
  ["create-project", "additive"],
78
98
  ["update-app", "destructive"],
79
99
  ["update-project-ui-config", "destructive"],
100
+ // Filed `Write` by RevenueCat, so it cannot take the read path, but it leaves
101
+ // the saved credentials alone and only records a check's outcome.
80
102
  ["validate-app-credentials", "additive"],
81
103
  // Products and prices
82
104
  ["archive-product", "destructive"],
83
105
  ["create-product", "additive"],
106
+ // Named `create-`, but "Configure prices for a product": the price set
107
+ // already exists, so this overwrites it, and it is money-facing. Deprecated
108
+ // in RevenueCat's reference; products are priced through the plan family
109
+ // below, but the tool is still served, so the classification stands.
84
110
  ["create-product-prices", "destructive"],
111
+ // "Fills *missing* App Store subscription territory prices" — by
112
+ // RevenueCat's own word it writes only where nothing is set. Deprecated in
113
+ // RevenueCat's reference; still served, so the classification stands.
85
114
  ["equalize-subscription-prices", "additive"],
115
+ // An upsert. Deprecated in RevenueCat's reference — the store-state plan
116
+ // family below replaces it — but still served, so the classification stands.
86
117
  ["set-product-store-state", "destructive"],
118
+ // Sends products to Apple for review.
87
119
  ["submit-products-to-store", "destructive"],
88
120
  ["unarchive-product", "destructive"],
89
121
  ["update-product", "destructive"],
122
+ // Reserves a new App Store Connect review screenshot slot; replaces nothing.
123
+ // Deprecated in RevenueCat's reference; still served, so the classification
124
+ // stands.
90
125
  ["upload-product-store-state-screenshot", "additive"],
126
+ // Product store state plans, the workflow RevenueCat deprecated
127
+ // `set-product-store-state` for. Create brings a draft into being and
128
+ // touches nothing else; plan recomputes an existing plan's proposed change,
129
+ // overwriting its previous planning; apply pushes the whole plan into
130
+ // RevenueCat and the app stores; update and discard act on an existing plan
131
+ // by name. Every step past create is asynchronous.
132
+ ["create-product-store-state-plan", "additive"],
133
+ ["plan-product-store-state-plan", "destructive"],
134
+ ["apply-product-store-state-plan", "destructive"],
135
+ ["update-product-store-state-plan", "destructive"],
136
+ ["discard-product-store-state-plan", "destructive"],
91
137
  // Entitlements
92
138
  ["archive-entitlement", "destructive"],
139
+ // Attach adds membership and removes nothing; detach is the destructive half.
140
+ // Filing both destructive would make the pair read identically to a human.
93
141
  ["attach-products-to-entitlement", "additive"],
94
142
  ["create-entitlement", "additive"],
95
143
  ["detach-products-from-entitlement", "destructive"],
@@ -97,6 +145,7 @@ const WRITE_TOOLS = new Map([
97
145
  ["update-entitlement", "destructive"],
98
146
  // Offerings and packages
99
147
  ["archive-offering", "destructive"],
148
+ // The attach/detach argument again, one level down.
100
149
  ["attach-products-to-package", "additive"],
101
150
  ["create-offering", "additive"],
102
151
  ["create-packages", "additive"],
@@ -107,9 +156,15 @@ const WRITE_TOOLS = new Map([
107
156
  // Targeting and audiences
108
157
  ["create-audience", "additive"],
109
158
  ["update-audience", "destructive"],
159
+ // Targeting rules follow the verb: create brings one into being, update
160
+ // and delete act on a rule the project already has.
161
+ ["create-targeting-rule", "additive"],
162
+ ["update-targeting-rule", "destructive"],
163
+ ["delete-targeting-rule", "destructive"],
110
164
  // Paywalls
111
165
  ["attach-offering-to-paywall", "destructive"],
112
166
  ["detach-offering-from-paywall", "destructive"],
167
+ // Copies an existing paywall's current draft; the original is untouched.
113
168
  ["duplicate-paywall", "additive"],
114
169
  ["publish-paywall", "destructive"],
115
170
  ["unpublish-paywall", "destructive"],
@@ -130,14 +185,23 @@ const WRITE_TOOLS = new Map([
130
185
  ["unarchive-virtual-currency", "destructive"],
131
186
  ["update-virtual-currency", "destructive"],
132
187
  // Integrations and webhooks
188
+ // Destructive on consequence, not on the verb: no existing integration
189
+ // changes, but with filters omitted the new one starts delivering every
190
+ // customer event in the project to a URL the caller typed.
133
191
  ["create-webhook-integration", "destructive"],
134
192
  ["delete-webhook-integration", "destructive"],
135
193
  ["update-webhook-integration", "destructive"],
136
- // Paywall editing
194
+ // Paywall editing. Both start an async task; the difference is what the task
195
+ // touches — a new paywall, or a draft that already exists.
137
196
  ["create-paywall-ai", "additive"],
138
197
  ["edit-paywall-ai", "destructive"],
139
198
  ]);
140
- /** Release-reviewed manifest; see provider conventions P5 and P13. */
199
+ /**
200
+ * One release-reviewed manifest, used both to classify a live tool and as the
201
+ * baseline the drift check compares against, so the annotation a caller gets
202
+ * and the verdict a check reads can never disagree. Names and verdicts only —
203
+ * no schemas are vendored; the live `tools/list` response stays authoritative.
204
+ */
141
205
  export const REVENUECAT_VETTED_CATALOG = vettedCatalog({
142
206
  reads: READ_ONLY_TOOLS,
143
207
  writes: WRITE_TOOLS,
@@ -166,7 +230,8 @@ function sharedUsageGuide() {
166
230
  - Customer and subscription objects are large, and a customer's history is larger. Page with the cursor the list returned rather than raising the page size, and reduce inside \`execute_code\` — select the fields the question needs and return those, not the whole object.
167
231
  - Whether a customer should have access is \`gives_access\` on each subscription from \`list-subscriptions\`, which RevenueCat calls the authoritative flag. \`status\` and \`expires_date\` describe the store-side state and disagree with it during grace periods, billing retries, and promotional grants — answer access questions from \`gives_access\` and say which subscription it came from.
168
232
  - \`get-chart-data\` is the metrics path: read \`get-chart-options-schema\` for the chart you want before calling it, rather than guessing an option name. \`get-overview-metrics\` and \`get-revenue-metric\` answer the summary questions in one call.
169
- - \`create-paywall-ai\`, \`edit-paywall-ai\`, and \`set-product-store-state\` are asynchronous. They return a task or operation id; poll it with \`get-paywall-ai-task\` or \`get-product-store-state-operation\` rather than assuming the work finished when the call returned.
233
+ - \`create-paywall-ai\` and \`edit-paywall-ai\` are asynchronous. Poll the task id they return with \`get-paywall-ai-task\` rather than assuming the work finished when the call returned.
234
+ - Store changes ride the plan workflow, not the deprecated direct tools: \`create-product-store-state-plan\`, then \`plan-product-store-state-plan\`, then \`apply-product-store-state-plan\`. Every step past the create is asynchronous — read the plan back with \`get-product-store-state-plan\` between steps instead of assuming the last one finished.
170
235
  - This connection's tool list is not a fixed set. RevenueCat gates parts of its MCP catalog by plan, platform, and beta enrollment — paywall AI editing, benchmarks, experiments, virtual currencies, and the account-billing tools are the usual absentees — so search this connector for what it actually exposes rather than assuming a documented tool is here.
171
236
  - \`render-paywall-screenshot\` is unclassified on purpose because RevenueCat's reference gives it no access column. The current server marks it read-only, which Connecta preserves; without that annotation it fails closed onto \`call_destructive_tool\`.
172
237
  - RevenueCat meters API v2 per minute and per domain, and the domains differ: 480 requests per minute for customer information and virtual currencies, 60 for project configuration and audiences, 25 for charts and metrics. It answers a breach with \`429\`, a \`Retry-After\` header, and a \`backoff_ms\` field. Back off on that rather than retrying immediately, and expect chart sweeps to hit the ceiling long before customer reads do.
@@ -28,8 +28,9 @@ export interface StripeOAuthOptions extends StripeCommonOptions {
28
28
  * Static credentials have one fixed mode, including Stripe Connect calls.
29
29
  *
30
30
  * Both static shapes belong here: a key the deployment supplies as a literal
31
- * header, and one the operator pastes at `/credentials`. Neither can discover
32
- * its own mode — a restricted key answers for exactly one — so both declare it.
31
+ * header, and one the operator pastes into the connection in the operator UI.
32
+ * Neither can discover its own mode, since a restricted key answers for
33
+ * exactly one, so both declare it.
33
34
  */
34
35
  export interface StripeHeaderOptions extends StripeCommonOptions {
35
36
  auth: Exclude<RemoteMcpAuth, {
@@ -40,7 +41,13 @@ export interface StripeHeaderOptions extends StripeCommonOptions {
40
41
  connectedAccount?: string;
41
42
  }
42
43
  export type StripeOptions = StripeOAuthOptions | StripeHeaderOptions;
43
- /** Release-reviewed manifest; see provider conventions P5 and P13. */
44
+ /**
45
+ * One release-reviewed manifest, used both to classify a live tool and as the
46
+ * baseline the drift check compares against, so the annotation a caller gets and
47
+ * the verdict a check reads can never disagree. Stripe publishes no stability or
48
+ * deprecation policy for this tool set, so treat it as unversioned: an
49
+ * unclassified, unannotated name fails closed onto the approval path (P5).
50
+ */
44
51
  export declare const STRIPE_VETTED_CATALOG: import("../catalog-drift.js").VettedCatalog;
45
52
  /** A maintained Stripe hosted-MCP connection. */
46
53
  export declare function stripe(id: string, options: StripeOptions): Connector;
@@ -3,7 +3,14 @@ import { vettedCatalog, withVettedCatalog } from "../catalog-drift.js";
3
3
  import { defined } from "../connectors/api.js";
4
4
  /** Stripe publishes one hosted MCP endpoint for every account and mode. */
5
5
  export const STRIPE_MCP_ENDPOINT = "https://mcp.stripe.com/";
6
- /** Account limits and local concurrency rationale: `documentation/stripe.md`. */
6
+ /**
7
+ * Stripe documents no MCP-specific limit, so these transcribe the account limit
8
+ * MCP traffic spends: 100 requests per second live, 25 in a sandbox. OAuth takes
9
+ * the sandbox rule (`mode ?? "sandbox"` below) because one session can reach
10
+ * either mode and the policy is fixed before the account-scoped call begins.
11
+ * The `maxConcurrency` figures are Connecta's own choice — Stripe says
12
+ * per-account and per-endpoint concurrency limits exist but publishes no number.
13
+ */
7
14
  const STRIPE_ADMISSION = {
8
15
  production: {
9
16
  rules: [
@@ -47,16 +54,30 @@ const READ_ONLY_TOOLS = new Set([
47
54
  "manage_stripe_accounts",
48
55
  "search_stripe_documentation",
49
56
  ]);
50
- /** Reviewed writes, including mixed read/create tools: `documentation/stripe.md`. */
57
+ /**
58
+ * Reviewed writes with their destructive verdict. `stripe_api_write` is the
59
+ * sibling of `stripe_api_read` and carries every `POST`, `PATCH`, `PUT`, and
60
+ * `DELETE`. Additive writes leave `destructiveHint` unset: `readOnlyHint: false`
61
+ * already routes them through the approval path, and asserting destruction they
62
+ * do not perform would misstate their effect.
63
+ */
51
64
  const WRITE_TOOLS = new Map([
52
65
  ["stripe_api_write", "destructive"],
53
66
  ["create_refund", "destructive"],
67
+ // Creates and continues provider-side guide state; destroys nothing.
54
68
  ["stripe_implementation_planner", "additive"],
69
+ // Retrieval mixed with query-run creation behind one tool.
55
70
  ["stripe_analytics", "additive"],
56
71
  ["stripe_report", "additive"],
57
72
  ["send_stripe_mcp_feedback", "additive"],
58
73
  ]);
59
- /** Release-reviewed manifest; see provider conventions P5 and P13. */
74
+ /**
75
+ * One release-reviewed manifest, used both to classify a live tool and as the
76
+ * baseline the drift check compares against, so the annotation a caller gets and
77
+ * the verdict a check reads can never disagree. Stripe publishes no stability or
78
+ * deprecation policy for this tool set, so treat it as unversioned: an
79
+ * unclassified, unannotated name fails closed onto the approval path (P5).
80
+ */
60
81
  export const STRIPE_VETTED_CATALOG = vettedCatalog({
61
82
  reads: READ_ONLY_TOOLS,
62
83
  writes: WRITE_TOOLS,
@@ -64,7 +85,12 @@ export const STRIPE_VETTED_CATALOG = vettedCatalog({
64
85
  /** Stripe key prefixes carry their own mode; only a clear reading counts. */
65
86
  const LIVE_KEY = /\b(?:sk|rk|pk)_live_/;
66
87
  const TEST_KEY = /\b(?:sk|rk|pk)_test_/;
67
- /** Refuse a recognizable key/mode mismatch; see `documentation/stripe.md`. */
88
+ /**
89
+ * Refuse a recognizable key/mode mismatch. This is the only mode guard Connecta
90
+ * can offer, and it needs the key as a literal header to read the prefix: an
91
+ * operator-managed key is not in the deployment file, so its declared mode
92
+ * stands alone and a key pointed at the other environment fails at Stripe.
93
+ */
68
94
  function assertModeMatchesKey(id, mode, auth) {
69
95
  if (auth.type !== "headers")
70
96
  return;