Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .Rbuildignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
^acquaint\.Rproj$
^mcptools\.Rproj$
^\.Rproj\.user$
^LICENSE\.md$
^README\.Rmd$
Expand Down
8 changes: 4 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## Overview

The acquaint package uses nanonext for inter-process communication between the MCP server and R sessions. nanonext provides asynchronous messaging using the nanomsg/nng protocols.
The mcptools package uses nanonext for inter-process communication between the MCP server and R sessions. nanonext provides asynchronous messaging using the nanomsg/nng protocols.

## Key Concepts

Expand All @@ -15,7 +15,7 @@ The acquaint package uses nanonext for inter-process communication between the M
- **dial/listen**: Client dials to connect, server listens for connections
- **pipe IDs**: poly sockets can multiplex multiple conversations

## acquaint Architecture
## mcptools Architecture

### Server Process
The MCP server (`mcp_server()`) runs in its own R process and:
Expand All @@ -40,7 +40,7 @@ The server uses a condition variable (`cv`) to coordinate multiple async operati

## Socket URLs and Connection Management

- Sessions listen on `inproc://acquaint-session-{i}` where `i` is auto-incremented
- Server dials to `inproc://acquaint-session-1` by default
- Sessions listen on `inproc://mcptools-session-{i}` where `i` is auto-incremented
- Server dials to `inproc://mcptools-session-1` by default
- `inproc://` transport is fast for same-machine communication
- Connections are cleaned up with `nanonext::reap()` on exit
6 changes: 3 additions & 3 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Package: acquaint
Package: mcptools
Title: Model Context Protocol Server For Your R Sessions
Version: 0.0.0.9000
Authors@R: c(
Expand Down Expand Up @@ -35,7 +35,7 @@ Imports:
promises,
rlang
Depends: R (>= 4.1.0)
URL: https://github.com/posit-dev/acquaint, https://posit-dev.github.io/acquaint/
BugReports: https://github.com/posit-dev/acquaint/issues
URL: https://github.com/posit-dev/mcptools, https://posit-dev.github.io/mcptools/
BugReports: https://github.com/posit-dev/mcptools/issues
Config/Needs/website: tidyverse/tidytemplate
VignetteBuilder: knitr
26 changes: 13 additions & 13 deletions R/client.R
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ the$mcp_servers <- list()
#' register functionality from third-party MCP servers such as those listed
#' here: <https://github.com/modelcontextprotocol/servers>.
#'
#' `mcp_tools()` fetches tools from MCP servers configured in the acquaint
#' `mcp_tools()` fetches tools from MCP servers configured in the mcptools
#' server config file and converts them to a list of
#' tools compatible with the `$set_tools()` method of [ellmer::Chat] objects.
#'
#' @param config A single string indicating the path to the acquaint MCP servers
#' configuration file. If one is not supplied, acquaint will look for one at
#' the file path configured with the option `.acquaint_config`, falling back to
#' `file.path("~", ".config", "acquaint", "config.json")`.
#' @param config A single string indicating the path to the mcptools MCP servers
#' configuration file. If one is not supplied, mcptools will look for one at
#' the file path configured with the option `.mcptools_config`, falling back to
#' `file.path("~", ".config", "mcptools", "config.json")`.
#'
#' @seealso
#' This function implements R as an MCP _client_. To use R as an MCP _server_,
Expand All @@ -31,13 +31,13 @@ the$mcp_servers <- list()
#'
#' @section Configuration:
#'
#' acquaint uses the same .json configuration file format as Claude Desktop;
#' mcptools uses the same .json configuration file format as Claude Desktop;
#' most MCP servers will define example .json to configure the server with
#' Claude Desktop in their README files. By default, acquaint will look to
#' `file.path("~", ".config", "acquaint", "config.json")`; you can edit that
#' file with `file.edit(file.path("~", ".config", "acquaint", "config.json"))`.
#' Claude Desktop in their README files. By default, mcptools will look to
#' `file.path("~", ".config", "mcptools", "config.json")`; you can edit that
#' file with `file.edit(file.path("~", ".config", "mcptools", "config.json"))`.
#'
#' The acquaint config file should be valid .json with an entry `mcpServers`.
#' The mcptools config file should be valid .json with an entry `mcpServers`.
#' That entry should contain named elements, each with at least a `command`
#' and `args` entry.
#'
Expand Down Expand Up @@ -118,13 +118,13 @@ mcp_tools <- function(config = NULL) {

mcp_client_config <- function() {
getOption(
".acquaint_config",
".mcptools_config",
default = default_mcp_client_config()
)
}

default_mcp_client_config <- function() {
file.path("~", ".config", "acquaint", "config.json")
file.path("~", ".config", "mcptools", "config.json")
}

read_mcp_config <- function(config, call = caller_env()) {
Expand Down Expand Up @@ -172,7 +172,7 @@ read_mcp_config <- function(config, call = caller_env()) {
error_no_mcp_config <- function(call) {
cli::cli_abort(
c(
"The acquaint MCP client configuration file does not exist.",
"The mcptools MCP client configuration file does not exist.",
i = "Supply a non-NULL file {.arg config} or create a file at the default
configuration location {.file {default_mcp_client_config()}}."
),
Expand Down
4 changes: 2 additions & 2 deletions R/envvars.R
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
acquaint_log_file <- function() {
Sys.getenv("ACQUAINT_LOG_FILE", tempfile(fileext = ".txt"))
mcptools_log_file <- function() {
Sys.getenv("mcptools_LOG_FILE", tempfile(fileext = ".txt"))
}
6 changes: 3 additions & 3 deletions R/acquaint-package.R → R/mcptools-package.R
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ NULL
.onLoad <- function(libname, pkgname) {
the$socket_url <- switch(
Sys.info()[["sysname"]],
Linux = "abstract://acquaint-socket",
Windows = "ipc://acquaint-socket",
"ipc:///tmp/acquaint-socket"
Linux = "abstract://mcptools-socket",
Windows = "ipc://mcptools-socket",
"ipc:///tmp/mcptools-socket"
)
}
16 changes: 8 additions & 8 deletions R/server.R
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@
#' mcp_server(tools = list(tool_rnorm))
#'
#' # can also supply a file path as `tools`
#' readLines(system.file("example-ellmer-tools.R", package = "acquaint"))
#' readLines(system.file("example-ellmer-tools.R", package = "mcptools"))
#'
#' mcp_server(tools = system.file("example-ellmer-tools.R", package = "acquaint"))
#' mcp_server(tools = system.file("example-ellmer-tools.R", package = "mcptools"))
#' }
mcp_server <- function(tools = NULL) {
# TODO: should this actually be a check for being called within Rscript or not?
Expand Down Expand Up @@ -100,15 +100,15 @@ handle_message_from_client <- function(line) {
res <- jsonrpc_response(
data$id,
list(
tools = get_acquaint_tools_as_json()
tools = get_mcptools_tools_as_json()
)
)

cat_json(res)
} else if (data$method == "tools/call") {
tool_name <- data$params$name
if (
# two tools provided by acquaint itself which must be executed in
# two tools provided by mcptools itself which must be executed in
# the server rather than a session (#18)
tool_name %in%
c("list_r_sessions", "select_r_session") ||
Expand Down Expand Up @@ -158,7 +158,7 @@ forward_request <- function(data) {
# visible. This function will log output to the `logfile` so that you can view
# it.
logcat <- function(x, ..., append = TRUE) {
log_file <- acquaint_log_file()
log_file <- mcptools_log_file()
cat(x, "\n", sep = "", append = append, file = log_file)
}

Expand All @@ -183,7 +183,7 @@ capabilities <- function() {
)
),
serverInfo = list(
name = "R acquaint server",
name = "R mcptools server",
version = "0.0.1"
),
instructions = "This provides information about a running R session."
Expand Down Expand Up @@ -245,7 +245,7 @@ append_tool_fn <- function(data) {

tool_name <- data$params$name

if (!tool_name %in% names(get_acquaint_tools())) {
if (!tool_name %in% names(get_mcptools_tools())) {
return(structure(
jsonrpc_response(
data$id,
Expand All @@ -255,6 +255,6 @@ append_tool_fn <- function(data) {
))
}

data$tool <- get_acquaint_tools()[[tool_name]]@fun
data$tool <- get_mcptools_tools()[[tool_name]]@fun
data
}
12 changes: 6 additions & 6 deletions R/session.R
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
#' ```json
#' {
#' "mcpServers": {
#' "r-acquaint": {
#' "r-mcptools": {
#' "command": "Rscript",
#' "args": ["-e", "acquaint::mcp_server()"]
#' "args": ["-e", "mcptools::mcp_server()"]
#' }
#' }
#' }
Expand All @@ -25,20 +25,20 @@
#' Or, to use with Claude Code, you might type in a terminal:
#'
#' ```bash
#' claude mcp add -s "user" r-acquaint Rscript -e "acquaint::mcp_server()"
#' claude mcp add -s "user" r-mcptools Rscript -e "mcptools::mcp_server()"
#' ```
#'
#' **mcp_server() is not intended for interactive use.**
#'
#' The server interfaces with the MCP client on behalf of your R session.
#' **Use [mcp_session()] to make your R session available to the server.**
#' Place a call to `acquaint::mcp_session()` in your `.Rprofile`, perhaps with
#' Place a call to `mcptools::mcp_session()` in your `.Rprofile`, perhaps with
#' `usethis::edit_r_profile()`, to make every interactive R session you start
#' available to the server.
#'
#' @seealso
#' - The "R as an MCP server" vignette at
#' `vignette("server", package = "acquaint")` delves into further detail
#' `vignette("server", package = "mcptools")` delves into further detail
#' on setup and customization.
#' - These functions implement R as an MCP _server_. To use R as an MCP _client_,
#' i.e. to configure tools from third-party MCP servers with ellmer chats, see
Expand All @@ -53,7 +53,7 @@
mcp_session <- function() {
# HACK: If a session is already available from another session via `.Rprofile`,
# `mcp_session()` will be called again when the client runs the command
# Rscript -e "acquaint::mcp_server()" and the existing session connection
# Rscript -e "mcptools::mcp_server()" and the existing session connection
# will be wiped. Returning early in this case allows for the desired R
# session to be running already before the client initiates the server.
if (!interactive()) {
Expand Down
12 changes: 6 additions & 6 deletions R/tools.R
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ set_server_tools <- function(x, x_arg = caller_arg(x), call = caller_env()) {
) {
cli::cli_abort(
"The tool names {.field list_r_sessions} and {.field select_r_session} are
reserved by {.pkg acquaint}.",
reserved by {.pkg mcptools}.",
call = call
)
}
Expand All @@ -65,7 +65,7 @@ source_tools <- function(x) {
}

# These two functions are supplied to the client as tools and allow the client
# to discover R sessions which have called `acquaint::mcp_session()`. They
# to discover R sessions which have called `mcptools::mcp_session()`. They
# are "model-facing" rather than user-facing.
list_r_sessions <- function() {
sock <- nanonext::socket("poly")
Expand Down Expand Up @@ -102,7 +102,7 @@ list_r_sessions_tool <-
.fun = list_r_sessions,
.description = paste(
"List the R sessions that are available to access.",
"R sessions which have run `acquaint::mcp_session()` will appear here.",
"R sessions which have run `mcptools::mcp_session()` will appear here.",
"In the output, start each session with 'Session #' and do NOT otherwise",
"prefix any index numbers to the output.",
"In general, do not use this tool unless asked to list or",
Expand Down Expand Up @@ -141,14 +141,14 @@ select_r_session_tool <-
session = ellmer::type_integer("The R session number to select.")
)

get_acquaint_tools <- function() {
get_mcptools_tools <- function() {
# must be called inside of the server session
res <- the$server_tools
set_names(res, vapply(res, \(x) x@name, character(1)))
}

get_acquaint_tools_as_json <- function() {
tools <- lapply(unname(get_acquaint_tools()), tool_as_json)
get_mcptools_tools_as_json <- function() {
tools <- lapply(unname(get_mcptools_tools()), tool_as_json)

compact(tools)
}
Expand Down
34 changes: 17 additions & 17 deletions README.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,25 @@ knitr::opts_chunk$set(
)
```

# acquaint <a href="https://simonpcouch.github.io/acquaint/"><img src="man/figures/logo.png" align="right" height="240" alt="A hexagonal logo showing a sparse, forested path opening up into a well-trodden meadow path." /></a>
# mcptools <a href="https://simonpcouch.github.io/mcptools/"><img src="man/figures/logo.png" align="right" height="240" alt="A hexagonal logo showing a sparse, forested path opening up into a well-trodden meadow path." /></a>

<!-- badges: start -->
[![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental)
[![CRAN status](https://www.r-pkg.org/badges/version/acquaint)](https://CRAN.R-project.org/package=acquaint)
[![R-CMD-check](https://github.com/simonpcouch/acquaint/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/simonpcouch/acquaint/actions/workflows/R-CMD-check.yaml)
[![CRAN status](https://www.r-pkg.org/badges/version/mcptools)](https://CRAN.R-project.org/package=mcptools)
[![R-CMD-check](https://github.com/simonpcouch/mcptools/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/simonpcouch/mcptools/actions/workflows/R-CMD-check.yaml)
<!-- badges: end -->

acquaint implements the [Model Context
mcptools implements the [Model Context
Protocol](https://modelcontextprotocol.io/) in R. There are two sides to
acquaint:
mcptools:

- **R as an MCP server**: When configured with acquaint, MCP-enabled
- **R as an MCP server**: When configured with mcptools, MCP-enabled
tools like Claude Desktop, Claude Code, and VS Code GitHub Copilot can
run R code *in the sessions you have running* to answer your
questions. While the package supports configuring arbitrary R
functions, you may be interested in the
[btw](https://posit-dev.github.io/btw/) package’s integrated support for
acquaint, which provides a default set of tools to to peruse the
mcptools, which provides a default set of tools to to peruse the
documentation of packages you have installed, check out the objects in your
global environment, and retrieve metadata about your session and platform.
- **R as an MCP client**: Register third-party MCP servers with ellmer
Expand All @@ -44,22 +44,22 @@ acquaint:

## Installation

You can install the development version of acquaint like so:
You can install the development version of mcptools like so:

```r
pak::pak("posit-dev/acquaint")
pak::pak("posit-dev/mcptools")
```

### R as an MCP server

acquaint can be hooked up to any application that supports MCP. For example, to use with Claude Desktop, you might paste the following in your Claude Desktop configuration (on macOS, at `~/Library/Application Support/Claude/claude_desktop_config.json`):
mcptools can be hooked up to any application that supports MCP. For example, to use with Claude Desktop, you might paste the following in your Claude Desktop configuration (on macOS, at `~/Library/Application Support/Claude/claude_desktop_config.json`):

```json
{
"mcpServers": {
"r-acquaint": {
"r-mcptools": {
"command": "Rscript",
"args": ["-e", "acquaint::mcp_server()"]
"args": ["-e", "mcptools::mcp_server()"]
}
}
}
Expand All @@ -68,14 +68,14 @@ acquaint can be hooked up to any application that supports MCP. For example, to
Or, to use with Claude Code, you might type in a terminal:

```bash
claude mcp add -s "user" r-acquaint -- Rscript -e "acquaint::mcp_server()"
claude mcp add -s "user" r-mcptools -- Rscript -e "mcptools::mcp_server()"
```

Then, if you'd like models to access variables in specific R sessions, call `acquaint::mcp_session()` in those sessions. (You might include a call to this function in your .Rprofile, perhaps using `usethis::edit_r_profile()`, to automatically register every session you start up.)
Then, if you'd like models to access variables in specific R sessions, call `mcptools::mcp_session()` in those sessions. (You might include a call to this function in your .Rprofile, perhaps using `usethis::edit_r_profile()`, to automatically register every session you start up.)

### R as an MCP client

acquaint uses the Claude Desktop configuration file format to register third-party MCP servers, as most MCP servers provide setup instructions for Claude Desktop in their documentation. For example, here's what the [official GitHub MCP server](https://github.com/github/github-mcp-server) configuration would look like:
mcptools uses the Claude Desktop configuration file format to register third-party MCP servers, as most MCP servers provide setup instructions for Claude Desktop in their documentation. For example, here's what the [official GitHub MCP server](https://github.com/github/github-mcp-server) configuration would look like:

```json
{
Expand All @@ -98,13 +98,13 @@ acquaint uses the Claude Desktop configuration file format to register third-par
}
```

Once the configuration file has been created (by default, acquaint will look to `file.path("~", ".config", "acquaint", "config.json")`), `mcp_tools()` will return a list of ellmer tools which you can pass directly to the `$set_tools()` method from ellmer:
Once the configuration file has been created (by default, mcptools will look to `file.path("~", ".config", "mcptools", "config.json")`), `mcp_tools()` will return a list of ellmer tools which you can pass directly to the `$set_tools()` method from ellmer:

```
ch <- ellmer::chat_anthropic()
ch$set_tools(mcp_tools())

ch$chat("What issues are open on posit-dev/acquaint?")
ch$chat("What issues are open on posit-dev/mcptools?")
```

## Example
Expand Down
Loading