@willjackson/claude-code-bridge 0.3.0 → 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 +75 -39
- package/dist/{chunk-LUL3SX2F.js → chunk-MHUQYPTB.js} +409 -5
- package/dist/chunk-MHUQYPTB.js.map +1 -0
- package/dist/cli.js +220 -18
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +74 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/dist/chunk-LUL3SX2F.js.map +0 -1
package/README.md
CHANGED
|
@@ -62,13 +62,15 @@ Claude Code Bridge connects two Claude Code instances running on different machi
|
|
|
62
62
|
### Global Installation (Recommended)
|
|
63
63
|
|
|
64
64
|
```bash
|
|
65
|
-
npm install -g claude-code-bridge
|
|
65
|
+
npm install -g @willjackson/claude-code-bridge
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
+
After installation, the `claude-bridge` command will be available globally.
|
|
69
|
+
|
|
68
70
|
### Run Without Installing
|
|
69
71
|
|
|
70
72
|
```bash
|
|
71
|
-
npx claude-code-bridge start
|
|
73
|
+
npx @willjackson/claude-code-bridge start
|
|
72
74
|
```
|
|
73
75
|
|
|
74
76
|
### Install from Source
|
|
@@ -91,25 +93,25 @@ npm link # Makes 'claude-bridge' available globally
|
|
|
91
93
|
claude-bridge start --port 8766
|
|
92
94
|
```
|
|
93
95
|
|
|
94
|
-
**Step 2: Start the bridge
|
|
96
|
+
**Step 2: Start the bridge on the remote machine and connect**
|
|
95
97
|
|
|
96
98
|
```bash
|
|
97
|
-
#
|
|
98
|
-
claude-bridge start --port 8765 --connect ws://
|
|
99
|
+
# On remote machine (replace MACHINE_A_IP with actual IP)
|
|
100
|
+
claude-bridge start --port 8765 --connect ws://MACHINE_A_IP:8766
|
|
99
101
|
```
|
|
100
102
|
|
|
101
103
|
### Scenario 2: Peer-to-Peer Mode
|
|
102
104
|
|
|
103
105
|
Both instances can initiate communication:
|
|
104
106
|
|
|
105
|
-
**
|
|
107
|
+
**Machine A:**
|
|
106
108
|
```bash
|
|
107
109
|
claude-bridge start --port 8766
|
|
108
110
|
```
|
|
109
111
|
|
|
110
|
-
**
|
|
112
|
+
**Machine B:**
|
|
111
113
|
```bash
|
|
112
|
-
claude-bridge start --port 8765 --connect ws://
|
|
114
|
+
claude-bridge start --port 8765 --connect ws://MACHINE_A_IP:8766
|
|
113
115
|
```
|
|
114
116
|
|
|
115
117
|
Once connected, either side can send context or delegate tasks to the other.
|
|
@@ -118,17 +120,17 @@ Once connected, either side can send context or delegate tasks to the other.
|
|
|
118
120
|
|
|
119
121
|
Enable file operations on the remote to allow reading, writing, and deleting files:
|
|
120
122
|
|
|
121
|
-
**
|
|
123
|
+
**Machine A (relay mode, no handlers):**
|
|
122
124
|
```bash
|
|
123
125
|
claude-bridge start --port 8766
|
|
124
126
|
```
|
|
125
127
|
|
|
126
|
-
**
|
|
128
|
+
**Machine B (with file handlers):**
|
|
127
129
|
```bash
|
|
128
|
-
claude-bridge start --with-handlers --connect ws://
|
|
130
|
+
claude-bridge start --with-handlers --connect ws://MACHINE_A_IP:8766
|
|
129
131
|
```
|
|
130
132
|
|
|
131
|
-
Now you can read, write, and edit files on
|
|
133
|
+
Now you can read, write, and edit files on Machine B from Machine A.
|
|
132
134
|
|
|
133
135
|
## CLI Reference
|
|
134
136
|
|
|
@@ -175,9 +177,65 @@ claude-bridge start --connect ws://192.168.1.100:8765
|
|
|
175
177
|
claude-bridge start --daemon
|
|
176
178
|
|
|
177
179
|
# Start with file operation handlers enabled
|
|
178
|
-
claude-bridge start --with-handlers --connect ws://
|
|
180
|
+
claude-bridge start --with-handlers --connect ws://192.168.1.100:8766
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Daemon Mode
|
|
184
|
+
|
|
185
|
+
Daemon mode runs the bridge as a background process, allowing you to close your terminal while the bridge continues running.
|
|
186
|
+
|
|
187
|
+
#### Starting in Daemon Mode
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
# Start bridge in background
|
|
191
|
+
claude-bridge start --daemon
|
|
192
|
+
|
|
193
|
+
# Start with additional options
|
|
194
|
+
claude-bridge start --daemon --port 8766 --with-handlers
|
|
195
|
+
|
|
196
|
+
# Start daemon and connect to remote
|
|
197
|
+
claude-bridge start --daemon --connect ws://192.168.1.100:8765
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
When started in daemon mode, the bridge:
|
|
201
|
+
- Detaches from the terminal and runs in the background
|
|
202
|
+
- Writes its PID and status to `~/.claude-bridge/status.json`
|
|
203
|
+
- Continues running until explicitly stopped
|
|
204
|
+
|
|
205
|
+
#### Checking Daemon Status
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
claude-bridge status
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
This shows whether a daemon is running, its PID, listening port, and connected peers.
|
|
212
|
+
|
|
213
|
+
#### Stopping the Daemon
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
claude-bridge stop
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
This reads the PID from the status file and gracefully shuts down the background process.
|
|
220
|
+
|
|
221
|
+
#### Status File
|
|
222
|
+
|
|
223
|
+
The daemon writes its status to `~/.claude-bridge/status.json`:
|
|
224
|
+
|
|
225
|
+
```json
|
|
226
|
+
{
|
|
227
|
+
"running": true,
|
|
228
|
+
"pid": 12345,
|
|
229
|
+
"port": 8766,
|
|
230
|
+
"host": "0.0.0.0",
|
|
231
|
+
"instanceName": "bridge-12345",
|
|
232
|
+
"startedAt": "2024-01-15T10:30:00.000Z",
|
|
233
|
+
"peers": []
|
|
234
|
+
}
|
|
179
235
|
```
|
|
180
236
|
|
|
237
|
+
This file is automatically removed when the daemon stops cleanly.
|
|
238
|
+
|
|
181
239
|
#### `stop` - Stop the Running Bridge
|
|
182
240
|
|
|
183
241
|
```bash
|
|
@@ -213,7 +271,7 @@ Arguments:
|
|
|
213
271
|
|
|
214
272
|
```bash
|
|
215
273
|
claude-bridge connect ws://localhost:8765
|
|
216
|
-
claude-bridge connect ws://
|
|
274
|
+
claude-bridge connect ws://192.168.1.100:8766
|
|
217
275
|
```
|
|
218
276
|
|
|
219
277
|
#### `info` - Show System Information
|
|
@@ -251,7 +309,6 @@ listen:
|
|
|
251
309
|
# Connection to remote bridge
|
|
252
310
|
connect:
|
|
253
311
|
url: ws://localhost:8765
|
|
254
|
-
hostGateway: true # Use host.docker.internal
|
|
255
312
|
|
|
256
313
|
# Context sharing settings
|
|
257
314
|
contextSharing:
|
|
@@ -283,7 +340,6 @@ interaction:
|
|
|
283
340
|
| `listen.port` | number | 8765 | Port to listen on |
|
|
284
341
|
| `listen.host` | string | 0.0.0.0 | Host to bind to |
|
|
285
342
|
| `connect.url` | string | - | WebSocket URL of remote bridge |
|
|
286
|
-
| `connect.hostGateway` | boolean | false | Use `host.docker.internal` |
|
|
287
343
|
| `contextSharing.autoSync` | boolean | true | Automatically sync context |
|
|
288
344
|
| `contextSharing.syncInterval` | number | 5000 | Sync interval in ms |
|
|
289
345
|
| `contextSharing.maxChunkTokens` | number | 4000 | Max tokens per context chunk |
|
|
@@ -461,20 +517,6 @@ await bridge.delegateTask({
|
|
|
461
517
|
|
|
462
518
|
> **Security Note:** All file operations are restricted to the project directory where the bridge is running. Paths outside the project root are rejected.
|
|
463
519
|
|
|
464
|
-
### Environment Detection
|
|
465
|
-
|
|
466
|
-
```typescript
|
|
467
|
-
import { detectEnvironment, getHostGateway } from 'claude-code-bridge';
|
|
468
|
-
|
|
469
|
-
const env = detectEnvironment();
|
|
470
|
-
console.log('Environment:', env.type); // 'native', 'docksal', 'ddev', 'lando', 'docker'
|
|
471
|
-
console.log('Is Container:', env.isContainer);
|
|
472
|
-
console.log('Project Name:', env.projectName);
|
|
473
|
-
|
|
474
|
-
const gateway = getHostGateway();
|
|
475
|
-
console.log('Host Gateway:', gateway); // 'host.docker.internal' or IP address
|
|
476
|
-
```
|
|
477
|
-
|
|
478
520
|
### Context Manager
|
|
479
521
|
|
|
480
522
|
```typescript
|
|
@@ -507,13 +549,13 @@ for (const change of delta.changes) {
|
|
|
507
549
|
|
|
508
550
|
### Connection Issues
|
|
509
551
|
|
|
510
|
-
**Problem:** Cannot connect to bridge from
|
|
552
|
+
**Problem:** Cannot connect to bridge from remote machine
|
|
511
553
|
|
|
512
554
|
**Solutions:**
|
|
513
555
|
1. Ensure the host bridge is running: `claude-bridge status`
|
|
514
556
|
2. Check firewall settings allow the port
|
|
515
|
-
3. Verify
|
|
516
|
-
4. Try using the
|
|
557
|
+
3. Verify the IP address is correct and reachable: `ping MACHINE_IP`
|
|
558
|
+
4. Try using the full URL: `claude-bridge connect ws://192.168.1.100:8766`
|
|
517
559
|
|
|
518
560
|
**Problem:** Connection keeps dropping
|
|
519
561
|
|
|
@@ -621,12 +663,6 @@ npm test -- tests/unit/bridge/protocol.test.ts
|
|
|
621
663
|
npm test -- --grep "WebSocket"
|
|
622
664
|
```
|
|
623
665
|
|
|
624
|
-
## Documentation
|
|
625
|
-
|
|
626
|
-
- [CLAUDE.md](./CLAUDE.md) - Detailed project specification and architecture
|
|
627
|
-
- [AGENTS.md](./AGENTS.md) - Guidelines for AI agents working on this codebase
|
|
628
|
-
- [instructions.md](./instructions.md) - Development phases and testing strategy
|
|
629
|
-
|
|
630
666
|
## License
|
|
631
667
|
|
|
632
668
|
MIT License - see [LICENSE](./LICENSE) for details.
|
|
@@ -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: {
|
|
@@ -1717,6 +1717,406 @@ function loadConfigSync(configPath) {
|
|
|
1717
1717
|
return { ...DEFAULT_CONFIG };
|
|
1718
1718
|
}
|
|
1719
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
|
+
|
|
1720
2120
|
export {
|
|
1721
2121
|
createLogger,
|
|
1722
2122
|
createChildLogger,
|
|
@@ -1745,6 +2145,10 @@ export {
|
|
|
1745
2145
|
DEFAULT_CONFIG,
|
|
1746
2146
|
mergeConfig,
|
|
1747
2147
|
loadConfig,
|
|
1748
|
-
loadConfigSync
|
|
2148
|
+
loadConfigSync,
|
|
2149
|
+
TOOL_DEFINITIONS,
|
|
2150
|
+
createToolHandlers,
|
|
2151
|
+
BridgeMcpServer,
|
|
2152
|
+
startMcpServer
|
|
1749
2153
|
};
|
|
1750
|
-
//# sourceMappingURL=chunk-
|
|
2154
|
+
//# sourceMappingURL=chunk-MHUQYPTB.js.map
|