@vornrun/mcp 0.6.1 → 0.7.0-beta.10

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 +243 -90
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -50,8 +50,32 @@ import fs from "fs";
50
50
  import path from "path";
51
51
 
52
52
  // ../shared/src/protocol.ts
53
+ var BOOTSTRAP_ENV_VAR = "SECRET_VORN_BOOTSTRAP_TOKEN";
53
54
  var LOCAL_TOKEN_FILENAME = "local-token";
54
55
 
56
+ // ../shared/src/types.ts
57
+ var DEFAULT_WORKSPACE = {
58
+ id: "personal",
59
+ name: "Personal",
60
+ icon: "User",
61
+ iconColor: "#6b7280",
62
+ order: 0
63
+ };
64
+ function isTerminalTaskStatus(status) {
65
+ return status === "done" || status === "cancelled";
66
+ }
67
+ var SDK_FILTER_KEYS = {
68
+ connectorId: "sdkConnectorId",
69
+ version: "sdkVersion",
70
+ icon: "sdkIcon",
71
+ implicit: "implicit"
72
+ };
73
+ var NEVER_BORROWED_ENV = { keys: ["CLAUDECODE"], prefixes: ["CLAUDE_CODE_"] };
74
+ function connectionConnectorId(connection2) {
75
+ const packaged = connection2.filters?.[SDK_FILTER_KEYS.connectorId];
76
+ return typeof packaged === "string" && packaged !== "" ? packaged : connection2.connectorId;
77
+ }
78
+
55
79
  // ../server/src/process-utils.ts
56
80
  function getDefaultShell(configured) {
57
81
  const chosen = configured?.trim();
@@ -76,30 +100,10 @@ function findWindowsShell() {
76
100
  if (fs.existsSync(windowsPowerShell)) return windowsPowerShell;
77
101
  return process.env.COMSPEC || "cmd.exe";
78
102
  }
79
- var STRIP_ENV_KEYS = ["CLAUDECODE"];
103
+ var STRIP_ENV_KEYS = NEVER_BORROWED_ENV.keys;
104
+ var STRIP_ENV_PREFIXES = [...NEVER_BORROWED_ENV.prefixes, BOOTSTRAP_ENV_VAR];
80
105
  var STRIP_ENV_KEYS_UPPER = STRIP_ENV_KEYS.map((k) => k.toUpperCase());
81
106
 
82
- // ../shared/src/types.ts
83
- var DEFAULT_WORKSPACE = {
84
- id: "personal",
85
- name: "Personal",
86
- icon: "User",
87
- iconColor: "#6b7280",
88
- order: 0
89
- };
90
- function isTerminalTaskStatus(status) {
91
- return status === "done" || status === "cancelled";
92
- }
93
- var SDK_FILTER_KEYS = {
94
- connectorId: "sdkConnectorId",
95
- version: "sdkVersion",
96
- icon: "sdkIcon"
97
- };
98
- function connectionConnectorId(connection2) {
99
- const packaged = connection2.filters?.[SDK_FILTER_KEYS.connectorId];
100
- return typeof packaged === "string" && packaged !== "" ? packaged : connection2.connectorId;
101
- }
102
-
103
107
  // ../server/src/default-workflows.ts
104
108
  var DEFAULT_TASK_WORKFLOW_ID = "system:default-task-workflow";
105
109
  function buildDefaultTaskWorkflow() {
@@ -888,6 +892,51 @@ function migrateSchema(d) {
888
892
  })();
889
893
  logger_default.info("[database] migrated schema to version 15 (config row revisions)");
890
894
  }
