@desplega.ai/agent-swarm 1.113.0 → 1.115.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 (237) hide show
  1. package/README.md +2 -2
  2. package/dist/{actions-q4n7cz6e.js → actions-17ya5wde.js} +8 -9
  3. package/dist/{app-es4nzc71.js → app-4f718yk0.js} +6 -6
  4. package/dist/{assistant-0x8ey0vs.js → assistant-ah7csnjz.js} +19 -18
  5. package/dist/{boot-reembed-69agkhv6.js → boot-reembed-4jpxhb25.js} +6 -6
  6. package/dist/{boot-reembed-p0ny05t8.js → boot-reembed-qata34xa.js} +5 -5
  7. package/dist/{boot-scrub-logs-yybrkmvr.js → boot-scrub-logs-tv48nn3z.js} +4 -4
  8. package/dist/{claude-adapter-mma9a1yr.js → claude-adapter-3csn5bks.js} +3 -3
  9. package/dist/{claude-managed-adapter-pv2nbqy4.js → claude-managed-adapter-kysmkz89.js} +2 -2
  10. package/dist/cli-0pygq9r1.js +429 -0
  11. package/dist/{cli-hsetkkd3.js → cli-12vb0xg6.js} +33 -9
  12. package/dist/{cli-ncfgsqbd.js → cli-15y7qhzp.js} +262 -33
  13. package/dist/{cli-7ga43spr.js → cli-1yjrbg1f.js} +1 -1
  14. package/dist/{cli-36ymmpmp.js → cli-3samkyap.js} +2 -2
  15. package/dist/cli-52fg7s7x.js +269 -0
  16. package/dist/{cli-hjhvekf4.js → cli-65ekje01.js} +11 -8
  17. package/dist/{cli-h3k622d0.js → cli-6aq8xhvm.js} +90 -3
  18. package/dist/{cli-f3qa069v.js → cli-7xje5amz.js} +1 -1
  19. package/dist/cli-86fdnerz.js +405 -0
  20. package/dist/{cli-9cy6nc5m.js → cli-8fjwym53.js} +1 -1
  21. package/dist/{cli-c3frqf65.js → cli-b23axgz4.js} +3 -3
  22. package/dist/{cli-a0t7vapf.js → cli-bc89my0z.js} +1 -1
  23. package/dist/{cli-bnzbn8j8.js → cli-beea4a3y.js} +73 -24
  24. package/dist/{cli-q21d49ac.js → cli-bw80ck94.js} +1 -1
  25. package/dist/{cli-pwc5e83c.js → cli-cchksf5q.js} +12 -7
  26. package/dist/{cli-30bbaveh.js → cli-cmw9rzy5.js} +2 -2
  27. package/dist/{cli-y6mrptcn.js → cli-cnwssmsr.js} +1 -1
  28. package/dist/{cli-r5g6ngtn.js → cli-dcca6j8m.js} +46 -5
  29. package/dist/{cli-tkmm5nf9.js → cli-e61fs2hp.js} +59 -2
  30. package/dist/{cli-6wscj9z0.js → cli-e7306swv.js} +14 -14
  31. package/dist/{cli-dcfyyh3t.js → cli-ftyf58xj.js} +2 -2
  32. package/dist/{cli-4xyj9jtq.js → cli-hrhye2rm.js} +1062 -478
  33. package/dist/cli-jkhwmmgg.js +136 -0
  34. package/dist/{cli-y4ycrnjc.js → cli-k3y4z3h5.js} +1 -1
  35. package/dist/{cli-6xd0bvf8.js → cli-ma04faw5.js} +1 -1
  36. package/dist/{cli-2fc97xm5.js → cli-mnbgv3w7.js} +4 -4
  37. package/dist/{cli-trmapn9k.js → cli-pet3aev6.js} +95 -10
  38. package/dist/{cli-z46pnksz.js → cli-q1rx7g0d.js} +175 -28
  39. package/dist/{cli-qwgtacd6.js → cli-r9f6ngrf.js} +1244 -336
  40. package/dist/{cli-gtkqc144.js → cli-shvaxw27.js} +13 -7
  41. package/dist/{cli-ywez6yps.js → cli-sq7f72d6.js} +1 -1
  42. package/dist/cli-t0vqevnm.js +393 -0
  43. package/dist/{cli-tbw13sx8.js → cli-tece6zgm.js} +1 -1
  44. package/dist/{cli-bgvskydy.js → cli-tffvrh6j.js} +1 -1
  45. package/dist/cli-wh91zby6.js +187222 -0
  46. package/dist/{cli-anrj584m.js → cli-xz9aq0rf.js} +1 -1
  47. package/dist/{cli-mq580e06.js → cli-zsrdty74.js} +18 -18
  48. package/dist/cli.js +32 -15
  49. package/dist/{codex-adapter-5cmy858w.js → codex-adapter-de3w0nch.js} +10 -10
  50. package/dist/{codex-session-runner-46q8mrba.js → codex-session-runner-bnw8cysk.js} +10 -10
  51. package/dist/{commands-z5dbwta3.js → commands-hch4e2k1.js} +4 -4
  52. package/dist/{db-bbgahh4z.js → db-p3h2j06x.js} +14 -4
  53. package/dist/{handlers-6b44317z.js → handlers-9er4zx9q.js} +17 -31
  54. package/dist/{hook-sbp5fmps.js → hook-4axs756z.js} +7 -7
  55. package/dist/{http-2xz43gz3.js → http-809fvtz2.js} +1934 -886
  56. package/dist/{index-wz1ea15p.js → index-2dgfj9av.js} +3 -3
  57. package/dist/{index-nhat35em.js → index-47em3d2n.js} +14 -11
  58. package/dist/{index-5ghxn8s6.js → index-9fqaktjr.js} +12 -9
  59. package/dist/{index-0gzmqj4k.js → index-9ht8gr56.js} +13 -10
  60. package/dist/{index-vfedgdw6.js → index-rm85cfdx.js} +368 -40
  61. package/dist/{index-ykvs43z4.js → index-t2snazez.js} +4 -4
  62. package/dist/{keepalive-62gfj91d.js → keepalive-3cps6e3e.js} +7 -6
  63. package/dist/{lead-ag0akn0v.js → lead-fkk95wgq.js} +36 -32
  64. package/dist/{maintenance-xxrrzk5x.js → maintenance-fweqh0yj.js} +6 -6
  65. package/dist/{mistral-conversations-26yrnbf1.js → mistral-conversations-7gqnz2wt.js} +7 -7
  66. package/dist/oauth-refresh-sweep-sycqtbph.js +114 -0
  67. package/dist/{onboard-64zyfcbs.js → onboard-cy0rkgvx.js} +2 -2
  68. package/dist/{opencode-adapter-mqsxr170.js → opencode-adapter-p6bxnbm4.js} +2 -2
  69. package/dist/{otel-zjd9zaxs.js → otel-g299k41b.js} +2 -2
  70. package/dist/{otel-impl-b86qseaa.js → otel-impl-wbns8hf7.js} +1 -1
  71. package/dist/{pi-mono-adapter-kxjxpfgy.js → pi-mono-adapter-05sck1ds.js} +17 -109
  72. package/dist/{pricing-refresh-cgtkffpa.js → pricing-refresh-0hvazmkf.js} +6 -6
  73. package/dist/rbac-roles-kfesjy9y.js +28 -0
  74. package/dist/rbac-roles-mwcwhbxc.js +283 -0
  75. package/dist/{seed-pricing-1r1y1xkq.js → seed-pricing-h2kdq4cq.js} +5 -5
  76. package/dist/{setup-mkcgkjf7.js → setup-8506q97w.js} +2 -2
  77. package/dist/{worker-351sze63.js → worker-4r5c6zyb.js} +36 -32
  78. package/openapi.json +1327 -20
  79. package/package.json +3 -3
  80. package/src/agentmail/handlers.ts +16 -6
  81. package/src/agentmail/templates.ts +25 -5
  82. package/src/be/audit-user.ts +28 -4
  83. package/src/be/db-queries/oauth.ts +42 -0
  84. package/src/be/db.ts +314 -28
  85. package/src/be/identity.ts +79 -0
  86. package/src/be/mcp-proxy.ts +132 -0
  87. package/src/be/migrations/109_rbac_roles.sql +79 -0
  88. package/src/be/migrations/111_oauth_credential_bindings.sql +6 -0
  89. package/src/be/migrations/112_script_connections_graphql.sql +96 -0
  90. package/src/be/migrations/113_task_routing_affinity.sql +9 -0
  91. package/src/be/migrations/114_backfill_gpt_5_6_pricing.sql +15 -0
  92. package/src/be/modelsdev-cache.json +135 -0
  93. package/src/be/oauth-credential-bindings.ts +35 -0
  94. package/src/be/oauth-refresh-sweep.ts +159 -0
  95. package/src/be/rbac-audit.ts +52 -13
  96. package/src/be/rbac-roles.ts +424 -0
  97. package/src/be/script-connections.ts +459 -40
  98. package/src/be/script-credential-broker.ts +29 -3
  99. package/src/be/scripts/typecheck.ts +51 -7
  100. package/src/be/seed-scripts/catalog/catalog-report.inline.ts +325 -24
  101. package/src/be/seed-scripts/catalog/catalog-report.ts +325 -24
  102. package/src/be/users.ts +24 -0
  103. package/src/cli.tsx +17 -0
  104. package/src/github/handlers.ts +10 -9
  105. package/src/github/templates.ts +45 -9
  106. package/src/gitlab/handlers.ts +20 -8
  107. package/src/gitlab/templates.ts +18 -6
  108. package/src/heartbeat/heartbeat.ts +143 -21
  109. package/src/http/all-routes.ts +3 -0
  110. package/src/http/approval-requests.ts +12 -0
  111. package/src/http/config.ts +44 -10
  112. package/src/http/core.ts +30 -3
  113. package/src/http/favorites.ts +1 -5
  114. package/src/http/index.ts +25 -1
  115. package/src/http/mcp-oauth.ts +2 -0
  116. package/src/http/mcp-servers.ts +44 -3
  117. package/src/http/oauth-generic.ts +105 -0
  118. package/src/http/poll.ts +20 -3
  119. package/src/http/script-connection-proxy.ts +116 -0
  120. package/src/http/script-connections.ts +1657 -0
  121. package/src/http/scripts.ts +27 -7
  122. package/src/http/skills.ts +11 -0
  123. package/src/http/tasks.ts +27 -13
  124. package/src/http/x.ts +6 -2
  125. package/src/integrations/kapso/inbound.ts +23 -6
  126. package/src/jira/sync.ts +111 -17
  127. package/src/jira/templates.ts +10 -2
  128. package/src/mcp-client/http-client.ts +194 -0
  129. package/src/oauth/app-validation.ts +16 -0
  130. package/src/oauth/ensure-token.ts +69 -14
  131. package/src/oauth/mcp-wrapper.ts +15 -0
  132. package/src/oauth/wrapper.ts +48 -20
  133. package/src/prompts/session-templates.ts +3 -3
  134. package/src/providers/codex-models.ts +100 -30
  135. package/src/providers/modelsdev-reasoning.json +51 -0
  136. package/src/providers/pi-mono-mcp-client.ts +2 -133
  137. package/src/providers/reasoning-effort.ts +11 -5
  138. package/src/rbac/admission.ts +68 -0
  139. package/src/rbac/index.ts +2 -0
  140. package/src/rbac/legacy-policy.ts +12 -0
  141. package/src/rbac/permissions.ts +48 -0
  142. package/src/scheduler/scheduler.ts +9 -1
  143. package/src/scripts-runtime/api-client.ts +151 -19
  144. package/src/scripts-runtime/api-types.ts +38 -6
  145. package/src/scripts-runtime/credential-broker/default-bindings.ts +1 -0
  146. package/src/scripts-runtime/credential-broker/types.ts +8 -0
  147. package/src/scripts-runtime/ctx.ts +11 -1
  148. package/src/scripts-runtime/eval-harness.ts +5 -1
  149. package/src/scripts-runtime/executors/types.ts +2 -1
  150. package/src/scripts-runtime/loader.ts +3 -1
  151. package/src/scripts-runtime/mcp-client.ts +87 -0
  152. package/src/scripts-runtime/sdk-allowlist.ts +1 -0
  153. package/src/scripts-runtime/types/stdlib.d.ts +6 -0
  154. package/src/scripts-runtime/types/swarm-sdk.d.ts +19 -0
  155. package/src/server-user.ts +114 -59
  156. package/src/server.ts +12 -0
  157. package/src/slack/assistant.ts +14 -6
  158. package/src/slack/enrich.ts +17 -0
  159. package/src/slack/handlers.ts +8 -22
  160. package/src/slack/responses.ts +91 -1
  161. package/src/slack/thread-buffer.ts +6 -3
  162. package/src/slack/watcher.ts +9 -6
  163. package/src/tasks/worker-follow-up.ts +100 -3
  164. package/src/tests/agentmail-handlers.test.ts +32 -3
  165. package/src/tests/approval-requests.test.ts +61 -0
  166. package/src/tests/audit-user.test.ts +47 -1
  167. package/src/tests/codex-adapter.test.ts +46 -23
  168. package/src/tests/credential-broker.test.ts +5 -2
  169. package/src/tests/delete-page-tool.test.ts +197 -0
  170. package/src/tests/github-handlers.test.ts +28 -2
  171. package/src/tests/gitlab-handlers.test.ts +125 -0
  172. package/src/tests/heartbeat.test.ts +48 -3
  173. package/src/tests/identity.test.ts +117 -0
  174. package/src/tests/jira-sync.test.ts +99 -1
  175. package/src/tests/kapso-inbound.test.ts +7 -0
  176. package/src/tests/mcp-tools-user.test.ts +60 -6
  177. package/src/tests/model-control.test.ts +8 -2
  178. package/src/tests/oauth-credential-bindings.test.ts +490 -0
  179. package/src/tests/oauth-refresh-sweep.test.ts +160 -0
  180. package/src/tests/pool-affinity.test.ts +401 -0
  181. package/src/tests/prompt-template-remaining.test.ts +2 -2
  182. package/src/tests/rbac-admission-e2e.test.ts +386 -0
  183. package/src/tests/rbac-admission.test.ts +492 -0
  184. package/src/tests/rbac-e2e-helpers.ts +3 -0
  185. package/src/tests/rbac-engine.test.ts +50 -0
  186. package/src/tests/rbac-mcp-admission-e2e.test.ts +362 -0
  187. package/src/tests/rbac-roles.test.ts +328 -0
  188. package/src/tests/reasoning-effort.test.ts +15 -2
  189. package/src/tests/schedule-target-type.test.ts +50 -0
  190. package/src/tests/scheduled-tasks.test.ts +40 -0
  191. package/src/tests/script-apis-mcp.test.ts +37 -13
  192. package/src/tests/script-connections-http.test.ts +1158 -0
  193. package/src/tests/script-connections-mcp.test.ts +894 -0
  194. package/src/tests/script-connections.test.ts +754 -6
  195. package/src/tests/scripts-external-api.test.ts +49 -1
  196. package/src/tests/scripts-runtime-secret-egress.test.ts +1 -0
  197. package/src/tests/scripts-runtime.test.ts +291 -0
  198. package/src/tests/seed-scripts.test.ts +36 -0
  199. package/src/tests/slack-identity-resolution.test.ts +37 -3
  200. package/src/tests/slack-inline-output.test.ts +65 -0
  201. package/src/tests/slack-thread-buffer.test.ts +28 -0
  202. package/src/tests/tool-annotations.test.ts +1 -0
  203. package/src/tests/user-token-rest-auth.test.ts +140 -1
  204. package/src/tests/workflow-swarm-script.test.ts +44 -0
  205. package/src/tests/workflow-triggers-v2.test.ts +300 -1
  206. package/src/tools/credential-bindings/tool.ts +277 -18
  207. package/src/tools/delete-page.ts +145 -0
  208. package/src/tools/mcp-servers/mcp-server-delete.ts +7 -4
  209. package/src/tools/request-human-input.ts +2 -0
  210. package/src/tools/resolve-user.ts +77 -27
  211. package/src/tools/script-apis.ts +62 -3
  212. package/src/tools/script-connections/tool.ts +275 -31
  213. package/src/tools/send-task.ts +15 -1
  214. package/src/tools/task-action.ts +34 -2
  215. package/src/tools/templates.ts +53 -1
  216. package/src/tools/tool-config.ts +2 -1
  217. package/src/tools/utils.ts +2 -0
  218. package/src/tools/workflows/create-workflow.ts +8 -1
  219. package/src/tools/workflows/update-workflow.ts +11 -2
  220. package/src/types.ts +90 -11
  221. package/src/utils/scoped-resource.ts +25 -0
  222. package/src/workflows/executors/human-in-the-loop.ts +1 -0
  223. package/src/workflows/executors/swarm-script.ts +7 -1
  224. package/src/workflows/triggers.ts +146 -18
  225. package/templates/skills/swarm-scripts/content.md +33 -8
  226. package/dist/cli-bgef578c.js +0 -771
  227. package/dist/cli-c9mtm09b.js +0 -43
  228. package/dist/cli-ceewqajw.js +0 -1698
  229. package/dist/{anthropic-messages-8nc6r2dw.js → anthropic-messages-kq3pjhbp.js} +6 -6
  230. package/dist/{azure-openai-responses-6yfzw3qw.js → azure-openai-responses-gm1ejtmn.js} +7 -7
  231. package/dist/{cli-vbraamxr.js → cli-6b4wwmm8.js} +9 -9
  232. package/dist/{cli-2r7s8jhp.js → cli-sznberje.js} +3 -3
  233. package/dist/{google-generative-ai-vfq6ymz9.js → google-generative-ai-hyrra2jm.js} +3 -3
  234. package/dist/{google-vertex-mj5y5rfj.js → google-vertex-8bs2pzgn.js} +3 -3
  235. package/dist/{openai-codex-responses-8h6kvfv8.js → openai-codex-responses-3nkvje79.js} +4 -4
  236. package/dist/{openai-completions-x6kjnqjd.js → openai-completions-0asftkwg.js} +11 -11
  237. package/dist/{openai-responses-7jwyz3ds.js → openai-responses-pmatek45.js} +10 -10
