Skip to content

Commit 5d8c323

Browse files
committed
revert client-side http support
Will instead recommend https://www.npmjs.com/package/mcp-remote for more full-featured support
1 parent 6715651 commit 5d8c323

File tree

7 files changed

+53
-294
lines changed

7 files changed

+53
-294
lines changed

CLAUDE.md

Lines changed: 0 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -46,63 +46,3 @@ The server uses a condition variable (`cv`) to coordinate multiple async operati
4646
- Server dials to `inproc://mcptools-session-1` by default
4747
- `inproc://` transport is fast for same-machine communication
4848
- Connections are cleaned up with `nanonext::reap()` on exit
49-
50-
## HTTP Transport Implementation
51-
52-
### Current Status
53-
54-
The HTTP transport implementation follows the "MUSTs only" principle from the MCP specification:
55-
56-
**Implemented (MUSTs):**
57-
- HTTP POST endpoint for JSON-RPC messages
58-
- `MCP-Protocol-Version` header validation
59-
- Origin validation for DNS rebinding protection
60-
- Client-side session ID tracking (if server provides `Mcp-Session-Id` header)
61-
- GET endpoint (returns 405 - SSE streaming not implemented)
62-
63-
**Not Implemented (MAYs/SHOULDs):**
64-
- Server-side session management (MAY in spec)
65-
- SSE streaming for GET requests (optional)
66-
- OAuth 2.1 authentication (OPTIONAL in spec)
67-
- HTTPS/TLS support
68-
69-
### HTTP vs HTTPS
70-
71-
**Protocol Requirements:**
72-
- The MCP protocol does NOT require HTTPS for authless HTTP servers
73-
- HTTPS is only REQUIRED for OAuth/authorization endpoints (which we don't implement)
74-
75-
**Client Compatibility:**
76-
- **Claude Code**: Works with `http://` servers ✓
77-
- **Claude Desktop**: Requires `https://` servers (product policy, not protocol requirement) ✗
78-
79-
Since HTTPS requires SSL certificates (self-signed for local dev, proper certs for production),
80-
we defer HTTPS support until OAuth 2.1 is implemented, when it becomes a MUST.
81-
82-
### Testing with the Inspector
83-
84-
The MCP inspector helps test MCP servers:
85-
86-
```bash
87-
Rscript -e "mcptools::mcp_server(type = 'http', port = 9000)"
88-
```
89-
90-
Then:
91-
92-
```bash
93-
npx @modelcontextprotocol/inspector --transport http --server-url http://127.0.0.1:9000
94-
```
95-
96-
### Using with Claude Code
97-
98-
Add the HTTP server to Claude Code:
99-
100-
```bash
101-
claude mcp add --transport http r-mcptools-http http://127.0.0.1:9000
102-
```
103-
104-
Remove it:
105-
106-
```bash
107-
claude mcp remove r-mcptools-http
108-
```

NEWS.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
# mcptools (development version)
22

3-
* `mcp_tools()` now errors more informatively when an MCP server process exits unexpectedly (#82).
3+
* `mcp_tools()` now errors more informatively when an MCP server process exits unexpectedly (#82).
44

55
* `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.
66

7-
* `mcp_tools()` now supports connecting to HTTP-based MCP servers. Configure servers with a `url` field in the config file instead of `command`/`args`.
8-
97
* JSON-RPC responses now retain an explicit `id = NULL` value, ensuring parse-error replies conform to the MCP specification.
108

119
* `mcp_server()` now formats tool results in the same way as ellmer (#78 by @gadenbuie).

R/client.R

Lines changed: 45 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -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

229132
mcp_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

323211
servers_as_ellmer_tools <- function() {
@@ -459,21 +347,13 @@ tool_ref <- function(server, tool, arguments) {
459347
}
460348

461349
call_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

inst/example-config-http.json

Lines changed: 0 additions & 7 deletions
This file was deleted.

man/client.Rd

Lines changed: 5 additions & 22 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)