Skip to content

API Interfaces

Dory Azar edited this page Nov 15, 2026 · 2 revisions

Interfaces: REST, the JS client and MCP

Caligrafy Quill 2.26.0. Generated from the source by php qa/build-api-reference.php; the guides explain how to use these classes, this reference says exactly what each public method takes and returns. Back to the API reference.

The channels that serve the capabilities besides web pages.

Class What it is
Caligrafy\Rest Answers HTTP requests with capabilities, in JSON: - Route::capability('POST', '/books', 'create_book') maps one route to one capability - Route::capabilities('/capabilities') adds GET /capabilities (what the caller may ...
Caligrafy\Client One ES module, no dependency, for the Vue apps, the Vite SPA and Node scripts: one async function per capability (create_book becomes createBook) plus call(name, input) and capabilities().
Caligrafy\Mcp\Http One JSON-RPC message per POST, answered with application/json (no event stream: every tool answers at once).
Caligrafy\Mcp\Server JSON-RPC 2.0 messages of MCP, independent of the transport (Http for Streamable HTTP, caligrafer mcp for stdio): initialize, ping, tools/list, tools/call; notifications get no answer.
Caligrafy\Async\Kernel What the async server runs, whatever server it is: boot() once per process (settings, constants, the class map, the application's config and container, the capabilities), then handle() for each request, one at a time ...
Caligrafy\Async\Eligibility The async server keeps the application in memory and answers many requests from one process, so a capability it runs must not keep anything from one caller to the next, nor talk to the web server's request directly.
Caligrafy\Async\WorkermanServer Workerman (pure PHP, composer require workerman/workerman: php caligrafer.php add async) keeps a few processes that each hold the application in memory (Kernel::boot) and many open connections, answering one request at ...

class Caligrafy\Rest

namespace Caligrafy;

class Rest

Source: framework/interfaces/Rest.php:25

Class Rest Answers HTTP requests with capabilities, in JSON:

  • Route::capability('POST', '/books', 'create_book') maps one route to one capability
  • Route::capabilities('/capabilities') adds GET /capabilities (what the caller may call, with the input schemas, and the CSRF token of the session in "csrf") and POST /capabilities/{name} (call any capability by name: the JS client of the Vue apps uses it)

Who calls: a personal access token (Authorization: Bearer cq_...), the legacy API key, or the signed-in user. Calls made with the session cookie need the CSRF token in an X-CSRF-Token header (or _token) unless they only read. Inputs: the JSON body (or form fields, or the query string for GET), then the route variables, which win. Responses: 200 with the result ({"data": ...}), 201 when a POST creates, or {"error": {"code", "message", "fields"}} with 401, 403, 404, 409 or 422.

Methods

Rest::respond()

public static function respond($name, $request = null)

Runs a capability for the current request and prints the response

Parameters

  • $name
  • $request, default null

Source: framework/interfaces/Rest.php:30

Rest::answer()

public static function answer(
    $name,
    callable $context,
    callable $input,
    $method = 'POST',
    ?callable $csrf = null,
    ?Uploads $uploads = null
)

The answer to a call of a capability, without printing it (2.26.0: the web server prints it, the async server sends it): ['status' => 200|201|4xx, 'body' => ['data' => ...] or ['error' => ...]]

Parameters

  • $name
  • $context callable: returns the caller's Context
  • $input callable: returns the inputs
  • $method string, default 'POST': the HTTP method (a POST that creates answers 201)
  • $csrf ?callable, default null: returns false when a session caller's write lacks its CSRF token
  • $uploads ?Uploads, default null

Source: framework/interfaces/Rest.php:59

Rest::listing()

public static function listing($request = null)

GET {prefix}: the capabilities the caller may call

Parameters

  • $request, default null

Source: framework/interfaces/Rest.php:82

Rest::input()

public static function input($request)

The inputs: body or query string, then the route variables

Parameters

  • $request

Source: framework/interfaces/Rest.php:93

class Caligrafy\Client

namespace Caligrafy;

class Client

Source: framework/interfaces/Client.php:19

Class Client One ES module, no dependency, for the Vue apps, the Vite SPA and Node scripts: one async function per capability (create_book becomes createBook) plus call(name, input) and capabilities(). It talks to Route::capabilities().

  • In a page of the application it uses the session: the cookie, and the CSRF token it reads once from GET {prefix}. Elsewhere (Node, another origin) configure({ baseUrl, token }) sends a personal access token.
  • A refused call throws CapabilityError with status, code (invalid, forbidden...), message and fields.

Methods

Client::generate()

public static function generate($prefix = '/capabilities')

The source of the module

Parameters

  • $prefix string, default '/capabilities': the uri of Route::capabilities()

Source: framework/interfaces/Client.php:25

Client::camel()

public static function camel($name)

create_book -> createBook

Parameters

  • $name

Source: framework/interfaces/Client.php:107

class Caligrafy\Mcp\Http

namespace Caligrafy\Mcp;

class Http

Source: framework/interfaces/Mcp/Http.php:22

Class Http One JSON-RPC message per POST, answered with application/json (no event stream: every tool answers at once).

  • Authentication: Authorization: Bearer <personal access token>. Without a valid token: 401 with WWW-Authenticate.
  • Origin: a browser request from another site is refused (403), against DNS rebinding. Allowed: no Origin header (agents), the application's own host, and the origins listed in MCP_ALLOWED_ORIGINS (comma separated).
  • Notifications are answered 202 with no body. GET and DELETE answer 405 (no server-initiated stream, no sessions).

Methods

Http::respond()

public static function respond()

No doc comment.

Source: framework/interfaces/Mcp/Http.php:24

Http::methodNotAllowed()

public static function methodNotAllowed()

No doc comment.

Source: framework/interfaces/Mcp/Http.php:51

Http::originAllowed()

public static function originAllowed($origin, $host)

Whether a browser Origin may call the endpoint

Parameters

  • $origin
  • $host

Source: framework/interfaces/Mcp/Http.php:62

class Caligrafy\Mcp\Server

namespace Caligrafy\Mcp;

class Server

Source: framework/interfaces/Mcp/Server.php:26

Class Server JSON-RPC 2.0 messages of MCP, independent of the transport (Http for Streamable HTTP, caligrafer mcp for stdio): initialize, ping, tools/list, tools/call; notifications get no answer.

The tools are the capabilities the context may call, with their input schema and the hints agents use to decide when to ask the user first (readOnlyHint, destructiveHint). A tool call goes through Capabilities::call(): the same permission checks, validation and audit as every other channel. A refused call (invalid input, forbidden...) is a tool result with isError, so the agent can read the reason and correct itself; protocol errors are JSON-RPC errors.

Constants

  • PROTOCOL_VERSIONS = array('2025-11-25', '2025-06-18', '2025-03-26', '2024-11-05')
  • PARSE_ERROR = -32700
  • INVALID_REQUEST = -32600
  • METHOD_NOT_FOUND = -32601
  • INVALID_PARAMS = -32602
  • INTERNAL_ERROR = -32603

Methods

->__construct()

public function __construct(Context $context, $name = null)

No doc comment.

Parameters

  • $context Context
  • $name, default null

Source: framework/interfaces/Mcp/Server.php:42

->handleJson()

public function handleJson($json)

Handles one message (a JSON string)

Parameters

  • $json

Returns array|null: the response, or null for a notification

Source: framework/interfaces/Mcp/Server.php:52

->handle()

public function handle($message)

Handles one decoded message

Parameters

  • $message

Returns array|null: the response, or null for a notification

Source: framework/interfaces/Mcp/Server.php:65

->tools()

public function tools()

The tools of this context

Source: framework/interfaces/Mcp/Server.php:101

Server::result()

public static function result($id, $result)

No doc comment.

Parameters

  • $id
  • $result

Source: framework/interfaces/Mcp/Server.php:161

Server::error()

public static function error($id, $code, $message)

No doc comment.

Parameters

  • $id
  • $code
  • $message

Source: framework/interfaces/Mcp/Server.php:166

class Caligrafy\Async\Kernel

namespace Caligrafy\Async;

class Kernel

Source: framework/core/Async/Kernel.php:34

Class Kernel What the async server runs, whatever server it is: boot() once per process (settings, constants, the class map, the application's config and container, the capabilities), then handle() for each request, one at a time per process.

It answers the capabilities channel only, as Route::capabilities() does on the web server:

GET  {prefix}         the capabilities the caller may call
POST {prefix}/{name}  a call (GET too for a read-only capability), inputs in the JSON body or the query string

The caller is known from its token (Authorization: Bearer cq_...), or is a visitor. There is no session and no cookie here: a request that brings the session cookie is the web server's (handles() says so), as is a capability that Eligibility refuses. Between requests, everything a request may leave behind is forgotten (endRequest()).

Methods

Kernel::boot()

public static function boot($prefix = '/capabilities')

Starts the framework once for this process (the root of the project is the current folder)

Parameters

  • $prefix, default '/capabilities'

Source: framework/core/Async/Kernel.php:43

Kernel::handles()

public static function handles($method, $path, array $cookies = array())

Whether this server answers the request: a path of the capabilities channel, no session cookie, and (for a call) a capability that may run here. Otherwise the web server answers it.

Parameters

  • $method
  • $path
  • $cookies array, default array()

Source: framework/core/Async/Kernel.php:62

Kernel::handle()

public static function handle($method, $path, array $headers, $body, array $query = array())

Answers one request

Parameters

  • $method
  • $path
  • $headers array: lower-case names
  • $body
  • $query array, default array(): the query string's values

Returns array: ['status' => int, 'headers' => [name => value], 'body' => string]

Source: framework/core/Async/Kernel.php:84

Kernel::eligible()

public static function eligible($name)

Whether a capability may run here (Eligibility, decided once per process)

Parameters

  • $name

Source: framework/core/Async/Kernel.php:120

Kernel::endRequest()

public static function endRequest()

Forgets what one request may leave in memory: this request's cache copies, a transaction left open, container instances, the query watch, the lazy-loading warnings. The database connection stays open for the next request.

Source: framework/core/Async/Kernel.php:132

class Caligrafy\Async\Eligibility

namespace Caligrafy\Async;

class Eligibility

Source: framework/core/Async/Eligibility.php:28

Class Eligibility The async server keeps the application in memory and answers many requests from one process, so a capability it runs must not keep anything from one caller to the next, nor talk to the web server's request directly. A capability is eligible when its class's own source (read as PHP tokens: comments and strings do not count) uses none of:

  • the request and the session: $_SESSION, $_COOKIE, $_GET, $_POST, $_REQUEST, $_FILES, $_SERVER, session(), request(), Auth::, redirect(), view() (the async channel knows its caller only from a token, or as a visitor);
  • output and the end of the process: echo, print, printf, var_dump, header(), setcookie(), http_response_code(), exit, die, ob_*() (it returns data; the server sends it);
  • state kept between calls: global, static variables, assignments to static properties;

and it is not webOnly and takes no file input (the file rule). Anything else (classes it calls) is not read: the isolation test (php caligrafer.php async check) runs each eligible capability for two callers in one process and compares. A capability that is not eligible is answered by the web server as before: being unsafe makes it slower, never shared.

Constants

  • SUPERGLOBALS = array('$_SESSION', '$_COOKIE', '$_GET', '$_POST', '$_REQUEST', '$_FILES', '$_SERVER', '$GLOBALS')
  • FUNCTIONS = ...

Methods

Eligibility::reasons()

public static function reasons($name)

Why a capability cannot run on the async server, or an empty list when it can

Parameters

  • $name

Returns string[]: the reasons

Source: framework/core/Async/Eligibility.php:39

Eligibility::eligible()

public static function eligible($name)

Whether a capability can run on the async server

Parameters

  • $name

Source: framework/core/Async/Eligibility.php:68

Eligibility::sourceReasons()

public static function sourceReasons($source)

What a PHP source uses that the async server cannot allow

Parameters

  • $source

Returns string[]: the reasons, at most one per kind

Source: framework/core/Async/Eligibility.php:77

class Caligrafy\Async\WorkermanServer

namespace Caligrafy\Async;

class WorkermanServer

Source: framework/core/Async/WorkermanServer.php:25

Class WorkermanServer Workerman (pure PHP, composer require workerman/workerman: php caligrafer.php add async) keeps a few processes that each hold the application in memory (Kernel::boot) and many open connections, answering one request at a time per process. In front of the web server (ASYNC=on in the production image: docker/production/start.sh), it answers the capabilities channel itself (Kernel::handles) and passes every other request, unchanged, to Apache on 127.0.0.1:<backend port>, adding X-Forwarded-For; without a backend (development), others get a 404.

Methods

WorkermanServer::run()

public static function run($listen, $workers, $backend = null, $prefix = '/capabilities')

Runs the server (this call does not return)

Parameters

  • $listen string: e.g. http://0.0.0.0:80
  • $workers int: processes (each holds one copy of the application)
  • $backend string|null, default null: the web server for everything else, e.g. 127.0.0.1:8080
  • $prefix, default '/capabilities'

Source: framework/core/Async/WorkermanServer.php:33

Caligrafy: Describe it. Approve it. Ship it. Your idea turned into a complete app: every feature built once, for web pages, Vue apps, a REST API and AI agents, and online.

Start here

Why Caligrafy: the benefits, the measurements, and what it is not yet

  1. Quickstart with Claude: describe it, approve it, ship it

    or the Quickstart: the same three steps by hand, in 5 minutes

  2. How Caligrafy works: the ideas in 5 minutes
  3. Your first capability: build a feature, page included

Capabilities

Experiences

Data and people

  1. Models
  2. Relationships
  3. Queries: filters, paging, related rows
  4. Sign-in, users and tokens

Built-in features

Put it online

Build with AI

Reference

Clone this wiki locally