Files
lda-wf/docs/superpowers/specs/2026-06-10-server-cli-transport-boundary.md
T

186 lines
5.3 KiB
Markdown

# Server CLI And Transport Boundary
Date: 2026-06-10
Status: first move slice implemented
Related:
- [Long-lived workflow API boundary](./2026-06-03-long-lived-workflow-api-boundary.md)
- [Runtime source lifecycle](./2026-06-09-runtime-source-lifecycle.md)
- [Workflow config targets and sources](./2026-06-03-workflow-config-targets-and-sources.md)
- [Project map](../../project_map.md)
## Purpose
`wf-rpc-server` used to live in `wf_transport_rpc_http.cli`, but that module did
more than transport work. It parsed server startup config, selected local static
vs MCP-backed composition, handled legacy `--mcp-config`, and started the
JSON-RPC HTTP transport.
That made sense while JSON-RPC was the only long-lived server path. It is now
the wrong long-term ownership. Server composition belongs in `wf_server`; HTTP
JSON-RPC should stay a transport adapter around an already-built
`WorkflowServer`.
## Ownership Rule
```text
wf_server
owns process startup policy
owns WorkflowServer composition from wf_config / legacy inputs
decides which transports to host
wf_transport_rpc_http
owns JSON-RPC HTTP app/client/envelope code
owns create_rpc_app(server)
may provide a small serve helper for an already-built WorkflowServer
```
The script name can remain `wf-rpc-server` for compatibility, but its entrypoint
should eventually point at `wf_server.cli:main`.
## Previous Shape
Previous script registration:
```toml
wf-rpc-server = "wf_transport_rpc_http.cli:main"
```
Previous responsibilities inside `wf_transport_rpc_http.cli`:
- parse `--config`
- parse `--store-root`
- parse legacy `--mcp-config`
- load neutral `wf_config`
- detect MCP sources
- select local/static or MCP-backed `WorkflowServer`
- select RPC bind host/port/path
- call `create_rpc_app(server)`
- call `uvicorn.run(...)`
Only the last two bullets are transport-specific.
## Desired Shape
Recommended first refactor:
```text
src/wf_server/cli.py
Typer app for durable server startup
parses config and startup overrides
builds WorkflowServer through wf_server.config
starts configured transports
src/wf_transport_rpc_http/cli.py
compatibility shim:
from wf_server.cli import app, main
src/wf_transport_rpc_http/app.py
unchanged create_rpc_app(server, rpc_path="/rpc")
```
Updated script registration:
```toml
wf-rpc-server = "wf_server.cli:main"
```
Keep `wf_transport_rpc_http.cli` temporarily so tests and imports do not break
immediately.
## Implementation Status
First move slice complete:
- `wf-rpc-server` script entrypoint points at `wf_server.cli:main`.
- `wf_server.cli` owns startup config parsing and server composition.
- `wf_transport_rpc_http.cli` remains as a compatibility shim.
- `wf_transport_rpc_http.create_rpc_app(server)` remains the JSON-RPC HTTP
transport adapter.
## Why Not Move `create_rpc_app`
`create_rpc_app(server)` is transport code:
- JSON-RPC route and method registration
- JSON-RPC error mapping
- HTTP health endpoint
- FastAPI/fastapi-jsonrpc object construction
It should stay in `wf_transport_rpc_http`. Future transports should have their
own equivalent adapter over `WorkflowServer`.
## Why Move The Typer Entrypoint
The Typer entrypoint answers process-level questions:
- Which config file is used?
- Is this local/static or MCP-backed?
- Are source providers loaded from neutral config?
- Is legacy config accepted?
- Which transports are hosted?
- Which store roles are used?
Those questions are server composition policy. They should not live in an HTTP
transport package because the same process may later host JSON-RPC HTTP, MCP
HTTP, WebSocket, or other transports together.
## Compatibility Policy
- Keep the command name `wf-rpc-server` during the move.
- Keep `wf_transport_rpc_http.cli.app` and `.main` as re-export shims for one
compatibility window.
- Update tests to target `wf_server.cli` for behavior.
- Keep one compatibility test proving the old import path is identity-equal.
- Do not change runtime behavior in the move slice.
## First Implementation Slice
The first slice should be a pure move/refactor:
1. Create `src/wf_server/cli.py` by moving the current implementation from
`src/wf_transport_rpc_http/cli.py`.
2. Replace `src/wf_transport_rpc_http/cli.py` with:
```python
from wf_server.cli import app, main
__all__ = ["app", "main"]
```
3. Update `pyproject.toml`:
```toml
wf-rpc-server = "wf_server.cli:main"
```
4. Move CLI behavior tests from `tests/wf_transport_rpc_http/test_cli.py` to a
server-focused test module, for example `tests/wf_server/test_cli.py`.
5. Update monkeypatch targets from `wf_transport_rpc_http.cli...` to
`wf_server.cli...`.
6. Add a compatibility import test for `wf_transport_rpc_http.cli`.
7. Update docs and project map.
## Non-Goals
- Do not add WebSocket, MCP HTTP, or multi-transport hosting in the move slice.
- Do not change `create_rpc_app`.
- Do not remove the `wf_transport_rpc_http.cli` shim immediately.
- Do not redesign `wf_config`.
- Do not change `wf` client targeting behavior.
## Future Follow-Up
After the move, `wf_server.cli` can grow toward multi-transport hosting:
```text
wf-server --config wf.config.json
hosts server.transports[] from config
```
`wf-rpc-server` can remain as an alias or convenience command for the RPC-only
case. The important boundary is that server startup composes transports; a
transport package does not compose the server.