@vibegrid/mcp 0.3.1 → 0.4.0-beta.0
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 +95 -578
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1525,59 +1525,37 @@ var AGENT_TYPES3 = [
|
|
|
1525
1525
|
function registerSessionTools(server) {
|
|
1526
1526
|
server.tool(
|
|
1527
1527
|
"list_sessions",
|
|
1528
|
-
|
|
1528
|
+
'List terminal sessions. Filter by status: "active" (running terminals), "recent" (past sessions), or "archived".',
|
|
1529
1529
|
{
|
|
1530
|
-
|
|
1530
|
+
filter: z4.enum(["active", "recent", "archived"]).optional().describe("Session filter (default: active)"),
|
|
1531
|
+
project_name: V.name.optional().describe("Filter by project name"),
|
|
1532
|
+
project_path: V.absolutePath.optional().describe("Filter by project path (for recent sessions)")
|
|
1531
1533
|
},
|
|
1532
1534
|
async (args) => {
|
|
1535
|
+
const filter = args.filter ?? "active";
|
|
1533
1536
|
try {
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
+
if (filter === "active") {
|
|
1538
|
+
let sessions = await rpcCall("terminal:listActive");
|
|
1539
|
+
if (args.project_name) {
|
|
1540
|
+
sessions = sessions.filter((s) => s.projectName === args.project_name);
|
|
1541
|
+
}
|
|
1542
|
+
const summary = sessions.map((s) => ({
|
|
1543
|
+
id: s.id,
|
|
1544
|
+
agentType: s.agentType,
|
|
1545
|
+
projectName: s.projectName,
|
|
1546
|
+
status: s.status,
|
|
1547
|
+
displayName: s.displayName,
|
|
1548
|
+
branch: s.branch,
|
|
1549
|
+
pid: s.pid
|
|
1550
|
+
}));
|
|
1551
|
+
return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
|
|
1552
|
+
} else if (filter === "recent") {
|
|
1553
|
+
const sessions = await rpcCall("sessions:getRecent", args.project_path);
|
|
1554
|
+
return { content: [{ type: "text", text: JSON.stringify(sessions, null, 2) }] };
|
|
1555
|
+
} else {
|
|
1556
|
+
const sessions = await rpcCall("session:listArchived");
|
|
1557
|
+
return { content: [{ type: "text", text: JSON.stringify(sessions, null, 2) }] };
|
|
1537
1558
|
}
|
|
1538
|
-
const summary = sessions.map((s) => ({
|
|
1539
|
-
id: s.id,
|
|
1540
|
-
agentType: s.agentType,
|
|
1541
|
-
projectName: s.projectName,
|
|
1542
|
-
status: s.status,
|
|
1543
|
-
displayName: s.displayName,
|
|
1544
|
-
branch: s.branch,
|
|
1545
|
-
pid: s.pid
|
|
1546
|
-
}));
|
|
1547
|
-
return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
|
|
1548
|
-
} catch (err) {
|
|
1549
|
-
return {
|
|
1550
|
-
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : err}` }],
|
|
1551
|
-
isError: true
|
|
1552
|
-
};
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
1555
|
-
);
|
|
1556
|
-
server.tool(
|
|
1557
|
-
"list_recent_sessions",
|
|
1558
|
-
"List recent session history for a project. Requires the VibeGrid app to be running.",
|
|
1559
|
-
{
|
|
1560
|
-
project_path: V.absolutePath.optional().describe("Filter by project path")
|
|
1561
|
-
},
|
|
1562
|
-
async (args) => {
|
|
1563
|
-
try {
|
|
1564
|
-
const sessions = await rpcCall("sessions:getRecent", args.project_path);
|
|
1565
|
-
return { content: [{ type: "text", text: JSON.stringify(sessions, null, 2) }] };
|
|
1566
|
-
} catch (err) {
|
|
1567
|
-
return {
|
|
1568
|
-
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : err}` }],
|
|
1569
|
-
isError: true
|
|
1570
|
-
};
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
);
|
|
1574
|
-
server.tool(
|
|
1575
|
-
"list_archived_sessions",
|
|
1576
|
-
"List archived sessions. Requires the VibeGrid app to be running.",
|
|
1577
|
-
async () => {
|
|
1578
|
-
try {
|
|
1579
|
-
const sessions = await rpcCall("session:listArchived");
|
|
1580
|
-
return { content: [{ type: "text", text: JSON.stringify(sessions, null, 2) }] };
|
|
1581
1559
|
} catch (err) {
|
|
1582
1560
|
return {
|
|
1583
1561
|
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : err}` }],
|
|
@@ -1587,8 +1565,8 @@ function registerSessionTools(server) {
|
|
|
1587
1565
|
}
|
|
1588
1566
|
);
|
|
1589
1567
|
server.tool(
|
|
1590
|
-
"
|
|
1591
|
-
"Launch an AI agent
|
|
1568
|
+
"launch_session",
|
|
1569
|
+
"Launch an AI agent session (interactive terminal or headless). Requires the VibeGrid app to be running.",
|
|
1592
1570
|
{
|
|
1593
1571
|
agent_type: z4.enum(AGENT_TYPES3).describe("Agent type to launch"),
|
|
1594
1572
|
project_name: V.name.describe("Project name"),
|
|
@@ -1596,7 +1574,8 @@ function registerSessionTools(server) {
|
|
|
1596
1574
|
prompt: V.prompt.optional().describe("Initial prompt to send to the agent"),
|
|
1597
1575
|
branch: V.shortText.optional().describe("Git branch to checkout"),
|
|
1598
1576
|
use_worktree: z4.boolean().optional().describe("Create a git worktree"),
|
|
1599
|
-
display_name: V.shortText.optional().describe("Display name for the session")
|
|
1577
|
+
display_name: V.shortText.optional().describe("Display name for the session"),
|
|
1578
|
+
headless: z4.boolean().optional().describe("Launch as headless (no UI) session")
|
|
1600
1579
|
},
|
|
1601
1580
|
async (args) => {
|
|
1602
1581
|
const payload = {
|
|
@@ -1608,8 +1587,10 @@ function registerSessionTools(server) {
|
|
|
1608
1587
|
...args.use_worktree && { useWorktree: args.use_worktree },
|
|
1609
1588
|
...args.display_name && { displayName: args.display_name }
|
|
1610
1589
|
};
|
|
1590
|
+
const rpcMethod = args.headless ? "headless:create" : "terminal:create";
|
|
1591
|
+
const label = args.headless ? "headless" : "terminal";
|
|
1611
1592
|
try {
|
|
1612
|
-
const session = await rpcCall(
|
|
1593
|
+
const session = await rpcCall(rpcMethod, payload);
|
|
1613
1594
|
return {
|
|
1614
1595
|
content: [
|
|
1615
1596
|
{
|
|
@@ -1633,7 +1614,7 @@ function registerSessionTools(server) {
|
|
|
1633
1614
|
content: [
|
|
1634
1615
|
{
|
|
1635
1616
|
type: "text",
|
|
1636
|
-
text: `Error launching agent: ${err instanceof Error ? err.message : err}`
|
|
1617
|
+
text: `Error launching ${label} agent: ${err instanceof Error ? err.message : err}`
|
|
1637
1618
|
}
|
|
1638
1619
|
],
|
|
1639
1620
|
isError: true
|
|
@@ -1642,95 +1623,24 @@ function registerSessionTools(server) {
|
|
|
1642
1623
|
}
|
|
1643
1624
|
);
|
|
1644
1625
|
server.tool(
|
|
1645
|
-
"
|
|
1646
|
-
"
|
|
1626
|
+
"kill_session",
|
|
1627
|
+
"Kill a terminal or headless session. Requires the VibeGrid app to be running.",
|
|
1647
1628
|
{
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
project_path: V.absolutePath.describe("Absolute path to project directory"),
|
|
1651
|
-
prompt: V.prompt.optional().describe("Initial prompt to send to the agent"),
|
|
1652
|
-
branch: V.shortText.optional().describe("Git branch to checkout"),
|
|
1653
|
-
use_worktree: z4.boolean().optional().describe("Create a git worktree"),
|
|
1654
|
-
display_name: V.shortText.optional().describe("Display name for the session")
|
|
1629
|
+
id: V.id.describe("Session ID to kill"),
|
|
1630
|
+
headless: z4.boolean().optional().describe("Kill a headless session instead of a terminal")
|
|
1655
1631
|
},
|
|
1656
1632
|
async (args) => {
|
|
1657
|
-
const
|
|
1658
|
-
|
|
1659
|
-
projectName: args.project_name,
|
|
1660
|
-
projectPath: args.project_path,
|
|
1661
|
-
...args.prompt && { initialPrompt: args.prompt },
|
|
1662
|
-
...args.branch && { branch: args.branch },
|
|
1663
|
-
...args.use_worktree && { useWorktree: args.use_worktree },
|
|
1664
|
-
...args.display_name && { displayName: args.display_name }
|
|
1665
|
-
};
|
|
1666
|
-
try {
|
|
1667
|
-
const session = await rpcCall("headless:create", payload);
|
|
1668
|
-
return {
|
|
1669
|
-
content: [
|
|
1670
|
-
{
|
|
1671
|
-
type: "text",
|
|
1672
|
-
text: JSON.stringify(
|
|
1673
|
-
{
|
|
1674
|
-
id: session.id,
|
|
1675
|
-
agentType: session.agentType,
|
|
1676
|
-
projectName: session.projectName,
|
|
1677
|
-
pid: session.pid,
|
|
1678
|
-
status: session.status
|
|
1679
|
-
},
|
|
1680
|
-
null,
|
|
1681
|
-
2
|
|
1682
|
-
)
|
|
1683
|
-
}
|
|
1684
|
-
]
|
|
1685
|
-
};
|
|
1686
|
-
} catch (err) {
|
|
1687
|
-
return {
|
|
1688
|
-
content: [
|
|
1689
|
-
{
|
|
1690
|
-
type: "text",
|
|
1691
|
-
text: `Error launching headless agent: ${err instanceof Error ? err.message : err}`
|
|
1692
|
-
}
|
|
1693
|
-
],
|
|
1694
|
-
isError: true
|
|
1695
|
-
};
|
|
1696
|
-
}
|
|
1697
|
-
}
|
|
1698
|
-
);
|
|
1699
|
-
server.tool(
|
|
1700
|
-
"kill_session",
|
|
1701
|
-
"Kill a terminal session. Requires the VibeGrid app to be running.",
|
|
1702
|
-
{ id: V.id.describe("Session ID to kill") },
|
|
1703
|
-
async (args) => {
|
|
1633
|
+
const rpcMethod = args.headless ? "headless:kill" : "terminal:kill";
|
|
1634
|
+
const label = args.headless ? "headless session" : "session";
|
|
1704
1635
|
try {
|
|
1705
|
-
await rpcCall(
|
|
1706
|
-
return { content: [{ type: "text", text: `Killed
|
|
1636
|
+
await rpcCall(rpcMethod, args.id);
|
|
1637
|
+
return { content: [{ type: "text", text: `Killed ${label}: ${args.id}` }] };
|
|
1707
1638
|
} catch (err) {
|
|
1708
1639
|
return {
|
|
1709
1640
|
content: [
|
|
1710
1641
|
{
|
|
1711
1642
|
type: "text",
|
|
1712
|
-
text: `Error killing
|
|
1713
|
-
}
|
|
1714
|
-
],
|
|
1715
|
-
isError: true
|
|
1716
|
-
};
|
|
1717
|
-
}
|
|
1718
|
-
}
|
|
1719
|
-
);
|
|
1720
|
-
server.tool(
|
|
1721
|
-
"kill_headless",
|
|
1722
|
-
"Kill a headless agent session. Requires the VibeGrid app to be running.",
|
|
1723
|
-
{ id: V.id.describe("Headless session ID to kill") },
|
|
1724
|
-
async (args) => {
|
|
1725
|
-
try {
|
|
1726
|
-
await rpcCall("headless:kill", args.id);
|
|
1727
|
-
return { content: [{ type: "text", text: `Killed headless session: ${args.id}` }] };
|
|
1728
|
-
} catch (err) {
|
|
1729
|
-
return {
|
|
1730
|
-
content: [
|
|
1731
|
-
{
|
|
1732
|
-
type: "text",
|
|
1733
|
-
text: `Error killing headless session: ${err instanceof Error ? err.message : err}`
|
|
1643
|
+
text: `Error killing ${label}: ${err instanceof Error ? err.message : err}`
|
|
1734
1644
|
}
|
|
1735
1645
|
],
|
|
1736
1646
|
isError: true
|
|
@@ -1747,7 +1657,9 @@ function registerSessionTools(server) {
|
|
|
1747
1657
|
},
|
|
1748
1658
|
async (args) => {
|
|
1749
1659
|
try {
|
|
1750
|
-
|
|
1660
|
+
const trimmed = args.data.replace(/[\r\n]+$/, "");
|
|
1661
|
+
const data = trimmed + "\r";
|
|
1662
|
+
await rpcNotify("terminal:write", { id: args.id, data });
|
|
1751
1663
|
return { content: [{ type: "text", text: `Wrote to session: ${args.id}` }] };
|
|
1752
1664
|
} catch (err) {
|
|
1753
1665
|
return {
|
|
@@ -1859,7 +1771,7 @@ function registerWorkflowTools(server) {
|
|
|
1859
1771
|
server.tool(
|
|
1860
1772
|
"create_workflow",
|
|
1861
1773
|
"Create a new workflow. Accepts either full nodes/edges or a convenience flat format (trigger + actions array).",
|
|
1862
|
-
|
|
1774
|
+
{
|
|
1863
1775
|
name: V.title.describe("Workflow name"),
|
|
1864
1776
|
trigger: triggerConfigSchema.optional().describe("Trigger config (convenience mode). Defaults to manual."),
|
|
1865
1777
|
actions: z5.array(launchAgentConfigSchema).optional().describe("Actions to execute (convenience mode). Auto-generates graph."),
|
|
@@ -1869,7 +1781,7 @@ function registerWorkflowTools(server) {
|
|
|
1869
1781
|
icon_color: V.hexColor.optional().describe("Hex color (default: #6366f1)"),
|
|
1870
1782
|
enabled: z5.boolean().optional().describe("Whether workflow is enabled (default: true)"),
|
|
1871
1783
|
stagger_delay_ms: z5.number().optional().describe("Delay in ms between actions")
|
|
1872
|
-
}
|
|
1784
|
+
},
|
|
1873
1785
|
async (args) => {
|
|
1874
1786
|
let nodes;
|
|
1875
1787
|
let edges;
|
|
@@ -1901,7 +1813,7 @@ function registerWorkflowTools(server) {
|
|
|
1901
1813
|
server.tool(
|
|
1902
1814
|
"update_workflow",
|
|
1903
1815
|
"Update a workflow's properties",
|
|
1904
|
-
|
|
1816
|
+
{
|
|
1905
1817
|
id: V.id.describe("Workflow ID"),
|
|
1906
1818
|
name: V.title.optional(),
|
|
1907
1819
|
nodes: z5.array(nodeSchema).optional(),
|
|
@@ -1910,7 +1822,7 @@ function registerWorkflowTools(server) {
|
|
|
1910
1822
|
icon_color: V.hexColor.optional(),
|
|
1911
1823
|
enabled: z5.boolean().optional(),
|
|
1912
1824
|
stagger_delay_ms: z5.number().optional()
|
|
1913
|
-
}
|
|
1825
|
+
},
|
|
1914
1826
|
async (args) => {
|
|
1915
1827
|
const workflows = dbListWorkflows();
|
|
1916
1828
|
const workflow = workflows.find((w) => w.id === args.id);
|
|
@@ -1955,460 +1867,66 @@ function registerWorkflowTools(server) {
|
|
|
1955
1867
|
);
|
|
1956
1868
|
server.tool(
|
|
1957
1869
|
"list_workflow_runs",
|
|
1958
|
-
"List execution history
|
|
1959
|
-
{
|
|
1960
|
-
workflow_id: V.id.describe("Workflow ID"),
|
|
1961
|
-
limit: z5.number().int().min(1).max(100).optional().describe("Max results (default: 20)")
|
|
1962
|
-
},
|
|
1963
|
-
async (args) => {
|
|
1964
|
-
const runs = listWorkflowRuns(args.workflow_id, args.limit ?? 20);
|
|
1965
|
-
return { content: [{ type: "text", text: JSON.stringify(runs, null, 2) }] };
|
|
1966
|
-
}
|
|
1967
|
-
);
|
|
1968
|
-
server.tool(
|
|
1969
|
-
"list_workflow_runs_by_task",
|
|
1970
|
-
"List workflow executions triggered by a specific task",
|
|
1870
|
+
"List workflow execution history. Filter by workflow_id or task_id.",
|
|
1971
1871
|
{
|
|
1972
|
-
|
|
1872
|
+
workflow_id: V.id.optional().describe("Filter by workflow ID"),
|
|
1873
|
+
task_id: V.id.optional().describe("Filter by task ID (runs triggered by this task)"),
|
|
1973
1874
|
limit: z5.number().int().min(1).max(100).optional().describe("Max results (default: 20)")
|
|
1974
1875
|
},
|
|
1975
1876
|
async (args) => {
|
|
1976
|
-
|
|
1977
|
-
return { content: [{ type: "text", text: JSON.stringify(runs, null, 2) }] };
|
|
1978
|
-
}
|
|
1979
|
-
);
|
|
1980
|
-
server.tool(
|
|
1981
|
-
"get_scheduler_log",
|
|
1982
|
-
"Get scheduler execution log for a workflow. Requires the VibeGrid app to be running.",
|
|
1983
|
-
{
|
|
1984
|
-
workflow_id: V.id.optional().describe("Workflow ID (omit for all workflows)")
|
|
1985
|
-
},
|
|
1986
|
-
async (args) => {
|
|
1987
|
-
try {
|
|
1988
|
-
const log2 = await rpcCall("scheduler:getLog", args.workflow_id);
|
|
1989
|
-
return { content: [{ type: "text", text: JSON.stringify(log2, null, 2) }] };
|
|
1990
|
-
} catch (err) {
|
|
1877
|
+
if (args.workflow_id && args.task_id) {
|
|
1991
1878
|
return {
|
|
1992
|
-
content: [{ type: "text", text:
|
|
1879
|
+
content: [{ type: "text", text: "Error: provide workflow_id or task_id, not both" }],
|
|
1993
1880
|
isError: true
|
|
1994
1881
|
};
|
|
1995
1882
|
}
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
"get_next_scheduled_run",
|
|
2000
|
-
"Get the next scheduled run time for a workflow. Requires the VibeGrid app to be running.",
|
|
2001
|
-
{
|
|
2002
|
-
workflow_id: V.id.describe("Workflow ID")
|
|
2003
|
-
},
|
|
2004
|
-
async (args) => {
|
|
2005
|
-
try {
|
|
2006
|
-
const nextRun = await rpcCall("scheduler:getNextRun", args.workflow_id);
|
|
2007
|
-
return {
|
|
2008
|
-
content: [
|
|
2009
|
-
{
|
|
2010
|
-
type: "text",
|
|
2011
|
-
text: nextRun ? JSON.stringify({ nextRun }, null, 2) : "No scheduled run (workflow may be manual or disabled)"
|
|
2012
|
-
}
|
|
2013
|
-
]
|
|
2014
|
-
};
|
|
2015
|
-
} catch (err) {
|
|
2016
|
-
return {
|
|
2017
|
-
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : err}` }],
|
|
2018
|
-
isError: true
|
|
2019
|
-
};
|
|
2020
|
-
}
|
|
2021
|
-
}
|
|
2022
|
-
);
|
|
2023
|
-
}
|
|
2024
|
-
|
|
2025
|
-
// src/tools/git.ts
|
|
2026
|
-
import { z as z6 } from "zod";
|
|
2027
|
-
|
|
2028
|
-
// ../server/src/git-utils.ts
|
|
2029
|
-
import { execFileSync } from "child_process";
|
|
2030
|
-
import path5 from "path";
|
|
2031
|
-
import fs4 from "fs";
|
|
2032
|
-
import crypto3 from "crypto";
|
|
2033
|
-
var EXEC_OPTS = {
|
|
2034
|
-
encoding: "utf-8",
|
|
2035
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
2036
|
-
};
|
|
2037
|
-
function getGitBranch(projectPath) {
|
|
2038
|
-
try {
|
|
2039
|
-
const branch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
2040
|
-
cwd: projectPath,
|
|
2041
|
-
...EXEC_OPTS,
|
|
2042
|
-
timeout: 3e3
|
|
2043
|
-
}).trim();
|
|
2044
|
-
return branch && branch !== "HEAD" ? branch : null;
|
|
2045
|
-
} catch {
|
|
2046
|
-
return null;
|
|
2047
|
-
}
|
|
2048
|
-
}
|
|
2049
|
-
function listBranches(projectPath) {
|
|
2050
|
-
try {
|
|
2051
|
-
const output = execFileSync("git", ["branch", "--format=%(refname:short)"], {
|
|
2052
|
-
cwd: projectPath,
|
|
2053
|
-
...EXEC_OPTS,
|
|
2054
|
-
timeout: 5e3
|
|
2055
|
-
}).trim();
|
|
2056
|
-
return output ? output.split("\n").map((b) => b.trim()).filter(Boolean) : [];
|
|
2057
|
-
} catch {
|
|
2058
|
-
return [];
|
|
2059
|
-
}
|
|
2060
|
-
}
|
|
2061
|
-
function listRemoteBranches(projectPath) {
|
|
2062
|
-
try {
|
|
2063
|
-
execFileSync("git", ["fetch", "--prune"], {
|
|
2064
|
-
cwd: projectPath,
|
|
2065
|
-
...EXEC_OPTS,
|
|
2066
|
-
timeout: 15e3
|
|
2067
|
-
});
|
|
2068
|
-
const output = execFileSync("git", ["branch", "-r", "--format=%(refname:short)"], {
|
|
2069
|
-
cwd: projectPath,
|
|
2070
|
-
...EXEC_OPTS,
|
|
2071
|
-
timeout: 5e3
|
|
2072
|
-
}).trim();
|
|
2073
|
-
return output ? output.split("\n").map((b) => b.trim().replace(/^origin\//, "")).filter((b) => b && b !== "HEAD") : [];
|
|
2074
|
-
} catch {
|
|
2075
|
-
return [];
|
|
2076
|
-
}
|
|
2077
|
-
}
|
|
2078
|
-
function createWorktree(projectPath, branch) {
|
|
2079
|
-
const projectName = path5.basename(projectPath);
|
|
2080
|
-
const shortId = crypto3.randomUUID().slice(0, 8);
|
|
2081
|
-
const baseDir = path5.join(path5.dirname(projectPath), ".vibegrid-worktrees", projectName);
|
|
2082
|
-
const worktreeDir = path5.join(baseDir, `${branch}-${shortId}`);
|
|
2083
|
-
fs4.mkdirSync(baseDir, { recursive: true });
|
|
2084
|
-
const localBranches = listBranches(projectPath);
|
|
2085
|
-
if (localBranches.includes(branch)) {
|
|
2086
|
-
try {
|
|
2087
|
-
execFileSync("git", ["worktree", "add", worktreeDir, branch], {
|
|
2088
|
-
cwd: projectPath,
|
|
2089
|
-
...EXEC_OPTS,
|
|
2090
|
-
timeout: 3e4
|
|
2091
|
-
});
|
|
2092
|
-
} catch {
|
|
2093
|
-
const newBranch = `${branch}-worktree-${shortId}`;
|
|
2094
|
-
execFileSync("git", ["worktree", "add", "-b", newBranch, worktreeDir, branch], {
|
|
2095
|
-
cwd: projectPath,
|
|
2096
|
-
...EXEC_OPTS,
|
|
2097
|
-
timeout: 3e4
|
|
2098
|
-
});
|
|
2099
|
-
return { worktreePath: worktreeDir, branch: newBranch };
|
|
2100
|
-
}
|
|
2101
|
-
} else {
|
|
2102
|
-
execFileSync("git", ["worktree", "add", "-b", branch, worktreeDir], {
|
|
2103
|
-
cwd: projectPath,
|
|
2104
|
-
...EXEC_OPTS,
|
|
2105
|
-
timeout: 3e4
|
|
2106
|
-
});
|
|
2107
|
-
}
|
|
2108
|
-
return { worktreePath: worktreeDir, branch };
|
|
2109
|
-
}
|
|
2110
|
-
function isWorktreeDirty(worktreePath) {
|
|
2111
|
-
try {
|
|
2112
|
-
const output = execFileSync("git", ["status", "--porcelain"], {
|
|
2113
|
-
cwd: worktreePath,
|
|
2114
|
-
...EXEC_OPTS,
|
|
2115
|
-
timeout: 5e3
|
|
2116
|
-
}).trim();
|
|
2117
|
-
return output.length > 0;
|
|
2118
|
-
} catch {
|
|
2119
|
-
return true;
|
|
2120
|
-
}
|
|
2121
|
-
}
|
|
2122
|
-
function getGitDiffStat(cwd) {
|
|
2123
|
-
try {
|
|
2124
|
-
const output = execFileSync("git", ["diff", "HEAD", "--numstat"], {
|
|
2125
|
-
cwd,
|
|
2126
|
-
...EXEC_OPTS,
|
|
2127
|
-
timeout: 1e4
|
|
2128
|
-
}).trim();
|
|
2129
|
-
if (!output) return { filesChanged: 0, insertions: 0, deletions: 0 };
|
|
2130
|
-
let insertions = 0;
|
|
2131
|
-
let deletions = 0;
|
|
2132
|
-
let filesChanged = 0;
|
|
2133
|
-
for (const line of output.split("\n")) {
|
|
2134
|
-
const parts = line.split(" ");
|
|
2135
|
-
if (parts[0] === "-") {
|
|
2136
|
-
filesChanged++;
|
|
2137
|
-
continue;
|
|
2138
|
-
}
|
|
2139
|
-
insertions += parseInt(parts[0], 10) || 0;
|
|
2140
|
-
deletions += parseInt(parts[1], 10) || 0;
|
|
2141
|
-
filesChanged++;
|
|
2142
|
-
}
|
|
2143
|
-
return { filesChanged, insertions, deletions };
|
|
2144
|
-
} catch {
|
|
2145
|
-
return null;
|
|
2146
|
-
}
|
|
2147
|
-
}
|
|
2148
|
-
function getGitDiffFull(cwd) {
|
|
2149
|
-
try {
|
|
2150
|
-
const stat = getGitDiffStat(cwd);
|
|
2151
|
-
if (!stat) return null;
|
|
2152
|
-
const MAX_DIFF_SIZE = 500 * 1024;
|
|
2153
|
-
let rawDiff = execFileSync("git", ["diff", "HEAD", "-U3"], {
|
|
2154
|
-
cwd,
|
|
2155
|
-
...EXEC_OPTS,
|
|
2156
|
-
timeout: 15e3,
|
|
2157
|
-
maxBuffer: MAX_DIFF_SIZE * 2
|
|
2158
|
-
});
|
|
2159
|
-
if (rawDiff.length > MAX_DIFF_SIZE) {
|
|
2160
|
-
rawDiff = rawDiff.slice(0, MAX_DIFF_SIZE) + "\n\n... diff truncated (too large) ...\n";
|
|
2161
|
-
}
|
|
2162
|
-
const numstatOutput = execFileSync("git", ["diff", "HEAD", "--numstat"], {
|
|
2163
|
-
cwd,
|
|
2164
|
-
...EXEC_OPTS,
|
|
2165
|
-
timeout: 1e4
|
|
2166
|
-
}).trim();
|
|
2167
|
-
const fileStats = /* @__PURE__ */ new Map();
|
|
2168
|
-
if (numstatOutput) {
|
|
2169
|
-
for (const line of numstatOutput.split("\n")) {
|
|
2170
|
-
const parts = line.split(" ");
|
|
2171
|
-
if (parts.length >= 3) {
|
|
2172
|
-
const ins = parts[0] === "-" ? 0 : parseInt(parts[0], 10) || 0;
|
|
2173
|
-
const del = parts[1] === "-" ? 0 : parseInt(parts[1], 10) || 0;
|
|
2174
|
-
fileStats.set(parts.slice(2).join(" "), { insertions: ins, deletions: del });
|
|
2175
|
-
}
|
|
2176
|
-
}
|
|
2177
|
-
}
|
|
2178
|
-
const fileDiffs = [];
|
|
2179
|
-
const diffSections = rawDiff.split(/^diff --git /m).filter(Boolean);
|
|
2180
|
-
for (const section of diffSections) {
|
|
2181
|
-
const fullSection = "diff --git " + section;
|
|
2182
|
-
const plusMatch = fullSection.match(/^\+\+\+ b\/(.+)$/m);
|
|
2183
|
-
const minusMatch = fullSection.match(/^--- a\/(.+)$/m);
|
|
2184
|
-
const filePath = plusMatch?.[1] || minusMatch?.[1]?.replace(/^\/dev\/null$/, "") || "unknown";
|
|
2185
|
-
let status = "modified";
|
|
2186
|
-
if (fullSection.includes("--- /dev/null")) {
|
|
2187
|
-
status = "added";
|
|
2188
|
-
} else if (fullSection.includes("+++ /dev/null")) {
|
|
2189
|
-
status = "deleted";
|
|
2190
|
-
} else if (fullSection.includes("rename from")) {
|
|
2191
|
-
status = "renamed";
|
|
2192
|
-
}
|
|
2193
|
-
const stats = fileStats.get(filePath) || { insertions: 0, deletions: 0 };
|
|
2194
|
-
fileDiffs.push({
|
|
2195
|
-
filePath,
|
|
2196
|
-
status,
|
|
2197
|
-
insertions: stats.insertions,
|
|
2198
|
-
deletions: stats.deletions,
|
|
2199
|
-
diff: fullSection
|
|
2200
|
-
});
|
|
2201
|
-
}
|
|
2202
|
-
return { stat, files: fileDiffs };
|
|
2203
|
-
} catch {
|
|
2204
|
-
return null;
|
|
2205
|
-
}
|
|
2206
|
-
}
|
|
2207
|
-
function gitCommit(cwd, message, includeUnstaged) {
|
|
2208
|
-
try {
|
|
2209
|
-
if (includeUnstaged) {
|
|
2210
|
-
execFileSync("git", ["add", "-A"], { cwd, ...EXEC_OPTS, timeout: 1e4 });
|
|
2211
|
-
}
|
|
2212
|
-
execFileSync("git", ["commit", "-m", message], {
|
|
2213
|
-
cwd,
|
|
2214
|
-
...EXEC_OPTS,
|
|
2215
|
-
timeout: 15e3
|
|
2216
|
-
});
|
|
2217
|
-
return { success: true };
|
|
2218
|
-
} catch (err) {
|
|
2219
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
2220
|
-
return { success: false, error: msg };
|
|
2221
|
-
}
|
|
2222
|
-
}
|
|
2223
|
-
function gitPush(cwd) {
|
|
2224
|
-
try {
|
|
2225
|
-
execFileSync("git", ["push"], { cwd, ...EXEC_OPTS, timeout: 3e4 });
|
|
2226
|
-
return { success: true };
|
|
2227
|
-
} catch (err) {
|
|
2228
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
2229
|
-
return { success: false, error: msg };
|
|
2230
|
-
}
|
|
2231
|
-
}
|
|
2232
|
-
function listWorktrees(projectPath) {
|
|
2233
|
-
try {
|
|
2234
|
-
const output = execFileSync("git", ["worktree", "list", "--porcelain"], {
|
|
2235
|
-
cwd: projectPath,
|
|
2236
|
-
...EXEC_OPTS,
|
|
2237
|
-
timeout: 5e3
|
|
2238
|
-
}).trim();
|
|
2239
|
-
if (!output) return [];
|
|
2240
|
-
const worktrees = [];
|
|
2241
|
-
const blocks = output.split("\n\n");
|
|
2242
|
-
for (const block of blocks) {
|
|
2243
|
-
const lines = block.split("\n");
|
|
2244
|
-
const wtPath = lines.find((l) => l.startsWith("worktree "))?.replace("worktree ", "");
|
|
2245
|
-
const branchLine = lines.find((l) => l.startsWith("branch "));
|
|
2246
|
-
const branch = branchLine?.replace("branch refs/heads/", "") || "detached";
|
|
2247
|
-
if (wtPath) {
|
|
2248
|
-
worktrees.push({ path: wtPath, branch, isMain: worktrees.length === 0 });
|
|
2249
|
-
}
|
|
2250
|
-
}
|
|
2251
|
-
return worktrees;
|
|
2252
|
-
} catch {
|
|
2253
|
-
return [];
|
|
2254
|
-
}
|
|
2255
|
-
}
|
|
2256
|
-
|
|
2257
|
-
// src/tools/git.ts
|
|
2258
|
-
function registerGitTools(server) {
|
|
2259
|
-
server.tool(
|
|
2260
|
-
"list_branches",
|
|
2261
|
-
"List git branches for a project",
|
|
2262
|
-
{ project_path: V.absolutePath.describe("Absolute path to project directory") },
|
|
2263
|
-
async (args) => {
|
|
2264
|
-
try {
|
|
2265
|
-
const local = listBranches(args.project_path);
|
|
2266
|
-
const current = getGitBranch(args.project_path);
|
|
2267
|
-
return {
|
|
2268
|
-
content: [{ type: "text", text: JSON.stringify({ current, branches: local }, null, 2) }]
|
|
2269
|
-
};
|
|
2270
|
-
} catch (err) {
|
|
2271
|
-
return {
|
|
2272
|
-
content: [{ type: "text", text: `Error listing branches: ${err}` }],
|
|
2273
|
-
isError: true
|
|
2274
|
-
};
|
|
1883
|
+
if (args.task_id) {
|
|
1884
|
+
const runs = listWorkflowRunsByTask(args.task_id, args.limit ?? 20);
|
|
1885
|
+
return { content: [{ type: "text", text: JSON.stringify(runs, null, 2) }] };
|
|
2275
1886
|
}
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
"list_remote_branches",
|
|
2280
|
-
"List remote git branches for a project",
|
|
2281
|
-
{ project_path: V.absolutePath.describe("Absolute path to project directory") },
|
|
2282
|
-
async (args) => {
|
|
2283
|
-
try {
|
|
2284
|
-
const remote = listRemoteBranches(args.project_path);
|
|
2285
|
-
return { content: [{ type: "text", text: JSON.stringify(remote, null, 2) }] };
|
|
2286
|
-
} catch (err) {
|
|
2287
|
-
return {
|
|
2288
|
-
content: [{ type: "text", text: `Error listing remote branches: ${err}` }],
|
|
2289
|
-
isError: true
|
|
2290
|
-
};
|
|
1887
|
+
if (args.workflow_id) {
|
|
1888
|
+
const runs = listWorkflowRuns(args.workflow_id, args.limit ?? 20);
|
|
1889
|
+
return { content: [{ type: "text", text: JSON.stringify(runs, null, 2) }] };
|
|
2291
1890
|
}
|
|
1891
|
+
return {
|
|
1892
|
+
content: [{ type: "text", text: "Error: provide either workflow_id or task_id" }],
|
|
1893
|
+
isError: true
|
|
1894
|
+
};
|
|
2292
1895
|
}
|
|
2293
1896
|
);
|
|
2294
1897
|
server.tool(
|
|
2295
|
-
"
|
|
2296
|
-
"Get
|
|
2297
|
-
{
|
|
1898
|
+
"get_workflow_schedule",
|
|
1899
|
+
"Get scheduler info for a workflow: execution log or next scheduled run. Requires the VibeGrid app to be running.",
|
|
1900
|
+
{
|
|
1901
|
+
workflow_id: V.id.optional().describe("Workflow ID (required for next_run, optional for log)"),
|
|
1902
|
+
info: z5.enum(["log", "next_run"]).optional().describe("What to retrieve (default: log)")
|
|
1903
|
+
},
|
|
2298
1904
|
async (args) => {
|
|
1905
|
+
const info = args.info ?? "log";
|
|
2299
1906
|
try {
|
|
2300
|
-
|
|
2301
|
-
|
|
1907
|
+
if (info === "next_run") {
|
|
1908
|
+
if (!args.workflow_id) {
|
|
1909
|
+
return {
|
|
1910
|
+
content: [{ type: "text", text: "Error: workflow_id is required for next_run" }],
|
|
1911
|
+
isError: true
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1914
|
+
const nextRun = await rpcCall("scheduler:getNextRun", args.workflow_id);
|
|
2302
1915
|
return {
|
|
2303
|
-
content: [
|
|
1916
|
+
content: [
|
|
1917
|
+
{
|
|
1918
|
+
type: "text",
|
|
1919
|
+
text: nextRun ? JSON.stringify({ nextRun }, null, 2) : "No scheduled run (workflow may be manual or disabled)"
|
|
1920
|
+
}
|
|
1921
|
+
]
|
|
2304
1922
|
};
|
|
1923
|
+
} else {
|
|
1924
|
+
const log2 = await rpcCall("scheduler:getLog", args.workflow_id);
|
|
1925
|
+
return { content: [{ type: "text", text: JSON.stringify(log2, null, 2) }] };
|
|
2305
1926
|
}
|
|
2306
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2307
|
-
} catch (err) {
|
|
2308
|
-
return { content: [{ type: "text", text: `Error getting diff: ${err}` }], isError: true };
|
|
2309
|
-
}
|
|
2310
|
-
}
|
|
2311
|
-
);
|
|
2312
|
-
server.tool(
|
|
2313
|
-
"get_diff_stat",
|
|
2314
|
-
"Get a summary of git changes (files changed, insertions, deletions)",
|
|
2315
|
-
{ project_path: V.absolutePath.describe("Absolute path to project directory") },
|
|
2316
|
-
async (args) => {
|
|
2317
|
-
try {
|
|
2318
|
-
const result = getGitDiffStat(args.project_path);
|
|
2319
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2320
|
-
} catch (err) {
|
|
2321
|
-
return {
|
|
2322
|
-
content: [{ type: "text", text: `Error getting diff stat: ${err}` }],
|
|
2323
|
-
isError: true
|
|
2324
|
-
};
|
|
2325
|
-
}
|
|
2326
|
-
}
|
|
2327
|
-
);
|
|
2328
|
-
server.tool(
|
|
2329
|
-
"git_commit",
|
|
2330
|
-
"Create a git commit",
|
|
2331
|
-
{
|
|
2332
|
-
project_path: V.absolutePath.describe("Absolute path to project directory"),
|
|
2333
|
-
message: V.description.describe("Commit message"),
|
|
2334
|
-
include_unstaged: z6.boolean().optional().describe("Stage all changes before committing")
|
|
2335
|
-
},
|
|
2336
|
-
async (args) => {
|
|
2337
|
-
try {
|
|
2338
|
-
const result = gitCommit(args.project_path, args.message, args.include_unstaged ?? false);
|
|
2339
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2340
|
-
} catch (err) {
|
|
2341
|
-
return {
|
|
2342
|
-
content: [{ type: "text", text: `Error committing: ${err}` }],
|
|
2343
|
-
isError: true
|
|
2344
|
-
};
|
|
2345
|
-
}
|
|
2346
|
-
}
|
|
2347
|
-
);
|
|
2348
|
-
server.tool(
|
|
2349
|
-
"git_push",
|
|
2350
|
-
"Push commits to the remote repository",
|
|
2351
|
-
{ project_path: V.absolutePath.describe("Absolute path to project directory") },
|
|
2352
|
-
async (args) => {
|
|
2353
|
-
try {
|
|
2354
|
-
const result = gitPush(args.project_path);
|
|
2355
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2356
|
-
} catch (err) {
|
|
2357
|
-
return {
|
|
2358
|
-
content: [{ type: "text", text: `Error pushing: ${err}` }],
|
|
2359
|
-
isError: true
|
|
2360
|
-
};
|
|
2361
|
-
}
|
|
2362
|
-
}
|
|
2363
|
-
);
|
|
2364
|
-
server.tool(
|
|
2365
|
-
"list_worktrees",
|
|
2366
|
-
"List git worktrees for a project",
|
|
2367
|
-
{ project_path: V.absolutePath.describe("Absolute path to project directory") },
|
|
2368
|
-
async (args) => {
|
|
2369
|
-
try {
|
|
2370
|
-
const worktrees = listWorktrees(args.project_path);
|
|
2371
|
-
return { content: [{ type: "text", text: JSON.stringify(worktrees, null, 2) }] };
|
|
2372
1927
|
} catch (err) {
|
|
2373
1928
|
return {
|
|
2374
|
-
content: [{ type: "text", text: `Error
|
|
2375
|
-
isError: true
|
|
2376
|
-
};
|
|
2377
|
-
}
|
|
2378
|
-
}
|
|
2379
|
-
);
|
|
2380
|
-
server.tool(
|
|
2381
|
-
"create_worktree",
|
|
2382
|
-
"Create a git worktree for a branch",
|
|
2383
|
-
{
|
|
2384
|
-
project_path: V.absolutePath.describe("Absolute path to project directory"),
|
|
2385
|
-
branch: V.shortText.describe("Branch name for the worktree")
|
|
2386
|
-
},
|
|
2387
|
-
async (args) => {
|
|
2388
|
-
try {
|
|
2389
|
-
const worktreePath = createWorktree(args.project_path, args.branch);
|
|
2390
|
-
return {
|
|
2391
|
-
content: [{ type: "text", text: JSON.stringify({ path: worktreePath }, null, 2) }]
|
|
2392
|
-
};
|
|
2393
|
-
} catch (err) {
|
|
2394
|
-
return {
|
|
2395
|
-
content: [{ type: "text", text: `Error creating worktree: ${err}` }],
|
|
2396
|
-
isError: true
|
|
2397
|
-
};
|
|
2398
|
-
}
|
|
2399
|
-
}
|
|
2400
|
-
);
|
|
2401
|
-
server.tool(
|
|
2402
|
-
"worktree_dirty",
|
|
2403
|
-
"Check if a worktree has uncommitted changes",
|
|
2404
|
-
{ worktree_path: V.absolutePath.describe("Absolute path to the worktree") },
|
|
2405
|
-
async (args) => {
|
|
2406
|
-
try {
|
|
2407
|
-
const dirty = isWorktreeDirty(args.worktree_path);
|
|
2408
|
-
return { content: [{ type: "text", text: JSON.stringify({ dirty }, null, 2) }] };
|
|
2409
|
-
} catch (err) {
|
|
2410
|
-
return {
|
|
2411
|
-
content: [{ type: "text", text: `Error checking worktree: ${err}` }],
|
|
1929
|
+
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : err}` }],
|
|
2412
1930
|
isError: true
|
|
2413
1931
|
};
|
|
2414
1932
|
}
|
|
@@ -2429,8 +1947,8 @@ function registerConfigTools(server) {
|
|
|
2429
1947
|
}
|
|
2430
1948
|
|
|
2431
1949
|
// src/tools/workspaces.ts
|
|
2432
|
-
import
|
|
2433
|
-
import { z as
|
|
1950
|
+
import crypto3 from "crypto";
|
|
1951
|
+
import { z as z6 } from "zod";
|
|
2434
1952
|
function registerWorkspaceTools(server) {
|
|
2435
1953
|
server.tool("list_workspaces", "List all workspaces", async () => {
|
|
2436
1954
|
const workspaces = dbListWorkspaces();
|
|
@@ -2448,7 +1966,7 @@ function registerWorkspaceTools(server) {
|
|
|
2448
1966
|
const existing = dbListWorkspaces();
|
|
2449
1967
|
const maxOrder = existing.reduce((max, w) => Math.max(max, w.order), 0);
|
|
2450
1968
|
const workspace = {
|
|
2451
|
-
id:
|
|
1969
|
+
id: crypto3.randomUUID(),
|
|
2452
1970
|
name: args.name,
|
|
2453
1971
|
order: maxOrder + 1,
|
|
2454
1972
|
...args.icon && { icon: args.icon },
|
|
@@ -2467,7 +1985,7 @@ function registerWorkspaceTools(server) {
|
|
|
2467
1985
|
name: V.name.optional().describe("New name"),
|
|
2468
1986
|
icon: V.shortText.optional().describe("Lucide icon name"),
|
|
2469
1987
|
icon_color: V.hexColor.optional().describe("Hex color for icon"),
|
|
2470
|
-
order:
|
|
1988
|
+
order: z6.number().int().min(0).optional().describe("Sort order")
|
|
2471
1989
|
},
|
|
2472
1990
|
async (args) => {
|
|
2473
1991
|
const existing = dbListWorkspaces();
|
|
@@ -2517,7 +2035,6 @@ function registerWorkspaceTools(server) {
|
|
|
2517
2035
|
// src/server.ts
|
|
2518
2036
|
function createMcpServer(version) {
|
|
2519
2037
|
const server = new McpServer({ name: "vibegrid", version }, { capabilities: { tools: {} } });
|
|
2520
|
-
registerGitTools(server);
|
|
2521
2038
|
registerConfigTools(server);
|
|
2522
2039
|
registerProjectTools(server);
|
|
2523
2040
|
registerTaskTools(server);
|
|
@@ -2536,7 +2053,7 @@ console.warn = (...args) => _origError("[mcp:warn]", ...args);
|
|
|
2536
2053
|
console.error = (...args) => _origError("[mcp:error]", ...args);
|
|
2537
2054
|
async function main() {
|
|
2538
2055
|
configManager.init();
|
|
2539
|
-
const version = true ? "0.
|
|
2056
|
+
const version = true ? "0.4.0-beta.0" : createRequire(import.meta.url)("../package.json").version;
|
|
2540
2057
|
const server = createMcpServer(version);
|
|
2541
2058
|
const transport = new StdioServerTransport();
|
|
2542
2059
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibegrid/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0-beta.0",
|
|
4
4
|
"description": "VibeGrid MCP server — task management, git, and workflow tools for AI coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
"dev": "tsx src/index.ts"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
34
|
+
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
35
35
|
"libsql": "^0.5.22",
|
|
36
36
|
"pino": "^9.6.0",
|
|
37
37
|
"ws": "^8.18.0",
|
|
38
|
-
"zod": "^
|
|
38
|
+
"zod": "^3.23.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@vibegrid/server": "0.0.1",
|