From 70c8e3cd51165afccbbb4566747c6e77f749fa2e Mon Sep 17 00:00:00 2001 From: tegwick Date: Sun, 1 Mar 2026 22:05:31 +0100 Subject: [PATCH] feat(mcp): add get_domain_summary() for low-token domain session orientation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit get_state_summary() returns ~10k tokens — too expensive for routine domain repo sessions that only need their own workstreams and decisions. New get_domain_summary(domain_slug): - 5 targeted API calls: topics (filter), workstreams (topic+status), decisions (topic+pending), progress (topic, limit 5), repos (domain, slug+SBOM only) - Returns: topic, active workstreams, blocking decisions, 5 recent events, repo SBOM status — all scoped to one domain - Estimated ~80-90% token reduction vs get_state_summary() get_state_summary() preserved unchanged for cross-domain / custodian sessions. Updated its docstring to note the large response and point to get_domain_summary. Template updated: Step 1 now calls get_domain_summary("{DOMAIN}") instead of get_state_summary() + get_next_steps(). TOOLS.md updated with usage guidance. Co-Authored-By: Claude Sonnet 4.6 --- mcp_server/TOOLS.md | 3 ++- mcp_server/server.py | 43 ++++++++++++++++++++++++++++++ scripts/project_claude_md.template | 3 +-- 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/mcp_server/TOOLS.md b/mcp_server/TOOLS.md index 9a060b0..c6483d3 100644 --- a/mcp_server/TOOLS.md +++ b/mcp_server/TOOLS.md @@ -24,7 +24,8 @@ Do not use them as a substitute for formal work definition inside the domain rep | Tool | Key Args | When to use | |------|----------|-------------| -| `get_state_summary()` | — | **Session start.** Full snapshot: totals, blocking decisions, blocked tasks, open workstreams, last 20 events. | +| `get_domain_summary(domain_slug)` | `domain_slug`: e.g. `"railiance"` | **Domain session start.** Scoped snapshot: active workstreams, blocking decisions, last 5 events, repo SBOM status — ~10% of get_state_summary() token cost. | +| `get_state_summary()` | — | **Cross-domain work / custodian sessions.** Full snapshot: totals, all blocking decisions, all blocked tasks, all open workstreams, last 20 events. Large (~10k tokens). | | `get_topic(slug)` | `slug`: e.g. `"markitect"` | Deep-dive on one topic + its workstreams + recent events. | | `list_blocked_tasks(workstream_id?)` | optional filter | Surface all impediments, optionally scoped to one workstream. | | `list_pending_decisions(topic_id?)` | optional filter | Decisions holding up work, sorted by deadline. | diff --git a/mcp_server/server.py b/mcp_server/server.py index fcf089e..f60059a 100644 --- a/mcp_server/server.py +++ b/mcp_server/server.py @@ -24,6 +24,8 @@ mcp = FastMCP( instructions=( "Custodian State Hub: tracks topics, workstreams, tasks, decisions, and progress events. " "Start every session with get_state_summary() for orientation. " + "When working inside a single registered domain repo, prefer get_domain_summary(domain_slug) " + "— it returns the same actionable data scoped to that domain at ~10% of the token cost. " "All writes emit a progress_event automatically." ), ) @@ -120,10 +122,51 @@ def get_state_summary() -> str: Returns a full snapshot: topic/workstream/task/decision totals, blocking decisions, blocked tasks, open workstreams, and the 20 most recent events. + + NOTE: This response is large (~10k tokens). When working inside a single + registered domain repo, use get_domain_summary(domain_slug) instead — + same actionable data scoped to one domain at ~10% of the token cost. """ return json.dumps(_get("/state/summary"), indent=2) +@mcp.tool() +def get_domain_summary(domain_slug: str) -> str: + """Lightweight session orientation for a single domain. + + Use this instead of get_state_summary() when working in a registered + domain repo — returns only what is relevant to the specified domain, + typically 80-90% fewer tokens than the full summary. + + Args: + domain_slug: the domain slug, e.g. "railiance", "markitect" + + Returns: topic, active workstreams, open blocking decisions for this + topic, 5 most recent progress events, and repo SBOM status for this domain. + """ + topics = _get("/topics") + topic = next((t for t in topics if t.get("domain_slug") == domain_slug), None) + if not topic: + return json.dumps({"error": f"No topic found for domain '{domain_slug}'"}) + + topic_id = topic["id"] + + workstreams = _get("/workstreams", {"topic_id": topic_id, "status": "active"}) + blocking = _get("/decisions", {"decision_type": "pending", "topic_id": topic_id}) + recent = _get("/progress", {"topic_id": topic_id, "limit": 5}) + repos = _get("/repos", {"domain": domain_slug}) + + return json.dumps({ + "domain": domain_slug, + "topic_id": topic_id, + "topic_title": topic["title"], + "workstreams": workstreams, + "blocking_decisions": blocking, + "recent_progress": recent, + "repos": [{"slug": r["slug"], "last_sbom_at": r.get("last_sbom_at")} for r in repos], + }, indent=2) + + @mcp.tool() def get_topic(slug: str) -> str: """Return a topic (with workstreams) by slug, plus its recent progress events.""" diff --git a/scripts/project_claude_md.template b/scripts/project_claude_md.template index 30c787d..d91a120 100644 --- a/scripts/project_claude_md.template +++ b/scripts/project_claude_md.template @@ -17,8 +17,7 @@ this orientation sequence. Do not greet, do not ask what to do first.** **Step 1 — Call the State Hub** ``` -get_state_summary() # orientation: workstreams, decisions, recent progress -get_next_steps() # contextual suggestions from resolved decisions +get_domain_summary("{DOMAIN}") # workstreams, blocking decisions, recent progress, SBOM status ``` If the call fails, the API is offline: `cd ~/the-custodian/state-hub && make api`