@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
|
@@ -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
|
+
}
|
package/dist/operator-ui/view.js
CHANGED
|
@@ -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 {
|
|
@@ -97,6 +97,113 @@ export function connectorStatusLabel(status) {
|
|
|
97
97
|
export function toolCountLabel(count) {
|
|
98
98
|
return `${count} ${count === 1 ? "tool" : "tools"}`;
|
|
99
99
|
}
|
|
100
|
+
export function connectorStatusTone(status) {
|
|
101
|
+
if (status === "ok")
|
|
102
|
+
return "ok";
|
|
103
|
+
if (status === "auth_required")
|
|
104
|
+
return "warn";
|
|
105
|
+
if (status === "loading")
|
|
106
|
+
return "neutral";
|
|
107
|
+
return "danger";
|
|
108
|
+
}
|
|
109
|
+
export function summarizeConnectors(connectors) {
|
|
110
|
+
const summary = {
|
|
111
|
+
total: connectors.length,
|
|
112
|
+
connected: 0,
|
|
113
|
+
attention: 0,
|
|
114
|
+
unavailable: 0,
|
|
115
|
+
tools: 0,
|
|
116
|
+
drifting: 0,
|
|
117
|
+
};
|
|
118
|
+
for (const connector of connectors) {
|
|
119
|
+
if (connector.status === "ok")
|
|
120
|
+
summary.connected += 1;
|
|
121
|
+
else if (connector.status === "auth_required")
|
|
122
|
+
summary.attention += 1;
|
|
123
|
+
else if (connector.status !== "loading")
|
|
124
|
+
summary.unavailable += 1;
|
|
125
|
+
summary.tools += connector.toolCount || 0;
|
|
126
|
+
if (driftState(connector.catalogDrift) === "warning")
|
|
127
|
+
summary.drifting += 1;
|
|
128
|
+
}
|
|
129
|
+
return summary;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The one line above the list. Connected and tool counts are always present;
|
|
133
|
+
* the two counts an operator may have to act on appear only when they are not
|
|
134
|
+
* zero, so a healthy deployment stays short.
|
|
135
|
+
*/
|
|
136
|
+
export function connectorSummaryParts(summary) {
|
|
137
|
+
return [
|
|
138
|
+
{ text: `${summary.connected} connected`, tone: "neutral" },
|
|
139
|
+
...(summary.attention
|
|
140
|
+
? [
|
|
141
|
+
{
|
|
142
|
+
text: `${summary.attention} need${summary.attention === 1 ? "s" : ""} authorization`,
|
|
143
|
+
tone: "warn",
|
|
144
|
+
},
|
|
145
|
+
]
|
|
146
|
+
: []),
|
|
147
|
+
...(summary.unavailable
|
|
148
|
+
? [{ text: `${summary.unavailable} unavailable`, tone: "danger" }]
|
|
149
|
+
: []),
|
|
150
|
+
{ text: toolCountLabel(summary.tools), tone: "neutral" },
|
|
151
|
+
];
|
|
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
|
+
}
|
|
189
|
+
/** Who owns this connector's downstream credentials, in two words. */
|
|
190
|
+
export function authScopeLabel(scope) {
|
|
191
|
+
return scope === "personal" ? "personal auth" : "shared auth";
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* What this identity may do with a connector, in one line. Being able to use it
|
|
195
|
+
* is implied by seeing it at all, so the sentence covers authentication, which
|
|
196
|
+
* is the part that differs between operators.
|
|
197
|
+
*/
|
|
198
|
+
export function permissionLabel(connector) {
|
|
199
|
+
if (connector.permissions?.manageSharedAuth) {
|
|
200
|
+
return "You can manage shared authentication for this connection.";
|
|
201
|
+
}
|
|
202
|
+
if (connector.permissions?.connectPersonal) {
|
|
203
|
+
return "You can connect your own account to this connection.";
|
|
204
|
+
}
|
|
205
|
+
return "Authentication for this connection is managed by your deployment.";
|
|
206
|
+
}
|
|
100
207
|
/**
|
|
101
208
|
* The four categories, in the order an operator should read them: what the
|
|
102
209
|
* deployment refuses to call, what it can no longer call, what contradicts a
|
|
@@ -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
|
+
}
|
|
@@ -6,9 +6,21 @@ export declare const CLOUDFLARE_API_BASE = "https://api.cloudflare.com/client/v4
|
|
|
6
6
|
export declare const CLOUDFLARE_MCP_ENDPOINT = "https://mcp.cloudflare.com/mcp";
|
|
7
7
|
/** Authentication schemes accepted by Cloudflare's v4 API. */
|
|
8
8
|
export type CloudflareAuthentication = "apiToken" | "globalApiKey";
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* The 21 record types Cloudflare accepts. Eight carry a single `content`
|
|
11
|
+
* string; the other thirteen carry a per-type structured `data` object.
|
|
12
|
+
* `list_dns_records` filters on all 21 because reading them costs nothing.
|
|
13
|
+
*/
|
|
10
14
|
export declare const CLOUDFLARE_DNS_RECORD_TYPES: readonly ["A", "AAAA", "CAA", "CERT", "CNAME", "DNSKEY", "DS", "HTTPS", "LOC", "MX", "NAPTR", "NS", "OPENPGPKEY", "PTR", "SMIMEA", "SRV", "SSHFP", "SVCB", "TLSA", "TXT", "URI"];
|
|
11
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Content-valued types only, and the only types `create_dns_record` and
|
|
17
|
+
* `update_dns_record` accept. Covering the thirteen structured-`data` types
|
|
18
|
+
* would mean either a free-form `data` passthrough — the untyped body this
|
|
19
|
+
* connection exists to avoid — or thirteen more hand-written schemas for record
|
|
20
|
+
* types that are rare in day-to-day zone administration. They stay fully
|
|
21
|
+
* readable; writing one goes through the approval-gated raw mutation tool with
|
|
22
|
+
* Cloudflare's documented per-type `data` body.
|
|
23
|
+
*/
|
|
12
24
|
export declare const CLOUDFLARE_CONTENT_DNS_RECORD_TYPES: readonly ["A", "AAAA", "CNAME", "MX", "NS", "OPENPGPKEY", "PTR", "TXT"];
|
|
13
25
|
interface CloudflareCommonOptions {
|
|
14
26
|
/** Human-readable display name; defaults identify the selected interface. */
|
|
@@ -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;
|