@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,39 +1,153 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import * as z from "zod";
3
3
  import { getAgentById } from "@/be/db";
4
+ import { upsertOAuthApp } from "@/be/db-queries/oauth";
5
+ import {
6
+ getOAuthBindingTokenStatus,
7
+ getOAuthProviderConfig,
8
+ type OAuthBindingTokenStatus,
9
+ } from "@/be/oauth-credential-bindings";
4
10
  import {
5
11
  disableCredentialBinding,
6
12
  importLegacyCredentialBindings,
7
13
  listRelationalCredentialBindings,
14
+ type ScriptCredentialBindingRecord,
8
15
  upsertCredentialBinding,
9
16
  } from "@/be/script-connections";
17
+ import { assertOAuthAppUrlsSafe, assertOAuthProviderIsNotReserved } from "@/oauth/app-validation";
18
+ import { buildAuthorizationUrl } from "@/oauth/wrapper";
10
19
  import { can } from "@/rbac";
11
20
  import {
12
21
  CredentialBindingSchema,
13
22
  placeholderForConfigKey,
14
23
  } from "@/scripts-runtime/credential-broker";
15
24
  import { createToolRegistrar } from "@/tools/utils";
25
+ import { getPublicMcpBaseUrl } from "@/utils/constants";
26
+ import { resolveScopedResourceId, scopedResourceScopeIdSchema } from "@/utils/scoped-resource";
27
+
28
+ const providerSchema = z
29
+ .string()
30
+ .min(1)
31
+ .max(255)
32
+ .regex(/^[A-Za-z0-9_-]+$/);
33
+ const tokenStatusSchema = z.enum(["ok", "expiring", "missing"]);
34
+ const credentialBindingToolBindingSchema = CredentialBindingSchema.and(
35
+ z.object({
36
+ id: z.string().optional(),
37
+ source: z.enum(["default", "user", "migration"]).optional(),
38
+ createdAt: z.string().optional(),
39
+ updatedAt: z.string().optional(),
40
+ createdBy: z.string().nullable().optional(),
41
+ updatedBy: z.string().nullable().optional(),
42
+ tokenStatus: tokenStatusSchema.optional(),
43
+ }),
44
+ );
16
45
 
17
46
  const credentialBindingsOutputSchema = z.object({
18
47
  yourAgentId: z.string().uuid().optional(),
19
48
  success: z.boolean(),
20
49
  message: z.string(),
21
- bindings: z.array(CredentialBindingSchema),
50
+ provider: z.string().optional(),
51
+ authorizeUrl: z.string().optional(),
52
+ redirectUri: z.string().optional(),
53
+ bindings: z.array(credentialBindingToolBindingSchema),
22
54
  });
23
55
 
