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