Skip to contents

The MCPR framework is built on a robust client-server architecture designed to bridge the gap between stateless AI agents and the stateful, interactive nature of R programming. This article delves into the technical design, communication flow, and core principles that enable this integration.

At its heart, MCPR implements a client-server model with a private R runtime and optional session attachment, distinguishing it from typical stateless execution environments.

┌─────────────┐    JSON-RPC 2.0    ┌────────────────────────────┐
│  AI Agent   │<──────────────────>│ MCPR Server + Private R    │
│  (Client)   │     via stdin/     │ Session                    │
└─────────────┘     stdout         └─────────────┬──────────────┘
                                                  │ optional socket attach
                                                  ▼
                                      ┌────────────────────────┐
                                      │ Human/Secondary R      │
                                      │ Session                │
                                      └────────────────────────┘

This architecture consists of three primary components:

  1. The AI Agent (Client): This is the large language model operating within its environment (e.g., Claude Desktop, a VS Code extension). It communicates using a standardized JSON-RPC 2.0 protocol over stdin/stdout.
  2. The MCPR Server + Private R Session: The process running MCPR::mcpr_server(). It handles JSON-RPC and is also the default persistent R workspace where ordinary tools execute.
  3. Optional Attached R Sessions: A human-owned interactive R session started with mcpr_session_start(), or an MCPR-owned secondary process created with manage_r_sessions("start"). These sessions are used only after explicit attachment.

Communication Workflow

The default workflow is direct: the MCP client launches MCPR::mcpr_server(), and ordinary tools execute in that private process. Optional attachment adds discovery and connection steps only when the user wants the agent to work inside another R session.

Optional Session Discovery and Connection

Before an agent can execute code in a human-owned session, it must discover and attach to that session. This process is separate from ordinary tool calls; ordinary tools do not take a session argument.

sequenceDiagram
    participant AI as AI Agent
    participant Server as MCPR Server
    participant Session as R Session

    Note over Session: User runs mcpr_session_start()
    Session->>Session: Creates mcprSession instance & finds an open port
    Session->>Session: Starts an asynchronous listener on a nanonext socket

    Note over AI: User asks agent to connect to R
    AI->>Server: Launches MCPR Server process
    Server->>Server: Initializes private/local active session
    AI->>Server: Sends 'manage_r_sessions("list")'
    Server->>Server: Scans local ports for attachable sessions
    Server->>Session: Sends discovery ping via socket
    Session->>Server: Responds with session metadata (ID, PID, etc.)
    Server->>AI: Forwards available session list (result of 'manage_r_sessions("list")')

    Note over AI: User asks agent to join a specific session
    AI->>Server: Sends 'manage_r_sessions("join", session=ID)'
    Server->>Session: Establishes persistent connection for attached execution
    Session->>Server: Acknowledges connection
    Server->>AI: Confirms the attached session is now active

Tool Execution

The agent can use provided tools (execute_r_code, show_plot, etc.) immediately in the private session. If manage_r_sessions() has attached another session, the runtime sends ordinary tools to that active session until detach returns execution to private/local.

sequenceDiagram
    participant AI as AI Agent
    participant Server as MCPR Server
    participant Session as R Session
    participant Tool as Tool Function

    AI->>Server: JSON-RPC Request: {tool: "execute_r_code", args: {code: "..."}, ...}
    Server->>Server: Validates tool exists
    Server->>Server: Resolves active session (private by default)
    Server->>Tool: Executes locally, or forwards only when attached
    Tool->>Server: Returns a standard R object (list, data.frame, etc.)
    Server->>Server: Serializes the R result into a type-preserving JSON structure
    Server->>AI: Forwards the final JSON response via stdout

Key Design Principles

The architecture is guided by several modern R development practices to ensure it is robust, maintainable, and extensible.

R6 Class-Based System

MCPR heavily utilizes the R6 class system to manage state and behavior.

  • BaseMCPR: A foundational class providing shared utilities like logging, state management, and resource cleanup.
  • mcprServer & mcprClient: Concrete implementations for the server/runtime and client roles, inheriting common functionality from BaseMCPR.
  • mcprSessionManager: Owns the server-local active session binding, including private/local execution and optional attachment state.
  • mcprSession: Manages the lifecycle of a single R session listener, including handling timeouts and ensuring proper resource cleanup on exit.

Layered Communication

The communication stack is divided into distinct layers, each with a specific responsibility:

  1. Protocol Layer: Handles the JSON-RPC 2.0 message structure, including requests, responses, and error notifications.
  2. Transport Layer: Manages asynchronous, non-blocking communication using the nanonext package, which provides a robust socket-based messaging implementation.
  3. Execution Layer: Dispatches requests to tool functions in the private session by default, or to the active attached session when session management has explicitly attached one.
  4. Type Conversion Layer: A critical component that handles the serialization (R to JSON) and deserialization (JSON to R) of data, preserving R’s specific data types across the wire.

Advanced Type Preservation

A significant challenge in cross-language communication is preserving data fidelity. A data.frame in R is not the same as a generic JSON array of objects. MCPR solves this by using a custom serialization system.

When an R object is converted to JSON, a special _mcp_type marker is added to annotate the data. When the JSON is received, this marker is used to reconstruct the original R object with its correct class and attributes (e.g., factor, Date, data.frame). This ensures that code executed by the agent behaves exactly as if it were run directly in the R console.

sequenceDiagram
    participant R_Side as R Environment
    participant JSON_Transport as JSON Payload
    participant Target_Side as Receiving Environment

    Note over R_Side: Has a data.frame object
    R_Side->>R_Side: to_mcpr_json() serializes it
    R_Side->>JSON_Transport: Sends JSON with {"_mcp_type": "data.frame", "value": [...] }
    JSON_Transport->>Target_Side: Receives JSON with type metadata
    Target_Side->>Target_Side: from_mcpr_json() uses "_mcp_type" to reconstruct the data.frame
    Note over Target_Side: Now has a native data.frame object

This bidirectional, type-preserving communication is fundamental to MCPR’s ability to maintain the integrity of the R workspace throughout a long and complex human-AI collaborative session.