@vornrun/mcp 0.7.1-beta.4 → 0.7.1-beta.5
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 +189 -61
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import "module";
|
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
6
|
|
|
7
7
|
// ../server/src/config-manager.ts
|
|
8
|
-
import
|
|
8
|
+
import fs4 from "fs";
|
|
9
9
|
|
|
10
10
|
// ../shared/src/agent-defaults.ts
|
|
11
11
|
var DEFAULT_AGENT_COMMANDS = {
|
|
@@ -34,9 +34,9 @@ var DEFAULT_AGENT_COMMANDS = {
|
|
|
34
34
|
|
|
35
35
|
// ../server/src/database.ts
|
|
36
36
|
import Database from "libsql";
|
|
37
|
-
import
|
|
37
|
+
import path3 from "path";
|
|
38
38
|
import os from "os";
|
|
39
|
-
import
|
|
39
|
+
import fs3 from "fs";
|
|
40
40
|
import { randomUUID } from "crypto";
|
|
41
41
|
|
|
42
42
|
// ../server/src/logger.ts
|
|
@@ -118,6 +118,12 @@ var STRIP_ENV_KEYS = NEVER_BORROWED_ENV.keys;
|
|
|
118
118
|
var STRIP_ENV_PREFIXES = [...NEVER_BORROWED_ENV.prefixes, BOOTSTRAP_ENV_VAR];
|
|
119
119
|
var STRIP_ENV_KEYS_UPPER = STRIP_ENV_KEYS.map((k) => k.toUpperCase());
|
|
120
120
|
|
|
121
|
+
// ../server/src/workflows/gate-views.ts
|
|
122
|
+
import fs2 from "fs";
|
|
123
|
+
import path2 from "path";
|
|
124
|
+
import crypto2 from "crypto";
|
|
125
|
+
var GATE_VIEW_MAX_BYTES = 5 * 1024 * 1024;
|
|
126
|
+
|
|
121
127
|
// ../server/src/default-workflows.ts
|
|
122
128
|
var DEFAULT_TASK_WORKFLOW_ID = "system:default-task-workflow";
|
|
123
129
|
var DEV_SERVER_WORKFLOW_ID = "system:dev-server-on-restore";
|
|
@@ -195,10 +201,10 @@ function buildDevServerWorkflow() {
|
|
|
195
201
|
}
|
|
196
202
|
|
|
197
203
|
// ../server/src/database.ts
|
|
198
|
-
var DEFAULT_DATA_DIR =
|
|
204
|
+
var DEFAULT_DATA_DIR = path3.join(os.homedir(), ".vorn");
|
|
199
205
|
var resolvedDataDir = null;
|
|
200
206
|
function dbPath() {
|
|
201
|
-
return
|
|
207
|
+
return path3.join(getDataDir(), "vorn.db");
|
|
202
208
|
}
|
|
203
209
|
var db = null;
|
|
204
210
|
function getDb() {
|
|
@@ -213,8 +219,8 @@ function getDataDir() {
|
|
|
213
219
|
}
|
|
214
220
|
function initDatabase(dataDir2) {
|
|
215
221
|
resolvedDataDir = dataDir2 ?? DEFAULT_DATA_DIR;
|
|
216
|
-
if (!
|
|
217
|
-
|
|
222
|
+
if (!fs3.existsSync(getDataDir())) {
|
|
223
|
+
fs3.mkdirSync(getDataDir(), { recursive: true, mode: 448 });
|
|
218
224
|
}
|
|
219
225
|
try {
|
|
220
226
|
db = new Database(dbPath());
|
|
@@ -288,13 +294,13 @@ function recoverCorruptDatabase() {
|
|
|
288
294
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
289
295
|
const backupPath = `${dbPath()}.corrupt-${timestamp}`;
|
|
290
296
|
try {
|
|
291
|
-
if (
|
|
292
|
-
|
|
297
|
+
if (fs3.existsSync(dbPath())) {
|
|
298
|
+
fs3.copyFileSync(dbPath(), backupPath);
|
|
293
299
|
logger_default.info(`[database] Backed up corrupt database to ${backupPath}`);
|
|
294
300
|
}
|
|
295
301
|
for (const suffix of ["", "-wal", "-shm"]) {
|
|
296
302
|
const file = dbPath() + suffix;
|
|
297
|
-
if (
|
|
303
|
+
if (fs3.existsSync(file)) fs3.unlinkSync(file);
|
|
298
304
|
}
|
|
299
305
|
} catch (backupErr) {
|
|
300
306
|
logger_default.error({ backupErr }, "[database] Failed to back up corrupt database:");
|
|
@@ -496,7 +502,8 @@ function createSchema() {
|
|
|
496
502
|
connector_item TEXT,
|
|
497
503
|
connector_inbox_id INTEGER,
|
|
498
504
|
connector_inbox_lease_token TEXT,
|
|
499
|
-
connector_inbox_disposition TEXT
|
|
505
|
+
connector_inbox_disposition TEXT,
|
|
506
|
+
definition TEXT
|
|
500
507
|
);
|
|
501
508
|
|
|
502
509
|
CREATE TABLE IF NOT EXISTS workflow_run_nodes (
|
|
@@ -523,6 +530,11 @@ function createSchema() {
|
|
|
523
530
|
worktree_name TEXT,
|
|
524
531
|
worktree_origin TEXT,
|
|
525
532
|
waiting_for TEXT,
|
|
533
|
+
message TEXT,
|
|
534
|
+
view_token TEXT,
|
|
535
|
+
round INTEGER,
|
|
536
|
+
feedback TEXT,
|
|
537
|
+
rejected_at TEXT,
|
|
526
538
|
FOREIGN KEY (run_id) REFERENCES workflow_runs(id) ON DELETE CASCADE
|
|
527
539
|
);
|
|
528
540
|
|
|
@@ -1081,7 +1093,40 @@ function migrateSchema(d) {
|
|
|
1081
1093
|
})();
|
|
1082
1094
|
logger_default.info("[database] migrated schema to version 21 (package connections belong to sdk)");
|
|
1083
1095
|
}
|
|
1096
|
+
if (version < 22) {
|
|
1097
|
+
d.transaction(() => {
|
|
1098
|
+
const runCols = d.prepare("PRAGMA table_info(workflow_runs)").all();
|
|
1099
|
+
if (!runCols.some((c) => c.name === "definition")) {
|
|
1100
|
+
d.exec("ALTER TABLE workflow_runs ADD COLUMN definition TEXT");
|
|
1101
|
+
}
|
|
1102
|
+
d.prepare(
|
|
1103
|
+
"INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('schema_version', '22')"
|
|
1104
|
+
).run();
|
|
1105
|
+
})();
|
|
1106
|
+
logger_default.info("[database] migrated schema to version 22 (the definition a run started with)");
|
|
1107
|
+
}
|
|
1108
|
+
if (version < 23) {
|
|
1109
|
+
d.transaction(() => {
|
|
1110
|
+
const nodeCols = d.prepare("PRAGMA table_info(workflow_run_nodes)").all();
|
|
1111
|
+
for (const [column, type] of GATE_COLUMNS) {
|
|
1112
|
+
if (!nodeCols.some((c) => c.name === column)) {
|
|
1113
|
+
d.exec(`ALTER TABLE workflow_run_nodes ADD COLUMN ${column} ${type}`);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
d.prepare(
|
|
1117
|
+
"INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('schema_version', '23')"
|
|
1118
|
+
).run();
|
|
1119
|
+
})();
|
|
1120
|
+
logger_default.info("[database] migrated schema to version 23 (what an approval gate asked and heard)");
|
|
1121
|
+
}
|
|
1084
1122
|
}
|
|
1123
|
+
var GATE_COLUMNS = [
|
|
1124
|
+
["message", "TEXT"],
|
|
1125
|
+
["view_token", "TEXT"],
|
|
1126
|
+
["round", "INTEGER"],
|
|
1127
|
+
["feedback", "TEXT"],
|
|
1128
|
+
["rejected_at", "TEXT"]
|
|
1129
|
+
];
|
|
1085
1130
|
var REVISIONED_TABLES = [
|
|
1086
1131
|
"projects",
|
|
1087
1132
|
"tasks",
|
|
@@ -1156,7 +1201,8 @@ function verifySchema(d) {
|
|
|
1156
1201
|
{
|
|
1157
1202
|
column: "connector_inbox_disposition",
|
|
1158
1203
|
ddl: "ALTER TABLE workflow_runs ADD COLUMN connector_inbox_disposition TEXT"
|
|
1159
|
-
}
|
|
1204
|
+
},
|
|
1205
|
+
{ column: "definition", ddl: "ALTER TABLE workflow_runs ADD COLUMN definition TEXT" }
|
|
1160
1206
|
],
|
|
1161
1207
|
connector_inbox: [
|
|
1162
1208
|
{
|
|
@@ -1191,7 +1237,11 @@ function verifySchema(d) {
|
|
|
1191
1237
|
ddl: "ALTER TABLE workflow_run_nodes ADD COLUMN structured_output TEXT"
|
|
1192
1238
|
},
|
|
1193
1239
|
// Which pass of a loop produced this row.
|
|
1194
|
-
{ column: "iteration", ddl: "ALTER TABLE workflow_run_nodes ADD COLUMN iteration INTEGER" }
|
|
1240
|
+
{ column: "iteration", ddl: "ALTER TABLE workflow_run_nodes ADD COLUMN iteration INTEGER" },
|
|
1241
|
+
...GATE_COLUMNS.map(([column, type]) => ({
|
|
1242
|
+
column,
|
|
1243
|
+
ddl: `ALTER TABLE workflow_run_nodes ADD COLUMN ${column} ${type}`
|
|
1244
|
+
}))
|
|
1195
1245
|
],
|
|
1196
1246
|
tasks: [
|
|
1197
1247
|
{
|
|
@@ -1815,16 +1865,12 @@ var ConfigManager = class {
|
|
|
1815
1865
|
cb(config);
|
|
1816
1866
|
}
|
|
1817
1867
|
}
|
|
1818
|
-
/**
|
|
1819
|
-
* Watch for external DB writes (e.g. MCP stdio process).
|
|
1820
|
-
* Detects: .db-signal (explicit), .db-wal changes, and .db changes (post-checkpoint).
|
|
1821
|
-
*/
|
|
1868
|
+
/** Reload when a writer touches `.db-signal` (`dbSignalChange`) after changing the configuration. */
|
|
1822
1869
|
watchDb() {
|
|
1823
1870
|
if (this.dbWatcher) return;
|
|
1824
|
-
const WATCH_SUFFIXES = [".db-signal", ".db-wal", ".db"];
|
|
1825
1871
|
try {
|
|
1826
|
-
this.dbWatcher =
|
|
1827
|
-
if (
|
|
1872
|
+
this.dbWatcher = fs4.watch(getDataDir(), (_eventType, filename) => {
|
|
1873
|
+
if (filename !== ".db-signal") return;
|
|
1828
1874
|
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
1829
1875
|
this.debounceTimer = setTimeout(() => {
|
|
1830
1876
|
this.notifyChanged();
|
|
@@ -1855,8 +1901,8 @@ var configManager = new ConfigManager();
|
|
|
1855
1901
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
1856
1902
|
|
|
1857
1903
|
// src/tools/tasks.ts
|
|
1858
|
-
import
|
|
1859
|
-
import
|
|
1904
|
+
import crypto3 from "crypto";
|
|
1905
|
+
import path5 from "path";
|
|
1860
1906
|
import { z as z2 } from "zod";
|
|
1861
1907
|
|
|
1862
1908
|
// src/validation.ts
|
|
@@ -1885,8 +1931,8 @@ var V = {
|
|
|
1885
1931
|
};
|
|
1886
1932
|
|
|
1887
1933
|
// ../server/src/rpc-client.ts
|
|
1888
|
-
import
|
|
1889
|
-
import
|
|
1934
|
+
import fs5 from "fs";
|
|
1935
|
+
import path4 from "path";
|
|
1890
1936
|
import os2 from "os";
|
|
1891
1937
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1892
1938
|
import { WebSocket } from "ws";
|
|
@@ -1895,13 +1941,13 @@ function named(value) {
|
|
|
1895
1941
|
return value?.trim() ? value : void 0;
|
|
1896
1942
|
}
|
|
1897
1943
|
function dataDir() {
|
|
1898
|
-
return dataDirOverride ?? named(process.env.VORN_DATA_DIR) ??
|
|
1944
|
+
return dataDirOverride ?? named(process.env.VORN_DATA_DIR) ?? path4.join(os2.homedir(), ".vorn");
|
|
1899
1945
|
}
|
|
1900
1946
|
function portFile() {
|
|
1901
|
-
return
|
|
1947
|
+
return path4.join(dataDir(), WS_PORT_FILENAME);
|
|
1902
1948
|
}
|
|
1903
1949
|
function localTokenFile() {
|
|
1904
|
-
return
|
|
1950
|
+
return path4.join(dataDir(), LOCAL_TOKEN_FILENAME);
|
|
1905
1951
|
}
|
|
1906
1952
|
function tokenFileMissingMessage() {
|
|
1907
1953
|
return `Vorn local credential not found (${localTokenFile()}).
|
|
@@ -1913,7 +1959,7 @@ MCP included, reaches a server that moved.`;
|
|
|
1913
1959
|
}
|
|
1914
1960
|
function readLocalToken() {
|
|
1915
1961
|
try {
|
|
1916
|
-
const token =
|
|
1962
|
+
const token = fs5.readFileSync(localTokenFile(), "utf-8").trim();
|
|
1917
1963
|
if (!token) throw new Error("empty");
|
|
1918
1964
|
return token;
|
|
1919
1965
|
} catch {
|
|
@@ -2033,8 +2079,8 @@ function discoverAndHeal() {
|
|
|
2033
2079
|
cacheTimestamp = now;
|
|
2034
2080
|
if (discovered) {
|
|
2035
2081
|
try {
|
|
2036
|
-
|
|
2037
|
-
|
|
2082
|
+
fs5.mkdirSync(dataDir(), { recursive: true });
|
|
2083
|
+
fs5.writeFileSync(portFile(), JSON.stringify({ port: discovered }), "utf-8");
|
|
2038
2084
|
} catch {
|
|
2039
2085
|
}
|
|
2040
2086
|
return { port: discovered };
|
|
@@ -2043,7 +2089,7 @@ function discoverAndHeal() {
|
|
|
2043
2089
|
}
|
|
2044
2090
|
function readPort() {
|
|
2045
2091
|
try {
|
|
2046
|
-
const raw =
|
|
2092
|
+
const raw = fs5.readFileSync(portFile(), "utf-8").trim();
|
|
2047
2093
|
if (!raw) return { port: null, reason: "invalid" };
|
|
2048
2094
|
if (raw.startsWith("{")) {
|
|
2049
2095
|
const parsed = JSON.parse(raw);
|
|
@@ -2298,7 +2344,7 @@ function registerTaskTools(server) {
|
|
|
2298
2344
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2299
2345
|
const status = args.status ?? "todo";
|
|
2300
2346
|
const task = {
|
|
2301
|
-
id:
|
|
2347
|
+
id: crypto3.randomUUID(),
|
|
2302
2348
|
projectName: args.project_name,
|
|
2303
2349
|
title: args.title,
|
|
2304
2350
|
description: args.description ?? "",
|
|
@@ -2481,12 +2527,12 @@ function registerTaskTools(server) {
|
|
|
2481
2527
|
};
|
|
2482
2528
|
}
|
|
2483
2529
|
const cwd = args.cwd || process.cwd();
|
|
2484
|
-
const normalizedCwd =
|
|
2530
|
+
const normalizedCwd = path5.resolve(cwd);
|
|
2485
2531
|
const projects = await dbListProjects();
|
|
2486
2532
|
let matchedProject = null;
|
|
2487
2533
|
let matchLen = 0;
|
|
2488
2534
|
for (const p of projects) {
|
|
2489
|
-
const normalizedPath =
|
|
2535
|
+
const normalizedPath = path5.resolve(p.path);
|
|
2490
2536
|
if (normalizedCwd.startsWith(normalizedPath) && normalizedPath.length > matchLen) {
|
|
2491
2537
|
matchedProject = p;
|
|
2492
2538
|
matchLen = normalizedPath.length;
|
|
@@ -2514,7 +2560,7 @@ function registerTaskTools(server) {
|
|
|
2514
2560
|
let matchedTask = null;
|
|
2515
2561
|
for (const t of projectTasks) {
|
|
2516
2562
|
if (t.worktreePath) {
|
|
2517
|
-
const normalizedWorktree =
|
|
2563
|
+
const normalizedWorktree = path5.resolve(t.worktreePath);
|
|
2518
2564
|
if (normalizedCwd.startsWith(normalizedWorktree)) {
|
|
2519
2565
|
matchedTask = t;
|
|
2520
2566
|
break;
|
|
@@ -2998,12 +3044,43 @@ function registerSessionTools(server) {
|
|
|
2998
3044
|
}
|
|
2999
3045
|
|
|
3000
3046
|
// src/tools/workflows.ts
|
|
3001
|
-
import
|
|
3047
|
+
import crypto4 from "crypto";
|
|
3002
3048
|
|
|
3003
3049
|
// ../shared/src/workflow-graph.ts
|
|
3050
|
+
var MAX_GATE_ROUNDS = 10;
|
|
3051
|
+
function nodesBetween(from, to, edges) {
|
|
3052
|
+
if (from === to) return /* @__PURE__ */ new Set();
|
|
3053
|
+
const { successors, predecessors } = buildGraph(edges);
|
|
3054
|
+
const reach = (start, next) => {
|
|
3055
|
+
const seen = /* @__PURE__ */ new Set([start]);
|
|
3056
|
+
const queue = [start];
|
|
3057
|
+
while (queue.length > 0) {
|
|
3058
|
+
for (const id of next.get(queue.shift()) ?? []) {
|
|
3059
|
+
if (!seen.has(id)) {
|
|
3060
|
+
seen.add(id);
|
|
3061
|
+
queue.push(id);
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
return seen;
|
|
3066
|
+
};
|
|
3067
|
+
const below = reach(from, successors);
|
|
3068
|
+
if (!below.has(to)) return /* @__PURE__ */ new Set();
|
|
3069
|
+
const above = reach(to, predecessors);
|
|
3070
|
+
return new Set([...below].filter((id) => above.has(id)));
|
|
3071
|
+
}
|
|
3004
3072
|
function isSignInWait(state) {
|
|
3005
3073
|
return state.status === "waiting" && state.waitingFor === "signIn";
|
|
3006
3074
|
}
|
|
3075
|
+
function buildGraph(edges) {
|
|
3076
|
+
const successors = /* @__PURE__ */ new Map();
|
|
3077
|
+
const predecessors = /* @__PURE__ */ new Map();
|
|
3078
|
+
for (const e of edges) {
|
|
3079
|
+
successors.set(e.source, [...successors.get(e.source) || [], e.target]);
|
|
3080
|
+
predecessors.set(e.target, [...predecessors.get(e.target) || [], e.source]);
|
|
3081
|
+
}
|
|
3082
|
+
return { successors, predecessors };
|
|
3083
|
+
}
|
|
3007
3084
|
|
|
3008
3085
|
// src/tools/workflows.ts
|
|
3009
3086
|
import { z as z5 } from "zod";
|
|
@@ -3364,7 +3441,7 @@ function buildGraphFromFlat(trigger, actions) {
|
|
|
3364
3441
|
const nodes = [];
|
|
3365
3442
|
const edges = [];
|
|
3366
3443
|
const triggerNode = {
|
|
3367
|
-
id:
|
|
3444
|
+
id: crypto4.randomUUID(),
|
|
3368
3445
|
type: "trigger",
|
|
3369
3446
|
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",
|
|
3370
3447
|
config: trigger,
|
|
@@ -3375,7 +3452,7 @@ function buildGraphFromFlat(trigger, actions) {
|
|
|
3375
3452
|
const NODE_GAP = 140;
|
|
3376
3453
|
for (let i = 0; i < actions.length; i++) {
|
|
3377
3454
|
const action = actions[i];
|
|
3378
|
-
const nodeId =
|
|
3455
|
+
const nodeId = crypto4.randomUUID();
|
|
3379
3456
|
nodes.push({
|
|
3380
3457
|
id: nodeId,
|
|
3381
3458
|
type: "launchAgent",
|
|
@@ -3384,7 +3461,7 @@ function buildGraphFromFlat(trigger, actions) {
|
|
|
3384
3461
|
position: { x: 0, y: (i + 1) * NODE_GAP }
|
|
3385
3462
|
});
|
|
3386
3463
|
edges.push({
|
|
3387
|
-
id:
|
|
3464
|
+
id: crypto4.randomUUID(),
|
|
3388
3465
|
source: prevId,
|
|
3389
3466
|
target: nodeId
|
|
3390
3467
|
});
|
|
@@ -3484,12 +3561,37 @@ function annotateWaitingGates(runs, workflows) {
|
|
|
3484
3561
|
asks: "Sign in to its connection in the Vorn app, and this step runs again"
|
|
3485
3562
|
};
|
|
3486
3563
|
}
|
|
3487
|
-
const asks = gateMessage(workflow, state.nodeId);
|
|
3564
|
+
const asks = state.message ?? gateMessage(workflow, state.nodeId);
|
|
3488
3565
|
return asks ? { ...state, asks } : state;
|
|
3489
3566
|
})
|
|
3490
3567
|
};
|
|
3491
3568
|
});
|
|
3492
3569
|
}
|
|
3570
|
+
var ANSWERED = {
|
|
3571
|
+
approve: "Approved",
|
|
3572
|
+
reject: "Rejected",
|
|
3573
|
+
changes: "Sent back"
|
|
3574
|
+
};
|
|
3575
|
+
function validateGateFeedback(nodes, edges) {
|
|
3576
|
+
const errors = [];
|
|
3577
|
+
for (const node of nodes) {
|
|
3578
|
+
if (node.type !== "approval" || node.config?.feedback === void 0) continue;
|
|
3579
|
+
const feedback = node.config.feedback;
|
|
3580
|
+
const name = node.label || node.id;
|
|
3581
|
+
const from = typeof feedback?.from === "string" ? feedback.from : "";
|
|
3582
|
+
const source2 = nodes.find((n) => n.id === from);
|
|
3583
|
+
if (!source2) errors.push(`gate "${name}" redoes from unknown step "${from}"`);
|
|
3584
|
+
else if (source2.type === "trigger") errors.push(`gate "${name}" cannot redo from its trigger`);
|
|
3585
|
+
else if (nodesBetween(from, node.id, edges).size === 0) {
|
|
3586
|
+
errors.push(`gate "${name}" redoes from "${source2.label || from}", which does not lead to it`);
|
|
3587
|
+
}
|
|
3588
|
+
const rounds = Number(feedback?.maxRounds);
|
|
3589
|
+
if (!Number.isInteger(rounds) || rounds < 2 || rounds > MAX_GATE_ROUNDS) {
|
|
3590
|
+
errors.push(`gate "${name}" needs maxRounds between 2 and ${MAX_GATE_ROUNDS}`);
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
return errors;
|
|
3594
|
+
}
|
|
3493
3595
|
async function runById(runId) {
|
|
3494
3596
|
const recent = (await listAllWorkflowRuns(void 0, 500)).find((r) => r.runId === runId);
|
|
3495
3597
|
if (recent) return recent;
|
|
@@ -3561,9 +3663,8 @@ function registerWorkflowTools(server) {
|
|
|
3561
3663
|
if (args.nodes && args.edges) {
|
|
3562
3664
|
nodes = args.nodes;
|
|
3563
3665
|
edges = args.edges;
|
|
3564
|
-
const
|
|
3565
|
-
|
|
3566
|
-
);
|
|
3666
|
+
const loose = nodes;
|
|
3667
|
+
const loopErrors = [...validateLoopBodies(loose), ...validateGateFeedback(loose, edges)];
|
|
3567
3668
|
if (loopErrors.length > 0) {
|
|
3568
3669
|
return {
|
|
3569
3670
|
content: [{ type: "text", text: `Error: ${loopErrors.join("; ")}` }],
|
|
@@ -3578,7 +3679,7 @@ function registerWorkflowTools(server) {
|
|
|
3578
3679
|
edges = graph.edges;
|
|
3579
3680
|
}
|
|
3580
3681
|
const workflow = {
|
|
3581
|
-
id:
|
|
3682
|
+
id: crypto4.randomUUID(),
|
|
3582
3683
|
name: args.name,
|
|
3583
3684
|
icon: args.icon ?? "Zap",
|
|
3584
3685
|
iconColor: args.icon_color ?? "#6366f1",
|
|
@@ -3622,9 +3723,9 @@ function registerWorkflowTools(server) {
|
|
|
3622
3723
|
const updates = {};
|
|
3623
3724
|
if (args.name !== void 0) updates.name = args.name;
|
|
3624
3725
|
if (args.nodes !== void 0) {
|
|
3625
|
-
const
|
|
3626
|
-
|
|
3627
|
-
);
|
|
3726
|
+
const loose = args.nodes;
|
|
3727
|
+
const edges = args.edges ?? workflow.edges;
|
|
3728
|
+
const loopErrors = [...validateLoopBodies(loose), ...validateGateFeedback(loose, edges)];
|
|
3628
3729
|
if (loopErrors.length > 0) {
|
|
3629
3730
|
return {
|
|
3630
3731
|
content: [{ type: "text", text: `Error: ${loopErrors.join("; ")}` }],
|
|
@@ -3753,10 +3854,15 @@ The run is stopped by the instance holding it, so confirm with list_workflow_run
|
|
|
3753
3854
|
);
|
|
3754
3855
|
server.tool(
|
|
3755
3856
|
"resolve_gate",
|
|
3756
|
-
"Approve or
|
|
3857
|
+
"Approve, reject, or send back the approval gate a workflow run is parked on, the way the Vorn app does. Requires the Vorn app to be running: the decision is broadcast, and the instance holding the run is what resumes it. Read what is being approved first \u2014 list_workflow_runs names the waiting node and what it asks.",
|
|
3757
3858
|
{
|
|
3758
3859
|
run_id: V.id.describe("Run ID (from list_workflow_runs)"),
|
|
3759
|
-
decision: z5.enum(["approve", "reject"]).describe(
|
|
3860
|
+
decision: z5.enum(["approve", "reject", "changes"]).describe(
|
|
3861
|
+
"approve lets the run go on; reject ends it; changes sends the work back to the step the gate redoes from, with your comment, and the gate asks again"
|
|
3862
|
+
),
|
|
3863
|
+
comment: z5.string().max(2e4).optional().describe(
|
|
3864
|
+
"Required for changes: what to change. Optional on approve and reject; kept with the run."
|
|
3865
|
+
),
|
|
3760
3866
|
node_id: V.id.optional().describe("The waiting node, when a run has more than one gate open")
|
|
3761
3867
|
},
|
|
3762
3868
|
async (args) => {
|
|
@@ -3789,15 +3895,35 @@ The run is stopped by the instance holding it, so confirm with list_workflow_run
|
|
|
3789
3895
|
isError: true
|
|
3790
3896
|
};
|
|
3791
3897
|
}
|
|
3898
|
+
if (args.decision === "changes" && !args.comment?.trim()) {
|
|
3899
|
+
return {
|
|
3900
|
+
content: [
|
|
3901
|
+
{ type: "text", text: "Error: changes needs a comment saying what to change." }
|
|
3902
|
+
],
|
|
3903
|
+
isError: true
|
|
3904
|
+
};
|
|
3905
|
+
}
|
|
3792
3906
|
const workflow = (await dbListWorkflows()).find((w) => w.id === run.workflowId);
|
|
3793
3907
|
const gateNode = approvalNode(workflow, target.nodeId);
|
|
3794
|
-
const asked = askedBy(gateNode);
|
|
3908
|
+
const asked = run.nodeStates.find((n) => n.nodeId === target.nodeId)?.message ?? askedBy(gateNode);
|
|
3795
3909
|
try {
|
|
3796
|
-
await rpcCall("workflow:resolveGate", {
|
|
3910
|
+
const answer = await rpcCall("workflow:resolveGate", {
|
|
3797
3911
|
runId: args.run_id,
|
|
3798
3912
|
nodeId: target.nodeId,
|
|
3799
|
-
decision: args.decision
|
|
3913
|
+
decision: args.decision,
|
|
3914
|
+
...args.comment?.trim() && { comment: args.comment.trim() }
|
|
3800
3915
|
});
|
|
3916
|
+
if (answer?.accepted === false) {
|
|
3917
|
+
return {
|
|
3918
|
+
content: [
|
|
3919
|
+
{
|
|
3920
|
+
type: "text",
|
|
3921
|
+
text: args.decision === "changes" ? "Error: this gate takes no changes now: it has no step to redo from, or its rounds are used up. Approve or reject it instead." : "Error: the gate did not take that answer. Check list_workflow_runs."
|
|
3922
|
+
}
|
|
3923
|
+
],
|
|
3924
|
+
isError: true
|
|
3925
|
+
};
|
|
3926
|
+
}
|
|
3801
3927
|
} catch (err) {
|
|
3802
3928
|
return {
|
|
3803
3929
|
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : err}` }],
|
|
@@ -3809,7 +3935,7 @@ The run is stopped by the instance holding it, so confirm with list_workflow_run
|
|
|
3809
3935
|
content: [
|
|
3810
3936
|
{
|
|
3811
3937
|
type: "text",
|
|
3812
|
-
text: `${args.decision
|
|
3938
|
+
text: `${ANSWERED[args.decision]} "${gate}" on run ${args.run_id}${run.workflowName ? ` of "${run.workflowName}"` : ""}.${asked ? `
|
|
3813
3939
|
|
|
3814
3940
|
What it asked: ${asked}` : ""}
|
|
3815
3941
|
|
|
@@ -4018,9 +4144,11 @@ Warning: ${unnamed.length} step(s) point at a connection this install could not
|
|
|
4018
4144
|
isError: true
|
|
4019
4145
|
};
|
|
4020
4146
|
}
|
|
4021
|
-
const
|
|
4022
|
-
|
|
4023
|
-
|
|
4147
|
+
const loose = parsed.nodes;
|
|
4148
|
+
const loopErrors = [
|
|
4149
|
+
...validateLoopBodies(loose),
|
|
4150
|
+
...validateGateFeedback(loose, parsed.edges)
|
|
4151
|
+
];
|
|
4024
4152
|
if (loopErrors.length > 0) {
|
|
4025
4153
|
return {
|
|
4026
4154
|
content: [{ type: "text", text: `Error: ${loopErrors.join("; ")}` }],
|
|
@@ -4080,7 +4208,7 @@ function registerConfigTools(server) {
|
|
|
4080
4208
|
}
|
|
4081
4209
|
|
|
4082
4210
|
// src/tools/workspaces.ts
|
|
4083
|
-
import
|
|
4211
|
+
import crypto5 from "crypto";
|
|
4084
4212
|
import { z as z6 } from "zod";
|
|
4085
4213
|
function registerWorkspaceTools(server) {
|
|
4086
4214
|
server.tool("list_workspaces", "List all workspaces", async () => {
|
|
@@ -4099,7 +4227,7 @@ function registerWorkspaceTools(server) {
|
|
|
4099
4227
|
const existing = await dbListWorkspaces();
|
|
4100
4228
|
const maxOrder = existing.reduce((max, w) => Math.max(max, w.order), 0);
|
|
4101
4229
|
const workspace = {
|
|
4102
|
-
id:
|
|
4230
|
+
id: crypto5.randomUUID(),
|
|
4103
4231
|
name: args.name,
|
|
4104
4232
|
order: maxOrder + 1,
|
|
4105
4233
|
...args.icon && { icon: args.icon },
|
|
@@ -4507,7 +4635,7 @@ function plural(count, word) {
|
|
|
4507
4635
|
}
|
|
4508
4636
|
|
|
4509
4637
|
// src/tools/browser.ts
|
|
4510
|
-
import
|
|
4638
|
+
import crypto6 from "crypto";
|
|
4511
4639
|
import { z as z8 } from "zod";
|
|
4512
4640
|
function sessionId(env = process.env) {
|
|
4513
4641
|
const id = env.VORN_SESSION_ID;
|
|
@@ -4534,7 +4662,7 @@ function source(label) {
|
|
|
4534
4662
|
return label.includes("WEB PAGE") || label.includes("BROWSER") ? "page" : "device";
|
|
4535
4663
|
}
|
|
4536
4664
|
function pageResult(data, label = "WEB PAGE CONTENT") {
|
|
4537
|
-
const nonce =
|
|
4665
|
+
const nonce = crypto6.randomUUID();
|
|
4538
4666
|
return {
|
|
4539
4667
|
content: [
|
|
4540
4668
|
{
|
|
@@ -4983,7 +5111,7 @@ console.warn = (...args) => _origError("[mcp:warn]", ...args);
|
|
|
4983
5111
|
console.error = (...args) => _origError("[mcp:error]", ...args);
|
|
4984
5112
|
async function main() {
|
|
4985
5113
|
configManager.init();
|
|
4986
|
-
const version = true ? "0.7.1-beta.
|
|
5114
|
+
const version = true ? "0.7.1-beta.5" : createRequire(import.meta.url)("../package.json").version;
|
|
4987
5115
|
const server = createMcpServer(version);
|
|
4988
5116
|
const transport = new StdioServerTransport();
|
|
4989
5117
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vornrun/mcp",
|
|
3
|
-
"version": "0.7.1-beta.
|
|
3
|
+
"version": "0.7.1-beta.5",
|
|
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.5.4"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@vornrun/server": "0.7.1-beta.
|
|
42
|
-
"@vornrun/shared": "0.7.1-beta.
|
|
41
|
+
"@vornrun/server": "0.7.1-beta.5",
|
|
42
|
+
"@vornrun/shared": "0.7.1-beta.5",
|
|
43
43
|
"tsup": "^8.5.1",
|
|
44
44
|
"tsx": "^4.23.1",
|
|
45
45
|
"typescript": "^6.0.3"
|