Skip to content

MCP server#

Spider-Gazelle can expose your API to AI agents as a Model Context Protocol (MCP) server. Claude, VS Code, Cursor and other MCP clients can then discover and call your routes as tools, and offer your prompts to users. There are no tool definitions to write: they're generated from the same annotations, types and doc comments that drive routing and OpenAPI.

claude mcp add --transport http my-app http://localhost:3000/mcp

Why it's built in#

Your API is already the tool definition

Hand-written tool definitions duplicate your API, and they drift: a renamed parameter or a changed type quietly breaks the agent. In Spider-Gazelle the tool is the route:

  • the description is the method's doc comment;
  • the input schema is generated from the argument types, @[AC::Param::Info] and the request body type;
  • a call runs the real route in-process, with the same parameter parsing, filters (authentication!), error handlers and responders as an HTTP request.

Improve a doc comment and your OpenAPI docs and agent tools both get better. Fix a validation bug and it's fixed for agents too.

How agents see your API#

Large APIs have hundreds of routes, and listing them all would fill the model's context before it does any work. Spider-Gazelle uses progressive disclosure instead:

  • Toolboxes: each controller is a toolbox, described by the controller's doc comment.
  • Starting tools: a session starts with just three tools:
Tool Purpose
list_toolboxes lists the toolboxes, their descriptions, and tool and prompt counts
open_toolbox(name) adds a toolbox's tools and prompts to the session
close_toolbox(name) removes them again
  • Loading on demand: the model opens only the toolboxes it needs. The client is notified (tools/list_changed) and refreshes its tool list.
  • Root items: anything marked root: true is always available without opening a toolbox. Use it for the few tools and prompts most sessions need.
# Manage meeting rooms and their bookings
class Rooms < AC::Base
  base "/rooms"

  # Lists the rooms in a building
  @[AC::Route::GET("/")]
  def index(building_id : String) : Array(Room)
    Room.where(building_id: building_id).to_a
  end
end

Here the toolbox is rooms ("Manage meeting rooms and their bookings"), and opening it adds the rooms_index tool ("Lists the rooms in a building").

Naming#

  • Toolbox names: the snake case controller name. The module namespace shared by every controller is left out, so MyApp::Api::Rooms and MyApp::Api::Rooms::Bookings become rooms and rooms_bookings.
  • Tool and prompt names: <toolbox>_<method>, such as rooms_index.
  • Multiple routes: a method with several route annotations is one tool. It uses the method's first GET route, otherwise its first route in verb order (POST, PUT, PATCH, DELETE).

What's exposed#

Exposed?
Annotated routes (@[AC::Route::GET] etc.) yes, as tools
Methods marked @[AC::MCP(prompt: true)] yes, as prompts
Routes marked @[AC::MCP(hide: true)] no
WebSocket and OPTIONS routes no
Macro DSL routes (get "/" do) no

Calls run as the user#

Tool calls forward the client's Authorization, Cookie and X-API-Key headers to the route, so your existing authentication applies to every call. For OAuth sign-in from MCP clients, see Authentication.

In this section#