@vornrun/mcp 0.5.2 → 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 +257 -4
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -107,6 +107,15 @@ var DEFAULT_WORKSPACE = {
107
107
  function isTerminalTaskStatus(status) {
108
108
  return status === "done" || status === "cancelled";
109
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
+ }
110
119
 
111
120
  // ../server/src/default-workflows.ts
112
121
  var DEFAULT_TASK_WORKFLOW_ID = "system:default-task-workflow";
@@ -2308,7 +2317,7 @@ function readPort() {
2308
2317
  return discoverAndHeal();
2309
2318
  }
2310
2319
  }
2311
- async function rpcCall(method, params) {
2320
+ async function rpcCall(method, params, timeoutMs = TIMEOUT_MS) {
2312
2321
  const result = readPort();
2313
2322
  if (!result.port) {
2314
2323
  throw new Error(result.reason === "invalid" ? PORT_FILE_INVALID_MSG : PORT_FILE_MISSING_MSG);
@@ -2318,8 +2327,8 @@ async function rpcCall(method, params) {
2318
2327
  const id = ++rpcId;
2319
2328
  const timer = setTimeout(() => {
2320
2329
  ws.close();
2321
- reject(new Error(`RPC call "${method}" timed out after ${TIMEOUT_MS}ms`));
2322
- }, TIMEOUT_MS);
2330
+ reject(new Error(`RPC call "${method}" timed out after ${timeoutMs}ms`));
2331
+ }, timeoutMs);
2323
2332
  ws.on("open", () => {
2324
2333
  ws.send(JSON.stringify({ jsonrpc: "2.0", id, method, params }));
2325
2334
  });
@@ -3097,6 +3106,249 @@ function registerWorkspaceTools(server) {
3097
3106
  );
3098
3107
  }
3099
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
+
3100
3352
  // src/server.ts
3101
3353
  function createMcpServer(version) {
3102
3354
  const server = new McpServer({ name: "vorn", version }, { capabilities: { tools: {} } });
@@ -3106,6 +3358,7 @@ function createMcpServer(version) {
3106
3358
  registerSessionTools(server);
3107
3359
  registerWorkflowTools(server);
3108
3360
  registerWorkspaceTools(server);
3361
+ registerConnectorTools(server);
3109
3362
  return server;
3110
3363
  }
3111
3364
 
@@ -3118,7 +3371,7 @@ console.warn = (...args) => _origError("[mcp:warn]", ...args);
3118
3371
  console.error = (...args) => _origError("[mcp:error]", ...args);
3119
3372
  async function main() {
3120
3373
  configManager.init();
3121
- const version = true ? "0.5.2" : createRequire(import.meta.url)("../package.json").version;
3374
+ const version = true ? "0.5.3" : createRequire(import.meta.url)("../package.json").version;
3122
3375
  const server = createMcpServer(version);
3123
3376
  const transport = new StdioServerTransport();
3124
3377
  await server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vornrun/mcp",
3
- "version": "0.5.2",
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.2",
42
- "@vornrun/shared": "0.5.2",
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"