{mcpr}

John Coene

What is {mcpr}?

Model Context Protocol server and client for R.

Oh no!

It’s about AI…

Not entirely…

It’s a new interface

For Developers

From one dev to the another.

flowchart LR
  A(Developers) --> B(Analysis,<br/>Algorithms,<br/>Business Logic,<br/>Cool stuff)
  B --> |R Package,<br/>CLI| D(Developers)

For Machines

flowchart LR
  A(Developers) --> B(Analysis,<br/>Algorithms,<br/>Business Logic,<br/>Cool stuff)
  B --> |R Package,<br/>CLI| D(Developers)
  B --> |RESTful API| E(Machines)

For Humans

flowchart LR
  A(Developers) --> B(Analysis,<br/>Algorithms,<br/>Business Logic,<br/>Cool stuff)
  B --> |R Package,<br/>CLI| D(Developers)
  B --> |API| E(Machines)
  B --> |App,<br/>Document| F(Humans)

For LLMs

flowchart LR
  A(Developers) --> B(Analysis,<br/>Algorithms,<br/>Business Logic,<br/>Cool stuff)
  B --> |R Package,<br/>CLI| D(Developers)
  B --> |API| E(Machines)
  B --> |App,<br/>Document| F(Humans)
  B --> |MCP| G(LLMs)

How it works

LLM



A model that takes text as input and generates text as output.

So how do they do stuff?

Tools


Give LLMs access to tools (functions).

Agents

  • Call Y Combinator
  • Become rich

What is this protocol?

Standardized transmission

JSON-RPC message format transmitted over standard I/O (recommended) or HTTP.


Specs: modelcontextprotocol.io


{mcpr} implements the Model Context Protocol

List tools

Request

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {}
}

Response

{
  "jsonrpc":"2.0",
  "result": {
    "tools":[
    {
      "name":"get_weather",
      "description":"Get weather for a location",
      "inputSchema":{
        "type":"object",
        "properties":{
          "location":{
            "type":"string",
            "title":"Location",
            "location":"The location to get weather for"
          }
        },
        "additionalProperties":false,
        "required":["location"]
      }
    },
    // more tools
  ]},
  "id":1
}

Call tool

Request

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": {
      "location": "New York"
    }
  }
}

Response

{
  "jsonrpc":"2.0",
  "result":{
    "content":[{
      "type":"text",
      "text":"13 Celsius"
    }],
    "isError":false
  },
  "id":2
}

{mcpr} by example

R helper

Let’s create a simple MCP server that provides help for R functions!


?dplyr::filter


Will enable the LLM to fetch contextual help for R functions.

Server

The server function is rather simple.

mcp <- new_server(
  name = "r-documentation-server",
  description = "Provides access to internal R ..."
  version = "1.0.0"
)

Tool

help <- new_tool(
  name = "help",
  description = "Retrieves internal documentation ..."
  input_schema = schema(
    properties = properties(
      pkg = property_string(
        "Package",
        "Name of the package",
        required = TRUE
      ),
      fnc = property_string(
        "Function",
        "Name of the function",
        required = TRUE
      )
    )
  ),
  handler = function(params) {
    response_text(
      get_documentation(params$pkg, params$fnc)
    )
  }
)

mcp <- add_capability(mcp, help)

Serve

Simply serve it.

serve_io(mcp)

Supports both HTTP and stdio (recommended).

Test

Claude

Tip

Make descriptions lengthy and descriptive.

This increases the chances of the LLM triggering the MCP call.

Features

Roclet

Roxygen tags

  • @type to specify the argument type
  • @mcp to define the name of the function and the description

Example

#' Add two numbers
#' @param x First number
#' @param y Second number  
#' @type x number
#' @type y number
#' @mcp add_numbers Add two numbers together
add_numbers <- function(x, y) {
  x + y
}

DESCRIPTION

Add the roclet to your roxygen config.

Roxygen: list(
  markdown = TRUE,
  roclets = c("collate", "rd", "namespace", "mcpr::mcp_roclet")
)

Document

Run devtools::document() to generate the MCP server at inst/mcp_server.R.

Client

Create

Initialise a new client.

client <- new_client_io(
  command = "Rscript help.R",
  name = "r-documentation-client",
  version = "1.0.0"
)

Supports both HTTP and stdio (recommended).

Call

List the tools available.

tools <- tools_list(client)

Call a tool.

result <- tools_call(
  client,
  params = list(
    name = "help",
    arguments = list(
      pkg = "dplyr",
      fnc = "filter"
    )
  ),
  id = 1L
)

Elmer integration

Tools

Use {ellmer} tools with {mcpr}.

ellmer_tool <- ellmer::tool(
  \(tz = "UTC") {
    format(Sys.time(), tz = tz, usetz = TRUE)
  },
  "Gets the current time in the given time zone.",
  tz = ellmer::type_string(
    "The time zone to get the current time in. Defaults to `\"UTC\"`.",
    required = FALSE
  )
)

mcp <- add_capability(mcp, ellmer_tool)

Register tools

Register {mcpr} tools with {ellmer}.

chat <- ellmer::chat_openai()

register_mcpr_tools(chat, mcp)

Why an MCP?

  • Make it easy to share code with LLMs.
  • Compartmentalise MCP logic from rest of infrastructure.
  • Provider agnostic.
  • Shareable!

Thank you!

john@opifex.org

I write code, founder of Opifex.

Slides: rrr.is/mcpr