MCP configuration reference#
All options are class properties on ActionController::MCPServer, usually set in
src/config.cr:
ActionController::MCPServer.tap do |mcp|
mcp.server_name = "my-app"
mcp.server_version = "1.0.0"
# describe your domain, then how to use toolboxes (which explains the proxies when enabled)
mcp.instructions = "Manages meeting rooms and bookings.\n\n#{mcp.toolbox_instructions}"
end
Server#
| Option | Default | Purpose |
|---|---|---|
server_name |
"action-controller" |
reported to clients on initialize |
server_version |
"1.0.0" |
reported to clients on initialize |
instructions |
nil (toolbox_instructions) |
text given to the model on initialize. Describe your domain here and append toolbox_instructions. "" sends none |
ui_base |
nil |
the folder of UI cards, ui:// paths resolve against it |
ui_meta |
nil |
the default card settings (UIMeta: CSP domains, permissions, border) |
tool_proxy |
true |
adds the call_read_only and call_tool meta tools, for clients that ignore tools/list_changed |
description_path |
"mcp.yml" |
the generated tool descriptions, relative to the working directory |
session_timeout |
30.minutes |
idle sessions are discarded |
allowed_origins |
[] |
browser origins allowed in addition to same-origin. "*" allows any |
forward_headers |
["Authorization", "Cookie", "X-API-Key"] |
request headers copied onto tool calls and prompts |
excluded_response_headers |
noise, credentials, transport and browser policy headers | response headers left out of tool results, matched case-insensitively. A trailing * matches a prefix |
Authentication#
Authentication is optional and off by default. It's enabled when auth_probe,
authenticator or resource_metadata is set. See Authentication.
| Option | Default | Purpose |
|---|---|---|
auth_probe |
nil |
a route requested in-process with the forwarded headers. A 2xx response means authenticated |
authenticator |
nil |
Proc(HTTP::Request, Bool), a custom check used instead of the probe |
auth_cache_ttl |
1.minute |
how long a successful check is cached for each credential |
resource_metadata |
nil |
Proc(HTTP::Request, ResourceMetadata), which advertises your OAuth authorization server |
Methods#
| Method | Purpose |
|---|---|
mount(router, path = "/mcp", endpoints = true) |
registers the endpoint (and the protected resource metadata), plus the controller endpoints |
mount_endpoints(router) |
registers just the controller endpoints |
endpoint_paths |
the path templates of the controller endpoints |
icon(src, **fields) |
adds an icon for the global server, see icons |
toolbox_instructions |
the toolbox usage instructions, to append to your own instructions |
write_description(path) |
generates mcp.yml from the code. Needs the source code |
generate_description |
the same, returning the Description |
description= |
replaces the loaded description (nil reloads it), useful in specs |
Transport details#
- Transport: Streamable HTTP, for protocol versions
2025-11-25,2025-06-18and2025-03-26. POST: returnsapplication/json. When a request produces notifications (e.g. opening a toolbox) and the client acceptstext/event-stream, the notifications are streamed ahead of the result.GET: withAccept: text/event-stream, opens a stream for server notifications.DELETE: ends the session.- Sessions: identified by the
Mcp-Session-Idheader and stored in memory for each process. Deployments with multiple instances need sticky sessions. Originheaders: must be same-origin or listed inallowed_origins, which protects against DNS rebinding.- Unhandled exceptions: are logged and returned as a generic
500tool error.Server.beforehandlers (such asErrorHandler) don't run for in-process tool calls.
Tool results#
A tool call returns the route's response as {status, headers, body}. The same JSON is
the text content (which most clients give the model) and structuredContent:
{
"status": 200,
"headers": {"X-Total-Count": "120", "Link": "</api/users?page=2>; rel=\"next\""},
"body": [{"id": 1, "name": "Steve"}]
}
status: the HTTP status code.headers: the response headers, minus those matchingexcluded_response_headers. Left out when there are none. This is how the model sees pagination (Link,X-Total-Count,Content-Range),Locationafter a create,Retry-AfterandETag.body: the parsed JSON, or the text of any other response. Left out when empty.- Errors: a status of 400 or above sets
isError, and the error body is returned in the same shape. A401when authentication is enabled challenges the client to sign in again instead. - Images and audio: returned as an
imageoraudiocontent block, followed by the envelope without a body.
By default these headers are excluded:
| Kind | Headers |
|---|---|
| Noise | Date, Content-Length, X-Request-ID, Content-Type, Server, Vary, Cache-Control, Pragma, Expires, Alt-Svc |
| Credentials | Set-Cookie, Cookie, Authorization, WWW-Authenticate, Proxy-* |
| Transport | Connection, Keep-Alive, Transfer-Encoding, Content-Encoding, Trailer, Upgrade |
| Browser policy | Strict-Transport-Security, Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Access-Control-* |
Add to the list, rather than replacing it, so credentials stay excluded:
ActionController::MCPServer.excluded_response_headers += ["X-Runtime", "X-Internal-*"]