Skip to content

Commit ccc18bc

Browse files
authored
merge PR #11 from shikokuchuo/dev: Support multiple proxies for mcp_server() instances
2 parents b11ac0d + e0368c8 commit ccc18bc

File tree

4 files changed

+59
-14
lines changed

4 files changed

+59
-14
lines changed

DESCRIPTION

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ Version: 0.0.0.9000
44
Authors@R: c(
55
person("Simon", "Couch", , "[email protected]", role = c("aut", "cre"),
66
comment = c(ORCID = "0000-0001-5676-5107")),
7-
person("Winston", "Chang", , "[email protected]", role = "aut",
8-
comment = c(ORCID = "0000-0001-5676-5107")),
9-
person("Posit Software, PBC", role = c("cph", "fnd"))
7+
person("Winston", "Chang", , "[email protected]", role = "aut"),
8+
person("Charlie", "Gao", , "[email protected]", role = "aut",
9+
comment = c(ORCID = "0000-0002-0750-061X")),
10+
person("Posit Software, PBC", role = c("cph", "fnd"),
11+
comment = c(ROR = "03wc8by49"))
1012
)
1113
Description: The goal of acquaint is to enable LLM-enabled tools like Claude Code to
1214
learn about the R packages you have installed using the
@@ -19,14 +21,14 @@ Suggests:
1921
Config/testthat/edition: 3
2022
Encoding: UTF-8
2123
Roxygen: list(markdown = TRUE)
22-
RoxygenNote: 7.3.2
24+
RoxygenNote: 7.3.2.9000
2325
Imports:
2426
btw (>= 0.0.1.9000),
2527
cli,
2628
ellmer,
2729
jsonlite,
2830
later,
29-
nanonext (>= 1.5.2.9009),
31+
nanonext (>= 1.5.2.9012),
3032
promises,
3133
rlang
3234
Depends: R (>= 4.1.0)

R/proxy.R

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ mcp_proxy <- function() {
77
# TODO: should this actually be a check for being called within Rscript or not?
88
check_not_interactive()
99

10-
the$proxy_socket <- nanonext::socket("pair", dial = acquaint_socket)
10+
the$proxy_socket <- nanonext::socket("poly")
11+
nanonext::dial(the$proxy_socket, url = sprintf("%s%d", acquaint_socket, 1L))
1112

1213
# Note that we're using file("stdin") instead of stdin(), which are not the
1314
# same.
@@ -103,6 +104,10 @@ schedule_handle_message_from_client <- function() {
103104
}
104105

105106
handle_message_from_server <- function(data) {
107+
if (!is.character(data)) {
108+
return()
109+
}
110+
106111
schedule_handle_message_from_server()
107112

108113
logcat("FROM SERVER: ", data)
@@ -119,7 +124,7 @@ schedule_handle_message_from_server <- function() {
119124
forward_request <- function(data) {
120125
logcat("TO SERVER: ", data)
121126

122-
the$saio <- nanonext::send_aio(the$proxy_socket, data, mode = "raw")
127+
nanonext::send_aio(the$proxy_socket, data, mode = "raw")
123128
}
124129

125130
# This process will be launched by the MCP client, so stdout/stderr aren't
@@ -201,3 +206,29 @@ check_not_interactive <- function(call = caller_env()) {
201206
)
202207
}
203208
}
209+
210+
mcp_discover <- function() {
211+
sock <- nanonext::socket("poly")
212+
on.exit(nanonext:::reap(sock))
213+
cv <- nanonext::cv()
214+
monitor <- nanonext::monitor(sock, cv)
215+
suppressWarnings(
216+
for (i in seq_len(1024L)) {
217+
nanonext::dial(sock, url = sprintf("%s%d", acquaint_socket, i), autostart = NA) &&
218+
break
219+
}
220+
)
221+
pipes <- nanonext::read_monitor(monitor)
222+
res <- lapply(seq_along(pipes), function(x) nanonext::recv_aio(sock))
223+
lapply(pipes, function(x) nanonext::send_aio(sock, "", mode = "raw", pipe = x))
224+
nanonext::collect_aio_(res)
225+
}
226+
227+
select_server <- function(i) {
228+
lapply(the$proxy_socket[["dialer"]], nanonext::reap)
229+
attr(the$proxy_socket, "dialer") <- NULL
230+
nanonext::dial(
231+
the$proxy_socket,
232+
url = sprintf("%s%d", acquaint_socket, as.integer(i))
233+
)
234+
}

R/server.R

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,26 @@ mcp_serve <- function() {
5353
return(invisible())
5454
}
5555

56-
the$server_socket <- nanonext::socket("pair", listen = acquaint_socket)
56+
the$server_socket <- nanonext::socket("poly")
57+
i <- 1L
58+
suppressWarnings(
59+
while (i < 1024L) { # prevent indefinite loop
60+
nanonext::listen(the$server_socket, url = sprintf("%s%d", acquaint_socket, i)) || break
61+
i <- i + 1L
62+
}
63+
)
64+
5765
schedule_handle_message_from_proxy()
5866
}
5967

6068
handle_message_from_proxy <- function(msg) {
69+
pipe <- nanonext::pipe_id(the$raio)
6170
schedule_handle_message_from_proxy()
6271

6372
# cat("RECV :", msg, "\n", sep = "", file = stderr())
73+
if (!nzchar(msg)) {
74+
return(nanonext::send_aio(the$server_socket, commandArgs(), pipe = pipe))
75+
}
6476
data <- jsonlite::parse_json(msg)
6577

6678
if (data$method == "tools/call") {
@@ -99,13 +111,12 @@ handle_message_from_proxy <- function(msg) {
99111
}
100112
# cat("SEND:", to_json(body), "\n", sep = "", file = stderr())
101113

102-
# TODO: consider if better / more robust using synchronous sends
103-
the$saio <- nanonext::send_aio(the$server_socket, to_json(body), mode = "raw")
114+
nanonext::send_aio(the$server_socket, to_json(body), mode = "raw", pipe = pipe)
104115
}
105116

106117
schedule_handle_message_from_proxy <- function() {
107-
r <- nanonext::recv_aio(the$server_socket, mode = "string")
108-
promises::as.promise(r)$then(handle_message_from_proxy)$catch(function(e) {
118+
the$raio <- nanonext::recv_aio(the$server_socket, mode = "string")
119+
promises::as.promise(the$raio)$then(handle_message_from_proxy)$catch(function(e) {
109120
print(e)
110121
})
111122
}

man/acquaint-package.Rd

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

0 commit comments

Comments
 (0)