@vornrun/mcp 0.7.0-beta.1 → 0.7.0-beta.11
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 +251 -92
- 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,41 +2749,63 @@ 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
|
+
if (node.type === "script") return "secretsFrom";
|
|
2784
|
+
return null;
|
|
2785
|
+
}
|
|
2786
|
+
var OPTIONAL_CONNECTION_KEYS = /* @__PURE__ */ new Set(["profileConnectionId", "secretsFrom"]);
|
|
2787
|
+
function resolveRequirement(requirement, connections) {
|
|
2788
|
+
const candidates = connections.filter(
|
|
2789
|
+
(connection2) => requirement.kind === "httpProfile" ? connectorOf(connection2) === HTTP_PROFILE_CONNECTOR : requirement.connectorId !== "" && connectorOf(connection2) === requirement.connectorId
|
|
2790
|
+
);
|
|
2791
|
+
if (candidates.length === 0) return void 0;
|
|
2792
|
+
if (requirement.name !== "") {
|
|
2793
|
+
const named = candidates.filter((connection2) => connection2.name === requirement.name);
|
|
2794
|
+
if (named.length === 1) return named[0].id;
|
|
2715
2795
|
}
|
|
2716
|
-
return
|
|
2796
|
+
return candidates.length === 1 ? candidates[0].id : void 0;
|
|
2717
2797
|
}
|
|
2718
|
-
function toPortable(workflow, projectPath) {
|
|
2798
|
+
function toPortable(workflow, projectPath, connections = []) {
|
|
2719
2799
|
const slug = slugify(workflow.name);
|
|
2800
|
+
const requires = [];
|
|
2720
2801
|
const nodes = workflow.nodes.map((node) => {
|
|
2721
2802
|
const config = { ...node.config };
|
|
2803
|
+
if (node.type === "trigger" && config.triggerType === "webhook") config.token = "";
|
|
2722
2804
|
if (node.type === "launchAgent" || node.type === "script") {
|
|
2723
|
-
for (const
|
|
2724
|
-
const value = config[
|
|
2805
|
+
for (const key2 of ["projectPath", "cwd", "existingWorktreePath"]) {
|
|
2806
|
+
const value = config[key2];
|
|
2725
2807
|
if (typeof value === "string" && value) {
|
|
2726
|
-
config[
|
|
2808
|
+
config[key2] = replacePath(value, projectPath);
|
|
2727
2809
|
}
|
|
2728
2810
|
}
|
|
2729
2811
|
if (typeof config.projectName === "string" && config.projectName) {
|
|
@@ -2731,6 +2813,26 @@ function toPortable(workflow, projectPath) {
|
|
|
2731
2813
|
}
|
|
2732
2814
|
delete config.remoteHostId;
|
|
2733
2815
|
}
|
|
2816
|
+
const key = boundConnectionKey(node, config);
|
|
2817
|
+
const bound = key === null ? "" : config[key];
|
|
2818
|
+
const unbound = key !== null && !OPTIONAL_CONNECTION_KEYS.has(key) && bound === "";
|
|
2819
|
+
if (key !== null && (typeof bound === "string" && bound !== "" || unbound)) {
|
|
2820
|
+
const source2 = connections.find((connection2) => connection2.id === bound);
|
|
2821
|
+
const event = config.event;
|
|
2822
|
+
const declared = config.connectorId;
|
|
2823
|
+
requires.push(
|
|
2824
|
+
key === "profileConnectionId" ? { kind: "httpProfile", nodeId: node.id, name: source2?.name ?? "" } : {
|
|
2825
|
+
kind: "connection",
|
|
2826
|
+
nodeId: node.id,
|
|
2827
|
+
connectorId: source2 ? connectorOf(source2) : typeof declared === "string" ? declared : "",
|
|
2828
|
+
name: source2?.name ?? "",
|
|
2829
|
+
...typeof event === "string" && event !== "" && { event },
|
|
2830
|
+
...key === "secretsFrom" && { key }
|
|
2831
|
+
}
|
|
2832
|
+
);
|
|
2833
|
+
if (OPTIONAL_CONNECTION_KEYS.has(key)) delete config[key];
|
|
2834
|
+
else config[key] = "";
|
|
2835
|
+
}
|
|
2734
2836
|
return { ...node, config };
|
|
2735
2837
|
});
|
|
2736
2838
|
return {
|
|
@@ -2740,6 +2842,7 @@ function toPortable(workflow, projectPath) {
|
|
|
2740
2842
|
...workflow.icon && { icon: workflow.icon },
|
|
2741
2843
|
...workflow.iconColor && { iconColor: workflow.iconColor },
|
|
2742
2844
|
...workflow.staggerDelayMs !== void 0 && { staggerDelayMs: workflow.staggerDelayMs },
|
|
2845
|
+
...requires.length > 0 && { requires },
|
|
2743
2846
|
nodes,
|
|
2744
2847
|
edges: workflow.edges
|
|
2745
2848
|
};
|
|
@@ -2750,16 +2853,40 @@ function normalizeForCompare(p) {
|
|
|
2750
2853
|
function replacePath(value, projectPath) {
|
|
2751
2854
|
const v = normalizeForCompare(value);
|
|
2752
2855
|
const root = normalizeForCompare(projectPath);
|
|
2856
|
+
if (root === "") return value;
|
|
2753
2857
|
if (v === root) return PROJECT_PATH_TOKEN;
|
|
2754
2858
|
if (v.startsWith(`${root}/`)) return `${PROJECT_PATH_TOKEN}/${v.slice(root.length + 1)}`;
|
|
2755
2859
|
return value;
|
|
2756
2860
|
}
|
|
2757
|
-
function
|
|
2861
|
+
function unresolvedRequirements(portable, connections) {
|
|
2862
|
+
const present = new Set(portable.nodes.map((node) => node.id));
|
|
2863
|
+
return (portable.requires ?? []).filter(
|
|
2864
|
+
(requirement) => present.has(requirement.nodeId) && (bindsOnlyByHand(requirement) || resolveRequirement(requirement, connections) === void 0)
|
|
2865
|
+
);
|
|
2866
|
+
}
|
|
2867
|
+
function bindsOnlyByHand(requirement) {
|
|
2868
|
+
return requirement.kind === "connection" && requirement.key === "secretsFrom";
|
|
2869
|
+
}
|
|
2870
|
+
function fromPortable(portable, bundle, project, connections = [], mintToken = () => crypto.randomUUID()) {
|
|
2871
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
2872
|
+
for (const requirement of portable.requires ?? []) {
|
|
2873
|
+
bindings.set(requirement.nodeId, [...bindings.get(requirement.nodeId) ?? [], requirement]);
|
|
2874
|
+
}
|
|
2758
2875
|
const nodes = portable.nodes.map((node) => {
|
|
2759
2876
|
const config = { ...node.config };
|
|
2760
|
-
|
|
2877
|
+
const key = boundConnectionKey(node, config);
|
|
2878
|
+
if (key !== null && OPTIONAL_CONNECTION_KEYS.has(key)) delete config[key];
|
|
2879
|
+
for (const [key2, value] of Object.entries(config)) {
|
|
2761
2880
|
if (typeof value !== "string") continue;
|
|
2762
|
-
config[
|
|
2881
|
+
config[key2] = value.split(PROJECT_PATH_TOKEN).join(project.path.replace(/[/\\]+$/, "")).split(PROJECT_NAME_TOKEN).join(project.name);
|
|
2882
|
+
}
|
|
2883
|
+
for (const requirement of bindings.get(node.id) ?? []) {
|
|
2884
|
+
if (bindsOnlyByHand(requirement) || key === null) continue;
|
|
2885
|
+
const resolved = resolveRequirement(requirement, connections);
|
|
2886
|
+
if (resolved !== void 0) config[key] = resolved;
|
|
2887
|
+
}
|
|
2888
|
+
if (node.type === "trigger" && config.triggerType === "webhook" && !config.token) {
|
|
2889
|
+
config.token = mintToken();
|
|
2763
2890
|
}
|
|
2764
2891
|
return { ...node, config };
|
|
2765
2892
|
});
|
|
@@ -2768,7 +2895,9 @@ function fromPortable(portable, bundle, project) {
|
|
|
2768
2895
|
name: portable.name,
|
|
2769
2896
|
icon: portable.icon ?? "Zap",
|
|
2770
2897
|
iconColor: portable.iconColor ?? "#6366f1",
|
|
2771
|
-
|
|
2898
|
+
// A file cannot ask to be running: a dropped cron workflow would start
|
|
2899
|
+
// firing before anyone had read it. Callers restore what they had.
|
|
2900
|
+
enabled: false,
|
|
2772
2901
|
...portable.staggerDelayMs !== void 0 && { staggerDelayMs: portable.staggerDelayMs },
|
|
2773
2902
|
nodes,
|
|
2774
2903
|
edges: portable.edges
|
|
@@ -2891,6 +3020,18 @@ var triggerConfigSchema = z5.union([
|
|
|
2891
3020
|
projectFilter: V.name.optional(),
|
|
2892
3021
|
fromStatus: z5.enum(["todo", "in_progress", "in_review", "done", "cancelled"]).optional(),
|
|
2893
3022
|
toStatus: z5.enum(["todo", "in_progress", "in_review", "done", "cancelled"]).optional()
|
|
3023
|
+
}),
|
|
3024
|
+
z5.object({
|
|
3025
|
+
triggerType: z5.literal("connectorPoll"),
|
|
3026
|
+
connectionId: V.id,
|
|
3027
|
+
event: V.shortText,
|
|
3028
|
+
cron: V.shortText,
|
|
3029
|
+
timezone: V.shortText.optional()
|
|
3030
|
+
}),
|
|
3031
|
+
z5.object({
|
|
3032
|
+
triggerType: z5.literal("webhook"),
|
|
3033
|
+
method: z5.enum(["POST", "GET"]),
|
|
3034
|
+
token: V.shortText
|
|
2894
3035
|
})
|
|
2895
3036
|
]);
|
|
2896
3037
|
var nodeSchema = z5.object({
|
|
@@ -2905,6 +3046,7 @@ var nodeSchema = z5.object({
|
|
|
2905
3046
|
"approval",
|
|
2906
3047
|
"createTaskFromItem",
|
|
2907
3048
|
"callConnectorAction",
|
|
3049
|
+
"httpRequest",
|
|
2908
3050
|
"loop"
|
|
2909
3051
|
]),
|
|
2910
3052
|
label: V.shortText,
|
|
@@ -2966,7 +3108,7 @@ function buildGraphFromFlat(trigger, actions) {
|
|
|
2966
3108
|
const nodes = [];
|
|
2967
3109
|
const edges = [];
|
|
2968
3110
|
const triggerNode = {
|
|
2969
|
-
id:
|
|
3111
|
+
id: crypto3.randomUUID(),
|
|
2970
3112
|
type: "trigger",
|
|
2971
3113
|
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
3114
|
config: trigger,
|
|
@@ -2977,7 +3119,7 @@ function buildGraphFromFlat(trigger, actions) {
|
|
|
2977
3119
|
const NODE_GAP = 140;
|
|
2978
3120
|
for (let i = 0; i < actions.length; i++) {
|
|
2979
3121
|
const action = actions[i];
|
|
2980
|
-
const nodeId =
|
|
3122
|
+
const nodeId = crypto3.randomUUID();
|
|
2981
3123
|
nodes.push({
|
|
2982
3124
|
id: nodeId,
|
|
2983
3125
|
type: "launchAgent",
|
|
@@ -2986,7 +3128,7 @@ function buildGraphFromFlat(trigger, actions) {
|
|
|
2986
3128
|
position: { x: 0, y: (i + 1) * NODE_GAP }
|
|
2987
3129
|
});
|
|
2988
3130
|
edges.push({
|
|
2989
|
-
id:
|
|
3131
|
+
id: crypto3.randomUUID(),
|
|
2990
3132
|
source: prevId,
|
|
2991
3133
|
target: nodeId
|
|
2992
3134
|
});
|
|
@@ -3063,6 +3205,13 @@ function resolveWorkflowId(args) {
|
|
|
3063
3205
|
}
|
|
3064
3206
|
return { id };
|
|
3065
3207
|
}
|
|
3208
|
+
async function listPortableConnections() {
|
|
3209
|
+
try {
|
|
3210
|
+
return await rpcCall("connection:list", { connectorId: void 0 });
|
|
3211
|
+
} catch {
|
|
3212
|
+
return [];
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3066
3215
|
function registerWorkflowTools(server) {
|
|
3067
3216
|
server.tool(
|
|
3068
3217
|
"list_workflows",
|
|
@@ -3115,7 +3264,7 @@ function registerWorkflowTools(server) {
|
|
|
3115
3264
|
edges = graph.edges;
|
|
3116
3265
|
}
|
|
3117
3266
|
const workflow = {
|
|
3118
|
-
id:
|
|
3267
|
+
id: crypto3.randomUUID(),
|
|
3119
3268
|
name: args.name,
|
|
3120
3269
|
icon: args.icon ?? "Zap",
|
|
3121
3270
|
iconColor: args.icon_color ?? "#6366f1",
|
|
@@ -3385,7 +3534,7 @@ Run history: list_workflow_runs with workflow_id ${args.workflow_id}`
|
|
|
3385
3534
|
);
|
|
3386
3535
|
server.tool(
|
|
3387
3536
|
"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.
|
|
3537
|
+
"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
3538
|
{
|
|
3390
3539
|
workflow_id: V.id.optional().describe("Workflow ID (from list_workflows)"),
|
|
3391
3540
|
id: V.id.optional().describe("Deprecated alias for workflow_id")
|
|
@@ -3402,18 +3551,6 @@ Run history: list_workflow_runs with workflow_id ${args.workflow_id}`
|
|
|
3402
3551
|
isError: true
|
|
3403
3552
|
};
|
|
3404
3553
|
}
|
|
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
3554
|
const projects = await dbListProjects();
|
|
3418
3555
|
const projectName = workflow.nodes.map((n) => n.config.projectName).find((name) => typeof name === "string" && name.length > 0);
|
|
3419
3556
|
const project = projects.find((p) => p.name === projectName);
|
|
@@ -3428,15 +3565,20 @@ Run history: list_workflow_runs with workflow_id ${args.workflow_id}`
|
|
|
3428
3565
|
isError: true
|
|
3429
3566
|
};
|
|
3430
3567
|
}
|
|
3431
|
-
const portable = toPortable(workflow, project.path);
|
|
3568
|
+
const portable = toPortable(workflow, project.path, await listPortableConnections());
|
|
3432
3569
|
const residual = residualAbsolutePaths(portable);
|
|
3570
|
+
const unnamed = (portable.requires ?? []).filter(
|
|
3571
|
+
(requirement) => requirement.kind === "connection" && requirement.connectorId === ""
|
|
3572
|
+
);
|
|
3433
3573
|
return {
|
|
3434
3574
|
content: [
|
|
3435
3575
|
{
|
|
3436
3576
|
type: "text",
|
|
3437
3577
|
text: JSON.stringify(portable, null, 2) + (residual.length > 0 ? `
|
|
3438
3578
|
|
|
3439
|
-
Warning: these still hold a machine-specific path and will not travel: ${residual.join(", ")}` : "")
|
|
3579
|
+
Warning: these still hold a machine-specific path and will not travel: ${residual.join(", ")}` : "") + (unnamed.length > 0 ? `
|
|
3580
|
+
|
|
3581
|
+
Warning: ${unnamed.length} step(s) point at a connection this install could not name, so an import cannot rebind them automatically.` : "")
|
|
3440
3582
|
}
|
|
3441
3583
|
]
|
|
3442
3584
|
};
|
|
@@ -3444,7 +3586,7 @@ Warning: these still hold a machine-specific path and will not travel: ${residua
|
|
|
3444
3586
|
);
|
|
3445
3587
|
server.tool(
|
|
3446
3588
|
"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.",
|
|
3589
|
+
"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
3590
|
{
|
|
3449
3591
|
workflow: z5.string().max(5e5).describe("The exported workflow JSON"),
|
|
3450
3592
|
project_name: V.name.describe("Registered project to resolve paths against"),
|
|
@@ -3504,38 +3646,38 @@ Warning: these still hold a machine-specific path and will not travel: ${residua
|
|
|
3504
3646
|
};
|
|
3505
3647
|
}
|
|
3506
3648
|
const bundle = args.bundle ?? slugify(project.name);
|
|
3507
|
-
const
|
|
3508
|
-
|
|
3649
|
+
const portable = { ...parsed, slug: parsed.slug ?? slugify(parsed.name) };
|
|
3650
|
+
const connections = await listPortableConnections();
|
|
3651
|
+
const resolved = fromPortable(
|
|
3652
|
+
portable,
|
|
3509
3653
|
bundle,
|
|
3510
3654
|
{
|
|
3511
3655
|
name: project.name,
|
|
3512
3656
|
path: project.path
|
|
3513
|
-
}
|
|
3657
|
+
},
|
|
3658
|
+
connections
|
|
3514
3659
|
);
|
|
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);
|
|
3660
|
+
const unresolved = unresolvedRequirements(portable, connections);
|
|
3661
|
+
const known = await dbListWorkflows();
|
|
3662
|
+
const id = importedWorkflowIdFor(bundle, portable.slug, portable.name, known);
|
|
3663
|
+
const existing = known.find((w) => w.id === id);
|
|
3664
|
+
const definition = { ...resolved, id, enabled: existing ? existing.enabled : false };
|
|
3528
3665
|
if (existing) {
|
|
3529
3666
|
await dbUpdateWorkflow(definition.id, definition);
|
|
3530
3667
|
} else {
|
|
3531
3668
|
await dbInsertWorkflow(definition);
|
|
3532
3669
|
}
|
|
3533
3670
|
dbSignalChange();
|
|
3671
|
+
const pending = unresolved.map(
|
|
3672
|
+
(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}"` : ""}`
|
|
3673
|
+
).join("; ");
|
|
3534
3674
|
return {
|
|
3535
3675
|
content: [
|
|
3536
3676
|
{
|
|
3537
3677
|
type: "text",
|
|
3538
|
-
text: `${existing ? "Updated" : "Imported"} "${definition.name}" as ${definition.id}, resolved against ${project.path}`
|
|
3678
|
+
text: `${existing ? "Updated" : "Imported"} "${definition.name}" as ${definition.id}, resolved against ${project.path}` + (existing || definition.enabled ? "" : ". It is disabled; enable it when ready") + (pending ? `
|
|
3679
|
+
|
|
3680
|
+
Still to connect: ${pending}` : "")
|
|
3539
3681
|
}
|
|
3540
3682
|
]
|
|
3541
3683
|
};
|
|
@@ -3556,7 +3698,7 @@ function registerConfigTools(server) {
|
|
|
3556
3698
|
}
|
|
3557
3699
|
|
|
3558
3700
|
// src/tools/workspaces.ts
|
|
3559
|
-
import
|
|
3701
|
+
import crypto4 from "crypto";
|
|
3560
3702
|
import { z as z6 } from "zod";
|
|
3561
3703
|
function registerWorkspaceTools(server) {
|
|
3562
3704
|
server.tool("list_workspaces", "List all workspaces", async () => {
|
|
@@ -3575,7 +3717,7 @@ function registerWorkspaceTools(server) {
|
|
|
3575
3717
|
const existing = await dbListWorkspaces();
|
|
3576
3718
|
const maxOrder = existing.reduce((max, w) => Math.max(max, w.order), 0);
|
|
3577
3719
|
const workspace = {
|
|
3578
|
-
id:
|
|
3720
|
+
id: crypto4.randomUUID(),
|
|
3579
3721
|
name: args.name,
|
|
3580
3722
|
order: maxOrder + 1,
|
|
3581
3723
|
...args.icon && { icon: args.icon },
|
|
@@ -3766,10 +3908,13 @@ function registerConnectorTools(server) {
|
|
|
3766
3908
|
);
|
|
3767
3909
|
server.tool(
|
|
3768
3910
|
"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.",
|
|
3911
|
+
"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
3912
|
{
|
|
3771
3913
|
connector_id: V.id.optional().describe("Catalog connector id (from list_connectors). Use this or package."),
|
|
3772
3914
|
package: V.shortText.optional().describe("npm package name or launch command"),
|
|
3915
|
+
pack_path: V.shortText.optional().describe(
|
|
3916
|
+
"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."
|
|
3917
|
+
),
|
|
3773
3918
|
name: V.title.optional().describe("Connection name (defaults to the connector name)"),
|
|
3774
3919
|
project: V.name.optional().describe("Vorn project tasks should be created in"),
|
|
3775
3920
|
trigger: V.shortText.optional().describe("Trigger type to configure (defaults to the first the connector offers)"),
|
|
@@ -3784,8 +3929,17 @@ function registerConnectorTools(server) {
|
|
|
3784
3929
|
`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
3930
|
);
|
|
3786
3931
|
}
|
|
3787
|
-
|
|
3788
|
-
if (
|
|
3932
|
+
let installed;
|
|
3933
|
+
if (args.pack_path) {
|
|
3934
|
+
const outcome = await rpcCall("connector:installPack", {
|
|
3935
|
+
kind: "file",
|
|
3936
|
+
path: args.pack_path
|
|
3937
|
+
});
|
|
3938
|
+
if (!outcome.ok) return failure(`The pack was refused: ${outcome.error}`);
|
|
3939
|
+
installed = outcome.pack;
|
|
3940
|
+
}
|
|
3941
|
+
const target = installed ? packLaunch(installed) : entry?.launch ?? args.package;
|
|
3942
|
+
if (!target) return failure("Provide either connector_id, package, or pack_path.");
|
|
3789
3943
|
const result = await probe(target);
|
|
3790
3944
|
if (!result.ok) return failure(result.error);
|
|
3791
3945
|
const manifest = result.manifest;
|
|
@@ -3837,6 +3991,7 @@ function registerConnectorTools(server) {
|
|
|
3837
3991
|
installed: manifest.name,
|
|
3838
3992
|
connectionId: connection2.id,
|
|
3839
3993
|
trigger: trigger?.type,
|
|
3994
|
+
...installed && { version: installed.version, path: installed.path },
|
|
3840
3995
|
note: "Poll it now with backfill_connection, or reference it from a workflow."
|
|
3841
3996
|
});
|
|
3842
3997
|
}
|
|
@@ -3874,6 +4029,9 @@ function registerConnectorTools(server) {
|
|
|
3874
4029
|
}
|
|
3875
4030
|
);
|
|
3876
4031
|
}
|
|
4032
|
+
function packLaunch(pack) {
|
|
4033
|
+
return { command: "node", args: [`${pack.path}/index.js`] };
|
|
4034
|
+
}
|
|
3877
4035
|
async function probe(target) {
|
|
3878
4036
|
const launch = typeof target === "string" ? parseLaunch(target) : target;
|
|
3879
4037
|
return rpcCall("connector:probeSdk", launch, PROBE_TIMEOUT_MS);
|
|
@@ -3895,7 +4053,7 @@ function plural(count, word) {
|
|
|
3895
4053
|
}
|
|
3896
4054
|
|
|
3897
4055
|
// src/tools/browser.ts
|
|
3898
|
-
import
|
|
4056
|
+
import crypto5 from "crypto";
|
|
3899
4057
|
import { z as z8 } from "zod";
|
|
3900
4058
|
function sessionId(env = process.env) {
|
|
3901
4059
|
const id = env.VORN_SESSION_ID;
|
|
@@ -3922,7 +4080,7 @@ function source(label) {
|
|
|
3922
4080
|
return label.includes("WEB PAGE") || label.includes("BROWSER") ? "page" : "device";
|
|
3923
4081
|
}
|
|
3924
4082
|
function pageResult(data, label = "WEB PAGE CONTENT") {
|
|
3925
|
-
const nonce =
|
|
4083
|
+
const nonce = crypto5.randomUUID();
|
|
3926
4084
|
return {
|
|
3927
4085
|
content: [
|
|
3928
4086
|
{
|
|
@@ -4177,6 +4335,7 @@ function registerDeviceTools(server) {
|
|
|
4177
4335
|
sessionId: id,
|
|
4178
4336
|
udid: args.udid
|
|
4179
4337
|
});
|
|
4338
|
+
if (!r.ok) throw new Error(r.message);
|
|
4180
4339
|
return {
|
|
4181
4340
|
content: [{ type: "text", text: `Claimed ${r.name} (${r.udid}).` }]
|
|
4182
4341
|
};
|
|
@@ -4370,7 +4529,7 @@ console.warn = (...args) => _origError("[mcp:warn]", ...args);
|
|
|
4370
4529
|
console.error = (...args) => _origError("[mcp:error]", ...args);
|
|
4371
4530
|
async function main() {
|
|
4372
4531
|
configManager.init();
|
|
4373
|
-
const version = true ? "0.7.0-beta.
|
|
4532
|
+
const version = true ? "0.7.0-beta.11" : createRequire(import.meta.url)("../package.json").version;
|
|
4374
4533
|
const server = createMcpServer(version);
|
|
4375
4534
|
const transport = new StdioServerTransport();
|
|
4376
4535
|
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.
|
|
3
|
+
"version": "0.7.0-beta.11",
|
|
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.
|
|
42
|
-
"@vornrun/shared": "0.7.0-beta.
|
|
41
|
+
"@vornrun/server": "0.7.0-beta.11",
|
|
42
|
+
"@vornrun/shared": "0.7.0-beta.11",
|
|
43
43
|
"tsup": "^8.5.1",
|
|
44
44
|
"tsx": "^4.23.1",
|
|
45
45
|
"typescript": "^6.0.3"
|