claude-ide-bridge 2.22.7 → 2.22.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,172 @@
1
+ # Deploy
2
+
3
+ Production VPS deployment files for claude-ide-bridge.
4
+
5
+ > **Deployment targets:** The **systemd + nginx** path (this directory) is the
6
+ > production-supported deployment for VPS/remote use. A `Dockerfile` and
7
+ > `docker-compose.yml` exist in the repo root as an alternative for
8
+ > containerised environments, but are not actively tested against the systemd
9
+ > config — if you use Docker, treat it as a community-supported path and keep
10
+ > it in sync with any service config changes.
11
+
12
+ ## Files
13
+
14
+ | File | Purpose |
15
+ |------|---------|
16
+ | `bootstrap-new-vps.sh` | **Full fresh-server setup** — Node.js, clone, build, user, firewall, systemd, nginx, Certbot |
17
+ | `install-vps-service.sh` | **Idempotent updater** — re-installs service + nginx after `git pull` on an existing server |
18
+ | `nginx-claude-bridge.conf.template` | nginx config reference (domain + port injected by scripts) |
19
+ | `claude-ide-bridge.service.template` | systemd unit reference (paths + user injected by scripts) |
20
+ | `claude-ide-bridge@.service` | Template unit for multi-user demo instances (alternate pattern) |
21
+ | `ecosystem.config.js.example` | PM2 ecosystem config template (alternative to systemd) |
22
+
23
+ ## First-time setup (new VPS)
24
+
25
+ ```bash
26
+ # Option A: run remotely on fresh server
27
+ DOMAIN=bridge.example.com bash <(curl -fsSL https://raw.githubusercontent.com/Oolab-labs/claude-ide-bridge/main/deploy/bootstrap-new-vps.sh)
28
+
29
+ # Option B: after cloning the repo
30
+ DOMAIN=bridge.example.com bash deploy/bootstrap-new-vps.sh
31
+ ```
32
+
33
+ The bootstrap script handles everything end-to-end:
34
+ 1. Installs Node.js 20, nginx, certbot
35
+ 2. Creates a dedicated `claude-bridge` system user (non-root)
36
+ 3. Clones the repo to `/opt/claude-ide-bridge`
37
+ 4. Runs `npm ci && npm run build`
38
+ 5. Generates `.env.vps` with a random auth token
39
+ 6. Opens ports 80/443 in ufw
40
+ 7. Installs and enables the systemd service
41
+ 8. Writes the nginx config with your domain injected
42
+ 9. Runs Certbot for HTTPS
43
+ 10. Starts the bridge and confirms it's healthy
44
+
45
+ **Required:** `DOMAIN` env var pointing to a subdomain that resolves to your VPS IP.
46
+
47
+ **Optional overrides:**
48
+
49
+ | Variable | Default | Description |
50
+ |----------|---------|-------------|
51
+ | `REPO_URL` | GitHub upstream | Git repo to clone |
52
+ | `INSTALL_DIR` | `/opt/claude-ide-bridge` | Where to install |
53
+ | `SERVICE_USER` | `claude-bridge` | System user to run as |
54
+ | `PORT` | `9000` | Bridge listen port |
55
+ | `BRANCH` | `main` | Git branch |
56
+ | `SKIP_CERTBOT` | `0` | Set to `1` to skip TLS (DNS not ready) |
57
+
58
+ ## Updating an existing server
59
+
60
+ ```bash
61
+ cd /opt/claude-ide-bridge # or wherever INSTALL_DIR is
62
+ git pull
63
+ npm ci
64
+ npm run build
65
+ bash deploy/install-vps-service.sh
66
+ ```
67
+
68
+ `install-vps-service.sh` re-generates the systemd unit and nginx config from the current state of `.env.vps`, then restarts the service automatically. It auto-detects the domain and service user from the existing installation — no config needed.
69
+
70
+ ## Day-to-day management
71
+
72
+ ```bash
73
+ # View live logs
74
+ journalctl -u claude-ide-bridge -f
75
+
76
+ # Check status
77
+ systemctl status claude-ide-bridge
78
+
79
+ # Restart after code change
80
+ npm run build && systemctl restart claude-ide-bridge
81
+
82
+ # Stop (won't restart until manually started)
83
+ systemctl stop claude-ide-bridge
84
+ ```
85
+
86
+ ## Using PM2 instead of systemd
87
+
88
+ PM2 is a simpler alternative when you're running as root, already have PM2 installed, or prefer not to configure systemd manually.
89
+
90
+ > **Important:** The bridge picks a **random port** by default. The port in your PM2 start command **must match** the port in your nginx `proxy_pass` directive, or every restart will cause a 502 Bad Gateway.
91
+
92
+ ### Quick start with PM2
93
+
94
+ ```bash
95
+ # Replace 4748 and YOUR_TOKEN_HERE with your nginx proxy_pass port and auth token
96
+ pm2 delete claude-bridge 2>/dev/null || true
97
+ pm2 start /root/claude-ide-bridge/dist/index.js \
98
+ --name claude-bridge \
99
+ -- --port 4748 --bind 0.0.0.0 --vps --fixed-token YOUR_TOKEN_HERE
100
+ pm2 save
101
+ ```
102
+
103
+ Retrieve your token anytime:
104
+ ```bash
105
+ cat ~/.claude/ide/*.lock | node -e "const d=require('fs').readFileSync('/dev/stdin','utf8').trim(); console.log(JSON.parse(d).authToken)"
106
+ # or if you have a .env.vps:
107
+ grep FIXED_TOKEN /root/claude-ide-bridge/.env.vps
108
+ ```
109
+
110
+ ### Persist across reboots
111
+
112
+ ```bash
113
+ # Generate and run the startup command PM2 prints
114
+ pm2 startup
115
+ # (run the command it outputs, then:)
116
+ pm2 save
117
+ ```
118
+
119
+ ### Using an ecosystem file
120
+
121
+ For repeatable deployments, use the included example:
122
+
123
+ ```bash
124
+ cp deploy/ecosystem.config.js.example ecosystem.config.js
125
+ # Edit ecosystem.config.js: set cwd, port, and fixed-token
126
+ pm2 start ecosystem.config.js
127
+ pm2 save
128
+ ```
129
+
130
+ ### Day-to-day management with PM2
131
+
132
+ ```bash
133
+ # View live logs
134
+ pm2 logs claude-bridge
135
+
136
+ # Check status
137
+ pm2 status
138
+
139
+ # Restart after code change
140
+ npm run build && pm2 restart claude-bridge
141
+
142
+ # Stop
143
+ pm2 stop claude-bridge
144
+ ```
145
+
146
+ ## MCP endpoint
147
+
148
+ ```
149
+ https://<your-domain>/mcp
150
+ ```
151
+
152
+ Use in `.mcp.json` for Claude Desktop, claude.ai Custom Connectors, or any remote MCP client:
153
+
154
+ ```json
155
+ {
156
+ "mcpServers": {
157
+ "claude-ide-bridge": {
158
+ "type": "http",
159
+ "url": "https://your-domain/mcp",
160
+ "headers": {
161
+ "Authorization": "Bearer ${BRIDGE_TOKEN}"
162
+ }
163
+ }
164
+ }
165
+ }
166
+ ```
167
+
168
+ Set `BRIDGE_TOKEN` in your shell profile. Retrieve the token anytime:
169
+
170
+ ```bash
171
+ grep FIXED_TOKEN /opt/claude-ide-bridge/.env.vps
172
+ ```
@@ -0,0 +1,364 @@
1
+ #!/usr/bin/env bash
2
+ # deploy/bootstrap-new-vps.sh
3
+ # Full fresh-server setup for claude-ide-bridge on a NEW VPS.
4
+ # Handles everything from Node.js install to running HTTPS service.
5
+ #
6
+ # Usage (run as root on a fresh Ubuntu 22.04/24.04 VPS):
7
+ # curl -fsSL https://raw.githubusercontent.com/Oolab-labs/claude-ide-bridge/main/deploy/bootstrap-new-vps.sh | \
8
+ # DOMAIN=bridge.example.com bash
9
+ #
10
+ # Or after cloning:
11
+ # DOMAIN=bridge.example.com bash deploy/bootstrap-new-vps.sh
12
+ #
13
+ # Required environment variables:
14
+ # DOMAIN Subdomain for the bridge (e.g. bridge.example.com)
15
+ #
16
+ # Optional environment variables:
17
+ # REPO_URL Git repo URL (default: https://github.com/Oolab-labs/claude-ide-bridge)
18
+ # INSTALL_DIR Where to clone the repo (default: /opt/claude-ide-bridge)
19
+ # SERVICE_USER System user to run bridge (default: claude-bridge)
20
+ # PORT Bridge port (default: 9000)
21
+ # BRANCH Git branch to clone (default: main)
22
+ # SKIP_CERTBOT Set to 1 to skip TLS cert (useful if DNS not yet set)
23
+
24
+ set -euo pipefail
25
+
26
+ # ── Config ────────────────────────────────────────────────────────────────────
27
+ DOMAIN="${DOMAIN:-}"
28
+ REPO_URL="${REPO_URL:-https://github.com/Oolab-labs/claude-ide-bridge}"
29
+ INSTALL_DIR="${INSTALL_DIR:-/opt/claude-ide-bridge}"
30
+ SERVICE_USER="${SERVICE_USER:-claude-bridge}"
31
+ PORT="${PORT:-9000}"
32
+ BRANCH="${BRANCH:-main}"
33
+ SKIP_CERTBOT="${SKIP_CERTBOT:-0}"
34
+ SERVICE_NAME="claude-ide-bridge"
35
+
36
+ RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
37
+ info() { echo -e "${GREEN}✓${NC} $*"; }
38
+ warn() { echo -e "${YELLOW}⚠${NC} $*"; }
39
+ err() { echo -e "${RED}✗${NC} $*" >&2; }
40
+ section() { echo ""; echo "── $* ──────────────────────────────────────────"; }
41
+
42
+ # ── Pre-flight ────────────────────────────────────────────────────────────────
43
+ section "Pre-flight"
44
+
45
+ [[ "$(id -u)" -eq 0 ]] || { err "Run as root: sudo bash deploy/bootstrap-new-vps.sh"; exit 1; }
46
+
47
+ if [[ -z "$DOMAIN" ]]; then
48
+ err "DOMAIN is required. Example:"
49
+ echo " DOMAIN=bridge.example.com bash deploy/bootstrap-new-vps.sh"
50
+ exit 1
51
+ fi
52
+
53
+ info "Domain: $DOMAIN"
54
+ info "Install dir: $INSTALL_DIR"
55
+ info "Service user: $SERVICE_USER"
56
+ info "Port: $PORT"
57
+ info "Branch: $BRANCH"
58
+
59
+ # ── 1. System packages ────────────────────────────────────────────────────────
60
+ section "System packages"
61
+
62
+ apt-get update -qq
63
+ apt-get install -y -qq curl git nginx certbot python3-certbot-nginx ufw
64
+ info "System packages installed"
65
+
66
+ # ── 2. Node.js 20+ ────────────────────────────────────────────────────────────
67
+ section "Node.js"
68
+
69
+ NODE_OK=false
70
+ if command -v node &>/dev/null; then
71
+ NODE_VER=$(node -e 'console.log(parseInt(process.version.slice(1)))')
72
+ [[ "$NODE_VER" -ge 20 ]] && NODE_OK=true
73
+ fi
74
+
75
+ if [[ "$NODE_OK" == false ]]; then
76
+ info "Installing Node.js 20..."
77
+ curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
78
+ apt-get install -y -qq nodejs
79
+ fi
80
+
81
+ NODE_VERSION=$(node --version)
82
+ info "Node $NODE_VERSION / npm $(npm --version)"
83
+
84
+ # ── 3. Service user ───────────────────────────────────────────────────────────
85
+ section "Service user"
86
+
87
+ if ! id "$SERVICE_USER" &>/dev/null; then
88
+ useradd --system --create-home --shell /bin/bash "$SERVICE_USER"
89
+ info "Created user: $SERVICE_USER"
90
+ else
91
+ info "User already exists: $SERVICE_USER"
92
+ fi
93
+
94
+ # ── 4. Clone or update repo ───────────────────────────────────────────────────
95
+ section "Repository"
96
+
97
+ if [[ -d "$INSTALL_DIR/.git" ]]; then
98
+ info "Repo exists — pulling latest..."
99
+ git -C "$INSTALL_DIR" fetch origin
100
+ git -C "$INSTALL_DIR" checkout "$BRANCH"
101
+ git -C "$INSTALL_DIR" pull origin "$BRANCH"
102
+ else
103
+ info "Cloning $REPO_URL..."
104
+ git clone --branch "$BRANCH" --depth 1 "$REPO_URL" "$INSTALL_DIR"
105
+ fi
106
+
107
+ chown -R "$SERVICE_USER:$SERVICE_USER" "$INSTALL_DIR"
108
+ info "Repo ready at $INSTALL_DIR"
109
+
110
+ # ── 5. Build ──────────────────────────────────────────────────────────────────
111
+ section "Build"
112
+
113
+ # Run npm install + build as SERVICE_USER
114
+ sudo -u "$SERVICE_USER" bash -c "cd $INSTALL_DIR && npm ci --prefer-offline 2>&1"
115
+ sudo -u "$SERVICE_USER" bash -c "cd $INSTALL_DIR && npm run build 2>&1"
116
+ info "Build complete"
117
+
118
+ # ── 6. Generate .env.vps ──────────────────────────────────────────────────────
119
+ section ".env.vps"
120
+
121
+ ENV_FILE="$INSTALL_DIR/.env.vps"
122
+ if [[ -f "$ENV_FILE" ]]; then
123
+ warn ".env.vps already exists — leaving unchanged"
124
+ source "$ENV_FILE"
125
+ info " PORT=$PORT WORKSPACE=$WORKSPACE TOKEN=***"
126
+ else
127
+ FIXED_TOKEN=$(node -e "console.log(require('crypto').randomUUID())")
128
+ WORKSPACE="$INSTALL_DIR"
129
+ cat > "$ENV_FILE" <<EOF
130
+ # Claude IDE Bridge — VPS config
131
+ # Generated by bootstrap-new-vps.sh on $(date -u +%Y-%m-%dT%H:%M:%SZ)
132
+ # Gitignored — never commit this file.
133
+
134
+ PORT=$PORT
135
+ WORKSPACE=$WORKSPACE
136
+ FIXED_TOKEN=$FIXED_TOKEN
137
+ BRIDGE_SESSION=bridge
138
+ NGROK_SESSION=ngrok
139
+ EOF
140
+ chmod 600 "$ENV_FILE"
141
+ chown "$SERVICE_USER:$SERVICE_USER" "$ENV_FILE"
142
+ info ".env.vps created"
143
+ info " PORT=$PORT"
144
+ info " WORKSPACE=$WORKSPACE"
145
+ info " TOKEN=$FIXED_TOKEN"
146
+ fi
147
+
148
+ # Reload in case it already existed
149
+ source "$ENV_FILE"
150
+
151
+ # ── 7. Firewall ───────────────────────────────────────────────────────────────
152
+ section "Firewall (ufw)"
153
+
154
+ ufw --force enable >/dev/null 2>&1 || true
155
+ ufw allow ssh >/dev/null 2>&1
156
+ ufw allow 80 >/dev/null 2>&1
157
+ ufw allow 443 >/dev/null 2>&1
158
+ info "ufw: ssh, 80, 443 open"
159
+
160
+ # ── 8. Systemd service ────────────────────────────────────────────────────────
161
+ section "Systemd service"
162
+
163
+ # Generate service file from template (parameterised for this install)
164
+ cat > "/etc/systemd/system/${SERVICE_NAME}.service" <<SERVICE
165
+ [Unit]
166
+ Description=Claude IDE Bridge MCP Server
167
+ Documentation=https://github.com/Oolab-labs/claude-ide-bridge
168
+ After=network.target
169
+ StartLimitIntervalSec=120
170
+ StartLimitBurst=5
171
+
172
+ [Service]
173
+ Type=simple
174
+ User=$SERVICE_USER
175
+ WorkingDirectory=$INSTALL_DIR
176
+
177
+ # Load config — FIXED_TOKEN, PORT, WORKSPACE
178
+ EnvironmentFile=$ENV_FILE
179
+
180
+ # Bridge process
181
+ ExecStart=/usr/bin/node $INSTALL_DIR/dist/index.js \\
182
+ --port \${PORT} \\
183
+ --workspace \${WORKSPACE} \\
184
+ --fixed-token \${FIXED_TOKEN} \\
185
+ --grace-period 120000 \\
186
+ --vps
187
+
188
+ KillMode=mixed
189
+ KillSignal=SIGTERM
190
+ TimeoutStopSec=15
191
+ Restart=always
192
+ RestartSec=5
193
+
194
+ StandardOutput=journal
195
+ StandardError=journal
196
+ SyslogIdentifier=claude-ide-bridge
197
+
198
+ NoNewPrivileges=true
199
+ PrivateTmp=true
200
+
201
+ [Install]
202
+ WantedBy=multi-user.target
203
+ SERVICE
204
+
205
+ systemctl daemon-reload
206
+ systemctl enable "$SERVICE_NAME"
207
+ info "Systemd service installed and enabled"
208
+
209
+ # ── 9. nginx ──────────────────────────────────────────────────────────────────
210
+ section "nginx"
211
+
212
+ # Add connection_upgrade map to nginx.conf if missing
213
+ if ! grep -q "connection_upgrade" /etc/nginx/nginx.conf 2>/dev/null; then
214
+ # Insert after the `http {` line
215
+ perl -i -0pe 's/(http \{)/$1\n map \$http_upgrade \$connection_upgrade {\n default upgrade;\n '"''"' close;\n }/' /etc/nginx/nginx.conf
216
+ info "Added connection_upgrade map to nginx.conf"
217
+ fi
218
+
219
+ # Write nginx site config (domain injected here, not hardcoded in repo)
220
+ cat > "/etc/nginx/sites-available/claude-bridge" <<NGINX
221
+ # Claude IDE Bridge — nginx reverse proxy
222
+ # Domain: $DOMAIN Port: ${PORT}
223
+ # Generated by bootstrap-new-vps.sh
224
+
225
+ server {
226
+ listen 80;
227
+ listen [::]:80;
228
+ server_name $DOMAIN;
229
+
230
+ location /.well-known/acme-challenge/ {
231
+ root /var/www/html;
232
+ }
233
+
234
+ location / {
235
+ return 301 https://\$host\$request_uri;
236
+ }
237
+ }
238
+
239
+ server {
240
+ listen 443 ssl http2;
241
+ listen [::]:443 ssl http2;
242
+ server_name $DOMAIN;
243
+
244
+ # TLS — populated by Certbot
245
+ # ssl_certificate /etc/letsencrypt/live/$DOMAIN/fullchain.pem;
246
+ # ssl_certificate_key /etc/letsencrypt/live/$DOMAIN/privkey.pem;
247
+ # include /etc/letsencrypt/options-ssl-nginx.conf;
248
+ # ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
249
+
250
+ add_header X-Content-Type-Options nosniff always;
251
+ add_header X-Frame-Options DENY always;
252
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
253
+
254
+ # /mcp — Streamable HTTP MCP endpoint (SSE-safe)
255
+ location /mcp {
256
+ proxy_pass http://127.0.0.1:${PORT};
257
+ proxy_http_version 1.1;
258
+ proxy_buffering off;
259
+ proxy_cache off;
260
+ proxy_read_timeout 3600s;
261
+ proxy_send_timeout 3600s;
262
+ proxy_connect_timeout 10s;
263
+ proxy_set_header Authorization \$http_authorization;
264
+ proxy_pass_header Authorization;
265
+ proxy_set_header Mcp-Session-Id \$http_mcp_session_id;
266
+ proxy_pass_header Mcp-Session-Id;
267
+ proxy_set_header Host \$host;
268
+ proxy_set_header X-Real-IP \$remote_addr;
269
+ proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
270
+ proxy_set_header X-Forwarded-Proto \$scheme;
271
+ proxy_set_header Upgrade \$http_upgrade;
272
+ proxy_set_header Connection \$connection_upgrade;
273
+ }
274
+
275
+ location /health {
276
+ proxy_pass http://127.0.0.1:${PORT};
277
+ proxy_http_version 1.1;
278
+ proxy_connect_timeout 5s;
279
+ proxy_read_timeout 10s;
280
+ }
281
+
282
+ location /.well-known/ {
283
+ proxy_pass http://127.0.0.1:${PORT};
284
+ proxy_http_version 1.1;
285
+ proxy_connect_timeout 5s;
286
+ proxy_read_timeout 10s;
287
+ }
288
+
289
+ location / {
290
+ return 404;
291
+ }
292
+ }
293
+ NGINX
294
+
295
+ # Enable site
296
+ [[ -L "/etc/nginx/sites-enabled/claude-bridge" ]] || \
297
+ ln -s /etc/nginx/sites-available/claude-bridge /etc/nginx/sites-enabled/claude-bridge
298
+
299
+ # Remove default site (conflicts on port 80)
300
+ [[ -L "/etc/nginx/sites-enabled/default" ]] && \
301
+ rm -f /etc/nginx/sites-enabled/default && info "Removed nginx default site"
302
+
303
+ nginx -t
304
+ systemctl reload nginx
305
+ info "nginx configured for $DOMAIN"
306
+
307
+ # ── 10. TLS certificate ───────────────────────────────────────────────────────
308
+ section "TLS certificate"
309
+
310
+ if [[ "$SKIP_CERTBOT" == "1" ]]; then
311
+ warn "SKIP_CERTBOT=1 — skipping cert. Run manually later:"
312
+ echo " certbot --nginx -d $DOMAIN"
313
+ else
314
+ if certbot --nginx -d "$DOMAIN" --non-interactive --agree-tos \
315
+ --register-unsafely-without-email --redirect 2>&1; then
316
+ info "TLS certificate obtained for $DOMAIN"
317
+ else
318
+ warn "Certbot failed. Common causes:"
319
+ echo " - DNS not yet pointing to this IP (check: dig $DOMAIN)"
320
+ echo " - Port 80 blocked"
321
+ echo " Re-run manually: certbot --nginx -d $DOMAIN"
322
+ fi
323
+ fi
324
+
325
+ # ── 11. Start service ─────────────────────────────────────────────────────────
326
+ section "Starting service"
327
+
328
+ systemctl restart "$SERVICE_NAME"
329
+
330
+ echo -n "Waiting for bridge..."
331
+ for i in $(seq 1 30); do
332
+ if curl -sf --max-time 3 "http://127.0.0.1:${PORT}/health" >/dev/null 2>&1; then
333
+ echo " ready."
334
+ break
335
+ fi
336
+ sleep 1; echo -n "."
337
+ if [[ $i -eq 30 ]]; then
338
+ echo " timed out."
339
+ warn "Check logs: journalctl -u $SERVICE_NAME -n 50"
340
+ fi
341
+ done
342
+
343
+ # ── Summary ───────────────────────────────────────────────────────────────────
344
+ echo ""
345
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
346
+ echo " Claude IDE Bridge — bootstrap complete"
347
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
348
+ echo ""
349
+ echo " MCP endpoint: https://$DOMAIN/mcp"
350
+ echo " Health check: https://$DOMAIN/health"
351
+ echo " Local: http://127.0.0.1:${PORT}/mcp"
352
+ echo " Token: ${FIXED_TOKEN}"
353
+ echo " Install dir: $INSTALL_DIR"
354
+ echo " Service user: $SERVICE_USER"
355
+ echo ""
356
+ echo " Service management:"
357
+ echo " systemctl status $SERVICE_NAME"
358
+ echo " journalctl -u $SERVICE_NAME -f"
359
+ echo " systemctl restart $SERVICE_NAME"
360
+ echo ""
361
+ echo " To update:"
362
+ echo " cd $INSTALL_DIR && git pull && npm ci && npm run build"
363
+ echo " systemctl restart $SERVICE_NAME"
364
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
@@ -0,0 +1,67 @@
1
+ # deploy/claude-ide-bridge.service.template
2
+ # Reference template — paths and user are injected by the install scripts.
3
+ # Do NOT copy this file to /etc/systemd/system/ directly.
4
+ # Use bootstrap-new-vps.sh or install-vps-service.sh which generate
5
+ # the final unit file with all variables substituted.
6
+
7
+ [Unit]
8
+ Description=Claude IDE Bridge MCP Server
9
+ Documentation=https://github.com/Oolab-labs/claude-ide-bridge
10
+ After=network.target
11
+ StartLimitIntervalSec=120
12
+ StartLimitBurst=5
13
+
14
+ [Service]
15
+ Type=simple
16
+ User=${SERVICE_USER}
17
+ WorkingDirectory=${INSTALL_DIR}
18
+
19
+ # Load personal config — FIXED_TOKEN, PORT, WORKSPACE must be set here
20
+ EnvironmentFile=${ENV_FILE}
21
+
22
+ # Bridge process — reads PORT, WORKSPACE, FIXED_TOKEN from EnvironmentFile
23
+ ExecStart=/usr/bin/node ${INSTALL_DIR}/dist/index.js \
24
+ --port ${PORT} \
25
+ --workspace ${WORKSPACE} \
26
+ --fixed-token ${FIXED_TOKEN} \
27
+ --grace-period 120000 \
28
+ --vps
29
+
30
+ # Graceful shutdown: SIGTERM → wait 15s → SIGKILL
31
+ KillMode=mixed
32
+ KillSignal=SIGTERM
33
+ TimeoutStopSec=15
34
+
35
+ # Restart policy: always restart unless manually stopped
36
+ Restart=always
37
+ RestartSec=5
38
+
39
+ # Logging — view with: journalctl -u claude-ide-bridge -f
40
+ StandardOutput=journal
41
+ StandardError=journal
42
+ SyslogIdentifier=claude-ide-bridge
43
+
44
+ # Hardening
45
+ NoNewPrivileges=true
46
+ PrivateTmp=true
47
+ ProtectSystem=strict
48
+ ProtectHome=read-only
49
+ ReadWritePaths=${INSTALL_DIR} /tmp /root/.claude
50
+ CapabilityBoundingSet=
51
+ AmbientCapabilities=
52
+ LockPersonality=true
53
+ RestrictNamespaces=true
54
+ RestrictRealtime=true
55
+ RestrictSUIDSGID=true
56
+ ProtectSystem=strict
57
+ ProtectHome=read-only
58
+ ReadWritePaths=${INSTALL_DIR} /tmp /root/.claude
59
+ CapabilityBoundingSet=
60
+ AmbientCapabilities=
61
+ LockPersonality=true
62
+ RestrictNamespaces=true
63
+ RestrictRealtime=true
64
+ RestrictSUIDSGID=true
65
+
66
+ [Install]
67
+ WantedBy=multi-user.target
@@ -0,0 +1,31 @@
1
+ [Unit]
2
+ Description=Claude IDE Bridge MCP Server (for user %i)
3
+ After=network.target
4
+ StartLimitIntervalSec=60
5
+ StartLimitBurst=5
6
+
7
+ [Service]
8
+ Type=simple
9
+ User=%i
10
+ WorkingDirectory=%h
11
+ ExecStart=/usr/local/bin/node /opt/claude-ide-bridge/dist/index.js --workspace %h --jsonl
12
+ Restart=on-failure
13
+ RestartSec=3
14
+ TimeoutStopSec=10
15
+ KillMode=mixed
16
+ KillSignal=SIGTERM
17
+ StandardOutput=journal
18
+ StandardError=journal
19
+ SyslogIdentifier=claude-ide-bridge
20
+ NoNewPrivileges=true
21
+ ProtectSystem=strict
22
+ ReadWritePaths=%h/.claude %h
23
+ PrivateTmp=true
24
+ ProtectHome=read-only
25
+ CapabilityBoundingSet=
26
+ LockPersonality=true
27
+ RestrictNamespaces=true
28
+ RestrictSUIDSGID=true
29
+
30
+ [Install]
31
+ WantedBy=default.target
@@ -0,0 +1,36 @@
1
+ // PM2 ecosystem config for claude-ide-bridge.
2
+ // Copy to the repo root and edit before use:
3
+ // cp deploy/ecosystem.config.js.example ecosystem.config.js
4
+ //
5
+ // Usage:
6
+ // pm2 start ecosystem.config.js
7
+ // pm2 save
8
+ //
9
+ // IMPORTANT: The --port value below must match the port in your nginx proxy_pass.
10
+ // The bridge defaults to a random port if --port is not set, which breaks nginx.
11
+
12
+ module.exports = {
13
+ apps: [
14
+ {
15
+ name: "claude-bridge",
16
+ script: "dist/index.js",
17
+ cwd: "/root/claude-ide-bridge", // adjust to your INSTALL_DIR
18
+
19
+ // All bridge flags go here — NOT in env (PORT env var is not read by the bridge)
20
+ args: [
21
+ "--port", "4748", // must match nginx proxy_pass port
22
+ "--bind", "0.0.0.0", // expose to all interfaces (nginx handles TLS)
23
+ "--vps", // expands command allowlist for server use
24
+ "--fixed-token", "YOUR_TOKEN_HERE", // stable token; prevents rotation on restart
25
+ ].join(" "),
26
+
27
+ restart_delay: 5000, // wait 5s before restart on crash
28
+ max_restarts: 20, // give up after 20 consecutive crashes
29
+ min_uptime: "10s", // don't count a restart as a crash if up for <10s
30
+
31
+ env: {
32
+ NODE_ENV: "production",
33
+ },
34
+ },
35
+ ],
36
+ };
@@ -0,0 +1,240 @@
1
+ #!/usr/bin/env bash
2
+ # deploy/install-vps-service.sh
3
+ # Idempotent updater — re-installs the systemd service and nginx config
4
+ # on an already-bootstrapped server after a git pull.
5
+ #
6
+ # For FIRST-TIME setup on a new VPS, use bootstrap-new-vps.sh instead.
7
+ #
8
+ # Usage (run as root from repo root):
9
+ # bash deploy/install-vps-service.sh
10
+ #
11
+ # Optional environment overrides:
12
+ # DOMAIN Override domain (default: read from existing nginx config or prompt)
13
+ # PORT Override port (default: read from .env.vps)
14
+ # SERVICE_USER Override user (default: auto-detect from existing service)
15
+
16
+ set -euo pipefail
17
+
18
+ REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
19
+ SERVICE_NAME="claude-ide-bridge"
20
+ ENV_FILE="$REPO_ROOT/.env.vps"
21
+
22
+ RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
23
+ info() { echo -e "${GREEN}✓${NC} $*"; }
24
+ warn() { echo -e "${YELLOW}⚠${NC} $*"; }
25
+ err() { echo -e "${RED}✗${NC} $*" >&2; }
26
+
27
+ echo "=== Claude IDE Bridge — Service Updater ==="
28
+ echo ""
29
+
30
+ # ── Pre-flight ────────────────────────────────────────────────────────────────
31
+ [[ "$(id -u)" -eq 0 ]] || { err "Run as root: sudo bash deploy/install-vps-service.sh"; exit 1; }
32
+ [[ -f "$ENV_FILE" ]] || { err "$ENV_FILE not found. Run bootstrap-new-vps.sh first."; exit 1; }
33
+
34
+ source "$ENV_FILE"
35
+ [[ -n "${FIXED_TOKEN:-}" ]] || { err "FIXED_TOKEN not set in $ENV_FILE"; exit 1; }
36
+ [[ -n "${PORT:-}" ]] || { err "PORT not set in $ENV_FILE"; exit 1; }
37
+ [[ -n "${WORKSPACE:-}" ]] || { err "WORKSPACE not set in $ENV_FILE"; exit 1; }
38
+
39
+ DIST="$REPO_ROOT/dist/index.js"
40
+ [[ -f "$DIST" ]] || { err "$DIST not found. Run: npm run build"; exit 1; }
41
+
42
+ command -v nginx >/dev/null 2>&1 || { err "nginx not found. Run bootstrap-new-vps.sh first."; exit 1; }
43
+ command -v systemctl >/dev/null 2>&1 || { err "systemd not available."; exit 1; }
44
+
45
+ # Detect service user from existing unit file, fall back to env or default
46
+ EXISTING_USER=$(grep -Po '(?<=^User=).+' /etc/systemd/system/${SERVICE_NAME}.service 2>/dev/null || echo "")
47
+ SERVICE_USER="${SERVICE_USER:-${EXISTING_USER:-claude-bridge}}"
48
+
49
+ # Detect domain from existing nginx config
50
+ EXISTING_DOMAIN=$(grep -Po '(?<=server_name )[\w.-]+' /etc/nginx/sites-available/claude-bridge 2>/dev/null | head -1 || echo "")
51
+ DOMAIN="${DOMAIN:-$EXISTING_DOMAIN}"
52
+
53
+ if [[ -z "$DOMAIN" ]]; then
54
+ err "Cannot determine DOMAIN. Set it: DOMAIN=bridge.example.com bash deploy/install-vps-service.sh"
55
+ exit 1
56
+ fi
57
+
58
+ info "Repo: $REPO_ROOT"
59
+ info "Domain: $DOMAIN"
60
+ info "Port: $PORT"
61
+ info "Service user: $SERVICE_USER"
62
+ echo ""
63
+
64
+ # ── Systemd service ───────────────────────────────────────────────────────────
65
+ echo "Updating systemd service..."
66
+
67
+ cat > "/etc/systemd/system/${SERVICE_NAME}.service" <<SERVICE
68
+ [Unit]
69
+ Description=Claude IDE Bridge MCP Server
70
+ Documentation=https://github.com/Oolab-labs/claude-ide-bridge
71
+ After=network.target
72
+ StartLimitIntervalSec=120
73
+ StartLimitBurst=5
74
+
75
+ [Service]
76
+ Type=simple
77
+ User=$SERVICE_USER
78
+ WorkingDirectory=$REPO_ROOT
79
+
80
+ # Load config — FIXED_TOKEN, PORT, WORKSPACE
81
+ EnvironmentFile=$ENV_FILE
82
+
83
+ # Bridge process
84
+ ExecStart=/usr/bin/node $REPO_ROOT/dist/index.js \\
85
+ --port \${PORT} \\
86
+ --workspace \${WORKSPACE} \\
87
+ --fixed-token \${FIXED_TOKEN} \\
88
+ --grace-period 120000 \\
89
+ --vps
90
+
91
+ KillMode=mixed
92
+ KillSignal=SIGTERM
93
+ TimeoutStopSec=15
94
+ Restart=always
95
+ RestartSec=5
96
+
97
+ StandardOutput=journal
98
+ StandardError=journal
99
+ SyslogIdentifier=claude-ide-bridge
100
+
101
+ NoNewPrivileges=true
102
+ PrivateTmp=true
103
+
104
+ [Install]
105
+ WantedBy=multi-user.target
106
+ SERVICE
107
+
108
+ systemctl daemon-reload
109
+ systemctl enable "$SERVICE_NAME"
110
+ info "Systemd service updated"
111
+
112
+ # ── nginx ─────────────────────────────────────────────────────────────────────
113
+ echo "Updating nginx config..."
114
+
115
+ # Add connection_upgrade map if missing
116
+ if ! grep -q "connection_upgrade" /etc/nginx/nginx.conf 2>/dev/null; then
117
+ perl -i -0pe 's/(http \{)/$1\n map \$http_upgrade \$connection_upgrade {\n default upgrade;\n '"''"' close;\n }/' /etc/nginx/nginx.conf
118
+ info "Added connection_upgrade map to nginx.conf"
119
+ fi
120
+
121
+ # Write site config with domain and port injected
122
+ cat > "/etc/nginx/sites-available/claude-bridge" <<NGINX
123
+ # Claude IDE Bridge — nginx reverse proxy
124
+ # Domain: $DOMAIN Port: ${PORT}
125
+ # Updated by install-vps-service.sh on $(date -u +%Y-%m-%dT%H:%M:%SZ)
126
+
127
+ server {
128
+ listen 80;
129
+ listen [::]:80;
130
+ server_name $DOMAIN;
131
+
132
+ location /.well-known/acme-challenge/ {
133
+ root /var/www/html;
134
+ }
135
+
136
+ location / {
137
+ return 301 https://\$host\$request_uri;
138
+ }
139
+ }
140
+
141
+ server {
142
+ listen 443 ssl http2;
143
+ listen [::]:443 ssl http2;
144
+ server_name $DOMAIN;
145
+
146
+ # TLS — managed by Certbot
147
+ # ssl_certificate /etc/letsencrypt/live/$DOMAIN/fullchain.pem;
148
+ # ssl_certificate_key /etc/letsencrypt/live/$DOMAIN/privkey.pem;
149
+ # include /etc/letsencrypt/options-ssl-nginx.conf;
150
+ # ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
151
+
152
+ add_header X-Content-Type-Options nosniff always;
153
+ add_header X-Frame-Options DENY always;
154
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
155
+
156
+ location /mcp {
157
+ proxy_pass http://127.0.0.1:${PORT};
158
+ proxy_http_version 1.1;
159
+ proxy_buffering off;
160
+ proxy_cache off;
161
+ proxy_read_timeout 86400s;
162
+ proxy_send_timeout 86400s;
163
+ proxy_connect_timeout 10s;
164
+ proxy_set_header Authorization \$http_authorization;
165
+ proxy_pass_header Authorization;
166
+ proxy_set_header Mcp-Session-Id \$http_mcp_session_id;
167
+ proxy_pass_header Mcp-Session-Id;
168
+ proxy_set_header Host \$host;
169
+ proxy_set_header X-Real-IP \$remote_addr;
170
+ proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
171
+ proxy_set_header X-Forwarded-Proto \$scheme;
172
+ proxy_set_header Upgrade \$http_upgrade;
173
+ proxy_set_header Connection \$connection_upgrade;
174
+ }
175
+
176
+ location /health {
177
+ proxy_pass http://127.0.0.1:${PORT};
178
+ proxy_http_version 1.1;
179
+ proxy_connect_timeout 5s;
180
+ proxy_read_timeout 10s;
181
+ }
182
+
183
+ location /.well-known/ {
184
+ proxy_pass http://127.0.0.1:${PORT};
185
+ proxy_http_version 1.1;
186
+ proxy_connect_timeout 5s;
187
+ proxy_read_timeout 10s;
188
+ }
189
+
190
+ location / {
191
+ return 404;
192
+ }
193
+ }
194
+ NGINX
195
+
196
+ [[ -L "/etc/nginx/sites-enabled/claude-bridge" ]] || \
197
+ ln -s /etc/nginx/sites-available/claude-bridge /etc/nginx/sites-enabled/claude-bridge
198
+
199
+ [[ -L "/etc/nginx/sites-enabled/default" ]] && \
200
+ rm -f /etc/nginx/sites-enabled/default
201
+
202
+ nginx -t
203
+ systemctl reload nginx
204
+ info "nginx config updated"
205
+
206
+ # ── Stop tmux session if still running (superseded by systemd) ────────────────
207
+ if command -v tmux >/dev/null 2>&1 && tmux has-session -t "bridge" 2>/dev/null; then
208
+ tmux kill-session -t "bridge" 2>/dev/null || true
209
+ info "Stopped legacy tmux bridge session"
210
+ fi
211
+
212
+ # ── Restart service ───────────────────────────────────────────────────────────
213
+ echo "Restarting service..."
214
+ systemctl restart "$SERVICE_NAME"
215
+
216
+ echo -n "Waiting for bridge..."
217
+ for i in $(seq 1 20); do
218
+ if curl -sf --max-time 3 "http://127.0.0.1:${PORT}/health" >/dev/null 2>&1; then
219
+ echo " ready."
220
+ break
221
+ fi
222
+ sleep 1; echo -n "."
223
+ if [[ $i -eq 20 ]]; then
224
+ echo " timed out."
225
+ warn "Check: journalctl -u $SERVICE_NAME -n 50"
226
+ fi
227
+ done
228
+
229
+ # ── Summary ───────────────────────────────────────────────────────────────────
230
+ echo ""
231
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
232
+ echo " Claude IDE Bridge — service updated"
233
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
234
+ echo ""
235
+ echo " MCP endpoint: https://$DOMAIN/mcp"
236
+ echo " Local: http://127.0.0.1:${PORT}/mcp"
237
+ echo " Token: ${FIXED_TOKEN}"
238
+ echo ""
239
+ echo " journalctl -u $SERVICE_NAME -f"
240
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
@@ -0,0 +1,129 @@
1
+ # deploy/nginx-claude-bridge.conf.template
2
+ # Reference template — domain and port are injected by the install scripts.
3
+ # Do NOT use this file directly. Use bootstrap-new-vps.sh or install-vps-service.sh
4
+ # which write the final nginx config to /etc/nginx/sites-available/claude-bridge
5
+ # with $DOMAIN and $PORT substituted.
6
+ #
7
+ # To manually generate from this template:
8
+ # DOMAIN=bridge.example.com PORT=9000 \
9
+ # envsubst '$DOMAIN $PORT' < deploy/nginx-claude-bridge.conf.template \
10
+ # > /etc/nginx/sites-available/claude-bridge
11
+
12
+ server {
13
+ listen 80;
14
+ listen [::]:80;
15
+ server_name ${DOMAIN};
16
+
17
+ # Let Certbot complete ACME HTTP-01 challenges
18
+ location /.well-known/acme-challenge/ {
19
+ root /var/www/html;
20
+ }
21
+
22
+ # Redirect all other HTTP to HTTPS
23
+ location / {
24
+ return 301 https://$host$request_uri;
25
+ }
26
+ }
27
+
28
+ server {
29
+ listen 443 ssl http2;
30
+ listen [::]:443 ssl http2;
31
+ server_name ${DOMAIN};
32
+
33
+ # TLS — Certbot populates these after: certbot --nginx -d ${DOMAIN}
34
+ # ssl_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem;
35
+ # ssl_certificate_key /etc/letsencrypt/live/${DOMAIN}/privkey.pem;
36
+ # include /etc/letsencrypt/options-ssl-nginx.conf;
37
+ # ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
38
+
39
+ # Security headers
40
+ add_header X-Content-Type-Options nosniff always;
41
+ add_header X-Frame-Options DENY always;
42
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
43
+ add_header Content-Security-Policy "default-src 'none'" always;
44
+
45
+ # /mcp — Streamable HTTP MCP endpoint (SSE-safe, never buffer)
46
+ location /mcp {
47
+ # Rate limit: 30 req/min per IP, burst of 10 (requires limit_req_zone in http{} block)
48
+ limit_req zone=mcp_zone burst=10 nodelay;
49
+ limit_req_status 429;
50
+
51
+ proxy_pass http://127.0.0.1:${PORT};
52
+ proxy_http_version 1.1;
53
+
54
+ # SSE requires no buffering
55
+ proxy_buffering off;
56
+ proxy_cache off;
57
+ proxy_read_timeout 86400s;
58
+ proxy_send_timeout 86400s;
59
+ proxy_connect_timeout 10s;
60
+
61
+ # Forward auth and MCP session headers
62
+ proxy_set_header Authorization $http_authorization;
63
+ proxy_pass_header Authorization;
64
+ proxy_set_header Mcp-Session-Id $http_mcp_session_id;
65
+ proxy_pass_header Mcp-Session-Id;
66
+
67
+ # Standard proxy headers
68
+ proxy_set_header Host $host;
69
+ proxy_set_header X-Real-IP $remote_addr;
70
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
71
+ proxy_set_header X-Forwarded-Proto $scheme;
72
+
73
+ # WebSocket upgrade passthrough
74
+ proxy_set_header Upgrade $http_upgrade;
75
+ proxy_set_header Connection $connection_upgrade;
76
+ }
77
+
78
+ # /health — unauthenticated health check
79
+ location /health {
80
+ proxy_pass http://127.0.0.1:${PORT};
81
+ proxy_http_version 1.1;
82
+ proxy_connect_timeout 5s;
83
+ proxy_read_timeout 10s;
84
+ }
85
+
86
+ # /.well-known — MCP server card + OAuth discovery (RFC 8414, RFC 9396)
87
+ location /.well-known/ {
88
+ proxy_pass http://127.0.0.1:${PORT};
89
+ proxy_http_version 1.1;
90
+ proxy_connect_timeout 5s;
91
+ proxy_read_timeout 10s;
92
+ }
93
+
94
+ # /oauth — OAuth 2.0 endpoints (authorize, token, register, revoke)
95
+ location /oauth/ {
96
+ proxy_pass http://127.0.0.1:${PORT};
97
+ proxy_http_version 1.1;
98
+ proxy_connect_timeout 5s;
99
+ proxy_read_timeout 30s;
100
+ proxy_set_header Host $host;
101
+ proxy_set_header X-Real-IP $remote_addr;
102
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
103
+ proxy_set_header X-Forwarded-Proto $scheme;
104
+ }
105
+
106
+ # /ping — unauthenticated health probe
107
+ location /ping {
108
+ proxy_pass http://127.0.0.1:${PORT};
109
+ proxy_http_version 1.1;
110
+ proxy_connect_timeout 5s;
111
+ proxy_read_timeout 10s;
112
+ }
113
+
114
+ # Block everything else
115
+ location / {
116
+ return 404;
117
+ }
118
+ }
119
+
120
+ # Note: add this to the `http {}` block in /etc/nginx/nginx.conf if not present
121
+ # (bootstrap-new-vps.sh and install-vps-service.sh do this automatically):
122
+ #
123
+ # map $http_upgrade $connection_upgrade {
124
+ # default upgrade;
125
+ # '' close;
126
+ # }
127
+ #
128
+ # Rate limiting zone — add to http {} block:
129
+ # limit_req_zone $binary_remote_addr zone=mcp_zone:10m rate=30r/m;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-ide-bridge",
3
- "version": "2.22.7",
3
+ "version": "2.22.8",
4
4
  "description": "Standalone MCP bridge for Claude Code IDE integration with any editor — 136+ tools for LSP, debugging, terminals, Git, GitHub, and more",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -54,7 +54,9 @@
