Compare commits

..

No commits in common. "386b9793ee9cdd569d82aef273e63e9ab451b001" and "68d8e875e0c0880e7e04941dd16200336a674d08" have entirely different histories.

27 changed files with 149 additions and 204 deletions

View file

@ -2,17 +2,6 @@
All notable user-visible changes to `pyro-mcp` are documented here. All notable user-visible changes to `pyro-mcp` are documented here.
## 4.0.0
- Flipped the default MCP/server profile from `workspace-full` to
`workspace-core`, so bare `pyro mcp serve`, `create_server()`, and
`Pyro.create_server()` now match the recommended narrow chat-host path.
- Rewrote MCP-facing docs and shipped host-specific examples so the normal
setup path no longer needs an explicit `--profile workspace-core` just to
get the default behavior.
- Added migration guidance for hosts that relied on the previous implicit full
surface: they now need `--profile workspace-full` or
`create_server(profile=\"workspace-full\")`.
## 3.11.0 ## 3.11.0
- Added first-class host-specific MCP onramps for Claude Code, Codex, and - Added first-class host-specific MCP onramps for Claude Code, Codex, and

View file

@ -23,7 +23,7 @@ It exposes the same runtime in three public forms:
- Stable workspace walkthrough GIF: [docs/assets/workspace-first-run.gif](docs/assets/workspace-first-run.gif) - Stable workspace walkthrough GIF: [docs/assets/workspace-first-run.gif](docs/assets/workspace-first-run.gif)
- Terminal walkthrough GIF: [docs/assets/first-run.gif](docs/assets/first-run.gif) - Terminal walkthrough GIF: [docs/assets/first-run.gif](docs/assets/first-run.gif)
- PyPI package: [pypi.org/project/pyro-mcp](https://pypi.org/project/pyro-mcp/) - PyPI package: [pypi.org/project/pyro-mcp](https://pypi.org/project/pyro-mcp/)
- What's new in 4.0.0: [CHANGELOG.md#400](CHANGELOG.md#400) - What's new in 3.11.0: [CHANGELOG.md#3110](CHANGELOG.md#3110)
- Host requirements: [docs/host-requirements.md](docs/host-requirements.md) - Host requirements: [docs/host-requirements.md](docs/host-requirements.md)
- Integration targets: [docs/integrations.md](docs/integrations.md) - Integration targets: [docs/integrations.md](docs/integrations.md)
- Public contract: [docs/public-contract.md](docs/public-contract.md) - Public contract: [docs/public-contract.md](docs/public-contract.md)
@ -60,7 +60,7 @@ What success looks like:
```bash ```bash
Platform: linux-x86_64 Platform: linux-x86_64
Runtime: PASS Runtime: PASS
Catalog version: 4.0.0 Catalog version: 3.11.0
... ...
[pull] phase=install environment=debian:12 [pull] phase=install environment=debian:12
[pull] phase=ready environment=debian:12 [pull] phase=ready environment=debian:12
@ -126,7 +126,7 @@ That stable workspace path gives you:
After the quickstart works: After the quickstart works:
- prove the full one-shot lifecycle with `uvx --from pyro-mcp pyro demo` - prove the full one-shot lifecycle with `uvx --from pyro-mcp pyro demo`
- start most chat hosts with `uvx --from pyro-mcp pyro mcp serve` - start most chat hosts with `uvx --from pyro-mcp pyro mcp serve --profile workspace-core`
- create a persistent workspace with `uvx --from pyro-mcp pyro workspace create debian:12 --seed-path ./repo` - create a persistent workspace with `uvx --from pyro-mcp pyro workspace create debian:12 --seed-path ./repo`
- add a human-friendly workspace name with `uvx --from pyro-mcp pyro workspace create debian:12 --name repro-fix --label issue=123` - add a human-friendly workspace name with `uvx --from pyro-mcp pyro workspace create debian:12 --name repro-fix --label issue=123`
- rediscover or retag workspaces with `uvx --from pyro-mcp pyro workspace list` and `uvx --from pyro-mcp pyro workspace update WORKSPACE_ID --label owner=codex` - rediscover or retag workspaces with `uvx --from pyro-mcp pyro workspace list` and `uvx --from pyro-mcp pyro workspace update WORKSPACE_ID --label owner=codex`
@ -148,12 +148,12 @@ After the quickstart works:
## Chat Host Quickstart ## Chat Host Quickstart
For most MCP chat hosts, bare `pyro mcp serve` now starts `workspace-core`. It exposes the practical For most MCP chat hosts, start with `workspace-core`. It exposes the practical
persistent editing loop without shells, services, snapshots, secrets, network persistent editing loop without shells, services, snapshots, secrets, network
policy, or disk tools. policy, or disk tools.
```bash ```bash
uvx --from pyro-mcp pyro mcp serve uvx --from pyro-mcp pyro mcp serve --profile workspace-core
``` ```
Copy-paste host-specific starts: Copy-paste host-specific starts:
@ -166,13 +166,13 @@ Copy-paste host-specific starts:
Claude Code: Claude Code:
```bash ```bash
claude mcp add pyro -- uvx --from pyro-mcp pyro mcp serve claude mcp add pyro -- uvx --from pyro-mcp pyro mcp serve --profile workspace-core
``` ```
Codex: Codex:
```bash ```bash
codex mcp add pyro -- uvx --from pyro-mcp pyro mcp serve codex mcp add pyro -- uvx --from pyro-mcp pyro mcp serve --profile workspace-core
``` ```
OpenCode `opencode.json` snippet: OpenCode `opencode.json` snippet:
@ -183,22 +183,20 @@ OpenCode `opencode.json` snippet:
"pyro": { "pyro": {
"type": "local", "type": "local",
"enabled": true, "enabled": true,
"command": ["uvx", "--from", "pyro-mcp", "pyro", "mcp", "serve"] "command": ["uvx", "--from", "pyro-mcp", "pyro", "mcp", "serve", "--profile", "workspace-core"]
} }
} }
} }
``` ```
If `pyro-mcp` is already installed, replace the `uvx --from pyro-mcp pyro` If `pyro-mcp` is already installed, replace the `uvx --from pyro-mcp pyro`
command with `pyro` in the same host-specific command or config shape. Use command with `pyro` in the same host-specific command or config shape.
`--profile workspace-full` only when the host truly needs the full advanced
workspace surface.
Profile progression: Profile progression:
- `workspace-core`: default and recommended first profile for normal persistent chat editing - `workspace-core`: recommended first profile for normal persistent chat editing
- `vm-run`: smallest one-shot-only surface - `vm-run`: smallest one-shot-only surface
- `workspace-full`: explicit advanced opt-in when the chat truly needs shells, services, snapshots, secrets, network policy, or disk tools - `workspace-full`: advanced 3.x compatibility surface when the chat truly needs shells, services, snapshots, secrets, network policy, or disk tools
## Supported Hosts ## Supported Hosts
@ -251,7 +249,7 @@ uvx --from pyro-mcp pyro env list
Expected output: Expected output:
```bash ```bash
Catalog version: 4.0.0 Catalog version: 3.11.0
debian:12 [installed|not installed] Debian 12 environment with Git preinstalled for common agent workflows. debian:12 [installed|not installed] Debian 12 environment with Git preinstalled for common agent workflows.
debian:12-base [installed|not installed] Minimal Debian 12 environment for shell and core Unix tooling. debian:12-base [installed|not installed] Minimal Debian 12 environment for shell and core Unix tooling.
debian:12-build [installed|not installed] Debian 12 environment with Git and common build tools preinstalled. debian:12-build [installed|not installed] Debian 12 environment with Git and common build tools preinstalled.
@ -368,7 +366,7 @@ machine consumption, use `--id-only` for only the identifier or `--json` for the
workspace payload. Use `--seed-path` when workspace payload. Use `--seed-path` when
you want the workspace to start from a host directory or a local `.tar` / `.tar.gz` / `.tgz` you want the workspace to start from a host directory or a local `.tar` / `.tar.gz` / `.tgz`
archive instead of an empty workspace. Use `pyro workspace sync push` when you want to import archive instead of an empty workspace. Use `pyro workspace sync push` when you want to import
later host-side changes into a started workspace. Sync is non-atomic in `4.0.0`; if it fails later host-side changes into a started workspace. Sync is non-atomic in `3.11.0`; if it fails
partway through, prefer `pyro workspace reset` to recover from `baseline` or one named snapshot. partway through, prefer `pyro workspace reset` to recover from `baseline` or one named snapshot.
Use `pyro workspace diff` to compare the live `/workspace` tree to its immutable create-time Use `pyro workspace diff` to compare the live `/workspace` tree to its immutable create-time
baseline, and `pyro workspace export` to copy one changed file or directory back to the host. Use baseline, and `pyro workspace export` to copy one changed file or directory back to the host. Use
@ -397,7 +395,7 @@ The public user-facing interface is `pyro` and `Pyro`. After the CLI validation
- `pyro` for direct CLI usage, including one-shot `run` and persistent `workspace` workflows - `pyro` for direct CLI usage, including one-shot `run` and persistent `workspace` workflows
- `from pyro_mcp import Pyro` for Python orchestration - `from pyro_mcp import Pyro` for Python orchestration
- `pyro mcp serve` for MCP clients - `pyro mcp serve --profile workspace-core` for MCP clients
Command forms: Command forms:
@ -464,7 +462,7 @@ Run the MCP server after the CLI path above works. Start most chat hosts with
`workspace-core`: `workspace-core`:
```bash ```bash
pyro mcp serve pyro mcp serve --profile workspace-core
``` ```
Profile progression for chat hosts: Profile progression for chat hosts:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Before After
Before After

