Skip to contents

Use tool() to make an R function discoverable and callable by an MCP client. If you have not connected a client yet, follow Get started first. The examples here can be called directly in R without starting a server.

Describe arguments and defaults

Give each tool a name and a description that tells the client when to use it. Use a named list of type_*() helpers to describe the arguments. Marking an argument required = FALSE permits the client to omit it; the R function must still provide its own default.

Incoming JSON arrays reach the function as R lists, not atomic vectors. Convert them when the R operation needs a vector. This tool uses unlist() before summing and defaults to a multiplier of 1:

sum_numbers <- tool(
  function(values, multiplier = 1) {
    sum(unlist(values, use.names = FALSE)) * multiplier
  },
  name = "sum_numbers",
  description = "Sum numeric values and apply an optional multiplier.",
  arguments = list(
    values = type_array(type_number("A value to include.")),
    multiplier = type_number("Scale the sum.", required = FALSE)
  )
)
sum_numbers(list(2, 3))
#> [1] 5
sum_numbers(list(2, 3), multiplier = 2)
#> [1] 10

The returned tool remains an R function, so direct calls are useful for checking its calculations. They do not exercise MCP serialization or client behavior. Pass the tool to mcp_server(list(sum_numbers)) in your server script when it is ready.

Schemas describe arguments; they do not coerce or validate values. Add the domain checks your function needs, rather than relying on a client to obey the schema. See tool schemas for the helpers and tool() for naming rules and optional tool annotations.

Report failures

An error raised by a tool, such as stop("Unknown item"), is contained and returned to the MCP client as a tool-level error. A direct R call still raises the ordinary R error.

For an expected domain failure, you can instead return an explicit result:

out_of_stock <- tool_result(
  content = content_text("The requested item is out of stock."),
  is_error = TRUE
)

Use is_error = TRUE to distinguish failure from a successful text response. See Return results for other result choices, and Return-value mappings for exact conversion rules and payload options.

Keep access and side effects explicit

Each tool owns domain validation, authorization, credential handling, safe side effects, output sanitization, and any needed rate or payload limits. Schemas are not a security boundary. In particular, type_ignore() only omits an argument from the advertised schema; it does not reject a caller-supplied argument or enforce permissions.

Keep stdout exclusively for MCP protocol traffic when the tool runs in the server. Send diagnostics to stderr, not print() or cat() on stdout.

Describe a complete input schema

Use type_from_schema() when property helpers are not enough. A complete input schema must have direct root type = "object". JSON Schema 2020-12 keywords such as $schema, $defs, $ref, composition, and conditionals are preserved. When $schema is absent, MCP treats the schema as JSON Schema 2020-12.

find_items <- tool(
  function(query) {
    paste(query, c("alpha", "beta"), sep = "-")
  },
  name = "find_items",
  description = "Find local items whose names start with a query.",
  arguments = type_from_schema(list(
    type = "object",
    properties = list(
      query = list(type = "string", minLength = 1)
    ),
    required = list("query"),
    additionalProperties = FALSE
  ))
)
find_items("tea")
#> [1] "tea-alpha" "tea-beta"

mcplite checks schema container shape and serializability, not full schema validity or runtime input/output conformance. The tool above advertises a nonempty query but does not enforce that constraint. Tool functions own those guarantees.

An output_schema describes structured results rather than ordinary text returns. See Advertise structured output for an example and the compatibility qualification.

Reuse ellmer tools

Compatible ellmer::tool() definitions can be supplied directly to mcp_server(). ellmer is optional; tools created with mcplite::tool() do not require it. See mcp_server() for server setup and supported scope.