@vornrun/mcp 0.7.0-beta.7 → 0.7.0-beta.9

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 +205 -88
  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() {
@@ -900,6 +904,39 @@ function migrateSchema(d) {
900
904
  })();
901
905
  logger_default.info("[database] migrated schema to version 16 (shell working directory)");
902
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
+ }
903
940
  }
904
941
  var REVISIONED_TABLES = [
905
942
  "projects",
@@ -1615,7 +1652,7 @@ var configManager = new ConfigManager();
1615
1652
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1616
1653
 
1617
1654
  // src/tools/tasks.ts
1618
- import crypto from "crypto";
1655
+ import crypto2 from "crypto";
1619
1656
  import path4 from "path";
1620
1657
  import { z as z2 } from "zod";
1621
1658
 
@@ -2012,7 +2049,7 @@ function registerTaskTools(server) {
2012
2049
  const now = (/* @__PURE__ */ new Date()).toISOString();
2013
2050
  const status = args.status ?? "todo";
2014
2051
  const task = {
2015
- id: crypto.randomUUID(),
2052
+ id: crypto2.randomUUID(),
2016
2053
  projectName: args.project_name,
2017
2054
  title: args.title,
2018
2055
  description: args.description ?? "",
@@ -2712,47 +2749,87 @@ function registerSessionTools(server) {
2712
2749
  }
2713
2750
 
2714
2751
  // src/tools/workflows.ts
2715
- import crypto2 from "crypto";
2752
+ import crypto3 from "crypto";
2716
2753
  import { z as z5 } from "zod";
2717
2754
 
2718
- // src/workflow-portability.ts
2755
+ // ../shared/src/workflow-portability.ts
2719
2756
  var PROJECT_PATH_TOKEN = "{{project.path}}";
2720
2757
  var PROJECT_NAME_TOKEN = "{{project.name}}";
2721
2758
  var PORTABLE_FORMAT_VERSION = 1;
2759
+ var HTTP_PROFILE_CONNECTOR = "http";
2722
2760
  function importedWorkflowId(bundle, slug) {
2723
2761
  return `import:${bundle}:${slug}`;
2724
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
+ }
2725
2770
  function slugify(name) {
2726
2771
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "workflow";
2727
2772
  }
2728
- function portabilityBlockers(workflow) {
2729
- const blockers = [];
2730
- for (const node of workflow.nodes) {
2731
- const config = node.config;
2732
- if (node.type === "trigger" && config.triggerType === "connectorPoll") {
2733
- blockers.push(`the trigger polls a connector connection, which exists only on this machine`);
2734
- }
2735
- if (node.type === "callConnectorAction") {
2736
- blockers.push(`step "${node.label}" calls a connector action bound to a local connection`);
2737
- }
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;
2738
2793
  }
2739
- return blockers;
2794
+ return candidates.length === 1 ? candidates[0].id : void 0;
2740
2795
  }
2741
- function toPortable(workflow, projectPath) {
2796
+ function toPortable(workflow, projectPath, connections = []) {
2742
2797
  const slug = slugify(workflow.name);
2798
+ const requires = [];
2743
2799
  const nodes = workflow.nodes.map((node) => {
2744
2800
  const config = { ...node.config };
2801
+ if (node.type === "trigger" && config.triggerType === "webhook") config.token = "";
2745
2802
  if (node.type === "launchAgent" || node.type === "script") {
2746
- for (const key of ["projectPath", "cwd", "existingWorktreePath"]) {
2747
- const value = config[key];
2803
+ for (const key2 of ["projectPath", "cwd", "existingWorktreePath"]) {
2804
+ const value = config[key2];
2748
2805
  if (typeof value === "string" && value) {
2749
- config[key] = replacePath(value, projectPath);
2806
+ config[key2] = replacePath(value, projectPath);
2750
2807
  }
2751
2808
  }
2752
2809
  if (typeof config.projectName === "string" && config.projectName) {
2753
2810
  config[`projectName`] = PROJECT_NAME_TOKEN;
2754
2811
  }
2755
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] = "";
2756
2833
  }
2757
2834
  return { ...node, config };
2758
2835
  });
@@ -2763,6 +2840,7 @@ function toPortable(workflow, projectPath) {
2763
2840
  ...workflow.icon && { icon: workflow.icon },
2764
2841
  ...workflow.iconColor && { iconColor: workflow.iconColor },
2765
2842
  ...workflow.staggerDelayMs !== void 0 && { staggerDelayMs: workflow.staggerDelayMs },
2843
+ ...requires.length > 0 && { requires },
2766
2844
  nodes,
2767
2845
  edges: workflow.edges
2768
2846
  };
@@ -2773,17 +2851,38 @@ function normalizeForCompare(p) {
2773
2851
  function replacePath(value, projectPath) {
2774
2852
  const v = normalizeForCompare(value);
2775
2853
  const root = normalizeForCompare(projectPath);
2854
+ if (root === "") return value;
2776
2855
  if (v === root) return PROJECT_PATH_TOKEN;
2777
2856
  if (v.startsWith(`${root}/`)) return `${PROJECT_PATH_TOKEN}/${v.slice(root.length + 1)}`;
2778
2857
  return value;
2779
2858
  }
2780
- 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
+ }
2781
2870
  const nodes = portable.nodes.map((node) => {
2782
2871
  const config = { ...node.config };
2872
+ delete config.secretsFrom;
2783
2873
  for (const [key, value] of Object.entries(config)) {
2784
2874
  if (typeof value !== "string") continue;
2785
2875
  config[key] = value.split(PROJECT_PATH_TOKEN).join(project.path.replace(/[/\\]+$/, "")).split(PROJECT_NAME_TOKEN).join(project.name);
2786
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
+ }
2787
2886
  return { ...node, config };
2788
2887
  });
2789
2888
  return {
@@ -2791,7 +2890,9 @@ function fromPortable(portable, bundle, project) {
2791
2890
  name: portable.name,
2792
2891
  icon: portable.icon ?? "Zap",
2793
2892
  iconColor: portable.iconColor ?? "#6366f1",
2794
- 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,
2795
2896
  ...portable.staggerDelayMs !== void 0 && { staggerDelayMs: portable.staggerDelayMs },
2796
2897
  nodes,
2797
2898
  edges: portable.edges
@@ -3002,7 +3103,7 @@ function buildGraphFromFlat(trigger, actions) {
3002
3103
  const nodes = [];
3003
3104
  const edges = [];
3004
3105
  const triggerNode = {
3005
- id: crypto2.randomUUID(),
3106
+ id: crypto3.randomUUID(),
3006
3107
  type: "trigger",
3007
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",
3008
3109
  config: trigger,
@@ -3013,7 +3114,7 @@ function buildGraphFromFlat(trigger, actions) {
3013
3114
  const NODE_GAP = 140;
3014
3115
  for (let i = 0; i < actions.length; i++) {
3015
3116
  const action = actions[i];
3016
- const nodeId = crypto2.randomUUID();
3117
+ const nodeId = crypto3.randomUUID();
3017
3118
  nodes.push({
3018
3119
  id: nodeId,
3019
3120
  type: "launchAgent",
@@ -3022,7 +3123,7 @@ function buildGraphFromFlat(trigger, actions) {
3022
3123
  position: { x: 0, y: (i + 1) * NODE_GAP }
3023
3124
  });
3024
3125
  edges.push({
3025
- id: crypto2.randomUUID(),
3126
+ id: crypto3.randomUUID(),
3026
3127
  source: prevId,
3027
3128
  target: nodeId
3028
3129
  });
@@ -3099,6 +3200,13 @@ function resolveWorkflowId(args) {
3099
3200
  }
3100
3201
  return { id };
3101
3202
  }
3203
+ async function listPortableConnections() {
3204
+ try {
3205
+ return await rpcCall("connection:list", { connectorId: void 0 });
3206
+ } catch {
3207
+ return [];
3208
+ }
3209
+ }
3102
3210
  function registerWorkflowTools(server) {
3103
3211
  server.tool(
3104
3212
  "list_workflows",
@@ -3151,7 +3259,7 @@ function registerWorkflowTools(server) {
3151
3259
  edges = graph.edges;
3152
3260
  }
3153
3261
  const workflow = {
3154
- id: crypto2.randomUUID(),
3262
+ id: crypto3.randomUUID(),
3155
3263
  name: args.name,
3156
3264
  icon: args.icon ?? "Zap",
3157
3265
  iconColor: args.icon_color ?? "#6366f1",
@@ -3421,7 +3529,7 @@ Run history: list_workflow_runs with workflow_id ${args.workflow_id}`
3421
3529
  );
3422
3530
  server.tool(
3423
3531
  "export_workflow",
3424
- "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.",
3425
3533
  {
3426
3534
  workflow_id: V.id.optional().describe("Workflow ID (from list_workflows)"),
3427
3535
  id: V.id.optional().describe("Deprecated alias for workflow_id")
@@ -3438,18 +3546,6 @@ Run history: list_workflow_runs with workflow_id ${args.workflow_id}`
3438
3546
  isError: true
3439
3547
  };
3440
3548
  }
3441
- const blockers = portabilityBlockers(workflow);
3442
- if (blockers.length > 0) {
3443
- return {
3444
- content: [
3445
- {
3446
- type: "text",
3447
- text: `Error: "${workflow.name}" cannot be exported portably because ` + blockers.join("; ") + ". Rebuild those steps without the connection, or keep this workflow local."
3448
- }
3449
- ],
3450
- isError: true
3451
- };
3452
- }
3453
3549
  const projects = await dbListProjects();
3454
3550
  const projectName = workflow.nodes.map((n) => n.config.projectName).find((name) => typeof name === "string" && name.length > 0);
3455
3551
  const project = projects.find((p) => p.name === projectName);
@@ -3464,15 +3560,20 @@ Run history: list_workflow_runs with workflow_id ${args.workflow_id}`
3464
3560
  isError: true
3465
3561
  };
3466
3562
  }
3467
- const portable = toPortable(workflow, project.path);
3563
+ const portable = toPortable(workflow, project.path, await listPortableConnections());
3468
3564
  const residual = residualAbsolutePaths(portable);
3565
+ const unnamed = (portable.requires ?? []).filter(
3566
+ (requirement) => requirement.kind === "connection" && requirement.connectorId === ""
3567
+ );
3469
3568
  return {
3470
3569
  content: [
3471
3570
  {
3472
3571
  type: "text",
3473
3572
  text: JSON.stringify(portable, null, 2) + (residual.length > 0 ? `
3474
3573
 
3475
- 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.` : "")
3476
3577
  }
3477
3578
  ]
3478
3579
  };
@@ -3480,7 +3581,7 @@ Warning: these still hold a machine-specific path and will not travel: ${residua
3480
3581
  );
3481
3582
  server.tool(
3482
3583
  "import_workflow",
3483
- "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.",
3484
3585
  {
3485
3586
  workflow: z5.string().max(5e5).describe("The exported workflow JSON"),
3486
3587
  project_name: V.name.describe("Registered project to resolve paths against"),
@@ -3540,38 +3641,38 @@ Warning: these still hold a machine-specific path and will not travel: ${residua
3540
3641
  };
3541
3642
  }
3542
3643
  const bundle = args.bundle ?? slugify(project.name);
3543
- const definition = fromPortable(
3544
- { ...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,
3545
3648
  bundle,
3546
3649
  {
3547
3650
  name: project.name,
3548
3651
  path: project.path
3549
- }
3652
+ },
3653
+ connections
3550
3654
  );
3551
- const blockers = portabilityBlockers(definition);
3552
- if (blockers.length > 0) {
3553
- return {
3554
- content: [
3555
- {
3556
- type: "text",
3557
- text: `Error: this workflow cannot be imported because ${blockers.join("; ")}.`
3558
- }
3559
- ],
3560
- isError: true
3561
- };
3562
- }
3563
- 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 };
3564
3660
  if (existing) {
3565
3661
  await dbUpdateWorkflow(definition.id, definition);
3566
3662
  } else {
3567
3663
  await dbInsertWorkflow(definition);
3568
3664
  }
3569
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("; ");
3570
3669
  return {
3571
3670
  content: [
3572
3671
  {
3573
3672
  type: "text",
3574
- 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}` : "")
3575
3676
  }
3576
3677
  ]
3577
3678
  };
@@ -3592,7 +3693,7 @@ function registerConfigTools(server) {
3592
3693
  }
3593
3694
 
3594
3695
  // src/tools/workspaces.ts
3595
- import crypto3 from "crypto";
3696
+ import crypto4 from "crypto";
3596
3697
  import { z as z6 } from "zod";
3597
3698
  function registerWorkspaceTools(server) {
3598
3699
  server.tool("list_workspaces", "List all workspaces", async () => {
@@ -3611,7 +3712,7 @@ function registerWorkspaceTools(server) {
3611
3712
  const existing = await dbListWorkspaces();
3612
3713
  const maxOrder = existing.reduce((max, w) => Math.max(max, w.order), 0);
3613
3714
  const workspace = {
3614
- id: crypto3.randomUUID(),
3715
+ id: crypto4.randomUUID(),
3615
3716
  name: args.name,
3616
3717
  order: maxOrder + 1,
3617
3718
  ...args.icon && { icon: args.icon },
@@ -3802,10 +3903,13 @@ function registerConnectorTools(server) {
3802
3903
  );
3803
3904
  server.tool(
3804
3905
  "install_connector",
3805
- "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.",
3806
3907
  {
3807
3908
  connector_id: V.id.optional().describe("Catalog connector id (from list_connectors). Use this or package."),
3808
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
+ ),
3809
3913
  name: V.title.optional().describe("Connection name (defaults to the connector name)"),
3810
3914
  project: V.name.optional().describe("Vorn project tasks should be created in"),
3811
3915
  trigger: V.shortText.optional().describe("Trigger type to configure (defaults to the first the connector offers)"),
@@ -3820,8 +3924,17 @@ function registerConnectorTools(server) {
3820
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.`
3821
3925
  );
3822
3926
  }
3823
- const target = entry ? entry.launch : args.package;
3824
- 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.");
3825
3938
  const result = await probe(target);
3826
3939
  if (!result.ok) return failure(result.error);
3827
3940
  const manifest = result.manifest;
@@ -3873,6 +3986,7 @@ function registerConnectorTools(server) {
3873
3986
  installed: manifest.name,
3874
3987
  connectionId: connection2.id,
3875
3988
  trigger: trigger?.type,
3989
+ ...installed && { version: installed.version, path: installed.path },
3876
3990
  note: "Poll it now with backfill_connection, or reference it from a workflow."
3877
3991
  });
3878
3992
  }
@@ -3910,6 +4024,9 @@ function registerConnectorTools(server) {
3910
4024
  }
3911
4025
  );
3912
4026
  }
4027
+ function packLaunch(pack) {
4028
+ return { command: "node", args: [`${pack.path}/index.js`] };
4029
+ }
3913
4030
  async function probe(target) {
3914
4031
  const launch = typeof target === "string" ? parseLaunch(target) : target;
3915
4032
  return rpcCall("connector:probeSdk", launch, PROBE_TIMEOUT_MS);
@@ -3931,7 +4048,7 @@ function plural(count, word) {
3931
4048
  }
3932
4049
 
3933
4050
  // src/tools/browser.ts
3934
- import crypto4 from "crypto";
4051
+ import crypto5 from "crypto";
3935
4052
  import { z as z8 } from "zod";
3936
4053
  function sessionId(env = process.env) {
3937
4054
  const id = env.VORN_SESSION_ID;
@@ -3958,7 +4075,7 @@ function source(label) {
3958
4075
  return label.includes("WEB PAGE") || label.includes("BROWSER") ? "page" : "device";
3959
4076
  }
3960
4077
  function pageResult(data, label = "WEB PAGE CONTENT") {
3961
- const nonce = crypto4.randomUUID();
4078
+ const nonce = crypto5.randomUUID();
3962
4079
  return {
3963
4080
  content: [
3964
4081
  {
@@ -4406,7 +4523,7 @@ console.warn = (...args) => _origError("[mcp:warn]", ...args);
4406
4523
  console.error = (...args) => _origError("[mcp:error]", ...args);
4407
4524
  async function main() {
4408
4525
  configManager.init();
4409
- const version = true ? "0.7.0-beta.7" : createRequire(import.meta.url)("../package.json").version;
4526
+ const version = true ? "0.7.0-beta.9" : createRequire(import.meta.url)("../package.json").version;
4410
4527
  const server = createMcpServer(version);
4411
4528
  const transport = new StdioServerTransport();
4412
4529
  await server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vornrun/mcp",
3
- "version": "0.7.0-beta.7",
3
+ "version": "0.7.0-beta.9",
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.7.0-beta.7",
42
- "@vornrun/shared": "0.7.0-beta.7",
41
+ "@vornrun/server": "0.7.0-beta.9",
42
+ "@vornrun/shared": "0.7.0-beta.9",
43
43
  "tsup": "^8.5.1",
44
44
  "tsx": "^4.23.1",
45
45
  "typescript": "^6.0.3"