@invect/mcp 0.0.1

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 (70) hide show
  1. package/LICENSE +21 -0
  2. package/dist/backend/audit.d.ts +29 -0
  3. package/dist/backend/audit.d.ts.map +1 -0
  4. package/dist/backend/auth.d.ts +32 -0
  5. package/dist/backend/auth.d.ts.map +1 -0
  6. package/dist/backend/client/direct-client.d.ts +223 -0
  7. package/dist/backend/client/direct-client.d.ts.map +1 -0
  8. package/dist/backend/client/http-client.d.ts +74 -0
  9. package/dist/backend/client/http-client.d.ts.map +1 -0
  10. package/dist/backend/client/types.d.ts +83 -0
  11. package/dist/backend/client/types.d.ts.map +1 -0
  12. package/dist/backend/index.cjs +357 -0
  13. package/dist/backend/index.cjs.map +1 -0
  14. package/dist/backend/index.d.cts +29 -0
  15. package/dist/backend/index.d.cts.map +1 -0
  16. package/dist/backend/index.d.mts +29 -0
  17. package/dist/backend/index.d.mts.map +1 -0
  18. package/dist/backend/index.d.ts +32 -0
  19. package/dist/backend/index.d.ts.map +1 -0
  20. package/dist/backend/index.mjs +356 -0
  21. package/dist/backend/index.mjs.map +1 -0
  22. package/dist/backend/mcp-server.d.ts +8 -0
  23. package/dist/backend/mcp-server.d.ts.map +1 -0
  24. package/dist/backend/prompts/index.d.ts +7 -0
  25. package/dist/backend/prompts/index.d.ts.map +1 -0
  26. package/dist/backend/resources/index.d.ts +7 -0
  27. package/dist/backend/resources/index.d.ts.map +1 -0
  28. package/dist/backend/response-mappers.d.ts +26 -0
  29. package/dist/backend/response-mappers.d.ts.map +1 -0
  30. package/dist/backend/session-manager.d.ts +30 -0
  31. package/dist/backend/session-manager.d.ts.map +1 -0
  32. package/dist/backend/tools/credential-tools.d.ts +8 -0
  33. package/dist/backend/tools/credential-tools.d.ts.map +1 -0
  34. package/dist/backend/tools/debug-tools.d.ts +7 -0
  35. package/dist/backend/tools/debug-tools.d.ts.map +1 -0
  36. package/dist/backend/tools/flow-tools.d.ts +7 -0
  37. package/dist/backend/tools/flow-tools.d.ts.map +1 -0
  38. package/dist/backend/tools/node-tools.d.ts +7 -0
  39. package/dist/backend/tools/node-tools.d.ts.map +1 -0
  40. package/dist/backend/tools/run-tools.d.ts +7 -0
  41. package/dist/backend/tools/run-tools.d.ts.map +1 -0
  42. package/dist/backend/tools/trigger-tools.d.ts +7 -0
  43. package/dist/backend/tools/trigger-tools.d.ts.map +1 -0
  44. package/dist/backend/tools/version-tools.d.ts +7 -0
  45. package/dist/backend/tools/version-tools.d.ts.map +1 -0
  46. package/dist/cli/index.cjs +213 -0
  47. package/dist/cli/index.cjs.map +1 -0
  48. package/dist/cli/index.d.cts +151 -0
  49. package/dist/cli/index.d.cts.map +1 -0
  50. package/dist/cli/index.d.mts +151 -0
  51. package/dist/cli/index.d.mts.map +1 -0
  52. package/dist/cli/index.d.ts +17 -0
  53. package/dist/cli/index.d.ts.map +1 -0
  54. package/dist/cli/index.mjs +211 -0
  55. package/dist/cli/index.mjs.map +1 -0
  56. package/dist/mcp-server-Brb4wc6I.mjs +671 -0
  57. package/dist/mcp-server-Brb4wc6I.mjs.map +1 -0
  58. package/dist/mcp-server-DzO5MpFJ.cjs +676 -0
  59. package/dist/mcp-server-DzO5MpFJ.cjs.map +1 -0
  60. package/dist/shared/types.cjs +39 -0
  61. package/dist/shared/types.cjs.map +1 -0
  62. package/dist/shared/types.d.cts +64 -0
  63. package/dist/shared/types.d.cts.map +1 -0
  64. package/dist/shared/types.d.mts +64 -0
  65. package/dist/shared/types.d.mts.map +1 -0
  66. package/dist/shared/types.d.ts +64 -0
  67. package/dist/shared/types.d.ts.map +1 -0
  68. package/dist/shared/types.mjs +38 -0
  69. package/dist/shared/types.mjs.map +1 -0
  70. package/package.json +76 -0
