Skip to content

Commit d8e5dad

Browse files
armishclaude
andcommitted
Fix MCP Inspector compatibility and add resource examples
- Add missing resource method handlers for MCP Inspector: - resources/templates (returns empty template array) - resources/templates/list (alias for templates) - resources/subscribe (returns empty object - not supported) - resources/unsubscribe (returns empty object - not supported) - Add debug logging to trace method calls - Fix DESCRIPTION file with required Author/Maintainer fields - Add comprehensive resource usage examples to README.md: - Custom resource examples (datasets, memory, models) - Dynamic resource creation patterns - Usage scenarios with AI assistants - Create complete working example in inst/examples/resources_example.R demonstrating various resource types These changes ensure full compatibility with MCP Inspector and provide clear documentation for users to implement resources effectively. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 4741b83 commit d8e5dad

4 files changed

Lines changed: 333 additions & 8 deletions

File tree

DESCRIPTION

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
Package: plumber2mcp
22
Title: Add Model Context Protocol Support to Plumber APIs
33
Version: 0.1.0
4+
Author: Bulent Arman Aksoy
5+
Maintainer: Bulent Arman Aksoy <arman@aksoy.org>
46
Authors@R:
57
person("Bulent Arman", "Aksoy", , "arman@aksoy.org", role = c("aut", "cre"))
68
Description: Extends plumber APIs with Model Context Protocol (MCP) support,

