ra8-firmware
0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
ra8_mcp.py
Go to the documentation of this file.
1
#!/usr/bin/env python3
2
# SPDX-License-Identifier: MIT
3
# Copyright (c) 2026 Brighton Sikarskie
4
"""A Model Context Protocol (MCP) server for this firmware repo.
5
6
This server gives an MCP-aware assistant live, structured context about the
7
ra8-firmware tree -- the firmware app catalogue, build / test / quality
8
workflows, the hardware-in-the-loop (HIL) rig, code search, and the project's
9
authoritative docs -- so the assistant can reason about what the repo can
10
actually *do* instead of guessing from loose markdown files.
11
12
Design goals
13
------------
14
* Zero third-party dependencies. It speaks the MCP stdio transport
15
(newline-delimited JSON-RPC 2.0) using only the Python standard library,
16
so it runs anywhere ``python3`` exists with nothing to ``pip install``.
17
That matches the repo's hand-written, minimal-dependency ethos.
18
* Read-only by default. Every tool that only inspects the tree (catalogue,
19
search, docs) runs freely. Every tool that touches real hardware (flash,
20
HIL) is gated behind an explicit ``confirm`` flag and otherwise returns a
21
dry-run preview of the exact command it would run.
22
* Honest output. Build / test / gate tools shell out to the repo's real
23
Just recipes and helper scripts, capture the combined output, and
24
return the tail verbatim with the real exit status.
25
26
Transport
27
---------
28
MCP stdio framing is one JSON-RPC message per line on stdin / stdout. All
29
human-readable logging goes to stderr so it never corrupts the protocol
30
stream. Run ``python3 tools/mcp/src/ra8_mcp.py --selftest`` to exercise the
31
dispatcher in-process without a client (used by ``just tools::mcp``).
32
33
The methods implemented are the subset a tools+resources server needs:
34
``initialize``, ``ping``, ``tools/list``, ``tools/call``, ``resources/list``,
35
``resources/read``, plus empty ``prompts/list`` /
36
``resources/templates/list`` replies for clients that probe them.
37
"""
38
39
from
__future__
import
annotations
40
41
import
argparse
42
import
json
43
import
sys
44
from
pathlib
import
Path
45
46
sys.path.insert(0, str(Path(__file__).resolve().parent))
47
48
from
mcp_protocol
import
(
49
ERR_INVALID_REQUEST,
50
ERR_PARSE,
51
SERVER_NAME,
52
SERVER_VERSION,
53
_error,
54
dispatch,
55
)
56
from
mcp_util
import
REPO_ROOT, log
57
58
59
# ---------------------------------------------------------------------------
60
# stdio serve loop and self-test
61
# ---------------------------------------------------------------------------
62
def
run_selftest() -> int:
63
"""Load and run the sibling test module only for ``--selftest``."""
64
tests_dir = Path(__file__).resolve().parents[1] /
"tests"
65
sys.path.insert(0, str(tests_dir))
66
from
mcp_selftest
import
selftest
# noqa: PLC0415 -- selftest path added above
67
68
return
selftest()
69
70
71
def
serve() -> int:
72
"""Run the newline-delimited JSON-RPC loop on stdin/stdout until EOF."""
73
log(f
"serving {SERVER_NAME} {SERVER_VERSION} on stdio (root {REPO_ROOT})"
)
74
out = sys.stdout
75
for
raw
in
sys.stdin:
76
line = raw.strip()
77
if
not
line:
78
continue
79
try
:
80
request = json.loads(line)
81
except
json.JSONDecodeError
as
exc:
82
out.write(json.dumps(_error(
None
, ERR_PARSE, f
"parse error: {exc}"
)) +
"\n"
)
83
out.flush()
84
continue
85
if
not
isinstance(request, dict):
86
out.write(
87
json.dumps(_error(
None
, ERR_INVALID_REQUEST,
"request must be an object"
)) +
"\n"
88
)
89
out.flush()
90
continue
91
response = dispatch(request)
92
if
response
is
not
None
:
93
out.write(json.dumps(response) +
"\n"
)
94
out.flush()
95
log(
"stdin closed, exiting"
)
96
return
0
97
98
99
def
main
(argv: list[str]) -> int:
100
"""Run the server, or the in-process self-test with `--selftest`.
101
102
With no flags this blocks in `serve()` reading JSON-RPC from stdin until
103
EOF, which is the normal mode: an MCP client spawns this as a subprocess and
104
speaks the stdio transport to it. Running it from a terminal looks like a
105
hang -- it is waiting for a request.
106
107
`--selftest` exercises the dispatcher without a client or a board, so it is
108
safe to run anywhere and is what CI invokes.
109
110
Args:
111
argv: Argument list WITHOUT the program name (callers pass
112
`sys.argv[1:]`).
113
114
Returns:
115
Process exit status: 0 on clean shutdown or a passing self-test, 1 on a
116
failing one.
117
"""
118
parser = argparse.ArgumentParser(description=
"MCP server for ra8-firmware."
)
119
parser.add_argument(
120
"--selftest"
, action=
"store_true"
, help=
"run the in-process dispatcher self-test and exit"
121
)
122
options = parser.parse_args(argv)
123
if
options.selftest:
124
return
run_selftest()
125
return
serve()
126
127
128
if
__name__ ==
"__main__"
:
129
sys.exit(
main
(sys.argv[1:]))
main
void main(void)
The application entry point Reset_Handler hands control to.
Definition
main.c:298
tools
mcp
src
ra8_mcp.py
Generated by
1.16.1