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:
parent
68d8e875e0
commit
c00c699a9f
25 changed files with 170 additions and 121 deletions
|
|
@ -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"})
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
],
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue