@willjackson/claude-code-bridge 0.2.4 → 0.4.1
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/README.md +83 -185
- package/dist/{chunk-5HWFDZKH.js → chunk-MHUQYPTB.js} +409 -359
- package/dist/chunk-MHUQYPTB.js.map +1 -0
- package/dist/cli.d.ts +1 -2
- package/dist/cli.js +245 -159
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +108 -290
- package/dist/index.js +9 -81
- package/dist/index.js.map +1 -1
- package/package.json +5 -6
- package/dist/chunk-5HWFDZKH.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/utils/logger.ts
|
|
2
2
|
import pino from "pino";
|
|
3
|
-
function
|
|
4
|
-
return process.env.NODE_ENV
|
|
3
|
+
function usePrettyPrint() {
|
|
4
|
+
return process.env.NODE_ENV === "development";
|
|
5
5
|
}
|
|
6
6
|
function getDefaultLevel() {
|
|
7
7
|
const envLevel = process.env.LOG_LEVEL?.toLowerCase();
|
|
@@ -17,7 +17,7 @@ function createLogger(name, level) {
|
|
|
17
17
|
name,
|
|
18
18
|
level: logLevel
|
|
19
19
|
};
|
|
20
|
-
if (
|
|
20
|
+
if (usePrettyPrint()) {
|
|
21
21
|
options.transport = {
|
|
22
22
|
target: "pino-pretty",
|
|
23
23
|
options: {
|
|
@@ -1613,350 +1613,6 @@ var Bridge = class {
|
|
|
1613
1613
|
}
|
|
1614
1614
|
};
|
|
1615
1615
|
|
|
1616
|
-
// src/transport/discovery.ts
|
|
1617
|
-
import { execSync } from "child_process";
|
|
1618
|
-
var logger3 = createLogger("transport:discovery");
|
|
1619
|
-
function parseDockerLabels(labels) {
|
|
1620
|
-
if (!labels) {
|
|
1621
|
-
return null;
|
|
1622
|
-
}
|
|
1623
|
-
const labelMap = {};
|
|
1624
|
-
for (const label of labels.split(",")) {
|
|
1625
|
-
const [key, value] = label.split("=");
|
|
1626
|
-
if (key && value) {
|
|
1627
|
-
labelMap[key.trim()] = value.trim();
|
|
1628
|
-
}
|
|
1629
|
-
}
|
|
1630
|
-
if (labelMap["claude.bridge.enabled"] !== "true") {
|
|
1631
|
-
return null;
|
|
1632
|
-
}
|
|
1633
|
-
return {
|
|
1634
|
-
enabled: true,
|
|
1635
|
-
port: labelMap["claude.bridge.port"] ? parseInt(labelMap["claude.bridge.port"], 10) : void 0,
|
|
1636
|
-
name: labelMap["claude.bridge.name"]
|
|
1637
|
-
};
|
|
1638
|
-
}
|
|
1639
|
-
function isDockerAvailable() {
|
|
1640
|
-
try {
|
|
1641
|
-
execSync("docker info", { stdio: "pipe", timeout: 5e3 });
|
|
1642
|
-
return true;
|
|
1643
|
-
} catch {
|
|
1644
|
-
return false;
|
|
1645
|
-
}
|
|
1646
|
-
}
|
|
1647
|
-
function discoverDockerPeers() {
|
|
1648
|
-
const peers = [];
|
|
1649
|
-
if (!isDockerAvailable()) {
|
|
1650
|
-
logger3.debug("Docker is not available");
|
|
1651
|
-
return peers;
|
|
1652
|
-
}
|
|
1653
|
-
try {
|
|
1654
|
-
const output = execSync(
|
|
1655
|
-
'docker ps --format "{{.ID}}|{{.Names}}|{{.Labels}}|{{.Status}}|{{.Ports}}"',
|
|
1656
|
-
{
|
|
1657
|
-
encoding: "utf-8",
|
|
1658
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1659
|
-
timeout: 1e4
|
|
1660
|
-
}
|
|
1661
|
-
);
|
|
1662
|
-
const lines = output.trim().split("\n").filter(Boolean);
|
|
1663
|
-
for (const line of lines) {
|
|
1664
|
-
const [id, names, labels, status, ports] = line.split("|");
|
|
1665
|
-
if (!id || !names) continue;
|
|
1666
|
-
const bridgeConfig = parseDockerLabels(labels);
|
|
1667
|
-
if (!bridgeConfig) continue;
|
|
1668
|
-
let port = bridgeConfig.port;
|
|
1669
|
-
if (!port && ports) {
|
|
1670
|
-
const portMatch = ports.match(/0\.0\.0\.0:(\d+)/);
|
|
1671
|
-
if (portMatch) {
|
|
1672
|
-
port = parseInt(portMatch[1], 10);
|
|
1673
|
-
}
|
|
1674
|
-
}
|
|
1675
|
-
if (port) {
|
|
1676
|
-
peers.push({
|
|
1677
|
-
name: bridgeConfig.name || names,
|
|
1678
|
-
source: "docker",
|
|
1679
|
-
url: `ws://localhost:${port}`,
|
|
1680
|
-
containerId: id,
|
|
1681
|
-
status
|
|
1682
|
-
});
|
|
1683
|
-
logger3.debug(
|
|
1684
|
-
{ containerId: id, name: bridgeConfig.name || names, port },
|
|
1685
|
-
"Found bridge-enabled container"
|
|
1686
|
-
);
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
} catch (error) {
|
|
1690
|
-
logger3.debug(
|
|
1691
|
-
{ error: error.message },
|
|
1692
|
-
"Failed to discover Docker peers"
|
|
1693
|
-
);
|
|
1694
|
-
}
|
|
1695
|
-
return peers;
|
|
1696
|
-
}
|
|
1697
|
-
function discoverDocksalProjects() {
|
|
1698
|
-
const peers = [];
|
|
1699
|
-
try {
|
|
1700
|
-
execSync("which fin", { stdio: "pipe" });
|
|
1701
|
-
const output = execSync('fin project list 2>/dev/null || echo ""', {
|
|
1702
|
-
encoding: "utf-8",
|
|
1703
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1704
|
-
timeout: 1e4
|
|
1705
|
-
});
|
|
1706
|
-
const lines = output.trim().split("\n").filter(Boolean);
|
|
1707
|
-
for (const line of lines) {
|
|
1708
|
-
if (line.includes("NAME") || line.startsWith("-")) continue;
|
|
1709
|
-
const parts = line.trim().split(/\s+/);
|
|
1710
|
-
const projectName = parts[0];
|
|
1711
|
-
const status = parts.slice(1).join(" ");
|
|
1712
|
-
if (projectName && status.toLowerCase().includes("running")) {
|
|
1713
|
-
peers.push({
|
|
1714
|
-
name: projectName,
|
|
1715
|
-
source: "docksal",
|
|
1716
|
-
url: `ws://${projectName}.docksal:8765`,
|
|
1717
|
-
status: "running"
|
|
1718
|
-
});
|
|
1719
|
-
logger3.debug({ projectName }, "Found Docksal project");
|
|
1720
|
-
}
|
|
1721
|
-
}
|
|
1722
|
-
} catch (error) {
|
|
1723
|
-
logger3.debug(
|
|
1724
|
-
{ error: error.message },
|
|
1725
|
-
"Docksal not available or no projects found"
|
|
1726
|
-
);
|
|
1727
|
-
}
|
|
1728
|
-
return peers;
|
|
1729
|
-
}
|
|
1730
|
-
function discoverDdevProjects() {
|
|
1731
|
-
const peers = [];
|
|
1732
|
-
try {
|
|
1733
|
-
execSync("which ddev", { stdio: "pipe" });
|
|
1734
|
-
const output = execSync('ddev list --json-output 2>/dev/null || echo "[]"', {
|
|
1735
|
-
encoding: "utf-8",
|
|
1736
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1737
|
-
timeout: 1e4
|
|
1738
|
-
});
|
|
1739
|
-
try {
|
|
1740
|
-
const data = JSON.parse(output);
|
|
1741
|
-
const projects = data.raw || [];
|
|
1742
|
-
for (const project of projects) {
|
|
1743
|
-
if (project.status === "running") {
|
|
1744
|
-
peers.push({
|
|
1745
|
-
name: project.name,
|
|
1746
|
-
source: "ddev",
|
|
1747
|
-
url: `ws://${project.name}.ddev.site:8765`,
|
|
1748
|
-
status: "running"
|
|
1749
|
-
});
|
|
1750
|
-
logger3.debug({ projectName: project.name }, "Found DDEV project");
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1753
|
-
} catch {
|
|
1754
|
-
logger3.debug("Failed to parse DDEV JSON output, trying text parsing");
|
|
1755
|
-
const lines = output.trim().split("\n").filter(Boolean);
|
|
1756
|
-
for (const line of lines) {
|
|
1757
|
-
if (line.includes("running")) {
|
|
1758
|
-
const parts = line.trim().split(/\s+/);
|
|
1759
|
-
if (parts[0]) {
|
|
1760
|
-
peers.push({
|
|
1761
|
-
name: parts[0],
|
|
1762
|
-
source: "ddev",
|
|
1763
|
-
url: `ws://${parts[0]}.ddev.site:8765`,
|
|
1764
|
-
status: "running"
|
|
1765
|
-
});
|
|
1766
|
-
}
|
|
1767
|
-
}
|
|
1768
|
-
}
|
|
1769
|
-
}
|
|
1770
|
-
} catch (error) {
|
|
1771
|
-
logger3.debug(
|
|
1772
|
-
{ error: error.message },
|
|
1773
|
-
"DDEV not available or no projects found"
|
|
1774
|
-
);
|
|
1775
|
-
}
|
|
1776
|
-
return peers;
|
|
1777
|
-
}
|
|
1778
|
-
function discoverLandoProjects() {
|
|
1779
|
-
const peers = [];
|
|
1780
|
-
try {
|
|
1781
|
-
execSync("which lando", { stdio: "pipe" });
|
|
1782
|
-
const output = execSync('lando list --format json 2>/dev/null || echo "[]"', {
|
|
1783
|
-
encoding: "utf-8",
|
|
1784
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1785
|
-
timeout: 1e4
|
|
1786
|
-
});
|
|
1787
|
-
try {
|
|
1788
|
-
const apps = JSON.parse(output);
|
|
1789
|
-
for (const app of apps) {
|
|
1790
|
-
if (app.running) {
|
|
1791
|
-
peers.push({
|
|
1792
|
-
name: app.app || app.name,
|
|
1793
|
-
source: "lando",
|
|
1794
|
-
url: `ws://localhost:8765`,
|
|
1795
|
-
// Would need to check app config for actual port
|
|
1796
|
-
status: "running"
|
|
1797
|
-
});
|
|
1798
|
-
logger3.debug({ appName: app.app || app.name }, "Found Lando app");
|
|
1799
|
-
}
|
|
1800
|
-
}
|
|
1801
|
-
} catch {
|
|
1802
|
-
logger3.debug("Failed to parse Lando JSON output");
|
|
1803
|
-
}
|
|
1804
|
-
} catch (error) {
|
|
1805
|
-
logger3.debug(
|
|
1806
|
-
{ error: error.message },
|
|
1807
|
-
"Lando not available or no projects found"
|
|
1808
|
-
);
|
|
1809
|
-
}
|
|
1810
|
-
return peers;
|
|
1811
|
-
}
|
|
1812
|
-
function discoverAllPeers() {
|
|
1813
|
-
const allPeers = [];
|
|
1814
|
-
const dockerPeers = discoverDockerPeers();
|
|
1815
|
-
allPeers.push(...dockerPeers);
|
|
1816
|
-
logger3.debug({ count: dockerPeers.length }, "Docker peers discovered");
|
|
1817
|
-
const docksalPeers = discoverDocksalProjects();
|
|
1818
|
-
allPeers.push(...docksalPeers);
|
|
1819
|
-
logger3.debug({ count: docksalPeers.length }, "Docksal peers discovered");
|
|
1820
|
-
const ddevPeers = discoverDdevProjects();
|
|
1821
|
-
allPeers.push(...ddevPeers);
|
|
1822
|
-
logger3.debug({ count: ddevPeers.length }, "DDEV peers discovered");
|
|
1823
|
-
const landoPeers = discoverLandoProjects();
|
|
1824
|
-
allPeers.push(...landoPeers);
|
|
1825
|
-
logger3.debug({ count: landoPeers.length }, "Lando peers discovered");
|
|
1826
|
-
return allPeers;
|
|
1827
|
-
}
|
|
1828
|
-
|
|
1829
|
-
// src/environment/detect.ts
|
|
1830
|
-
import { existsSync as existsSync2 } from "fs";
|
|
1831
|
-
function isInDocker() {
|
|
1832
|
-
return existsSync2("/.dockerenv");
|
|
1833
|
-
}
|
|
1834
|
-
function isDocksalEnvironment() {
|
|
1835
|
-
return !!process.env.DOCKSAL_STACK;
|
|
1836
|
-
}
|
|
1837
|
-
function isDdevEnvironment() {
|
|
1838
|
-
return process.env.IS_DDEV_PROJECT === "true";
|
|
1839
|
-
}
|
|
1840
|
-
function isLandoEnvironment() {
|
|
1841
|
-
return process.env.LANDO === "ON";
|
|
1842
|
-
}
|
|
1843
|
-
function isDockerComposeEnvironment() {
|
|
1844
|
-
return !!process.env.COMPOSE_PROJECT_NAME;
|
|
1845
|
-
}
|
|
1846
|
-
function detectEnvironment() {
|
|
1847
|
-
const platform = process.platform;
|
|
1848
|
-
const inDocker = isInDocker();
|
|
1849
|
-
if (isDocksalEnvironment()) {
|
|
1850
|
-
return {
|
|
1851
|
-
type: "docksal",
|
|
1852
|
-
isContainer: true,
|
|
1853
|
-
projectName: process.env.DOCKSAL_PROJECT,
|
|
1854
|
-
projectRoot: process.env.PROJECT_ROOT,
|
|
1855
|
-
hostGateway: getHostGateway(),
|
|
1856
|
-
platform,
|
|
1857
|
-
meta: {
|
|
1858
|
-
stack: process.env.DOCKSAL_STACK || "",
|
|
1859
|
-
environment: process.env.DOCKSAL_ENVIRONMENT || ""
|
|
1860
|
-
}
|
|
1861
|
-
};
|
|
1862
|
-
}
|
|
1863
|
-
if (isDdevEnvironment()) {
|
|
1864
|
-
return {
|
|
1865
|
-
type: "ddev",
|
|
1866
|
-
isContainer: true,
|
|
1867
|
-
projectName: process.env.DDEV_PROJECT,
|
|
1868
|
-
projectRoot: process.env.DDEV_DOCROOT,
|
|
1869
|
-
hostGateway: getHostGateway(),
|
|
1870
|
-
platform,
|
|
1871
|
-
meta: {
|
|
1872
|
-
hostname: process.env.DDEV_HOSTNAME || "",
|
|
1873
|
-
primaryUrl: process.env.DDEV_PRIMARY_URL || ""
|
|
1874
|
-
}
|
|
1875
|
-
};
|
|
1876
|
-
}
|
|
1877
|
-
if (isLandoEnvironment()) {
|
|
1878
|
-
return {
|
|
1879
|
-
type: "lando",
|
|
1880
|
-
isContainer: true,
|
|
1881
|
-
projectName: process.env.LANDO_APP_NAME,
|
|
1882
|
-
projectRoot: process.env.LANDO_APP_ROOT,
|
|
1883
|
-
hostGateway: getHostGateway(),
|
|
1884
|
-
platform,
|
|
1885
|
-
meta: {
|
|
1886
|
-
service: process.env.LANDO_SERVICE_NAME || "",
|
|
1887
|
-
type: process.env.LANDO_SERVICE_TYPE || ""
|
|
1888
|
-
}
|
|
1889
|
-
};
|
|
1890
|
-
}
|
|
1891
|
-
if (isDockerComposeEnvironment() && inDocker) {
|
|
1892
|
-
return {
|
|
1893
|
-
type: "docker-compose",
|
|
1894
|
-
isContainer: true,
|
|
1895
|
-
projectName: process.env.COMPOSE_PROJECT_NAME,
|
|
1896
|
-
hostGateway: getHostGateway(),
|
|
1897
|
-
platform,
|
|
1898
|
-
meta: {
|
|
1899
|
-
service: process.env.COMPOSE_SERVICE || ""
|
|
1900
|
-
}
|
|
1901
|
-
};
|
|
1902
|
-
}
|
|
1903
|
-
if (inDocker) {
|
|
1904
|
-
return {
|
|
1905
|
-
type: "docker",
|
|
1906
|
-
isContainer: true,
|
|
1907
|
-
hostGateway: getHostGateway(),
|
|
1908
|
-
platform
|
|
1909
|
-
};
|
|
1910
|
-
}
|
|
1911
|
-
return {
|
|
1912
|
-
type: "native",
|
|
1913
|
-
isContainer: false,
|
|
1914
|
-
platform
|
|
1915
|
-
};
|
|
1916
|
-
}
|
|
1917
|
-
function getHostGateway() {
|
|
1918
|
-
if (process.env.DOCKER_HOST_GATEWAY) {
|
|
1919
|
-
return process.env.DOCKER_HOST_GATEWAY;
|
|
1920
|
-
}
|
|
1921
|
-
if (process.platform === "darwin" || process.platform === "win32") {
|
|
1922
|
-
return "host.docker.internal";
|
|
1923
|
-
}
|
|
1924
|
-
if (isInDocker()) {
|
|
1925
|
-
return "host.docker.internal";
|
|
1926
|
-
}
|
|
1927
|
-
return "localhost";
|
|
1928
|
-
}
|
|
1929
|
-
function getDefaultConfig(env) {
|
|
1930
|
-
const baseConfig = {
|
|
1931
|
-
mode: "peer",
|
|
1932
|
-
instanceName: env.projectName || `${env.type}-instance`
|
|
1933
|
-
};
|
|
1934
|
-
if (env.isContainer) {
|
|
1935
|
-
return {
|
|
1936
|
-
...baseConfig,
|
|
1937
|
-
listen: {
|
|
1938
|
-
port: 8765,
|
|
1939
|
-
host: "0.0.0.0"
|
|
1940
|
-
},
|
|
1941
|
-
connect: {
|
|
1942
|
-
url: `ws://${env.hostGateway || "host.docker.internal"}:8766`,
|
|
1943
|
-
hostGateway: true
|
|
1944
|
-
}
|
|
1945
|
-
};
|
|
1946
|
-
}
|
|
1947
|
-
return {
|
|
1948
|
-
...baseConfig,
|
|
1949
|
-
listen: {
|
|
1950
|
-
port: 8766,
|
|
1951
|
-
host: "0.0.0.0"
|
|
1952
|
-
},
|
|
1953
|
-
connect: {
|
|
1954
|
-
url: "ws://localhost:8765",
|
|
1955
|
-
hostGateway: false
|
|
1956
|
-
}
|
|
1957
|
-
};
|
|
1958
|
-
}
|
|
1959
|
-
|
|
1960
1616
|
// src/utils/config.ts
|
|
1961
1617
|
import * as fs2 from "fs";
|
|
1962
1618
|
import * as path2 from "path";
|
|
@@ -2061,6 +1717,406 @@ function loadConfigSync(configPath) {
|
|
|
2061
1717
|
return { ...DEFAULT_CONFIG };
|
|
2062
1718
|
}
|
|
2063
1719
|
|
|
1720
|
+
// src/mcp/tools.ts
|
|
1721
|
+
import { z as z2 } from "zod";
|
|
1722
|
+
var logger3 = createLogger("mcp:tools");
|
|
1723
|
+
var ReadFileInputSchema = z2.object({
|
|
1724
|
+
path: z2.string().describe("Path to the file to read")
|
|
1725
|
+
});
|
|
1726
|
+
var WriteFileInputSchema = z2.object({
|
|
1727
|
+
path: z2.string().describe("Path to the file to write"),
|
|
1728
|
+
content: z2.string().describe("Content to write to the file")
|
|
1729
|
+
});
|
|
1730
|
+
var DeleteFileInputSchema = z2.object({
|
|
1731
|
+
path: z2.string().describe("Path to the file to delete")
|
|
1732
|
+
});
|
|
1733
|
+
var ListDirectoryInputSchema = z2.object({
|
|
1734
|
+
path: z2.string().describe("Path to the directory to list")
|
|
1735
|
+
});
|
|
1736
|
+
var DelegateTaskInputSchema = z2.object({
|
|
1737
|
+
description: z2.string().describe("Description of the task to delegate"),
|
|
1738
|
+
scope: z2.enum(["execute", "analyze", "suggest"]).describe("Task scope"),
|
|
1739
|
+
data: z2.record(z2.unknown()).optional().describe("Additional task data")
|
|
1740
|
+
});
|
|
1741
|
+
var RequestContextInputSchema = z2.object({
|
|
1742
|
+
query: z2.string().describe("Query describing what files to retrieve")
|
|
1743
|
+
});
|
|
1744
|
+
var TOOL_DEFINITIONS = [
|
|
1745
|
+
{
|
|
1746
|
+
name: "bridge_read_file",
|
|
1747
|
+
description: "Read a file from the remote connected instance",
|
|
1748
|
+
inputSchema: ReadFileInputSchema
|
|
1749
|
+
},
|
|
1750
|
+
{
|
|
1751
|
+
name: "bridge_write_file",
|
|
1752
|
+
description: "Write a file to the remote connected instance",
|
|
1753
|
+
inputSchema: WriteFileInputSchema
|
|
1754
|
+
},
|
|
1755
|
+
{
|
|
1756
|
+
name: "bridge_delete_file",
|
|
1757
|
+
description: "Delete a file on the remote connected instance",
|
|
1758
|
+
inputSchema: DeleteFileInputSchema
|
|
1759
|
+
},
|
|
1760
|
+
{
|
|
1761
|
+
name: "bridge_list_directory",
|
|
1762
|
+
description: "List files and folders in a directory on the remote connected instance",
|
|
1763
|
+
inputSchema: ListDirectoryInputSchema
|
|
1764
|
+
},
|
|
1765
|
+
{
|
|
1766
|
+
name: "bridge_delegate_task",
|
|
1767
|
+
description: "Delegate a custom task to the remote connected instance",
|
|
1768
|
+
inputSchema: DelegateTaskInputSchema
|
|
1769
|
+
},
|
|
1770
|
+
{
|
|
1771
|
+
name: "bridge_request_context",
|
|
1772
|
+
description: "Request files matching a query from the remote connected instance",
|
|
1773
|
+
inputSchema: RequestContextInputSchema
|
|
1774
|
+
},
|
|
1775
|
+
{
|
|
1776
|
+
name: "bridge_status",
|
|
1777
|
+
description: "Get bridge status and connected peers",
|
|
1778
|
+
inputSchema: z2.object({})
|
|
1779
|
+
}
|
|
1780
|
+
];
|
|
1781
|
+
function createToolHandlers(bridge) {
|
|
1782
|
+
const handlers = {};
|
|
1783
|
+
function errorResponse(message) {
|
|
1784
|
+
return {
|
|
1785
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
1786
|
+
isError: true
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
function successResponse(text) {
|
|
1790
|
+
return {
|
|
1791
|
+
content: [{ type: "text", text }]
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
async function delegateFileTask(action, data, description) {
|
|
1795
|
+
const taskId = `mcp-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
1796
|
+
return bridge.delegateTask({
|
|
1797
|
+
id: taskId,
|
|
1798
|
+
description,
|
|
1799
|
+
scope: "execute",
|
|
1800
|
+
data: { action, ...data }
|
|
1801
|
+
});
|
|
1802
|
+
}
|
|
1803
|
+
handlers["bridge_read_file"] = async (args) => {
|
|
1804
|
+
const input = ReadFileInputSchema.parse(args);
|
|
1805
|
+
logger3.debug({ path: input.path }, "Reading file");
|
|
1806
|
+
try {
|
|
1807
|
+
const result = await delegateFileTask(
|
|
1808
|
+
"read_file",
|
|
1809
|
+
{ path: input.path },
|
|
1810
|
+
`Read file: ${input.path}`
|
|
1811
|
+
);
|
|
1812
|
+
if (!result.success) {
|
|
1813
|
+
return errorResponse(result.error || "Failed to read file");
|
|
1814
|
+
}
|
|
1815
|
+
return successResponse(result.data.content);
|
|
1816
|
+
} catch (err) {
|
|
1817
|
+
logger3.error({ error: err.message, path: input.path }, "Failed to read file");
|
|
1818
|
+
return errorResponse(err.message);
|
|
1819
|
+
}
|
|
1820
|
+
};
|
|
1821
|
+
handlers["bridge_write_file"] = async (args) => {
|
|
1822
|
+
const input = WriteFileInputSchema.parse(args);
|
|
1823
|
+
logger3.debug({ path: input.path, contentLength: input.content.length }, "Writing file");
|
|
1824
|
+
try {
|
|
1825
|
+
const result = await delegateFileTask(
|
|
1826
|
+
"write_file",
|
|
1827
|
+
{ path: input.path, content: input.content },
|
|
1828
|
+
`Write file: ${input.path}`
|
|
1829
|
+
);
|
|
1830
|
+
if (!result.success) {
|
|
1831
|
+
return errorResponse(result.error || "Failed to write file");
|
|
1832
|
+
}
|
|
1833
|
+
return successResponse(`File written successfully: ${input.path} (${result.data.bytesWritten} bytes)`);
|
|
1834
|
+
} catch (err) {
|
|
1835
|
+
logger3.error({ error: err.message, path: input.path }, "Failed to write file");
|
|
1836
|
+
return errorResponse(err.message);
|
|
1837
|
+
}
|
|
1838
|
+
};
|
|
1839
|
+
handlers["bridge_delete_file"] = async (args) => {
|
|
1840
|
+
const input = DeleteFileInputSchema.parse(args);
|
|
1841
|
+
logger3.debug({ path: input.path }, "Deleting file");
|
|
1842
|
+
try {
|
|
1843
|
+
const result = await delegateFileTask(
|
|
1844
|
+
"delete_file",
|
|
1845
|
+
{ path: input.path },
|
|
1846
|
+
`Delete file: ${input.path}`
|
|
1847
|
+
);
|
|
1848
|
+
if (!result.success) {
|
|
1849
|
+
return errorResponse(result.error || "Failed to delete file");
|
|
1850
|
+
}
|
|
1851
|
+
return successResponse(`File deleted successfully: ${input.path}`);
|
|
1852
|
+
} catch (err) {
|
|
1853
|
+
logger3.error({ error: err.message, path: input.path }, "Failed to delete file");
|
|
1854
|
+
return errorResponse(err.message);
|
|
1855
|
+
}
|
|
1856
|
+
};
|
|
1857
|
+
handlers["bridge_list_directory"] = async (args) => {
|
|
1858
|
+
const input = ListDirectoryInputSchema.parse(args);
|
|
1859
|
+
logger3.debug({ path: input.path }, "Listing directory");
|
|
1860
|
+
try {
|
|
1861
|
+
const result = await delegateFileTask(
|
|
1862
|
+
"list_directory",
|
|
1863
|
+
{ path: input.path },
|
|
1864
|
+
`List directory: ${input.path}`
|
|
1865
|
+
);
|
|
1866
|
+
if (!result.success) {
|
|
1867
|
+
return errorResponse(result.error || "Failed to list directory");
|
|
1868
|
+
}
|
|
1869
|
+
const entries = result.data.entries;
|
|
1870
|
+
if (!entries || entries.length === 0) {
|
|
1871
|
+
return successResponse(`Directory is empty: ${input.path}`);
|
|
1872
|
+
}
|
|
1873
|
+
const listing = entries.map((e) => `${e.type === "directory" ? "\u{1F4C1}" : "\u{1F4C4}"} ${e.name}`).join("\n");
|
|
1874
|
+
return successResponse(`Contents of ${input.path}:
|
|
1875
|
+
${listing}`);
|
|
1876
|
+
} catch (err) {
|
|
1877
|
+
logger3.error({ error: err.message, path: input.path }, "Failed to list directory");
|
|
1878
|
+
return errorResponse(err.message);
|
|
1879
|
+
}
|
|
1880
|
+
};
|
|
1881
|
+
handlers["bridge_delegate_task"] = async (args) => {
|
|
1882
|
+
const input = DelegateTaskInputSchema.parse(args);
|
|
1883
|
+
logger3.debug({ description: input.description, scope: input.scope }, "Delegating task");
|
|
1884
|
+
try {
|
|
1885
|
+
const taskId = `mcp-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
1886
|
+
const result = await bridge.delegateTask({
|
|
1887
|
+
id: taskId,
|
|
1888
|
+
description: input.description,
|
|
1889
|
+
scope: input.scope,
|
|
1890
|
+
data: input.data
|
|
1891
|
+
});
|
|
1892
|
+
if (!result.success) {
|
|
1893
|
+
return errorResponse(result.error || "Task failed");
|
|
1894
|
+
}
|
|
1895
|
+
return successResponse(JSON.stringify(result.data, null, 2));
|
|
1896
|
+
} catch (err) {
|
|
1897
|
+
logger3.error({ error: err.message }, "Failed to delegate task");
|
|
1898
|
+
return errorResponse(err.message);
|
|
1899
|
+
}
|
|
1900
|
+
};
|
|
1901
|
+
handlers["bridge_request_context"] = async (args) => {
|
|
1902
|
+
const input = RequestContextInputSchema.parse(args);
|
|
1903
|
+
logger3.debug({ query: input.query }, "Requesting context");
|
|
1904
|
+
try {
|
|
1905
|
+
const files = await bridge.requestContext(input.query);
|
|
1906
|
+
if (files.length === 0) {
|
|
1907
|
+
return successResponse("No files found matching the query.");
|
|
1908
|
+
}
|
|
1909
|
+
const fileResults = files.map((f) => {
|
|
1910
|
+
const header = `=== ${f.path} ===`;
|
|
1911
|
+
const content = f.content;
|
|
1912
|
+
return `${header}
|
|
1913
|
+
${content}`;
|
|
1914
|
+
}).join("\n\n");
|
|
1915
|
+
return successResponse(`Found ${files.length} file(s):
|
|
1916
|
+
|
|
1917
|
+
${fileResults}`);
|
|
1918
|
+
} catch (err) {
|
|
1919
|
+
logger3.error({ error: err.message }, "Failed to request context");
|
|
1920
|
+
return errorResponse(err.message);
|
|
1921
|
+
}
|
|
1922
|
+
};
|
|
1923
|
+
handlers["bridge_status"] = async () => {
|
|
1924
|
+
logger3.debug("Getting bridge status");
|
|
1925
|
+
const peers = bridge.getPeers();
|
|
1926
|
+
const peerCount = bridge.getPeerCount();
|
|
1927
|
+
const isStarted = bridge.isStarted();
|
|
1928
|
+
const mode = bridge.getMode();
|
|
1929
|
+
const instanceName = bridge.getInstanceName();
|
|
1930
|
+
const status = {
|
|
1931
|
+
instanceName,
|
|
1932
|
+
mode,
|
|
1933
|
+
started: isStarted,
|
|
1934
|
+
peerCount,
|
|
1935
|
+
peers: peers.map((p) => ({
|
|
1936
|
+
id: p.id,
|
|
1937
|
+
name: p.name,
|
|
1938
|
+
connectedAt: new Date(p.connectedAt).toISOString(),
|
|
1939
|
+
lastActivity: new Date(p.lastActivity).toISOString()
|
|
1940
|
+
}))
|
|
1941
|
+
};
|
|
1942
|
+
return successResponse(JSON.stringify(status, null, 2));
|
|
1943
|
+
};
|
|
1944
|
+
return handlers;
|
|
1945
|
+
}
|
|
1946
|
+
function zodToJsonSchema(schema) {
|
|
1947
|
+
if (schema instanceof z2.ZodObject) {
|
|
1948
|
+
const shape = schema.shape;
|
|
1949
|
+
const properties = {};
|
|
1950
|
+
const required = [];
|
|
1951
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
1952
|
+
const zodValue = value;
|
|
1953
|
+
properties[key] = zodToJsonSchema(zodValue);
|
|
1954
|
+
if (!(zodValue instanceof z2.ZodOptional)) {
|
|
1955
|
+
required.push(key);
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
return {
|
|
1959
|
+
type: "object",
|
|
1960
|
+
properties,
|
|
1961
|
+
...required.length > 0 ? { required } : {}
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
if (schema instanceof z2.ZodString) {
|
|
1965
|
+
const result = { type: "string" };
|
|
1966
|
+
if (schema.description) {
|
|
1967
|
+
result.description = schema.description;
|
|
1968
|
+
}
|
|
1969
|
+
return result;
|
|
1970
|
+
}
|
|
1971
|
+
if (schema instanceof z2.ZodEnum) {
|
|
1972
|
+
return {
|
|
1973
|
+
type: "string",
|
|
1974
|
+
enum: schema.options
|
|
1975
|
+
};
|
|
1976
|
+
}
|
|
1977
|
+
if (schema instanceof z2.ZodOptional) {
|
|
1978
|
+
return zodToJsonSchema(schema.unwrap());
|
|
1979
|
+
}
|
|
1980
|
+
if (schema instanceof z2.ZodRecord) {
|
|
1981
|
+
return {
|
|
1982
|
+
type: "object",
|
|
1983
|
+
additionalProperties: true
|
|
1984
|
+
};
|
|
1985
|
+
}
|
|
1986
|
+
if (schema instanceof z2.ZodUnknown) {
|
|
1987
|
+
return {};
|
|
1988
|
+
}
|
|
1989
|
+
return { type: "string" };
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
// src/mcp/server.ts
|
|
1993
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
1994
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1995
|
+
import {
|
|
1996
|
+
CallToolRequestSchema,
|
|
1997
|
+
ListToolsRequestSchema
|
|
1998
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
1999
|
+
var logger4 = createLogger("mcp:server");
|
|
2000
|
+
var BridgeMcpServer = class {
|
|
2001
|
+
server;
|
|
2002
|
+
bridge;
|
|
2003
|
+
config;
|
|
2004
|
+
toolHandlers = null;
|
|
2005
|
+
constructor(config) {
|
|
2006
|
+
this.config = config;
|
|
2007
|
+
this.server = new Server(
|
|
2008
|
+
{
|
|
2009
|
+
name: config.name ?? "claude-bridge",
|
|
2010
|
+
version: config.version ?? "0.4.0"
|
|
2011
|
+
},
|
|
2012
|
+
{
|
|
2013
|
+
capabilities: {
|
|
2014
|
+
tools: {}
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
);
|
|
2018
|
+
const bridgeConfig = {
|
|
2019
|
+
mode: "client",
|
|
2020
|
+
instanceName: config.instanceName ?? `mcp-server-${process.pid}`,
|
|
2021
|
+
connect: {
|
|
2022
|
+
url: config.bridgeUrl
|
|
2023
|
+
},
|
|
2024
|
+
taskTimeout: config.taskTimeout ?? 6e4
|
|
2025
|
+
};
|
|
2026
|
+
this.bridge = new Bridge(bridgeConfig);
|
|
2027
|
+
this.registerHandlers();
|
|
2028
|
+
}
|
|
2029
|
+
/**
|
|
2030
|
+
* Register MCP request handlers
|
|
2031
|
+
*/
|
|
2032
|
+
registerHandlers() {
|
|
2033
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
2034
|
+
logger4.debug("Listing tools");
|
|
2035
|
+
return {
|
|
2036
|
+
tools: TOOL_DEFINITIONS.map((tool) => ({
|
|
2037
|
+
name: tool.name,
|
|
2038
|
+
description: tool.description,
|
|
2039
|
+
inputSchema: zodToJsonSchema(tool.inputSchema)
|
|
2040
|
+
}))
|
|
2041
|
+
};
|
|
2042
|
+
});
|
|
2043
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
2044
|
+
const { name, arguments: args } = request.params;
|
|
2045
|
+
logger4.debug({ tool: name }, "Tool call received");
|
|
2046
|
+
if (!this.toolHandlers) {
|
|
2047
|
+
this.toolHandlers = createToolHandlers(this.bridge);
|
|
2048
|
+
}
|
|
2049
|
+
const handler = this.toolHandlers[name];
|
|
2050
|
+
if (!handler) {
|
|
2051
|
+
logger4.warn({ tool: name }, "Unknown tool requested");
|
|
2052
|
+
return {
|
|
2053
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
2054
|
+
isError: true
|
|
2055
|
+
};
|
|
2056
|
+
}
|
|
2057
|
+
try {
|
|
2058
|
+
const result = await handler(args ?? {});
|
|
2059
|
+
logger4.debug({ tool: name, isError: result.isError }, "Tool call completed");
|
|
2060
|
+
return {
|
|
2061
|
+
content: result.content,
|
|
2062
|
+
isError: result.isError
|
|
2063
|
+
};
|
|
2064
|
+
} catch (err) {
|
|
2065
|
+
logger4.error({ tool: name, error: err.message }, "Tool call failed");
|
|
2066
|
+
return {
|
|
2067
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
2068
|
+
isError: true
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2071
|
+
});
|
|
2072
|
+
}
|
|
2073
|
+
/**
|
|
2074
|
+
* Start the MCP server
|
|
2075
|
+
* Connects to bridge daemon and starts listening on stdio
|
|
2076
|
+
*/
|
|
2077
|
+
async start() {
|
|
2078
|
+
console.error("[MCP] Starting bridge MCP server...");
|
|
2079
|
+
console.error(`[MCP] Connecting to bridge at ${this.config.bridgeUrl}`);
|
|
2080
|
+
try {
|
|
2081
|
+
await this.bridge.start();
|
|
2082
|
+
console.error("[MCP] Connected to bridge daemon");
|
|
2083
|
+
this.toolHandlers = createToolHandlers(this.bridge);
|
|
2084
|
+
const transport = new StdioServerTransport();
|
|
2085
|
+
await this.server.connect(transport);
|
|
2086
|
+
console.error("[MCP] MCP server started and listening on stdio");
|
|
2087
|
+
logger4.info("MCP server started");
|
|
2088
|
+
} catch (err) {
|
|
2089
|
+
console.error(`[MCP] Failed to start: ${err.message}`);
|
|
2090
|
+
throw err;
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
/**
|
|
2094
|
+
* Stop the MCP server
|
|
2095
|
+
*/
|
|
2096
|
+
async stop() {
|
|
2097
|
+
console.error("[MCP] Stopping MCP server...");
|
|
2098
|
+
try {
|
|
2099
|
+
await this.bridge.stop();
|
|
2100
|
+
await this.server.close();
|
|
2101
|
+
console.error("[MCP] MCP server stopped");
|
|
2102
|
+
logger4.info("MCP server stopped");
|
|
2103
|
+
} catch (err) {
|
|
2104
|
+
console.error(`[MCP] Error during shutdown: ${err.message}`);
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
/**
|
|
2108
|
+
* Get the bridge instance
|
|
2109
|
+
*/
|
|
2110
|
+
getBridge() {
|
|
2111
|
+
return this.bridge;
|
|
2112
|
+
}
|
|
2113
|
+
};
|
|
2114
|
+
async function startMcpServer(config) {
|
|
2115
|
+
const server = new BridgeMcpServer(config);
|
|
2116
|
+
await server.start();
|
|
2117
|
+
return server;
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2064
2120
|
export {
|
|
2065
2121
|
createLogger,
|
|
2066
2122
|
createChildLogger,
|
|
@@ -2086,19 +2142,13 @@ export {
|
|
|
2086
2142
|
createContextRequestMessage,
|
|
2087
2143
|
createNotificationMessage,
|
|
2088
2144
|
Bridge,
|
|
2089
|
-
parseDockerLabels,
|
|
2090
|
-
isDockerAvailable,
|
|
2091
|
-
discoverDockerPeers,
|
|
2092
|
-
discoverDocksalProjects,
|
|
2093
|
-
discoverDdevProjects,
|
|
2094
|
-
discoverLandoProjects,
|
|
2095
|
-
discoverAllPeers,
|
|
2096
|
-
detectEnvironment,
|
|
2097
|
-
getHostGateway,
|
|
2098
|
-
getDefaultConfig,
|
|
2099
2145
|
DEFAULT_CONFIG,
|
|
2100
2146
|
mergeConfig,
|
|
2101
2147
|
loadConfig,
|
|
2102
|
-
loadConfigSync
|
|
2148
|
+
loadConfigSync,
|
|
2149
|
+
TOOL_DEFINITIONS,
|
|
2150
|
+
createToolHandlers,
|
|
2151
|
+
BridgeMcpServer,
|
|
2152
|
+
startMcpServer
|
|
2103
2153
|
};
|
|
2104
|
-
//# sourceMappingURL=chunk-
|
|
2154
|
+
//# sourceMappingURL=chunk-MHUQYPTB.js.map
|