@zackbart/connecta 0.24.4 → 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 (107) hide show
  1. package/AGENTS.md +40 -10
  2. package/CHANGELOG.md +253 -0
  3. package/dist/advertised-schema.js +53 -0
  4. package/dist/auth/downstream-oauth.js +490 -106
  5. package/dist/call-admission.js +139 -113
  6. package/dist/catalog-service.js +141 -105
  7. package/dist/catalog.js +258 -33
  8. package/dist/concurrency.js +24 -23
  9. package/dist/connector-scope.js +6 -37
  10. package/dist/connectors/api.d.ts +2 -2
  11. package/dist/connectors/remote-mcp.d.ts +6 -5
  12. package/dist/connectors/remote-mcp.js +317 -241
  13. package/dist/credential-contract.d.ts +9 -0
  14. package/dist/credential-rules.js +2 -2
  15. package/dist/credentials.d.ts +6 -1
  16. package/dist/credentials.js +45 -3
  17. package/dist/execute.js +363 -257
  18. package/dist/executor-admission.js +67 -50
  19. package/dist/executors/quickjs-runtime.js +4 -2
  20. package/dist/executors/quickjs.js +251 -210
  21. package/dist/fix-prompt.js +37 -0
  22. package/dist/index.d.ts +10 -1
  23. package/dist/index.js +51 -20
  24. package/dist/invocation.js +324 -285
  25. package/dist/meta-tools.js +206 -92
  26. package/dist/oauth-callback-outcome.js +94 -0
  27. package/dist/oauth-sealing.js +29 -0
  28. package/dist/operator-ui/fix-prompts.js +87 -0
  29. package/dist/operator-ui/generated.js +2 -2
  30. package/dist/operator-ui/model.d.ts +46 -1
  31. package/dist/operator-ui/setup-commands.js +53 -0
  32. package/dist/operator-ui/view.js +38 -2
  33. package/dist/program-source.js +205 -0
  34. package/dist/providers/notion.js +2 -2
  35. package/dist/providers/revenuecat.js +32 -5
  36. package/dist/providers/stripe.d.ts +3 -2
  37. package/dist/providers/vercel.js +1 -1
  38. package/dist/registry.d.ts +1 -1
  39. package/dist/registry.js +305 -260
  40. package/dist/routes/activity.js +120 -137
  41. package/dist/routes/credentials.js +174 -163
  42. package/dist/routes/mcp.js +129 -70
  43. package/dist/routes/oauth-management.js +60 -71
  44. package/dist/routes/oauth.js +44 -16
  45. package/dist/routes/operator.js +82 -0
  46. package/dist/routes/shared.d.ts +2 -0
  47. package/dist/routes/ui-data.js +210 -0
  48. package/dist/routes/ui.js +64 -32
  49. package/dist/runtime/admission.js +43 -0
  50. package/dist/runtime/call-admission.js +67 -0
  51. package/dist/runtime/connector-scope.js +72 -0
  52. package/dist/runtime/run.js +174 -0
  53. package/dist/runtime/services.js +130 -0
  54. package/dist/runtime/storage.js +66 -0
  55. package/dist/server.js +111 -89
  56. package/dist/skills.js +1 -1
  57. package/dist/storage/file.js +39 -2
  58. package/dist/storage/memory.js +28 -16
  59. package/dist/timeout.js +17 -47
  60. package/dist/types.d.ts +27 -7
  61. package/dist/ui.d.ts +16 -2
  62. package/dist/ui.js +50 -166
  63. package/dist/url-safety.js +157 -0
  64. package/dist/version.d.ts +1 -1
  65. package/dist/version.js +1 -1
  66. package/documentation/architecture.md +184 -10
  67. package/documentation/auth.md +80 -0
  68. package/documentation/code-mode.md +69 -24
  69. package/documentation/meta-tools.md +164 -27
  70. package/ethos.md +44 -47
  71. package/examples/worker/README.md +51 -5
  72. package/examples/worker/src/d1-storage.ts +116 -0
  73. package/examples/worker/src/index.ts +6 -6
  74. package/examples/worker/wrangler.jsonc +16 -4
  75. package/package.json +11 -3
  76. package/templates/node/package.json +1 -1
  77. package/dist/auth/downstream-oauth.d.ts +0 -200
  78. package/dist/catalog-fingerprint.d.ts +0 -19
  79. package/dist/catalog-limits.d.ts +0 -9
  80. package/dist/catalog-service.d.ts +0 -263
  81. package/dist/catalog.d.ts +0 -87
  82. package/dist/concurrency.d.ts +0 -3
  83. package/dist/connectors/guarded-fetch.d.ts +0 -96
  84. package/dist/credential-rules.d.ts +0 -85
  85. package/dist/execute.d.ts +0 -154
  86. package/dist/executor-result.d.ts +0 -10
  87. package/dist/executors/quickjs-child.d.ts +0 -1
  88. package/dist/executors/quickjs-protocol.d.ts +0 -67
  89. package/dist/favicon.d.ts +0 -1
  90. package/dist/identity.d.ts +0 -4
  91. package/dist/invocation.d.ts +0 -74
  92. package/dist/mcp-result.d.ts +0 -8
  93. package/dist/meta-tools.d.ts +0 -129
  94. package/dist/operator-ui/generated.d.ts +0 -2
  95. package/dist/operator-ui/view.d.ts +0 -188
  96. package/dist/result-shapes.d.ts +0 -13
  97. package/dist/retired-toolkits.d.ts +0 -2
  98. package/dist/routes/activity.d.ts +0 -2
  99. package/dist/routes/credentials.d.ts +0 -2
  100. package/dist/routes/mcp.d.ts +0 -10
  101. package/dist/routes/oauth-management.d.ts +0 -2
  102. package/dist/routes/oauth.d.ts +0 -2
  103. package/dist/routes/ui.d.ts +0 -2
  104. package/dist/server.d.ts +0 -9
  105. package/dist/skills.d.ts +0 -44
  106. package/dist/timeout.d.ts +0 -21
  107. package/dist/tool-safety.d.ts +0 -9
