@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.
- package/AGENTS.md +58 -30
- package/CHANGELOG.md +317 -1
- package/README.md +5 -6
- package/dist/advertised-schema.js +53 -0
- package/dist/auth/downstream-oauth.js +490 -106
- package/dist/branding.d.ts +31 -2
- package/dist/branding.js +116 -8
- package/dist/call-admission.js +139 -113
- package/dist/catalog-service.js +141 -105
- package/dist/catalog.js +258 -33
- package/dist/concurrency.js +24 -23
- package/dist/connector-scope.js +6 -37
- package/dist/connectors/api.d.ts +3 -3
- package/dist/connectors/api.js +10 -2
- package/dist/connectors/guarded-fetch.js +34 -4
- package/dist/connectors/remote-mcp.d.ts +6 -5
- package/dist/connectors/remote-mcp.js +325 -245
- package/dist/credential-contract.d.ts +9 -0
- package/dist/credential-rules.js +2 -2
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +45 -3
- package/dist/errors.d.ts +11 -3
- package/dist/errors.js +2 -1
- package/dist/execute.js +363 -257
- package/dist/executor-admission.js +67 -50
- package/dist/executors/quickjs-runtime.js +4 -2
- package/dist/executors/quickjs.js +251 -210
- package/dist/fix-prompt.js +37 -0
- package/dist/index.d.ts +11 -2
- package/dist/index.js +63 -21
- package/dist/invocation.js +324 -285
- package/dist/meta-tools.js +304 -114
- package/dist/oauth-callback-outcome.js +94 -0
- package/dist/oauth-sealing.js +29 -0
- package/dist/operator-ui/fix-prompts.js +87 -0
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/model.d.ts +46 -1
- package/dist/operator-ui/setup-commands.js +53 -0
- package/dist/operator-ui/view.js +109 -2
- package/dist/program-source.js +205 -0
- package/dist/providers/cloudflare.d.ts +14 -2
- package/dist/providers/cloudflare.js +107 -16
- package/dist/providers/linear.d.ts +26 -4
- package/dist/providers/linear.js +19 -4
- package/dist/providers/mixpanel.d.ts +16 -3
- package/dist/providers/mixpanel.js +13 -2
- package/dist/providers/notion.d.ts +8 -1
- package/dist/providers/notion.js +85 -12
- package/dist/providers/revenuecat.d.ts +30 -4
- package/dist/providers/revenuecat.js +70 -5
- package/dist/providers/stripe.d.ts +10 -3
- package/dist/providers/stripe.js +30 -4
- package/dist/providers/vercel.js +12 -2
- package/dist/registry.d.ts +13 -5
- package/dist/registry.js +317 -258
- package/dist/routes/activity.js +120 -137
- package/dist/routes/credentials.js +174 -163
- package/dist/routes/mcp.js +129 -70
- package/dist/routes/oauth-management.js +60 -71
- package/dist/routes/oauth.js +44 -16
- package/dist/routes/operator.js +82 -0
- package/dist/routes/shared.d.ts +2 -0
- package/dist/routes/ui-data.js +210 -0
- package/dist/routes/ui.js +64 -32
- package/dist/runtime/admission.js +43 -0
- package/dist/runtime/call-admission.js +67 -0
- package/dist/runtime/connector-scope.js +72 -0
- package/dist/runtime/run.js +174 -0
- package/dist/runtime/services.js +130 -0
- package/dist/runtime/storage.js +66 -0
- package/dist/server.js +111 -89
- package/dist/skills.js +1 -1
- package/dist/storage/file.js +39 -2
- package/dist/storage/memory.js +28 -16
- package/dist/timeout.js +17 -47
- package/dist/types.d.ts +64 -7
- package/dist/ui.d.ts +16 -2
- package/dist/ui.js +68 -176
- package/dist/url-safety.js +157 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +368 -182
- package/documentation/auth.md +277 -176
- package/documentation/code-mode.md +479 -329
- package/documentation/meta-tools.md +494 -417
- package/ethos.md +44 -47
- package/examples/worker/AGENTS.md +2 -1
- package/examples/worker/README.md +62 -14
- package/examples/worker/src/d1-storage.ts +116 -0
- package/examples/worker/src/index.ts +18 -21
- package/examples/worker/wrangler.jsonc +16 -4
- package/package.json +11 -4
- package/templates/node/.env.example +3 -3
- package/templates/node/AGENTS.md +5 -4
- package/templates/node/README.md +2 -1
- package/templates/node/package.json +1 -1
- package/templates/node/src/index.ts +23 -22
- package/dist/auth/downstream-oauth.d.ts +0 -200
- package/dist/catalog-fingerprint.d.ts +0 -19
- package/dist/catalog-limits.d.ts +0 -9
- package/dist/catalog-service.d.ts +0 -263
- package/dist/catalog.d.ts +0 -87
- package/dist/concurrency.d.ts +0 -3
- package/dist/connectors/guarded-fetch.d.ts +0 -92
- package/dist/credential-rules.d.ts +0 -85
- package/dist/execute.d.ts +0 -154
- package/dist/executor-result.d.ts +0 -10
- package/dist/executors/quickjs-child.d.ts +0 -1
- package/dist/executors/quickjs-protocol.d.ts +0 -67
- package/dist/favicon.d.ts +0 -1
- package/dist/identity.d.ts +0 -4
- package/dist/invocation.d.ts +0 -74
- package/dist/mcp-result.d.ts +0 -8
- package/dist/meta-tools.d.ts +0 -129
- package/dist/operator-ui/generated.d.ts +0 -2
- package/dist/operator-ui/view.d.ts +0 -151
- package/dist/result-shapes.d.ts +0 -13
- package/dist/retired-toolkits.d.ts +0 -2
- package/dist/routes/activity.d.ts +0 -2
- package/dist/routes/credentials.d.ts +0 -2
- package/dist/routes/mcp.d.ts +0 -10
- package/dist/routes/oauth-management.d.ts +0 -2
- package/dist/routes/oauth.d.ts +0 -2
- package/dist/routes/ui.d.ts +0 -2
- package/dist/server.d.ts +0 -9
- package/dist/skills.d.ts +0 -44
- package/dist/timeout.d.ts +0 -21
- package/dist/tool-safety.d.ts +0 -9
- package/documentation/call-admission.md +0 -158
- package/documentation/cloudflare.md +0 -471
- package/documentation/connector-guides.md +0 -176
- package/documentation/connectors.md +0 -431
- package/documentation/linear.md +0 -193
- package/documentation/mixpanel.md +0 -160
- package/documentation/notion.md +0 -308
- package/documentation/operations.md +0 -359
- package/documentation/operator-ui.md +0 -135
- package/documentation/optional-modules-upgrade.md +0 -243
- package/documentation/provider-conventions.md +0 -729
- package/documentation/request-admission.md +0 -204
- package/documentation/revenuecat.md +0 -305
- package/documentation/storage-and-credentials.md +0 -254
- package/documentation/stripe.md +0 -262
- package/documentation/upgrading.md +0 -768
- package/documentation/vercel.md +0 -241
package/dist/providers/linear.js
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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. */
|
package/dist/providers/notion.js
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
32
|
-
* its own mode
|
|
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
|
-
/**
|
|
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;
|
package/dist/providers/stripe.js
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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;
|