gabriel / musehub public
type-contracts.md markdown
1621 lines 63.7 KB
d4eb1c39 Theme overhaul: domains, new-repo, MCP docs, copy icons; legacy CSS rem… Gabriel Cardona <cgcardona@gmail.com> 3d ago
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 ```