okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

nekomimi.petcodemode: mcp callable from python, 256 MiB kernel memory cap, cell output spilled to a file4359cba

main
5.1 KiB142 linesraw
1"""MCP operations as Python bindings, so a cell composes, batches and filters
2MCP calls without each result passing through the model's context.
3
4`mcp.tools()`, `mcp.describe()` and `mcp.call()` read the live catalogue;
5`mcp.<server>.<tool>(**arguments)` methods are minted from the catalogue at
6kernel boot, so an operation a server adds later is reached through `call`.
7"""
8
9from __future__ import annotations
10
11from collections.abc import Callable, Coroutine
12from typing import Any, cast
13import keyword
14import re
15
16from albedo_api import Host, PythonApi, Record
17import albedo_trace
18
19Operation = dict[str, object]
20
21
22def _record(value: object) -> object:
23    """A result as nested records, so `r.content[0].text` reads."""
24    if isinstance(value, dict):
25        return Record({key: _record(item) for key, item in value.items()})
26    if isinstance(value, list):
27        return [_record(item) for item in value]
28    return value
29
30
31def _named(operations: list[Operation], name: str) -> list[Operation]:
32    """The operations `name` means: the advertised name, or `server/tool`."""
33    found = [item for item in operations if item.get("name") == name]
34    if not found and "/" in name:
35        server, tool = name.split("/", 1)
36        found = [
37            item
38            for item in operations
39            if item.get("server") == server and item.get("tool") == tool
40        ]
41    return found
42
43
44class Mcp:
45    """This session's MCP servers. Results are records of the server's
46    CallToolResult: untrusted data, with a tool-reported failure in `isError`
47    rather than raised; McpError is raised when the call itself fails."""
48
49    def __init__(self, host: Host) -> None:
50        self._host = host
51
52    async def _operations(self) -> list[Operation]:
53        return cast(list[Operation], await self._host("mcp.list", {}))
54
55    async def tools(self, server: str | None = None) -> list[Record]:
56        """Every operation (name, server, kind, tool, description), or one server's."""
57        return [
58            Record(
59                {
60                    key: item.get(key, "")
61                    for key in ("name", "server", "kind", "tool", "description")
62                }
63            )
64            for item in await self._operations()
65            if server is None or item.get("server") == server
66        ]
67
68    async def describe(self, name: str) -> Record:
69        """One operation with its `parameters` schema; name as for call()."""
70        found = _named(await self._operations(), name)
71        if not found:
72            raise LookupError(f"unknown MCP operation {name!r}; mcp.tools() lists them")
73        return Record(found[0])
74
75    async def call(
76        self, name: str, arguments: dict[str, object] | None = None, **kwargs: object
77    ) -> Record:
78        """Call one operation by advertised name or `server/tool`; keyword
79        arguments join `arguments`."""
80        merged = {**(arguments or {}), **kwargs}
81        albedo_trace.note("mcp", name, str(merged)[:512])
82        result = await self._host("mcp.call", {"name": name, "arguments": merged})
83        return cast(Record, _record(result))
84
85
86def _identifier(value: str) -> str:
87    return re.sub(r"[^a-zA-Z0-9_]", "_", value)
88
89
90def _mintable(name: str, taken: set[str]) -> bool:
91    return (
92        bool(name)
93        and not name.startswith("_")
94        and not keyword.iskeyword(name)
95        and name not in taken
96    )
97
98
99def _method(mcp: Mcp, item: Operation) -> Callable[..., Coroutine[Any, Any, Record]]:
100    name = cast(str, item["name"])
101    schema = cast(dict[str, object], item.get("parameters") or {})
102    required = cast(list[str], schema.get("required") or [])
103    names = required + [
104        key for key in cast(dict, schema.get("properties") or {}) if key not in required
105    ]
106
107    async def call(**arguments: object) -> Record:
108        return await mcp.call(name, arguments)
109
110    call.__name__ = _identifier(cast(str, item["tool"]))
111    call.__qualname__ = f"mcp.{_identifier(cast(str, item['server']))}.{call.__name__}"
112    call.__doc__ = str(item.get("description", "")) + (
113        "\n\nArguments: " + ", ".join(names) if names else ""
114    )
115    return call
116
117
118class Server:
119    """One MCP server's operations as methods."""
120
121    def __init__(self, name: str) -> None:
122        self.name = name
123
124
125async def setup(api: PythonApi) -> dict[str, object]:
126    """Mint `mcp.<server>.<tool>` from the boot catalogue; an operation whose
127    identifiers clash or are not mintable stays reachable through mcp.call."""
128    mcp = Mcp(api.host)
129    servers: dict[str, Server] = {}
130    for item in cast(list[Operation], await api.host("mcp.list", {})):
131        server = _identifier(str(item.get("server", "")))
132        tool = _identifier(str(item.get("tool", "")))
133        if not _mintable(server, set(dir(mcp))) or not _mintable(
134            tool, set(dir(Server))
135        ):
136            continue
137        holder = servers.setdefault(server, Server(str(item["server"])))
138        if not hasattr(holder, tool):
139            setattr(holder, tool, _method(mcp, item))
140    for server, holder in servers.items():
141        setattr(mcp, server, holder)
142    return {"mcp": mcp, "McpError": api.HostError}