gabriel / musehub public
musehub.py python
3,724 lines 159.7 KB
Raw
sha256:94ef169c149a452bff7c604ded8b280b19bd477c2dabcb56972780b0b784c7aa Merge 'fix/assignee-sigil-inline' into 'dev' — proposal: As… Human 2 days ago
1 """MuseHub MCP tool definitions — 40 tools for AI agents (MCP 2025-11-25).
2
3 Covers the full MuseHub surface: reads (20), writes (15), and elicitation-powered
4 interactive tools (5).
5
6 Elicitation-powered tools use the ToolCallContext to collect user preferences
7 mid-call via the MCP 2025-11-25 elicitation protocol. They require an active
8 session (``Mcp-Session-Id``) and degrade gracefully without one.
9
10 Reads give agents complete browsing power — repos, branches, commits, files,
11 domain insights, issues, proposals, releases, and global discovery.
12
13 Writes give agents full contribution capability — create repos, open and
14 manage issues and proposals, merge, publish releases, and build social graphs.
15
16 Naming convention:
17 ``musehub_<verb>_<noun>`` — hub API tools.
18 ``muse_<verb>`` — Muse CLI-analogous tools (push, pull, remote, config).
19
20 Addressing: all repo-scoped tools accept either ``repo_id`` (sha256 genesis hash) or both
21 ``owner`` + ``slug`` (e.g. ``owner='gabriel'``, ``slug='jazz-standards'``).
22 The dispatcher resolves ``owner``+``slug`` to ``repo_id`` transparently.
23
24 All tools carry ``server_side: True`` so the dispatcher routes them to the
25 executor layer rather than forwarding to a connected DAW.
26 """
27
28 from musehub.types.mcp_types import MCPPropertyDef, MCPToolDef
29
30 type _PropMap = dict[str, MCPPropertyDef]
31
32 # ── Shared schema fragments ───────────────────────────────────────────────────
33
34 _REPO_ID_PROP: _PropMap = {
35 "repo_id": {
36 "type": "string",
37 "description": (
38 "sha256 genesis ID of the MuseHub repository. "
39 "Alternatively, provide 'owner' + 'slug' to identify by URL path. "
40 "All three can be omitted if session context is set via musehub_set_context."
41 ),
42 },
43 "owner": {
44 "type": "string",
45 "description": (
46 "Repository owner username (e.g. 'gabriel'). "
47 "Use with 'slug' as an alternative to 'repo_id'. "
48 "Omit if session context is already set via musehub_set_context."
49 ),
50 },
51 "slug": {
52 "type": "string",
53 "description": (
54 "Repository slug — the URL-safe name (e.g. 'jazz-standards'). "
55 "Use with 'owner' as an alternative to 'repo_id'. "
56 "Omit if session context is already set via musehub_set_context."
57 ),
58 },
59 }
60
61
62 # ── Read tools ────────────────────────────────────────────────────────────────
63
64
65 MUSEHUB_READ_TOOLS: list[MCPToolDef] = [
66 {
67 "name": "musehub_set_context",
68 "server_side": True,
69 "description": (
70 "Set the session's focused repository. After calling this, all subsequent "
71 "tool calls that accept owner/slug will use this repo automatically — "
72 "you no longer need to pass owner+slug on every call. "
73 "Calling again with different owner/slug switches focus. "
74 "Example: musehub_set_context(owner='gabriel', slug='jazz-standards') "
75 "then musehub_list_branches() with no other args."
76 ),
77 "inputSchema": {
78 "type": "object",
79 "properties": {
80 "owner": {
81 "type": "string",
82 "description": "Repository owner username.",
83 },
84 "slug": {
85 "type": "string",
86 "description": "Repository slug (URL-safe name).",
87 },
88 },
89 "required": ["owner", "slug"],
90 },
91 },
92 {
93 "name": "musehub_read_context",
94 "server_side": True,
95 "description": (
96 "Start here. Get the full AI context document for a MuseHub repository: "
97 "domain plugin (scoped_id, dimensions, capabilities), branches, recent commits, "
98 "and artifact inventory — in a single call. "
99 "This is the primary oracle for any agent: always call it before creating "
100 "or modifying state to ensure coherence with the existing multidimensional content. "
101 "For computed analytics (per-dimension scores), follow up with musehub_read_domain_insights. "
102 "For the full viewer payload (dimension slices, navigation strip), follow up with musehub_read_view. "
103 "Repo identifier required: provide repo_id OR both owner+slug. "
104 "Example: musehub_read_context(repo_id='a3f2-...') "
105 "or musehub_read_context(owner='gabriel', slug='jazz-standards'). "
106 "If no repo identifier is provided and session context is set via musehub_set_context, the focused repo is used automatically."
107 ),
108 "inputSchema": {
109 "type": "object",
110 "properties": _REPO_ID_PROP,
111 "required": [],
112 },
113 },
114 {
115 "name": "musehub_list_branches",
116 "server_side": True,
117 "description": (
118 "List all branches in a MuseHub repository with their head commit IDs. "
119 "Call before musehub_list_commits to identify the target branch ref. "
120 "Example: musehub_list_branches(repo_id='a3f2-...') "
121 "or musehub_list_branches(owner='gabriel', slug='jazz-standards')."
122 ),
123 "inputSchema": {
124 "type": "object",
125 "properties": _REPO_ID_PROP,
126 "required": [],
127 },
128 },
129 {
130 "name": "musehub_list_commits",
131 "server_side": True,
132 "description": (
133 "List commits on a MuseHub repository (newest first). "
134 "Optionally filter by branch name and cap the result count. "
135 "Example: musehub_list_commits(repo_id='a3f2-...', branch='main', limit=10) "
136 "or musehub_list_commits(owner='gabriel', slug='jazz-standards', branch='main')."
137 ),
138 "inputSchema": {
139 "type": "object",
140 "properties": {
141 **_REPO_ID_PROP,
142 "branch": {
143 "type": "string",
144 "description": "Branch name filter (e.g. 'main'). Omit to list across all branches.",
145 },
146 "limit": {
147 "type": "integer",
148 "description": "Maximum commits to return (default: 20, max: 100).",
149 "default": 20,
150 "minimum": 1,
151 "maximum": 100,
152 },
153 },
154 "required": [],
155 },
156 },
157 {
158 "name": "musehub_read_file",
159 "server_side": True,
160 "description": (
161 "Read the metadata for a stored artifact (MIDI, MP3, WebP piano roll) "
162 "in a MuseHub repo. Returns path, size_bytes, mime_type, and object_id. "
163 "Binary content is not returned — discover object IDs via musehub_read_context first. "
164 "Example: musehub_read_file(repo_id='a3f2-...', object_id='sha256:abc...')."
165 ),
166 "inputSchema": {
167 "type": "object",
168 "properties": {
169 **_REPO_ID_PROP,
170 "object_id": {
171 "type": "string",
172 "description": "Content-addressed object ID (e.g. 'sha256:abc...').",
173 },
174 },
175 "required": ["object_id"],
176 },
177 },
178 {
179 "name": "musehub_search",
180 "server_side": True,
181 "description": (
182 "Search within a MuseHub repository by substring query. "
183 "Mode 'path' matches artifact file paths (e.g. 'tracks/jazz'); "
184 "mode 'commit' searches commit messages (e.g. 'add bass'). "
185 "Returns matching items with their metadata. "
186 "Example: musehub_search(repo_id='a3f2-...', query='bass', mode='path') "
187 "or musehub_search(owner='gabriel', slug='jazz-standards', query='bass')."
188 ),
189 "inputSchema": {
190 "type": "object",
191 "properties": {
192 **_REPO_ID_PROP,
193 "query": {
194 "type": "string",
195 "description": "Substring query string (case-insensitive).",
196 },
197 "mode": {
198 "type": "string",
199 "description": "Search mode: 'path' searches object paths; 'commit' searches commit messages.",
200 "enum": ["path", "commit"],
201 "default": "path",
202 },
203 },
204 "required": ["query"],
205 },
206 },
207 {
208 "name": "musehub_read_commit",
209 "server_side": True,
210 "description": (
211 "Get detailed information about a single commit, including its message, "
212 "author, timestamp, parent IDs, and the full list of artifact paths "
213 "at that snapshot. Use to inspect what changed at a specific point in history. "
214 "Example: musehub_read_commit(repo_id='a3f2-...', commit_id='sha256:abc...')."
215 ),
216 "inputSchema": {
217 "type": "object",
218 "properties": {
219 **_REPO_ID_PROP,
220 "commit_id": {
221 "type": "string",
222 "description": "Commit ID (SHA or short ID).",
223 },
224 },
225 "required": ["commit_id"],
226 },
227 },
228 {
229 "name": "musehub_compare",
230 "server_side": True,
231 "description": (
232 "Compare two refs (branches or commit IDs) in a MuseHub repository. "
233 "Returns a multidimensional state diff: which artifacts were added, removed, or modified, "
234 "and per-dimension change scores sourced from the repo's domain plugin capabilities. "
235 "Example: musehub_compare(repo_id='a3f2-...', base_ref='main', head_ref='feature/new-section') "
236 "or musehub_compare(owner='gabriel', slug='jazz-standards', base_ref='main', head_ref='feature/bridge')."
237 ),
238 "inputSchema": {
239 "type": "object",
240 "properties": {
241 **_REPO_ID_PROP,
242 "base_ref": {
243 "type": "string",
244 "description": "Base branch name or commit ID.",
245 },
246 "head_ref": {
247 "type": "string",
248 "description": "Head branch name or commit ID to compare against base.",
249 },
250 },
251 "required": ["base_ref", "head_ref"],
252 },
253 },
254 {
255 "name": "musehub_list_issues",
256 "server_side": True,
257 "description": (
258 "List issues for a MuseHub repository. "
259 "Filter by state (open/closed/all) or label string. "
260 "Returns issue summaries (title, state, labels) — "
261 "call musehub_read_issue with the number to get the full body and comment thread. "
262 "Example: musehub_list_issues(repo_id='a3f2-...', state='open', label='bug') "
263 "or musehub_list_issues(owner='gabriel', slug='jazz-standards')."
264 ),
265 "inputSchema": {
266 "type": "object",
267 "properties": {
268 **_REPO_ID_PROP,
269 "state": {
270 "type": "string",
271 "description": "Filter by state: 'open', 'closed', or 'all'.",
272 "enum": ["open", "closed", "all"],
273 "default": "open",
274 },
275 "label": {
276 "type": "string",
277 "description": "Filter to issues with this label string.",
278 },
279 },
280 "required": [],
281 },
282 },
283 {
284 "name": "musehub_read_issue",
285 "server_side": True,
286 "description": (
287 "Read a single issue by its per-repo number, including the full body and comment thread. "
288 "Use musehub_list_issues to discover issue numbers, then this tool to read the detail. "
289 "Example: musehub_read_issue(repo_id='a3f2-...', issue_number=42) "
290 "or musehub_read_issue(owner='gabriel', slug='jazz-standards', issue_number=42)."
291 ),
292 "inputSchema": {
293 "type": "object",
294 "properties": {
295 **_REPO_ID_PROP,
296 "issue_number": {
297 "type": "integer",
298 "description": "Per-repo issue number.",
299 },
300 },
301 "required": ["issue_number"],
302 },
303 },
304 {
305 "name": "musehub_list_proposals",
306 "server_side": True,
307 "description": (
308 "List merge proposals for a MuseHub repository. "
309 "Filter by state (open/merged/closed/all). "
310 "Returns merge proposal summaries — call musehub_read_proposal with the proposal_id to get reviews and inline comments. "
311 "Example: musehub_list_proposals(repo_id='a3f2-...', state='open') "
312 "or musehub_list_proposals(owner='gabriel', slug='jazz-standards')."
313 ),
314 "inputSchema": {
315 "type": "object",
316 "properties": {
317 **_REPO_ID_PROP,
318 "state": {
319 "type": "string",
320 "description": "Filter by state: 'open', 'merged', 'closed', or 'all'.",
321 "enum": ["open", "merged", "closed", "all"],
322 "default": "all",
323 },
324 },
325 "required": [],
326 },
327 },
328 {
329 "name": "musehub_list_proposals_context",
330 "server_side": True,
331 "description": (
332 "List merge proposals with full enrichment: risk band, approval status, active domains, "
333 "blocking dependencies, and author type — all in a single call. "
334 "Use this instead of musehub_list_proposals when you need to triage the merge queue "
335 "without calling musehub_read_proposal for each entry. "
336 "Returns up to 50 proposals sorted by the state filter. "
337 "Example: musehub_list_proposals_context(repo_id='a3f2-...', state='open') "
338 "or musehub_list_proposals_context(owner='gabriel', slug='jazz-standards')."
339 ),
340 "inputSchema": {
341 "type": "object",
342 "properties": {
343 **_REPO_ID_PROP,
344 "state": {
345 "type": "string",
346 "description": "Filter by state: 'open', 'merged', 'settling', or 'all'.",
347 "enum": ["open", "merged", "settling", "all"],
348 "default": "open",
349 },
350 },
351 "required": [],
352 },
353 },
354 {
355 "name": "musehub_read_proposal",
356 "server_side": True,
357 "description": (
358 "Read a single merge proposal by ID, including all reviews and inline dimension-anchored comments. "
359 "Use musehub_list_proposals to discover proposal_ids, then this tool to read the full detail. "
360 "Example: musehub_read_proposal(repo_id='a3f2-...', proposal_id='b5e8-...') "
361 "or musehub_read_proposal(owner='gabriel', slug='jazz-standards', proposal_id='b5e8-...')."
362 ),
363 "inputSchema": {
364 "type": "object",
365 "properties": {
366 **_REPO_ID_PROP,
367 "proposal_id": {
368 "type": "string",
369 "description": "sha256 genesis ID of the merge proposal.",
370 },
371 },
372 "required": ["proposal_id"],
373 },
374 },
375 {
376 "name": "musehub_read_proposal_risk",
377 "server_side": True,
378 "description": (
379 "Get the computed risk score for a merge proposal. "
380 "Returns risk score (0-100), band (low/medium/high/critical), blast delta, "
381 "breakage count, symbol totals, agent commit ratio, and signing status. "
382 "Use this before merging to decide whether to require additional review. "
383 "Example: musehub_read_proposal_risk(repo_id='a3f2-...', proposal_id='b5e8-...'). "
384 "Pair with musehub/safe-to-merge prompt for a full pre-merge audit."
385 ),
386 "inputSchema": {
387 "type": "object",
388 "properties": {
389 **_REPO_ID_PROP,
390 "proposal_id": {
391 "type": "string",
392 "description": "sha256 genesis ID of the merge proposal.",
393 },
394 },
395 "required": ["proposal_id"],
396 },
397 },
398 {
399 "name": "musehub_read_proposal_diff",
400 "server_side": True,
401 "description": (
402 "Get the full symbol-level diff for a merge proposal — added, modified, and deleted "
403 "named symbols across all commits on the from-branch. "
404 "Returns sym_added, sym_modified, sym_deleted counts and all symbol addresses by category. "
405 "Use this to understand the semantic scope of a proposal before reviewing or merging. "
406 "Example: musehub_read_proposal_diff(repo_id='a3f2-...', proposal_id='b5e8-...')."
407 ),
408 "inputSchema": {
409 "type": "object",
410 "properties": {
411 **_REPO_ID_PROP,
412 "proposal_id": {
413 "type": "string",
414 "description": "sha256 genesis ID of the merge proposal.",
415 },
416 },
417 "required": ["proposal_id"],
418 },
419 },
420 {
421 "name": "musehub_read_proposal_breakage",
422 "server_side": True,
423 "description": (
424 "Get the list of breaking changes for a merge proposal — symbols deleted or "
425 "structurally modified in a way that breaks callers. "
426 "An empty list means no breaking changes were detected across all commits. "
427 "Example: musehub_read_proposal_breakage(repo_id='a3f2-...', proposal_id='b5e8-...')."
428 ),
429 "inputSchema": {
430 "type": "object",
431 "properties": {
432 **_REPO_ID_PROP,
433 "proposal_id": {
434 "type": "string",
435 "description": "sha256 genesis ID of the merge proposal.",
436 },
437 },
438 "required": ["proposal_id"],
439 },
440 },
441 {
442 "name": "musehub_list_releases",
443 "server_side": True,
444 "description": (
445 "List all releases for a MuseHub repository, ordered newest first. "
446 "Each release includes tag, title, release notes summary, and asset counts. "
447 "Example: musehub_list_releases(repo_id='a3f2-...') "
448 "or musehub_list_releases(owner='gabriel', slug='jazz-standards')."
449 ),
450 "inputSchema": {
451 "type": "object",
452 "properties": _REPO_ID_PROP,
453 "required": [],
454 },
455 },
456 {
457 "name": "musehub_search_repos",
458 "server_side": True,
459 "description": (
460 "Discover public MuseHub repositories across all domains by text query, "
461 "domain plugin, or free-text tags. "
462 "Filter by domain scoped ID (e.g. '@gabriel/midi') to browse repos of a specific type. "
463 "Returns repos sorted by relevance including repo_id, owner, and slug for each result. "
464 "Example: musehub_search_repos(query='jazz', domain='@gabriel/midi')."
465 ),
466 "inputSchema": {
467 "type": "object",
468 "properties": {
469 "query": {
470 "type": "string",
471 "description": "Free-text query matched against repo names and descriptions.",
472 },
473 "domain": {
474 "type": "string",
475 "description": "Filter by domain scoped ID, e.g. '@gabriel/midi' or '@gabriel/code'.",
476 },
477 "tags": {
478 "type": "array",
479 "description": "Filter repos that have all of these tags.",
480 "items": {"type": "string"},
481 },
482 "limit": {
483 "type": "integer",
484 "description": "Maximum results to return (default: 20, max: 100).",
485 "default": 20,
486 "minimum": 1,
487 "maximum": 100,
488 },
489 },
490 "required": [],
491 },
492 },
493 {
494 "name": "musehub_list_repo_forks",
495 "server_side": True,
496 "description": (
497 "List all direct forks of a MuseHub repository. "
498 "Returns each fork's full repo metadata (owner, slug, repo_id, description, tags, "
499 "created_at) plus source attribution so you can render 'forked from {owner}/{slug}'. "
500 "Use musehub_get_fork_network to traverse the full recursive tree including "
501 "forks-of-forks. "
502 "Example: musehub_list_repo_forks(repo_id='a3f2-...')."
503 ),
504 "inputSchema": {
505 "type": "object",
506 "properties": {
507 "repo_id": {
508 "type": "string",
509 "description": "sha256 genesis ID of the source repository whose forks you want to list.",
510 },
511 },
512 "required": ["repo_id"],
513 },
514 },
515 {
516 "name": "musehub_get_fork_network",
517 "server_side": True,
518 "description": (
519 "Return the complete recursive fork network tree rooted at a MuseHub repository. "
520 "The response has a 'root' node (the source repo) with 'children' (direct forks), "
521 "each child carrying its own 'children' for second-level forks, etc. "
522 "'totalForks' is the flat count of all fork nodes (excluding root). "
523 "Use this to find the most-diverged fork, visualise the fork graph, or decide "
524 "which fork to merge back into the upstream before a breaking change. "
525 "Example: musehub_get_fork_network(repo_id='a3f2-...')."
526 ),
527 "inputSchema": {
528 "type": "object",
529 "properties": {
530 "repo_id": {
531 "type": "string",
532 "description": "sha256 genesis ID of the root repository.",
533 },
534 },
535 "required": ["repo_id"],
536 },
537 },
538 {
539 "name": "musehub_get_user_forks",
540 "server_side": True,
541 "description": (
542 "List all repositories that a MuseHub user has forked, with source attribution. "
543 "Returns each fork entry with the fork repo's full metadata plus "
544 "'sourceOwner' and 'sourceSlug' so you can render 'forked from {sourceOwner}/{sourceSlug}'. "
545 "Ordered newest-first. "
546 "Example: musehub_get_user_forks(username='gabriel')."
547 ),
548 "inputSchema": {
549 "type": "object",
550 "properties": {
551 "username": {
552 "type": "string",
553 "description": "MSign handle of the user whose forks to retrieve.",
554 },
555 },
556 "required": ["username"],
557 },
558 },
559 {
560 "name": "musehub_list_domains",
561 "server_side": True,
562 "description": (
563 "List and search all registered Muse domain plugins. "
564 "Domains are the extensibility layer that give Muse its domain-agnostic power — "
565 "each domain defines its own dimensions, viewer, merge semantics, CLI commands, "
566 "and artifact types. "
567 "Filter by query string, viewer_type, or verified status. "
568 "Returns scoped_id (@author/slug), display_name, description, capabilities, "
569 "repo_count, and install_count for each domain. "
570 "Example: musehub_list_domains(query='genomics', verified=true)."
571 ),
572 "inputSchema": {
573 "type": "object",
574 "properties": {
575 "query": {
576 "type": "string",
577 "description": "Full-text search across name and description.",
578 },
579 "viewer_type": {
580 "type": "string",
581 "description": "Filter by viewer type (e.g. 'piano_roll', 'code_graph', 'generic').",
582 },
583 "verified": {
584 "type": "boolean",
585 "description": "When true, return only officially-verified domains.",
586 },
587 "limit": {
588 "type": "integer",
589 "description": "Maximum results (default 20, max 100).",
590 "default": 20,
591 "minimum": 1,
592 "maximum": 100,
593 },
594 "cursor": {
595 "type": "string",
596 "description": "Opaque cursor from a previous call's next_cursor field to fetch the next page.",
597 },
598 },
599 "required": [],
600 },
601 },
602 {
603 "name": "musehub_read_domain",
604 "server_side": True,
605 "description": (
606 "Fetch the full manifest for a specific Muse domain plugin by its scoped ID. "
607 "Returns all capabilities: dimensions list, viewer_type, merge_semantics, "
608 "cli_commands, artifact_types, manifest_hash (content-addressed, immutable), "
609 "version, repo_count, and install_count. "
610 "Call this after musehub_read_context to understand the domain a repo uses, "
611 "or before creating a repo to verify domain capabilities. "
612 "Example: musehub_read_domain(scoped_id='@gabriel/midi')."
613 ),
614 "inputSchema": {
615 "type": "object",
616 "properties": {
617 "scoped_id": {
618 "type": "string",
619 "description": "Domain scoped identifier in '@author/slug' format.",
620 },
621 },
622 "required": ["scoped_id"],
623 },
624 },
625 {
626 "name": "musehub_read_domain_insights",
627 "server_side": True,
628 "description": (
629 "Get computed analytics for a MuseHub repository across any of its domain's dimensions. "
630 "Returns numeric scores and structured metrics — e.g. harmonic tension score, "
631 "rhythmic complexity, melodic contour for MIDI; symbol hotspots, coupling metrics for Code. "
632 "The available dimensions are defined by the repo's domain plugin — call "
633 "musehub_read_domain first to learn the dimension names. "
634 "dimension='overview' always returns cross-domain stats (commits, objects, collaborators). "
635 "Prefer musehub_read_context for structural understanding; use this tool when you need "
636 "quantitative analysis of a specific dimension. "
637 "For the viewer-ready state payload, use musehub_read_view instead. "
638 "Example: musehub_read_domain_insights(repo_id='a3f2-...', dimension='harmonic')."
639 ),
640 "inputSchema": {
641 "type": "object",
642 "properties": {
643 **_REPO_ID_PROP,
644 "dimension": {
645 "type": "string",
646 "description": (
647 "Insight dimension to fetch. 'overview' is always available; "
648 "other values depend on the repo's domain plugin."
649 ),
650 "default": "overview",
651 },
652 "ref": {
653 "type": "string",
654 "description": "Branch name, tag, or commit SHA to scope the insights to. Defaults to HEAD.",
655 },
656 },
657 "required": [],
658 },
659 },
660 {
661 "name": "musehub_read_view",
662 "server_side": True,
663 "description": (
664 "Fetch the universal viewer payload for a repo at a given ref. "
665 "Returns the structured representation of multidimensional state as rendered by the "
666 "domain's viewer — dimension slices, navigation strip entries, and domain-specific "
667 "viewer metadata. This is the MCP equivalent of the /{owner}/{repo}/insights/{ref} page. "
668 "Use when you need the full state payload for reasoning or rendering. "
669 "For quantitative analytics (scores, metrics), use musehub_read_domain_insights instead. "
670 "For repo identity and inventory, use musehub_read_context instead. "
671 "Example: musehub_read_view(repo_id='a3f2-...', ref='main') "
672 "or musehub_read_view(owner='gabriel', slug='jazz-standards', ref='main')."
673 ),
674 "inputSchema": {
675 "type": "object",
676 "properties": {
677 **_REPO_ID_PROP,
678 "ref": {
679 "type": "string",
680 "description": "Branch name, tag, or commit SHA. Defaults to HEAD of default branch.",
681 },
682 "dimension": {
683 "type": "string",
684 "description": (
685 "Optional: restrict the view payload to a single dimension slice. "
686 "Omit to get the full multi-dimensional view."
687 ),
688 },
689 },
690 "required": [],
691 },
692 },
693 {
694 "name": "musehub_whoami",
695 "server_side": True,
696 "description": (
697 "Return identity information for the currently authenticated caller. "
698 "Call this first to confirm authentication before any write operations. "
699 "Works for both human users and AI agent tokens. "
700 "Authenticated response includes: user_id (handle), username (handle), "
701 "display_name, repo_count, is_admin, and token_type ('human' or 'agent'). "
702 "Returns {authenticated: false} when called without a valid MSign header — never errors. "
703 "Example: musehub_whoami()."
704 ),
705 "inputSchema": {
706 "type": "object",
707 "properties": {},
708 "required": [],
709 },
710 },
711 {
712 "name": "muse_pull",
713 "server_side": True,
714 "description": (
715 "Fetch missing commits and blobs from a MuseHub repository. "
716 "Equivalent to 'muse pull' — returns new commits and blob metadata "
717 "since the given commit ID. Use since_commit_id to fetch incrementally. "
718 "Pass blob_ids to download specific blobs — their content is returned "
719 "as base64-encoded strings in the 'content_b64' field of each blob entry; "
720 "decode with base64.b64decode() before writing to disk. "
721 "Example: muse_pull(repo_id='a3f2-...', branch='main', since_commit_id='abc123') "
722 "or muse_pull(owner='gabriel', slug='jazz-standards', branch='main')."
723 ),
724 "inputSchema": {
725 "type": "object",
726 "properties": {
727 **_REPO_ID_PROP,
728 "branch": {
729 "type": "string",
730 "description": "Branch to pull from. Defaults to the default branch.",
731 },
732 "since_commit_id": {
733 "type": "string",
734 "description": "Only return commits newer than this commit ID.",
735 },
736 "blob_ids": {
737 "type": "array",
738 "description": "Specific blob IDs to fetch (content-addressed sha256: prefixed IDs).",
739 "items": {"type": "string"},
740 },
741 },
742 "required": [],
743 },
744 },
745 {
746 "name": "muse_remote",
747 "server_side": True,
748 "description": (
749 "Return the remote URL, push/pull API endpoints, and clone command for a MuseHub repository. "
750 "Covers both 'muse remote -v' and 'muse clone' use cases in a single call. "
751 "Returns: origin URL, push endpoint, pull endpoint, clone command (with optional ref), "
752 "and the 'muse remote add origin' command. "
753 "Use this when setting up a local repo to push to MuseHub, or to get the clone URL. "
754 "Example: muse_remote(owner='gabriel', slug='neo-soul-experiment') "
755 "or muse_remote(owner='gabriel', slug='neo-soul-experiment', ref='feat/jazz-bridge')."
756 ),
757 "inputSchema": {
758 "type": "object",
759 "properties": {
760 "owner": {
761 "type": "string",
762 "description": "Repository owner username.",
763 },
764 "slug": {
765 "type": "string",
766 "description": "Repository slug (URL-safe name).",
767 },
768 "ref": {
769 "type": "string",
770 "description": "Optional branch or tag to target (used in the clone command).",
771 },
772 },
773 "required": ["owner", "slug"],
774 },
775 },
776 {
777 "name": "musehub_read_prompt",
778 "server_side": True,
779 "description": (
780 "Return the fully assembled content of a named MuseHub MCP prompt. "
781 "This is a tool-layer shim over the MCP prompts/get primitive, enabling "
782 "agents whose client only supports tools/call to access prompt content "
783 "programmatically — identical output to prompts/get, callable as a tool. "
784 "Use musehub_list_prompts() (prompts/list) to discover available prompt names. "
785 "The eleven available prompts are: musehub/orientation, musehub/contribute, "
786 "musehub/create, musehub/review_proposal, musehub/issue_triage, musehub/release_prep, "
787 "musehub/onboard, musehub/safe-to-merge, musehub/pre-release-audit, "
788 "musehub/agent-onboarding, musehub/symbol-investigation. "
789 "Pass caller_type='agent' to musehub/orientation for agent-specific guidance. "
790 "Example: musehub_read_prompt(name='musehub/orientation', "
791 "arguments={'caller_type': 'agent'})."
792 ),
793 "inputSchema": {
794 "type": "object",
795 "properties": {
796 "name": {
797 "type": "string",
798 "description": (
799 "Prompt name to fetch, e.g. 'musehub/orientation' or "
800 "'musehub/review_proposal'. Must be one of the eleven musehub/* prompts."
801 ),
802 "enum": [
803 "musehub/orientation",
804 "musehub/contribute",
805 "musehub/create",
806 "musehub/review_proposal",
807 "musehub/issue_triage",
808 "musehub/release_prep",
809 "musehub/onboard",
810 "musehub/safe-to-merge",
811 "musehub/pre-release-audit",
812 "musehub/agent-onboarding",
813 "musehub/symbol-investigation",
814 ],
815 },
816 "arguments": {
817 "type": "object",
818 "description": (
819 "Optional prompt arguments as string key-value pairs. "
820 "E.g. {'caller_type': 'agent'} for musehub/orientation, "
821 "{'repo_id': '<sha256>'} for musehub/contribute, "
822 "{'repo_id': '<sha256>', 'proposal_id': '<sha256>'} for musehub/review_proposal."
823 ),
824 },
825 },
826 "required": ["name"],
827 },
828 },
829 # ── Symbol Intelligence (V2) ──────────────────────────────────────────────
830 {
831 "name": "musehub_list_symbols",
832 "server_side": True,
833 "description": (
834 "List or search all symbols known to MuseHub for a repo. "
835 "Uses the materialized symbol index built from commit structured_delta on each push. "
836 "Supports substring search with ?q= and op-kind filter (add|modify|delete). "
837 "Returns address, last op, content_id, last_modified, history_count per symbol. "
838 "Repo identifier required: provide repo_id OR both owner+slug. "
839 "Example: musehub_list_symbols(repo_id='a3f2-...', q='MyClass') "
840 "or musehub_list_symbols(owner='gabriel', slug='muse', q='CommitRecord')."
841 ),
842 "inputSchema": {
843 "type": "object",
844 "properties": {
845 **_REPO_ID_PROP,
846 "q": {"type": "string", "description": "Substring to match against symbol address"},
847 "kind": {"type": "string", "enum": ["add", "modify", "delete"], "description": "Filter by last op kind"},
848 "limit": {"type": "integer", "default": 100, "description": "Max symbols to return"},
849 "cursor": {"type": "string", "description": "Opaque cursor from a previous call's next_cursor field to fetch the next page."},
850 },
851 },
852 },
853 {
854 "name": "musehub_read_symbol",
855 "server_side": True,
856 "description": (
857 "Get the full history timeline for a single symbol address. "
858 "Returns every commit that touched the symbol, with op type (add/modify/delete), "
859 "content_id (body hash), and timestamp. "
860 "Use this to understand how a symbol evolved, who changed it, and when. "
861 "Repo identifier and address both required. "
862 "Example: musehub_read_symbol(repo_id='a3f2-...', address='muse/core/store.py::CommitRecord') "
863 "or musehub_read_symbol(owner='gabriel', slug='muse', address='muse/core/store.py::CommitRecord')."
864 ),
865 "inputSchema": {
866 "type": "object",
867 "properties": {
868 **_REPO_ID_PROP,
869 "address": {"type": "string", "description": "Symbol address (e.g. 'file.py::ClassName::method')"},
870 },
871 "required": ["address"],
872 },
873 },
874 {
875 "name": "musehub_symbol_impact",
876 "server_side": True,
877 "description": (
878 "Get the blast radius for a symbol: which other symbols are co-changed with it most frequently. "
879 "Uses commit co-occurrence analysis — if symbols A and B are modified in the same commits often, "
880 "they are likely coupled. Essential before refactoring: reveals hidden dependencies. "
881 "Returns co_changed list sorted by shared_commit_count descending. "
882 "Example: musehub_symbol_impact(repo_id='a3f2-...', address='muse/core/store.py::CommitRecord')."
883 ),
884 "inputSchema": {
885 "type": "object",
886 "properties": {
887 **_REPO_ID_PROP,
888 "address": {"type": "string", "description": "Symbol address"},
889 "depth": {"type": "integer", "default": 1, "minimum": 1, "maximum": 3},
890 },
891 "required": ["address"],
892 },
893 },
894 {
895 "name": "musehub_symbol_clones",
896 "server_side": True,
897 "description": (
898 "Find symbols that share the exact same body hash as the given symbol (clones / duplicates). "
899 "Uses hash_occurrence index for O(1) lookup. "
900 "Example: musehub_symbol_clones(repo_id='a3f2-...', address='muse/core/store.py::_utc_now')."
901 ),
902 "inputSchema": {
903 "type": "object",
904 "properties": {
905 **_REPO_ID_PROP,
906 "address": {"type": "string", "description": "Symbol address to find clones of"},
907 },
908 "required": ["address"],
909 },
910 },
911 {
912 "name": "musehub_read_intel_index_status",
913 "server_side": True,
914 "description": (
915 "Check whether the symbol index has been built for a repo, when it was last built, "
916 "and how many symbols it contains. Returns status: 'ready' or 'not_built'. "
917 "Example: musehub_read_intel_index_status(repo_id='a3f2-...')."
918 ),
919 "inputSchema": {
920 "type": "object",
921 "properties": {**_REPO_ID_PROP},
922 },
923 },
924 {
925 "name": "musehub_read_intel_health_score",
926 "server_side": True,
927 "description": (
928 "Return the repository health score (0–100) and label ('Excellent'/'Good'/'Fair'/'Poor'/'Critical'). "
929 "Also returns the four penalty components: dead_penalty, hotspot_penalty, coupling_penalty, "
930 "breakage_penalty. Use before a release or after a large merge to gauge repo hygiene. "
931 "Example: musehub_read_intel_health_score(repo_id='a3f2-...')."
932 ),
933 "inputSchema": {
934 "type": "object",
935 "properties": {**_REPO_ID_PROP},
936 "required": ["repo_id"],
937 },
938 },
939 {
940 "name": "musehub_read_intel_hotspots",
941 "server_side": True,
942 "description": (
943 "Return symbols ranked by change frequency — the highest-churn code paths. "
944 "Each entry contains: address, change_count, last_changed (ISO-8601). "
945 "Results are sorted descending by change_count. "
946 "Example: musehub_read_intel_hotspots(repo_id='a3f2-...')."
947 ),
948 "inputSchema": {
949 "type": "object",
950 "properties": {**_REPO_ID_PROP},
951 "required": ["repo_id"],
952 },
953 },
954 {
955 "name": "musehub_read_intel_dead",
956 "server_side": True,
957 "description": (
958 "Return dead code candidates — symbols added but rarely or never modified, "
959 "cold for 90+ days. Each entry contains: address, days_cold, blast_radius, added_at. "
960 "Sorted by days_cold descending (stalest first). "
961 "Example: musehub_read_intel_dead(repo_id='a3f2-...')."
962 ),
963 "inputSchema": {
964 "type": "object",
965 "properties": {**_REPO_ID_PROP},
966 "required": ["repo_id"],
967 },
968 },
969 {
970 "name": "musehub_read_intel_blast_risk",
971 "server_side": True,
972 "description": (
973 "Return symbols with the highest historical co-change blast radius. "
974 "Each entry contains: address, co_change_count, top_co_symbols (up to 5). "
975 "Sorted by co_change_count descending. Use to identify 'leverage points' — "
976 "symbols where a change ripples farthest. "
977 "Example: musehub_read_intel_blast_risk(repo_id='a3f2-...')."
978 ),
979 "inputSchema": {
980 "type": "object",
981 "properties": {**_REPO_ID_PROP},
982 "required": ["repo_id"],
983 },
984 },
985 {
986 "name": "musehub_list_labels",
987 "server_side": True,
988 "description": (
989 "List all labels defined in a MuseHub repository. "
990 "Returns label_id, name, color, and description for each label. "
991 "Use label_id values with musehub_update_label and musehub_delete_label. "
992 "Example: musehub_list_labels(repo_id='a3f2-...')."
993 ),
994 "inputSchema": {
995 "type": "object",
996 "properties": {
997 **_REPO_ID_PROP,
998 },
999 "required": ["repo_id"],
1000 },
1001 "annotations": {
1002 "readOnlyHint": True,
1003 "destructiveHint": False,
1004 "idempotentHint": True,
1005 "openWorldHint": True,
1006 "audience": ["assistant"],
1007 },
1008 },
1009 {
1010 "name": "musehub_get_repo",
1011 "server_side": True,
1012 "description": (
1013 "Fetch metadata for a single MuseHub repository by repo_id or owner+slug. "
1014 "Returns name, owner, slug, visibility, description, tags, default_branch, "
1015 "clone_url, created_at, updated_at, and pushed_at. "
1016 "Use this to inspect a specific repo without searching. "
1017 "Example: musehub_get_repo(owner='gabriel', slug='jazz-standards') "
1018 "or musehub_get_repo(repo_id='a3f2-...')."
1019 ),
1020 "inputSchema": {
1021 "type": "object",
1022 "properties": {
1023 "repo_id": {
1024 "type": "string",
1025 "description": "Repository sha256 genesis ID. Takes precedence over owner+slug when both provided.",
1026 },
1027 "owner": {
1028 "type": "string",
1029 "description": "Repository owner handle (used together with slug).",
1030 },
1031 "slug": {
1032 "type": "string",
1033 "description": "URL-safe repository slug (used together with owner).",
1034 },
1035 },
1036 "required": [],
1037 },
1038 "annotations": {
1039 "readOnlyHint": True,
1040 "idempotentHint": True,
1041 "openWorldHint": False,
1042 "audience": ["assistant"],
1043 },
1044 },
1045 {
1046 "name": "musehub_list_repos",
1047 "server_side": True,
1048 "description": (
1049 "List repositories owned by or collaborated on by the authenticated user. "
1050 "Results are ordered newest-first. Use next_cursor from the response to page. "
1051 "This is the only way to enumerate your own private repos — "
1052 "musehub_search_repos only surfaces public repositories. "
1053 "Example: musehub_list_repos() or musehub_list_repos(limit=50, cursor='...')."
1054 ),
1055 "inputSchema": {
1056 "type": "object",
1057 "properties": {
1058 "limit": {
1059 "type": "integer",
1060 "description": "Maximum repos per page (default 20, max 100).",
1061 "default": 20,
1062 "minimum": 1,
1063 "maximum": 100,
1064 },
1065 "cursor": {
1066 "type": "string",
1067 "description": "Opaque pagination cursor from a previous next_cursor field.",
1068 },
1069 },
1070 "required": [],
1071 },
1072 "annotations": {
1073 "readOnlyHint": True,
1074 "idempotentHint": True,
1075 "openWorldHint": False,
1076 "audience": ["assistant"],
1077 },
1078 },
1079 # ── Mist read tools ───────────────────────────────────────────────────────
1080 {
1081 "name": "muse_mist_read",
1082 "server_side": True,
1083 "description": (
1084 "Fetch the full content and metadata of a single Mist by its content-addressed ID. "
1085 "Public mists are readable by any caller. Secret mists require authentication as the owner. "
1086 "The view counter is incremented on each call. "
1087 "Example: muse_mist_read(mist_id='aB3xKq9dPwNm')"
1088 ),
1089 "inputSchema": {
1090 "type": "object",
1091 "properties": {
1092 "mist_id": {
1093 "type": "string",
1094 "description": "12-character content-addressed mist ID.",
1095 },
1096 },
1097 "required": ["mist_id"],
1098 },
1099 "annotations": {
1100 "readOnlyHint": False,
1101 "idempotentHint": False,
1102 "openWorldHint": False,
1103 "audience": ["assistant"],
1104 },
1105 },
1106 {
1107 "name": "muse_mist_list",
1108 "server_side": True,
1109 "description": (
1110 "List Mists — either for a specific owner or the global public discovery feed. "
1111 "When owner is omitted, returns the newest public mists from all users (explore mode). "
1112 "When owner is provided, returns that handle's mists; pass include_secret=true as the "
1113 "authenticated owner to include your own secret mists. "
1114 "Supports cursor-based pagination via next_cursor. "
1115 "Filter by artifact_type (e.g. 'code', 'midi', 'schema', 'dataset'). "
1116 "Examples: muse_mist_list() — global explore; "
1117 "muse_mist_list(owner='gabriel') — gabriel's public mists; "
1118 "muse_mist_list(owner='gabriel', include_secret=True) — all gabriel's mists (owner only)."
1119 ),
1120 "inputSchema": {
1121 "type": "object",
1122 "properties": {
1123 "owner": {
1124 "type": "string",
1125 "description": "Handle to filter by. Omit for global explore.",
1126 },
1127 "artifact_type": {
1128 "type": "string",
1129 "description": "Filter by artifact type (e.g. 'code', 'midi', 'schema').",
1130 },
1131 "include_secret": {
1132 "type": "boolean",
1133 "description": "Include secret mists (only works when authenticated as the owner).",
1134 "default": False,
1135 },
1136 "cursor": {
1137 "type": "string",
1138 "description": "Pagination cursor from a previous next_cursor field.",
1139 },
1140 "limit": {
1141 "type": "integer",
1142 "description": "Results per page (default 20, max 200).",
1143 "default": 20,
1144 "minimum": 1,
1145 "maximum": 200,
1146 },
1147 },
1148 "required": [],
1149 },
1150 "annotations": {
1151 "readOnlyHint": True,
1152 "idempotentHint": True,
1153 "openWorldHint": False,
1154 "audience": ["assistant"],
1155 },
1156 },
1157 {
1158 "name": "muse_mist_embed",
1159 "server_side": True,
1160 "description": (
1161 "Get embeddable codes (iframe, JavaScript snippet, markdown badge) for a public Mist. "
1162 "Use these to share a Mist in documentation, READMEs, or web pages. "
1163 "The embed counter is incremented so the owner can track distribution. "
1164 "Only available for public mists. "
1165 "Example: muse_mist_embed(mist_id='aB3xKq9dPwNm', owner='gabriel')"
1166 ),
1167 "inputSchema": {
1168 "type": "object",
1169 "properties": {
1170 "mist_id": {
1171 "type": "string",
1172 "description": "12-character mist ID.",
1173 },
1174 "owner": {
1175 "type": "string",
1176 "description": "Handle of the mist owner (used to build canonical URLs).",
1177 },
1178 },
1179 "required": ["mist_id", "owner"],
1180 },
1181 "annotations": {
1182 "readOnlyHint": False,
1183 "idempotentHint": False,
1184 "openWorldHint": False,
1185 "audience": ["assistant"],
1186 },
1187 },
1188 {
1189 "name": "muse_mist_list_forks",
1190 "server_side": True,
1191 "description": (
1192 "List the direct (one-level) forks of a mist, newest first. "
1193 "Returns each fork's mist_id, owner, filename, artifact_type, fork_depth, and created_at. "
1194 "The parent mist must exist and be readable by the caller — secret parents require authentication. "
1195 "Example: muse_mist_list_forks(mist_id='aB3xKq9dPwNm')"
1196 ),
1197 "inputSchema": {
1198 "type": "object",
1199 "properties": {
1200 "mist_id": {
1201 "type": "string",
1202 "description": "12-character mist ID whose direct forks to list.",
1203 },
1204 "limit": {
1205 "type": "integer",
1206 "description": "Maximum number of forks to return (1–100, default 20).",
1207 "default": 20,
1208 "minimum": 1,
1209 "maximum": 100,
1210 },
1211 },
1212 "required": ["mist_id"],
1213 },
1214 "annotations": {
1215 "readOnlyHint": True,
1216 "idempotentHint": True,
1217 "openWorldHint": False,
1218 "audience": ["assistant"],
1219 },
1220 },
1221 {
1222 "name": "muse_mist_raw",
1223 "server_side": True,
1224 "description": (
1225 "Fetch the raw artifact content of a mist as a plain string — faster than muse_mist_read "
1226 "when you only need the content without full metadata. "
1227 "Secret mists require authentication as the owner. "
1228 "Increments the view counter. "
1229 "Example: muse_mist_raw(mist_id='aB3xKq9dPwNm')"
1230 ),
1231 "inputSchema": {
1232 "type": "object",
1233 "properties": {
1234 "mist_id": {
1235 "type": "string",
1236 "description": "12-character mist ID whose content to fetch.",
1237 },
1238 },
1239 "required": ["mist_id"],
1240 },
1241 "annotations": {
1242 "readOnlyHint": False,
1243 "idempotentHint": False,
1244 "openWorldHint": False,
1245 "audience": ["assistant"],
1246 },
1247 },
1248 ]
1249
1250
1251 # ── Write tools ───────────────────────────────────────────────────────────────
1252
1253
1254 MUSEHUB_WRITE_TOOLS: list[MCPToolDef] = [
1255 {
1256 "name": "musehub_create_repo",
1257 "server_side": True,
1258 "description": (
1259 "Create a new MuseHub repository for any Muse domain. "
1260 "The slug is auto-generated from the name. "
1261 "Specify a domain scoped ID (e.g. '@gabriel/midi') to associate the repo with "
1262 "a domain plugin — this unlocks domain-specific viewers, insights, and CLI commands. "
1263 "Call musehub_list_domains first to discover available domains. "
1264 "Set initialize=true (default) to get an initial commit and default branch. "
1265 "Example: musehub_create_repo(name='Genome Edit Session', "
1266 "domain='@alice/genomics', visibility='public')."
1267 ),
1268 "inputSchema": {
1269 "type": "object",
1270 "properties": {
1271 "name": {
1272 "type": "string",
1273 "description": "Human-readable repository name (slug auto-generated).",
1274 },
1275 "description": {
1276 "type": "string",
1277 "description": "Optional markdown description of the repository.",
1278 },
1279 "visibility": {
1280 "type": "string",
1281 "description": "Repository visibility: 'public' or 'private'.",
1282 "enum": ["public", "private"],
1283 "default": "public",
1284 },
1285 "domain": {
1286 "type": "string",
1287 "description": (
1288 "Domain plugin scoped ID (e.g. '@gabriel/midi', '@gabriel/code'). "
1289 "Call musehub_list_domains first to discover available domains."
1290 ),
1291 },
1292 "domain_meta": {
1293 "type": "object",
1294 "description": "Domain-specific metadata dict declared by the domain plugin.",
1295 },
1296 "tags": {
1297 "type": "array",
1298 "description": "Free-form tags for discovery (e.g. ['jazz', 'trio']).",
1299 "items": {"type": "string"},
1300 },
1301 "initialize": {
1302 "type": "boolean",
1303 "description": "When true (default), creates an initial commit and default branch.",
1304 "default": True,
1305 },
1306 },
1307 "required": ["name"],
1308 },
1309 },
1310 {
1311 "name": "musehub_fork_repo",
1312 "server_side": True,
1313 "description": (
1314 "Fork a public MuseHub repository into your account. "
1315 "Creates a new repository owned by the authenticated caller, copies the source "
1316 "repo's tags and description, and records the fork relationship. "
1317 "Rules: the source repo must be public; you cannot fork your own repo; "
1318 "you cannot fork the same repo twice (returns conflict). "
1319 "The fork is public by default — pass visibility='private' for a private fork. "
1320 "Returns full fork metadata including fork_id, fork_repo_id, and source attribution. "
1321 "Example: musehub_fork_repo(source_repo_id='a3f2-...', name='my-fork')."
1322 ),
1323 "inputSchema": {
1324 "type": "object",
1325 "properties": {
1326 "source_repo_id": {
1327 "type": "string",
1328 "description": "sha256 genesis ID of the public repository to fork.",
1329 },
1330 "name": {
1331 "type": "string",
1332 "description": (
1333 "Name for the new fork (slug auto-generated). "
1334 "Defaults to the source repo's name."
1335 ),
1336 },
1337 "visibility": {
1338 "type": "string",
1339 "description": "Visibility of the fork: 'public' (default) or 'private'.",
1340 "enum": ["public", "private"],
1341 "default": "public",
1342 },
1343 "description": {
1344 "type": "string",
1345 "description": (
1346 "Description for the fork. "
1347 "Defaults to 'Fork of {owner}/{slug}: {source description}'."
1348 ),
1349 },
1350 },
1351 "required": ["source_repo_id"],
1352 },
1353 },
1354 {
1355 "name": "musehub_create_issue",
1356 "server_side": True,
1357 "description": (
1358 "Open a new issue in a MuseHub repository. "
1359 "Use issues to track bugs, feature requests, or work items — anchored directly to Muse "
1360 "symbols and commits so agents can trace issues back to the exact code that needs changing. "
1361 "Always supply symbol_anchors (e.g. 'musehub/services/foo.py::create_foo') and "
1362 "commit_anchors when the issue originates from specific code or a specific commit. "
1363 "When filing on behalf of an AI agent, set agent_id and model_id for full provenance. "
1364 "Example: musehub_create_issue(repo_id='a3f2-...', title='fix: crash in create_issue', "
1365 "symbol_anchors=['musehub/services/musehub_issues.py::create_issue'], "
1366 "agent_id='agentception-worker-42', model_id='claude-sonnet-4-6') "
1367 "or musehub_create_issue(owner='gabriel', slug='musehub', title='perf: slow list_issues')."
1368 ),
1369 "inputSchema": {
1370 "type": "object",
1371 "properties": {
1372 **_REPO_ID_PROP,
1373 "title": {
1374 "type": "string",
1375 "description": "Issue title.",
1376 },
1377 "body": {
1378 "type": "string",
1379 "description": "Optional markdown description of the issue.",
1380 },
1381 "labels": {
1382 "type": "array",
1383 "description": "Label strings to apply on creation.",
1384 "items": {"type": "string"},
1385 },
1386 "symbol_anchors": {
1387 "type": "array",
1388 "description": (
1389 "Muse symbol addresses this issue is anchored to. "
1390 "Format: 'path/to/file.py::SymbolName' or 'path/to/file.py::Class.method'. "
1391 "Obtain these via muse code grep or muse code symbols before filing the issue. "
1392 "Example: ['musehub/services/musehub_issues.py::create_issue', "
1393 "'musehub/db/musehub_models.py::MusehubIssue']"
1394 ),
1395 "items": {"type": "string"},
1396 },
1397 "commit_anchors": {
1398 "type": "array",
1399 "description": (
1400 "Muse commit IDs this issue is anchored to. "
1401 "Use when the issue was introduced by or relates to a specific commit. "
1402 "Obtain commit IDs via muse log --json."
1403 ),
1404 "items": {"type": "string"},
1405 },
1406 "agent_id": {
1407 "type": "string",
1408 "description": (
1409 "Agent identifier when filing on behalf of an AI agent "
1410 "(e.g. 'agentception-worker-42'). Leave empty for human-filed issues."
1411 ),
1412 },
1413 "model_id": {
1414 "type": "string",
1415 "description": (
1416 "Model identifier when filing on behalf of an AI agent "
1417 "(e.g. 'claude-sonnet-4-6', 'claude-opus-4-6'). Leave empty for human-filed issues."
1418 ),
1419 },
1420 },
1421 "required": ["title"],
1422 },
1423 },
1424 {
1425 "name": "musehub_update_issue",
1426 "server_side": True,
1427 "description": (
1428 "Update an existing issue's title, body, labels, symbol anchors, commit anchors, "
1429 "state, or assignee. Only provided fields are modified — omitted fields are left unchanged. "
1430 "Set state='closed' to close the issue, state='open' to reopen it. "
1431 "Use symbol_anchors / commit_anchors to add or replace the VCS links for an issue "
1432 "after discovering the relevant code (send the full replacement list, not a diff). "
1433 "Example: musehub_update_issue(repo_id='a3f2-...', issue_number=42, state='closed', "
1434 "symbol_anchors=['musehub/services/musehub_issues.py::create_issue'])."
1435 ),
1436 "inputSchema": {
1437 "type": "object",
1438 "properties": {
1439 **_REPO_ID_PROP,
1440 "issue_number": {
1441 "type": "integer",
1442 "description": "Per-repo issue number.",
1443 },
1444 "title": {
1445 "type": "string",
1446 "description": "New title (optional).",
1447 },
1448 "body": {
1449 "type": "string",
1450 "description": "New markdown body (optional).",
1451 },
1452 "labels": {
1453 "type": "array",
1454 "description": "Replacement label list (replaces all existing labels).",
1455 "items": {"type": "string"},
1456 },
1457 "symbol_anchors": {
1458 "type": "array",
1459 "description": (
1460 "Replacement symbol anchor list (replaces all existing anchors). "
1461 "Format: 'path/to/file.py::SymbolName'. "
1462 "Obtain via muse code grep or muse code symbols."
1463 ),
1464 "items": {"type": "string"},
1465 },
1466 "commit_anchors": {
1467 "type": "array",
1468 "description": (
1469 "Replacement commit anchor list (replaces all existing anchors). "
1470 "Obtain commit IDs via muse log --json."
1471 ),
1472 "items": {"type": "string"},
1473 },
1474 "state": {
1475 "type": "string",
1476 "description": "New state: 'open' or 'closed'.",
1477 "enum": ["open", "closed"],
1478 },
1479 "assignee": {
1480 "type": "string",
1481 "description": "Username to assign, or empty string to unassign.",
1482 },
1483 },
1484 "required": ["issue_number"],
1485 },
1486 },
1487 {
1488 "name": "musehub_create_issue_comment",
1489 "server_side": True,
1490 "description": (
1491 "Add a comment to an existing issue. "
1492 "Example: musehub_create_issue_comment(repo_id='a3f2-...', issue_number=42, "
1493 "body='Fixed in commit abc...')."
1494 ),
1495 "inputSchema": {
1496 "type": "object",
1497 "properties": {
1498 **_REPO_ID_PROP,
1499 "issue_number": {
1500 "type": "integer",
1501 "description": "Per-repo issue number.",
1502 },
1503 "body": {
1504 "type": "string",
1505 "description": "Markdown comment body.",
1506 },
1507 },
1508 "required": ["issue_number", "body"],
1509 },
1510 },
1511 {
1512 "name": "musehub_close_issue",
1513 "server_side": True,
1514 "description": (
1515 "Close an open issue. Idempotent — closing an already-closed issue returns the issue unchanged. "
1516 "Use musehub_reopen_issue to reverse. "
1517 "Example: musehub_close_issue(repo_id='a3f2-...', issue_number=42)."
1518 ),
1519 "inputSchema": {
1520 "type": "object",
1521 "properties": {
1522 **_REPO_ID_PROP,
1523 "issue_number": {
1524 "type": "integer",
1525 "description": "Per-repo issue number.",
1526 },
1527 },
1528 "required": ["issue_number"],
1529 },
1530 },
1531 {
1532 "name": "musehub_reopen_issue",
1533 "server_side": True,
1534 "description": (
1535 "Reopen a closed issue. Idempotent — reopening an already-open issue returns the issue unchanged. "
1536 "Use musehub_close_issue to close. "
1537 "Example: musehub_reopen_issue(repo_id='a3f2-...', issue_number=42)."
1538 ),
1539 "inputSchema": {
1540 "type": "object",
1541 "properties": {
1542 **_REPO_ID_PROP,
1543 "issue_number": {
1544 "type": "integer",
1545 "description": "Per-repo issue number.",
1546 },
1547 },
1548 "required": ["issue_number"],
1549 },
1550 },
1551 {
1552 "name": "musehub_assign_issue",
1553 "server_side": True,
1554 "description": (
1555 "Assign or unassign a collaborator on an issue. "
1556 "Pass assignee='' to unassign. "
1557 "Example: musehub_assign_issue(repo_id='a3f2-...', issue_number=42, assignee='gabriel')."
1558 ),
1559 "inputSchema": {
1560 "type": "object",
1561 "properties": {
1562 **_REPO_ID_PROP,
1563 "issue_number": {
1564 "type": "integer",
1565 "description": "Per-repo issue number.",
1566 },
1567 "assignee": {
1568 "type": "string",
1569 "description": "Username to assign, or empty string to unassign.",
1570 },
1571 },
1572 "required": ["issue_number", "assignee"],
1573 },
1574 },
1575 {
1576 "name": "musehub_update_issue_labels",
1577 "server_side": True,
1578 "description": (
1579 "Bulk-replace the label list on an issue. The new list replaces all existing labels. "
1580 "Pass labels=[] to remove all labels. "
1581 "Use musehub_remove_issue_label to remove a single label without affecting others. "
1582 "Example: musehub_update_issue_labels(repo_id='a3f2-...', issue_number=42, labels=['bug', 'enhancement'])."
1583 ),
1584 "inputSchema": {
1585 "type": "object",
1586 "properties": {
1587 **_REPO_ID_PROP,
1588 "issue_number": {
1589 "type": "integer",
1590 "description": "Per-repo issue number.",
1591 },
1592 "labels": {
1593 "type": "array",
1594 "description": "Replacement label list (replaces all existing labels). Pass [] to clear all.",
1595 "items": {"type": "string"},
1596 },
1597 },
1598 "required": ["issue_number", "labels"],
1599 },
1600 },
1601 {
1602 "name": "musehub_remove_issue_label",
1603 "server_side": True,
1604 "description": (
1605 "Remove a single label from an issue. Idempotent — no-ops when the label is not present. "
1606 "Use musehub_update_issue_labels to replace the entire label list. "
1607 "Example: musehub_remove_issue_label(repo_id='a3f2-...', issue_number=42, label='bug')."
1608 ),
1609 "inputSchema": {
1610 "type": "object",
1611 "properties": {
1612 **_REPO_ID_PROP,
1613 "issue_number": {
1614 "type": "integer",
1615 "description": "Per-repo issue number.",
1616 },
1617 "label": {
1618 "type": "string",
1619 "description": "Label name to remove.",
1620 },
1621 },
1622 "required": ["issue_number", "label"],
1623 },
1624 },
1625 {
1626 "name": "musehub_create_proposal",
1627 "server_side": True,
1628 "description": (
1629 "Open a new merge proposal proposing to merge from_branch into to_branch. "
1630 "Call musehub_list_branches first to confirm both branches exist. "
1631 "Supports draft proposals, domain-selective merge strategies, and DAG-based dependency ordering. "
1632 "Example: musehub_create_proposal(repo_id='a3f2-...', title='Add jazz bridge', "
1633 "from_branch='feature/jazz-bridge', to_branch='main', merge_strategy='weave')."
1634 ),
1635 "inputSchema": {
1636 "type": "object",
1637 "properties": {
1638 **_REPO_ID_PROP,
1639 "title": {
1640 "type": "string",
1641 "description": "Merge proposal title.",
1642 },
1643 "from_branch": {
1644 "type": "string",
1645 "description": "Source branch name to merge from.",
1646 },
1647 "to_branch": {
1648 "type": "string",
1649 "description": "Target branch name to merge into.",
1650 },
1651 "body": {
1652 "type": "string",
1653 "description": "Optional markdown description.",
1654 },
1655 "proposal_type": {
1656 "type": "string",
1657 "enum": [
1658 "state_merge", "stem_integration", "midi_evolution",
1659 "payment_settlement", "agent_delegation",
1660 "identity_transition", "canonical_release",
1661 ],
1662 "description": "Semantic proposal type. Defaults to 'state_merge'.",
1663 "default": "state_merge",
1664 },
1665 "is_draft": {
1666 "type": "boolean",
1667 "description": "Open as a draft (not ready for review). Default false.",
1668 "default": False,
1669 },
1670 "merge_strategy": {
1671 "type": "string",
1672 "enum": ["overlay", "weave", "replay", "selective", "phased", "cherry_pick"],
1673 "description": "How branches are merged. Default 'overlay'.",
1674 "default": "overlay",
1675 },
1676 "merge_conditions": {
1677 "type": "object",
1678 "description": "Optional JSON object with additional merge gate conditions.",
1679 },
1680 "selective_domains": {
1681 "type": "array",
1682 "items": {"type": "string"},
1683 "description": "Domain names to include when merge_strategy='selective'.",
1684 },
1685 "depends_on": {
1686 "type": "array",
1687 "items": {"type": "string"},
1688 "description": "Proposal IDs that must be merged before this one (DAG ordering).",
1689 },
1690 },
1691 "required": ["title", "from_branch", "to_branch"],
1692 },
1693 },
1694 {
1695 "name": "musehub_merge_proposal",
1696 "server_side": True,
1697 "description": (
1698 "Merge an open merge proposal. Creates a merge commit on the target branch. "
1699 "The proposal must be in 'open' state. Obtain proposal_id from musehub_list_proposals. "
1700 "Example: musehub_merge_proposal(repo_id='a3f2-...', proposal_id='b5e8-...')."
1701 ),
1702 "inputSchema": {
1703 "type": "object",
1704 "properties": {
1705 **_REPO_ID_PROP,
1706 "proposal_id": {
1707 "type": "string",
1708 "description": "sha256 genesis ID of the merge proposal to merge.",
1709 },
1710 "merge_strategy": {
1711 "type": "string",
1712 "description": "Merge strategy: 'merge_commit' (default), 'squash', or 'rebase'.",
1713 "enum": ["merge_commit", "squash", "rebase"],
1714 "default": "merge_commit",
1715 },
1716 },
1717 "required": ["proposal_id"],
1718 },
1719 },
1720 {
1721 "name": "musehub_create_proposal_comment",
1722 "server_side": True,
1723 "description": (
1724 "Post a comment on a merge proposal, optionally anchored to a specific symbol address. "
1725 "Use symbol_address to pin the comment directly to a named symbol in the Symbol Delta "
1726 "(e.g. 'core/engine.py::Engine.process'). "
1727 "For non-code domains, pass dimension_ref instead to target a track, beat region, or note. "
1728 "Omit both for a general proposal-level comment. "
1729 "Example: musehub_create_proposal_comment(repo_id='a3f2-...', proposal_id='b5e8-...', "
1730 "body='This symbol has a blast radius of 34 — needs test coverage', "
1731 "symbol_address='core/engine.py::Engine.process')."
1732 ),
1733 "inputSchema": {
1734 "type": "object",
1735 "properties": {
1736 **_REPO_ID_PROP,
1737 "proposal_id": {
1738 "type": "string",
1739 "description": "sha256 genesis ID of the merge proposal.",
1740 },
1741 "body": {
1742 "type": "string",
1743 "description": "Markdown comment body.",
1744 },
1745 "symbol_address": {
1746 "type": "string",
1747 "description": (
1748 "Symbol address to anchor this comment to "
1749 "(e.g. 'core/engine.py::Engine.process'). "
1750 "Use the addresses returned by musehub_read_proposal symAdded/symModified/symDeleted. "
1751 "Takes precedence over dimension_ref for code-domain proposals."
1752 ),
1753 },
1754 "dimension_ref": {
1755 "type": "object",
1756 "description": (
1757 "Optional domain-specific anchor for non-code domains (MIDI, genomics, etc.). "
1758 "Schema is defined by the repo's domain plugin. "
1759 "Prefer symbol_address for code-domain proposals."
1760 ),
1761 },
1762 "parent_comment_id": {
1763 "type": "string",
1764 "description": "ID of the parent comment when creating a threaded reply.",
1765 },
1766 },
1767 "required": ["proposal_id", "body"],
1768 },
1769 },
1770 {
1771 "name": "musehub_create_proposal_review",
1772 "server_side": True,
1773 "description": (
1774 "Submit a formal review on a merge proposal. "
1775 "event='approve' approves the proposal; event='request_changes' blocks merge; "
1776 "event='comment' adds a neutral review comment. "
1777 "Example: musehub_create_proposal_review(repo_id='a3f2-...', proposal_id='b5e8-...', "
1778 "event='approve', body='Sounds great! The bridge lands perfectly.')."
1779 ),
1780 "inputSchema": {
1781 "type": "object",
1782 "properties": {
1783 **_REPO_ID_PROP,
1784 "proposal_id": {
1785 "type": "string",
1786 "description": "sha256 genesis ID of the merge proposal.",
1787 },
1788 "event": {
1789 "type": "string",
1790 "description": "Review verdict: 'approve', 'request_changes', or 'comment'.",
1791 "enum": ["approve", "request_changes", "comment"],
1792 },
1793 "body": {
1794 "type": "string",
1795 "description": "Optional review summary.",
1796 },
1797 },
1798 "required": ["proposal_id", "event"],
1799 },
1800 },
1801 {
1802 "name": "musehub_create_release",
1803 "server_side": True,
1804 "description": (
1805 "Publish a new release for a MuseHub repository. "
1806 "A release pins a version tag to a commit and packages the state snapshot. "
1807 "Tags must be unique per repo (e.g. 'v1.0', 'final-mix'). "
1808 "For an interactive release flow with elicitation, use musehub_create_release_interactive. "
1809 "Example: musehub_create_release(repo_id='a3f2-...', tag='v1.0', title='First Release', "
1810 "body='Initial session recording.')."
1811 ),
1812 "inputSchema": {
1813 "type": "object",
1814 "properties": {
1815 **_REPO_ID_PROP,
1816 "tag": {
1817 "type": "string",
1818 "description": "Version tag string (e.g. 'v1.0'). Must be unique per repo.",
1819 },
1820 "title": {
1821 "type": "string",
1822 "description": "Human-readable release title.",
1823 },
1824 "body": {
1825 "type": "string",
1826 "description": "Markdown release notes.",
1827 },
1828 "commit_id": {
1829 "type": "string",
1830 "description": "Optional commit SHA to pin this release to. Defaults to HEAD of the default branch.",
1831 },
1832 "is_prerelease": {
1833 "type": "boolean",
1834 "description": "When true, marks as pre-release.",
1835 "default": False,
1836 },
1837 },
1838 "required": ["tag", "title"],
1839 },
1840 },
1841 {
1842 "name": "musehub_create_label",
1843 "server_side": True,
1844 "description": (
1845 "Create a repo-scoped label with a name and hex colour. "
1846 "Labels can be applied to issues and proposals for categorisation. "
1847 "Label names must be unique within the repository (max 50 chars). "
1848 "Color must be a 7-character hex string starting with '#' (e.g. '#d73a4a'). "
1849 "Example: musehub_create_label(repo_id='a3f2-...', name='bug', color='#d73a4a')."
1850 ),
1851 "inputSchema": {
1852 "type": "object",
1853 "properties": {
1854 **_REPO_ID_PROP,
1855 "name": {
1856 "type": "string",
1857 "description": "Label name (unique per repo, max 50 chars).",
1858 "maxLength": 50,
1859 },
1860 "color": {
1861 "type": "string",
1862 "description": "7-character hex colour starting with '#' (e.g. '#d73a4a').",
1863 "pattern": "^#[0-9a-fA-F]{6}$",
1864 },
1865 "description": {
1866 "type": "string",
1867 "description": "Optional label description (max 200 chars).",
1868 "maxLength": 200,
1869 },
1870 },
1871 "required": ["repo_id", "name", "color"],
1872 },
1873 },
1874 {
1875 "name": "musehub_update_label",
1876 "server_side": True,
1877 "description": (
1878 "Partially update an existing label — rename it, change its colour, or update its description. "
1879 "Only the fields you provide are changed; omitted fields stay as-is. "
1880 "The new name must still be unique within the repository. "
1881 "Get label_id from musehub_list_labels. "
1882 "Example: musehub_update_label(repo_id='sha256:...', label_id='sha256:...', name='critical-bug', color='#b60205')."
1883 ),
1884 "inputSchema": {
1885 "type": "object",
1886 "properties": {
1887 **_REPO_ID_PROP,
1888 "label_id": {
1889 "type": "string",
1890 "description": "ID of the label to update (from musehub_list_labels).",
1891 },
1892 "name": {
1893 "type": "string",
1894 "description": "New label name (optional, max 50 chars, must be unique per repo).",
1895 "maxLength": 50,
1896 },
1897 "color": {
1898 "type": "string",
1899 "description": "New hex colour starting with '#' (optional, e.g. '#b60205').",
1900 "pattern": "^#[0-9a-fA-F]{6}$",
1901 },
1902 "description": {
1903 "type": "string",
1904 "description": "New description (optional, max 200 chars; pass '' to clear).",
1905 "maxLength": 200,
1906 },
1907 },
1908 "required": ["repo_id", "label_id"],
1909 },
1910 },
1911 {
1912 "name": "musehub_delete_label",
1913 "server_side": True,
1914 "description": (
1915 "Permanently delete a label from a MuseHub repository and remove it from every "
1916 "issue and proposal it is currently attached to. This operation is irreversible. "
1917 "Get label_id from musehub_list_labels. "
1918 "Example: musehub_delete_label(repo_id='sha256:...', label_id='sha256:...')."
1919 ),
1920 "inputSchema": {
1921 "type": "object",
1922 "properties": {
1923 **_REPO_ID_PROP,
1924 "label_id": {
1925 "type": "string",
1926 "description": "ID of the label to delete (from musehub_list_labels).",
1927 },
1928 },
1929 "required": ["repo_id", "label_id"],
1930 },
1931 },
1932 {
1933 "name": "muse_push",
1934 "server_side": True,
1935 "description": (
1936 "Push commits, snapshots, and binary objects to a MuseHub repository. "
1937 "Equivalent to 'muse push' — uploads new commits, their snapshot manifests, "
1938 "and base64-encoded binary objects in a single atomic batch. "
1939 "Enforces fast-forward semantics unless force=true. "
1940 "All three arrays are optional — a push with only commits and no new file content "
1941 "is valid (e.g. a merge commit that modifies no files). "
1942 "Authentication required: use `muse auth keygen` + `muse auth register --agent`. "
1943 "Example: muse_push(repo_id='a3f2-...', branch='main', head_commit_id='abc123', "
1944 "commits=[{commit_id, parent_ids, message, snapshot_id, ...}], "
1945 "snapshots=[{snapshot_id, manifest:{path: object_id}}], "
1946 "objects=[{object_id, path, content_b64}])."
1947 ),
1948 "inputSchema": {
1949 "type": "object",
1950 "properties": {
1951 **_REPO_ID_PROP,
1952 "branch": {
1953 "type": "string",
1954 "description": "Target branch name (e.g. 'main').",
1955 },
1956 "head_commit_id": {
1957 "type": "string",
1958 "description": "SHA of the new HEAD commit after this push.",
1959 },
1960 "commits": {
1961 "type": "array",
1962 "description": (
1963 "List of CommitInput objects to push. Each has: "
1964 "commit_id (str), parent_ids (list[str]), message (str), "
1965 "author (str), timestamp (ISO-8601 str), snapshot_id (str)."
1966 ),
1967 "items": {"type": "object"},
1968 },
1969 "snapshots": {
1970 "type": "array",
1971 "description": (
1972 "Snapshot manifests for the pushed commits. Each has: "
1973 "snapshot_id (str, SHA-256 of sorted path:oid pairs), "
1974 "manifest (dict[str, str] mapping path → object_id), "
1975 "created_at (ISO-8601 str, optional). "
1976 "Snapshots are idempotent — already-stored snapshots are skipped. "
1977 "Include one snapshot per commit that introduces file changes."
1978 ),
1979 "items": {"type": "object"},
1980 },
1981 "blobs": {
1982 "type": "array",
1983 "description": (
1984 "List of blob objects to upload. Each has: "
1985 "object_id (str, e.g. 'sha256:abc...'), "
1986 "path (str, e.g. 'tracks/bass.mid'), "
1987 "content_b64 (str, base64-encoded bytes)."
1988 ),
1989 "items": {"type": "object"},
1990 },
1991 "force": {
1992 "type": "boolean",
1993 "description": "Allow non-fast-forward push (overwrites remote head). Use with caution.",
1994 "default": False,
1995 },
1996 },
1997 "required": ["branch", "head_commit_id"],
1998 },
1999 },
2000 {
2001 "name": "muse_config",
2002 "server_side": True,
2003 "description": (
2004 "Read info about Muse configuration keys or generate a 'muse config set' command. "
2005 "Equivalent to 'muse config get <key>' or 'muse config set <key> <value>'. "
2006 "Call without arguments to list all known config keys. "
2007 "Pass key and value to get the exact CLI command to run. "
2008 "Repo-level keys (stored in .muse/config.toml): hub.url, user.type. "
2009 "Auth tokens are stored per-hub in ~/.muse/identity.toml — use 'muse auth login' "
2010 "or write the token directly under the hub hostname section. "
2011 "Example: muse_config(key='hub.url', value='https://musehub.ai')."
2012 ),
2013 "inputSchema": {
2014 "type": "object",
2015 "properties": {
2016 "key": {
2017 "type": "string",
2018 "description": (
2019 "Configuration key to query or set "
2020 "(e.g. 'hub.url', 'user.type'). "
2021 "Note: auth tokens live in ~/.muse/identity.toml, not config.toml. "
2022 "Omit to list all known keys."
2023 ),
2024 },
2025 "value": {
2026 "type": "string",
2027 "description": (
2028 "When provided together with key, returns the CLI command "
2029 "'muse config set <key> <value>'."
2030 ),
2031 },
2032 },
2033 "required": [],
2034 },
2035 },
2036 {
2037 "name": "musehub_publish_domain",
2038 "server_side": True,
2039 "description": (
2040 "Register a new Muse domain plugin in the MuseHub marketplace. "
2041 "After registration the domain appears in musehub_list_domains and can be "
2042 "selected when creating repos (musehub_create_repo). "
2043 "The scoped identifier '@{author_slug}/{slug}' must be globally unique. "
2044 "Authentication required: use `muse auth keygen` + `muse auth register --agent`. "
2045 "The 'capabilities' object must follow the Muse domain schema:\n"
2046 " dimensions: list of {name, description} objects\n"
2047 " viewer_type: primary viewer identifier (e.g. 'midi', 'code', 'spatial')\n"
2048 " artifact_types: list of MIME types the domain produces\n"
2049 " merge_semantics: 'ot' | 'crdt' | 'three_way'\n"
2050 " supported_commands: list of muse CLI commands this domain supports\n"
2051 "Returns: {domain_id, scoped_id, manifest_hash} on success. "
2052 "Example: musehub_publish_domain(author_slug='gabriel', slug='genomics', "
2053 "display_name='Genomics', description='Version DNA sequences', "
2054 "capabilities={...}, viewer_type='sequence', version='0.1.0')."
2055 ),
2056 "inputSchema": {
2057 "type": "object",
2058 "properties": {
2059 "author_slug": {
2060 "type": "string",
2061 "description": "Your MuseHub username (owner of the domain).",
2062 },
2063 "slug": {
2064 "type": "string",
2065 "description": "URL-safe domain name (e.g. 'genomics', 'spatial-3d').",
2066 },
2067 "display_name": {
2068 "type": "string",
2069 "description": "Human-readable name shown in the marketplace.",
2070 },
2071 "description": {
2072 "type": "string",
2073 "description": "What this domain models and why it benefits from semantic VCS.",
2074 },
2075 "capabilities": {
2076 "type": "object",
2077 "description": (
2078 "Domain capabilities manifest. Required keys: "
2079 "dimensions (list of {name, description}), "
2080 "viewer_type (string), "
2081 "artifact_types (list of MIME strings), "
2082 "merge_semantics ('ot'|'crdt'|'three_way'), "
2083 "supported_commands (list of muse CLI command names)."
2084 ),
2085 },
2086 "viewer_type": {
2087 "type": "string",
2088 "description": "Primary viewer identifier (e.g. 'midi', 'code', 'spatial', 'genome').",
2089 },
2090 "version": {
2091 "type": "string",
2092 "description": "Semver string (e.g. '0.1.0').",
2093 "default": "0.1.0",
2094 },
2095 },
2096 "required": [
2097 "author_slug", "slug", "display_name",
2098 "description", "capabilities", "viewer_type",
2099 ],
2100 },
2101 },
2102 {
2103 "name": "musehub_agent_notify",
2104 "server_side": True,
2105 "description": (
2106 "Send a real-time signal to another agent's active MCP session(s) via SSE.\n\n"
2107 "The target agent receives a notifications/agent_message event on their GET /mcp SSE stream "
2108 "immediately — no polling needed.\n\n"
2109 "Use cases:\n"
2110 " - Handoff: 'I finished processing the repo, your turn'\n"
2111 " - Unblock: 'I released the reservation on AudioEngine, you can proceed'\n"
2112 " - Coordinate: 'I found a conflict in track 3, assigning to you'\n\n"
2113 "target_handle is the MSign handle (username) of the receiving agent.\n"
2114 "event is a short string naming the signal (e.g. 'task_complete', 'handoff', 'conflict_found').\n"
2115 "payload is arbitrary JSON for the receiver to act on.\n\n"
2116 "Returns sessions_reached=0 with not_ready error if the target has no active sessions.\n"
2117 "Check musehub_read_coord_swarm() to see which agents are currently connected."
2118 ),
2119 "inputSchema": {
2120 "type": "object",
2121 "properties": {
2122 "target_handle": {
2123 "type": "string",
2124 "description": "MSign handle (username) of the target agent to notify.",
2125 },
2126 "event": {
2127 "type": "string",
2128 "description": "Short signal name (e.g. 'task_complete', 'handoff', 'unblocked').",
2129 },
2130 "payload": {
2131 "type": "object",
2132 "description": "Arbitrary JSON payload for the receiving agent.",
2133 },
2134 },
2135 "required": ["target_handle", "event", "payload"],
2136 },
2137 },
2138 {
2139 "name": "musehub_agent_broadcast",
2140 "server_side": True,
2141 "description": (
2142 "Broadcast a real-time signal to ALL agents focused on the same repo via SSE.\n\n"
2143 "Delivers notifications/agent_message to every active MCP session whose repo focus "
2144 "matches your current session focus (set via musehub_set_context). "
2145 "Your own session is excluded.\n\n"
2146 "Use cases:\n"
2147 " - Swarm coordination: 'Starting merge, pause your pushes'\n"
2148 " - Progress updates: 'Analysis complete, results in task queue'\n"
2149 " - Phase transitions: 'Phase 1 done, all workers proceed to phase 2'\n\n"
2150 "Requires musehub_set_context(owner, slug) to be called first.\n"
2151 "Returns sessions_reached=0 (not an error) when no other agents are focused on the repo."
2152 ),
2153 "inputSchema": {
2154 "type": "object",
2155 "properties": {
2156 "event": {
2157 "type": "string",
2158 "description": "Short signal name (e.g. 'phase_complete', 'merge_starting', 'results_ready').",
2159 },
2160 "payload": {
2161 "type": "object",
2162 "description": "Arbitrary JSON payload for receiving agents.",
2163 },
2164 },
2165 "required": ["event", "payload"],
2166 },
2167 },
2168 # ── Issue comment management ──────────────────────────────────────────────
2169 {
2170 "name": "musehub_delete_issue_comment",
2171 "server_side": True,
2172 "description": (
2173 "Soft-delete a comment from an issue. Deleted comments are hidden from list results "
2174 "but preserved in the audit log. Requires write/admin access or repo ownership. "
2175 "Get comment_id from musehub_list_issue_comments or the comment creation response. "
2176 "Example: musehub_delete_issue_comment(repo_id='a3f2-...', issue_number=42, "
2177 "comment_id='sha256:...')."
2178 ),
2179 "inputSchema": {
2180 "type": "object",
2181 "properties": {
2182 **_REPO_ID_PROP,
2183 "issue_number": {
2184 "type": "integer",
2185 "description": "Per-repo issue number.",
2186 "minimum": 1,
2187 },
2188 "comment_id": {
2189 "type": "string",
2190 "description": "sha256 genesis ID of the comment to delete.",
2191 },
2192 },
2193 "required": ["repo_id", "issue_number", "comment_id"],
2194 },
2195 },
2196 # ── Collaborator management ───────────────────────────────────────────────
2197 {
2198 "name": "musehub_list_collaborators",
2199 "server_side": True,
2200 "description": (
2201 "List all collaborators for a repository with their permission levels. "
2202 "Returns read, write, admin, and owner entries. "
2203 "Authentication required. "
2204 "Example: musehub_list_collaborators(repo_id='a3f2-...')."
2205 ),
2206 "inputSchema": {
2207 "type": "object",
2208 "properties": {**_REPO_ID_PROP},
2209 "required": ["repo_id"],
2210 },
2211 },
2212 {
2213 "name": "musehub_invite_collaborator",
2214 "server_side": True,
2215 "description": (
2216 "Invite a user as a collaborator on a repository. "
2217 "Requires the caller to be the repo owner or have admin permission. "
2218 "Permission levels: read (view only), write (push + issue management), "
2219 "admin (write + invite/remove collaborators). "
2220 "Returns error_code='conflict' if the user is already a collaborator. "
2221 "Example: musehub_invite_collaborator(repo_id='a3f2-...', handle='carol', "
2222 "permission='write')."
2223 ),
2224 "inputSchema": {
2225 "type": "object",
2226 "properties": {
2227 **_REPO_ID_PROP,
2228 "handle": {
2229 "type": "string",
2230 "description": "MSign handle of the user to invite.",
2231 },
2232 "permission": {
2233 "type": "string",
2234 "description": "Permission level: 'read', 'write' (default), or 'admin'.",
2235 "enum": ["read", "write", "admin"],
2236 "default": "write",
2237 },
2238 },
2239 "required": ["repo_id", "handle"],
2240 },
2241 },
2242 {
2243 "name": "musehub_update_collaborator_permission",
2244 "server_side": True,
2245 "description": (
2246 "Update an existing collaborator's permission level. "
2247 "Requires admin or owner access. The repository owner's own permission "
2248 "cannot be changed through this tool. "
2249 "Example: musehub_update_collaborator_permission(repo_id='a3f2-...', "
2250 "handle='carol', permission='admin')."
2251 ),
2252 "inputSchema": {
2253 "type": "object",
2254 "properties": {
2255 **_REPO_ID_PROP,
2256 "handle": {
2257 "type": "string",
2258 "description": "MSign handle of the collaborator to update.",
2259 },
2260 "permission": {
2261 "type": "string",
2262 "description": "New permission level: 'read', 'write', or 'admin'.",
2263 "enum": ["read", "write", "admin"],
2264 },
2265 },
2266 "required": ["repo_id", "handle", "permission"],
2267 },
2268 },
2269 {
2270 "name": "musehub_remove_collaborator",
2271 "server_side": True,
2272 "description": (
2273 "Remove a collaborator from a repository. "
2274 "Requires admin or owner access. The repository owner cannot be removed. "
2275 "Example: musehub_remove_collaborator(repo_id='a3f2-...', handle='carol')."
2276 ),
2277 "inputSchema": {
2278 "type": "object",
2279 "properties": {
2280 **_REPO_ID_PROP,
2281 "handle": {
2282 "type": "string",
2283 "description": "MSign handle of the collaborator to remove.",
2284 },
2285 },
2286 "required": ["repo_id", "handle"],
2287 },
2288 },
2289 # ── Webhook management ────────────────────────────────────────────────────
2290 {
2291 "name": "musehub_create_webhook",
2292 "server_side": True,
2293 "description": (
2294 "Register a new webhook subscription for a repository. "
2295 "The webhook URL will receive HTTP POST payloads for each subscribed event type. "
2296 "Valid event types: push, proposal, issue, release, branch, tag, session, analysis. "
2297 "Requires write/admin access or repo ownership. "
2298 "The secret is used to compute an HMAC-SHA256 signature on each payload "
2299 "(sent in the X-MuseHub-Signature header). "
2300 "Example: musehub_create_webhook(repo_id='a3f2-...', "
2301 "url='https://example.com/hook', events=['push', 'release'])."
2302 ),
2303 "inputSchema": {
2304 "type": "object",
2305 "properties": {
2306 **_REPO_ID_PROP,
2307 "url": {
2308 "type": "string",
2309 "description": "HTTPS URL that will receive event payloads.",
2310 },
2311 "events": {
2312 "type": "array",
2313 "description": (
2314 "Event types to subscribe to. "
2315 "Valid: push, proposal, issue, release, branch, tag, session, analysis."
2316 ),
2317 "items": {"type": "string"},
2318 "minItems": 1,
2319 },
2320 "secret": {
2321 "type": "string",
2322 "description": "Optional HMAC-SHA256 signing secret (plaintext). Store securely.",
2323 "default": "",
2324 },
2325 },
2326 "required": ["repo_id", "url", "events"],
2327 },
2328 },
2329 {
2330 "name": "musehub_list_webhooks",
2331 "server_side": True,
2332 "description": (
2333 "List all webhook subscriptions for a repository. "
2334 "Returns webhook IDs, URLs, event subscriptions, and active status. "
2335 "Authentication required. "
2336 "Example: musehub_list_webhooks(repo_id='a3f2-...')."
2337 ),
2338 "inputSchema": {
2339 "type": "object",
2340 "properties": {**_REPO_ID_PROP},
2341 "required": ["repo_id"],
2342 },
2343 },
2344 {
2345 "name": "musehub_delete_webhook",
2346 "server_side": True,
2347 "description": (
2348 "Delete a webhook subscription and all its delivery history. "
2349 "This operation is irreversible. "
2350 "Requires write/admin access or repo ownership. "
2351 "Get webhook_id from musehub_list_webhooks. "
2352 "Example: musehub_delete_webhook(repo_id='sha256:...', webhook_id='sha256:...')."
2353 ),
2354 "inputSchema": {
2355 "type": "object",
2356 "properties": {
2357 **_REPO_ID_PROP,
2358 "webhook_id": {
2359 "type": "string",
2360 "description": "ID of the webhook to delete (from musehub_list_webhooks).",
2361 },
2362 },
2363 "required": ["repo_id", "webhook_id"],
2364 },
2365 },
2366 # ── Release asset management ──────────────────────────────────────────────
2367 {
2368 "name": "musehub_attach_release_asset",
2369 "server_side": True,
2370 "description": (
2371 "Attach a downloadable asset file to an existing release. "
2372 "Assets represent build artifacts, MIDI bundles, model checkpoints, or any "
2373 "file associated with a release. "
2374 "Requires write/admin access or repo ownership. "
2375 "The release must already exist (create it with musehub_create_release first). "
2376 "Example: musehub_attach_release_asset(repo_id='a3f2-...', tag='v1.2.3', "
2377 "name='myapp-v1.2.3-linux.tar.gz', "
2378 "download_url='https://cdn.example.com/myapp-v1.2.3-linux.tar.gz')."
2379 ),
2380 "inputSchema": {
2381 "type": "object",
2382 "properties": {
2383 **_REPO_ID_PROP,
2384 "tag": {
2385 "type": "string",
2386 "description": "Release tag the asset belongs to (e.g. 'v1.2.3').",
2387 },
2388 "name": {
2389 "type": "string",
2390 "description": "Filename shown in the UI (e.g. 'myapp-v1.2.3-linux.tar.gz').",
2391 },
2392 "download_url": {
2393 "type": "string",
2394 "description": "Direct download URL for the artifact.",
2395 },
2396 "label": {
2397 "type": "string",
2398 "description": "Optional human-readable label (e.g. 'Linux bundle').",
2399 "default": "",
2400 },
2401 "content_type": {
2402 "type": "string",
2403 "description": "MIME type of the artifact (e.g. 'application/gzip').",
2404 "default": "",
2405 },
2406 "size": {
2407 "type": "integer",
2408 "description": "File size in bytes (0 if unknown).",
2409 "default": 0,
2410 },
2411 },
2412 "required": ["repo_id", "tag", "name", "download_url"],
2413 },
2414 },
2415 {
2416 "name": "musehub_delete_release_asset",
2417 "server_side": True,
2418 "description": (
2419 "Permanently remove an asset from a release. "
2420 "This operation is irreversible — the download URL remains valid only "
2421 "if the external storage is not also deleted. "
2422 "Requires write/admin access or repo ownership. "
2423 "Get asset_id from musehub_list_release_assets or the asset creation response. "
2424 "Example: musehub_delete_release_asset(repo_id='a3f2-...', tag='v1.2.3', "
2425 "asset_id='sha256:...')."
2426 ),
2427 "inputSchema": {
2428 "type": "object",
2429 "properties": {
2430 **_REPO_ID_PROP,
2431 "tag": {
2432 "type": "string",
2433 "description": "Release tag the asset belongs to.",
2434 },
2435 "asset_id": {
2436 "type": "string",
2437 "description": "ID of the asset to remove (from musehub_list_release_assets).",
2438 },
2439 },
2440 "required": ["repo_id", "tag", "asset_id"],
2441 },
2442 },
2443 # ── Proposal comment & reviewer management ────────────────────────────────
2444 {
2445 "name": "musehub_list_proposal_comments",
2446 "server_side": True,
2447 "description": (
2448 "Return all review comments for a merge proposal, threaded by parent. "
2449 "Each top-level comment includes a 'replies' list of direct children. "
2450 "Authentication required. "
2451 "Example: musehub_list_proposal_comments(repo_id='sha256:...', proposal_id='sha256:...')."
2452 ),
2453 "inputSchema": {
2454 "type": "object",
2455 "properties": {
2456 **_REPO_ID_PROP,
2457 "proposal_id": {
2458 "type": "string",
2459 "description": "sha256 genesis ID of the merge proposal.",
2460 },
2461 },
2462 "required": ["repo_id", "proposal_id"],
2463 },
2464 },
2465 {
2466 "name": "musehub_request_proposal_reviewers",
2467 "server_side": True,
2468 "description": (
2469 "Request one or more reviewers on a merge proposal. "
2470 "Idempotent: requesting an already-assigned reviewer leaves their state unchanged. "
2471 "Requires write/admin access or repo ownership. "
2472 "Example: musehub_request_proposal_reviewers(repo_id='a3f2-...', "
2473 "proposal_id='sha256:...', reviewers=['alice', 'bob'])."
2474 ),
2475 "inputSchema": {
2476 "type": "object",
2477 "properties": {
2478 **_REPO_ID_PROP,
2479 "proposal_id": {
2480 "type": "string",
2481 "description": "sha256 genesis ID of the merge proposal.",
2482 },
2483 "reviewers": {
2484 "type": "array",
2485 "items": {"type": "string"},
2486 "description": "List of MSign handles to request as reviewers.",
2487 "minItems": 1,
2488 },
2489 },
2490 "required": ["repo_id", "proposal_id", "reviewers"],
2491 },
2492 },
2493 {
2494 "name": "musehub_remove_proposal_reviewer",
2495 "server_side": True,
2496 "description": (
2497 "Remove a pending review request from a merge proposal. "
2498 "Only 'pending' rows can be removed — submitted reviews are immutable. "
2499 "Requires write/admin access or repo ownership. "
2500 "Example: musehub_remove_proposal_reviewer(repo_id='a3f2-...', "
2501 "proposal_id='sha256:...', reviewer='alice')."
2502 ),
2503 "inputSchema": {
2504 "type": "object",
2505 "properties": {
2506 **_REPO_ID_PROP,
2507 "proposal_id": {
2508 "type": "string",
2509 "description": "sha256 genesis ID of the merge proposal.",
2510 },
2511 "reviewer": {
2512 "type": "string",
2513 "description": "MSign handle of the reviewer to remove.",
2514 },
2515 },
2516 "required": ["repo_id", "proposal_id", "reviewer"],
2517 },
2518 },
2519 {
2520 "name": "musehub_list_proposal_reviews",
2521 "server_side": True,
2522 "description": (
2523 "Return all reviews for a merge proposal, optionally filtered by state. "
2524 "States: 'pending', 'approved', 'changes_requested', 'dismissed'. "
2525 "Authentication required. "
2526 "Example: musehub_list_proposal_reviews(repo_id='sha256:...', proposal_id='sha256:...')."
2527 ),
2528 "inputSchema": {
2529 "type": "object",
2530 "properties": {
2531 **_REPO_ID_PROP,
2532 "proposal_id": {
2533 "type": "string",
2534 "description": "sha256 genesis ID of the merge proposal.",
2535 },
2536 "state": {
2537 "type": "string",
2538 "enum": ["pending", "approved", "changes_requested", "dismissed"],
2539 "description": "Optional state filter. Omit to return all reviews.",
2540 },
2541 },
2542 "required": ["repo_id", "proposal_id"],
2543 },
2544 },
2545 # ── Proposal simulations ──────────────────────────────────────────────────
2546 {
2547 "name": "musehub_get_proposal",
2548 "server_side": True,
2549 "description": (
2550 "Read a single merge proposal with full enrichment: blocked_by, blocks, is_blocked, "
2551 "and latest_simulations (conflict_scan / risk_projection / dependency_order results). "
2552 "Use this when you need the complete proposal picture including DAG position and simulation cache. "
2553 "Authentication required. "
2554 "Example: musehub_get_proposal(repo_id='sha256:...', proposal_id='sha256:...')."
2555 ),
2556 "inputSchema": {
2557 "type": "object",
2558 "properties": {
2559 **_REPO_ID_PROP,
2560 "proposal_id": {
2561 "type": "string",
2562 "description": "sha256 genesis ID of the merge proposal.",
2563 },
2564 },
2565 "required": ["repo_id", "proposal_id"],
2566 },
2567 },
2568 {
2569 "name": "musehub_run_proposal_simulation",
2570 "server_side": True,
2571 "description": (
2572 "Run a simulation for a merge proposal. Always recomputes — ignores the cache. "
2573 "Stores the result so musehub_get_proposal_simulation can return it without recomputing. "
2574 "simulation_type must be one of: 'conflict_scan', 'risk_projection', 'dependency_order'. "
2575 "conflict_scan: detects file-level conflicts between branches. "
2576 "risk_projection: projects multi-domain merge risk and returns a risk_band (low/medium/high). "
2577 "dependency_order: computes topological order of the proposal DAG; detects cycles. "
2578 "Check is_stale in the response to know if the from-branch has advanced. "
2579 "Example: musehub_run_proposal_simulation(repo_id='sha256:...', proposal_id='sha256:...', "
2580 "simulation_type='conflict_scan')."
2581 ),
2582 "inputSchema": {
2583 "type": "object",
2584 "properties": {
2585 **_REPO_ID_PROP,
2586 "proposal_id": {
2587 "type": "string",
2588 "description": "sha256 genesis ID of the merge proposal.",
2589 },
2590 "simulation_type": {
2591 "type": "string",
2592 "enum": ["conflict_scan", "risk_projection", "dependency_order"],
2593 "description": "Type of simulation to run.",
2594 },
2595 },
2596 "required": ["repo_id", "proposal_id", "simulation_type"],
2597 },
2598 },
2599 {
2600 "name": "musehub_get_proposal_simulation",
2601 "server_side": True,
2602 "description": (
2603 "Read the cached simulation result for a merge proposal without recomputing. "
2604 "Returns error_code='not_found' when no simulation has been run yet — "
2605 "call musehub_run_proposal_simulation first. "
2606 "Check is_stale: when True, the from-branch has advanced and results may be outdated. "
2607 "Example: musehub_get_proposal_simulation(repo_id='sha256:...', proposal_id='sha256:...', "
2608 "simulation_type='risk_projection')."
2609 ),
2610 "inputSchema": {
2611 "type": "object",
2612 "properties": {
2613 **_REPO_ID_PROP,
2614 "proposal_id": {
2615 "type": "string",
2616 "description": "sha256 genesis ID of the merge proposal.",
2617 },
2618 "simulation_type": {
2619 "type": "string",
2620 "enum": ["conflict_scan", "risk_projection", "dependency_order"],
2621 "description": "Type of simulation to read.",
2622 },
2623 },
2624 "required": ["repo_id", "proposal_id", "simulation_type"],
2625 },
2626 },
2627 {
2628 "name": "musehub_list_proposal_simulations",
2629 "server_side": True,
2630 "description": (
2631 "List all cached simulations for a merge proposal (up to 3 — one per type). "
2632 "Each entry includes is_stale so you can identify which simulations need refreshing. "
2633 "Returns an empty list when no simulations have been run. "
2634 "Example: musehub_list_proposal_simulations(repo_id='sha256:...', proposal_id='sha256:...')."
2635 ),
2636 "inputSchema": {
2637 "type": "object",
2638 "properties": {
2639 **_REPO_ID_PROP,
2640 "proposal_id": {
2641 "type": "string",
2642 "description": "sha256 genesis ID of the merge proposal.",
2643 },
2644 },
2645 "required": ["repo_id", "proposal_id"],
2646 },
2647 },
2648 # ── Repo management ───────────────────────────────────────────────────────
2649 {
2650 "name": "musehub_delete_repo",
2651 "server_side": True,
2652 "description": (
2653 "Soft-delete a MuseHub repository. Only the repo owner may delete. "
2654 "Data is retained for audit; subsequent reads return 404. "
2655 "Example: musehub_delete_repo(repo_id='a3f2-...')."
2656 ),
2657 "inputSchema": {
2658 "type": "object",
2659 "properties": {**_REPO_ID_PROP},
2660 "required": ["repo_id"],
2661 },
2662 },
2663 {
2664 "name": "musehub_update_repo",
2665 "server_side": True,
2666 "description": (
2667 "Partially update mutable settings for a repository. "
2668 "Only non-null fields are written. Caller must be owner or admin collaborator. "
2669 "'visibility' must be 'public' or 'private'. 'topics' replaces the full tag list. "
2670 "Example: musehub_update_repo(repo_id='a3f2-...', visibility='private', "
2671 "has_issues=true)."
2672 ),
2673 "inputSchema": {
2674 "type": "object",
2675 "properties": {
2676 **_REPO_ID_PROP,
2677 "name": {"type": "string", "description": "New repo name."},
2678 "description": {"type": "string", "description": "New markdown description."},
2679 "visibility": {
2680 "type": "string",
2681 "enum": ["public", "private"],
2682 "description": "'public' or 'private'.",
2683 },
2684 "default_branch": {"type": "string", "description": "New default branch name."},
2685 "has_issues": {"type": "boolean", "description": "Enable or disable the issues tracker."},
2686 "has_wiki": {"type": "boolean", "description": "Enable or disable the wiki."},
2687 "topics": {
2688 "type": "array",
2689 "items": {"type": "string"},
2690 "description": "Full replacement topic tag list.",
2691 },
2692 "homepage_url": {"type": "string", "description": "Project homepage URL."},
2693 "allow_merge_commit": {"type": "boolean", "description": "Allow merge commits on proposals."},
2694 "allow_squash_merge": {"type": "boolean", "description": "Allow squash merges on proposals."},
2695 "allow_rebase_merge": {"type": "boolean", "description": "Allow rebase merges on proposals."},
2696 "delete_branch_on_merge": {
2697 "type": "boolean",
2698 "description": "Auto-delete head branch after proposal merge.",
2699 },
2700 },
2701 "required": ["repo_id"],
2702 },
2703 },
2704 {
2705 "name": "musehub_transfer_repo_ownership",
2706 "server_side": True,
2707 "description": (
2708 "Transfer ownership of a repository to another user. "
2709 "Only the current owner may initiate — admin collaborators are not permitted. "
2710 "After transfer the calling user loses owner privileges immediately. "
2711 "Example: musehub_transfer_repo_ownership(repo_id='a3f2-...', new_owner='alice')."
2712 ),
2713 "inputSchema": {
2714 "type": "object",
2715 "properties": {
2716 **_REPO_ID_PROP,
2717 "new_owner": {
2718 "type": "string",
2719 "description": "MSign handle of the new owner.",
2720 },
2721 },
2722 "required": ["repo_id", "new_owner"],
2723 },
2724 },
2725 # ── Issue comment list ────────────────────────────────────────────────────
2726 {
2727 "name": "musehub_list_issue_comments",
2728 "server_side": True,
2729 "description": (
2730 "List all non-deleted comments on an issue, oldest first. "
2731 "Returns commentId, author, body, createdAt for each comment. "
2732 "Call musehub_read_issue first to confirm the issue exists. "
2733 "Example: musehub_list_issue_comments(repo_id='a3f2-...', issue_number=42)."
2734 ),
2735 "inputSchema": {
2736 "type": "object",
2737 "properties": {
2738 **_REPO_ID_PROP,
2739 "issue_number": {
2740 "type": "integer",
2741 "description": "Per-repo issue number.",
2742 },
2743 "limit": {
2744 "type": "integer",
2745 "description": "Max comments to return (default 100).",
2746 "default": 100,
2747 },
2748 "cursor": {
2749 "type": "string",
2750 "description": "Pagination cursor from a previous nextCursor value.",
2751 },
2752 },
2753 "required": ["issue_number"],
2754 },
2755 },
2756 # ── Release update ────────────────────────────────────────────────────────
2757 {
2758 "name": "musehub_update_release",
2759 "server_side": True,
2760 "description": (
2761 "Update a release's title, body, channel, or draft status. "
2762 "Only fields provided are changed — omitted fields are left unchanged. "
2763 "Requires repo owner or write/admin collaborator. "
2764 "Example: musehub_update_release(repo_id='a3f2-...', tag='v1.0.0', title='New title')."
2765 ),
2766 "inputSchema": {
2767 "type": "object",
2768 "properties": {
2769 **_REPO_ID_PROP,
2770 "tag": {"type": "string", "description": "Release tag to update (e.g. 'v1.0.0')."},
2771 "title": {"type": "string", "description": "New release title."},
2772 "body": {"type": "string", "description": "New release notes (Markdown)."},
2773 "channel": {
2774 "type": "string",
2775 "enum": ["stable", "beta", "alpha", "nightly"],
2776 "description": "Distribution channel.",
2777 },
2778 "is_draft": {"type": "boolean", "description": "Draft status."},
2779 },
2780 "required": ["tag"],
2781 },
2782 },
2783 # ── Release asset list ────────────────────────────────────────────────────
2784 {
2785 "name": "musehub_list_release_assets",
2786 "server_side": True,
2787 "description": (
2788 "List all assets attached to a release. "
2789 "Returns assetId, name, label, contentType, size, downloadUrl, downloadCount. "
2790 "Example: musehub_list_release_assets(repo_id='a3f2-...', tag='v1.0.0')."
2791 ),
2792 "inputSchema": {
2793 "type": "object",
2794 "properties": {
2795 **_REPO_ID_PROP,
2796 "tag": {"type": "string", "description": "Release tag."},
2797 "limit": {"type": "integer", "default": 50},
2798 "cursor": {"type": "string"},
2799 },
2800 "required": ["tag"],
2801 },
2802 },
2803 # ── User / profile ────────────────────────────────────────────────────────
2804 {
2805 "name": "musehub_read_user_profile",
2806 "server_side": True,
2807 "description": (
2808 "Fetch a user's public profile: bio, avatarUrl, repoCount, followerCount, followingCount, pinnedRepos. "
2809 "No auth required — profiles are publicly accessible. "
2810 "Example: musehub_read_user_profile(username='gabriel')."
2811 ),
2812 "inputSchema": {
2813 "type": "object",
2814 "properties": {
2815 "username": {"type": "string", "description": "MuseHub username."},
2816 },
2817 "required": ["username"],
2818 },
2819 },
2820 {
2821 "name": "musehub_update_user_profile",
2822 "server_side": True,
2823 "description": (
2824 "Update the authenticated user's profile. "
2825 "Accepts bio, avatarUrl, and pinnedRepoIds (list of repo sha256 genesis IDs). "
2826 "Caller must be the owner of the profile. "
2827 "Example: musehub_update_user_profile(username='gabriel', bio='Composer and engineer')."
2828 ),
2829 "inputSchema": {
2830 "type": "object",
2831 "properties": {
2832 "username": {"type": "string", "description": "Your MuseHub username."},
2833 "bio": {"type": "string", "description": "Profile bio text."},
2834 "avatar_url": {"type": "string", "description": "Avatar image URL."},
2835 "pinned_repo_ids": {
2836 "type": "array",
2837 "items": {"type": "string"},
2838 "description": "List of repo sha256 genesis IDs to pin (replaces all pinned repos).",
2839 },
2840 },
2841 "required": ["username"],
2842 },
2843 },
2844 # ── Profile manifest + attestations + MPay ────────────────────────────────
2845 {
2846 "name": "musehub_read_profile_manifest",
2847 "server_side": True,
2848 "description": (
2849 "Return the full archetype-aware profile manifest for a MuseHub identity. "
2850 "Includes identity_type, activity canvas (5 domains × 364-day grids), "
2851 "attestation badges, avax_address (human), agent_model + trust_chain (agent), "
2852 "org manifest (org), and MPay ledger totals. "
2853 "Example: musehub_read_profile_manifest(handle='gabriel')."
2854 ),
2855 "inputSchema": {
2856 "type": "object",
2857 "properties": {
2858 "handle": {"type": "string", "description": "MSign handle of the identity."},
2859 },
2860 "required": ["handle"],
2861 },
2862 },
2863 {
2864 "name": "musehub_issue_attestation",
2865 "server_side": True,
2866 "description": (
2867 "Issue a cryptographically-verified attestation about a MuseHub identity. "
2868 "The caller must supply an Ed25519 signature over "
2869 "ATTEST\\n{attester}\\n{subject}\\n{claim}\\n{issued_at_iso}. "
2870 "Idempotent — re-issuing the same attestation returns the existing record. "
2871 "Example: musehub_issue_attestation(attester='gabriel', subject='aria', "
2872 "claim='{\"type\":\"human\"}', issued_at_iso='2026-04-21T12:00:00+00:00', "
2873 "signature='ed25519:...', attester_public_key='ed25519:...')."
2874 ),
2875 "inputSchema": {
2876 "type": "object",
2877 "properties": {
2878 "attester": {"type": "string", "description": "Handle of the attesting identity."},
2879 "subject": {"type": "string", "description": "Handle of the attested identity."},
2880 "claim": {"type": "string", "description": "JSON claim payload."},
2881 "issued_at_iso": {"type": "string", "description": "ISO-8601 timestamp of issuance."},
2882 "signature": {"type": "string", "description": "Ed25519 signature; 'ed25519:<base64url>'."},
2883 "attester_public_key": {"type": "string", "description": "Attester public key; 'ed25519:<base64url>'."},
2884 },
2885 "required": ["attester", "subject", "claim", "issued_at_iso", "signature", "attester_public_key"],
2886 },
2887 },
2888 {
2889 "name": "musehub_revoke_attestation",
2890 "server_side": True,
2891 "description": (
2892 "Revoke an attestation by ID. "
2893 "Only the original attester may revoke their own attestation. "
2894 "Example: musehub_revoke_attestation(attestation_id='sha256:...', revoker='gabriel')."
2895 ),
2896 "inputSchema": {
2897 "type": "object",
2898 "properties": {
2899 "attestation_id": {"type": "string", "description": "sha256:-prefixed attestation ID."},
2900 "revoker": {"type": "string", "description": "MSign handle of the caller (must be original attester)."},
2901 },
2902 "required": ["attestation_id", "revoker"],
2903 },
2904 },
2905 {
2906 "name": "musehub_list_attestations",
2907 "server_side": True,
2908 "description": (
2909 "List all attestations about a subject identity. "
2910 "Set include_revoked=true to include revoked attestations. "
2911 "Example: musehub_list_attestations(subject='aria')."
2912 ),
2913 "inputSchema": {
2914 "type": "object",
2915 "properties": {
2916 "subject": {"type": "string", "description": "Handle of the identity whose attestations to list."},
2917 "include_revoked": {"type": "boolean", "default": False, "description": "Include revoked attestations."},
2918 },
2919 "required": ["subject"],
2920 },
2921 },
2922 {
2923 "name": "musehub_record_mpay_claim",
2924 "server_side": True,
2925 "description": (
2926 "Record a verified MPay micropayment claim. "
2927 "The caller must supply an Ed25519 signature over "
2928 "MPAY\\n{sender}\\n{recipient}\\n{amount_nano}\\n{nonce_hex}. "
2929 "Idempotent on nonce_hex. amount_nano must be > 0 and sender != recipient. "
2930 "Example: musehub_record_mpay_claim(sender='gabriel', recipient='aria', "
2931 "amount_nano=500000, nonce_hex='bb...bb', signature='ed25519:...', "
2932 "sender_public_key='ed25519:...')."
2933 ),
2934 "inputSchema": {
2935 "type": "object",
2936 "properties": {
2937 "sender": {"type": "string", "description": "Handle of the payer."},
2938 "recipient": {"type": "string", "description": "Handle of the payee."},
2939 "amount_nano": {"type": "integer", "description": "Payment in nanoMUSE (> 0)."},
2940 "nonce_hex": {"type": "string", "description": "64-char hex nonce for replay protection."},
2941 "signature": {"type": "string", "description": "Ed25519 signature; 'ed25519:<base64url>'."},
2942 "sender_public_key": {"type": "string", "description": "Sender public key; 'ed25519:<base64url>'."},
2943 "memo": {"type": "string", "description": "Optional payment memo."},
2944 },
2945 "required": ["sender", "recipient", "amount_nano", "nonce_hex", "signature", "sender_public_key"],
2946 },
2947 },
2948 {
2949 "name": "musehub_get_mpay_ledger",
2950 "server_side": True,
2951 "description": (
2952 "Return the MPay ledger for a handle — sent and received claims with totals. "
2953 "limit is clamped to 500. "
2954 "Example: musehub_get_mpay_ledger(handle='gabriel', limit=50)."
2955 ),
2956 "inputSchema": {
2957 "type": "object",
2958 "properties": {
2959 "handle": {"type": "string", "description": "MSign handle whose ledger to return."},
2960 "limit": {"type": "integer", "default": 100, "description": "Max claims per direction (clamped to 500)."},
2961 },
2962 "required": ["handle"],
2963 },
2964 },
2965 # ── Topics ────────────────────────────────────────────────────────────────
2966 {
2967 "name": "musehub_list_topics",
2968 "server_side": True,
2969 "description": (
2970 "Browse all topics registered on MuseHub with their repo counts. "
2971 "Filter by substring query. "
2972 "Example: musehub_list_topics(query='jazz')."
2973 ),
2974 "inputSchema": {
2975 "type": "object",
2976 "properties": {
2977 "query": {"type": "string", "description": "Substring filter on topic names."},
2978 "limit": {"type": "integer", "default": 50},
2979 },
2980 "required": [],
2981 },
2982 },
2983 {
2984 "name": "musehub_set_repo_topics",
2985 "server_side": True,
2986 "description": (
2987 "Replace all topics for a repository (max 20). "
2988 "Pass an empty list to clear all topics. "
2989 "Requires repo owner or write/admin collaborator. "
2990 "Example: musehub_set_repo_topics(repo_id='a3f2-...', topics=['jazz', 'midi', 'generative'])."
2991 ),
2992 "inputSchema": {
2993 "type": "object",
2994 "properties": {
2995 **_REPO_ID_PROP,
2996 "topics": {
2997 "type": "array",
2998 "items": {"type": "string"},
2999 "description": "Full topic list to apply (replaces existing topics).",
3000 "maxItems": 20,
3001 },
3002 },
3003 "required": ["topics"],
3004 },
3005 },
3006 # ── Webhook deliveries ────────────────────────────────────────────────────
3007 {
3008 "name": "musehub_list_webhook_deliveries",
3009 "server_side": True,
3010 "description": (
3011 "List recent delivery attempts for a webhook. "
3012 "Returns deliveryId, event, status, statusCode, duration, createdAt per delivery. "
3013 "Use deliveryId with musehub_redeliver_webhook to retry a failed delivery. "
3014 "Example: musehub_list_webhook_deliveries(repo_id='sha256:...', webhook_id='sha256:...')."
3015 ),
3016 "inputSchema": {
3017 "type": "object",
3018 "properties": {
3019 **_REPO_ID_PROP,
3020 "webhook_id": {"type": "string", "description": "ID of the webhook."},
3021 "limit": {"type": "integer", "default": 20},
3022 "cursor": {"type": "string"},
3023 },
3024 "required": ["webhook_id"],
3025 },
3026 },
3027 {
3028 "name": "musehub_redeliver_webhook",
3029 "server_side": True,
3030 "description": (
3031 "Re-send a previously attempted webhook delivery. "
3032 "Requires the webhook_id and the delivery_id to retry. "
3033 "Example: musehub_redeliver_webhook(repo_id='sha256:...', webhook_id='sha256:...', delivery_id='sha256:...')."
3034 ),
3035 "inputSchema": {
3036 "type": "object",
3037 "properties": {
3038 **_REPO_ID_PROP,
3039 "webhook_id": {"type": "string", "description": "ID of the webhook."},
3040 "delivery_id": {"type": "string", "description": "ID of the delivery to redeliver."},
3041 },
3042 "required": ["webhook_id", "delivery_id"],
3043 },
3044 },
3045 # ── Mist write tools ──────────────────────────────────────────────────────
3046 {
3047 "name": "muse_mist_create",
3048 "server_side": True,
3049 "description": (
3050 "Publish a new content-addressed, cryptographically signed Mist. "
3051 "The mist_id is derived automatically from the artifact content (SHA-256 → base-58, 12 chars) — "
3052 "identical content from any owner produces the same mist_id. "
3053 "Artifact type and language are detected from filename and content. "
3054 "Symbol anchors are extracted automatically for Python/JS/TS code. "
3055 "Rate-limited to 20 creates per minute per handle. "
3056 "Example: muse_mist_create(filename='validate.py', content='def validate(x): ...', "
3057 "title='Handle validation primitive', tags=['security', 'utils'])"
3058 ),
3059 "inputSchema": {
3060 "type": "object",
3061 "properties": {
3062 "filename": {
3063 "type": "string",
3064 "description": "Original filename (used for type detection). Must not be empty.",
3065 },
3066 "content": {
3067 "type": "string",
3068 "description": "Artifact content (UTF-8 text, or base64 for binary). Must not be empty.",
3069 },
3070 "title": {
3071 "type": "string",
3072 "description": "Optional human-readable title.",
3073 "default": "",
3074 },
3075 "description": {
3076 "type": "string",
3077 "description": "Optional Markdown description.",
3078 "default": "",
3079 },
3080 "visibility": {
3081 "type": "string",
3082 "enum": ["public", "secret"],
3083 "description": "Visibility: 'public' (default) or 'secret'.",
3084 "default": "public",
3085 },
3086 "tags": {
3087 "type": "array",
3088 "items": {"type": "string"},
3089 "description": "Freeform tags (max 10).",
3090 },
3091 "agent_id": {
3092 "type": "string",
3093 "description": "Agent identifier for AI-authored mists.",
3094 "default": "",
3095 },
3096 "model_id": {
3097 "type": "string",
3098 "description": "Model identifier for AI provenance.",
3099 "default": "",
3100 },
3101 "gpg_signature": {
3102 "type": "string",
3103 "description": "ASCII-armoured Ed25519 signature (optional).",
3104 },
3105 },
3106 "required": ["filename", "content"],
3107 },
3108 },
3109 {
3110 "name": "muse_mist_update",
3111 "server_side": True,
3112 "description": (
3113 "Update a mist's metadata or replace its content. "
3114 "Only the mist owner may update. Fields that are omitted are left unchanged "
3115 "(partial update). Replacing content increments the version counter. "
3116 "Example: muse_mist_update(mist_id='aB3xKq9dPwNm', title='Better title', visibility='secret')"
3117 ),
3118 "inputSchema": {
3119 "type": "object",
3120 "properties": {
3121 "mist_id": {
3122 "type": "string",
3123 "description": "12-character mist ID to update.",
3124 },
3125 "title": {
3126 "type": "string",
3127 "description": "New title (omit to leave unchanged).",
3128 },
3129 "description": {
3130 "type": "string",
3131 "description": "New Markdown description (omit to leave unchanged).",
3132 },
3133 "visibility": {
3134 "type": "string",
3135 "enum": ["public", "secret"],
3136 "description": "New visibility (omit to leave unchanged).",
3137 },
3138 "tags": {
3139 "type": "array",
3140 "items": {"type": "string"},
3141 "description": "New tag list (omit to leave unchanged).",
3142 },
3143 "content": {
3144 "type": "string",
3145 "description": "New artifact content (omit to leave unchanged). Increments version.",
3146 },
3147 },
3148 "required": ["mist_id"],
3149 },
3150 },
3151 {
3152 "name": "muse_mist_fork",
3153 "server_side": True,
3154 "description": (
3155 "Fork an existing Mist into the authenticated caller's namespace. "
3156 "The fork inherits the source content and metadata but gets a new mist_id and owner. "
3157 "The source's fork_count is incremented. Fork depth is enforced (max 5 levels). "
3158 "Rate-limited to 30 forks per minute per handle. "
3159 "Example: muse_mist_fork(mist_id='aB3xKq9dPwNm')"
3160 ),
3161 "inputSchema": {
3162 "type": "object",
3163 "properties": {
3164 "mist_id": {
3165 "type": "string",
3166 "description": "mist_id of the Mist to fork.",
3167 },
3168 },
3169 "required": ["mist_id"],
3170 },
3171 },
3172 {
3173 "name": "muse_mist_delete",
3174 "server_side": True,
3175 "description": (
3176 "Permanently delete a Mist and its underlying Muse repository. "
3177 "Only the mist owner may delete. The backing repo (commits, snapshots) is removed via cascade. "
3178 "This action is irreversible. "
3179 "Example: muse_mist_delete(mist_id='aB3xKq9dPwNm')"
3180 ),
3181 "inputSchema": {
3182 "type": "object",
3183 "properties": {
3184 "mist_id": {
3185 "type": "string",
3186 "description": "12-character mist ID to delete.",
3187 },
3188 },
3189 "required": ["mist_id"],
3190 },
3191 },
3192 ]
3193
3194
3195 # ── CI / Selective CI tools ────────────────────────────────────────────────────
3196
3197
3198 MUSEHUB_WORKSPACE_TOOLS: list[MCPToolDef] = [
3199 {
3200 "name": "musehub_read_cross_repo_impact",
3201 "server_side": True,
3202 "description": (
3203 "Compute the blast radius of a symbol across all repos in the owner's workspace. "
3204 "Returns local co-changed symbols (within the source repo) AND symbols from other "
3205 "repos that were modified during overlapping time windows — surfacing cross-repo "
3206 "coupling heuristically from shared commit timestamps. "
3207 "Example: musehub_read_cross_repo_impact(repo_id='a3f2-...', "
3208 "address='musehub.services.musehub_wire.wire_push')."
3209 ),
3210 "inputSchema": {
3211 "type": "object",
3212 "properties": {
3213 **_REPO_ID_PROP,
3214 "address": {
3215 "type": "string",
3216 "description": "Fully-qualified symbol address to analyse.",
3217 },
3218 },
3219 "required": ["repo_id", "address"],
3220 },
3221 },
3222 {
3223 "name": "musehub_read_workspace_intel",
3224 "server_side": True,
3225 "description": (
3226 "Return a health overview for all repos in an owner's workspace. "
3227 "Includes per-repo symbol counts, health scores, and the top 10 "
3228 "cross-repo blast-risk symbols. "
3229 "Example: musehub_read_workspace_intel(owner='gabriel')."
3230 ),
3231 "inputSchema": {
3232 "type": "object",
3233 "properties": {
3234 "owner": {
3235 "type": "string",
3236 "description": "Owner username whose workspace to inspect.",
3237 },
3238 },
3239 "required": ["owner"],
3240 },
3241 },
3242 ]
3243
3244
3245
3246 # ── Elicitation-powered tools (MCP 2025-11-25) ────────────────────────────────
3247
3248
3249 MUSEHUB_ELICITATION_TOOLS: list[MCPToolDef] = [
3250 {
3251 "name": "musehub_review_proposal_interactive",
3252 "server_side": True,
3253 "description": (
3254 "Review a merge proposal interactively by first eliciting the reviewer's focus "
3255 "dimension and depth (quick / standard / thorough). Returns a deep structured "
3256 "review targeting the chosen dimensions with per-dimension divergence scores. "
3257 "Currently implements the MIDI domain's dimension vocabulary "
3258 "(melodic / harmonic / rhythmic / structural / dynamic / all). "
3259 "For repos on other domains, call musehub_read_domain first to learn the correct "
3260 "dimension names, then pass dimension= directly to skip elicitation. "
3261 "For a non-interactive review, use musehub_read_proposal + musehub_create_proposal_review instead. "
3262 "ELICITATION BEHAVIOUR: requires an active MCP session (Mcp-Session-Id header). "
3263 "Without a session, pass dimension= and depth= directly to skip elicitation. "
3264 "Example: musehub_review_proposal_interactive(repo_id='sha256:...', proposal_id='sha256:...') "
3265 "or musehub_review_proposal_interactive(repo_id='sha256:...', proposal_id='sha256:...', "
3266 "dimension='harmonic', depth='thorough')."
3267 ),
3268 "inputSchema": {
3269 "type": "object",
3270 "properties": {
3271 **_REPO_ID_PROP,
3272 "proposal_id": {
3273 "type": "string",
3274 "description": "sha256 genesis ID of the merge proposal to review.",
3275 },
3276 "dimension": {
3277 "type": "string",
3278 "description": (
3279 "Focus dimension to bypass elicitation. "
3280 "One of: melodic, harmonic, rhythmic, structural, dynamic, all."
3281 ),
3282 "enum": ["melodic", "harmonic", "rhythmic", "structural", "dynamic", "all"],
3283 },
3284 "depth": {
3285 "type": "string",
3286 "description": "Review depth to bypass elicitation: quick, standard, or thorough.",
3287 "enum": ["quick", "standard", "thorough"],
3288 },
3289 },
3290 "required": ["proposal_id"],
3291 },
3292 },
3293 {
3294 "name": "musehub_create_release_interactive",
3295 "server_side": True,
3296 "description": (
3297 "Create a release interactively in two chained elicitation steps: "
3298 "(1) form-mode: collects tag, title, release notes, changelog highlight, "
3299 "and pre-release flag; "
3300 "(2) URL-mode (optional): offers streaming platform OAuth connection. "
3301 "Creates the release then returns distribution guidance for connected platforms. "
3302 "For a non-interactive release, use musehub_create_release with explicit arguments. "
3303 "ELICITATION BEHAVIOUR: requires an active MCP session with elicitation capability. "
3304 "Without a session, pass tag=, title=, notes= directly to bypass elicitation and "
3305 "create the release immediately (identical to musehub_create_release). "
3306 "Example: musehub_create_release_interactive(repo_id='a3f2-...') "
3307 "or musehub_create_release_interactive(repo_id='a3f2-...', "
3308 "tag='v1.0.0', title='First release', notes='Initial commit')."
3309 ),
3310 "inputSchema": {
3311 "type": "object",
3312 "properties": {
3313 **_REPO_ID_PROP,
3314 "tag": {
3315 "type": "string",
3316 "description": "Semver tag string (e.g. 'v1.0.0'). Elicited if omitted.",
3317 },
3318 "title": {
3319 "type": "string",
3320 "description": "Release title. Elicited if omitted.",
3321 },
3322 "notes": {
3323 "type": "string",
3324 "description": "Release notes markdown. Elicited if omitted.",
3325 },
3326 "pre_release": {
3327 "type": "boolean",
3328 "description": "Whether this is a pre-release. Default: false.",
3329 "default": False,
3330 },
3331 },
3332 "required": [],
3333 },
3334 },
3335 ]
3336
3337
3338 # ── Coordination tools ────────────────────────────────────────────────────────
3339
3340 MUSEHUB_COORD_READ_TOOLS: list[MCPToolDef] = [
3341 {
3342 "name": "musehub_read_coord_swarm",
3343 "server_side": True,
3344 "description": (
3345 "MULTI-AGENT ENTRY POINT: Call this first when joining or starting a multi-agent session.\n\n"
3346 "Returns: active agents, symbols each agent has reserved, and task queue depths per queue.\n"
3347 "Use this to:\n"
3348 " - Understand who is working and what is already claimed\n"
3349 " - Decide which task queue to join\n"
3350 " - Detect if the swarm is idle (no active agents, no pending tasks)\n\n"
3351 "Follow-up tools:\n"
3352 " - musehub_read_coord_conflicts(addresses=[...]) before reserving symbols\n"
3353 " - musehub_list_coord_tasks(status='pending') to find claimable work\n"
3354 " - musehub_create_coord_reservation(...) to lock symbols before editing"
3355 ),
3356 "inputSchema": {
3357 "type": "object",
3358 "properties": _REPO_ID_PROP,
3359 "required": [],
3360 },
3361 },
3362 {
3363 "name": "musehub_list_coord_reservations",
3364 "server_side": True,
3365 "description": (
3366 "List active symbol reservations (locked symbols) in the repository.\n\n"
3367 "A reservation means another agent is actively editing that symbol — do not edit it.\n"
3368 "Use agent_id filter to see what a specific agent has locked.\n"
3369 "For a quick conflict check before reserving, prefer musehub_read_coord_conflicts(addresses=[...]) — "
3370 "it returns only the subset that conflicts rather than the full list.\n\n"
3371 "Part of the SYMBOL EDITING PROTOCOL:\n"
3372 " read_coord_conflicts → create_coord_reservation → [edit] → delete_coord_reservation"
3373 ),
3374 "inputSchema": {
3375 "type": "object",
3376 "properties": {
3377 **_REPO_ID_PROP,
3378 "agent_id": {
3379 "type": "string",
3380 "description": "Filter reservations by this agent ID.",
3381 },
3382 "include_expired": {
3383 "type": "boolean",
3384 "description": "Include expired (but not released) reservations. Default false.",
3385 "default": False,
3386 },
3387 "limit": {
3388 "type": "integer",
3389 "description": "Max reservations to return (default 200, max 1000).",
3390 "default": 200,
3391 "minimum": 1,
3392 "maximum": 1000,
3393 },
3394 },
3395 "required": [],
3396 },
3397 },
3398 {
3399 "name": "musehub_read_coord_conflicts",
3400 "server_side": True,
3401 "description": (
3402 "SYMBOL EDITING PROTOCOL — Step 1 of 3: Check if target symbols are free before reserving.\n\n"
3403 "Returns has_conflicts=false when all addresses are free (safe to proceed to reserve).\n"
3404 "Returns has_conflicts=true with a conflicts list when symbols are locked by other agents.\n\n"
3405 "If conflicts exist:\n"
3406 " - Check conflicts[].expires_at — wait until expiry if close\n"
3407 " - Contact the owning agent via the task queue or choose different symbols\n"
3408 " - Do NOT proceed to edit without a reservation\n\n"
3409 "Step 1: musehub_read_coord_conflicts(addresses=[...])\n"
3410 "Step 2: musehub_create_coord_reservation(addresses=[...], agent_id='...') if no conflicts\n"
3411 "Step 3: musehub_delete_coord_reservation(reservation_id='...') after committing your changes"
3412 ),
3413 "inputSchema": {
3414 "type": "object",
3415 "properties": {
3416 **_REPO_ID_PROP,
3417 "addresses": {
3418 "type": "array",
3419 "items": {"type": "string"},
3420 "description": "Symbol addresses to check (e.g. 'file.py::ClassName').",
3421 "minItems": 1,
3422 "maxItems": 100,
3423 },
3424 },
3425 "required": ["addresses"],
3426 },
3427 },
3428 {
3429 "name": "musehub_list_coord_tasks",
3430 "server_side": True,
3431 "description": (
3432 "TASK QUEUE PROTOCOL — Worker step 1: Find pending tasks to claim.\n\n"
3433 "Tasks flow: pending → claimed → completed | failed\n\n"
3434 "Typical worker loop:\n"
3435 " 1. musehub_list_coord_tasks(status='pending', queue='my-queue') — find work\n"
3436 " 2. musehub_claim_coord_task(task_id='...', agent_id='me') — atomically claim one\n"
3437 " 3. [do the work described in task.payload]\n"
3438 " 4. musehub_complete_coord_task(...) or musehub_fail_coord_task(...)\n\n"
3439 "Filter by queue to specialise (e.g. queue='analysis' for symbol-analysis agents).\n"
3440 "Sort order: highest priority first, then created_at ascending."
3441 ),
3442 "inputSchema": {
3443 "type": "object",
3444 "properties": {
3445 **_REPO_ID_PROP,
3446 "queue": {
3447 "type": "string",
3448 "description": "Filter by queue name (e.g. 'tasks', 'analysis'). Omit for all queues.",
3449 },
3450 "status": {
3451 "type": "string",
3452 "enum": ["pending", "claimed", "completed", "failed"],
3453 "description": "Filter by task status. Omit to return all statuses.",
3454 },
3455 "limit": {
3456 "type": "integer",
3457 "description": "Max tasks to return (default 100, max 500).",
3458 "default": 100,
3459 "minimum": 1,
3460 "maximum": 500,
3461 },
3462 },
3463 "required": [],
3464 },
3465 },
3466 ]
3467
3468 MUSEHUB_COORD_WRITE_TOOLS: list[MCPToolDef] = [
3469 {
3470 "name": "musehub_claim_coord_task",
3471 "server_side": True,
3472 "description": (
3473 "TASK QUEUE PROTOCOL — Worker step 2: Atomically claim a pending task.\n\n"
3474 "Atomic: only one agent can claim a given task even under concurrent calls.\n"
3475 "Returns the task record with status='claimed' on success.\n"
3476 "Returns task_not_found if the task is already claimed, completed, or doesn't exist — "
3477 "call musehub_list_coord_tasks(status='pending') to find currently claimable tasks.\n\n"
3478 "After claiming:\n"
3479 " - Execute the work described in the task payload\n"
3480 " - Call musehub_complete_coord_task when done\n"
3481 " - Call musehub_fail_coord_task if an unrecoverable error occurs\n"
3482 " - Do not abandon a claimed task — it blocks the queue until TTL expiry"
3483 ),
3484 "inputSchema": {
3485 "type": "object",
3486 "properties": {
3487 **_REPO_ID_PROP,
3488 "task_id": {
3489 "type": "string",
3490 "description": "ID of the task to claim.",
3491 },
3492 "agent_id": {
3493 "type": "string",
3494 "description": "Identifier of the claiming agent (e.g. 'agentception-worker-42').",
3495 },
3496 },
3497 "required": ["task_id", "agent_id"],
3498 },
3499 },
3500 {
3501 "name": "musehub_complete_coord_task",
3502 "server_side": True,
3503 "description": (
3504 "TASK QUEUE PROTOCOL — Worker step 3a: Mark your claimed task as completed.\n\n"
3505 "The agent_id must match the agent that claimed the task.\n"
3506 "Attach a result payload with any output the orchestrator needs "
3507 "(e.g. commit_id, analysis summary, generated file paths).\n"
3508 "Returns task_not_found if the task is not claimed by this agent."
3509 ),
3510 "inputSchema": {
3511 "type": "object",
3512 "properties": {
3513 **_REPO_ID_PROP,
3514 "task_id": {
3515 "type": "string",
3516 "description": "ID of the claimed task to complete.",
3517 },
3518 "agent_id": {
3519 "type": "string",
3520 "description": "Agent ID that claimed this task.",
3521 },
3522 "result": {
3523 "type": "object",
3524 "description": "Optional JSON result payload to attach to the completed task.",
3525 },
3526 },
3527 "required": ["task_id", "agent_id"],
3528 },
3529 },
3530 {
3531 "name": "musehub_fail_coord_task",
3532 "server_side": True,
3533 "description": (
3534 "TASK QUEUE PROTOCOL — Worker step 3b: Mark your claimed task as failed.\n\n"
3535 "Use this when the task cannot be completed (error, conflict, missing dependency).\n"
3536 "Include a reason so the orchestrator can diagnose and re-enqueue or escalate.\n"
3537 "The agent_id must match the agent that claimed the task.\n"
3538 "Returns task_not_found if the task is not claimed by this agent."
3539 ),
3540 "inputSchema": {
3541 "type": "object",
3542 "properties": {
3543 **_REPO_ID_PROP,
3544 "task_id": {
3545 "type": "string",
3546 "description": "ID of the claimed task to fail.",
3547 },
3548 "agent_id": {
3549 "type": "string",
3550 "description": "Agent ID that claimed this task.",
3551 },
3552 "reason": {
3553 "type": "string",
3554 "description": "Human-readable reason the task failed.",
3555 "default": "",
3556 },
3557 },
3558 "required": ["task_id", "agent_id"],
3559 },
3560 },
3561 {
3562 "name": "musehub_extend_coord_reservation",
3563 "server_side": True,
3564 "description": (
3565 "SYMBOL EDITING PROTOCOL — Optional: Extend a reservation's TTL mid-work.\n\n"
3566 "Call this if your editing work will exceed the original TTL before you can release.\n"
3567 "Extend by 30–3600 seconds (default 300 / 5 min).\n"
3568 "Better to extend proactively than to let the reservation expire — "
3569 "an expired reservation allows other agents to reserve the same symbols.\n\n"
3570 "Use the reservation_id returned by musehub_create_coord_reservation."
3571 ),
3572 "inputSchema": {
3573 "type": "object",
3574 "properties": {
3575 **_REPO_ID_PROP,
3576 "reservation_id": {
3577 "type": "string",
3578 "description": "ID of the reservation to extend.",
3579 },
3580 "extend_by_s": {
3581 "type": "integer",
3582 "description": "Seconds to add to the current expiry (30–3600, default 300).",
3583 "default": 300,
3584 "minimum": 30,
3585 "maximum": 3600,
3586 },
3587 },
3588 "required": ["reservation_id"],
3589 },
3590 },
3591 {
3592 "name": "musehub_create_coord_reservation",
3593 "server_side": True,
3594 "description": (
3595 "SYMBOL EDITING PROTOCOL — Step 2 of 3: Reserve symbol addresses before editing them.\n\n"
3596 "Step 1: musehub_read_coord_conflicts(addresses=[...]) — verify symbols are free.\n"
3597 "Step 2: musehub_create_coord_reservation(addresses=[...], agent_id='...', ttl_s=300) — lock them.\n"
3598 "Step 3: Do your work, then musehub_delete_coord_reservation(reservation_id='...') when done.\n\n"
3599 "If another agent has the symbols reserved, read_coord_conflicts will tell you — "
3600 "wait and retry rather than proceeding without a reservation.\n"
3601 "One reservation_id covers all addresses in the batch.\n"
3602 "Default TTL is 300s (5 min). Extend with musehub_extend_coord_reservation if work takes longer."
3603 ),
3604 "inputSchema": {
3605 "type": "object",
3606 "properties": {
3607 **_REPO_ID_PROP,
3608 "addresses": {
3609 "type": "array",
3610 "items": {"type": "string"},
3611 "description": "Symbol addresses to reserve (e.g. 'src/engine.py::AudioEngine').",
3612 "minItems": 1,
3613 "maxItems": 50,
3614 },
3615 "agent_id": {
3616 "type": "string",
3617 "description": "Identifier of the reserving agent (e.g. 'agentception-worker-42').",
3618 },
3619 "ttl_s": {
3620 "type": "integer",
3621 "description": "Reservation TTL in seconds (30–3600, default 300).",
3622 "default": 300,
3623 "minimum": 30,
3624 "maximum": 3600,
3625 },
3626 },
3627 "required": ["addresses", "agent_id"],
3628 },
3629 },
3630 {
3631 "name": "musehub_delete_coord_reservation",
3632 "server_side": True,
3633 "description": (
3634 "SYMBOL EDITING PROTOCOL — Step 3 of 3: Release a reservation when done editing.\n\n"
3635 "Always release after your work is committed so other agents can proceed.\n"
3636 "If you crash or forget, the TTL will expire the reservation automatically.\n"
3637 "Use the reservation_id returned by musehub_create_coord_reservation."
3638 ),
3639 "inputSchema": {
3640 "type": "object",
3641 "properties": {
3642 **_REPO_ID_PROP,
3643 "reservation_id": {
3644 "type": "string",
3645 "description": "ID returned by musehub_create_coord_reservation.",
3646 },
3647 "agent_id": {
3648 "type": "string",
3649 "description": "Agent ID that created the reservation.",
3650 },
3651 },
3652 "required": ["reservation_id", "agent_id"],
3653 },
3654 },
3655 {
3656 "name": "musehub_enqueue_coord_task",
3657 "server_side": True,
3658 "description": (
3659 "TASK QUEUE PROTOCOL — Orchestrator step: Enqueue a task for worker agents to claim.\n\n"
3660 "Full task lifecycle:\n"
3661 " Orchestrator: musehub_enqueue_coord_task(queue='...', payload={...}, agent_id='orchestrator') → task_id\n"
3662 " Worker: musehub_list_coord_tasks(status='pending') → find claimable tasks\n"
3663 " Worker: musehub_claim_coord_task(task_id='...', agent_id='worker-N') → atomic claim\n"
3664 " Worker: [do the work]\n"
3665 " Worker: musehub_complete_coord_task(task_id='...', agent_id='worker-N', result={...})\n"
3666 " or musehub_fail_coord_task(task_id='...', agent_id='worker-N', reason='...')\n\n"
3667 "Priority 0–100 (higher = processed first). Default 50.\n"
3668 "Use depends_on to declare task_ids this task should wait for."
3669 ),
3670 "inputSchema": {
3671 "type": "object",
3672 "properties": {
3673 **_REPO_ID_PROP,
3674 "queue": {
3675 "type": "string",
3676 "description": "Queue name (e.g. 'tasks', 'analysis', 'review').",
3677 },
3678 "payload": {
3679 "type": "object",
3680 "description": "Arbitrary JSON payload describing the task for the worker.",
3681 },
3682 "agent_id": {
3683 "type": "string",
3684 "description": "Agent creating this task (e.g. 'orchestrator', 'agentception-main').",
3685 },
3686 "priority": {
3687 "type": "integer",
3688 "description": "Task priority 0–100. Higher is processed first. Default 50.",
3689 "default": 50,
3690 "minimum": 0,
3691 "maximum": 100,
3692 },
3693 "depends_on": {
3694 "type": "array",
3695 "items": {"type": "string"},
3696 "description": "task_ids this task depends on (informational, not enforced by server).",
3697 },
3698 },
3699 "required": ["queue", "payload", "agent_id"],
3700 },
3701 },
3702 ]
3703
3704
3705 # ── Combined catalogue ────────────────────────────────────────────────────────
3706
3707 MUSEHUB_TOOLS: list[MCPToolDef] = (
3708 MUSEHUB_READ_TOOLS + MUSEHUB_COORD_READ_TOOLS
3709 + MUSEHUB_WRITE_TOOLS + MUSEHUB_COORD_WRITE_TOOLS
3710 + MUSEHUB_WORKSPACE_TOOLS + MUSEHUB_ELICITATION_TOOLS
3711 )
3712
3713 MUSEHUB_TOOL_NAMES: set[str] = {t["name"] for t in MUSEHUB_TOOLS}
3714 """Set of all musehub_* and muse_* tool names — used by the MCP dispatcher to route calls."""
3715
3716 MUSEHUB_WRITE_TOOL_NAMES: set[str] = {
3717 t["name"] for t in (
3718 MUSEHUB_WRITE_TOOLS + MUSEHUB_COORD_WRITE_TOOLS + MUSEHUB_ELICITATION_TOOLS
3719 )
3720 }
3721 """Set of write/interactive tool names — requires authentication."""
3722
3723 MUSEHUB_ELICITATION_TOOL_NAMES: set[str] = {t["name"] for t in MUSEHUB_ELICITATION_TOOLS}
3724 """Set of elicitation-powered tool names — requires active session."""
File History 3 commits
sha256:94ef169c149a452bff7c604ded8b280b19bd477c2dabcb56972780b0b784c7aa Merge 'fix/assignee-sigil-inline' into 'dev' — proposal: As… Human 2 days ago
sha256:6b1949fc2797ca4c1936a637a4cbfec828ef56cf52398a2e74ca3c4f494e728f fix: use wire_bytes not mpack_bytes_raw in compute_object_b… Sonnet 4.6 patch 10 days ago
sha256:4aed3d8601c8dd3ed37074de35f11f4a9699a0a4b99d43727048fd3f8e6fd13d chore: doc sweep, ignore wrangler build state, misc fixes Sonnet 4.6 minor 13 days ago