Skip to content

Prompts and visibility#

@[AC::MCP] controls how a controller or method appears to MCP clients:

Option Applies to Effect
prompt: true methods the method is an MCP prompt rather than a route
root: true controllers or methods always available, without opening the toolbox
hide: true controllers or methods not exposed over MCP

On a controller, root and hide apply to every route and prompt in it. They aren't inherited by subclasses, so annotate each controller. A method level annotation takes precedence, so you can hide a controller but expose one route:

@[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

Prompts#

Prompts are reusable message templates that users pick in their MCP client, often shown as slash commands. They're a good way to package your domain knowledge: what to ask, which tools to use, and in what order.

class Welcome < AC::Base
  base "/"

  # Learn a surprising fact about a number
  @[AC::MCP(prompt: true, root: true)]
  def number_fact(
    @[AC::Param::Info(description: "the number to share a fact about", example: "42")]
    number : Int32,
    @[AC::Param::Info(description: "who the fact is for", example: "a curious five year old")]
    audience : String = "a general audience",
  ) : String
    <<-PROMPT
      Share one surprising fact about the number #{number}, explained for #{audience}.
      Use the welcome toolbox to confirm the number with this service first.
      PROMPT
  end
end
  • Description: the doc comment is what users see in their client.
  • Arguments: the method's arguments, described with @[AC::Param::Info]. They're required unless nilable or defaulted, exactly like route parameters.
  • Return type: must be declared, and must be String (one user message) or Array(AC::PromptMessage) (a conversation).

Conversations#

Return messages to prime a multi-turn exchange:

# Review a pull request with the team's checklist
@[AC::MCP(prompt: true)]
def review(id : Int64) : Array(AC::PromptMessage)
  pr = PullRequest.find!(id)
  [
    AC::PromptMessage.user("Review pull request ##{id}: #{pr.title}\n\n#{pr.diff}"),
    AC::PromptMessage.assistant("I'll check it against the checklist. Which areas worry you most?"),
  ]
end

Prompts run like routes#

A prompt isn't an HTTP route, so it can't be reached over HTTP and isn't in your OpenAPI document. Internally, though, it runs through the same pipeline as a route:

  • Parameters are parsed and validated by the same converters, including config: and custom converters.
  • Filters run: before_action authentication, loading records, and so on.
  • Exception handlers apply.

So a prompt can safely load and embed data, such as the pull request above, with the user's permissions enforced.

Warning

A prompt that is also a route is a compile error, and so is a prompt without a String or Array(AC::PromptMessage) return type.

Root tools and prompts#

Toolboxes keep the model's context small, but a few tools are needed in almost every session. Mark them root: true:

class Rooms < AC::Base
  # always listed, no need to open the rooms toolbox
  @[AC::MCP(root: true)]
  @[AC::Route::GET("/search")]
  def search(query : String) : Array(Room)
    Room.search(query)
  end
end

# every route and prompt in the controller is a root item
@[AC::MCP(root: true)]
class Me < AC::Base
end

Root items keep their <toolbox>_<method> names. A toolbox that only contains root items isn't listed by list_toolboxes, as there's nothing to open.

Hiding routes#

Hide routes that aren't useful or safe for a model, such as health checks, webhooks, binary downloads or large documents:

# the OpenAPI document is for people and code generators, not models
@[AC::MCP(hide: true)]
@[AC::Route::GET("/openapi")]
def openapi : YAML::Any
  OPENAPI
end

Hiding isn't access control

hide: true only removes a tool from the MCP listing; the HTTP route still works. Protect routes with filters as usual. Tool calls run your filters too.

See also#