24
56
  const credentialBindingsInputSchema = z.object({
25
57
  action: z
26
- .enum(["list", "upsert", "disable", "import-legacy"])
27
- .describe("List, add/update, disable, or import legacy JSON bindings."),
28
- id: z.string().uuid().optional(),
29
- configKey: z.string().min(1).max(255).optional(),
30
- allowedHosts: z.array(z.string().min(1)).min(1).optional(),
31
- headerTemplate: z.string().min(1).optional(),
32
- queryTemplate: z.string().min(1).optional(),
33
- scope: z.enum(["global", "agent", "repo"]).default("global").optional(),
34
- scopeId: z.string().uuid().nullable().optional(),
58
+ .enum(["list", "upsert", "disable", "import-legacy", "oauth-app-upsert", "oauth-authorize-url"])
59
+ .describe(
60
+ "List, add/update, disable, import legacy JSON bindings, or register/authorize OAuth apps.",
61
+ ),
62
+ id: z
63
+ .string()
64
+ .uuid()
65
+ .optional()
66
+ .describe("Existing credential binding ID for update or disable."),
67
+ configKey: z
68
+ .string()
69
+ .min(1)
70
+ .max(255)
71
+ .optional()
72
+ .describe("Swarm config key whose secret value is injected through templates."),
73
+ allowedHosts: z
74
+ .array(z.string().min(1))
75
+ .min(1)
76
+ .optional()
77
+ .describe("Allowed outbound hostnames for this binding."),
78
+ headerTemplate: z
79
+ .string()
80
+ .min(1)
81
+ .optional()
82
+ .describe("Header template containing the config-key placeholder."),
83
+ queryTemplate: z
84
+ .string()
85
+ .min(1)
86
+ .optional()
87
+ .describe("Query parameter template containing the config-key placeholder."),
88
+ scope: z
89
+ .enum(["global", "agent", "repo"])
90
+ .default("global")
91
+ .optional()
92
+ .describe("Binding visibility scope."),
93
+ scopeId: scopedResourceScopeIdSchema
94
+ .nullable()
95
+ .optional()
96
+ .describe("Agent UUID for agent scope or repo id (owner/name) for repo scope."),
97
+ authKind: z
98
+ .enum(["config", "oauth"])
99
+ .default("config")
100
+ .optional()
101
+ .describe("Use config for stored swarm config secrets or oauth for OAuth token resolution."),
102
+ oauthProvider: providerSchema
103
+ .optional()
104
+ .describe("OAuth provider slug required when authKind is oauth."),
105
+ provider: providerSchema
106
+ .optional()
107
+ .describe("OAuth provider slug for oauth-app-upsert and oauth-authorize-url."),
108
+ clientId: z.string().min(1).optional().describe("OAuth client ID for oauth-app-upsert."),
109
+ clientSecret: z.string().min(1).optional().describe("OAuth client secret for oauth-app-upsert."),
110
+ authorizeUrl: z
111
+ .string()
112
+ .url()
113
+ .optional()
114
+ .describe("OAuth authorization URL for oauth-app-upsert."),
115
+ tokenUrl: z.string().url().optional().describe("OAuth token URL for oauth-app-upsert."),
116
+ scopes: z.array(z.string().min(1)).optional().describe("OAuth scopes for oauth-app-upsert."),
117
+ extraParams: z
118
+ .record(z.string(), z.string())
119
+ .optional()
120
+ .describe("Extra OAuth authorization parameters stored with the OAuth app."),
121
+ tokenAuthStyle: z
122
+ .enum(["body", "basic"])
123
+ .optional()
124
+ .describe(
125
+ "How client credentials reach the token endpoint: body params (default) or HTTP Basic auth (required by e.g. Notion).",
126
+ ),
127
+ tokenBodyFormat: z
128
+ .enum(["form", "json"])
129
+ .optional()
130
+ .describe(
131
+ "Token request body encoding: form-urlencoded (default) or JSON (required by e.g. Notion).",
132
+ ),
35
133
  });
36
134
 
