Introduction
The Model Context Protocol (MCP) is a standard protocol that enables
AI assistants (like Claude, ChatGPT, etc.) to interact with external
tools and services. The plumber2mcp package makes it
incredibly easy to add MCP support to your existing Plumber APIs with
just a single function call.
By adding MCP support, your R functions become available to AI assistants as:
- Tools: AI assistants can call your API endpoints directly
- Resources: AI assistants can read documentation, data, and analysis results
- Prompts: AI assistants can use pre-defined templates to guide interactions
Quick Start
Basic Example
Let’s create a simple Plumber API and add MCP support:
# Create a simple plumber API file
library(plumber)
library(plumber2mcp)
# Define your API
pr <- plumb(text = '
#* Echo back the input
#* @param msg The message to echo
#* @get /echo
function(msg = "Hello World") {
list(message = paste("Echo:", msg))
}
#* Add two numbers
#* @param a First number
#* @param b Second number
#* @post /add
function(a, b) {
list(result = as.numeric(a) + as.numeric(b))
}
')
# Add MCP support with HTTP transport
pr %>%
pr_mcp(transport = "http") %>%
pr_run(port = 8000)That’s it! Your API now has: - Regular HTTP endpoints at
http://localhost:8000/ - MCP server at
http://localhost:8000/mcp
Transport Options
HTTP Transport (Default)
HTTP transport is great for testing and when you want to run a traditional web server:
Your MCP endpoints are now available at: - GET /mcp -
Server information - POST /mcp/messages - JSON-RPC message
handler
Stdio Transport (Native MCP)
For native MCP client compatibility (mcp-cli, Claude Desktop, etc.), use stdio transport:
This starts a server that communicates over standard input/output using the MCP protocol.
Enhanced Documentation
One of the most powerful features of plumber2mcp is
automatic schema generation. When you document your endpoints with
roxygen comments, the package creates rich, detailed tool descriptions
that help AI assistants understand and use your APIs effectively.
Basic Documentation
#* Calculate statistics on numeric data
#*
#* @param numbers Numeric vector of values
#* @param operation:string Operation to perform: "mean", "median", "sum", "sd"
#* @param na_rm:bool Remove NA values (default: TRUE)
#* @post /stats
function(numbers, operation = "mean", na_rm = TRUE) {
if (is.character(numbers)) {
numbers <- as.numeric(strsplit(numbers, ",")[[1]])
}
result <- switch(operation,
"mean" = mean(numbers, na.rm = na_rm),
"median" = median(numbers, na.rm = na_rm),
"sum" = sum(numbers, na.rm = na_rm),
"sd" = sd(numbers, na.rm = na_rm),
stop("Unknown operation")
)
list(result = result, operation = operation)
}This creates an MCP tool with: - Full parameter descriptions - Type information (string, bool, number) - Default values - Required vs optional parameters
Working with Resources
Resources allow AI assistants to read content from your R environment, such as documentation, data descriptions, or analysis results.
Adding Custom Resources
pr("api.R") %>%
pr_mcp(transport = "stdio") %>%
# Add dataset information
pr_mcp_resource(
uri = "/data/iris-summary",
name = "Iris Dataset Summary",
description = "Statistical summary of the iris dataset",
func = function() {
paste(
"Dataset: iris",
paste("Dimensions:", paste(dim(iris), collapse = " x ")),
"",
capture.output(summary(iris)),
sep = "\n"
)
}
) %>%
# Add memory usage info
pr_mcp_resource(
uri = "/system/memory",
name = "R Memory Usage",
description = "Current R session memory statistics",
func = function() {
mem <- gc()
paste(
"R Memory Usage:",
paste("Used (Mb):", round(sum(mem[,2]), 2)),
sep = "\n"
)
}
)Built-in Help Resources
Quickly add R help documentation:
pr("api.R") %>%
pr_mcp(transport = "stdio") %>%
pr_mcp_help_resources()This adds resources for: - R help topics (/help/mean,
/help/lm) - Session information
(/r/session-info) - Installed packages
(/r/packages)
Working with Prompts
Prompts are reusable templates that guide AI assistants in interacting with your API.
Prompts with Arguments
pr("api.R") %>%
pr_mcp(transport = "stdio") %>%
pr_mcp_prompt(
name = "analyze-dataset",
description = "Generate analysis plan for a dataset",
arguments = list(
list(
name = "dataset",
description = "Name of the R dataset to analyze",
required = TRUE
),
list(
name = "focus",
description = "Specific aspect to focus on",
required = FALSE
)
),
func = function(dataset, focus = "general") {
sprintf(
paste(
"Please analyze the %s dataset in R.",
"Focus: %s",
"",
"Provide:",
"1. Summary statistics",
"2. Data quality assessment",
"3. Key insights",
sep = "\n"
),
dataset, focus
)
}
)Multi-turn Conversation Prompts
pr("api.R") %>%
pr_mcp(transport = "stdio") %>%
pr_mcp_prompt(
name = "code-review",
description = "Review R code for quality",
arguments = list(
list(name = "code", description = "R code to review", required = TRUE)
),
func = function(code) {
list(
list(
role = "user",
content = list(type = "text", text = paste("Review this R code:", code, sep = "\n"))
),
list(
role = "assistant",
content = list(type = "text", text = "I'll review your code for correctness and style.")
),
list(
role = "user",
content = list(type = "text", text = "Please provide specific suggestions.")
)
)
}
)Advanced Configuration
Filtering Endpoints
Include only specific endpoints:
pr("api.R") %>%
pr_mcp(
transport = "http",
include_endpoints = c("GET__echo", "POST__add")
) %>%
pr_run(port = 8000)Exclude specific endpoints:
Complete Example
Here’s a comprehensive example bringing it all together:
library(plumber)
library(plumber2mcp)
# Create the API
pr <- plumb(text = '
#* @apiTitle Statistical Analysis API
#* @apiDescription MCP-enabled API for statistical operations
#* Calculate descriptive statistics
#* @param values:string Comma-separated numeric values
#* @param stats:string Statistics to compute: "all", "mean", "median", "sd"
#* @post /describe
function(values, stats = "all") {
nums <- as.numeric(strsplit(values, ",")[[1]])
result <- list()
if (stats %in% c("all", "mean")) result$mean <- mean(nums, na.rm = TRUE)
if (stats %in% c("all", "median")) result$median <- median(nums, na.rm = TRUE)
if (stats %in% c("all", "sd")) result$sd <- sd(nums, na.rm = TRUE)
result
}
#* Perform t-test
#* @param group1:string Comma-separated values for group 1
#* @param group2:string Comma-separated values for group 2
#* @param paired:bool Whether to perform paired t-test
#* @post /ttest
function(group1, group2, paired = FALSE) {
g1 <- as.numeric(strsplit(group1, ",")[[1]])
g2 <- as.numeric(strsplit(group2, ",")[[1]])
test <- t.test(g1, g2, paired = paired)
list(
statistic = test$statistic,
p_value = test$p.value,
conf_int = test$conf.int,
method = test$method
)
}
')
# Add MCP capabilities
pr %>%
pr_mcp(
transport = "stdio",
server_name = "stats-api",
server_version = "1.0.0"
) %>%
# Add resource for available datasets
pr_mcp_resource(
uri = "/info/datasets",
name = "Available Datasets",
description = "List of R datasets available for analysis",
func = function() {
datasets <- data(package = "datasets")$results[, c("Item", "Title")]
paste(apply(datasets, 1, paste, collapse = ": "), collapse = "\n")
}
) %>%
# Add analysis prompt
pr_mcp_prompt(
name = "statistical-analysis",
description = "Guide for statistical analysis workflow",
arguments = list(
list(name = "question", description = "Research question", required = TRUE)
),
func = function(question) {
sprintf(
"Research Question: %s\n\nHelp me:\n1. Choose appropriate test\n2. Check assumptions\n3. Interpret results",
question
)
}
)Testing Your MCP Server
Direct HTTP Testing
For HTTP transport, test with curl:
# List tools
curl -X POST http://localhost:8000/mcp/messages \
-H 'Content-Type: application/json' \
-d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}'
# Call a tool
curl -X POST http://localhost:8000/mcp/messages \
-H 'Content-Type: application/json' \
-d '{"jsonrpc": "2.0", "id": 2, "method": "tools/call",
"params": {"name": "GET__echo", "arguments": {"msg": "Hello!"}}}'Best Practices
1. Document Everything
Use roxygen comments extensively:
#* Calculate correlation between two variables
#*
#* This endpoint computes Pearson or Spearman correlation
#* and provides confidence intervals and p-values.
#*
#* @param x:string Comma-separated numeric values for variable X
#* @param y:string Comma-separated numeric values for variable Y
#* @param method:string Correlation method: "pearson" or "spearman" (default: "pearson")
#* @param conf_level:number Confidence level for intervals (default: 0.95)
#* @post /correlate
function(x, y, method = "pearson", conf_level = 0.95) {
# Implementation...
}2. Provide Meaningful Defaults
Default values appear in the schema and help AI assistants:
function(data, method = "lm", alpha = 0.05, max_iter = 1000) {
# Defaults make it easy for AI to call with minimal parameters
}4. Add Resources for Context
Help AI assistants understand your data:
pr %>%
pr_mcp_resource(
uri = "/data/schema",
name = "Data Schema",
description = "Schema and description of available datasets",
func = function() {
# Return schema information
}
)5. Use Prompts for Workflows
Guide AI through complex multi-step processes:
pr %>%
pr_mcp_prompt(
name = "full-analysis",
description = "Complete data analysis workflow",
func = function() {
paste(
"Guide me through:",
"1. Data exploration",
"2. Statistical testing",
"3. Visualization",
"4. Interpretation",
sep = "\n"
)
}
)Next Steps
- Explore the package documentation
- Check out example APIs in
inst/examples/ - Read about the Model Context Protocol
- Try integrating with Claude Desktop or other MCP clients
Getting Help
- GitHub Issues: https://github.com/armish/plumber2mcp/issues
- Package documentation:
?pr_mcp - Examples:
system.file("examples", package = "plumber2mcp")