@desplega.ai/agent-swarm 1.123.0 → 1.124.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/{actions-e4c9f0xy.js → actions-z3d0fk7z.js} +6 -6
- package/dist/{app-vmvmj2v3.js → app-q2rc9tdr.js} +4 -4
- package/dist/{assistant-yn7a792a.js → assistant-3stjq7fz.js} +9 -9
- package/dist/{boot-reembed-xg1r86h2.js → boot-reembed-5867whnb.js} +3 -3
- package/dist/{boot-reembed-chxty50a.js → boot-reembed-v6r1df32.js} +4 -4
- package/dist/{boot-scrub-logs-exwddvp5.js → boot-scrub-logs-8b8jegk3.js} +2 -2
- package/dist/{cli-1592nfd6.js → cli-0925phzv.js} +1 -1
- package/dist/{cli-hbbxmhec.js → cli-2yr6edr7.js} +4 -4
- package/dist/{cli-8cq4z4x7.js → cli-3z4q09tn.js} +182 -19
- package/dist/{cli-vz9drmce.js → cli-5jn6wnd9.js} +17 -8
- package/dist/{cli-5arfywtm.js → cli-5ncdb7ff.js} +19 -9
- package/dist/{cli-3tv9z4wx.js → cli-92bqgbgy.js} +1 -1
- package/dist/{cli-fjf04hpa.js → cli-dq2ssyf0.js} +48 -56
- package/dist/{cli-mngsj9fx.js → cli-gkvf8d83.js} +1 -1
- package/dist/{cli-mepk1j0t.js → cli-gzjsamwc.js} +2 -2
- package/dist/{cli-c9m59f83.js → cli-h89g0qrd.js} +1 -1
- package/dist/{cli-n8mf7x4b.js → cli-jp4mdj9d.js} +2 -2
- package/dist/{cli-822d4bjk.js → cli-kk58tg33.js} +7 -7
- package/dist/{cli-9hj1adgp.js → cli-mcez7z4p.js} +1 -1
- package/dist/{cli-byv3720g.js → cli-mmk5vagg.js} +2 -2
- package/dist/{cli-zdp11t8m.js → cli-p0ec4vv6.js} +1 -1
- package/dist/{cli-h2say6xk.js → cli-p0zgv8pj.js} +3 -1
- package/dist/{cli-8ef5ky75.js → cli-perb20ma.js} +1 -1
- package/dist/{cli-zkj6f2sk.js → cli-qf7acvyr.js} +90 -4
- package/dist/{cli-kmm1rwmp.js → cli-qwsagsmg.js} +1 -1
- package/dist/{cli-we91ywvy.js → cli-sqv6c9b8.js} +6 -6
- package/dist/{cli-np0yhmmk.js → cli-vm20sh8d.js} +3 -3
- package/dist/{cli-p11rf59d.js → cli-wn3m6q54.js} +3 -3
- package/dist/{cli-mkpf4ndf.js → cli-x268say8.js} +2976 -5621
- package/dist/{cli-ngbsk4yx.js → cli-y43n987v.js} +1 -1
- package/dist/{cli-4509mwm5.js → cli-ykthjw29.js} +8 -8
- package/dist/{cli-9eka6jwv.js → cli-zxb72ctk.js} +2 -2
- package/dist/cli.js +12 -10
- package/dist/{commands-sjk3zfyr.js → commands-sda76yn8.js} +2 -2
- package/dist/{db-2je5agjb.js → db-vn9e4rqj.js} +6 -2
- package/dist/{e2b-2s4psnbf.js → e2b-q345y8s4.js} +0 -4
- package/dist/{handlers-yzv369hf.js → handlers-xfja18ac.js} +9 -9
- package/dist/{hook-rfsj2r8x.js → hook-2medrp47.js} +1 -40
- package/dist/{http-v9rr144m.js → http-937pyjfc.js} +99 -44
- package/dist/{index-nr2199m6.js → index-2bqd2q9v.js} +11 -11
- package/dist/{index-3xtk777g.js → index-gw4en7jg.js} +9 -9
- package/dist/{index-c3tan44f.js → index-knqczmnm.js} +10 -10
- package/dist/{index-smjdc3h9.js → index-vah60vxp.js} +8 -8
- package/dist/{keepalive-5ds53bjd.js → keepalive-20rd6hxh.js} +5 -5
- package/dist/{lead-5xhez25b.js → lead-7w2k3a49.js} +21 -21
- package/dist/{maintenance-b43d24q6.js → maintenance-p3x918yw.js} +4 -4
- package/dist/{oauth-refresh-sweep-qe286vn2.js → oauth-refresh-sweep-shm0nzm4.js} +4 -4
- package/dist/{onboard-c4r3ds3z.js → onboard-py9dnf54.js} +6 -2
- package/dist/{otel-impl-nv3rjw61.js → otel-impl-7yjkfg68.js} +1 -1
- package/dist/{pi-mono-adapter-2syx1x4d.js → pi-mono-adapter-z6h98fva.js} +4 -38
- package/dist/{pricing-refresh-5ct2wp4k.js → pricing-refresh-3snb6370.js} +4 -4
- package/dist/{rbac-roles-m502w0qv.js → rbac-roles-5e72bbpk.js} +3 -3
- package/dist/{rbac-roles-b82cxpxn.js → rbac-roles-gqxdp8st.js} +4 -4
- package/dist/{seed-pricing-jhjphbf0.js → seed-pricing-bg42d7h2.js} +3 -3
- package/dist/{setup-zbaspydq.js → setup-nzk3kbbq.js} +2 -2
- package/dist/{worker-snvhpax5.js → worker-0p1jdzkm.js} +21 -21
- package/openapi.json +146 -1
- package/package.json +3 -1
- package/src/be/db.ts +81 -4
- package/src/be/models-catalog.ts +156 -0
- package/src/be/modelsdev-cache.ts +14 -1
- package/src/be/pricing-refresh.ts +2 -0
- package/src/be/scripts/typecheck.ts +17 -4
- package/src/be/seed-scripts/catalog/task-context-gathering.ts +1 -1
- package/src/commands/onboard/env-generator.ts +12 -0
- package/src/e2b/env.ts +0 -4
- package/src/hooks/hook.ts +0 -67
- package/src/http/all-routes.ts +1 -0
- package/src/http/index.ts +2 -0
- package/src/http/models-catalog.ts +65 -0
- package/src/http/workflows.ts +16 -4
- package/src/prompts/base-prompt.ts +8 -7
- package/src/prompts/session-templates.ts +106 -12
- package/src/providers/pi-mono-adapter.ts +12 -2
- package/src/providers/pi-mono-extension.ts +0 -64
- package/src/providers/pricing-sources.md +16 -1
- package/src/scripts-runtime/eval-harness.ts +18 -1
- package/src/scripts-runtime/swarm-sdk.ts +10 -0
- package/src/scripts-runtime/types/stdlib.d.ts +13 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +13 -0
- package/src/server-user.ts +4 -7
- package/src/slack/blocks.ts +21 -8
- package/src/slack/responses.ts +27 -51
- package/src/slack/watcher.ts +22 -5
- package/src/telemetry.ts +169 -3
- package/src/tests/base-prompt.test.ts +79 -0
- package/src/tests/mcp-tools-user.test.ts +62 -16
- package/src/tests/mcp-tools.test.ts +6 -3
- package/src/tests/memory-edit.test.ts +21 -4
- package/src/tests/model-groups-live-catalog.test.ts +65 -0
- package/src/tests/models-catalog.test.ts +119 -0
- package/src/tests/oauth-access-token-tool.test.ts +71 -1
- package/src/tests/pricing-refresh.test.ts +25 -0
- package/src/tests/prompt-template-session.test.ts +142 -5
- package/src/tests/rbac-charact-skills.test.ts +81 -14
- package/src/tests/script-apis-mcp.test.ts +56 -8
- package/src/tests/scripts-mcp-e2e.test.ts +157 -8
- package/src/tests/scripts-typecheck.test.ts +17 -0
- package/src/tests/sdk-allowlist.test.ts +34 -1
- package/src/tests/seed-scripts.test.ts +43 -0
- package/src/tests/slack-blocks.test.ts +2 -1
- package/src/tests/slack-inline-output.test.ts +24 -30
- package/src/tests/slack-watcher.test.ts +42 -0
- package/src/tests/steer-task-tool.test.ts +37 -8
- package/src/tests/steering-transport.test.ts +18 -3
- package/src/tests/swarm-tool-result-gate.test.ts +399 -0
- package/src/tests/task-tools-ctx.test.ts +49 -17
- package/src/tests/task-tools-ownership.test.ts +41 -13
- package/src/tests/telemetry-init.test.ts +376 -2
- package/src/tests/tool-output-agent-id.test.ts +14 -2
- package/src/tests/workflow-http-v2.test.ts +116 -0
- package/src/tools/accept-steer.ts +45 -53
- package/src/tools/cancel-task.ts +31 -41
- package/src/tools/context-diff.ts +19 -63
- package/src/tools/context-history.ts +22 -51
- package/src/tools/create-channel.ts +24 -39
- package/src/tools/create-metric.ts +19 -82
- package/src/tools/create-page.ts +20 -81
- package/src/tools/credential-bindings/tool.ts +125 -246
- package/src/tools/db-query.ts +13 -24
- package/src/tools/delete-channel.ts +25 -71
- package/src/tools/delete-page.ts +27 -75
- package/src/tools/get-metrics.ts +29 -22
- package/src/tools/get-swarm.ts +44 -15
- package/src/tools/get-task-details.ts +150 -32
- package/src/tools/get-tasks.ts +63 -29
- package/src/tools/inject-learning.ts +8 -38
- package/src/tools/join-swarm.ts +45 -47
- package/src/tools/kv/kv-delete.ts +13 -37
- package/src/tools/kv/kv-get.ts +38 -29
- package/src/tools/kv/kv-incr.ts +28 -56
- package/src/tools/kv/kv-list.ts +40 -31
- package/src/tools/kv/kv-set.ts +34 -76
- package/src/tools/list-channels.ts +24 -17
- package/src/tools/list-services.ts +37 -45
- package/src/tools/manage-user.ts +53 -66
- package/src/tools/mcp-servers/mcp-server-create.ts +19 -55
- package/src/tools/mcp-servers/mcp-server-delete.ts +9 -39
- package/src/tools/mcp-servers/mcp-server-get.ts +9 -35
- package/src/tools/mcp-servers/mcp-server-install.ts +13 -60
- package/src/tools/mcp-servers/mcp-server-list.ts +28 -26
- package/src/tools/mcp-servers/mcp-server-uninstall.ts +10 -36
- package/src/tools/mcp-servers/mcp-server-update.ts +13 -57
- package/src/tools/memory-delete.ts +16 -46
- package/src/tools/memory-edit.ts +48 -49
- package/src/tools/memory-get.ts +40 -36
- package/src/tools/memory-rate.ts +14 -44
- package/src/tools/memory-search.ts +46 -52
- package/src/tools/my-agent-info.ts +39 -33
- package/src/tools/oauth-access-token.ts +9 -24
- package/src/tools/poll-task.ts +50 -77
- package/src/tools/post-message.ts +24 -38
- package/src/tools/prompt-templates/delete.ts +16 -48
- package/src/tools/prompt-templates/get.ts +45 -42
- package/src/tools/prompt-templates/list.ts +29 -44
- package/src/tools/prompt-templates/preview.ts +14 -40
- package/src/tools/prompt-templates/set.ts +30 -52
- package/src/tools/read-messages.ts +45 -54
- package/src/tools/register-agentmail-inbox.ts +28 -84
- package/src/tools/register-kapso-number.ts +20 -50
- package/src/tools/register-service.ts +36 -44
- package/src/tools/repos/get-repos.ts +29 -21
- package/src/tools/repos/update-repo.ts +30 -27
- package/src/tools/request-human-input.ts +10 -33
- package/src/tools/resolve-user.ts +59 -16
- package/src/tools/schedules/create-schedule.ts +58 -173
- package/src/tools/schedules/delete-schedule.ts +16 -56
- package/src/tools/schedules/list-schedules.ts +41 -67
- package/src/tools/schedules/patch-schedule.ts +57 -153
- package/src/tools/schedules/run-schedule-now.ts +18 -59
- package/src/tools/schedules/update-schedule.ts +57 -153
- package/src/tools/script-apis.ts +46 -28
- package/src/tools/script-common.ts +129 -34
- package/src/tools/script-connections/tool.ts +59 -154
- package/src/tools/script-query-types.ts +43 -3
- package/src/tools/script-run.ts +41 -2
- package/src/tools/script-runs.ts +69 -1
- package/src/tools/script-search.ts +24 -1
- package/src/tools/script-upsert.ts +18 -3
- package/src/tools/send-task.ts +32 -81
- package/src/tools/skills/skill-create.ts +14 -39
- package/src/tools/skills/skill-delete.ts +14 -42
- package/src/tools/skills/skill-get-file.ts +10 -43
- package/src/tools/skills/skill-get.ts +9 -32
- package/src/tools/skills/skill-install-remote.ts +16 -52
- package/src/tools/skills/skill-install.ts +14 -52
- package/src/tools/skills/skill-list.ts +24 -26
- package/src/tools/skills/skill-publish.ts +18 -54
- package/src/tools/skills/skill-search.ts +21 -18
- package/src/tools/skills/skill-sync-remote.ts +12 -24
- package/src/tools/skills/skill-uninstall.ts +10 -26
- package/src/tools/skills/skill-update.ts +24 -81
- package/src/tools/slack-delete.ts +8 -32
- package/src/tools/slack-download-file.ts +17 -57
- package/src/tools/slack-list-channels.ts +16 -43
- package/src/tools/slack-post.ts +11 -40
- package/src/tools/slack-read.ts +33 -103
- package/src/tools/slack-reply.ts +16 -53
- package/src/tools/slack-start-thread.ts +14 -55
- package/src/tools/slack-update.ts +8 -35
- package/src/tools/slack-upload-file.ts +22 -122
- package/src/tools/steer-task.ts +22 -35
- package/src/tools/store-progress.ts +11 -26
- package/src/tools/swarm-config/delete-config.ts +19 -57
- package/src/tools/swarm-config/get-config.ts +36 -44
- package/src/tools/swarm-config/list-config.ts +36 -44
- package/src/tools/swarm-config/set-config.ts +35 -73
- package/src/tools/swarm-x.ts +20 -26
- package/src/tools/task-action.ts +26 -46
- package/src/tools/task-tool-ctx.ts +5 -13
- package/src/tools/tracker/tracker-link-task.ts +8 -22
- package/src/tools/tracker/tracker-map-agent.ts +8 -22
- package/src/tools/tracker/tracker-status.ts +27 -19
- package/src/tools/tracker/tracker-sync-status.ts +8 -18
- package/src/tools/tracker/tracker-unlink.ts +4 -16
- package/src/tools/unregister-service.ts +19 -59
- package/src/tools/update-profile.ts +60 -100
- package/src/tools/update-service-status.ts +42 -67
- package/src/tools/utils.ts +189 -8
- package/src/tools/whatsapp-message.ts +19 -32
- package/src/tools/workflows/cancel-workflow-run.ts +4 -16
- package/src/tools/workflows/create-workflow.ts +9 -38
- package/src/tools/workflows/delete-workflow.ts +5 -17
- package/src/tools/workflows/get-workflow-run.ts +41 -31
- package/src/tools/workflows/get-workflow.ts +10 -21
- package/src/tools/workflows/list-workflow-runs.ts +107 -34
- package/src/tools/workflows/list-workflows.ts +5 -17
- package/src/tools/workflows/patch-workflow-node.ts +11 -39
- package/src/tools/workflows/patch-workflow.ts +11 -36
- package/src/tools/workflows/retry-workflow-run.ts +4 -16
- package/src/tools/workflows/trigger-workflow.ts +22 -58
- package/src/tools/workflows/update-workflow.ts +10 -42
- package/templates/skills/swarm-scripts/SKILL.md +1 -1
package/src/tools/utils.ts
CHANGED
|
@@ -12,9 +12,10 @@ import type {
|
|
|
12
12
|
ServerRequest,
|
|
13
13
|
ToolAnnotations,
|
|
14
14
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
15
|
+
import * as z from "zod";
|
|
15
16
|
import { withSpan } from "../otel";
|
|
16
17
|
import type { PermissionVerb } from "../rbac/permissions";
|
|
17
|
-
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
18
|
+
import { scrubObject, scrubSecrets } from "../utils/secret-scrubber";
|
|
18
19
|
|
|
19
20
|
type Meta = RequestHandlerExtra<ServerRequest, ServerNotification>;
|
|
20
21
|
|
|
@@ -91,6 +92,183 @@ function toolResultAttributes(result: CallToolResult) {
|
|
|
91
92
|
};
|
|
92
93
|
}
|
|
93
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Canonical result every swarm MCP tool returns. The registrar — not the tool —
|
|
97
|
+
* composes the wire-level CallToolResult from it, so the text channel and
|
|
98
|
+
* structuredContent can never diverge (see runbooks/mcp-tool-results.md for the
|
|
99
|
+
* per-harness evidence behind this contract).
|
|
100
|
+
*/
|
|
101
|
+
export type SwarmToolData = Record<string, unknown>;
|
|
102
|
+
|
|
103
|
+
export type SwarmToolResult<TData extends SwarmToolData = SwarmToolData> = {
|
|
104
|
+
/** Truthful outcome. Becomes `isError = !ok` and `structuredContent.success`. */
|
|
105
|
+
ok: boolean;
|
|
106
|
+
/** One-line summary. Required, non-empty — the first thing every harness shows the model. */
|
|
107
|
+
message: string;
|
|
108
|
+
/** Model-needed payload rendering (tables, lists, error detail). Appended to the text channel. */
|
|
109
|
+
details?: string;
|
|
110
|
+
/** Structured payload, spread into structuredContent alongside the envelope keys. */
|
|
111
|
+
data?: TData;
|
|
112
|
+
/** Single-sentence conditional steer, appended to BOTH channels. */
|
|
113
|
+
nudge?: string;
|
|
114
|
+
/**
|
|
115
|
+
* Skip the finalize pipeline's secret scrubbing for this result. ONLY for
|
|
116
|
+
* deliberate credential-reveal branches (oauth-access-token, script-apis
|
|
117
|
+
* create/rotate, get-config includeSecrets) whose entire purpose is handing
|
|
118
|
+
* the agent a secret — the central scrubber would otherwise redact the
|
|
119
|
+
* reveal. Everything else stays scrubbed.
|
|
120
|
+
*/
|
|
121
|
+
allowSecretEgress?: boolean;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const toolOk = <TData extends SwarmToolData = SwarmToolData>(
|
|
125
|
+
message: string,
|
|
126
|
+
extras: Omit<SwarmToolResult<TData>, "ok" | "message"> = {},
|
|
127
|
+
): SwarmToolResult<TData> => ({ ok: true, message, ...extras });
|
|
128
|
+
|
|
129
|
+
export const toolErr = <TData extends SwarmToolData = SwarmToolData>(
|
|
130
|
+
message: string,
|
|
131
|
+
extras: Omit<SwarmToolResult<TData>, "ok" | "message"> = {},
|
|
132
|
+
): SwarmToolResult<TData> => ({ ok: false, message, ...extras });
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Envelope keys the registrar writes into structuredContent for every tool.
|
|
136
|
+
* Output schemas must include these and must be LOOSE (`z.looseObject`):
|
|
137
|
+
* plain `z.object` emits `additionalProperties: false`, which makes
|
|
138
|
+
* client-side validators (opencode's official-SDK client) reject the spread
|
|
139
|
+
* `data` keys after the write already landed.
|
|
140
|
+
*/
|
|
141
|
+
export const swarmToolEnvelopeShape = {
|
|
142
|
+
success: z.boolean(),
|
|
143
|
+
message: z.string(),
|
|
144
|
+
details: z.string().optional(),
|
|
145
|
+
nudge: z.string().optional(),
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/** Build a permissive output schema: envelope + optional tool-specific data shape. */
|
|
149
|
+
export const swarmToolOutputSchema = <S extends z.ZodRawShape>(dataShape?: S) =>
|
|
150
|
+
z.looseObject({ ...swarmToolEnvelopeShape, ...(dataShape ?? ({} as S)) });
|
|
151
|
+
|
|
152
|
+
export const SCRIPT_AUTHORING_NUDGE =
|
|
153
|
+
"Scripts must `export default async function (args, ctx)` — args FIRST, ctx second; run script-query-types (no name) for the full ctx/SDK type surface, and see the `swarm-scripts` skill for authoring patterns.";
|
|
154
|
+
|
|
155
|
+
// Only steer on failures plausibly caused by the script itself (typecheck or
|
|
156
|
+
// runtime) — on lookup/transport/authorization errors the authoring advice
|
|
157
|
+
// distracts from the reported problem.
|
|
158
|
+
const scriptAuthoringNudge = (r: SwarmToolResult): string | undefined =>
|
|
159
|
+
!r.ok && /Typecheck failed:|Script run .*failed/.test(r.message)
|
|
160
|
+
? SCRIPT_AUTHORING_NUDGE
|
|
161
|
+
: undefined;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Central conditional nudges, keyed by tool name. Applied by the finalize
|
|
165
|
+
* pipeline when the tool did not set an explicit nudge. Keep entries to a
|
|
166
|
+
* single sentence; derive only from already-scrubbed result fields.
|
|
167
|
+
*/
|
|
168
|
+
export const NUDGES: Record<string, (result: SwarmToolResult) => string | undefined> = {
|
|
169
|
+
"script-run": scriptAuthoringNudge,
|
|
170
|
+
"script-upsert": scriptAuthoringNudge,
|
|
171
|
+
"launch-script-run": scriptAuthoringNudge,
|
|
172
|
+
"get-script-run": scriptAuthoringNudge,
|
|
173
|
+
"script-search": (r) => {
|
|
174
|
+
if (!r.ok) return undefined;
|
|
175
|
+
// proxyScriptsApi wraps the parsed HTTP body as data = { status, data },
|
|
176
|
+
// so the results array lives one level down.
|
|
177
|
+
const body = (r.data as { data?: { results?: unknown[] } } | undefined)?.data;
|
|
178
|
+
const results = body?.results;
|
|
179
|
+
return Array.isArray(results) && results.length === 0
|
|
180
|
+
? "No scripts matched — the catalog ships seeded example scripts; re-run script-search with an empty query to list them."
|
|
181
|
+
: undefined;
|
|
182
|
+
},
|
|
183
|
+
"memory-search": (r) => {
|
|
184
|
+
if (!r.ok) return undefined;
|
|
185
|
+
const results = (r.data as { results?: Array<{ rateHint?: unknown }> } | undefined)?.results;
|
|
186
|
+
return Array.isArray(results) && results.some((entry) => Boolean(entry?.rateHint))
|
|
187
|
+
? "Rate memories that help or mislead you with memory_rate."
|
|
188
|
+
: undefined;
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// Cap for any rendered details string — Codex's ~10KB middle-out truncation is
|
|
193
|
+
// the tightest harness budget. Structured data remains intact; only the
|
|
194
|
+
// human/model-facing rendering is bounded.
|
|
195
|
+
const DETAILS_CAP = 8_000;
|
|
196
|
+
|
|
197
|
+
function truncateForText(text: string): string {
|
|
198
|
+
if (text.length <= DETAILS_CAP) return text;
|
|
199
|
+
return `${text.slice(0, DETAILS_CAP)}\n… [truncated ${text.length - DETAILS_CAP} chars]`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
type FinalizeContext = { toolName: string };
|
|
203
|
+
type FinalizeMiddleware = (result: SwarmToolResult, ctx: FinalizeContext) => SwarmToolResult;
|
|
204
|
+
|
|
205
|
+
const scrubMiddleware: FinalizeMiddleware = (result) =>
|
|
206
|
+
result.allowSecretEgress ? result : scrubObject(result);
|
|
207
|
+
|
|
208
|
+
const nudgeMiddleware: FinalizeMiddleware = (result, ctx) => {
|
|
209
|
+
if (result.nudge) return result;
|
|
210
|
+
const nudge = NUDGES[ctx.toolName]?.(result);
|
|
211
|
+
return nudge ? { ...result, nudge } : result;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// Ordered: scrub first so nudges (and any future middleware) only ever see
|
|
215
|
+
// scrubbed data. Future ctx-control middleware (response pruning, auto-KV
|
|
216
|
+
// overflow) slots in between nudge and the final transform.
|
|
217
|
+
const FINALIZE_PIPELINE: FinalizeMiddleware[] = [scrubMiddleware, nudgeMiddleware];
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Transform a SwarmToolResult into the wire CallToolResult. Both channels are
|
|
221
|
+
* composed identically and are independently self-sufficient: Codex reads only
|
|
222
|
+
* structuredContent, pi/opencode/claude-managed read only content.text.
|
|
223
|
+
* structuredContent is ALWAYS present (opencode's SDK client throws when a
|
|
224
|
+
* declared outputSchema has no structuredContent).
|
|
225
|
+
*/
|
|
226
|
+
export function finalizeSwarmToolResult(toolName: string, result: SwarmToolResult): CallToolResult {
|
|
227
|
+
let r = result;
|
|
228
|
+
if (!r.message?.trim()) {
|
|
229
|
+
console.warn(`[mcp] tool ${toolName} returned an empty message — every tool must summarize`);
|
|
230
|
+
r = {
|
|
231
|
+
...r,
|
|
232
|
+
message: r.ok
|
|
233
|
+
? "Tool call succeeded (no message provided)."
|
|
234
|
+
: "Tool call failed (no message provided).",
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
for (const middleware of FINALIZE_PIPELINE) r = middleware(r, { toolName });
|
|
238
|
+
|
|
239
|
+
// Normalize and cap explicit details only after the scrub middleware has
|
|
240
|
+
// removed secrets. The same bounded value feeds both wire channels.
|
|
241
|
+
// Whitespace-only details count as absent so data fallback remains visible.
|
|
242
|
+
const explicitDetails = r.details?.trim() ? truncateForText(r.details.trim()) : undefined;
|
|
243
|
+
|
|
244
|
+
// Text-channel completeness guarantee: when a tool sets data but no details,
|
|
245
|
+
// render the data as JSON into the text channel. Most harnesses only ever
|
|
246
|
+
// show the model content.text — without this fallback, a data-only payload
|
|
247
|
+
// would be invisible there. Not copied into structuredContent.details (the
|
|
248
|
+
// structured channel already carries data verbatim).
|
|
249
|
+
const dataFallback =
|
|
250
|
+
!explicitDetails && r.data && Object.keys(r.data).length > 0
|
|
251
|
+
? truncateForText(JSON.stringify(r.data, null, 2))
|
|
252
|
+
: undefined;
|
|
253
|
+
|
|
254
|
+
const text = [r.message, explicitDetails ?? dataFallback, r.nudge]
|
|
255
|
+
.filter((part): part is string => Boolean(part?.trim()))
|
|
256
|
+
.join("\n\n");
|
|
257
|
+
const structuredContent: Record<string, unknown> = {
|
|
258
|
+
...(r.data ?? {}),
|
|
259
|
+
success: r.ok,
|
|
260
|
+
message: r.message,
|
|
261
|
+
};
|
|
262
|
+
if (explicitDetails) structuredContent.details = explicitDetails;
|
|
263
|
+
if (r.nudge) structuredContent.nudge = r.nudge;
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
content: [{ type: "text", text }],
|
|
267
|
+
structuredContent,
|
|
268
|
+
isError: !r.ok,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
94
272
|
// Infer the input type from the schema
|
|
95
273
|
type InferInput<Args extends undefined | ZodRawShapeCompat | AnySchema> =
|
|
96
274
|
Args extends ZodRawShapeCompat
|
|
@@ -99,15 +277,16 @@ type InferInput<Args extends undefined | ZodRawShapeCompat | AnySchema> =
|
|
|
99
277
|
? SchemaOutput<Args>
|
|
100
278
|
: undefined;
|
|
101
279
|
|
|
102
|
-
// Callback type with requestInfo injected as second parameter
|
|
280
|
+
// Callback type with requestInfo injected as second parameter.
|
|
281
|
+
// Tools return SwarmToolResult (ours) — never a raw MCP CallToolResult.
|
|
103
282
|
type ToolCallbackWithInfo<Args extends undefined | ZodRawShapeCompat | AnySchema = undefined> =
|
|
104
283
|
Args extends undefined
|
|
105
|
-
? (requestInfo: RequestInfo, meta: Meta) =>
|
|
284
|
+
? (requestInfo: RequestInfo, meta: Meta) => SwarmToolResult | Promise<SwarmToolResult>
|
|
106
285
|
: (
|
|
107
286
|
args: InferInput<Args>,
|
|
108
287
|
requestInfo: RequestInfo,
|
|
109
288
|
meta: Meta,
|
|
110
|
-
) =>
|
|
289
|
+
) => SwarmToolResult | Promise<SwarmToolResult>;
|
|
111
290
|
|
|
112
291
|
type ToolConfig<
|
|
113
292
|
InputArgs extends undefined | ZodRawShapeCompat | AnySchema,
|
|
@@ -155,12 +334,13 @@ export const createToolRegistrar = (server: McpServer) => {
|
|
|
155
334
|
return withSpan(
|
|
156
335
|
"mcp.tool",
|
|
157
336
|
async (span) => {
|
|
158
|
-
const
|
|
337
|
+
const outcome = await (
|
|
159
338
|
cb as (
|
|
160
339
|
requestInfo: RequestInfo,
|
|
161
340
|
meta: Meta,
|
|
162
|
-
) =>
|
|
341
|
+
) => SwarmToolResult | Promise<SwarmToolResult>
|
|
163
342
|
)(requestInfo, meta);
|
|
343
|
+
const result = finalizeSwarmToolResult(name, outcome);
|
|
164
344
|
span.setAttributes(toolResultAttributes(result));
|
|
165
345
|
return result;
|
|
166
346
|
},
|
|
@@ -176,13 +356,14 @@ export const createToolRegistrar = (server: McpServer) => {
|
|
|
176
356
|
// trace tree. Cardinality is bounded — tool names are a fixed enum.
|
|
177
357
|
`mcp.tool ${name}`,
|
|
178
358
|
async (span) => {
|
|
179
|
-
const
|
|
359
|
+
const outcome = await (
|
|
180
360
|
cb as (
|
|
181
361
|
args: InferInput<InputArgs>,
|
|
182
362
|
requestInfo: RequestInfo,
|
|
183
363
|
meta: Meta,
|
|
184
|
-
) =>
|
|
364
|
+
) => SwarmToolResult | Promise<SwarmToolResult>
|
|
185
365
|
)(args, requestInfo, meta);
|
|
366
|
+
const result = finalizeSwarmToolResult(name, outcome);
|
|
186
367
|
span.setAttributes(toolResultAttributes(result));
|
|
187
368
|
return result;
|
|
188
369
|
},
|
|
@@ -2,16 +2,20 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
2
2
|
import * as z from "zod";
|
|
3
3
|
import { sendKapsoText } from "@/integrations/kapso/client";
|
|
4
4
|
import { getKapsoConfig } from "@/integrations/kapso/config";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
createToolRegistrar,
|
|
7
|
+
type SwarmToolResult,
|
|
8
|
+
swarmToolOutputSchema,
|
|
9
|
+
toolErr,
|
|
10
|
+
toolOk,
|
|
11
|
+
} from "@/tools/utils";
|
|
6
12
|
|
|
7
13
|
/** Shared structured-error message for the 24h session-window case. */
|
|
8
14
|
const SESSION_WINDOW_HINT =
|
|
9
15
|
'Outside the 24h WhatsApp session window — free-form text is rejected. Use a pre-approved template message (see the `kapso-whatsapp` skill, "Send a template").';
|
|
10
16
|
|
|
11
|
-
const outputSchema =
|
|
17
|
+
const outputSchema = swarmToolOutputSchema({
|
|
12
18
|
yourAgentId: z.string().optional(),
|
|
13
|
-
success: z.boolean(),
|
|
14
|
-
message: z.string(),
|
|
15
19
|
messageId: z.string().optional(),
|
|
16
20
|
sessionWindowExpired: z.boolean().optional(),
|
|
17
21
|
});
|
|
@@ -79,15 +83,13 @@ async function sendAndFormat(
|
|
|
79
83
|
params: { phoneNumberId: string; to: string; body: string; previewUrl?: boolean },
|
|
80
84
|
agentId: string | undefined,
|
|
81
85
|
contextMessageId: string | undefined,
|
|
82
|
-
) {
|
|
86
|
+
): Promise<SwarmToolResult> {
|
|
83
87
|
try {
|
|
84
88
|
const config = getKapsoConfig();
|
|
85
89
|
if (!config.apiKey) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
structuredContent: { yourAgentId: agentId, success: false, message: msg },
|
|
90
|
-
};
|
|
90
|
+
return toolErr("KAPSO_API_KEY is not configured in swarm config.", {
|
|
91
|
+
data: { yourAgentId: agentId },
|
|
92
|
+
});
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
const result = await sendKapsoText({
|
|
@@ -102,34 +104,19 @@ async function sendAndFormat(
|
|
|
102
104
|
|
|
103
105
|
if (result.ok) {
|
|
104
106
|
const text = `Sent WhatsApp message to ${params.to} (wamid ${result.messageId ?? "unknown"})`;
|
|
105
|
-
return {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
yourAgentId: agentId,
|
|
109
|
-
success: true,
|
|
110
|
-
message: text,
|
|
111
|
-
messageId: result.messageId,
|
|
112
|
-
},
|
|
113
|
-
};
|
|
107
|
+
return toolOk(text, {
|
|
108
|
+
data: { yourAgentId: agentId, messageId: result.messageId },
|
|
109
|
+
});
|
|
114
110
|
}
|
|
115
111
|
|
|
116
112
|
const text = result.sessionWindowExpired
|
|
117
113
|
? `${SESSION_WINDOW_HINT} (Kapso: ${result.errorMessage})`
|
|
118
114
|
: `Kapso send failed: ${result.errorMessage}`;
|
|
119
|
-
return {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
yourAgentId: agentId,
|
|
123
|
-
success: false,
|
|
124
|
-
message: text,
|
|
125
|
-
sessionWindowExpired: result.sessionWindowExpired,
|
|
126
|
-
},
|
|
127
|
-
};
|
|
115
|
+
return toolErr(text, {
|
|
116
|
+
data: { yourAgentId: agentId, sessionWindowExpired: result.sessionWindowExpired },
|
|
117
|
+
});
|
|
128
118
|
} catch (err) {
|
|
129
119
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
130
|
-
return {
|
|
131
|
-
content: [{ type: "text" as const, text: `Error: ${errorMessage}` }],
|
|
132
|
-
structuredContent: { yourAgentId: agentId, success: false, message: errorMessage },
|
|
133
|
-
};
|
|
120
|
+
return toolErr(errorMessage, { data: { yourAgentId: agentId } });
|
|
134
121
|
}
|
|
135
122
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { createToolRegistrar } from "@/tools/utils";
|
|
3
|
+
import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
|
|
4
4
|
import { cancelWorkflowRun } from "@/workflows";
|
|
5
5
|
|
|
6
6
|
export const registerCancelWorkflowRunTool = (server: McpServer) => {
|
|
@@ -15,26 +15,14 @@ export const registerCancelWorkflowRunTool = (server: McpServer) => {
|
|
|
15
15
|
runId: z.string().uuid().describe("Workflow run ID to cancel"),
|
|
16
16
|
reason: z.string().optional().describe("Optional reason for cancellation"),
|
|
17
17
|
}),
|
|
18
|
-
outputSchema:
|
|
19
|
-
success: z.boolean(),
|
|
20
|
-
message: z.string(),
|
|
21
|
-
}),
|
|
18
|
+
outputSchema: swarmToolOutputSchema(),
|
|
22
19
|
},
|
|
23
20
|
async ({ runId, reason }) => {
|
|
24
21
|
try {
|
|
25
22
|
cancelWorkflowRun(runId, reason);
|
|
26
|
-
return {
|
|
27
|
-
content: [{ type: "text" as const, text: `Cancelled workflow run ${runId}.` }],
|
|
28
|
-
structuredContent: {
|
|
29
|
-
success: true,
|
|
30
|
-
message: `Cancelled workflow run ${runId}.`,
|
|
31
|
-
},
|
|
32
|
-
};
|
|
23
|
+
return toolOk(`Cancelled workflow run ${runId}.`);
|
|
33
24
|
} catch (err) {
|
|
34
|
-
return
|
|
35
|
-
content: [{ type: "text" as const, text: `Failed: ${err}` }],
|
|
36
|
-
structuredContent: { success: false, message: String(err) },
|
|
37
|
-
};
|
|
25
|
+
return toolErr(String(err));
|
|
38
26
|
}
|
|
39
27
|
},
|
|
40
28
|
);
|
|
@@ -3,7 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import { authorizeAssetKeyWrite } from "@/be/asset-key-auth";
|
|
4
4
|
import { resolveTaskAuditUserId } from "@/be/audit-user";
|
|
5
5
|
import { createWorkflow } from "@/be/db";
|
|
6
|
-
import { createToolRegistrar } from "@/tools/utils";
|
|
6
|
+
import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
|
|
7
7
|
import {
|
|
8
8
|
AssetKeySchema,
|
|
9
9
|
CooldownConfigSchema,
|
|
@@ -84,10 +84,8 @@ export const registerCreateWorkflowTool = (server: McpServer) => {
|
|
|
84
84
|
"Other JSON-Schema keywords are silently ignored.",
|
|
85
85
|
),
|
|
86
86
|
}),
|
|
87
|
-
outputSchema:
|
|
87
|
+
outputSchema: swarmToolOutputSchema({
|
|
88
88
|
yourAgentId: z.string().optional(),
|
|
89
|
-
success: z.boolean(),
|
|
90
|
-
message: z.string(),
|
|
91
89
|
workflow: z.unknown().optional(),
|
|
92
90
|
}),
|
|
93
91
|
},
|
|
@@ -107,27 +105,13 @@ export const registerCreateWorkflowTool = (server: McpServer) => {
|
|
|
107
105
|
requestInfo,
|
|
108
106
|
) => {
|
|
109
107
|
if (!requestInfo.agentId) {
|
|
110
|
-
return
|
|
111
|
-
content: [{ type: "text" as const, text: "Agent ID required." }],
|
|
112
|
-
structuredContent: { success: false, message: "Agent ID required." },
|
|
113
|
-
};
|
|
108
|
+
return toolErr("Agent ID required.");
|
|
114
109
|
}
|
|
115
110
|
try {
|
|
116
111
|
// Validate definition structure
|
|
117
112
|
const validation = validateDefinition(definition);
|
|
118
113
|
if (!validation.valid) {
|
|
119
|
-
return {
|
|
120
|
-
content: [
|
|
121
|
-
{
|
|
122
|
-
type: "text" as const,
|
|
123
|
-
text: `Invalid definition: ${validation.errors.join("; ")}`,
|
|
124
|
-
},
|
|
125
|
-
],
|
|
126
|
-
structuredContent: {
|
|
127
|
-
success: false,
|
|
128
|
-
message: `Invalid definition: ${validation.errors.join("; ")}`,
|
|
129
|
-
},
|
|
130
|
-
};
|
|
114
|
+
return toolErr(`Invalid definition: ${validation.errors.join("; ")}`);
|
|
131
115
|
}
|
|
132
116
|
|
|
133
117
|
const createdBy =
|
|
@@ -148,25 +132,12 @@ export const registerCreateWorkflowTool = (server: McpServer) => {
|
|
|
148
132
|
createdByAgentId: requestInfo.agentId,
|
|
149
133
|
createdBy,
|
|
150
134
|
});
|
|
151
|
-
return {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
text: `Created workflow "${workflow.name}" (${workflow.id}).`,
|
|
156
|
-
},
|
|
157
|
-
],
|
|
158
|
-
structuredContent: {
|
|
159
|
-
yourAgentId: requestInfo.agentId,
|
|
160
|
-
success: true,
|
|
161
|
-
message: `Created workflow "${workflow.name}".`,
|
|
162
|
-
workflow,
|
|
163
|
-
},
|
|
164
|
-
};
|
|
135
|
+
return toolOk(`Created workflow "${workflow.name}".`, {
|
|
136
|
+
details: `Created workflow "${workflow.name}" (${workflow.id}).`,
|
|
137
|
+
data: { yourAgentId: requestInfo.agentId, workflow },
|
|
138
|
+
});
|
|
165
139
|
} catch (err) {
|
|
166
|
-
return
|
|
167
|
-
content: [{ type: "text" as const, text: `Failed: ${err}` }],
|
|
168
|
-
structuredContent: { success: false, message: String(err) },
|
|
169
|
-
};
|
|
140
|
+
return toolErr(String(err));
|
|
170
141
|
}
|
|
171
142
|
},
|
|
172
143
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { deleteWorkflow } from "@/be/db";
|
|
4
|
-
import { createToolRegistrar } from "@/tools/utils";
|
|
4
|
+
import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
|
|
5
5
|
|
|
6
6
|
export const registerDeleteWorkflowTool = (server: McpServer) => {
|
|
7
7
|
createToolRegistrar(server)(
|
|
@@ -13,29 +13,17 @@ export const registerDeleteWorkflowTool = (server: McpServer) => {
|
|
|
13
13
|
inputSchema: z.object({
|
|
14
14
|
id: z.string().uuid().describe("Workflow ID to delete"),
|
|
15
15
|
}),
|
|
16
|
-
outputSchema:
|
|
17
|
-
success: z.boolean(),
|
|
18
|
-
message: z.string(),
|
|
19
|
-
}),
|
|
16
|
+
outputSchema: swarmToolOutputSchema(),
|
|
20
17
|
},
|
|
21
18
|
async ({ id }) => {
|
|
22
19
|
try {
|
|
23
20
|
const deleted = deleteWorkflow(id);
|
|
24
21
|
if (!deleted) {
|
|
25
|
-
return {
|
|
26
|
-
content: [{ type: "text" as const, text: `Workflow not found: ${id}` }],
|
|
27
|
-
structuredContent: { success: false, message: `Workflow not found: ${id}` },
|
|
28
|
-
};
|
|
22
|
+
return toolErr(`Workflow not found: ${id}`);
|
|
29
23
|
}
|
|
30
|
-
return {
|
|
31
|
-
content: [{ type: "text" as const, text: `Deleted workflow ${id}.` }],
|
|
32
|
-
structuredContent: { success: true, message: `Deleted workflow ${id}.` },
|
|
33
|
-
};
|
|
24
|
+
return toolOk(`Deleted workflow ${id}.`);
|
|
34
25
|
} catch (err) {
|
|
35
|
-
return
|
|
36
|
-
content: [{ type: "text" as const, text: `Failed: ${err}` }],
|
|
37
|
-
structuredContent: { success: false, message: String(err) },
|
|
38
|
-
};
|
|
26
|
+
return toolErr(String(err));
|
|
39
27
|
}
|
|
40
28
|
},
|
|
41
29
|
);
|
|
@@ -1,7 +1,39 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { getWorkflowRun, getWorkflowRunStepsByRunId } from "@/be/db";
|
|
4
|
-
import { createToolRegistrar } from "@/tools/utils";
|
|
4
|
+
import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
|
|
5
|
+
|
|
6
|
+
const STEP_VALUE_CAP = 400;
|
|
7
|
+
|
|
8
|
+
function stepValuePreview(value: unknown): string | undefined {
|
|
9
|
+
if (value === undefined || value === null) return undefined;
|
|
10
|
+
const serialized = typeof value === "string" ? value : JSON.stringify(value);
|
|
11
|
+
if (!serialized) return undefined;
|
|
12
|
+
return serialized.length > STEP_VALUE_CAP
|
|
13
|
+
? `${serialized.slice(0, STEP_VALUE_CAP)}…`
|
|
14
|
+
: serialized;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function renderSteps(steps: ReturnType<typeof getWorkflowRunStepsByRunId>): string | undefined {
|
|
18
|
+
if (steps.length === 0) return undefined;
|
|
19
|
+
return steps
|
|
20
|
+
.map((step) => {
|
|
21
|
+
const nodeId = (step as { nodeId?: unknown }).nodeId ?? "?";
|
|
22
|
+
const status = (step as { status?: unknown }).status ?? "?";
|
|
23
|
+
const error = (step as { error?: unknown }).error;
|
|
24
|
+
const errorSuffix = typeof error === "string" && error ? ` — error: ${error}` : "";
|
|
25
|
+
// Step results must reach the text channel — details suppresses the
|
|
26
|
+
// JSON fallback, and text-only harnesses never see structured data.
|
|
27
|
+
const output = errorSuffix
|
|
28
|
+
? undefined
|
|
29
|
+
: stepValuePreview((step as { output?: unknown }).output);
|
|
30
|
+
const outputSuffix = output ? ` — output: ${output}` : "";
|
|
31
|
+
const diagnostics = stepValuePreview((step as { diagnostics?: unknown }).diagnostics);
|
|
32
|
+
const diagnosticsSuffix = diagnostics ? ` — diagnostics: ${diagnostics}` : "";
|
|
33
|
+
return `- ${String(nodeId)}: ${String(status)}${errorSuffix}${outputSuffix}${diagnosticsSuffix}`;
|
|
34
|
+
})
|
|
35
|
+
.join("\n");
|
|
36
|
+
}
|
|
5
37
|
|
|
6
38
|
export const registerGetWorkflowRunTool = (server: McpServer) => {
|
|
7
39
|
createToolRegistrar(server)(
|
|
@@ -13,46 +45,24 @@ export const registerGetWorkflowRunTool = (server: McpServer) => {
|
|
|
13
45
|
inputSchema: z.object({
|
|
14
46
|
id: z.string().uuid().describe("Workflow run ID"),
|
|
15
47
|
}),
|
|
16
|
-
outputSchema:
|
|
17
|
-
success: z.boolean(),
|
|
18
|
-
message: z.string(),
|
|
48
|
+
outputSchema: swarmToolOutputSchema({
|
|
19
49
|
run: z.unknown().optional(),
|
|
20
|
-
steps: z.array(z.unknown()),
|
|
50
|
+
steps: z.array(z.unknown()).optional(),
|
|
21
51
|
}),
|
|
22
52
|
},
|
|
23
53
|
async ({ id }) => {
|
|
24
54
|
try {
|
|
25
55
|
const run = getWorkflowRun(id);
|
|
26
56
|
if (!run) {
|
|
27
|
-
return {
|
|
28
|
-
content: [{ type: "text" as const, text: `Workflow run not found: ${id}` }],
|
|
29
|
-
structuredContent: {
|
|
30
|
-
success: false,
|
|
31
|
-
message: `Workflow run not found: ${id}`,
|
|
32
|
-
steps: [],
|
|
33
|
-
},
|
|
34
|
-
};
|
|
57
|
+
return toolErr(`Workflow run not found: ${id}`, { data: { steps: [] } });
|
|
35
58
|
}
|
|
36
59
|
const steps = getWorkflowRunStepsByRunId(id);
|
|
37
|
-
return {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
text: `Run ${id} — status: ${run.status}, steps: ${steps.length}.`,
|
|
42
|
-
},
|
|
43
|
-
],
|
|
44
|
-
structuredContent: {
|
|
45
|
-
success: true,
|
|
46
|
-
message: `Run ${id} status: ${run.status}.`,
|
|
47
|
-
run,
|
|
48
|
-
steps,
|
|
49
|
-
},
|
|
50
|
-
};
|
|
60
|
+
return toolOk(`Run ${id} status: ${run.status}.`, {
|
|
61
|
+
details: renderSteps(steps),
|
|
62
|
+
data: { run, steps },
|
|
63
|
+
});
|
|
51
64
|
} catch (err) {
|
|
52
|
-
return {
|
|
53
|
-
content: [{ type: "text" as const, text: `Failed: ${err}` }],
|
|
54
|
-
structuredContent: { success: false, message: String(err), steps: [] },
|
|
55
|
-
};
|
|
65
|
+
return toolErr(String(err), { data: { steps: [] } });
|
|
56
66
|
}
|
|
57
67
|
},
|
|
58
68
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { getWorkflow } from "@/be/db";
|
|
4
|
-
import { createToolRegistrar } from "@/tools/utils";
|
|
4
|
+
import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
|
|
5
5
|
import { generateEdges } from "@/workflows/definition";
|
|
6
6
|
|
|
7
7
|
export const registerGetWorkflowTool = (server: McpServer) => {
|
|
@@ -15,9 +15,7 @@ export const registerGetWorkflowTool = (server: McpServer) => {
|
|
|
15
15
|
inputSchema: z.object({
|
|
16
16
|
id: z.string().uuid().describe("Workflow ID"),
|
|
17
17
|
}),
|
|
18
|
-
outputSchema:
|
|
19
|
-
success: z.boolean(),
|
|
20
|
-
message: z.string(),
|
|
18
|
+
outputSchema: swarmToolOutputSchema({
|
|
21
19
|
workflow: z.unknown().optional(),
|
|
22
20
|
edges: z.array(z.unknown()).optional(),
|
|
23
21
|
}),
|
|
@@ -26,27 +24,18 @@ export const registerGetWorkflowTool = (server: McpServer) => {
|
|
|
26
24
|
try {
|
|
27
25
|
const workflow = getWorkflow(id);
|
|
28
26
|
if (!workflow) {
|
|
29
|
-
return {
|
|
30
|
-
content: [{ type: "text" as const, text: `Workflow not found: ${id}` }],
|
|
31
|
-
structuredContent: { success: false, message: `Workflow not found: ${id}` },
|
|
32
|
-
};
|
|
27
|
+
return toolErr(`Workflow not found: ${id}`);
|
|
33
28
|
}
|
|
34
29
|
// Auto-generate edges for UI rendering
|
|
35
30
|
const edges = generateEdges(workflow.definition);
|
|
36
|
-
return {
|
|
37
|
-
|
|
38
|
-
structuredContent
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
edges,
|
|
43
|
-
},
|
|
44
|
-
};
|
|
31
|
+
return toolOk(`Workflow "${workflow.name}" (${id}).`, {
|
|
32
|
+
// The definition must reach the text channel — most harnesses never
|
|
33
|
+
// show the model structuredContent.
|
|
34
|
+
details: JSON.stringify(workflow, null, 2),
|
|
35
|
+
data: { workflow, edges },
|
|
36
|
+
});
|
|
45
37
|
} catch (err) {
|
|
46
|
-
return
|
|
47
|
-
content: [{ type: "text" as const, text: `Failed: ${err}` }],
|
|
48
|
-
structuredContent: { success: false, message: String(err) },
|
|
49
|
-
};
|
|
38
|
+
return toolErr(String(err));
|
|
50
39
|
}
|
|
51
40
|
},
|
|
52
41
|
);
|