Compare commits

...

2 commits

Author SHA1 Message Date
386b9793ee Refresh stable workspace walkthrough recording
Update the README walkthrough asset to match the current workspace-first flow instead of the older JSON-plus-parsing demo.

The new tape now shows the recommended 4.x handoff path: workspace create with --id-only, model-native file read and patch apply, snapshot creation, drift and full reset, service start, host export, and cleanup.

Re-render the README GIF from that tape so the embedded recording demonstrates the current product story directly.

Validation: vhs validate docs/assets/workspace-first-run.tape; scripts/render_tape.sh docs/assets/workspace-first-run.tape (rendered outside the sandbox because vhs crashes on local-port allocation inside the sandbox).
2026-03-13 14:21:33 -03:00
c00c699a9f Make workspace-core the default MCP profile
Flip bare pyro mcp serve, create_server(), and Pyro.create_server() to default to workspace-core in 4.0.0 while keeping workspace-full as the explicit advanced opt-in surface.

Rewrite the MCP-facing docs and host-specific examples around the bare default command, update package and catalog compatibility to 4.x, and move the public-contract wording from 3.x compatibility guidance to the new stable default.

Adjust the server, API, and contract tests so bare server creation now asserts the workspace-core tool set, while explicit workspace-full coverage continues to prove shells, services, snapshots, and disk tools remain available.

Validation: uv lock; .venv/bin/pytest --no-cov tests/test_cli.py tests/test_api.py tests/test_server.py tests/test_public_contract.py; UV_CACHE_DIR=.uv-cache make check; UV_CACHE_DIR=.uv-cache make dist-check; real guest-backed smoke for bare Pyro.create_server() plus explicit profile="workspace-full".
2026-03-13 14:14:15 -03:00
27 changed files with 205 additions and 150 deletions

View file

@ -2,6 +2,17 @@
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
- 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)
- 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/)
- What's new in 3.11.0: [CHANGELOG.md#3110](CHANGELOG.md#3110)
- What's new in 4.0.0: [CHANGELOG.md#400](CHANGELOG.md#400)
- Host requirements: [docs/host-requirements.md](docs/host-requirements.md)
- Integration targets: [docs/integrations.md](docs/integrations.md)
- Public contract: [docs/public-contract.md](docs/public-contract.md)
@ -60,7 +60,7 @@ What success looks like:
```bash
Platform: linux-x86_64
Runtime: PASS
Catalog version: 3.11.0
Catalog version: 4.0.0
...
[pull] phase=install environment=debian:12
[pull] phase=ready environment=debian:12
@ -126,7 +126,7 @@ That stable workspace path gives you:
After the quickstart works:
- 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 --profile workspace-core`
- start most chat hosts with `uvx --from pyro-mcp pyro mcp serve`
- 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`
- 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
For most MCP chat hosts, start with `workspace-core`. It exposes the practical
For most MCP chat hosts, bare `pyro mcp serve` now starts `workspace-core`. It exposes the practical
persistent editing loop without shells, services, snapshots, secrets, network
policy, or disk tools.
```bash
uvx --from pyro-mcp pyro mcp serve --profile workspace-core
uvx --from pyro-mcp pyro mcp serve
```
Copy-paste host-specific starts:
@ -166,13 +166,13 @@ Copy-paste host-specific starts:
Claude Code:
```bash
claude mcp add pyro -- uvx --from pyro-mcp pyro mcp serve --profile workspace-core
claude mcp add pyro -- uvx --from pyro-mcp pyro mcp serve
```
Codex:
```bash
codex mcp add pyro -- uvx --from pyro-mcp pyro mcp serve --profile workspace-core
codex mcp add pyro -- uvx --from pyro-mcp pyro mcp serve
```
OpenCode `opencode.json` snippet:
@ -183,20 +183,22 @@ OpenCode `opencode.json` snippet:
"pyro": {
"type": "local",
"enabled": true,
"command": ["uvx", "--from", "pyro-mcp", "pyro", "mcp", "serve", "--profile", "workspace-core"]
"command": ["uvx", "--from", "pyro-mcp", "pyro", "mcp", "serve"]
}
}
}
```
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.
command with `pyro` in the same host-specific command or config shape. Use
`--profile workspace-full` only when the host truly needs the full advanced
workspace surface.
Profile progression:
- `workspace-core`: recommended first profile for normal persistent chat editing
- `workspace-core`: default and recommended first profile for normal persistent chat editing
- `vm-run`: smallest one-shot-only surface
- `workspace-full`: advanced 3.x compatibility surface when the chat truly needs shells, services, snapshots, secrets, network policy, or disk tools
- `workspace-full`: explicit advanced opt-in when the chat truly needs shells, services, snapshots, secrets, network policy, or disk tools
## Supported Hosts
@ -249,7 +251,7 @@ uvx --from pyro-mcp pyro env list
Expected output:
```bash
Catalog version: 3.11.0
Catalog version: 4.0.0
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-build [installed|not installed] Debian 12 environment with Git and common build tools preinstalled.
@ -366,7 +368,7 @@ machine consumption, use `--id-only` for only the identifier or `--json` for the
workspace payload. Use `--seed-path` when
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
later host-side changes into a started workspace. Sync is non-atomic in `3.11.0`; if it fails
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` to recover from `baseline` or one named snapshot.
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
@ -395,7 +397,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
- `from pyro_mcp import Pyro` for Python orchestration
- `pyro mcp serve --profile workspace-core` for MCP clients
- `pyro mcp serve` for MCP clients
Command forms:
@ -462,7 +464,7 @@ Run the MCP server after the CLI path above works. Start most chat hosts with
`workspace-core`:
```bash
pyro mcp serve --profile workspace-core
pyro mcp serve
```
Profile progression for chat hosts:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 3 MiB

