@vornrun/mcp 0.5.1 → 0.5.3
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 +522 -20
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -39,6 +39,7 @@ import Database from "libsql";
|
|
|
39
39
|
import path2 from "path";
|
|
40
40
|
import os from "os";
|
|
41
41
|
import fs2 from "fs";
|
|
42
|
+
import { randomUUID } from "crypto";
|
|
42
43
|
|
|
43
44
|
// ../server/src/logger.ts
|
|
44
45
|
import pino from "pino";
|
|
@@ -106,6 +107,15 @@ var DEFAULT_WORKSPACE = {
|
|
|
106
107
|
function isTerminalTaskStatus(status) {
|
|
107
108
|
return status === "done" || status === "cancelled";
|
|
108
109
|
}
|
|
110
|
+
var SDK_FILTER_KEYS = {
|
|
111
|
+
connectorId: "sdkConnectorId",
|
|
112
|
+
version: "sdkVersion",
|
|
113
|
+
icon: "sdkIcon"
|
|
114
|
+
};
|
|
115
|
+
function connectionConnectorId(connection) {
|
|
116
|
+
const packaged = connection.filters?.[SDK_FILTER_KEYS.connectorId];
|
|
117
|
+
return typeof packaged === "string" && packaged !== "" ? packaged : connection.connectorId;
|
|
118
|
+
}
|
|
109
119
|
|
|
110
120
|
// ../server/src/default-workflows.ts
|
|
111
121
|
var DEFAULT_TASK_WORKFLOW_ID = "system:default-task-workflow";
|
|
@@ -400,7 +410,12 @@ function createSchema() {
|
|
|
400
410
|
started_at TEXT NOT NULL,
|
|
401
411
|
completed_at TEXT,
|
|
402
412
|
status TEXT NOT NULL DEFAULT 'running',
|
|
403
|
-
trigger_task_id TEXT
|
|
413
|
+
trigger_task_id TEXT,
|
|
414
|
+
inputs TEXT,
|
|
415
|
+
connector_item TEXT,
|
|
416
|
+
connector_inbox_id INTEGER,
|
|
417
|
+
connector_inbox_lease_token TEXT,
|
|
418
|
+
connector_inbox_disposition TEXT
|
|
404
419
|
);
|
|
405
420
|
|
|
406
421
|
CREATE TABLE IF NOT EXISTS workflow_run_nodes (
|
|
@@ -438,9 +453,70 @@ function createSchema() {
|
|
|
438
453
|
|
|
439
454
|
CREATE INDEX IF NOT EXISTS idx_session_events_session ON session_events(session_id, timestamp DESC);
|
|
440
455
|
CREATE INDEX IF NOT EXISTS idx_session_events_type ON session_events(event_type, timestamp DESC);
|
|
456
|
+
|
|
457
|
+
CREATE TABLE IF NOT EXISTS connector_poll_state (
|
|
458
|
+
workflow_id TEXT PRIMARY KEY REFERENCES workflows(id) ON DELETE CASCADE,
|
|
459
|
+
connection_id TEXT NOT NULL REFERENCES source_connections(id) ON DELETE CASCADE,
|
|
460
|
+
cursor TEXT,
|
|
461
|
+
last_polled_at TEXT,
|
|
462
|
+
last_error TEXT
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
CREATE TABLE IF NOT EXISTS connector_inbox (
|
|
466
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
467
|
+
workflow_id TEXT NOT NULL REFERENCES workflows(id) ON DELETE CASCADE,
|
|
468
|
+
connection_id TEXT NOT NULL REFERENCES source_connections(id) ON DELETE CASCADE,
|
|
469
|
+
connector_id TEXT NOT NULL,
|
|
470
|
+
event_id TEXT NOT NULL,
|
|
471
|
+
event_type TEXT NOT NULL,
|
|
472
|
+
event_timestamp TEXT NOT NULL,
|
|
473
|
+
payload TEXT NOT NULL,
|
|
474
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
475
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
476
|
+
available_at TEXT NOT NULL,
|
|
477
|
+
lease_until TEXT,
|
|
478
|
+
lease_token TEXT,
|
|
479
|
+
last_error TEXT,
|
|
480
|
+
created_at TEXT NOT NULL,
|
|
481
|
+
processed_at TEXT,
|
|
482
|
+
UNIQUE (workflow_id, connection_id, event_type, event_id)
|
|
483
|
+
);
|
|
484
|
+
|
|
485
|
+
CREATE INDEX IF NOT EXISTS idx_connector_inbox_ready
|
|
486
|
+
ON connector_inbox(status, available_at, lease_until);
|
|
487
|
+
CREATE INDEX IF NOT EXISTS idx_connector_inbox_connection
|
|
488
|
+
ON connector_inbox(connection_id, created_at);
|
|
441
489
|
`);
|
|
442
490
|
migrateSchema(d);
|
|
443
491
|
verifySchema(d);
|
|
492
|
+
seedLegacyConnectorPollState(d);
|
|
493
|
+
}
|
|
494
|
+
function seedLegacyConnectorPollState(d) {
|
|
495
|
+
const workflows = d.prepare("SELECT id, nodes FROM workflows").all();
|
|
496
|
+
const readConnection = d.prepare(
|
|
497
|
+
"SELECT sync_cursor, last_sync_at FROM source_connections WHERE id = ?"
|
|
498
|
+
);
|
|
499
|
+
const insertState = d.prepare(
|
|
500
|
+
`INSERT OR IGNORE INTO connector_poll_state (
|
|
501
|
+
workflow_id, connection_id, cursor, last_polled_at, last_error
|
|
502
|
+
) VALUES (?, ?, ?, ?, NULL)`
|
|
503
|
+
);
|
|
504
|
+
for (const workflow of workflows) {
|
|
505
|
+
let nodes;
|
|
506
|
+
try {
|
|
507
|
+
nodes = JSON.parse(workflow.nodes);
|
|
508
|
+
} catch {
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
const trigger = nodes.find(
|
|
512
|
+
(node) => node.type === "trigger" && node.config?.triggerType === "connectorPoll"
|
|
513
|
+
);
|
|
514
|
+
const connectionId = trigger?.config?.connectionId;
|
|
515
|
+
if (!connectionId) continue;
|
|
516
|
+
const connection = readConnection.get(connectionId);
|
|
517
|
+
if (!connection) continue;
|
|
518
|
+
insertState.run(workflow.id, connectionId, connection.sync_cursor, connection.last_sync_at);
|
|
519
|
+
}
|
|
444
520
|
}
|
|
445
521
|
function migrateSchema(d) {
|
|
446
522
|
const row = d.prepare("SELECT value FROM schema_meta WHERE key = 'schema_version'").get();
|
|
@@ -642,6 +718,126 @@ function migrateSchema(d) {
|
|
|
642
718
|
})();
|
|
643
719
|
logger_default.info("[database] migrated schema to version 10 (drop session_logs)");
|
|
644
720
|
}
|
|
721
|
+
if (version < 11) {
|
|
722
|
+
d.transaction(() => {
|
|
723
|
+
const runCols = d.prepare("PRAGMA table_info(workflow_runs)").all();
|
|
724
|
+
if (!runCols.some((c) => c.name === "inputs")) {
|
|
725
|
+
d.exec("ALTER TABLE workflow_runs ADD COLUMN inputs TEXT");
|
|
726
|
+
}
|
|
727
|
+
d.prepare(
|
|
728
|
+
"INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('schema_version', '11')"
|
|
729
|
+
).run();
|
|
730
|
+
})();
|
|
731
|
+
logger_default.info("[database] migrated schema to version 11 (workflow run inputs)");
|
|
732
|
+
}
|
|
733
|
+
if (version < 12) {
|
|
734
|
+
d.transaction(() => {
|
|
735
|
+
const runCols = d.prepare("PRAGMA table_info(workflow_runs)").all();
|
|
736
|
+
if (!runCols.some((column) => column.name === "connector_inbox_id")) {
|
|
737
|
+
d.exec("ALTER TABLE workflow_runs ADD COLUMN connector_inbox_id INTEGER");
|
|
738
|
+
}
|
|
739
|
+
if (!runCols.some((column) => column.name === "connector_item")) {
|
|
740
|
+
d.exec("ALTER TABLE workflow_runs ADD COLUMN connector_item TEXT");
|
|
741
|
+
}
|
|
742
|
+
if (!runCols.some((column) => column.name === "connector_inbox_lease_token")) {
|
|
743
|
+
d.exec("ALTER TABLE workflow_runs ADD COLUMN connector_inbox_lease_token TEXT");
|
|
744
|
+
}
|
|
745
|
+
if (!runCols.some((column) => column.name === "connector_inbox_disposition")) {
|
|
746
|
+
d.exec("ALTER TABLE workflow_runs ADD COLUMN connector_inbox_disposition TEXT");
|
|
747
|
+
}
|
|
748
|
+
d.exec(`
|
|
749
|
+
CREATE TABLE IF NOT EXISTS connector_poll_state (
|
|
750
|
+
workflow_id TEXT PRIMARY KEY REFERENCES workflows(id) ON DELETE CASCADE,
|
|
751
|
+
connection_id TEXT NOT NULL REFERENCES source_connections(id) ON DELETE CASCADE,
|
|
752
|
+
cursor TEXT,
|
|
753
|
+
last_polled_at TEXT,
|
|
754
|
+
last_error TEXT
|
|
755
|
+
);
|
|
756
|
+
|
|
757
|
+
CREATE TABLE IF NOT EXISTS connector_inbox (
|
|
758
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
759
|
+
workflow_id TEXT NOT NULL REFERENCES workflows(id) ON DELETE CASCADE,
|
|
760
|
+
connection_id TEXT NOT NULL REFERENCES source_connections(id) ON DELETE CASCADE,
|
|
761
|
+
connector_id TEXT NOT NULL,
|
|
762
|
+
event_id TEXT NOT NULL,
|
|
763
|
+
event_type TEXT NOT NULL,
|
|
764
|
+
event_timestamp TEXT NOT NULL,
|
|
765
|
+
payload TEXT NOT NULL,
|
|
766
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
767
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
768
|
+
available_at TEXT NOT NULL,
|
|
769
|
+
lease_until TEXT,
|
|
770
|
+
lease_token TEXT,
|
|
771
|
+
last_error TEXT,
|
|
772
|
+
created_at TEXT NOT NULL,
|
|
773
|
+
processed_at TEXT,
|
|
774
|
+
UNIQUE (workflow_id, event_type, event_id)
|
|
775
|
+
);
|
|
776
|
+
|
|
777
|
+
CREATE INDEX IF NOT EXISTS idx_connector_inbox_ready
|
|
778
|
+
ON connector_inbox(status, available_at, lease_until);
|
|
779
|
+
CREATE INDEX IF NOT EXISTS idx_connector_inbox_connection
|
|
780
|
+
ON connector_inbox(connection_id, created_at);
|
|
781
|
+
`);
|
|
782
|
+
d.prepare(
|
|
783
|
+
"INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('schema_version', '12')"
|
|
784
|
+
).run();
|
|
785
|
+
})();
|
|
786
|
+
logger_default.info("[database] migrated schema to version 12 (durable connector ingestion)");
|
|
787
|
+
}
|
|
788
|
+
if (version < 13) {
|
|
789
|
+
d.transaction(() => {
|
|
790
|
+
const inboxCols = d.prepare("PRAGMA table_info(connector_inbox)").all();
|
|
791
|
+
if (!inboxCols.some((column) => column.name === "lease_token")) {
|
|
792
|
+
d.exec("ALTER TABLE connector_inbox ADD COLUMN lease_token TEXT");
|
|
793
|
+
}
|
|
794
|
+
d.exec(`
|
|
795
|
+
ALTER TABLE connector_inbox RENAME TO connector_inbox_v12;
|
|
796
|
+
|
|
797
|
+
CREATE TABLE connector_inbox (
|
|
798
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
799
|
+
workflow_id TEXT NOT NULL REFERENCES workflows(id) ON DELETE CASCADE,
|
|
800
|
+
connection_id TEXT NOT NULL REFERENCES source_connections(id) ON DELETE CASCADE,
|
|
801
|
+
connector_id TEXT NOT NULL,
|
|
802
|
+
event_id TEXT NOT NULL,
|
|
803
|
+
event_type TEXT NOT NULL,
|
|
804
|
+
event_timestamp TEXT NOT NULL,
|
|
805
|
+
payload TEXT NOT NULL,
|
|
806
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
807
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
808
|
+
available_at TEXT NOT NULL,
|
|
809
|
+
lease_until TEXT,
|
|
810
|
+
lease_token TEXT,
|
|
811
|
+
last_error TEXT,
|
|
812
|
+
created_at TEXT NOT NULL,
|
|
813
|
+
processed_at TEXT,
|
|
814
|
+
UNIQUE (workflow_id, connection_id, event_type, event_id)
|
|
815
|
+
);
|
|
816
|
+
|
|
817
|
+
INSERT INTO connector_inbox (
|
|
818
|
+
id, workflow_id, connection_id, connector_id, event_id, event_type,
|
|
819
|
+
event_timestamp, payload, status, attempts, available_at, lease_until,
|
|
820
|
+
lease_token, last_error, created_at, processed_at
|
|
821
|
+
)
|
|
822
|
+
SELECT
|
|
823
|
+
id, workflow_id, connection_id, connector_id, event_id, event_type,
|
|
824
|
+
event_timestamp, payload, status, attempts, available_at, lease_until,
|
|
825
|
+
lease_token, last_error, created_at, processed_at
|
|
826
|
+
FROM connector_inbox_v12;
|
|
827
|
+
|
|
828
|
+
DROP TABLE connector_inbox_v12;
|
|
829
|
+
|
|
830
|
+
CREATE INDEX idx_connector_inbox_ready
|
|
831
|
+
ON connector_inbox(status, available_at, lease_until);
|
|
832
|
+
CREATE INDEX idx_connector_inbox_connection
|
|
833
|
+
ON connector_inbox(connection_id, created_at);
|
|
834
|
+
`);
|
|
835
|
+
d.prepare(
|
|
836
|
+
"INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('schema_version', '13')"
|
|
837
|
+
).run();
|
|
838
|
+
})();
|
|
839
|
+
logger_default.info("[database] migrated schema to version 13 (connection-scoped inbox)");
|
|
840
|
+
}
|
|
645
841
|
}
|
|
646
842
|
function verifySchema(d) {
|
|
647
843
|
const expectedByTable = {
|
|
@@ -679,6 +875,31 @@ function verifySchema(d) {
|
|
|
679
875
|
ddl: "ALTER TABLE agent_commands ADD COLUMN headless_args TEXT"
|
|
680
876
|
}
|
|
681
877
|
],
|
|
878
|
+
workflow_runs: [
|
|
879
|
+
{ column: "inputs", ddl: "ALTER TABLE workflow_runs ADD COLUMN inputs TEXT" },
|
|
880
|
+
{
|
|
881
|
+
column: "connector_item",
|
|
882
|
+
ddl: "ALTER TABLE workflow_runs ADD COLUMN connector_item TEXT"
|
|
883
|
+
},
|
|
884
|
+
{
|
|
885
|
+
column: "connector_inbox_id",
|
|
886
|
+
ddl: "ALTER TABLE workflow_runs ADD COLUMN connector_inbox_id INTEGER"
|
|
887
|
+
},
|
|
888
|
+
{
|
|
889
|
+
column: "connector_inbox_lease_token",
|
|
890
|
+
ddl: "ALTER TABLE workflow_runs ADD COLUMN connector_inbox_lease_token TEXT"
|
|
891
|
+
},
|
|
892
|
+
{
|
|
893
|
+
column: "connector_inbox_disposition",
|
|
894
|
+
ddl: "ALTER TABLE workflow_runs ADD COLUMN connector_inbox_disposition TEXT"
|
|
895
|
+
}
|
|
896
|
+
],
|
|
897
|
+
connector_inbox: [
|
|
898
|
+
{
|
|
899
|
+
column: "lease_token",
|
|
900
|
+
ddl: "ALTER TABLE connector_inbox ADD COLUMN lease_token TEXT"
|
|
901
|
+
}
|
|
902
|
+
],
|
|
682
903
|
workflow_run_nodes: [
|
|
683
904
|
{ column: "agent_type", ddl: "ALTER TABLE workflow_run_nodes ADD COLUMN agent_type TEXT" },
|
|
684
905
|
{
|
|
@@ -886,13 +1107,30 @@ function saveConfig(config) {
|
|
|
886
1107
|
p.workspaceId ?? "personal"
|
|
887
1108
|
);
|
|
888
1109
|
}
|
|
889
|
-
|
|
890
|
-
const
|
|
1110
|
+
const workflows = config.workflows ?? [];
|
|
1111
|
+
const workflowIds = new Set(workflows.map((workflow) => workflow.id));
|
|
1112
|
+
const existingWorkflowIds = d.prepare("SELECT id FROM workflows").all();
|
|
1113
|
+
const deleteWorkflow = d.prepare("DELETE FROM workflows WHERE id = ?");
|
|
1114
|
+
for (const { id } of existingWorkflowIds) {
|
|
1115
|
+
if (!workflowIds.has(id)) deleteWorkflow.run(id);
|
|
1116
|
+
}
|
|
1117
|
+
const upsertWorkflow = d.prepare(
|
|
891
1118
|
`INSERT INTO workflows (id, name, icon, icon_color, nodes, edges, enabled, last_run_at, last_run_status, stagger_delay_ms, workspace_id)
|
|
892
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1119
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1120
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
1121
|
+
name = excluded.name,
|
|
1122
|
+
icon = excluded.icon,
|
|
1123
|
+
icon_color = excluded.icon_color,
|
|
1124
|
+
nodes = excluded.nodes,
|
|
1125
|
+
edges = excluded.edges,
|
|
1126
|
+
enabled = excluded.enabled,
|
|
1127
|
+
last_run_at = excluded.last_run_at,
|
|
1128
|
+
last_run_status = excluded.last_run_status,
|
|
1129
|
+
stagger_delay_ms = excluded.stagger_delay_ms,
|
|
1130
|
+
workspace_id = excluded.workspace_id`
|
|
893
1131
|
);
|
|
894
|
-
for (const w of
|
|
895
|
-
|
|
1132
|
+
for (const w of workflows) {
|
|
1133
|
+
upsertWorkflow.run(
|
|
896
1134
|
w.id,
|
|
897
1135
|
w.name,
|
|
898
1136
|
w.icon,
|
|
@@ -1357,17 +1595,37 @@ function fetchNodesByRunIds(d, runIds) {
|
|
|
1357
1595
|
}
|
|
1358
1596
|
return out;
|
|
1359
1597
|
}
|
|
1598
|
+
function parseRunInputs(raw) {
|
|
1599
|
+
if (!raw) return void 0;
|
|
1600
|
+
try {
|
|
1601
|
+
const parsed = JSON.parse(raw);
|
|
1602
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
1603
|
+
} catch {
|
|
1604
|
+
return void 0;
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1360
1607
|
function mapRunRows(rows, nodesByRun) {
|
|
1361
|
-
return rows.map((r) =>
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1608
|
+
return rows.map((r) => {
|
|
1609
|
+
const inputs = parseRunInputs(r.inputs);
|
|
1610
|
+
const connectorItem = parseRunInputs(r.connector_item);
|
|
1611
|
+
return {
|
|
1612
|
+
runId: r.id,
|
|
1613
|
+
workflowId: r.workflow_id,
|
|
1614
|
+
startedAt: r.started_at,
|
|
1615
|
+
...r.completed_at != null && { completedAt: r.completed_at },
|
|
1616
|
+
status: r.status,
|
|
1617
|
+
...r.trigger_task_id != null && { triggerTaskId: r.trigger_task_id },
|
|
1618
|
+
...inputs && { inputs },
|
|
1619
|
+
...connectorItem && { connectorItem },
|
|
1620
|
+
...r.connector_inbox_id != null && { connectorInboxId: r.connector_inbox_id },
|
|
1621
|
+
...r.connector_inbox_lease_token != null && {
|
|
1622
|
+
connectorInboxLeaseToken: r.connector_inbox_lease_token
|
|
1623
|
+
},
|
|
1624
|
+
...r.connector_inbox_disposition === "processed" || r.connector_inbox_disposition === "retry" ? { connectorInboxDisposition: r.connector_inbox_disposition } : {},
|
|
1625
|
+
...r.workflow_name != null && { workflowName: r.workflow_name },
|
|
1626
|
+
nodeStates: nodesByRun.get(r.id) ?? []
|
|
1627
|
+
};
|
|
1628
|
+
});
|
|
1371
1629
|
}
|
|
1372
1630
|
function listWorkflowRuns(workflowId, limit = 20) {
|
|
1373
1631
|
const d = getDb();
|
|
@@ -2059,7 +2317,7 @@ function readPort() {
|
|
|
2059
2317
|
return discoverAndHeal();
|
|
2060
2318
|
}
|
|
2061
2319
|
}
|
|
2062
|
-
async function rpcCall(method, params) {
|
|
2320
|
+
async function rpcCall(method, params, timeoutMs = TIMEOUT_MS) {
|
|
2063
2321
|
const result = readPort();
|
|
2064
2322
|
if (!result.port) {
|
|
2065
2323
|
throw new Error(result.reason === "invalid" ? PORT_FILE_INVALID_MSG : PORT_FILE_MISSING_MSG);
|
|
@@ -2069,8 +2327,8 @@ async function rpcCall(method, params) {
|
|
|
2069
2327
|
const id = ++rpcId;
|
|
2070
2328
|
const timer = setTimeout(() => {
|
|
2071
2329
|
ws.close();
|
|
2072
|
-
reject(new Error(`RPC call "${method}" timed out after ${
|
|
2073
|
-
},
|
|
2330
|
+
reject(new Error(`RPC call "${method}" timed out after ${timeoutMs}ms`));
|
|
2331
|
+
}, timeoutMs);
|
|
2074
2332
|
ws.on("open", () => {
|
|
2075
2333
|
ws.send(JSON.stringify({ jsonrpc: "2.0", id, method, params }));
|
|
2076
2334
|
});
|
|
@@ -2848,6 +3106,249 @@ function registerWorkspaceTools(server) {
|
|
|
2848
3106
|
);
|
|
2849
3107
|
}
|
|
2850
3108
|
|
|
3109
|
+
// src/tools/connectors.ts
|
|
3110
|
+
import { z as z7 } from "zod";
|
|
3111
|
+
var PROBE_TIMEOUT_MS = 12e4;
|
|
3112
|
+
var json = (value) => ({
|
|
3113
|
+
content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
|
|
3114
|
+
});
|
|
3115
|
+
var failure = (message) => ({
|
|
3116
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
3117
|
+
isError: true
|
|
3118
|
+
});
|
|
3119
|
+
function registerConnectorTools(server) {
|
|
3120
|
+
server.tool(
|
|
3121
|
+
"list_connectors",
|
|
3122
|
+
"List every connector: the ones built into Vorn, the ones installable from a package, and how many connections each already has. Use this before creating a workflow that calls a connector action, or to find the id of a connector to install.",
|
|
3123
|
+
{
|
|
3124
|
+
installable_only: z7.boolean().optional().describe("Only connectors that are not set up yet")
|
|
3125
|
+
},
|
|
3126
|
+
async (args) => {
|
|
3127
|
+
const [builtIns, catalog, connections, statuses] = await Promise.all([
|
|
3128
|
+
rpcCall("connector:list"),
|
|
3129
|
+
rpcCall("connector:catalog"),
|
|
3130
|
+
rpcCall("connection:list", { connectorId: void 0 }),
|
|
3131
|
+
rpcCall("connector:status")
|
|
3132
|
+
]);
|
|
3133
|
+
const countFor = (id) => connections.filter((conn) => connectionConnectorId(conn) === id).length;
|
|
3134
|
+
const statusFor = (id) => statuses.find((s) => s.connectorId === id);
|
|
3135
|
+
const entries = [
|
|
3136
|
+
...builtIns.map((c) => ({
|
|
3137
|
+
id: c.id,
|
|
3138
|
+
name: c.name,
|
|
3139
|
+
source: "built-in",
|
|
3140
|
+
capabilities: c.capabilities,
|
|
3141
|
+
connections: countFor(c.id),
|
|
3142
|
+
// Only meaningful for connectors that authenticate up front; the
|
|
3143
|
+
// rest report nothing rather than a misleading "not authed".
|
|
3144
|
+
...statusFor(c.id) && {
|
|
3145
|
+
authenticated: statusFor(c.id).authed,
|
|
3146
|
+
...statusFor(c.id).message && { authMessage: statusFor(c.id).message }
|
|
3147
|
+
}
|
|
3148
|
+
})),
|
|
3149
|
+
...catalog.map((entry) => ({
|
|
3150
|
+
id: entry.id,
|
|
3151
|
+
name: entry.name,
|
|
3152
|
+
source: "package",
|
|
3153
|
+
description: entry.description,
|
|
3154
|
+
package: entry.packageName,
|
|
3155
|
+
capabilities: entry.capabilities,
|
|
3156
|
+
connections: countFor(entry.id),
|
|
3157
|
+
...entry.auth && { auth: entry.auth }
|
|
3158
|
+
}))
|
|
3159
|
+
];
|
|
3160
|
+
return json(args.installable_only ? entries.filter((e) => e.connections === 0) : entries);
|
|
3161
|
+
}
|
|
3162
|
+
);
|
|
3163
|
+
server.tool(
|
|
3164
|
+
"list_connections",
|
|
3165
|
+
"List configured connector connections, including when each last synced and the error from its last failure. Use this to diagnose a connector that is not producing tasks.",
|
|
3166
|
+
{
|
|
3167
|
+
connector_id: V.id.optional().describe("Only connections for this connector"),
|
|
3168
|
+
failing_only: z7.boolean().optional().describe("Only connections whose last sync failed")
|
|
3169
|
+
},
|
|
3170
|
+
async (args) => {
|
|
3171
|
+
const connections = await rpcCall("connection:list", {
|
|
3172
|
+
connectorId: void 0
|
|
3173
|
+
});
|
|
3174
|
+
const visible = connections.filter((conn) => !args.connector_id || connectionConnectorId(conn) === args.connector_id).filter((conn) => !args.failing_only || !!conn.lastSyncError);
|
|
3175
|
+
return json(
|
|
3176
|
+
visible.map((conn) => ({
|
|
3177
|
+
id: conn.id,
|
|
3178
|
+
name: conn.name,
|
|
3179
|
+
connectorId: connectionConnectorId(conn),
|
|
3180
|
+
project: conn.executionProject,
|
|
3181
|
+
syncIntervalMinutes: conn.syncIntervalMinutes,
|
|
3182
|
+
lastSyncAt: conn.lastSyncAt,
|
|
3183
|
+
lastSyncError: conn.lastSyncError,
|
|
3184
|
+
// Deliberately not the whole `filters` blob: it holds encrypted
|
|
3185
|
+
// credentials, and an agent has no use for ciphertext.
|
|
3186
|
+
config: publicFilters(conn)
|
|
3187
|
+
}))
|
|
3188
|
+
);
|
|
3189
|
+
}
|
|
3190
|
+
);
|
|
3191
|
+
server.tool(
|
|
3192
|
+
"list_connector_actions",
|
|
3193
|
+
"List the actions a connection can execute, with their input schemas. Call this before run_connector_action or before adding a callConnectorAction node to a workflow.",
|
|
3194
|
+
{ connection_id: V.id.describe("Connection ID") },
|
|
3195
|
+
async (args) => {
|
|
3196
|
+
const actions = await rpcCall(
|
|
3197
|
+
"connection:listActions",
|
|
3198
|
+
args.connection_id
|
|
3199
|
+
);
|
|
3200
|
+
if (actions.length === 0) {
|
|
3201
|
+
return failure(
|
|
3202
|
+
`No actions for connection "${args.connection_id}". Either the connection does not exist, or its connector exposes no actions yet \u2014 for an MCP connection, tool discovery may still be running.`
|
|
3203
|
+
);
|
|
3204
|
+
}
|
|
3205
|
+
return json(actions);
|
|
3206
|
+
}
|
|
3207
|
+
);
|
|
3208
|
+
server.tool(
|
|
3209
|
+
"inspect_connector_package",
|
|
3210
|
+
"Start a connector package and read what it offers \u2014 its triggers, actions and required environment variables \u2014 without installing it. Use this to review a connector before install_connector, or to check a local build.",
|
|
3211
|
+
{
|
|
3212
|
+
package: V.shortText.describe(
|
|
3213
|
+
'npm package name, or a command to run a local build (e.g. "node /path/to/dist/index.js")'
|
|
3214
|
+
)
|
|
3215
|
+
},
|
|
3216
|
+
async (args) => {
|
|
3217
|
+
const result = await probe(args.package);
|
|
3218
|
+
if (!result.ok) return failure(result.error);
|
|
3219
|
+
return json(result.manifest);
|
|
3220
|
+
}
|
|
3221
|
+
);
|
|
3222
|
+
server.tool(
|
|
3223
|
+
"install_connector",
|
|
3224
|
+
"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.",
|
|
3225
|
+
{
|
|
3226
|
+
connector_id: V.id.optional().describe("Catalog connector id (from list_connectors). Use this or package."),
|
|
3227
|
+
package: V.shortText.optional().describe("npm package name or launch command"),
|
|
3228
|
+
name: V.title.optional().describe("Connection name (defaults to the connector name)"),
|
|
3229
|
+
project: V.name.optional().describe("Vorn project tasks should be created in"),
|
|
3230
|
+
trigger: V.shortText.optional().describe("Trigger type to configure (defaults to the first the connector offers)"),
|
|
3231
|
+
env: z7.record(z7.string(), z7.string()).optional().describe("Non-secret environment variables the connector needs"),
|
|
3232
|
+
sync_interval_minutes: z7.number().int().min(1).max(1440).optional()
|
|
3233
|
+
},
|
|
3234
|
+
async (args) => {
|
|
3235
|
+
const catalog = await rpcCall("connector:catalog");
|
|
3236
|
+
const entry = args.connector_id ? catalog.find((c) => c.id === args.connector_id) : void 0;
|
|
3237
|
+
if (args.connector_id && !entry) {
|
|
3238
|
+
return failure(
|
|
3239
|
+
`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.`
|
|
3240
|
+
);
|
|
3241
|
+
}
|
|
3242
|
+
const target = entry ? entry.launch : args.package;
|
|
3243
|
+
if (!target) return failure("Provide either connector_id or package.");
|
|
3244
|
+
const result = await probe(target);
|
|
3245
|
+
if (!result.ok) return failure(result.error);
|
|
3246
|
+
const manifest = result.manifest;
|
|
3247
|
+
const supplied = args.env ?? {};
|
|
3248
|
+
const unknown = Object.keys(supplied).filter(
|
|
3249
|
+
(name) => !manifest.env.some((e) => e.name === name)
|
|
3250
|
+
);
|
|
3251
|
+
if (unknown.length > 0) {
|
|
3252
|
+
return failure(
|
|
3253
|
+
`${manifest.name} does not use ${unknown.join(", ")}. It accepts: ${manifest.env.map((e) => e.name).join(", ") || "(none)"}.`
|
|
3254
|
+
);
|
|
3255
|
+
}
|
|
3256
|
+
const secrets = manifest.env.filter((e) => e.secret && (e.required || supplied[e.name]));
|
|
3257
|
+
if (secrets.length > 0) {
|
|
3258
|
+
return failure(
|
|
3259
|
+
`${manifest.name} uses the secret ${plural(secrets.length, "value")} ${secrets.map((e) => e.name).join(", ")}, which this tool cannot accept: it runs outside the desktop process, where encryption lives, so it could only store them unprotected. They must be entered by a person in Settings > Connectors to reach the OS keychain. Everything else about the connector is ready to install.`
|
|
3260
|
+
);
|
|
3261
|
+
}
|
|
3262
|
+
const missing = manifest.env.filter((e) => e.required && !supplied[e.name]?.trim());
|
|
3263
|
+
if (missing.length > 0) {
|
|
3264
|
+
return failure(
|
|
3265
|
+
`${manifest.name} needs ${missing.map((e) => describeEnv(e)).join(", ")}. Pass them in \`env\`.`
|
|
3266
|
+
);
|
|
3267
|
+
}
|
|
3268
|
+
const trigger = args.trigger ? manifest.triggers.find((t) => t.type === args.trigger) : manifest.triggers[0];
|
|
3269
|
+
if (args.trigger && !trigger) {
|
|
3270
|
+
return failure(
|
|
3271
|
+
`${manifest.name} has no trigger "${args.trigger}". It offers: ${manifest.triggers.map((t) => t.type).join(", ") || "(none)"}.`
|
|
3272
|
+
);
|
|
3273
|
+
}
|
|
3274
|
+
const launch = typeof target === "string" ? parseLaunch(target) : target;
|
|
3275
|
+
const connection = await rpcCall("connection:create", {
|
|
3276
|
+
connectorId: "mcp",
|
|
3277
|
+
name: args.name ?? (trigger ? `${manifest.name}: ${trigger.label}` : manifest.name),
|
|
3278
|
+
filters: {
|
|
3279
|
+
command: launch.command,
|
|
3280
|
+
args: JSON.stringify(launch.args),
|
|
3281
|
+
env: JSON.stringify(supplied),
|
|
3282
|
+
[SDK_FILTER_KEYS.connectorId]: manifest.id,
|
|
3283
|
+
[SDK_FILTER_KEYS.version]: manifest.version,
|
|
3284
|
+
...manifest.icon && { [SDK_FILTER_KEYS.icon]: JSON.stringify(manifest.icon) },
|
|
3285
|
+
...trigger?.filters ?? {}
|
|
3286
|
+
},
|
|
3287
|
+
syncIntervalMinutes: args.sync_interval_minutes ?? 5,
|
|
3288
|
+
statusMapping: {},
|
|
3289
|
+
...args.project && { executionProject: args.project }
|
|
3290
|
+
});
|
|
3291
|
+
return json({
|
|
3292
|
+
installed: manifest.name,
|
|
3293
|
+
connectionId: connection.id,
|
|
3294
|
+
trigger: trigger?.type,
|
|
3295
|
+
note: "Poll it now with backfill_connection, or reference it from a workflow."
|
|
3296
|
+
});
|
|
3297
|
+
}
|
|
3298
|
+
);
|
|
3299
|
+
server.tool(
|
|
3300
|
+
"run_connector_action",
|
|
3301
|
+
"Execute one action on a connection \u2014 create an issue, run a query, close a work item. Call list_connector_actions first for the action name and its arguments.",
|
|
3302
|
+
{
|
|
3303
|
+
connection_id: V.id.describe("Connection ID"),
|
|
3304
|
+
action: V.shortText.describe("Action name from list_connector_actions"),
|
|
3305
|
+
args: z7.record(z7.string(), z7.unknown()).optional().describe("Action arguments")
|
|
3306
|
+
},
|
|
3307
|
+
async (args) => {
|
|
3308
|
+
const result = await rpcCall("connection:executeAction", {
|
|
3309
|
+
connectionId: args.connection_id,
|
|
3310
|
+
action: args.action,
|
|
3311
|
+
args: args.args ?? {}
|
|
3312
|
+
});
|
|
3313
|
+
if (!result.success) return failure(result.error ?? "Action failed");
|
|
3314
|
+
return json(result);
|
|
3315
|
+
}
|
|
3316
|
+
);
|
|
3317
|
+
server.tool(
|
|
3318
|
+
"backfill_connection",
|
|
3319
|
+
"Pull items from a connection now and turn them into tasks, without waiting for its poll interval. Use this to verify a connection works after installing it.",
|
|
3320
|
+
{ connection_id: V.id.describe("Connection ID") },
|
|
3321
|
+
async (args) => {
|
|
3322
|
+
const result = await rpcCall(
|
|
3323
|
+
"connection:backfill",
|
|
3324
|
+
{ connectionId: args.connection_id },
|
|
3325
|
+
PROBE_TIMEOUT_MS
|
|
3326
|
+
);
|
|
3327
|
+
if (result.error) return failure(result.error);
|
|
3328
|
+
return json(result);
|
|
3329
|
+
}
|
|
3330
|
+
);
|
|
3331
|
+
}
|
|
3332
|
+
async function probe(target) {
|
|
3333
|
+
const launch = typeof target === "string" ? parseLaunch(target) : target;
|
|
3334
|
+
return rpcCall("connector:probeSdk", launch, PROBE_TIMEOUT_MS);
|
|
3335
|
+
}
|
|
3336
|
+
function parseLaunch(spec) {
|
|
3337
|
+
const parts = spec.trim().split(/\s+/);
|
|
3338
|
+
if (parts.length === 1) return { command: "npx", args: ["-y", parts[0]] };
|
|
3339
|
+
return { command: parts[0], args: parts.slice(1) };
|
|
3340
|
+
}
|
|
3341
|
+
function publicFilters(conn) {
|
|
3342
|
+
const hidden = /* @__PURE__ */ new Set(["secretEnv", "discoveredTools"]);
|
|
3343
|
+
return Object.fromEntries(Object.entries(conn.filters ?? {}).filter(([key]) => !hidden.has(key)));
|
|
3344
|
+
}
|
|
3345
|
+
function describeEnv(entry) {
|
|
3346
|
+
return entry.description ? `${entry.name} (${entry.description})` : entry.name;
|
|
3347
|
+
}
|
|
3348
|
+
function plural(count, word) {
|
|
3349
|
+
return count === 1 ? word : `${word}s`;
|
|
3350
|
+
}
|
|
3351
|
+
|
|
2851
3352
|
// src/server.ts
|
|
2852
3353
|
function createMcpServer(version) {
|
|
2853
3354
|
const server = new McpServer({ name: "vorn", version }, { capabilities: { tools: {} } });
|
|
@@ -2857,6 +3358,7 @@ function createMcpServer(version) {
|
|
|
2857
3358
|
registerSessionTools(server);
|
|
2858
3359
|
registerWorkflowTools(server);
|
|
2859
3360
|
registerWorkspaceTools(server);
|
|
3361
|
+
registerConnectorTools(server);
|
|
2860
3362
|
return server;
|
|
2861
3363
|
}
|
|
2862
3364
|
|
|
@@ -2869,7 +3371,7 @@ console.warn = (...args) => _origError("[mcp:warn]", ...args);
|
|
|
2869
3371
|
console.error = (...args) => _origError("[mcp:error]", ...args);
|
|
2870
3372
|
async function main() {
|
|
2871
3373
|
configManager.init();
|
|
2872
|
-
const version = true ? "0.5.
|
|
3374
|
+
const version = true ? "0.5.3" : createRequire(import.meta.url)("../package.json").version;
|
|
2873
3375
|
const server = createMcpServer(version);
|
|
2874
3376
|
const transport = new StdioServerTransport();
|
|
2875
3377
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vornrun/mcp",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
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.5.
|
|
42
|
-
"@vornrun/shared": "0.5.
|
|
41
|
+
"@vornrun/server": "0.5.3",
|
|
42
|
+
"@vornrun/shared": "0.5.3",
|
|
43
43
|
"tsup": "^8.5.1",
|
|
44
44
|
"tsx": "^4.23.1",
|
|
45
45
|
"typescript": "^6.0.3"
|