
MCP Integration: From Development to Production
mcp-integration.Rmd
Introduction
The Model Context Protocol (MCP) is a standardized way to connect AI assistants with external tools and data sources. The llmr package provides seamless integration with MCP through the mcpr package, enabling a powerful “lift and shift” approach to tool development.
This vignette demonstrates how to take the calculator tool from the “Get Started” guide and deploy it as a production-ready MCP server, while maintaining the same simple client interface.
What You’ll Learn
By the end of this guide, you’ll understand how to:
- Transform inline tools into standalone MCP servers
- Connect llmr agents to external MCP services
- Leverage the “lift and shift” approach for production deployment
- Scale your tool architecture from development to production
The Development-to-Production Journey
Development Phase: Inline Tools
In the “Get Started” vignette, we created a calculator tool directly within our agent:
# Development approach - tool embedded in agent
calculator <- new_tool(
name = "calculator",
description = "Performs basic arithmetic operations",
input_schema = schema(
properties = properties(
operation = property_enum(
"Operation",
"Math operation to perform",
values = c("add", "subtract", "multiply", "divide"),
required = TRUE
),
a = property_number("First number", "First operand", required = TRUE),
b = property_number("Second number", "Second operand", required = TRUE)
)
),
handler = function(params) {
result <- switch(
params$operation,
"add" = params$a + params$b,
"subtract" = params$a - params$b,
"multiply" = params$a * params$b,
"divide" = params$a / params$b
)
response_text(result)
}
)
agent <- add_tool(agent, calculator)
This approach is perfect for: - Rapid prototyping - Testing new functionality - Simple, single-use tools
Production Phase: MCP Servers
For production deployment, we can “lift and shift” the same tool logic into an MCP server:
# Production approach - tool as external MCP server
client <- new_client_io(command = "Rscript", args = "calculator-server.R")
agent <- register_mcp(agent, client)
This approach provides: - Scalability - Tools run as separate processes - Reusability - Multiple agents can share the same tools - Maintainability - Tools can be updated independently - Security - Tools run in isolated environments
Step 1: Understanding the MCP Server
Let’s transform our calculator tool into a standalone MCP server. Here’s what the server code looks like:
library(mcpr)
# Create the same calculator tool from the Get Started guide
calculator <- new_tool(
name = "calculator",
description = "Performs basic arithmetic operations",
input_schema = schema(
properties = properties(
operation = property_enum(
"Operation",
"Math operation to perform",
values = c("add", "subtract", "multiply", "divide"),
required = TRUE
),
a = property_number("First number", "First operand", required = TRUE),
b = property_number("Second number", "Second operand", required = TRUE)
)
),
handler = function(params) {
result <- switch(
params$operation,
"add" = params$a + params$b,
"subtract" = params$a - params$b,
"multiply" = params$a * params$b,
"divide" = params$a / params$b
)
response_text(result)
}
)
# Create an MCP server and add the calculator tool
mcp_server <- new_server(
name = "Calculator Server",
description = "A production-ready calculator service",
version = "1.0.0"
)
# Add the tool to the server
mcp_server <- add_capability(mcp_server, calculator)
# Start the server (listening on stdin/stdout)
serve_io(mcp_server)
Key Insight: Notice how the tool definition is identical to the inline version. This is the power of the “lift and shift” approach - no code changes required!
The llmr package includes this exact server as a ready-to-use example:
Step 2: Create the MCP Client
Now let’s create an agent that connects to our MCP server:
library(llmr)
#>
#> Attaching package: 'llmr'
#> The following object is masked from 'package:stats':
#>
#> step
library(mcpr)
#>
#> Attaching package: 'mcpr'
#> The following object is masked from 'package:methods':
#>
#> initialize
#> The following object is masked from 'package:base':
#>
#> write
library(ellmer)
# Create an agent with ellmer chat object
agent <- new_agent("calculator", ellmer::chat_anthropic())
#> Using model = "claude-sonnet-4-20250514".
Step 3: Connect to the MCP Server
Instead of adding tools directly, we’ll connect to our external MCP server. The llmr package includes a ready-to-use calculator server:
# Get the path to the example calculator server included with llmr
server_path <- system.file("examples", "calculator-server.R", package = "llmr")
# Create an MCP client that connects to our calculator server
calculator_client <- new_client_io(
command = "Rscript",
args = server_path,
name = "calculator"
)
# Register the MCP client with our agent
agent <- register_mcp(agent, calculator_client)
What’s happening here: -
new_client_io()
creates a client that launches our server
as a subprocess - The server communicates via stdin/stdout (standard MCP
protocol) - register_mcp()
makes all server tools available
to the agent
Step 4: Use the Agent (Same Interface!)
The beauty of this approach is that the client interface remains exactly the same:
# Same usage as the inline tool version
request(agent, "What is 25 multiplied by 4?")
#> Warning in read.client_io(x, timeout): Timeout waiting for client response
get_last_message(agent)
#> assistant: 25 multiplied by 4 equals 100.
# Complex calculations work the same way
request(agent, "I have 150 dollars, spend 47.50 on dinner and 23.75 on a book. How much is left?")
#> Warning in read.client_io(x, timeout): Timeout waiting for client response
#> Warning in read.client_io(x, timeout): Timeout waiting for client response
get_last_message(agent)
#> assistant: After spending $47.50 on dinner and $23.75 on a book, you have $78.75 left from your original $150.
The “Lift and Shift” Advantage
Development Workflow
- Start with inline tools for rapid prototyping
- Test and refine your tool logic
- Lift the tool definition to an MCP server
- Shift your agent to use the MCP client
- Deploy to production with no client code changes
Benefits of This Approach
🚀 Scalability
# Multiple agents can share the same calculator service
agent1 <- new_agent("financial_advisor", ellmer::chat_anthropic())
agent2 <- new_agent("math_tutor", ellmer::chat_anthropic())
agent3 <- new_agent("data_analyst", ellmer::chat_anthropic())
# All connect to the same MCP server
register_mcp(agent1, calculator_client)
register_mcp(agent2, calculator_client)
register_mcp(agent3, calculator_client)
🔄 Reusability
- One calculator server serves multiple use cases
- Tools become organizational assets, not agent-specific code
- Easy to discover and share tools across teams
Advanced MCP Patterns
Multiple MCP Services
You can connect to multiple MCP servers for different capabilities:
# Connect to different specialized services
calculator_client <- new_client_io(command = "Rscript", args = "calculator-server.R")
weather_client <- new_client_io(command = "Rscript", args = "weather-server.R")
database_client <- new_client_io(command = "Rscript", args = "database-server.R")
# Register all services with the agent
agent <- register_mcp(agent, calculator_client)
agent <- register_mcp(agent, weather_client)
agent <- register_mcp(agent, database_client)
Hybrid Approach
You can mix inline tools with MCP services:
# Quick inline tool for simple tasks
simple_tool <- new_tool(name = "timestamp", ...)
agent <- add_tool(agent, simple_tool)
# External MCP service for complex operations
complex_client <- new_client_io(...)
agent <- register_mcp(agent, complex_client)
Production Deployment Considerations
Server Management
- Use process managers (systemd, Docker, etc.) for MCP servers
- Implement health checks and automatic restarts
- Monitor server performance and resource usage
Migration Strategy
Phase 1: Development
# Start with inline tools
agent <- add_tool(agent, my_tool)
Phase 2: Testing
# Test MCP version alongside inline version
agent <- add_tool(agent, my_tool) # Keep original
agent <- register_mcp(agent, mcp_client) # Add MCP version
Phase 3: Production
# Remove inline tool, use only MCP
agent <- register_mcp(agent, mcp_client)
Summary
The MCP integration in llmr provides a seamless path from development to production:
- Develop quickly with inline tools
- Lift tool definitions to MCP servers without code changes
- Shift agents to use MCP clients with minimal changes
- Scale confidently with production-ready architecture
This approach gives you the best of both worlds: the speed of inline development and the robustness of distributed production systems.
Key Takeaways
- Same tool logic works in both inline and MCP modes
- Client interface remains unchanged during migration
- MCP provides production benefits without development complexity
- Gradual migration is possible and recommended
The Model Context Protocol integration makes llmr a powerful platform for building scalable AI agent systems that can grow from prototype to production seamlessly.