View file

@ -5,10 +5,10 @@ Require python3
Set Shell "zsh" Set Shell "zsh"
Set FontSize 18 Set FontSize 18
Set Width 1480 Set Width 1400
Set Height 900 Set Height 860
Set Theme "Dracula" Set Theme "Dracula"
Set TypingSpeed 34ms Set TypingSpeed 35ms
Set Padding 24 Set Padding 24
Set WindowBar Colorful Set WindowBar Colorful
@ -25,68 +25,62 @@ Type "alias pyro='uv run pyro'"
Enter Enter
Type "SEED_DIR=$(mktemp -d)" Type "SEED_DIR=$(mktemp -d)"
Enter Enter
Type "SYNC_DIR=$(mktemp -d)"
Enter
Type "EXPORT_DIR=$(mktemp -d)" Type "EXPORT_DIR=$(mktemp -d)"
Enter Enter
Type 'printf "%s\n" "hello from seed" > "$SEED_DIR/note.txt"' Type 'printf "%s\n" "hello from seed" > "$SEED_DIR/note.txt"'
Enter Enter
Type 'printf "%s\n" "--- a/note.txt" "+++ b/note.txt" "@@ -1 +1 @@" "-hello from seed" "+hello from patch" > "$SEED_DIR/fix.patch"' Type 'printf "%s\n" "hello from sync" > "$SYNC_DIR/note.txt"'
Enter
Type 'printf "%s\n" "temporary drift" > "$SEED_DIR/drift.txt"'
Enter Enter
Type "pyro env pull debian:12 >/dev/null" Type "pyro env pull debian:12 >/dev/null"
Enter Enter
Show Show
Type "# Create a named workspace from host content" Type "# Create a stable workspace from host content and capture its id"
Enter Enter
Sleep 700ms Sleep 700ms
Type 'WORKSPACE_ID="$(pyro workspace create debian:12 --seed-path "$SEED_DIR" --name repro-fix --label issue=123 --id-only)"' Type 'pyro workspace create debian:12 --seed-path "$SEED_DIR" --json | tee /tmp/pyro-workspace.json'
Enter Enter
Sleep 500ms Sleep 2200ms
Type 'echo "$WORKSPACE_ID"'
Enter
Sleep 1600ms
Type "# Inspect the seeded file, then patch it without shell quoting" Hide
Type 'export WORKSPACE_ID=$(python3 -c "import json; print(json.load(open(\"/tmp/pyro-workspace.json\", encoding=\"utf-8\"))[\"workspace_id\"])")'
Enter
Show
Type "# Push a later host-side change into the same workspace"
Enter Enter
Sleep 700ms Sleep 700ms
Type 'pyro workspace file read "$WORKSPACE_ID" note.txt --content-only' Type 'pyro workspace sync push "$WORKSPACE_ID" "$SYNC_DIR"'
Enter
Sleep 1400ms
Type 'pyro workspace patch apply "$WORKSPACE_ID" --patch-file "$SEED_DIR/fix.patch"'
Enter Enter
Sleep 1800ms Sleep 1800ms
Type "# Run inside the live workspace"
Enter
Sleep 700ms
Type 'pyro workspace exec "$WORKSPACE_ID" -- cat note.txt' Type 'pyro workspace exec "$WORKSPACE_ID" -- cat note.txt'
Enter Enter
Sleep 1800ms Sleep 2000ms
Type "# Capture a checkpoint, then drift away from it" Type "# Capture a checkpoint, then start one long-running service"
Enter Enter
Sleep 700ms Sleep 700ms
Type 'pyro workspace snapshot create "$WORKSPACE_ID" checkpoint' Type 'pyro workspace snapshot create "$WORKSPACE_ID" checkpoint'
Enter Enter
Sleep 1600ms
Type 'pyro workspace file write "$WORKSPACE_ID" note.txt --text-file "$SEED_DIR/drift.txt"'
Enter
Sleep 1800ms Sleep 1800ms
Type 'pyro workspace exec "$WORKSPACE_ID" -- cat note.txt'
Enter
Sleep 1800ms
Type "# Start one service, then reset the whole sandbox to the checkpoint"
Enter
Sleep 700ms
Type 'pyro workspace service start "$WORKSPACE_ID" web --ready-file .web-ready -- sh -lc "touch .web-ready && while true; do sleep 60; done"' Type 'pyro workspace service start "$WORKSPACE_ID" web --ready-file .web-ready -- sh -lc "touch .web-ready && while true; do sleep 60; done"'
Enter Enter
Sleep 2200ms Sleep 2200ms
Type "# Reset the full sandbox back to that checkpoint"
Enter
Sleep 700ms
Type 'pyro workspace reset "$WORKSPACE_ID" --snapshot checkpoint' Type 'pyro workspace reset "$WORKSPACE_ID" --snapshot checkpoint'
Enter Enter
Sleep 2200ms Sleep 2200ms
Type 'pyro workspace exec "$WORKSPACE_ID" -- cat note.txt'
Enter
Sleep 1800ms
Type "# Export the recovered file back to the host" Type "# Export one file back to the host and inspect it locally"
Enter Enter
Sleep 700ms Sleep 700ms
Type 'pyro workspace export "$WORKSPACE_ID" note.txt --output "$EXPORT_DIR/note.txt"' Type 'pyro workspace export "$WORKSPACE_ID" note.txt --output "$EXPORT_DIR/note.txt"'
@ -94,7 +88,7 @@ Enter
Sleep 1800ms Sleep 1800ms
Type 'cat "$EXPORT_DIR/note.txt"' Type 'cat "$EXPORT_DIR/note.txt"'
Enter Enter
Sleep 1600ms Sleep 1800ms
Type "# Remove the workspace when the loop is done" Type "# Remove the workspace when the loop is done"
Enter Enter

