@vornrun/mcp 0.5.3 → 0.5.4

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 (2) hide show
  1. package/dist/index.js +217 -32
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -50,28 +50,6 @@ var logger_default = log;
50
50
  import { execFileSync, execFile } from "child_process";
51
51
  import fs from "fs";
52
52
  import path from "path";
53
- function getUserShellEnv() {
54
- if (process.platform === "win32") return { ...process.env };
55
- try {
56
- const shell = process.env.SHELL || "/bin/zsh";
57
- const output = execFileSync(shell, ["-ilc", "env"], {
58
- encoding: "utf-8",
59
- timeout: 5e3,
60
- stdio: ["pipe", "pipe", "pipe"]
61
- });
62
- const env = {};
63
- for (const line of output.split("\n")) {
64
- const idx = line.indexOf("=");
65
- if (idx > 0) {
66
- env[line.substring(0, idx)] = line.substring(idx + 1);
67
- }
68
- }
69
- return env;
70
- } catch {
71
- return { ...process.env };
72
- }
73
- }
74
- var resolvedEnv = getUserShellEnv();
75
53
  function getDefaultShell(configured) {
76
54
  const chosen = configured?.trim();
77
55
  if (chosen) return chosen;
@@ -95,6 +73,8 @@ function findWindowsShell() {
95
73
  if (fs.existsSync(windowsPowerShell)) return windowsPowerShell;
96
74
  return process.env.COMSPEC || "cmd.exe";
97
75
  }
76
+ var STRIP_ENV_KEYS = ["CLAUDECODE"];
77
+ var STRIP_ENV_KEYS_UPPER = STRIP_ENV_KEYS.map((k) => k.toUpperCase());
98
78
 
99
79
  // ../shared/src/types.ts
100
80
  var DEFAULT_WORKSPACE = {
@@ -994,6 +974,15 @@ function loadDefaults(d) {
994
974
  hasSeenOnboarding: map.hasSeenOnboarding
995
975
  },
996
976
  ...map.reopenSessions !== void 0 && { reopenSessions: map.reopenSessions },
977
+ // Saving iterates over every key in defaults, but loading is this explicit
978
+ // list — so a key missing here round-trips to nothing and its feature is
979
+ // silently inert.
980
+ // Array-checked rather than cast: the value came from JSON.parse of a row a
981
+ // user can edit, and broadcasting a non-array under a string[] type would
982
+ // break any consumer that trusts the declaration.
983
+ ...Array.isArray(map.envPassthrough) && {
984
+ envPassthrough: map.envPassthrough.filter((k) => typeof k === "string")
985
+ },
997
986
  // Terminal block rendering. Default on; the key only appears once the
998
987
  // user has toggled it, so absence means "not yet decided", not "off".
999
988
  domBlockRendering: map.domBlockRendering ?? true,
@@ -2748,10 +2737,68 @@ var launchAgentConfigSchema = z5.object({
2748
2737
  message: "outputSchema requires headless: true",
2749
2738
  path: ["outputSchema"]
2750
2739
  });
2740
+ var workflowInputDefSchema = z5.object({
2741
+ key: z5.string().regex(
2742
+ /^[A-Za-z_][A-Za-z0-9_]*$/,
2743
+ "key must be a valid identifier \u2014 it becomes {{inputs.<key>}}"
2744
+ ).max(100),
2745
+ label: V.shortText,
2746
+ type: z5.enum(["text", "textarea", "number", "select", "boolean", "project", "branch"]),
2747
+ required: z5.boolean().optional(),
2748
+ defaultValue: V.shortText.optional(),
2749
+ options: z5.array(z5.object({ value: V.shortText, label: V.shortText })).optional(),
2750
+ placeholder: V.shortText.optional(),
2751
+ description: V.shortText.optional()
2752
+ }).superRefine((def, ctx) => {
2753
+ const options = def.options ?? [];
2754
+ if (def.type === "select" && options.length === 0) {
2755
+ ctx.addIssue({
2756
+ code: "custom",
2757
+ path: ["options"],
2758
+ message: `select input "${def.key}" declares no options, so the run dialog could offer nothing`
2759
+ });
2760
+ }
2761
+ if (def.defaultValue === void 0) return;
2762
+ if (def.type === "number" && !Number.isFinite(Number(def.defaultValue))) {
2763
+ ctx.addIssue({
2764
+ code: "custom",
2765
+ path: ["defaultValue"],
2766
+ message: `default "${def.defaultValue}" for number input "${def.key}" is not a finite number`
2767
+ });
2768
+ }
2769
+ if (def.type === "boolean" && !["true", "false"].includes(def.defaultValue)) {
2770
+ ctx.addIssue({
2771
+ code: "custom",
2772
+ path: ["defaultValue"],
2773
+ message: `default "${def.defaultValue}" for boolean input "${def.key}" must be "true" or "false"`
2774
+ });
2775
+ }
2776
+ if (def.type === "select" && options.length > 0 && !options.some((o) => o.value === def.defaultValue)) {
2777
+ ctx.addIssue({
2778
+ code: "custom",
2779
+ path: ["defaultValue"],
2780
+ message: `default "${def.defaultValue}" for select input "${def.key}" is not one of its options`
2781
+ });
2782
+ }
2783
+ });
2784
+ var workflowInputsSchema = z5.array(workflowInputDefSchema).superRefine((inputs, ctx) => {
2785
+ const seen = /* @__PURE__ */ new Set();
2786
+ inputs.forEach((def, index) => {
2787
+ if (seen.has(def.key)) {
2788
+ ctx.addIssue({
2789
+ code: "custom",
2790
+ path: [index, "key"],
2791
+ message: `duplicate input key "${def.key}" \u2014 only one value can survive under {{inputs.${def.key}}}`
2792
+ });
2793
+ }
2794
+ seen.add(def.key);
2795
+ });
2796
+ });
2751
2797
  var triggerConfigSchema = z5.union([
2752
2798
  z5.object({
2753
2799
  triggerType: z5.literal("manual"),
2754
- contextual: z5.boolean().optional()
2800
+ contextual: z5.boolean().optional(),
2801
+ inputs: workflowInputsSchema.optional()
2755
2802
  }),
2756
2803
  z5.object({ triggerType: z5.literal("once"), runAt: V.shortText }),
2757
2804
  z5.object({
@@ -2827,6 +2874,75 @@ function buildGraphFromFlat(trigger, actions) {
2827
2874
  }
2828
2875
  return { nodes, edges };
2829
2876
  }
2877
+ function resolveWorkflowInputs(defs, supplied) {
2878
+ const errors = [];
2879
+ const known = new Set(defs.map((d) => d.key));
2880
+ for (const key of Object.keys(supplied)) {
2881
+ if (!known.has(key)) {
2882
+ errors.push(
2883
+ `unknown input "${key}" \u2014 this workflow declares: ${Array.from(known).join(", ") || "(none)"}`
2884
+ );
2885
+ }
2886
+ }
2887
+ const values = {};
2888
+ for (const def of defs) {
2889
+ const provided = Object.prototype.hasOwnProperty.call(supplied, def.key);
2890
+ const raw = provided ? supplied[def.key] : def.defaultValue;
2891
+ if (def.type === "boolean") {
2892
+ if (raw === void 0) {
2893
+ values[def.key] = false;
2894
+ } else if (typeof raw === "boolean") {
2895
+ values[def.key] = raw;
2896
+ } else if (raw === "true" || raw === "false") {
2897
+ values[def.key] = raw === "true";
2898
+ } else {
2899
+ errors.push(`input "${def.key}" must be a boolean, got ${JSON.stringify(raw)}`);
2900
+ }
2901
+ continue;
2902
+ }
2903
+ if (raw === void 0 || typeof raw === "string" && raw.trim() === "") {
2904
+ if (def.required) errors.push(`missing required input "${def.key}" (${def.label})`);
2905
+ continue;
2906
+ }
2907
+ switch (def.type) {
2908
+ case "number": {
2909
+ if (typeof raw === "boolean") {
2910
+ errors.push(`input "${def.key}" must be a number, got ${JSON.stringify(raw)}`);
2911
+ break;
2912
+ }
2913
+ const n = typeof raw === "number" ? raw : Number(String(raw).trim());
2914
+ if (!Number.isFinite(n)) {
2915
+ errors.push(`input "${def.key}" must be a finite number, got ${JSON.stringify(raw)}`);
2916
+ } else {
2917
+ values[def.key] = n;
2918
+ }
2919
+ break;
2920
+ }
2921
+ case "select": {
2922
+ const allowed = (def.options ?? []).map((o) => o.value);
2923
+ if (allowed.length === 0) {
2924
+ errors.push(`input "${def.key}" is a select but declares no options`);
2925
+ } else if (!allowed.includes(String(raw))) {
2926
+ errors.push(`input "${def.key}" must be one of: ${allowed.join(", ")}`);
2927
+ } else {
2928
+ values[def.key] = String(raw);
2929
+ }
2930
+ break;
2931
+ }
2932
+ default:
2933
+ values[def.key] = String(raw);
2934
+ }
2935
+ }
2936
+ return { values, errors };
2937
+ }
2938
+ function resolveWorkflowId(args) {
2939
+ const id = args.workflow_id ?? args.id;
2940
+ if (!id) return { error: "provide workflow_id" };
2941
+ if (args.workflow_id && args.id && args.workflow_id !== args.id) {
2942
+ return { error: "workflow_id and id disagree \u2014 pass only workflow_id" };
2943
+ }
2944
+ return { id };
2945
+ }
2830
2946
  function registerWorkflowTools(server) {
2831
2947
  server.tool(
2832
2948
  "list_workflows",
@@ -2888,7 +3004,8 @@ function registerWorkflowTools(server) {
2888
3004
  "update_workflow",
2889
3005
  "Update a workflow's properties",
2890
3006
  {
2891
- id: V.id.describe("Workflow ID"),
3007
+ workflow_id: V.id.optional().describe("Workflow ID (from list_workflows)"),
3008
+ id: V.id.optional().describe("Deprecated alias for workflow_id"),
2892
3009
  name: V.title.optional(),
2893
3010
  nodes: z5.array(nodeSchema).optional(),
2894
3011
  edges: z5.array(edgeSchema).optional(),
@@ -2898,11 +3015,15 @@ function registerWorkflowTools(server) {
2898
3015
  stagger_delay_ms: z5.number().optional()
2899
3016
  },
2900
3017
  async (args) => {
3018
+ const resolved = resolveWorkflowId(args);
3019
+ if ("error" in resolved) {
3020
+ return { content: [{ type: "text", text: `Error: ${resolved.error}` }], isError: true };
3021
+ }
2901
3022
  const workflows = dbListWorkflows();
2902
- const workflow = workflows.find((w) => w.id === args.id);
3023
+ const workflow = workflows.find((w) => w.id === resolved.id);
2903
3024
  if (!workflow) {
2904
3025
  return {
2905
- content: [{ type: "text", text: `Error: workflow "${args.id}" not found` }],
3026
+ content: [{ type: "text", text: `Error: workflow "${resolved.id}" not found` }],
2906
3027
  isError: true
2907
3028
  };
2908
3029
  }
@@ -2914,7 +3035,7 @@ function registerWorkflowTools(server) {
2914
3035
  if (args.icon_color !== void 0) updates.iconColor = args.icon_color;
2915
3036
  if (args.enabled !== void 0) updates.enabled = args.enabled;
2916
3037
  if (args.stagger_delay_ms !== void 0) updates.staggerDelayMs = args.stagger_delay_ms;
2917
- dbUpdateWorkflow(args.id, updates);
3038
+ dbUpdateWorkflow(resolved.id, updates);
2918
3039
  dbSignalChange();
2919
3040
  return {
2920
3041
  content: [{ type: "text", text: JSON.stringify({ ...workflow, ...updates }, null, 2) }]
@@ -2924,17 +3045,24 @@ function registerWorkflowTools(server) {
2924
3045
  server.tool(
2925
3046
  "delete_workflow",
2926
3047
  "Delete a workflow",
2927
- { id: V.id.describe("Workflow ID") },
3048
+ {
3049
+ workflow_id: V.id.optional().describe("Workflow ID (from list_workflows)"),
3050
+ id: V.id.optional().describe("Deprecated alias for workflow_id")
3051
+ },
2928
3052
  async (args) => {
3053
+ const resolved = resolveWorkflowId(args);
3054
+ if ("error" in resolved) {
3055
+ return { content: [{ type: "text", text: `Error: ${resolved.error}` }], isError: true };
3056
+ }
2929
3057
  const workflows = dbListWorkflows();
2930
- const workflow = workflows.find((w) => w.id === args.id);
3058
+ const workflow = workflows.find((w) => w.id === resolved.id);
2931
3059
  if (!workflow) {
2932
3060
  return {
2933
- content: [{ type: "text", text: `Error: workflow "${args.id}" not found` }],
3061
+ content: [{ type: "text", text: `Error: workflow "${resolved.id}" not found` }],
2934
3062
  isError: true
2935
3063
  };
2936
3064
  }
2937
- dbDeleteWorkflow(args.id);
3065
+ dbDeleteWorkflow(resolved.id);
2938
3066
  dbSignalChange();
2939
3067
  return { content: [{ type: "text", text: `Deleted workflow: ${workflow.name}` }] };
2940
3068
  }
@@ -3006,6 +3134,63 @@ function registerWorkflowTools(server) {
3006
3134
  }
3007
3135
  }
3008
3136
  );
3137
+ server.tool(
3138
+ "execute_workflow",
3139
+ "Run a workflow now, as if triggered manually. Supply values for any parameters the workflow declares (see the trigger node's inputs); declared defaults fill in anything omitted. Requires the Vorn app to be running. Returns as soon as the run is queued \u2014 poll list_workflow_runs for the outcome.",
3140
+ {
3141
+ workflow_id: V.id.describe("Workflow ID (from list_workflows)"),
3142
+ inputs: z5.record(z5.string(), z5.union([z5.string(), z5.number(), z5.boolean()])).optional().describe("Values for the declared parameters, keyed by input key ({{inputs.<key>}})")
3143
+ },
3144
+ async (args) => {
3145
+ const workflow = dbListWorkflows().find((w) => w.id === args.workflow_id);
3146
+ if (!workflow) {
3147
+ return {
3148
+ content: [{ type: "text", text: `Error: workflow "${args.workflow_id}" not found` }],
3149
+ isError: true
3150
+ };
3151
+ }
3152
+ const trigger = workflow.nodes.find((n) => n.type === "trigger")?.config;
3153
+ const defs = trigger?.triggerType === "manual" ? trigger.inputs ?? [] : [];
3154
+ const supplied = args.inputs ?? {};
3155
+ let inputs;
3156
+ if (defs.length > 0) {
3157
+ const { values, errors } = resolveWorkflowInputs(defs, supplied);
3158
+ if (errors.length > 0) {
3159
+ return {
3160
+ content: [
3161
+ { type: "text", text: `Error: invalid inputs
3162
+ - ${errors.join("\n - ")}` }
3163
+ ],
3164
+ isError: true
3165
+ };
3166
+ }
3167
+ inputs = values;
3168
+ } else if (Object.keys(supplied).length > 0) {
3169
+ inputs = supplied;
3170
+ }
3171
+ try {
3172
+ await rpcCall("workflow:runManual", { workflowId: args.workflow_id, inputs });
3173
+ } catch (err) {
3174
+ return {
3175
+ content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : err}` }],
3176
+ isError: true
3177
+ };
3178
+ }
3179
+ const disabled = workflow.enabled === false ? " (workflow is disabled; manual runs still execute)" : "";
3180
+ const shown = inputs ? `
3181
+ inputs: ${JSON.stringify(inputs, null, 2)}` : "\nno inputs";
3182
+ return {
3183
+ content: [
3184
+ {
3185
+ type: "text",
3186
+ text: `Queued "${workflow.name}"${disabled}${shown}
3187
+
3188
+ Run history: list_workflow_runs with workflow_id ${args.workflow_id}`
3189
+ }
3190
+ ]
3191
+ };
3192
+ }
3193
+ );
3009
3194
  }
3010
3195
 
3011
3196
  // src/tools/config.ts
@@ -3371,7 +3556,7 @@ console.warn = (...args) => _origError("[mcp:warn]", ...args);
3371
3556
  console.error = (...args) => _origError("[mcp:error]", ...args);
3372
3557
  async function main() {
3373
3558
  configManager.init();
3374
- const version = true ? "0.5.3" : createRequire(import.meta.url)("../package.json").version;
3559
+ const version = true ? "0.5.4" : createRequire(import.meta.url)("../package.json").version;
3375
3560
  const server = createMcpServer(version);
3376
3561
  const transport = new StdioServerTransport();
3377
3562
  await server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vornrun/mcp",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "description": "Vorn MCP server — task management, git, and workflow tools for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -38,8 +38,8 @@
38
38
  "zod": "^4.4.3"
39
39
  },
40
40
  "devDependencies": {
41
- "@vornrun/server": "0.5.3",
42
- "@vornrun/shared": "0.5.3",
41
+ "@vornrun/server": "0.5.4",
42
+ "@vornrun/shared": "0.5.4",
43
43
  "tsup": "^8.5.1",
44
44
  "tsx": "^4.23.1",
45
45
  "typescript": "^6.0.3"