Before After
Before After

View file

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

View file

@ -22,7 +22,7 @@ Networking: tun=yes ip_forward=yes
```bash
$ uvx --from pyro-mcp pyro env list
Catalog version: 3.11.0
Catalog version: 4.0.0
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-build [installed|not installed] Debian 12 environment with Git and common build tools preinstalled.
@ -115,12 +115,13 @@ $ 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 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 mcp serve --profile workspace-core
$ 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 --profile workspace-core
$ uvx --from pyro-mcp pyro mcp serve
$ claude mcp add pyro -- uvx --from pyro-mcp pyro mcp serve
$ codex mcp add pyro -- uvx --from pyro-mcp pyro mcp serve
```
For most chat hosts, `workspace-core` is the recommended first MCP profile.
For most chat hosts, bare `pyro mcp serve` now starts `workspace-core`, the
recommended first MCP profile.
Move to `workspace-full` only when the host truly needs shells, services,
snapshots, secrets, network policy, or disk tools.
@ -268,7 +269,7 @@ State: started
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
`pyro workspace sync push` when you need to import 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`
workspace. Sync is non-atomic in `4.0.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` 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

View file

@ -85,7 +85,7 @@ uvx --from pyro-mcp pyro env list
Expected output:
```bash
Catalog version: 3.11.0
Catalog version: 4.0.0
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-build [installed|not installed] Debian 12 environment with Git and common build tools preinstalled.
@ -231,16 +231,17 @@ After the CLI path works, you can move on to:
- 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'`
- 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 --profile workspace-core`
- MCP: `pyro mcp serve`
- Python SDK: `from pyro_mcp import Pyro`
- Demos: `pyro demo` or `pyro demo --network`
## Chat Host Quickstart
For most chat-host integrations, start with `workspace-core`:
For most chat-host integrations, bare `pyro mcp serve` now starts
`workspace-core`:
```bash
uvx --from pyro-mcp pyro mcp serve --profile workspace-core
uvx --from pyro-mcp pyro mcp serve
```
Copy-paste host-specific starts:
@ -253,25 +254,27 @@ Copy-paste host-specific starts:
Claude Code:
```bash
claude mcp add pyro -- uvx --from pyro-mcp pyro mcp serve --profile workspace-core
claude mcp add pyro -- uvx --from pyro-mcp pyro mcp serve
```
Codex:
```bash
codex mcp add pyro -- uvx --from pyro-mcp pyro mcp serve --profile workspace-core
codex mcp add pyro -- uvx --from pyro-mcp pyro mcp serve
```
OpenCode uses the `mcp`/`type: "local"` config shape shown in
[examples/opencode_mcp_config.json](../examples/opencode_mcp_config.json). 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.
command with `pyro` in the same host-specific command or config shape. Use
`--profile workspace-full` only when the host truly needs the full advanced
workspace surface.
Use profile progression like this:
- `workspace-core`: recommended first profile for normal persistent chat editing
- `workspace-core`: default and recommended first profile for normal persistent chat editing
- `vm-run`: one-shot-only integrations
- `workspace-full`: advanced 3.x compatibility surface when the host truly needs shells, services, snapshots, secrets, network policy, or disk tools
- `workspace-full`: explicit advanced opt-in when the host truly needs shells, services, snapshots, secrets, network policy, or disk tools
## Stable Workspace
@ -320,7 +323,7 @@ the identifier programmatically, use `--id-only` for only the identifier or `--j
workspace payload. Use `--seed-path`
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
is non-atomic in `3.11.0`; if it fails partway through, prefer `pyro workspace reset` to recover
is non-atomic in `4.0.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
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

View file

@ -7,7 +7,7 @@ CLI path in [install.md](install.md) or [first-run.md](first-run.md).
## Recommended Default
Start most chat hosts with `workspace-core`. Use `vm_run` only for one-shot
Bare `pyro mcp serve` now starts `workspace-core`. Use `vm_run` only for one-shot
integrations, and promote the chat surface to `workspace-full` only when it
truly needs shells, services, snapshots, secrets, network policy, or disk
tools.
@ -21,7 +21,7 @@ That keeps the model-facing contract small:
Profile progression:
- `workspace-core`: recommended first profile for persistent chat editing
- `workspace-core`: default and recommended first profile for persistent chat editing
- `vm-run`: one-shot only
- `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:
- `pyro mcp serve --profile workspace-core`
- `pyro mcp serve`
Profile progression:
- `pyro mcp serve --profile vm-run` for the smallest one-shot surface
- `pyro mcp serve --profile workspace-core` for the normal persistent chat loop
- `pyro mcp serve` for the normal persistent chat loop
- `pyro mcp serve --profile workspace-full` only when the model truly needs advanced workspace tools
Host-specific onramps:
@ -84,7 +84,7 @@ Best when:
Recommended default:
- `Pyro.run_in_vm(...)`
- `Pyro.create_server(profile="workspace-core")` for most chat hosts
- `Pyro.create_server()` for most chat hosts now that `workspace-core` is the default profile
- `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.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:
- `create_server(profile="workspace-full")`
- `Pyro.create_server(profile="workspace-full")`
- `create_server()`
- `Pyro.create_server()`
- `Pyro.list_environments()`
- `Pyro.pull_environment(environment)`
- `Pyro.inspect_environment(environment)`
@ -180,7 +180,7 @@ Supported public entrypoints:
Stable public method names:
- `create_server(profile="workspace-full")`
- `create_server()`
- `list_environments()`
- `pull_environment(environment)`
- `inspect_environment(environment)`
@ -277,9 +277,9 @@ Stable MCP profiles:
Behavioral defaults:
- `pyro mcp serve` and `create_server()` default to `workspace-full` for 3.x compatibility.
- `workspace-core` is the recommended first profile for most new chat-host integrations.
- `create_server(profile="workspace-core")` and `Pyro.create_server(profile="workspace-core")` are the recommended entrypoints for most new chat-host integrations.
- `pyro mcp serve`, `create_server()`, and `Pyro.create_server()` default to `workspace-core`.
- `workspace-core` is the default and 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.
- `workspace-core` narrows `workspace_create` by omitting `network_policy` and `secrets`.
- `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
interface.
Current baseline is `3.11.0`:
Current baseline is `4.0.0`:
- the stable workspace contract exists across CLI, SDK, and MCP
- one-shot `pyro run` still exists as the narrow entrypoint
@ -35,10 +35,7 @@ More concretely, the model should not need to:
The remaining UX friction for a technically strong new user is now narrower:
- 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
- no major chat-host ergonomics gaps remain in the current roadmap
## Locked Decisions
@ -64,7 +61,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
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
11. [`4.0.0` Workspace-Core Default Profile](llm-chat-ergonomics/4.0.0-workspace-core-default-profile.md)
11. [`4.0.0` Workspace-Core Default Profile](llm-chat-ergonomics/4.0.0-workspace-core-default-profile.md) - Done
Completed so far:
@ -93,13 +90,12 @@ Completed so far:
- `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
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:
- `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.
- no further chat-ergonomics milestones are currently planned in this roadmap.
## Expected Outcome

