Define Phast v0 management MCP vocabulary
This commit is contained in:
1 parent
0efec8fbe8
commit
8ad4814a0d
1 file changed
+37
-12
@@ -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")))
|
||||
Reference in new issue
Block a user