135
+ type BindingWithTokenStatus = ScriptCredentialBindingRecord & {
136
+ tokenStatus?: OAuthBindingTokenStatus;
137
+ };
138
+
139
+ function genericOAuthRedirectUri(provider: string): string {
140
+ return `${getPublicMcpBaseUrl()}/api/oauth/${encodeURIComponent(provider)}/callback`;
141
+ }
142
+
143
+ function decorateBindings(bindings: ScriptCredentialBindingRecord[]): BindingWithTokenStatus[] {
144
+ return bindings.map((binding) =>
145
+ binding.authKind === "oauth" && binding.oauthProvider
146
+ ? { ...binding, tokenStatus: getOAuthBindingTokenStatus(binding.oauthProvider) }
147
+ : binding,
148
+ );
149
+ }
150
+
37
151
  export const registerCredentialBindingsTool = (server: McpServer) => {
38
152
  createToolRegistrar(server)(
39
153
  "credential-bindings",
@@ -80,7 +194,133 @@ export const registerCredentialBindingsTool = (server: McpServer) => {
80
194
  };
81
195
  }
82
196
 
83
- const bindings = listRelationalCredentialBindings({ includeInactive: true });
197
+ const currentBindings = () =>
198
+ decorateBindings(listRelationalCredentialBindings({ includeInactive: true }));
199
+ const bindings = currentBindings();
200
+
201
+ if (args.action === "oauth-app-upsert") {
202
+ if (
203
+ !args.provider ||
204
+ !args.clientId ||
205
+ !args.clientSecret ||
206
+ !args.authorizeUrl ||
207
+ !args.tokenUrl ||
208
+ !args.scopes
209
+ ) {
210
+ return {
211
+ content: [
212
+ {
213
+ type: "text",
214
+ text: "provider, clientId, clientSecret, authorizeUrl, tokenUrl, and scopes are required for oauth-app-upsert.",
215
+ },
216
+ ],
217
+ structuredContent: {
218
+ yourAgentId: requestInfo.agentId,
219
+ success: false,
220
+ message:
221
+ "provider, clientId, clientSecret, authorizeUrl, tokenUrl, and scopes are required for oauth-app-upsert.",
222
+ bindings,
223
+ },
224
+ };
225
+ }
226
+
227
+ try {
228
+ assertOAuthProviderIsNotReserved(args.provider);
229
+ assertOAuthAppUrlsSafe({
230
+ authorizeUrl: args.authorizeUrl,
231
+ tokenUrl: args.tokenUrl,
232
+ });
233
+ } catch (err) {
234
+ const message = err instanceof Error ? err.message : String(err);
235
+ return {
236
+ content: [{ type: "text", text: message }],
237
+ structuredContent: {
238
+ yourAgentId: requestInfo.agentId,
239
+ success: false,
240
+ message,
241
+ bindings,
242
+ },
243
+ };
244
+ }
245
+
246
+ const redirectUri = genericOAuthRedirectUri(args.provider);
247
+ upsertOAuthApp(args.provider, {
248
+ clientId: args.clientId,
249
+ clientSecret: args.clientSecret,
250
+ authorizeUrl: args.authorizeUrl,
251
+ tokenUrl: args.tokenUrl,
252
+ redirectUri,
253
+ scopes: args.scopes.join(","),
254
+ ...(args.extraParams || args.tokenAuthStyle || args.tokenBodyFormat
255
+ ? {
256
+ metadata: JSON.stringify({
257
+ ...(args.extraParams ? { extraParams: args.extraParams } : {}),
258
+ ...(args.tokenAuthStyle ? { tokenAuthStyle: args.tokenAuthStyle } : {}),
259
+ ...(args.tokenBodyFormat ? { tokenBodyFormat: args.tokenBodyFormat } : {}),
260
+ }),
261
+ }
262
+ : {}),
263
+ });
264
+
265
+ return {
266
+ content: [
267
+ {
268
+ type: "text",
269
+ text: `OAuth app ${args.provider} saved. Redirect URI: ${redirectUri}`,
270
+ },
271
+ ],
272
+ structuredContent: {
273
+ yourAgentId: requestInfo.agentId,
274
+ success: true,
275
+ message: `OAuth app ${args.provider} saved.`,
276
+ provider: args.provider,
277
+ redirectUri,
278
+ bindings: currentBindings(),
279
+ },
280
+ };
281
+ }
282
+
283
+ if (args.action === "oauth-authorize-url") {
284
+ if (!args.provider) {
285
+ return {
286
+ content: [{ type: "text", text: "provider is required for oauth-authorize-url." }],
287
+ structuredContent: {
288
+ yourAgentId: requestInfo.agentId,
289
+ success: false,
290
+ message: "provider is required for oauth-authorize-url.",
291
+ bindings,
292
+ },
293
+ };
294
+ }
295
+
296
+ const config = getOAuthProviderConfig(args.provider);
297
+ if (!config) {
298
+ return {
299
+ content: [{ type: "text", text: `OAuth app ${args.provider} is not configured.` }],
300
+ structuredContent: {
301
+ yourAgentId: requestInfo.agentId,
302
+ success: false,
303
+ message: `OAuth app ${args.provider} is not configured.`,
304
+ provider: args.provider,
305
+ bindings,
306
+ },
307
+ };
308
+ }
309
+
310
+ const result = await buildAuthorizationUrl(config);
311
+ return {
312
+ content: [{ type: "text", text: result.url }],
313
+ structuredContent: {
314
+ yourAgentId: requestInfo.agentId,
315
+ success: true,
316
+ message: `OAuth authorization URL generated for ${args.provider}.`,
317
+ provider: args.provider,
318
+ authorizeUrl: result.url,
319
+ redirectUri: config.redirectUri,
320
+ bindings,
321
+ },
322
+ };
323
+ }
84
324
 
85
325
  if (args.action === "list") {
86
326
  return {
@@ -107,7 +347,7 @@ export const registerCredentialBindingsTool = (server: McpServer) => {
107
347
 
108
348
  if (args.action === "import-legacy") {
109
349
  const imported = importLegacyCredentialBindings();
110
- const nextBindings = listRelationalCredentialBindings({ includeInactive: true });
350
+ const nextBindings = currentBindings();
111
351
  return {
112
352
  content: [{ type: "text", text: `Imported ${imported} legacy credential binding(s).` }],
113
353
  structuredContent: {
@@ -133,7 +373,7 @@ export const registerCredentialBindingsTool = (server: McpServer) => {
133
373
  };
134
374
  }
135
375
 
136
- const nextBindings = listRelationalCredentialBindings({ includeInactive: true });
376
+ const nextBindings = currentBindings();
137
377
  return {
138
378
  content: [{ type: "text", text: `Credential binding ${disabled.configKey} disabled.` }],
139
379
  structuredContent: {
@@ -158,14 +398,17 @@ export const registerCredentialBindingsTool = (server: McpServer) => {
158
398
  }
159
399
 
160
400
  const scope = args.scope ?? "global";
161
- const scopeId = scope === "global" ? null : (args.scopeId ?? null);
162
- if (scope !== "global" && !scopeId) {
401
+ let scopeId: string | null;
402
+ try {
403
+ scopeId = resolveScopedResourceId(scope, args.scopeId, "bindings");
404
+ } catch (err) {
405
+ const message = err instanceof Error ? err.message : String(err);
163
406
  return {
164
- content: [{ type: "text", text: `scopeId is required for ${scope} bindings.` }],
407
+ content: [{ type: "text", text: message }],
165
408
  structuredContent: {
166
409
  yourAgentId: requestInfo.agentId,
167
410
  success: false,
168
- message: `scopeId is required for ${scope} bindings.`,
411
+ message,
169
412
  bindings,
170
413
  },
171
414
  };
@@ -189,6 +432,18 @@ export const registerCredentialBindingsTool = (server: McpServer) => {
189
432
  };
190
433
  }
191
434
 
435
+ if ((args.authKind ?? "config") === "oauth" && !args.oauthProvider) {
436
+ return {
437
+ content: [{ type: "text", text: "oauthProvider is required for oauth bindings." }],
438
+ structuredContent: {
439
+ yourAgentId: requestInfo.agentId,
440
+ success: false,
441
+ message: "oauthProvider is required for oauth bindings.",
442
+ bindings,
443
+ },
444
+ };
445
+ }
446
+
192
447
  const placeholder = placeholderForConfigKey(args.configKey);
193
448
  if (args.headerTemplate && !args.headerTemplate.includes(placeholder)) {
194
449
  return {
@@ -221,6 +476,8 @@ export const registerCredentialBindingsTool = (server: McpServer) => {
221
476
  scope,
222
477
  scopeId,
223
478
  active: true,
479
+ authKind: args.authKind ?? "config",
480
+ oauthProvider: args.oauthProvider,
224
481
  });
225
482
 
226
483
  upsertCredentialBinding({
@@ -232,8 +489,10 @@ export const registerCredentialBindingsTool = (server: McpServer) => {
232
489
  scope: nextBinding.scope,
233
490
  scopeId: nextBinding.scopeId ?? null,
234
491
  active: true,
492
+ authKind: nextBinding.authKind,
493
+ oauthProvider: nextBinding.oauthProvider ?? null,
235
494
  });
236
- const nextBindings = listRelationalCredentialBindings({ includeInactive: true });
495
+ const nextBindings = currentBindings();
237
496
 
238
497
  return {
239
498
  content: [{ type: "text", text: `Credential binding ${args.configKey} saved.` }],
@@ -0,0 +1,145 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import * as z from "zod";
3
+ import { deletePage, getAgentById, getPage, getPageBySlug } from "@/be/db";
4
+ import { can } from "@/rbac";
5
+ import { createToolRegistrar } from "@/tools/utils";
6
+
7
+ export const registerDeletePageTool = (server: McpServer) => {
8
+ createToolRegistrar(server)(
9
+ "delete-page",
10
+ {
11
+ title: "Delete Page",
12
+ description:
13
+ "Permanently delete one page by pageId, or by slug in the caller's page namespace. Only the lead or the page owner can delete a page.",
14
+ annotations: { destructiveHint: true },
15
+ inputSchema: z.object({
16
+ pageId: z.string().min(1).optional().describe("Page ID to delete."),
17
+ slug: z
18
+ .string()
19
+ .min(1)
20
+ .optional()
21
+ .describe(
22
+ "Page slug to delete from the caller's own (agentId, slug) namespace. Alternative to pageId.",
23
+ ),
24
+ }),
25
+ outputSchema: z.object({
26
+ yourAgentId: z.string().optional(),
27
+ success: z.boolean(),
28
+ message: z.string(),
29
+ deletedPage: z
30
+ .object({
31
+ id: z.string(),
32
+ slug: z.string(),
33
+ title: z.string(),
34
+ })
35
+ .optional(),
36
+ }),
37
+ },
38
+ async ({ pageId, slug }, requestInfo, _meta) => {
39
+ if (!requestInfo.agentId) {
40
+ return {
41
+ content: [{ type: "text", text: 'Agent ID not found. Set the "X-Agent-ID" header.' }],
42
+ structuredContent: {
43
+ success: false,
44
+ message: 'Agent ID not found. Set the "X-Agent-ID" header.',
45
+ },
46
+ };
47
+ }
48
+
49
+ if (!pageId && !slug) {
50
+ return {
51
+ content: [{ type: "text", text: "Either pageId or slug must be provided." }],
52
+ structuredContent: {
53
+ yourAgentId: requestInfo.agentId,
54
+ success: false,
55
+ message: "Either pageId or slug must be provided.",
56
+ },
57
+ };
58
+ }
59
+
60
+ const caller = getAgentById(requestInfo.agentId);
61
+ if (!caller) {
62
+ return {
63
+ content: [{ type: "text", text: "Agent not found." }],
64
+ structuredContent: {
65
+ yourAgentId: requestInfo.agentId,
66
+ success: false,
67
+ message: "Agent not found.",
68
+ },
69
+ };
70
+ }
71
+
72
+ const page = pageId
73
+ ? getPage(pageId)
74
+ : slug
75
+ ? getPageBySlug(requestInfo.agentId, slug)
76
+ : null;
77
+ if (!page) {
78
+ return {
79
+ content: [{ type: "text", text: "Page not found." }],
80
+ structuredContent: {
81
+ yourAgentId: requestInfo.agentId,
82
+ success: false,
83
+ message: "Page not found.",
84
+ },
85
+ };
86
+ }
87
+
88
+ const decision = can({
89
+ principal: { kind: "agent", agentId: caller.id, isLead: caller.isLead },
90
+ verb: "page.delete.any",
91
+ resource: { kind: "owned", ownerAgentId: page.agentId },
92
+ source: "mcp",
93
+ });
94
+ if (!decision.allow) {
95
+ return {
96
+ content: [{ type: "text", text: "Only the lead or page owner can delete pages." }],
97
+ structuredContent: {
98
+ yourAgentId: requestInfo.agentId,
99
+ success: false,
100
+ message: "Only the lead or page owner can delete pages.",
101
+ },
102
+ };
103
+ }
104
+
105
+ try {
106
+ const deleted = deletePage(page.id);
107
+ if (!deleted) {
108
+ return {
109
+ content: [{ type: "text", text: "Failed to delete page." }],
110
+ structuredContent: {
111
+ yourAgentId: requestInfo.agentId,
112
+ success: false,
113
+ message: "Failed to delete page.",
114
+ },
115
+ };
116
+ }
117
+
118
+ const deletedPage = {
119
+ id: page.id,
120
+ slug: page.slug,
121
+ title: page.title,
122
+ };
123
+ return {
124
+ content: [{ type: "text", text: `Deleted page "${page.title}".` }],
125
+ structuredContent: {
126
+ yourAgentId: requestInfo.agentId,
127
+ success: true,
128
+ message: `Deleted page "${page.title}".`,
129
+ deletedPage,
130
+ },
131
+ };
132
+ } catch (error) {
133
+ const message = error instanceof Error ? error.message : "Unknown error";
134
+ return {
135
+ content: [{ type: "text", text: `Failed to delete page: ${message}` }],
136
+ structuredContent: {
137
+ yourAgentId: requestInfo.agentId,
138
+ success: false,
139
+ message: `Failed to delete page: ${message}`,
140
+ },
141
+ };
142
+ }
143
+ },
144
+ );
145
+ };
@@ -64,18 +64,21 @@ export const registerMcpServerDeleteTool = (server: McpServer) => {
64
64
  };
65
65
  }
66
66
 
67
- const deleted = deleteMcpServer(args.id);
67
+ const result = deleteMcpServer(args.id);
68
+ const message = result.deleted
69
+ ? `Deleted MCP server "${existing.name}" and ${result.deletedScriptConnectionCount} script connection(s).`
70
+ : "Delete failed.";
68
71
  return {
69
72
  content: [
70
73
  {
71
74
  type: "text",
72
- text: deleted ? `Deleted MCP server "${existing.name}".` : "Delete failed.",
75
+ text: message,
73
76
  },
74
77
  ],
75
78
  structuredContent: {
76
79
  yourAgentId: requestInfo.agentId,
77
- success: deleted,
78
- message: deleted ? `Deleted MCP server "${existing.name}".` : "Delete failed.",
80
+ success: result.deleted,
81
+ message,
79
82
  },
80
83
  };
81
84
  },
@@ -1,5 +1,6 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import * as z from "zod";
3
+ import { resolveTaskAuditUserId } from "@/be/audit-user";
3
4
  import { createApprovalRequest, getAgentCurrentTask } from "@/be/db";
4
5
  import { createToolRegistrar } from "@/tools/utils";
5
6
  import { getAppUrl } from "@/utils/constants";
@@ -93,6 +94,7 @@ export const registerRequestHumanInputTool = (server: McpServer) => {
93
94
  approvers: { policy: "any" },
94
95
  sourceTaskId: sourceTaskId ?? undefined,
95
96
  timeoutSeconds,
97
+ createdBy: resolveTaskAuditUserId(sourceTaskId, requestInfo.agentId) ?? undefined,
96
98
  });
97
99
 
98
100
  const appUrl = getAppUrl();