View file

@ -22,7 +22,7 @@ Networking: tun=yes ip_forward=yes
```bash ```bash
$ uvx --from pyro-mcp pyro env list $ uvx --from pyro-mcp pyro env list
Catalog version: 4.0.0 Catalog version: 3.11.0
debian:12 [installed|not installed] Debian 12 environment with Git preinstalled for common agent workflows. debian:12 [installed|not installed] Debian 12 environment with Git preinstalled for common agent workflows.
debian:12-base [installed|not installed] Minimal Debian 12 environment for shell and core Unix tooling. debian:12-base [installed|not installed] Minimal Debian 12 environment for shell and core Unix tooling.
debian:12-build [installed|not installed] Debian 12 environment with Git and common build tools preinstalled. debian:12-build [installed|not installed] Debian 12 environment with Git and common build tools preinstalled.
@ -115,13 +115,12 @@ $ uvx --from pyro-mcp pyro workspace shell open WORKSPACE_ID --secret-env API_TO
$ uvx --from pyro-mcp pyro workspace service start WORKSPACE_ID app --secret-env API_TOKEN --ready-file .ready -- sh -lc 'touch .ready && while true; do sleep 60; done' $ uvx --from pyro-mcp pyro workspace service start WORKSPACE_ID app --secret-env API_TOKEN --ready-file .ready -- sh -lc 'touch .ready && while true; do sleep 60; done'
$ uvx --from pyro-mcp pyro workspace create debian:12 --network-policy egress+published-ports $ uvx --from pyro-mcp pyro workspace create debian:12 --network-policy egress+published-ports
$ uvx --from pyro-mcp pyro workspace service start WORKSPACE_ID app --ready-http http://127.0.0.1:8080/ --publish 18080:8080 -- ./start-app $ uvx --from pyro-mcp pyro workspace service start WORKSPACE_ID app --ready-http http://127.0.0.1:8080/ --publish 18080:8080 -- ./start-app
$ uvx --from pyro-mcp pyro mcp serve $ uvx --from pyro-mcp pyro mcp serve --profile workspace-core
$ claude mcp add pyro -- uvx --from pyro-mcp pyro mcp serve $ claude mcp add pyro -- uvx --from pyro-mcp pyro mcp serve --profile workspace-core
$ codex mcp add pyro -- uvx --from pyro-mcp pyro mcp serve $ codex mcp add pyro -- uvx --from pyro-mcp pyro mcp serve --profile workspace-core
``` ```
For most chat hosts, bare `pyro mcp serve` now starts `workspace-core`, the For most chat hosts, `workspace-core` is the recommended first MCP profile.
recommended first MCP profile.
Move to `workspace-full` only when the host truly needs shells, services, Move to `workspace-full` only when the host truly needs shells, services,
snapshots, secrets, network policy, or disk tools. snapshots, secrets, network policy, or disk tools.
@ -269,7 +268,7 @@ State: started
Use `--seed-path` when the workspace should start from a host directory or a local Use `--seed-path` when the workspace should start from a host directory or a local
`.tar` / `.tar.gz` / `.tgz` archive instead of an empty `/workspace`. Use `.tar` / `.tar.gz` / `.tgz` archive instead of an empty `/workspace`. Use
`pyro workspace sync push` when you need to import later host-side changes into a started `pyro workspace sync push` when you need to import later host-side changes into a started
workspace. Sync is non-atomic in `4.0.0`; if it fails partway through, prefer `pyro workspace reset` workspace. Sync is non-atomic in `3.11.0`; if it fails partway through, prefer `pyro workspace reset`
to recover from `baseline` or one named snapshot. Use `pyro workspace diff` to compare the current to recover from `baseline` or one named snapshot. Use `pyro workspace diff` to compare the current
`/workspace` tree to its immutable create-time baseline, `pyro workspace snapshot *` to create `/workspace` tree to its immutable create-time baseline, `pyro workspace snapshot *` to create
named checkpoints, and `pyro workspace export` to copy one changed file or directory back to the named checkpoints, and `pyro workspace export` to copy one changed file or directory back to the

View file

