@zackbart/connecta 0.38.0 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/README.md +3 -2
  3. package/bin/connecta.mjs +4 -8
  4. package/bin/version.mjs +1 -1
  5. package/dist/access-tokens.js +6 -4
  6. package/dist/agent-routes.js +231 -0
  7. package/dist/auth/downstream-oauth/consent.js +519 -0
  8. package/dist/auth/downstream-oauth/grant-store.js +439 -0
  9. package/dist/auth/downstream-oauth/refresh-coordinator.js +562 -0
  10. package/dist/auth/downstream-oauth/refresh-records.js +447 -0
  11. package/dist/auth/downstream-oauth/sdk-hooks.js +242 -0
  12. package/dist/auth/downstream-oauth.js +66 -2382
  13. package/dist/auth/native.js +37 -0
  14. package/dist/auth/oauth-grant.js +13 -0
  15. package/dist/auth/static-oauth.js +153 -131
  16. package/dist/auth-elicitation.js +7 -1
  17. package/dist/authorization-handoff.js +11 -5
  18. package/dist/catalog-cache.js +219 -59
  19. package/dist/catalog-policy.js +18 -0
  20. package/dist/catalog-service.js +284 -47
  21. package/dist/catalog.js +18 -2
  22. package/dist/config.d.ts +16 -11
  23. package/dist/config.js +9 -6
  24. package/dist/connector-caller.d.ts +1 -1
  25. package/dist/connector-health.js +59 -0
  26. package/dist/connector-status.js +117 -0
  27. package/dist/connectors/api-connector.d.ts +7 -2
  28. package/dist/connectors/api-connector.js +7 -4
  29. package/dist/connectors/negotiation-cache.js +22 -4
  30. package/dist/connectors/remote-mcp/call.js +164 -0
  31. package/dist/connectors/remote-mcp/catalog.js +278 -0
  32. package/dist/connectors/remote-mcp/config.js +263 -0
  33. package/dist/connectors/remote-mcp/connection.js +682 -0
  34. package/dist/connectors/remote-mcp/errors.js +488 -0
  35. package/dist/connectors/remote-mcp/oauth.js +473 -0
  36. package/dist/connectors/remote-mcp/resources.js +473 -0
  37. package/dist/connectors/remote-mcp/runtime.js +1 -0
  38. package/dist/connectors/remote-mcp/transport.js +555 -0
  39. package/dist/connectors/remote-mcp.js +62 -2863
  40. package/dist/credential-contract.d.ts +23 -0
  41. package/dist/credential-reads.js +17 -0
  42. package/dist/credential-verification.js +53 -0
  43. package/dist/credentials.d.ts +7 -0
  44. package/dist/credentials.js +60 -25
  45. package/dist/describe-config.d.ts +12 -2
  46. package/dist/describe-config.js +2 -0
  47. package/dist/discovery-health.js +199 -0
  48. package/dist/downstream-input.js +2 -0
  49. package/dist/errors.d.ts +55 -17
  50. package/dist/errors.js +95 -41
  51. package/dist/execute.js +152 -114
  52. package/dist/executor-result.js +3 -1
  53. package/dist/guest-functions.js +83 -0
  54. package/dist/guest-runtime.js +60 -19
  55. package/dist/guest-types.d.ts +19 -5
  56. package/dist/http/argument-problems.js +35 -0
  57. package/dist/http/errors.js +5 -4
  58. package/dist/http/fields.js +192 -13
  59. package/dist/http/hatch-response.js +73 -0
  60. package/dist/http/hatch.js +88 -23
  61. package/dist/http/oauth.js +3 -2
  62. package/dist/http/provider.js +32 -4
  63. package/dist/http/safety.js +8 -0
  64. package/dist/http/tools.js +44 -23
  65. package/dist/index.d.ts +1 -1
  66. package/dist/index.js +9 -2
  67. package/dist/invocation.js +26 -8
  68. package/dist/membership.d.ts +13 -5
  69. package/dist/membership.js +34 -2
  70. package/dist/meta-tools.js +54 -31
  71. package/dist/native-recovery.js +4 -16
  72. package/dist/native-tools.js +5 -2
  73. package/dist/oauth-sealing.js +10 -0
  74. package/dist/operation-vocabulary.js +135 -13
  75. package/dist/operator-record.d.ts +2 -2
  76. package/dist/operator-record.js +1 -0
  77. package/dist/operator-ui/contract.d.ts +10 -0
  78. package/dist/operator-ui/generated.js +2 -2
  79. package/dist/operator-ui/model.d.ts +22 -1
  80. package/dist/provider-call-facts.js +6 -2
  81. package/dist/providers/_shared/rest/operation-index.js +48 -50
  82. package/dist/providers/_shared/rest/value-safety.js +80 -0
  83. package/dist/providers/breeze/api.js +7 -1
  84. package/dist/providers/breeze/skill.generated.js +1 -1
  85. package/dist/providers/breeze/tools.js +4 -3
  86. package/dist/providers/ccb/api.js +2 -1
  87. package/dist/providers/ccb/index.js +5 -0
  88. package/dist/providers/ccb/skill.generated.js +1 -1
  89. package/dist/providers/cloudflare/api.js +8 -1
  90. package/dist/providers/cloudflare/skill.generated.js +1 -1
  91. package/dist/providers/docs/lib/schemas.js +24 -22
  92. package/dist/providers/docs/safety.js +2 -0
  93. package/dist/providers/docs/skill.generated.js +1 -1
  94. package/dist/providers/docs/tools.js +2 -4
  95. package/dist/providers/drive/api.js +1 -0
  96. package/dist/providers/drive/lib/contracts.js +8 -5
  97. package/dist/providers/drive/skill.generated.js +1 -1
  98. package/dist/providers/drive/tools.js +11 -16
  99. package/dist/providers/forms/api.js +1 -0
  100. package/dist/providers/forms/skill.generated.js +1 -1
  101. package/dist/providers/forms/tools.js +10 -7
  102. package/dist/providers/github/api.js +1 -0
  103. package/dist/providers/github/lib/discovery.js +1 -1
  104. package/dist/providers/github/skill.generated.js +1 -1
  105. package/dist/providers/github/tools.js +61 -17
  106. package/dist/providers/gmail/lib/schemas.js +60 -50
  107. package/dist/providers/gmail/skill.generated.js +1 -1
  108. package/dist/providers/gmail/tools.js +7 -5
  109. package/dist/providers/granola/index.js +2 -0
  110. package/dist/providers/granola/skill.generated.js +1 -1
  111. package/dist/providers/overflow/api.js +7 -1
  112. package/dist/providers/overflow/skill.generated.js +1 -1
  113. package/dist/providers/planning-center/api.js +3 -1
  114. package/dist/providers/planning-center/lib/constants.d.ts +1 -1
  115. package/dist/providers/planning-center/skill.generated.js +1 -1
  116. package/dist/providers/quickbooks/safety.js +80 -39
  117. package/dist/providers/quickbooks/skill.generated.js +1 -1
  118. package/dist/providers/sheets/api.js +1 -0
  119. package/dist/providers/sheets/skill.generated.js +1 -1
  120. package/dist/providers/sheets/tools.js +13 -14
  121. package/dist/providers/slides/skill.generated.js +1 -1
  122. package/dist/providers/slides/tools.js +15 -13
  123. package/dist/providers/stripe/skill.generated.js +1 -1
  124. package/dist/providers/tithely/api.js +3 -1
  125. package/dist/providers/tithely/skill.generated.js +1 -1
  126. package/dist/providers/vercel/api.js +1 -1
  127. package/dist/providers/vercel/skill.generated.js +1 -1
  128. package/dist/providers/vercel/tools.js +4 -3
  129. package/dist/registry.d.ts +10 -4
  130. package/dist/registry.js +41 -13
  131. package/dist/routes/credentials.js +129 -60
  132. package/dist/routes/dispatch.js +5 -1
  133. package/dist/routes/mcp.js +30 -14
  134. package/dist/routes/operator.js +2 -2
  135. package/dist/routes/shared.d.ts +6 -1
  136. package/dist/routes/shared.js +7 -1
  137. package/dist/routes/ui-config.js +2 -2
  138. package/dist/routes/ui-data.js +126 -20
  139. package/dist/routes/ui.js +16 -0
  140. package/dist/runtime/call.js +7 -2
  141. package/dist/runtime/native-storage.js +12 -0
  142. package/dist/runtime/request-reads.js +207 -0
  143. package/dist/runtime/request-scope.js +16 -0
  144. package/dist/runtime/storage.js +12 -0
  145. package/dist/skills.js +37 -20
  146. package/dist/storage/keys.js +53 -3
  147. package/dist/storage/memory.js +8 -2
  148. package/dist/storage/sql-native.js +53 -1
  149. package/dist/storage/sql.js +12 -2
  150. package/dist/storage-digest.js +32 -0
  151. package/dist/timeout.js +8 -0
  152. package/dist/types.d.ts +33 -0
  153. package/dist/usage-guide.js +16 -35
  154. package/dist/validate.d.ts +3 -0
  155. package/dist/validate.js +220 -158
  156. package/dist/version.d.ts +1 -1
  157. package/dist/version.js +1 -1
  158. package/dist/write-outcome.js +0 -3
  159. package/documentation/README.md +13 -13
  160. package/documentation/architecture.md +385 -20
  161. package/documentation/auth.md +233 -64
  162. package/documentation/code-mode.md +122 -39
  163. package/documentation/deploying.md +6 -3
  164. package/documentation/integrating.md +16 -2
  165. package/documentation/meta-tools.md +240 -49
  166. package/documentation/migration-0.33.md +5 -1
  167. package/documentation/migration-0.34.md +3 -1
  168. package/documentation/operator-tests.md +1 -1
  169. package/documentation/operator-ui.md +105 -12
  170. package/documentation/pool-membership-upgrade.md +44 -0
  171. package/documentation/providers.md +105 -0
  172. package/examples/worker/AGENTS.md +10 -10
  173. package/examples/worker/README.md +4 -3
  174. package/examples/worker/src/connecta.config.ts +1 -0
  175. package/guides/connecting-agents.md +2 -1
  176. package/guides/how-it-works.md +4 -2
  177. package/package.json +1 -1
  178. package/templates/node/AGENTS.md +13 -12
  179. package/templates/node/package.json +1 -1
  180. package/templates/node/src/connecta.config.ts +1 -0
