Connects to a Model Context Protocol server and returns its tools as
LLMR::llm_tool() objects ready for agent(), wrapped so the agent's use of
them is safe and recorded. The defaults are strict
because MCP's documented attack surface (tool/schema poisoning, line jumping,
rug pulls, confused-deputy) lives in exactly the trust an agent places in a
server's tool descriptions.
Usage
mcp_tools(
config,
policy = c("read_only", "read_write"),
approve_writes = TRUE,
audit = TRUE,
allow = NULL,
pin_schemas = TRUE,
transport = NULL,
timeout_s = 30,
max_bytes = 1e+06
)Arguments
- config
A connection spec: a list with
url(HTTP) orcommand(stdio), or anything yourtransportunderstands.- policy
"read_only"(default) or"read_write".- approve_writes
If
TRUE(default), write/external calls pass a human gate.- audit
If
TRUE(default), schemas/calls/results are recorded.- allow
Optional character vector of tool names to expose (others are dropped and logged).
- pin_schemas
If
TRUE(default), pin and re-check tool schemas.- transport
Test/extension seam: a function
(method, params) -> resultspeaking JSON-RPC to the server. Default builds a real HTTP/stdio client.- timeout_s, max_bytes
Per-call limits.
Details
Defenses applied:
Read-only floor (
policy = "read_only"): the floor is an allowlist, not a denylist. A tool is exposed only when it is positively known to be read-only (itsannotations$readOnlyHintisTRUE). Any tool that is not positively read-only (one with no annotations, or one that looks write-like) is refused unlesspolicy = "read_write". A malicious server cannot slip a writing tool past the floor by giving it a benign name and no annotations.Human gate for writes (
approve_writes = TRUE): any write/external call pauses for sign-off (seehuman_gate()).Schema pinning (
pin_schemas = TRUE): each tool's full advertised signature (input schema, description, and annotations) is hashed at first listing and re-verified before every call. A later change, or a server that refuses to let us re-verify (atools/getthat errors or returns no schema), raisesllmragent_mcp_schema_driftrather than trusting the new definition or failing open (the schema-drift defense). The re-check fails closed: if it cannot confirm the tool is unchanged, it refuses.Description sanitation: server descriptions are treated as untrusted, never spliced into a system prompt, and scanned for injection patterns; a flagged tool is downgraded to require approval (the line-jumping defense).
Audit (
audit = TRUE): tool schemas, call argument hashes, and result hashes are recorded.
Examples
if (FALSE) { # \dontrun{
# offline, via a fake transport returning canned JSON-RPC
fake <- function(method, params) {
if (method == "tools/list") list(tools = list(list(
name = "search", description = "Search docs.",
inputSchema = list(type = "object",
properties = list(q = list(type = "string"))))))
else list(content = list(list(type = "text", text = "result")))
}
tools <- mcp_tools(list(url = "http://localhost:9000"), transport = fake)
} # }