3"""The two read-only surfaces: resources (documents) and prompts (canned tasks).
5Neither runs anything. Resources hand back repository documents by URI, and
6prompts hand back a pre-written instruction with the caller's arguments
7substituted -- so both are grouped apart from :mod:`mcp_tools`, whose entries
8build firmware, run tests and touch hardware.
11from __future__
import annotations
13from collections.abc
import Callable
16from mcp_tools
import tool_list_apps
17from mcp_util
import REPO_ROOT, read_text
23def _resource_doc(rel_path: str) -> Callable[[], str]:
25 path = REPO_ROOT / rel_path
26 if not path.is_file():
27 return f
"{rel_path} not found in this tree."
28 return read_text(path)
33def _resource_catalogue() -> str:
34 return tool_list_apps({})
37RESOURCES: list[dict[str, Any]] = [
39 "uri":
"ra8d2://doc/claude-md",
41 "description":
"Project rules for AI assistants (the most-violated rules).",
42 "mimeType":
"text/markdown",
43 "reader": _resource_doc(
"CLAUDE.md"),
46 "uri":
"ra8d2://doc/style-guide",
47 "name":
"docs/STYLE_GUIDE.md",
48 "description":
"Authoritative C23 + Doxygen style guide.",
49 "mimeType":
"text/markdown",
50 "reader": _resource_doc(
"docs/STYLE_GUIDE.md"),
53 "uri":
"ra8d2://doc/ring-and-world",
54 "name":
"docs/RING_AND_WORLD.md",
55 "description":
"Architectural-ring + TrustZone-world tagging system.",
56 "mimeType":
"text/markdown",
57 "reader": _resource_doc(
"docs/RING_AND_WORLD.md"),
60 "uri":
"ra8d2://doc/contributing",
61 "name":
"CONTRIBUTING.md",
62 "description":
"Contributor workflow and gate reference.",
63 "mimeType":
"text/markdown",
64 "reader": _resource_doc(
"CONTRIBUTING.md"),
67 "uri":
"ra8d2://reference/chapter-map",
68 "name":
"HUM chapter map",
69 "description":
"Hardware User's Manual chapter-to-page ranges for citations.",
70 "mimeType":
"text/markdown",
71 "reader": _resource_doc(
"docs/reference/CHAPTER_MAP.md"),
74 "uri":
"ra8d2://doc/hil",
75 "name":
"docs/HIL_SUITE.md",
76 "description":
"Hardware-in-the-loop suite + how each app is verified in CI.",
77 "mimeType":
"text/markdown",
78 "reader": _resource_doc(
"docs/HIL_SUITE.md"),
81 "uri":
"ra8d2://doc/ai-attribution",
82 "name":
"docs/AI_ATTRIBUTION_POLICY.md",
83 "description":
"The zero-AI-attribution policy enforced across the tree.",
84 "mimeType":
"text/markdown",
85 "reader": _resource_doc(
"docs/AI_ATTRIBUTION_POLICY.md"),
88 "uri":
"ra8d2://apps/catalogue",
89 "name":
"Firmware app catalogue",
90 "description":
"Live list of every discovered firmware app.",
91 "mimeType":
"text/plain",
92 "reader": _resource_catalogue,
96RESOURCE_INDEX: dict[str, dict[str, Any]] = {r[
"uri"]: r
for r
in RESOURCES}
102PROMPTS: list[dict[str, Any]] = [
104 "name":
"audit_register_access",
105 "description":
"Audit a direct MMIO register access for a valid HUM citation + style.",
107 {
"name":
"code",
"description":
"the register read/write line(s)",
"required":
True}
110 "Audit this RA8D2 register access against the project rules: every direct "
111 "register read/write MUST be immediately preceded by a HUM citation comment "
112 '`/* HUM Ch X.Y "section" p NNNN */`, must go through an inline accessor '
113 "(never a macro address), and must be pure 7-bit ASCII. Report each violation "
114 "with a concrete fix.\n\n```c\n{code}\n```"
118 "name":
"mcdc_vectors",
119 "description":
"Write minimal MC/DC test vectors for a compound boolean decision.",
121 {
"name":
"decision",
"description":
"the C boolean decision",
"required":
True}
124 "Write the minimal (N+1) MC/DC test vectors for this decision, demonstrating "
125 "that each condition independently affects the outcome, formatted as the "
126 "project's `@par MC/DC:` Doxygen block.\n\n```c\n{decision}\n```"
131PROMPT_INDEX: dict[str, dict[str, Any]] = {p[
"name"]: p
for p
in PROMPTS}
134def handle_prompts_list() -> dict[str, Any]:
135 """Answer MCP `prompts/list` with the catalogue's public fields.
137 Deliberately projects each entry rather than returning it whole: `template`
138 is server-side detail, and shipping it would leak the prompt text to every
139 client that merely enumerates.
142 `{"prompts": [...]}` with name, description and arguments per entry, in
146 {
"name": p[
"name"],
"description": p[
"description"],
"arguments": p[
"arguments"]}
149 return {
"prompts": listed}
152def handle_prompts_get(params: dict[str, Any]) -> dict[str, Any]:
153 """Answer MCP `prompts/get` by substituting arguments into a template.
155 Every declared argument is substituted, and a missing one becomes "" rather
156 than an error -- so a partially-filled prompt renders with a gap instead of
157 failing the call. Only declared arguments are passed to `format()`, so an
158 undeclared extra in `params` is ignored, and a `{placeholder}` in the
159 template with no matching declared argument raises.
162 params: JSON-RPC params; "name" selects the prompt, "arguments" is an
163 optional name -> value mapping whose values are coerced to str.
166 `{"description": ..., "messages": [...]}` with one user-role text
170 ValueError: No prompt by that name, or the template referenced a
171 placeholder that is not a declared argument. `dispatch` converts
172 this into a JSON-RPC error response.
174 name = str(params.get(
"name",
""))
175 prompt = PROMPT_INDEX.get(name)
177 msg = f
"unknown prompt: {name}"
178 raise ValueError(msg)
179 arguments = params.get(
"arguments")
or {}
181 text = prompt[
"template"].format(
182 **{a[
"name"]: str(arguments.get(a[
"name"],
""))
for a
in prompt[
"arguments"]}
184 except (KeyError, IndexError)
as exc:
185 msg = f
"bad prompt arguments: {exc}"
186 raise ValueError(msg)
from exc
188 "description": prompt[
"description"],
189 "messages": [{
"role":
"user",
"content": {
"type":
"text",
"text": text}}],