@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.
- package/dist/index.js +243 -90
- 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 =
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
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
|
|
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
|
|
2724
|
-
const value = config[
|
|
2803
|
+
for (const key2 of ["projectPath", "cwd", "existingWorktreePath"]) {
|
|
2804
|
+
const value = config[key2];
|
|
2725
2805
|
if (typeof value === "string" && value) {
|
|
2726
|
-
config[
|
|
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
|
|
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
|
-
|
|
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:
|
|
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 =
|
|
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:
|
|
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:
|
|
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.
|
|
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
|
|
3508
|
-
|
|
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
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
3788
|
-
if (
|
|
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
|
|
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 =
|
|
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.
|
|
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.
|
|
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.
|
|
42
|
-
"@vornrun/shared": "0.
|
|
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"
|