Define Phast v0 management MCP vocabulary

This commit is contained in:
matcha committed 2026-10-04 03:25:09 +00:00
1 parent 0efec8fbe8
commit 8ad4814a0d
1 file changed
+37 -12
+37 -12
View File
@@ -28,7 +28,7 @@ except RegistryUnavailable as error:
mcp = FastMCP(
"My Phast",
instructions="Owner control plane for Phast app manifests. Mutations fail closed until owner auth is installed.",
instructions="Management MCP for portable Phast tool belts and Prefab homes. Mutations fail closed until owner auth and durable provisioning are installed.",
)
def _require_registry() -> AppRegistry:
@@ -53,35 +53,60 @@ def phast_status() -> dict:
"auth_blocker": AUTH_BLOCKER,
}
@mcp.tool(name="apps_list", annotations={"readOnlyHint": True, "destructiveHint": False, "idempotentHint": True})
def apps_list() -> dict:
@mcp.tool(name="phasts_list", annotations={"readOnlyHint": True, "destructiveHint": False, "idempotentHint": True})
def phasts_list() -> dict:
return {"apps": _require_registry().list()}
@mcp.tool(name="apps_get", annotations={"readOnlyHint": True, "destructiveHint": False, "idempotentHint": True})
def apps_get(slug: str) -> dict:
@mcp.tool(name="phasts_get", annotations={"readOnlyHint": True, "destructiveHint": False, "idempotentHint": True})
def phasts_get(slug: str) -> dict:
result = _require_registry().get(slug)
if result is None:
raise KeyError(f"unknown app: {slug}")
return result
@mcp.tool(name="apps_create", annotations={"readOnlyHint": False, "destructiveHint": True, "idempotentHint": False})
def apps_create(slug: str, name: str, base_url: str, human_route: str = "/", mcp_route: str = "/mcp") -> dict:
@mcp.tool(name="phasts_create", annotations={"readOnlyHint": False, "destructiveHint": True, "idempotentHint": False})
def phasts_create(slug: str, name: str, base_url: str, human_route: str = "/", mcp_route: str = "/mcp") -> dict:
_require_owner_auth()
return _require_registry().create(slug=slug, name=name, base_url=base_url, human_route=human_route, mcp_route=mcp_route)
@mcp.tool(name="apps_update", annotations={"readOnlyHint": False, "destructiveHint": True, "idempotentHint": True})
def apps_update(slug: str, name: str | None = None, base_url: str | None = None,
@mcp.tool(name="phasts_update", annotations={"readOnlyHint": False, "destructiveHint": True, "idempotentHint": True})
def phasts_update(slug: str, name: str | None = None, base_url: str | None = None,
human_route: str | None = None, mcp_route: str | None = None) -> dict:
_require_owner_auth()
return _require_registry().update(slug, name=name, base_url=base_url, human_route=human_route, mcp_route=mcp_route)
@mcp.tool(name="providers_search", annotations={"readOnlyHint": True, "destructiveHint": False, "idempotentHint": True})
def providers_search(query: str) -> dict:
"""V0 seam: provider discovery belongs to Oracle, not a hard-coded Phast catalog."""
return {"query": query, "source": "oracle", "status": "discovery-route-pending"}
@mcp.tool(name="widgets_list", annotations={"readOnlyHint": True, "destructiveHint": False, "idempotentHint": True})
def widgets_list(slug: str) -> dict:
item = phasts_get(slug)
return {"phast": slug, "widgets": item.get("prefab", {}).get("home", {}).get("widgets", [])}
@mcp.tool(name="widgets_add", annotations={"readOnlyHint": False, "destructiveHint": True, "idempotentHint": False})
def widgets_add(slug: str, widget: dict) -> dict:
_require_owner_auth()
raise NotImplementedError("durable phast.json widget persistence is not installed yet")
@mcp.tool(name="widgets_update", annotations={"readOnlyHint": False, "destructiveHint": True, "idempotentHint": True})
def widgets_update(slug: str, widget_id: str, widget: dict) -> dict:
_require_owner_auth()
raise NotImplementedError("durable phast.json widget persistence is not installed yet")
@mcp.tool(name="widgets_remove", annotations={"readOnlyHint": False, "destructiveHint": True, "idempotentHint": True})
def widgets_remove(slug: str, widget_id: str) -> dict:
_require_owner_auth()
raise NotImplementedError("durable phast.json widget persistence is not installed yet")
def control_app() -> PrefabApp:
apps = registry.list() if registry is not None else []
with PrefabApp(title="My Phast", css_class="max-w-4xl mx-auto p-6", mode="light") as result:
with Column(gap=5):
with Row(gap=3, justify="between", align="center"):
with Column(gap=1):
Text("PHAST · OWNER CONTROL PLANE", code=True)
Text("PHAST · OWNER CONTROL PLANE", code=[REDACTED]
Heading("My Phast", level=1)
Badge("read-only" if registry is not None else "setup required", variant="warning")
Text("Your apps share one boring convention: a human surface at / and an agent surface at /mcp.")
@@ -96,7 +121,7 @@ def control_app() -> PrefabApp:
with Row(gap=3, justify="between", align="center"):
with Column(gap=1):
Heading(item["name"], level=3)
Text(item["base_url"], code=True)
Text(item["base_url"], code=[REDACTED]
Badge(item["runtime"], variant="outline")
Text(f"human {item['human']['route']} · mcp {item['mcp']['route']}")
else:
@@ -145,4 +170,4 @@ app = mcp.http_app(
)
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", "8080")))
uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", "8080")))