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