895
+ if (version < 16) {
896
+ d.transaction(() => {
897
+ const cols = d.prepare("PRAGMA table_info(sessions)").all();
898
+ if (!cols.some((c) => c.name === "shell_cwd")) {
899
+ d.exec("ALTER TABLE sessions ADD COLUMN shell_cwd TEXT");
900
+ }
901
+ d.prepare(
902
+ "INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('schema_version', '16')"
903
+ ).run();
904
+ })();
905
+ logger_default.info("[database] migrated schema to version 16 (shell working directory)");
906
+ }
907
+ if (version < 17) {
908
+ d.transaction(() => {
909
+ const derived = `(
910
+ SELECT json_extract(sc.filters, '$.sdkConnectorId')
911
+ FROM task_source_links tsl
912
+ JOIN source_connections sc ON sc.id = tsl.connection_id
913
+ WHERE tsl.task_id = tasks.id
914
+ )`;
915
+ d.exec(`
916
+ UPDATE tasks
917
+ SET source_connector_id = ${derived}
918
+ WHERE source_connector_id = 'mcp' AND ${derived} IS NOT NULL
919
+ `);
920
+ d.exec(`
921
+ UPDATE task_source_links
922
+ SET connector_id = (
923
+ SELECT json_extract(sc.filters, '$.sdkConnectorId')
924
+ FROM source_connections sc
925
+ WHERE sc.id = task_source_links.connection_id
926
+ )
927
+ WHERE connector_id = 'mcp'
928
+ AND (
929
+ SELECT json_extract(sc.filters, '$.sdkConnectorId')
930
+ FROM source_connections sc
931
+ WHERE sc.id = task_source_links.connection_id
932
+ ) IS NOT NULL
933
+ `);
934
+ d.prepare(
935
+ "INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('schema_version', '17')"
936
+ ).run();
937
+ })();
938
+ logger_default.info("[database] migrated schema to version 17 (packaged connector task ids)");
939
+ }
891
940
  }
892
941
  var REVISIONED_TABLES = [
893
942
  "projects",
@@ -930,7 +979,8 @@ function verifySchema(d) {
930
979
  ddl: "ALTER TABLE sessions ADD COLUMN sort_order INTEGER NOT NULL DEFAULT 0"
931
980
  },
932
981
  { column: "worktree_name", ddl: "ALTER TABLE sessions ADD COLUMN worktree_name TEXT" },
933
- { column: "agent_session_id", ddl: "ALTER TABLE sessions ADD COLUMN agent_session_id TEXT" }
982
+ { column: "agent_session_id", ddl: "ALTER TABLE sessions ADD COLUMN agent_session_id TEXT" },
983
+ { column: "shell_cwd", ddl: "ALTER TABLE sessions ADD COLUMN shell_cwd TEXT" }
934
984
  ],
