@willjackson/claude-code-bridge 0.3.0 → 0.5.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/README.md +103 -43
- package/dist/{chunk-LUL3SX2F.js → chunk-MHUQYPTB.js} +409 -5
- package/dist/chunk-MHUQYPTB.js.map +1 -0
- package/dist/cli.js +234 -19
- 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
|
|
@@ -83,33 +85,53 @@ npm link # Makes 'claude-bridge' available globally
|
|
|
83
85
|
|
|
84
86
|
## Quick Start
|
|
85
87
|
|
|
86
|
-
### Scenario 1:
|
|
88
|
+
### Scenario 1: Start Bridge and Launch Claude Code
|
|
89
|
+
|
|
90
|
+
The easiest way to get started - start the bridge and launch Claude Code in one command:
|
|
91
|
+
|
|
92
|
+
**Step 1: Configure the MCP server (one-time setup)**
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
claude mcp add remote-bridge -- npx @willjackson/claude-code-bridge mcp-server --connect ws://localhost:8766
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Step 2: Start bridge and launch Claude Code**
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
claude-bridge start --port 8766 --launch-claude
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
This starts the bridge daemon in the background and launches Claude Code with access to the remote bridge tools.
|
|
105
|
+
|
|
106
|
+
### Scenario 2: Mac to Remote Server
|
|
87
107
|
|
|
88
108
|
**Step 1: Start the bridge on your Mac**
|
|
89
109
|
|
|
90
110
|
```bash
|
|
91
|
-
claude-bridge start --port 8766
|
|
111
|
+
claude-bridge start --port 8766 --launch-claude
|
|
92
112
|
```
|
|
93
113
|
|
|
94
|
-
**Step 2: Start the bridge
|
|
114
|
+
**Step 2: Start the bridge on the remote machine and connect**
|
|
95
115
|
|
|
96
116
|
```bash
|
|
97
|
-
#
|
|
98
|
-
claude-bridge start --
|
|
117
|
+
# On remote machine (replace YOUR_MAC_IP with actual IP)
|
|
118
|
+
claude-bridge start --with-handlers --connect ws://YOUR_MAC_IP:8766
|
|
99
119
|
```
|
|
100
120
|
|
|
101
|
-
|
|
121
|
+
Now Claude Code on your Mac can read/write files on the remote machine.
|
|
122
|
+
|
|
123
|
+
### Scenario 3: Peer-to-Peer Mode
|
|
102
124
|
|
|
103
125
|
Both instances can initiate communication:
|
|
104
126
|
|
|
105
|
-
**
|
|
127
|
+
**Machine A:**
|
|
106
128
|
```bash
|
|
107
129
|
claude-bridge start --port 8766
|
|
108
130
|
```
|
|
109
131
|
|
|
110
|
-
**
|
|
132
|
+
**Machine B:**
|
|
111
133
|
```bash
|
|
112
|
-
claude-bridge start --port 8765 --connect ws://
|
|
134
|
+
claude-bridge start --port 8765 --connect ws://MACHINE_A_IP:8766
|
|
113
135
|
```
|
|
114
136
|
|
|
115
137
|
Once connected, either side can send context or delegate tasks to the other.
|
|
@@ -118,17 +140,17 @@ Once connected, either side can send context or delegate tasks to the other.
|
|
|
118
140
|
|
|
119
141
|
Enable file operations on the remote to allow reading, writing, and deleting files:
|
|
120
142
|
|
|
121
|
-
**
|
|
143
|
+
**Machine A (relay mode, no handlers):**
|
|
122
144
|
```bash
|
|
123
145
|
claude-bridge start --port 8766
|
|
124
146
|
```
|
|
125
147
|
|
|
126
|
-
**
|
|
148
|
+
**Machine B (with file handlers):**
|
|
127
149
|
```bash
|
|
128
|
-
claude-bridge start --with-handlers --connect ws://
|
|
150
|
+
claude-bridge start --with-handlers --connect ws://MACHINE_A_IP:8766
|
|
129
151
|
```
|
|
130
152
|
|
|
131
|
-
Now you can read, write, and edit files on
|
|
153
|
+
Now you can read, write, and edit files on Machine B from Machine A.
|
|
132
154
|
|
|
133
155
|
## CLI Reference
|
|
134
156
|
|
|
@@ -157,11 +179,15 @@ Options:
|
|
|
157
179
|
-c, --connect <url> URL to connect to on startup (e.g., ws://localhost:8765)
|
|
158
180
|
-d, --daemon Run in background
|
|
159
181
|
--with-handlers Enable file operations and task handling (read/write/delete files)
|
|
182
|
+
--launch-claude Start bridge daemon and launch Claude Code
|
|
160
183
|
```
|
|
161
184
|
|
|
162
185
|
**Examples:**
|
|
163
186
|
|
|
164
187
|
```bash
|
|
188
|
+
# Start bridge and launch Claude Code (recommended for local use)
|
|
189
|
+
claude-bridge start --port 8766 --launch-claude
|
|
190
|
+
|
|
165
191
|
# Start with default settings
|
|
166
192
|
claude-bridge start
|
|
167
193
|
|
|
@@ -174,10 +200,66 @@ claude-bridge start --connect ws://192.168.1.100:8765
|
|
|
174
200
|
# Start in daemon mode
|
|
175
201
|
claude-bridge start --daemon
|
|
176
202
|
|
|
177
|
-
# Start with file operation handlers enabled
|
|
178
|
-
claude-bridge start --with-handlers --connect ws://
|
|
203
|
+
# Start with file operation handlers enabled (for remote machines)
|
|
204
|
+
claude-bridge start --with-handlers --connect ws://192.168.1.100:8766
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Daemon Mode
|
|
208
|
+
|
|
209
|
+
Daemon mode runs the bridge as a background process, allowing you to close your terminal while the bridge continues running.
|
|
210
|
+
|
|
211
|
+
#### Starting in Daemon Mode
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
# Start bridge in background
|
|
215
|
+
claude-bridge start --daemon
|
|
216
|
+
|
|
217
|
+
# Start with additional options
|
|
218
|
+
claude-bridge start --daemon --port 8766 --with-handlers
|
|
219
|
+
|
|
220
|
+
# Start daemon and connect to remote
|
|
221
|
+
claude-bridge start --daemon --connect ws://192.168.1.100:8765
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
When started in daemon mode, the bridge:
|
|
225
|
+
- Detaches from the terminal and runs in the background
|
|
226
|
+
- Writes its PID and status to `~/.claude-bridge/status.json`
|
|
227
|
+
- Continues running until explicitly stopped
|
|
228
|
+
|
|
229
|
+
#### Checking Daemon Status
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
claude-bridge status
|
|
179
233
|
```
|
|
180
234
|
|
|
235
|
+
This shows whether a daemon is running, its PID, listening port, and connected peers.
|
|
236
|
+
|
|
237
|
+
#### Stopping the Daemon
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
claude-bridge stop
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
This reads the PID from the status file and gracefully shuts down the background process.
|
|
244
|
+
|
|
245
|
+
#### Status File
|
|
246
|
+
|
|
247
|
+
The daemon writes its status to `~/.claude-bridge/status.json`:
|
|
248
|
+
|
|
249
|
+
```json
|
|
250
|
+
{
|
|
251
|
+
"running": true,
|
|
252
|
+
"pid": 12345,
|
|
253
|
+
"port": 8766,
|
|
254
|
+
"host": "0.0.0.0",
|
|
255
|
+
"instanceName": "bridge-12345",
|
|
256
|
+
"startedAt": "2024-01-15T10:30:00.000Z",
|
|
257
|
+
"peers": []
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
This file is automatically removed when the daemon stops cleanly.
|
|
262
|
+
|
|
181
263
|
#### `stop` - Stop the Running Bridge
|
|
182
264
|
|
|
183
265
|
```bash
|
|
@@ -213,7 +295,7 @@ Arguments:
|
|
|
213
295
|
|
|
214
296
|
```bash
|
|
215
297
|
claude-bridge connect ws://localhost:8765
|
|
216
|
-
claude-bridge connect ws://
|
|
298
|
+
claude-bridge connect ws://192.168.1.100:8766
|
|
217
299
|
```
|
|
218
300
|
|
|
219
301
|
#### `info` - Show System Information
|
|
@@ -251,7 +333,6 @@ listen:
|
|
|
251
333
|
# Connection to remote bridge
|
|
252
334
|
connect:
|
|
253
335
|
url: ws://localhost:8765
|
|
254
|
-
hostGateway: true # Use host.docker.internal
|
|
255
336
|
|
|
256
337
|
# Context sharing settings
|
|
257
338
|
contextSharing:
|
|
@@ -283,7 +364,6 @@ interaction:
|
|
|
283
364
|
| `listen.port` | number | 8765 | Port to listen on |
|
|
284
365
|
| `listen.host` | string | 0.0.0.0 | Host to bind to |
|
|
285
366
|
| `connect.url` | string | - | WebSocket URL of remote bridge |
|
|
286
|
-
| `connect.hostGateway` | boolean | false | Use `host.docker.internal` |
|
|
287
367
|
| `contextSharing.autoSync` | boolean | true | Automatically sync context |
|
|
288
368
|
| `contextSharing.syncInterval` | number | 5000 | Sync interval in ms |
|
|
289
369
|
| `contextSharing.maxChunkTokens` | number | 4000 | Max tokens per context chunk |
|
|
@@ -461,20 +541,6 @@ await bridge.delegateTask({
|
|
|
461
541
|
|
|
462
542
|
> **Security Note:** All file operations are restricted to the project directory where the bridge is running. Paths outside the project root are rejected.
|
|
463
543
|
|
|
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
544
|
### Context Manager
|
|
479
545
|
|
|
480
546
|
```typescript
|
|
@@ -507,13 +573,13 @@ for (const change of delta.changes) {
|
|
|
507
573
|
|
|
508
574
|
### Connection Issues
|
|
509
575
|
|
|
510
|
-
**Problem:** Cannot connect to bridge from
|
|
576
|
+
**Problem:** Cannot connect to bridge from remote machine
|
|
511
577
|
|
|
512
578
|
**Solutions:**
|
|
513
579
|
1. Ensure the host bridge is running: `claude-bridge status`
|
|
514
580
|
2. Check firewall settings allow the port
|
|
515
|
-
3. Verify
|
|
516
|
-
4. Try using the
|
|
581
|
+
3. Verify the IP address is correct and reachable: `ping MACHINE_IP`
|
|
582
|
+
4. Try using the full URL: `claude-bridge connect ws://192.168.1.100:8766`
|
|
517
583
|
|
|
518
584
|
**Problem:** Connection keeps dropping
|
|
519
585
|
|
|
@@ -621,12 +687,6 @@ npm test -- tests/unit/bridge/protocol.test.ts
|
|
|
621
687
|
npm test -- --grep "WebSocket"
|
|
622
688
|
```
|
|
623
689
|
|
|
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
690
|
## License
|
|
631
691
|
|
|
632
692
|
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
|