Expand description
An MCP client Pack: one Pack over every MCP server its configuration names, each reached over Streamable HTTP, whose tools a prompt lists and calls through two static tools.
The crate’s Plugin, install, inserts the Pack runvil/mcp, whose
Pack id is mcp, with the sub-object of its config under mcp. A Host
passes the mcpServers object of an mcp.json file there: each
server’s entry under the server’s name. A null configuration means
no servers, and any other value that is not an object leaves the Pack
unavailable. The Pack has no prelude, so declaring it, as in
uses: [mcp], only makes it required.
The Pack offers exactly two tools under the Pack id construct
receives, such as mcp/get_dynamic_tools under mcp, modeled on
Cursor’s GetDynamicTools and CallDynamicTool:
get_dynamic_toolstakes the optional stringsnamespaceandtoolNameand answers JSON text, which a script reads withjson.decode. With no arguments it lists each server’s namespace, status, and tool names, in name order; withnamespace, that server’s tools with their input schemas; with both, one tool.call_dynamic_tooltakes the stringsnamespaceandtoolNameand an optional objectarguments, which defaults to{}, and calls that server’s tool.
A namespace is the server’s name exactly as the configuration writes
it, and a tool’s name is the one its server lists. Both must match
exactly. A server’s status is ready, loading while it is still
starting, or error with the reason. A tool’s description is its
description, else its title, else its name. An unknown namespace
or tool, a toolName without a namespace, and a call to a server
that is not ready each give an error a model can read.
Each entry is parsed on its own. A remote entry has url and optional
headers, and a type of "sse", the legacy transport, is refused. A
local entry has command, with optional args, env, and cwd. This
Pack recognizes that shape and refuses it with local MCP servers (command) are not supported yet. Other type values and unknown
fields are ignored, because the format is defined outside this
repository. A refused entry is a namespace with status error and the
reason, and the other servers still connect, so one bad entry never
leaves the Pack unavailable.
String values in an entry may use ${env:NAME}, which an unset
variable refuses naming NAME, and ${userHome}. Any other ${...}
form passes through unchanged. headers may not set accept,
Mcp-Session-Id, or Last-Event-Id, which the client owns.
Building the Pack does no I/O. It reads the Host’s runtime from the
Plugin contract’s [runvil_plugin::TOKIO_RUNTIME] service, even with
no servers, and starts one background task per parsed entry on it, which
connects, runs the handshake, and lists every page of tools within two
minutes. The Pack’s ready resolves at once, so a run can see the two
tools while servers are still connecting, and the listing shows each
such server as loading. A server that fails to start shows as
error with the reason, and its task keeps trying in the background,
waiting 1 second after the first failure and doubling up to a minute,
so the server lists its tools once it answers. A server that is ready
stays ready, and a later failure shows as a transport error on the
call.
A call sends tools/call with the tool’s name and waits up to five
minutes. Text blocks and embedded text resources come back joined
with blank lines. An image, audio, or binary resource becomes a
placeholder such as [image omitted: image/png], a resource link
becomes [resource: <uri>], and a result with no blocks but with
structuredContent gives that JSON. A result marked isError is a
backend error with the server’s text, and a transport failure or the
deadline is a transport error. Dropping a call sends the server
notifications/cancelled for its request.
The client declares no capabilities, because an optional extension
would make GitHub’s remote server answer with interactive forms this
Pack cannot show. Its HTTP client refuses redirects, which keeps the
configured headers off any other server.
§Invariants
- May depend on:
runvil-plugin,shared-*crates, and outside libraries.cargo test -p build-xtaskenforces the Plugin family’s allow-list. Onlyinstallis public. - Every output is untrusted, because descriptions, schemas, and results all come from the servers.
- No reason, log line,
Debugoutput, or error names anenvorheadersvalue. They may name keys. - It runs on the Host’s runtime, read through the contract’s
TOKIO_RUNTIMEservice, andconstructdoes no I/O. - It starts no process: each local entry is refused at
construct, on its own. - Dropping a call tells the server to drop the request.
Functions§
- install
- The Plugin: inserts the
mcpPack intopackswithconfig["mcp"], which isnull, meaning no servers, whenconfighas nomcpkey.