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
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

export(mcp_server)
export(mcp_session)
export(mcp_set_tools)
import(rlang)
74 changes: 73 additions & 1 deletion R/tools.R
Original file line number Diff line number Diff line change
@@ -1,3 +1,75 @@
#' Set the tools available to run in your R session
#'
#' @description
#' By default, acquaint supplies tools from [btw::btw_tools()] to allow clients
#' to peruse package documentation, inspect your global environment, and query
#' session details. This function allows you register any tools created with
#' [ellmer::tool()] instead.
#'
#' A call to this function must be placed in your `.Rprofile` and the client
#' (i.e. Claude Desktop or Claude Code) restarted in order for the new tools
#' to be registered.
#'
#' acquaint will always register the tools "list_r_sessions" and
#' "select_r_session" in addition to the tools provided here; those tool names
#' are thus reserved for the package.
#'
#' @param x A list of tools created with [ellmer::tool()]. Any list that could
#' be passed to `chat$set_tools()` can be passed here.
#'
#' @returns
#' `x`, invisibly. Called for side effects. The function will error if `x` is
#' not a list of `ellmer::ToolDef` objects or if any tool name is one of the
#' reserved names "list_r_sessions" or "select_r_session".
#'
#' @examples
#' library(ellmer)
#'
#' tool_rnorm <- tool(
#' rnorm,
#' "Draw numbers from a random normal distribution",
#' n = type_integer("The number of observations. Must be a positive integer."),
#' mean = type_number("The mean value of the distribution."),
#' sd = type_number("The standard deviation of the distribution. Must be a non-negative number.")
#' )
#'
#' # supply only one tool, tool_rnorm
#' mcp_set_tools(list(tool_rnorm))
#'
#' # supply both tool_rnorm and `btw_tools()`
#' mcp_set_tools(c(list(tool_rnorm), btw::btw_tools()))
#' @export
mcp_set_tools <- function(x) {
check_acquaint_tools(x)

options(.acquaint_tools = x)

invisible(x)
}

check_acquaint_tools <- function(x, call = caller_env()) {
if (!is_list(x) || !all(vapply(x, inherits, logical(1), "ellmer::ToolDef"))) {
msg <- "{.arg x} must be a list of tools created with {.fn ellmer::tool}."
if (inherits(x, "ellmer::ToolDef")) {
msg <- c(msg, "i" = "Did you mean to wrap {.arg x} in `list()`?")
}
cli::cli_abort(msg, call = call)
}

if (
any(
vapply(x, \(.x) .x@name, character(1)) %in%
c("list_r_sessions", "select_r_session")
)
) {
cli::cli_abort(
"The tool names {.field list_r_sessions} and {.field select_r_session} are
reserved by {.pkg acquaint}.",
call = call
)
}
}

# 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
# are "model-facing" rather than user-facing.
Expand Down Expand Up @@ -77,7 +149,7 @@ select_r_session_tool <-

get_acquaint_tools <- function() {
res <- c(
btw::btw_tools(),
getOption(".acquaint_tools", default = btw::btw_tools()),
list(
list_r_sessions_tool,
select_r_session_tool
Expand Down
48 changes: 48 additions & 0 deletions man/mcp_set_tools.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions tests/testthat/_snaps/tools.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# mcp_set_tools works

Code
mcp_set_tools("boop")
Condition
Error in `mcp_set_tools()`:
! `x` must be a list of tools created with `ellmer::tool()`.

---

Code
mcp_set_tools(tool_rnorm)
Condition
Error in `mcp_set_tools()`:
! `x` must be a list of tools created with `ellmer::tool()`.
i Did you mean to wrap `x` in `list()`?

---

Code
mcp_set_tools(list(tool_rnorm))
Condition
Error in `mcp_set_tools()`:
! The tool names list_r_sessions and select_r_session are reserved by acquaint.

33 changes: 33 additions & 0 deletions tests/testthat/test-tools.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
test_that("mcp_set_tools works", {
old_option <- getOption("acquaint_tools")
on.exit(options(.acquaint_tools = old_option))

# must be a list
expect_snapshot(error = TRUE, mcp_set_tools("boop"))

tool_rnorm <- ellmer::tool(
rnorm,
"Draw numbers from a random normal distribution",
n = ellmer::type_integer(
"The number of observations. Must be a positive integer."
),
mean = ellmer::type_number("The mean value of the distribution."),
sd = ellmer::type_number(
"The standard deviation of the distribution. Must be a non-negative number."
)
)
tool_rnorm_list <- list(tool_rnorm)

# tools themselves need to be in a list
expect_snapshot(error = TRUE, mcp_set_tools(tool_rnorm))

# uses reserved name
tool_rnorm@name <- "list_r_sessions"
expect_snapshot(error = TRUE, mcp_set_tools(list(tool_rnorm)))

expect_equal(mcp_set_tools(tool_rnorm_list), tool_rnorm_list)
expect_equal(
names(get_acquaint_tools()),
c("rnorm", "list_r_sessions", "select_r_session")
)
})