feat(issues): S2 MCP tools — system CRUD + issue/system wiring
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 50s
CI & Build / Build & push image (push) Successful in 1m3s

Second slice of Issues + Systems (spec #825).

New mcp/tools/systems.py: create_system, list_systems, get_system (records
split into issues/tasks/notes), update_system (incl. archive via status),
list_system_records (kind/open_only filters), delete_system. Registered in
register_all; read tools (get_system, list_systems, list_system_records) added
to the read-only-key allowlist (write tools default-deny).

create_task/update_task: kind now accepts 'issue'; new system_ids (set-semantics
associations) and arose_from_id (provenance, 0=unchanged/-1=clear) args.
create_note/update_note: new system_ids arg (notes associate with systems too).
services/notes.create_note: arose_from_id passthrough (update_note already
handles it via setattr).

Tests: MCP system tools + create_task issue-wiring (kind/provenance/systems),
service layer mocked.

Refs plan 825 (S2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-13 23:07:37 -04:00
parent b91c447b0b
commit 85e0501705
7 changed files with 291 additions and 7 deletions
+23 -2
View File
@@ -15,6 +15,7 @@ from __future__ import annotations
from scribe.mcp._context import current_user_id
from scribe.services import notes as notes_svc
from scribe.services import systems as systems_svc
from scribe.services import trash as trash_svc
@@ -68,6 +69,7 @@ async def create_note(
body: str = "",
tags: list[str] | None = None,
project_id: int = 0,
system_ids: list[int] | None = None,
) -> dict:
"""Create a new note in Scribe.
@@ -76,6 +78,8 @@ async def create_note(
body: Markdown content. Supports [[wikilinks]] to other notes by title.
tags: List of plain-string tags without # prefix, e.g. ["python", "ideas"].
project_id: Associate with a project (use 0 for no project / orphan note).
system_ids: Ids of the project's Systems to associate this note with
(e.g. research about a subsystem). See list_systems / create_system.
Returns the created note object including its assigned id.
"""
@@ -87,7 +91,14 @@ async def create_note(
tags=tags,
project_id=project_id or None,
)
return note.to_dict()
if system_ids:
await systems_svc.set_record_systems(uid, note.id, system_ids)
data = note.to_dict()
if system_ids:
data["systems"] = [
s.to_dict() for s in await systems_svc.list_record_systems(uid, note.id)
]
return data
async def update_note(
@@ -96,6 +107,7 @@ async def update_note(
body: str = "",
tags: list[str] | None = None,
project_id: int = 0,
system_ids: list[int] | None = None,
) -> dict:
"""Update an existing Scribe note. Only explicitly provided fields are changed.
@@ -105,6 +117,8 @@ async def update_note(
body: New markdown body, or omit to leave unchanged.
tags: Replaces the full tag list. Pass [] to clear all tags. Omit to leave unchanged.
project_id: New project association. Omit (or pass 0) to leave unchanged.
system_ids: Replace this note's System associations with these ids
(set-semantics). None = leave unchanged; [] = clear all.
"""
uid = current_user_id()
fields: dict = {}
@@ -119,7 +133,14 @@ async def update_note(
note = await notes_svc.update_note(uid, note_id, **fields)
if note is None:
raise ValueError(f"note {note_id} not found")
return note.to_dict()
if system_ids is not None:
await systems_svc.set_record_systems(uid, note_id, system_ids)
data = note.to_dict()
if system_ids is not None:
data["systems"] = [
s.to_dict() for s in await systems_svc.list_record_systems(uid, note_id)
]
return data
async def delete_note(note_id: int) -> dict: