Make workspace-core the default MCP profile

Flip bare pyro mcp serve, create_server(), and Pyro.create_server() to default to workspace-core in 4.0.0 while keeping workspace-full as the explicit advanced opt-in surface.

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

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

Validation: uv lock; .venv/bin/pytest --no-cov tests/test_cli.py tests/test_api.py tests/test_server.py tests/test_public_contract.py; UV_CACHE_DIR=.uv-cache make check; UV_CACHE_DIR=.uv-cache make dist-check; real guest-backed smoke for bare Pyro.create_server() plus explicit profile="workspace-full".
This commit is contained in:
Thales Maciel 2026-03-13 14:14:15 -03:00
parent 68d8e875e0
commit c00c699a9f
25 changed files with 170 additions and 121 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

2
uv.lock generated
View file

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