btw_mcp_server() errors at startup when `tools` is a character vector of length > 1

Open Beginner friendly
#206 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
78/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Quiet
Tech stack
r
Domain
api, backend

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.

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
Dominant language
R
Stars
137
Forks
10
Avg merge
10h 48m
Merged PRs (30d)
4

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from posit-dev/btw

All issues in posit-dev/btw

Similar issues

More R issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.