action-controller

Rails-esque controller framework
9.4.2 released

Spider-Gazelle Action Controller

CI

A fast, type-safe web framework for Crystal. Write a method with typed arguments and a return type, and Action Controller parses the request, renders the response, documents the route in OpenAPI and serves it to AI agents over MCP.

Read the guide at spider-gazelle.net.

Features

  • Strong parameters: path, query, header, form and body values become typed arguments, and invalid input is rejected before your code runs.
  • OpenAPI 3.1 generation: the document is built from your routes, types and doc comments, so it stays in step with the code.
  • MCP server: your routes become tools that Claude, ChatGPT and other agents can call through your filters and authentication, with prompts, resources, subscriptions and UI cards.
  • Content negotiation: JSON and YAML out of the box, plus your own parsers and responders. Binary types, such as BinData, are read and written as they are.
  • Filters, exception handlers and WebSockets, declared with annotations and inherited by subclasses.
  • Composable applications: mount controllers or whole apps under other paths, with one routing table, OpenAPI document and MCP server.
  • Performance: routes compile into a single routing trie with direct dispatch, and heavy routes can run on their own execution contexts. See how it compares in the web frameworks benchmark.

Installation

Start from the application template, which includes logging, specs, Docker, OpenAPI and MCP, or add the shard to shard.yml:

dependencies:
  action-controller:
    github: spider-gazelle/action-controller

See your first app for a walkthrough.

Strong parameters

Arguments are read from the path, query string, headers or body by name, and converted to their type. A required argument that's missing, or a value that doesn't convert, raises an error your controller can turn into a 400:

require "action-controller"

abstract class Application < ActionController::Base
  @[AC::Route::Exception(AC::Route::Param::Error, status_code: HTTP::Status::BAD_REQUEST)]
  def invalid_param(error) : NamedTuple(error: String?, parameter: String?)
    {error: error.message, parameter: error.parameter}
  end
end

# Manages the books in the library
class Books < Application
  base "/books"

  struct Book
    include JSON::Serializable
    getter title : String
    getter year : Int32

    def initialize(@title, @year)
    end
  end

  enum Genre
    Fiction
    History
    Science
  end

  # Searches the catalogue
  # GET /books?genre=history&tag=war&tag=empire&limit=5
  @[AC::Route::GET("/")]
  def index(
    genre : Genre? = nil,
    tag : Array(String) = [] of String,
    @[AC::Param::Info(description: "results per page")]
    limit : Int32 = 20,
  ) : Array(Book)
    [] of Book
  end

  # Fetches a book by its ID, in decimal or hex
  # GET /books/42 or /books/2A/hex
  @[AC::Route::GET("/:id")]
  @[AC::Route::GET("/:id/hex", config: {id: {base: 16}})]
  def show(id : UInt64) : Book
    Book.new(title: "Dune", year: 1965)
  end

  # Adds a book
  @[AC::Route::POST("/", body: :book, status_code: HTTP::Status::CREATED)]
  def create(book : Book) : Book
    book
  end
end

Optional arguments take their default, enums parse by name or value, arrays and sets collect every repetition of a name, and custom converters handle anything else. See parameters and responses.

OpenAPI

The routes above describe themselves: paths, parameters, schemas for Book and Genre, status codes, and the doc comments as summaries. Generate the document with the template's --docs flag, or in code:

docs = ActionController::OpenAPI.generate_open_api_docs(title: "Library", version: "1.0.0")
File.write("openapi.yml", docs.to_yaml)

See OpenAPI.

MCP server

Mount the Model Context Protocol server and every annotated route is a tool, described by the same comments and types:

require "action-controller/server"
require "action-controller/mcp"

server = ActionController::Server.new
ActionController::MCPServer.mount(server, "/mcp")
server.run

Calls are dispatched through the router with the client's headers, so filters, authentication and exception handlers apply as they do to any request. Controllers are listed as toolboxes to keep the model's context small, and @[AC::MCP(...)] adds prompts, resources and UI cards. See MCP.

More information

action-controller:
  github: spider-gazelle/action-controller
  version: ~> 9.4.2
License MIT
Crystal none

Authors

Dependencies 5

  • exception_page >= 0.5
    {'github' => 'crystal-loot/exception_page', 'version' => '>= 0.5'}
  • habitat >= 0.4
    {'github' => 'luckyframework/habitat', 'version' => '>= 0.4'}
  • hot_topic
    {'github' => 'jgaskins/hot_topic'}
  • json-schema >= 2.1.1
    {'github' => 'spider-gazelle/json-schema', 'version' => '>= 2.1.1'}
  • lucky_router main
    {'branch' => 'main', 'github' => 'luckyframework/lucky_router'}

Development Dependencies 3

  • ameba ~> 1.7
    {'github' => 'crystal-ameba/ameba', 'version' => '~> 1.7'}
  • bindata ~> 3.2
    {'github' => 'spider-gazelle/bindata', 'version' => '~> 3.2'}
  • kilt
    {'github' => 'jeromegn/kilt'}

Dependents 1

Last synced .
search fire star recently