endpoints.py
python
| 1 | """Protocol introspection endpoints. |
| 2 | |
| 3 | Exposes the protocol version, hash, event schemas, and MCP tool schemas |
| 4 | so clients can detect contract drift without reading source code. |
| 5 | """ |
| 6 | from __future__ import annotations |
| 7 | |
| 8 | |
| 9 | |
| 10 | from fastapi import APIRouter |
| 11 | |
| 12 | from musehub.contracts.mcp_types import MCPToolDefWire |
| 13 | from musehub.contracts.pydantic_types import PydanticJson |
| 14 | from musehub.protocol.hash import compute_protocol_hash |
| 15 | from musehub.protocol.registry import ALL_EVENT_TYPES, EVENT_REGISTRY |
| 16 | from musehub.protocol.responses import ( |
| 17 | ProtocolEventsResponse, |
| 18 | ProtocolInfoResponse, |
| 19 | ProtocolSchemaResponse, |
| 20 | ProtocolToolsResponse, |
| 21 | ) |
| 22 | from musehub.protocol.version import MUSE_VERSION |
| 23 | |
| 24 | router = APIRouter() |
| 25 | |
| 26 | |
| 27 | @router.get("/protocol") |
| 28 | async def protocol_info() -> ProtocolInfoResponse: |
| 29 | """Protocol version, hash, and registered event types.""" |
| 30 | return ProtocolInfoResponse( |
| 31 | protocolVersion=MUSE_VERSION, |
| 32 | protocolHash=compute_protocol_hash(), |
| 33 | eventTypes=sorted(ALL_EVENT_TYPES), |
| 34 | eventCount=len(EVENT_REGISTRY), |
| 35 | ) |
| 36 | |
| 37 | |
| 38 | @router.get("/protocol/events.json") |
| 39 | async def protocol_events() -> ProtocolEventsResponse: |
| 40 | """JSON Schema for every registered event type.""" |
| 41 | return ProtocolEventsResponse( |
| 42 | protocolVersion=MUSE_VERSION, |
| 43 | events={ |
| 44 | event_type: PydanticJson.model_validate( |
| 45 | EVENT_REGISTRY[event_type].model_json_schema() |
| 46 | ) |
| 47 | for event_type in sorted(EVENT_REGISTRY) |
| 48 | }, |
| 49 | ) |
| 50 | |
| 51 | |
| 52 | @router.get("/protocol/tools.json") |
| 53 | async def protocol_tools() -> ProtocolToolsResponse: |
| 54 | """MCP tool definitions for all registered server-side tools.""" |
| 55 | from musehub.mcp.tools import MUSEHUB_TOOLS |
| 56 | |
| 57 | return ProtocolToolsResponse( |
| 58 | protocolVersion=MUSE_VERSION, |
| 59 | tools=[MCPToolDefWire.model_validate(t) for t in MUSEHUB_TOOLS], |
| 60 | toolCount=len(MUSEHUB_TOOLS), |
| 61 | ) |
| 62 | |
| 63 | |
| 64 | @router.get("/protocol/schema.json") |
| 65 | async def protocol_schema() -> ProtocolSchemaResponse: |
| 66 | """Unified protocol schema — version + hash + events + tools in one fetch.""" |
| 67 | from musehub.mcp.tools import MUSEHUB_TOOLS |
| 68 | |
| 69 | return ProtocolSchemaResponse( |
| 70 | protocolVersion=MUSE_VERSION, |
| 71 | protocolHash=compute_protocol_hash(), |
| 72 | events={ |
| 73 | event_type: PydanticJson.model_validate( |
| 74 | EVENT_REGISTRY[event_type].model_json_schema() |
| 75 | ) |
| 76 | for event_type in sorted(EVENT_REGISTRY) |
| 77 | }, |
| 78 | tools=[MCPToolDefWire.model_validate(t) for t in MUSEHUB_TOOLS], |
| 79 | toolCount=len(MUSEHUB_TOOLS), |
| 80 | eventCount=len(EVENT_REGISTRY), |
| 81 | ) |