File size: 3,917 Bytes
9e909f5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# 20251009 Added plot tool
# 20251023 Added help tools

# Load ellmer for tool() and type_*()
library(ellmer)

# Read prompts
source("prompts.R")

# Get help for a package
help_package <- function(package) {
  help_page <- help(package = (package), help_type = "text")
  paste(unlist(help_page$info), collapse = "\n")
}

# Get help for a topic
# Adapted from https://github.com/posit-dev/btw:::help_to_rd
help_topic <- function(topic) {
  help_page <- help(topic = (topic), help_type = "text")
  if(length(help_page) == 0) {
    return(paste0("No help found for '", topic, "'. Please check the name and try again."))
  }
  # Handle multiple help files for a topic
  # e.g. help_topic(plot) returns the help for both base::plot and graphics::plot.default
  help_paths <- as.character(help_page)
  help_result <- sapply(help_paths, function(help_path) {
    rd_name <- basename(help_path)
    rd_package <- basename(dirname(dirname(help_path)))
    db <- tools::Rd_db(rd_package)[[paste0(rd_name, ".Rd")]]
    paste(as.character(db), collapse = "")
  })
  # Insert headings to help the LLM distinguish multiple help files
  # Heading before each help file (e.g. Help file 1, Help file 2)
  help_result <- paste0("## Help file ", seq_along(help_result), ":\n", help_result)
  # Heading at start of message (e.g. 2 help files were retrieved)
  if(length(help_paths) == 1) help_info <- paste0("# ", length(help_paths), " help file was retrieved: ", paste(help_paths, collapse = ", "), ":\n")
  if(length(help_paths) > 1) help_info <- paste0("# ", length(help_paths), " help files were retrieved: ", paste(help_paths, collapse = ", "), ":\n")
  help_result <- c(help_info, help_result)
  help_result
}

# Run R code and return the result
# https://github.com/posit-dev/mcptools/issues/71
run_visible <- function(code) {
  eval(parse(text = code), globalenv())
}

# Run R code without returning the result
# https://github.com/posit-dev/mcptools/issues/71
run_hidden <- function(code) {
  eval(parse(text = code), globalenv())
  return("The code executed successfully")
}

# Run R code to make a plot and return the image data
make_plot <- function(code) {
  # Cursor, Bing and Google AI all suggest this but it causes an error:
  # Error in png(filename = raw_conn) : 
  #   'filename' must be a non-empty character string
  ## Write plot to an in-memory PNG
  #raw_conn <- rawConnection(raw(), open = "wb")
  #png(filename = raw_conn)

  # Use a temporary file to save the plot
  filename <- tempfile(fileext = ".dat")
  on.exit(unlink(filename))

  # Run the plotting code (this should include e.g. png() and dev.off())
  # The code uses a local variable (filename), so don't use envir = globalenv() here
  eval(parse(text = code))

  # Return a PNG image as raw bytes so ADK can save it as an artifact
  readr::read_file_raw(filename)
}

# This is the same code as make_plot() but has a different tool description
make_ggplot <- function(code) {
  filename <- tempfile(fileext = ".dat")
  on.exit(unlink(filename))
  eval(parse(text = code))
  readr::read_file_raw(filename)
}

mcptools::mcp_server(tools = list(

  tool(
    help_package,
    help_package_prompt,
    arguments = list(
      package = type_string("Package to get help for.")
    )
  ),

  tool(
    help_topic,
    help_topic_prompt,
    arguments = list(
      topic = type_string("Topic or function to get help for.")
    )
  ),

  tool(
    run_visible,
    run_visible_prompt,
    arguments = list(
      code = type_string("R code to run.")
    )
  ),

  tool(
    run_hidden,
    run_hidden_prompt,
    arguments = list(
      code = type_string("R code to run.")
    )
  ),

  tool(
    make_plot,
    make_plot_prompt,
    arguments = list(
      code = type_string("R code to make the plot.")
    )
  ),

  tool(
    make_ggplot,
    make_ggplot_prompt,
    arguments = list(
      code = type_string("R code to make the plot.")
    )
  )

))