Skip to contents

Model Context Protocol (MCP) servers provide tools, documents (“resources”) and prompt templates to any client that speaks the protocol. Deputy connects to them through the mcptools package, and treats their tools like any other: every call goes through the agent’s permissions, hooks and usage limits.

Configure servers in mcptools’ configuration file, ~/.config/mcptools/config.json by default. The mcptools documentation explains the format.

Deputy works with mcptools 1.0.2 and 1.0.3, and refuses other versions until they have been tested. It relies on parts of mcptools that aren’t yet public API.

Load tools from a server

tools_mcp() connects to the servers you name and returns their tools:

library(deputy)

agent <- Agent$new(
  chat = ellmer::chat("anthropic/claude-sonnet-5"),
  tools = c(tools_file(), tools_mcp(servers = "github"))
)

Name servers exactly as they appear in the config file. If mcptools isn’t installed or the servers offer no tools, tools_mcp() warns and returns an empty list. agent$load_mcp() does the same for an existing agent, and agent$mcp_status() reports each load attempt.

Deputy keeps the annotations each server declares for its tools, so permissions can judge them. Tools without annotations get the cautious defaults described in Tools: with the default permissions, a tool that might reach the network needs web = TRUE. A server’s tool is never confused with a built-in one: a remote tool called read_file is judged by its own annotations, not given Deputy’s file permissions, and file checkpoints don’t cover MCP tools. Deputy can’t see what a server does with a call, so connect only to servers you trust.

When a server restarts or reconnects, its old tools stop working. Reload them with agent$load_mcp(servers = "github", replace = TRUE), which also removes tools the server no longer offers. Tools from other servers are left alone.

A connection per conversation

tools_mcp() shares one connection per server across your R session. When each conversation needs its own connection, for example in a Shiny app, or when you want resources and prompts as well as tools, use McpConnection. It belongs to one agent, and you choose exactly which tools, resources and prompts it exposes:

agent <- Agent$new(
  chat = ellmer::chat("openai/gpt-6-luna"),
  permissions = Permissions(web = TRUE)
)

connection <- McpConnection$new(
  config = "~/.config/mcptools/config.json",
  server = "evidence",
  agent = agent,
  tools = "inspect_evidence",
  resources = "evidence://report/current",
  prompts = "summarize"
)

agent$register_tools(c(connection$tools(), connection$capability_tools()))

$tools() returns the allowed tools. $capability_tools() returns tools for reading the allowed resources and prompts; with several connections on one agent, give each a distinct prefix so their names don’t clash. These tools need web = TRUE, because the content comes from outside R.

The allow lists can’t change after the connection is created. Your code can browse what the server offers with $discover("tools") (or "resources", "resource_templates", "prompts"), but discovering an item doesn’t allow it. $read_resource(uri) and $get_prompt(name, arguments) fetch allowed items directly; a fetched prompt is returned to you, not added to any chat.

Calls return promises, and a connection handles one request at a time: a second request while one is running fails with a busy error, while other connections carry on. Call $close() when the conversation ends, for example from session$onSessionEnded() in Shiny. $cancel() kills the connection and discards the server’s session state, as does a request timeout. Tools from a closed connection stop working; create a new connection to continue.

Slow servers

mcptools waits about four seconds for each reply from a server that runs as a local process, and doesn’t check that a reply belongs to the request it answers. A late reply could therefore become the answer to the next call. Deputy checks every reply’s ID, and when a server doesn’t answer in time or answers out of order, the call fails with a deputy_mcp_desynchronized error and the connection closes. The server’s session state is lost, so create a new connection. Tools loaded with tools_mcp() stop that server on the first lost reply.

Sandboxed interpreters

mcp-repl and MCP Console are MCP servers that run R (and, for MCP Console, Python and SQL) inside an OS sandbox. Deputy has dedicated helpers for them that check the sandbox is really on: see Running R code.