A question to a language model sits on the screen. The cursor blinks in a
<TEXTAREA>rendered by Netscape Navigator 3, under System 7.6.1, on a machine whose architecture is older than the phrase “search engine”. Between clicking Send to Claude and the answer lie thirty years of web history — and somebody has to bridge them.
ClaudeBridge 2.0 is that somebody: an HTTP server running on the host that makes Claude reachable from a browser built in 1996. No porting effort, no emulation trick. The server simply speaks the language of the older shore.
Source: github.com/LoetLuemmel/ClaudeBridge — MIT licensed, Python 3.8+.
Deutsche Fassung: ClaudeBridge 2.0 — Claude auf einem Macintosh von 1996

The target system dictates everything
Most design decisions in this project are not decisions. They are consequences.
Netscape Navigator 3.04 on MacOS 7.5/7.6.1 understands HTML 3.2. No CSS, no JavaScript, no class=, no modern form elements. Whatever the server delivers has to look like 1996 — not out of nostalgia, but because anything else simply is not displayed.
The entire interface follows from that:
| Modern web | ClaudeBridge |
|---|---|
| CSS layout | <TABLE> with BGCOLOR |
| AJAX / fetch | <META HTTP-EQUIV="Refresh"> |
| UTF-8 | ISO-8859-1 |
| WebSockets | polling every three seconds |
| SPA routing | one endpoint per page |
The encoding is the delicate part. Netscape 3 on a Mac is picky: the Script Manager turns the delivered bytes into MacRoman glyphs, and sending UTF-8 gets you question marks instead of umlauts. ISO-8859-1 is the compromise in which ä, ö, ü and ß all exist.
Architecture
flowchart LR
A["Netscape 3
System 7.6.1"] -->|"HTTP 1.0"| B["ClaudeBridge
:8080"]
G["THINK C client
(native)"] -->|"JSON"| B
B --> C["Claude interface"]
B --> D["web proxy"]
C -->|"background thread"| E["Claude API"]
D -->|"HTTPS"| F["modern web"]
The server routes by path: /web, /proxy and /proxyimg go to the proxy, everything else to the Claude interface. The two halves deliberately share no state — separate caches, separate job queues, separate rate limiters.
Why threads are unavoidable
Netscape 3 has very short timeouts. A synchronous API call that thinks for twenty seconds drops the connection before the first byte flows.
The solution is a pattern you would have built the same way in 1996: the request creates a job, starts a background thread and returns a waiting page immediately. That page carries a META REFRESH pointing at /result/<id>. Every three seconds the browser asks again, until the job is done.
That is the only form of “asynchronous” Netscape 3 knows. Push the refresh interval below two seconds and the browser cannot keep up.
The seven tools
The home page is a table with seven entries — each its own endpoint:
[*] Claude Chat — open conversation, with context across several rounds.
[C] Code Assistant — specialised in THINK C 7.0. That is not a cosmetic distinction: THINK C has no full ANSI C89, no // comments, no inline functions, no variable-length arrays, and identifiers stop at 31 characters. What it does need is Pascal strings ("\pHello"), handle-based memory management and Toolbox calls. A model proposing modern C is worthless here — the system prompt keeps it on the platform. How well that holds can be measured: Five Bugs in THINK C is a test case whose two hardest bugs are formally correct C and can only be found with knowledge of how Classic Mac OS manages memory.
[R] Resource Generator — produces Rez source for MENU, DLOG, DITL, WIND, ICON.
[?] Ask & Answer — general Toolbox questions.
[W] Web Proxy — the most interesting part, more on it below.
[F] Shared Folder — reads files from the shared folder and sends them to Claude. Classic Mac files are interpreted as MacRoman first, with UTF-8 as the fallback.
[V] History — recent questions and answers, persistent across restarts.

The history is more than a convenience. It survives server restarts, and because there is no second window sitting beside you on this machine, it is often the only way back to yesterday’s answer.


A full four-round transcript — including the bugs it exposed — is recorded separately in A Conversation with Claude on a 1996 Macintosh.
The web proxy: HTTPS for a browser without usable TLS
Netscape 3 speaks SSL 2.0 and 3.0 with 40-bit export ciphers. The modern web accepts none of that — and conversely the browser knows nothing of TLS 1.2. For it, the HTTPS web is simply unreachable.
The proxy solves this by acting as intermediary: it fetches the page over HTTPS, simplifies it down to HTML 3.2 and serves it over plain HTTP.
The simplification is thorough — <script> and <style> go, <div> becomes <p>, <span> is unwrapped, navigation and footers are stripped. Images run through a second endpoint that scales them to 500 pixels wide, reduces them to an adaptive palette of 24–64 colours and delivers them as GIF. SVG gets a transparent 1×1 pixel placeholder, because Netscape 3 can do nothing with it.

How well that holds up shows in the result. The Wikipedia article on the Macintosh, rendered in Netscape 3: the 1984 press photograph has been reduced from JPEG to an adaptive GIF palette on the way. The visible dithering is not a failure but the method — this is how photographs looked on these machines.

The second client that does not exist
The server can serve two clients. It tells them apart by User-Agent: if it contains ClaudeAssistant, JSON comes back, otherwise HTML. Switching clients is a header, not a second server.
Except the second client does not exist.
ClassicClient/ in the repository holds a THINK C application — event loop, MacTCP networking, preferences, menus, Rez resources, about 2,900 lines. It has never been compiled, and as written it does not work. Three defects, all in network.c:
URLEncode() | percent-encodes raw MacRoman bytes; the server decodes ISO-8859-1, so ü (0x9F) arrives as something else entirely |
| chat request | sends the field prompt, while /chat expects message |
URLEncode() | writes into a fixed buffer with no length check — on a 68k without memory protection that takes the whole machine down |
The server side, by contrast, is finished and verified. The JSON branch answers correctly, umlauts included, and can be exercised without any vintage hardware:
curl -H "User-Agent: ClaudeAssistant/1.0" \
-X POST --data 'message=Hallo' http://127.0.0.1:8080/chat
# {"job_id": "1"}
It is left in that state deliberately. Anyone with THINK C 7.0 and an afternoon has a well-defined piece of work with a known finish line here. Everything else works through the browser alone in the meantime — and for simply working with Claude, the native client is not needed at all.
Where thirty years diverge
Three findings from recent work show how narrow the ridge is.
A hexadecimal character reference
What stood on the screen:
Good morning! I'm doing wonderfully
The cause was Python’s html.escape(). The function defaults to quote=True and emits hexadecimal character references — ' becomes '. That notation arrived with HTML 4.0. Netscape 3 knows only decimal references and renders the rest literally.
The replacement escapes only &, < and >, plus " in attribute mode. Apostrophes are deliberately left alone: every attribute in the project is delimited with double quotes, where ' is harmless.
A single standard-library function, correct by today’s standard — and still wrong for this target.
Umlauts turned into question marks
The reverse direction had the same character. Typing äöüÄÖÜß into a form produced a row of question marks in the chat.
A browser submits a form in the character set of the document — and this project serves every page as ISO-8859-1. So ü arrives as %FC. Python’s parse_qs, however, decodes percent-sequences as UTF-8 by default, and there 0xFC is an invalid byte. It becomes U+FFFD, and the sanitize stage turns that into the question mark.
The trap: one line above there already was a decode("iso-8859-1"). But that only covers the raw request body, not the percent-sequences. Search only there and you never find the bug. The correction is a single argument — parse_qs(body, encoding="iso-8859-1").
The tempting wrong turn is called MacRoman. The Mac displays text in MacRoman through the Script Manager, so it seems to follow that it sends it that way too. It does not — and 0xFC there is a cedilla. The symptom then stops being question marks and becomes Gr¸sse aus M¸nchen: close enough to look like partial success, and doubly misleading for it. That detour was taken here before being corrected. The full account, with the measurement that settled it, is in Five Question Marks.
An open relay
The proxy accepted any URL starting with http:// or https://. Nothing else was checked. That made it an open relay: any device on the network could have the server fetch http://127.0.0.1:9001/ or the router’s admin page — addresses unreachable from the caller’s position but not from the server’s.
The filter now checks every address a hostname resolves to against loopback, private ranges, link-local (and with it 169.254.169.254), multicast and reserved blocks. Two details naive filters let through:
- IPv4-mapped IPv6 —
http://[::ffff:127.0.0.1]/is folded back to127.0.0.1before the check. - Redirects —
urllibfollows 302s automatically. A public server pointing at127.0.0.1is the classic detour. Every hop is validated separately.
The networking question
The emulator can attach to the network in two ways, and the choice decides how exposed the server has to be.
flowchart TB
subgraph B["bridge"]
B1["guest = its own host on the LAN"] --> B2["server on 0.0.0.0"]
B2 --> B3["firewall must be off"]
B3 --> B4["port 8080 open to the whole WLAN"]
end
subgraph S["slirp"]
S1["guest behind NAT inside the host"] --> S2["server on 127.0.0.1"]
S2 --> S3["firewall stays on"]
S3 --> S4["the LAN cannot reach the port"]
end
In bridge mode the emulated Mac is its own machine on the WLAN. That is convenient, but it forces a chain: the server must listen on 0.0.0.0, the firewall must be open, and port 8080 is thereby available to every device on the network.
In slirp mode the guest sits behind a NAT inside the host machine and reaches it at 10.0.2.2. The server can bind loopback, the firewall stays active — and the attack surface shrinks from “anyone on the WLAN” to “processes on this machine”.
ClaudeBridge 2.0 has decided: slirp-only. The server binds 127.0.0.1 and refuses to start on any other address, with an error explaining why and pointing at AppleBridge for the case where the guest genuinely has to sit on the LAN. Bridge mode is not merely discouraged, it is ruled out. The price: the guest loses its own network presence, with no AppleTalk to other machines.
A mode change always has two halves. The host half is one line in the emulator’s settings; the guest half is the TCP/IP control panel, which sits on a static address in bridge mode and has to be switched to DHCP for slirp. Flip only one half and you end up with no network, suspecting the wrong culprit.

The second mode is the more honest choice. Because one thing remains either way:
What cannot be solved
The connection between guest and server is unencrypted, and nothing can change that. Netscape 3 speaks only SSL 2.0/3.0 with export ciphers; modern OpenSSL versions have not disabled those but removed them. There is no configuration in which the two sides agree on a method.
This is not a question of effort. It is a property of the arrangement, and it deserves naming rather than working around: a browser from 1996 cannot speak securely with the web of 2026. It can only speak inside a network you trust — and slirp mode makes that network as small as possible.
What follows from it
ClaudeBridge is not a tool that forces a modern model onto old hardware. It is a translator between two sets of rules, neither of which yields.
The work is not in the API call — that part is trivial. It is in the places where assumptions diverge: a character reference in the wrong number base, a default value in a library function, a timeout conceived for connections of 1996.
The Mac from 1996 is no museum piece in this. It merely makes demands we have grown out of the habit of meeting.
Quellen
- Source on GitHub (MIT): https://github.com/LoetLuemmel/ClaudeBridge
- A Conversation with Claude on a 1996 Macintosh: /claudebridge/a-conversation-on-a-1996-macintosh/
- Five Bugs in THINK C: /claudebridge/five-bugs-in-think-c/
- Five Question Marks — an encoding bug: /claudebridge/five-question-marks-encoding-bug/
- What the 68k Mac needs: /claudebridge/guest-setup-for-claudebridge/
- AppleBridge — full project documentation: https://pit.390er.de/applebridge/
