Skip to content

Commit 4b201e1

Browse files
bhaumanclaude
andauthored
Setup environment for Claude Code Web explore (#133)
* Add Claude Code environment setup for authenticated proxy This commit adds support for running Clojure in Claude Code's authenticated proxy environment by implementing a local proxy wrapper solution. Files added: - proxy-wrapper.py: Local HTTP/HTTPS proxy that adds authentication headers to requests before forwarding to Claude Code's authenticated proxy - setup-claude-code-env.sh: Automated setup script that configures Maven, Gradle, and Java system properties to use the local proxy - CLAUDE_CODE_SETUP.md: Documentation explaining the setup process and troubleshooting steps The solution addresses Java's limitation where it cannot send authentication headers during HTTPS CONNECT requests, which prevents Clojure CLI from accessing Maven repositories through Claude Code's proxy. Based on: https://github.com/michaelwhitford/claude-code-explore * Reorganize Claude Code setup files - Move proxy-wrapper.py and setup-claude-code-env.sh to claude-code-setup/ folder - Rename CLAUDE_CODE_SETUP.md to CLAUDE_CODE_WEB_SETUP.md - Update all documentation references to reflect new file locations --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 78cf3ca commit 4b201e1

File tree

3 files changed

+479
-0
lines changed

3 files changed

+479
-0
lines changed

CLAUDE_CODE_WEB_SETUP.md

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# Claude Code Environment Setup Guide
2+
3+
This guide explains how to set up the Clojure MCP project in Claude Code's authenticated proxy environment.
4+
5+
## The Problem
6+
7+
Claude Code uses an authenticated proxy for all network requests. However, Java applications (including Clojure CLI) cannot send authentication headers during HTTPS CONNECT requests, which prevents them from accessing Maven repositories through the proxy.
8+
9+
## The Solution
10+
11+
We use a local proxy wrapper that:
12+
1. Listens on localhost (port 8888 by default)
13+
2. Accepts requests from Java/Clojure
14+
3. Adds authentication headers
15+
4. Forwards requests to Claude Code's authenticated proxy
16+
17+
## Quick Setup
18+
19+
1. **Run the setup script:**
20+
```bash
21+
source claude-code-setup/setup-claude-code-env.sh
22+
```
23+
24+
This will:
25+
- Start the proxy wrapper on port 8888
26+
- Configure Maven settings (`~/.m2/settings.xml`)
27+
- Configure Gradle settings (`~/.gradle/gradle.properties`)
28+
- Export `JAVA_TOOL_OPTIONS` with proxy configuration
29+
30+
2. **Verify the setup:**
31+
```bash
32+
clojure -M -e '(println "Success!")'
33+
```
34+
35+
3. **Run the MCP server:**
36+
```bash
37+
clojure -X:mcp
38+
```
39+
40+
## Files
41+
42+
- **`claude-code-setup/proxy-wrapper.py`** - Python script that acts as a local proxy wrapper
43+
- **`claude-code-setup/setup-claude-code-env.sh`** - Setup script to configure the environment
44+
- **`CLAUDE_CODE_WEB_SETUP.md`** - This documentation file
45+
46+
## Manual Setup (if needed)
47+
48+
If you need to manually configure the environment:
49+
50+
### 1. Start the Proxy Wrapper
51+
52+
```bash
53+
python3 claude-code-setup/proxy-wrapper.py 8888 > /tmp/proxy.log 2>&1 &
54+
```
55+
56+
### 2. Configure Maven
57+
58+
Create `~/.m2/settings.xml`:
59+
60+
```xml
61+
<?xml version="1.0" encoding="UTF-8"?>
62+
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
63+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
64+
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
65+
http://maven.apache.org/xsd/settings-1.0.0.xsd">
66+
<proxies>
67+
<proxy>
68+
<id>local-proxy-http</id>
69+
<active>true</active>
70+
<protocol>http</protocol>
71+
<host>127.0.0.1</host>
72+
<port>8888</port>
73+
<nonProxyHosts>localhost|127.0.0.1</nonProxyHosts>
74+
</proxy>
75+
<proxy>
76+
<id>local-proxy-https</id>
77+
<active>true</active>
78+
<protocol>https</protocol>
79+
<host>127.0.0.1</host>
80+
<port>8888</port>
81+
<nonProxyHosts>localhost|127.0.0.1</nonProxyHosts>
82+
</proxy>
83+
</proxies>
84+
</settings>
85+
```
86+
87+
### 3. Set Java System Properties
88+
89+
```bash
90+
export JAVA_TOOL_OPTIONS="-Dhttp.proxyHost=127.0.0.1 -Dhttp.proxyPort=8888 -Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=8888 -Dmaven.artifact.threads=2"
91+
```
92+
93+
Note: We limit `maven.artifact.threads` to 2 to avoid overwhelming the proxy with too many parallel connections.
94+
95+
## Troubleshooting
96+
97+
### Check if proxy wrapper is running
98+
99+
```bash
100+
pgrep -f proxy-wrapper.py
101+
```
102+
103+
### Check proxy logs
104+
105+
```bash
106+
tail -f /tmp/proxy.log
107+
```
108+
109+
### Test network connectivity
110+
111+
```bash
112+
# Test direct access (should work via Claude Code proxy)
113+
curl -I https://repo1.maven.org/maven2/
114+
115+
# Test through local proxy
116+
curl -x http://127.0.0.1:8888 -I https://repo1.maven.org/maven2/
117+
```
118+
119+
### Restart the proxy wrapper
120+
121+
```bash
122+
pkill -f proxy-wrapper.py
123+
python3 claude-code-setup/proxy-wrapper.py 8888 > /tmp/proxy.log 2>&1 &
124+
```
125+
126+
### Use a different port
127+
128+
```bash
129+
PROXY_PORT=9999 source claude-code-setup/setup-claude-code-env.sh
130+
```
131+
132+
## Credits
133+
134+
This setup is based on the approach documented in:
135+
https://github.com/michaelwhitford/claude-code-explore
136+
137+
The proxy wrapper solution addresses Java's limitation with authenticated proxies in the Claude Code runtime environment.

claude-code-setup/proxy-wrapper.py

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
#!/usr/bin/env python3
2+
"""
3+
HTTP/HTTPS Proxy Wrapper with Authentication
4+
5+
This script creates a local HTTP proxy that handles proxy authentication
6+
and forwards requests to an upstream proxy that requires JWT authentication.
7+
8+
Usage:
9+
python3 proxy-wrapper.py [local_port]
10+
11+
The upstream proxy configuration is read from the http_proxy environment variable.
12+
"""
13+
14+
import socket
15+
import threading
16+
import select
17+
import base64
18+
import os
19+
import sys
20+
import re
21+
from urllib.parse import urlparse
22+
23+
class ProxyHandler:
24+
def __init__(self, upstream_proxy_url):
25+
self.upstream_proxy_url = upstream_proxy_url
26+
self.parse_upstream_proxy()
27+
28+
def parse_upstream_proxy(self):
29+
"""Parse the upstream proxy URL to extract host, port, and credentials."""
30+
# Format: http://username:password@host:port
31+
parsed = urlparse(self.upstream_proxy_url)
32+
self.upstream_host = parsed.hostname
33+
self.upstream_port = parsed.port or 80
34+
35+
# Extract credentials from userinfo
36+
if parsed.username and parsed.password:
37+
auth_string = f"{parsed.username}:{parsed.password}"
38+
self.auth_header = "Basic " + base64.b64encode(auth_string.encode()).decode()
39+
else:
40+
self.auth_header = None
41+
42+
print(f"[CONFIG] Upstream proxy: {self.upstream_host}:{self.upstream_port}")
43+
print(f"[CONFIG] Authentication: {'configured' if self.auth_header else 'not configured'}")
44+
45+
def handle_client(self, client_socket, address):
46+
"""Handle a client connection."""
47+
try:
48+
# Set timeout to prevent hanging on slow clients
49+
client_socket.settimeout(30)
50+
request = client_socket.recv(4096).decode('utf-8', errors='ignore')
51+
if not request:
52+
client_socket.close()
53+
return
54+
55+
# Parse the request line
56+
first_line = request.split('\n')[0]
57+
print(f"[REQUEST] {address[0]}:{address[1]} -> {first_line.strip()}")
58+
59+
# Check if this is a CONNECT request (for HTTPS)
60+
if first_line.startswith('CONNECT'):
61+
self.handle_connect(client_socket, request, first_line)
62+
else:
63+
# For regular HTTP requests
64+
self.handle_http(client_socket, request)
65+
66+
except Exception as e:
67+
print(f"[ERROR] {e}")
68+
client_socket.close()
69+
70+
def handle_connect(self, client_socket, request, first_line):
71+
"""Handle CONNECT requests for HTTPS tunneling."""
72+
# Extract target host:port from CONNECT line
73+
parts = first_line.split()
74+
if len(parts) < 2:
75+
client_socket.close()
76+
return
77+
78+
target = parts[1]
79+
80+
# Connect to upstream proxy
81+
upstream_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
82+
upstream_socket.connect((self.upstream_host, self.upstream_port))
83+
84+
# Forward CONNECT request to upstream proxy with authentication
85+
connect_request = first_line + "\r\n"
86+
87+
# Add Proxy-Authorization header if we have credentials
88+
if self.auth_header:
89+
connect_request += f"Proxy-Authorization: {self.auth_header}\r\n"
90+
91+
connect_request += "\r\n"
92+
93+
upstream_socket.sendall(connect_request.encode())
94+
95+
# Get response from upstream proxy
96+
response = upstream_socket.recv(4096)
97+
response_str = response.decode('utf-8', errors='ignore')
98+
99+
# Check if connection was successful
100+
if "200" in response_str.split('\n')[0]:
101+
# Send success response to client
102+
client_socket.sendall(b"HTTP/1.1 200 Connection Established\r\n\r\n")
103+
104+
# Now relay data between client and upstream proxy
105+
self.relay_data(client_socket, upstream_socket)
106+
else:
107+
# Forward the error response to client
108+
print(f"[ERROR] Upstream proxy responded: {response_str.split(chr(10))[0]}")
109+
client_socket.sendall(response)
110+
client_socket.close()
111+
upstream_socket.close()
112+
113+
def handle_http(self, client_socket, request):
114+
"""Handle regular HTTP requests."""
115+
# Connect to upstream proxy
116+
upstream_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
117+
upstream_socket.connect((self.upstream_host, self.upstream_port))
118+
119+
# Add Proxy-Authorization header if not present and we have credentials
120+
if self.auth_header and "Proxy-Authorization:" not in request:
121+
# Insert the header after the first line
122+
lines = request.split('\r\n')
123+
lines.insert(1, f"Proxy-Authorization: {self.auth_header}")
124+
request = '\r\n'.join(lines)
125+
126+
# Forward request to upstream proxy
127+
upstream_socket.sendall(request.encode())
128+
129+
# Relay response back to client
130+
self.relay_data(client_socket, upstream_socket)
131+
132+
def relay_data(self, client_socket, upstream_socket):
133+
"""Relay data bidirectionally between client and upstream proxy."""
134+
sockets = [client_socket, upstream_socket]
135+
136+
while True:
137+
try:
138+
readable, _, exceptional = select.select(sockets, [], sockets, 60)
139+
140+
if exceptional:
141+
break
142+
143+
for sock in readable:
144+
data = sock.recv(8192)
145+
if not data:
146+
client_socket.close()
147+
upstream_socket.close()
148+
return
149+
150+
# Send to the other socket
151+
if sock is client_socket:
152+
upstream_socket.sendall(data)
153+
else:
154+
client_socket.sendall(data)
155+
156+
except Exception as e:
157+
break
158+
159+
try:
160+
client_socket.close()
161+
upstream_socket.close()
162+
except:
163+
pass
164+
165+
166+
def start_proxy(local_port, upstream_proxy_url):
167+
"""Start the local proxy server."""
168+
handler = ProxyHandler(upstream_proxy_url)
169+
170+
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
171+
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
172+
server_socket.bind(('127.0.0.1', local_port))
173+
server_socket.listen(100)
174+
175+
print(f"[INFO] Proxy server listening on 127.0.0.1:{local_port}")
176+
print(f"[INFO] Forwarding to: {handler.upstream_host}:{handler.upstream_port}")
177+
print(f"[INFO] Configure your application to use: http://127.0.0.1:{local_port}")
178+
print()
179+
180+
try:
181+
while True:
182+
client_socket, address = server_socket.accept()
183+
thread = threading.Thread(target=handler.handle_client, args=(client_socket, address))
184+
thread.daemon = True
185+
thread.start()
186+
except KeyboardInterrupt:
187+
print("\n[INFO] Shutting down proxy server...")
188+
server_socket.close()
189+
190+
191+
if __name__ == "__main__":
192+
# Get upstream proxy from environment
193+
upstream_proxy_url = os.environ.get('http_proxy') or os.environ.get('HTTP_PROXY')
194+
195+
if not upstream_proxy_url:
196+
print("ERROR: No http_proxy environment variable found!")
197+
sys.exit(1)
198+
199+
# Get local port from command line or use default
200+
local_port = int(sys.argv[1]) if len(sys.argv) > 1 else 8888
201+
202+
print("=" * 60)
203+
print("HTTP/HTTPS Proxy Wrapper with Authentication")
204+
print("=" * 60)
205+
print()
206+
207+
start_proxy(local_port, upstream_proxy_url)

0 commit comments

Comments
 (0)