Skip to content

Commit 6715651

Browse files
committed
error informatively when server process exits (closes #82)
1 parent ac9b176 commit 6715651

File tree

4 files changed

+62
-11
lines changed

4 files changed

+62
-11
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# mcptools (development version)
22

3+
* `mcp_tools()` now errors more informatively when an MCP server process exits unexpectedly (#82).
4+
35
* `mcp_server()` now supports HTTP transport in addition to stdio. Use `type = "http"` to start an HTTP server, with optional `host` and `port` arguments. For now, the implementation is authless.
46

57
* `mcp_tools()` now supports connecting to HTTP-based MCP servers. Configure servers with a `url` field in the config file instead of `command`/`args`.

R/client.R

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ mcp_tools <- function(config = NULL) {
128128
servers_as_ellmer_tools()
129129
}
130130

131-
add_mcp_server_stdio <- function(config, name) {
131+
add_mcp_server_stdio <- function(config, name, call = caller_env()) {
132132
config_env <- if ("env" %in% names(config)) {
133133
unlist(config$env)
134134
} else {
@@ -137,7 +137,7 @@ add_mcp_server_stdio <- function(config, name) {
137137

138138
process <- processx::process$new(
139139
command = Sys.which(config$command),
140-
args = config$args,
140+
args = config$args %||% character(),
141141
env = config_env,
142142
stdin = "|",
143143
stdout = "|",
@@ -147,18 +147,40 @@ add_mcp_server_stdio <- function(config, name) {
147147
the$server_processes <- c(
148148
the$server_processes,
149149
list2(
150-
!!paste0(c(config$command, config$args), collapse = " ") := process
150+
!!paste0(
151+
c(config$command, config$args %||% ""),
152+
collapse = " "
153+
) := process
151154
)
152155
)
153156

154-
response_initialize <- send_and_receive_stdio(
155-
process,
156-
mcp_request_initialize()
157-
)
158-
send_and_receive_stdio(process, mcp_request_initialized())
159-
response_tools_list <- send_and_receive_stdio(
160-
process,
161-
mcp_request_tools_list()
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+
)
164+
165+
send_and_receive_stdio(process, mcp_request_initialized())
166+
response_tools_list <- send_and_receive_stdio(
167+
process,
168+
mcp_request_tools_list()
169+
)
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+
}
162184
)
163185

164186
the$mcp_servers[[name]] <- list(

tests/testthat/_snaps/client.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,12 @@
2929
! Configuration processing failed.
3030
i `config` must have a top-level mcpServers entry.
3131

32+
# mcp_tools() errors informatively when process exits
33+
34+
Code
35+
mcp_tools(tmpfile)
36+
Condition
37+
Error in `mcp_tools()`:
38+
! The command `Rscript` failed with the following error:
39+
x Error: intentional error. Execution halted
40+

tests/testthat/test-client.R

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,21 @@ test_that("mcp_tools() returns mcpServers when valid", {
8686
result <- read_mcp_config(tmp_file)
8787
expect_equal(result, config$mcpServers)
8888
})
89+
90+
test_that("mcp_tools() errors informatively when process exits", {
91+
skip_on_cran()
92+
93+
config <- list(
94+
mcpServers = list(
95+
"test" = list(
96+
command = "Rscript",
97+
args = c("-e", "stop('intentional error')")
98+
)
99+
)
100+
)
101+
102+
tmpfile <- withr::local_tempfile(fileext = ".json")
103+
jsonlite::write_json(config, tmpfile, auto_unbox = TRUE)
104+
105+
expect_snapshot(error = TRUE, mcp_tools(tmpfile))
106+
})

0 commit comments

Comments
 (0)