@zackbart/connecta 0.24.2 → 0.24.4
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 +18 -20
- package/CHANGELOG.md +205 -1
- package/README.md +5 -6
- package/dist/auth/bearer.js +2 -0
- package/dist/auth/downstream-oauth.d.ts +12 -1
- package/dist/auth/downstream-oauth.js +147 -35
- package/dist/branding.d.ts +31 -2
- package/dist/branding.js +116 -8
- package/dist/call-admission.d.ts +4 -0
- package/dist/call-admission.js +26 -0
- package/dist/catalog-drift.js +9 -4
- package/dist/catalog-service.d.ts +2 -0
- package/dist/catalog-service.js +25 -8
- package/dist/catalog.d.ts +2 -0
- package/dist/catalog.js +246 -121
- package/dist/connectors/api.d.ts +1 -1
- package/dist/connectors/api.js +21 -3
- package/dist/connectors/guarded-fetch.d.ts +6 -2
- package/dist/connectors/guarded-fetch.js +61 -24
- package/dist/connectors/remote-mcp.js +92 -57
- package/dist/errors.d.ts +28 -3
- package/dist/errors.js +60 -1
- package/dist/execute.js +85 -23
- package/dist/executor-result.js +3 -1
- package/dist/executors/quickjs-child.js +5 -1
- package/dist/executors/quickjs-protocol.d.ts +4 -0
- package/dist/executors/quickjs-runtime.d.ts +1 -1
- package/dist/executors/quickjs-runtime.js +38 -21
- package/dist/executors/quickjs.js +68 -27
- package/dist/index.d.ts +15 -1
- package/dist/index.js +36 -4
- package/dist/invocation.js +134 -93
- package/dist/mcp-result.js +3 -2
- package/dist/meta-tools.js +191 -36
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/view.d.ts +38 -1
- package/dist/operator-ui/view.js +71 -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 +83 -10
- package/dist/providers/revenuecat.d.ts +30 -4
- package/dist/providers/revenuecat.js +42 -4
- package/dist/providers/stripe.d.ts +7 -1
- package/dist/providers/stripe.js +30 -4
- package/dist/providers/vercel.js +11 -1
- package/dist/registry.d.ts +22 -2
- package/dist/registry.js +101 -13
- package/dist/routes/mcp.d.ts +4 -1
- package/dist/routes/mcp.js +84 -13
- package/dist/routes/oauth.js +4 -0
- package/dist/routes/shared.d.ts +1 -0
- package/dist/routes/shared.js +4 -4
- package/dist/server.js +15 -3
- package/dist/skills.js +6 -5
- package/dist/storage/file.d.ts +6 -2
- package/dist/storage/file.js +312 -34
- package/dist/storage/memory.js +12 -1
- package/dist/types.d.ts +37 -0
- package/dist/ui.js +18 -10
- package/dist/validate.js +3 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +193 -165
- package/documentation/auth.md +199 -145
- package/documentation/code-mode.md +433 -316
- package/documentation/meta-tools.md +363 -355
- package/examples/worker/AGENTS.md +2 -1
- package/examples/worker/README.md +12 -10
- package/examples/worker/src/index.ts +12 -15
- package/package.json +1 -2
- 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/documentation/call-admission.md +0 -142
- package/documentation/cloudflare.md +0 -471
- package/documentation/connector-guides.md +0 -176
- package/documentation/connectors.md +0 -389
- 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 -722
- package/documentation/request-admission.md +0 -170
- package/documentation/revenuecat.md +0 -305
- package/documentation/storage-and-credentials.md +0 -201
- package/documentation/stripe.md +0 -262
- package/documentation/upgrading.md +0 -754
- package/documentation/vercel.md +0 -241
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* No official `cloudflare` SDK on purpose. What the SDK sells — typed request
|
|
3
|
+
* wrappers and pagination helpers — is what this connection replaces: an agent
|
|
4
|
+
* gets a projected result and a `page.hasMore` boolean, so the SDK's types
|
|
5
|
+
* would be re-projected away at the boundary, and the dependency would cost an
|
|
6
|
+
* optional peer, an install step, and an import that never belongs in the root
|
|
7
|
+
* graph. Cloudflare's v4 API is authenticated `fetch` over a uniform
|
|
8
|
+
* `{ success, errors, messages, result, result_info }` envelope, so Web APIs
|
|
9
|
+
* alone keep this provider Workers-clean. `test/package-surface.test.ts` pins
|
|
10
|
+
* it: no `cloudflare` package in any dependency field, every import relative.
|
|
11
|
+
*/
|
|
2
12
|
import { api, defined } from "../connectors/api.js";
|
|
3
13
|
import { remoteMcp, withCredentialDefaults, } from "../connectors/remote-mcp.js";
|
|
4
14
|
import { vettedCatalog, withVettedCatalog } from "../catalog-drift.js";
|
|
@@ -8,7 +18,11 @@ import { ConnectorCallError } from "../errors.js";
|
|
|
8
18
|
export const CLOUDFLARE_API_BASE = "https://api.cloudflare.com/client/v4";
|
|
9
19
|
/** Cloudflare's official whole-API hosted MCP endpoint. */
|
|
10
20
|
export const CLOUDFLARE_MCP_ENDPOINT = "https://mcp.cloudflare.com/mcp";
|
|
11
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* The 21 record types Cloudflare accepts. Eight carry a single `content`
|
|
23
|
+
* string; the other thirteen carry a per-type structured `data` object.
|
|
24
|
+
* `list_dns_records` filters on all 21 because reading them costs nothing.
|
|
25
|
+
*/
|
|
12
26
|
export const CLOUDFLARE_DNS_RECORD_TYPES = [
|
|
13
27
|
"A",
|
|
14
28
|
"AAAA",
|
|
@@ -32,7 +46,15 @@ export const CLOUDFLARE_DNS_RECORD_TYPES = [
|
|
|
32
46
|
"TXT",
|
|
33
47
|
"URI",
|
|
34
48
|
];
|
|
35
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* Content-valued types only, and the only types `create_dns_record` and
|
|
51
|
+
* `update_dns_record` accept. Covering the thirteen structured-`data` types
|
|
52
|
+
* would mean either a free-form `data` passthrough — the untyped body this
|
|
53
|
+
* connection exists to avoid — or thirteen more hand-written schemas for record
|
|
54
|
+
* types that are rare in day-to-day zone administration. They stay fully
|
|
55
|
+
* readable; writing one goes through the approval-gated raw mutation tool with
|
|
56
|
+
* Cloudflare's documented per-type `data` body.
|
|
57
|
+
*/
|
|
36
58
|
export const CLOUDFLARE_CONTENT_DNS_RECORD_TYPES = [
|
|
37
59
|
"A",
|
|
38
60
|
"AAAA",
|
|
@@ -43,7 +65,17 @@ export const CLOUDFLARE_CONTENT_DNS_RECORD_TYPES = [
|
|
|
43
65
|
"PTR",
|
|
44
66
|
"TXT",
|
|
45
67
|
];
|
|
46
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* Cloudflare documents a global limit of 1,200 requests per five minutes per
|
|
70
|
+
* user, counted cumulatively across the dashboard, API keys, and API tokens
|
|
71
|
+
* (developers.cloudflare.com/fundamentals/api/reference/limits/). The matching
|
|
72
|
+
* rolling window here is a best-effort approximation, not an enforcement: each
|
|
73
|
+
* runtime keeps its own counter, so N isolates or processes can each admit
|
|
74
|
+
* 1,200, and a human's dashboard traffic counts for Cloudflare but not for us.
|
|
75
|
+
* `maxConcurrency` is the bound that actually protects a shared credential,
|
|
76
|
+
* because one `execute_code` program can fan out faster than the window
|
|
77
|
+
* notices.
|
|
78
|
+
*/
|
|
47
79
|
function admissionPolicy(maxConcurrency) {
|
|
48
80
|
return {
|
|
49
81
|
rules: [
|
|
@@ -155,7 +187,17 @@ function errorCodes(errors) {
|
|
|
155
187
|
* would risk telling an agent its token was broken when its arguments were.
|
|
156
188
|
* Genuine 10000 auth failures arrive with 401 or 403 and are caught by status.
|
|
157
189
|
*
|
|
158
|
-
*
|
|
190
|
+
* Provenance worth knowing before editing this set: Cloudflare publishes no
|
|
191
|
+
* official table mapping error codes to causes, so these six — and the 10000
|
|
192
|
+
* observation above — come from community reports and probing rather than
|
|
193
|
+
* documentation. They are well-supported readings Cloudflare could invalidate
|
|
194
|
+
* without notice; prefer `verify_api_token` or `verify_global_api_key` when a
|
|
195
|
+
* diagnosis actually matters.
|
|
196
|
+
*
|
|
197
|
+
* Cloudflare also rate-limits authentication failures separately from the
|
|
198
|
+
* global limit: a few requests with a bad token return 429 with code 10502.
|
|
199
|
+
* That is one more reason to diagnose a broken token once with a verify tool
|
|
200
|
+
* rather than by retrying real calls.
|
|
159
201
|
*/
|
|
160
202
|
const AUTH_ERROR_CODES = new Set([1001, 6003, 6111, 9103, 9106, 9107]);
|
|
161
203
|
/**
|
|
@@ -169,7 +211,9 @@ const AUTH_ERROR_CODES = new Set([1001, 6003, 6111, 9103, 9106, 9107]);
|
|
|
169
211
|
function failureFor(status, headers, errors) {
|
|
170
212
|
const detail = describeErrors(errors);
|
|
171
213
|
const codes = errorCodes(errors);
|
|
172
|
-
//
|
|
214
|
+
// 429 is checked before the authentication codes on purpose: Cloudflare
|
|
215
|
+
// reuses the generic 10000 code on throttled responses, and reading a rate
|
|
216
|
+
// limit as an auth failure would tell an agent to stop when it should wait.
|
|
173
217
|
if (status === 429) {
|
|
174
218
|
const wait = retryAfterMs(headers);
|
|
175
219
|
return new ConnectorCallError("rate_limited", `Cloudflare rate limit reached (HTTP 429). ${detail} The documented limit is 1,200 requests per five minutes per user, counted across the dashboard and every token.`,
|
|
@@ -184,7 +228,10 @@ function failureFor(status, headers, errors) {
|
|
|
184
228
|
if (status === 400 || status === 409 || status === 422) {
|
|
185
229
|
return new ConnectorCallError("invalid_args", `Cloudflare rejected the request (HTTP ${status}). ${detail}`);
|
|
186
230
|
}
|
|
187
|
-
//
|
|
231
|
+
// Cloudflare refuses a token that may not touch a resource with 401 or 403,
|
|
232
|
+
// so a 404 here is a real absence rather than a permission gap wearing a
|
|
233
|
+
// miss — the unambiguous case `not_found` exists for, and something an agent
|
|
234
|
+
// can act on by re-running discovery for the id.
|
|
188
235
|
if (status === 404) {
|
|
189
236
|
return new ConnectorCallError("not_found", `Cloudflare found no such resource (HTTP 404). ${detail} Confirm the zone or account id with list_zones or list_accounts.`);
|
|
190
237
|
}
|
|
@@ -576,7 +623,17 @@ const RAW_INPUT_PROPERTY = {
|
|
|
576
623
|
type: "boolean",
|
|
577
624
|
description: "Return Cloudflare's unprojected result instead of the lean shape. Use only when a field the projection drops is genuinely needed; the raw shape is much larger.",
|
|
578
625
|
};
|
|
579
|
-
/**
|
|
626
|
+
/**
|
|
627
|
+
* Local enforcement of a `perPage` range is only a favor when the bound is
|
|
628
|
+
* really Cloudflare's, so `bounds` decides what the description admits to.
|
|
629
|
+
* `list_accounts`/`list_zones` (5–50) and `list_kv_namespaces` (1–1000) are
|
|
630
|
+
* Cloudflare's documented bounds. `list_dns_records` takes Cloudflare's
|
|
631
|
+
* minimum but is `clamped` at 1,000: Cloudflare's schema documents `per_page`
|
|
632
|
+
* on `/zones/{id}/dns_records` up to 5,000,000, a nominal ceiling no listing
|
|
633
|
+
* will honor, and a local cap an agent is told about beats a page size that
|
|
634
|
+
* fails somewhere inside Cloudflare. `list_pages_projects` is `undocumented` —
|
|
635
|
+
* Cloudflare publishes no bounds and no default for it, so 1–100 is ours.
|
|
636
|
+
*/
|
|
580
637
|
function pagingInputProperties(minPerPage, maxPerPage, options = {}) {
|
|
581
638
|
const { defaultPerPage, bounds = "cloudflare" } = options;
|
|
582
639
|
const defaultNote = defaultPerPage === undefined
|
|
@@ -601,7 +658,12 @@ function pagingInputProperties(minPerPage, maxPerPage, options = {}) {
|
|
|
601
658
|
},
|
|
602
659
|
};
|
|
603
660
|
}
|
|
604
|
-
/**
|
|
661
|
+
/**
|
|
662
|
+
* Four endpoints — `list_zone_rulesets`, `list_r2_buckets`, `list_r2_objects`,
|
|
663
|
+
* `list_kv_keys` — page by cursor and get no `page` object. Both the argument
|
|
664
|
+
* and the result say so, so the loop condition is legible from either end of
|
|
665
|
+
* one tool rather than only to a reader who compared all of them.
|
|
666
|
+
*/
|
|
605
667
|
const CURSOR_INPUT_PROPERTY = {
|
|
606
668
|
type: "string",
|
|
607
669
|
description: "Opaque cursor from a previous call's nextCursor. This endpoint pages by cursor, not page number.",
|
|
@@ -1013,7 +1075,12 @@ const QUERY_INPUT_PROPERTY = {
|
|
|
1013
1075
|
additionalProperties: false,
|
|
1014
1076
|
},
|
|
1015
1077
|
};
|
|
1016
|
-
//
|
|
1078
|
+
// Connector-owned and refused: Authorization, Cookie, Host, Content-Length,
|
|
1079
|
+
// Content-Type, Transfer-Encoding — authentication, host selection, content
|
|
1080
|
+
// type, and request framing are not the caller's to set. That list stays out of
|
|
1081
|
+
// this description on purpose: the compact renderer inlines a shared property
|
|
1082
|
+
// description once per tool, and spelling the six out three times pushed
|
|
1083
|
+
// cloudflare_api_upload's compact input past the 1,024-byte discovery budget.
|
|
1017
1084
|
const HEADERS_INPUT_PROPERTY = {
|
|
1018
1085
|
type: "array",
|
|
1019
1086
|
description: "Endpoint headers as name/value pairs, e.g. cf-r2-jurisdiction or Range. Connector-owned headers are refused.",
|
|
@@ -1412,7 +1479,11 @@ function buildTools(scope, authentication) {
|
|
|
1412
1479
|
path: "/zones",
|
|
1413
1480
|
query: {
|
|
1414
1481
|
name: optionalString(args, "name"),
|
|
1415
|
-
// Undefaulted on purpose
|
|
1482
|
+
// Undefaulted on purpose: list_zones is the discovery step,
|
|
1483
|
+
// and quietly filtering it by a configured accountId would
|
|
1484
|
+
// be a restriction in all but name — one with no argument
|
|
1485
|
+
// that escapes it, since an empty accountId would fall back
|
|
1486
|
+
// to the default again.
|
|
1416
1487
|
"account.id": optionalString(args, "accountId"),
|
|
1417
1488
|
status: optionalString(args, "status"),
|
|
1418
1489
|
page: optionalNumber(args, "page"),
|
|
@@ -1424,7 +1495,13 @@ function buildTools(scope, authentication) {
|
|
|
1424
1495
|
cfTool("get_zone", "Fetch one zone's settings summary by id: status, plan, name servers, and owning account.", readOnly, "zoneId", scope.zoneId, {
|
|
1425
1496
|
raw: RAW_INPUT_PROPERTY
|
|
1426
1497
|
}, [], ZONE_SCHEMA, getResult(send, (args) => ({ method: "GET", path: `/zones/${encodeURIComponent(zoneArg(args))}` }), (result, args) => args["raw"] === true ? result : projectZone(result))),
|
|
1427
|
-
//
|
|
1498
|
+
// No bulk `list_zone_settings` on purpose (#361): Cloudflare's published
|
|
1499
|
+
// document marks GET /zones/{zoneId}/settings and its PATCH sibling
|
|
1500
|
+
// deprecated with no bulk replacement, and the tool projected nothing while
|
|
1501
|
+
// *growing* the payload 22.7% by wrapping an unpaginated settings array in a
|
|
1502
|
+
// page object. Read one setting here; an operator who genuinely wants the
|
|
1503
|
+
// whole set names /zones/{zoneId}/settings through cloudflare_api_get, at
|
|
1504
|
+
// the caller's risk rather than promised by connecta's catalog.
|
|
1428
1505
|
cfTool("get_zone_setting", "Get one zone setting by its Cloudflare setting id, such as ssl, always_use_https, min_tls_version, brotli, or development_mode.", readOnly, "zoneId", scope.zoneId, {
|
|
1429
1506
|
settingId: SETTING_ID_PROPERTY,
|
|
1430
1507
|
raw: RAW_INPUT_PROPERTY
|
|
@@ -2019,8 +2096,18 @@ function buildTools(scope, authentication) {
|
|
|
2019
2096
|
path: `/accounts/${encodeURIComponent(accountArg(args))}/r2/buckets/${encodeURIComponent(requireString(args, "bucketName"))}/objects/${encodeObjectKey(objectKey)}`,
|
|
2020
2097
|
headers: r2Headers(args),
|
|
2021
2098
|
}))),
|
|
2022
|
-
// No `get_r2_metrics`, `set_r2_cors`, or `delete_r2_cors` on purpose (#350)
|
|
2023
|
-
//
|
|
2099
|
+
// No `get_r2_metrics`, `set_r2_cors`, or `delete_r2_cors` on purpose (#350).
|
|
2100
|
+
// A named tool is a permanent line item in every deployment's catalog, and
|
|
2101
|
+
// these lost the comparison against the escape hatches: `get_r2_metrics`
|
|
2102
|
+
// took an account id, put it in a path, and returned the response untouched,
|
|
2103
|
+
// which `cloudflare_api_get` at /accounts/{accountId}/r2/metrics already
|
|
2104
|
+
// does. `set_r2_cors` declared its rule list as free-form objects — the
|
|
2105
|
+
// untyped body refused everywhere else — and returned Cloudflare's response
|
|
2106
|
+
// unprojected, so it beat the raw route on nothing. `delete_r2_cors`
|
|
2107
|
+
// validated fine and went anyway, because naming only the delete would mean
|
|
2108
|
+
// one CORS policy is set through the raw route and cleared through a named
|
|
2109
|
+
// tool. Read with `get_r2_cors`; write with `cloudflare_api_mutate` at
|
|
2110
|
+
// PUT/DELETE /accounts/{accountId}/r2/buckets/{bucketName}/cors.
|
|
2024
2111
|
cfTool("get_r2_cors", "Get the browser CORS rules configured on an R2 bucket.", readOnly, "accountId", scope.accountId, {
|
|
2025
2112
|
bucketName: R2_BUCKET_NAME_PROPERTY,
|
|
2026
2113
|
jurisdiction: R2_JURISDICTION_PROPERTY
|
|
@@ -2342,8 +2429,12 @@ function buildTools(scope, authentication) {
|
|
|
2342
2429
|
const zoneId = zoneArg(args);
|
|
2343
2430
|
const targeted = ["files", "tags", "hosts", "prefixes"].filter((key) => Array.isArray(args[key]) && args[key].length > 0);
|
|
2344
2431
|
const everything = args["everything"] === true;
|
|
2345
|
-
//
|
|
2346
|
-
//
|
|
2432
|
+
// One variant per call is this connection's contract, not a
|
|
2433
|
+
// documented API restriction: Cloudflare's schema models the body as
|
|
2434
|
+
// `anyOf`, which does not forbid combining. Refusing locally gives an
|
|
2435
|
+
// agent `invalid_args` naming the conflict instead of a purge whose
|
|
2436
|
+
// actual scope is ambiguous. Combined purging would be a deliberate
|
|
2437
|
+
// change here.
|
|
2347
2438
|
if (everything && targeted.length > 0) {
|
|
2348
2439
|
throw new ConnectorCallError("invalid_args", "purge_cache takes either everything: true or one targeted list, never both.", {
|
|
2349
2440
|
validation: {
|
|
@@ -19,18 +19,40 @@ export interface LinearOptions {
|
|
|
19
19
|
authScope?: "shared" | "personal";
|
|
20
20
|
/** Which workspace this is and what decisions it answers. */
|
|
21
21
|
purpose: string;
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Endpoint selection, required with no default. Linear's read-only endpoint
|
|
24
|
+
* advertises the `read` scope alone, so its token cannot reach the write APIs
|
|
25
|
+
* — a stronger guarantee than any annotation. Neither default is safe:
|
|
26
|
+
* read-write hands out access nobody asked for, and read-only breaks a
|
|
27
|
+
* writing deployment at runtime, where no agent can repair it.
|
|
28
|
+
*/
|
|
23
29
|
access: LinearAccess;
|
|
24
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* OAuth 2.1 with dynamic client registration by default. Linear's MCP server
|
|
32
|
+
* also accepts a personal API key in `Authorization: Bearer` — note this is
|
|
33
|
+
* the MCP endpoint's own documented contract, not the GraphQL API convention.
|
|
34
|
+
* A personal key carries the acting user's full permissions, so pair it with
|
|
35
|
+
* `access: "read-only"` unless the deployment genuinely writes.
|
|
36
|
+
*/
|
|
25
37
|
auth?: RemoteMcpAuth;
|
|
26
38
|
/** Workspace-specific conventions appended to the maintained provider guide. */
|
|
27
39
|
instructions?: string;
|
|
28
40
|
/** Connector-specific inline result limit; omit to inherit the deployment. */
|
|
29
41
|
maxResultBytes?: number;
|
|
30
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Optional per-runtime policy. There is no default: Linear documents no
|
|
44
|
+
* MCP-specific limit, the GraphQL limits it rides are metered per user per
|
|
45
|
+
* hour (and its own page disagrees with itself on the API-key figure), and
|
|
46
|
+
* Connecta's counter is per runtime. Only the operator knows the workspace.
|
|
47
|
+
*/
|
|
31
48
|
callAdmission?: ConnectorCallAdmissionPolicy;
|
|
32
49
|
}
|
|
33
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* One release-reviewed manifest, used both to classify a live tool and as the
|
|
52
|
+
* baseline the drift check compares against, so the annotation a caller gets
|
|
53
|
+
* and the verdict a check reads can never disagree. Drift must surface as an
|
|
54
|
+
* unclassified tool on the approval path (P5), never a quiet re-guess.
|
|
55
|
+
*/
|
|
34
56
|
export declare const LINEAR_VETTED_CATALOG: import("../catalog-drift.js").VettedCatalog;
|
|
35
57
|
/** A maintained Linear hosted-MCP connection. */
|
|
36
58
|
export declare function linear(id: string, options: LinearOptions): Connector;
|
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()
|
|
@@ -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,
|
|
@@ -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;
|