@@ -39,12 +39,38 @@ ragnar_register_tool_retrieve <- function(
3939 check_string(name , allow_null = TRUE )
4040 check_string(title , allow_null = TRUE )
4141
42- name <- name %|| % glue :: glue(" rag_retrieve_from_{store@name}" )
42+ tool_def <- ragnar_tool_retrieve(
43+ store = store ,
44+ store_description = store_description ,
45+ ... ,
46+ name = name ,
47+ title = title
48+ )
49+
50+ chat $ register_tool(tool_def )
51+ invisible (chat )
52+ }
53+
54+ # Internal: build an ellmer tool for retrieving from a Ragnar store
55+ ragnar_tool_retrieve <- function (
56+ store ,
57+ store_description = " the knowledge store" ,
58+ ... ,
59+ name = NULL ,
60+ title = NULL
61+ ) {
62+ rlang :: check_installed(" ellmer" )
63+
64+ check_string(name , allow_null = TRUE )
65+ check_string(title , allow_null = TRUE )
66+
67+ name <- name %|| % glue :: glue(" search_store_{store@name}" )
4368 title <- title %|| % store @ title
4469
4570 previously_retrieved_chunk_ids <- integer()
71+ list (... ) # force
4672
47- tool_def <- ellmer :: tool(
73+ ellmer :: tool(
4874 function (text ) {
4975 chunks <- ragnar_retrieve(
5076 store ,
@@ -54,7 +80,14 @@ ragnar_register_tool_retrieve <- function(
5480 )
5581 previously_retrieved_chunk_ids <<-
5682 unique(unlist(c(chunks $ chunk_id , previously_retrieved_chunk_ids )))
57- chunks
83+ jsonlite :: toJSON(
84+ chunks ,
85+ pretty = TRUE ,
86+ auto_unbox = TRUE ,
87+ null = " null" ,
88+ na = " null" ,
89+ rownames = FALSE
90+ )
5891 },
5992 name = name ,
6093 description = glue :: glue(
@@ -71,7 +104,78 @@ ragnar_register_tool_retrieve <- function(
71104 open_world_hint = FALSE
72105 )
73106 )
107+ }
74108
75- chat $ register_tool(tool_def )
76- invisible (chat )
109+ # ' Serve a Ragnar store over MCP
110+ # '
111+ # ' Launches an MCP server (via [mcptools::mcp_server()]) that exposes a
112+ # ' retrieval tool backed by a Ragnar store. This lets MCP-enabled clients (e.g.,
113+ # ' Codex CLI, Claude Code) call into your store to retrieve relevant
114+ # ' excerpts.
115+ # '
116+ # ' @param store A `RagnarStore` object or a file path to a Ragnar DuckDB store.
117+ # ' If a character path is supplied, it is opened with
118+ # ' [ragnar_store_connect()].
119+ # ' @param store_description Optional string used in the tool description
120+ # ' presented to clients.
121+ # ' @inheritParams ragnar_register_tool_retrieve
122+ # ' @param ... Additional arguments forwarded to [ragnar_retrieve()].
123+ # ' @param name,title Optional identifiers for the tool. By default, derives from
124+ # ' `store@name` and `store@title` when available.
125+ # ' @param extra_tools Optional additional tools (list of `ellmer::tool()`
126+ # ' objects) to serve alongside the retrieval tool.
127+ # '
128+ # ' @return This function blocks the current R process by running an MCP server.
129+ # ' It is intended for non-interactive use. Called primarily for side-effects.
130+ # '
131+ # ' @details
132+ # '
133+ # ' To use this function with [Codex CLI](https://developers.openai.com/codex/cli/), add something like this
134+ # ' to `~/.codex/config.toml`
135+ # '
136+ # ' ```toml
137+ # ' [mcp_servers.quartohelp]
138+ # ' command = "Rscript"
139+ # ' args = [
140+ # ' "-e",
141+ # ' "ragnar::mcp_serve_store(quartohelp:::quartohelp_ragnar_store(), top_k=10)"
142+ # ' ]
143+ # ' ```
144+ # '
145+ # ' You can confirm the agent can search the ragnar store by inspecting the
146+ # ' output from the `/mcp` command, or by asking it "What tools do you have
147+ # ' available?".
148+ # '
149+ # '
150+ # ' @export
151+ mcp_serve_store <- function (
152+ store ,
153+ store_description = " the knowledge store" ,
154+ ... ,
155+ name = NULL ,
156+ title = NULL ,
157+ extra_tools = NULL
158+ ) {
159+ rlang :: check_installed(" mcptools" )
160+ rlang :: check_installed(" ellmer" )
161+
162+ if (is.character(store )) {
163+ store <- ragnar_store_connect(store )
164+ }
165+
166+ retrieve_tool <- ragnar_tool_retrieve(
167+ store = store ,
168+ store_description = store_description ,
169+ ... ,
170+ name = name ,
171+ title = title ,
172+ as_json = TRUE
173+ )
174+
175+ tools <- c(list (retrieve_tool ), extra_tools )
176+ if (rlang :: is_installed(" mcptools" , version = " 0.1.1.9001" )) {
177+ mcptools :: mcp_server(tools , include_session_tools = FALSE )
178+ } else {
179+ mcptools :: mcp_server(tools )
180+ }
77181}
0 commit comments