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: trueis 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::RoomsandMyApp::Api::Rooms::Bookingsbecomeroomsandrooms_bookings. - Tool and prompt names:
<toolbox>_<method>, such asrooms_index. - Multiple routes: a method with several route annotations is one tool. It uses
the method's first
GETroute, 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#
- Setup: mounting the server, generating
mcp.yml, connecting clients and testing. - Prompts and visibility: prompts,
root, andhide. - Authentication: API keys, and OAuth sign-in with multi_auth and authly.
- Configuration: every option, and transport details.