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