@@ -0,0 +1,671 @@
1
+ import { TOOL_IDS } from "./shared/types.mjs";
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { z } from "zod";
4
+ //#region src/backend/auth.ts
5
+ /**
6
+ * Maps the MCP SDK's `authInfo` (from `ctx.http.authInfo`) to an InvectIdentity.
7
+ * The authInfo is populated by the framework adapter's middleware which resolves
8
+ * the session/API key before routing to the MCP plugin endpoint.
9
+ */
10
+ function mapAuthInfoToIdentity(authInfo) {
11
+ if (!authInfo || typeof authInfo !== "object") return null;
12
+ const info = authInfo;
13
+ if (info.userId && typeof info.userId === "string") return authInfo;
14
+ return null;
15
+ }
16
+ /**
17
+ * Extracts identity if available, returns null otherwise.
18
+ * Used by tool handlers — the underlying client (HttpClient/DirectClient)
19
+ * handles auth at its own layer, so a null identity is acceptable for
20
+ * transports like stdio where auth is pre-established.
21
+ */
22
+ function resolveIdentity(authInfo) {
23
+ return mapAuthInfoToIdentity(authInfo);
24
+ }
25
+ //#endregion
26
+ //#region src/backend/response-mappers.ts
27
+ /**
28
+ * Response mappers — transform raw API responses into concise, LLM-friendly text.
29
+ *
30
+ * LLMs work better with:
31
+ * - Flat, readable summaries instead of deeply nested JSON
32
+ * - Relevant fields only (no internal IDs like flowVersion numbers unless useful)
33
+ * - Human-readable dates and durations
34
+ * - Markdown formatting for structure
35
+ * - Truncated large payloads with clear indicators
36
+ */
37
+ function formatDate(d) {
38
+ if (!d) return "N/A";
39
+ const date = typeof d === "string" ? new Date(d) : d instanceof Date ? d : null;
40
+ if (!date || isNaN(date.getTime())) return String(d);
41
+ return date.toISOString().replace("T", " ").replace(/\.\d+Z$/, " UTC");
42
+ }
43
+ function durationMs(start, end) {
44
+ if (!start || !end) return "N/A";
45
+ const s = new Date(start).getTime();
46
+ const ms = new Date(end).getTime() - s;
47
+ if (isNaN(ms)) return "N/A";
48
+ if (ms < 1e3) return `${ms}ms`;
49
+ if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
50
+ return `${(ms / 6e4).toFixed(1)}m`;
51
+ }
52
+ function truncate(s, max = 200) {
53
+ if (s.length <= max) return s;
54
+ return s.slice(0, max) + "…";
55
+ }
56
+ function jsonCompact(obj, maxLen = 500) {
57
+ const s = JSON.stringify(obj);
58
+ if (s.length <= maxLen) return s;
59
+ return s.slice(0, maxLen) + `… (${s.length} chars total)`;
60
+ }
61
+ function mapFlowList(raw) {
62
+ const items = extractArray(raw);
63
+ if (items.length === 0) return "No flows found.";
64
+ const lines = items.map((f) => {
65
+ const tags = Array.isArray(f.tags) && f.tags.length > 0 ? ` [${f.tags.join(", ")}]` : "";
66
+ return `- **${f.name || "Untitled"}** (id: \`${f.id}\`)${tags}${f.description ? ` — ${truncate(String(f.description), 100)}` : ""}`;
67
+ });
68
+ return `**${items.length} flow(s):**\n\n${lines.join("\n")}`;
69
+ }
70
+ function mapFlow(raw) {
71
+ const f = raw;
72
+ if (!f || !f.id) return JSON.stringify(raw, null, 2);
73
+ return [
74
+ `**${f.name || "Untitled"}**`,
75
+ `- ID: \`${f.id}\``,
76
+ f.description ? `- Description: ${f.description}` : null,
77
+ f.status ? `- Status: ${f.status}` : null,
78
+ f.createdAt ? `- Created: ${formatDate(f.createdAt)}` : null,
79
+ f.updatedAt ? `- Updated: ${formatDate(f.updatedAt)}` : null
80
+ ].filter(Boolean).join("\n");
81
+ }
82
+ function mapFlowDefinition(raw) {
83
+ const v = raw;
84
+ if (!v) return "No definition found.";
85
+ const def = v.invectDefinition ?? v.definition ?? v;
86
+ const nodes = Array.isArray(def.nodes) ? def.nodes : [];
87
+ const edges = Array.isArray(def.edges) ? def.edges : [];
88
+ const parts = [
89
+ v.version !== null && v.version !== void 0 ? `**Version ${v.version}**` : null,
90
+ v.flowId ? `Flow: \`${v.flowId}\`` : null,
91
+ `**${nodes.length} node(s), ${edges.length} edge(s)**`
92
+ ];
93
+ if (nodes.length > 0) {
94
+ parts.push("", "## Nodes");
95
+ for (const n of nodes) {
96
+ const label = n.label || n.referenceId || n.id;
97
+ parts.push(`\n### \`${label}\``);
98
+ parts.push(`- **type:** ${n.type}`);
99
+ parts.push(`- **id:** \`${n.id}\``);
100
+ if (n.referenceId) parts.push(`- **referenceId:** \`${n.referenceId}\``);
101
+ const params = n.params;
102
+ if (params && Object.keys(params).length > 0) {
103
+ parts.push("- **params:**");
104
+ for (const [key, val] of Object.entries(params)) {
105
+ const valStr = typeof val === "string" ? val.includes("\n") ? `\n \`\`\`\n ${val.replace(/\n/g, "\n ")}\n \`\`\`` : val : JSON.stringify(val);
106
+ parts.push(` - \`${key}\`: ${valStr}`);
107
+ }
108
+ }
109
+ }
110
+ }
111
+ if (edges.length > 0) {
112
+ parts.push("", "## Edges");
113
+ const nodeMap = new Map(nodes.map((n) => [n.id, n.label || n.referenceId || n.id]));
114
+ for (const e of edges) {
115
+ const from = nodeMap.get(e.source) || e.source;
116
+ const to = nodeMap.get(e.target) || e.target;
117
+ parts.push(`- ${from} → ${to}${e.sourceHandle ? ` (${e.sourceHandle})` : ""}`);
118
+ }
119
+ }
120
+ return parts.filter((p) => p !== null).join("\n");
121
+ }
122
+ function mapValidation(raw) {
123
+ const r = raw;
124
+ if (r.valid || r.isValid) return "Flow definition is **valid**.";
125
+ const errors = r.errors ?? [];
126
+ return `Flow definition is **invalid** (${errors.length} error(s)):\n${errors.map((e) => `- ${typeof e === "string" ? e : e.message || JSON.stringify(e)}`).join("\n")}`;
127
+ }
128
+ function mapVersionList(raw) {
129
+ const items = extractArray(raw);
130
+ if (items.length === 0) return "No versions found.";
131
+ return `**${items.length} version(s):**\n\n${items.map((v) => `- v${v.version}${v.createdAt ? ` (${formatDate(v.createdAt)})` : ""}${v.description ? ` — ${truncate(String(v.description), 80)}` : ""}`).join("\n")}`;
132
+ }
133
+ function mapRunList(raw) {
134
+ const items = extractArray(raw);
135
+ if (items.length === 0) return "No runs found.";
136
+ const lines = items.slice(0, 25).map((r) => {
137
+ const status = String(r.status || "UNKNOWN");
138
+ const icon = status === "SUCCESS" ? "✓" : status === "FAILED" ? "✗" : status === "RUNNING" ? "⏳" : "○";
139
+ const dur = durationMs(r.startedAt, r.completedAt);
140
+ const err = r.error ? ` — ${truncate(String(r.error), 60)}` : "";
141
+ return `- ${icon} \`${r.id}\` ${status} (${dur})${err}`;
142
+ });
143
+ const suffix = items.length > 25 ? `\n\n…and ${items.length - 25} more` : "";
144
+ return `**${items.length} run(s):**\n\n${lines.join("\n")}${suffix}`;
145
+ }
146
+ function mapRun(raw) {
147
+ const r = raw;
148
+ if (!r || !r.id) return JSON.stringify(raw, null, 2);
149
+ return [
150
+ `**Run \`${r.id}\`**`,
151
+ `- Status: **${r.status}**`,
152
+ `- Flow: \`${r.flowId}\``,
153
+ r.startedAt ? `- Started: ${formatDate(r.startedAt)}` : null,
154
+ r.completedAt ? `- Completed: ${formatDate(r.completedAt)} (${durationMs(r.startedAt, r.completedAt)})` : null,
155
+ r.error ? `- Error: ${truncate(String(r.error), 200)}` : null,
156
+ r.outputs ? `- Output: ${jsonCompact(r.outputs)}` : null
157
+ ].filter(Boolean).join("\n");
158
+ }
159
+ function mapRunStarted(raw) {
160
+ const r = raw;
161
+ return `Flow run started: \`${r.flowRunId || r.id}\` (status: ${r.status || "RUNNING"})`;
162
+ }
163
+ function mapNodeExecutions(raw) {
164
+ const items = Array.isArray(raw) ? raw : [];
165
+ if (items.length === 0) return "No node executions found.";
166
+ const lines = items.map((n) => {
167
+ const status = String(n.status || "UNKNOWN");
168
+ const icon = status === "SUCCESS" ? "✓" : status === "FAILED" ? "✗" : "○";
169
+ const dur = durationMs(n.startedAt, n.completedAt);
170
+ const parts = [`### ${icon} \`${n.nodeId}\` (${n.nodeType}) — ${status} (${dur})`];
171
+ if (n.error) parts.push(`**Error:** ${String(n.error)}`);
172
+ if (n.inputs && Object.keys(n.inputs).length > 0) parts.push(`**Input:**\n\`\`\`json\n${JSON.stringify(n.inputs, null, 2)}\n\`\`\``);
173
+ if (n.outputs) parts.push(`**Output:**\n\`\`\`json\n${JSON.stringify(n.outputs, null, 2)}\n\`\`\``);
174
+ return parts.join("\n");
175
+ });
176
+ return `**${items.length} node execution(s):**\n\n${lines.join("\n\n")}`;
177
+ }
178
+ function mapTestResult(raw) {
179
+ const r = raw;
180
+ if (r.success === false || r.error) return `**Failed:** ${r.error || "Unknown error"}`;
181
+ return `**Result:** ${jsonCompact(r.output ?? r.result ?? r, 1e3)}`;
182
+ }
183
+ function mapCredentialList(raw) {
184
+ const items = Array.isArray(raw) ? raw : [];
185
+ if (items.length === 0) return "No credentials found.";
186
+ const lines = items.map((c) => `- **${c.name}** (id: \`${c.id}\`, type: ${c.type}${c.provider ? `, provider: ${c.provider}` : ""})`);
187
+ return `**${items.length} credential(s):**\n\n${lines.join("\n")}`;
188
+ }
189
+ function mapTriggerList(raw) {
190
+ const items = Array.isArray(raw) ? raw : [];
191
+ if (items.length === 0) return "No triggers found.";
192
+ const lines = items.map((t) => {
193
+ const type = t.type || t.triggerType || "unknown";
194
+ const enabled = t.enabled !== false ? "" : " (disabled)";
195
+ return `- **${t.name || type}** (id: \`${t.id}\`, type: ${type})${enabled}`;
196
+ });
197
+ return `**${items.length} trigger(s):**\n\n${lines.join("\n")}`;
198
+ }
199
+ function mapTrigger(raw) {
200
+ const t = raw;
201
+ if (!t || !t.id) return JSON.stringify(raw, null, 2);
202
+ return [
203
+ `**Trigger \`${t.id}\`**`,
204
+ `- Type: ${t.type || t.triggerType}`,
205
+ `- Flow: \`${t.flowId}\``,
206
+ t.enabled === false ? "- Status: **disabled**" : "- Status: enabled",
207
+ t.config ? `- Config: ${jsonCompact(t.config)}` : null
208
+ ].filter(Boolean).join("\n");
209
+ }
210
+ function mapNodeList(raw) {
211
+ const items = Array.isArray(raw) ? raw : [];
212
+ if (items.length === 0) return "No nodes available.";
213
+ const grouped = /* @__PURE__ */ new Map();
214
+ for (const node of items) {
215
+ const p = node.provider;
216
+ const providerName = p ? String(p.name || p.id || "core") : "core";
217
+ if (!grouped.has(providerName)) grouped.set(providerName, []);
218
+ grouped.get(providerName)?.push(node);
219
+ }
220
+ const sections = [`**${items.length} node type(s) across ${grouped.size} provider(s):**`];
221
+ for (const [provider, nodes] of grouped) {
222
+ sections.push(`\n### ${provider} (${nodes.length})`);
223
+ for (const n of nodes) sections.push(`- \`${n.id}\` — ${n.name || n.id}${n.description ? `: ${truncate(String(n.description), 80)}` : ""}`);
224
+ }
225
+ return sections.join("\n");
226
+ }
227
+ function mapProviderList(raw) {
228
+ const items = Array.isArray(raw) ? raw : [];
229
+ if (items.length === 0) return "No providers found.";
230
+ const lines = items.map((p) => `- **${p.name || p.id}** (\`${p.id}\`)${p.description ? ` — ${truncate(String(p.description), 80)}` : ""}`);
231
+ return `**${items.length} provider(s):**\n\n${lines.join("\n")}`;
232
+ }
233
+ function extractArray(raw) {
234
+ if (Array.isArray(raw)) return raw;
235
+ if (raw && typeof raw === "object") {
236
+ const obj = raw;
237
+ if (Array.isArray(obj.data)) return obj.data;
238
+ if (Array.isArray(obj.items)) return obj.items;
239
+ }
240
+ return [];
241
+ }
242
+ //#endregion
243
+ //#region src/backend/tools/flow-tools.ts
244
+ /**
245
+ * Flow management tools — CRUD operations on flows.
246
+ */
247
+ function registerFlowTools(server, client) {
248
+ server.tool(TOOL_IDS.FLOW_LIST, "List all flows with their names, IDs, status, and metadata", {}, async (_params, extra) => {
249
+ const identity = resolveIdentity(extra.authInfo);
250
+ return { content: [{
251
+ type: "text",
252
+ text: mapFlowList(await client.listFlows(identity))
253
+ }] };
254
+ });
255
+ server.tool(TOOL_IDS.FLOW_GET, "Get detailed metadata for a specific flow by ID", { flowId: z.string().describe("The flow ID") }, async ({ flowId }, extra) => {
256
+ const identity = resolveIdentity(extra.authInfo);
257
+ return { content: [{
258
+ type: "text",
259
+ text: mapFlow(await client.getFlow(identity, flowId))
260
+ }] };
261
+ });
262
+ server.tool(TOOL_IDS.FLOW_GET_DEFINITION, "Get the current flow definition including all nodes, edges, and configuration parameters", { flowId: z.string().describe("The flow ID") }, async ({ flowId }, extra) => {
263
+ const identity = resolveIdentity(extra.authInfo);
264
+ return { content: [{
265
+ type: "text",
266
+ text: mapFlowDefinition(await client.getFlowDefinition(identity, flowId))
267
+ }] };
268
+ });
269
+ server.tool(TOOL_IDS.FLOW_CREATE, "Create a new empty flow with a name and optional description", {
270
+ name: z.string().describe("Flow name"),
271
+ description: z.string().optional().describe("Flow description")
272
+ }, async ({ name, description }, extra) => {
273
+ const identity = resolveIdentity(extra.authInfo);
274
+ return { content: [{
275
+ type: "text",
276
+ text: mapFlow(await client.createFlow(identity, {
277
+ name,
278
+ description
279
+ }))
280
+ }] };
281
+ });
282
+ server.tool(TOOL_IDS.FLOW_UPDATE, "Update a flow's name or description", {
283
+ flowId: z.string().describe("The flow ID"),
284
+ name: z.string().optional().describe("New name"),
285
+ description: z.string().optional().describe("New description")
286
+ }, async ({ flowId, name, description }, extra) => {
287
+ const identity = resolveIdentity(extra.authInfo);
288
+ return { content: [{
289
+ type: "text",
290
+ text: mapFlow(await client.updateFlow(identity, flowId, {
291
+ name,
292
+ description
293
+ }))
294
+ }] };
295
+ });
296
+ server.tool(TOOL_IDS.FLOW_DELETE, "Permanently delete a flow and all its versions and run history", { flowId: z.string().describe("The flow ID to delete") }, async ({ flowId }, extra) => {
297
+ const identity = resolveIdentity(extra.authInfo);
298
+ await client.deleteFlow(identity, flowId);
299
+ return { content: [{
300
+ type: "text",
301
+ text: `Flow ${flowId} deleted successfully.`
302
+ }] };
303
+ });
304
+ server.tool(TOOL_IDS.FLOW_VALIDATE, "Validate a flow definition against the schema without saving it", {
305
+ flowId: z.string().describe("The flow ID"),
306
+ definition: z.any().describe("The flow definition to validate")
307
+ }, async ({ flowId, definition }, extra) => {
308
+ const identity = resolveIdentity(extra.authInfo);
309
+ return { content: [{
310
+ type: "text",
311
+ text: mapValidation(await client.validateFlow(identity, flowId, definition))
312
+ }] };
313
+ });
314
+ }
315
+ //#endregion
316
+ //#region src/backend/tools/version-tools.ts
317
+ /**
318
+ * Flow version management tools.
319
+ */
320
+ function registerVersionTools(server, client) {
321
+ server.tool(TOOL_IDS.VERSION_LIST, "List all versions of a flow with version numbers and timestamps", { flowId: z.string().describe("The flow ID") }, async ({ flowId }, extra) => {
322
+ const identity = resolveIdentity(extra.authInfo);
323
+ return { content: [{
324
+ type: "text",
325
+ text: mapVersionList(await client.listVersions(identity, flowId))
326
+ }] };
327
+ });
328
+ server.tool(TOOL_IDS.VERSION_GET, "Get a specific version's full definition by version number or \"latest\"", {
329
+ flowId: z.string().describe("The flow ID"),
330
+ version: z.string().describe("Version number or \"latest\"")
331
+ }, async ({ flowId, version }, extra) => {
332
+ const identity = resolveIdentity(extra.authInfo);
333
+ return { content: [{
334
+ type: "text",
335
+ text: mapFlowDefinition(await client.getVersion(identity, flowId, version))
336
+ }] };
337
+ });
338
+ server.tool(TOOL_IDS.VERSION_PUBLISH, "Publish a new version of a flow with a complete definition (nodes, edges, configuration)", {
339
+ flowId: z.string().describe("The flow ID"),
340
+ definition: z.any().describe("The complete flow definition to publish")
341
+ }, async ({ flowId, definition }, extra) => {
342
+ const identity = resolveIdentity(extra.authInfo);
343
+ return { content: [{
344
+ type: "text",
345
+ text: mapFlowDefinition(await client.publishVersion(identity, flowId, definition))
346
+ }] };
347
+ });
348
+ }
349
+ //#endregion
350
+ //#region src/backend/tools/run-tools.ts
351
+ /**
352
+ * Flow execution tools — start, monitor, and control flow runs.
353
+ */
354
+ function registerRunTools(server, client) {
355
+ server.tool(TOOL_IDS.RUN_START, "Execute a flow synchronously with optional input values. Returns the full run result when complete.", {
356
+ flowId: z.string().describe("The flow ID to execute"),
357
+ inputs: z.record(z.unknown()).optional().describe("Optional key-value input data for the flow")
358
+ }, async ({ flowId, inputs }, extra) => {
359
+ const identity = resolveIdentity(extra.authInfo);
360
+ return { content: [{
361
+ type: "text",
362
+ text: mapRunStarted(await client.startRun(identity, flowId, inputs))
363
+ }] };
364
+ });
365
+ server.tool(TOOL_IDS.RUN_TO_NODE, "Execute a flow up to a specific node (for debugging). Returns partial results stopping at the target node.", {
366
+ flowId: z.string().describe("The flow ID"),
367
+ nodeId: z.string().describe("Stop execution at this node ID"),
368
+ inputs: z.record(z.unknown()).optional().describe("Optional input data")
369
+ }, async ({ flowId, nodeId, inputs }, extra) => {
370
+ const identity = resolveIdentity(extra.authInfo);
371
+ return { content: [{
372
+ type: "text",
373
+ text: mapRun(await client.runToNode(identity, flowId, nodeId, inputs))
374
+ }] };
375
+ });
376
+ server.tool(TOOL_IDS.RUN_LIST, "List execution history for a flow, including status, timing, and errors", { flowId: z.string().describe("The flow ID") }, async ({ flowId }, extra) => {
377
+ const identity = resolveIdentity(extra.authInfo);
378
+ return { content: [{
379
+ type: "text",
380
+ text: mapRunList(await client.listRuns(identity, flowId))
381
+ }] };
382
+ });
383
+ server.tool(TOOL_IDS.RUN_GET, "Get detailed results of a specific flow run including status, output, timing, and any errors", { flowRunId: z.string().describe("The flow run ID") }, async ({ flowRunId }, extra) => {
384
+ const identity = resolveIdentity(extra.authInfo);
385
+ return { content: [{
386
+ type: "text",
387
+ text: mapRun(await client.getRun(identity, flowRunId))
388
+ }] };
389
+ });
390
+ server.tool(TOOL_IDS.RUN_CANCEL, "Cancel a running flow execution", { flowRunId: z.string().describe("The flow run ID to cancel") }, async ({ flowRunId }, extra) => {
391
+ const identity = resolveIdentity(extra.authInfo);
392
+ return { content: [{
393
+ type: "text",
394
+ text: `Run ${flowRunId} cancelled. ${(await client.cancelRun(identity, flowRunId)).message || ""}`
395
+ }] };
396
+ });
397
+ server.tool(TOOL_IDS.RUN_PAUSE, "Pause a running flow execution (can be resumed later)", { flowRunId: z.string().describe("The flow run ID to pause") }, async ({ flowRunId }, extra) => {
398
+ const identity = resolveIdentity(extra.authInfo);
399
+ return { content: [{
400
+ type: "text",
401
+ text: `Run ${flowRunId} paused. ${(await client.pauseRun(identity, flowRunId)).message || ""}`
402
+ }] };
403
+ });
404
+ server.tool(TOOL_IDS.RUN_RESUME, "Resume a previously paused flow execution", { flowRunId: z.string().describe("The flow run ID to resume") }, async ({ flowRunId }, extra) => {
405
+ const identity = resolveIdentity(extra.authInfo);
406
+ return { content: [{
407
+ type: "text",
408
+ text: `Run ${flowRunId} resumed. ${(await client.resumeRun(identity, flowRunId)).message || ""}`
409
+ }] };
410
+ });
411
+ }
412
+ //#endregion
413
+ //#region src/backend/tools/debug-tools.ts
414
+ /**
415
+ * Debugging & testing tools — node execution traces, expression testing, stats.
416
+ */
417
+ function registerDebugTools(server, client) {
418
+ server.tool(TOOL_IDS.DEBUG_NODE_EXECUTIONS, "Get per-node execution traces for a flow run, including each node's input, output, timing, and errors. Essential for debugging failed runs.", { flowRunId: z.string().describe("The flow run ID") }, async ({ flowRunId }, extra) => {
419
+ const identity = resolveIdentity(extra.authInfo);
420
+ return { content: [{
421
+ type: "text",
422
+ text: mapNodeExecutions(await client.getNodeExecutions(identity, flowRunId))
423
+ }] };
424
+ });
425
+ server.tool(TOOL_IDS.DEBUG_TEST_NODE, "Execute a single node type in isolation with given parameters and input data. Useful for testing node configuration before adding to a flow.", {
426
+ nodeType: z.string().describe("Action ID of the node type (e.g., \"core.jq\", \"http.request\")"),
427
+ params: z.record(z.unknown()).describe("Node configuration parameters"),
428
+ inputData: z.record(z.unknown()).optional().describe("Simulated incoming data from upstream nodes")
429
+ }, async ({ nodeType, params, inputData }, extra) => {
430
+ const identity = resolveIdentity(extra.authInfo);
431
+ return { content: [{
432
+ type: "text",
433
+ text: mapTestResult(await client.testNode(identity, nodeType, params, inputData))
434
+ }] };
435
+ });
436
+ server.tool(TOOL_IDS.DEBUG_TEST_EXPRESSION, "Test a JavaScript template expression ({{ ... }}) against a sample data context. Uses the QuickJS WASM sandbox.", {
437
+ expression: z.string().describe("JS template expression to test (e.g., \"{{ users.filter(u => u.active).length }}\")"),
438
+ context: z.record(z.unknown()).describe("Sample data context to evaluate the expression against")
439
+ }, async ({ expression, context }, extra) => {
440
+ const identity = resolveIdentity(extra.authInfo);
441
+ return { content: [{
442
+ type: "text",
443
+ text: mapTestResult(await client.testJsExpression(identity, expression, context))
444
+ }] };
445
+ });
446
+ server.tool(TOOL_IDS.DEBUG_TEST_MAPPER, "Test a data mapper/transformation expression against sample incoming data", {
447
+ expression: z.string().describe("Mapper expression to evaluate"),
448
+ incomingData: z.record(z.unknown()).describe("Sample incoming data to map")
449
+ }, async ({ expression, incomingData }, extra) => {
450
+ const identity = resolveIdentity(extra.authInfo);
451
+ return { content: [{
452
+ type: "text",
453
+ text: mapTestResult(await client.testMapper(identity, expression, incomingData))
454
+ }] };
455
+ });
456
+ }
457
+ //#endregion
458
+ //#region src/backend/tools/credential-tools.ts
459
+ /**
460
+ * Credential tools — reference-only (list metadata, test connectivity).
461
+ * No create/delete/get-values for security.
462
+ */
463
+ function registerCredentialTools(server, client) {
464
+ server.tool(TOOL_IDS.CREDENTIAL_LIST, "List all credentials with their names, types, and providers. Does NOT expose secrets — only metadata for referencing in flow configs.", {}, async (_params, extra) => {
465
+ const identity = resolveIdentity(extra.authInfo);
466
+ return { content: [{
467
+ type: "text",
468
+ text: mapCredentialList(await client.listCredentials(identity))
469
+ }] };
470
+ });
471
+ server.tool(TOOL_IDS.CREDENTIAL_TEST, "Test connectivity of a credential by ID. Verifies the credential is valid and can reach the external service.", { credentialId: z.string().describe("The credential ID to test") }, async ({ credentialId }, extra) => {
472
+ const identity = resolveIdentity(extra.authInfo);
473
+ return { content: [{
474
+ type: "text",
475
+ text: mapTestResult(await client.testCredential(identity, credentialId))
476
+ }] };
477
+ });
478
+ }
479
+ //#endregion
480
+ //#region src/backend/tools/trigger-tools.ts
481
+ /**
482
+ * Trigger management tools — CRUD for flow triggers (cron, webhook, manual).
483
+ */
484
+ function registerTriggerTools(server, client) {
485
+ server.tool(TOOL_IDS.TRIGGER_LIST, "List all triggers configured for a flow (cron schedules, webhooks, manual)", { flowId: z.string().describe("The flow ID") }, async ({ flowId }, extra) => {
486
+ const identity = resolveIdentity(extra.authInfo);
487
+ return { content: [{
488
+ type: "text",
489
+ text: mapTriggerList(await client.listTriggers(identity, flowId))
490
+ }] };
491
+ });
492
+ server.tool(TOOL_IDS.TRIGGER_GET, "Get details of a specific trigger", { triggerId: z.string().describe("The trigger ID") }, async ({ triggerId }, extra) => {
493
+ const identity = resolveIdentity(extra.authInfo);
494
+ return { content: [{
495
+ type: "text",
496
+ text: mapTrigger(await client.getTrigger(identity, triggerId))
497
+ }] };
498
+ });
499
+ server.tool(TOOL_IDS.TRIGGER_CREATE, "Create a new trigger for a flow (e.g., cron schedule, webhook)", { input: z.any().describe("Trigger creation input including flowId, type, and configuration") }, async ({ input }, extra) => {
500
+ const identity = resolveIdentity(extra.authInfo);
501
+ return { content: [{
502
+ type: "text",
503
+ text: mapTrigger(await client.createTrigger(identity, input))
504
+ }] };
505
+ });
506
+ server.tool(TOOL_IDS.TRIGGER_UPDATE, "Update an existing trigger's configuration", {
507
+ triggerId: z.string().describe("The trigger ID to update"),
508
+ input: z.any().describe("Updated trigger configuration")
509
+ }, async ({ triggerId, input }, extra) => {
510
+ const identity = resolveIdentity(extra.authInfo);
511
+ return { content: [{
512
+ type: "text",
513
+ text: mapTrigger(await client.updateTrigger(identity, triggerId, input))
514
+ }] };
515
+ });
516
+ server.tool(TOOL_IDS.TRIGGER_DELETE, "Delete a trigger", { triggerId: z.string().describe("The trigger ID to delete") }, async ({ triggerId }, extra) => {
517
+ const identity = resolveIdentity(extra.authInfo);
518
+ await client.deleteTrigger(identity, triggerId);
519
+ return { content: [{
520
+ type: "text",
521
+ text: `Trigger ${triggerId} deleted.`
522
+ }] };
523
+ });
524
+ }
525
+ //#endregion
526
+ //#region src/backend/tools/node-tools.ts
527
+ function registerNodeTools(server, client) {
528
+ server.tool(TOOL_IDS.NODE_LIST_PROVIDERS, "List all available node providers (core, http, gmail, slack, github, etc.) with their metadata", {}, async (_params, extra) => {
529
+ const identity = resolveIdentity(extra.authInfo);
530
+ return { content: [{
531
+ type: "text",
532
+ text: mapProviderList(await client.listProviders(identity))
533
+ }] };
534
+ });
535
+ server.tool(TOOL_IDS.NODE_LIST_AVAILABLE, "List all available node types with their IDs, names, parameter schemas, and field definitions. Use this to understand what nodes can be added to flows.", {}, async (_params, extra) => {
536
+ const identity = resolveIdentity(extra.authInfo);
537
+ return { content: [{
538
+ type: "text",
539
+ text: mapNodeList(await client.listAvailableNodes(identity))
540
+ }] };
541
+ });
542
+ }
543
+ //#endregion
544
+ //#region src/backend/resources/index.ts
545
+ function registerResources(server, client) {
546
+ server.resource("flow-definition", "invect://flows/{flowId}/definition", { description: "The current definition of a specific flow (nodes, edges, params)" }, async (uri, extra) => {
547
+ const identity = resolveIdentity(extra.authInfo);
548
+ const match = uri.href.match(/^invect:\/\/flows\/([^/]+)\/definition$/);
549
+ if (!match?.[1]) return { contents: [{
550
+ uri: uri.href,
551
+ text: "Invalid flow URI",
552
+ mimeType: "text/plain"
553
+ }] };
554
+ const flowId = decodeURIComponent(match[1]);
555
+ const def = await client.getFlowDefinition(identity, flowId);
556
+ return { contents: [{
557
+ uri: uri.href,
558
+ text: JSON.stringify(def, null, 2),
559
+ mimeType: "application/json"
560
+ }] };
561
+ });
562
+ server.resource("flow-run", "invect://runs/{flowRunId}", { description: "The result details of a specific flow run" }, async (uri, extra) => {
563
+ const identity = resolveIdentity(extra.authInfo);
564
+ const match = uri.href.match(/^invect:\/\/runs\/([^/]+)$/);
565
+ if (!match?.[1]) return { contents: [{
566
+ uri: uri.href,
567
+ text: "Invalid run URI",
568
+ mimeType: "text/plain"
569
+ }] };
570
+ const flowRunId = decodeURIComponent(match[1]);
571
+ const run = await client.getRun(identity, flowRunId);
572
+ return { contents: [{
573
+ uri: uri.href,
574
+ text: JSON.stringify(run, null, 2),
575
+ mimeType: "application/json"
576
+ }] };
577
+ });
578
+ }
579
+ //#endregion
580
+ //#region src/backend/prompts/index.ts
581
+ /**
582
+ * MCP Prompts — reusable prompt templates for common Invect tasks.
583
+ */
584
+ function registerPrompts(server, _client) {
585
+ server.prompt("debug-flow-run", "Analyze a failed or problematic flow run to identify the root cause", { flowRunId: z.string().describe("The flow run ID to debug") }, async ({ flowRunId }) => {
586
+ return { messages: [{
587
+ role: "user",
588
+ content: {
589
+ type: "text",
590
+ text: [
591
+ `Debug the flow run "${flowRunId}". Follow these steps:`,
592
+ "",
593
+ "1. Use run_get to fetch the run details (status, error, timing).",
594
+ "2. Use debug_node_executions to get per-node traces.",
595
+ "3. Identify which node(s) failed or produced unexpected output.",
596
+ "4. If a node has an expression error, use debug_test_expression with the failed expression and the node's input data.",
597
+ "5. Summarize: root cause, which node failed, and suggest a fix."
598
+ ].join("\n")
599
+ }
600
+ }] };
601
+ });
602
+ server.prompt("create-flow", "Step-by-step guide to create a new flow with nodes and connections", {
603
+ name: z.string().describe("Name for the new flow"),
604
+ description: z.string().optional().describe("What the flow should do")
605
+ }, async ({ name, description }) => {
606
+ return { messages: [{
607
+ role: "user",
608
+ content: {
609
+ type: "text",
610
+ text: [
611
+ `Create a new Invect flow called "${name}"${description ? `: ${description}` : ""}.`,
612
+ "",
613
+ "Follow these steps:",
614
+ "1. Use node_list_available to see all available node types.",
615
+ "2. Use flow_create to create the flow.",
616
+ "3. Plan the node graph: decide which nodes are needed, their parameters, and how they connect.",
617
+ "4. Use version_publish to publish the flow definition with nodes and edges.",
618
+ "5. Use flow_validate to verify the definition is correct.",
619
+ "6. Optionally use run_start to test the flow."
620
+ ].join("\n")
621
+ }
622
+ }] };
623
+ });
624
+ server.prompt("explain-flow", "Get a detailed explanation of what a flow does, its nodes, and data flow", { flowId: z.string().describe("The flow ID to explain") }, async ({ flowId }) => {
625
+ return { messages: [{
626
+ role: "user",
627
+ content: {
628
+ type: "text",
629
+ text: [
630
+ `Explain the flow "${flowId}" in detail.`,
631
+ "",
632
+ "1. Use flow_get to get the flow metadata.",
633
+ "2. Use flow_get_definition to get the full definition.",
634
+ "3. Analyze the nodes, their types, parameters, and connections (edges).",
635
+ "4. Describe the data flow from inputs through each node to outputs.",
636
+ "5. Flag any potential issues or improvements."
637
+ ].join("\n")
638
+ }
639
+ }] };
640
+ });
641
+ }
642
+ //#endregion
643
+ //#region src/backend/mcp-server.ts
644
+ /**
645
+ * MCP Server factory — creates and configures the McpServer with all tools,
646
+ * resources, and prompts.
647
+ */
648
+ function createMcpServer(client) {
649
+ const server = new McpServer({
650
+ name: "invect-mcp",
651
+ version: "0.0.1"
652
+ }, { capabilities: {
653
+ tools: {},
654
+ resources: {},
655
+ prompts: {}
656
+ } });
657
+ registerFlowTools(server, client);
658
+ registerVersionTools(server, client);
659
+ registerRunTools(server, client);
660
+ registerDebugTools(server, client);
661
+ registerCredentialTools(server, client);
662
+ registerTriggerTools(server, client);
663
+ registerNodeTools(server, client);
664
+ registerResources(server, client);
665
+ registerPrompts(server, client);
666
+ return server;
667
+ }
668
+ //#endregion
669
+ export { createMcpServer as t };
670
+
671
+ //# sourceMappingURL=mcp-server-Brb4wc6I.mjs.map