View file

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

View file

@ -5,16 +5,20 @@ Recommended profile: `workspace-core`.
Package without install:
```bash
claude mcp add pyro -- uvx --from pyro-mcp pyro mcp serve --profile workspace-core
claude mcp add pyro -- uvx --from pyro-mcp pyro mcp serve
claude mcp list
```
Already installed:
```bash
claude mcp add pyro -- pyro mcp serve --profile workspace-core
claude mcp add pyro -- pyro mcp serve
claude mcp list
```
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": {
"pyro": {
"command": "uvx",
"args": ["--from", "pyro-mcp", "pyro", "mcp", "serve", "--profile", "workspace-core"]
"args": ["--from", "pyro-mcp", "pyro", "mcp", "serve"]
}
}
}

View file

@ -5,16 +5,20 @@ Recommended profile: `workspace-core`.
Package without install:
```bash
codex 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 list
```
Already installed:
```bash
codex mcp add pyro -- pyro mcp serve --profile workspace-core
codex mcp add pyro -- pyro mcp serve
codex mcp list
```
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": {
"pyro": {
"command": "uvx",
"args": ["--from", "pyro-mcp", "pyro", "mcp", "serve", "--profile", "workspace-core"]
"args": ["--from", "pyro-mcp", "pyro", "mcp", "serve"]
}
}
}

