posit-dev / posit-dev/btw

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.

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

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.

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.