Skip to content

implement R as an MCP client #34

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open

implement R as an MCP client #34

wants to merge 16 commits into from

Conversation

simonpcouch
Copy link
Collaborator

@simonpcouch simonpcouch commented May 28, 2025

A proof of concept to close #29. Marking as draft as 1) this is a very scrappy implementation at the moment and 2) we would definitely want to revisit naming before merging. Also worth noting that this only implements the stdio protocol and not (yet) the http one.

With the GitHub MCP server mentioned in the docs configured:

library(acquaint)
  
ch <- ellmer::chat_anthropic()
#> Using model = "claude-3-7-sonnet-latest".

# currently have the github MCP server configured
ch$set_tools(mcp_tools())

ch$chat("What issues are open on posit-dev/acquaint?")
#> I'll help you find the open issues on the posit-dev/acquaint repository. Let me
#> search for that information.
#> ◯ [tool call] list_issues(owner = "posit-dev", repo = "acquaint", page = 1L,
#> perPage = 100L, state = "open", direction = "desc", since = "", sort =
#> "created")
#> ● #> {"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"[{\"i…
#> Here are the currently open issues on the posit-dev/acquaint repository:
#> 
#> 1. **Issue #29: Supporting "the other direction"**
#>    - Opened by: simonpcouch
#>    - Created on: May 20, 2025
#>    - This issue discusses adding functionality to allow ellmer chats to act as 
#> a client to other existing MCP servers, which would be the opposite direction 
#> of the current functionality (where acquaint allows clients to interface with R
#> sessions via an MCP server).
#> 
#> 2. **Issue #8: Does the server work with vscode mcp client?**
#>    - Opened by: sounkou-bioinfo
#>    - Created on: April 28, 2025
#>    - This issue reports problems using the acquaint server with VSCode Copilot.
#> The user is experiencing issues where VSCode gets stuck when trying to use 
#> GPT-4.1 or Claude 3.7 with the acquaint MCP server on Ubuntu 24.04.
#>    - This issue has 3 comments.
#> 
#> There are 2 open issues in total on the posit-dev/acquaint repository.

Created on 2025-05-28 with reprex v2.1.1

The first portion of this test should run with the configured GitHub secrets, and we can enable the second portion with a `live-api.yaml`
In attempt to resolve:

```
── Error ('test-client.R:25:3'): mcp_tools works ───────────────────────────────
<c_error/rlib_error_3_0/rlib_error/error/condition>
Error in `c("process_initialize(self, private, command, args, stdin, stdout, ",
"    stderr, pty, pty_options, connections, poll_connection, env, "
)`: ! Native call to `processx_exec` failed
Caused by error in `chain_call(c_processx_exec, command, c(command, args), pty, pty_options, ...`:
! cannot start processx process '' (system error 2, No such file or directory) @unix/processx.c:613 (processx_exec)
```
* Use `parse_json()` over `fromJSON()` to avoid conversion to data frame
* Handle every ellmer object type, including arrays/objects, and actually note if they're required or not

Still need to find a way to not set defaults in the function definition if the argument is required.
This Action fails on macOS and the post action runs indefinitely when that happens.
@simonpcouch
Copy link
Collaborator Author

Naming things feels really tricky here. Here's one proposal, but I'm not that excited about it:

R as... Current New
Server mcp_server() mcp_server()
mcp_session() mcp_session()
mcp_set_tools() mcp_server_tools()
Client mcp_tools() mcp_client_tools()
set_mcp_config() mcp_client_config()
  • In the "New" case mcp_*_tools() is both a getter and a setter :/
    • This might be made clearer by exporting a getter for the server tools?
  • I'd anticipate that what is currently mcp_tools() will get a lot of traffic, whereas mcp_set_tools() will get comparatively little.

@simonpcouch simonpcouch requested a review from gadenbuie June 6, 2025 18:30
@simonpcouch simonpcouch marked this pull request as ready for review June 6, 2025 18:30
Copy link
Collaborator Author

@simonpcouch simonpcouch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some specific bits where your intuition is appreciated, @gadenbuie :)

R/client.R Outdated
#' @returns
#' * `mcp_tools()` returns a list of ellmer tools that can be passed directly
#' to the `$set_tools()` method of an [ellmer::Chat] object.
#' * `set_mcp_config()` returns the path to the new MCP config file, invisibly.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you feel these two entry points are a reasonable interface to this functionality? i.e. 1) configure connections in the same way as Claude Desktop via a .json file, and then 2) collect them all as a $set_tools()-compatible list?

server_as_ellmer_tools <- function(server) {
tools <- server$tools$tools

tools_out <- list()
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be a browser()-worthy point, to poke at the tools object. Is this a reasonable way to go from that json to a list of ellmer tools? Does this duplicate anything in ellmer / fail to make use of relevant functions from that package?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could potentially build on the contents_record() and contents_replay() helper methods proposed by Barret over in tidyverse/ellmer#503. In that world, I think you'd want to create an McpToolDef class with ellmer:::ToolDef as its parent and then you'd have custom methods for each. (Or anyway, you'll probably want to keep contents_record() and contents_replay() in mind because their intended to support serializing a Chat object for bookmarking in shinychat.)

NAMESPACE Outdated
@@ -3,4 +3,6 @@
export(mcp_server)
export(mcp_session)
export(mcp_set_tools)
export(mcp_tools)
export(set_mcp_config)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts on better names certainly appreciated, though maybe this has much to do with #35.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think mcp_tools() needs a verb. Is it a counterpart to mcp_set_tools()? If so, then maybe mcp_get_tools()?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're right in #34 (comment) and I'd advocate for

  1. Sticking with mcp_server_ and mcp_client_ as prefixes
  2. Calling mcp_session() something else like mcp_server_add_session() or mcp_server_connect_session() or just mcp_server_connect() (I think you could get away with this last one if there's a hard line between mcp_server_ and mcp_client_.
  3. Separating mcp_{serer,client}_tools() into _get_tools() and _set_tools(), but only if both actions are relevant.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, I think you should also differentiate between mcp_server_ as in R-as-a-server and other external or remote MCP servers, which might use remote as the name. I think this would mean the$mcp_servers would be the$mcp_remote_servers or just the$mcp_remotes. Just in general, anything you can do to remove the cognitive overhead of "server" appearing all over the place would be great

`mcp_client_config()` is a getter, and its output can be adjusted by configuring an option. Users can just supply a different path to `mcp_tools()` to fetch a different set of tools.

Uses the newer suggested name since `set_*()` no longer makes sense, but otherwise doesn't make changes to naming.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Supporting "the other direction"
2 participants