Skip to content

annotation ActionController::MCP #

configures how a route, or every route in a controller, is exposed by the optional MCP server (see ActionController::MCPServer)

  • hide: true excludes the route(s) from the MCP toolboxes
  • root: true always lists the tool(s) or prompt(s), without opening the toolbox
  • endpoint: true (controllers only) also serves the controller as its own MCP server at <base>/mcp, or at a sub path such as endpoint: "/assistant". Path params in the base (base "/accounts/:account_id") are bound from the endpoint URL. The controller is hidden from the global server unless it's annotated hide: false, and a method annotated hide: true is hidden from both
  • ui: "bookings/card.html" renders the HTML card at that path, in MCPServer.ui_base, for the tool's results in clients that support MCP Apps (ui://bookings/card.html)
  • visibility: :card makes the tool callable by cards only, it's hidden from the model. visibility: :model stops cards calling it, the default is [:model, :card]
  • behaviour: describes what the tool does, a symbol or an array of :read_only, :additive, :destructive, :idempotent, :open_world and :closed_world. It replaces the defaults inferred from the HTTP verb: GET is :read_only, PUT is :idempotent and DELETE is [:destructive, :idempotent]. Read only tools can be run with the call_read_only proxy
  • title: "Book a room" is the tool or prompt's display name
  • resource: "app://widgets/{id}" (GET routes) also exposes the route as an MCP resource template. The variables are the route's path params, and {?name,other} its query params. mime_type: is the type of the resource contents, cache_ttl: (seconds) the time clients may cache a read and subscribe: true lets clients subscribe to updates, which requires a watcher, see MCPServer.watch
  • long_running: true streams progress to MCP clients while the route runs, see MCPServer::RequestContext, and spools its response to a temporary file rather than memory. max_bytes: limits the response size

tools with ui: or visibility: :card are root: true unless annotated root: false, as hosts only render cards for, and let cards call, the tools in their tool list. Icons are added with @[AC::Icon] * prompt: true exposes a method as an MCP prompt (not as a HTTP route). The method must return String or Array(AC::PromptMessage)

a method level annotation overrides the controller level annotation The nearest annotated controller supplies defaults for inherited and newly declared actions; a child controller annotation replaces those defaults.

@[AC::MCP(hide: true)]
class Internal < AC::Base
  # exposed even though the rest of the controller is hidden
  @[AC::MCP(hide: false)]
  @[AC::Route::GET("/status")]
  def status : String
    "ok"
  end
end

class Widgets < AC::Base
  # summarise a widget for the user
  @[AC::MCP(prompt: true)]
  def summarise(
    @[AC::Param::Info(description: "the widget to summarise")]
    id : Int64,
  ) : String
    "Summarise the widget: #{Widget.find(id).to_json}"
  end
end