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.
## 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 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)
- 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: 4.0.0
Catalog version: 3.11.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`
- 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`
- 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, 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
policy, or disk tools.
```bash
uvx --from pyro-mcp pyro mcp serve
uvx --from pyro-mcp pyro mcp serve --profile workspace-core
```
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
claude mcp add pyro -- uvx --from pyro-mcp pyro mcp serve --profile workspace-core
```
Codex:
```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:
@ -183,22 +183,20 @@ OpenCode `opencode.json` snippet:
"pyro": {
"type": "local",
"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`
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.
command with `pyro` in the same host-specific command or config shape.
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
- `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
@ -251,7 +249,7 @@ uvx --from pyro-mcp pyro env list
Expected output:
```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-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.
@ -368,7 +366,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 `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.
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
@ -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
- `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:
@ -464,7 +462,7 @@ Run the MCP server after the CLI path above works. Start most chat hosts with
`workspace-core`:
```bash
pyro mcp serve
pyro mcp serve --profile workspace-core
```
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 FontSize 18
Set Width 1480
Set Height 900
Set Width 1400
Set Height 860
Set Theme "Dracula"
Set TypingSpeed 34ms
Set TypingSpeed 35ms
Set Padding 24
Set WindowBar Colorful
@ -25,68 +25,62 @@ 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" "--- 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"'
Type 'printf "%s\n" "hello from sync" > "$SYNC_DIR/note.txt"'
Enter
Type "pyro env pull debian:12 >/dev/null"
Enter
Show
Type "# Create a named workspace from host content"
Type "# Create a stable workspace from host content and capture its id"
Enter
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
Sleep 500ms
Type 'echo "$WORKSPACE_ID"'
Enter
Sleep 1600ms
Sleep 2200ms
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
Sleep 700ms
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"'
Type 'pyro workspace sync push "$WORKSPACE_ID" "$SYNC_DIR"'
Enter
Sleep 1800ms
Type "# Run inside the live workspace"
Enter
Sleep 700ms
Type 'pyro workspace exec "$WORKSPACE_ID" -- cat note.txt'
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
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 the recovered file back to the host"
Type "# Export one file back to the host and inspect it locally"
Enter
Sleep 700ms
Type 'pyro workspace export "$WORKSPACE_ID" note.txt --output "$EXPORT_DIR/note.txt"'
@ -94,7 +88,7 @@ Enter
Sleep 1800ms
Type 'cat "$EXPORT_DIR/note.txt"'
Enter
Sleep 1600ms
Sleep 1800ms
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: 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-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,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 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
$ claude mcp add pyro -- uvx --from pyro-mcp pyro mcp serve
$ codex mcp add pyro -- 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 --profile workspace-core
$ 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
recommended first MCP profile.
For most chat hosts, `workspace-core` is the recommended first MCP profile.
Move to `workspace-full` only when the host truly needs shells, services,
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
`.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 `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
`/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: 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-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,17 +231,16 @@ 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`
- MCP: `pyro mcp serve --profile workspace-core`
- Python SDK: `from pyro_mcp import Pyro`
- Demos: `pyro demo` or `pyro demo --network`
## Chat Host Quickstart
For most chat-host integrations, bare `pyro mcp serve` now starts
`workspace-core`:
For most chat-host integrations, start with `workspace-core`:
```bash
uvx --from pyro-mcp pyro mcp serve
uvx --from pyro-mcp pyro mcp serve --profile workspace-core
```
Copy-paste host-specific starts:
@ -254,27 +253,25 @@ Copy-paste host-specific starts:
Claude Code:
```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:
```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
[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. Use
`--profile workspace-full` only when the host truly needs the full advanced
workspace surface.
command with `pyro` in the same host-specific command or config shape.
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
- `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
@ -323,7 +320,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 `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
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
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
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`: default and recommended first profile for persistent chat editing
- `workspace-core`: 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`
- `pyro mcp serve --profile workspace-core`
Profile progression:
- `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
Host-specific onramps:
@ -84,7 +84,7 @@ Best when:
Recommended default:
- `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(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()`
- `Pyro.create_server()`
- `create_server(profile="workspace-full")`
- `Pyro.create_server(profile="workspace-full")`
- `Pyro.list_environments()`
- `Pyro.pull_environment(environment)`
- `Pyro.inspect_environment(environment)`
@ -180,7 +180,7 @@ Supported public entrypoints:
Stable public method names:
- `create_server()`
- `create_server(profile="workspace-full")`
- `list_environments()`
- `pull_environment(environment)`
- `inspect_environment(environment)`
@ -277,9 +277,9 @@ Stable MCP profiles:
Behavioral defaults:
- `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.
- `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.
- `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 `4.0.0`:
Current baseline is `3.11.0`:
- the stable workspace contract exists across CLI, SDK, and MCP
- 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:
- 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
@ -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
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) - Done
11. [`4.0.0` Workspace-Core Default Profile](llm-chat-ergonomics/4.0.0-workspace-core-default-profile.md)
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
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:
- 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

View file

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

View file

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

View file

@ -2,7 +2,7 @@
"mcpServers": {
"pyro": {
"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:
```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
```
Already installed:
```bash
codex mcp add pyro -- pyro mcp serve
codex mcp add pyro -- pyro mcp serve --profile workspace-core
codex mcp list
```
Move to `workspace-full` only when the chat truly needs shells, services,
snapshots, secrets, network policy, or disk tools:
```bash
codex mcp add pyro -- uvx --from pyro-mcp pyro mcp serve --profile workspace-full
```
snapshots, secrets, network policy, or disk tools.

View file

@ -2,7 +2,7 @@
"mcpServers": {
"pyro": {
"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
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:
@ -20,7 +20,7 @@ Generic stdio MCP configuration using `uvx`:
"mcpServers": {
"pyro": {
"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": {
"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:
- `workspace-core`: the default and recommended first persistent chat profile
- `workspace-core`: the recommended first persistent chat profile
- `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:

View file

@ -4,10 +4,10 @@ Requirements:
- `pip install openai` or `uv add openai`
- `OPENAI_API_KEY`
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.
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.
"""
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()
server = pyro.create_server(profile="workspace-core")
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"]
"command": ["uvx", "--from", "pyro-mcp", "pyro", "mcp", "serve", "--profile", "workspace-core"]
}
}
}

View file

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

View file

@ -462,12 +462,11 @@ class Pyro:
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.
`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.
`workspace-full` remains the default for 3.x compatibility. New chat
hosts should usually start with `profile="workspace-core"`.
"""
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`. Bare `pyro "
"mcp serve` now starts the recommended `workspace-core` profile."
"guest execution with `pyro doctor` and `pyro run`. Start most "
"chat hosts with `workspace-core`."
),
epilog=dedent(
"""
Examples:
pyro mcp serve
pyro mcp serve --profile workspace-core
pyro mcp serve --profile vm-run
pyro mcp serve --profile workspace-full
"""
@ -778,23 +778,22 @@ def _build_parser() -> argparse.ArgumentParser:
"serve",
help="Run the MCP server over stdio.",
description=(
"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."
"Expose pyro tools over stdio for an MCP client. "
"`workspace-core` is the recommended first profile for most chat hosts."
),
epilog=dedent(
"""
Default and recommended first start:
pyro mcp serve
Recommended first start:
pyro mcp serve --profile workspace-core
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
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
Use --profile workspace-full only when the host truly needs the full
advanced workspace surface.
`workspace-full` remains the default in 3.x for compatibility, but most new
chat hosts should start with `workspace-core`.
"""
),
formatter_class=_HelpFormatter,
@ -802,11 +801,11 @@ def _build_parser() -> argparse.ArgumentParser:
mcp_serve_parser.add_argument(
"--profile",
choices=PUBLIC_MCP_PROFILES,
default="workspace-core",
default="workspace-full",
help=(
"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."
"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."
),
)

View file

@ -11,13 +11,12 @@ from pyro_mcp.vm_manager import VmManager
def create_server(
manager: VmManager | None = None,
*,
profile: McpToolProfile = "workspace-core",
profile: McpToolProfile = "workspace-full",
) -> FastMCP:
"""Create and return a configured MCP server instance.
`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.
`workspace-full` remains the default for 3.x compatibility. New chat hosts
should usually start with `profile="workspace-core"`.
"""
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 = "4.0.0"
DEFAULT_CATALOG_VERSION = "3.11.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 = ">=4.0.0,<5.0.0"
compatibility: str = ">=3.0.0,<4.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_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(
manager=VmManager(
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}
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"]
@ -596,7 +568,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(profile="workspace-full")
server = pyro.create_server()
stopped = _extract_structured(
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)
async def _run() -> tuple[dict[str, Any], ...]:
server = pyro.create_server(profile="workspace-full")
server = pyro.create_server()
status = _extract_structured(
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 "vm-run" 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 "workspace-full: advanced 4.x opt-in surface" in mcp_help
assert "default in 3.x for compatibility" in mcp_help
workspace_help = _subparser_choice(parser, "workspace").format_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")
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"
codex_cmd = "codex mcp add pyro -- uvx --from pyro-mcp pyro mcp serve"
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"
)
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 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 "uvx --from pyro-mcp pyro mcp serve" in install
assert "pyro mcp serve --profile workspace-core" in install
assert claude_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 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/codex_mcp.md" in integrations
assert "examples/opencode_mcp_config.json" in integrations
assert (
'`Pyro.create_server()` for most chat hosts now that `workspace-core` '
"is the default profile" in integrations
)
assert '`Pyro.create_server(profile="workspace-core")` for most chat hosts' 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 "claude_code_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",
"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_WORKSPACE_CORE_PROFILE_TOOLS,
PUBLIC_MCP_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_WORKSPACE_CORE_PROFILE_TOOLS))
assert tool_names == tuple(sorted(PUBLIC_MCP_TOOLS))
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 (
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
@ -29,7 +30,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_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:
@ -122,7 +123,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, profile="workspace-full")
server = create_server(manager=manager)
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")
@ -298,7 +299,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, profile="workspace-full")
server = create_server(manager=manager)
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 = "4.0.0"
version = "3.11.0"
source = { editable = "." }
dependencies = [
{ name = "mcp" },