@@ -0,0 +1,473 @@
1
+ // Skills and resources: the Skills extension's bounded listing and reads,
2
+ // and resource reads limited to what the downstream advertises.
3
+ import { utf8Length } from "../../bytes.js";
4
+ import { authFlow, authCall, authInvoke, authMethod, authCatch } from "../../auth/native.js";
5
+ import { ResourceNotFoundError, isInputRequiredResult, UnauthorizedError } from "@modelcontextprotocol/client";
6
+ import { requestSentSecrets } from "../../sent-secrets.js";
7
+ import { MAX_SERIALIZED_CATALOG_BYTES } from "../../catalog-limits.js";
8
+ import { ConnectorCallError } from "../../errors.js";
9
+ import { carryFailureFacts } from "../../operator-record.js";
10
+ import { resourceUriMatchesTemplates } from "../resource-uri.js";
11
+ import { downstreamCallError, hasSdkPayload, ownAbortReason, requiresAuthorization, withheldAs } from "./errors.js";
12
+ function unadvertisedResource() {
13
+ return new ConnectorCallError("not_found", "The resource URI is not advertised by this connector.", {
14
+ retryable: false,
15
+ });
16
+ }
17
+ export const SKILLS_EXTENSION = "io.modelcontextprotocol/skills";
18
+ const MAX_SKILLS = 10_000;
19
+ const MAX_SKILL_FILES = 512;
20
+ const MAX_SKILL_BYTES = 16 * 1024 * 1024;
21
+ // A valid UTF-8 text file can expand sixfold when JSON escapes control bytes.
22
+ // Leave room for the envelope while accepting every file up to the spec limit.
23
+ export const MAX_SKILL_READ_RPC_BYTES = 6 * MAX_SKILL_BYTES + 1024 * 1024;
24
+ /** Count UTF-8 without allocating another full copy of a bounded response. */
25
+ export function skillObject(value) {
26
+ return typeof value === "object" && value !== null && !Array.isArray(value);
27
+ }
28
+ function skillJsonBytes(value) {
29
+ return utf8Length(JSON.stringify(value));
30
+ }
31
+ function validSkill(value) {
32
+ if (!skillObject(value) ||
33
+ typeof value.uri !== "string" ||
34
+ !value.uri ||
35
+ !skillObject(value.frontmatter) ||
36
+ typeof value.frontmatter.name !== "string" ||
37
+ !value.frontmatter.name ||
38
+ typeof value.frontmatter.description !== "string" ||
39
+ !value.frontmatter.description)
40
+ return false;
41
+ if (value.resources === "dynamic")
42
+ return true;
43
+ if (!Array.isArray(value.resources) || !value.resources.length || value.resources.length > MAX_SKILL_FILES)
44
+ return false;
45
+ const uris = new Set();
46
+ let bytes = 0;
47
+ for (const resource of value.resources) {
48
+ if (!skillObject(resource) ||
49
+ typeof resource.uri !== "string" ||
50
+ !resource.uri ||
51
+ uris.has(resource.uri) ||
52
+ typeof resource.digest !== "string" ||
53
+ !/^sha256:[a-f0-9]{64}$/.test(resource.digest) ||
54
+ typeof resource.size !== "number" ||
55
+ !Number.isSafeInteger(resource.size) ||
56
+ resource.size < 0)
57
+ return false;
58
+ uris.add(resource.uri);
59
+ bytes += resource.size;
60
+ if (bytes > MAX_SKILL_BYTES)
61
+ return false;
62
+ }
63
+ return uris.has(value.uri);
64
+ }
65
+ function completeSkillResult(value) {
66
+ // The modern SDK codec rejects non-complete wire results and removes
67
+ // resultType before invoking an explicit result schema.
68
+ return (skillObject(value) &&
69
+ typeof value.ttlMs === "number" &&
70
+ Number.isSafeInteger(value.ttlMs) &&
71
+ value.ttlMs >= 0 &&
72
+ (value.cacheScope === "public" || value.cacheScope === "private"));
73
+ }
74
+ const SkillPageSchema = {
75
+ "~standard": {
76
+ version: 1,
77
+ vendor: "connecta",
78
+ validate(value) {
79
+ if (!completeSkillResult(value) ||
80
+ !Array.isArray(value.skills) ||
81
+ value.skills.length > MAX_SKILLS ||
82
+ !value.skills.every(validSkill) ||
83
+ (value.nextCursor !== undefined && typeof value.nextCursor !== "string")) {
84
+ return { issues: [{ message: "Invalid or incomplete Skills listing." }] };
85
+ }
86
+ const bytes = skillJsonBytes(value);
87
+ if (bytes > MAX_SERIALIZED_CATALOG_BYTES)
88
+ return { issues: [{ message: "Skills listing exceeds the byte limit." }] };
89
+ return {
90
+ value: {
91
+ skills: value.skills,
92
+ bytes,
93
+ ...(typeof value.nextCursor === "string" ? { nextCursor: value.nextCursor } : {}),
94
+ },
95
+ };
96
+ },
97
+ },
98
+ };
99
+ function skillReadSchema(uri) {
100
+ return {
101
+ "~standard": {
102
+ version: 1,
103
+ vendor: "connecta",
104
+ validate(value) {
105
+ if (!completeSkillResult(value) ||
106
+ !Array.isArray(value.contents) ||
107
+ value.contents.length !== 1 ||
108
+ skillJsonBytes(value) > MAX_SKILL_READ_RPC_BYTES) {
109
+ return { issues: [{ message: "Invalid or incomplete skill resource response." }] };
110
+ }
111
+ const content = value.contents[0];
112
+ if (!skillObject(content) ||
113
+ content.uri !== uri ||
114
+ (content.mimeType !== undefined && typeof content.mimeType !== "string")) {
115
+ return { issues: [{ message: "Unexpected skill resource contents." }] };
116
+ }
117
+ const mimeType = typeof content.mimeType === "string" ? { mimeType: content.mimeType } : {};
118
+ if (typeof content.text === "string" && !("blob" in content) && utf8Length(content.text) <= MAX_SKILL_BYTES) {
119
+ return { value: [{ uri, ...mimeType, text: content.text }] };
120
+ }
121
+ if (typeof content.blob === "string" &&
122
+ !("text" in content) &&
123
+ content.blob.length % 4 === 0 &&
124
+ !/[^A-Za-z0-9+/]/.test(content.blob.replace(/={1,2}$/, ""))) {
125
+ const bytes = (content.blob.length / 4) * 3 - (content.blob.endsWith("==") ? 2 : content.blob.endsWith("=") ? 1 : 0);
126
+ if (bytes <= MAX_SKILL_BYTES)
127
+ return { value: [{ uri, ...mimeType, blob: content.blob }] };
128
+ }
129
+ return { issues: [{ message: "Invalid or oversized skill resource bytes." }] };
130
+ },
131
+ },
132
+ };
133
+ }
134
+ /** Bound the body before SDK consumption, including its detached SSE reader. */
135
+ export async function boundedSkillResponse(response, limit, rpcId, signal) {
136
+ const exceeded = () => new ConnectorCallError("connector_call_failed", "Downstream Skills response exceeds the RPC byte limit.");
137
+ const declared = response.headers.get("content-length");
138
+ if (declared !== null && Number(declared) > limit) {
139
+ await response.body?.cancel().catch(() => { });
140
+ throw exceeded();
141
+ }
142
+ if (!response.body)
143
+ return response;
144
+ const reader = response.body.getReader();
145
+ const abort = () => {
146
+ void reader.cancel().catch(() => { });
147
+ };
148
+ signal?.addEventListener("abort", abort, { once: true });
149
+ let bytes = 0;
150
+ const chunks = [];
151
+ let buffer;
152
+ let buffered = 0;
153
+ const sse = response.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase() === "text/event-stream";
154
+ // Decode an entire SSE message once. Its byte buffer has no per-chunk or
155
+ // per-line objects, and preserves embedded resource BOMs on workerd too.
156
+ const decoder = new TextDecoder("utf-8", { ignoreBOM: true, fatal: false });
157
+ let data = new Uint8Array(0);
158
+ let dataBytes = 0;
159
+ let hasData = false;
160
+ const newline = new Uint8Array([10]);
161
+ const field = [100, 97, 116, 97, 58]; // data:
162
+ const bom = [239, 187, 191];
163
+ let bomOffset = 0;
164
+ let fieldOffset = 0;
165
+ // 0: field prefix, 1: optional value space, 2: data value, 3: ignored line.
166
+ let state = 0;
167
+ let lineBytes = false;
168
+ let skipLf = false;
169
+ const append = (value) => {
170
+ const needed = dataBytes + value.byteLength;
171
+ if (needed > limit)
172
+ throw exceeded();
173
+ if (needed > data.length) {
174
+ const next = new Uint8Array(Math.min(limit, Math.max(65_536, needed, data.length * 2)));
175
+ next.set(data.subarray(0, dataBytes));
176
+ data = next;
177
+ }
178
+ data.set(value, dataBytes);
179
+ dataBytes = needed;
180
+ };
181
+ const beginData = () => {
182
+ if (hasData)
183
+ append(newline);
184
+ hasData = true;
185
+ };
186
+ const consume = (chunk) => {
187
+ for (let offset = 0; offset < chunk.length;) {
188
+ const byte = chunk[offset];
189
+ if (skipLf) {
190
+ skipLf = false;
191
+ if (byte === 10) {
192
+ offset++;
193
+ continue;
194
+ }
195
+ }
196
+ // Strip exactly one initial UTF-8 BOM, even across single-byte chunks.
197
+ if (bomOffset >= 0) {
198
+ if (byte === bom[bomOffset]) {
199
+ if (++bomOffset === bom.length)
200
+ bomOffset = -1;
201
+ offset++;
202
+ continue;
203
+ }
204
+ if (bomOffset) {
205
+ lineBytes = true;
206
+ state = 3;
207
+ }
208
+ bomOffset = -1;
209
+ }
210
+ if (byte === 10 || byte === 13) {
211
+ if (lineBytes) {
212
+ if (state === 0 && fieldOffset === 4)
213
+ beginData();
214
+ }
215
+ else {
216
+ const json = hasData && dataBytes ? decoder.decode(data.subarray(0, dataBytes)) : "";
217
+ dataBytes = 0;
218
+ hasData = false;
219
+ if (json) {
220
+ const message = JSON.parse(json);
221
+ if (skillObject(message) && message.id === rpcId && ("result" in message || "error" in message)) {
222
+ data = new Uint8Array(0);
223
+ return json;
224
+ }
225
+ }
226
+ }
227
+ state = 0;
228
+ fieldOffset = 0;
229
+ lineBytes = false;
230
+ skipLf = byte === 13;
231
+ offset++;
232
+ continue;
233
+ }
234
+ lineBytes = true;
235
+ if (state === 0) {
236
+ if (byte === field[fieldOffset]) {
237
+ if (++fieldOffset === field.length) {
238
+ beginData();
239
+ state = 1;
240
+ }
241
+ }
242
+ else
243
+ state = 3;
244
+ offset++;
245
+ continue;
246
+ }
247
+ if (state === 1) {
248
+ state = 2;
249
+ if (byte === 32) {
250
+ offset++;
251
+ continue;
252
+ }
253
+ }
254
+ let end = offset;
255
+ while (end < chunk.length && chunk[end] !== 13 && chunk[end] !== 10)
256
+ end++;
257
+ if (state === 2)
258
+ append(chunk.subarray(offset, end));
259
+ offset = end;
260
+ }
261
+ };
262
+ const terminalResponse = async (json) => {
263
+ // The SDK's detached SSE consumer does not settle body-read errors. Hand
264
+ // it the bounded terminal answer and close a server-kept-open stream.
265
+ await reader.cancel().catch(() => { });
266
+ const headers = new Headers(response.headers);
267
+ headers.set("content-type", "application/json");
268
+ headers.delete("content-length");
269
+ return new Response(json, { status: response.status, statusText: response.statusText, headers });
270
+ };
271
+ try {
272
+ while (true) {
273
+ if (signal?.aborted)
274
+ throw signal.reason;
275
+ const chunk = await reader.read();
276
+ if (signal?.aborted)
277
+ throw signal.reason;
278
+ if (chunk.done) {
279
+ break;
280
+ }
281
+ bytes += chunk.value.byteLength;
282
+ if (bytes > limit)
283
+ throw exceeded();
284
+ if (!sse) {
285
+ // Network chunk count and backing-buffer size are untrusted. Retain
286
+ // only owned fixed-size blocks, never one object per incoming byte.
287
+ for (let offset = 0; offset < chunk.value.byteLength;) {
288
+ buffer ??= new Uint8Array(65_536);
289
+ const size = Math.min(buffer.length - buffered, chunk.value.byteLength - offset);
290
+ buffer.set(chunk.value.subarray(offset, offset + size), buffered);
291
+ offset += size;
292
+ buffered += size;
293
+ if (buffered === buffer.length) {
294
+ chunks.push(buffer);
295
+ buffer = undefined;
296
+ buffered = 0;
297
+ }
298
+ }
299
+ continue;
300
+ }
301
+ const terminal = consume(chunk.value);
302
+ if (terminal !== undefined)
303
+ return await terminalResponse(terminal);
304
+ }
305
+ if (sse)
306
+ throw new ConnectorCallError("connector_call_failed", "Downstream Skills stream ended without a terminal RPC response.");
307
+ if (buffer && buffered)
308
+ chunks.push(buffer.subarray(0, buffered));
309
+ return new Response(new ReadableStream({
310
+ pull(controller) {
311
+ const chunk = chunks.shift();
312
+ if (chunk)
313
+ controller.enqueue(chunk);
314
+ else
315
+ controller.close();
316
+ },
317
+ }), { status: response.status, statusText: response.statusText, headers: response.headers });
318
+ }
319
+ catch (error) {
320
+ await reader.cancel().catch(() => { });
321
+ throw error;
322
+ }
323
+ finally {
324
+ signal?.removeEventListener("abort", abort);
325
+ reader.releaseLock();
326
+ }
327
+ }
328
+ /** One connector's Skills and resource read operations. */
329
+ export function remoteMcpResources(rt) {
330
+ const { id } = rt;
331
+ const resourceTemplateRefusals = new Set();
332
+ const skillScopeOpen = (ctx, state) => {
333
+ if (rt.isClosed(state))
334
+ throw rt.scopeEndedError();
335
+ if (ctx.signal?.aborted)
336
+ throw ctx.signal.reason instanceof Error ? ctx.signal.reason : rt.scopeEndedError();
337
+ };
338
+ const withSkillsClient = authMethod((ctx, method, run) => authFlow(function* () {
339
+ const state = rt.stateFor(ctx);
340
+ const client = (yield authCall(rt.ensureConnected, [ctx, state]));
341
+ try {
342
+ skillScopeOpen(ctx, state);
343
+ const capabilities = client.getServerCapabilities();
344
+ const extension = capabilities?.extensions?.[SKILLS_EXTENSION];
345
+ if (client.getProtocolEra() !== "modern" ||
346
+ !skillObject(capabilities?.resources) ||
347
+ !skillObject(extension) ||
348
+ (extension.directoryRead !== undefined && typeof extension.directoryRead !== "boolean")) {
349
+ throw new ConnectorCallError("connector_call_failed", "The downstream did not declare a valid Skills extension and resources capability.");
350
+ }
351
+ const result = (yield authCall(run, [client, state]));
352
+ skillScopeOpen(ctx, state);
353
+ return result;
354
+ }
355
+ catch (error) {
356
+ if (ownAbortReason(error, [ctx.signal]) && !hasSdkPayload(error))
357
+ throw error;
358
+ const classified = rt.atMcpBoundary(ctx, error, method, client.transport, [ctx.signal]);
359
+ if (requiresAuthorization(classified) && state.client === client)
360
+ state.authRequired = true;
361
+ throw carryFailureFacts(classified, withheldAs(`Connector "${id}" downstream ${method} failed.`, classified));
362
+ }
363
+ }));
364
+ const listSkills = authMethod((ctx) => authCall(withSkillsClient, [
365
+ ctx,
366
+ "skills/list",
367
+ authMethod((client, state) => authFlow(function* () {
368
+ const listed = [];
369
+ const uris = new Set();
370
+ const cursors = new Set();
371
+ let cursor;
372
+ let bytes = 0;
373
+ for (let page = 0; page < MAX_SKILLS; page++) {
374
+ skillScopeOpen(ctx, state);
375
+ const result = (yield authInvoke(client, "request", [
376
+ { method: "skills/list", ...(cursor === undefined ? {} : { params: { cursor } }) },
377
+ SkillPageSchema,
378
+ rt.requestOptions(ctx),
379
+ ]));
380
+ bytes += result.bytes;
381
+ if (bytes > MAX_SERIALIZED_CATALOG_BYTES || listed.length + result.skills.length > MAX_SKILLS) {
382
+ throw new ConnectorCallError("connector_call_failed", "Downstream Skills listing exceeds the aggregate limit.");
383
+ }
384
+ for (const skill of result.skills) {
385
+ if (uris.has(skill.uri))
386
+ throw new ConnectorCallError("connector_call_failed", "Downstream Skills listing contains duplicate skill URIs.");
387
+ uris.add(skill.uri);
388
+ listed.push(skill);
389
+ }
390
+ if (result.nextCursor === undefined)
391
+ return listed;
392
+ if (!result.skills.length || cursors.has(result.nextCursor)) {
393
+ throw new ConnectorCallError("connector_call_failed", "Downstream Skills pagination loops or makes no progress.");
394
+ }
395
+ cursors.add(result.nextCursor);
396
+ cursor = result.nextCursor;
397
+ }
398
+ throw new ConnectorCallError("connector_call_failed", "Downstream Skills listing exceeds the page limit.");
399
+ })),
400
+ ]));
401
+ const readSkill = authMethod((uri, ctx) => authCall(withSkillsClient, [
402
+ ctx,
403
+ "resources/read",
404
+ authMethod((client) => authInvoke(client, "request", [
405
+ { method: "resources/read", params: { uri } },
406
+ skillReadSchema(uri),
407
+ rt.requestOptions(ctx),
408
+ ])),
409
+ ]));
410
+ const readResourceOperation = authMethod((uri, ctx) => authFlow(function* () {
411
+ const state = rt.stateFor(ctx);
412
+ const client = (yield authCall(rt.ensureConnected, [ctx, state]));
413
+ try {
414
+ const options = rt.requestOptions(ctx) ?? {};
415
+ rt.listingContexts.set(options, ctx);
416
+ try {
417
+ if (!client.getServerCapabilities()?.resources)
418
+ throw unadvertisedResource();
419
+ const resources = (yield authInvoke(client, "listResources", [undefined, options]));
420
+ const templates = (yield authInvoke(client, "listResourceTemplates", [
421
+ undefined,
422
+ options,
423
+ ]));
424
+ const exact = resources.resources.some((resource) => resource.uri === uri);
425
+ if (!exact) {
426
+ const match = resourceUriMatchesTemplates(uri, templates.resourceTemplates, (code) => resourceTemplateRefusals.add(code));
427
+ if (!match.matched) {
428
+ if (match.refusal)
429
+ throw new ConnectorCallError(match.refusal, match.refusal === "resource_template_ambiguous"
430
+ ? "The advertised resource templates have ambiguous expression boundaries."
431
+ : "Resource template matching exceeds the read work limit.", { retryable: false });
432
+ throw unadvertisedResource();
433
+ }
434
+ }
435
+ }
436
+ catch (err) {
437
+ throw rt.atMcpBoundary(ctx, err, "resources/read", client.transport, [ctx.signal]);
438
+ }
439
+ finally {
440
+ rt.listingContexts.delete(options);
441
+ }
442
+ const result = (yield authCatch(authInvoke(client, "readResource", [
443
+ { uri },
444
+ {
445
+ ...rt.requestOptions(ctx),
446
+ cacheMode: "bypass",
447
+ allowInputRequired: true,
448
+ },
449
+ ]), (err) => {
450
+ if (err instanceof ResourceNotFoundError) {
451
+ throw new ConnectorCallError("not_found", "The downstream resource does not exist.");
452
+ }
453
+ throw rt.atMcpBoundary(ctx, err, "resources/read", client.transport, [ctx.signal]);
454
+ }));
455
+ if (isInputRequiredResult(result)) {
456
+ throw new ConnectorCallError("input_required_unsupported", "Resource reads inside programs cannot request mid-call input.");
457
+ }
458
+ return result;
459
+ }
460
+ catch (err) {
461
+ if (ownAbortReason(err, [ctx.signal]) && !hasSdkPayload(err))
462
+ throw err;
463
+ if (requiresAuthorization(err)) {
464
+ if (state.client === client)
465
+ state.authRequired = true;
466
+ if (err instanceof UnauthorizedError)
467
+ throw carryFailureFacts(err, rt.authRequiredError(ctx));
468
+ }
469
+ throw downstreamCallError(err, undefined, undefined, undefined, requestSentSecrets(ctx));
470
+ }
471
+ }));
472
+ return { listSkills, readSkill, readResourceOperation, resourceTemplateRefusals };
473
+ }
@@ -0,0 +1 @@
1
+ export {};