type-contracts.md
markdown
| 1 | # MuseHub — Type Contracts Reference |
| 2 | |
| 3 | > Updated: 2026-03-17 | MCP Protocol: **2025-11-25** | Covers every named entity in the MuseHub surface: |
| 4 | > MIDI type aliases, JSON wire types, MCP protocol types (including Elicitation, Session, and SSE), |
| 5 | > Pydantic API models, auth tokens, SSE event hierarchy, SQLAlchemy ORM models, and the full |
| 6 | > MCP integration layer (Streamable HTTP transport, session management, dispatcher, resources, |
| 7 | > prompts, read tools, write tools, elicitation-powered tools). |
| 8 | > `Any` and bare `list` / `dict` (without type arguments) do not appear in any |
| 9 | > production file. Every type boundary is named. The mypy strict ratchet |
| 10 | > enforces zero violations on every CI run across 111 source files. |
| 11 | |
| 12 | --- |
| 13 | |
| 14 | ## Table of Contents |
| 15 | |
| 16 | 1. [Design Philosophy](#design-philosophy) |
| 17 | 2. [MIDI Type Aliases (`contracts/midi_types.py`)](#midi-type-aliases) |
| 18 | 3. [JSON Type Aliases (`contracts/json_types.py`)](#json-type-aliases) |
| 19 | 4. [JSON Wire TypedDicts (`contracts/json_types.py`)](#json-wire-typeddicts) |
| 20 | 5. [MCP Protocol Types (`contracts/mcp_types.py`)](#mcp-protocol-types) |
| 21 | 6. [MCP Integration Layer (`mcp/`)](#mcp-integration-layer) |
| 22 | 7. [Pydantic Base Types (`contracts/pydantic_types.py`, `models/base.py`)](#pydantic-base-types) |
| 23 | 8. [Auth Types (`auth/tokens.py`)](#auth-types) |
| 24 | 9. [Protocol Events (`protocol/events.py`)](#protocol-events) |
| 25 | 10. [Protocol HTTP Responses (`protocol/responses.py`)](#protocol-http-responses) |
| 26 | 11. [API Models (`models/musehub.py`)](#api-models) |
| 27 | 12. [Database ORM Models (`db/`)](#database-orm-models) |
| 28 | 13. [Entity Hierarchy](#entity-hierarchy) |
| 29 | 14. [Entity Graphs (Mermaid)](#entity-graphs-mermaid) |
| 30 | |
| 31 | --- |
| 32 | |
| 33 | ## Design Philosophy |
| 34 | |
| 35 | Every entity in this codebase follows five rules: |
| 36 | |
| 37 | 1. **No `Any`. No bare `object`. Ever.** Both collapse type safety for downstream |
| 38 | callers. Every boundary is typed with a concrete named entity — `TypedDict`, |
| 39 | `dataclass`, Pydantic model, or a specific union. The CI mypy strict ratchet |
| 40 | enforces zero violations. |
| 41 | |
| 42 | 2. **No covariance in collection aliases.** `dict[str, str]` and `list[str]` |
| 43 | are used directly. If a return mixes value types, a `TypedDict` names that |
| 44 | shape instead of a `dict[str, str | int]`. |
| 45 | |
| 46 | 3. **Boundaries own coercion.** When external data arrives (JSON over HTTP, |
| 47 | bytes from the database, MIDI off the wire), the boundary module coerces to |
| 48 | the canonical internal type. Downstream code always sees clean types. |
| 49 | |
| 50 | 4. **Wire-format TypedDicts for JSON serialisation, Pydantic models for HTTP.** |
| 51 | `TokenClaims`, `NoteDict`, `MCPRequest` etc. are JSON-serialisable and |
| 52 | used at IO boundaries. Pydantic `CamelModel` subclasses are used for all |
| 53 | FastAPI route return types and request bodies. |
| 54 | |
| 55 | 5. **No `# type: ignore`. Fix the underlying error instead.** The one designated |
| 56 | exception is the `json_list()` coercion boundary — a single |
| 57 | `type: ignore[arg-type]` inside the implementation body of that helper. |
| 58 | |
| 59 | ### Banned → Use instead |
| 60 | |
| 61 | | Banned | Use instead | |
| 62 | |--------|-------------| |
| 63 | | `Any` | `TypedDict`, `dataclass`, specific union | |
| 64 | | `object` | The actual type or a constrained union | |
| 65 | | `list` (bare) | `list[X]` with concrete element type | |
| 66 | | `dict` (bare) | `dict[K, V]` with concrete key/value types | |
| 67 | | `dict[str, X]` with known keys | `TypedDict` — name the keys | |
| 68 | | `Optional[X]` | `X \| None` | |
| 69 | | Legacy `List`, `Dict`, `Set`, `Tuple` | Lowercase builtins | |
| 70 | | `Union[A, B]` | `A \| B` | |
| 71 | | `Type[X]` | `type[X]` | |
| 72 | | `cast(T, x)` | Fix the callee to return `T` | |
| 73 | | `# type: ignore` | Fix the underlying type error | |
| 74 | |
| 75 | --- |
| 76 | |
| 77 | ## MIDI Type Aliases |
| 78 | |
| 79 | **Path:** `musehub/contracts/midi_types.py` |
| 80 | |
| 81 | Constrained `int` and `float` aliases via `Annotated[int, Field(...)]`. |
| 82 | Every MIDI boundary uses these instead of bare `int` so that range constraints |
| 83 | are part of the type signature and enforced by Pydantic at validation time. |
| 84 | |
| 85 | | Alias | Base | Constraint | Description | |
| 86 | |-------|------|------------|-------------| |
| 87 | | `MidiPitch` | `int` | 0–127 | MIDI note number (C-1=0, Middle C=60, G9=127) | |
| 88 | | `MidiVelocity` | `int` | 0–127 | Note velocity; 0=note-off, 1–127=audible | |
| 89 | | `MidiChannel` | `int` | 0–15 | Zero-indexed MIDI channel; drums=9 | |
| 90 | | `MidiCC` | `int` | 0–127 | MIDI Control Change controller number | |
| 91 | | `MidiCCValue` | `int` | 0–127 | MIDI Control Change value | |
| 92 | | `MidiAftertouchValue` | `int` | 0–127 | Channel or poly aftertouch pressure | |
| 93 | | `MidiGMProgram` | `int` | 0–127 | General MIDI program / patch number (0-indexed) | |
| 94 | | `MidiPitchBend` | `int` | −8192–8191 | 14-bit signed pitch bend; 0=centre | |
| 95 | | `MidiBPM` | `int` | 20–300 | Tempo in beats per minute (always integer) | |
| 96 | | `BeatPosition` | `float` | ≥ 0.0 | Absolute beat position; fractional allowed | |
| 97 | | `BeatDuration` | `float` | > 0.0 | Duration in beats; must be strictly positive | |
| 98 | | `ArrangementBeat` | `int` | ≥ 0 | Bar-aligned beat offset for section-level timing | |
| 99 | | `ArrangementDuration` | `int` | ≥ 1 | Section duration in beats (bars × numerator) | |
| 100 | | `Bars` | `int` | ≥ 1 | Bar count; always a positive integer | |
| 101 | |
| 102 | --- |
| 103 | |
| 104 | ## JSON Type Aliases |
| 105 | |
| 106 | **Path:** `musehub/contracts/json_types.py` |
| 107 | |
| 108 | Type aliases for recursive and domain-specific JSON shapes. |
| 109 | |
| 110 | | Alias | Definition | Description | |
| 111 | |-------|-----------|-------------| |
| 112 | | `JSONScalar` | `str \| int \| float \| bool \| None` | A JSON leaf value with no recursive structure | |
| 113 | | `JSONValue` | `str \| int \| float \| bool \| None \| list["JSONValue"] \| dict[str, "JSONValue"]` | Recursive JSON value — use sparingly, never in Pydantic models | |
| 114 | | `JSONObject` | `dict[str, JSONValue]` | A JSON object with an unknown key set | |
| 115 | | `InternalNoteDict` | `NoteDict` | Alias for `NoteDict` on the snake_case storage path | |
| 116 | | `RegionNotesMap` | `dict[str, list[NoteDict]]` | Maps `region_id` → ordered list of MIDI notes | |
| 117 | | `RegionCCMap` | `dict[str, list[CCEventDict]]` | Maps `region_id` → ordered list of MIDI CC events | |
| 118 | | `RegionPitchBendMap` | `dict[str, list[PitchBendDict]]` | Maps `region_id` → ordered list of MIDI pitch bend events | |
| 119 | | `RegionAftertouchMap` | `dict[str, list[AftertouchDict]]` | Maps `region_id` → ordered list of MIDI aftertouch events | |
| 120 | | `EventJsonSchema` | `dict[str, JSONValue]` | JSON Schema dict for a single event type, as produced by `model_json_schema()` | |
| 121 | | `EventSchemaMap` | `dict[str, EventJsonSchema]` | Maps `event_type` → its JSON Schema; returned by `/protocol/events.json` | |
| 122 | |
| 123 | --- |
| 124 | |
| 125 | ## JSON Wire TypedDicts |
| 126 | |
| 127 | **Path:** `musehub/contracts/json_types.py` |
| 128 | |
| 129 | These are the MIDI note and event shapes exchanged across service boundaries. |
| 130 | All use `total=False` where the producer may omit fields, and `Required[T]` |
| 131 | to mark fields that must always be present. |
| 132 | |
| 133 | ### `NoteDict` |
| 134 | |
| 135 | `TypedDict (total=False)` — A single MIDI note. Accepts both camelCase (wire |
| 136 | format) and snake_case (internal storage) to avoid a transform boundary. |
| 137 | |
| 138 | | Field | Type | Description | |
| 139 | |-------|------|-------------| |
| 140 | | `pitch` | `MidiPitch` | MIDI pitch number | |
| 141 | | `velocity` | `MidiVelocity` | Note velocity | |
| 142 | | `channel` | `MidiChannel` | MIDI channel | |
| 143 | | `startBeat` | `BeatPosition` | Onset beat (camelCase wire) | |
| 144 | | `durationBeats` | `BeatDuration` | Duration in beats (camelCase wire) | |
| 145 | | `noteId` | `str` | Note UUID (camelCase wire) | |
| 146 | | `trackId` | `str` | Parent track UUID (camelCase wire) | |
| 147 | | `regionId` | `str` | Parent region UUID (camelCase wire) | |
| 148 | | `start_beat` | `BeatPosition` | Onset beat (snake_case storage) | |
| 149 | | `duration_beats` | `BeatDuration` | Duration in beats (snake_case storage) | |
| 150 | | `note_id` | `str` | Note UUID (snake_case storage) | |
| 151 | | `track_id` | `str` | Parent track UUID (snake_case storage) | |
| 152 | | `region_id` | `str` | Parent region UUID (snake_case storage) | |
| 153 | | `layer` | `str` | Optional layer grouping (e.g. `"melody"`) | |
| 154 | |
| 155 | ### `CCEventDict` |
| 156 | |
| 157 | `TypedDict` — A single MIDI Control Change event. |
| 158 | |
| 159 | | Field | Type | |
| 160 | |-------|------| |
| 161 | | `cc` | `MidiCC` | |
| 162 | | `beat` | `BeatPosition` | |
| 163 | | `value` | `MidiCCValue` | |
| 164 | |
| 165 | ### `PitchBendDict` |
| 166 | |
| 167 | `TypedDict` — A single MIDI pitch bend event. |
| 168 | |
| 169 | | Field | Type | |
| 170 | |-------|------| |
| 171 | | `beat` | `BeatPosition` | |
| 172 | | `value` | `MidiPitchBend` | |
| 173 | |
| 174 | ### `AftertouchDict` |
| 175 | |
| 176 | `TypedDict (total=False)` — MIDI aftertouch (channel or poly). |
| 177 | |
| 178 | | Field | Type | Required | |
| 179 | |-------|------|----------| |
| 180 | | `beat` | `BeatPosition` | Yes | |
| 181 | | `value` | `MidiAftertouchValue` | Yes | |
| 182 | | `pitch` | `MidiPitch` | No (poly only) | |
| 183 | |
| 184 | ### `SectionDict` |
| 185 | |
| 186 | `TypedDict (total=False)` — A composition section (verse, chorus, bridge, etc.). |
| 187 | Used by the analysis service to describe structural section metadata. |
| 188 | |
| 189 | | Field | Type | |
| 190 | |-------|------| |
| 191 | | `name` | `str` | |
| 192 | | `start_beat` | `float` | |
| 193 | | `length_beats` | `float` | |
| 194 | | `description` | `str` | |
| 195 | | `per_track_description` | `dict[str, str]` | |
| 196 | |
| 197 | ### Conversion helpers |
| 198 | |
| 199 | | Function | Signature | Description | |
| 200 | |----------|-----------|-------------| |
| 201 | | `is_note_dict(v)` | `(JSONValue) -> TypeGuard[NoteDict]` | Narrows `JSONValue` to `NoteDict` in list comprehensions | |
| 202 | | `jfloat(v, default)` | `(JSONValue, float) -> float` | Safe float extraction from `JSONValue` | |
| 203 | | `jint(v, default)` | `(JSONValue, int) -> int` | Safe int extraction from `JSONValue` | |
| 204 | | `json_list(items)` | overloaded | Coerces a `list[TypedDict]` to `list[JSONValue]` at insertion boundaries | |
| 205 | |
| 206 | --- |
| 207 | |
| 208 | ## MCP Protocol Types |
| 209 | |
| 210 | **Path:** `musehub/contracts/mcp_types.py` |
| 211 | |
| 212 | Typed shapes for the Model Context Protocol 2025-11-25 JSON-RPC 2.0 interface. |
| 213 | TypedDicts cover the wire format; Pydantic models (`MCPToolDefWire` etc.) are |
| 214 | used for FastAPI response serialisation. |
| 215 | |
| 216 | ### Type Aliases |
| 217 | |
| 218 | | Alias | Definition | |
| 219 | |-------|-----------| |
| 220 | | `MCPResponse` | `MCPSuccessResponse \| MCPErrorResponse` | |
| 221 | | `MCPMethodResponse` | `MCPInitializeResponse \| MCPToolsListResponse \| MCPCallResponse \| MCPSuccessResponse \| MCPErrorResponse` | |
| 222 | |
| 223 | ### Request / Response TypedDicts |
| 224 | |
| 225 | | Name | Kind | Description | |
| 226 | |------|------|-------------| |
| 227 | | `MCPRequest` | `total=False` | Incoming JSON-RPC 2.0 message from an MCP client | |
| 228 | | `MCPSuccessResponse` | required | JSON-RPC 2.0 success response | |
| 229 | | `MCPErrorDetail` | `total=False` | The `error` object inside a JSON-RPC error response | |
| 230 | | `MCPErrorResponse` | required | JSON-RPC 2.0 error response | |
| 231 | | `MCPInitializeParams` | `total=False` | Params for the `initialize` method | |
| 232 | | `MCPInitializeResult` | required | Result body for `initialize` | |
| 233 | | `MCPInitializeResponse` | required | Full response for `initialize` | |
| 234 | | `MCPToolsListResult` | required | Result body for `tools/list` | |
| 235 | | `MCPToolsListResponse` | required | Full response for `tools/list` | |
| 236 | | `MCPCallResult` | `total=False` | Result body for `tools/call` | |
| 237 | | `MCPCallResponse` | required | Full response for `tools/call` | |
| 238 | |
| 239 | ### Tool Definition TypedDicts |
| 240 | |
| 241 | | Name | Kind | Description | |
| 242 | |------|------|-------------| |
| 243 | | `MCPPropertyDef` | `total=False` | JSON Schema definition for a single tool property | |
| 244 | | `MCPInputSchema` | `total=False` | JSON Schema for a tool's accepted arguments | |
| 245 | | `MCPToolDef` | `total=False` | Complete definition of an MCP tool | |
| 246 | | `MCPContentBlock` | required | Content block in a tool result (`type`, `text`) | |
| 247 | |
| 248 | ### Capability TypedDicts |
| 249 | |
| 250 | | Name | Kind | Description | |
| 251 | |------|------|-------------| |
| 252 | | `MCPToolsCapability` | `total=False` | `tools` entry in `MCPCapabilities` | |
| 253 | | `MCPResourcesCapability` | `total=False` | `resources` entry in `MCPCapabilities` | |
| 254 | | `MCPCapabilities` | `total=False` | Server capabilities advertised during `initialize` | |
| 255 | | `MCPServerInfo` | required | Server info returned in `initialize` responses | |
| 256 | | `MCPCapabilitiesResult` | required | Capability block in `initialize` result | |
| 257 | | `MCPToolCallParams` | required | Params for `tools/call` | |
| 258 | |
| 259 | ### Elicitation TypedDicts (MCP 2025-11-25) |
| 260 | |
| 261 | | Name | Kind | Description | |
| 262 | |------|------|-------------| |
| 263 | | `ElicitationAction` | `Literal` | `"accept"` \| `"decline"` \| `"cancel"` | |
| 264 | | `ElicitationRequest` | `total=False` | Server→client elicitation request: `mode` (`"form"` \| `"url"`), `schema` (form), `url` (URL), `message`, `request_id` | |
| 265 | | `ElicitationResponse` | required | Client→server elicitation response: `action` (`ElicitationAction`), `content` (form fields on accept) | |
| 266 | | `SessionInfo` | required | Active session summary: `session_id`, `user_id`, `created_at`, `last_active`, `pending_elicitations_count`, `sse_queue_count` | |
| 267 | |
| 268 | ### DAW ↔ MCP Bridge TypedDicts |
| 269 | |
| 270 | | Name | Kind | Description | |
| 271 | |------|------|-------------| |
| 272 | | `DAWToolCallMessage` | required | Message sent from MCP server to a DAW client over WebSocket | |
| 273 | | `DAWToolResponse` | `total=False` | Response sent from the DAW back after tool execution | |
| 274 | |
| 275 | ### Pydantic Wire Models (FastAPI) |
| 276 | |
| 277 | | Name | Description | |
| 278 | |------|-------------| |
| 279 | | `MCPPropertyDefWire` | Pydantic-safe JSON Schema property for FastAPI responses | |
| 280 | | `MCPInputSchemaWire` | Pydantic-safe tool input schema for FastAPI responses | |
| 281 | | `MCPToolDefWire` | Pydantic-safe tool definition for FastAPI route return types | |
| 282 | |
| 283 | --- |
| 284 | |
| 285 | ## MCP Integration Layer |
| 286 | |
| 287 | **Paths:** `musehub/mcp/dispatcher.py`, `musehub/mcp/resources.py`, |
| 288 | `musehub/mcp/prompts.py`, `musehub/mcp/tools/`, `musehub/mcp/write_tools/`, |
| 289 | `musehub/mcp/session.py`, `musehub/mcp/sse.py`, `musehub/mcp/context.py`, |
| 290 | `musehub/mcp/elicitation.py`, `musehub/api/routes/mcp.py`, `musehub/mcp/stdio_server.py` |
| 291 | |
| 292 | The MCP integration layer implements the full [MCP 2025-11-25 specification](https://modelcontextprotocol.io/specification/2025-11-25) |
| 293 | as a pure-Python async stack. No external MCP SDK dependency. Three HTTP endpoints |
| 294 | (`POST /mcp`, `GET /mcp`, `DELETE /mcp`) and stdio are supported. |
| 295 | |
| 296 | ### Session Layer (`mcp/session.py`) |
| 297 | |
| 298 | Stateful connection management for the Streamable HTTP transport. |
| 299 | |
| 300 | | Type / Export | Kind | Description | |
| 301 | |---------------|------|-------------| |
| 302 | | `MCPSession` | `@dataclass` | Active session: `session_id`, `user_id`, `client_capabilities`, `pending` (elicitation Futures), `sse_queues`, `event_buffer`, `created_at`, `last_active` | |
| 303 | | `create_session(user_id, client_capabilities)` | function | Mint a new session; registers background cleanup task | |
| 304 | | `get_session(session_id)` | function | Look up by ID; returns `None` if expired | |
| 305 | | `delete_session(session_id)` | function | Terminate session; drains SSE queues; cancels pending Futures | |
| 306 | | `push_to_session(session, event_text)` | function | Broadcast SSE event to all open GET /mcp consumers | |
| 307 | | `register_sse_queue(session, last_event_id)` | async generator | Yield SSE events; replay buffer; heartbeat-friendly | |
| 308 | | `create_pending_elicitation(session, request_id)` | function | Register a `Future` for an outbound `elicitation/create` | |
| 309 | | `resolve_elicitation(session, request_id, result)` | function | Set the Future result on client response | |
| 310 | | `cancel_elicitation(session, request_id)` | function | Cancel the Future on `notifications/cancelled` | |
| 311 | |
| 312 | Session TTL: 1 hour of inactivity. Background cleanup runs every 5 minutes. SSE ring buffer holds 50 events for `Last-Event-ID` replay. |
| 313 | |
| 314 | ### SSE Utilities (`mcp/sse.py`) |
| 315 | |
| 316 | | Export | Signature | Description | |
| 317 | |--------|-----------|-------------| |
| 318 | | `sse_event(data, *, event_id, event_type, retry_ms)` | `(JSONObject, ...) → str` | Format a JSON object as an SSE event string ending with `\n\n` | |
| 319 | | `sse_notification(method, params, *, event_id)` | `(str, ...) → str` | Format a JSON-RPC 2.0 notification as SSE | |
| 320 | | `sse_request(req_id, method, params, *, event_id)` | `(str\|int, str, ...) → str` | Format a JSON-RPC 2.0 request (server-initiated) as SSE | |
| 321 | | `sse_response(req_id, result, *, event_id)` | `(str\|int\|None, JSONObject, ...) → str` | Format a JSON-RPC 2.0 success response as SSE | |
| 322 | | `sse_heartbeat()` | `() → str` | Return the SSE heartbeat comment (`": heartbeat\n\n"`) | |
| 323 | | `heartbeat_stream(event_stream, *, interval_seconds)` | `async generator` | Interleave heartbeat comments into an event stream | |
| 324 | | `SSE_CONTENT_TYPE` | `str` | `"text/event-stream"` | |
| 325 | |
| 326 | ### Tool Call Context (`mcp/context.py`) |
| 327 | |
| 328 | | Type | Kind | Description | |
| 329 | |------|------|-------------| |
| 330 | | `ToolCallContext` | `@dataclass` | Passed to every tool executor; carries `user_id` and `session` | |
| 331 | | `.elicit_form(schema, message)` | `async → dict \| None` | Send form elicitation, await Future (5 min timeout); returns `content` dict on accept, `None` on decline/cancel/no-session | |
| 332 | | `.elicit_url(url, message, elicitation_id)` | `async → bool` | Send URL elicitation, await Future; returns `True` on accept | |
| 333 | | `.progress(token, value, total, label)` | `async → None` | Push `notifications/progress` SSE event; silent no-op without session | |
| 334 | | `.has_session` | `bool` | `True` if session is attached | |
| 335 | | `.has_elicitation` | `bool` | `True` if client supports form-mode elicitation | |
| 336 | |
| 337 | ### Elicitation Schemas (`mcp/elicitation.py`) |
| 338 | |
| 339 | | Export | Type | Description | |
| 340 | |--------|------|-------------| |
| 341 | | `SCHEMAS` | `dict[str, JSONObject]` | 5 restricted JSON Schema objects for musical form elicitation | |
| 342 | | `AVAILABLE_KEYS` | `list[str]` | 24 musical key signatures | |
| 343 | | `AVAILABLE_MOODS` | `list[str]` | 10 mood enums | |
| 344 | | `AVAILABLE_GENRES` | `list[str]` | 10 genre enums | |
| 345 | | `AVAILABLE_DAWS` | `list[str]` | 10 DAW names | |
| 346 | | `AVAILABLE_PLATFORMS` | `list[str]` | 8 streaming platform names | |
| 347 | | `AVAILABLE_DAW_CLOUDS` | `list[str]` | 5 cloud DAW / mastering service names | |
| 348 | | `build_form_elicitation(schema_key, message, *, request_id)` | function | Build form-mode elicitation params dict | |
| 349 | | `build_url_elicitation(url, message, *, elicitation_id)` | function | Build URL-mode elicitation params dict + stable ID | |
| 350 | | `oauth_connect_url(platform, elicitation_id, base_url)` | function | Build MuseHub OAuth start page URL | |
| 351 | | `daw_cloud_connect_url(service, elicitation_id, base_url)` | function | Build cloud DAW OAuth start page URL | |
| 352 | |
| 353 | Schema keys: `compose_preferences`, `repo_creation`, `pr_review_focus`, `release_metadata`, `platform_connect_confirm`. |
| 354 | |
| 355 | ### Tool Catalogue (`mcp/tools/`) |
| 356 | |
| 357 | | Export | Type | Description | |
| 358 | |--------|------|-------------| |
| 359 | | `MUSEHUB_READ_TOOLS` | `list[MCPToolDef]` | 20 read tool definitions (context, search, inspect, Muse CLI reads) | |
| 360 | | `MUSEHUB_WRITE_TOOLS` | `list[MCPToolDef]` | 15 write tool definitions (create, update, merge, star, push) | |
| 361 | | `MUSEHUB_ELICITATION_TOOLS` | `list[MCPToolDef]` | 5 elicitation-powered tool definitions (MCP 2025-11-25) | |
| 362 | | `MUSEHUB_TOOLS` | `list[MCPToolDef]` | Combined catalogue of all 40 `musehub_*` and `muse_*` tools | |
| 363 | | `MUSEHUB_TOOL_NAMES` | `set[str]` | All tool name strings for fast routing | |
| 364 | | `MUSEHUB_WRITE_TOOL_NAMES` | `set[str]` | Write + interactive names; presence triggers JWT auth check | |
| 365 | | `MUSEHUB_ELICITATION_TOOL_NAMES` | `set[str]` | Elicitation-powered names; require session | |
| 366 | | `MCP_TOOLS` | `list[MCPToolDef]` | Full registered tool list | |
| 367 | | `TOOL_CATEGORIES` | `dict[str, str]` | Maps tool name → `"musehub-read"`, `"musehub-write"`, or `"musehub-elicitation"` | |
| 368 | |
| 369 | **Read tools (20):** `musehub_get_context`, `musehub_list_branches`, `musehub_list_commits`, |
| 370 | `musehub_read_file`, `musehub_search`, `musehub_get_commit`, `musehub_compare`, |
| 371 | `musehub_list_issues`, `musehub_get_issue`, `musehub_list_prs`, `musehub_get_pr`, |
| 372 | `musehub_list_releases`, `musehub_search_repos`, `musehub_list_domains`, `musehub_get_domain`, |
| 373 | `musehub_get_domain_insights`, `musehub_get_view`, `musehub_whoami`, `muse_pull`, `muse_remote` |
| 374 | |
| 375 | **Write tools (15):** `musehub_create_repo`, `musehub_fork_repo`, `musehub_create_issue`, |
| 376 | `musehub_update_issue`, `musehub_create_issue_comment`, `musehub_create_pr`, |
| 377 | `musehub_merge_pr`, `musehub_create_pr_comment`, `musehub_submit_pr_review`, |
| 378 | `musehub_create_release`, `musehub_star_repo`, `musehub_create_label`, |
| 379 | `musehub_create_agent_token`, `muse_push`, `muse_config` |
| 380 | |
| 381 | **Elicitation tools (5):** `musehub_create_with_preferences`, `musehub_review_pr_interactive`, |
| 382 | `musehub_connect_streaming_platform`, `musehub_connect_daw_cloud`, `musehub_create_release_interactive` |
| 383 | |
| 384 | ### Resource Catalogue (`mcp/resources.py`) |
| 385 | |
| 386 | TypedDicts for the `musehub://` URI scheme. |
| 387 | |
| 388 | | Name | Kind | Description | |
| 389 | |------|------|-------------| |
| 390 | | `MCPResource` | `TypedDict total=False` | Static resource entry: `uri`, `name`, `description`, `mimeType` | |
| 391 | | `MCPResourceTemplate` | `TypedDict total=False` | RFC 6570 URI template entry: `uriTemplate`, `name`, `description`, `mimeType` | |
| 392 | | `MCPResourceContent` | `TypedDict` | Content block returned by `resources/read`: `uri`, `mimeType`, `text` | |
| 393 | |
| 394 | | Export | Type | Description | |
| 395 | |--------|------|-------------| |
| 396 | | `STATIC_RESOURCES` | `list[MCPResource]` | 5 static URIs (`trending`, `me`, `me/notifications`, `me/starred`, `me/feed`) | |
| 397 | | `RESOURCE_TEMPLATES` | `list[MCPResourceTemplate]` | 15 RFC 6570 URI templates for repos, issues, PRs, releases, users | |
| 398 | | `read_resource(uri, user_id)` | `async (str, str \| None) → dict[str, JSONValue]` | Dispatches a `musehub://` URI to the appropriate handler | |
| 399 | |
| 400 | ### Prompt Catalogue (`mcp/prompts.py`) |
| 401 | |
| 402 | TypedDicts for workflow-oriented agent guidance. |
| 403 | |
| 404 | | Name | Kind | Description | |
| 405 | |------|------|-------------| |
| 406 | | `MCPPromptArgument` | `TypedDict total=False` | Named argument for a prompt: `name` (required), `description`, `required` | |
| 407 | | `MCPPromptDef` | `TypedDict total=False` | Prompt definition: `name` (required), `description` (required), `arguments` | |
| 408 | | `MCPPromptMessageContent` | `TypedDict` | Content inside a prompt message: `type`, `text` | |
| 409 | | `MCPPromptMessage` | `TypedDict` | A single prompt message: `role`, `content` | |
| 410 | | `MCPPromptResult` | `TypedDict` | Prompt assembly result: `description`, `messages` | |
| 411 | |
| 412 | | Export | Type | Description | |
| 413 | |--------|------|-------------| |
| 414 | | `PROMPT_CATALOGUE` | `list[MCPPromptDef]` | 8 workflow prompts | |
| 415 | | `PROMPT_NAMES` | `set[str]` | All prompt name strings for fast lookup | |
| 416 | | `get_prompt(name, arguments)` | `(str, dict[str, str] \| None) → MCPPromptResult \| None` | Assembles a prompt by name with optional argument substitution | |
| 417 | |
| 418 | **Prompts:** `musehub/orientation`, `musehub/contribute`, `musehub/compose`, |
| 419 | `musehub/review_pr`, `musehub/issue_triage`, `musehub/release_prep`, |
| 420 | `musehub/onboard` _(MCP 2025-11-25 elicitation-aware)_, |
| 421 | `musehub/release_to_world` _(MCP 2025-11-25 elicitation-aware)_ |
| 422 | |
| 423 | ### Dispatcher (`mcp/dispatcher.py`) |
| 424 | |
| 425 | The pure-Python async JSON-RPC 2.0 engine. Receives parsed request dicts, |
| 426 | routes to tools/resources/prompts, and returns JSON-RPC 2.0 response dicts. |
| 427 | `ToolCallContext` is threaded into `_dispatch` so elicitation tools get access |
| 428 | to their session. |
| 429 | |
| 430 | | Export | Signature | Description | |
| 431 | |--------|-----------|-------------| |
| 432 | | `handle_request(raw, user_id, session)` | `async (JSONObject, str \| None, MCPSession \| None) → JSONObject \| None` | Handle one JSON-RPC 2.0 message; returns `None` for notifications | |
| 433 | | `handle_batch(raw, user_id, session)` | `async (list[JSONValue], str \| None, MCPSession \| None) → list[JSONObject]` | Handle a batch (array); filters out notification `None`s | |
| 434 | |
| 435 | **Supported methods:** |
| 436 | |
| 437 | | Method | Auth required | Description | |
| 438 | |--------|---------------|-------------| |
| 439 | | `initialize` | No | Handshake; advertises `elicitation` capability (MCP 2025-11-25) | |
| 440 | | `tools/list` | No | Returns all 32 tool definitions | |
| 441 | | `tools/call` | Write + elicitation tools only | Routes to read, write, or elicitation executor | |
| 442 | | `resources/list` | No | Returns 5 static resources | |
| 443 | | `resources/templates/list` | No | Returns 15 URI templates | |
| 444 | | `resources/read` | No (visibility checked) | Reads a `musehub://` URI | |
| 445 | | `prompts/list` | No | Returns 8 prompt definitions | |
| 446 | | `prompts/get` | No | Assembles a named prompt | |
| 447 | | `notifications/cancelled` | No | Cancels a pending elicitation Future | |
| 448 | | `notifications/elicitation/complete` | No | Resolves a URL-mode elicitation Future | |
| 449 | |
| 450 | ### HTTP Transport (`api/routes/mcp.py`) |
| 451 | |
| 452 | Full MCP 2025-11-25 Streamable HTTP transport. |
| 453 | |
| 454 | | Endpoint | Description | |
| 455 | |----------|-------------| |
| 456 | | `POST /mcp` | Accepts JSON or JSON array. Returns `application/json` or `text/event-stream` for elicitation tools. Validates `Origin`, `Mcp-Session-Id`, and optional `MCP-Protocol-Version`. | |
| 457 | | `GET /mcp` | Persistent SSE push channel. Requires `Mcp-Session-Id`. Supports `Last-Event-ID` replay from a 50-event ring buffer. | |
| 458 | | `DELETE /mcp` | Terminates session: drains SSE queues, cancels pending elicitation Futures. Returns `200 OK`. | |
| 459 | |
| 460 | ### Elicitation UI Routes (`api/routes/musehub/ui_mcp_elicitation.py`) |
| 461 | |
| 462 | Browser-facing pages for URL-mode OAuth elicitation flows. |
| 463 | |
| 464 | | Route | Description | |
| 465 | |-------|-------------| |
| 466 | | `GET /musehub/ui/mcp/connect/{platform_slug}` | OAuth start page for streaming platforms; renders `mcp/elicitation_connect.html` with platform context and permissions. | |
| 467 | | `GET /musehub/ui/mcp/connect/daw/{service_slug}` | OAuth start page for cloud DAW services. | |
| 468 | | `GET /musehub/ui/mcp/elicitation/{elicitation_id}/callback` | OAuth redirect target; resolves elicitation Future; renders `mcp/elicitation_callback.html` (auto-close). | |
| 469 | |
| 470 | **Templates:** |
| 471 | - `musehub/templates/mcp/elicitation_connect.html` — OAuth consent / connect page |
| 472 | - `musehub/templates/mcp/elicitation_callback.html` — Post-OAuth result page (auto-close tab) |
| 473 | |
| 474 | ### Stdio Transport (`mcp/stdio_server.py`) |
| 475 | |
| 476 | Line-delimited JSON-RPC over `stdin` / `stdout` for local development and |
| 477 | Cursor IDE integration. Registered in `.cursor/mcp.json` as: |
| 478 | |
| 479 | ```json |
| 480 | { |
| 481 | "mcpServers": { |
| 482 | "musehub": { |
| 483 | "command": "python", |
| 484 | "args": ["-m", "musehub.mcp.stdio_server"], |
| 485 | "cwd": "/Users/gabriel/musehub" |
| 486 | } |
| 487 | } |
| 488 | } |
| 489 | ``` |
| 490 | |
| 491 | --- |
| 492 | |
| 493 | ## Pydantic Base Types |
| 494 | |
| 495 | **Path:** `musehub/contracts/pydantic_types.py`, `musehub/models/base.py` |
| 496 | |
| 497 | ### `PydanticJson` |
| 498 | |
| 499 | `RootModel[str | int | float | bool | None | list["PydanticJson"] | dict[str, "PydanticJson"]]` |
| 500 | |
| 501 | The only safe recursive JSON field type for Pydantic models. Used wherever |
| 502 | a model field accepts an arbitrary JSON value (e.g. tool call parameters or |
| 503 | protocol introspection schemas). Replaces `dict[str, Any]` at every Pydantic |
| 504 | boundary. |
| 505 | |
| 506 | **Helpers:** |
| 507 | |
| 508 | | Function | Description | |
| 509 | |----------|-------------| |
| 510 | | `wrap(v: JSONValue) -> PydanticJson` | Convert a `JSONValue` to a `PydanticJson` instance | |
| 511 | | `unwrap(p: PydanticJson) -> JSONValue` | Convert a `PydanticJson` back to a `JSONValue` | |
| 512 | | `wrap_dict(d: dict[str, JSONValue]) -> dict[str, PydanticJson]` | Wrap each value in a dict | |
| 513 | |
| 514 | ### `CamelModel` |
| 515 | |
| 516 | `BaseModel` subclass. All Pydantic API models (request bodies and response |
| 517 | types) inherit from this. Configures: |
| 518 | |
| 519 | - `alias_generator = to_camel` — fields are serialised to camelCase on the wire. |
| 520 | - `populate_by_name = True` — allows snake_case names in Python code. |
| 521 | - `extra = "ignore"` — unknown fields from clients are silently dropped. |
| 522 | |
| 523 | --- |
| 524 | |
| 525 | ## Auth Types |
| 526 | |
| 527 | **Path:** `musehub/auth/tokens.py` |
| 528 | |
| 529 | ### `TokenClaims` |
| 530 | |
| 531 | `TypedDict (total=False)` — Decoded JWT payload returned by `validate_access_code`. |
| 532 | `type`, `iat`, and `exp` are always present (`Required`); `sub` and `role` are |
| 533 | optional claims added by the issuer. |
| 534 | |
| 535 | | Field | Type | Required | |
| 536 | |-------|------|----------| |
| 537 | | `type` | `str` | Yes | |
| 538 | | `iat` | `int` | Yes | |
| 539 | | `exp` | `int` | Yes | |
| 540 | | `sub` | `str` | No | |
| 541 | | `role` | `str` | No | |
| 542 | |
| 543 | --- |
| 544 | |
| 545 | ## Protocol Events |
| 546 | |
| 547 | **Path:** `musehub/protocol/events.py` |
| 548 | |
| 549 | Protocol events are Pydantic `CamelModel` subclasses of `MuseEvent`, which |
| 550 | provides `type`, `seq`, and `protocol_version` on every payload. MuseHub |
| 551 | defines two concrete event types — both in the MCP relay path. |
| 552 | |
| 553 | ### Base Class |
| 554 | |
| 555 | | Name | Kind | Description | |
| 556 | |------|------|-------------| |
| 557 | | `MuseEvent` | Pydantic (CamelModel) | Base class: `type: str`, `seq: int = -1`, `protocol_version: str` | |
| 558 | |
| 559 | ### Concrete Event Types |
| 560 | |
| 561 | | Event | `type` Literal | Description | |
| 562 | |-------|---------------|-------------| |
| 563 | | `MCPMessageEvent` | `"mcp.message"` | MCP tool-call message relayed over SSE; `payload: dict[str, object]` | |
| 564 | | `MCPPingEvent` | `"mcp.ping"` | MCP SSE keepalive heartbeat | |
| 565 | |
| 566 | The event registry (`protocol/registry.py`) maps these two type strings to |
| 567 | their model classes and is frozen at import time. |
| 568 | |
| 569 | --- |
| 570 | |
| 571 | ## Protocol HTTP Responses |
| 572 | |
| 573 | **Path:** `musehub/protocol/responses.py` |
| 574 | |
| 575 | Pydantic response models for the four protocol introspection endpoints. |
| 576 | Fields use camelCase by declaration to match the wire format. |
| 577 | |
| 578 | | Name | Route | Description | |
| 579 | |------|-------|-------------| |
| 580 | | `ProtocolInfoResponse` | `GET /protocol` | Version, hash, and registered event type list | |
| 581 | | `ProtocolEventsResponse` | `GET /protocol/events.json` | JSON Schema per event type | |
| 582 | | `ProtocolToolsResponse` | `GET /protocol/tools.json` | All registered MCP tool definitions | |
| 583 | | `ProtocolSchemaResponse` | `GET /protocol/schema.json` | Unified snapshot — version + hash + events + tools | |
| 584 | |
| 585 | The protocol hash (`protocol/hash.py`) is a SHA-256 over the serialised event |
| 586 | schemas and tool schemas, computed deterministically at request time. |
| 587 | |
| 588 | --- |
| 589 | |
| 590 | ## API Models |
| 591 | |
| 592 | **Path:** `musehub/models/musehub.py` |
| 593 | |
| 594 | All are Pydantic `CamelModel` subclasses. Organized by domain feature. |
| 595 | |
| 596 | ### Git / VCS |
| 597 | |
| 598 | | Name | Description | |
| 599 | |------|-------------| |
| 600 | | `CommitInput` | A single commit in a push payload | |
| 601 | | `ObjectInput` | A binary object in a push payload | |
| 602 | | `PushRequest` | Body for `POST /repos/{id}/push` | |
| 603 | | `PushResponse` | Push confirmation with new branch head | |
| 604 | | `PullRequest` | Body for `POST /repos/{id}/pull` | |
| 605 | | `ObjectResponse` | Binary object returned in a pull response | |
| 606 | | `PullResponse` | Pull response — missing commits and objects | |
| 607 | |
| 608 | ### Repositories |
| 609 | |
| 610 | | Name | Description | |
| 611 | |------|-------------| |
| 612 | | `CreateRepoRequest` | Repository creation wizard body | |
| 613 | | `RepoResponse` | Wire representation of a MuseHub repo | |
| 614 | | `TransferOwnershipRequest` | Transfer repo to another user | |
| 615 | | `RepoListResponse` | Paginated list of repos | |
| 616 | | `RepoStatsResponse` | Aggregated commit / branch / release counts | |
| 617 | |
| 618 | ### Branches |
| 619 | |
| 620 | | Name | Description | |
| 621 | |------|-------------| |
| 622 | | `BranchResponse` | Branch name and head commit pointer | |
| 623 | | `BranchListResponse` | Paginated list of branches | |
| 624 | | `BranchDivergenceScores` | Five-dimensional musical divergence scores | |
| 625 | | `BranchDetailResponse` | Branch with ahead/behind counts and divergence | |
| 626 | | `BranchDetailListResponse` | List of branches with detail | |
| 627 | |
| 628 | ### Commits and Tags |
| 629 | |
| 630 | | Name | Description | |
| 631 | |------|-------------| |
| 632 | | `CommitResponse` | Wire representation of a pushed commit | |
| 633 | | `CommitListResponse` | Paginated list of commits | |
| 634 | | `TagResponse` | A single tag entry | |
| 635 | | `TagListResponse` | All tags grouped by namespace | |
| 636 | |
| 637 | ### Issues |
| 638 | |
| 639 | | Name | Description | |
| 640 | |------|-------------| |
| 641 | | `IssueCreate` | Create issue body | |
| 642 | | `IssueUpdate` | Partial update body | |
| 643 | | `IssueResponse` | Wire representation of an issue | |
| 644 | | `IssueListResponse` | Paginated list of issues | |
| 645 | | `MusicalRef` | Parsed musical context reference (e.g. `track:bass`) | |
| 646 | | `IssueCommentCreate` | Create comment body | |
| 647 | | `IssueCommentResponse` | Wire representation of a comment | |
| 648 | | `IssueCommentListResponse` | Threaded discussion on an issue | |
| 649 | | `IssueAssignRequest` | Assign or unassign a user | |
| 650 | | `IssueLabelAssignRequest` | Replace the label list on an issue | |
| 651 | |
| 652 | ### Milestones |
| 653 | |
| 654 | | Name | Description | |
| 655 | |------|-------------| |
| 656 | | `MilestoneCreate` | Create milestone body | |
| 657 | | `MilestoneResponse` | Wire representation of a milestone | |
| 658 | | `MilestoneListResponse` | List of milestones | |
| 659 | |
| 660 | ### Pull Requests |
| 661 | |
| 662 | | Name | Description | |
| 663 | |------|-------------| |
| 664 | | `PRCreate` | Create PR body | |
| 665 | | `PRResponse` | Wire representation of a pull request | |
| 666 | | `PRListResponse` | Paginated list of pull requests | |
| 667 | | `PRMergeRequest` | Merge strategy selection body | |
| 668 | | `PRMergeResponse` | Merge confirmation | |
| 669 | | `PRDiffDimensionScore` | Per-dimension musical change score | |
| 670 | | `PRDiffResponse` | Musical diff between PR branches | |
| 671 | | `PRCommentCreate` | PR review comment body (supports four targeting granularities) | |
| 672 | | `PRCommentResponse` | Wire representation of a PR review comment | |
| 673 | | `PRCommentListResponse` | Threaded list of PR comments | |
| 674 | | `PRReviewerRequest` | Request reviewers | |
| 675 | | `PRReviewCreate` | Submit a formal review (approve / request_changes / comment) | |
| 676 | | `PRReviewResponse` | Wire representation of a review | |
| 677 | | `PRReviewListResponse` | List of reviews for a PR | |
| 678 | |
| 679 | ### Releases |
| 680 | |
| 681 | | Name | Description | |
| 682 | |------|-------------| |
| 683 | | `ReleaseCreate` | Create release body | |
| 684 | | `ReleaseDownloadUrls` | Structured download package URLs | |
| 685 | | `ReleaseResponse` | Wire representation of a release | |
| 686 | | `ReleaseListResponse` | List of releases | |
| 687 | | `ReleaseAssetCreate` | Attach asset to release | |
| 688 | | `ReleaseAssetResponse` | Wire representation of an asset | |
| 689 | | `ReleaseAssetListResponse` | Assets for a release | |
| 690 | | `ReleaseAssetDownloadCount` | Per-asset download count | |
| 691 | | `ReleaseDownloadStatsResponse` | Download counts for all assets | |
| 692 | |
| 693 | ### Profile and Social |
| 694 | |
| 695 | | Name | Description | |
| 696 | |------|-------------| |
| 697 | | `ProfileUpdateRequest` | Update profile body | |
| 698 | | `ProfileRepoSummary` | Compact repo summary on a profile page | |
| 699 | | `ProfileResponse` | Full wire representation of a user profile | |
| 700 | | `ContributorCredits` | Single contributor's credit record | |
| 701 | | `CreditsResponse` | Full credits roll for a repo | |
| 702 | | `StarResponse` | Star added / removed confirmation | |
| 703 | | `ContributionDay` | One day in the contribution heatmap | |
| 704 | |
| 705 | ### Discovery and Search |
| 706 | |
| 707 | | Name | Description | |
| 708 | |------|-------------| |
| 709 | | `ExploreRepoResult` | Public repo card on the explore page | |
| 710 | | `ExploreResponse` | Paginated discover response | |
| 711 | | `SearchCommitMatch` | Single commit returned by an in-repo search | |
| 712 | | `SearchResponse` | Response for all four in-repo search modes | |
| 713 | | `GlobalSearchCommitMatch` | Commit match in a cross-repo search | |
| 714 | | `GlobalSearchRepoGroup` | All matches for one repo in a cross-repo search | |
| 715 | | `GlobalSearchResult` | Top-level cross-repo search response | |
| 716 | |
| 717 | ### Timeline, DAG, and Analytics |
| 718 | |
| 719 | | Name | Description | |
| 720 | |------|-------------| |
| 721 | | `TimelineCommitEvent` | A commit plotted on the timeline | |
| 722 | | `TimelineEmotionEvent` | Emotion-vector data point on the timeline | |
| 723 | | `TimelineSectionEvent` | Detected section change marker | |
| 724 | | `TimelineTrackEvent` | Track addition or removal event | |
| 725 | | `TimelineResponse` | Chronological musical evolution timeline | |
| 726 | | `DivergenceDimensionResponse` | Per-dimension divergence score | |
| 727 | | `DivergenceResponse` | Full musical divergence report between two branches | |
| 728 | | `CommitDiffDimensionScore` | Per-dimension change score vs parent | |
| 729 | | `CommitDiffSummaryResponse` | Multi-dimensional diff summary | |
| 730 | | `DagNode` | Single commit node in the DAG | |
| 731 | | `DagEdge` | Directed edge in the commit DAG | |
| 732 | | `DagGraphResponse` | Topologically sorted commit graph | |
| 733 | |
| 734 | ### Webhooks |
| 735 | |
| 736 | | Name | Description | |
| 737 | |------|-------------| |
| 738 | | `WebhookCreate` | Create webhook body | |
| 739 | | `WebhookResponse` | Wire representation of a webhook | |
| 740 | | `WebhookListResponse` | List of webhooks | |
| 741 | | `WebhookDeliveryResponse` | Single delivery attempt | |
| 742 | | `WebhookDeliveryListResponse` | Delivery history | |
| 743 | | `WebhookRedeliverResponse` | Redeliver confirmation | |
| 744 | | `PushEventPayload` | TypedDict — push event webhook payload | |
| 745 | | `IssueEventPayload` | TypedDict — issue event webhook payload | |
| 746 | | `PullRequestEventPayload` | TypedDict (total=False) — PR event webhook payload | |
| 747 | | `WebhookEventPayload` | TypeAlias — `PushEventPayload \| IssueEventPayload \| PullRequestEventPayload` | |
| 748 | |
| 749 | ### Context |
| 750 | |
| 751 | | Name | Description | |
| 752 | |------|-------------| |
| 753 | | `MuseHubContextCommitInfo` | Minimal commit metadata in a context document | |
| 754 | | `MuseHubContextHistoryEntry` | Ancestor commit in evolutionary history | |
| 755 | | `MuseHubContextMusicalState` | Musical state at the target commit | |
| 756 | | `MuseHubContextResponse` | Full context document for a commit | |
| 757 | |
| 758 | ### Objects |
| 759 | |
| 760 | | Name | Description | |
| 761 | |------|-------------| |
| 762 | | `ObjectMetaResponse` | Artifact metadata (no content) | |
| 763 | | `ObjectMetaListResponse` | List of artifact metadata | |
| 764 | |
| 765 | --- |
| 766 | |
| 767 | ## Database ORM Models |
| 768 | |
| 769 | **Path:** `musehub/db/` |
| 770 | |
| 771 | All are SQLAlchemy ORM subclasses of a declarative `Base`. |
| 772 | |
| 773 | ### `db/models.py` — Auth |
| 774 | |
| 775 | | Model | Table | Description | |
| 776 | |-------|-------|-------------| |
| 777 | | `User` | `muse_users` | User account; `id` = JWT `sub` claim | |
| 778 | | `AccessToken` | `muse_access_tokens` | JWT token tracking — stores SHA-256 hash, never the raw token | |
| 779 | |
| 780 | ### `db/muse_cli_models.py` — Muse CLI VCS |
| 781 | |
| 782 | | Model | Table | Description | |
| 783 | |-------|-------|-------------| |
| 784 | | `MuseCliObject` | `muse_objects` | Content-addressed blob | |
| 785 | | `MuseCliSnapshot` | `muse_snapshots` | Immutable snapshot manifest | |
| 786 | | `MuseCliCommit` | `muse_commits` | Versioned commit pointing to a snapshot | |
| 787 | | `MuseCliTag` | `muse_tags` | Music-semantic tag on a CLI commit | |
| 788 | |
| 789 | ### `db/musehub_models.py` — MuseHub Core |
| 790 | |
| 791 | | Model | Table | Description | |
| 792 | |-------|-------|-------------| |
| 793 | | `MusehubRepo` | `musehub_repos` | Remote repository with music-semantic metadata | |
| 794 | | `MusehubBranch` | `musehub_branches` | Named branch pointer | |
| 795 | | `MusehubCommit` | `musehub_commits` | Commit pushed to MuseHub | |
| 796 | | `MusehubObject` | `musehub_objects` | Content-addressed binary artifact | |
| 797 | | `MusehubMilestone` | `musehub_milestones` | Milestone grouping issues | |
| 798 | | `MusehubIssueMilestone` | `musehub_issue_milestones` | Issue ↔ Milestone join table | |
| 799 | | `MusehubIssue` | `musehub_issues` | Issue opened against a repo | |
| 800 | | `MusehubIssueComment` | `musehub_issue_comments` | Threaded issue comment | |
| 801 | | `MusehubPullRequest` | `musehub_pull_requests` | Pull request | |
| 802 | | `MusehubPRReview` | `musehub_pr_reviews` | Formal PR review submission | |
| 803 | | `MusehubPRComment` | `musehub_pr_comments` | Inline musical diff comment on a PR | |
| 804 | | `MusehubRelease` | `musehub_releases` | Published version release | |
| 805 | | `MusehubReleaseAsset` | `musehub_release_assets` | Downloadable file attachment | |
| 806 | | `MusehubProfile` | `musehub_profiles` | Public user musical portfolio | |
| 807 | | `MusehubWebhook` | `musehub_webhooks` | Registered webhook subscription | |
| 808 | | `MusehubWebhookDelivery` | `musehub_webhook_deliveries` | Single webhook delivery attempt | |
| 809 | | `MusehubStar` | `musehub_stars` | User star on a public repo | |
| 810 | | `MusehubSession` | `musehub_sessions` | Recording session record pushed from the CLI | |
| 811 | | `MusehubComment` | `musehub_comments` | Threaded comment on any repo object | |
| 812 | | `MusehubReaction` | `musehub_reactions` | Emoji reaction on a comment or target | |
| 813 | | `MusehubFollow` | `musehub_follows` | User follows user — social graph | |
| 814 | | `MusehubWatch` | `musehub_watches` | User watches a repo for notifications | |
| 815 | | `MusehubNotification` | `musehub_notifications` | Notification delivered to a user | |
| 816 | | `MusehubFork` | `musehub_forks` | Fork relationship between two repos | |
| 817 | | `MusehubViewEvent` | `musehub_view_events` | Debounced repo view (one row per visitor per day) | |
| 818 | | `MusehubDownloadEvent` | `musehub_download_events` | Artifact export download event | |
| 819 | | `MusehubRenderJob` | `musehub_render_jobs` | Async MP3/piano-roll render job tracking | |
| 820 | | `MusehubEvent` | `musehub_events` | Append-only repo activity event stream | |
| 821 | |
| 822 | ### `db/musehub_collaborator_models.py` |
| 823 | |
| 824 | | Model | Table | Description | |
| 825 | |-------|-------|-------------| |
| 826 | | `MusehubCollaborator` | `musehub_collaborators` | Explicit push/admin access grant for a user on a repo | |
| 827 | |
| 828 | ### `db/musehub_label_models.py` |
| 829 | |
| 830 | | Model | Table | Description | |
| 831 | |-------|-------|-------------| |
| 832 | | `MusehubLabel` | `musehub_labels` | Coloured label tag scoped to a repo | |
| 833 | | `MusehubIssueLabel` | `musehub_issue_labels` | Issue ↔ Label join table | |
| 834 | | `MusehubPRLabel` | `musehub_pr_labels` | PR ↔ Label join table | |
| 835 | |
| 836 | ### `db/musehub_stash_models.py` |
| 837 | |
| 838 | | Model | Table | Description | |
| 839 | |-------|-------|-------------| |
| 840 | | `MusehubStash` | `musehub_stash` | Named save point for uncommitted changes | |
| 841 | | `MusehubStashEntry` | `musehub_stash_entries` | Single MIDI file snapshot within a stash | |
| 842 | |
| 843 | --- |
| 844 | |
| 845 | ## Entity Hierarchy |
| 846 | |
| 847 | ``` |
| 848 | MuseHub |
| 849 | │ |
| 850 | ├── MIDI Type Aliases (contracts/midi_types.py) |
| 851 | │ ├── MidiPitch, MidiVelocity, MidiChannel, MidiCC, MidiCCValue |
| 852 | │ ├── MidiAftertouchValue, MidiGMProgram, MidiPitchBend, MidiBPM |
| 853 | │ └── BeatPosition, BeatDuration, ArrangementBeat, ArrangementDuration, Bars |
| 854 | │ |
| 855 | ├── JSON Types (contracts/json_types.py) |
| 856 | │ ├── Primitives: JSONScalar, JSONValue, JSONObject |
| 857 | │ ├── Region maps: RegionNotesMap, RegionCCMap, RegionPitchBendMap, RegionAftertouchMap |
| 858 | │ ├── Protocol aliases: EventJsonSchema, EventSchemaMap |
| 859 | │ └── TypedDicts: NoteDict, CCEventDict, PitchBendDict, AftertouchDict, SectionDict |
| 860 | │ |
| 861 | ├── MCP Protocol (contracts/mcp_types.py) |
| 862 | │ ├── MCPRequest — TypedDict (total=False) |
| 863 | │ ├── MCPResponse — MCPSuccessResponse | MCPErrorResponse |
| 864 | │ ├── MCPMethodResponse — 5-way union of concrete response types |
| 865 | │ ├── MCPToolDef — TypedDict (total=False) |
| 866 | │ ├── DAWToolCallMessage — TypedDict |
| 867 | │ ├── DAWToolResponse — TypedDict (total=False) |
| 868 | │ └── MCPToolDefWire — Pydantic (FastAPI serialisation) |
| 869 | │ |
| 870 | ├── Pydantic Base (contracts/pydantic_types.py, models/base.py) |
| 871 | │ ├── PydanticJson — RootModel: recursive JSON, safe for Pydantic fields |
| 872 | │ └── CamelModel — BaseModel: camelCase alias, populate_by_name, extra=ignore |
| 873 | │ |
| 874 | ├── Auth (auth/tokens.py) |
| 875 | │ └── TokenClaims — TypedDict (total=False): decoded JWT payload |
| 876 | │ |
| 877 | ├── Protocol Events (protocol/events.py) |
| 878 | │ ├── MuseEvent — Pydantic base: type + seq + protocol_version |
| 879 | │ ├── MCPMessageEvent — type: "mcp.message"; payload: dict[str, object] |
| 880 | │ └── MCPPingEvent — type: "mcp.ping" (keepalive) |
| 881 | │ |
| 882 | ├── Protocol Introspection (protocol/) |
| 883 | │ ├── EVENT_REGISTRY — dict[str, type[MuseEvent]] (2 entries) |
| 884 | │ ├── compute_protocol_hash() — SHA-256 over events + tools schemas |
| 885 | │ ├── ProtocolInfoResponse — GET /protocol |
| 886 | │ ├── ProtocolEventsResponse — GET /protocol/events.json |
| 887 | │ ├── ProtocolToolsResponse — GET /protocol/tools.json |
| 888 | │ └── ProtocolSchemaResponse — GET /protocol/schema.json |
| 889 | │ |
| 890 | ├── MCP Integration Layer (mcp/) |
| 891 | │ ├── Tools (mcp/tools/) |
| 892 | │ │ ├── MUSEHUB_READ_TOOLS — 20 read tool definitions |
| 893 | │ │ ├── MUSEHUB_WRITE_TOOLS — 15 write tool definitions |
| 894 | │ │ ├── MUSEHUB_TOOLS — combined 40-tool catalogue |
| 895 | │ │ ├── MUSEHUB_TOOL_NAMES — set[str] for routing |
| 896 | │ │ ├── MUSEHUB_WRITE_TOOL_NAMES — set[str] auth-gated writes |
| 897 | │ │ ├── MCP_TOOLS — registered list (alias) |
| 898 | │ │ └── TOOL_CATEGORIES — dict[str, str] tool → category |
| 899 | │ ├── Resources (mcp/resources.py) |
| 900 | │ │ ├── MCPResource — TypedDict (total=False): static resource |
| 901 | │ │ ├── MCPResourceTemplate — TypedDict (total=False): RFC 6570 template |
| 902 | │ │ ├── MCPResourceContent — TypedDict: read result content block |
| 903 | │ │ ├── STATIC_RESOURCES — 5 static musehub:// URIs |
| 904 | │ │ ├── RESOURCE_TEMPLATES — 15 RFC 6570 URI templates |
| 905 | │ │ └── read_resource() — async URI dispatcher |
| 906 | │ ├── Prompts (mcp/prompts.py) |
| 907 | │ │ ├── MCPPromptArgument — TypedDict (total=False): prompt argument |
| 908 | │ │ ├── MCPPromptDef — TypedDict (total=False): prompt definition |
| 909 | │ │ ├── MCPPromptMessage — TypedDict: role + content |
| 910 | │ │ ├── MCPPromptResult — TypedDict: description + messages |
| 911 | │ │ ├── PROMPT_CATALOGUE — 6 workflow prompts |
| 912 | │ │ ├── PROMPT_NAMES — set[str] for lookup |
| 913 | │ │ └── get_prompt() — assembler with argument substitution |
| 914 | │ ├── Dispatcher (mcp/dispatcher.py) |
| 915 | │ │ ├── handle_request() — async JSON-RPC 2.0 single request |
| 916 | │ │ └── handle_batch() — async JSON-RPC 2.0 batch |
| 917 | │ ├── HTTP Transport (api/routes/mcp.py) |
| 918 | │ │ └── POST /mcp — HTTP Streamable, JWT auth, batch support |
| 919 | │ └── Stdio Transport (mcp/stdio_server.py) |
| 920 | │ └── line-delimited JSON-RPC over stdin/stdout |
| 921 | │ |
| 922 | ├── API Models (models/musehub.py) ~98 Pydantic models |
| 923 | │ ├── VCS: PushRequest, PullResponse, … |
| 924 | │ ├── Repos: CreateRepoRequest, RepoResponse, … |
| 925 | │ ├── Issues: IssueCreate, IssueResponse, IssueCommentResponse, … |
| 926 | │ ├── Pull Requests: PRCreate, PRResponse, PRDiffResponse, … |
| 927 | │ ├── Releases: ReleaseCreate, ReleaseResponse, ReleaseAssetResponse, … |
| 928 | │ ├── Profile + Social: ProfileResponse, StarResponse, … |
| 929 | │ ├── Discovery: ExploreResponse, SearchResponse, GlobalSearchResult, … |
| 930 | │ ├── Timeline + DAG: TimelineResponse, DagGraphResponse, … |
| 931 | │ └── Webhooks: WebhookCreate, WebhookDeliveryResponse, … |
| 932 | │ |
| 933 | └── Database ORM (db/) 37 SQLAlchemy models |
| 934 | ├── Auth: User, AccessToken |
| 935 | ├── CLI VCS: MuseCliObject, MuseCliSnapshot, MuseCliCommit, MuseCliTag |
| 936 | ├── Hub Core: MusehubRepo, MusehubBranch, MusehubCommit, MusehubObject, … |
| 937 | ├── Social: MusehubStar, MusehubFollow, MusehubWatch, MusehubNotification, … |
| 938 | ├── Labels: MusehubLabel, MusehubIssueLabel, MusehubPRLabel |
| 939 | └── Stash: MusehubStash, MusehubStashEntry |
| 940 | ``` |
| 941 | |
| 942 | --- |
| 943 | |
| 944 | ## Entity Graphs (Mermaid) |
| 945 | |
| 946 | Arrow conventions: |
| 947 | - `*--` composition (owns, lifecycle-coupled) |
| 948 | - `-->` association (references) |
| 949 | - `..>` dependency (uses / produces) |
| 950 | - `..|>` implements / extends |
| 951 | |
| 952 | --- |
| 953 | |
| 954 | ### Diagram 1 — MIDI Note and Event Wire Types |
| 955 | |
| 956 | The core typed shapes for MIDI data. `NoteDict` is dual-keyed (camelCase wire |
| 957 | + snake_case storage). The region maps alias these into domain containers. |
| 958 | |
| 959 | ```mermaid |
| 960 | classDiagram |
| 961 | class NoteDict { |
| 962 | <<TypedDict total=False>> |
| 963 | pitch : MidiPitch |
| 964 | velocity : MidiVelocity |
| 965 | channel : MidiChannel |
| 966 | startBeat : BeatPosition |
| 967 | durationBeats : BeatDuration |
| 968 | noteId : str |
| 969 | trackId : str |
| 970 | regionId : str |
| 971 | start_beat : BeatPosition |
| 972 | duration_beats : BeatDuration |
| 973 | note_id : str |
| 974 | track_id : str |
| 975 | region_id : str |
| 976 | layer : str |
| 977 | } |
| 978 | class CCEventDict { |
| 979 | <<TypedDict>> |
| 980 | cc : MidiCC |
| 981 | beat : BeatPosition |
| 982 | value : MidiCCValue |
| 983 | } |
| 984 | class PitchBendDict { |
| 985 | <<TypedDict>> |
| 986 | beat : BeatPosition |
| 987 | value : MidiPitchBend |
| 988 | } |
| 989 | class AftertouchDict { |
| 990 | <<TypedDict total=False>> |
| 991 | beat : BeatPosition (Required) |
| 992 | value : MidiAftertouchValue (Required) |
| 993 | pitch : MidiPitch |
| 994 | } |
| 995 | class SectionDict { |
| 996 | <<TypedDict total=False>> |
| 997 | name : str |
| 998 | start_beat : float |
| 999 | length_beats : float |
| 1000 | description : str |
| 1001 | per_track_description : dict~str, str~ |
| 1002 | } |
| 1003 | class RegionNotesMap { |
| 1004 | <<TypeAlias>> |
| 1005 | dict~str, list~NoteDict~~ |
| 1006 | } |
| 1007 | class RegionCCMap { |
| 1008 | <<TypeAlias>> |
| 1009 | dict~str, list~CCEventDict~~ |
| 1010 | } |
| 1011 | class RegionPitchBendMap { |
| 1012 | <<TypeAlias>> |
| 1013 | dict~str, list~PitchBendDict~~ |
| 1014 | } |
| 1015 | class RegionAftertouchMap { |
| 1016 | <<TypeAlias>> |
| 1017 | dict~str, list~AftertouchDict~~ |
| 1018 | } |
| 1019 | |
| 1020 | RegionNotesMap ..> NoteDict : list element |
| 1021 | RegionCCMap ..> CCEventDict : list element |
| 1022 | RegionPitchBendMap ..> PitchBendDict : list element |
| 1023 | RegionAftertouchMap ..> AftertouchDict : list element |
| 1024 | ``` |
| 1025 | |
| 1026 | --- |
| 1027 | |
| 1028 | ### Diagram 2 — MCP Protocol Event Types |
| 1029 | |
| 1030 | The two concrete events MuseHub relays over SSE — both in the MCP path. |
| 1031 | `MuseEvent` is the typed base; the event registry maps type strings to |
| 1032 | model classes. |
| 1033 | |
| 1034 | ```mermaid |
| 1035 | classDiagram |
| 1036 | class MuseEvent { |
| 1037 | <<Pydantic CamelModel>> |
| 1038 | +type : str |
| 1039 | +seq : int = -1 |
| 1040 | +protocol_version : str |
| 1041 | extra = "forbid" |
| 1042 | } |
| 1043 | class MCPMessageEvent { |
| 1044 | <<Pydantic CamelModel>> |
| 1045 | +type : Literal~"mcp.message"~ |
| 1046 | +payload : dict~str, object~ |
| 1047 | } |
| 1048 | class MCPPingEvent { |
| 1049 | <<Pydantic CamelModel>> |
| 1050 | +type : Literal~"mcp.ping"~ |
| 1051 | } |
| 1052 | class EVENT_REGISTRY { |
| 1053 | <<dict frozen at import>> |
| 1054 | "mcp.message" : MCPMessageEvent |
| 1055 | "mcp.ping" : MCPPingEvent |
| 1056 | } |
| 1057 | |
| 1058 | MCPMessageEvent --|> MuseEvent |
| 1059 | MCPPingEvent --|> MuseEvent |
| 1060 | EVENT_REGISTRY ..> MCPMessageEvent : registers |
| 1061 | EVENT_REGISTRY ..> MCPPingEvent : registers |
| 1062 | ``` |
| 1063 | |
| 1064 | --- |
| 1065 | |
| 1066 | ### Diagram 3 — Protocol Introspection |
| 1067 | |
| 1068 | The hash computation pipeline and the four HTTP response types it drives. |
| 1069 | |
| 1070 | ```mermaid |
| 1071 | classDiagram |
| 1072 | class ProtocolInfoResponse { |
| 1073 | <<Pydantic BaseModel>> |
| 1074 | +protocolVersion : str |
| 1075 | +protocolHash : str |
| 1076 | +eventTypes : list~str~ |
| 1077 | +eventCount : int |
| 1078 | } |
| 1079 | class ProtocolEventsResponse { |
| 1080 | <<Pydantic BaseModel>> |
| 1081 | +protocolVersion : str |
| 1082 | +events : dict~str, PydanticJson~ |
| 1083 | } |
| 1084 | class ProtocolToolsResponse { |
| 1085 | <<Pydantic BaseModel>> |
| 1086 | +protocolVersion : str |
| 1087 | +tools : list~MCPToolDefWire~ |
| 1088 | +toolCount : int |
| 1089 | } |
| 1090 | class ProtocolSchemaResponse { |
| 1091 | <<Pydantic BaseModel>> |
| 1092 | +protocolVersion : str |
| 1093 | +protocolHash : str |
| 1094 | +events : dict~str, PydanticJson~ |
| 1095 | +tools : list~MCPToolDefWire~ |
| 1096 | +toolCount : int |
| 1097 | +eventCount : int |
| 1098 | } |
| 1099 | class MCPToolDefWire { |
| 1100 | <<Pydantic BaseModel>> |
| 1101 | +name : str |
| 1102 | +description : str |
| 1103 | +inputSchema : MCPInputSchemaWire |
| 1104 | } |
| 1105 | |
| 1106 | ProtocolSchemaResponse ..> MCPToolDefWire : tools list |
| 1107 | ProtocolToolsResponse ..> MCPToolDefWire : tools list |
| 1108 | ProtocolSchemaResponse ..> ProtocolEventsResponse : events subset |
| 1109 | ProtocolSchemaResponse ..> ProtocolToolsResponse : tools subset |
| 1110 | ``` |
| 1111 | |
| 1112 | --- |
| 1113 | |
| 1114 | ### Diagram 4 — MCP Tool Routing |
| 1115 | |
| 1116 | How a tool name flows from an incoming request through the server routing layer |
| 1117 | to the executor and back as a `ToolCallResult`. |
| 1118 | |
| 1119 | ```mermaid |
| 1120 | classDiagram |
| 1121 | class MuseMCPServer { |
| 1122 | +call_tool(name: str, params: dict) ToolCallResult |
| 1123 | -_execute_musehub_tool(name, params) MusehubToolResult |
| 1124 | -_build_result(result: MusehubToolResult) ToolCallResult |
| 1125 | } |
| 1126 | class ToolCallResult { |
| 1127 | <<dataclass>> |
| 1128 | +success : bool |
| 1129 | +is_error : bool |
| 1130 | +bad_request : bool |
| 1131 | +content : list~dict~str, str~~ |
| 1132 | } |
| 1133 | class MusehubToolResult { |
| 1134 | <<dataclass frozen>> |
| 1135 | +ok : bool |
| 1136 | +data : dict~str, JSONValue~ |
| 1137 | +error_code : MusehubErrorCode | None |
| 1138 | +error_message : str | None |
| 1139 | } |
| 1140 | class MusehubErrorCode { |
| 1141 | <<Literal>> |
| 1142 | "not_found" |
| 1143 | "invalid_dimension" |
| 1144 | "invalid_mode" |
| 1145 | "db_unavailable" |
| 1146 | } |
| 1147 | class MUSEHUB_READ_TOOLS { |
| 1148 | <<list of MCPToolDef — 20 tools>> |
| 1149 | musehub_get_context · musehub_list_branches |
| 1150 | musehub_list_commits · musehub_read_file |
| 1151 | musehub_search · musehub_get_commit |
| 1152 | musehub_compare · musehub_list_issues |
| 1153 | musehub_get_issue · musehub_list_prs |
| 1154 | musehub_get_pr · musehub_list_releases |
| 1155 | musehub_search_repos · musehub_list_domains |
| 1156 | musehub_get_domain · musehub_get_domain_insights |
| 1157 | musehub_get_view · musehub_whoami |
| 1158 | muse_pull · muse_remote |
| 1159 | } |
| 1160 | class MUSEHUB_WRITE_TOOLS { |
| 1161 | <<list of MCPToolDef — 15 tools>> |
| 1162 | musehub_create_repo · musehub_fork_repo |
| 1163 | musehub_create_issue · musehub_update_issue |
| 1164 | musehub_create_issue_comment · musehub_create_pr |
| 1165 | musehub_merge_pr · musehub_create_pr_comment |
| 1166 | musehub_submit_pr_review · musehub_create_release |
| 1167 | musehub_star_repo · musehub_create_label |
| 1168 | musehub_create_agent_token · muse_push · muse_config |
| 1169 | } |
| 1170 | class MCPDispatcher { |
| 1171 | +handle_request(raw, user_id) JSONObject | None |
| 1172 | +handle_batch(raw, user_id) list~JSONObject~ |
| 1173 | -initialize() MCPSuccessResponse |
| 1174 | -tools_list() MCPSuccessResponse |
| 1175 | -tools_call(name, args, user_id) MCPSuccessResponse |
| 1176 | -resources_read(uri, user_id) MCPSuccessResponse |
| 1177 | -prompts_get(name, args) MCPSuccessResponse |
| 1178 | } |
| 1179 | |
| 1180 | MCPDispatcher --> ToolCallResult : returns |
| 1181 | MCPDispatcher ..> MusehubToolResult : executor produces |
| 1182 | MusehubToolResult --> MusehubErrorCode : error_code |
| 1183 | MCPDispatcher ..> MUSEHUB_READ_TOOLS : routes read calls |
| 1184 | MCPDispatcher ..> MUSEHUB_WRITE_TOOLS : routes write calls (auth required) |
| 1185 | ``` |
| 1186 | |
| 1187 | --- |
| 1188 | |
| 1189 | ### Diagram 5 — MCP JSON-RPC Wire Types |
| 1190 | |
| 1191 | The full JSON-RPC 2.0 type hierarchy used by the MCP HTTP adapter. |
| 1192 | |
| 1193 | ```mermaid |
| 1194 | classDiagram |
| 1195 | class MCPRequest { |
| 1196 | <<TypedDict total=False>> |
| 1197 | +jsonrpc : str |
| 1198 | +id : str | int | None |
| 1199 | +method : str |
| 1200 | +params : dict~str, object~ |
| 1201 | } |
| 1202 | class MCPSuccessResponse { |
| 1203 | <<TypedDict>> |
| 1204 | +jsonrpc : str |
| 1205 | +id : str | int | None |
| 1206 | +result : object |
| 1207 | } |
| 1208 | class MCPErrorDetail { |
| 1209 | <<TypedDict total=False>> |
| 1210 | +code : int |
| 1211 | +message : str |
| 1212 | +data : object |
| 1213 | } |
| 1214 | class MCPErrorResponse { |
| 1215 | <<TypedDict>> |
| 1216 | +jsonrpc : str |
| 1217 | +id : str | int | None |
| 1218 | +error : MCPErrorDetail |
| 1219 | } |
| 1220 | class MCPToolDef { |
| 1221 | <<TypedDict total=False>> |
| 1222 | +name : str |
| 1223 | +description : str |
| 1224 | +inputSchema : MCPInputSchema |
| 1225 | +server_side : bool |
| 1226 | } |
| 1227 | class MCPContentBlock { |
| 1228 | <<TypedDict>> |
| 1229 | +type : str |
| 1230 | +text : str |
| 1231 | } |
| 1232 | class MCPCallResult { |
| 1233 | <<TypedDict total=False>> |
| 1234 | +content : list~MCPContentBlock~ |
| 1235 | +isError : bool |
| 1236 | } |
| 1237 | |
| 1238 | MCPErrorResponse *-- MCPErrorDetail : error |
| 1239 | MCPCallResult *-- MCPContentBlock : content |
| 1240 | MCPToolDef ..> MCPContentBlock : tools/call returns |
| 1241 | ``` |
| 1242 | |
| 1243 | --- |
| 1244 | |
| 1245 | ### Diagram 6 — Auth and JWT |
| 1246 | |
| 1247 | Token lifecycle from issuance through validation to the decoded `TokenClaims`. |
| 1248 | |
| 1249 | ```mermaid |
| 1250 | classDiagram |
| 1251 | class TokenClaims { |
| 1252 | <<TypedDict total=False>> |
| 1253 | +type : str (Required) |
| 1254 | +iat : int (Required) |
| 1255 | +exp : int (Required) |
| 1256 | +sub : str |
| 1257 | +role : str |
| 1258 | } |
| 1259 | class AccessToken { |
| 1260 | <<SQLAlchemy ORM>> |
| 1261 | +id : int |
| 1262 | +token_hash : str |
| 1263 | +user_id : str |
| 1264 | +created_at : datetime |
| 1265 | +expires_at : datetime |
| 1266 | +revoked : bool |
| 1267 | } |
| 1268 | class User { |
| 1269 | <<SQLAlchemy ORM>> |
| 1270 | +id : str (JWT sub) |
| 1271 | +username : str |
| 1272 | +email : str |
| 1273 | +created_at : datetime |
| 1274 | } |
| 1275 | |
| 1276 | AccessToken --> User : user_id FK |
| 1277 | TokenClaims ..> User : sub maps to User.id |
| 1278 | TokenClaims ..> AccessToken : validated against token_hash |
| 1279 | ``` |
| 1280 | |
| 1281 | --- |
| 1282 | |
| 1283 | ### Diagram 7 — MuseHub Repository Object Graph |
| 1284 | |
| 1285 | The core VCS entity relationships in the database. |
| 1286 | |
| 1287 | ```mermaid |
| 1288 | classDiagram |
| 1289 | class MusehubRepo { |
| 1290 | <<SQLAlchemy ORM>> |
| 1291 | +id : str |
| 1292 | +name : str |
| 1293 | +owner_id : str |
| 1294 | +description : str |
| 1295 | +is_public : bool |
| 1296 | +created_at : datetime |
| 1297 | } |
| 1298 | class MusehubBranch { |
| 1299 | <<SQLAlchemy ORM>> |
| 1300 | +id : str |
| 1301 | +repo_id : str (FK) |
| 1302 | +name : str |
| 1303 | +head_commit_id : str |
| 1304 | } |
| 1305 | class MusehubCommit { |
| 1306 | <<SQLAlchemy ORM>> |
| 1307 | +id : str |
| 1308 | +repo_id : str (FK) |
| 1309 | +branch_id : str (FK) |
| 1310 | +snapshot_id : str |
| 1311 | +message : str |
| 1312 | +committed_at : datetime |
| 1313 | } |
| 1314 | class MusehubObject { |
| 1315 | <<SQLAlchemy ORM>> |
| 1316 | +id : str |
| 1317 | +repo_id : str (FK) |
| 1318 | +commit_id : str (FK) |
| 1319 | +path : str |
| 1320 | +content_hash : str |
| 1321 | +mime_type : str |
| 1322 | } |
| 1323 | class MusehubIssue { |
| 1324 | <<SQLAlchemy ORM>> |
| 1325 | +id : str |
| 1326 | +repo_id : str (FK) |
| 1327 | +title : str |
| 1328 | +state : str |
| 1329 | +created_at : datetime |
| 1330 | } |
| 1331 | class MusehubPullRequest { |
| 1332 | <<SQLAlchemy ORM>> |
| 1333 | +id : str |
| 1334 | +repo_id : str (FK) |
| 1335 | +source_branch : str |
| 1336 | +target_branch : str |
| 1337 | +state : str |
| 1338 | } |
| 1339 | class MusehubRelease { |
| 1340 | <<SQLAlchemy ORM>> |
| 1341 | +id : str |
| 1342 | +repo_id : str (FK) |
| 1343 | +tag : str |
| 1344 | +title : str |
| 1345 | +created_at : datetime |
| 1346 | } |
| 1347 | |
| 1348 | MusehubRepo *-- MusehubBranch : branches |
| 1349 | MusehubRepo *-- MusehubCommit : commits |
| 1350 | MusehubRepo *-- MusehubObject : objects |
| 1351 | MusehubRepo *-- MusehubIssue : issues |
| 1352 | MusehubRepo *-- MusehubPullRequest : pull requests |
| 1353 | MusehubRepo *-- MusehubRelease : releases |
| 1354 | MusehubBranch --> MusehubCommit : head_commit_id |
| 1355 | MusehubCommit --> MusehubObject : objects |
| 1356 | ``` |
| 1357 | |
| 1358 | --- |
| 1359 | |
| 1360 | ### Diagram 8 — Social and Discovery Graph |
| 1361 | |
| 1362 | User-to-user and user-to-repo relationships powering the social feed and |
| 1363 | discovery pages. |
| 1364 | |
| 1365 | ```mermaid |
| 1366 | classDiagram |
| 1367 | class User { |
| 1368 | <<SQLAlchemy ORM>> |
| 1369 | +id : str |
| 1370 | +username : str |
| 1371 | } |
| 1372 | class MusehubProfile { |
| 1373 | <<SQLAlchemy ORM>> |
| 1374 | +user_id : str (FK) |
| 1375 | +display_name : str |
| 1376 | +bio : str |
| 1377 | } |
| 1378 | class MusehubStar { |
| 1379 | <<SQLAlchemy ORM>> |
| 1380 | +user_id : str (FK) |
| 1381 | +repo_id : str (FK) |
| 1382 | +starred_at : datetime |
| 1383 | } |
| 1384 | class MusehubFollow { |
| 1385 | <<SQLAlchemy ORM>> |
| 1386 | +follower_id : str (FK) |
| 1387 | +followee_id : str (FK) |
| 1388 | +created_at : datetime |
| 1389 | } |
| 1390 | class MusehubWatch { |
| 1391 | <<SQLAlchemy ORM>> |
| 1392 | +user_id : str (FK) |
| 1393 | +repo_id : str (FK) |
| 1394 | } |
| 1395 | class MusehubNotification { |
| 1396 | <<SQLAlchemy ORM>> |
| 1397 | +id : str |
| 1398 | +user_id : str (FK) |
| 1399 | +event_type : str |
| 1400 | +read : bool |
| 1401 | } |
| 1402 | class MusehubRepo { |
| 1403 | <<SQLAlchemy ORM>> |
| 1404 | +id : str |
| 1405 | +name : str |
| 1406 | } |
| 1407 | |
| 1408 | User *-- MusehubProfile : profile |
| 1409 | User ..> MusehubStar : stars |
| 1410 | User ..> MusehubFollow : follows / followed_by |
| 1411 | User ..> MusehubWatch : watches |
| 1412 | User ..> MusehubNotification : receives |
| 1413 | MusehubStar --> MusehubRepo : repo_id |
| 1414 | MusehubWatch --> MusehubRepo : repo_id |
| 1415 | ``` |
| 1416 | |
| 1417 | --- |
| 1418 | |
| 1419 | ### Diagram 9 — Full Entity Overview |
| 1420 | |
| 1421 | All named layers and the dependency flow between them. |
| 1422 | |
| 1423 | ```mermaid |
| 1424 | classDiagram |
| 1425 | class MIDIAliases { |
| 1426 | <<contracts/midi_types.py>> |
| 1427 | MidiPitch · MidiVelocity · BeatPosition · BeatDuration · … |
| 1428 | } |
| 1429 | class JSONTypes { |
| 1430 | <<contracts/json_types.py>> |
| 1431 | NoteDict · CCEventDict · PitchBendDict · AftertouchDict · SectionDict |
| 1432 | JSONValue · JSONObject · RegionNotesMap · EventJsonSchema |
| 1433 | } |
| 1434 | class MCPTypes { |
| 1435 | <<contracts/mcp_types.py>> |
| 1436 | MCPRequest · MCPResponse · MCPToolDef · DAWToolCallMessage |
| 1437 | MCPToolDefWire (Pydantic) |
| 1438 | } |
| 1439 | class PydanticBase { |
| 1440 | <<contracts/pydantic_types.py + models/base.py>> |
| 1441 | PydanticJson (RootModel) |
| 1442 | CamelModel (BaseModel) |
| 1443 | } |
| 1444 | class AuthTypes { |
| 1445 | <<auth/tokens.py>> |
| 1446 | TokenClaims (TypedDict) |
| 1447 | } |
| 1448 | class ProtocolEvents { |
| 1449 | <<protocol/events.py>> |
| 1450 | MuseEvent (base) |
| 1451 | MCPMessageEvent · MCPPingEvent |
| 1452 | } |
| 1453 | class ProtocolResponses { |
| 1454 | <<protocol/responses.py>> |
| 1455 | ProtocolInfoResponse |
| 1456 | ProtocolEventsResponse |
| 1457 | ProtocolToolsResponse |
| 1458 | ProtocolSchemaResponse |
| 1459 | } |
| 1460 | class MCPIntegration { |
| 1461 | <<mcp/>> |
| 1462 | 27 tools (15 read + 12 write) |
| 1463 | 20 resources (5 static + 15 templates) |
| 1464 | 6 workflow prompts |
| 1465 | MCPDispatcher · handle_request · handle_batch |
| 1466 | HTTP transport POST /mcp |
| 1467 | stdio transport |
| 1468 | } |
| 1469 | class APIModels { |
| 1470 | <<models/musehub.py>> |
| 1471 | ~98 Pydantic CamelModel subclasses |
| 1472 | VCS · Repos · Issues · PRs · Releases · Social · Search |
| 1473 | } |
| 1474 | class DatabaseORM { |
| 1475 | <<db/>> |
| 1476 | 37 SQLAlchemy models |
| 1477 | User · AccessToken · MusehubRepo · MusehubBranch · MusehubCommit · … |
| 1478 | } |
| 1479 | |
| 1480 | JSONTypes ..> MIDIAliases : constrained int/float aliases |
| 1481 | MCPTypes ..> PydanticBase : MCPToolDefWire extends CamelModel |
| 1482 | ProtocolEvents ..> PydanticBase : MuseEvent extends CamelModel |
| 1483 | ProtocolResponses ..> PydanticBase : all extend BaseModel |
| 1484 | ProtocolResponses ..> MCPTypes : MCPToolDefWire in tools fields |
| 1485 | MCPIntegration ..> MCPTypes : tool defs match MCPToolDef shape |
| 1486 | MCPIntegration ..> JSONTypes : MusehubToolResult.data uses JSONValue |
| 1487 | MCPIntegration ..> AuthTypes : JWT → user_id for write tools |
| 1488 | MCPIntegration ..> DatabaseORM : executors query DB via AsyncSessionLocal |
| 1489 | APIModels ..> PydanticBase : all extend CamelModel |
| 1490 | AuthTypes ..> DatabaseORM : TokenClaims.sub → User.id |
| 1491 | ``` |
| 1492 | |
| 1493 | --- |
| 1494 | |
| 1495 | ### Diagram 10 — MCP Transport and Resource Architecture |
| 1496 | |
| 1497 | The full request path from an MCP client through transports, dispatcher, |
| 1498 | and executors to the database and back. |
| 1499 | |
| 1500 | ```mermaid |
| 1501 | classDiagram |
| 1502 | class HTTPTransport { |
| 1503 | <<api/routes/mcp.py — MCP 2025-11-25>> |
| 1504 | +POST /mcp JSON | SSE stream |
| 1505 | +GET /mcp SSE push channel |
| 1506 | +DELETE /mcp session termination |
| 1507 | +JWT auth (Authorization: Bearer) |
| 1508 | +Mcp-Session-Id header |
| 1509 | +Origin validation |
| 1510 | +batch support (array body) |
| 1511 | +202 for notifications |
| 1512 | } |
| 1513 | class StdioTransport { |
| 1514 | <<mcp/stdio_server.py>> |
| 1515 | +stdin line reader |
| 1516 | +stdout JSON-RPC responses |
| 1517 | +Cursor IDE integration |
| 1518 | } |
| 1519 | class SessionStore { |
| 1520 | <<mcp/session.py>> |
| 1521 | +create_session(user_id, caps) |
| 1522 | +get_session(session_id) |
| 1523 | +delete_session(session_id) |
| 1524 | +push_to_session(session, event) |
| 1525 | +register_sse_queue(session, last_event_id) |
| 1526 | +create_pending_elicitation(session, id) |
| 1527 | +resolve_elicitation(session, id, result) |
| 1528 | +cancel_elicitation(session, id) |
| 1529 | } |
| 1530 | class MCPDispatcher { |
| 1531 | <<mcp/dispatcher.py — 2025-11-25>> |
| 1532 | +handle_request(raw, user_id, session) JSONObject|None |
| 1533 | +handle_batch(raw, user_id, session) list~JSONObject~ |
| 1534 | -initialize() elicitation capability |
| 1535 | -tools_list() 32 tools |
| 1536 | -tools_call(name, args, ctx) |
| 1537 | -resources_read(uri, user_id) |
| 1538 | -prompts_get(name, args) |
| 1539 | -notifications_cancelled() |
| 1540 | -notifications_elicitation_complete() |
| 1541 | } |
| 1542 | class ToolCallContext { |
| 1543 | <<mcp/context.py>> |
| 1544 | +user_id : str | None |
| 1545 | +session : MCPSession | None |
| 1546 | +has_session : bool |
| 1547 | +has_elicitation : bool |
| 1548 | +elicit_form(schema, message) dict|None |
| 1549 | +elicit_url(url, message, id) bool |
| 1550 | +progress(token, value, total, label) |
| 1551 | } |
| 1552 | class ReadExecutors { |
| 1553 | <<mcp/services/musehub_mcp_executor.py>> |
| 1554 | execute_browse_repo() |
| 1555 | execute_list_branches() |
| 1556 | execute_list_commits() |
| 1557 | execute_read_file() |
| 1558 | execute_get_analysis() |
| 1559 | execute_search() |
| 1560 | execute_get_context() |
| 1561 | execute_get_commit() |
| 1562 | execute_compare() |
| 1563 | execute_list_issues() |
| 1564 | execute_get_issue() |
| 1565 | execute_list_prs() |
| 1566 | execute_get_pr() |
| 1567 | execute_list_releases() |
| 1568 | execute_search_repos() |
| 1569 | } |
| 1570 | class WriteExecutors { |
| 1571 | <<mcp/write_tools/>> |
| 1572 | repos: execute_create_repo() execute_fork_repo() |
| 1573 | issues: execute_create_issue() execute_update_issue() execute_create_issue_comment() |
| 1574 | pulls: execute_create_pr() execute_merge_pr() execute_create_pr_comment() execute_submit_pr_review() |
| 1575 | releases: execute_create_release() |
| 1576 | social: execute_star_repo() execute_create_label() |
| 1577 | } |
| 1578 | class ElicitationExecutors { |
| 1579 | <<mcp/write_tools/elicitation_tools.py — MCP 2025-11-25>> |
| 1580 | execute_compose_with_preferences() |
| 1581 | execute_review_pr_interactive() |
| 1582 | execute_connect_streaming_platform() |
| 1583 | execute_connect_daw_cloud() |
| 1584 | execute_create_release_interactive() |
| 1585 | } |
| 1586 | class ResourceHandlers { |
| 1587 | <<mcp/resources.py>> |
| 1588 | read_resource(uri, user_id) |
| 1589 | STATIC: trending · me · me/notifications · me/starred · me/feed |
| 1590 | TEMPLATED: repos/{owner}/{slug} + 14 sub-resources |
| 1591 | users/{username} |
| 1592 | } |
| 1593 | class PromptAssembler { |
| 1594 | <<mcp/prompts.py>> |
| 1595 | get_prompt(name, arguments) |
| 1596 | orientation · contribute · compose |
| 1597 | review_pr · issue_triage · release_prep |
| 1598 | onboard · release_to_world (2025-11-25) |
| 1599 | } |
| 1600 | class MusehubToolResult { |
| 1601 | <<dataclass frozen>> |
| 1602 | +ok : bool |
| 1603 | +data : dict~str, JSONValue~ |
| 1604 | +error_code : MusehubErrorCode | None |
| 1605 | +error_message : str | None |
| 1606 | } |
| 1607 | |
| 1608 | HTTPTransport ..> SessionStore : Mcp-Session-Id lifecycle |
| 1609 | HTTPTransport ..> MCPDispatcher : delegates (with session) |
| 1610 | StdioTransport ..> MCPDispatcher : delegates (no session) |
| 1611 | MCPDispatcher ..> ToolCallContext : threads into tool calls |
| 1612 | MCPDispatcher ..> ReadExecutors : tools/call (read) |
| 1613 | MCPDispatcher ..> WriteExecutors : tools/call (write, auth required) |
| 1614 | MCPDispatcher ..> ElicitationExecutors : tools/call (session + auth required) |
| 1615 | ElicitationExecutors ..> ToolCallContext : uses for elicitation & progress |
| 1616 | ToolCallContext ..> SessionStore : pushes SSE events, resolves Futures |
| 1617 | MCPDispatcher ..> ResourceHandlers : resources/read |
| 1618 | MCPDispatcher ..> PromptAssembler : prompts/get |
| 1619 | ReadExecutors --> MusehubToolResult : returns |
| 1620 | WriteExecutors --> MusehubToolResult : returns |
| 1621 | ``` |