btw_mcp_server() errors at startup when `tools` is a character vector of length > 1
Nobody has claimed this yet.
- Dominant language
- R
- Stars
- 137
- Forks
- 10
- Avg merge
- 10h 48m
- Merged PRs (30d)
- 4
Description
btw_mcp_server() opens by testing whether its tools argument names an R file:
https://github.com/posit-dev/btw/blob/main/R/mcp.R#L165-L172
is_likely_r_file <-
is.character(tools) &&
file.exists(tools) &&
grepl("[.]r$", tools, ignore.case = TRUE)
Since R 4.3, an && whose operand has length > 1 is an error rather than a warning. So if tools is a character vector naming two or more tool groups, file.exists(tools) returns a length-n logical and the call dies immediately:
btw::btw_mcp_server(c("docs", "env", "sessioninfo"))
#> Error in is.character(tools) && file.exists(tools) :
#> 'length = 3' in coercion to 'logical(1)'
A length-1 character vector is fine (file.exists("docs") is FALSE, so the guard short-circuits), which makes the failure look arbitrary — btw_mcp_server("docs") works, adding one more group breaks it.
Nothing else in the function objects to a character vector. The guard is the only obstacle; flatten_and_check_tools() handles one happily:
length(btw:::flatten_and_check_tools(c("docs", "env", "ide")))
#> [1] 8
Why this is worse than an ordinary error
The intended use is as an MCP server behind Rscript -e, where nothing has a terminal. The process dies before the initialize handshake, so the client sees only a closed pipe and reports a generic transport failure — Claude Code shows Failed to reconnect to r-btw: -32000 — while the R message goes nowhere. There is no indication that the argument is at fault, or even that R produced an error. It took reconstructing the Rscript invocation by hand and running it in a terminal to see the real message.
Suggested fix
Guard the length, e.g.
is_likely_r_file <-
is.character(tools) &&
length(tools) == 1 &&
file.exists(tools) &&
grepl("[.]r$", tools, ignore.case = TRUE)
which also matches the intent, since the file branch only makes sense for a single path. If a character vector is meant to be rejected rather than accepted, an explicit error naming tools would still be a large improvement over the coercion message.
Workaround
Wrap the groups so is.character() is FALSE — either btw_mcp_server(btw_tools("docs", "env", "sessioninfo")) or the list(...) form the docs use.
A documentation note
Every example in ?btw_mcp_server passes list(...) or btw_tools(...), so the docs are consistent and correct. But c(...) is a natural thing to reach for, it is what a character-vector-shaped argument invites, and the rest of the function supports it — so people will hit this. Rejecting it loudly or accepting it both seem better than the current behaviour.
Session info
R version 4.6.1 (2026-06-24)
btw 1.3.0
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in R/mcp.R around lines 165-172 and reproduce the reported call with a multi-element character vector. Check that btw_mcp_server() no longer fails during startup and that the existing tool handling still accepts the vector; verify the reported single-path behavior remains intact.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- r
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100