action-controller

Version, currently 8.14.1139 versions

github.com/spider-gazelle/action-controller

A rails-esque controller framework for crystal lang

45 stars
41 dependents
License: MIT

Nothing has been indexed for 8.14.1 yet. The tag is recorded, its shard.yml has not been read, so the manifest and dependency list below are empty because they are unknown rather than because they are absent.

Installation

# Add this to your shard.yml
dependencies:
  action-controller:
    github: spider-gazelle/action-controller
    version: ~> 8.14.1

Then run:

shards install

shard.yml

No shard.yml has been indexed for 8.14.1. You can read it on the repository.

Dependencies

Unknown: the shard.yml for this version has not been read yet.

README

This README is the one indexed from the repository at its latest ref, not from the tag for this version.

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