Add opinionated MCP modes for workspace workflows
Introduce explicit repro-fix, inspect, cold-start, and review-eval modes across the MCP server, CLI, and host helpers, with canonical mode-to-tool mappings, narrowed schemas, and mode-specific tool descriptions on top of the existing workspace runtime. Reposition the docs, host onramps, and use-case recipes so named modes are the primary user-facing startup story while the generic no-mode workspace-core path remains the escape hatch, and update the shared smoke runner to validate repro-fix and cold-start through mode-backed servers. Validation: UV_OFFLINE=1 UV_CACHE_DIR=.uv-cache uv run pytest --no-cov tests/test_api.py tests/test_server.py tests/test_host_helpers.py tests/test_public_contract.py tests/test_cli.py tests/test_workspace_use_case_smokes.py; UV_OFFLINE=1 UV_CACHE_DIR=.uv-cache make check; UV_OFFLINE=1 UV_CACHE_DIR=.uv-cache make dist-check; real guest-backed make smoke-repro-fix-loop smoke-cold-start-validation outside the sandbox.
This commit is contained in:
parent
dc86d84e96
commit
d0cf6d8f21
33 changed files with 1034 additions and 274 deletions
|
|
@ -6,8 +6,14 @@ import time
|
|||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
|
||||
import pytest
|
||||
|
||||
from pyro_mcp.api import Pyro
|
||||
from pyro_mcp.contract import (
|
||||
PUBLIC_MCP_COLD_START_MODE_TOOLS,
|
||||
PUBLIC_MCP_INSPECT_MODE_TOOLS,
|
||||
PUBLIC_MCP_REPRO_FIX_MODE_TOOLS,
|
||||
PUBLIC_MCP_REVIEW_EVAL_MODE_TOOLS,
|
||||
PUBLIC_MCP_VM_RUN_PROFILE_TOOLS,
|
||||
PUBLIC_MCP_WORKSPACE_CORE_PROFILE_TOOLS,
|
||||
PUBLIC_MCP_WORKSPACE_FULL_PROFILE_TOOLS,
|
||||
|
|
@ -157,6 +163,120 @@ def test_pyro_create_server_workspace_core_profile_registers_expected_tools_and_
|
|||
assert "workspace_disk_export" not in tool_map
|
||||
|
||||
|
||||
def test_pyro_create_server_repro_fix_mode_registers_expected_tools_and_schemas(
|
||||
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(mode="repro-fix")
|
||||
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_REPRO_FIX_MODE_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 "service_start" not in tool_map
|
||||
assert "shell_open" not in tool_map
|
||||
assert "snapshot_create" not in tool_map
|
||||
assert "reproduce a failure" in str(tool_map["workspace_create"]["description"])
|
||||
|
||||
|
||||
def test_pyro_create_server_cold_start_mode_registers_expected_tools_and_schemas(
|
||||
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(mode="cold-start")
|
||||
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_COLD_START_MODE_TOOLS))
|
||||
assert "shell_open" not in tool_map
|
||||
assert "snapshot_create" not in tool_map
|
||||
service_start_properties = tool_map["service_start"]["inputSchema"]["properties"]
|
||||
assert "secret_env" not in service_start_properties
|
||||
assert "published_ports" not in service_start_properties
|
||||
create_properties = tool_map["workspace_create"]["inputSchema"]["properties"]
|
||||
assert "network_policy" not in create_properties
|
||||
|
||||
|
||||
def test_pyro_create_server_review_eval_mode_registers_expected_tools_and_schemas(
|
||||
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(mode="review-eval")
|
||||
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_REVIEW_EVAL_MODE_TOOLS))
|
||||
assert "service_start" not in tool_map
|
||||
assert "shell_open" in tool_map
|
||||
assert "snapshot_create" in tool_map
|
||||
shell_open_properties = tool_map["shell_open"]["inputSchema"]["properties"]
|
||||
assert "secret_env" not in shell_open_properties
|
||||
|
||||
|
||||
def test_pyro_create_server_inspect_mode_registers_expected_tools(tmp_path: Path) -> None:
|
||||
pyro = Pyro(
|
||||
manager=VmManager(
|
||||
backend_name="mock",
|
||||
base_dir=tmp_path / "vms",
|
||||
network_manager=TapNetworkManager(enabled=False),
|
||||
)
|
||||
)
|
||||
|
||||
async def _run() -> list[str]:
|
||||
server = pyro.create_server(mode="inspect")
|
||||
tools = await server.list_tools()
|
||||
return sorted(tool.name for tool in tools)
|
||||
|
||||
assert tuple(asyncio.run(_run())) == tuple(sorted(PUBLIC_MCP_INSPECT_MODE_TOOLS))
|
||||
|
||||
|
||||
def test_pyro_create_server_rejects_mode_and_non_default_profile(tmp_path: Path) -> None:
|
||||
pyro = Pyro(
|
||||
manager=VmManager(
|
||||
backend_name="mock",
|
||||
base_dir=tmp_path / "vms",
|
||||
network_manager=TapNetworkManager(enabled=False),
|
||||
)
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="mutually exclusive"):
|
||||
pyro.create_server(profile="workspace-full", mode="repro-fix")
|
||||
|
||||
|
||||
def test_pyro_create_server_project_path_updates_workspace_create_description_and_default_seed(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue