diff --git a/CHANGELOG.md b/CHANGELOG.md index 863a6d0..2b6ae71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index 793a7b3..cb7a3eb 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/docs/assets/workspace-first-run.gif b/docs/assets/workspace-first-run.gif index e4ad6b1..595a0e6 100644 Binary files a/docs/assets/workspace-first-run.gif and b/docs/assets/workspace-first-run.gif differ diff --git a/docs/assets/workspace-first-run.tape b/docs/assets/workspace-first-run.tape index df33f48..f7fa7da 100644 --- a/docs/assets/workspace-first-run.tape +++ b/docs/assets/workspace-first-run.tape @@ -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 diff --git a/docs/first-run.md b/docs/first-run.md index 842bb59..40e5c3d 100644 --- a/docs/first-run.md +++ b/docs/first-run.md @@ -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 diff --git a/docs/install.md b/docs/install.md index 88f3d84..c7b2ccf 100644 --- a/docs/install.md +++ b/docs/install.md @@ -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 diff --git a/docs/integrations.md b/docs/integrations.md index 2883784..36b5bb3 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -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 diff --git a/docs/public-contract.md b/docs/public-contract.md index d225c0b..df85826 100644 --- a/docs/public-contract.md +++ b/docs/public-contract.md @@ -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`. diff --git a/docs/roadmap/llm-chat-ergonomics.md b/docs/roadmap/llm-chat-ergonomics.md index 93fa8e4..1c83344 100644 --- a/docs/roadmap/llm-chat-ergonomics.md +++ b/docs/roadmap/llm-chat-ergonomics.md @@ -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 diff --git a/docs/roadmap/llm-chat-ergonomics/4.0.0-workspace-core-default-profile.md b/docs/roadmap/llm-chat-ergonomics/4.0.0-workspace-core-default-profile.md index 55924b9..104fb6e 100644 --- a/docs/roadmap/llm-chat-ergonomics/4.0.0-workspace-core-default-profile.md +++ b/docs/roadmap/llm-chat-ergonomics/4.0.0-workspace-core-default-profile.md @@ -1,6 +1,6 @@ # `4.0.0` Workspace-Core Default Profile -Status: Done +Status: Planned ## Goal diff --git a/examples/claude_code_mcp.md b/examples/claude_code_mcp.md index d62cae4..6f7a18f 100644 --- a/examples/claude_code_mcp.md +++ b/examples/claude_code_mcp.md @@ -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. diff --git a/examples/claude_desktop_mcp_config.json b/examples/claude_desktop_mcp_config.json index 81f0bd6..f828de0 100644 --- a/examples/claude_desktop_mcp_config.json +++ b/examples/claude_desktop_mcp_config.json @@ -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"] } } } diff --git a/examples/codex_mcp.md b/examples/codex_mcp.md index 6838a7d..f9f2861 100644 --- a/examples/codex_mcp.md +++ b/examples/codex_mcp.md @@ -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. diff --git a/examples/cursor_mcp_config.json b/examples/cursor_mcp_config.json index 81f0bd6..f828de0 100644 --- a/examples/cursor_mcp_config.json +++ b/examples/cursor_mcp_config.json @@ -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"] } } } diff --git a/examples/mcp_client_config.md b/examples/mcp_client_config.md index 51d7419..0fa7645 100644 --- a/examples/mcp_client_config.md +++ b/examples/mcp_client_config.md @@ -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: diff --git a/examples/openai_responses_workspace_core.py b/examples/openai_responses_workspace_core.py index 1c83551..6664c18 100644 --- a/examples/openai_responses_workspace_core.py +++ b/examples/openai_responses_workspace_core.py @@ -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}] diff --git a/examples/opencode_mcp_config.json b/examples/opencode_mcp_config.json index c060946..ce23db7 100644 --- a/examples/opencode_mcp_config.json +++ b/examples/opencode_mcp_config.json @@ -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"] } } } diff --git a/pyproject.toml b/pyproject.toml index 2f078d7..d6e3ded 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" } diff --git a/src/pyro_mcp/api.py b/src/pyro_mcp/api.py index 6dfc5fb..56b16eb 100644 --- a/src/pyro_mcp/api.py +++ b/src/pyro_mcp/api.py @@ -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]) diff --git a/src/pyro_mcp/cli.py b/src/pyro_mcp/cli.py index 47df4ee..fc3b70c 100644 --- a/src/pyro_mcp/cli.py +++ b/src/pyro_mcp/cli.py @@ -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." ), ) diff --git a/src/pyro_mcp/server.py b/src/pyro_mcp/server.py index daf1820..3015991 100644 --- a/src/pyro_mcp/server.py +++ b/src/pyro_mcp/server.py @@ -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) diff --git a/src/pyro_mcp/vm_environments.py b/src/pyro_mcp/vm_environments.py index 6419792..dc2f67a 100644 --- a/src/pyro_mcp/vm_environments.py +++ b/src/pyro_mcp/vm_environments.py @@ -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) diff --git a/tests/test_api.py b/tests/test_api.py index 8772754..b882fef 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -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"}) ) diff --git a/tests/test_cli.py b/tests/test_cli.py index aaef8d1..52807f2 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -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", ], } } diff --git a/tests/test_public_contract.py b/tests/test_public_contract.py index 5033d4a..6009d66 100644 --- a/tests/test_public_contract.py +++ b/tests/test_public_contract.py @@ -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: diff --git a/tests/test_server.py b/tests/test_server.py index 1481149..9571dcd 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -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", diff --git a/uv.lock b/uv.lock index 95bd68b..f993b93 100644 --- a/uv.lock +++ b/uv.lock @@ -715,7 +715,7 @@ crypto = [ [[package]] name = "pyro-mcp" -version = "4.0.0" +version = "3.11.0" source = { editable = "." } dependencies = [ { name = "mcp" },