935
985
  agent_commands: [
936
986
  {
@@ -1078,7 +1128,13 @@ function loadDefaults(d) {
1078
1128
  ...map.hasSeenOnboarding !== void 0 && {
1079
1129
  hasSeenOnboarding: map.hasSeenOnboarding
1080
1130
  },
1081
- ...map.reopenSessions !== void 0 && { reopenSessions: map.reopenSessions },
1131
+ // Default on, and that changed meaning rather than merely flipping. It used
1132
+ // to decide whether every saved session was relaunched at start-up, which
1133
+ // spends tokens and starts processes -- worth asking about, so it was off.
1134
+ // Bringing a pane back no longer does either: it shows the last screen its
1135
+ // terminal drew and waits. There is nothing to ask, and leaving it off made
1136
+ // the whole thing invisible unless somebody went looking for a toggle.
1137
+ reopenSessions: map.reopenSessions ?? true,
1082
1138
  // Saving iterates over every key in defaults, but loading is this explicit
1083
1139
  // list — so a key missing here round-trips to nothing and its feature is
1084
1140
  // silently inert.
@@ -1092,6 +1148,10 @@ function loadDefaults(d) {
1092
1148
  // user has toggled it, so absence means "not yet decided", not "off".
1093
1149
  domBlockRendering: map.domBlockRendering ?? true,
1094
1150
  minimalShellPrompt: map.minimalShellPrompt ?? true,
1151
+ // Sessions outlive the window. Default on, same reasoning as above: the key
1152
+ // only appears once the user has turned it off, so absence is "not yet
1153
+ // decided". Read by the main process at quit, not by the renderer.
1154
+ keepSessionsRunning: map.keepSessionsRunning ?? true,
1095
1155
  ...map.widgetEnabled !== void 0 && { widgetEnabled: map.widgetEnabled },
1096
1156
  ...map.taskViewMode !== void 0 && {
1097
1157
  taskViewMode: map.taskViewMode
@@ -1592,7 +1652,7 @@ var configManager = new ConfigManager();
1592
1652
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1593
1653
 
1594
1654
  // src/tools/tasks.ts
1595
- import crypto from "crypto";
1655
+ import crypto2 from "crypto";
1596
1656
  import path4 from "path";
1597
1657
  import { z as z2 } from "zod";
1598
1658
 
@@ -1989,7 +2049,7 @@ function registerTaskTools(server) {
1989
2049
  const now = (/* @__PURE__ */ new Date()).toISOString();
1990
2050
  const status = args.status ?? "todo";
1991
2051
  const task = {
1992
- id: crypto.randomUUID(),
2052
+ id: crypto2.randomUUID(),
1993
2053
  projectName: args.project_name,
1994
2054
  title: args.title,
1995
2055
  description: args.description ?? "",
@@ -2689,47 +2749,87 @@ function registerSessionTools(server) {
2689
2749
  }
2690
2750
 
2691
2751
  // src/tools/workflows.ts
2692
- import crypto2 from "crypto";
2752
+ import crypto3 from "crypto";
2693
2753
  import { z as z5 } from "zod";
2694
2754
 
2695
- // src/workflow-portability.ts
2755
+ // ../shared/src/workflow-portability.ts
2696
2756
  var PROJECT_PATH_TOKEN = "{{project.path}}";
2697
2757
  var PROJECT_NAME_TOKEN = "{{project.name}}";
2698
2758
  var PORTABLE_FORMAT_VERSION = 1;
2759
+ var HTTP_PROFILE_CONNECTOR = "http";
2699
2760
  function importedWorkflowId(bundle, slug) {
2700
2761
  return `import:${bundle}:${slug}`;
2701
2762
  }
2763
+ function importedWorkflowIdFor(bundle, slug, name, existing) {
2764
+ for (let attempt = 1; ; attempt++) {
2765
+ const id = importedWorkflowId(bundle, attempt === 1 ? slug : `${slug}-${attempt}`);
2766
+ const held = existing.find((workflow) => workflow.id === id);
2767
+ if (!held || held.name === name) return id;
2768
+ }
2769
+ }
2702
2770
  function slugify(name) {
2703
2771
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "workflow";
2704
2772
  }
2705
- function portabilityBlockers(workflow) {
2706
- const blockers = [];
2707
- for (const node of workflow.nodes) {
2708
- const config = node.config;
2709
- if (node.type === "trigger" && config.triggerType === "connectorPoll") {
2710
- blockers.push(`the trigger polls a connector connection, which exists only on this machine`);
2711
- }
2712
- if (node.type === "callConnectorAction") {
2713
- blockers.push(`step "${node.label}" calls a connector action bound to a local connection`);
2714
- }
2773
+ function connectorOf(connection2) {
2774
+ return connectionConnectorId({
2775
+ connectorId: connection2.connectorId,
2776
+ filters: connection2.filters ?? {}
2777
+ });
2778
+ }
2779
+ function boundConnectionKey(node, config) {
2780
+ if (node.type === "trigger" && config.triggerType === "connectorPoll") return "connectionId";
2781
+ if (node.type === "callConnectorAction") return "connectionId";
2782
+ if (node.type === "httpRequest") return "profileConnectionId";
2783
+ return null;
2784
+ }
2785
+ function resolveRequirement(requirement, connections) {
2786
+ const candidates = connections.filter(
2787
+ (connection2) => requirement.kind === "httpProfile" ? connectorOf(connection2) === HTTP_PROFILE_CONNECTOR : requirement.connectorId !== "" && connectorOf(connection2) === requirement.connectorId
2788
+ );
2789
+ if (candidates.length === 0) return void 0;
2790
+ if (requirement.name !== "") {
2791
+ const named = candidates.filter((connection2) => connection2.name === requirement.name);
2792
+ if (named.length === 1) return named[0].id;
2715
2793
  }
2716
- return blockers;
2794
+ return candidates.length === 1 ? candidates[0].id : void 0;
2717
2795
  }
2718
- function toPortable(workflow, projectPath) {
2796
+ function toPortable(workflow, projectPath, connections = []) {
2719
2797
  const slug = slugify(workflow.name);
2798
+ const requires = [];
2720
2799
  const nodes = workflow.nodes.map((node) => {
2721
2800
  const config = { ...node.config };
2801
+ if (node.type === "trigger" && config.triggerType === "webhook") config.token = "";
2722
2802
  if (node.type === "launchAgent" || node.type === "script") {
2723
- for (const key of ["projectPath", "cwd", "existingWorktreePath"]) {
2724
- const value = config[key];
2803
+ for (const key2 of ["projectPath", "cwd", "existingWorktreePath"]) {
2804
+ const value = config[key2];
2725
2805
  if (typeof value === "string" && value) {
2726
- config[key] = replacePath(value, projectPath);
2806
+ config[key2] = replacePath(value, projectPath);
2727
2807
  }
2728
2808
  }
2729
2809
  if (typeof config.projectName === "string" && config.projectName) {
2730
2810
  config[`projectName`] = PROJECT_NAME_TOKEN;
2731
2811
  }
2732
2812
  delete config.remoteHostId;
2813
+ delete config.secretsFrom;
2814
+ }
2815
+ const key = boundConnectionKey(node, config);
2816
+ const bound = key === null ? "" : config[key];
2817
+ const unbound = key !== null && key !== "profileConnectionId" && bound === "";
2818
+ if (key !== null && (typeof bound === "string" && bound !== "" || unbound)) {
2819
+ const source2 = connections.find((connection2) => connection2.id === bound);
2820
+ const event = config.event;
2821
+ const declared = config.connectorId;
2822
+ requires.push(
2823
+ key === "profileConnectionId" ? { kind: "httpProfile", nodeId: node.id, name: source2?.name ?? "" } : {
2824
+ kind: "connection",
2825
+ nodeId: node.id,
2826
+ connectorId: source2 ? connectorOf(source2) : typeof declared === "string" ? declared : "",
2827
+ name: source2?.name ?? "",
2828
+ ...typeof event === "string" && event !== "" && { event }
2829
+ }
2830
+ );
2831
+ if (key === "profileConnectionId") delete config[key];
2832
+ else config[key] = "";
2733
2833
  }
2734
2834
  return { ...node, config };
2735
2835
  });
@@ -2740,6 +2840,7 @@ function toPortable(workflow, projectPath) {
2740
2840
  ...workflow.icon && { icon: workflow.icon },
2741
2841
  ...workflow.iconColor && { iconColor: workflow.iconColor },
2742
2842
  ...workflow.staggerDelayMs !== void 0 && { staggerDelayMs: workflow.staggerDelayMs },
2843
+ ...requires.length > 0 && { requires },
2743
2844
  nodes,
2744
2845
  edges: workflow.edges
2745
2846
  };
@@ -2750,17 +2851,38 @@ function normalizeForCompare(p) {
2750
2851
  function replacePath(value, projectPath) {
2751
2852
  const v = normalizeForCompare(value);
2752
2853
  const root = normalizeForCompare(projectPath);
2854
+ if (root === "") return value;
2753
2855
  if (v === root) return PROJECT_PATH_TOKEN;
2754
2856
  if (v.startsWith(`${root}/`)) return `${PROJECT_PATH_TOKEN}/${v.slice(root.length + 1)}`;
2755
2857
  return value;
2756
2858
  }
2757
- function fromPortable(portable, bundle, project) {
2859
+ function unresolvedRequirements(portable, connections) {
2860
+ const present = new Set(portable.nodes.map((node) => node.id));
2861
+ return (portable.requires ?? []).filter(
2862
+ (requirement) => present.has(requirement.nodeId) && resolveRequirement(requirement, connections) === void 0
2863
+ );
2864
+ }
2865
+ function fromPortable(portable, bundle, project, connections = [], mintToken = () => crypto.randomUUID()) {
2866
+ const bindings = /* @__PURE__ */ new Map();
2867
+ for (const requirement of portable.requires ?? []) {
2868
+ bindings.set(requirement.nodeId, [...bindings.get(requirement.nodeId) ?? [], requirement]);
2869
+ }
2758
2870
  const nodes = portable.nodes.map((node) => {
2759
2871
  const config = { ...node.config };
2872
+ delete config.secretsFrom;
2760
2873
  for (const [key, value] of Object.entries(config)) {
2761
2874
  if (typeof value !== "string") continue;
2762
2875
  config[key] = value.split(PROJECT_PATH_TOKEN).join(project.path.replace(/[/\\]+$/, "")).split(PROJECT_NAME_TOKEN).join(project.name);
2763
2876
  }
2877
+ for (const requirement of bindings.get(node.id) ?? []) {
2878
+ const resolved = resolveRequirement(requirement, connections);
2879
+ if (resolved === void 0) continue;
2880
+ if (requirement.kind === "httpProfile") config.profileConnectionId = resolved;
2881
+ else config.connectionId = resolved;
2882
+ }
2883
+ if (node.type === "trigger" && config.triggerType === "webhook" && !config.token) {
2884
+ config.token = mintToken();
2885
+ }
2764
2886
  return { ...node, config };
2765
2887
  });
2766
2888
  return {
@@ -2768,7 +2890,9 @@ function fromPortable(portable, bundle, project) {
2768
2890
  name: portable.name,
2769
2891
  icon: portable.icon ?? "Zap",
2770
2892
  iconColor: portable.iconColor ?? "#6366f1",
2771
- enabled: true,
2893
+ // A file cannot ask to be running: a dropped cron workflow would start
2894
+ // firing before anyone had read it. Callers restore what they had.
2895
+ enabled: false,
2772
2896
  ...portable.staggerDelayMs !== void 0 && { staggerDelayMs: portable.staggerDelayMs },
2773
2897
  nodes,
2774
2898
  edges: portable.edges
@@ -2891,6 +3015,18 @@ var triggerConfigSchema = z5.union([
2891
3015
  projectFilter: V.name.optional(),
2892
3016
  fromStatus: z5.enum(["todo", "in_progress", "in_review", "done", "cancelled"]).optional(),
2893
3017
  toStatus: z5.enum(["todo", "in_progress", "in_review", "done", "cancelled"]).optional()
3018
+ }),
3019
+ z5.object({
3020
+ triggerType: z5.literal("connectorPoll"),
3021
+ connectionId: V.id,
3022
+ event: V.shortText,
3023
+ cron: V.shortText,
3024
+ timezone: V.shortText.optional()
3025
+ }),
3026
+ z5.object({
3027
+ triggerType: z5.literal("webhook"),
3028
+ method: z5.enum(["POST", "GET"]),
3029
+ token: V.shortText
2894
3030
  })
2895
3031
  ]);
2896
3032
  var nodeSchema = z5.object({
@@ -2905,6 +3041,7 @@ var nodeSchema = z5.object({
2905
3041
  "approval",
2906
3042
  "createTaskFromItem",
2907
3043
  "callConnectorAction",
3044
+ "httpRequest",
2908
3045
  "loop"
2909
3046
  ]),
2910
3047
  label: V.shortText,
@@ -2966,7 +3103,7 @@ function buildGraphFromFlat(trigger, actions) {
2966
3103
  const nodes = [];
2967
3104
  const edges = [];
2968
3105
  const triggerNode = {
2969
- id: crypto2.randomUUID(),
3106
+ id: crypto3.randomUUID(),
2970
3107
  type: "trigger",
2971
3108
  label: trigger.triggerType === "manual" ? "Manual Trigger" : trigger.triggerType === "once" ? "Schedule (Once)" : trigger.triggerType === "recurring" ? "Schedule (Recurring)" : trigger.triggerType === "taskCreated" ? "When Task Created" : trigger.triggerType === "taskStatusChanged" ? "When Task Status Changes" : "Trigger",
2972
3109
  config: trigger,
@@ -2977,7 +3114,7 @@ function buildGraphFromFlat(trigger, actions) {
2977
3114
  const NODE_GAP = 140;
2978
3115
  for (let i = 0; i < actions.length; i++) {
2979
3116
  const action = actions[i];
2980
- const nodeId = crypto2.randomUUID();
3117
+ const nodeId = crypto3.randomUUID();
2981
3118
  nodes.push({
2982
3119
  id: nodeId,
2983
3120
  type: "launchAgent",
@@ -2986,7 +3123,7 @@ function buildGraphFromFlat(trigger, actions) {
2986
3123
  position: { x: 0, y: (i + 1) * NODE_GAP }
2987
3124
  });
2988
3125
  edges.push({
2989
- id: crypto2.randomUUID(),
3126
+ id: crypto3.randomUUID(),
2990
3127
  source: prevId,
2991
3128
  target: nodeId
2992
3129
  });
@@ -3063,6 +3200,13 @@ function resolveWorkflowId(args) {
3063
3200
  }
3064
3201
  return { id };
3065
3202
  }
3203
+ async function listPortableConnections() {
3204
+ try {
3205
+ return await rpcCall("connection:list", { connectorId: void 0 });
3206
+ } catch {
3207
+ return [];
3208
+ }
3209
+ }
3066
3210
  function registerWorkflowTools(server) {
3067
3211
  server.tool(
3068
3212
  "list_workflows",
@@ -3115,7 +3259,7 @@ function registerWorkflowTools(server) {
3115
3259
  edges = graph.edges;
3116
3260
  }
3117
3261
  const workflow = {
3118
- id: crypto2.randomUUID(),
3262
+ id: crypto3.randomUUID(),
3119
3263
  name: args.name,
3120
3264
  icon: args.icon ?? "Zap",
3121
3265
  iconColor: args.icon_color ?? "#6366f1",
@@ -3385,7 +3529,7 @@ Run history: list_workflow_runs with workflow_id ${args.workflow_id}`
3385
3529
  );
3386
3530
  server.tool(
3387
3531
  "export_workflow",
3388
- "Export a workflow as a portable file you can commit beside the code it drives. Absolute paths become {{project.path}} and the local remote-host binding is dropped, so it runs on another machine after import. Refuses a workflow bound to a connector connection, whose id means nothing elsewhere.",
3532
+ "Export a workflow as a portable file you can commit beside the code it drives. Absolute paths become {{project.path}} and the local remote-host binding is dropped, so it runs on another machine after import. Connections are dropped too and recorded as requirements the importing machine rebinds by connector and name.",
3389
3533
  {
3390
3534
  workflow_id: V.id.optional().describe("Workflow ID (from list_workflows)"),
3391
3535
  id: V.id.optional().describe("Deprecated alias for workflow_id")
@@ -3402,18 +3546,6 @@ Run history: list_workflow_runs with workflow_id ${args.workflow_id}`
3402
3546
  isError: true
3403
3547
  };
3404
3548
  }
3405
- const blockers = portabilityBlockers(workflow);
3406
- if (blockers.length > 0) {
3407
- return {
3408
- content: [
3409
- {
3410
- type: "text",
3411
- text: `Error: "${workflow.name}" cannot be exported portably because ` + blockers.join("; ") + ". Rebuild those steps without the connection, or keep this workflow local."
3412
- }
3413
- ],
3414
- isError: true
3415
- };
3416
- }
3417
3549
  const projects = await dbListProjects();
3418
3550
  const projectName = workflow.nodes.map((n) => n.config.projectName).find((name) => typeof name === "string" && name.length > 0);
3419
3551
  const project = projects.find((p) => p.name === projectName);
@@ -3428,15 +3560,20 @@ Run history: list_workflow_runs with workflow_id ${args.workflow_id}`
3428
3560
  isError: true
3429
3561
  };
3430
3562
  }
3431
- const portable = toPortable(workflow, project.path);
3563
+ const portable = toPortable(workflow, project.path, await listPortableConnections());
3432
3564
  const residual = residualAbsolutePaths(portable);
3565
+ const unnamed = (portable.requires ?? []).filter(
3566
+ (requirement) => requirement.kind === "connection" && requirement.connectorId === ""
3567
+ );
3433
3568
  return {
3434
3569
  content: [
3435
3570
  {
3436
3571
  type: "text",
3437
3572
  text: JSON.stringify(portable, null, 2) + (residual.length > 0 ? `
3438
3573
 
3439
- Warning: these still hold a machine-specific path and will not travel: ${residual.join(", ")}` : "")
3574
+ Warning: these still hold a machine-specific path and will not travel: ${residual.join(", ")}` : "") + (unnamed.length > 0 ? `
3575
+
3576
+ Warning: ${unnamed.length} step(s) point at a connection this install could not name, so an import cannot rebind them automatically.` : "")
3440
3577
  }
3441
3578
  ]
3442
3579
  };
@@ -3444,7 +3581,7 @@ Warning: these still hold a machine-specific path and will not travel: ${residua
3444
3581
  );
3445
3582
  server.tool(
3446
3583
  "import_workflow",
3447
- "Import a workflow exported by export_workflow, resolving {{project.path}} and {{project.name}} against a registered project. The id is derived from the bundle and the workflow's slug, so importing the same file again updates it in place instead of creating a duplicate.",
3584
+ "Import a workflow exported by export_workflow, resolving {{project.path}} and {{project.name}} against a registered project. Recorded connection requirements are rebound when this machine has one unambiguous match, and reported as still to connect otherwise. The id is derived from the bundle and the workflow's slug, so importing the same file again updates it in place instead of creating a duplicate.",
3448
3585
  {
3449
3586
  workflow: z5.string().max(5e5).describe("The exported workflow JSON"),
3450
3587
  project_name: V.name.describe("Registered project to resolve paths against"),
@@ -3504,38 +3641,38 @@ Warning: these still hold a machine-specific path and will not travel: ${residua
3504
3641
  };
3505
3642
  }
3506
3643
  const bundle = args.bundle ?? slugify(project.name);
3507
- const definition = fromPortable(
3508
- { ...parsed, slug: parsed.slug ?? slugify(parsed.name) },
3644
+ const portable = { ...parsed, slug: parsed.slug ?? slugify(parsed.name) };
3645
+ const connections = await listPortableConnections();
3646
+ const resolved = fromPortable(
3647
+ portable,
3509
3648
  bundle,
3510
3649
  {
3511
3650
  name: project.name,
3512
3651
  path: project.path
3513
- }
3652
+ },
3653
+ connections
3514
3654
  );
3515
- const blockers = portabilityBlockers(definition);
3516
- if (blockers.length > 0) {
3517
- return {
3518
- content: [
3519
- {
3520
- type: "text",
3521
- text: `Error: this workflow cannot be imported because ${blockers.join("; ")}.`
3522
- }
3523
- ],
3524
- isError: true
3525
- };
3526
- }
3527
- const existing = (await dbListWorkflows()).find((w) => w.id === definition.id);
3655
+ const unresolved = unresolvedRequirements(portable, connections);
3656
+ const known = await dbListWorkflows();
3657
+ const id = importedWorkflowIdFor(bundle, portable.slug, portable.name, known);
3658
+ const existing = known.find((w) => w.id === id);
3659
+ const definition = { ...resolved, id, enabled: existing ? existing.enabled : false };
3528
3660
  if (existing) {
3529
3661
  await dbUpdateWorkflow(definition.id, definition);
3530
3662
  } else {
3531
3663
  await dbInsertWorkflow(definition);
3532
3664
  }
3533
3665
  dbSignalChange();
3666
+ const pending = unresolved.map(
3667
+ (requirement) => requirement.kind === "httpProfile" ? `${requirement.nodeId} needs an HTTP profile${requirement.name ? ` like "${requirement.name}"` : ""}` : `${requirement.nodeId} needs a ${requirement.connectorId || "connector"} connection${requirement.name ? ` like "${requirement.name}"` : ""}`
3668
+ ).join("; ");
3534
3669
  return {
3535
3670
  content: [
3536
3671
  {
3537
3672
  type: "text",
3538
- text: `${existing ? "Updated" : "Imported"} "${definition.name}" as ${definition.id}, resolved against ${project.path}`
3673
+ text: `${existing ? "Updated" : "Imported"} "${definition.name}" as ${definition.id}, resolved against ${project.path}` + (existing || definition.enabled ? "" : ". It is disabled; enable it when ready") + (pending ? `
3674
+
3675
+ Still to connect: ${pending}` : "")
3539
3676
  }
3540
3677
  ]
3541
3678
  };
@@ -3556,7 +3693,7 @@ function registerConfigTools(server) {
3556
3693
  }
3557
3694
 
3558
3695
  // src/tools/workspaces.ts
3559
- import crypto3 from "crypto";
3696
+ import crypto4 from "crypto";
3560
3697
  import { z as z6 } from "zod";
3561
3698
  function registerWorkspaceTools(server) {
3562
3699
  server.tool("list_workspaces", "List all workspaces", async () => {
@@ -3575,7 +3712,7 @@ function registerWorkspaceTools(server) {
3575
3712
  const existing = await dbListWorkspaces();
3576
3713
  const maxOrder = existing.reduce((max, w) => Math.max(max, w.order), 0);
3577
3714
  const workspace = {
3578
- id: crypto3.randomUUID(),
3715
+ id: crypto4.randomUUID(),
3579
3716
  name: args.name,
3580
3717
  order: maxOrder + 1,
3581
3718
  ...args.icon && { icon: args.icon },
@@ -3766,10 +3903,13 @@ function registerConnectorTools(server) {
3766
3903
  );
3767
3904
  server.tool(
3768
3905
  "install_connector",
3769
- "Install a connector from the catalog or from an npm package, creating a connection ready to poll. Call list_connectors for catalog ids and inspect_connector_package to see which environment variables are needed. Secrets cannot be set this way \u2014 see the error it returns if the connector requires one.",
3906
+ "Install a connector from a pack file, from the catalog, or from an npm package, creating a connection ready to poll. Call list_connectors for catalog ids and inspect_connector_package to see which environment variables are needed. Secrets cannot be set this way \u2014 see the error it returns if the connector requires one.",
3770
3907
  {
3771
3908
  connector_id: V.id.optional().describe("Catalog connector id (from list_connectors). Use this or package."),
3772
3909
  package: V.shortText.optional().describe("npm package name or launch command"),
3910
+ pack_path: V.shortText.optional().describe(
3911
+ "Path to a .vorn.tgz pack to install first. It is verified and copied to disk, and the connection then launches those files rather than resolving a package."
3912
+ ),
3773
3913
  name: V.title.optional().describe("Connection name (defaults to the connector name)"),
3774
3914
  project: V.name.optional().describe("Vorn project tasks should be created in"),
3775
3915
  trigger: V.shortText.optional().describe("Trigger type to configure (defaults to the first the connector offers)"),
@@ -3784,8 +3924,17 @@ function registerConnectorTools(server) {
3784
3924
  `No connector "${args.connector_id}" in the catalog. Known: ${catalog.map((c) => c.id).join(", ") || "(none)"}. To install something not in the catalog, pass \`package\` instead.`
3785
3925
  );
3786
3926
  }
3787
- const target = entry ? entry.launch : args.package;
3788
- if (!target) return failure("Provide either connector_id or package.");
3927
+ let installed;
3928
+ if (args.pack_path) {
3929
+ const outcome = await rpcCall("connector:installPack", {
3930
+ kind: "file",
3931
+ path: args.pack_path
3932
+ });
3933
+ if (!outcome.ok) return failure(`The pack was refused: ${outcome.error}`);
3934
+ installed = outcome.pack;
3935
+ }
3936
+ const target = installed ? packLaunch(installed) : entry?.launch ?? args.package;
3937
+ if (!target) return failure("Provide either connector_id, package, or pack_path.");
3789
3938
  const result = await probe(target);
3790
3939
  if (!result.ok) return failure(result.error);
3791
3940
  const manifest = result.manifest;
@@ -3837,6 +3986,7 @@ function registerConnectorTools(server) {
3837
3986
  installed: manifest.name,
3838
3987
  connectionId: connection2.id,
3839
3988
  trigger: trigger?.type,
3989
+ ...installed && { version: installed.version, path: installed.path },
3840
3990
  note: "Poll it now with backfill_connection, or reference it from a workflow."
3841
3991
  });
3842
3992
  }
@@ -3874,6 +4024,9 @@ function registerConnectorTools(server) {
3874
4024
  }
3875
4025
  );
3876
4026
  }
4027
+ function packLaunch(pack) {
4028
+ return { command: "node", args: [`${pack.path}/index.js`] };
4029
+ }
3877
4030
  async function probe(target) {
3878
4031
  const launch = typeof target === "string" ? parseLaunch(target) : target;
3879
4032
  return rpcCall("connector:probeSdk", launch, PROBE_TIMEOUT_MS);
@@ -3895,7 +4048,7 @@ function plural(count, word) {
3895
4048
  }
3896
4049
 
3897
4050
  // src/tools/browser.ts
3898
- import crypto4 from "crypto";
4051
+ import crypto5 from "crypto";
3899
4052
  import { z as z8 } from "zod";
3900
4053
  function sessionId(env = process.env) {
3901
4054
  const id = env.VORN_SESSION_ID;
@@ -3922,7 +4075,7 @@ function source(label) {
3922
4075
  return label.includes("WEB PAGE") || label.includes("BROWSER") ? "page" : "device";
3923
4076
  }
3924
4077
  function pageResult(data, label = "WEB PAGE CONTENT") {
3925
- const nonce = crypto4.randomUUID();
4078
+ const nonce = crypto5.randomUUID();
3926
4079
  return {
3927
4080
  content: [
3928
4081
  {
@@ -4370,7 +4523,7 @@ console.warn = (...args) => _origError("[mcp:warn]", ...args);
4370
4523
  console.error = (...args) => _origError("[mcp:error]", ...args);
4371
4524
  async function main() {
4372
4525
  configManager.init();
4373
- const version = true ? "0.6.1" : createRequire(import.meta.url)("../package.json").version;
4526
+ const version = true ? "0.7.0-beta.10" : createRequire(import.meta.url)("../package.json").version;
4374
4527
  const server = createMcpServer(version);
4375
4528
  const transport = new StdioServerTransport();
4376
4529
  await server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vornrun/mcp",
3
- "version": "0.6.1",
3
+ "version": "0.7.0-beta.10",
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.6.1",
42
- "@vornrun/shared": "0.6.1",
41
+ "@vornrun/server": "0.7.0-beta.10",
42
+ "@vornrun/shared": "0.7.0-beta.10",
43
43
  "tsup": "^8.5.1",
44
44
  "tsx": "^4.23.1",
45
45
  "typescript": "^6.0.3"