Skip to content

Remote gpu server - #35

Merged
dkackman merged 14 commits into
masterfrom
remote-gpu-server
Sep 5, 2026
Merged

dkackman merged 14 commits into
masterfrom
remote-gpu-server

Conversation

@dkackman

@dkackman dkackman commented Sep 5, 2026

Copy link
Copy Markdown
Owner

No description provided.

dkackman and others added 12 commits September 4, 2026 22:07
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Origin check only allowed loopback origins, so a browser reaching a
--host 0.0.0.0 server by LAN IP got 403 on every POST. An Origin whose
hostname matches the request's own Host is now accepted; a rebinding
attack still differs from Host and is still refused.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
dw-mcp exits 2 when --url is not loopback and no token is set, and makes
one GET /api/health before serving so a wrong URL or token is reported
once with a message rather than as a 401 on every tool call. --no-probe
skips the check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
create_app(mcp=True) mounts the dw_mcp tool surface inside the HTTP
server, behind the same bearer token as /api. The tools call back to
the REST API over loopback, so dw_mcp stays a pure HTTP client. An
agent on another machine needs no local install: one URL and a token.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Refused on a non-loopback bind without a token: an MCP endpoint can
write and run workflows and has no UI-style token prompt in front of it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
stdout is block-buffered when it is not a terminal, so under the systemd
unit (StandardOutput=journal) or nohup the banner - including whether MCP
is mounted - was withheld until the server shut down.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds #/server: status (hostname, version, device, worker, queue, current
job), the bind and what it means, the machine's interface addresses with
the URL each makes, whether MCP is mounted and where, whether a token is
required, copy-paste snippets for a browser and for `claude mcp add`, and
the workflow/output/prompt directories.

The token is never rendered - the mcp command carries the literal
placeholder <token> - and nothing derived from it is shown either. URL and
command composition lives in lib/serverinfo.ts so it is testable on its
own; the page reads GET /api/server for the static detail and polls
/api/health for the live half.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Server page has to answer "what URL and command do I give the other
machine?", and the only half of that the server knows is its own bind
address, port, whether a token is required, whether MCP is mounted, and
which non-loopback addresses its interfaces carry. It reports those and
stops there - URL composition is the UI's job.

The token is never reported in any form, only auth_required; a test
asserts the value, a prefix of it and its length are all absent from the
body. Address enumeration prefers psutil (the only method that can name
the interface) but never requires it - psutil is transitive here, not a
declared dependency - and falls back to getaddrinfo plus the connectionless
UDP outbound-address trick, which matters on a host whose name resolves to
127.0.1.1. Enumeration failure yields [], not a 500.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread ui/src/lib/pages/ServerPage.test.ts Dismissed
- Updated `build_mcp_app` to accept host parameter for correct client base URL.
- Added `client_base_url` function to determine the server address for mounted tools.
- Enhanced same-origin checks in the server to reject opaque origins.
- Added tests for address handling in mounted tools and server configurations.
- Updated address labeling logic to handle unknown interfaces gracefully.
@dkackman
dkackman merged commit eb67e8c into master Sep 5, 2026
8 of 9 checks passed
@dkackman
dkackman deleted the remote-gpu-server branch September 5, 2026 01:35
dkackman added a commit that referenced this pull request Sep 5, 2026
* docs: move scoping notes from superpowers/scope to docs/proposals

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(server): accept same-origin browser requests on a non-loopback bind

The Origin check only allowed loopback origins, so a browser reaching a
--host 0.0.0.0 server by LAN IP got 403 on every POST. An Origin whose
hostname matches the request's own Host is now accepted; a rebinding
attack still differs from Host and is still refused.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(mcp): refuse an unauthenticated remote server and probe at startup

dw-mcp exits 2 when --url is not loopback and no token is set, and makes
one GET /api/health before serving so a wrong URL or token is reported
once with a message rather than as a 401 on every tool call. --no-probe
skips the check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(server): report hostname, device and mcp in /api/health

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(server): mount MCP over Streamable HTTP at /mcp

create_app(mcp=True) mounts the dw_mcp tool surface inside the HTTP
server, behind the same bearer token as /api. The tools call back to
the REST API over loopback, so dw_mcp stays a pure HTTP client. An
agent on another machine needs no local install: one URL and a token.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(serve): --mcp serves the tool surface at /mcp

Refused on a non-loopback bind without a token: an MCP endpoint can
write and run workflows and has no UI-style token prompt in front of it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: remote GPU server guide, systemd unit, MCP security rewrite

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: quickref no longer says MCP is localhost only

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* delete

* fix(serve): flush the startup banner

stdout is block-buffered when it is not a terminal, so under the systemd
unit (StandardOutput=journal) or nohup the banner - including whether MCP
is mounted - was withheld until the server shut down.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(ui): Server page with the details needed to connect from elsewhere

Adds #/server: status (hostname, version, device, worker, queue, current
job), the bind and what it means, the machine's interface addresses with
the URL each makes, whether MCP is mounted and where, whether a token is
required, copy-paste snippets for a browser and for `claude mcp add`, and
the workflow/output/prompt directories.

The token is never rendered - the mcp command carries the literal
placeholder <token> - and nothing derived from it is shown either. URL and
command composition lives in lib/serverinfo.ts so it is testable on its
own; the page reads GET /api/server for the static detail and polls
/api/health for the live half.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(server): GET /api/server describes how to reach this machine

The Server page has to answer "what URL and command do I give the other
machine?", and the only half of that the server knows is its own bind
address, port, whether a token is required, whether MCP is mounted, and
which non-loopback addresses its interfaces carry. It reports those and
stops there - URL composition is the UI's job.

The token is never reported in any form, only auth_required; a test
asserts the value, a prefix of it and its length are all absent from the
body. Address enumeration prefers psutil (the only method that can name
the interface) but never requires it - psutil is transitive here, not a
declared dependency - and falls back to getaddrinfo plus the connectionless
UDP outbound-address trick, which matters on a host whose name resolves to
127.0.1.1. Enumeration failure yields [], not a 500.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(server): enhance MCP app configuration and improve address handling

- Updated `build_mcp_app` to accept host parameter for correct client base URL.
- Added `client_base_url` function to determine the server address for mounted tools.
- Enhanced same-origin checks in the server to reject opaque origins.
- Added tests for address handling in mounted tools and server configurations.
- Updated address labeling logic to handle unknown interfaces gracefully.

* fix(tests): update HEAD request assertion to handle 404 and 405 status codes

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants