@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.
Files changed (2) hide show
  1. package/dist/index.js +522 -20
  2. 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
- d.prepare("DELETE FROM workflows").run();
890
- const insertWorkflow = d.prepare(
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 config.workflows ?? []) {
895
- insertWorkflow.run(
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
- runId: r.id,
1363
- workflowId: r.workflow_id,
1364
- startedAt: r.started_at,
1365
- ...r.completed_at != null && { completedAt: r.completed_at },
1366
- status: r.status,
1367
- ...r.trigger_task_id != null && { triggerTaskId: r.trigger_task_id },
1368
- ...r.workflow_name != null && { workflowName: r.workflow_name },
1369
- nodeStates: nodesByRun.get(r.id) ?? []
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 ${TIMEOUT_MS}ms`));
2073
- }, TIMEOUT_MS);
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.1" : createRequire(import.meta.url)("../package.json").version;
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.1",
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.1",
42
- "@vornrun/shared": "0.5.1",
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"