@@ -38,12 +38,12 @@ the$mcp_servers <- list()
3838# ' file with `file.edit(file.path("~", ".config", "mcptools", "config.json"))`.
3939# '
4040# ' The mcptools config file should be valid .json with an entry `mcpServers`.
41- # ' That entry should contain named elements, each configured for either
42- # ' **stdio** or **HTTP** transport .
41+ # ' That entry should contain named elements, each with at least a `command`
42+ # ' and `args` entry .
4343# '
44- # ' ## Local servers (via stdio)
45- # '
46- # ' For stdio-based servers, provide `command` and `args` entries :
44+ # ' For example, to configure `mcp_tools()` with GitHub's official MCP Server
45+ # ' <https://github.com/github/github-mcp-server>, you could write the following
46+ # ' in that file :
4747# '
4848# ' ```json
4949# ' {
@@ -66,23 +66,6 @@ the$mcp_servers <- list()
6666# ' }
6767# ' ```
6868# '
69- # ' ## Remote servers (via http)
70- # '
71- # ' For HTTP-based servers, provide a `url` entry instead of `command`/`args`:
72- # '
73- # ' ```json
74- # ' {
75- # ' "mcpServers": {
76- # ' "local-http": {
77- # ' "url": "https://localhost:8080"
78- # ' },
79- # ' "remote-http": {
80- # ' "url": "https://mcp.example.com/mcp"
81- # ' }
82- # ' }
83- # ' }
84- # ' ```
85- # '
8669# ' @returns
8770# ' * `mcp_tools()` returns a list of ellmer tools that can be passed directly
8871# ' to the `$set_tools()` method of an [ellmer::Chat] object. If the file at
@@ -117,113 +100,33 @@ mcp_tools <- function(config = NULL) {
117100 for (i in seq_along(config )) {
118101 config_i <- config [[i ]]
119102 name_i <- names(config )[i ]
120-
121- if (" url" %in% names(config_i )) {
122- add_mcp_server_http(config = config_i , name = name_i )
103+ config_i_env <- if (" env" %in% names(config_i )) {
104+ unlist(config_i $ env )
123105 } else {
124- add_mcp_server_stdio( config = config_i , name = name_i )
106+ NULL
125107 }
126- }
127-
128- servers_as_ellmer_tools()
129- }
130108
131- add_mcp_server_stdio <- function (config , name , call = caller_env()) {
132- config_env <- if (" env" %in% names(config )) {
133- unlist(config $ env )
134- } else {
135- NULL
136- }
137-
138- process <- processx :: process $ new(
139- command = Sys.which(config $ command ),
140- args = config $ args %|| % character (),
141- env = config_env ,
142- stdin = " |" ,
143- stdout = " |" ,
144- stderr = " |"
145- )
146-
147- the $ server_processes <- c(
148- the $ server_processes ,
149- list2(
150- !! paste0(
151- c(config $ command , config $ args %|| % " " ),
152- collapse = " "
153- ) : = process
109+ process <- processx :: process $ new(
110+ # seems like the R process has a different PATH than process_exec
111+ command = Sys.which(config_i $ command ),
112+ args = config_i $ args ,
113+ env = config_i_env ,
114+ stdin = " |" ,
115+ stdout = " |" ,
116+ stderr = " |"
154117 )
155- )
156-
157- # Fail gracefully if the process failed on startup (#82)
158- tryCatch(
159- {
160- response_initialize <- send_and_receive_stdio(
161- process ,
162- mcp_request_initialize()
163- )
164118
165- send_and_receive_stdio( process , mcp_request_initialized())
166- response_tools_list <- send_and_receive_stdio(
167- process ,
168- mcp_request_tools_list()
119+ the $ server_processes <- c(
120+ the $ server_processes ,
121+ list2(
122+ !! paste0(c( config_i $ command , config_i $ args ), collapse = " " ) : = process
169123 )
170- },
171- error = function (e ) {
172- if (process $ get_exit_status() %in% c(1L , 2L )) {
173- cli :: cli_abort(
174- c(
175- " The command {.code {config$command}} failed with the following error:" ,
176- " x" = " {paste0(process$read_all_error_lines(), collapse = '. ')}"
177- ),
178- call = call
179- )
180- }
181-
182- cnd_signal(e )
183- }
184- )
185-
186- the $ mcp_servers [[name ]] <- list (
187- name = name ,
188- type = " stdio" ,
189- process = process ,
190- tools = response_tools_list $ result ,
191- id = 3
192- )
193-
194- the $ mcp_servers [[name ]]
195- }
196-
197- add_mcp_server_http <- function (config , name ) {
198- response_initialize <- send_and_receive_http(
199- url = config $ url ,
200- request = mcp_request_initialize()
201- )
202-
203- session_id <- response_initialize $ session_id
204-
205- send_and_receive_http(
206- url = config $ url ,
207- request = mcp_request_initialized(),
208- session_id = session_id
209- )
210-
211- response_tools_list <- send_and_receive_http(
212- url = config $ url ,
213- request = mcp_request_tools_list(),
214- session_id = session_id
215- )
124+ )
216125
217- the $ mcp_servers [[name ]] <- list (
218- name = name ,
219- type = " http" ,
220- url = config $ url ,
221- session_id = session_id ,
222- tools = response_tools_list $ result ,
223- id = 3
224- )
126+ add_mcp_server(process = process , name = name_i )
127+ }
225128
226- the $ mcp_servers [[ name ]]
129+ servers_as_ellmer_tools()
227130}
228131
229132mcp_client_config <- function () {
@@ -290,34 +193,19 @@ error_no_mcp_config <- function(call) {
290193 )
291194}
292195
293- send_and_receive_http <- function (url , request , session_id = NULL ) {
294- req <- httr2 :: request(url ) | >
295- httr2 :: req_method(" POST" ) | >
296- httr2 :: req_headers(
297- " Content-Type" = " application/json" ,
298- " Accept" = " application/json" ,
299- " MCP-Protocol-Version" = " 2025-06-18"
300- ) | >
301- httr2 :: req_body_json(request )
302-
303- if (! is.null(session_id )) {
304- req <- httr2 :: req_headers(req , " Mcp-Session-Id" = session_id )
305- }
306-
307- resp <- httr2 :: req_perform(req )
308-
309- if (httr2 :: resp_status(resp ) == 202L || httr2 :: resp_body_string(resp ) == " " ) {
310- return (NULL )
311- }
312-
313- body <- httr2 :: resp_body_json(resp )
196+ add_mcp_server <- function (process , name ) {
197+ response_initialize <- send_and_receive(process , mcp_request_initialize())
198+ send_and_receive(process , mcp_request_initialized())
199+ response_tools_list <- send_and_receive(process , mcp_request_tools_list())
314200
315- session_id_header <- httr2 :: resp_header(resp , " Mcp-Session-Id" )
316- if (! is.null(session_id_header )) {
317- body $ session_id <- session_id_header
318- }
201+ the $ mcp_servers [[name ]] <- list (
202+ name = name ,
203+ process = process ,
204+ tools = response_tools_list $ result ,
205+ id = 3
206+ )
319207
320- body
208+ the $ mcp_servers [[ name ]]
321209}
322210
323211servers_as_ellmer_tools <- function () {
@@ -459,21 +347,13 @@ tool_ref <- function(server, tool, arguments) {
459347}
460348
461349call_tool <- function (... , server , tool ) {
462- server_config <- the $ mcp_servers [[server ]]
463-
464- request <- mcp_request_tool_call(
465- id = jsonrpc_id(server ),
466- tool = tool ,
467- arguments = list (... )
468- )
469-
470- switch (
471- server_config $ type ,
472- stdio = send_and_receive_stdio(server_config $ process , request ),
473- http = send_and_receive_http(
474- url = server_config $ url ,
475- request = request ,
476- session_id = server_config $ session_id
350+ server_process <- the $ mcp_servers [[server ]]$ process
351+ send_and_receive(
352+ server_process ,
353+ mcp_request_tool_call(
354+ id = jsonrpc_id(server ),
355+ tool = tool ,
356+ arguments = list (... )
477357 )
478358 )
479359}
@@ -492,11 +372,13 @@ log_cat_client <- function(x, append = TRUE) {
492372 cat(x , " \n\n " , sep = " " , append = append , file = log_file )
493373}
494374
495- send_and_receive_stdio <- function (process , message ) {
375+ send_and_receive <- function (process , message ) {
376+ # send the message
496377 json_msg <- jsonlite :: toJSON(message , auto_unbox = TRUE )
497378 log_cat_client(c(" FROM CLIENT: " , json_msg ))
498379 process $ write_input(paste0(json_msg , " \n " ))
499380
381+ # poll for response
500382 output <- NULL
501383 attempts <- 0
502384 max_attempts <- 20
0 commit comments