server
def server(
host:NoneType=None, agent:NoneType=None, name:str='ramabana', readonly:bool=True, delegate:bool=True, **kw
):An MCP server over one host’s tools, and optionally over Ramabana’s own agent.
server builds the MCP server. Pass a host to serve its tools. Pass an agent as well and the server mounts the agent’s own tools, the same objects a turn gets, so a client’s calls appear in the same activity feed and changes() report as a turn’s. Agent._record wraps each tool with functools.wraps, which keeps its signature and docstring, and FastMCP builds each tool’s schema from those. A tool is therefore described in one place, and the description a client sees cannot drift from the one a model sees. Without an agent, the server builds the tools from the host with tools_for.
readonly defaults to True because the client is another agent, and this server cannot put an approval prompt in front of the client’s user. Read-only leaves out every write tool. The read tools stay, and so do acting tools such as inspect_python and generate_image. readonly=False mounts the write tools too, and then the client’s own approval settings are what stands in front of them. delegate=False leaves out ask even when an agent is given, and any other keyword goes to FastMCP.
INSTRUCTIONS is the text the server sends a client when it connects. It tells the client’s model how to read and edit through these tools.
An MCP server over one host’s tools, and optionally over Ramabana’s own agent.
Below is a server over FullHost, a test host from testing that declares the code, file, git, memory, notebook, session, shell and web groups. The server is read-only by default, so replace_text and create_file are absent. Without an agent it also mounts shalya’s legacy group (list_files, list_vars and others) for one release, so that a client written against version 0.1 keeps working. edit_file belongs to the opt-in exhash group and is not mounted.
Each tool arrives with the schema FastMCP derived from the function itself. The description is the docstring a model reads in a turn, and the parameters come from the type annotations.
MCP tool annotations tell a client which calls only read, which are destructive, and which reach the open world, meaning the web and API tools. _annotate sets them from the same catalog entry that approvals and the activity feed use. A client can use them to decide which calls to ask its user about. This server is built with readonly=False, so replace_text is there to compare.
Calling a tool goes through the same code path as a turn, and the client gets back exactly the text the model would get. view_file returns one lineno|hash|content line per line of the file.
A write tool left out of a read-only server is absent from the MCP surface, and calling its name returns an error. The last line lists the write tools that readonly=False mounts.
with expect_fail(contains='Unknown tool'): await mcp.call_tool('replace_text', {'path': 'pkg/sizes.py', 'edits': []})
with expect_fail(contains='Unknown tool'): await mcp.call_tool('edit_file', {'path': 'pkg/sizes.py', 'commands': []})
[t.name for t in await mcp_w.list_tools() if t.name in WRITE_TOOLS]Skills are offered as resources, not tools. A resource is text a client can read, where a tool is a call that may have an effect. The server publishes three kinds:
ramabana://skills, an index of every skill with its one-line description of when it appliesskill://{name}, one skill in full, the same text read_skill returns in a turnramabana://status, a JSON summary of the open folders, the mounted tools, the skills and, with an agent, the agent’s statusA client reads the index and fetches only the skill it needs, the same saving read_skill gives a model in a turn. Without an agent, the server finds skills in the served folders and in ~/.agents/skills.
With an agent passed in, the server also offers ask, which runs a whole Ramabana turn behind one call. The client sends one task and gets back one answer. The tool loop, the tool results and any compaction of the history happen on this side, and the client never sees them. Below, fake_agent from testing supplies an agent with a scripted reply, so the example needs no model.
ask runs the turn on a worker thread, because a turn blocks and an MCP server is asynchronous. The client’s one call sent the agent’s backend one message.
ramabana-mcp is the command-line entry point, main. It splits --root into folders and serves them over --transport, which is stdio by default. --model builds a full agent on that model and turns on the ask tool. Without it there is nothing to hand a task to, so the server offers tools only. --write mounts the write tools, and the other options build the host the way the terminal does.
The agent behind ask is built with approve='none', which means no approval gate at all, so the turn ask runs may use the agent’s write tools without asking anyone. --write decides only which tools the client may call directly.
def main(
root:str='.', # comma-separated folders to serve
model:str=None, # model for `ask`; omit to serve tools only
write:bool=False, # mount the write tools too
web:bool=True, # let web tools reach the network via fossick
read_outside:bool=False, # reads may name any path; writes stay inside
vault:bool=False, # keep reads in a vishalakshi vault
pii:str='off', # off | redact | refuse PII from the vault
pii_ner:bool=False, # --pii also gates titled names, not only patterns
transport:str='stdio', # stdio | sse | streamable-http
cfg:str=None, # config dir, for skills and extensions
):Serve Ramabana tools over MCP.
A client that launches its own servers takes the command and its arguments. Over a repository, with ask running on a local model, that is:
Start with the default, read-only tools and no model: