feat: serve mcp backed rpc server
This commit is contained in:
@@ -0,0 +1,435 @@
|
|||||||
|
# RPC Server MCP Config Hookup Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Let `wf-rpc-server` serve a real MCP-backed `WorkflowServer` from an MCP broker config file, so `wf --url ...` can target a long-lived server with MCP sources and source-registry/admin surfaces.
|
||||||
|
|
||||||
|
**Architecture:** Keep JSON-RPC transport code as the place that selects a server composition for the process. `--mcp-config` loads legacy MCP broker config and calls `wf_mcp.broker.build_workflow_server_from_config`; existing `--store-root` / neutral `--config` behavior continues to build the local/static server. The transport still receives only a neutral `WorkflowServer` and calls `create_rpc_app(server)`.
|
||||||
|
|
||||||
|
**Tech Stack:** Python 3.14, Typer, `wf_transport_rpc_http`, `wf_mcp.broker`, `wf_server`, pytest, ruff, basedpyright.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current Context
|
||||||
|
|
||||||
|
Implemented before this plan:
|
||||||
|
|
||||||
|
- `wf_mcp.broker.server.build_workflow_server_from_config(config)` returns a neutral `WorkflowServer`.
|
||||||
|
- `wf_transport_rpc_http.cli.serve()` currently always calls `build_local_static_workflow_server(...)`.
|
||||||
|
- Existing CLI tests in `tests/wf_transport_rpc_http/test_cli.py` monkeypatch server construction, `create_rpc_app`, and `uvicorn.run`; use that pattern.
|
||||||
|
|
||||||
|
Out of scope:
|
||||||
|
|
||||||
|
- No new neutral config schema for MCP source transports.
|
||||||
|
- No server hot reload.
|
||||||
|
- No process manager/daemon work.
|
||||||
|
- No auth redesign.
|
||||||
|
- No real socket startup in tests.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
- Modify `src/wf_transport_rpc_http/cli.py`
|
||||||
|
- Add `--mcp-config`.
|
||||||
|
- Select MCP-backed server when `--mcp-config` is supplied.
|
||||||
|
- Keep local/static path unchanged for existing config/store-root flows.
|
||||||
|
- Modify `tests/wf_transport_rpc_http/test_cli.py`
|
||||||
|
- Add help assertion for `--mcp-config`.
|
||||||
|
- Add server-selection tests.
|
||||||
|
- Modify `docs/wf_cli.md`
|
||||||
|
- Document local/static server and MCP-backed server startup examples.
|
||||||
|
- Modify `docs/current_roadmap.md`
|
||||||
|
- Mark RPC server CLI MCP config hookup complete.
|
||||||
|
- Modify `docs/superpowers/specs/2026-06-03-long-lived-workflow-api-boundary.md`
|
||||||
|
- Record Slice 5 status.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Add MCP Config Server Selection to RPC CLI
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/wf_transport_rpc_http/cli.py`
|
||||||
|
- Modify: `tests/wf_transport_rpc_http/test_cli.py`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add failing help and selection tests**
|
||||||
|
|
||||||
|
In `tests/wf_transport_rpc_http/test_cli.py`, update `test_rpc_server_cli_help_mentions_store_root`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def test_rpc_server_cli_help_mentions_store_root() -> None:
|
||||||
|
result = CliRunner().invoke(app, ["--help"])
|
||||||
|
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "--store-root" in result.output
|
||||||
|
assert "--mcp-config" in result.output
|
||||||
|
assert "--host" in result.output
|
||||||
|
assert "--port" in result.output
|
||||||
|
```
|
||||||
|
|
||||||
|
Add:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def test_rpc_server_cli_uses_mcp_config_server(monkeypatch, tmp_path) -> None:
|
||||||
|
config_path = tmp_path / "wf_mcp.config.json"
|
||||||
|
config_path.write_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"store_root": str(tmp_path / "store"),
|
||||||
|
"connections": [],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
captured: dict[str, object] = {}
|
||||||
|
|
||||||
|
def fake_load_broker_config(path):
|
||||||
|
captured["mcp_config_path"] = path
|
||||||
|
return "broker-config"
|
||||||
|
|
||||||
|
def fake_build_mcp_server(config):
|
||||||
|
captured["mcp_config"] = config
|
||||||
|
return object()
|
||||||
|
|
||||||
|
def fake_create_rpc_app(server, *, rpc_path="/rpc"):
|
||||||
|
captured["server"] = server
|
||||||
|
captured["rpc_path"] = rpc_path
|
||||||
|
return object()
|
||||||
|
|
||||||
|
def fake_uvicorn_run(app_obj, *, host, port, access_log):
|
||||||
|
captured["app"] = app_obj
|
||||||
|
captured["host"] = host
|
||||||
|
captured["port"] = port
|
||||||
|
captured["access_log"] = access_log
|
||||||
|
|
||||||
|
monkeypatch.setattr("wf_transport_rpc_http.cli.load_broker_config", fake_load_broker_config)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"wf_transport_rpc_http.cli.build_workflow_server_from_config",
|
||||||
|
fake_build_mcp_server,
|
||||||
|
)
|
||||||
|
monkeypatch.setattr("wf_transport_rpc_http.cli.create_rpc_app", fake_create_rpc_app)
|
||||||
|
monkeypatch.setattr("wf_transport_rpc_http.cli.uvicorn.run", fake_uvicorn_run)
|
||||||
|
|
||||||
|
result = CliRunner().invoke(
|
||||||
|
app,
|
||||||
|
[
|
||||||
|
"--mcp-config",
|
||||||
|
str(config_path),
|
||||||
|
"--host",
|
||||||
|
"127.0.0.9",
|
||||||
|
"--port",
|
||||||
|
"9988",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.exit_code == 0, result.output
|
||||||
|
assert captured["mcp_config_path"] == config_path
|
||||||
|
assert captured["mcp_config"] == "broker-config"
|
||||||
|
assert captured["server"] is not None
|
||||||
|
assert captured["rpc_path"] == "/rpc"
|
||||||
|
assert captured["host"] == "127.0.0.9"
|
||||||
|
assert captured["port"] == 9988
|
||||||
|
assert captured["access_log"] is False
|
||||||
|
```
|
||||||
|
|
||||||
|
Add conflict test:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def test_rpc_server_cli_rejects_mcp_config_with_store_root(tmp_path) -> None:
|
||||||
|
config_path = tmp_path / "wf_mcp.config.json"
|
||||||
|
config_path.write_text(
|
||||||
|
json.dumps({"store_root": str(tmp_path / "store"), "connections": []}),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
result = CliRunner().invoke(
|
||||||
|
app,
|
||||||
|
[
|
||||||
|
"--mcp-config",
|
||||||
|
str(config_path),
|
||||||
|
"--store-root",
|
||||||
|
str(tmp_path / "other"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.exit_code != 0
|
||||||
|
assert "--mcp-config cannot be combined with --store-root" in result.output
|
||||||
|
```
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest tests/wf_transport_rpc_http/test_cli.py -q
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: FAIL because `--mcp-config` is not implemented.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Implement `--mcp-config` option**
|
||||||
|
|
||||||
|
In `src/wf_transport_rpc_http/cli.py`, import:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from wf_mcp.broker import build_workflow_server_from_config, load_broker_config
|
||||||
|
```
|
||||||
|
|
||||||
|
Add option to `serve(...)`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
mcp_config: Path | None = typer.Option(
|
||||||
|
None,
|
||||||
|
"--mcp-config",
|
||||||
|
help="Path to MCP broker config JSON for MCP-backed workflow server.",
|
||||||
|
),
|
||||||
|
```
|
||||||
|
|
||||||
|
Update docstring:
|
||||||
|
|
||||||
|
```python
|
||||||
|
"""Serve WorkflowApi over JSON-RPC HTTP."""
|
||||||
|
```
|
||||||
|
|
||||||
|
Before resolving local/static store root, add validation:
|
||||||
|
|
||||||
|
```python
|
||||||
|
if mcp_config is not None and store_root is not None:
|
||||||
|
raise typer.BadParameter("--mcp-config cannot be combined with --store-root")
|
||||||
|
```
|
||||||
|
|
||||||
|
If `mcp_config` is supplied, build MCP server:
|
||||||
|
|
||||||
|
```python
|
||||||
|
server = None
|
||||||
|
if mcp_config is not None:
|
||||||
|
broker_config = load_broker_config(mcp_config)
|
||||||
|
server = build_workflow_server_from_config(broker_config)
|
||||||
|
```
|
||||||
|
|
||||||
|
Then keep existing neutral `--config` parsing for host/port/path. When selecting
|
||||||
|
the final server, only call `build_local_static_workflow_server(...)` if
|
||||||
|
`server is None`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
if server is None:
|
||||||
|
if resolved_store_root is None:
|
||||||
|
raise typer.BadParameter(
|
||||||
|
"--store-root is required when --config is not supplied"
|
||||||
|
)
|
||||||
|
server = build_local_static_workflow_server(resolved_store_root)
|
||||||
|
```
|
||||||
|
|
||||||
|
Important:
|
||||||
|
|
||||||
|
- `--config` may still be used with `--mcp-config` for transport host/port/path.
|
||||||
|
- `--mcp-config` owns the workflow server/store/source side.
|
||||||
|
- `--store-root` is local/static-only and must conflict with `--mcp-config`.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run CLI tests**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest tests/wf_transport_rpc_http/test_cli.py -q
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/wf_transport_rpc_http/cli.py tests/wf_transport_rpc_http/test_cli.py
|
||||||
|
git commit -m "feat: serve mcp backed rpc server"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: Prove MCP Config Server Supports Registry RPC Through CLI Path
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `tests/wf_transport_rpc_http/test_cli.py`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add integration-style construction test**
|
||||||
|
|
||||||
|
Add this test to `tests/wf_transport_rpc_http/test_cli.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def test_rpc_server_cli_mcp_config_builds_registry_capable_server(monkeypatch, tmp_path) -> None:
|
||||||
|
config_path = tmp_path / "wf_mcp.config.json"
|
||||||
|
config_path.write_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"store_root": str(tmp_path / "store"),
|
||||||
|
"connections": [],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
captured: dict[str, object] = {}
|
||||||
|
|
||||||
|
def fake_create_rpc_app(server, *, rpc_path="/rpc"):
|
||||||
|
captured["source_registry_admin"] = server.source_registry_admin
|
||||||
|
captured["rpc_path"] = rpc_path
|
||||||
|
return object()
|
||||||
|
|
||||||
|
def fake_uvicorn_run(app_obj, *, host, port, access_log):
|
||||||
|
captured["host"] = host
|
||||||
|
captured["port"] = port
|
||||||
|
|
||||||
|
monkeypatch.setattr("wf_transport_rpc_http.cli.create_rpc_app", fake_create_rpc_app)
|
||||||
|
monkeypatch.setattr("wf_transport_rpc_http.cli.uvicorn.run", fake_uvicorn_run)
|
||||||
|
|
||||||
|
result = CliRunner().invoke(app, ["--mcp-config", str(config_path)])
|
||||||
|
|
||||||
|
assert result.exit_code == 0, result.output
|
||||||
|
assert captured["source_registry_admin"] is not None
|
||||||
|
assert captured["rpc_path"] == "/rpc"
|
||||||
|
assert captured["host"] == "127.0.0.1"
|
||||||
|
assert captured["port"] == 8765
|
||||||
|
```
|
||||||
|
|
||||||
|
This test uses the real `load_broker_config()` and
|
||||||
|
`build_workflow_server_from_config()` but still avoids starting uvicorn.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run CLI tests**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest tests/wf_transport_rpc_http/test_cli.py -q
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add tests/wf_transport_rpc_http/test_cli.py
|
||||||
|
git commit -m "test: cover mcp config rpc server path"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: Update User-Facing Docs and Roadmap
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `docs/wf_cli.md`
|
||||||
|
- Modify: `docs/current_roadmap.md`
|
||||||
|
- Modify: `docs/superpowers/specs/2026-06-03-long-lived-workflow-api-boundary.md`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add RPC server startup docs**
|
||||||
|
|
||||||
|
In `docs/wf_cli.md`, after the opening config paragraph, add:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Remote Server
|
||||||
|
|
||||||
|
Start a local/static JSON-RPC workflow server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wf-rpc-server --store-root .wf_store --host 127.0.0.1 --port 8765
|
||||||
|
```
|
||||||
|
|
||||||
|
Start a JSON-RPC server backed by MCP broker config and MCP-capable sources:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wf-rpc-server --mcp-config wf_mcp.config.json --host 127.0.0.1 --port 8765
|
||||||
|
```
|
||||||
|
|
||||||
|
Then point `wf` at it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wf --url http://127.0.0.1:8765/rpc cap list
|
||||||
|
wf --url http://127.0.0.1:8765/rpc admin registry list
|
||||||
|
```
|
||||||
|
|
||||||
|
`--mcp-config` owns the server's workflow stores, MCP connections, and source
|
||||||
|
registry. `--store-root` is for the local/static server path and cannot be
|
||||||
|
combined with `--mcp-config`.
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Update roadmap**
|
||||||
|
|
||||||
|
In `docs/current_roadmap.md`, under **Durable API service shape**, add:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
- Completed: `wf-rpc-server --mcp-config wf_mcp.config.json` starts the
|
||||||
|
JSON-RPC transport over an MCP-backed `WorkflowServer`, making the remote
|
||||||
|
CLI path usable with MCP sources and desired source registry operations.
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Update long-lived API spec**
|
||||||
|
|
||||||
|
In `docs/superpowers/specs/2026-06-03-long-lived-workflow-api-boundary.md`,
|
||||||
|
update status from "Slices 1-4 implemented" to "Slices 1-5 implemented", and
|
||||||
|
add under implementation status:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
- Slice 5 complete: `wf-rpc-server --mcp-config <path>` starts JSON-RPC over an
|
||||||
|
MCP-backed `WorkflowServer`; `--store-root` remains local/static-only.
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run docs grep**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rg -n "wf-rpc-server --mcp-config|Slices 1-5 implemented|--store-root.*--mcp-config" docs/wf_cli.md docs/current_roadmap.md docs/superpowers/specs/2026-06-03-long-lived-workflow-api-boundary.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: all three docs mention the new server path.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add docs/wf_cli.md docs/current_roadmap.md docs/superpowers/specs/2026-06-03-long-lived-workflow-api-boundary.md
|
||||||
|
git commit -m "docs: document mcp backed rpc server"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4: Final Verification
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Verify only.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Run focused tests**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest tests/wf_transport_rpc_http/test_cli.py tests/wf_transport_rpc_http/test_mcp_backed_server_rpc.py tests/wf_mcp/test_mcp_workflow_server.py -q
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: all tests pass.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run lint and type checks**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run ruff check src/wf_transport_rpc_http/cli.py tests/wf_transport_rpc_http/test_cli.py
|
||||||
|
uv run basedpyright --level error src/wf_transport_rpc_http/cli.py tests/wf_transport_rpc_http/test_cli.py
|
||||||
|
git diff --check
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: all commands exit 0. CRLF warnings from Git are acceptable; whitespace
|
||||||
|
errors are not.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Final report**
|
||||||
|
|
||||||
|
Report:
|
||||||
|
|
||||||
|
- changed files
|
||||||
|
- verification output
|
||||||
|
- exact command a user can run for MCP-backed RPC server
|
||||||
|
- any deviations from this plan
|
||||||
|
|
||||||
|
Do not run the full suite unless the focused verification is green.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Self-Review
|
||||||
|
|
||||||
|
- Product fit: this is the smallest visible hook after MCP-backed `WorkflowServer` construction.
|
||||||
|
- Boundary: `wf_transport_rpc_http.cli` imports `wf_mcp.broker` only for process startup selection; JSON-RPC app/method modules still take a neutral `WorkflowServer`.
|
||||||
|
- Config semantics: `--mcp-config` and `--store-root` conflict because they own different server composition paths.
|
||||||
|
- Testing: no real socket startup; tests monkeypatch `uvicorn.run`.
|
||||||
@@ -11,6 +11,8 @@ from wf_config import (
|
|||||||
load_workflow_config,
|
load_workflow_config,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from wf_mcp.broker import build_workflow_server_from_config, load_broker_config
|
||||||
|
|
||||||
from wf_server import build_local_static_workflow_server
|
from wf_server import build_local_static_workflow_server
|
||||||
|
|
||||||
from .app import create_rpc_app
|
from .app import create_rpc_app
|
||||||
@@ -30,6 +32,11 @@ def serve(
|
|||||||
"--store-root",
|
"--store-root",
|
||||||
help="Override filesystem workflow store root.",
|
help="Override filesystem workflow store root.",
|
||||||
),
|
),
|
||||||
|
mcp_config: Path | None = typer.Option(
|
||||||
|
None,
|
||||||
|
"--mcp-config",
|
||||||
|
help="Path to MCP broker config JSON for MCP-backed workflow server.",
|
||||||
|
),
|
||||||
host: str | None = typer.Option(
|
host: str | None = typer.Option(
|
||||||
None,
|
None,
|
||||||
"--host",
|
"--host",
|
||||||
@@ -43,11 +50,20 @@ def serve(
|
|||||||
help="Override RPC bind port; defaults to config or 8765.",
|
help="Override RPC bind port; defaults to config or 8765.",
|
||||||
),
|
),
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Serve the local/static WorkflowApi over JSON-RPC HTTP."""
|
"""Serve WorkflowApi over JSON-RPC HTTP."""
|
||||||
|
if mcp_config is not None and store_root is not None:
|
||||||
|
raise typer.BadParameter("--mcp-config cannot be combined with --store-root")
|
||||||
|
|
||||||
resolved_store_root = store_root
|
resolved_store_root = store_root
|
||||||
resolved_host = host
|
resolved_host = host
|
||||||
resolved_port = port
|
resolved_port = port
|
||||||
resolved_rpc_path = "/rpc"
|
resolved_rpc_path = "/rpc"
|
||||||
|
|
||||||
|
server = None
|
||||||
|
if mcp_config is not None:
|
||||||
|
broker_config = load_broker_config(mcp_config)
|
||||||
|
server = build_workflow_server_from_config(broker_config)
|
||||||
|
|
||||||
if config is not None:
|
if config is not None:
|
||||||
workflow_config = load_workflow_config(config)
|
workflow_config = load_workflow_config(config)
|
||||||
store = workflow_config.server.store
|
store = workflow_config.server.store
|
||||||
@@ -68,12 +84,14 @@ def serve(
|
|||||||
resolved_host = host or rpc_transport.host
|
resolved_host = host or rpc_transport.host
|
||||||
resolved_port = port or rpc_transport.port
|
resolved_port = port or rpc_transport.port
|
||||||
resolved_rpc_path = rpc_transport.path
|
resolved_rpc_path = rpc_transport.path
|
||||||
|
|
||||||
|
if server is None:
|
||||||
if resolved_store_root is None:
|
if resolved_store_root is None:
|
||||||
raise typer.BadParameter(
|
raise typer.BadParameter(
|
||||||
"--store-root is required when --config is not supplied"
|
"--store-root is required when --config is not supplied"
|
||||||
)
|
)
|
||||||
|
|
||||||
server = build_local_static_workflow_server(resolved_store_root)
|
server = build_local_static_workflow_server(resolved_store_root)
|
||||||
|
|
||||||
rpc_app = create_rpc_app(server, rpc_path=resolved_rpc_path)
|
rpc_app = create_rpc_app(server, rpc_path=resolved_rpc_path)
|
||||||
uvicorn.run(
|
uvicorn.run(
|
||||||
rpc_app,
|
rpc_app,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ def test_rpc_server_cli_help_mentions_store_root() -> None:
|
|||||||
|
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert "--store-root" in result.output
|
assert "--store-root" in result.output
|
||||||
|
assert "--mcp-config" in result.output
|
||||||
assert "--host" in result.output
|
assert "--host" in result.output
|
||||||
assert "--port" in result.output
|
assert "--port" in result.output
|
||||||
|
|
||||||
@@ -99,3 +100,86 @@ def test_rpc_server_cli_uses_configured_store_and_transport(
|
|||||||
assert captured["host"] == "127.0.0.2"
|
assert captured["host"] == "127.0.0.2"
|
||||||
assert captured["port"] == 9999
|
assert captured["port"] == 9999
|
||||||
assert captured["access_log"] is False
|
assert captured["access_log"] is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_rpc_server_cli_uses_mcp_config_server(monkeypatch, tmp_path) -> None:
|
||||||
|
config_path = tmp_path / "wf_mcp.config.json"
|
||||||
|
config_path.write_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"store_root": str(tmp_path / "store"),
|
||||||
|
"connections": [],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
captured: dict[str, object] = {}
|
||||||
|
|
||||||
|
def fake_load_broker_config(path):
|
||||||
|
captured["mcp_config_path"] = path
|
||||||
|
return "broker-config"
|
||||||
|
|
||||||
|
def fake_build_mcp_server(config):
|
||||||
|
captured["mcp_config"] = config
|
||||||
|
return object()
|
||||||
|
|
||||||
|
def fake_create_rpc_app(server, *, rpc_path="/rpc"):
|
||||||
|
captured["server"] = server
|
||||||
|
captured["rpc_path"] = rpc_path
|
||||||
|
return object()
|
||||||
|
|
||||||
|
def fake_uvicorn_run(app_obj, *, host, port, access_log):
|
||||||
|
captured["app"] = app_obj
|
||||||
|
captured["host"] = host
|
||||||
|
captured["port"] = port
|
||||||
|
captured["access_log"] = access_log
|
||||||
|
|
||||||
|
monkeypatch.setattr("wf_transport_rpc_http.cli.load_broker_config", fake_load_broker_config)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"wf_transport_rpc_http.cli.build_workflow_server_from_config",
|
||||||
|
fake_build_mcp_server,
|
||||||
|
)
|
||||||
|
monkeypatch.setattr("wf_transport_rpc_http.cli.create_rpc_app", fake_create_rpc_app)
|
||||||
|
monkeypatch.setattr("wf_transport_rpc_http.cli.uvicorn.run", fake_uvicorn_run)
|
||||||
|
|
||||||
|
result = CliRunner().invoke(
|
||||||
|
app,
|
||||||
|
[
|
||||||
|
"--mcp-config",
|
||||||
|
str(config_path),
|
||||||
|
"--host",
|
||||||
|
"127.0.0.9",
|
||||||
|
"--port",
|
||||||
|
"9988",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.exit_code == 0, result.output
|
||||||
|
assert captured["mcp_config_path"] == config_path
|
||||||
|
assert captured["mcp_config"] == "broker-config"
|
||||||
|
assert captured["server"] is not None
|
||||||
|
assert captured["rpc_path"] == "/rpc"
|
||||||
|
assert captured["host"] == "127.0.0.9"
|
||||||
|
assert captured["port"] == 9988
|
||||||
|
assert captured["access_log"] is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_rpc_server_cli_rejects_mcp_config_with_store_root(tmp_path) -> None:
|
||||||
|
config_path = tmp_path / "wf_mcp.config.json"
|
||||||
|
config_path.write_text(
|
||||||
|
json.dumps({"store_root": str(tmp_path / "store"), "connections": []}),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
result = CliRunner().invoke(
|
||||||
|
app,
|
||||||
|
[
|
||||||
|
"--mcp-config",
|
||||||
|
str(config_path),
|
||||||
|
"--store-root",
|
||||||
|
str(tmp_path / "other"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.exit_code != 0
|
||||||
|
assert "--mcp-config cannot be combined with --store-root" in result.output
|
||||||
|
|||||||
Reference in New Issue
Block a user