54
54
  "scripts/mcp-stdio-shim.cjs",
55
55
  "scripts/postinstall.mjs",
56
56
  "scripts/start-vps.sh",
57
+ "scripts/gen-claude-desktop-config.sh",
57
58
  "templates",
59
+ "deploy",
58
60
  "LICENSE",
59
61
  "README.md"
60
62
  ],
@@ -62,7 +64,7 @@
62
64
  "node": ">=20.0.0"
63
65
  },
64
66
  "scripts": {
65
- "build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc && node scripts/postinstall.mjs",
67
+ "build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc && (node scripts/postinstall.mjs || true)",
66
68
  "dev": "tsx src/index.ts",
67
69
  "start": "node dist/index.js",
68
70
  "test": "vitest run",
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env bash
2
+ # Generate or update claude_desktop_config.json so Claude Desktop connects
3
+ # to the running claude-ide-bridge via the stdio shim.
4
+ #
5
+ # Run with `bash`, do not source this script.
6
+ #
7
+ # Usage:
8
+ # bash scripts/gen-claude-desktop-config.sh # Print config
9
+ # bash scripts/gen-claude-desktop-config.sh --write # Write to config file
10
+ #
11
+ # The shim auto-discovers the running bridge via lock files in ~/.claude/ide/.
12
+ # No port or token needs to be hard-coded -- just start the bridge first.
13
+
14
+ set -euo pipefail
15
+
16
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
17
+ SHIM_PATH="${SCRIPT_DIR}/mcp-stdio-shim.cjs"
18
+
19
+ # --- Detect config path ---
20
+ if [[ "$(uname)" == "Darwin" ]]; then
21
+ CONFIG_PATH="${HOME}/Library/Application Support/Claude/claude_desktop_config.json"
22
+ else
23
+ CONFIG_PATH="${APPDATA:-${HOME}/.config}/Claude/claude_desktop_config.json"
24
+ fi
25
+
26
+ WRITE=false
27
+ for arg in "$@"; do
28
+ case "$arg" in
29
+ --write) WRITE=true ;;
30
+ --help|-h)
31
+ echo "Usage: $0 [--write]"
32
+ echo ""
33
+ echo "Generates a claude_desktop_config.json entry so Claude Desktop"
34
+ echo "connects to the running claude-ide-bridge via the stdio shim."
35
+ echo ""
36
+ echo " --write Merge into ${CONFIG_PATH}"
37
+ echo " (backs up existing config first)"
38
+ exit 0
39
+ ;;
40
+ esac
41
+ done
42
+
43
+ # --- Verify shim exists ---
44
+ if [[ ! -f "$SHIM_PATH" ]]; then
45
+ echo "Error: stdio shim not found at $SHIM_PATH" >&2
46
+ echo "Make sure you're running this from the bridge repo." >&2
47
+ exit 1
48
+ fi
49
+
50
+ # --- Verify bridge is running (optional, warn only) ---
51
+ CLAUDE_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
52
+ LOCK_DIR="$CLAUDE_DIR/ide"
53
+ LOCK_COUNT=$(find "$LOCK_DIR" -maxdepth 1 -name "*.lock" 2>/dev/null | wc -l | tr -d ' ')
54
+ if [[ "$LOCK_COUNT" -eq 0 ]]; then
55
+ echo "Warning: No bridge lock files found. Start the bridge first (npm run start-all)." >&2
56
+ echo "The config will still be generated -- the shim will connect when the bridge starts." >&2
57
+ echo ""
58
+ fi
59
+
60
+ # --- Build or merge config ---
61
+ # Pass SHIM_PATH via env var to avoid path injection in python -c string
62
+ if [[ -f "$CONFIG_PATH" ]]; then
63
+ MERGED=$(SHIM_PATH="$SHIM_PATH" CONFIG_PATH="$CONFIG_PATH" python3 - <<'PYEOF'
64
+ import json, os, sys
65
+ shim = os.environ["SHIM_PATH"]
66
+ config_path = os.environ["CONFIG_PATH"]
67
+ try:
68
+ with open(config_path, "r") as f:
69
+ config = json.load(f)
70
+ except (json.JSONDecodeError, FileNotFoundError):
71
+ config = {}
72
+ if "mcpServers" not in config:
73
+ config["mcpServers"] = {}
74
+ config["mcpServers"]["claude-ide-bridge"] = {
75
+ "command": "node",
76
+ "args": [shim]
77
+ }
78
+ print(json.dumps(config, indent=2))
79
+ PYEOF
80
+ )
81
+ else
82
+ MERGED=$(SHIM_PATH="$SHIM_PATH" python3 - <<'PYEOF'
83
+ import json, os
84
+ shim = os.environ["SHIM_PATH"]
85
+ config = {
86
+ "mcpServers": {
87
+ "claude-ide-bridge": {
88
+ "command": "node",
89
+ "args": [shim]
90
+ }
91
+ }
92
+ }
93
+ print(json.dumps(config, indent=2))
94
+ PYEOF
95
+ )
96
+ fi
97
+
98
+ echo "=== Claude Desktop MCP Config ==="
99
+ echo "Config path: $CONFIG_PATH"
100
+ echo ""
101
+ echo "$MERGED"
102
+
103
+ if $WRITE; then
104
+ echo ""
105
+ # Atomic write: write to .tmp then mv to prevent partial writes
106
+ TMP_PATH="${CONFIG_PATH}.tmp"
107
+ mkdir -p "$(dirname "$CONFIG_PATH")"
108
+ # Back up existing config with timestamp to avoid clobbering previous backups
109
+ if [[ -f "$CONFIG_PATH" ]]; then
110
+ BACKUP="${CONFIG_PATH}.$(date +%Y%m%d%H%M%S).bak"
111
+ cp "$CONFIG_PATH" "$BACKUP"
112
+ echo "Backed up existing config to $(basename "$BACKUP")"
113
+ fi
114
+ printf '%s\n' "$MERGED" > "$TMP_PATH"
115
+ mv "$TMP_PATH" "$CONFIG_PATH"
116
+ echo "Written: $CONFIG_PATH"
117
+ echo ""
118
+ echo "Restart Claude Desktop to pick up the new config."
119
+ echo "Then ask Claude: \"What files are open in my IDE?\""
120
+ else
121
+ echo ""
122
+ echo "Run with --write to save this config, or copy it manually to:"
123
+ echo " $CONFIG_PATH"
124
+ fi