@@ -1,9 +1,41 @@
1
1
  import type { CatalogAccessObservation, CatalogDriftReport } from "../types.js";
2
+ /**
3
+ * Which call path a tool takes, decided on the server by the same
4
+ * `isExplicitlyReadOnly` predicate discovery and invocation use
5
+ * (`src/tool-safety.ts`) and never re-derived in the browser: a second copy of
6
+ * the rule is a second place for it to be wrong.
7
+ *
8
+ * - `runs_in_programs` — explicitly read-only, so `execute_code` may call it.
9
+ * - `needs_approval` — everything else, which crosses `call_destructive_tool`.
10
+ *
11
+ * Planned resumable writes add a third value for a config `approval: "never"`
12
+ * exemption; the badge renders from a table keyed by this union so that value
13
+ * is one entry, not a new branch.
14
+ */
15
+ export type UiToolSafety = "runs_in_programs" | "needs_approval";
2
16
  export interface UiTool {
3
17
  name: string;
4
18
  address: string;
5
19
  description?: string;
20
+ /** Absent only in payloads older than the field; the page then shows no badge. */
21
+ safety?: UiToolSafety;
6
22
  }
23
+ /**
24
+ * What went wrong with a connector, as a closed set the server chooses from.
25
+ * The browser keys both its on-screen description and a fixed fix prompt off
26
+ * this. There is deliberately no message beside it: a connector's status
27
+ * message can quote a downstream error body, so it goes to the deployment's
28
+ * log and never into the operator payload.
29
+ *
30
+ * `auth_required` splits by who has to act: a downstream OAuth grant
31
+ * (`oauth_required`, which also covers an expired or revoked grant — the
32
+ * status seam cannot tell those apart), an operator-managed credential slot
33
+ * (`credential_required`), or a secret that lives in deployment configuration
34
+ * (`auth_required`).
35
+ */
36
+ export type UiProblem = "connector_unavailable" | "oauth_required" | "credential_required" | "auth_required" | "credential_mismatch" | "catalog_failed";
37
+ /** A stored credential that cannot be used, by cause. */
38
+ export type UiCredentialProblem = "credential_mismatch" | "credential_unreadable";
7
39
  interface UiCredentialField {
8
40
  name: string;
9
41
  label: string;
@@ -26,6 +58,8 @@ interface UiCredential {
26
58
  updatedAt?: string;
27
59
  testable: boolean;
28
60
  error?: string;
61
+ /** Present exactly when `error` is: the cause, for the fix prompt. */
62
+ problem?: UiCredentialProblem;
29
63
  /**
30
64
  * Something true and non-blocking about a working credential — today, that
31
65
  * the vault still holds fields the connector has stopped declaring.
@@ -45,7 +79,12 @@ export interface UiConnector {
45
79
  manageSharedAuth: boolean;
46
80
  connectPersonal: boolean;
47
81
  };
48
- message?: string;
82
+ /**
83
+ * Why this connector is not usable, when it is not. See `UiProblem`. The
84
+ * only failure detail the payload carries; the status message behind it is
85
+ * logged on the server, not shipped.
86
+ */
87
+ problem?: UiProblem;
49
88
  authorizationUrl?: string;
50
89
  toolCount: number;
51
90
  tools: UiTool[];
@@ -77,6 +116,12 @@ export interface UiData {
77
116
  credentialManagement: CredentialManagementCapability;
78
117
  /** True when this interactive human may manage any visible OAuth connector. */
79
118
  oauthManagement: boolean;
119
+ /**
120
+ * Names of the `/mcp/<name>` pools this identity's grant admits. A pool
121
+ * whose grant refuses or throws is absent, exactly as the endpoint answers
122
+ * it with a 404, so the page never enumerates a pool its reader cannot open.
123
+ */
124
+ pools?: string[];
80
125
  }
81
126
  export interface FilteredUiConnector {
82
127
  connector: UiConnector;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Client setup snippets for one MCP endpoint: the deployment's `/mcp` or a
3
+ * `/mcp/<pool>`. Three formats and no more — Claude Code's CLI, Codex's CLI,
4
+ * and the `mcpServers` JSON most other clients read — because a longer list
5
+ * is a list to keep current.
6
+ *
7
+ * None of them carries a credential. A client authenticates through the
8
+ * deployment's inbound auth — OAuth discovery where that is configured — and
9
+ * a token pasted into a snippet on this page would be one rendered by it.
10
+ */
11
+ /**
12
+ * The name a client files this server under. Derived from the deployment's
13
+ * configured server name, reduced to the characters every client CLI accepts
14
+ * unquoted, with the pool appended so two endpoints never collide.
15
+ */
16
+ export function clientServerName(serverName, pool) {
17
+ const base = (serverName ?? "")
18
+ .toLowerCase()
19
+ .replace(/[^a-z0-9_-]+/g, "-")
20
+ .replace(/-{2,}/g, "-")
21
+ .replace(/^-+|-+$/g, "")
22
+ .slice(0, 48) || "connecta";
23
+ return pool ? `${base}-${pool}` : base;
24
+ }
25
+ /** The URL for a pool: `/mcp` plus its name, which config limits to `[a-z0-9_-]`. */
26
+ export function poolEndpointUrl(mcpUrl, pool) {
27
+ return `${mcpUrl.replace(/\/+$/, "")}/${encodeURIComponent(pool)}`;
28
+ }
29
+ /** POSIX single-quoting, only when the value needs it. */
30
+ function shellWord(value) {
31
+ return /^[A-Za-z0-9_./:@%+=,~-]+$/.test(value)
32
+ ? value
33
+ : `'${value.replace(/'/g, `'"'"'`)}'`;
34
+ }
35
+ export function clientSetupCommands(name, url) {
36
+ return [
37
+ {
38
+ id: "claude",
39
+ label: "Claude Code",
40
+ text: `claude mcp add --transport http ${shellWord(name)} ${shellWord(url)}`,
41
+ },
42
+ {
43
+ id: "codex",
44
+ label: "Codex",
45
+ text: `codex mcp add ${shellWord(name)} --url ${shellWord(url)}`,
46
+ },
47
+ {
48
+ id: "json",
49
+ label: "JSON config",
50
+ text: JSON.stringify({ mcpServers: { [name]: { type: "http", url } } }, null, 2),
51
+ },
52
+ ];
53
+ }
@@ -13,8 +13,8 @@ export function pageForPath(path) {
13
13
  export function info(message) {
14
14
  return { message, tone: "info" };
15
15
  }
16
- export function failure(message) {
17
- return { message, tone: "error" };
16
+ export function failure(message, fix) {
17
+ return fix ? { message, tone: "error", fix } : { message, tone: "error" };
18
18
  }
19
19
  export function initialState(page) {
20
20
  return {
@@ -150,6 +150,42 @@ export function connectorSummaryParts(summary) {
150
150
  { text: toolCountLabel(summary.tools), tone: "neutral" },
151
151
  ];
152
152
  }
153
+ /**
154
+ * The badge for each call path, keyed by the server's classification so the
155
+ * planned third state is a row here rather than a new branch in a component.
156
+ */
157
+ export const TOOL_SAFETY_BADGE = {
158
+ runs_in_programs: {
159
+ label: "runs in programs",
160
+ tone: "ok",
161
+ title: "Explicitly read-only: execute_code programs may call it without asking.",
162
+ },
163
+ needs_approval: {
164
+ label: "needs approval",
165
+ tone: "warn",
166
+ title: "Not explicitly read-only: calls cross call_destructive_tool, where the host asks first.",
167
+ },
168
+ };
169
+ /**
170
+ * What the page says about a connector that is not usable, one fixed sentence
171
+ * per problem kind the server classified. This is the only account of a
172
+ * failure the Connections page renders: a connector's status message can quote
173
+ * a downstream error body, and a downstream error body can quote the secret
174
+ * that was just rejected, so the raw text stays in the deployment's log and
175
+ * never reaches the payload or the DOM. The fix prompt beside it is keyed off
176
+ * the same kind.
177
+ */
178
+ const PROBLEM_COPY = {
179
+ connector_unavailable: "Unavailable: its status check or catalog load failed, or did not finish in time. The deployment's log has the downstream error.",
180
+ oauth_required: "Needs OAuth authorization: no grant is stored, or the stored grant expired or was revoked.",
181
+ credential_required: "Needs a credential: nothing usable is stored in its credential slot.",
182
+ auth_required: "Needs authorization. Its secret lives in deployment configuration, not on this page.",
183
+ credential_mismatch: "The stored credential does not match the fields this connector declares, so it cannot be used.",
184
+ catalog_failed: "Connected, but its tool catalog could not be loaded, so none of its tools are served.",
185
+ };
186
+ export function problemCopy(problem) {
187
+ return problem ? PROBLEM_COPY[problem] ?? null : null;
188
+ }
153
189
  /** Who owns this connector's downstream credentials, in two words. */
154
190
  export function authScopeLabel(scope) {
155
191
  return scope === "personal" ? "personal auth" : "shared auth";
@@ -0,0 +1,205 @@
1
+ // Program source handling shared by every executor. Web-API only: execute_code
2
+ // runs it before handing a program to QuickJS or a Dynamic Worker, and the
3
+ // QuickJS normalizer runs it too for callers that drive that executor directly.
4
+ /** A whole-program markdown fence, as QuickJS's normalizer recognizes one. */
5
+ const FENCE = /^```[\w-]*\s*\n([\s\S]*?)\n?```$/;
6
+ /** Leading whitespace and comments, which may precede the arrow expression. */
7
+ const LEADING_TRIVIA = /^(?:\s+|\/\/[^\n]*|\/\*[\s\S]*?\*\/)+/;
8
+ /** One character of an identifier, keyword, or number; `\\` for escapes. */
9
+ const WORD = /[\w$\\\u0080-\uffff]/;
10
+ /**
11
+ * Words after which a `/` begins a regular expression rather than dividing.
12
+ * Any other word is an identifier or a literal, which a `/` divides.
13
+ */
14
+ const REGEX_AFTER_WORD = new Set([
15
+ "await", "case", "delete", "do", "else", "in", "instanceof", "new", "of",
16
+ "return", "throw", "typeof", "void", "yield",
17
+ ]);
18
+ /**
19
+ * Remove the statement terminators after a program's arrow expression:
20
+ * `async () => { … };`, with any whitespace or comments around the trailing
21
+ * semicolons, becomes `async () => { … }` with those comments kept. Both
22
+ * executors evaluate a program as a parenthesized expression, where a
23
+ * terminator is a syntax error on QuickJS and, on a Dynamic Worker, either the
24
+ * same error or a program that silently returns `undefined`. Models often end
25
+ * a program with `};`, so P1 accepts it on both.
26
+ *
27
+ * Deliberately narrow, so it cannot broaden what a program is: only a program
28
+ * whose first token is `async` or `(` — what QuickJS treats as a function
29
+ * expression — and only when every semicolon at bracket depth zero trails it,
30
+ * with nothing but whitespace and comments after. `async () => 1; 2();` keeps
31
+ * both its semicolons, and a bare statement body is never touched, since its
32
+ * trailing `;` can be an empty loop body. A whole-program markdown fence is
33
+ * looked through, and dropped when a terminator came out from under it.
34
+ *
35
+ * A small lexer, not a parser: it skips strings, template literals (with
36
+ * `${}` nesting), comments, and regular expressions, telling a regex from a
37
+ * division by the token before it. Any source it cannot close — an
38
+ * unterminated string or comment, unbalanced brackets — comes back unchanged,
39
+ * so the executor reports the same error it always did.
40
+ */
41
+ export function withoutProgramTerminator(code) {
42
+ const fenced = FENCE.exec(code.trim())?.[1];
43
+ const source = fenced === undefined ? code : fenced.trim();
44
+ const stripped = stripTerminators(source);
45
+ return stripped === source ? code : stripped;
46
+ }
47
+ function stripTerminators(source) {
48
+ const head = source.replace(LEADING_TRIVIA, "");
49
+ if (!/^async\b/.test(head) && !head.startsWith("("))
50
+ return source;
51
+ const n = source.length;
52
+ let depth = 0;
53
+ /** Bracket depth at which each open template substitution resumes its template. */
54
+ const substitutions = [];
55
+ /** Positions of the top-level semicolons, all of which must trail. */
56
+ const terminators = [];
57
+ /**
58
+ * The previous significant token: a punctuator character or a word, with a
59
+ * string, template, or regex literal recorded as `0` — a value, like a number.
60
+ */
61
+ let previous = "";
62
+ let i = 0;
63
+ /** Scan template text from `from` to its closing backtick or next `${`. */
64
+ const template = (from) => {
65
+ for (let j = from; j < n; j++) {
66
+ const c = source[j];
67
+ if (c === "\\") {
68
+ j++;
69
+ }
70
+ else if (c === "`") {
71
+ return j + 1;
72
+ }
73
+ else if (c === "$" && source[j + 1] === "{") {
74
+ substitutions.push(depth);
75
+ depth++;
76
+ return j + 2;
77
+ }
78
+ }
79
+ return undefined;
80
+ };
81
+ while (i < n) {
82
+ const c = source[i];
83
+ if (/\s/.test(c)) {
84
+ i++;
85
+ continue;
86
+ }
87
+ if (c === "/" && source[i + 1] === "/") {
88
+ const end = source.indexOf("\n", i);
89
+ i = end === -1 ? n : end;
90
+ continue;
91
+ }
92
+ if (c === "/" && source[i + 1] === "*") {
93
+ const end = source.indexOf("*/", i + 2);
94
+ if (end === -1)
95
+ return source;
96
+ i = end + 2;
97
+ continue;
98
+ }
99
+ if (c === ";") {
100
+ if (depth === 0)
101
+ terminators.push(i);
102
+ previous = ";";
103
+ i++;
104
+ continue;
105
+ }
106
+ // Code after a top-level semicolon means the program is more than one
107
+ // expression; that is for the executor to refuse, not for us to repair.
108
+ if (terminators.length > 0)
109
+ return source;
110
+ if (c === '"' || c === "'") {
111
+ let j = i + 1;
112
+ while (j < n && source[j] !== c) {
113
+ if (source[j] === "\n")
114
+ return source;
115
+ j += source[j] === "\\" ? 2 : 1;
116
+ }
117
+ if (j >= n)
118
+ return source;
119
+ i = j + 1;
120
+ previous = "0";
121
+ continue;
122
+ }
123
+ if (c === "`") {
124
+ const end = template(i + 1);
125
+ if (end === undefined)
126
+ return source;
127
+ i = end;
128
+ previous = source[end - 1] === "`" ? "0" : "{";
129
+ continue;
130
+ }
131
+ if (c === "}" && substitutions.at(-1) === depth - 1) {
132
+ substitutions.pop();
133
+ depth--;
134
+ const end = template(i + 1);
135
+ if (end === undefined)
136
+ return source;
137
+ i = end;
138
+ previous = source[end - 1] === "`" ? "0" : "{";
139
+ continue;
140
+ }
141
+ if (c === "/" && startsRegex(previous)) {
142
+ let j = i + 1;
143
+ let inClass = false;
144
+ for (; j < n; j++) {
145
+ const r = source[j];
146
+ if (r === "\n")
147
+ return source;
148
+ if (r === "\\")
149
+ j++;
150
+ else if (r === "[")
151
+ inClass = true;
152
+ else if (r === "]")
153
+ inClass = false;
154
+ else if (r === "/" && !inClass)
155
+ break;
156
+ }
157
+ if (j >= n)
158
+ return source;
159
+ i = j + 1;
160
+ while (i < n && /[\w$]/.test(source[i]))
161
+ i++;
162
+ previous = "0";
163
+ continue;
164
+ }
165
+ if (WORD.test(c)) {
166
+ let j = i + 1;
167
+ while (j < n && WORD.test(source[j]))
168
+ j++;
169
+ previous = source.slice(i, j);
170
+ i = j;
171
+ continue;
172
+ }
173
+ if (c === "(" || c === "[" || c === "{")
174
+ depth++;
175
+ else if (c === ")" || c === "]" || c === "}") {
176
+ if (depth === 0)
177
+ return source;
178
+ depth--;
179
+ }
180
+ previous = c;
181
+ i++;
182
+ }
183
+ if (depth !== 0 || substitutions.length > 0 || terminators.length === 0) {
184
+ return source;
185
+ }
186
+ let out = "";
187
+ let from = 0;
188
+ for (const at of terminators) {
189
+ out += source.slice(from, at);
190
+ from = at + 1;
191
+ }
192
+ return out + source.slice(from);
193
+ }
194
+ /** Whether a `/` after `previous` opens a regular expression. */
195
+ function startsRegex(previous) {
196
+ if (previous === "")
197
+ return true;
198
+ if (WORD.test(previous[0])) {
199
+ // A number or an identifier divides; a keyword introduces an operand.
200
+ return REGEX_AFTER_WORD.has(previous);
201
+ }
202
+ // After a closing bracket a value precedes; after any other punctuator
203
+ // an operand is expected.
204
+ return previous !== ")" && previous !== "]" && previous !== "}";
205
+ }
@@ -89,7 +89,7 @@ function notionFailure(status, body, headers) {
89
89
  return new ConnectorCallError("unavailable", `${labelled} Notion is overloaded; retry after the reported window.`, { retryAfterMs: retryAfter ?? 5_000 });
90
90
  }
91
91
  if (status === 401) {
92
- 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.`);
93
93
  }
94
94
  if (status === 403) {
95
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 });
@@ -133,7 +133,7 @@ const send = guardedFetch({
133
133
  authenticate: async (ctx) => {
134
134
  const token = (await ctx.credential?.get())?.trim();
135
135
  if (!token) {
136
- 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.");
137
137
  }
138
138
  return { Authorization: `Bearer ${token}` };
139
139
  },
@@ -5,7 +5,7 @@ import { defined } from "../connectors/api.js";
5
5
  export const REVENUECAT_MCP_ENDPOINT = "https://mcp.revenuecat.ai/mcp";
6
6
  /**
7
7
  * Reviewed reads, every `Read` row of RevenueCat's tool reference as read on
8
- * 2026-08-30 (P5). The list is a superset: plan, platform, and beta enrollment
8
+ * 2026-09-21 (P5). The list is a superset: plan, platform, and beta enrollment
9
9
  * gate parts of the catalog, so a name this account never returns costs
10
10
  * nothing while an unlisted new one fails closed.
11
11
  */
@@ -23,7 +23,11 @@ const READ_ONLY_TOOLS = new Set([
23
23
  // Products and prices
24
24
  "get-product",
25
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.
26
28
  "get-product-store-state-operation",
29
+ "get-product-store-state-plan",
30
+ "list-product-store-state-plans",
27
31
  "list-products",
28
32
  // Entitlements
29
33
  "get-entitlement",
@@ -100,19 +104,36 @@ const WRITE_TOOLS = new Map([
100
104
  ["archive-product", "destructive"],
101
105
  ["create-product", "additive"],
102
106
  // Named `create-`, but "Configure prices for a product": the price set
103
- // already exists, so this overwrites it, and it is money-facing.
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.
104
110
  ["create-product-prices", "destructive"],
105
111
  // "Fills *missing* App Store subscription territory prices" — by
106
- // RevenueCat's own word it writes only where nothing is set.
112
+ // RevenueCat's own word it writes only where nothing is set. Deprecated in
113
+ // RevenueCat's reference; still served, so the classification stands.
107
114
  ["equalize-subscription-prices", "additive"],
108
- // An upsert.
115
+ // An upsert. Deprecated in RevenueCat's reference — the store-state plan
116
+ // family below replaces it — but still served, so the classification stands.
109
117
  ["set-product-store-state", "destructive"],
110
118
  // Sends products to Apple for review.
111
119
  ["submit-products-to-store", "destructive"],
112
120
  ["unarchive-product", "destructive"],
113
121
  ["update-product", "destructive"],
114
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.
115
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"],
116
137
  // Entitlements
117
138
  ["archive-entitlement", "destructive"],
118
139
  // Attach adds membership and removes nothing; detach is the destructive half.
@@ -135,6 +156,11 @@ const WRITE_TOOLS = new Map([
135
156
  // Targeting and audiences
136
157
  ["create-audience", "additive"],
137
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"],
138
164
  // Paywalls
139
165
  ["attach-offering-to-paywall", "destructive"],
140
166
  ["detach-offering-from-paywall", "destructive"],
@@ -204,7 +230,8 @@ function sharedUsageGuide() {
204
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.
205
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.
206
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.
207
- - \`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.
208
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.
209
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\`.
210
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, {
@@ -138,7 +138,7 @@ function vercelTransport(baseUrl) {
138
138
  authenticate: async (ctx) => {
139
139
  const token = (await ctx.credential?.get())?.trim();
140
140
  if (!token) {
141
- throw new ConnectorCallError("auth_required", "No Vercel access token is configured for this connector. An operator must add one on /credentials before any Vercel call can run.");
141
+ throw new ConnectorCallError("auth_required", "No Vercel access 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.");
142
142
  }
143
143
  return { Authorization: `Bearer ${token}` };
144
144
  },
@@ -58,7 +58,7 @@ export interface RegistryOptions {
58
58
  /**
59
59
  * Cap on inline result size before truncation + get_result paging. Must be a
60
60
  * whole number of bytes >= 1; anything else warns at startup and falls back
61
- * to the default 50_000.
61
+ * to the default 24_000.
62
62
  */
63
63
  maxResultBytes?: number | undefined;
64
64
  results?: {