@@ -1,10 +1,33 @@
1
1
  import type {
2
2
  ScriptApiConnectionDescriptor,
3
+ ScriptApiCredentialDescriptor,
3
4
  ScriptApiOperationDescriptor,
4
5
  ScriptApiRegistryClient,
5
6
  } from "./api-types";
6
7
 
7
- function applyTemplate(template: string, _placeholder: string): [string, string] | null {
8
+ type FetchResponse = Awaited<ReturnType<typeof fetch>>;
9
+ type TextResponse = {
10
+ headers: { get(name: string): string | null };
11
+ text(): Promise<string>;
12
+ };
13
+
14
+ type ScriptApiRawResult = {
15
+ ok: boolean;
16
+ status: number;
17
+ statusText: string;
18
+ headers: Record<string, string>;
19
+ url: string;
20
+ response: FetchResponse;
21
+ };
22
+
23
+ type ScriptApiError = Error & {
24
+ status?: number;
25
+ statusText?: string;
26
+ body?: unknown;
27
+ response?: FetchResponse;
28
+ };
29
+
30
+ function applyTemplate(template: string): [string, string] | null {
8
31
  const idx = template.indexOf("=");
9
32
  if (idx >= 0) return [template.slice(0, idx), template.slice(idx + 1)];
10
33
  const colon = template.indexOf(":");
@@ -12,6 +35,22 @@ function applyTemplate(template: string, _placeholder: string): [string, string]
12
35
  return null;
13
36
  }
14
37
 
38
+ function applyCredential(
39
+ credential: ScriptApiCredentialDescriptor | null,
40
+ url: URL,
41
+ headers: Headers,
42
+ ) {
43
+ if (!credential) return;
44
+ if (credential.headerTemplate) {
45
+ const parsed = applyTemplate(credential.headerTemplate);
46
+ if (parsed) headers.set(parsed[0], parsed[1]);
47
+ }
48
+ if (credential.queryTemplate) {
49
+ const parsed = applyTemplate(credential.queryTemplate);
50
+ if (parsed) url.searchParams.set(parsed[0], parsed[1]);
51
+ }
52
+ }
53
+
15
54
  function operationUrl(
16
55
  baseUrl: string,
17
56
  operation: ScriptApiOperationDescriptor,
@@ -28,7 +67,9 @@ function operationUrl(
28
67
  throw new Error(`Missing path parameter ${param.name}`);
29
68
  path = path.replace(`{${param.name}}`, encodeURIComponent(String(value ?? "")));
30
69
  }
31
- const url = new URL(path, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`);
70
+ // Spec paths are absolute ("/store/inventory"); resolve them relative to the
71
+ // base URL so a base path prefix like "/api/v3" is preserved.
72
+ const url = new URL(path.replace(/^\/+/, ""), baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`);
32
73
  const queryArgs = (args.query && typeof args.query === "object" ? args.query : {}) as Record<
33
74
  string,
34
75
  unknown
@@ -44,14 +85,113 @@ function operationUrl(
44
85
  return url;
45
86
  }
46
87
 
88
+ function graphqlErrorMessage(errors: unknown): string {
89
+ if (!Array.isArray(errors)) return JSON.stringify(errors);
90
+ return errors
91
+ .map((error) => {
92
+ if (error && typeof error === "object" && "message" in error) {
93
+ return String((error as { message: unknown }).message);
94
+ }
95
+ return JSON.stringify(error);
96
+ })
97
+ .join("; ");
98
+ }
99
+
100
+ function isRawOptions(value: unknown): value is { raw: true } {
101
+ return Boolean(value && typeof value === "object" && (value as { raw?: unknown }).raw === true);
102
+ }
103
+
104
+ function headersRecord(headers: Headers): Record<string, string> {
105
+ return Object.fromEntries(headers.entries());
106
+ }
107
+
108
+ function rawResult(response: FetchResponse): ScriptApiRawResult {
109
+ return {
110
+ ok: response.ok,
111
+ status: response.status,
112
+ statusText: response.statusText,
113
+ headers: headersRecord(response.headers),
114
+ url: response.url,
115
+ response,
116
+ };
117
+ }
118
+
119
+ async function parseResponseBody(response: TextResponse): Promise<unknown> {
120
+ const text = await response.text();
121
+ if (!text) return "";
122
+ const contentType = response.headers.get("content-type") ?? "";
123
+ if (contentType.includes("application/json")) {
124
+ try {
125
+ return JSON.parse(text);
126
+ } catch {
127
+ return text;
128
+ }
129
+ }
130
+ return text;
131
+ }
132
+
133
+ async function throwApiResponseError(
134
+ slug: string,
135
+ operationName: string,
136
+ response: FetchResponse,
137
+ ): Promise<never> {
138
+ const error = new Error(
139
+ `ctx.api.${slug}.${operationName} failed with ${response.status}`,
140
+ ) as ScriptApiError;
141
+ error.status = response.status;
142
+ error.statusText = response.statusText;
143
+ error.response = response;
144
+ try {
145
+ error.body = await parseResponseBody(response.clone());
146
+ } catch {
147
+ error.body = undefined;
148
+ }
149
+ throw error;
150
+ }
151
+
47
152
  export function createApiRegistryClient(
48
153
  descriptors: ScriptApiConnectionDescriptor[] = [],
154
+ options: { fetch?: typeof fetch } = {},
49
155
  ): ScriptApiRegistryClient {
156
+ const fetchImpl = options.fetch ?? fetch;
50
157
  const registry: ScriptApiRegistryClient = {};
51
158
  for (const descriptor of descriptors) {
52
159
  const client: ScriptApiRegistryClient[string] = {};
160
+ if (descriptor.kind === "graphql") {
161
+ client.graphql = async (query, variables) => {
162
+ if (typeof query !== "string") {
163
+ throw new Error(`ctx.api.${descriptor.slug}.graphql query must be a string`);
164
+ }
165
+ const url = new URL(descriptor.baseUrl);
166
+ const headers = new Headers({ "content-type": "application/json" });
167
+ applyCredential(descriptor.credential, url, headers);
168
+ const response = await fetchImpl(url, {
169
+ method: "POST",
170
+ headers,
171
+ body: JSON.stringify({ query, variables }),
172
+ });
173
+ if (!response.ok) {
174
+ throw new Error(`ctx.api.${descriptor.slug}.graphql failed with ${response.status}`);
175
+ }
176
+ const body = (await response.json()) as unknown;
177
+ if (body && typeof body === "object") {
178
+ const record = body as { data?: unknown; errors?: unknown };
179
+ if (Array.isArray(record.errors) && !("data" in record)) {
180
+ throw new Error(
181
+ `ctx.api.${descriptor.slug}.graphql failed: ${graphqlErrorMessage(record.errors)}`,
182
+ );
183
+ }
184
+ if ("data" in record) return record.data;
185
+ }
186
+ return body;
187
+ };
188
+ registry[descriptor.slug] = client;
189
+ continue;
190
+ }
191
+
53
192
  for (const operation of descriptor.operations) {
54
- client[operation.name] = async (rawArgs = {}) => {
193
+ client[operation.name] = async (rawArgs = {}, options?: unknown) => {
194
+ const raw = isRawOptions(options);
55
195
  const args = rawArgs as Record<string, unknown>;
56
196
  const url = operationUrl(descriptor.baseUrl, operation, args);
57
197
  const headers = new Headers();
@@ -67,28 +207,20 @@ export function createApiRegistryClient(
67
207
  headers.set(param.name, String(value));
68
208
  }
69
209
 
70
- const placeholder = descriptor.credential
71
- ? `[REDACTED:${descriptor.credential.configKey}]`
72
- : null;
73
- if (placeholder && descriptor.credential?.headerTemplate) {
74
- const parsed = applyTemplate(descriptor.credential.headerTemplate, placeholder);
75
- if (parsed) headers.set(parsed[0], parsed[1]);
76
- }
77
- if (placeholder && descriptor.credential?.queryTemplate) {
78
- const parsed = applyTemplate(descriptor.credential.queryTemplate, placeholder);
79
- if (parsed) url.searchParams.set(parsed[0], parsed[1]);
80
- }
210
+ applyCredential(descriptor.credential, url, headers);
81
211
 
82
212
  const init: RequestInit = { method: operation.method, headers };
83
- if (operation.hasBody) {
213
+ // Guard against stored runtimes generated before hasBody excluded
214
+ // GET/HEAD — fetch() throws on GET/HEAD requests with a body.
215
+ const methodAllowsBody = !["GET", "HEAD"].includes(operation.method.toUpperCase());
216
+ if (operation.hasBody && methodAllowsBody) {
84
217
  headers.set("content-type", headers.get("content-type") ?? "application/json");
85
218
  init.body = JSON.stringify(args.body ?? null);
86
219
  }
87
- const response = await fetch(url, init);
220
+ const response = await fetchImpl(url, init);
221
+ if (raw) return rawResult(response);
88
222
  if (!response.ok) {
89
- throw new Error(
90
- `ctx.api.${descriptor.slug}.${operation.name} failed with ${response.status}`,
91
- );
223
+ await throwApiResponseError(descriptor.slug, operation.name, response);
92
224
  }
93
225
  const contentType = response.headers.get("content-type") ?? "";
94
226
  return contentType.includes("application/json") ? response.json() : response.text();
@@ -28,18 +28,50 @@ export type ScriptApiOperationDescriptor = {
28
28
  responseType: string;
29
29
  };
30
30
 
31
- export type ScriptApiConnectionDescriptor = {
31
+ export type ScriptApiCredentialDescriptor = {
32
+ configKey: string;
33
+ headerTemplate?: string;
34
+ queryTemplate?: string;
35
+ };
36
+
37
+ export type ScriptOpenApiConnectionDescriptor = {
32
38
  slug: string;
39
+ kind?: "openapi";
33
40
  baseUrl: string;
34
- credential: {
35
- configKey: string;
36
- headerTemplate?: string;
37
- queryTemplate?: string;
38
- } | null;
41
+ credential: ScriptApiCredentialDescriptor | null;
39
42
  operations: ScriptApiOperationDescriptor[];
40
43
  };
41
44
 
45
+ export type ScriptGraphqlConnectionDescriptor = {
46
+ slug: string;
47
+ kind: "graphql";
48
+ baseUrl: string;
49
+ credential: ScriptApiCredentialDescriptor | null;
50
+ };
51
+
52
+ export type ScriptApiConnectionDescriptor =
53
+ | ScriptOpenApiConnectionDescriptor
54
+ | ScriptGraphqlConnectionDescriptor;
55
+
56
+ export type ScriptMcpToolDescriptor = {
57
+ name: string;
58
+ description?: string;
59
+ inputSchema: ScriptApiJsonSchema;
60
+ };
61
+
62
+ export type ScriptMcpConnectionDescriptor = {
63
+ slug: string;
64
+ kind: "mcp";
65
+ connectionId: string;
66
+ tools: ScriptMcpToolDescriptor[];
67
+ };
68
+
42
69
  export type ScriptApiRegistryClient = Record<
70
+ string,
71
+ Record<string, (...args: unknown[]) => Promise<unknown>>
72
+ >;
73
+
74
+ export type ScriptMcpRegistryClient = Record<
43
75
  string,
44
76
  Record<string, (args?: Record<string, unknown>) => Promise<unknown>>
45
77
  >;
@@ -15,5 +15,6 @@ export const DEFAULT_CREDENTIAL_BINDINGS: CredentialBinding[] = [
15
15
  scope: "global",
16
16
  scopeId: null,
17
17
  active: true,
18
+ authKind: "config",
18
19
  },
19
20
  ];
@@ -5,6 +5,7 @@ export const CREDENTIAL_BINDINGS_CONFIG_KEY = "SCRIPT_CREDENTIAL_BINDINGS";
5
5
  export const REDACTED_PLACEHOLDER_PREFIX = "[REDACTED:";
6
6
 
7
7
  export const CredentialBindingScopeSchema = z.enum(["global", "agent", "repo"]);
8
+ export const CredentialBindingAuthKindSchema = z.enum(["config", "oauth"]);
8
9
 
9
10
  export const CredentialBindingSchema = z
10
11
  .object({
@@ -15,9 +16,15 @@ export const CredentialBindingSchema = z
15
16
  scope: CredentialBindingScopeSchema.default("global"),
16
17
  scopeId: z.string().nullable().optional(),
17
18
  active: z.boolean().default(true),
19
+ authKind: CredentialBindingAuthKindSchema.default("config"),
20
+ oauthProvider: z.string().min(1).max(255).optional(),
18
21
  })
19
22
  .refine((binding) => binding.headerTemplate || binding.queryTemplate, {
20
23
  message: "At least one of headerTemplate or queryTemplate is required.",
24
+ })
25
+ .refine((binding) => binding.authKind !== "oauth" || !!binding.oauthProvider, {
26
+ message: "oauthProvider is required when authKind is oauth.",
27
+ path: ["oauthProvider"],
21
28
  });
22
29
 
23
30
  export const CredentialBindingsDocumentSchema = z.union([
@@ -26,6 +33,7 @@ export const CredentialBindingsDocumentSchema = z.union([
26
33
  ]);
27
34
 
28
35
  export type CredentialBindingScope = z.infer<typeof CredentialBindingScopeSchema>;
36
+ export type CredentialBindingAuthKind = z.infer<typeof CredentialBindingAuthKindSchema>;
29
37
  export type CredentialBinding = z.infer<typeof CredentialBindingSchema>;
30
38
 
31
39
  export type ResolvedCredentialBinding = CredentialBinding & {
@@ -1,5 +1,11 @@
1
1
  import { createApiRegistryClient } from "./api-client";
2
- import type { ScriptApiConnectionDescriptor, ScriptApiRegistryClient } from "./api-types";
2
+ import type {
3
+ ScriptApiConnectionDescriptor,
4
+ ScriptApiRegistryClient,
5
+ ScriptMcpConnectionDescriptor,
6
+ ScriptMcpRegistryClient,
7
+ } from "./api-types";
8
+ import { createMcpRegistryClient } from "./mcp-client";
3
9
  import { stdlib } from "./stdlib";
4
10
  import type { SwarmConfig } from "./swarm-config";
5
11
  import { createSwarmSdk } from "./swarm-sdk";
@@ -7,6 +13,7 @@ import { createSwarmSdk } from "./swarm-sdk";
7
13
  export type RuntimeCtx = {
8
14
  swarm: Record<string, unknown> & { config: SwarmConfig };
9
15
  api: ScriptApiRegistryClient;
16
+ mcp: ScriptMcpRegistryClient;
10
17
  stdlib: typeof stdlib;
11
18
  logger: {
12
19
  log: (...args: unknown[]) => void;
@@ -18,15 +25,18 @@ export type RuntimeCtx = {
18
25
  export function buildCtx({
19
26
  swarmConfig,
20
27
  apiConnections,
28
+ mcpConnections,
21
29
  }: {
22
30
  swarmConfig: SwarmConfig;
23
31
  apiConnections?: ScriptApiConnectionDescriptor[];
32
+ mcpConnections?: ScriptMcpConnectionDescriptor[];
24
33
  }): RuntimeCtx {
25
34
  const swarm = createSwarmSdk(swarmConfig) as Record<string, unknown> & { config: SwarmConfig };
26
35
  swarm.config = swarmConfig;
27
36
  return {
28
37
  swarm,
29
38
  api: createApiRegistryClient(apiConnections),
39
+ mcp: createMcpRegistryClient(mcpConnections, swarmConfig),
30
40
  stdlib,
31
41
  logger: console,
32
42
  };
@@ -92,7 +92,11 @@ try {
92
92
  const rawArgs = JSON.parse(await Bun.file(requiredEnv("SWARM_SCRIPT_ARGS_FILE")).text());
93
93
  // Accept both shapes: callers may pass an already-serialized JSON string.
94
94
  const parsedArgs = typeof rawArgs === "string" ? JSON.parse(rawArgs) : rawArgs;
95
- const ctx = buildCtx({ swarmConfig, apiConnections: payload.apiConnections });
95
+ const ctx = buildCtx({
96
+ swarmConfig,
97
+ apiConnections: payload.apiConnections,
98
+ mcpConnections: payload.mcpConnections,
99
+ });
96
100
 
97
101
  const sourceText = await Bun.file(requiredEnv("SWARM_SCRIPT_SOURCE_FILE")).text();
98
102
  await Bun.write(userModulePath, sourceText);
@@ -1,4 +1,4 @@
1
- import type { ScriptApiConnectionDescriptor } from "../api-types";
1
+ import type { ScriptApiConnectionDescriptor, ScriptMcpConnectionDescriptor } from "../api-types";
2
2
  import type { ResolvedCredentialBinding } from "../credential-broker";
3
3
 
4
4
  export type ScriptFsMode = "none" | "workspace-rw";
@@ -14,6 +14,7 @@ export type SwarmConfigPayload = {
14
14
  user: Record<string, { value: string; isSecret: boolean }>;
15
15
  egressSecrets?: EgressSecretEntry[];
16
16
  apiConnections?: ScriptApiConnectionDescriptor[];
17
+ mcpConnections?: ScriptMcpConnectionDescriptor[];
17
18
  };
18
19
 
19
20
  export type ScriptResourcePolicy = {
@@ -1,6 +1,6 @@
1
1
  import { getApiKey } from "../utils/api-key";
2
2
  import { scrubObject, scrubSecrets } from "../utils/secret-scrubber";
3
- import type { ScriptApiConnectionDescriptor } from "./api-types";
3
+ import type { ScriptApiConnectionDescriptor, ScriptMcpConnectionDescriptor } from "./api-types";
4
4
  import { buildEgressSecrets } from "./egress-secrets";
5
5
  import { getScriptExecutor } from "./executors/registry";
6
6
  import {
@@ -25,6 +25,7 @@ export type RunScriptInput = {
25
25
  userConfig?: Record<string, { value: string; isSecret: boolean }>;
26
26
  egressSecrets?: EgressSecretEntry[];
27
27
  apiConnections?: ScriptApiConnectionDescriptor[];
28
+ mcpConnections?: ScriptMcpConnectionDescriptor[];
28
29
  };
29
30
 
30
31
  export type RunScriptOutput = Omit<ExecutorOutput, "result" | "stdout" | "stderr"> & {
@@ -51,6 +52,7 @@ function buildConfigPayload(input: RunScriptInput): SwarmConfigPayload {
51
52
  user: input.userConfig ?? {},
52
53
  egressSecrets: input.egressSecrets ?? buildEgressSecrets(),
53
54
  apiConnections: input.apiConnections ?? [],
55
+ mcpConnections: input.mcpConnections ?? [],
54
56
  };
55
57
  }
56
58
 
@@ -0,0 +1,87 @@
1
+ import type { ScriptMcpConnectionDescriptor, ScriptMcpRegistryClient } from "./api-types";
2
+ import { Redacted } from "./redacted";
3
+ import type { SwarmConfig } from "./swarm-config";
4
+
5
+ function methodName(name: string): string {
6
+ const cleaned = name
7
+ .trim()
8
+ .replace(/[^A-Za-z0-9]+(.)/g, (_m, chr: string) => chr.toUpperCase())
9
+ .replace(/^[^A-Za-z_]+/, "")
10
+ .replace(/[^A-Za-z0-9_]/g, "");
11
+ if (!cleaned) throw new Error("MCP tool name must contain at least one letter");
12
+ return `${cleaned[0]?.toLowerCase()}${cleaned.slice(1)}`;
13
+ }
14
+
15
+ function methodNames(
16
+ tools: Array<{ name: string }>,
17
+ ): Array<{ tool: { name: string }; methodName: string }> {
18
+ const used = new Set<string>();
19
+ const baseCounts = new Map<string, number>();
20
+ return tools.map((tool) => {
21
+ const base = methodName(tool.name);
22
+ const count = baseCounts.get(base) ?? 0;
23
+ baseCounts.set(base, count + 1);
24
+ let toolMethod = count === 0 ? base : `${base}${count + 1}`;
25
+ let suffix = count + 2;
26
+ while (used.has(toolMethod)) {
27
+ toolMethod = `${base}${suffix}`;
28
+ suffix += 1;
29
+ }
30
+ used.add(toolMethod);
31
+ return { tool, methodName: toolMethod };
32
+ });
33
+ }
34
+
35
+ function headers(config: SwarmConfig): Record<string, string> {
36
+ return {
37
+ Authorization: `Bearer ${Redacted.value(config.apiKey)}`,
38
+ "X-Agent-ID": Redacted.value(config.agentId),
39
+ "Content-Type": "application/json",
40
+ };
41
+ }
42
+
43
+ function errorMessage(data: unknown): string {
44
+ if (data && typeof data === "object") {
45
+ if ("error" in data) return JSON.stringify((data as { error: unknown }).error);
46
+ if ("message" in data) return String((data as { message: unknown }).message);
47
+ }
48
+ return "unknown MCP proxy error";
49
+ }
50
+
51
+ export function createMcpRegistryClient(
52
+ descriptors: ScriptMcpConnectionDescriptor[] = [],
53
+ config: SwarmConfig,
54
+ ): ScriptMcpRegistryClient {
55
+ const registry: ScriptMcpRegistryClient = {};
56
+ const baseUrl = Redacted.value(config.mcpBaseUrl).replace(/\/$/, "");
57
+
58
+ for (const descriptor of descriptors) {
59
+ const client: ScriptMcpRegistryClient[string] = {};
60
+ for (const { tool, methodName: toolMethod } of methodNames(descriptor.tools)) {
61
+ client[toolMethod] = async (rawArgs = {}) => {
62
+ const response = await fetch(
63
+ `${baseUrl}/api/script-connections/${encodeURIComponent(descriptor.connectionId)}/mcp-call`,
64
+ {
65
+ method: "POST",
66
+ headers: headers(config),
67
+ body: JSON.stringify({ tool: tool.name, arguments: rawArgs ?? {} }),
68
+ },
69
+ );
70
+ const text = await response.text();
71
+ const data = text ? JSON.parse(text) : {};
72
+ if (!response.ok) {
73
+ throw new Error(
74
+ `ctx.mcp.${descriptor.slug}.${toolMethod} failed with ${response.status}: ${errorMessage(data)}`,
75
+ );
76
+ }
77
+ if (!data || typeof data !== "object" || (data as { ok?: unknown }).ok !== true) {
78
+ throw new Error(`ctx.mcp.${descriptor.slug}.${toolMethod} failed: ${errorMessage(data)}`);
79
+ }
80
+ return (data as { result: unknown }).result;
81
+ };
82
+ }
83
+ registry[descriptor.slug] = client;
84
+ }
85
+
86
+ return registry;
87
+ }
@@ -139,6 +139,7 @@ export const SDK_TOOL_NAME_MAP = {
139
139
 
140
140
  // ── pages & metrics ──
141
141
  page_create: "create_page",
142
+ page_delete: "delete-page", // destructive
142
143
  metric_create: "create_metric",
143
144
 
144
145
  // ── human input ──
@@ -108,6 +108,7 @@ declare module "swarm-sdk" {
108
108
  externalId?: string;
109
109
  email?: string;
110
110
  userId?: string;
111
+ name?: string;
111
112
  }): Promise<unknown>;
112
113
  db_query(args: { sql: string; params?: unknown[] }): Promise<unknown>;
113
114
  // --- config ---
@@ -242,6 +243,8 @@ declare module "swarm-sdk" {
242
243
  slack_startThread(args: { channelId: string; message: string }): Promise<unknown>;
243
244
  slack_uploadFile(args: Record<string, unknown>): Promise<unknown>;
244
245
  slack_downloadFile(args: { url: string }): Promise<unknown>;
246
+ slack_delete(args: { channelId: string; messageTs: string }): Promise<unknown>;
247
+ slack_update(args: { channelId: string; messageTs: string; message: string }): Promise<unknown>;
245
248
 
246
249
  // --- write: messaging (internal) ---
247
250
  message_post(args: { channel?: string; content: string; to?: string }): Promise<unknown>;
@@ -346,6 +349,7 @@ declare module "swarm-sdk" {
346
349
 
347
350
  // --- pages & metrics ---
348
351
  page_create(args: Record<string, unknown>): Promise<unknown>;
352
+ page_delete(args: { pageId?: string; slug?: string }): Promise<unknown>;
349
353
  metric_create(args: Record<string, unknown>): Promise<unknown>;
350
354
 
351
355
  // --- human input ---
@@ -411,6 +415,8 @@ declare module "swarm-sdk" {
411
415
  run?: ScriptRunContext;
412
416
  step?: ScriptWorkflowSteps;
413
417
  swarm: SwarmSdk & { config: SwarmConfig };
418
+ api: ScriptApiRegistry;
419
+ mcp: ScriptMcpRegistry;
414
420
  stdlib: ScriptStdlib;
415
421
  logger: ScriptLogger;
416
422
  }
@@ -8,6 +8,17 @@ declare module "swarm-sdk" {
8
8
  | { [key: string]: JsonValue };
9
9
  export type ScriptScope = "agent" | "global";
10
10
  export type ScriptFsMode = "none" | "workspace-rw";
11
+ export type ScriptApiRawOptions = { raw: true };
12
+ export type ScriptApiDefaultOptions = { raw?: false };
13
+
14
+ export interface ScriptApiRawResult {
15
+ ok: boolean;
16
+ status: number;
17
+ statusText: string;
18
+ headers: Record<string, string>;
19
+ url: string;
20
+ response: Response;
21
+ }
11
22
 
12
23
  export interface Redacted<T> {
13
24
  readonly __redactedBrand?: T;
@@ -90,6 +101,7 @@ declare module "swarm-sdk" {
90
101
  externalId?: string;
91
102
  email?: string;
92
103
  userId?: string;
104
+ name?: string;
93
105
  }): Promise<unknown>;
94
106
  db_query(args: { sql: string; params?: unknown[] }): Promise<unknown>;
95
107
  // --- config ---
@@ -330,6 +342,7 @@ declare module "swarm-sdk" {
330
342
 
331
343
  // --- pages & metrics ---
332
344
  page_create(args: Record<string, unknown>): Promise<unknown>;
345
+ page_delete(args: { pageId?: string; slug?: string }): Promise<unknown>;
333
346
  metric_create(args: Record<string, unknown>): Promise<unknown>;
334
347
 
335
348
  // --- human input ---
@@ -395,10 +408,16 @@ declare module "swarm-sdk" {
395
408
  run?: ScriptRunContext;
396
409
  step?: ScriptWorkflowSteps;
397
410
  swarm: SwarmSdk & { config: SwarmConfig };
411
+ api: ScriptApiRegistry;
412
+ mcp: ScriptMcpRegistry;
398
413
  stdlib: ScriptStdlib;
399
414
  logger: ScriptLogger;
400
415
  }
401
416
 
402
417
  // biome-ignore lint/suspicious/noExplicitAny: scripts may narrow their args type at the entrypoint.
403
418
  export type ScriptMain = (args: any, ctx: ScriptContext) => unknown | Promise<unknown>;
419
+
420
+ export interface ScriptApiRegistry {}
421
+
422
+ export interface ScriptMcpRegistry {}
404
423
  }