R/pr_mcp_stdio.R

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ run_stdio_server <- function(tools, resources, server_name, server_version, pr,
8181
request <- jsonlite::fromJSON(line, simplifyVector = FALSE)
8282

8383
# Process request
84-
response <- process_mcp_request(request, tools, resources, server_name, server_version, pr)
84+
response <- process_mcp_request(request, tools, resources, server_name, server_version, pr, debug)
8585

8686
# Write response to stdout (only if not NULL for notifications)
8787
if (!is.null(response)) {
@@ -124,7 +124,7 @@ run_stdio_server <- function(tools, resources, server_name, server_version, pr,
124124

125125
#' Process a single MCP request
126126
#' @noRd
127-
process_mcp_request <- function(request, tools, resources, server_name, server_version, pr) {
127+
process_mcp_request <- function(request, tools, resources, server_name, server_version, pr, debug = FALSE) {
128128

129129
# Validate JSON-RPC
130130
if (is.null(request$jsonrpc) || request$jsonrpc != "2.0") {
@@ -135,6 +135,11 @@ process_mcp_request <- function(request, tools, resources, server_name, server_v
135135
))
136136
}
137137

138+
# Debug: log the method being called
139+
if (debug) {
140+
message("Processing method: ", request$method)
141+
}
142+
138143
# Route to appropriate handler
139144
result <- switch(request$method,
140145
"initialize" = handle_initialize_stdio(request, server_name, server_version),
@@ -144,6 +149,10 @@ process_mcp_request <- function(request, tools, resources, server_name, server_v
144149
"tools/call" = handle_tools_call_stdio(request, tools, pr),
145150
"resources/list" = handle_resources_list_stdio(request, resources),
146151
"resources/read" = handle_resources_read_stdio(request, resources),
152+
"resources/templates" = handle_resources_templates_stdio(request),
153+
"resources/templates/list" = handle_resources_templates_stdio(request),
154+
"resources/subscribe" = handle_resources_subscribe_stdio(request),
155+
"resources/unsubscribe" = handle_resources_unsubscribe_stdio(request),
147156
{
148157
list(
149158
jsonrpc = "2.0",
@@ -288,6 +297,38 @@ handle_resources_read_stdio <- function(body, resources) {
288297
})
289298
}
290299

300+
#' Handle resources/templates request for stdio transport
301+
#' @noRd
302+
handle_resources_templates_stdio <- function(body) {
303+
list(
304+
jsonrpc = "2.0",
305+
id = body$id,
306+
result = list(
307+
resourceTemplates = list() # Empty array - no dynamic templates supported yet
308+
)
309+
)
310+
}
311+
312+
#' Handle resources/subscribe request for stdio transport
313+
#' @noRd
314+
handle_resources_subscribe_stdio <- function(body) {
315+
list(
316+
jsonrpc = "2.0",
317+
id = body$id,
318+
result = structure(list(), names = character(0)) # Empty object - subscriptions not supported
319+
)
320+
}
321+
322+
#' Handle resources/unsubscribe request for stdio transport
323+
#' @noRd
324+
handle_resources_unsubscribe_stdio <- function(body) {
325+
list(
326+
jsonrpc = "2.0",
327+
id = body$id,
328+
result = structure(list(), names = character(0)) # Empty object - subscriptions not supported
329+
)
330+
}
331+
291332
#' Handle tools/call request for stdio transport
292333
#' @noRd
293334
handle_tools_call_stdio <- function(body, tools, pr) {

README.md

Lines changed: 112 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -125,16 +125,66 @@ These endpoints become MCP tools:
125125

126126
Resources allow AI assistants to read content from your R environment, such as documentation, data descriptions, or analysis results.
127127

128-
### Adding Resources
128+
### Adding Custom Resources
129129

130130
```r
131-
pr %>%
131+
# Create a Plumber API with resources
132+
pr <- pr() %>%
132133
pr_mcp(transport = "stdio") %>%
134+
135+
# Add a resource that provides dataset information
136+
pr_mcp_resource(
137+
uri = "/data/iris-summary",
138+
func = function() {
139+
paste(
140+
"Dataset: iris",
141+
paste("Dimensions:", paste(dim(iris), collapse = " x ")),
142+
"",
143+
capture.output(summary(iris)),
144+
sep = "\n"
145+
)
146+
},
147+
name = "Iris Dataset Summary",
148+
description = "Statistical summary and structure of the iris dataset"
149+
) %>%
150+
151+
# Add a resource that shows current memory usage
133152
pr_mcp_resource(
134-
uri = "/data/my-dataset",
135-
func = function() capture.output(summary(my_data)),
136-
name = "My Dataset Summary",
137-
description = "Statistical summary of my dataset"
153+
uri = "/system/memory",
154+
func = function() {
155+
mem <- gc()
156+
paste(
157+
"R Memory Usage:",
158+
paste("Used (Mb):", round(sum(mem[,2]), 2)),
159+
paste("Gc Trigger (Mb):", round(sum(mem[,6]), 2)),
160+
"",
161+
"Object sizes in workspace:",
162+
capture.output(print(object.size(ls.str()), units = "Mb")),
163+
sep = "\n"
164+
)
165+
},
166+
name = "R Memory Usage",
167+
description = "Current R session memory statistics"
168+
) %>%
169+
170+
# Add a resource with model diagnostics
171+
pr_mcp_resource(
172+
uri = "/models/latest-lm",
173+
func = function() {
174+
# Example: fit a model and return diagnostics
175+
model <- lm(mpg ~ wt + hp, data = mtcars)
176+
paste(
177+
"Linear Model: mpg ~ wt + hp",
178+
"",
179+
capture.output(summary(model)),
180+
"",
181+
"Diagnostic plots saved to: /tmp/lm_diagnostics.png",
182+
sep = "\n"
183+
)
184+
},
185+
name = "Latest Linear Model",
186+
description = "Diagnostics for the most recent linear model",
187+
mimeType = "text/plain"
138188
)
139189
```
140190

@@ -151,6 +201,62 @@ This automatically adds resources for:
151201
- R session information (`/r/session-info`)
152202
- Installed packages (`/r/packages`)
153203

204+
### Dynamic Resources with Parameters
205+
206+
While the current implementation doesn't support URI templates, you can create resources that adapt based on runtime conditions:
207+
208+
```r
209+
# Create resources based on available data files
210+
data_files <- list.files("data/", pattern = "\\.csv$")
211+
212+
pr <- pr()
213+
for (file in data_files) {
214+
pr <- pr %>%
215+
pr_mcp_resource(
216+
uri = paste0("/data/", tools::file_path_sans_ext(file)),
217+
func = local({
218+
current_file <- file # Capture the file name
219+
function() {
220+
data <- read.csv(file.path("data", current_file))
221+
paste(
222+
paste("File:", current_file),
223+
paste("Rows:", nrow(data)),
224+
paste("Columns:", paste(names(data), collapse = ", ")),
225+
"",
226+
"First 5 rows:",
227+
capture.output(print(head(data, 5))),
228+
sep = "\n"
229+
)
230+
}
231+
}),
232+
name = paste("Dataset:", tools::file_path_sans_ext(file)),
233+
description = paste("Contents of", file)
234+
)
235+
}
236+
```
237+
238+
### Resource Usage Examples
239+
240+
Once your MCP server is running with resources, AI assistants can:
241+
242+
1. **Browse available resources** - The AI can list all resources to see what information is available
243+
2. **Read specific resources** - The AI can read resource content to understand your data and environment
244+
3. **Use resources for context** - The AI can reference resource information when answering questions or writing code
245+
246+
Example interaction with an AI assistant:
247+
```
248+
You: "What datasets are available in this R session?"
249+
AI: *Lists resources and reads relevant ones*
250+
"I can see you have the iris dataset available. Based on the
251+
/data/iris-summary resource, it has 150 observations of 5 variables..."
252+
253+
You: "Can you help me analyze the relationship between Sepal.Length and Petal.Length?"
254+
AI: *Reads the iris summary resource for context*
255+
"Based on the iris dataset summary, I can see that Sepal.Length ranges
256+
from 4.3 to 7.9 and Petal.Length from 1.0 to 6.9. Let me create a
257+
linear model to analyze their relationship..."
258+
```
259+
154260
## Advanced Usage
155261

156262
### Customizing MCP Path

0 commit comments

Comments
 (0)