@ -85,7 +85,7 @@ uvx --from pyro-mcp pyro env list
Expected output: Expected output:
```bash ```bash
Catalog version: 4.0.0 Catalog version: 3.11.0
debian:12 [installed|not installed] Debian 12 environment with Git preinstalled for common agent workflows. debian:12 [installed|not installed] Debian 12 environment with Git preinstalled for common agent workflows.
debian:12-base [installed|not installed] Minimal Debian 12 environment for shell and core Unix tooling. debian:12-base [installed|not installed] Minimal Debian 12 environment for shell and core Unix tooling.
debian:12-build [installed|not installed] Debian 12 environment with Git and common build tools preinstalled. debian:12-build [installed|not installed] Debian 12 environment with Git and common build tools preinstalled.
@ -231,17 +231,16 @@ After the CLI path works, you can move on to:
- interactive shells: `pyro workspace shell open WORKSPACE_ID --id-only` - interactive shells: `pyro workspace shell open WORKSPACE_ID --id-only`
- long-running services: `pyro workspace service start WORKSPACE_ID app --ready-file .ready -- sh -lc 'touch .ready && while true; do sleep 60; done'` - long-running services: `pyro workspace service start WORKSPACE_ID app --ready-file .ready -- sh -lc 'touch .ready && while true; do sleep 60; done'`
- localhost-published ports: `pyro workspace create debian:12 --network-policy egress+published-ports` and `pyro workspace service start WORKSPACE_ID app --ready-http http://127.0.0.1:8080/ --publish 18080:8080 -- ./start-app` - localhost-published ports: `pyro workspace create debian:12 --network-policy egress+published-ports` and `pyro workspace service start WORKSPACE_ID app --ready-http http://127.0.0.1:8080/ --publish 18080:8080 -- ./start-app`
- MCP: `pyro mcp serve` - MCP: `pyro mcp serve --profile workspace-core`
- Python SDK: `from pyro_mcp import Pyro` - Python SDK: `from pyro_mcp import Pyro`
- Demos: `pyro demo` or `pyro demo --network` - Demos: `pyro demo` or `pyro demo --network`
## Chat Host Quickstart ## Chat Host Quickstart
For most chat-host integrations, bare `pyro mcp serve` now starts For most chat-host integrations, start with `workspace-core`:
`workspace-core`:
```bash ```bash
uvx --from pyro-mcp pyro mcp serve uvx --from pyro-mcp pyro mcp serve --profile workspace-core
``` ```
Copy-paste host-specific starts: Copy-paste host-specific starts:
@ -254,27 +253,25 @@ Copy-paste host-specific starts:
Claude Code: Claude Code:
```bash ```bash
claude mcp add pyro -- uvx --from pyro-mcp pyro mcp serve claude mcp add pyro -- uvx --from pyro-mcp pyro mcp serve --profile workspace-core
``` ```
Codex: Codex:
```bash ```bash
codex mcp add pyro -- uvx --from pyro-mcp pyro mcp serve codex mcp add pyro -- uvx --from pyro-mcp pyro mcp serve --profile workspace-core
``` ```
OpenCode uses the `mcp`/`type: "local"` config shape shown in OpenCode uses the `mcp`/`type: "local"` config shape shown in
[examples/opencode_mcp_config.json](../examples/opencode_mcp_config.json). If [examples/opencode_mcp_config.json](../examples/opencode_mcp_config.json). If
`pyro-mcp` is already installed, replace the `uvx --from pyro-mcp pyro` `pyro-mcp` is already installed, replace the `uvx --from pyro-mcp pyro`
command with `pyro` in the same host-specific command or config shape. Use command with `pyro` in the same host-specific command or config shape.
`--profile workspace-full` only when the host truly needs the full advanced
workspace surface.
Use profile progression like this: Use profile progression like this:
- `workspace-core`: default and recommended first profile for normal persistent chat editing - `workspace-core`: recommended first profile for normal persistent chat editing
- `vm-run`: one-shot-only integrations - `vm-run`: one-shot-only integrations
- `workspace-full`: explicit advanced opt-in when the host truly needs shells, services, snapshots, secrets, network policy, or disk tools - `workspace-full`: advanced 3.x compatibility surface when the host truly needs shells, services, snapshots, secrets, network policy, or disk tools
## Stable Workspace ## Stable Workspace
@ -323,7 +320,7 @@ the identifier programmatically, use `--id-only` for only the identifier or `--j
workspace payload. Use `--seed-path` workspace payload. Use `--seed-path`
when the workspace should start from a host directory or a local `.tar` / `.tar.gz` / `.tgz` when the workspace should start from a host directory or a local `.tar` / `.tar.gz` / `.tgz`
archive. Use `pyro workspace sync push` for later host-side changes to a started workspace. Sync archive. Use `pyro workspace sync push` for later host-side changes to a started workspace. Sync
is non-atomic in `4.0.0`; if it fails partway through, prefer `pyro workspace reset` to recover is non-atomic in `3.11.0`; if it fails partway through, prefer `pyro workspace reset` to recover
from `baseline` or one named snapshot. Use `pyro workspace diff` to compare the current workspace from `baseline` or one named snapshot. Use `pyro workspace diff` to compare the current workspace
tree to its immutable create-time baseline, `pyro workspace snapshot *` to capture named tree to its immutable create-time baseline, `pyro workspace snapshot *` to capture named
checkpoints, and `pyro workspace export` to copy one changed file or directory back to the host. Use checkpoints, and `pyro workspace export` to copy one changed file or directory back to the host. Use

View file

@ -7,7 +7,7 @@ CLI path in [install.md](install.md) or [first-run.md](first-run.md).
## Recommended Default ## Recommended Default
Bare `pyro mcp serve` now starts `workspace-core`. Use `vm_run` only for one-shot Start most chat hosts with `workspace-core`. Use `vm_run` only for one-shot
integrations, and promote the chat surface to `workspace-full` only when it integrations, and promote the chat surface to `workspace-full` only when it
truly needs shells, services, snapshots, secrets, network policy, or disk truly needs shells, services, snapshots, secrets, network policy, or disk
tools. tools.
@ -21,7 +21,7 @@ That keeps the model-facing contract small:
Profile progression: Profile progression:
- `workspace-core`: default and recommended first profile for persistent chat editing - `workspace-core`: recommended first profile for persistent chat editing
- `vm-run`: one-shot only - `vm-run`: one-shot only
- `workspace-full`: the full stable workspace surface, including shells, services, snapshots, secrets, network policy, and disk tools - `workspace-full`: the full stable workspace surface, including shells, services, snapshots, secrets, network policy, and disk tools
@ -55,12 +55,12 @@ Best when:
Recommended entrypoint: Recommended entrypoint:
- `pyro mcp serve` - `pyro mcp serve --profile workspace-core`
Profile progression: Profile progression:
- `pyro mcp serve --profile vm-run` for the smallest one-shot surface - `pyro mcp serve --profile vm-run` for the smallest one-shot surface
- `pyro mcp serve` for the normal persistent chat loop - `pyro mcp serve --profile workspace-core` for the normal persistent chat loop
- `pyro mcp serve --profile workspace-full` only when the model truly needs advanced workspace tools - `pyro mcp serve --profile workspace-full` only when the model truly needs advanced workspace tools
Host-specific onramps: Host-specific onramps:
@ -84,7 +84,7 @@ Best when:
Recommended default: Recommended default:
- `Pyro.run_in_vm(...)` - `Pyro.run_in_vm(...)`
- `Pyro.create_server()` for most chat hosts now that `workspace-core` is the default profile - `Pyro.create_server(profile="workspace-core")` for most chat hosts
- `Pyro.create_workspace(name=..., labels=...)` + `Pyro.list_workspaces()` + `Pyro.update_workspace(...)` when repeated workspaces need human-friendly discovery metadata - `Pyro.create_workspace(name=..., labels=...)` + `Pyro.list_workspaces()` + `Pyro.update_workspace(...)` when repeated workspaces need human-friendly discovery metadata
- `Pyro.create_workspace(seed_path=...)` + `Pyro.push_workspace_sync(...)` + `Pyro.exec_workspace(...)` when repeated workspace commands are required - `Pyro.create_workspace(seed_path=...)` + `Pyro.push_workspace_sync(...)` + `Pyro.exec_workspace(...)` when repeated workspace commands are required
- `Pyro.list_workspace_files(...)` / `Pyro.read_workspace_file(...)` / `Pyro.write_workspace_file(...)` / `Pyro.apply_workspace_patch(...)` when the agent needs model-native file inspection and text edits inside one live workspace - `Pyro.list_workspace_files(...)` / `Pyro.read_workspace_file(...)` / `Pyro.write_workspace_file(...)` / `Pyro.apply_workspace_patch(...)` when the agent needs model-native file inspection and text edits inside one live workspace

View file

@ -129,8 +129,8 @@ Primary facade:
Supported public entrypoints: Supported public entrypoints:
- `create_server()` - `create_server(profile="workspace-full")`
- `Pyro.create_server()` - `Pyro.create_server(profile="workspace-full")`
- `Pyro.list_environments()` - `Pyro.list_environments()`
- `Pyro.pull_environment(environment)` - `Pyro.pull_environment(environment)`
- `Pyro.inspect_environment(environment)` - `Pyro.inspect_environment(environment)`
@ -180,7 +180,7 @@ Supported public entrypoints:
Stable public method names: Stable public method names:
- `create_server()` - `create_server(profile="workspace-full")`
- `list_environments()` - `list_environments()`
- `pull_environment(environment)` - `pull_environment(environment)`
- `inspect_environment(environment)` - `inspect_environment(environment)`
@ -277,9 +277,9 @@ Stable MCP profiles:
Behavioral defaults: Behavioral defaults:
- `pyro mcp serve`, `create_server()`, and `Pyro.create_server()` default to `workspace-core`. - `pyro mcp serve` and `create_server()` default to `workspace-full` for 3.x compatibility.
- `workspace-core` is the default and recommended first profile for most new chat-host integrations. - `workspace-core` is the recommended first profile for most new chat-host integrations.
- `create_server(profile="workspace-full")` and `Pyro.create_server(profile="workspace-full")` opt into the full advanced workspace surface explicitly. - `create_server(profile="workspace-core")` and `Pyro.create_server(profile="workspace-core")` are the recommended entrypoints for most new chat-host integrations.
- `workspace-core` narrows `workspace_create` by omitting `network_policy` and `secrets`. - `workspace-core` narrows `workspace_create` by omitting `network_policy` and `secrets`.
- `workspace-core` narrows `workspace_exec` by omitting `secret_env`. - `workspace-core` narrows `workspace_exec` by omitting `secret_env`.

View file

@ -6,7 +6,7 @@ goal:
make the core agent-workspace use cases feel trivial from a chat-driven LLM make the core agent-workspace use cases feel trivial from a chat-driven LLM
interface. interface.
Current baseline is `4.0.0`: Current baseline is `3.11.0`:
- the stable workspace contract exists across CLI, SDK, and MCP - the stable workspace contract exists across CLI, SDK, and MCP
- one-shot `pyro run` still exists as the narrow entrypoint - one-shot `pyro run` still exists as the narrow entrypoint
@ -35,7 +35,10 @@ More concretely, the model should not need to:
The remaining UX friction for a technically strong new user is now narrower: The remaining UX friction for a technically strong new user is now narrower:
- no major chat-host ergonomics gaps remain in the current roadmap - the generic MCP guidance is strong, but Codex and OpenCode still ask the user
to translate the generic config into host-specific setup steps
- `workspace-core` is clearly the recommended profile, but `pyro mcp serve` and
`create_server()` still default to `workspace-full` for `3.x` compatibility
## Locked Decisions ## Locked Decisions
@ -61,7 +64,7 @@ The remaining UX friction for a technically strong new user is now narrower:
8. [`3.9.0` Content-Only Reads And Human Output Polish](llm-chat-ergonomics/3.9.0-content-only-reads-and-human-output-polish.md) - Done 8. [`3.9.0` Content-Only Reads And Human Output Polish](llm-chat-ergonomics/3.9.0-content-only-reads-and-human-output-polish.md) - Done
9. [`3.10.0` Use-Case Smoke Trust And Recipe Fidelity](llm-chat-ergonomics/3.10.0-use-case-smoke-trust-and-recipe-fidelity.md) - Done 9. [`3.10.0` Use-Case Smoke Trust And Recipe Fidelity](llm-chat-ergonomics/3.10.0-use-case-smoke-trust-and-recipe-fidelity.md) - Done
10. [`3.11.0` Host-Specific MCP Onramps](llm-chat-ergonomics/3.11.0-host-specific-mcp-onramps.md) - Done 10. [`3.11.0` Host-Specific MCP Onramps](llm-chat-ergonomics/3.11.0-host-specific-mcp-onramps.md) - Done
11. [`4.0.0` Workspace-Core Default Profile](llm-chat-ergonomics/4.0.0-workspace-core-default-profile.md) - Done 11. [`4.0.0` Workspace-Core Default Profile](llm-chat-ergonomics/4.0.0-workspace-core-default-profile.md)
Completed so far: Completed so far:
@ -90,12 +93,13 @@ Completed so far:
- `3.11.0` added exact host-specific MCP onramps for Claude Code, Codex, and OpenCode so new - `3.11.0` added exact host-specific MCP onramps for Claude Code, Codex, and OpenCode so new
chat-host users can copy one known-good setup example instead of translating the generic MCP chat-host users can copy one known-good setup example instead of translating the generic MCP
config manually. config manually.
- `4.0.0` flipped the default MCP/server profile to `workspace-core`, so the bare entrypoint now
matches the recommended narrow chat-host profile across CLI, SDK, and package-level factories.
Planned next: Planned next:
- no further chat-ergonomics milestones are currently planned in this roadmap. - `4.0.0` flips the default MCP profile from `workspace-full` to
`workspace-core` so the no-flag server entrypoint finally matches the
recommended docs path, while keeping explicit opt-in access to the full
advanced surface.
## Expected Outcome ## Expected Outcome

View file

@ -1,6 +1,6 @@
# `4.0.0` Workspace-Core Default Profile # `4.0.0` Workspace-Core Default Profile
Status: Done Status: Planned
## Goal ## Goal

View file

@ -5,20 +5,16 @@ Recommended profile: `workspace-core`.
Package without install: Package without install:
```bash ```bash
claude mcp add pyro -- uvx --from pyro-mcp pyro mcp serve claude mcp add pyro -- uvx --from pyro-mcp pyro mcp serve --profile workspace-core
claude mcp list claude mcp list
``` ```
Already installed: Already installed:
```bash ```bash
claude mcp add pyro -- pyro mcp serve claude mcp add pyro -- pyro mcp serve --profile workspace-core
claude mcp list claude mcp list
``` ```
Move to `workspace-full` only when the chat truly needs shells, services, Move to `workspace-full` only when the chat truly needs shells, services,
snapshots, secrets, network policy, or disk tools: snapshots, secrets, network policy, or disk tools.
```bash
claude mcp add pyro -- uvx --from pyro-mcp pyro mcp serve --profile workspace-full
```

View file

@ -2,7 +2,7 @@
"mcpServers": { "mcpServers": {
"pyro": { "pyro": {
"command": "uvx", "command": "uvx",
"args": ["--from", "pyro-mcp", "pyro", "mcp", "serve"] "args": ["--from", "pyro-mcp", "pyro", "mcp", "serve", "--profile", "workspace-core"]
} }
} }
} }

View file

@ -5,20 +5,16 @@ Recommended profile: `workspace-core`.
Package without install: Package without install:
```bash ```bash
codex mcp add pyro -- uvx --from pyro-mcp pyro mcp serve codex mcp add pyro -- uvx --from pyro-mcp pyro mcp serve --profile workspace-core
codex mcp list codex mcp list
``` ```
Already installed: Already installed:
```bash ```bash
codex mcp add pyro -- pyro mcp serve codex mcp add pyro -- pyro mcp serve --profile workspace-core
codex mcp list codex mcp list
``` ```
Move to `workspace-full` only when the chat truly needs shells, services, Move to `workspace-full` only when the chat truly needs shells, services,
snapshots, secrets, network policy, or disk tools: snapshots, secrets, network policy, or disk tools.
```bash
codex mcp add pyro -- uvx --from pyro-mcp pyro mcp serve --profile workspace-full
```

View file

@ -2,7 +2,7 @@
"mcpServers": { "mcpServers": {
"pyro": { "pyro": {
"command": "uvx", "command": "uvx",
"args": ["--from", "pyro-mcp", "pyro", "mcp", "serve"] "args": ["--from", "pyro-mcp", "pyro", "mcp", "serve", "--profile", "workspace-core"]
} }
} }
} }

View file

@ -1,6 +1,6 @@
# MCP Client Config Example # MCP Client Config Example
Default for most chat hosts in `4.x`: `workspace-core`. Recommended default for most chat hosts: `workspace-core`.
Use the host-specific examples first when they apply: Use the host-specific examples first when they apply:
@ -20,7 +20,7 @@ Generic stdio MCP configuration using `uvx`:
"mcpServers": { "mcpServers": {
"pyro": { "pyro": {
"command": "uvx", "command": "uvx",
"args": ["--from", "pyro-mcp", "pyro", "mcp", "serve"] "args": ["--from", "pyro-mcp", "pyro", "mcp", "serve", "--profile", "workspace-core"]
} }
} }
} }
@ -33,7 +33,7 @@ If `pyro-mcp` is already installed locally, the same server can be configured wi
"mcpServers": { "mcpServers": {
"pyro": { "pyro": {
"command": "pyro", "command": "pyro",
"args": ["mcp", "serve"] "args": ["mcp", "serve", "--profile", "workspace-core"]
} }
} }
} }
@ -41,9 +41,9 @@ If `pyro-mcp` is already installed locally, the same server can be configured wi
Profile progression: Profile progression:
- `workspace-core`: the default and recommended first persistent chat profile - `workspace-core`: the recommended first persistent chat profile
- `vm-run`: expose only `vm_run` - `vm-run`: expose only `vm_run`
- `workspace-full`: explicit advanced opt-in for shells, services, snapshots, secrets, network policy, and disk tools - `workspace-full`: advanced 3.x compatibility surface for shells, services, snapshots, secrets, network policy, and disk tools
Primary profile for most agents: Primary profile for most agents:

View file

@ -4,10 +4,10 @@ Requirements:
- `pip install openai` or `uv add openai` - `pip install openai` or `uv add openai`
- `OPENAI_API_KEY` - `OPENAI_API_KEY`
This is the recommended persistent-chat example. In 4.x the default MCP server This is the recommended persistent-chat example. It mirrors the
profile is already `workspace-core`, so it derives tool schemas from `workspace-core` MCP profile by deriving tool schemas from
`Pyro.create_server()` and dispatches tool calls back through that same `Pyro.create_server(profile="workspace-core")` and dispatching tool calls back
default-profile server. through that same profiled server.
""" """
from __future__ import annotations from __future__ import annotations
@ -45,7 +45,7 @@ async def run_openai_workspace_core_example(*, prompt: str, model: str = DEFAULT
from openai import OpenAI # type: ignore[import-not-found] from openai import OpenAI # type: ignore[import-not-found]
pyro = Pyro() pyro = Pyro()
server = pyro.create_server() server = pyro.create_server(profile="workspace-core")
tools = [_tool_to_openai(tool) for tool in await server.list_tools()] tools = [_tool_to_openai(tool) for tool in await server.list_tools()]
client = OpenAI() client = OpenAI()
input_items: list[dict[str, Any]] = [{"role": "user", "content": prompt}] input_items: list[dict[str, Any]] = [{"role": "user", "content": prompt}]

View file

@ -3,7 +3,7 @@
"pyro": { "pyro": {
"type": "local", "type": "local",
"enabled": true, "enabled": true,
"command": ["uvx", "--from", "pyro-mcp", "pyro", "mcp", "serve"] "command": ["uvx", "--from", "pyro-mcp", "pyro", "mcp", "serve", "--profile", "workspace-core"]
} }
} }
} }

View file

@ -1,6 +1,6 @@
[project] [project]
name = "pyro-mcp" name = "pyro-mcp"
version = "4.0.0" version = "3.11.0"
description = "Stable Firecracker workspaces, one-shot sandboxes, and MCP tools for coding agents." description = "Stable Firecracker workspaces, one-shot sandboxes, and MCP tools for coding agents."
readme = "README.md" readme = "README.md"
license = { file = "LICENSE" } license = { file = "LICENSE" }

View file

@ -462,12 +462,11 @@ class Pyro:
allow_host_compat=allow_host_compat, allow_host_compat=allow_host_compat,
) )
def create_server(self, *, profile: McpToolProfile = "workspace-core") -> FastMCP: def create_server(self, *, profile: McpToolProfile = "workspace-full") -> FastMCP:
"""Create an MCP server for one of the stable public tool profiles. """Create an MCP server for one of the stable public tool profiles.
`workspace-core` is the default stable chat-host profile in 4.x. Use `workspace-full` remains the default for 3.x compatibility. New chat
`profile="workspace-full"` only when the host truly needs the full hosts should usually start with `profile="workspace-core"`.
advanced workspace surface.
""" """
normalized_profile = _validate_mcp_profile(profile) normalized_profile = _validate_mcp_profile(profile)
enabled_tools = set(_PROFILE_TOOLS[normalized_profile]) enabled_tools = set(_PROFILE_TOOLS[normalized_profile])

View file

@ -760,13 +760,13 @@ def _build_parser() -> argparse.ArgumentParser:
help="Run the MCP server.", help="Run the MCP server.",
description=( description=(
"Run the MCP server after you have already validated the host and " "Run the MCP server after you have already validated the host and "
"guest execution with `pyro doctor` and `pyro run`. Bare `pyro " "guest execution with `pyro doctor` and `pyro run`. Start most "
"mcp serve` now starts the recommended `workspace-core` profile." "chat hosts with `workspace-core`."
), ),
epilog=dedent( epilog=dedent(
""" """
Examples: Examples:
pyro mcp serve pyro mcp serve --profile workspace-core
pyro mcp serve --profile vm-run pyro mcp serve --profile vm-run
pyro mcp serve --profile workspace-full pyro mcp serve --profile workspace-full
""" """
@ -778,23 +778,22 @@ def _build_parser() -> argparse.ArgumentParser:
"serve", "serve",
help="Run the MCP server over stdio.", help="Run the MCP server over stdio.",
description=( description=(
"Expose pyro tools over stdio for an MCP client. Bare `pyro mcp " "Expose pyro tools over stdio for an MCP client. "
"serve` now starts `workspace-core`, the recommended first profile " "`workspace-core` is the recommended first profile for most chat hosts."
"for most chat hosts."
), ),
epilog=dedent( epilog=dedent(
""" """
Default and recommended first start: Recommended first start:
pyro mcp serve pyro mcp serve --profile workspace-core
Profiles: Profiles:
workspace-core: default for normal persistent chat editing workspace-core: recommended default for normal persistent chat editing
vm-run: smallest one-shot-only surface vm-run: smallest one-shot-only surface
workspace-full: advanced 4.x opt-in surface for shells, services, workspace-full: advanced 3.x compatibility surface for shells, services,
snapshots, secrets, network policy, and disk tools snapshots, secrets, network policy, and disk tools
Use --profile workspace-full only when the host truly needs the full `workspace-full` remains the default in 3.x for compatibility, but most new
advanced workspace surface. chat hosts should start with `workspace-core`.
""" """
), ),
formatter_class=_HelpFormatter, formatter_class=_HelpFormatter,
@ -802,11 +801,11 @@ def _build_parser() -> argparse.ArgumentParser:
mcp_serve_parser.add_argument( mcp_serve_parser.add_argument(
"--profile", "--profile",
choices=PUBLIC_MCP_PROFILES, choices=PUBLIC_MCP_PROFILES,
default="workspace-core", default="workspace-full",
help=( help=(
"Expose only one model-facing tool profile. `workspace-core` is " "Expose only one model-facing tool profile. `workspace-core` is the "
"the default and recommended first profile for most chat hosts; " "recommended first profile for most chat hosts; `workspace-full` "
"`workspace-full` is the explicit advanced opt-in surface." "preserves the current full MCP surface for 3.x compatibility."
), ),
) )

View file

@ -11,13 +11,12 @@ from pyro_mcp.vm_manager import VmManager
def create_server( def create_server(
manager: VmManager | None = None, manager: VmManager | None = None,
*, *,
profile: McpToolProfile = "workspace-core", profile: McpToolProfile = "workspace-full",
) -> FastMCP: ) -> FastMCP:
"""Create and return a configured MCP server instance. """Create and return a configured MCP server instance.
`workspace-core` is the default stable chat-host profile in 4.x. Use `workspace-full` remains the default for 3.x compatibility. New chat hosts
`profile="workspace-full"` only when the host truly needs the full should usually start with `profile="workspace-core"`.
advanced workspace surface.
""" """
return Pyro(manager=manager).create_server(profile=profile) return Pyro(manager=manager).create_server(profile=profile)

View file

@ -19,7 +19,7 @@ from typing import Any
from pyro_mcp.runtime import DEFAULT_PLATFORM, RuntimePaths from pyro_mcp.runtime import DEFAULT_PLATFORM, RuntimePaths
DEFAULT_ENVIRONMENT_VERSION = "1.0.0" DEFAULT_ENVIRONMENT_VERSION = "1.0.0"
DEFAULT_CATALOG_VERSION = "4.0.0" DEFAULT_CATALOG_VERSION = "3.11.0"
OCI_MANIFEST_ACCEPT = ", ".join( OCI_MANIFEST_ACCEPT = ", ".join(
( (
"application/vnd.oci.image.index.v1+json", "application/vnd.oci.image.index.v1+json",
@ -48,7 +48,7 @@ class VmEnvironment:
oci_repository: str | None = None oci_repository: str | None = None
oci_reference: str | None = None oci_reference: str | None = None
source_digest: str | None = None source_digest: str | None = None
compatibility: str = ">=4.0.0,<5.0.0" compatibility: str = ">=3.0.0,<4.0.0"
@dataclass(frozen=True) @dataclass(frozen=True)

View file

@ -37,7 +37,7 @@ def test_pyro_run_in_vm_delegates_to_manager(tmp_path: Path) -> None:
assert str(result["stdout"]) == "ok\n" assert str(result["stdout"]) == "ok\n"
def test_pyro_create_server_defaults_to_workspace_core_profile(tmp_path: Path) -> None: def test_pyro_create_server_registers_full_profile_and_shell_read_schema(tmp_path: Path) -> None:
pyro = Pyro( pyro = Pyro(
manager=VmManager( manager=VmManager(
backend_name="mock", backend_name="mock",
@ -52,34 +52,6 @@ def test_pyro_create_server_defaults_to_workspace_core_profile(tmp_path: Path) -
tool_map = {tool.name: tool.model_dump() for tool in tools} tool_map = {tool.name: tool.model_dump() for tool in tools}
return sorted(tool_map), tool_map return sorted(tool_map), tool_map
tool_names, tool_map = asyncio.run(_run())
assert tuple(tool_names) == tuple(sorted(PUBLIC_MCP_WORKSPACE_CORE_PROFILE_TOOLS))
create_properties = tool_map["workspace_create"]["inputSchema"]["properties"]
assert "network_policy" not in create_properties
assert "secrets" not in create_properties
exec_properties = tool_map["workspace_exec"]["inputSchema"]["properties"]
assert "secret_env" not in exec_properties
assert "shell_open" not in tool_map
assert "service_start" not in tool_map
assert "snapshot_create" not in tool_map
assert "workspace_disk_export" not in tool_map
def test_pyro_create_server_workspace_full_profile_keeps_shell_read_schema(tmp_path: Path) -> None:
pyro = Pyro(
manager=VmManager(
backend_name="mock",
base_dir=tmp_path / "vms",
network_manager=TapNetworkManager(enabled=False),
)
)
async def _run() -> tuple[list[str], dict[str, dict[str, Any]]]:
server = pyro.create_server(profile="workspace-full")
tools = await server.list_tools()
tool_map = {tool.name: tool.model_dump() for tool in tools}
return sorted(tool_map), tool_map
tool_names, tool_map = asyncio.run(_run()) tool_names, tool_map = asyncio.run(_run())
assert tuple(tool_names) == tuple(sorted(PUBLIC_MCP_WORKSPACE_FULL_PROFILE_TOOLS)) assert tuple(tool_names) == tuple(sorted(PUBLIC_MCP_WORKSPACE_FULL_PROFILE_TOOLS))
shell_read_properties = tool_map["shell_read"]["inputSchema"]["properties"] shell_read_properties = tool_map["shell_read"]["inputSchema"]["properties"]
@ -596,7 +568,7 @@ def test_pyro_create_server_workspace_disk_tools_delegate() -> None:
return cast(dict[str, Any], structured) return cast(dict[str, Any], structured)
async def _run() -> tuple[dict[str, Any], ...]: async def _run() -> tuple[dict[str, Any], ...]:
server = pyro.create_server(profile="workspace-full") server = pyro.create_server()
stopped = _extract_structured( stopped = _extract_structured(
await server.call_tool("workspace_stop", {"workspace_id": "workspace-123"}) await server.call_tool("workspace_stop", {"workspace_id": "workspace-123"})
) )
@ -1106,7 +1078,7 @@ def test_pyro_create_server_workspace_status_shell_and_service_delegate() -> Non
return cast(dict[str, Any], structured) return cast(dict[str, Any], structured)
async def _run() -> tuple[dict[str, Any], ...]: async def _run() -> tuple[dict[str, Any], ...]:
server = pyro.create_server(profile="workspace-full") server = pyro.create_server()
status = _extract_structured( status = _extract_structured(
await server.call_tool("workspace_status", {"workspace_id": "workspace-123"}) await server.call_tool("workspace_status", {"workspace_id": "workspace-123"})
) )

View file

@ -68,8 +68,7 @@ def test_cli_subcommand_help_includes_examples_and_guidance() -> None:
assert "workspace-full" in mcp_help assert "workspace-full" in mcp_help
assert "vm-run" in mcp_help assert "vm-run" in mcp_help
assert "recommended first profile for most chat hosts" in mcp_help assert "recommended first profile for most chat hosts" in mcp_help
assert "workspace-core: default for normal persistent chat editing" in mcp_help assert "default in 3.x for compatibility" in mcp_help
assert "workspace-full: advanced 4.x opt-in surface" in mcp_help
workspace_help = _subparser_choice(parser, "workspace").format_help() workspace_help = _subparser_choice(parser, "workspace").format_help()
assert "stable workspace contract" in workspace_help assert "stable workspace contract" in workspace_help
@ -2814,35 +2813,36 @@ def test_chat_host_docs_and_examples_recommend_workspace_core() -> None:
claude_code = Path("examples/claude_code_mcp.md").read_text(encoding="utf-8") claude_code = Path("examples/claude_code_mcp.md").read_text(encoding="utf-8")
codex = Path("examples/codex_mcp.md").read_text(encoding="utf-8") codex = Path("examples/codex_mcp.md").read_text(encoding="utf-8")
opencode = json.loads(Path("examples/opencode_mcp_config.json").read_text(encoding="utf-8")) opencode = json.loads(Path("examples/opencode_mcp_config.json").read_text(encoding="utf-8"))
claude_cmd = "claude mcp add pyro -- uvx --from pyro-mcp pyro mcp serve" claude_cmd = (
codex_cmd = "codex mcp add pyro -- uvx --from pyro-mcp pyro mcp serve" "claude mcp add pyro -- uvx --from pyro-mcp pyro mcp serve --profile workspace-core"
)
codex_cmd = (
"codex mcp add pyro -- uvx --from pyro-mcp pyro mcp serve --profile workspace-core"
)
assert "## Chat Host Quickstart" in readme assert "## Chat Host Quickstart" in readme
assert "uvx --from pyro-mcp pyro mcp serve" in readme assert "pyro mcp serve --profile workspace-core" in readme
assert claude_cmd in readme assert claude_cmd in readme
assert codex_cmd in readme assert codex_cmd in readme
assert "examples/opencode_mcp_config.json" in readme assert "examples/opencode_mcp_config.json" in readme
assert "recommended first profile for normal persistent chat editing" in readme assert "recommended first profile for normal persistent chat editing" in readme
assert "## Chat Host Quickstart" in install assert "## Chat Host Quickstart" in install
assert "uvx --from pyro-mcp pyro mcp serve" in install assert "pyro mcp serve --profile workspace-core" in install
assert claude_cmd in install assert claude_cmd in install
assert codex_cmd in install assert codex_cmd in install
assert "workspace-full" in install assert "advanced 3.x compatibility surface" in install
assert claude_cmd in first_run assert claude_cmd in first_run
assert codex_cmd in first_run assert codex_cmd in first_run
assert "Bare `pyro mcp serve` now starts `workspace-core`." in integrations assert "Start most chat hosts with `workspace-core`." in integrations
assert "examples/claude_code_mcp.md" in integrations assert "examples/claude_code_mcp.md" in integrations
assert "examples/codex_mcp.md" in integrations assert "examples/codex_mcp.md" in integrations
assert "examples/opencode_mcp_config.json" in integrations assert "examples/opencode_mcp_config.json" in integrations
assert ( assert '`Pyro.create_server(profile="workspace-core")` for most chat hosts' in integrations
'`Pyro.create_server()` for most chat hosts now that `workspace-core` '
"is the default profile" in integrations
)
assert "Default for most chat hosts in `4.x`: `workspace-core`." in mcp_config assert "Recommended default for most chat hosts: `workspace-core`." in mcp_config
assert "Use the host-specific examples first when they apply:" in mcp_config assert "Use the host-specific examples first when they apply:" in mcp_config
assert "claude_code_mcp.md" in mcp_config assert "claude_code_mcp.md" in mcp_config
assert "codex_mcp.md" in mcp_config assert "codex_mcp.md" in mcp_config
@ -2868,6 +2868,8 @@ def test_chat_host_docs_and_examples_recommend_workspace_core() -> None:
"pyro", "pyro",
"mcp", "mcp",
"serve", "serve",
"--profile",
"workspace-core",
], ],
} }
} }

View file

@ -57,7 +57,7 @@ from pyro_mcp.contract import (
PUBLIC_CLI_WORKSPACE_SYNC_SUBCOMMANDS, PUBLIC_CLI_WORKSPACE_SYNC_SUBCOMMANDS,
PUBLIC_CLI_WORKSPACE_UPDATE_FLAGS, PUBLIC_CLI_WORKSPACE_UPDATE_FLAGS,
PUBLIC_MCP_PROFILES, PUBLIC_MCP_PROFILES,
PUBLIC_MCP_WORKSPACE_CORE_PROFILE_TOOLS, PUBLIC_MCP_TOOLS,
PUBLIC_SDK_METHODS, PUBLIC_SDK_METHODS,
) )
from pyro_mcp.vm_manager import VmManager from pyro_mcp.vm_manager import VmManager
@ -335,7 +335,7 @@ def test_public_mcp_tools_match_contract(tmp_path: Path) -> None:
return tuple(sorted(tool.name for tool in tools)) return tuple(sorted(tool.name for tool in tools))
tool_names = asyncio.run(_run()) tool_names = asyncio.run(_run())
assert tool_names == tuple(sorted(PUBLIC_MCP_WORKSPACE_CORE_PROFILE_TOOLS)) assert tool_names == tuple(sorted(PUBLIC_MCP_TOOLS))
def test_pyproject_exposes_single_public_cli_script() -> None: def test_pyproject_exposes_single_public_cli_script() -> None:

View file

@ -10,6 +10,7 @@ import pyro_mcp.server as server_module
from pyro_mcp.contract import ( from pyro_mcp.contract import (
PUBLIC_MCP_VM_RUN_PROFILE_TOOLS, PUBLIC_MCP_VM_RUN_PROFILE_TOOLS,
PUBLIC_MCP_WORKSPACE_CORE_PROFILE_TOOLS, PUBLIC_MCP_WORKSPACE_CORE_PROFILE_TOOLS,
PUBLIC_MCP_WORKSPACE_FULL_PROFILE_TOOLS,
) )
from pyro_mcp.server import create_server from pyro_mcp.server import create_server
from pyro_mcp.vm_manager import VmManager from pyro_mcp.vm_manager import VmManager
@ -29,7 +30,7 @@ def test_create_server_registers_vm_tools(tmp_path: Path) -> None:
return sorted(tool.name for tool in tools) return sorted(tool.name for tool in tools)
tool_names = asyncio.run(_run()) tool_names = asyncio.run(_run())
assert tuple(tool_names) == tuple(sorted(PUBLIC_MCP_WORKSPACE_CORE_PROFILE_TOOLS)) assert tuple(tool_names) == tuple(sorted(PUBLIC_MCP_WORKSPACE_FULL_PROFILE_TOOLS))
def test_create_server_vm_run_profile_registers_only_vm_run(tmp_path: Path) -> None: def test_create_server_vm_run_profile_registers_only_vm_run(tmp_path: Path) -> None:
@ -122,7 +123,7 @@ def test_vm_tools_status_stop_delete_and_reap(tmp_path: Path) -> None:
list[dict[str, object]], list[dict[str, object]],
dict[str, Any], dict[str, Any],
]: ]:
server = create_server(manager=manager, profile="workspace-full") server = create_server(manager=manager)
environments_raw = await server.call_tool("vm_list_environments", {}) environments_raw = await server.call_tool("vm_list_environments", {})
if not isinstance(environments_raw, tuple) or len(environments_raw) != 2: if not isinstance(environments_raw, tuple) or len(environments_raw) != 2:
raise TypeError("unexpected environments result") raise TypeError("unexpected environments result")
@ -298,7 +299,7 @@ def test_workspace_tools_round_trip(tmp_path: Path) -> None:
return cast(dict[str, Any], structured) return cast(dict[str, Any], structured)
async def _run() -> tuple[dict[str, Any], ...]: async def _run() -> tuple[dict[str, Any], ...]:
server = create_server(manager=manager, profile="workspace-full") server = create_server(manager=manager)
created = _extract_structured( created = _extract_structured(
await server.call_tool( await server.call_tool(
"workspace_create", "workspace_create",

2
uv.lock generated
View file

@ -715,7 +715,7 @@ crypto = [
[[package]] [[package]]
name = "pyro-mcp" name = "pyro-mcp"
version = "4.0.0" version = "3.11.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "mcp" }, { name = "mcp" },