@vornrun/mcp 0.7.0-beta.6 → 0.7.0-beta.8
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 +155 -66
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1615,7 +1615,7 @@ var configManager = new ConfigManager();
|
|
|
1615
1615
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
1616
1616
|
|
|
1617
1617
|
// src/tools/tasks.ts
|
|
1618
|
-
import
|
|
1618
|
+
import crypto2 from "crypto";
|
|
1619
1619
|
import path4 from "path";
|
|
1620
1620
|
import { z as z2 } from "zod";
|
|
1621
1621
|
|
|
@@ -2012,7 +2012,7 @@ function registerTaskTools(server) {
|
|
|
2012
2012
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2013
2013
|
const status = args.status ?? "todo";
|
|
2014
2014
|
const task = {
|
|
2015
|
-
id:
|
|
2015
|
+
id: crypto2.randomUUID(),
|
|
2016
2016
|
projectName: args.project_name,
|
|
2017
2017
|
title: args.title,
|
|
2018
2018
|
description: args.description ?? "",
|
|
@@ -2712,41 +2712,61 @@ function registerSessionTools(server) {
|
|
|
2712
2712
|
}
|
|
2713
2713
|
|
|
2714
2714
|
// src/tools/workflows.ts
|
|
2715
|
-
import
|
|
2715
|
+
import crypto3 from "crypto";
|
|
2716
2716
|
import { z as z5 } from "zod";
|
|
2717
2717
|
|
|
2718
|
-
// src/workflow-portability.ts
|
|
2718
|
+
// ../shared/src/workflow-portability.ts
|
|
2719
2719
|
var PROJECT_PATH_TOKEN = "{{project.path}}";
|
|
2720
2720
|
var PROJECT_NAME_TOKEN = "{{project.name}}";
|
|
2721
2721
|
var PORTABLE_FORMAT_VERSION = 1;
|
|
2722
|
+
var HTTP_PROFILE_CONNECTOR = "http";
|
|
2722
2723
|
function importedWorkflowId(bundle, slug) {
|
|
2723
2724
|
return `import:${bundle}:${slug}`;
|
|
2724
2725
|
}
|
|
2726
|
+
function importedWorkflowIdFor(bundle, slug, name, existing) {
|
|
2727
|
+
for (let attempt = 1; ; attempt++) {
|
|
2728
|
+
const id = importedWorkflowId(bundle, attempt === 1 ? slug : `${slug}-${attempt}`);
|
|
2729
|
+
const held = existing.find((workflow) => workflow.id === id);
|
|
2730
|
+
if (!held || held.name === name) return id;
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2725
2733
|
function slugify(name) {
|
|
2726
2734
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "workflow";
|
|
2727
2735
|
}
|
|
2728
|
-
function
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2736
|
+
function connectorOf(connection2) {
|
|
2737
|
+
return connectionConnectorId({
|
|
2738
|
+
connectorId: connection2.connectorId,
|
|
2739
|
+
filters: connection2.filters ?? {}
|
|
2740
|
+
});
|
|
2741
|
+
}
|
|
2742
|
+
function boundConnectionKey(node, config) {
|
|
2743
|
+
if (node.type === "trigger" && config.triggerType === "connectorPoll") return "connectionId";
|
|
2744
|
+
if (node.type === "callConnectorAction") return "connectionId";
|
|
2745
|
+
if (node.type === "httpRequest") return "profileConnectionId";
|
|
2746
|
+
return null;
|
|
2747
|
+
}
|
|
2748
|
+
function resolveRequirement(requirement, connections) {
|
|
2749
|
+
const candidates = connections.filter(
|
|
2750
|
+
(connection2) => requirement.kind === "httpProfile" ? connectorOf(connection2) === HTTP_PROFILE_CONNECTOR : requirement.connectorId !== "" && connectorOf(connection2) === requirement.connectorId
|
|
2751
|
+
);
|
|
2752
|
+
if (candidates.length === 0) return void 0;
|
|
2753
|
+
if (requirement.name !== "") {
|
|
2754
|
+
const named = candidates.filter((connection2) => connection2.name === requirement.name);
|
|
2755
|
+
if (named.length === 1) return named[0].id;
|
|
2738
2756
|
}
|
|
2739
|
-
return
|
|
2757
|
+
return candidates.length === 1 ? candidates[0].id : void 0;
|
|
2740
2758
|
}
|
|
2741
|
-
function toPortable(workflow, projectPath) {
|
|
2759
|
+
function toPortable(workflow, projectPath, connections = []) {
|
|
2742
2760
|
const slug = slugify(workflow.name);
|
|
2761
|
+
const requires = [];
|
|
2743
2762
|
const nodes = workflow.nodes.map((node) => {
|
|
2744
2763
|
const config = { ...node.config };
|
|
2764
|
+
if (node.type === "trigger" && config.triggerType === "webhook") config.token = "";
|
|
2745
2765
|
if (node.type === "launchAgent" || node.type === "script") {
|
|
2746
|
-
for (const
|
|
2747
|
-
const value = config[
|
|
2766
|
+
for (const key2 of ["projectPath", "cwd", "existingWorktreePath"]) {
|
|
2767
|
+
const value = config[key2];
|
|
2748
2768
|
if (typeof value === "string" && value) {
|
|
2749
|
-
config[
|
|
2769
|
+
config[key2] = replacePath(value, projectPath);
|
|
2750
2770
|
}
|
|
2751
2771
|
}
|
|
2752
2772
|
if (typeof config.projectName === "string" && config.projectName) {
|
|
@@ -2754,6 +2774,23 @@ function toPortable(workflow, projectPath) {
|
|
|
2754
2774
|
}
|
|
2755
2775
|
delete config.remoteHostId;
|
|
2756
2776
|
}
|
|
2777
|
+
const key = boundConnectionKey(node, config);
|
|
2778
|
+
const bound = key === null ? "" : config[key];
|
|
2779
|
+
if (key !== null && typeof bound === "string" && bound !== "") {
|
|
2780
|
+
const source2 = connections.find((connection2) => connection2.id === bound);
|
|
2781
|
+
const event = config.event;
|
|
2782
|
+
requires.push(
|
|
2783
|
+
key === "profileConnectionId" ? { kind: "httpProfile", nodeId: node.id, name: source2?.name ?? "" } : {
|
|
2784
|
+
kind: "connection",
|
|
2785
|
+
nodeId: node.id,
|
|
2786
|
+
connectorId: source2 ? connectorOf(source2) : "",
|
|
2787
|
+
name: source2?.name ?? "",
|
|
2788
|
+
...typeof event === "string" && event !== "" && { event }
|
|
2789
|
+
}
|
|
2790
|
+
);
|
|
2791
|
+
if (key === "profileConnectionId") delete config[key];
|
|
2792
|
+
else config[key] = "";
|
|
2793
|
+
}
|
|
2757
2794
|
return { ...node, config };
|
|
2758
2795
|
});
|
|
2759
2796
|
return {
|
|
@@ -2763,6 +2800,7 @@ function toPortable(workflow, projectPath) {
|
|
|
2763
2800
|
...workflow.icon && { icon: workflow.icon },
|
|
2764
2801
|
...workflow.iconColor && { iconColor: workflow.iconColor },
|
|
2765
2802
|
...workflow.staggerDelayMs !== void 0 && { staggerDelayMs: workflow.staggerDelayMs },
|
|
2803
|
+
...requires.length > 0 && { requires },
|
|
2766
2804
|
nodes,
|
|
2767
2805
|
edges: workflow.edges
|
|
2768
2806
|
};
|
|
@@ -2773,17 +2811,37 @@ function normalizeForCompare(p) {
|
|
|
2773
2811
|
function replacePath(value, projectPath) {
|
|
2774
2812
|
const v = normalizeForCompare(value);
|
|
2775
2813
|
const root = normalizeForCompare(projectPath);
|
|
2814
|
+
if (root === "") return value;
|
|
2776
2815
|
if (v === root) return PROJECT_PATH_TOKEN;
|
|
2777
2816
|
if (v.startsWith(`${root}/`)) return `${PROJECT_PATH_TOKEN}/${v.slice(root.length + 1)}`;
|
|
2778
2817
|
return value;
|
|
2779
2818
|
}
|
|
2780
|
-
function
|
|
2819
|
+
function unresolvedRequirements(portable, connections) {
|
|
2820
|
+
const present = new Set(portable.nodes.map((node) => node.id));
|
|
2821
|
+
return (portable.requires ?? []).filter(
|
|
2822
|
+
(requirement) => present.has(requirement.nodeId) && resolveRequirement(requirement, connections) === void 0
|
|
2823
|
+
);
|
|
2824
|
+
}
|
|
2825
|
+
function fromPortable(portable, bundle, project, connections = [], mintToken = () => crypto.randomUUID()) {
|
|
2826
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
2827
|
+
for (const requirement of portable.requires ?? []) {
|
|
2828
|
+
bindings.set(requirement.nodeId, [...bindings.get(requirement.nodeId) ?? [], requirement]);
|
|
2829
|
+
}
|
|
2781
2830
|
const nodes = portable.nodes.map((node) => {
|
|
2782
2831
|
const config = { ...node.config };
|
|
2783
2832
|
for (const [key, value] of Object.entries(config)) {
|
|
2784
2833
|
if (typeof value !== "string") continue;
|
|
2785
2834
|
config[key] = value.split(PROJECT_PATH_TOKEN).join(project.path.replace(/[/\\]+$/, "")).split(PROJECT_NAME_TOKEN).join(project.name);
|
|
2786
2835
|
}
|
|
2836
|
+
for (const requirement of bindings.get(node.id) ?? []) {
|
|
2837
|
+
const resolved = resolveRequirement(requirement, connections);
|
|
2838
|
+
if (resolved === void 0) continue;
|
|
2839
|
+
if (requirement.kind === "httpProfile") config.profileConnectionId = resolved;
|
|
2840
|
+
else config.connectionId = resolved;
|
|
2841
|
+
}
|
|
2842
|
+
if (node.type === "trigger" && config.triggerType === "webhook" && !config.token) {
|
|
2843
|
+
config.token = mintToken();
|
|
2844
|
+
}
|
|
2787
2845
|
return { ...node, config };
|
|
2788
2846
|
});
|
|
2789
2847
|
return {
|
|
@@ -2791,7 +2849,9 @@ function fromPortable(portable, bundle, project) {
|
|
|
2791
2849
|
name: portable.name,
|
|
2792
2850
|
icon: portable.icon ?? "Zap",
|
|
2793
2851
|
iconColor: portable.iconColor ?? "#6366f1",
|
|
2794
|
-
|
|
2852
|
+
// A file cannot ask to be running: a dropped cron workflow would start
|
|
2853
|
+
// firing before anyone had read it. Callers restore what they had.
|
|
2854
|
+
enabled: false,
|
|
2795
2855
|
...portable.staggerDelayMs !== void 0 && { staggerDelayMs: portable.staggerDelayMs },
|
|
2796
2856
|
nodes,
|
|
2797
2857
|
edges: portable.edges
|
|
@@ -2914,6 +2974,18 @@ var triggerConfigSchema = z5.union([
|
|
|
2914
2974
|
projectFilter: V.name.optional(),
|
|
2915
2975
|
fromStatus: z5.enum(["todo", "in_progress", "in_review", "done", "cancelled"]).optional(),
|
|
2916
2976
|
toStatus: z5.enum(["todo", "in_progress", "in_review", "done", "cancelled"]).optional()
|
|
2977
|
+
}),
|
|
2978
|
+
z5.object({
|
|
2979
|
+
triggerType: z5.literal("connectorPoll"),
|
|
2980
|
+
connectionId: V.id,
|
|
2981
|
+
event: V.shortText,
|
|
2982
|
+
cron: V.shortText,
|
|
2983
|
+
timezone: V.shortText.optional()
|
|
2984
|
+
}),
|
|
2985
|
+
z5.object({
|
|
2986
|
+
triggerType: z5.literal("webhook"),
|
|
2987
|
+
method: z5.enum(["POST", "GET"]),
|
|
2988
|
+
token: V.shortText
|
|
2917
2989
|
})
|
|
2918
2990
|
]);
|
|
2919
2991
|
var nodeSchema = z5.object({
|
|
@@ -2928,6 +3000,7 @@ var nodeSchema = z5.object({
|
|
|
2928
3000
|
"approval",
|
|
2929
3001
|
"createTaskFromItem",
|
|
2930
3002
|
"callConnectorAction",
|
|
3003
|
+
"httpRequest",
|
|
2931
3004
|
"loop"
|
|
2932
3005
|
]),
|
|
2933
3006
|
label: V.shortText,
|
|
@@ -2989,7 +3062,7 @@ function buildGraphFromFlat(trigger, actions) {
|
|
|
2989
3062
|
const nodes = [];
|
|
2990
3063
|
const edges = [];
|
|
2991
3064
|
const triggerNode = {
|
|
2992
|
-
id:
|
|
3065
|
+
id: crypto3.randomUUID(),
|
|
2993
3066
|
type: "trigger",
|
|
2994
3067
|
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",
|
|
2995
3068
|
config: trigger,
|
|
@@ -3000,7 +3073,7 @@ function buildGraphFromFlat(trigger, actions) {
|
|
|
3000
3073
|
const NODE_GAP = 140;
|
|
3001
3074
|
for (let i = 0; i < actions.length; i++) {
|
|
3002
3075
|
const action = actions[i];
|
|
3003
|
-
const nodeId =
|
|
3076
|
+
const nodeId = crypto3.randomUUID();
|
|
3004
3077
|
nodes.push({
|
|
3005
3078
|
id: nodeId,
|
|
3006
3079
|
type: "launchAgent",
|
|
@@ -3009,7 +3082,7 @@ function buildGraphFromFlat(trigger, actions) {
|
|
|
3009
3082
|
position: { x: 0, y: (i + 1) * NODE_GAP }
|
|
3010
3083
|
});
|
|
3011
3084
|
edges.push({
|
|
3012
|
-
id:
|
|
3085
|
+
id: crypto3.randomUUID(),
|
|
3013
3086
|
source: prevId,
|
|
3014
3087
|
target: nodeId
|
|
3015
3088
|
});
|
|
@@ -3086,6 +3159,13 @@ function resolveWorkflowId(args) {
|
|
|
3086
3159
|
}
|
|
3087
3160
|
return { id };
|
|
3088
3161
|
}
|
|
3162
|
+
async function listPortableConnections() {
|
|
3163
|
+
try {
|
|
3164
|
+
return await rpcCall("connection:list", { connectorId: void 0 });
|
|
3165
|
+
} catch {
|
|
3166
|
+
return [];
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3089
3169
|
function registerWorkflowTools(server) {
|
|
3090
3170
|
server.tool(
|
|
3091
3171
|
"list_workflows",
|
|
@@ -3138,7 +3218,7 @@ function registerWorkflowTools(server) {
|
|
|
3138
3218
|
edges = graph.edges;
|
|
3139
3219
|
}
|
|
3140
3220
|
const workflow = {
|
|
3141
|
-
id:
|
|
3221
|
+
id: crypto3.randomUUID(),
|
|
3142
3222
|
name: args.name,
|
|
3143
3223
|
icon: args.icon ?? "Zap",
|
|
3144
3224
|
iconColor: args.icon_color ?? "#6366f1",
|
|
@@ -3408,7 +3488,7 @@ Run history: list_workflow_runs with workflow_id ${args.workflow_id}`
|
|
|
3408
3488
|
);
|
|
3409
3489
|
server.tool(
|
|
3410
3490
|
"export_workflow",
|
|
3411
|
-
"Export a workflow as a portable file you can commit beside the code it drives. Absolute paths become {{project.path}} and the local remote-host binding is dropped, so it runs on another machine after import.
|
|
3491
|
+
"Export a workflow as a portable file you can commit beside the code it drives. Absolute paths become {{project.path}} and the local remote-host binding is dropped, so it runs on another machine after import. Connections are dropped too and recorded as requirements the importing machine rebinds by connector and name.",
|
|
3412
3492
|
{
|
|
3413
3493
|
workflow_id: V.id.optional().describe("Workflow ID (from list_workflows)"),
|
|
3414
3494
|
id: V.id.optional().describe("Deprecated alias for workflow_id")
|
|
@@ -3425,18 +3505,6 @@ Run history: list_workflow_runs with workflow_id ${args.workflow_id}`
|
|
|
3425
3505
|
isError: true
|
|
3426
3506
|
};
|
|
3427
3507
|
}
|
|
3428
|
-
const blockers = portabilityBlockers(workflow);
|
|
3429
|
-
if (blockers.length > 0) {
|
|
3430
|
-
return {
|
|
3431
|
-
content: [
|
|
3432
|
-
{
|
|
3433
|
-
type: "text",
|
|
3434
|
-
text: `Error: "${workflow.name}" cannot be exported portably because ` + blockers.join("; ") + ". Rebuild those steps without the connection, or keep this workflow local."
|
|
3435
|
-
}
|
|
3436
|
-
],
|
|
3437
|
-
isError: true
|
|
3438
|
-
};
|
|
3439
|
-
}
|
|
3440
3508
|
const projects = await dbListProjects();
|
|
3441
3509
|
const projectName = workflow.nodes.map((n) => n.config.projectName).find((name) => typeof name === "string" && name.length > 0);
|
|
3442
3510
|
const project = projects.find((p) => p.name === projectName);
|
|
@@ -3451,15 +3519,20 @@ Run history: list_workflow_runs with workflow_id ${args.workflow_id}`
|
|
|
3451
3519
|
isError: true
|
|
3452
3520
|
};
|
|
3453
3521
|
}
|
|
3454
|
-
const portable = toPortable(workflow, project.path);
|
|
3522
|
+
const portable = toPortable(workflow, project.path, await listPortableConnections());
|
|
3455
3523
|
const residual = residualAbsolutePaths(portable);
|
|
3524
|
+
const unnamed = (portable.requires ?? []).filter(
|
|
3525
|
+
(requirement) => requirement.kind === "connection" && requirement.connectorId === ""
|
|
3526
|
+
);
|
|
3456
3527
|
return {
|
|
3457
3528
|
content: [
|
|
3458
3529
|
{
|
|
3459
3530
|
type: "text",
|
|
3460
3531
|
text: JSON.stringify(portable, null, 2) + (residual.length > 0 ? `
|
|
3461
3532
|
|
|
3462
|
-
Warning: these still hold a machine-specific path and will not travel: ${residual.join(", ")}` : "")
|
|
3533
|
+
Warning: these still hold a machine-specific path and will not travel: ${residual.join(", ")}` : "") + (unnamed.length > 0 ? `
|
|
3534
|
+
|
|
3535
|
+
Warning: ${unnamed.length} step(s) point at a connection this install could not name, so an import cannot rebind them automatically.` : "")
|
|
3463
3536
|
}
|
|
3464
3537
|
]
|
|
3465
3538
|
};
|
|
@@ -3467,7 +3540,7 @@ Warning: these still hold a machine-specific path and will not travel: ${residua
|
|
|
3467
3540
|
);
|
|
3468
3541
|
server.tool(
|
|
3469
3542
|
"import_workflow",
|
|
3470
|
-
"Import a workflow exported by export_workflow, resolving {{project.path}} and {{project.name}} against a registered project. The id is derived from the bundle and the workflow's slug, so importing the same file again updates it in place instead of creating a duplicate.",
|
|
3543
|
+
"Import a workflow exported by export_workflow, resolving {{project.path}} and {{project.name}} against a registered project. Recorded connection requirements are rebound when this machine has one unambiguous match, and reported as still to connect otherwise. The id is derived from the bundle and the workflow's slug, so importing the same file again updates it in place instead of creating a duplicate.",
|
|
3471
3544
|
{
|
|
3472
3545
|
workflow: z5.string().max(5e5).describe("The exported workflow JSON"),
|
|
3473
3546
|
project_name: V.name.describe("Registered project to resolve paths against"),
|
|
@@ -3527,38 +3600,38 @@ Warning: these still hold a machine-specific path and will not travel: ${residua
|
|
|
3527
3600
|
};
|
|
3528
3601
|
}
|
|
3529
3602
|
const bundle = args.bundle ?? slugify(project.name);
|
|
3530
|
-
const
|
|
3531
|
-
|
|
3603
|
+
const portable = { ...parsed, slug: parsed.slug ?? slugify(parsed.name) };
|
|
3604
|
+
const connections = await listPortableConnections();
|
|
3605
|
+
const resolved = fromPortable(
|
|
3606
|
+
portable,
|
|
3532
3607
|
bundle,
|
|
3533
3608
|
{
|
|
3534
3609
|
name: project.name,
|
|
3535
3610
|
path: project.path
|
|
3536
|
-
}
|
|
3611
|
+
},
|
|
3612
|
+
connections
|
|
3537
3613
|
);
|
|
3538
|
-
const
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
type: "text",
|
|
3544
|
-
text: `Error: this workflow cannot be imported because ${blockers.join("; ")}.`
|
|
3545
|
-
}
|
|
3546
|
-
],
|
|
3547
|
-
isError: true
|
|
3548
|
-
};
|
|
3549
|
-
}
|
|
3550
|
-
const existing = (await dbListWorkflows()).find((w) => w.id === definition.id);
|
|
3614
|
+
const unresolved = unresolvedRequirements(portable, connections);
|
|
3615
|
+
const known = await dbListWorkflows();
|
|
3616
|
+
const id = importedWorkflowIdFor(bundle, portable.slug, portable.name, known);
|
|
3617
|
+
const existing = known.find((w) => w.id === id);
|
|
3618
|
+
const definition = { ...resolved, id, enabled: existing ? existing.enabled : false };
|
|
3551
3619
|
if (existing) {
|
|
3552
3620
|
await dbUpdateWorkflow(definition.id, definition);
|
|
3553
3621
|
} else {
|
|
3554
3622
|
await dbInsertWorkflow(definition);
|
|
3555
3623
|
}
|
|
3556
3624
|
dbSignalChange();
|
|
3625
|
+
const pending = unresolved.map(
|
|
3626
|
+
(requirement) => requirement.kind === "httpProfile" ? `${requirement.nodeId} needs an HTTP profile${requirement.name ? ` like "${requirement.name}"` : ""}` : `${requirement.nodeId} needs a ${requirement.connectorId || "connector"} connection${requirement.name ? ` like "${requirement.name}"` : ""}`
|
|
3627
|
+
).join("; ");
|
|
3557
3628
|
return {
|
|
3558
3629
|
content: [
|
|
3559
3630
|
{
|
|
3560
3631
|
type: "text",
|
|
3561
|
-
text: `${existing ? "Updated" : "Imported"} "${definition.name}" as ${definition.id}, resolved against ${project.path}`
|
|
3632
|
+
text: `${existing ? "Updated" : "Imported"} "${definition.name}" as ${definition.id}, resolved against ${project.path}` + (existing || definition.enabled ? "" : ". It is disabled; enable it when ready") + (pending ? `
|
|
3633
|
+
|
|
3634
|
+
Still to connect: ${pending}` : "")
|
|
3562
3635
|
}
|
|
3563
3636
|
]
|
|
3564
3637
|
};
|
|
@@ -3579,7 +3652,7 @@ function registerConfigTools(server) {
|
|
|
3579
3652
|
}
|
|
3580
3653
|
|
|
3581
3654
|
// src/tools/workspaces.ts
|
|
3582
|
-
import
|
|
3655
|
+
import crypto4 from "crypto";
|
|
3583
3656
|
import { z as z6 } from "zod";
|
|
3584
3657
|
function registerWorkspaceTools(server) {
|
|
3585
3658
|
server.tool("list_workspaces", "List all workspaces", async () => {
|
|
@@ -3598,7 +3671,7 @@ function registerWorkspaceTools(server) {
|
|
|
3598
3671
|
const existing = await dbListWorkspaces();
|
|
3599
3672
|
const maxOrder = existing.reduce((max, w) => Math.max(max, w.order), 0);
|
|
3600
3673
|
const workspace = {
|
|
3601
|
-
id:
|
|
3674
|
+
id: crypto4.randomUUID(),
|
|
3602
3675
|
name: args.name,
|
|
3603
3676
|
order: maxOrder + 1,
|
|
3604
3677
|
...args.icon && { icon: args.icon },
|
|
@@ -3789,10 +3862,13 @@ function registerConnectorTools(server) {
|
|
|
3789
3862
|
);
|
|
3790
3863
|
server.tool(
|
|
3791
3864
|
"install_connector",
|
|
3792
|
-
"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.",
|
|
3865
|
+
"Install a connector from a pack file, from the catalog, or from an npm package, creating a connection ready to poll. Call list_connectors for catalog ids and inspect_connector_package to see which environment variables are needed. Secrets cannot be set this way \u2014 see the error it returns if the connector requires one.",
|
|
3793
3866
|
{
|
|
3794
3867
|
connector_id: V.id.optional().describe("Catalog connector id (from list_connectors). Use this or package."),
|
|
3795
3868
|
package: V.shortText.optional().describe("npm package name or launch command"),
|
|
3869
|
+
pack_path: V.shortText.optional().describe(
|
|
3870
|
+
"Path to a .vorn.tgz pack to install first. It is verified and copied to disk, and the connection then launches those files rather than resolving a package."
|
|
3871
|
+
),
|
|
3796
3872
|
name: V.title.optional().describe("Connection name (defaults to the connector name)"),
|
|
3797
3873
|
project: V.name.optional().describe("Vorn project tasks should be created in"),
|
|
3798
3874
|
trigger: V.shortText.optional().describe("Trigger type to configure (defaults to the first the connector offers)"),
|
|
@@ -3807,8 +3883,17 @@ function registerConnectorTools(server) {
|
|
|
3807
3883
|
`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.`
|
|
3808
3884
|
);
|
|
3809
3885
|
}
|
|
3810
|
-
|
|
3811
|
-
if (
|
|
3886
|
+
let installed;
|
|
3887
|
+
if (args.pack_path) {
|
|
3888
|
+
const outcome = await rpcCall("connector:installPack", {
|
|
3889
|
+
kind: "file",
|
|
3890
|
+
path: args.pack_path
|
|
3891
|
+
});
|
|
3892
|
+
if (!outcome.ok) return failure(`The pack was refused: ${outcome.error}`);
|
|
3893
|
+
installed = outcome.pack;
|
|
3894
|
+
}
|
|
3895
|
+
const target = installed ? packLaunch(installed) : entry?.launch ?? args.package;
|
|
3896
|
+
if (!target) return failure("Provide either connector_id, package, or pack_path.");
|
|
3812
3897
|
const result = await probe(target);
|
|
3813
3898
|
if (!result.ok) return failure(result.error);
|
|
3814
3899
|
const manifest = result.manifest;
|
|
@@ -3860,6 +3945,7 @@ function registerConnectorTools(server) {
|
|
|
3860
3945
|
installed: manifest.name,
|
|
3861
3946
|
connectionId: connection2.id,
|
|
3862
3947
|
trigger: trigger?.type,
|
|
3948
|
+
...installed && { version: installed.version, path: installed.path },
|
|
3863
3949
|
note: "Poll it now with backfill_connection, or reference it from a workflow."
|
|
3864
3950
|
});
|
|
3865
3951
|
}
|
|
@@ -3897,6 +3983,9 @@ function registerConnectorTools(server) {
|
|
|
3897
3983
|
}
|
|
3898
3984
|
);
|
|
3899
3985
|
}
|
|
3986
|
+
function packLaunch(pack) {
|
|
3987
|
+
return { command: "node", args: [`${pack.path}/index.js`] };
|
|
3988
|
+
}
|
|
3900
3989
|
async function probe(target) {
|
|
3901
3990
|
const launch = typeof target === "string" ? parseLaunch(target) : target;
|
|
3902
3991
|
return rpcCall("connector:probeSdk", launch, PROBE_TIMEOUT_MS);
|
|
@@ -3918,7 +4007,7 @@ function plural(count, word) {
|
|
|
3918
4007
|
}
|
|
3919
4008
|
|
|
3920
4009
|
// src/tools/browser.ts
|
|
3921
|
-
import
|
|
4010
|
+
import crypto5 from "crypto";
|
|
3922
4011
|
import { z as z8 } from "zod";
|
|
3923
4012
|
function sessionId(env = process.env) {
|
|
3924
4013
|
const id = env.VORN_SESSION_ID;
|
|
@@ -3945,7 +4034,7 @@ function source(label) {
|
|
|
3945
4034
|
return label.includes("WEB PAGE") || label.includes("BROWSER") ? "page" : "device";
|
|
3946
4035
|
}
|
|
3947
4036
|
function pageResult(data, label = "WEB PAGE CONTENT") {
|
|
3948
|
-
const nonce =
|
|
4037
|
+
const nonce = crypto5.randomUUID();
|
|
3949
4038
|
return {
|
|
3950
4039
|
content: [
|
|
3951
4040
|
{
|
|
@@ -4393,7 +4482,7 @@ console.warn = (...args) => _origError("[mcp:warn]", ...args);
|
|
|
4393
4482
|
console.error = (...args) => _origError("[mcp:error]", ...args);
|
|
4394
4483
|
async function main() {
|
|
4395
4484
|
configManager.init();
|
|
4396
|
-
const version = true ? "0.7.0-beta.
|
|
4485
|
+
const version = true ? "0.7.0-beta.8" : createRequire(import.meta.url)("../package.json").version;
|
|
4397
4486
|
const server = createMcpServer(version);
|
|
4398
4487
|
const transport = new StdioServerTransport();
|
|
4399
4488
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vornrun/mcp",
|
|
3
|
-
"version": "0.7.0-beta.
|
|
3
|
+
"version": "0.7.0-beta.8",
|
|
4
4
|
"description": "Vorn MCP server — task management, git, and workflow tools for AI coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"zod": "^4.4.3"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@vornrun/server": "0.7.0-beta.
|
|
42
|
-
"@vornrun/shared": "0.7.0-beta.
|
|
41
|
+
"@vornrun/server": "0.7.0-beta.8",
|
|
42
|
+
"@vornrun/shared": "0.7.0-beta.8",
|
|
43
43
|
"tsup": "^8.5.1",
|
|
44
44
|
"tsx": "^4.23.1",
|
|
45
45
|
"typescript": "^6.0.3"
|