View file

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

View file

@ -4,10 +4,10 @@ Requirements:
- `pip install openai` or `uv add openai`
- `OPENAI_API_KEY`
This is the recommended persistent-chat example. It mirrors the
`workspace-core` MCP profile by deriving tool schemas from
`Pyro.create_server(profile="workspace-core")` and dispatching tool calls back
through that same profiled server.
This is the recommended persistent-chat example. In 4.x the default MCP server
profile is already `workspace-core`, so it derives tool schemas from
`Pyro.create_server()` and dispatches tool calls back through that same
default-profile server.
"""
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]
pyro = Pyro()
server = pyro.create_server(profile="workspace-core")
server = pyro.create_server()
tools = [_tool_to_openai(tool) for tool in await server.list_tools()]
client = OpenAI()
input_items: list[dict[str, Any]] = [{"role": "user", "content": prompt}]

View file

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

View file

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

View file

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

View file

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

View file

@ -11,12 +11,13 @@ from pyro_mcp.vm_manager import VmManager
def create_server(
manager: VmManager | None = None,
*,
profile: McpToolProfile = "workspace-full",
profile: McpToolProfile = "workspace-core",
) -> FastMCP:
"""Create and return a configured MCP server instance.
`workspace-full` remains the default for 3.x compatibility. New chat hosts
should usually start with `profile="workspace-core"`.
`workspace-core` is the default stable chat-host profile in 4.x. Use
`profile="workspace-full"` only when the host truly needs the full
advanced workspace surface.
"""
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
DEFAULT_ENVIRONMENT_VERSION = "1.0.0"
DEFAULT_CATALOG_VERSION = "3.11.0"
DEFAULT_CATALOG_VERSION = "4.0.0"
OCI_MANIFEST_ACCEPT = ", ".join(
(
"application/vnd.oci.image.index.v1+json",
@ -48,7 +48,7 @@ class VmEnvironment:
oci_repository: str | None = None
oci_reference: str | None = None
source_digest: str | None = None
compatibility: str = ">=3.0.0,<4.0.0"
compatibility: str = ">=4.0.0,<5.0.0"
@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"
def test_pyro_create_server_registers_full_profile_and_shell_read_schema(tmp_path: Path) -> None:
def test_pyro_create_server_defaults_to_workspace_core_profile(tmp_path: Path) -> None:
pyro = Pyro(
manager=VmManager(
backend_name="mock",
@ -52,6 +52,34 @@ def test_pyro_create_server_registers_full_profile_and_shell_read_schema(tmp_pat
tool_map = {tool.name: tool.model_dump() for tool in tools}
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())
assert tuple(tool_names) == tuple(sorted(PUBLIC_MCP_WORKSPACE_FULL_PROFILE_TOOLS))
shell_read_properties = tool_map["shell_read"]["inputSchema"]["properties"]
@ -568,7 +596,7 @@ def test_pyro_create_server_workspace_disk_tools_delegate() -> None:
return cast(dict[str, Any], structured)
async def _run() -> tuple[dict[str, Any], ...]:
server = pyro.create_server()
server = pyro.create_server(profile="workspace-full")
stopped = _extract_structured(
await server.call_tool("workspace_stop", {"workspace_id": "workspace-123"})
)
@ -1078,7 +1106,7 @@ def test_pyro_create_server_workspace_status_shell_and_service_delegate() -> Non
return cast(dict[str, Any], structured)
async def _run() -> tuple[dict[str, Any], ...]:
server = pyro.create_server()
server = pyro.create_server(profile="workspace-full")
status = _extract_structured(
await server.call_tool("workspace_status", {"workspace_id": "workspace-123"})
)

View file

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

View file

@ -57,7 +57,7 @@ from pyro_mcp.contract import (
PUBLIC_CLI_WORKSPACE_SYNC_SUBCOMMANDS,
PUBLIC_CLI_WORKSPACE_UPDATE_FLAGS,
PUBLIC_MCP_PROFILES,
PUBLIC_MCP_TOOLS,
PUBLIC_MCP_WORKSPACE_CORE_PROFILE_TOOLS,
PUBLIC_SDK_METHODS,
)
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))
tool_names = asyncio.run(_run())
assert tool_names == tuple(sorted(PUBLIC_MCP_TOOLS))
assert tool_names == tuple(sorted(PUBLIC_MCP_WORKSPACE_CORE_PROFILE_TOOLS))
def test_pyproject_exposes_single_public_cli_script() -> None:

View file

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

2
uv.lock generated
View file

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