# MARS-Curiosity > MARS-Curiosity: REST library for Delphi, server and client. JAX-RS style resources, JWT, OpenAPI 3, FireDAC, server-sent events, MCP servers for AI agents; Windows and Linux. MARS-Curiosity is an open source (MPL 2.0) library to build REST servers and clients with Embarcadero Delphi. Source code, releases and demos: https://github.com/andrea-magni/MARS --- Source: https://andrea-magni.github.io/MARS/guide/introduction # Introduction **MARS-Curiosity** is a lightweight, Delphi-native library for building RESTful web services and the clients that consume them. It supports Delphi 10.4 Sydney up to 13 Florence and runs on Windows and Linux. ## Why MARS? REST is the lingua franca of modern application integration. MARS lets you expose business logic written in Delphi as clean, standards-compliant REST endpoints — consumable from web front-ends, mobile apps, .NET, Java, PHP, or any HTTP client — and to write Delphi clients against any REST server. The library is built around six guiding principles: 1. **Lightweight** — no constraints on your application architecture and no heavy dependencies. You include only the units you actually use. 2. **Easy and powerful** — a small, attribute-driven API that scales from a one-method "hello world" to large, secured, data-aware servers. 3. **Pure RESTful Web Services** — interoperable with any consumer technology. 4. **Delphi-like** — leans on modern language features (custom attributes, generics, RTTI, anonymous methods) on the server, and a classic RAD component model on the client. 5. **Advanced dataset support** — deep FireDAC integration for Delphi-to-Delphi data-aware applications. 6. **OpenAPI 3 support** — automatic specification generation and Swagger UI. ## How it works in one minute A MARS server is organized as a small hierarchy: - An **Engine** hosts the server and routes every incoming HTTP request. - One or more **Applications** group related resources under a base path (e.g. `/default`). - **Resources** are ordinary Delphi classes decorated with `[Path]`. Their methods are decorated with HTTP-verb attributes (`[GET]`, `[POST]`, …) and become your endpoints. - For each request, MARS creates an **Activation**: it selects the resource and method, checks authorization, injects parameters, invokes your code, and serializes the result. ``` HTTP request ─► Engine ─► Application ─► Activation ─► your resource method ─► response ``` You never write request-parsing or routing code. You declare *what* an endpoint is with attributes, and MARS does the plumbing. ## Server and client, both included MARS ships two complementary parts: - **MARS Server** — the units under `MARS.Core.*`, `MARS.Data.*`, `MARS.OpenAPI.*`, etc. Host it in a console app, a Windows service, a VCL/FMX app, Apache (mod), ISAPI, FastCGI, or as a Linux daemon. - **MARS Client** — a set of runtime/design-time components (`TMARSClient`, `TMARSClientApplication`, `TMARSClientResource*`, `TMARSClientToken`, …) for consuming any REST API from Delphi, with first-class support for MARS servers (including FireDAC dataset sync). ## What's next - [Install MARS](/guide/installation) into your RAD Studio IDE. - Build [your first server](/guide/getting-started). - Understand the [Core Concepts](/guide/core-concepts) before diving into the [Server](/server/engine) and [Client](/client/overview) sections. --- Source: https://andrea-magni.github.io/MARS/guide/why-mars # Why MARS? MARS-Curiosity is an open source (MPL 2.0) library to build **REST servers and clients with Delphi**, developed since 2015 and used in production by its author's customers and by the community. This page sums up what it offers and when it fits. ## Declarative, JAX-RS style An endpoint is a plain Delphi class with attributes: no routing tables, no request parsing. ```pascal [Path('customers'), RolesAllowed('standard')] TCustomersResource = class protected [Context] FD: TMARSFireDAC; public [GET, Produces(TMediaType.APPLICATION_JSON)] function List: TFDDataSet; [GET, Path('{id}'), Produces(TMediaType.APPLICATION_JSON)] function Get([PathParam] id: Integer): TCustomer; [POST, Consumes(TMediaType.APPLICATION_JSON)] function Add([BodyParam] ACustomer: TCustomer): TCustomer; end; ``` Records, objects, arrays and datasets are serialized to JSON for you; parameters come from the path, the query string, headers, cookies, forms or the body. Developers coming from Java (JAX-RS) or .NET (ASP.NET Web API) recognize the model at once. See [Resources](/server/resources) and [Attributes](/server/attributes). ## Server and client in one library The same library has a client side: RAD components (`TMARSNetClient`, `TMARSClientResourceJSON`, `TMARSClientToken`, ...) to call MARS servers and any other REST API, with JSON to record mapping, JWT handling, asynchronous calls and [logging of every request](/client/logging). Delphi-to-Delphi applications share the record types between server and client. See [Client](/client/overview). ## Your data access, your choice MARS has been designed from scratch to plug in whatever ORM or data access library (DAC) you need. Not bundling one is a precise choice: MARS is not dogmatic about how you reach your data, so you pick the ORM or DAC that fits your project and your team, and keep the one you already use. A [custom injection service](/server/injection#writing-a-custom-injection-service) hands your ORM session, repository or connection to the resources through `[Context]`, the same way MARS injects its own objects. FireDAC and UniDAC come with ready integration. With FireDAC a method can return a dataset (or several) and MARS writes it as JSON; the client fetches it into memory tables, lets the user edit and sends back the changes (delta) for the server to apply. See [FireDAC & Datasets](/features/firedac), [UniDAC](/features/firedac#unidac) and [FireDAC Client](/client/firedac). ## Security built in - [JWT authentication](/features/authentication) with a ready token resource, Bearer header or cookie, key rotation, token renewal; - declarative [authorization](/features/authorization) with roles (`[RolesAllowed]`, `[PermitAll]`, `[DenyAll]`) on resources and methods; - a new project from [MARSCmd](/guide/installation#bootstrap-a-new-project-with-marscmd) gets its own random JWT secret. ## OpenAPI 3 out of the box The OpenAPI 3 document is generated from the resources (paths, parameters, schemas of records and classes, security), and Swagger UI is ready to serve. See [OpenAPI 3 & Swagger](/features/openapi). ## Delphi code callable by AI agents (MCP) MARS has native support for the [Model Context Protocol](/features/mcp): derive a resource from `TMCPResource`, mark methods with `[MCPTool]`, and Claude, ChatGPT, Copilot or a local model can call your Delphi code and query your FireDAC data, with per-tool roles, resources, prompts, interactive views (MCP Apps) and a built-in OAuth 2.1 server for client onboarding. See the [MCPServer demo](/demos/#mcpserver). ## AI coding agents know MARS Official [Agent Skills](/guide/agent-skills) teach Claude Code and other AI coding agents to scaffold, develop and secure MARS servers, so they write idiomatic MARS code instead of guessing. ## Host it anywhere The same server code runs as a console or GUI application, a Windows service, a Linux daemon (systemd, Docker), an IIS ISAPI module, an Apache module or a FastCGI program, on [Indy or Delphi Cross Socket](/server/engine#https) (HTTPS without a reverse proxy). See [Deployment](/guide/deployment). ## And more [Server-sent events](/features/sse), [HTML and templates](/features/templates) (WebStencils, htmx, static files), [JSON request/response logging](/features/logging) for Grafana/Loki, [shared configuration files](/reference/parameters#shared-configuration-include), YAML, an installer with IDE integration, [TMS Smart Setup](/guide/installation#tms-smart-setup) support, Delphi 10.4 Sydney to 13 Florence. ## When you may not need MARS If you only call a couple of REST APIs and have no server to build, the Delphi RTL (`THTTPClient`, `TRESTClient`) may be enough. The MARS client shines when you want typed records, tokens, datasets, logging and MARS servers. ## Get started [Install MARS](/guide/installation), create a project with MARSCmd and follow [Your First Server](/guide/getting-started); the [FAQ](/guide/faq) answers the most common questions. --- Source: https://andrea-magni.github.io/MARS/guide/installation # Installation MARS-Curiosity can be installed with the executable installer (recommended), with [TMS Smart Setup](https://github.com/tmssoftware/smartsetup), or manually from sources. ## Option 1 — Executable installer The fastest way to get started: 1. Download the setup from the [latest release page](https://github.com/andrea-magni/MARS/releases/latest). 2. Run it. The installer configures the library paths and installs the design-time packages for your RAD Studio version. ::: warning Keep your projects out of the MARS folder Uninstalling MARS, which the setup also does before installing a new version, deletes the content of the MARS folder. Since 1.8.1 the uninstaller leaves alone the folders of `Demos` that are not demos shipped with MARS, and the setup moves the ones it finds in the `Demos` folder of the previous version to `Documents\MARS Projects` before uninstalling it (older uninstallers delete the whole `Demos` folder). Anyway, create your projects somewhere else: [MARSCmd](#bootstrap-a-new-project-with-marscmd) proposes `Documents\MARS Projects`. ::: ## Option 2 — TMS Smart Setup {#tms-smart-setup} [TMS Smart Setup](https://doc.tmssoftware.com/smartsetup/) is a free, open-source command-line tool that downloads, builds and registers Delphi libraries. MARS ships a `tmsbuild.yaml`, so Smart Setup can build it from sources for every supported Delphi version installed on your machine (**10.4 Sydney** and newer, Win32/Win64). 1. [Download and install Smart Setup](https://doc.tmssoftware.com/smartsetup/download/) (version 3.5 or later). 2. The community server, where open-source libraries are listed, is disabled by default. Enable it once: ```bash tms server-enable community true ``` 3. Install MARS: ```bash tms install andreamagni.mars ``` Smart Setup clones the repository, compiles the runtime and design-time packages (Debug and Release), installs the design-time packages in the IDE and adds the MARS source folders to the library path. Later on, `tms update andreamagni.mars` gets the latest version and rebuilds it, and `tms uninstall andreamagni.mars` removes it. The JOSE [JWT backend](/features/authentication#jwt-backends) (`MARS.JOSE` package) uses the [delphi-jose-jwt](https://github.com/paolo-rossi/delphi-jose-jwt) library, which Smart Setup installs as a product of its own: `MARS.JOSE` is built only when it is installed. The mORMot backend, the default on Windows, needs nothing else. To use JOSE: ```bash tms install paolo-rossi.delphi-jose-jwt ``` Installing it after MARS is fine: Smart Setup rebuilds MARS and adds `MARS.JOSE`. ::: warning Use one installation method only. If MARS is already installed with the executable installer or manually, remove it first, so the IDE doesn't load two copies of the same packages. ::: `MARS.UniDAC` is not built by Smart Setup, because it requires Devart UniDAC: if you need it, build it manually from the `Packages` folder. The test projects (the `...Tests` project of an application created with MARSCmd, `MARS.Tests`) also need [Delphi-Mocks](https://github.com/VSoftTechnologies/Delphi-Mocks), which is not a Smart Setup product: clone it and add its `Source` folder to the library path. ## Option 3 — Manual installation 1. Get a copy of MARS with `git clone`, including its submodules ([delphi-jose-jwt](https://github.com/paolo-rossi/delphi-jose-jwt), used by the JOSE JWT backend, and [Delphi-Mocks](https://github.com/VSoftTechnologies/Delphi-Mocks), used by the test projects; the other third-party libraries are part of the repository): ```bash git clone --recurse-submodules https://github.com/andrea-magni/MARS.git ``` In a clone made without `--recurse-submodules`, run `git submodule update --init`. The **Download ZIP** button of GitHub leaves `ThirdParty\delphi-jose-jwt` and `ThirdParty\Delphi-Mocks` empty: download them at the versions shown in [`ThirdParty/README.md`](https://github.com/andrea-magni/MARS/blob/master/ThirdParty/README.md) and extract them there. 2. Add the following folders to your RAD Studio **Library Path** (Tools ▸ Options ▸ Language ▸ Delphi ▸ Library): - `[MARS Folder]\Source` - `[MARS Folder]\ThirdParty\delphi-jose-jwt\Source\Common` - `[MARS Folder]\ThirdParty\delphi-jose-jwt\Source\JOSE` - `[MARS Folder]\ThirdParty\mORMot\Source` - `[MARS Folder]\ThirdParty\Neslib.Yaml` - `[MARS Folder]\ThirdParty\Neslib.Yaml\Neslib` - `[MARS Folder]\ThirdParty\Delphi-Mocks\Source` (test projects: `MARS.Tests`, the `...Tests` project of a new application) 3. Build the runtime/design-time packages. For example, on **13 Florence**: - Open `[MARS Folder]\Packages\13Florence\MARS.groupproj` - **Build All** (it also builds the `JOSE` package of delphi-jose-jwt, required by `MARS.JOSE`) - Open `[MARS Folder]\Packages\13Florence\MARSClient.groupproj` - **Build All** - **Install** `MARSClient.CoreDesign` - **Install** `MARSClient.FireDACDesign` Adjust the package folder to match your Delphi version. ::: tip Compatibility MARS supports Delphi **10.4 Sydney** up to **13 Florence**. Earlier versions are not supported: compiling MARS with them stops with an explicit error. ::: ## Bootstrap a new project with MARSCmd MARS ships a small command-line utility that scaffolds a complete, ready-to-run project for you from a template: `MARSTemplate` (Indy) or `MARSTemplateDCS` (Delphi Cross Socket). 1. Compile and run [`MARScmd_VCL.dproj`](https://github.com/andrea-magni/MARS/blob/master/Utils/Source/MARScmd/MARScmd_VCL.dproj) in `[MARS Folder]\Utils\Source\MARScmd`. 2. Follow the prompts. Choose the template on the first page: MARSCmd lists the `Demos\MARSTemplate*` folders (`...` picks a template from another folder). It clones the template into a new folder with your chosen project name, giving you a server (console / VCL / FMX / service / ISAPI / Apache / daemon variants), a client, and a test project. The `.ini` files of the new project get a freshly generated random `JWT.Secret`. The settings of the new project are in `bin\Server.ini`, shared by all its server flavors (console, VCL, FMX, service, daemon, ISAPI, Apache, FastCGI): each flavor has its own small `.ini`, named after the executable, that includes `Server.ini` with an [`[Include]` section](/reference/parameters#shared-configuration-include) and can override any value (i.e. a different `Port`). The generated `JWT.Secret` is in `Server.ini`. The new project goes to `Documents\MARS Projects\` by default; next time MARSCmd proposes the folder used last (saved in `%APPDATA%\MARS-Curiosity\MARSCmd.ini`; a saved folder that no longer exists, or that is inside the MARS folder or the temp folder, is ignored). A destination inside the MARS folder asks for confirmation, as uninstalling or upgrading MARS would delete it, and an existing folder that is not empty is never overwritten. The template refers to the MARS folder with relative paths (`..\..\Source`). Outside the MARS folder, MARSCmd writes them as `$(MARSDIR)\Source`, `$(MARSDIR)\ThirdParty\...`: `MARSDIR` is the IDE environment variable set by the setup to the MARS folder (Tools ▸ Options ▸ IDE ▸ Environment Variables). With TMS Smart Setup or a manual installation, define it yourself or rely on the library path. This is the recommended way to start a brand-new MARS application — see [Your First Server](/guide/getting-started) for a walkthrough of what the generated code does. ## Project structure After installation, the repository layout is: | Folder | Contents | | --- | --- | | `Source` | The MARS library units (server + client). | | `Packages` | RAD Studio packages, one subfolder per Delphi version. | | `Demos` | Ready-to-run sample projects (see [Demos](/demos/)). | | `Utils` | Tools, including the `MARSCmd` project bootstrapper. | | `ThirdParty` | Bundled dependencies (Delphi-Cross-Socket, JOSE-JWT, mORMot, Neslib.Yaml, Delphi-Mocks): origin, version and license of each in [`ThirdParty/README.md`](https://github.com/andrea-magni/MARS/blob/master/ThirdParty/README.md). | | `tests` | DUnitX test suite. | --- Source: https://andrea-magni.github.io/MARS/guide/getting-started # Your First Server This walkthrough builds a minimal MARS server from scratch and explains every moving part. The same structure is what `MARSCmd` generates and what the [`MARSTemplate`](https://github.com/andrea-magni/MARS/tree/master/Demos/MARSTemplate) demo contains. A MARS server is made of three pieces of code you write: 1. A **resource** unit — your endpoints. 2. An **ignition** unit — creates and configures the engine. 3. A **host** — the executable that owns an HTTP server (console, VCL/FMX form, service, …) and forwards requests to the engine. ## 1. Define a resource A resource is a plain class decorated with `[Path]`. Each public method decorated with an HTTP-verb attribute becomes an endpoint. ```pascal unit Server.Resources.HelloWorld; interface uses SysUtils, Classes , MARS.Core.Attributes, MARS.Core.MediaType, MARS.Core.URL , MARS.Core.JSON, MARS.Core.Response; type [Path('helloworld')] THelloWorldResource = class public [GET, Produces(TMediaType.TEXT_PLAIN)] function SayHelloWorld: string; end; implementation uses MARS.Core.Registry; function THelloWorldResource.SayHelloWorld: string; begin Result := 'Hello World!'; end; initialization MARSRegister(THelloWorldResource); end. ``` Two things make this work: - **`[Path('helloworld')]`** mounts the resource at the `helloworld` path segment. - **`MARSRegister(THelloWorldResource)`** in the `initialization` section registers the class so applications can find it by name. Each resource unit registers itself this way. The `[GET, Produces(TMediaType.TEXT_PLAIN)]` method answers `GET …/helloworld` and tells MARS the result is `text/plain`. Returning a `string` is enough — MARS picks the right *MessageBodyWriter* to serialize it. ## 2. Ignite the engine The engine is created once for the lifetime of the process. A common pattern is a class with a class-constructor: ```pascal unit Server.Ignition; interface uses System.Classes, System.SysUtils , MARS.Core.Engine.Interfaces; type TServerEngine = class private class var FEngine: IMARSEngine; public class constructor CreateEngine; class destructor DestroyEngine; class property Default: IMARSEngine read FEngine; end; implementation uses MARS.Core.Engine, MARS.Core.Activation, MARS.Core.Activation.Interfaces , MARS.Core.Application.Interfaces, MARS.Core.RequestAndResponse.Interfaces , MARS.Core.MessageBodyWriter, MARS.Core.MessageBodyWriters , MARS.Core.MessageBodyReaders , MARS.Utils.Parameters.IniFile , Server.Resources.HelloWorld; class constructor TServerEngine.CreateEngine; begin FEngine := TMARSEngine.Create; // Engine configuration (Port, ThreadPoolSize, BasePath, ...) from .ini FEngine.Parameters.LoadFromIniFile; // Register an application that exposes all Server.Resources.* classes FEngine.AddApplication('DefaultApp', '/default', ['Server.Resources.*']); end; class destructor TServerEngine.DestroyEngine; begin FEngine := nil; end; end. ``` What this does: - `TMARSEngine.Create` builds the engine. `FEngine.Parameters.LoadFromIniFile` reads `Port`, `ThreadPoolSize`, `BasePath` and other settings from an `.ini` next to the executable. - `AddApplication('DefaultApp', '/default', ['Server.Resources.*'])` creates an [application](/server/application) at base path `/default` and registers every resource whose unit name matches `Server.Resources.*`. (You can also list resources by full class name.) With the engine's default `BasePath` of `/rest`, the hello-world endpoint is now reachable at: ``` GET http://localhost:8080/rest/default/helloworld ``` ## 3. Host it (the HTTP server) The engine doesn't open a socket by itself — you pair it with a host. The simplest host is an Indy-based console or VCL server that calls `Engine.HandleRequest`. MARS provides ready-made host helpers; the template's VCL host wires an Indy `TIdHTTPWebBrokerBridge` to the engine. A minimal console host looks like this: ```pascal program MyServer; {$APPTYPE CONSOLE} uses MARS.http.Server.Indy , Server.Ignition; var LServer: TMARShttpServerIndy; begin LServer := TMARShttpServerIndy.Create(TServerEngine.Default); try LServer.DefaultPort := TServerEngine.Default.Port; LServer.Active := True; Writeln('Server started on port ' + LServer.DefaultPort.ToString); Writeln('Press ENTER to stop.'); Readln; finally LServer.Free; end; end. ``` ::: tip Many hosts, one engine The same engine/resources can be hosted as a console app, VCL or FMX application, Windows service, Apache module, ISAPI DLL, FastCGI, or a Linux daemon. The `MARSTemplate` demo includes a project for each of these — only the host changes; your resources and ignition stay the same. ::: ## 4. Try it Run the server and call the endpoint with any HTTP client: ```bash curl http://localhost:8080/rest/default/helloworld # Hello World! ``` ## Returning JSON Return a `record` (or an object, or an array of them) and MARS serializes it to JSON automatically: ```pascal type TPerson = record Name: string; Age: Integer; end; [Path('people')] TPeopleResource = class public [GET, Produces(TMediaType.APPLICATION_JSON)] function GetFirst: TPerson; end; function TPeopleResource.GetFirst: TPerson; begin Result.Name := 'Andrea'; Result.Age := 42; end; ``` ```bash curl http://localhost:8080/rest/default/people # {"Name":"Andrea","Age":42} ``` ## Where to go next - [Core Concepts](/guide/core-concepts) — the mental model behind engine, application, resource and activation. - [Resources & Methods](/server/resources) — paths, sub-paths, return types, response control. - [Attributes](/server/attributes) — the full attribute toolbox for binding parameters. - [Authentication](/features/authentication) — add JWT login and protected endpoints. --- Source: https://andrea-magni.github.io/MARS/guide/core-concepts # Core Concepts MARS has a small number of concepts that, once understood, explain everything else. This page is the mental model; the [Server section](/server/engine) covers each in depth. ## The object hierarchy ``` TMARSEngine (IMARSEngine) └── Application "DefaultApp" (base path /default) ├── Resource THelloWorldResource [Path('helloworld')] │ ├── method SayHelloWorld [GET] │ └── method ... └── Resource TPeopleResource [Path('people')] └── ... ``` | Concept | Type | Role | | --- | --- | --- | | **Engine** | `IMARSEngine` / `TMARSEngine` | Owns global configuration and routes every request. One per process. | | **Application** | `IMARSApplication` | Groups resources under a base path; holds its own parameters (e.g. JWT secret). | | **Resource** | your class with `[Path]` | A unit of functionality; a class whose methods are endpoints. | | **Method** | your method with `[GET]`/`[POST]`/… | A single endpoint (operation). | | **Activation** | `IMARSActivation` | The per-request context that runs the whole pipeline. | | **Token** | `TMARSToken` | The authenticated identity (JWT claims + roles) for the request. | ## URLs are composed top-down A request URL is built by concatenating the levels: ``` http://host:port /rest /default /helloworld /42 └ engine └ application └ resource └ method sub-path BasePath base path [Path] [Path] + params ``` - The engine's `BasePath` (default `/rest`) is stripped first. - The next segment selects the **application** by its base path (`/default`). - The next segment selects the **resource** by its `[Path]`. - Remaining segments match the **method**'s `[Path]` (which may contain template parameters like `{id}`). ## Everything is declared with attributes You don't write routing or parsing code. You annotate classes, methods and parameters, and MARS reads those annotations via RTTI at runtime: ```pascal [Path('orders')] TOrdersResource = class [GET, Path('{id}'), Produces(TMediaType.APPLICATION_JSON)] function GetOrder([PathParam] id: Integer): TOrder; end; ``` - `[Path('orders')]` + `[Path('{id}')]` ⇒ route `…/orders/{id}`. - `[GET]` ⇒ HTTP method. - `[Produces(...)]` ⇒ the response media type. - `[PathParam] id` ⇒ bind the `{id}` URL segment to the parameter, converting it to `Integer`. See [Attributes](/server/attributes) for the full set. ## The request lifecycle (Activation) For every request the engine creates a `TMARSActivation` and runs three phases: 1. **Setup** — select resource + method, read authorization rules, check authentication & authorization, instantiate the resource, resolve method arguments, and perform `[Context]` injection. 2. **Invocation** — fire *before-invoke* hooks, call your method, serialize the result with a *MessageBodyWriter*, fire *after-invoke* hooks. 3. **Teardown** — free injected objects and the resource instance, fire *after-cleanup* hooks. Errors are funneled through dedicated *invoke-error* hooks and mapped to HTTP responses. See [Request Lifecycle](/server/request-lifecycle). ## Dependency injection with `[Context]` Resource fields, properties and method parameters marked `[Context]` are filled in by MARS before your code runs: ```pascal [Path('me')] TMeResource = class private [Context] Token: TMARSToken; // the authenticated identity [Context] Request: IMARSRequest; // raw HTTP request public [GET] function WhoAmI: string; end; function TMeResource.WhoAmI: string; begin Result := Token.UserName; end; ``` Built-in injectables include `IMARSRequest`, `IMARSResponse`, `TMARSURL`, `IMARSActivation`, `TMARSToken`, the `TFDConnection` / `TMARSFireDAC` (with the data units), the generated `TOpenAPI`, and configuration parameters via `[EngineParam]` / `[ApplicationParam]`. You can register your own. See [Parameters & Injection](/server/injection). ## Content negotiation MARS decides how to read the request body and write the response body using **MessageBodyReaders** and **MessageBodyWriters**, matched against: - the method's `[Consumes]` / `[Produces]` declarations, - the request's `Content-Type` / `Accept` headers, - the Delphi type involved (string, record, object, array, `TJSONValue`, `TStream`, `TDataSet`/`TFDDataSet`, …). You rarely touch this directly — returning a record gives JSON, returning a `TStream` gives a binary download — but you can register custom readers/writers for your own formats. See [Content Negotiation](/server/content-negotiation). ## Security model - **Authentication** is JWT-based. A login resource (typically a subclass of `TMARSTokenResource`) validates credentials and issues a signed token, delivered as a Bearer header or a cookie. - **Authorization** is declarative via `[PermitAll]`, `[DenyAll]` and `[RolesAllowed('admin')]` on resources/methods. The activation enforces them before invoking your code. See [Authentication](/features/authentication) and [Authorization](/features/authorization). ## Server *and* client The same library contains a [client](/client/overview) made of RAD components (`TMARSClient`, `TMARSClientApplication`, `TMARSClientResource*`, `TMARSClientToken`). Their hierarchy mirrors the server's (Client → Application → Resource), so consuming a MARS server from Delphi feels symmetric — including automatic JSON ↔ record mapping and FireDAC dataset synchronization. --- Source: https://andrea-magni.github.io/MARS/guide/deployment # Deployment A MARS server is one core (`Server.Ignition.pas` plus your resource units) that you can host in several ways. The [`MARSTemplate`](/demos/#marstemplate) and `MARSTemplateDCS` templates (and the projects [MARSCmd](/guide/installation#bootstrap-a-new-project-with-marscmd) creates from them) contain a ready project for each host. | Host | Project in the template | Typical use | | --- | --- | --- | | Console | `...ServerConsoleApplication` | development, quick tests | | VCL / FMX application | `...ServerApplication`, `...ServerFMXApplication` | development, desktop tools | | Windows service | `...ServerService` | production on Windows | | Linux daemon | `...ServerDaemon` (`...ServerDCSDaemon`) | production on Linux, Docker | | ISAPI (IIS) | `...ServerISAPI` | inside IIS | | Apache module | `...ServerApacheModule` | inside Apache httpd | | FastCGI | `...ServerFCGI` | behind nginx | The self-hosted flavors (console, GUI, service, daemon) run their own HTTP server: Indy (`TMARShttpServerIndy`, `MARSTemplate`) or Delphi Cross Socket (`TMARShttpServerDCS`, `MARSTemplateDCS`). The ISAPI, Apache and FastCGI flavors go through WebBroker and the web server does the HTTP part. ## Files to deploy Next to the executable: - the `.ini` files: `Server.ini` with the settings shared by all the flavors and `.ini` that [includes it](/reference/parameters#shared-configuration-include); - the certificate and private key, if the server terminates HTTPS itself ([HTTPS](/server/engine#https)); - the OpenSSL libraries for HTTPS on Windows (`libssl-3-x64.dll`, `libcrypto-3-x64.dll` for DCS); - the static files, if any (i.e. Swagger UI, see below). The `.ini` files are found next to the executable whatever the working directory, so services and daemons read them too. ::: warning Static files and paths The templates serve Swagger UI from the `www` folder of MARS with a relative Windows path (`RootFolder('{bin}\..\..\..\www\swagger-ui-3.52.5-dist', True)`), which works on the development machine only. For a deployment copy the folder next to the executable and use a portable path, for example: ```pascal [Path('www/{*}'), RootFolder('{bin}' + PathDelim + 'www', True), MetaVisible(False)] ``` `{bin}` is the folder of the executable; `PathDelim` makes the same code work on Windows and Linux. ::: ## Windows service The `...ServerService` project is a standard VCL service. Its name and display name come from the configuration (`ServiceName`, `ServiceDisplayName` in its `.ini` file), so copies of the executable in different folders, each with its own `.ini` files, can run side by side as different services. ```bat REM from an administrator prompt MyProjectServerService.exe /install sc start MyProjectService REM remove it sc stop MyProjectService MyProjectServerService.exe /uninstall ``` ## Linux with systemd Build the `...ServerDaemon` project for the Linux64 platform (RAD Studio with the Linux SDK and PAServer), then copy the binary and the `.ini` files to the server, i.e. to `/opt/myproject`. The daemon runs in two ways: - `./MyProjectServerDaemon` detaches from the terminal (classic daemon: fork, new session) and logs to `MyProjectServerDaemon.log` next to the binary; - `./MyProjectServerDaemon --foreground` (or `-f`) stays in the current process and logs to standard output. This is what systemd and Docker expect. Both stop on `SIGTERM` (and `SIGINT` in foreground). The foreground mode is available after MARS 1.8.1; with older versions use `Type=forking` and no `--foreground`. A systemd unit, `/etc/systemd/system/myproject.service`: ```ini [Unit] Description=MyProject REST server After=network-online.target Wants=network-online.target [Service] Type=simple User=myproject WorkingDirectory=/opt/myproject ExecStart=/opt/myproject/MyProjectServerDaemon --foreground Restart=on-failure [Install] WantedBy=multi-user.target ``` ```bash sudo systemctl daemon-reload sudo systemctl enable --now myproject journalctl -u myproject -f ``` ## Docker Run the daemon in foreground as the main process of the container. A `Dockerfile` next to the Linux build output (`bin`): ```dockerfile FROM ubuntu:24.04 # OpenSSL is needed only if the server terminates HTTPS itself (DCS) RUN apt-get update \ && apt-get install -y --no-install-recommends openssl ca-certificates \ && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY bin/MyProjectServerDCSDaemon bin/*.ini /app/ COPY bin/www /app/www EXPOSE 8080 CMD ["./MyProjectServerDCSDaemon", "--foreground"] ``` ```bash docker build -t myproject . docker run -d --name myproject -p 8080:8080 myproject docker logs -f myproject ``` `docker stop` sends `SIGTERM` and the server stops cleanly. Use a base image close to the Linux SDK you build with (the RAD Studio Linux SDKs are Ubuntu based); `ldd ./MyProjectServerDCSDaemon` lists the libraries the binary needs. Keep secrets such as `JWT.Secret` out of the image: mount the `.ini` file holding them (`-v /etc/myproject/Server.ini:/app/Server.ini:ro`). ## Behind a reverse proxy The most common production setup: the MARS server listens on plain HTTP on the local machine (or in the container network) and a reverse proxy terminates HTTPS, with certificates renewed automatically (i.e. Let's Encrypt). nginx: ```nginx server { listen 443 ssl; server_name api.example.com; ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem; location / { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; # server-sent events: no buffering, long reads proxy_buffering off; proxy_read_timeout 1h; } } ``` The `X-Forwarded-*` headers matter: the [MCP](/features/mcp) OAuth metadata uses them to publish the public `https` address. On Windows the same works with IIS and Application Request Routing (ARR), or with Caddy (`reverse_proxy 127.0.0.1:8080`, certificates included). If browsers call the API from another origin, enable the `CORS.*` [parameters](/reference/parameters#engine-parameters). ## HTTPS without a proxy Both self-hosted servers can terminate HTTPS: `PortSSL` plus the certificate and key, see [HTTPS](/server/engine#https). - **DCS** loads a current OpenSSL (3.x) at run time. Use the certificate chain (`fullchain.pem`) as certificate file. - **Indy** requires OpenSSL 1.0.2 (`libeay32.dll`, `ssleay32.dll`), out of support since 2019. `Indy.SSL.Version=sslvTLSv1_2` (the default) means TLS 1.2 only, and with OpenSSL 1.0.2 Indy negotiates RSA key exchange only (no ECDHE, so no forward secrecy), which recent browsers may refuse. With Indy, prefer a reverse proxy for public endpoints. ## ISAPI, Apache, FastCGI These flavors build a library or a FastCGI program that the web server loads; ports, HTTPS and the process lifetime are up to the web server, so `Port`, `PortSSL` and the SSL parameters do not apply. - **IIS**: deploy the `...ServerISAPI` DLL in an application with the ISAPI handler enabled; the bitness of the DLL must match the application pool (32-bit pools need a Win32 build). - **Apache 2.4**: `LoadModule` the `...ServerApacheModule` library (`.dll` on Windows, `.so` on Linux) and map a location to its handler: ```apache LoadModule myproject_module modules/mod_myproject.so SetHandler mod_myproject-handler ``` The module name is the one exported by the project (`exports GModuleData name 'myproject_module'`). - **FastCGI**: run `...ServerFCGI` and point nginx (`fastcgi_pass`) or Apache to it. ## Production checklist - **Release build**, with a strong `JWT.Secret` in `Server.ini` (MARSCmd generates one per project; a RELEASE build refuses to issue tokens without it). See [Authentication](/features/authentication). - **HTTPS**: a reverse proxy, or DCS directly (see above). - **Indy**: `Indy.KeepAlive=true` (the `MARSTemplate` default since 1.8.1) and `ThreadPoolSize` at least as large as the expected concurrent connections: each open connection holds a thread and the pool size is also the connection limit. - **CORS** parameters if browsers call the API from other origins. - **Logging**: the [JSON logger](/features/logging#file-logging-for-grafana-json) for Grafana/Loki, or the daemon output collected by journald or Docker. - **Static files** copied next to the executable with a portable `RootFolder` (see above). - **Many cores, many concurrent requests**: the default Delphi memory manager serializes allocations from many threads; a multi-threaded memory manager (i.e. FastMM5) lets the server use all the cores. --- Source: https://andrea-magni.github.io/MARS/guide/agent-skills # AI Agent Skills MARS ships with three [Agent Skills](https://code.claude.com/docs/en/skills) that teach AI coding agents (Claude Code, Cowork, and any tool supporting the open `SKILL.md` format) how to work with MARS-Curiosity. They live in the [`Skills/`](https://github.com/andrea-magni/MARS/tree/master/Skills) folder of the repository: | Skill | What it does | | --- | --- | | **`mars-new-project`** | Scaffolds a new MARS server project — from the official `MARSTemplate` project group or from minimal bundled templates — and covers deployment (Windows service, ISAPI on IIS, Apache module, FastCGI, Linux daemon, HTTPS/SSL). | | **`mars-development`** | Day-to-day MARS development: resources and REST attributes, parameter binding, JWT authentication and roles, FireDAC dataset publishing, Server-Sent Events, WebStencils templating, client components, configuration and serialization. | | **`mars-mcp-server`** | Building [MCP servers](/features/mcp) with MARS: exposing Delphi methods as tools for AI agents with `[MCPTool]`, database tools via FireDAC, per-tool role authorization, Bearer and OAuth 2.1 authentication. | A skill is a folder with a `SKILL.md` file (instructions plus a `description` that tells the agent *when* to activate it) and optional `references/` and `assets/` files loaded on demand. Once installed, the agent picks the right skill automatically based on what you ask — you don't need to mention MARS explicitly if your code clearly uses it. ## Installation in Claude Code (recommended: plugin) The MARS repository is also a **Claude plugin marketplace**. From Claude Code: ``` /plugin marketplace add andrea-magni/MARS /plugin install mars-curiosity@mars ``` All skills become available immediately and are updated whenever a new version is published. They can also be invoked explicitly as slash commands: ``` /mars-curiosity:mars-new-project /mars-curiosity:mars-development /mars-curiosity:mars-mcp-server ``` ## Installation with npx (any AI tool) If you use other agentic tools besides Claude Code (Cursor, Codex, ...) or prefer not to use plugins, the [skills CLI](https://github.com/vercel-labs/skills) installs the same skills into any compatible tool with one command (requires Node.js, no permanent install): ``` npx skills add andrea-magni/MARS ``` Pick the skills and target tools when prompted (`--all` installs everything for every detected tool), and update later with `npx skills update`. ## Manual installation (Claude Code / Cowork) Alternatively, copy the skill folders into one of the standard skill locations: - `.claude/skills/` inside your project — project-level, shared with your team via git; - `~/.claude/skills/` — personal, available in every project. Example, from your project root (Windows): ``` git clone https://github.com/andrea-magni/MARS xcopy /E /I MARS\Skills\mars-development .claude\skills\mars-development xcopy /E /I MARS\Skills\mars-new-project .claude\skills\mars-new-project xcopy /E /I MARS\Skills\mars-mcp-server .claude\skills\mars-mcp-server ``` Verify with `/skills`, or simply ask the agent to create a MARS server. ## Installation in other AI agents The skills are plain Markdown following the open [Agent Skills](https://code.claude.com/docs/en/skills) format, so they are not tied to Claude: - **Agents with native skill support** — install with `npx skills add andrea-magni/MARS` (see above) or place the skill folders in the agent's skills directory (the same `SKILL.md` layout is being adopted by a growing number of tools). - **Any other agent (Copilot, ...)** — reference the skill files from your agent instructions file (`AGENTS.md`, `.cursorrules`, custom instructions, ...), for example: ```markdown When working on MARS-Curiosity (Delphi REST) code, read and follow: - Skills/mars-development/SKILL.md (and its references/ folder) - Skills/mars-new-project/SKILL.md (when creating a new server) - Skills/mars-mcp-server/SKILL.md (when exposing tools to AI agents via MCP) ``` Since the instructions are self-contained Markdown, even a plain copy-paste into a system prompt works. ## Usage examples With the skills installed, prompts like these produce idiomatic MARS code without further guidance: **Scaffold a new server** (activates `mars-new-project`): > Create a new MARS REST server called `OrdersAPI`, console host, listening on port 8090. The agent generates the `.dpr`, `Server.Ignition.pas`, resource units and ini configuration from the bundled templates, with correct library paths and JWT backend selection. **Add an endpoint** (activates `mars-development`): > Add a `GET /customers/{id}` endpoint returning a customer record as JSON. ```pascal [Path('customers'), Produces(TMediaType.APPLICATION_JSON)] TCustomersResource = class public [GET, Path('{id}')] function GetById([PathParam] id: Integer): TCustomer; end; ``` **Secure endpoints with JWT**: > Protect the customers resource so only authenticated users with the 'admin' role can POST. The agent knows about `Token` resources, `[Context] Token: TMARSToken` injection and the `[RolesAllowed('admin')]` attribute. **Publish a dataset**: > Expose the `EMPLOYEE` table from my FireDAC connection as a REST endpoint. The agent applies the FireDAC integration patterns from `Skills/mars-development/references/firedac.md`. **Expose tools to AI agents** (activates `mars-mcp-server`): > Make my orders search callable from Claude via MCP, and restrict order cancellation to the 'manager' role. The agent derives a `TMCPResource` (or `TMCPDataResource` for FireDAC-backed tools), annotates methods with `[MCPTool]`/`[MCPParam]` and applies per-tool `[RolesAllowed]` — see [MCP Servers](/features/mcp). **Deploy**: > Turn this console server into a Windows service and prepare an ISAPI dll for IIS. Deployment recipes come from `Skills/mars-new-project/references/deployment.md`. ::: tip Contributions The skills are versioned with the library — improvements and corrections are welcome via Pull Request, like any other part of MARS. ::: --- Source: https://andrea-magni.github.io/MARS/guide/faq # FAQ Short answers to the most common questions, each with a link to the page that covers the topic in depth. ## General ### What is MARS-Curiosity? An open source (MPL 2.0) library to build REST servers and REST clients with Embarcadero Delphi. Endpoints are plain Delphi classes with attributes (JAX-RS style); MARS handles routing, parameters, JSON serialization, authentication, OpenAPI and hosting. See [Why MARS?](/guide/why-mars). ### Which Delphi versions and platforms are supported? Delphi 10.4 Sydney to Delphi 13 Florence. Servers run on Windows and Linux; the client components use the Delphi RTL HTTP client, available on all the Delphi platforms. See [Introduction](/guide/introduction). ### Is MARS free for commercial use? Yes. MARS is released under the Mozilla Public License 2.0 (MPL 2.0): you can use it in commercial and closed source applications. The MPL is a file-level license: if you distribute modified MARS source files, those files stay under the MPL; your own units are not affected. ### How do I install MARS? Run the setup of the [latest release](https://github.com/andrea-magni/MARS/releases/latest), or install it with TMS Smart Setup (`tms install andreamagni.mars`), or add the sources to the library path. See [Installation](/guide/installation). ### Where do I get help? The [MARS forum on Delphi-Praxis](https://en.delphipraxis.net/forum/34-mars-curiosity-rest-library/) and the [GitHub issues](https://github.com/andrea-magni/MARS/issues). AI coding agents can learn MARS from the official [Agent Skills](/guide/agent-skills). ## Building a server ### How do I create a REST server in Delphi with MARS? Run MARSCmd (in the MARS folder, `Utils`), pick a template (`MARSTemplate` with Indy, `MARSTemplateDCS` with Delphi Cross Socket) and a project name: you get a project group with the server in several flavors (console, VCL, FMX, Windows service, Linux daemon and, with `MARSTemplate`, ISAPI, Apache and FastCGI), a client and a test project. See [Bootstrap a new project](/guide/installation#bootstrap-a-new-project-with-marscmd) and [Your First Server](/guide/getting-started). ### How do I return JSON? Return a record, an object, an array or a dataset: MARS serializes it. ```pascal [Path('people')] TPeopleResource = class public [GET, Produces(TMediaType.APPLICATION_JSON)] function GetFirst: TPerson; // a record: {"Name":"Andrea","Age":42} end; ``` See [Resources & Methods](/server/resources) and [JSON Serialization](/features/serialization). ### How do I read path, query and body parameters? Decorate the method parameters: `[PathParam]`, `[QueryParam]`, `[HeaderParam]`, `[CookieParam]`, `[FormParam]`, `[BodyParam]` (a record or an object read from JSON). See [Parameters & Injection](/server/injection). ### How do I return an error with a status code? Raise `EMARSHttpException.Create('Not found', 404)`, or an `EMARSWithResponseException` to send a structured error body. See [Error Handling](/server/error-handling). ### How do I expose a database table or query? Inject `[Context] FD: TMARSFireDAC` and return the dataset (`Result := FD.Query('SELECT ...')`); clients can also send back changes as a delta. See [FireDAC & Datasets](/features/firedac). ### Can I use my ORM or data access library? Yes. MARS has been designed to plug in whatever ORM or data access library you need, and not bundling one is a deliberate choice: use the one that fits your project. Register a custom injection service to hand your ORM session or repository to the resources with `[Context]`; FireDAC and UniDAC have ready integration. See [Parameters & Injection](/server/injection#writing-a-custom-injection-service) and [Why MARS?](/guide/why-mars#your-data-access-your-choice). ### How do I generate OpenAPI (Swagger) documentation? Add a resource returning `TOpenAPI` (the templates already have one, `Server.Resources.OpenAPI`): the document is generated from your resources, and the templates serve Swagger UI too. See [OpenAPI 3 & Swagger](/features/openapi). ### How do I enable CORS? Set the `CORS.*` parameters in the configuration file (`CORS.Enabled=True`, `CORS.Origin`, `CORS.Methods`, `CORS.Headers`). See [CORS](/server/engine#cors). ### How do I push events to clients? Return a `TMARSServerSideEvent` from a method that produces `text/event-stream` (server-sent events). See [Server-Sent Events](/features/sse). ### Can I serve HTML pages and static files? Yes: static files with `TFileSystemResource`, server-side templates with WebStencils, hypermedia with htmx. See [HTML & Templates](/features/templates). ## Security ### How do I protect an endpoint with JWT? Put `[RolesAllowed('standard')]` (or `[PermitAll]`) on the resource or on the method: requests need a valid token, sent as `Authorization: Bearer ` or as a cookie. See [Authorization](/features/authorization). ### How do users log in? Derive a resource from `TMARSTokenResource` and override `Authenticate` with your credential check; set `Token.UserName` and `Token.Roles` and MARS returns a signed JWT. The base implementation is a demo stub: always override it. See [Authentication](/features/authentication). ### Where is the JWT secret configured? In the configuration file, `JWT.Secret` (per application, i.e. `DefaultApp.JWT.Secret`). Projects created with MARSCmd get a random one; a RELEASE build refuses to issue tokens without it. Keys can be rotated with `JWT.KeyId`. See [Key rotation](/features/authentication#key-rotation). ### How do I enable HTTPS? Put the server behind a reverse proxy (nginx, IIS, Caddy) that terminates TLS, or let the server do it: set `PortSSL` and the certificate files; the Delphi Cross Socket server uses a current OpenSSL. See [HTTPS](/server/engine#https) and [Deployment](/guide/deployment#https-without-a-proxy). ## Deployment ### How do I run a MARS server on Linux? Build the Linux daemon project of your MARS project for Linux64 and run it as a systemd service (`--foreground`, `Type=simple`). See [Linux with systemd](/guide/deployment#linux-with-systemd). ### Can I run MARS in Docker? Yes: the Linux daemon in foreground mode is the main process of the container and stops on `docker stop`. See [Docker](/guide/deployment#docker). ### How do I run a MARS server as a Windows service? Use the service project of the template: install it with `/install` and configure its name in the `.ini` file. See [Windows service](/guide/deployment#windows-service). ### Indy or Delphi Cross Socket? Both host the same MARS server code. `MARSTemplate` uses Indy (one thread per connection, mature, many deployments); `MARSTemplateDCS` uses Delphi Cross Socket (asynchronous I/O, few threads, HTTPS with a current OpenSSL). See [Engine](/server/engine) and [Deployment](/guide/deployment). ### Can I host MARS in IIS or Apache? Yes, as an ISAPI DLL, an Apache module or a FastCGI program: the templates include these projects. See [Deployment](/guide/deployment#isapi-apache-fastcgi). ## Client ### How do I call a REST API from Delphi with MARS? Use `TMARSNetClient`, `TMARSClientApplication` and a resource component (`TMARSClientResourceJSON` for JSON), at design time or in code; the client works with any REST server, not only MARS. See [Client Overview](/client/overview). ### Which client component should I use? `TMARSNetClient` (Delphi RTL `TNetHTTPClient`, all platforms, system TLS) is the default choice; `TMARSHttpClient` gives finer control and server-sent events; `TMARSIndyClient` is for code bases standardized on Indy (it needs the OpenSSL libraries for HTTPS). See [Choosing a transport](/client/overview#choosing-a-transport). ### How do I avoid blocking the user interface? Use the asynchronous methods (`GETAsync`, `POSTAsync`, ...): the request runs in background and the completion handler runs in the main thread. See [Asynchronous calls](/client/resources#asynchronous-calls). ### How do I log the client requests? Assign the `OnLog` event of the client component or register a logger with `TMARSCustomClient.RegisterLogger`; sensitive headers and fields are masked by default. See [Client Logging](/client/logging). ## AI ### Can AI agents call my Delphi code? Yes: MARS has native support for the Model Context Protocol. Derive a resource from `TMCPResource` and mark methods with `[MCPTool]`; Claude, ChatGPT, Copilot or a local model can call them, with roles and OAuth 2.1. See [MCP Servers](/features/mcp). ### Can AI coding assistants write MARS code? Yes: the official [Agent Skills](/guide/agent-skills) teach Claude Code and other agents how to create and develop MARS projects. This documentation is also available as [`llms.txt`](https://andrea-magni.github.io/MARS/llms.txt) and [`llms-full.txt`](https://andrea-magni.github.io/MARS/llms-full.txt) for AI tools. --- Source: https://andrea-magni.github.io/MARS/server/engine # Engine The **engine** (`IMARSEngine`, implemented by `TMARSEngine` in `MARS.Core.Engine.pas`) is the top of the server hierarchy. It owns global configuration, holds the applications, and routes every incoming HTTP request to the right application and resource. You create exactly one engine per process. ## Creating the engine The recommended pattern is a class with a class-constructor (see [Your First Server](/guide/getting-started)): ```pascal uses MARS.Core.Engine, MARS.Core.Engine.Interfaces; FEngine := TMARSEngine.Create; FEngine.Parameters.LoadFromIniFile; // Port, BasePath, ThreadPoolSize, ... FEngine.AddApplication('DefaultApp', '/default', ['Server.Resources.*']); ``` The engine is reference-counted through `IMARSEngine`; release it by setting your reference to `nil`. ## Configuration via Parameters Engine settings live in `FEngine.Parameters`, a name/value store that can be loaded from an `.ini` file with `LoadFromIniFile`. Common engine parameters: | Parameter | Meaning | Typical default | | --- | --- | --- | | `Port` | HTTP port | `8080` | | `PortSSL` | HTTPS port | `0` (disabled) | | `ThreadPoolSize` | Worker threads | `75` | | `BasePath` | Engine root path stripped from every URL | `/rest` | ```ini [Engine] Port=8080 ThreadPoolSize=75 BasePath=/rest ``` You can read any parameter in code with `FEngine.Parameters.ByName('Port').AsInteger`, and inject parameters into resources with [`[EngineParam]`](/server/injection). ## Registering applications ```pascal function AddApplication(const AName, ABasePath: string; const AResources: TArray): IMARSApplication; ``` - `AName` — unique application name (e.g. used by `ApplicationByName`). - `ABasePath` — the URL segment that selects this application (e.g. `/default`). - `AResources` — resource class names or wildcards. `'Server.Resources.*'` registers every resource declared in matching units; you can also pass fully-qualified class names. ```pascal FEngine.AddApplication('DefaultApp', '/default', ['Server.Resources.*']); FEngine.AddApplication('Admin', '/admin', ['Admin.Resources.*']); ``` Look up applications later with `ApplicationByName`, `ApplicationByBasePath`, or iterate them with `EnumerateApplications`. See [Applications](/server/application). ## Request routing The HTTP host calls `Engine.HandleRequest(ARequest, AResponse)` for each request. The engine then: 1. Parses the URL and strips its `BasePath`. 2. Applies CORS handling if enabled. 3. Calls the `BeforeHandleRequest` hook (which may pre-handle or abort the request). 4. Matches the next path segment to an application's base path. 5. Calls the `OnGetApplication` hook (optional custom application selection). 6. Creates a `TMARSActivation` and runs the [request lifecycle](/server/request-lifecycle). 7. Calls the `AfterHandleRequest` hook. You don't call `HandleRequest` yourself; the [host](/guide/getting-started#_3-host-it-the-http-server) does. ## Engine hooks The engine exposes anonymous-method hooks you assign during ignition. ### BeforeHandleRequest Runs before application matching. Return `False` (and set `Handled`) to short-circuit. A common use is skipping `favicon.ico` and answering CORS pre-flight `OPTIONS`: ```pascal FEngine.BeforeHandleRequest := function (const AEngine: IMARSEngine; const AURL: TMARSURL; const ARequest: IMARSRequest; const AResponse: IMARSResponse; var Handled: Boolean): Boolean begin Result := True; if SameText(AURL.Document, 'favicon.ico') then begin Result := False; Handled := True; end; if FEngine.IsCORSEnabled and SameText(ARequest.Method, 'OPTIONS') then begin Handled := True; // answer pre-flight Result := False; end; end; ``` ### OnGetApplication Lets you override which application serves a request — useful for multi-tenant routing or a fallback application: ```pascal FEngine.OnGetApplication := procedure (const AEngine: IMARSEngine; const AURL: TMARSURL; const ARequest: IMARSRequest; const AResponse: IMARSResponse; var AApplication: IMARSApplication) begin if AApplication = nil then AApplication := FEngine.ApplicationByName('DefaultApp'); end; ``` ### AfterHandleRequest Runs after the activation completes — handy for logging or post-processing. ## HTTPS The self-hosted servers can serve HTTPS directly, without a reverse proxy in front. `Port` is the HTTP port and `PortSSL` the HTTPS one; either can be `0` to disable it. **Delphi Cross Socket** (`TMARShttpServerDCS`, `Demos/MARSTemplateDCS`): ```ini [DefaultEngine] Port=0 PortSSL=443 DCS.SSL.CertFile=fullchain.pem DCS.SSL.KeyFile=privkey.pem ``` - The certificate and its private key are PEM files; the certificate file can hold the whole chain (e.g. `fullchain.pem` of Let's Encrypt). Relative paths are relative to the folder of the executable. Defaults: `localhost.crt` and `localhost.key`. - OpenSSL is loaded at run time: `libssl-3-x64.dll` and `libcrypto-3-x64.dll` (Win64) or `libssl-3.dll` and `libcrypto-3.dll` (Win32), next to the executable or in the `PATH` (1.1 works too); on Linux the `libssl` package of the distribution. - In code: `SSLPort`, `CertificateFile`, `PrivateKeyFile`, or `Certificate`/`PrivateKey` with the PEM content. A missing certificate or OpenSSL library makes `Active := True` raise `EMARSDCSServerException`, with the reason. - HTTP and HTTPS are two DCS servers sharing the same engine (`HttpServer`, `HttpsServer` properties, for fine tuning). **Indy** (`TMARShttpServerIndy`): `Indy.SSL.CertFile`, `Indy.SSL.KeyFile`, `Indy.SSL.RootCertFile`, `Indy.SSL.Version`, `Indy.SSL.Mode`, see the [parameters reference](/reference/parameters#engine-parameters). With HTTPS, enable keep-alive too (`Indy.KeepAlive=true`): without it every request costs a new TLS handshake. `Request.IsSecure` tells whether the request came in over TLS to this server; the URL of the request (`TMARSURL`) and the OAuth metadata of [MCP](/features/mcp) use it. Behind a reverse proxy terminating TLS it is `False`, and the `X-Forwarded-Proto` header tells the original scheme. ## CORS When CORS is enabled (via parameters such as `CORS.Origin`, `CORS.Methods`, `CORS.Headers`), the engine adds the appropriate `Access-Control-*` headers. Check `FEngine.IsCORSEnabled` and handle the `OPTIONS` pre-flight in `BeforeHandleRequest` as shown above. ## Cross-cutting concerns at the engine level Two facilities are commonly configured during ignition: - **Global activation hooks** — `TMARSActivation.RegisterBeforeInvoke` / `RegisterAfterInvoke` / `RegisterInvokeError` apply to *every* request across all applications. See [Request Lifecycle](/server/request-lifecycle). - **Response compression** — register an `AfterInvoke` hook that gzips the response stream when the client sends `Accept-Encoding: gzip`: ```pascal if FEngine.Parameters.ByName('Compression.Enabled').AsBoolean then TMARSActivation.RegisterAfterInvoke( procedure (const AActivation: IMARSActivation) var LOut: TBytesStream; begin if ContainsText(AActivation.Request.GetHeaderParamValue('Accept-Encoding'), 'gzip') and Assigned(AActivation.Response.ContentStream) and (AActivation.Response.ContentStream.Size > 0) then begin LOut := TBytesStream.Create(nil); try AActivation.Response.ContentStream.Position := 0; ZipStream(AActivation.Response.ContentStream, LOut, 15 + 16); AActivation.Response.ContentStream.Free; AActivation.Response.ContentStream := LOut; AActivation.Response.ContentEncoding := 'gzip'; except LOut.Free; raise; end; end; end); ``` ## Next - [Applications](/server/application) — grouping resources and per-app configuration. - [Resources & Methods](/server/resources) — defining endpoints. - [Request Lifecycle](/server/request-lifecycle) — what happens inside an activation. --- Source: https://andrea-magni.github.io/MARS/server/application # Applications An **application** (`IMARSApplication`, implemented by `TMARSApplication`) groups related [resources](/server/resources) under a base path and carries its own configuration. An [engine](/server/engine) can host several applications — for example a public API at `/api` and an admin API at `/admin`. ## Creating an application Applications are created through the engine: ```pascal FEngine.AddApplication('DefaultApp', '/default', ['Server.Resources.*']); ``` - **Name** (`DefaultApp`) — unique identifier; use it with `ApplicationByName`. - **Base path** (`/default`) — the URL segment, after the engine `BasePath`, that selects this application. - **Resources** — an array of class names and/or wildcards. `AddApplication` returns the `IMARSApplication`, so you can configure it further: ```pascal var LApp := FEngine.AddApplication('DefaultApp', '/default', ['Server.Resources.*']); LApp.Parameters.Values['JWT.Secret'] := 'my-very-secret-key'; ``` ## Registering resources Each resource class registers itself in the global registry via `MARSRegister` in its unit `initialization`. An application then declares *which* of those resources it exposes: ```pascal // Wildcard: every resource declared in units named Server.Resources.* FEngine.AddApplication('App', '/app', ['Server.Resources.*']); // Explicit list by fully-qualified class name FEngine.AddApplication('App', '/app', ['Server.Resources.HelloWorld.THelloWorldResource', 'Server.Resources.Token.TTokenResource']); ``` Wildcards are the usual choice: add a new `Server.Resources.Foo.pas` unit, register the class in its `initialization`, make sure the unit is used by the ignition unit, and it is automatically exposed. ::: warning Use the unit in the ignition A resource unit's `initialization` only runs if the unit is reachable from the program. List your resource units in the `uses` clause of the ignition unit (as the template does) so their `MARSRegister` calls execute. ::: ## Application parameters Like the engine, an application has a `Parameters` collection. When you call `AddApplication`, the matching slice of the engine parameters is copied into the application, so you can keep per-application settings (a different JWT secret, a database connection name, feature flags) in the same `.ini`: ```ini [DefaultApp] JWT.Secret={788A2FD0-8E93-4C11-B5AF-51867CF26EE7} JWT.Duration=1 ``` Read them in code via `LApp.Parameters.ByName('JWT.Secret').AsString`, or inject them into resources with [`[ApplicationParam]`](/server/injection): ```pascal [Path('secure')] TSecureResource = class [ApplicationParam('JWT.Secret')] JWTSecret: string; end; ``` JWT-related parameters (`JWT.Secret`, `JWT.Duration`, `JWT.CookieEnabled`, …) are read per application, which is why authentication settings naturally live at the application level. ## Introspection `IMARSApplication` can enumerate what it exposes — used internally by the [metadata](/features/openapi) and OpenAPI generators, and available to you: ```pascal LApp.EnumerateResources( procedure (const AName: string; const AInfo: TMARSConstructorInfo) begin Writeln(AName); end); LApp.EnumerateEndpoints( procedure (const AName: string; const AInfo: TMARSConstructorInfo; const AMethodPath, AHttpMethod: string) begin Writeln(Format('%s %s', [AHttpMethod, AMethodPath])); end); ``` ## Multiple applications Hosting several applications under one engine is just multiple `AddApplication` calls: ```pascal FEngine.AddApplication('Public', '/api', ['Public.Resources.*']); FEngine.AddApplication('Admin', '/admin', ['Admin.Resources.*']); ``` Each gets its own base path, resource set and parameter slice. The engine routes by matching the first path segment after `BasePath` to an application's base path; the `OnGetApplication` hook can customize or provide a fallback (see [Engine](/server/engine#ongetapplication)). --- Source: https://andrea-magni.github.io/MARS/server/resources # Resources & Methods A **resource** is an ordinary Delphi class decorated with `[Path]`. Its public methods, decorated with HTTP-verb attributes, are your endpoints. Resources are instantiated *per request* — a fresh instance is created, used, and freed within a single [activation](/server/request-lifecycle), so they are inherently thread-safe with respect to instance state. ## Anatomy of a resource ```pascal unit Server.Resources.Orders; interface uses MARS.Core.Attributes, MARS.Core.MediaType, MARS.Core.JSON; type TOrder = record Id: Integer; Customer: string; Total: Currency; end; [Path('orders')] TOrdersResource = class public [GET, Produces(TMediaType.APPLICATION_JSON)] function List: TArray; [GET, Path('{id}'), Produces(TMediaType.APPLICATION_JSON)] function GetById([PathParam] id: Integer): TOrder; [POST, Consumes(TMediaType.APPLICATION_JSON), Produces(TMediaType.APPLICATION_JSON)] function Create([BodyParam] AOrder: TOrder): TOrder; [DELETE, Path('{id}')] procedure Delete([PathParam] id: Integer); end; implementation uses MARS.Core.Registry; // ... method bodies ... initialization MARSRegister(TOrdersResource); end. ``` The routes produced by this class (under application `/default`, engine `BasePath` `/rest`) are: | Method | Route | | --- | --- | | `List` | `GET /rest/default/orders` | | `GetById` | `GET /rest/default/orders/{id}` | | `Create` | `POST /rest/default/orders` | | `Delete` | `DELETE /rest/default/orders/{id}` | ## Registration Every resource unit registers its class in its `initialization` section: ```pascal initialization MARSRegister(TOrdersResource); ``` `MARSRegister` accepts a single class or an array of classes. The class is added to the global `TMARSResourceRegistry`; [applications](/server/application) then choose which registered resources to expose. ## Paths and sub-paths The route of a method is `resource [Path]` + `method [Path]`: - A class-level `[Path('orders')]` sets the resource root. - A method-level `[Path('{id}/items')]` appends a sub-path, which may contain template tokens (`{id}`) and a trailing wildcard (`{*}`) to capture the remainder of the URL. ```pascal [GET, Path('{id}/items/{itemId}')] function GetItem([PathParam] id, itemId: Integer): TItem; ``` A method without a `[Path]` answers the resource root for its verb. ## HTTP verbs One verb attribute per endpoint: `[GET]`, `[POST]`, `[PUT]`, `[DELETE]`, `[PATCH]`, `[HEAD]`, `[OPTIONS]`, `[QUERY]`. The same path can be served by several methods differing only by verb. `[QUERY]` maps the HTTP `QUERY` method (IETF draft *safe method with body*): a safe, idempotent request whose query travels in the body, like a `GET` with a payload. Bind the body with `[BodyParam]` exactly as for `POST`: ```pascal [Path('items'), Consumes(TMediaType.APPLICATION_JSON), Produces(TMediaType.APPLICATION_JSON)] TItemsResource = class public [QUERY] function Search([BodyParam] const AFilter: TItemFilter): TArray; end; ``` The Indy and DCS hosts accept the verb out of the box. On IIS the verb has to be allowed in the handler mapping, or IIS rejects it with `405` before MARS sees the request. The `query` operation only exists since OpenAPI 3.2, so `[QUERY]` endpoints are left out of the generated document unless you raise its version with the `OpenAPI.openapi` parameter (see [OpenAPI](/features/openapi)). On the client side, `TMARSClientCustomResource` has matching `QUERY` and `QUERYAsync` methods. ## Return types and serialization The method's **return type** drives serialization. MARS selects a [MessageBodyWriter](/server/content-negotiation) based on the type and the negotiated media type: | Return type | Typical output | | --- | --- | | `string`, numbers, booleans | text / JSON scalar | | `record` / `TArray` | JSON object / array | | `TObject` / `TArray` | JSON object / array | | `TJSONValue` | raw JSON | | `TStream` | binary download (`application/octet-stream` or your `[Produces]`) | | `TDataSet` / `TFDDataSet` / `TArray` | JSON (or FireDAC formats) — see [FireDAC](/features/firedac) | | `TMARSResponse` | full manual control of status, headers and body | | `TMARSServerSideEvent` | an SSE stream — see [Server-Sent Events](/features/sse) | A `procedure` (no result) typically yields `204 No Content` (or an empty `200`), suitable for `DELETE`/commands. ### Memory ownership of returned objects When you return a `TObject`/dataset/stream that MARS should serialize and then free, MARS manages it as part of the activation. If you return a reference you must **not** have freed by MARS (e.g. a long-lived singleton), mark the method with `[IsReference]` so the activation leaves it alone. ## Controlling the raw response For full control over status code, headers and body, return a `TMARSResponse`, or inject `IMARSResponse` with `[Context]` and set it directly: ```pascal [GET, Path('download')] function Download([Context] AResponse: IMARSResponse): TStream; begin AResponse.ContentType := 'application/pdf'; AResponse.SetCustomHeader('Content-Disposition', 'attachment; filename="report.pdf"'); Result := TFileStream.Create('report.pdf', fmOpenRead or fmShareDenyWrite); end; ``` You can also add response headers declaratively with `[CustomHeader('X-Powered-By', 'MARS')]` and set the content type with `[ContentType('...')]`. ## Binding request data to parameters Method parameters are filled from the request via attributes (covered fully in [Attributes](/server/attributes)): ```pascal [GET, Path('{id}')] function Search( [PathParam] id: Integer; // from the URL template [QueryParam] q: string; // from the query string ?q=... [HeaderParam('X-Trace')] trace: string; // from a request header [Context] Token: TMARSToken // injected identity ): TResult; ``` `[BodyParam]` deserializes the whole request body (using a [MessageBodyReader](/server/content-negotiation)) into the parameter type — a record, object, `TJSONValue`, `TArray<…>`, `TStream`, etc. If the body is missing or cannot be parsed where JSON is expected, the request is rejected with `400 Bad Request` before the method runs (see [Error Handling](/server/error-handling#errors-while-binding-parameters)). ## Resource state and `[Context]` fields Fields and properties marked `[Context]` are injected before the method runs — useful for dependencies needed by several methods: ```pascal [Path('customers')] TCustomersResource = class private [Context] FD: TMARSFireDAC; // a FireDAC helper bound to a connection [Context] Token: TMARSToken; public [GET] function List: TFDDataSet; end; ``` See [Parameters & Injection](/server/injection) for the full list of injectables and how to add your own. ## Sub-resources by convention There is no special "sub-resource locator" syntax: model nested routes with path templates (`{id}/items/{itemId}`) and, where it helps, split functionality across multiple resource classes sharing a path prefix. --- Source: https://andrea-magni.github.io/MARS/server/attributes # Attributes Attributes are how you describe endpoints in MARS. They live mainly in `MARS.Core.Attributes.pas` (with more in `MARS.Metadata.Attributes.pas`, `MARS.Core.JSON.pas` and the data units). This page groups them by purpose; see [Reference ▸ Attributes](/reference/attributes) for a compact cheat-sheet. All MARS attributes descend from `MARSAttribute`. ## Routing ### `[Path]` Sets the route segment. On a **class** it is the resource root; on a **method** it is appended as a sub-path. Supports template tokens `{name}` and a trailing wildcard `{*}`. ```pascal [Path('orders')] // resource TOrdersResource = class [GET, Path('{id}/items')] // method -> orders/{id}/items function Items([PathParam] id: Integer): TArray; end; ``` ### HTTP method attributes `[GET]`, `[POST]`, `[PUT]`, `[DELETE]`, `[PATCH]`, `[HEAD]`, `[OPTIONS]`, `[QUERY]`. Exactly one per endpoint method. Internally each derives from `HttpMethodAttribute` and matches the request's HTTP method. ## Content negotiation ### `[Produces('media/type')]` Declares the media type(s) a method can return. Multiple `[Produces]` are allowed; MARS picks the best match against the request's `Accept` header. ```pascal [GET, Produces(TMediaType.APPLICATION_JSON), Produces(TMediaType.APPLICATION_YAML)] function GetData: TData; ``` ### `[Consumes('media/type')]` Declares the media type(s) a method accepts for its request body — used to pick the [MessageBodyReader](/server/content-negotiation) for `[BodyParam]`. ```pascal [POST, Consumes(TMediaType.APPLICATION_JSON)] function Save([BodyParam] AData: TData): TData; ``` Use the constants in [`TMediaType`](/reference/media-types) (e.g. `TMediaType.APPLICATION_JSON`) rather than literal strings. ## Parameter binding These decorate **method parameters** and bind request data to them. The value is converted to the parameter's Delphi type (using a MessageBodyReader when one matches, otherwise a string-to-type fallback). | Attribute | Source | Example | | --- | --- | --- | | `[PathParam]` | a `{token}` in the route | `[PathParam] id: Integer` | | `[QueryParam]` | the query string `?q=…` | `[QueryParam] q: string` | | `[FormParam]` | a form field (urlencoded / multipart) | `[FormParam] file: TBytes` | | `[HeaderParam]` | a request header | `[HeaderParam('X-Trace')] trace: string` | | `[CookieParam]` | a cookie | `[CookieParam('sid')] sid: string` | | `[BodyParam]` | the entire request body | `[BodyParam] order: TOrder` | By default the parameter name is used to find the value; pass an explicit name to override: `[QueryParam('page')] aPage: Integer`. ### Collection binders To receive *all* values of a kind at once, use the plural attributes: ```pascal [GET, Path('{name}/{surname}')] function Info( [PathParams] AParams: TMARSPathParams; [QueryParams] AQuery: TMARSQueryParams; [Headers] AHeaders: TMARSHeaders; [Cookies] ACookies: TMARSCookies ): string; ``` (`[FormParams]` works similarly for form data.) See the [NewAttributesDemo](/demos/#newattributesdemo). ### `[Required]` Marks a bound parameter as mandatory; a missing value raises an HTTP error. `[PathParam]` is always required by nature. ```pascal [GET] function Find([QueryParam, Required] q: string): TArray; ``` ## Dependency injection ### `[Context]` Injects a framework- or user-provided value into a **field, property or parameter** before the method runs. Built-in context values include `IMARSRequest`, `IMARSResponse`, `TMARSURL`, `IMARSActivation`, `TMARSToken`, `TFDConnection`/`TMARSFireDAC` (data units) and the generated `TOpenAPI`. ```pascal [Context] Token: TMARSToken; [Context] Request: IMARSRequest; ``` ### Configuration parameter injection Inject values from the engine/application `Parameters`: - `[EngineParam('Name'[, Default])]` — from engine parameters. - `[ApplicationParam('Name'[, Default])]` — from application parameters. ```pascal [ApplicationParam('JWT.Secret')] JWTSecret: string; [EngineParam('Port', 8080)] Port: Integer; ``` There are also function-valued variants (`[EngineParamFunc]`, `[ApplicationParamFunc]`) that inject a `TConfigParamFunc` for dynamic lookups. See [Parameters & Injection](/server/injection) for registering custom injection services. ## Security Applied to a **resource** (affecting all its methods) or an individual **method** (overriding the resource-level rule): | Attribute | Effect | | --- | --- | | `[PermitAll]` | Allow any caller, authenticated or not. | | `[DenyAll]` | Deny everyone (highest priority). | | `[RolesAllowed('a','b')]` | Allow callers whose token has **any** listed role. | ```pascal [Path('admin'), RolesAllowed('admin')] TAdminResource = class [GET, PermitAll] function Ping: string; // public [DELETE, Path('{id}')] procedure Remove([PathParam] id: Integer); // admin only end; ``` See [Authorization](/features/authorization). ## Response shaping | Attribute | Effect | | --- | --- | | `[ContentType('media/type')]` | Force the response `Content-Type`. | | `[CustomHeader('Name','Value')]` | Add a fixed response header. | | `[Encoding('UTF8')]` | Set the text encoding used when serializing (see below). | | `[IsReference]` | The returned object is a reference MARS must **not** free. | | `[JSONP(True)]` | Wrap a JSON response as JSONP using a `callback` query parameter. | ## Lifecycle hooks (method-level) Mark methods on a resource to run at specific points of the [activation](/server/request-lifecycle): | Attribute | When it runs | | --- | --- | | `[BeforeInvoke]` | Before the selected endpoint method is invoked. | | `[AfterInvoke]` | After the endpoint method returns. | | `[InvokeError]` | When the endpoint method raises. | | `[AfterContextCleanup]` | After injected context objects are freed (teardown). | These complement the global `TMARSActivation.RegisterBeforeInvoke/AfterInvoke/InvokeError` hooks. ## Metadata & OpenAPI From `MARS.Metadata.Attributes.pas` and the OpenAPI units — they enrich the generated [OpenAPI 3 spec](/features/openapi): | Attribute | Effect | | --- | --- | | `[MetaSummary('...')]` | Short summary for a resource/method. | | `[MetaDescription('...')]` | Longer description for a resource/method/parameter. | | `[MetaVisible(False)]` | Hide the resource/method from metadata and OpenAPI. | | `[OAPISummary]`, `[OAPIDescription]` | OpenAPI-only text for a resource, method, type, field or parameter. | | `[OAPIRequired]`, `[OAPIDefault]`, `[OAPIPattern]`, `[OAPIMinimum]`, `[OAPIMaximum]`, `[OAPIMinLength]`, `[OAPIMaxLength]` | JSON-schema hints on record/class fields and method parameters. | ## JSON serialization control From `MARS.Core.JSON.pas` — applied to record/class fields: | Attribute | Effect | | --- | --- | | `[JSONName('customName')]` | Map the field to a different JSON key. | | `[JSONSkip]` | Exclude the field from (de)serialization. | | `[JSONSkipEmptyValues]` / `[JSONIncludeEmptyValues]` | Override the empty/null inclusion policy. | See [JSON Serialization](/features/serialization). ## FireDAC From the data units — see [FireDAC & Datasets](/features/firedac): | Attribute | Effect | | --- | --- | | `[Connection('DEFNAME')]` | Select the FireDAC connection definition to inject. | | `[SQLStatement('Name','SELECT …')]` | Declare a named SQL statement on a dataset resource. | --- Source: https://andrea-magni.github.io/MARS/server/injection # Parameters & Injection MARS fills resource fields, properties and method parameters for you before your code runs. There are two related mechanisms: - **Request parameter binding** — `[PathParam]`, `[QueryParam]`, `[BodyParam]`, … pull data out of the HTTP request (see [Attributes](/server/attributes#parameter-binding)). - **Context injection** — `[Context]` supplies framework objects and your own services. This page focuses on `[Context]` injection and how to extend it. ## `[Context]` injection Decorate anything that should be supplied by the framework with `[Context]`: ```pascal [Path('me')] TMeResource = class private [Context] Token: TMARSToken; // injected field [Context] Request: IMARSRequest; public [GET] function WhoAmI([Context] AActivation: IMARSActivation): string; // injected parameter begin Result := Token.UserName + ' @ ' + AActivation.Id; end; end; ``` Injection happens during the **setup** phase of the [activation](/server/request-lifecycle), before authorization-cleared method invocation. Injected objects that MARS owns are freed during **teardown**, in reverse order. ## Built-in injectables Out of the box you can inject: | Type | What you get | | --- | --- | | `IMARSRequest` | The HTTP request (method, headers, body, query/form params, cookies). | | `IMARSResponse` | The HTTP response (status, headers, content stream) for manual control. | | `TMARSURL` | The parsed request URL (path tokens, query string). | | `IMARSActivation` | The whole per-request context (timings, resource/method RTTI, token…). | | `IMARSEngine` / `IMARSApplication` | The hosting engine / application. | | `TMARSToken` | The authenticated identity — claims and roles (see [Authentication](/features/authentication)). | | `TFDConnection` | A FireDAC connection (with the data units; see [FireDAC](/features/firedac)). | | `TMARSFireDAC` | A FireDAC helper bound to a connection. | | `TOpenAPI` | The generated OpenAPI 3 document (with the OpenAPI injection service; see [OpenAPI](/features/openapi)). | ## Injecting configuration parameters Engine and application [parameters](/reference/parameters) can be injected directly: ```pascal [Path('cfg')] TCfgResource = class [EngineParam('Port', 8080)] Port: Integer; [ApplicationParam('JWT.Secret')] Secret: string; end; ``` - `[EngineParam('Name', Default)]` reads from `Engine.Parameters`. - `[ApplicationParam('Name', Default)]` reads from `Application.Parameters`. Both have function-valued variants (`[EngineParamFunc]`, `[ApplicationParamFunc]`) that inject a `TConfigParamFunc` — a `reference to function(const AName: string): TValue` — for looking up parameters dynamically at call time. ## How injection resolves For each `[Context]` destination, MARS consults the `TMARSInjectionServiceRegistry`. Each registered service answers two questions: *can I provide a value for this destination?* and *what is the value?* The first service that claims the destination wins. The framework's built-in services cover the types listed above; the data, token and OpenAPI units register their own. ## Writing a custom injection service To inject your own dependency (a repository, a logged-in user object, a tenant context…), implement `IMARSInjectionService` and register it. ```pascal unit MyApp.Injection; interface uses System.Rtti , MARS.Core.Injection, MARS.Core.Injection.Interfaces, MARS.Core.Injection.Types , MARS.Core.Activation.Interfaces; type TMyServiceInjection = class(TInterfacedObject, IMARSInjectionService) public procedure GetValue(const ADestination: TRttiObject; const AActivation: IMARSActivation; out AValue: TInjectionValue); end; implementation uses MARS.Rtti.Utils; procedure TMyServiceInjection.GetValue(const ADestination: TRttiObject; const AActivation: IMARSActivation; out AValue: TInjectionValue); begin // Build the dependency for this request. // The second argument (True) means "MARS owns it" -> freed during teardown. AValue := TInjectionValue.Create(TMyService.Create(AActivation), True); end; initialization TMARSInjectionServiceRegistry.Instance.RegisterService( function: IMARSInjectionService begin Result := TMyServiceInjection.Create; end, function (const ADestination: TRttiObject): Boolean begin // Claim destinations typed as TMyService Result := ADestination.GetRttiType.IsObjectOfType(TMyService); end ); end. ``` Now any resource can simply declare it: ```pascal [Context] FService: TMyService; ``` ::: tip Ownership `TInjectionValue.Create(value, AOwned)` — set `AOwned := True` for objects MARS should free at teardown, `False` for shared/long-lived instances. ::: ## Parameter binding vs context injection Both use attributes and both run during setup, but: - **Binding** (`[PathParam]`, `[QueryParam]`, `[BodyParam]`, …) extracts *request data* and converts it to a value type, possibly via a [MessageBodyReader](/server/content-negotiation). - **`[Context]`** supplies *objects/services* (framework or custom) via the injection registry. A single method commonly mixes them: ```pascal [POST, Path('{id}'), Consumes(TMediaType.APPLICATION_JSON)] function Update( [PathParam] id: Integer; // binding [BodyParam] AData: TData; // binding (deserialized body) [Context] Token: TMARSToken // injection ): TData; ``` --- Source: https://andrea-magni.github.io/MARS/server/content-negotiation # Content Negotiation MARS converts between Delphi values and the bytes on the wire using two registries: - **MessageBodyReaders** — turn a request body (or a single parameter value) into a Delphi value. Used by `[BodyParam]` and the other binders. - **MessageBodyWriters** — turn a method's return value into the response body. You usually don't touch them directly: returning a record produces JSON, returning a `TStream` produces a binary download. But understanding the matching rules — and how to register your own — lets you support any format. ## How a writer is chosen When a method returns a value, MARS asks `TMARSMessageBodyRegistry` for the best writer, considering: 1. The **return type** (string, record, object, array, `TJSONValue`, `TStream`, `TDataSet`/`TFDDataSet`, …). 2. The method's **`[Produces]`** declarations. 3. The request's **`Accept`** header (with quality factors). 4. The writer's declared **`[Produces]`** and its **affinity**. Affinity breaks ties when several writers qualify: | Affinity | Constant | Used by | | --- | --- | --- | | 0 | `AFFINITY_ZERO` | catch-all fallbacks (e.g. primitive types, `*/*`) | | 10 | `AFFINITY_LOW` | generic `TObject` | | 50 | `AFFINITY_MEDIUM` | records, strings | | 100 | `AFFINITY_HIGH` | exact/specialized matches (e.g. FireDAC datasets) | The reader side works symmetrically against **`[Consumes]`** and the request `Content-Type`. ## Built-in writers Registered by `MARS.Core.MessageBodyWriters.pas` (and data units): | Writer | Handles | Produces | | --- | --- | --- | | `TObjectWriter` / `TArrayOfObjectWriter` | `TObject`, `TArray` | `application/json` | | `TRecordWriter` / `TArrayOfRecordWriter` | records, `TArray` | `application/json` | | `TJSONValueWriter` | `TJSONValue`, `TArray` | `application/json` | | `TPrimitiveTypesWriter` | numbers, booleans, strings | `*/*` | | `TStreamValueWriter` | `TStream` | `application/octet-stream`, `*/*` | | `TStandardMethodWriter` | wraps result + output params as JSON | `application/json` | | `TDataSetWriter` / `TArrayDataSetWriter` (data units) | `TDataSet`/`TFDDataSet` | JSON / FireDAC formats | Textual responses declare their encoding: the primitive-types writer appends `charset=…` to the content type (defaulting to `text/plain`) using the IANA name of the encoding — `utf-8` unless an `[Encoding('…')]` attribute on the method or the resource says otherwise. An explicit `charset` in `[Produces]` is left untouched. ## Built-in readers Registered by `MARS.Core.MessageBodyReaders.pas` (and data units): | Reader | Handles | Consumes | | --- | --- | --- | | `TObjectReader` / `TArrayOfObjectReader` | `TObject`, `TArray` | `application/json` | | `TRecordReader` / `TArrayOfRecordReader` | records, `TArray` | `application/json` | | `TJSONValueReader` | `TJSONValue` | `application/json` | | `TXMLReader` | `IXMLDocument` | `application/xml` | | `TStringReader` | `string` | `text/plain` | | `TStreamReader` | `TStream` | `application/octet-stream`, `*/*` | | `TFormParamReader` / `TArrayOfTFormParamReader` | `TFormParam`(s) | urlencoded, multipart | The JSON readers validate what they receive: if the body is missing or cannot be parsed as JSON where a record or object is expected, they raise `EMARSHttpException` with status `400`, and MARS keeps that status while wrapping the failure (see [Error Handling ▸ Errors while binding parameters](/server/error-handling#errors-while-binding-parameters)). So this method round-trips JSON with no extra code: ```pascal [POST, Consumes(TMediaType.APPLICATION_JSON), Produces(TMediaType.APPLICATION_JSON)] function Save([BodyParam] AOrder: TOrder): TOrder; // record in, record out ``` ## The reader/writer interfaces ```pascal IMessageBodyReader = interface function ReadFrom(const AInputData: TBytes; const ADestination: TRttiObject; const AMediaType: TMediaType; const AActivation: IMARSActivation): TValue; end; IMessageBodyWriter = interface procedure WriteTo(const AValue: TValue; const AMediaType: TMediaType; AOutputStream: TStream; const AActivation: IMARSActivation); end; // Optional: let MARS stream your content without buffering it first IMessageBodyStreamProvider = interface function GetStream(const AValue: TValue; const AMediaType: TMediaType; const AActivation: IMARSActivation): TStream; end; ``` ## Registering a custom writer Suppose you want to emit CSV for a particular record array. ```pascal type [Produces('text/csv')] TCsvWriter = class(TInterfacedObject, IMessageBodyWriter) public procedure WriteTo(const AValue: TValue; const AMediaType: TMediaType; AOutputStream: TStream; const AActivation: IMARSActivation); end; procedure TCsvWriter.WriteTo(const AValue: TValue; const AMediaType: TMediaType; AOutputStream: TStream; const AActivation: IMARSActivation); var LText: string; begin LText := MyValueToCsv(AValue); var LBytes := TEncoding.UTF8.GetBytes(LText); AOutputStream.WriteBuffer(LBytes, Length(LBytes)); end; initialization TMARSMessageBodyRegistry.Instance.RegisterWriter( TCsvWriter, function (AType: TRttiType; const AAttributes: TAttributeArray; AMediaType: string): Boolean begin Result := AType.IsDynamicArrayOf; // claim the type end, function (AType: TRttiType; const AAttributes: TAttributeArray; AMediaType: string): Integer begin Result := TMARSMessageBodyRegistry.AFFINITY_HIGH; end ); ``` A method that opts into it: ```pascal [GET, Produces('text/csv')] function Export: TArray; ``` Registering a custom **reader** follows the same shape with `TMARSMessageBodyReaderRegistry.Instance.RegisterReader` and an `IMessageBodyReader`. ## Where this fits in the pipeline Readers run during **setup** (when binding `[BodyParam]` and friends); writers run during **invocation**, right after your method returns, to fill `Response.ContentStream`. See [Request Lifecycle](/server/request-lifecycle). Because readers run before your method body, an exception raised there never reaches your code: it becomes the response. Raise `EMARSHttpException.Create('…', 400)` from a custom reader to reject invalid input with a proper client-error status. --- Source: https://andrea-magni.github.io/MARS/server/request-lifecycle # Request Lifecycle Every request is handled by a `TMARSActivation` (`MARS.Core.Activation.pas`), exposed to your code as `IMARSActivation`. Understanding its phases explains where authorization, injection, serialization and hooks happen — and where you can plug in. ## The big picture ``` Engine.HandleRequest └─ creates TMARSActivation, calls Invoke ├─ SETUP │ ├─ select resource + method (by URL + HTTP verb) │ ├─ read authorization rules ([DenyAll]/[PermitAll]/[RolesAllowed]) │ ├─ check authentication (token valid & not expired, if required) │ ├─ check authorization (token has an allowed role, if required) │ ├─ instantiate the resource │ ├─ resolve method arguments (binding + injection) │ └─ inject [Context] fields/properties ├─ INVOCATION │ ├─ BeforeInvoke hooks (global + [BeforeInvoke] methods) │ ├─ call your method │ ├─ serialize result via a MessageBodyWriter │ └─ AfterInvoke hooks (global + [AfterInvoke] methods) └─ TEARDOWN ├─ free injected context objects (reverse order) ├─ AfterContextCleanup hooks └─ free the resource instance ``` If your method raises, control jumps to **error handling** (see below and [Error Handling](/server/error-handling)). ## Setup phase 1. **Resource selection** — the URL path after the application base path is matched to a registered resource's `[Path]`. 2. **Method selection** — among that resource's methods, MARS finds the one whose HTTP-verb attribute matches the request and whose `[Path]` template matches the remaining URL tokens. 3. **Authorization rules** — `[DenyAll]`, `[PermitAll]`, `[RolesAllowed]` on the method and the resource are collected into an authorization descriptor. 4. **Authentication** — if the endpoint requires it, the [token](/features/authentication) must be present, verified and not expired; otherwise `403` is raised. Expired tokens are cleared. 5. **Authorization** — `[DenyAll]` always denies; `[PermitAll]` always allows; otherwise the token must hold at least one allowed role. See [Authorization](/features/authorization). 6. **Instantiation** — the resource class is constructed for this request. 7. **Argument resolution** — each method parameter is filled by binding (`[PathParam]`, `[QueryParam]`, `[BodyParam]`, …) or by `[Context]` injection. A failure here never reaches your method: it becomes an `EMARSApplicationException` naming resource, method and path, keeping the status of the original exception when it is an `EMARSHttpException` (so a malformed body is a `400`, not a `500`) — see [Error Handling](/server/error-handling#errors-while-binding-parameters). 8. **Context injection** — `[Context]` fields and properties on the resource are set. ## Invocation phase 1. **Before-invoke hooks** run: global ones registered with `TMARSActivation.RegisterBeforeInvoke`, then any method on the resource marked `[BeforeInvoke]`. A before-invoke hook can veto execution. 2. **Your method runs.** `[CustomHeader]` attributes are applied to the response. 3. **Serialization** — if the method returns a value, MARS picks a [MessageBodyWriter](/server/content-negotiation) (unless the result is a `TMARSResponse`, which is copied verbatim) and writes it to `Response.ContentStream`. 4. **After-invoke hooks** run: `[AfterInvoke]` methods, then global `RegisterAfterInvoke` handlers (e.g. gzip compression). ## Teardown phase Injected context objects that MARS owns are freed in reverse order, `[AfterContextCleanup]` methods and global after-cleanup handlers run, and the resource instance is freed. This guarantees per-request resources (connections, helpers) are released deterministically. ## The `IMARSActivation` object Injectable with `[Context]`, it exposes everything about the current request: | Member | Description | | --- | --- | | `Request`, `Response` | The HTTP request/response interfaces. | | `URL`, `URLPrototype` | The actual URL and the route template. | | `Token` | The authenticated identity (lazily created). | | `Engine`, `Application` | The hosting engine/application. | | `Method`, `MethodReturnType` | RTTI of the invoked method. | | `Resource`, `ResourceInstance` | RTTI and instance of the resource. | | `MethodArguments`, `MethodResult` | Resolved arguments and the return value. | | `Id` | A unique GUID for the activation (handy for logging/tracing). | | `SetupTime`, `InvocationTime`, `TeardownTime`, `SerializationTime` | `TStopwatch` timings for profiling. | ## Global hooks Register process-wide hooks during ignition. They apply to every request in every application: ```pascal // Run something before each activation; set AIsAllowed := False to block. TMARSActivation.RegisterBeforeInvoke( procedure (const AActivation: IMARSActivation; out AIsAllowed: Boolean) begin AIsAllowed := True; // e.g. rate-limiting, request logging, tenant resolution end); // Run something after each successful activation. TMARSActivation.RegisterAfterInvoke( procedure (const AActivation: IMARSActivation) begin LogRequest(AActivation.Method.Name, AActivation.InvocationTime.ElapsedMilliseconds); end); // Centralized error mapping. TMARSActivation.RegisterInvokeError( procedure (const AActivation: IMARSActivation; const AException: Exception; var AHandled: Boolean) begin if AException is EMyDomainException then begin AActivation.Response.StatusCode := 422; AActivation.Response.Content := AException.Message; AHandled := True; end; end); ``` ## Per-resource hooks The same four moments are available as method attributes on a resource, scoped to that resource: `[BeforeInvoke]`, `[AfterInvoke]`, `[InvokeError]`, `[AfterContextCleanup]`. They are convenient for resource-specific concerns (e.g. opening/closing a unit-of-work). ## Error handling When a method raises: - `[InvokeError]` methods and global `RegisterInvokeError` handlers get a chance to handle it (set `AHandled := True`). - Unhandled `EMARSHttpException` (and subclasses) map to their HTTP status and message. - `EMARSWithResponseException` serializes a custom payload as the error body. - Anything else becomes `500 Internal Server Error`. See [Error Handling](/server/error-handling) for the exception types and patterns. --- Source: https://andrea-magni.github.io/MARS/server/error-handling # Error Handling When a resource method raises, MARS turns the exception into an HTTP response. You control the status code, content type and body either by raising the right exception type or by handling errors centrally with an [invoke-error hook](/server/request-lifecycle#global-hooks). The exception types live in `MARS.Core.Exceptions.pas`. ## Exception hierarchy ``` Exception └─ EMARSException └─ EMARSHttpException (status + content type + reason) ├─ EMARSEngineException └─ EMARSApplicationException ├─ EMARSResourceNotFoundException ├─ EMARSMethodNotFoundException ├─ EMARSAuthenticationException ├─ EMARSAuthorizationException └─ EMARSWithResponseException (status + structured body) ``` Any exception that is **not** a MARS HTTP exception becomes `500 Internal Server Error`, with a generic `Internal server error` body (the exception class and message are appended only in `DEBUG` builds, so production servers do not leak internals). ## `EMARSHttpException` — status + message The workhorse. Raise it to return a specific HTTP status with a plain-text (or custom content-type) message. ```pascal constructor Create(const AMessage: string; const AStatus: Integer = 500; const AContentType: string = TMediaType.TEXT_PLAIN_UTF8; const AReasonString: string = ''); ``` ```pascal [GET, Path('{id}')] function GetById([PathParam] id: Integer): TItem; begin if not TryLoad(id, Result) then raise EMARSHttpException.Create('Item not found', 404); end; ``` Use `CreateFmt` for formatted messages: ```pascal raise EMARSHttpException.CreateFmt('Item %d not found', [id], 404); ``` The exception's `Status`, `ContentType` and `ReasonString` map directly onto the response. ::: tip Charset The default content type is `text/plain; charset=utf-8` (`TMediaType.TEXT_PLAIN_UTF8`), so a message containing non-ASCII characters (a localized exception text, a file name, …) reaches the client intact. The same applies to the generic `500` body produced for non-MARS exceptions. ::: ## `EMARSWithResponseException` — structured error body When clients need machine-readable error details, raise `EMARSWithResponseException` with a payload. By default the payload is serialized using the normal [MessageBodyWriter](/server/content-negotiation) machinery (`AUseMBW = True`), so a record or object becomes JSON. ```pascal constructor Create(const AMessage: string; const AContent: TValue; const AStatus: Integer = 500; const AReasonString: string = ''; const AContentType: string = TMediaType.APPLICATION_JSON; const AUseMBW: Boolean = True; const AIsReference: Boolean = False); ``` Example (from the [ErrorObjects demo](/demos/#errorobjects)): ```pascal type TErrorDetails = record TimeStamp: TDateTime; Details: string; ReferenceNumber: Integer; end; [GET, Path('MARSWithResponse')] function RaiseDetailed: string; var LError: TErrorDetails; begin LError.TimeStamp := Now; LError.Details := 'Details about the error!'; LError.ReferenceNumber := 123456; raise EMARSWithResponseException.Create( 'Error Message!', TValue.From(LError), 530, // custom status 'The reason of the error' // reason phrase ); end; ``` The client receives status `530` and a JSON body describing the error. ## Built-in exceptions raised by MARS | Exception | Status | Raised when | | --- | --- | --- | | `EMARSResourceNotFoundException` | 404 | The URL matches no resource. | | `EMARSMethodNotFoundException` | 404 | No method matches the verb/path. | | `EMARSAuthenticationException` | 403 | Authentication required but the token is missing/invalid/expired. | | `EMARSAuthorizationException` | 403 | The token lacks an allowed role. | | `EMARSApplicationException` | 400 | A parameter could not be bound because the request body is missing or malformed (see below). | | `EMARSApplicationException` | 500 | Any other failure while binding a parameter value. | You can catch and re-map these in an invoke-error hook if you want different status codes or bodies. ## Errors while binding parameters Parameter binding happens during the setup phase, before your method body runs (see [Request Lifecycle](/server/request-lifecycle)). When a binder or a [MessageBodyReader](/server/content-negotiation) raises, MARS wraps the failure in an `EMARSApplicationException` whose message names the resource, the method and the path: ``` Bad parameter value for method TItemResource.Create (/items). Malformed or missing request body (JSON object expected) ``` If the original exception is an `EMARSHttpException`, **its status is preserved** through the wrapping; anything else falls back to `500`. This is what lets a reader classify bad input as a client error: ```pascal // inside a custom IMessageBodyReader if not Assigned(LJSON) then raise EMARSHttpException.Create('Malformed or missing request body', 400); ``` The built-in JSON readers already do this. A body that the client got wrong — absent, not parsable, or not shaped like the parameter it has to fill — produces a clean `400 Bad Request`, instead of the `500` (or the silently empty value, later surfacing as an access violation on first field access) of earlier versions: | `POST /items` body | `[BodyParam] AItem: TItem` (record or class) | `[BodyParam] AItems: TArray` | | --- | --- | --- | | `{"id":1,…}` | bound | bound (array of one element) | | `[{"id":1,…},{"id":2,…}]` | `400` — JSON object expected | bound | | `[1,2,3]` | `400` — JSON object expected | `400` — JSON object expected at index 0 | | `42`, `"text"` | `400` — JSON object expected | `400` — JSON array expected | | *(empty body)*, `not json at all` | `400` — malformed or missing body | empty array | Follow the same convention in your own readers: raise `EMARSHttpException` with `400` for input the client got wrong, and let genuinely unexpected failures surface as `500`. ## Centralized error handling Register a global handler during ignition to map domain exceptions to HTTP responses in one place: ```pascal TMARSActivation.RegisterInvokeError( procedure (const AActivation: IMARSActivation; const AException: Exception; var AHandled: Boolean) begin if AException is EValidationError then begin AActivation.Response.StatusCode := 422; AActivation.Response.ContentType := TMediaType.APPLICATION_JSON; AActivation.Response.Content := ValidationErrorToJson(AException); AHandled := True; // stop further default handling end; end); ``` Set `AHandled := True` to take ownership of the response; leave it `False` to fall through to MARS's default mapping. You can also handle errors per resource with an `[InvokeError]` method (see [Request Lifecycle](/server/request-lifecycle#per-resource-hooks)), which is handy when only one resource needs special treatment. ## Consuming errors on the client The [MARS client](/client/overview) surfaces non-success responses through its `OnError` event and per-call exception callbacks, and `EMARSWithResponseException` bodies can be deserialized back into a record/object on the client side — see the [ErrorObjects demo](/demos/#errorobjects) for the round trip. --- Source: https://andrea-magni.github.io/MARS/features/authentication # Authentication (JWT) MARS uses **JSON Web Tokens (JWT)** for authentication. The flow is: 1. The client posts credentials to a *token resource*. 2. Your code validates them and sets the user name and roles. 3. MARS signs a JWT and returns it (as a Bearer token and/or a cookie). 4. The client sends that token on subsequent requests. 5. The activation verifies the token and enforces [authorization](/features/authorization). The token itself is represented by `TMARSToken` (`MARS.Core.Token.pas`). ## The token resource The quickest way to add login is to subclass `TMARSTokenResource`, which already implements the HTTP endpoints: ```pascal unit Server.Resources.Token; interface uses MARS.Core.Attributes, MARS.Core.MediaType, MARS.Core.Token.Resource; type [Path('token')] TTokenResource = class(TMARSTokenResource) end; implementation uses MARS.Core.Registry; initialization MARSRegister(TTokenResource); end. ``` That base class gives you, under `…/token`: | Verb | Method | Purpose | | --- | --- | --- | | `GET` | `GetCurrent` | Return the current token (to inspect validity / expiration). | | `POST` | `DoLogin` | Authenticate (form `username` + `password`) and issue a JWT. | | `DELETE` | `Logout` | Clear the token (and its cookie). | `DoLogin` consumes `application/x-www-form-urlencoded`, so the client sends `username=…&password=…`. ## Implementing credential validation Override `Authenticate` to check credentials and populate the identity. Set `Token.UserName` and `Token.Roles`; if you return `True`, MARS calls `Token.Build` with the application's JWT secret and returns the signed token. ```pascal type [Path('token')] TTokenResource = class(TMARSTokenResource) protected function Authenticate(const AUserName, APassword: string): Boolean; override; end; function TTokenResource.Authenticate(const AUserName, APassword: string): Boolean; begin Result := MyUserStore.CheckPassword(AUserName, APassword); if Result then begin Token.UserName := AUserName; if MyUserStore.IsAdmin(AUserName) then Token.Roles := ['standard', 'admin'] else Token.Roles := ['standard']; end; end; ``` Optional hooks let you run logic around the process: `BeforeLogin`, `AfterLogin`, `BeforeLogout`, `AfterLogout`, and `GetCredentials` (override the latter to read credentials from somewhere other than the form, e.g. JSON or Basic auth). ::: warning Default demo behavior The base `TMARSTokenResource.Authenticate` is a **demo stub** that accepts any user whose password equals the current hour. Always override it with real validation in production. ::: ## What `Token.Build` does `Token.Build(App.Parameters)` writes the standard claims (`iat`, `exp`, `iss`) plus `UserName` and `Roles`, signs the payload with HMAC-SHA256 using the application's signing key (see [Key rotation](#key-rotation)) and marks the token verified. `Token.Build(secret)` signs with an explicit secret instead. Duration and other settings come from the application [parameters](/reference/parameters): ```ini [DefaultApp] JWT.Secret= JWT.Issuer=MARS-Curiosity JWT.Duration=1 ; days (also JWT.Duration.InMinutes / .InSeconds) JWT.CookieEnabled=true JWT.CookieName=access_token JWT.CookieSecure=false ``` ::: danger The public default is never used silently `JWT_SECRET_PARAM_DEFAULT` ships in the public source, so MARS does not fall back to it. When `JWT.Secret` is missing, or still equal to that default, `TMARSToken.SecretFromParameters` applies `TMARSToken.DefaultSecretPolicy`: - `Generate` (the default in `DEBUG` builds): a random secret is created once per process and used to sign and verify; tokens do not survive a restart. `TMARSToken.GeneratedSecretInUse` tells you it happened, and a debug message is emitted on Windows. - `Refuse` (the default in `RELEASE` builds): the first operation that needs the secret raises `EMARSException` with an explicit message. The secret is required only where JWT is actually used: a request to a protected resource (`[RolesAllowed]`, `[PermitAll]`, `[DenyAll]`), or the issuing of a token. An application whose resources are all public needs no JWT configuration at all, even in `RELEASE` builds; a token sent to such an application cannot be verified and is simply never trusted (`IsVerified = False`). Set `JWT.AllowDefaultSecret=true` to knowingly keep the public default (never in production). Every reader of the secret, the token resource, the MCP OAuth server and the test helpers, goes through the same function. Projects created with MARSCmd get a random `JWT.Secret` in their `.ini` files. ::: ## Key rotation A secret should not live forever: rotate it periodically, and immediately if it may have leaked. Replacing `JWT.Secret` alone invalidates every token already issued, logging everybody out. To rotate without that, give each key an id and keep the previous key around for verification only, until the tokens it signed have expired: ```ini [DefaultApp] ; the active key: signs new tokens, its id goes in the "kid" header JWT.KeyId=2026-10 JWT.Secret= ; retired keys: accepted only for tokens carrying that "kid" JWT.PreviousSecret.2026-07= ``` The `kid` (key id, [RFC 7515](https://www.rfc-editor.org/rfc/rfc7515#section-4.1.4)) in the header of each token tells MARS which key signed it: - a token with a `kid` is checked only against the key with that id (the active one or a `JWT.PreviousSecret.`); an unknown `kid` makes it invalid; - a token without a `kid`, issued before key ids were configured, is checked against `JWT.Secret`, then against `JWT.PreviousSecret` (no suffix), if set. A rotation, step by step: 1. Generate a new secret and pick a new id (a date works well). Key ids use 1 to 64 characters among `A-Z a-z 0-9 . _ -`. 2. Move the current secret to `JWT.PreviousSecret.` (or to `JWT.PreviousSecret` if you were not using key ids yet), then set `JWT.KeyId` and `JWT.Secret` to the new ones. 3. After one token lifetime (`JWT.Duration`), remove the previous secret. If the old secret leaked, skip the waiting: drop it right away and accept that its tokens stop working. ::: warning Several servers Every server verifying the tokens must know the same keys. Update all of them before tokens signed with the new key reach them, for example by distributing the new key as `JWT.PreviousSecret.` first, then making it the active key everywhere. ::: Keys are read through `TMARSToken.KeyProvider` (`IMARSTokenKeyProvider`); the default `TMARSParametersTokenKeyProvider` implements the parameters above. Assign your own provider at startup to keep keys elsewhere, for example in a database or a secrets vault, or to rotate them automatically. ## Reading the identity in a resource Inject `TMARSToken` with `[Context]` to read the authenticated user and claims: ```pascal [Path('me')] TMeResource = class private [Context] Token: TMARSToken; public [GET] function WhoAmI: string; begin if not Token.IsVerified then raise EMARSAuthenticationException.Create('Not logged in', 403); Result := Token.UserName + ' [' + string.Join(',', Token.Roles) + ']'; end; end; ``` Useful `TMARSToken` members: | Member | Meaning | | --- | --- | | `Token` | The raw JWT string. | | `IsVerified` | Passed signature verification. | | `IsExpired` | `exp` is in the past. | | `UserName` | The authenticated user. | | `Roles` | `TArray` of granted roles. | | `HasRole(role)` | Membership test. | | `Claims` | All JWT claims as name/value pairs. | | `Expiration`, `IssuedAt`, `Duration`, `DurationSecs` | Lifetime info. | | `KeyId` | Id of the key that signed the token (`kid` header), empty when it has none. | | `Build(App.Parameters)` / `Load(token, App.Parameters)` | Issue / verify a token with the application's keys. | | `Build(secret)` / `Load(token, secret)` | Issue / verify a token with an explicit secret. | | `Clear` | Drop the token (and cookie). | ## Bearer header vs cookie MARS can carry the token two ways, both enabled by default when `JWT.CookieEnabled=true`: - **Authorization header** — `Authorization: Bearer `. - **Cookie** — e.g. `access_token=`; MARS sets it on login and reads it on each request. On the [client](/client/authentication), `TMARSCustomClient.AuthEndorsement` chooses between `Cookie` and `AuthorizationBearer`. ## JWT backends Two interchangeable signing backends are provided; pick one by adding the corresponding unit to your ignition `uses`: - **mORMot** — `MARS.mORMotJWT.Token` (common on Windows). - **JOSE** — `MARS.JOSEJWT.Token` (used on Linux and where JOSE is preferred). ```pascal {$IFDEF MSWINDOWS} , MARS.mORMotJWT.Token {$ELSE} , MARS.JOSEJWT.Token {$ENDIF} ``` Both produce standard HS256 tokens and accept only HS256 tokens (the `alg` in the token header never selects the algorithm); they differ only in the underlying library. The mORMot units ship with MARS. The JOSE backend relies on the [delphi-jose-jwt](https://github.com/paolo-rossi/delphi-jose-jwt) library: a git submodule of the MARS repository (`ThirdParty\delphi-jose-jwt`) and, with [TMS Smart Setup](/guide/installation#tms-smart-setup), the `paolo-rossi.delphi-jose-jwt` product. The `MARS.JOSE` package requires its `JOSE` package. ## Token renewal To keep a session alive without a fresh login, re-`Build` the token when it is close to expiry. See the [TokenRenew demo](/demos/#tokenrenew): ```pascal [Context] Token: TMARSToken; [Context] App: IMARSApplication; // ... if Token.IsVerified then begin var LRemaining := Round(TTimeSpan.Subtract(Token.Expiration, Now).TotalSeconds); if LRemaining < (Token.DurationSecs / 2) then Token.Build(App.Parameters); // issue a fresh token (with the active key), resetting the clock end; ``` ## Next - [Authorization](/features/authorization) — gate endpoints by role with `[RolesAllowed]`. - [Client ▸ Authentication](/client/authentication) — logging in from a Delphi client with `TMARSClientToken`. --- Source: https://andrea-magni.github.io/MARS/features/authorization # Authorization Once a request is [authenticated](/features/authentication), MARS decides whether it is *allowed* to run the selected method. Authorization is **declarative**: you annotate resources and methods, and the [activation](/server/request-lifecycle) enforces the rules before your code runs. ## The three attributes | Attribute | Effect | | --- | --- | | `[PermitAll]` | Skip the role check. Note: it does **not** waive authentication when roles are declared elsewhere on the endpoint (see below). | | `[DenyAll]` | Deny everyone. | | `[RolesAllowed('a', 'b')]` | Allow callers whose token holds **at least one** of the listed roles (which implies a valid token). | They can be placed on a **resource** (applies to all its methods) and/or on a **method** (overrides/refines the resource rule). ## Resolution rules When evaluating an endpoint, MARS **merges** the authorization attributes from the method *and* the resource class into a single set: `DenyAll`/`PermitAll` flags plus the **union** of all `[RolesAllowed]` roles from both levels. Two checks then run, in order: **Authentication first.** If the merged set contains *any* roles — whether they came from the method or from the resource class — a valid, verified token is required. A missing, invalid or expired token fails here with `403`, *before* `[PermitAll]` is even considered. **Then authorization**, with this precedence: 1. **`[DenyAll]` wins** → `403 Forbidden`, always. 2. Otherwise **`[PermitAll]`** → allowed (the role check is skipped — but authentication above has already run). 3. Otherwise, if there are **allowed roles**, the token must hold at least one → else `403`. 4. With **no authorization attribute at all**, the endpoint is open (default allow). Consequence: a method-level `[PermitAll]` on a resource marked `[RolesAllowed(...)]` does **not** make that endpoint public — any valid token is still required; `[PermitAll]` only bypasses the role check. An endpoint is truly open only when no roles appear at either level. ## Example ```pascal [Path('admin')] [RolesAllowed('admin')] // default for the whole resource: admin only TAdminResource = class protected [Context] Token: TMARSToken; public [GET, Path('ping')] [PermitAll] // any valid token: skips the 'admin' role check function Ping: string; // (NOT public: resource roles still require a token) [GET, Path('stats')] function Stats: TStats; // inherits resource rule: admin only [DELETE, Path('users/{id}')] [RolesAllowed('admin', 'super')] // either role may delete procedure DeleteUser([PathParam] id: Integer); [PATCH, Path('danger')] [DenyAll] // disabled, regardless of roles procedure Danger; end; ``` | Request | Outcome | | --- | --- | | `GET /admin/ping` (no token) | ⛔ 403 — authentication fails: the resource's `[RolesAllowed]` makes a token mandatory | | `GET /admin/ping` (token with `standard`) | ✅ allowed — `[PermitAll]` skips the role check | | `GET /admin/stats` (token with `standard`) | ⛔ 403 — needs `admin` | | `GET /admin/stats` (token with `admin`) | ✅ allowed | | `DELETE /admin/users/7` (token with `super`) | ✅ allowed | | `PATCH /admin/danger` (token with `admin`) | ⛔ 403 — `[DenyAll]` | ## Where roles come from Roles are part of the JWT, set during login (see [Authentication](/features/authentication)): ```pascal Token.UserName := AUserName; Token.Roles := ['standard', 'admin']; // becomes the "Roles" claim ``` At request time MARS reads them back from the verified token and checks them against `[RolesAllowed]` via `Token.HasRole`. The claim is stored as a comma-separated string, so a token issued elsewhere with `"Roles": "admin, manager"` works too: entries are trimmed and empty ones are dropped when the claim is parsed. ## Fine-grained checks in code For logic that can't be expressed with roles alone (ownership, tenant, attribute-based rules), inject the token and check inside the method: ```pascal [GET, Path('orders/{id}')] function GetOrder([PathParam] id: Integer; [Context] Token: TMARSToken): TOrder; begin Result := LoadOrder(id); if not SameText(Result.Owner, Token.UserName) and not Token.HasRole('admin') then raise EMARSHttpException.Create('Not your order', 403); end; ``` ## Cross-cutting authorization To enforce a policy across *every* endpoint (e.g. require a valid token globally, or check a tenant header), register a global before-invoke hook during ignition and veto by setting `AIsAllowed := False`: ```pascal TMARSActivation.RegisterBeforeInvoke( procedure (const AActivation: IMARSActivation; out AIsAllowed: Boolean) begin AIsAllowed := AActivation.Token.IsVerified or AActivation.Method.HasAttribute; end); ``` See [Request Lifecycle](/server/request-lifecycle#global-hooks). ## How clients see denials Both authentication and authorization failures return `403 Forbidden` (`EMARSAuthenticationException` / `EMARSAuthorizationException`). You can re-map these to other status codes or richer bodies in an [invoke-error hook](/server/error-handling#centralized-error-handling). --- Source: https://andrea-magni.github.io/MARS/features/firedac # FireDAC & Datasets MARS has deep, first-class support for **FireDAC**: a resource method can return a `TFDDataSet` (or an array of them) and MARS serializes it to JSON automatically; clients can send back the changed rows (a *delta*) and the server applies the updates. This makes Delphi-to-Delphi, data-aware REST servers extremely concise. The relevant units are `MARS.Data.FireDAC.pas`, `MARS.Data.FireDAC.Resources.pas`, `MARS.Data.FireDAC.ReadersAndWriters.pas`, `MARS.Data.FireDAC.InjectionService.pas` and `MARS.Data.MessageBodyWriters.pas`. Add them (and `MARS.Data.MessageBodyWriters`) to your ignition `uses` and build with the `MARS_FIREDAC` define. ## Enabling FireDAC During ignition, load the connection definitions from the engine parameters: ```pascal {$IFDEF MARS_FIREDAC} FAvailableConnectionDefs := TMARSFireDAC.LoadConnectionDefs(FEngine.Parameters, 'FireDAC'); {$ENDIF} ``` and close them on shutdown: ```pascal TMARSFireDAC.CloseConnectionDefs(FAvailableConnectionDefs); ``` Connection definitions live in your parameters file under the `FireDAC` slice, naming a connection def (e.g. `MAIN_DB`) that maps to a FireDAC `ConnectionDefName`. ## Injecting a connection Mark fields/parameters `[Context]`. The FireDAC injection service supplies either a raw `TFDConnection` or the higher-level `TMARSFireDAC` helper. The `[Connection('DEFNAME')]` attribute selects which definition to use (otherwise the default — typically `MAIN_DB` — is used): ```pascal [Path('customers')] TCustomersResource = class protected [Context] FD: TMARSFireDAC; // helper bound to the default connection [Context][Connection('REPORTS')] FRep: TFDConnection; // a specific definition public // ... end; ``` The injected connection is owned by the activation and released during teardown. ## Returning a dataset as JSON Just return the query — MARS picks the dataset writer: ```pascal [GET, Produces(TMediaType.APPLICATION_JSON)] function List: TFDDataSet; begin Result := FD.Query('SELECT id, name, total FROM customer ORDER BY name'); end; ``` Response: ```json [ { "id": 1, "name": "Acme", "total": 1234.56 }, { "id": 2, "name": "Globex","total": 987.00 } ] ``` Return `TArray` to ship several datasets in one call. Field types map naturally: numbers → JSON numbers, booleans → `true`/`false`, dates → ISO-8601 strings (configurable via [serialization options](/features/serialization)). ### The `TMARSFireDAC` helper `TMARSFireDAC` wraps a connection with convenient, context-aware methods: | Member | Purpose | | --- | --- | | `Query(sql[, transaction])` | Open a `TFDQuery`; auto-injects URL params/macros and is freed at teardown. | | `ExecuteSQL(sql[, transaction], …)` | Run a non-select command, returns affected rows. | | `CreateTransaction` / `InTransaction(proc)` | Manage transactions. | | `ApplyUpdates(datasets, deltas)` | Apply client changes, returns per-dataset results. | | `InjectParamValues` / `InjectMacroValues` | Bind `:param` / `{macro}` from the request context. | Parameters and macros named after request values are filled automatically. For example a query using `:QueryParam_newAddress` picks up the `newAddress` query string value. ## Transactions ```pascal [GET] function Report([QueryParam] newAddress: string): TFDDataSet; begin var LTx := FD.CreateTransaction(); LTx.StartTransaction; try FD.Query('select * from employee', LTx); FD.ExecuteSQL('update customer set address_line1 = :QueryParam_newAddress', LTx); Result := FD.Query('select * from sales left join customer ...', LTx); LTx.Commit; except LTx.Rollback; raise; end; end; ``` See the [ConnectionPoolingProject demo](/demos/#connectionpoolingproject). ## CRUD with `TMARSFDDatasetResource` For full read/write resources, subclass `TMARSFDDatasetResource`. It implements `GET` (retrieve) and `POST` (apply deltas) for you; you only declare the SQL via `[SQLStatement]` or by overriding `SetupStatements`: ```pascal [Path('orders')] TOrdersResource = class(TMARSFDDatasetResource) protected procedure SetupStatements; override; end; procedure TOrdersResource.SetupStatements; begin Statements.Add('orders', 'SELECT * FROM orders'); Statements.Add('items', 'SELECT * FROM order_items'); end; ``` - `GET …/orders` → returns all configured datasets as JSON. - `POST …/orders` with a JSON delta → calls `ApplyUpdates` and returns an array of `TMARSFDApplyUpdatesRes` (one per dataset, with applied count and any errors). ### Applying updates manually ```pascal [POST] function Update([BodyParam] const ADeltas: TArray): TArray; begin var LDataSets := [ FD.Query('select * from orders'), FD.Query('select * from order_items') ]; Result := FD.ApplyUpdates(LDataSets, ADeltas); end; ``` Each result row: ```json { "dataset": "orders", "result": 3, "errorCount": 0, "errors": [] } ``` ## Wire formats The FireDAC readers/writers support several media types so the *client* can choose efficiency vs interoperability: | Media type | Format | | --- | --- | | `application/json` | Plain JSON array of records (interoperable). | | `application/json;dialect=FireDAC` | Base64 of zipped FireDAC binary inside JSON (compact, Delphi-to-Delphi). | | `application/xml;dialect=FireDAC` | FireDAC native XML. | | `application/octet-stream` | Raw FireDAC binary. | A Delphi client using `TMARSFDResource` (see [Client ▸ FireDAC](/client/firedac)) negotiates the compact FireDAC format and reconstructs live `TFDMemTable`s, including change tracking for round-trip updates. ## UniDAC A parallel set of units (`MARS.Data.UniDAC.*`) provides equivalent support for **Devart UniDAC**, with the same patterns (`[Context]` connection injection, dataset readers/writers). --- Source: https://andrea-magni.github.io/MARS/features/serialization # JSON Serialization MARS maps Delphi values to and from JSON through the helpers in `MARS.Core.JSON.pas`. In day-to-day use you simply return records/objects/arrays and accept them as `[BodyParam]` — the [content-negotiation](/server/content-negotiation) layer does the rest. This page covers the conversion rules and how to customize them. ## What serializes to what | Delphi type | JSON | | --- | --- | | `string`, `Char` | string | | `Integer`, `Int64`, `Double`, `Currency` | number | | `Boolean` | `true` / `false` | | `TDateTime` / `TDate` / `TTime` | string (ISO-8601 by default) or Unix number | | `enum` | string or number | | `record` | object (one key per field) | | `class` (`TObject`) | object (published/visible properties) | | `TArray` / `TObjectList` | array | | `TJSONValue` | passed through as-is | | `TDataSet` / `TFDDataSet` | array of objects (see [FireDAC](/features/firedac)) | Nested records, arrays of records, and arrays of objects all serialize recursively. ## Direct conversion helpers When you need to convert explicitly (not through a resource result), use the `TJSONObject` class helpers: ```pascal uses MARS.Core.JSON; // record <-> JSON var LJson := TJSONObject.RecordToJSON(LPerson, DefaultMARSJSONSerializationOptions); var LPerson := TJSONObject.JSONToRecord(LJson, DefaultMARSJSONSerializationOptions); // object <-> JSON var LJson := TJSONObject.ObjectToJSON(LCustomer, DefaultMARSJSONSerializationOptions); var LCustomer := TJSONObject.JSONToObject(LJson); // any TValue -> TJSONValue var LValue := TJSONObject.TValueToJSONValue(TValue.From(LPerson), DefaultMARSJSONSerializationOptions); ``` When mapping JSON to an object, a member whose key is not in the JSON keeps its current value: defaults set by the constructor survive, sub-objects the constructor created stay in place (a nested JSON object fills them, it does not replace them), and `ToObject` on an existing instance merges the keys present into it. Declare a `_AssignedValues: TArray` field to be told which members actually came from the JSON. Members of a record start zeroed, so the same rule applies. ## Serialization options `TMARSJSONSerializationOptions` controls how empty/null values and dates are emitted. There is a global default you can tune once during ignition: ```pascal uses MARS.Core.JSON; // Include empty/null values in output... DefaultMARSJSONSerializationOptions.IncludeEmptyOrNullValues; // ...or strip them all DefaultMARSJSONSerializationOptions.SkipAllEmptyOrNullValues; ``` The fields you can set: | Field | Effect | | --- | --- | | `SkipEmptyStrings` | Omit `""` values. | | `SkipEmptyNumbers` | Omit zero numbers. | | `SkipEmptyBooleans` | Omit `false` values. | | `SkipEmptyObjects` / `SkipEmptyArrays` | Omit empty `{}` / `[]`. | | `SkipNullValues` | Omit `null`. | | `DateIsUTC` | Treat `TDateTime` as UTC. | | `DateFormat` | Reserved: dates are always written and read as ISO 8601. | | `UseDisplayFormatForNumericFields` | Use a field's display format for dataset numbers. | The default is "skip most empty/null values, ISO-8601 dates". `DateIsUTC` defaults to `True` only when the machine runs at UTC+0; set it explicitly (in code or in the configuration file) to get the same behavior everywhere. ### From the configuration file The same options can be set per application with `JSON.*` [parameters](/reference/parameters#json-parameters-per-application), without recompiling: ```ini [DefaultEngine] ; send empty strings too DefaultApp.JSON.SkipEmptyStrings=false ; keep dates in UTC DefaultApp.JSON.DateIsUTC=true ``` `JSON.SkipEmptyValues` sets all the `Skip*` options at once; a specific parameter written along with it wins. A value that is not `true`/`false` raises an error at the first request that uses it. Each request combines the options in this order, the last one winning: 1. the global default, `DefaultMARSJSONSerializationOptions` (set in code); 2. the `JSON.*` parameters of the application; 3. the attributes of the resource class (`[JSONIncludeEmptyValues]`, `[JSONSkipEmptyValues]`); 4. the attributes of the method. They apply to responses (objects, records, arrays, datasets) and to requests: the readers of objects and records use the same options, so dates are read with the `DateIsUTC` they are written with. MCP dataset results follow them too. ## Non-ASCII characters By default the JSON text of a response escapes every character above 127: `"Città"` is sent as `"Citt\u00E0"`. It is valid JSON and every client decodes it, but it is hard to read while debugging and takes more bytes. Set the `JSON.EscapeNonASCII` [application parameter](/reference/parameters#json-parameters-per-application) to `false` to send the characters as they are: ```ini [DefaultEngine] DefaultApp.JSON.EscapeNonASCII=false ``` or change the default for every application in code, during ignition: ```pascal uses MARS.Core.MessageBodyWriters; TJSONValueWriter.DefaultEscapeNonASCII := False; ``` Control characters (below 32) are always escaped, as JSON requires. When a resource sets a non-Unicode response encoding with `[Encoding]`, the escapes are kept, so no character is lost. The setting applies to every JSON response written by MARS: objects, records, arrays, datasets. ## Per-field control attributes Annotate record/class fields to override the global behavior: | Attribute | Effect | | --- | --- | | `[JSONName('customKey')]` | Serialize the field under a different JSON key. | | `[JSONSkip]` | Exclude the field entirely. | | `[JSONSkipEmptyValues]` | For this object, omit empty/null members. | | `[JSONIncludeEmptyValues]` | For this object, include empty/null members. | ```pascal type TUser = record [JSONName('user_name')] Name: string; Email: string; [JSONSkip] PasswordHash: string; // never leaves the server end; ``` `[JSONSkipEmptyValues]` / `[JSONIncludeEmptyValues]` can also decorate a *resource* or *method* to set the policy for its responses — as the [OpenAPI resource](/features/openapi) does with `[JSONSkipEmptyValues]`. ## Custom JSON shapes If you need a response shape that doesn't match a Delphi type one-to-one, you have three options, in increasing order of control: 1. Build a `TJSONObject`/`TJSONArray` yourself and return it (it passes through unchanged). 2. Register a custom [MessageBodyWriter](/server/content-negotiation#registering-a-custom-writer) for your type. 3. Return a `TMARSResponse` and write the body directly. ```pascal [GET, Produces(TMediaType.APPLICATION_JSON)] function Summary: TJSONObject; begin Result := TJSONObject.Create; Result.AddPair('count', TJSONNumber.Create(ComputeCount)); Result.AddPair('generatedAt', DateToISO8601(Now)); end; ``` ## YAML With `MARS.YAML.ReadersAndWriters` in your ignition `uses`, methods that `[Produces(TMediaType.APPLICATION_YAML)]` can emit YAML for the same record/object types — this is how the [OpenAPI](/features/openapi) endpoint serves both JSON and YAML from one method. --- Source: https://andrea-magni.github.io/MARS/features/openapi # OpenAPI 3 & Swagger MARS can generate an **OpenAPI 3** specification for an application directly from your resources — no hand-written YAML. It reflects over the registered resources, methods, parameters, return types and authorization rules to build a `TOpenAPI` document (`MARS.OpenAPI.v3.pas`), which you expose as JSON or YAML and render with **Swagger UI**. ## How it works 1. Add `MARS.OpenAPI.v3.InjectionService` to your ignition `uses`. This registers an injection service that builds a `TOpenAPI` for the current application on demand. 2. Declare a resource whose method takes `[Context] AOpenAPI: TOpenAPI` and returns it. MARS serializes it to JSON/YAML. 3. Optionally serve the Swagger UI static files from a folder. Internally, `TOpenAPIHelper.BuildFrom(engine, application)` drives a `TMARSMetadataReader` (see [Metadata](#metadata)) to enumerate everything and fills the OpenAPI object — including server URLs, the application path, and security schemes for JWT (Bearer and/or cookie) when the app has a JWT secret configured. ## Exposing the spec This is the canonical resource from the [`MARSTemplate`](https://github.com/andrea-magni/MARS/tree/master/Demos/MARSTemplate) demo: ```pascal unit Server.Resources.OpenAPI; interface uses MARS.Core.Attributes, MARS.Core.MediaType, MARS.Core.JSON , MARS.WebServer.Resources , MARS.OpenAPI.v3, MARS.Metadata.Attributes; type [Path('openapi'), MetaVisible(False), JSONSkipEmptyValues] TOpenAPIResource = class public [GET, Produces(TMediaType.APPLICATION_JSON), Produces(TMediaType.APPLICATION_YAML)] function GetOpenAPI([Context] AOpenAPI: TOpenAPI): TOpenAPI; end; [Path('www/{*}'), RootFolder('{bin}\..\..\..\www\swagger-ui-3.52.5-dist', True), MetaVisible(False)] TStaticContentResource = class(TFileSystemResource) end; implementation uses MARS.Core.Registry; function TOpenAPIResource.GetOpenAPI(AOpenAPI: TOpenAPI): TOpenAPI; begin Result := AOpenAPI; // injected & fully built for this application end; initialization MARSRegister([TOpenAPIResource, TStaticContentResource]); end. ``` Notes: - `[MetaVisible(False)]` keeps these helper resources out of the generated spec itself. - `[JSONSkipEmptyValues]` produces a clean document without empty members. - `GET …/openapi` returns the spec; an `Accept: application/yaml` header (with `MARS.YAML.ReadersAndWriters` registered) returns YAML instead. - `TStaticContentResource` serves the bundled Swagger UI from the repository's `www/swagger-ui-*` folder via `TFileSystemResource` + `[RootFolder]`. Endpoints (under application `/default`): ``` GET /rest/default/openapi → OpenAPI 3 JSON (or YAML) GET /rest/default/www/ → Swagger UI, pointed at the spec ``` ## Enriching the spec The generator reads metadata and JSON-schema attributes so you can produce a rich, accurate document: - **Summaries / descriptions** — `[MetaSummary('...')]`, `[MetaDescription('...')]` on resources, methods and parameters. - **Visibility** — `[MetaVisible(False)]` hides a resource or method. - **OpenAPI-specific text** — `[OAPISummary]` and `[OAPIDescription]` override the `[Meta…]` values in the generated document only; they can be put on a resource class, on a method, on a record/class type or one of its fields, and on a method parameter. - **Schema hints** — `[OAPIRequired]`, `[OAPIDefault]`, `[OAPIPattern]`, `[OAPIMinimum]`, `[OAPIMaximum]`, `[OAPIMinLength]`, `[OAPIMaxLength]` on record/class fields and on method parameters. - **Security** — `[RolesAllowed]` / `[PermitAll]` / `[DenyAll]` are reflected as security requirements; when JWT is configured, Bearer and cookie security schemes are added automatically. ```pascal [Path('users')] [MetaSummary('User management')] [MetaDescription('Create, list and remove application users')] [RolesAllowed('admin')] TUserResource = class public [GET] [MetaSummary('List users')] function List: TArray; [POST] [MetaSummary('Create user')] function Create([BodyParam] AUser: TUser): TUser; end; ``` Parameter kinds (`[PathParam]`, `[QueryParam]`, `[HeaderParam]`, `[BodyParam]`) and their Delphi types become the corresponding OpenAPI parameters, request bodies and schemas. The request body is documented with the media types of `[Consumes]` (on the method or on the resource). Without `[Consumes]` the media type depends on the parameters: `application/x-www-form-urlencoded` for `[FormParam]` parameters; for a `[BodyParam]`, `application/octet-stream` for `TStream` and `TBytes`, `multipart/form-data` for `TFormParam` and `TArray`, `text/plain` for `string`, `application/json` for anything else (records, objects, arrays), whose schema is added to `components/schemas`. A method that reads the body by itself (`Request.Body`, `Request.GetFormParamValue`, ...) instead of through `[BodyParam]`/`[FormParam]` parameters can describe it with `[MetaRequestBody]` (unit `MARS.Metadata.Attributes`): the qualified name of a record or class with the shape of the body, and an optional description. The built-in token resource uses it for the `username` and `password` form fields read by `GetCredentials`: ```pascal [POST, Consumes(TMediaType.APPLICATION_FORM_URLENCODED_TYPE) , MetaRequestBody('MARS.Core.Token.Resource.TCredentials', 'Credentials: username and password')] function DoLogin: TMARSToken; ``` Parameters describing the body, if any, take precedence. Without `[Consumes]` the media type follows the type, as for `[BodyParam]`. A name that cannot be resolved is ignored. Where each `[OAPI…]` attribute is read: | Placement | Attributes honored | | --- | --- | | Resource class | `[OAPISummary]`, `[OAPIDescription]` (become the tag's text) | | Method | `[OAPISummary]`, `[OAPIDescription]` (path item and operation) | | Record/class type | `[OAPIDescription]` (component schema) | | Record/class field or property | `[OAPIDescription]` + all schema hints | | Method parameter | `[OAPIDescription]` + all schema hints | ```pascal type [OAPIDescription('An application user')] TUser = record [OAPIRequired(True)] Name: string; [OAPIPattern('^[0-9]{5}$')] ZipCode: string; end; ... [GET, Path('search')] [OAPISummary('Full-text search')] function Search( [QueryParam] [OAPIDescription('Text to look for')] [OAPIMinLength('3')] q: string ): TArray; ``` `[OAPIRequired]` takes a Boolean, every other `[OAPI…]` attribute takes a string. ### Document version and `[QUERY]` endpoints The generated document declares OpenAPI `3.0.2`, the version the bundled Swagger UI renders. The engine parameter `OpenAPI.openapi` overrides it. This matters for `[QUERY]` endpoints: the `query` operation only exists since OpenAPI 3.2, so they are left out of the document unless the version is `3.2.0` or later: ```ini [Engine] OpenAPI.openapi=3.2.0 ``` ## Metadata OpenAPI generation is built on a general **metadata** layer (`MARS.Metadata.*`) that models your API as a tree of `TMARSApplicationMetadata` → `TMARSResourceMetadata` → `TMARSMethodMetadata` → `TMARSRequestParamMetadata`. `TMARSMetadataReader` populates it by RTTI reflection over the engine's applications. You can use this metadata directly for your own tooling — for instance, the [HtmxDemo](/demos/#htmxdemo) reads the generated OpenAPI document at runtime to render a live list of endpoints in an HTML page. ## Consuming the spec Because the document is standard OpenAPI 3, you can feed `…/openapi` into any compatible tool: Swagger UI (bundled), Postman, client-code generators, or API gateways. --- Source: https://andrea-magni.github.io/MARS/features/sse # Server-Sent Events **Server-Sent Events (SSE)** let a server push a stream of events to a client over a single, long-lived HTTP connection. MARS supports SSE on both ends: a resource method returns a `TMARSServerSideEvent` that writes events for as long as the client stays connected, and the [client](/client/resources) consumes them with `TMARSClientResourceSSE`. The server units are `MARS.Core.ServerSideEvents.pas` and `MARS.Core.ServerSideEvents.MessageBodyWriters.pas`. ## A streaming endpoint Declare a method that `[Produces(TMediaType.TEXT_EVENT_STREAM)]` and returns a `TMARSServerSideEvent`. Its constructor takes an anonymous procedure that receives the response stream; loop while `AStream.Connected` and write events: ```pascal uses MARS.Core.ServerSideEvents; type [Path('helloworld')] THelloWorldResource = class type TMyEventPayload = record sequence: UInt64; timeStamp: TDateTime; procedure Update; end; public [GET, Produces(TMediaType.TEXT_EVENT_STREAM)] function SayHelloWorld: TMARSServerSideEvent; end; function THelloWorldResource.SayHelloWorld: TMARSServerSideEvent; begin Result := TMARSServerSideEvent.Create( procedure (AStream: TWebResponseStream) var LPayload: TMyEventPayload; begin LPayload := Default(TMyEventPayload); try while AStream.Connected do begin LPayload.Update; AStream.WriteEvent('heartbeat'); // event: heartbeat AStream.Write(LPayload.sequence.ToString, LPayload); // id + data AStream.EndEvent; // blank line terminator Sleep(1000); end; except // client disconnected / write failed — exit quietly end; end , 500); // retry hint (ms) sent to the client end; ``` This is the [SSEDemo](/demos/#ssedemo). It pushes a `heartbeat` event once per second, each carrying a JSON payload (the record is serialized for you) and an incrementing `id`. ## The `TWebResponseStream` API Inside the writer callback you build the SSE wire format with helpers: | Call | Emits | | --- | --- | | `WriteEvent(name)` | an `event: ` line | | `Write(id, value)` | `id: ` and `data: ` lines | | `WriteData(text)` | a raw `data: ` line | | `EndEvent` | the blank line that dispatches the event | | `Connected` | `False` once the client has disconnected — your exit condition | The integer passed to `TMARSServerSideEvent.Create` (e.g. `500`) becomes the SSE `retry:` field, telling the browser how long to wait before reconnecting. ## Important: thread and connection handling An SSE handler **occupies its worker thread for the lifetime of the connection**. Keep this in mind: - Always loop on `AStream.Connected` and break out when it turns `False`. - Wrap the loop in `try/except` so a broken pipe ends the handler cleanly. - Size your engine's `ThreadPoolSize` for the number of concurrent streams you expect — each open stream holds a thread. - Use `Sleep`/event-wait to pace output; don't busy-loop. ## Consuming from a Delphi client `TMARSClientResourceSSE` (`MARS.Client.Resource.SSE.pas`) opens the stream and surfaces events through handlers: ```pascal SSEResource.Resource := 'helloworld'; SSEResource.OnMessage := procedure (const AEvent: string; const AId, AData: string) begin // AEvent = 'heartbeat', AData = JSON payload Memo1.Lines.Add(Format('[%s #%s] %s', [AEvent, AId, AData])); end; SSEResource.Open; // starts receiving; Close to stop ``` It exposes `OnOpen`, `OnMessage`, `OnComment`, `OnReconnect`, `OnClose` and `OnError`, and manages reconnection using the server's `retry` hint. See [Calling Resources](/client/resources). ## Browsers Because SSE is a standard, any browser `EventSource` can consume a MARS stream directly: ```js const es = new EventSource('/rest/default/helloworld'); es.addEventListener('heartbeat', e => console.log(e.lastEventId, e.data)); ``` The SSEDemo serves a small HTML page (via a `TFileSystemResource`) that does exactly this. --- Source: https://andrea-magni.github.io/MARS/features/mcp # MCP Servers (AI Agents) **Model Context Protocol (MCP)** is the open standard that lets AI agents (Claude, Claude Code, ChatGPT, Copilot and many others) discover and call tools exposed by a server. MARS ships first-class MCP support: derive a resource from `TMCPResource`, mark methods with `[MCPTool]`, and any MARS server becomes an MCP server that AI agents can connect to over the **Streamable HTTP** transport. The units are `MARS.MCP.pas`, `MARS.MCP.Attributes.pas` and `MARS.MCP.Resource.pas`. A complete example is available in `Demos/MCPServer`. ## An MCP resource ```pascal uses MARS.MCP.Resource, MARS.MCP.Attributes; type TCalculationResult = record operation: string; a: Double; b: Double; value: Double; end; [Path('mcp') , MCPServerInfo('My MCP Server', '1.0.0' , 'Optional instructions the AI agent will read on connection.') ] TMyMCPResource = class(TMCPResource) public [MCPTool('say_hello', 'Returns a friendly greeting for the given name')] function SayHello( [MCPParam('name', 'Name of the person to greet')] const AName: string): string; [MCPTool('add_numbers', 'Adds two numbers and returns a structured result')] function AddNumbers( [MCPParam('a', 'First operand')] const A: Double; [MCPParam('b', 'Second operand')] const B: Double): TCalculationResult; end; initialization MARSRegister([TMyMCPResource]); ``` That's all: the resource answers JSON-RPC 2.0 messages (`initialize`, `ping`, `tools/list`, `tools/call`) on POST, acknowledges notifications with `202 Accepted` and negotiates the MCP protocol version (`2025-06-18`, `2025-03-26`, `2024-11-05`). With the default engine configuration the endpoint is: ``` http://localhost:8080/rest/default/mcp ``` ## Tool discovery and JSON Schema `tools/list` is generated automatically via RTTI: - the **tool name** comes from `[MCPTool('name', 'description')]`, or the method name when omitted; - each **parameter** becomes a property of the tool's `inputSchema`, with the name and description taken from `[MCPParam('name', 'description')]` (parameter name when omitted); - parameters are **required**, unless marked `[MCPDefault('')]`: the parameter is left out of `required`, the schema advertises the value as `default`, and a missing or `null` argument is bound to it. The literal is converted like any argument: `[MCPDefault('true')]`, `[MCPDefault('7')]`, `[MCPDefault('"value_date"')]`, `[MCPDefault('[]')]`; - Delphi types map to JSON Schema: strings → `string`, integers → `integer`, floats → `number`, `Boolean` → `boolean`, enumerations → `string` with `enum` values, `TDateTime` → `string` with `format: date-time`, dynamic arrays → `array`, records → `object` (fields included recursively). ## Tool results The return value of the method becomes the tool result: - a `string` is returned as a `text` content block; - a **record** (or array, number, etc.) is serialized with the MARS [JSON serializer](/features/serialization) and returned both as `text` and — for records — as `structuredContent`; - exceptions raised inside the tool method are reported as tool execution errors (`isError: true`), while protocol errors (unknown tool, missing or invalid arguments) map to standard JSON-RPC error codes. ## Connecting an AI agent Any MCP-capable client can connect via Streamable HTTP. For example, with **Claude Code**: ```bash claude mcp add --transport http my-delphi-server http://localhost:8080/rest/default/mcp ``` or in a `.mcp.json` / `claude_desktop_config.json`: ```json { "mcpServers": { "my-delphi-server": { "type": "http", "url": "http://localhost:8080/rest/default/mcp" } } } ``` You can verify conformance interactively with the official [MCP Inspector](https://github.com/modelcontextprotocol/inspector): ```bash npx @modelcontextprotocol/inspector --cli http://localhost:8080/rest/default/mcp \ --transport http --method tools/list ``` ## Database tools with FireDAC Derive from `TMCPDataResource` (unit `MARS.MCP.Data`) and your tools can return any `TDataSet` — typically a `TFDQuery` obtained through the injected [`TMARSFireDAC`](/features/firedac). Rows are serialized automatically into the tool result as `structuredContent: { rowCount, rows }` plus a text fallback: ```pascal uses MARS.MCP.Data, MARS.Data.FireDAC, FireDAC.Comp.Client; type [Path('mcpdb'), RolesAllowed('standard')] TMyDBMCPResource = class(TMCPDataResource) protected [Context] FD: TMARSFireDAC; public [MCPTool('find_employees', 'Finds employees whose name contains the given text')] function FindEmployees( [MCPParam('nameContains', 'Text to search for')] const AText: string): TFDQuery; end; function TMyDBMCPResource.FindEmployees(const AText: string): TFDQuery; begin Result := FD.Query( 'select * from EMPLOYEES where upper(NAME) like upper(:TXT)' , nil, True , procedure (AQuery: TFDQuery) begin AQuery.ParamByName('TXT').AsString := '%' + AText + '%'; end); end; ``` Dataset results returned by `TMARSFireDAC.Query` are context-owned: MARS frees them at the end of the request, the MCP dispatcher never does. Rows go through the same [serialization options](/features/serialization) as an HTTP dataset response: the dispatcher applies the attributes found on the resource class first, then those on the tool method, exactly like `TDataSetWriterJSON`. So date formats, field-name casing and the other serialization attributes you already use on REST endpoints behave identically when the same data is exposed as an MCP tool. ## Resources and prompts Tools are what the *model* decides to call; MCP offers two more capabilities with a different controller: - **Resources** — readable content identified by a URI, that the *client or user* attaches to the conversation (in Claude they appear in the attach menu). Perfect for content the agent should *know* rather than *fetch*: database schemas, configuration, records under discussion. - **Prompts** — reusable, parameterized prompt templates the *user* picks from the client UI (slash-command style). The right place to encode how your tools are meant to be used. Both map to annotated methods, exactly like tools: ```pascal // static resource: listed in resources/list [MCPResource('db://schema', 'schema', 'DDL of the database: read this to learn tables and columns', 'text/plain')] function DbSchema: string; // URI template: listed in resources/templates/list; {id} binds to the parameter // (placeholders match the *exposed* parameter names — rename with [MCPParam]) [MCPResource('employees://{id}', 'employee', 'A single employee record by numeric id')] function EmployeeResource([MCPParam('id', 'Employee id')] const AId: Integer): TFDQuery; [MCPPrompt('salary_review', 'Guided salary review for an employee')] function SalaryReviewPrompt( [MCPParam('employeeName', 'Employee to review')] const AName: string): string; ``` Serialization follows the return type: strings become `text/plain` text, records/arrays/`TJSONValue` become `application/json` text, `TStream` becomes a base64 `blob`, and on a `TMCPDataResource` a `TDataSet` result becomes its rows as JSON. The MIME type can be forced via the attribute, otherwise it is inferred. A prompt method returning a `string` produces a single user message; return a `TJSONArray` to provide a full `messages` array verbatim. Authorization applies here too: `[RolesAllowed]`/`[DenyAll]` on resource or prompt methods hide them from the list requests, `resources/read` answers `Resource not found` (-32002) and `prompts/get` answers `Unknown prompt` for unauthorized callers. The `initialize` response advertises the `resources` and `prompts` capabilities only when the class actually declares any. Client support varies: Claude (Desktop/Code) handles both, several other clients are tools-only — design your server so tools remain self-sufficient. ## MCP Apps (interactive UIs) [MCP Apps](https://github.com/modelcontextprotocol/ext-apps) (extension `io.modelcontextprotocol/ui`) lets a tool come with an interactive HTML view that the host renders inline in the conversation, in a sandboxed iframe: charts, forms, dashboards. On the server side it takes two pieces, both plain attributes: - a **UI resource**: a `ui://` resource whose method returns the HTML document of the view (MIME type `text/html;profile=mcp-app`); - a **link** from the tool to that resource (`_meta.ui.resourceUri` in `tools/list`). ```pascal const DASHBOARD_VIEW = 'ui://my-server/dashboard.html'; // the host renders the result of this tool with the view below [MCPTool('server_dashboard', 'Shows an interactive dashboard about this server') , MCPToolUI(DASHBOARD_VIEW)] function ServerDashboard: TServerInfo; // called by the view only (e.g. a Refresh button): hidden from the model [MCPTool('dashboard_refresh', 'Refreshes the dashboard'), MCPToolUI(DASHBOARD_VIEW, 'app')] function DashboardRefresh: TServerInfo; // the view [MCPAppResource(DASHBOARD_VIEW, 'dashboard_view', 'Interactive server dashboard') , MCPAppCSP('https://api.example.com', 'https://cdn.jsdelivr.net') , MCPAppBorder(True)] function DashboardView: string; ``` How the pieces work together: 1. The host reads `tools/list`, sees `_meta.ui.resourceUri` and fetches the view with `resources/read`. 2. When the model calls the tool, the host shows the view and passes it the tool arguments and the result. The view uses `structuredContent`, so return a record or an object: MARS sends it both as `structuredContent` and as JSON text in `content`, the text being what the model and the hosts without MCP Apps support see. 3. The view can call tools of the same server (the host forwards a regular `tools/call`): with `MCPToolUI(uri, 'app')` a tool is reserved to the view and hosts hide it from the model. | Attribute | Where | Effect | | --- | --- | --- | | `MCPToolUI(uri [, visibility])` | tool method | `_meta.ui.resourceUri` (plus the deprecated `_meta["ui/resourceUri"]`, as the reference SDK does). `visibility`: `'model,app'` (default), `'app'` or `'model'`. | | `MCPAppResource(uri, [name,] description)` | method returning the HTML | a resource with MIME type `text/html;profile=mcp-app`; the URI must start with `ui://`. | | `MCPAppCSP(connect [, resource, frame, baseUri])` | UI resource | `_meta.ui.csp`: comma separated origins the view may reach (fetch/XHR/WebSocket, scripts/styles/images/fonts, nested iframes, base URIs). Without it the host blocks every external origin. | | `MCPAppBorder(Boolean)` | UI resource | `_meta.ui.prefersBorder`. | | `MCPMeta('')` | any tool or resource | free-form `_meta`, merged with the attributes above, e.g. `MCPMeta('{"ui":{"permissions":{"clipboardWrite":{}}}}')` or a host-specific `"domain"`. | The view talks to the host with JSON-RPC over `postMessage` (`ui/initialize`, then the `ui/notifications/tool-input` and `ui/notifications/tool-result` notifications, `tools/call` for interactions). It can use the [`@modelcontextprotocol/ext-apps`](https://www.npmjs.com/package/@modelcontextprotocol/ext-apps) SDK, or a few lines of plain JavaScript like the view of the [MCPServer demo](/demos/#mcpserver), which needs no external resource at all. ::: tip Testing The ext-apps repository includes `basic-host`, a minimal MCP Apps host to try your views locally. It listens on ports 8080 and 8081, so move the MARS server to another port (e.g. `Port=8090`) and start the host with `SERVERS='["http://localhost:8090/rest/default/mcp"]'`. It runs in the browser: enable CORS on the MARS engine (`CORS.Enabled=True`, with `mcp-protocol-version` among the `CORS.Headers`). ::: Since the server is stateless it does not look at the `io.modelcontextprotocol/ui` capability the client declares in `initialize`: UI metadata is always sent, hosts without MCP Apps support ignore it and use the text content. ## Authentication and authorization `TMCPResource` descendants are ordinary MARS resources, so both levels of the standard [authorization](/features/authorization) system apply: - **Endpoint level** — put `[RolesAllowed('standard')]` on the resource class: MARS answers `403` before any MCP message is processed unless the request carries a valid `Authorization: Bearer ` with that role. Issue tokens with a regular [token resource](/features/authentication) (`POST /rest/default/token`). Note that `[PermitAll]` alone does *not* require authentication — it allows any caller. - **Per-tool level** — put `[RolesAllowed('...')]` (or `[DenyAll]`) on individual tool methods: unauthorized tools are *hidden* from `tools/list` and `tools/call` answers as if they did not exist, so agents without the role never see them. ```pascal [RolesAllowed('admin')] [MCPTool('raise_salary', 'Raises the salary of an employee (admin only)')] function RaiseSalary(const AId: Integer; const APercent: Double): TOperationResult; ``` Most MCP clients support Bearer tokens for remote servers: in Open WebUI set the token in the External Tools connection, with MCP Inspector pass `--header "Authorization: Bearer "`, in a `.mcp.json` use the `headers` section. ## OAuth 2.1 (automatic onboarding for MCP clients) Static tokens require copy-paste; consumer MCP clients (Claude, ChatGPT connectors) expect the [MCP authorization flow](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) instead: automatic discovery, a browser login window, and token refresh. MARS ships a **self-contained OAuth 2.1 authorization server** in `MARS.MCP.OAuth`: authorization code + PKCE (S256), refresh token rotation, dynamic client registration (RFC 7591) and the RFC 8414/9728 metadata documents. The issued access token **is a regular MARS JWT**, so OAuth and statically issued tokens coexist on the same endpoint. Three steps: ```pascal // 1. an authorization server resource with your credential check [Path('oauth')] TMyOAuthServer = class(TMCPOAuthServer) protected function Authenticate(const AUserName, APassword: string; out ARoles: TArray): Boolean; override; end; // 2. mark the MCP resource: unauthenticated requests now answer // 401 + WWW-Authenticate (resource_metadata) instead of an error [Path('mcpdb'), MCPOAuth] TMyDBMCPResource = class(TMCPDataResource) ... // 3. serve the discovery documents (they live at the server root, // outside the engine's BasePath) from BeforeHandleRequest: if TMCPOAuthMetadata.HandleWellKnownRequest(ARequest, AResponse , FEngine.BasePath + '/default/oauth') then begin Result := False; Handled := True; end; ``` The user experience on an OAuth-capable client: add the connector URL → a browser window opens on your login page → sign in → the client stores and refreshes tokens automatically. `Authenticate` decides the roles, so per-tool `[RolesAllowed]` filtering applies to OAuth users too. The built-in login page carries a small *built with MARS-Curiosity* badge. To customize the page, either override `RenderAuthorizePage` or point `TMCPOAuthServer.SetAuthorizePageTemplateFile('...')` to an HTML file on disk: the file is read on each render (so it can be edited while the server runs) and falls back to the built-in page when missing. The template must contain a `
` with `username`/`password` inputs and supports these placeholders: `{client_name}`, `{scope}`, `{scope_note}`, `{error}`, `{hidden_fields}` (required inside the form: the hidden inputs carrying the OAuth request parameters), `{mars_footer}` (the MARS badge). See `Demos/MCPServer/CustomAuthorizePage.html` for a complete example. ::: warning Run behind HTTPS in production (a TLS-terminating reverse proxy or a tunnel like ngrok works fine: the public URLs of the OAuth metadata honor the `X-Forwarded-Proto`/`-Host`/`-Port` headers, so make sure the proxy sets them). Client/code/refresh-token storage is in-memory by default — call `TMCPOAuthServer.SetPersistenceFile('...')` at startup so registered clients and refresh tokens survive restarts (the file stores refresh tokens in cleartext: protect it), or override the `Store*`/`Consume*` virtual methods for a custom store. ::: ## Notes and current scope - The implementation is **stateless**: no `Mcp-Session-Id` is issued, `GET`/`DELETE` answer `405` (allowed by the specification for servers that do not offer server-initiated streams). Every JSON-RPC request is answered with a single `application/json` response. - Advertised capabilities cover **tools**, **resources** and **prompts** (the latter two only when the resource class declares any); subscriptions and `listChanged` notifications are not offered (they require server-initiated streams). Additional protocol methods can be added by overriding `TMCPDispatcher.DispatchRequest`. - When exposing the server beyond localhost, follow the MCP security guidance: validate the `Origin` header (e.g. in the engine's `BeforeHandleRequest`), bind to localhost when possible and require authentication. --- Source: https://andrea-magni.github.io/MARS/features/templates # HTML & Templates MARS is not only for JSON APIs — it can serve HTML, static files and server-rendered pages. This is handy for admin panels, landing pages, dashboards, or hypermedia front-ends (htmx). Several integrations are available; pick the one that fits your stack. ## Serving static files `TFileSystemResource` (`MARS.WebServer.Resources`) maps a URL path to a folder on disk. Subclass it and point `[RootFolder]` at the directory: ```pascal uses MARS.WebServer.Resources; type [Path('www/{*}'), RootFolder('.\www', True)] TStaticContentResource = class(TFileSystemResource) end; ``` - `{*}` captures the remainder of the URL as the file path within the root. - The second `[RootFolder]` argument is `IncludeSubFolders`. - `[RootFolder]` supports placeholders like `{bin}` for the executable folder, e.g. `RootFolder('{bin}\..\..\..\www\swagger-ui-3.52.5-dist', True)` — used to ship Swagger UI (see [OpenAPI](/features/openapi)). A request pointing at a directory serves the first match among `IndexFileNames` (`index.html`, `index.htm`, `default.html`, `default.htm`) and, failing that, a minimal HTML listing of the directory. Non-matching paths produce a `404`. The listing can be switched off with `[DirectoryListing(False)]` (or the `DirectoryListingEnabled` property): directories without an index file then answer `404`. Entry names are HTML-encoded in the page and percent-encoded in the links, so a file dropped in the folder cannot inject markup. Every response of the resource carries `X-Content-Type-Options: nosniff`, so browsers stick to the declared content type instead of guessing one from the bytes. `HEAD` requests are answered too, with the same status, `Content-Type` and `Content-Length` a `GET` would produce and no body: useful for link checkers, CDNs and clients probing a file's size before downloading it. The implementation reuses `GetContent`, so a subclass overriding it gets `HEAD` support for free. `[Exclude('mask')]` and `[Include('mask')]` (both repeatable, or the `ExclusionFilters` and `InclusionFilters` lists) restrict what the resource serves. Masks are matched case-insensitively against the full path of the file: an exclusion always wins and, once an inclusion is declared, only matching files are served. Filtered files answer `404` and are left out of the directory listing. ```pascal [Path('app/{*}'), RootFolder('{bin}\app', True), Exclude('*\web.config'), Exclude('*.map')] TAppResource = class(TFileSystemResource) end; ``` ### Path safety The request path is validated before the file system is touched, and anything that fails the checks is answered with `404`: - every segment is checked in isolation: `.` and `..` are rejected (by default, see below), so are segments containing a separator (an encoded `%2f` or `%5c`), `:` (drive letters, NTFS alternate data streams such as `file.txt::$DATA`), the characters `* ? " < > |`, control characters, and segments ending with a dot (which Windows silently strips; leading and trailing whitespace is trimmed off the URL tokens before they reach the resource); - the resulting path is canonicalized and must still lie under the canonical `RootFolder`; - with `IncludeSubFolders = False` only files directly in the root are served; - on Windows, 8.3 short names are expanded before the filters and the content-type lookup: `WEB~1.CON` opens the same file as `web.config`, and would otherwise slip past `[Exclude('*\web.config')]`. Some front-ends and generated pages rely on relative links such as `css/../img/logo.png`. Mark the resource with `[DotSegments]` (or set the `AllowDotSegments` property) to accept `.` and `..` segments: every other rule still applies, and the path may never climb above `RootFolder`, not even halfway through (`..//file` is a `404`, although it would resolve inside the root). `IncludeSubFolders = False` is evaluated on the resolved path. Note that browsers and most HTTP clients collapse dot-segments before sending the request, so the option mostly matters for other kinds of clients. ```pascal [Path('www/{*}'), RootFolder('{bin}\www', True), DotSegments] TWebResource = class(TFileSystemResource) end; ``` The two checks are independent on purpose. Both are virtual (`CheckPathSegment`, `ResolveFullPath`), so a subclass can tighten them further, for instance by limiting the allowed extensions. This is also how the [SSEDemo](/demos/#ssedemo) and OpenAPI/Swagger setup serve their HTML/JS assets. ### Content types and charset The extension of the file selects the `Content-Type` header, from a dictionary initialized in the virtual `InitContentTypesForExt` method; unknown extensions fall back to `application/octet-stream`. **Textual types are declared as UTF-8**: | Extension | Content-Type | | --- | --- | | `.htm`, `.html` | `text/html; charset=utf-8` | | `.css` | `text/css; charset=utf-8` | | `.js` | `application/javascript; charset=utf-8` | | `.txt` | `text/plain; charset=utf-8` | | `.jpg`, `.jpeg` | `image/jpeg` | | `.png` | `image/png` | | `.pdf` | `application/pdf` | The explicit `charset` matters: an HTTP `text/*` response that does not declare one is interpreted by the client with its own default (historically ISO-8859-1), and the header wins over the file's BOM, over a page's `` and over a stylesheet's `@charset`. Since files on disk are UTF-8 nowadays, declaring it here is what keeps accented characters intact. The bytes of the file are served untouched — only the declaration changed. The HTML directory listing is served as `text/html; charset=utf-8` for the same reason (entry names may contain non-ASCII characters). Add or override a mapping with `[ContentTypeForFileExt]`, which is applied after the defaults: ```pascal type [ Path('www/{*}'), RootFolder('.\www', True) , ContentTypeForFileExt('image/svg+xml', '.svg') // new extension , ContentTypeForFileExt('text/plain; charset=iso-8859-1', '.txt') // override ] TStaticContentResource = class(TFileSystemResource) end; ``` The dictionary is also reachable in code: override the virtual `InitContentTypesForExt` when the mapping is easier to express there (e.g. serving legacy files in another encoding): ```pascal type TLegacyContentResource = class(TFileSystemResource) protected procedure InitContentTypesForExt; override; end; procedure TLegacyContentResource.InitContentTypesForExt; begin inherited; ContentTypesForExt.AddOrSetValue('.txt', 'text/plain; charset=iso-8859-1'); end; ``` ## Returning HTML from a method Any method can return an HTML `string` with `[Produces(TMediaType.TEXT_HTML)]`: ```pascal [GET, Produces(TMediaType.TEXT_HTML)] function Home: string; begin Result := '

Hello

'; end; ``` For anything beyond trivial markup, use a template engine instead of string concatenation. ## WebStencils Embarcadero's **WebStencils** template engine integrates via `TMARSWebStencils`, injected with `[Context]`. You register variables and datasets, then render a template file: ```pascal [Path('helloworld')] THelloWorldResource = class protected [Context] FWS: TMARSWebStencils; public [GET, Path('/{datasetName}'), Produces(TMediaType.TEXT_HTML)] function RenderDataset([PathParam] datasetName: string): string; end; function THelloWorldResource.RenderDataset(datasetName: string): string; var LTable: TFDMemTable; begin LTable := TFDMemTable.Create(nil); try LTable.LoadFromFile(DatasetFileFor(datasetName)); LTable.Name := datasetName; FWS.AddVarValue('datasetName', datasetName); FWS.AddDataVar('dataset', LTable, True); // True: WebStencils owns it except LTable.Free; raise; end; Result := FWS.ContentFromFile('dataset.html'); // template iterates over @dataset end; ``` The template can iterate collections and bind values, producing fully server-rendered HTML backed by live FireDAC data. See the [WebStencilsDemo](/demos/#webstencilsdemo). ## htmx [htmx](https://htmx.org/) lets you build dynamic pages where HTML fragments are fetched and swapped into the DOM via attributes like `hx-get` / `hx-target`, with no SPA framework. MARS pairs naturally with it: expose endpoints that return JSON (or HTML fragments) and let htmx drive the page. The [HtmxDemo](/demos/#htmxdemo) reads the application's own [OpenAPI](/features/openapi) document at runtime and returns a list of endpoints that the page renders client-side: ```pascal function THelloworldResource.RetrieveData([Context] AOpenAPI: TOpenAPI): TDataResponse; begin Result := Default(TDataResponse); for var LPath in AOpenAPI.paths do begin var LEndpoint := TEndpoint.Create(LPath.Key, LPath.Value.Methods); LEndpoint.summary := LPath.Value.summary; Result.endpoints := Result.endpoints + [LEndpoint]; end; end; ``` ## DelphiRazor For projects already using **DelphiRazor**, the `MARS.DelphiRazor.*` units provide an injection service and resources to render Razor (`.cshtml`-style) templates from MARS endpoints, following the same `[Context]`-injection pattern as WebStencils. ## Which to choose? | Need | Use | | --- | --- | | Serve a folder of static assets | `TFileSystemResource` + `[RootFolder]` | | Server-rendered pages with Delphi data | **WebStencils** (`TMARSWebStencils`) | | Dynamic, partial-update UIs without a JS framework | **htmx** over JSON/HTML endpoints | | Existing Razor templates | **DelphiRazor** integration | | A pure SPA / mobile front-end | Just expose JSON; serve the built front-end as static files | --- Source: https://andrea-magni.github.io/MARS/features/logging # Request/Response Logging MARS can log every request and response by plugging into the [global activation hooks](/server/request-lifecycle#global-hooks). A logger registers `BeforeInvoke`, `AfterInvoke` and `InvokeError` handlers during ignition and writes an entry for each phase, so you get incoming requests, completed responses (with timing) and errors without touching your resource code. All loggers implement the small `IMARSReqRespLogger` interface (`MARS.Utils.ReqRespLogger.Interfaces.pas`) and self-register in their unit's `initialization`. **You enable a logger simply by adding its unit to your server's `uses` clause** (typically in `Server.Ignition.pas`), then toggling it with a configuration parameter. ## Available loggers | Unit | Sink | Typical use | | --- | --- | --- | | `MARS.Utils.ReqRespLogger.JSON` | **JSON Lines (NDJSON) file** | Production logging, ingestion by Grafana Alloy/Promtail → Loki | | `MARS.Utils.ReqRespLogger.CodeSite` | CodeSite | Development-time inspection | | `MARS.Utils.ReqRespLogger.Memory` | In-memory dataset | Live "last requests" views inside the app | ::: tip Pick one sink Each logger registers its own hooks, so adding several units means every request is logged several times. Enable the one that matches your target and leave the others out of the `uses` clause (or disable them via their parameter). ::: ## File logging for Grafana (JSON) `MARS.Utils.ReqRespLogger.JSON` (`TMARSReqRespLoggerJSON`) writes one JSON object per line to a log file. This **JSON Lines / NDJSON** format is exactly what log shippers such as [Grafana Alloy](https://grafana.com/docs/alloy/) and Promtail expect, so you can tail the file and forward the entries to Loki with a minimal pipeline. ### Enabling it Add the unit to your server's `uses` clause: ```pascal uses // ... , MARS.Utils.ReqRespLogger.JSON ; ``` Then turn it on in the engine section of your `.ini`: ```ini [DefaultEngine] JSONLogging.Enabled=True ; Folder default: \logs ;JSONLogging.Folder=C:\logs\mars JSONLogging.FileName=mars-reqresp.log JSONLogging.DailyRotation=True ``` ### Configuration parameters | Parameter | Type | Default | Purpose | | --- | --- | --- | --- | | `JSONLogging.Enabled` | Boolean | `False` | Master switch — the hooks check it on every activation. | | `JSONLogging.BuiltInEntries` | Boolean | `True` | Write the built-in `in`/`out`/`error` lines. Set it to `False` to write only [your own entries](#custom-entries-with-structured-data). | | `JSONLogging.Folder` | string | `\logs` | Target directory (created if missing). | | `JSONLogging.FileName` | string | `mars-reqresp.log` | Base log file name. | | `JSONLogging.DailyRotation` | Boolean | `True` | Insert the date (`yyyymmdd`) before the extension, e.g. `mars-reqresp-20260630.log`. | ### Line format Each line is a self-contained JSON object: an ISO-8601/RFC3339 UTC timestamp, a set of fields, and the human-readable `message`. These are plain JSON fields: which of them become Loki labels is decided by the log shipper (see [Ingesting into Loki](#ingesting-into-loki-with-grafana-alloy)). ```json {"ts":"2026-06-30T12:34:56.789Z","detected_level":"INFO","source":"MARS","engine":"DefaultEngine","application":"DefaultApp","direction":"in","message":"ResourcePath:helloworld | Verb:GET | Path:/rest/default/helloworld"} ``` | Field | Meaning | | --- | --- | | `ts` | UTC timestamp with milliseconds (RFC3339). | | `detected_level` | `INFO` for requests/responses, `ERR` for errors. | | `source` | Always `MARS`. | | `engine`, `application` | The hosting engine/application names. | | `direction` | `in` (before invoke), `out` (after invoke, includes `InvocationTime`), `error`. | | `message` | Pipe-separated details: `ResourcePath`, `Verb`, `Path`, and — on `out` — `InvocationTime` in ms, or — on `error` — the exception `Error`. | The file is written UTF-8 **without BOM**, one entry per line. ### Custom entries with structured data The built-in lines carry a few fields. To log whatever your application needs (status code, token claims, tenant, timings…) as real JSON fields that LogQL can filter on, write your own entries with `Log`: any data is serialized with the MARS JSON serializer, the same one used for response bodies. - The fields of an object (class, record, `TDictionary`, `TJSONObject`) become **top level fields** of the line, numbers and booleans keep their JSON type. - Anything else (a scalar, an array) is written under `data`. - `[JSONName]` renames a field, `[JSONSkip]` leaves it out; the `JSON.*` engine parameters (e.g. `JSON.SkipEmptyValues`) apply. - Extra fields passed as `TArray` (e.g. `['source:MyApp']`, split at the first `:`, or `TLogField.Create(name, value)`) come first, then the data, then the optional message: a later field replaces an earlier one with the same name. `ts` is always the logger's timestamp. ```pascal uses MARS.Core.JSON, MARS.Utils.ReqRespLogger.JSON; type TRequestLogEntry = record detected_level: string; direction: string; activation_id: string; [JSONName('status_code')] StatusCode: Integer; execution_ms: Int64; id_user: Integer; end; // ... TMARSActivation.RegisterAfterInvoke( procedure (const AActivation: IMARSActivation) begin if not TMARSReqRespLoggerJSON.IsEnabledFor(AActivation) then // JSONLogging.Enabled and no [NoLog] Exit; var LEntry := Default(TRequestLogEntry); LEntry.detected_level := 'INFO'; LEntry.direction := 'out'; LEntry.activation_id := AActivation.Id; LEntry.StatusCode := AActivation.Response.StatusCode; LEntry.execution_ms := AActivation.InvocationTime.ElapsedMilliseconds; if Assigned(AActivation.Token) then LEntry.id_user := AActivation.Token.Claims.ByName('id_user', 0).AsInteger; TMARSReqRespLoggerJSON.Instance.Configure(AActivation); TMARSReqRespLoggerJSON.Instance.Log( ['source:MyApp', 'engine:' + AActivation.Engine.Name, 'application:' + AActivation.Application.Name] , LEntry , AActivation.Request.Method + ' ' + AActivation.URL.Path ); end ); ``` ```json {"ts":"2026-10-06T10:12:03.123Z","source":"MyApp","engine":"DefaultEngine","application":"DefaultApp","detected_level":"INFO","direction":"out","activation_id":"…","status_code":200,"execution_ms":12,"id_user":5,"message":"GET /rest/default/helloworld"} ``` With `JSONLogging.BuiltInEntries=False` these are the only lines in the file; leave it `True` to get both. A `TJSONObject` can be passed as it is (`Log(LFields, 'message')`): it is copied, the caller keeps its ownership. To log outside of an activation (e.g. at startup) call `Configure(Engine.Parameters)` first, so the configured folder and file are used. ::: tip Labels vs fields Which fields become Loki labels is decided by your Alloy pipeline, not by the logger. Promote only low-cardinality fields (`level`, `source`, `engine`, `application`, `direction`); keep ids, users and paths as JSON fields and filter them at query time, e.g. `{application="DefaultApp"} | json | status_code >= 500`. ::: ### Excluding endpoints Mark a resource or a method with the `[NoLog]` attribute to keep it out of the log — handy for health checks, high-traffic polling endpoints or anything sensitive: ```pascal uses MARS.Core.Attributes; [Path('health'), NoLog] THealthResource = class [GET] function Ping: string; end; ``` The JSON file logger skips both the request and the response (and any error) for `[NoLog]` endpoints. ::: tip Reading the file while the server runs The logger opens the file allowing concurrent readers (`fmShareDenyWrite`), so log shippers and tools like `Get-Content -Wait` can read it live while the server keeps writing. With daily rotation, the current file name follows the date automatically. ::: ## Ingesting into Loki with Grafana Alloy A minimal Alloy pipeline discovers the rotating files, parses each JSON line, uses the log's own timestamp and promotes a few low-cardinality fields to Loki labels: ```hcl local.file_match "mars_logs" { path_targets = [{ __path__ = "C:/path/to/bin/logs/mars-reqresp-*.log", job = "mars" }] } loki.source.file "mars" { targets = local.file_match.mars_logs.targets forward_to = [loki.process.mars.receiver] } loki.process "mars" { forward_to = [loki.write.default.receiver] stage.json { expressions = { ts = "ts", level = "detected_level", source = "source", engine = "engine", application = "application", direction = "direction", message = "message", } } stage.timestamp { source = "ts" format = "RFC3339" } stage.labels { values = { level = "", source = "", engine = "", application = "", direction = "" } } stage.output { source = "message" } } loki.write "default" { endpoint { url = "http://localhost:3100/loki/api/v1/push" } } ``` In Grafana Explore (Loki data source) you can then query e.g. `{source="MARS"}`, `{application="DefaultApp"}` or `{direction="error"}`. ::: warning Endpoint reachability `loki.write` must point at a Loki that Alloy can actually reach. If Alloy runs in a VM and Loki lives on the host, use the host address (for example `http://host.parallels:3100`) rather than `localhost`. ::: ## In-memory logging `MARS.Utils.ReqRespLogger.Memory` keeps recent requests/responses in a `TFDMemTable` (status code, content, timing, remote IP, cookies, …), which you can surface through a resource for a live "last requests" panel. It honors the `[NoLog]` attribute: mark a resource or method with it to exclude that endpoint from the in-memory log. Linking the unit registers the hooks, but nothing is retained until you switch it on: ```ini [Engine] MemoryLogging.Enabled=True ``` | Parameter | Type | Default | Description | | --- | --- | --- | --- | | `MemoryLogging.Enabled` | Boolean | `False` | Master switch, checked on every activation. | ::: warning Clear-text retention The buffer holds complete requests and responses (bodies, cookies, token claims, login forms) in clear text, without a size limit, for the life of the process. Keep it off in production builds, or enable it only while diagnosing. ::: ## See also - [Client ▸ Logging](/client/logging) — logging the requests sent by a Delphi client. - [Request Lifecycle](/server/request-lifecycle) — the hooks these loggers build on. - [Configuration Parameters](/reference/parameters#logging-parameters) — all logging keys in one place. --- Source: https://andrea-magni.github.io/MARS/client/overview # Client Overview MARS ships a complete **client** library for consuming REST services from Delphi. It works against any REST server, with extra conveniences when the server is MARS (JSON ↔ record mapping, JWT handling, FireDAC dataset sync). The client is a set of components you can drop on a form at design time or create in code. Their hierarchy mirrors the server: ``` TMARSClient (transport: Net / Http / Indy) └── TMARSClientApplication (AppName, default media types) ├── TMARSClientToken (login / JWT) ├── TMARSClientResource (raw) ├── TMARSClientResourceJSON (records & objects ↔ JSON) ├── TMARSClientResourceStream (binary) ├── TMARSClientResourceFormData / ...FormUrlEncoded ├── TMARSClientResourceSSE (server-sent events) └── TMARSFDResource (FireDAC datasets) ``` The URL of a call is composed the same way as on the server: ``` Client.MARSEngineURL + /Application.AppName + /Resource.Resource + /path params http://localhost:8080/rest /default /helloworld ``` ## A first call in code ```pascal uses MARS.Client.Client.Net, MARS.Client.Application, MARS.Client.Resource; var LClient: TMARSNetClient; LApp: TMARSClientApplication; LRes: TMARSClientResource; begin LClient := TMARSNetClient.Create(nil); LApp := TMARSClientApplication.Create(nil); LRes := TMARSClientResource.Create(nil); try LClient.MARSEngineURL := 'http://localhost:8080/rest'; LApp.Client := LClient; LApp.AppName := 'default'; LRes.Application := LApp; LRes.Resource := 'helloworld'; ShowMessage(LRes.GETAsString); // -> "Hello World!" finally LRes.Free; LApp.Free; LClient.Free; end; end; ``` The same wiring at **design time** is just dropping the three components and setting their properties in the Object Inspector — see [Components](/client/components). ## Choosing a transport Three interchangeable client transports are provided; they share the same API (`MARSEngineURL`, timeouts, auth, HTTP verbs): | Component | Unit | Backed by | Notes | | --- | --- | --- | --- | | `TMARSNetClient` | `MARS.Client.Client.Net` | `TNetHTTPClient` (RTL) | Default modern choice; cross-platform. | | `TMARSHttpClient` | `MARS.Client.Client.Http` | `THTTPClient` (RTL) | Fine-grained control (redirects, certificates, compression). | | `TMARSIndyClient` | `MARS.Client.Client.Indy` | Indy | For environments that standardize on Indy. | All descend from `TMARSCustomClient` (`MARS.Client.Client`). ## What's next - [Components](/client/components) — the component palette and how they connect. - [Calling Resources](/client/resources) — GET/POST, sync vs async, JSON mapping, streams, SSE. - [Authentication](/client/authentication) — logging in with `TMARSClientToken`. - [FireDAC Client](/client/firedac) — synchronizing datasets with the server. --- Source: https://andrea-magni.github.io/MARS/client/components # Components The MARS client is component-based, so you can build a consumer visually. After [installing](/guide/installation) the `MARSClient.CoreDesign` (and `MARSClient.FireDACDesign`) packages, the components appear on the **MARS-Curiosity Client** palette page. ## The component tree A typical form wires components like this: ``` TMARSNetClient Client1 MARSEngineURL = 'http://localhost:8080/rest' └ TMARSClientApplication App1 Client = Client1; AppName = 'default' ├ TMARSClientToken Token1 Application = App1 └ TMARSClientResourceJSON Res1 Application = App1; Token = Token1; Resource = 'people' ``` At design time the components **auto-discover** their parents: dropping a `TMARSClientApplication` finds a client on the form; dropping a resource finds an application (and a token). You can always set the links explicitly in the Object Inspector. ## Client — `TMARSCustomClient` The transport. Use `TMARSNetClient`, `TMARSHttpClient` or `TMARSIndyClient` (see [Overview](/client/overview#choosing-a-transport)). Key properties: | Property | Purpose | | --- | --- | | `MARSEngineURL` | Base server URL, including the engine `BasePath` (e.g. `http://host:8080/rest`). | | `ConnectTimeout`, `ReadTimeout` | Network timeouts (ms). | | `AuthEndorsement` | How the JWT is sent: `AuthorizationBearer` or `Cookie`. | | `ProxyConfig` | Proxy host/port/credentials. | | `OnError` | Central handler for failed requests. | | `OnLog`, `LogOptions`, `SynchronizeLog` | Log of every request and response, see [Logging](/client/logging). | ## Application — `TMARSClientApplication` Represents the server-side [application](/server/application) you are talking to. | Property | Purpose | | --- | --- | | `Client` | The transport component. | | `AppName` | The application's base-path name (e.g. `default`). | | `DefaultMediaType` | Default `Accept` (default `application/json`). | | `DefaultContentType` | Default request `Content-Type`. | ## Resources All resource components descend from `TMARSClientCustomResource` and share these properties: | Property | Purpose | | --- | --- | | `Application` | The owning application component. | | `Resource` | The resource path segment (e.g. `people`). | | `PathParamsValues` | `TStringList` of values substituted into the resource path. | | `QueryParams` | `TStringList` of `name=value` query parameters. | | `CustomHeaders` | Extra request headers. | | `Token` | A `TMARSClientToken` to authenticate with (optional). | | `SpecificClient` / `SpecificToken` / `SpecificURL` | Overrides that bypass `Application`/`Token`/path-building. | | `SpecificAccept` / `SpecificContentType` | Per-resource media-type overrides. | The specialized resource types add convenience on top: | Component | For | Adds | | --- | --- | --- | | `TMARSClientResource` | raw payloads | `GETAsString`, stream verbs | | `TMARSClientResourceJSON` | records/objects | `Response: TJSONValue`, `ResponseAs`, typed `POST` | | `TMARSClientResourceStream` | binary | `Response: TStream` | | `TMARSClientResourceFormData` | multipart uploads | `FormData: TArray` | | `TMARSClientResourceFormUrlEncoded` | urlencoded forms | `FormUrlEncoded` parameters | | `TMARSClientResourceSSE` | server-sent events | `Open`/`Close`, `OnMessage` | | `TMARSFDResource` / `TMARSFDDataSetResource` | FireDAC datasets | dataset sync & deltas | ## Token — `TMARSClientToken` Manages authentication for the resources that reference it. | Member | Purpose | | --- | --- | | `UserName`, `Password` | Credentials to send on login. | | `Authenticate` | Perform the login (`POST` to the token resource). | | `Token` (read-only) | The current JWT string. | | `IsVerified`, `Authenticated` | Status flags. | | `UserRoles`, `Claims`, `Expiration` | Decoded token info. | | `SaveToFile` / `LoadFromFile` | Persist the token between sessions. | See [Authentication](/client/authentication). ## Setting it up in code Everything you set in the Object Inspector you can also do in code (handy for unit tests and services): ```pascal LClient := TMARSNetClient.Create(Self); LClient.MARSEngineURL := 'http://localhost:8080/rest'; LApp := TMARSClientApplication.Create(Self); LApp.Client := LClient; LApp.AppName := 'default'; LRes := TMARSClientResourceJSON.Create(Self); LRes.Application := LApp; LRes.Resource := 'people'; ``` Because the components have an owner (`Self`), they are freed with the form — no manual cleanup needed. --- Source: https://andrea-magni.github.io/MARS/client/resources # Calling Resources This page covers how to issue requests with the client resource components: HTTP verbs, passing path/query parameters, JSON record mapping, streams, and asynchronous calls. ## HTTP verbs `TMARSClientCustomResource` exposes the verbs as methods that take up to three callbacks: *before-execute*, *after-execute* (with the response stream) and *on-exception*. All parameters are optional. ```pascal procedure GET (ABeforeExecute; AAfterExecute; AOnException); procedure POST (ABeforeExecute; AAfterExecute; AOnException); // also POST(ABody: TStream; ...) procedure PUT (ABeforeExecute; AAfterExecute; AOnException); procedure DELETE(ABeforeExecute; AAfterExecute; AOnException); procedure PATCH(ABeforeExecute; AAfterExecute; AOnException); procedure QUERY(ABeforeExecute; AAfterExecute; AOnException); // HTTP QUERY: fill the body in ABeforeExecute function GETAsString(AEncoding = nil; ABeforeExecute = nil; AOnException = nil): string; ``` `QUERY` sends the HTTP `QUERY` method (a safe request carrying its query in the body, see [Resources ▸ HTTP verbs](/server/resources#http-verbs)); write the query into the stream handed to the *before-execute* callback. `POST`, `PUT`, `DELETE`, `PATCH` and `QUERY` have an `…Async` counterpart too. The simplest possible call: ```pascal Memo1.Text := Resource1.GETAsString; ``` With callbacks: ```pascal Resource1.GET( nil, // before execute procedure (AStream: TStream) // after execute begin Memo1.Lines.LoadFromStream(AStream); end, procedure (AException: Exception) // on exception begin ShowMessage('Failed: ' + AException.Message); end); ``` ## Path and query parameters Set `PathParamsValues` (substituted into the resource path) and `QueryParams` (the query string) before calling: ```pascal // Server route: people/{id} PeopleResource.Resource := 'people'; PeopleResource.PathParamsValues.Clear; PeopleResource.PathParamsValues.Add('42'); // -> people/42 PeopleResource.QueryParams.Clear; PeopleResource.QueryParams.Values['expand'] := 'orders'; // -> ?expand=orders PeopleResource.GET; ``` ## JSON records and objects `TMARSClientResourceJSON` maps Delphi records to/from JSON. After a call, `Response` holds the parsed `TJSONValue`; `ResponseAs` and `ResponseAsArray` deserialize into your record types. ```pascal type TPerson = record Name: string; Age: Integer; end; // GET a single record PeopleResource.GET; var LPerson := PeopleResource.ResponseAs; // GET an array PeopleResource.GET; var LPeople := PeopleResource.ResponseAsArray; for var P in LPeople do Memo1.Lines.Add(P.Name); ``` Posting a record (or array of records) serializes it to JSON automatically: ```pascal var LNew: TPerson; LNew.Name := 'Ada'; LNew.Age := 36; PeopleResource.POST(LNew, procedure (AStream: TStream) begin var LCreated := PeopleResource.ResponseAs; // server echoes the created record end); ``` You can also `POST(AJSONValue: TJSONValue)` directly, and read `ResponseAsString` / `ResponseAsJSON` when you want the raw text. ## Binary streams `TMARSClientResourceStream` exposes the response as a `TStream`: ```pascal ImageResource.Resource := 'image/binary/cats/whiskers'; ImageResource.GET; Image1.Picture.LoadFromStream(ImageResource.Response); ``` To upload binary, use `POST(ABody: TStream, ...)` with the stream form of the verb. ## Form uploads `TMARSClientResourceFormData` posts multipart form data (including files): ```pascal DocResource.FormData := [ TFormParam.Create('title', 'Report'), TFormParam.Create('file', 'C:\files\report.pdf') // a file path is uploaded as a part ]; DocResource.POST; ``` `TMARSClientResourceFormUrlEncoded` does the same for `application/x-www-form-urlencoded` key/value pairs. ## Asynchronous calls Each verb has an `…Async` counterpart taking a completion handler and an `ASynchronize` flag (when `True`, the completion runs in the main thread — safe for UI updates): ```pascal PeopleResource.GETAsync( nil, procedure (AResource: TMARSClientCustomResource) begin var LPeople := TMARSClientResourceJSON(AResource).ResponseAsArray; Grid.Load(LPeople); // runs on the main thread end, procedure (AException: Exception) begin ShowMessage(AException.Message); end, True); // synchronize completion to UI thread ``` The UI stays responsive while the request is in flight. ## Server-Sent Events `TMARSClientResourceSSE` keeps a streaming connection open and raises events as the server pushes them: ```pascal SSEResource.Resource := 'helloworld'; SSEResource.OnMessage := procedure (const AEvent, AId, AData: string) begin Memo1.Lines.Add(Format('[%s #%s] %s', [AEvent, AId, AData])); end; SSEResource.Open; // ... SSEResource.Close to stop ``` It also exposes `OnOpen`, `OnComment`, `OnReconnect`, `OnClose` and `OnError`, and reconnects using the server's retry hint. See [Server-Sent Events](/features/sse). ## Error handling Failures surface in two places: - the per-call `AOnException` callback, and - the client's central `OnError` event. When the server raised an [`EMARSWithResponseException`](/server/error-handling#emarswithresponseexception-structured-error-body), the structured error body is available in the response and can be deserialized into a record on the client — see the [ErrorObjects demo](/demos/#errorobjects). --- Source: https://andrea-magni.github.io/MARS/client/authentication # Client Authentication `TMARSClientToken` performs the login handshake against a server [token resource](/features/authentication) and holds the resulting JWT. Resources that reference the token component automatically send it on every request. ## Logging in Drop a `TMARSClientToken`, link it to your `TMARSClientApplication`, set the credentials and call `Authenticate`: ```pascal Token1.Application := App1; Token1.UserName := 'admin'; Token1.Password := 'secret'; Token1.Authenticate; // POSTs username/password to the 'token' resource if Token1.IsVerified then ShowMessage('Welcome ' + Token1.UserName + ' [' + string.Join(',', Token1.UserRoles) + ']') else ShowMessage('Login failed'); ``` By default the token resource is named `token`; set `Token1.Resource` if your server mounts it elsewhere. ## Authenticated requests Point a resource's `Token` property at the token component. From then on the client attaches the JWT to each call: ```pascal SecureResource.Application := App1; SecureResource.Token := Token1; // requests now carry the JWT SecureResource.Resource := 'me'; ShowMessage(SecureResource.GETAsString); ``` How the token travels is decided by the **client**: `Client1.AuthEndorsement` selects `AuthorizationBearer` (an `Authorization: Bearer …` header) or `Cookie`. This must match what the server expects (cookies are enabled by default — see the server-side [JWT settings](/features/authentication#what-token-build-does)). ## Inspecting the token After authenticating, `TMARSClientToken` decodes the JWT for you: | Member | Meaning | | --- | --- | | `Token` | Raw JWT string. | | `IsVerified` / `Authenticated` | Login succeeded. | | `UserName` | Authenticated user. | | `UserRoles` | Granted roles. | | `Claims` | All JWT claims. | | `Expiration`, `IssuedAt` | Lifetime info. | ## Persisting the session To avoid forcing a re-login every time the app starts, persist the token and reload it: ```pascal // on close Token1.SaveToFile('session.jwt'); // on startup if TFile.Exists('session.jwt') then begin Token1.LoadFromFile('session.jwt'); if Token1.IsVerified and (Token1.Expiration > Now) then GoToMainScreen else ShowLogin; end; ``` `SaveToStream` / `LoadFromStream` are available for non-file storage. ## Logout Clear the token locally (and optionally call the server's `DELETE token` to invalidate the cookie): ```pascal Token1.Resource := 'token'; SomeResource.Token := Token1; // DELETE the token resource to log out server-side, then: Token1.Clear; ``` ## Token renewal If the server supports renewal (re-issuing a token as it nears expiry — see the [TokenRenew demo](/demos/#tokenrenew)), a fresh token is returned on normal requests; the client picks it up transparently when the server sets the cookie or returns a new token. You can also re-`Authenticate` proactively before `Expiration`. --- Source: https://andrea-magni.github.io/MARS/client/firedac # FireDAC Client When the server exposes [FireDAC datasets](/features/firedac), the client can fetch them into live `TFDMemTable`s, let the user edit them, and post the changes back as a *delta*. This gives you a near-classic data-aware experience over REST. The component is `TMARSFDResource` (`MARS.Client.FireDAC.pas`), installed by the `MARSClient.FireDACDesign` package. ## Setup Drop a `TMARSFDResource`, link it to an application, and tell it which local datasets correspond to the server's: ```pascal FDResource.Application := App1; FDResource.Resource := 'orders'; // a TMARSFDDatasetResource on the server // ResourceDataSets maps server dataset names to local TFDMemTables ``` `TMARSFDResource` holds a collection (`ResourceDataSets`) pairing each server-side dataset name with a local `TFDMemTable`. Set these up at design time (the component editor lists the names) or in code. ## Fetching data A `GET` populates the linked mem-tables: ```pascal FDResource.GET( nil, procedure (AStream: TStream) begin // OrdersMemTable and ItemsMemTable are now filled and active Grid1.DataSource.DataSet := OrdersMemTable; end, nil); ``` Because the server can return the compact FireDAC wire format, transfers are efficient and field metadata (types, constraints) is preserved. ## Sending changes back Edit the mem-tables as usual (FireDAC tracks the changes). A `POST` sends only the **delta** to the server, which applies it with `ApplyUpdates` and returns a per-dataset result: ```pascal // user edited OrdersMemTable / ItemsMemTable ... FDResource.POST( nil, procedure (AStream: TStream) begin if FDResource.ApplyUpdatesResults.AllOK then ShowMessage('Saved') else ShowMessage('Some rows were rejected — see error details'); end, nil); ``` The server returns an array of `TMARSFDApplyUpdatesRes` (applied count and any per-row errors per dataset), which the client exposes so you can report or reconcile failures. ## Single-dataset resource For the common one-dataset case, `TMARSFDDataSetResource` binds a single `TFDMemTable` and exposes convenience properties like `Filter` and `Sort` (sent as query parameters) and flags controlling whether to send deltas. The usage pattern is the same: `GET` to load, `POST` to save. ## End-to-end shape ``` [Client] TMARSFDResource.GET ──► GET /rest/default/orders server: TMARSFDDatasetResource.Retrieve local TFDMemTables ◄── JSON / FireDAC binary (datasets) user edits rows (change tracking) ... [Client] TMARSFDResource.POST ──► POST /rest/default/orders (delta) server: ApplyUpdates(datasets, deltas) ApplyUpdatesResults ◄── [{ dataset, result, errorCount, errors }] ``` See the server side in [FireDAC & Datasets](/features/firedac) and the working [ConnectionPoolingProject demo](/demos/#connectionpoolingproject). --- Source: https://andrea-magni.github.io/MARS/client/logging # Client Logging Every request sent by a MARS client, and the response it gets, can be logged: verb, URL, headers, bodies, status, duration and exception. It works the same way with `TMARSNetClient`, `TMARSHttpClient` and `TMARSIndyClient`, and costs nothing when nobody is listening. Two ways to listen, which can be combined: - **`OnLog`**, an event of the client component; - **`TMARSCustomClient.RegisterLogger`**, for code without components. It also covers the clients MARS creates internally: the class function shortcuts (`GetJSON`, `PostJSON`, ...) and the copies used by the `...Async` methods. ## With the component Assign `OnLog` in the Object Inspector, or in code: ```pascal procedure TMainForm.MARSClient1Log(Sender: TObject; const AEntry: TMARSClientLogEntry); begin LogMemo.Lines.Add(AEntry.ToString); // GET http://localhost:8080/rest/default/helloworld -> 200 OK (4 ms) end; ``` `OnLog` runs after each request, also when it fails, **in the thread of the call**: with the `...Async` methods that is a background thread. Set `SynchronizeLog` to `True` to have it run in the main thread (through `TThread.Synchronize`), or move to the main thread yourself: ```pascal procedure TMainForm.MARSClient1Log(Sender: TObject; const AEntry: TMARSClientLogEntry); var LLine: string; begin LLine := AEntry.ToString; TThread.Queue(nil, procedure begin LogMemo.Lines.Add(LLine); end); end; ``` ::: warning SynchronizeLog `TThread.Synchronize` waits for the main thread: don't use `SynchronizeLog` if the main thread can be blocked waiting for the request (for example in a console application or a service without a message loop). ::: ## Without components Register a logger once, for example at startup. It is called for every request of every client: ```pascal uses MARS.Client.Client, MARS.Client.Log; TMARSCustomClient.RegisterLogger( procedure (const AEntry: TMARSClientLogEntry) begin if not AEntry.Succeeded then TMARSClientLog.ToFile(AEntry, 'logs\client-errors.log'); end ); ``` Loggers run in the thread of the call and may run concurrently: keep them thread-safe. `RegisterLogger` returns an index for `UnregisterLogger`; `ClearLoggers` removes them all. `TMARSClientLog` (unit `MARS.Client.Log`) has ready-made sinks: | Sink | Writes | | --- | --- | | `ToDebugOutput(AEntry)` | The entry, headers and bodies included, with `OutputDebugString` (Windows; the console elsewhere). | | `ToFile(AEntry, AFileName)` | One JSON object per line, in the style of the [server JSON logger](/features/logging) with `"direction":"out"`: ready for Grafana/Loki. Thread-safe. | | `ToStrings(AEntry, AStrings, AMaxLines)` | One line per request, keeping at most `AMaxLines` lines. Calls are serialized, but the list must not belong to a visual control when the call runs in a background thread. | and shortcuts registering them: `TMARSClientLog.LogToDebugOutput`, `TMARSClientLog.LogToFile(AFileName)`. ## The log entry `TMARSClientLogEntry` is a record: | Field | Content | | --- | --- | | `Client` | The client making the call. | | `Event` | Empty for a request; for an event stream, see [Server-sent events](#server-sent-events). | | `Verb`, `URL` | `GET`, `POST`, ...; the full URL. | | `RequestHeaders`, `RequestContentType` | The headers set by MARS: `Accept`, `Content-Type`, authorization, custom headers. Those added by the HTTP library (`User-Agent`, `Host`, ...) are not included. | | `RequestBody`, `RequestSize` | Text of the body (see below) and its size in bytes. | | `StatusCode`, `StatusText` | `0` and empty when no response was received (DNS, connection, timeout). | | `ResponseHeaders`, `ResponseContentType` | As received. | | `ResponseBody`, `ResponseSize` | Text of the body and its size in bytes. | | `StartedAt`, `DurationMs` | Start time (UTC) and duration. | | `ExceptionClass`, `ExceptionMessage` | The exception raised by the call, if any. | `Succeeded` is `True` for a 2xx answer, or a stream event, without exceptions. `ToString` gives one line, `ToText` adds headers and bodies, `ToJSON` returns a `TJSONObject` (free it). ## What is logged `LogOptions` (a published property of the client) decides what is logged: | Property | Default | | | --- | --- | --- | | `Content` | `Truncated` | `HeadersOnly`: no bodies, only their size. `Truncated`: the first `MaxBodySize` bytes of each body. `Full`: the whole bodies. | | `MaxBodySize` | `65536` | Bytes of each body logged with `Content = Truncated`. | | `Masking` | `HeadersAndFields` | See below. | | `MaskedHeaders` | `Authorization`, `Proxy-Authorization`, `Cookie`, `Set-Cookie` | Comma separated, case insensitive. | | `MaskedFields` | `password`, `secret`, `client_secret`, `token`, `access_token`, `refresh_token`, `id_token` | Comma separated, case insensitive. | Text bodies (`text/*`, JSON, XML, form url-encoded) are logged as text, UTF-8; other content types only as their size, e.g. `<34512 bytes>`. Multipart form data is logged field by field, files as name and size. ## Masking Credentials end up in the log easily: the token is in every request, and the login request carries the password. `LogOptions.Masking` replaces them with `***`: | `Masking` | Masked | | --- | --- | | `None` | Nothing. | | `HeadersOnly` | The values of the `MaskedHeaders`. | | `HeadersAndFields` (default) | The `MaskedHeaders`, and the `MaskedFields` in JSON bodies (at any depth), form url-encoded bodies and form data. | | `All` | Every header value but `Accept` and `Content-Type`, and every body (only sizes are logged). | ```pascal MARSClient1.LogOptions.Masking := TMARSClientLogMasking.HeadersAndFields; MARSClient1.LogOptions.MaskedFields := 'password,pin,iban'; ``` ::: tip Field masking works on the text of the body, so it also applies to truncated bodies; values that look like secrets but sit in fields with other names are not detected. Review what your application sends before enabling `Content = Full` with `Masking = None` outside development. ::: ## Server-sent events A `TMARSClientResourceSSE` keeps a request open to receive events, so it is not logged as one request: its client logs the life of the stream, with `Event` set to | `Event` | When | | --- | --- | | `sse.open` | The first data arrives. | | `sse.error` | The stream fails (`ExceptionClass`, `ExceptionMessage`), e.g. the server answers with something else than an event stream. | | `sse.reconnect` | The stream is going to reconnect. | | `sse.close` | The stream ends. `DurationMs` is its lifetime. | ```text GET http://localhost:8080/rest/default/helloworld -> sse.open (98 ms) GET http://localhost:8080/rest/default/helloworld -> sse.close (3513 ms) ``` Single events are not logged. A close requested by the application (`Active := False`, `Close`) is logged in the thread of the caller; the other entries in the thread of the stream. ## See also - [Request/Response Logging](/features/logging) — logging on the server side. - [Components](/client/components) — the client components. - `Demos/SSEDemo` — the client lists its log entries along with the events. --- Source: https://andrea-magni.github.io/MARS/reference/attributes # Attributes Reference A compact cheat-sheet of MARS attributes. See [Server ▸ Attributes](/server/attributes) for explanations and examples. Unless noted, attributes are in `MARS.Core.Attributes`. ## Routing & verbs | Attribute | Target | Purpose | | --- | --- | --- | | `[Path('seg')]` | class, method | Route segment (supports `{token}` and `{*}`). | | `[GET]` `[POST]` `[PUT]` `[DELETE]` `[PATCH]` `[HEAD]` `[OPTIONS]` `[QUERY]` | method | HTTP verb (`QUERY`: safe method with a body, bind it with `[BodyParam]`). | ## Content negotiation | Attribute | Target | Purpose | | --- | --- | --- | | `[Produces('type')]` | method, writer | Response media type(s). | | `[Consumes('type')]` | method, reader | Accepted request body media type(s). | | `[Encoding('UTF8')]` | method, resource | Text encoding for serialization: `UTF8` (default), `ANSI`, `ASCII`, `Unicode`, `BigEndianUnicode`, `UTF7`, `Default`. | ## Parameter binding (on method parameters) | Attribute | Source | | --- | --- | | `[PathParam('name')]` | URL path token (always required). | | `[QueryParam('name')]` | Query string. | | `[FormParam('name')]` | Form field (urlencoded / multipart). | | `[HeaderParam('name')]` | Request header. | | `[CookieParam('name')]` | Cookie. | | `[BodyParam]` | Entire request body (deserialized). | | `[PathParams]` `[QueryParams]` `[Headers]` `[Cookies]` `[FormParams]` | All values of that kind as a collection. | | `[Required]` | Marks a bound parameter mandatory. | ## Injection | Attribute | Target | Purpose | | --- | --- | --- | | `[Context]` | field, property, parameter | Inject a framework/custom service. | | `[EngineParam('Name', Default)]` | field, property, parameter | Value from engine parameters. | | `[ApplicationParam('Name', Default)]` | field, property, parameter | Value from application parameters. | | `[EngineParamFunc]` `[ApplicationParamFunc]` | as above | Inject a `TConfigParamFunc` for dynamic lookup. | ## Security | Attribute | Target | Purpose | | --- | --- | --- | | `[PermitAll]` | resource, method | Allow everyone. | | `[DenyAll]` | resource, method | Deny everyone (highest priority). | | `[RolesAllowed('a','b')]` | resource, method | Require any listed role. | ## Response shaping | Attribute | Target | Purpose | | --- | --- | --- | | `[ContentType('type')]` | method | Force response `Content-Type`. | | `[CustomHeader('Name','Value')]` | method | Add a fixed response header. | | `[IsReference]` | method | Returned object must not be freed by MARS. | | `[JSONP(True, 'callback', 'text/javascript')]` | method | Wrap JSON as JSONP. | ## Lifecycle hooks (on resource methods) | Attribute | When | | --- | --- | | `[BeforeInvoke]` | Before the endpoint method runs. | | `[AfterInvoke]` | After it returns. | | `[InvokeError]` | When it raises. | | `[AfterContextCleanup]` | After injected context is freed. | ## Metadata & OpenAPI (`MARS.Metadata.Attributes`, OpenAPI units) | Attribute | Target | Purpose | | --- | --- | --- | | `[MetaSummary('...')]` | resource, method | Short summary. | | `[MetaDescription('...')]` | resource, method, parameter | Longer description. | | `[MetaVisible(False)]` | resource, method | Hide from metadata/OpenAPI. | | `[OAPISummary]` `[OAPIDescription]` | resource, method, type, field, parameter | Text used in the OpenAPI document only (overrides `[Meta…]`). | | `[OAPIRequired]` `[OAPIDefault]` `[OAPIPattern]` `[OAPIMinimum]` `[OAPIMaximum]` `[OAPIMinLength]` `[OAPIMaxLength]` | record/class field, parameter | JSON-schema hints. | ## JSON serialization (`MARS.Core.JSON`) | Attribute | Target | Purpose | | --- | --- | --- | | `[JSONName('key')]` | field | Custom JSON key. | | `[JSONSkip]` | field | Exclude from (de)serialization. | | `[JSONSkipEmptyValues]` `[JSONIncludeEmptyValues]` | field, resource, method | Empty/null inclusion policy. | ## Data — FireDAC (`MARS.Data.FireDAC.*`) | Attribute | Target | Purpose | | --- | --- | --- | | `[Connection('DEFNAME')]` | resource, field, parameter | Select FireDAC connection definition. | | `[SQLStatement('Name','SELECT …')]` | resource | Declare a named SQL statement. | ## Web server (`MARS.WebServer.Resources`) | Attribute | Target | Purpose | | --- | --- | --- | | `[RootFolder('path', AIncludeSubFolders)]` | resource | Map a `TFileSystemResource` to a disk folder; `AIncludeSubFolders = False` serves the root folder only. | | `[DirectoryListing(False)]` | resource | Disable the HTML listing of directories without an index file (`404` instead). | | `[DotSegments]` | resource | Accept `.` and `..` in the paths of a `TFileSystemResource`, as long as they stay inside the root folder (default: `404`). | | `[Exclude('mask')]` | resource | Never serve files whose full path matches the mask (case-insensitive, repeatable; `404`). | | `[Include('mask')]` | resource | Serve only files whose full path matches one of the masks (repeatable; exclusions still win). | --- Source: https://andrea-magni.github.io/MARS/reference/media-types # Media Types Reference `TMediaType` (`MARS.Core.MediaType`) defines constants for the common MIME types used in `[Produces]` / `[Consumes]` and content negotiation. Prefer these constants over string literals. ```pascal [GET, Produces(TMediaType.APPLICATION_JSON)] function GetData: TData; ``` ## Constants | Constant | Value | | --- | --- | | `TMediaType.TEXT_PLAIN` | `text/plain` | | `TMediaType.TEXT_PLAIN_UTF8` | `text/plain; charset=utf-8` | | `TMediaType.TEXT_XML` | `text/xml` | | `TMediaType.TEXT_HTML` | `text/html` | | `TMediaType.TEXT_YAML` | `text/yaml` | | `TMediaType.TEXT_EVENT_STREAM` | `text/event-stream` | | `TMediaType.APPLICATION_XML` | `application/xml` | | `TMediaType.APPLICATION_XML_FireDAC` | `application/xml-firedac` | | `TMediaType.APPLICATION_JSON` | `application/json` | | `TMediaType.APPLICATION_JSON_FireDAC` | `application/json-firedac` | | `TMediaType.APPLICATION_XHTML_XML` | `application/xhtml+xml` | | `TMediaType.APPLICATION_SVG_XML` | `application/svg+xml` | | `TMediaType.APPLICATION_ATOM_XML` | `application/atom+xml` | | `TMediaType.APPLICATION_OCTET_STREAM` | `application/octet-stream` | | `TMediaType.APPLICATION_FORM_URLENCODED_TYPE` | `application/x-www-form-urlencoded` | | `TMediaType.APPLICATION_YAML` | `application/x-yaml` | | `TMediaType.APPLICATION_PDF` | `application/pdf` | | `TMediaType.MULTIPART_FORM_DATA` | `multipart/form-data` | | `TMediaType.WILDCARD` | `*/*` | ## Charset constants | Constant | Value | | --- | --- | | `TMediaType.CHARSET_NAME` | `charset` | | `TMediaType.CHARSET_UTF8` | `utf-8` | | `TMediaType.CHARSET_UTF8_DEF` | `charset=utf-8` | | `TMediaType.CHARSET_UTF16` / `…_DEF` | `utf-16` / `charset=utf-16` | | `TMediaType.CHARSET_ISO_8859_1` / `…_DEF` | `iso-8859-1` / `charset=iso-8859-1` | Use them to build a content type that declares its encoding, e.g. `TMediaType.TEXT_HTML + '; ' + TMediaType.CHARSET_UTF8_DEF`. This matters for textual responses: without an explicit `charset`, the client applies its own default (historically ISO-8859-1) and non-ASCII characters get mangled. MARS declares UTF-8 on the error responses it produces (`TEXT_PLAIN_UTF8` is the default content type of [`EMARSHttpException`](/server/error-handling)) and on the textual files served by `TFileSystemResource` (see [HTML & Templates](/features/templates#content-types-and-charset)). ## Notes - **`*/*`** (`WILDCARD`) matches any type; writers/readers registered for it act as fallbacks (lowest affinity). - **FireDAC variants** (`…-firedac`) carry datasets in a compact Delphi-native format; plain `application/json` carries them as an interoperable array of objects. See [FireDAC & Datasets](/features/firedac#wire-formats). - **`text/event-stream`** is used by [Server-Sent Events](/features/sse). - **`application/x-yaml`** requires `MARS.YAML.ReadersAndWriters` to be registered (used by the [OpenAPI](/features/openapi) endpoint). ## Parsing and matching `TMediaType.Create('application/json;charset=utf-8')` parses a header value (type, subtype and parameters such as `charset` and quality factor `q`). `Matches` performs content-type negotiation, honoring wildcards and `q` priorities — this is what the [content-negotiation](/server/content-negotiation) layer uses to pick a reader/writer against the request's `Accept`/`Content-Type`. --- Source: https://andrea-magni.github.io/MARS/reference/parameters # Configuration Parameters MARS configuration is a name/value store (`TMARSParameters`) available at the [engine](/server/engine) and [application](/server/application) levels. Values are typically loaded from an `.ini` file next to the executable with `FEngine.Parameters.LoadFromIniFile`, and can be read in code or injected with [`[EngineParam]` / `[ApplicationParam]`](/server/injection). ```ini [Engine] Port=8080 ThreadPoolSize=75 BasePath=/rest [DefaultApp] JWT.Secret=please-change-me JWT.Duration=1 ``` When `AddApplication` runs, the engine copies the matching `.ini` section (by application name) into the application's parameters. ## Which `.ini` file is used `LoadFromIniFile` (and `SaveToIniFile` / `IniFileExists`) accept an explicit file name. Called without one, they resolve it in this order: 1. the `-configFileName ` command-line switch, if present — handy to run the same binary against different configurations (dev, staging, service instances); 2. otherwise the module name with the extension changed to `.ini` — `MyServer.exe` → `MyServer.ini`, an ISAPI DLL → `.ini`. `Parameters.GetFileName` returns the path that would be used, with the same rules, which is what to log or display when a server starts up with unexpected settings: ```pascal if not FEngine.Parameters.IniFileExists then Writeln('No configuration file at ' + FEngine.Parameters.GetFileName); ``` ## Shared configuration: `[Include]` Several servers often share most of their configuration (JWT settings, logging, database connections) and differ in a few values (port, secret, application specific settings). Put the common values in a base file and include it in the `.ini` of each server with an `[Include]` section: the call to `LoadFromIniFile` in `Server.Ignition` stays the same. ```text C:\Servers\ ├─ BaseConfiguration.ini ├─ Orders\ │ ├─ OrdersServer.exe │ └─ OrdersServer.ini └─ Invoices\ ├─ InvoicesServer.exe └─ InvoicesServer.ini ``` `C:\Servers\BaseConfiguration.ini`, shared: ```ini [DefaultEngine] Port=8080 ThreadPoolSize=50 JSONLogging.Enabled=true [DefaultApp] JWT.Issuer=MyCompany JWT.Duration=8 JWT.Secret=base-secret-replaced-by-each-server ``` `C:\Servers\Orders\OrdersServer.ini`: ```ini [Include] Base=..\BaseConfiguration.ini [DefaultEngine] Port=8081 [DefaultApp] JWT.Secret=a-long-random-value-for-orders Orders.MaxItems=100 ``` The parameters of `OrdersServer` are the sum of the two files, the including file winning: | Parameter | Value | From | | --- | --- | --- | | `Port` | `8081` | `OrdersServer.ini` | | `ThreadPoolSize` | `50` | `BaseConfiguration.ini` | | `JSONLogging.Enabled` | `true` | `BaseConfiguration.ini` | | `DefaultApp.JWT.Issuer` | `MyCompany` | `BaseConfiguration.ini` | | `DefaultApp.JWT.Duration` | `8` | `BaseConfiguration.ini` | | `DefaultApp.JWT.Secret` | `a-long-random-value-for-orders` | `OrdersServer.ini` | | `DefaultApp.Orders.MaxItems` | `100` | `OrdersServer.ini` | The `MARSTemplate` and `MARSTemplateDCS` templates, and so the projects created with MARSCmd, use this layout: `bin\Server.ini` holds the settings, and each server flavor has a small `.ini` named after its executable that includes it. The rules: - each value of `[Include]` is a file to load; the names (`Base` above) are free and only identify the line. Relative paths are relative to the folder of the file containing the `[Include]` section, not to the current folder; - included files are loaded first, in the order they are listed, then the values of the including file: the including file wins, and a later include wins over an earlier one; - an included file can have its own `[Include]` section, e.g. `BaseConfiguration.ini` could include a `CompanyDefaults.ini`. A file including itself, directly or through other files, raises `EMARSParametersIniFileException`; - an included file that does not exist raises `EMARSParametersIniFileException` (a missing main file, instead, still gives empty parameters, as before); - `[Include]` is not a parameters section; an included file cannot remove a value, only replace it (`Key=` sets an empty value); - `SaveToIniFile` writes all the parameters to a single file, without `[Include]`. ## Names are case insensitive in `.ini` files Like the `.ini` files themselves, the parameters read from them ignore case: `jwt.secret` in the file is found as `JWT.Secret` in code, and `Feature.X` in a base file and `feature.x` in the including file are the same parameter (the first spelling is kept). Parameters read from JSON (`LoadFromJSON`) keep matching the exact case. ## Engine parameters | Parameter | Type | Default | Purpose | | --- | --- | --- | --- | | `Port` | Integer | `8080` | HTTP listening port. | | `PortSSL` | Integer | `0` | HTTPS port (0 = disabled), for the Indy and DCS servers. See [HTTPS](/server/engine#https). | | `DCS.SSL.CertFile` | string | `localhost.crt` | DCS server: certificate (PEM, may hold the chain); relative to the executable folder. | | `DCS.SSL.KeyFile` | string | `localhost.key` | DCS server: private key (PEM); relative to the executable folder. | | `Indy.SSL.CertFile`, `Indy.SSL.KeyFile`, `Indy.SSL.RootCertFile` | string | `localhost.crt`, `localhost.key`, `localhost.pem` | Indy server: certificate, key and root certificate. | | `Indy.SSL.Version`, `Indy.SSL.Mode` | string | `sslvTLSv1_2`, `sslmServer` | Indy server: TLS version and mode. | | `Indy.KeepAlive` | Boolean | `false` | Indy server: HTTP keep-alive (without it every request opens a new connection, and a TLS handshake with HTTPS). Each open connection holds a thread of the pool: `ThreadPoolSize` is also the maximum number of connections. `true` in the `MARSTemplate` projects. The DCS server always supports keep-alive. | | `ThreadPoolSize` | Integer | `75` | Worker threads (size for concurrent requests, incl. open SSE streams). | | `BasePath` | string | `/rest` | Root path stripped from every URL before application matching. | CORS-related parameters (when CORS is enabled) include `CORS.Origin`, `CORS.Methods`, `CORS.Headers`. See [Engine ▸ CORS](/server/engine#cors). ## JWT / authentication parameters (per application) Read by the [token resource](/features/authentication) and JWT backends: | Parameter | Default | Purpose | | --- | --- | --- | | `JWT.Secret` | — | HMAC signing secret. Missing or equal to the public default: see `JWT.AllowDefaultSecret`. | | `JWT.AllowDefaultSecret` | `false` | Knowingly use the public default secret. Otherwise a `DEBUG` build generates a random per-process secret and a `RELEASE` build raises (`TMARSToken.DefaultSecretPolicy`). | | `JWT.KeyId` | — | Id of `JWT.Secret`, written as `kid` in the header of new tokens. See [Key rotation](/features/authentication#key-rotation). | | `JWT.PreviousSecret.` | — | A retired key, accepted only to verify tokens whose `kid` is ``. | | `JWT.PreviousSecret` | — | A retired key for tokens without `kid` (issued before `JWT.KeyId` was set). | | `JWT.Issuer` | `MARS-Curiosity` | `iss` claim. | | `JWT.Duration` | `1` | Token lifetime in **days**. | | `JWT.Duration.InMinutes` | — | Lifetime in minutes (alternative). | | `JWT.Duration.InSeconds` | — | Lifetime in seconds (alternative). | | `JWT.CookieEnabled` | `true` | Also deliver/accept the token as a cookie. | | `JWT.CookieName` | `access_token` | Cookie name. | | `JWT.CookieDomain` | — | Cookie domain. | | `JWT.CookiePath` | — | Cookie path. | | `JWT.CookieSecure` | `false` | Mark the cookie `Secure` (HTTPS only). | ::: danger Set `JWT.Secret` The default secret ships in the public source and is never used unless you opt in with `JWT.AllowDefaultSecret`. Set a strong, unique secret per deployment; MARSCmd writes a random one into the `.ini` files of every project it creates. ::: ## JSON parameters (per application) | Parameter | Type | Default | Purpose | | --- | --- | --- | --- | | `JSON.EscapeNonASCII` | Boolean | `True` | Escape the characters above 127 as `\uXXXX` in JSON responses. `False` writes them as they are (Unicode response encodings only). See [Non-ASCII characters](/features/serialization#non-ascii-characters). | | `JSON.SkipEmptyValues` | Boolean | — | Shortcut: sets all the `JSON.Skip*` options below. | | `JSON.SkipEmptyStrings` | Boolean | `True` | Omit `""` values. | | `JSON.SkipEmptyNumbers` | Boolean | `False` | Omit zero numbers. | | `JSON.SkipEmptyBooleans` | Boolean | `True` | Omit `false` values. | | `JSON.SkipEmptyObjects` | Boolean | `True` | Omit empty `{}`. | | `JSON.SkipEmptyArrays` | Boolean | `True` | Omit empty `[]`. | | `JSON.SkipNullValues` | Boolean | `True` | Omit `null`. | | `JSON.DateIsUTC` | Boolean | `True` only on a UTC+0 machine | Write and read `TDateTime` values as UTC. | | `JSON.UseDisplayFormatForNumericFields` | Boolean | `False` | Use the display format of dataset numeric fields. | Defaults are those of `DefaultMARSJSONSerializationOptions`; resource and method attributes win over these parameters. See [Serialization options](/features/serialization#serialization-options). ## Logging parameters Read by the [request/response loggers](/features/logging) (engine section). Each logger is inert until both its unit is in the server's `uses` clause and its `Enabled` flag is set. | Parameter | Type | Default | Purpose | | --- | --- | --- | --- | | `JSONLogging.Enabled` | Boolean | `False` | Enable the NDJSON file logger (`MARS.Utils.ReqRespLogger.JSON`). | | `MemoryLogging.Enabled` | Boolean | `False` | Enable the in-memory logger (`MARS.Utils.ReqRespLogger.Memory`); it retains whole requests and responses in clear text. | | `JSONLogging.BuiltInEntries` | Boolean | `True` | Write the built-in `in`/`out`/`error` lines of the JSON logger; `False` leaves the file to the entries your code writes with `Log`. | | `JSONLogging.Folder` | string | `\logs` | Target directory (created if missing). | | `JSONLogging.FileName` | string | `mars-reqresp.log` | Base log file name. | | `JSONLogging.DailyRotation` | Boolean | `True` | Insert the date before the extension for daily rotation. | | `CodeSiteLogging.Enabled` | Boolean | `False` | Enable CodeSite output (`MARS.Utils.ReqRespLogger.CodeSite`). | See [Request/Response Logging](/features/logging) for the log line format and a Grafana Alloy ingestion example. ## FireDAC parameters Connection definitions live under a slice (commonly `FireDAC`) and are loaded with `TMARSFireDAC.LoadConnectionDefs(FEngine.Parameters, 'FireDAC')`. Each named definition maps to a FireDAC `ConnectionDefName` with its usual driver-specific keys (`DriverID`, `Database`, `Server`, `User_Name`, `Password`, pooling options, …). See [FireDAC & Datasets](/features/firedac#enabling-firedac). ## Reading and injecting parameters ```pascal // In code var LPort := FEngine.Parameters.ByName('Port').AsInteger; var LSecret := LApp.Parameters.ByName('JWT.Secret').AsString; // Injected into a resource [Path('cfg')] TCfgResource = class [EngineParam('Port', 8080)] Port: Integer; [ApplicationParam('JWT.Secret')] Secret: string; end; ``` Provide a default as the second argument to `ByName`/`[EngineParam]`/`[ApplicationParam]` so missing keys degrade gracefully. ## Custom parameters You can add your own keys to the `.ini` and read/inject them the same way — a convenient place for feature flags, external service URLs, file paths, etc. ```ini [DefaultApp] Feature.NewSearch=true Storage.Path=C:\data\uploads ``` ```pascal [ApplicationParam('Storage.Path')] StoragePath: string; ``` --- Source: https://andrea-magni.github.io/MARS/demos/ # Demos The [`Demos`](https://github.com/andrea-magni/MARS/tree/master/Demos) folder contains ready-to-run projects, each focused on a specific MARS feature. Open the project group, build, and run the server (most also include a client and a test project). Below is what each one teaches, with a representative snippet. The `MARSTemplate` project is also the starting point produced by the [MARSCmd bootstrapper](/guide/installation#bootstrap-a-new-project-with-marscmd). ## MARSTemplate A complete, minimal application scaffold: a `helloworld` resource, a JWT `token` resource, an OpenAPI/Swagger endpoint, and host projects for every deployment target (console, VCL, FMX, Windows service, ISAPI, Apache module, FastCGI, Linux daemon). The recommended starting point — see [Your First Server](/guide/getting-started). ```pascal [Path('helloworld')] THelloWorldResource = class [GET, Produces(TMediaType.TEXT_PLAIN)] function SayHelloWorld: string; end; ``` ## ErrorObjects How to return errors at three levels of richness: a plain Delphi exception (→ 500), a MARS HTTP exception with a custom status/message, and a MARS exception carrying a structured JSON body — plus how the client reads that body back. See [Error Handling](/server/error-handling). ```pascal raise EMARSWithResponseException.Create('Error Message!', TValue.From(LErrorDetails), 530, 'The reason of the error'); ``` ## SSEDemo Server-Sent Events: a resource that pushes a `heartbeat` event every second over a persistent connection, plus a static HTML page that consumes it with the browser `EventSource`. See [Server-Sent Events](/features/sse). ```pascal [GET, Produces(TMediaType.TEXT_EVENT_STREAM)] function SayHelloWorld: TMARSServerSideEvent; ``` ## MCPServer An MCP (Model Context Protocol) server for AI agents: derive a resource from `TMCPResource`, mark methods with `[MCPTool]` and Claude, Claude Code or any MCP client can discover and call them over Streamable HTTP — tool list and JSON Schema are generated automatically via RTTI. See [MCP Servers](/features/mcp). ```pascal [MCPTool('add_numbers', 'Adds two numbers and returns a structured result')] function AddNumbers( [MCPParam('a', 'First operand')] const A: Double; [MCPParam('b', 'Second operand')] const B: Double): TCalculationResult; ``` The `server_dashboard` tool is an [MCP App](/features/mcp#mcp-apps-interactive-uis): hosts supporting the extension render its result with an interactive view (`bin/ServerDashboard.html`, plain JavaScript) whose Refresh button calls the view-only `dashboard_refresh` tool. ## TokenRenew JWT lifecycle management: checking remaining validity and automatically re-issuing the token when it drops below half its duration. See [Authentication ▸ Token renewal](/features/authentication#token-renewal). ```pascal if LRemainingSecs < (Token.DurationSecs / 2) then Token.Build(App.Parameters); // sliding-expiration renewal, with the active key ``` ## OTPDemo A full two-factor-authentication example: time-based one-time passwords (TOTP, RFC 6238) compatible with Microsoft/Google Authenticator, QR-code provisioning, and FireDAC-backed user storage. Includes both server and client. ```pascal [GET, Path('/verify/{username}/{otp}')] function Verify([PathParam] AUserName, AOTP: string): TVerifyOTPResponse; // Result.verified := TOTP.VerifyTotp(LUser.OTP_Secret, AOTP); ``` ## WebStencilsDemo Server-side HTML rendering with Embarcadero's WebStencils engine, binding live FireDAC datasets into templates. See [HTML & Templates](/features/templates#webstencils). ```pascal FWS.AddVarValue('datasetName', LDatasetName); FWS.AddDataVar('dataset', LMemTable, True); Result := FWS.ContentFromFile('dataset.html'); ``` ## HtmxDemo A hypermedia front-end with [htmx](https://htmx.org/): the server reads its own OpenAPI document and returns the endpoint list, which the page renders client-side without a SPA framework. See [HTML & Templates ▸ htmx](/features/templates#htmx). ```pascal function THelloworldResource.RetrieveData([Context] AOpenAPI: TOpenAPI): TDataResponse; begin for var LPath in AOpenAPI.paths do Result.endpoints := Result.endpoints + [TEndpoint.Create(LPath.Key, LPath.Value.Methods)]; end; ``` ## TailwindcssDemo A complete server-rendered web application rather than a single feature: users sign in against a FireDAC-queried table, confirm a time-based one-time password, and reach a styled dashboard where they can browse and manage users. Pages are rendered with WebStencils, updated with [htmx](https://htmx.org/) and styled with [Tailwind CSS](https://tailwindcss.com/). The token issued after the password step carries an `mfa_pending` claim, so a half-authenticated session cannot reach the application until the second factor clears it. A step-by-step walkthrough is in [Tailwind CSS for Delphi developers](https://github.com/andrea-magni/MARS/blob/master/docs/demos/tailwindcss-tutorial.md). ```pascal function IsFullyAuthenticated(const AToken: TMARSToken): Boolean; begin Result := AToken.IsVerified and not AToken.Claims.ByNameText('mfa_pending', False).AsBoolean; end; ``` ## MARS and Embarcadero KAI There is a video walkthrough of MARS with Embarcadero KAI: [YouTube — MARS and KAI](https://www.youtube.com/watch?v=C8HvfmgnVus). --- Source: https://andrea-magni.github.io/MARS/demos/tailwindcss-tutorial # Tailwind CSS for Delphi developers *A beginner's guide to styling MARS web apps.* This tutorial walks a Delphi developer through setting up [Tailwind CSS](https://tailwindcss.com/) in a MARS-Curiosity web project that renders its pages with WebStencils. No prior front-end experience is assumed: every new term is explained the first time it appears, and every example is taken from the [TailwindcssDemo](https://github.com/andrea-magni/MARS/tree/master/Demos/TailwindcssDemo) project, which you can build and run while reading. ## What you'll need, and what the words mean - **Tailwind CSS** — a library of small, reusable style "recipes" (classes such as `text-emerald-600`) that you attach directly to HTML tags instead of writing custom CSS rules. - **CLI (command-line interface)** — a small program you run from a terminal, much like running `dcc32.exe` to compile a Delphi project, except this one compiles CSS. - **Standalone CLI** — a build of the Tailwind CLI that needs no Node.js or npm: a single `.exe`, which is what most Delphi developers want. - **Static files** — plain files (CSS, JS, images) served exactly as they are on disk, with no server-side processing. - **WebStencils** — Embarcadero's server-side template engine. It merges data from Pascal objects into `.html` templates, much as a Delphi report template merges data into a printed document. - **Post-build event** — a command RAD Studio runs automatically right after a successful compile. ## Why combine Tailwind with MARS and WebStencils **MARS** is the REST/HTTP framework that receives the browser's request and calls a Pascal method. That method returns HTML by way of a small helper class, `TWebRenderService`, which uses **WebStencils** to fill placeholders such as `@page.Title` with real data. Tailwind's job is purely visual: it supplies ready-made CSS classes so the result looks finished without hand-writing stylesheets. ::: tip Reference material Embarcadero has an official video and demo project covering this same combination: - Video: [WebStencils + TailwindCSS](https://www.youtube.com/watch?v=NGYIF_CjEgo) - Demo project: [Embarcadero/WebStencilsDemos (TailwindCSSBased)](https://github.com/Embarcadero/WebStencilsDemos/tree/main/FeatureDemos/Delphi/TailwindCSSBased) ::: ## Installing the Tailwind standalone CLI Tailwind's CLI is normally installed through npm. Because most Delphi machines have no Node.js set up, use the standalone build instead — a single executable with no dependencies. 1. Open the [releases page](https://github.com/tailwindlabs/tailwindcss/releases). 2. Download the executable for your platform (for example `tailwindcss-windows-x64.exe`). 3. Rename it to `tailwindcss.exe` and put it in your project, next to the other build tools — think of it like copying a `.dll` next to your executable. The demo's committed `www/css/output.css` was generated with **v4.3.3**; use that version if you want a byte-comparable rebuild. The CLI is not committed to the repository — it is a ~107 MB binary, over GitHub's per-file limit — so this download is a one-time setup step. ::: tip The Tailwind CLI is only needed to *change* the styling. The compiled stylesheet ships with the demo, so you can build and run it without downloading anything. ::: 4. Open a command prompt in that folder and run: ```bash tailwindcss.exe --help ``` If a list of commands appears, the CLI works. There is no installer and no setup wizard. ![Command prompt showing the output of tailwindcss.exe --help](./images/tailwincss-help.png) ## Setting up the folder structure Create these folders alongside the Delphi project. This is the layout `TailwindcssDemo` uses: ``` Demos/TailwindcssDemo/ src/ input.css <- you write this www/ <- served as /static/... by MARS css/ output.css <- Tailwind generates this js/ htmx.min.js templates/ layouts/ application.html partials/ sidebar.html topbar.html pages/ users/ list.html detail.html bin/ <- the compiled server lives here ``` Two details are worth pausing on, because they are what make the layout portable: - `www` and `templates` sit **next to** `bin`, and both are located at run time relative to the executable — so the demo works wherever the repository is checked out. - The folder on disk is `www/css`, while the URL the browser requests is `/static/css` — the static resource maps one onto the other. ## Your first Tailwind input file Create `src/input.css` with a single line: ```css @import "tailwindcss"; ``` This is the equivalent of a `uses` clause that pulls in an entire library: it tells Tailwind to include its utility classes. ## Compiling CSS (the build step) This works exactly like compiling Delphi code: source in, compiler runs, output the runtime — here, the browser — can consume. ```bash tailwindcss.exe -i src/input.css -o www/css/output.css --config tools/tailwind.config.js --watch ``` | Flag | Meaning | | --- | --- | | `-i` | input file — your Tailwind source | | `-o` | output file — the compiled CSS the browser loads | | `--config` | the Tailwind config file to use | | `--watch` | recompiles automatically every time you save a change | For a release build, run once with `--minify` instead of leaving `--watch` running: ```bash tailwindcss.exe -i src/input.css -o www/css/output.css --config tools/tailwind.config.js --minify ``` ### Automating it with a post-build event Running the CLI by hand works, but it is easy to forget. RAD Studio can run the command for you after every successful compile: 1. **Project → Options…** 2. **Building → Build Events → Post-build event** 3. Enter the `--minify` command shown above. ![RAD Studio Build Events page with the Tailwind post-build command](./images/build-events.png) ::: tip Paths in a build event are resolved relative to the project's working directory — adjust them if your folder layout differs. Use `--minify` (which exits) rather than `--watch` (which does not) in a build event, or the build will never finish. ::: ::: warning `TailwindcssDemo` ships **without** these build events, and with `www/css/output.css` committed, so the demo compiles and runs on a machine that has no Tailwind CLI at all. Add the event to your own project, not to the demo. ::: ## Serving static files from MARS Register a static resource so MARS knows how to serve the compiled CSS and JS. This is the complete unit from the demo: ```pascal unit Server.Resources.Web.Static; interface uses MARS.Core.Attributes, MARS.Core.URL, MARS.WebServer.Resources; type [Path('static/{*}'), RootFolder('{bin}\..\www', True), MetaVisible(False)] TWebStaticResource = class(TFileSystemResource) end; implementation uses MARS.Core.Registry; initialization MARSRegister(TWebStaticResource); end. ``` Three things are happening here: - `[Path('static/{*}')]` claims every URL beginning with `/static/`; `{*}` captures the rest of the path as the file name. - `[RootFolder('{bin}\..\www', True)]` points at the folder to read from. `{bin}` is the folder holding the executable, so this resolves to `www` next to `bin`; `True` includes subfolders. - `MARSRegister(TWebStaticResource)` registers the class with the engine at startup. The same resource serves `www/js/htmx.min.js` and `www/js/ui.js`, so the demo's own JavaScript travels through exactly the mechanism you just registered. See [HTML & Templates](/features/templates) for the rest of what `TFileSystemResource` can do, including per-extension content types. ::: warning MARS resources register themselves once, when the process starts. After adding or changing a resource you must rebuild **and** restart the server — a running process will not pick up new routes. ::: ## Linking the output into your layout The master layout, `templates/layouts/application.html`, loads the compiled CSS and htmx: ```html @page.Title - MARS TailwindcssDemo
@Import partials/sidebar.html
@Import partials/topbar.html
@Import partials/flash-message.html @RenderBody
``` - `@page.BasePath` is a property of the page model, so the same template works whatever base path the engine is mounted on. - `htmx.min.js` adds interactivity — form posts and partial updates — without custom JavaScript. - `@Import` pulls in a reusable partial, much like sharing a frame between Delphi forms. - `@RenderBody` is where the individual page's content is inserted. ::: warning The interactive parts are a licensing decision Dropdowns, the account menu and the mobile navigation drawer need *some* JavaScript. The demo implements them in about a hundred lines of plain DOM code (`www/js/ui.js`), because the obvious off-the-shelf option — Tailwind Plus Elements — is commercially licensed. The next section covers both routes. ::: ## Interactive components: two options Tailwind CSS styles things; it does not open a drawer or toggle a menu. For that you need either a component library or a little JavaScript of your own. The two routes are genuinely different, and the choice has a licence attached — so it is worth making deliberately. ### Option A — plain Tailwind plus a little JavaScript (what the demo ships) `www/js/ui.js` is roughly a hundred lines with no dependencies. It listens for clicks on elements carrying data attributes and flips the `hidden` property: ```js document.addEventListener('click', function (event) { if (hit(event.target, '[data-sidebar-open]')) { openSidebar(); return; } if (hit(event.target, '[data-sidebar-close]')) { closeSidebar(); return; } var button = hit(event.target, '[data-menu-button]'); if (button) { toggleMenu(button); return; } if (!hit(event.target, '[data-menu-panel]')) closeMenus(null); // click-away }); ``` The markup side is ordinary Tailwind. The sidebar is one element that is a drawer on small screens and a static column from `xl` up, and the topbar button points at it: ```html ``` Two details make this work without any framework: - The drawer uses the **`hidden` attribute**, not a `hidden` class, so JavaScript can toggle it with `panel.hidden = false` while Tailwind's `xl:hidden` still removes it entirely on large screens. - The main content is inset by the sidebar's width at that same breakpoint — `xl:pl-72` in `application.html` — because the desktop sidebar is `xl:fixed` and therefore out of the normal flow. `Escape` closes both the drawer and the menu, and growing the window past `xl` resets the drawer. No licence, works on a phone, and it is small enough to read in one sitting. ### Option B — Tailwind Plus Elements [Tailwind Plus](https://tailwindcss.com/plus) sells the *Elements* library along with the UI Blocks markup it is designed for. It gives you custom elements — ``, ``, `` and friends — plus a declarative `command` / `commandfor` attribute pair, so the same behaviour needs no JavaScript of your own, and adds transition states you would otherwise hand-write. It is loaded as a module: ```html ``` ::: danger Elements and the UI Blocks markup are both commercially licensed This is **not** part of open-source Tailwind CSS. Using Elements — or shipping the Tailwind Plus UI Blocks markup it pairs with — requires a paid Tailwind Plus licence, and neither may be redistributed in a public repository. Read the [licence](https://tailwindcss.com/plus/license) before you commit either into a project other people can clone. That constraint is exactly why this demo went the Option A route. ::: The two are not drop-in replacements for each other: Elements keys off its own element names and `command` attributes, while Option A keys off `data-*` attributes and the `hidden` property. Adding the script to Option A's markup does nothing, and vice versa. Pick one per project. ## Choosing Tailwind classes from Delphi Some classes have to change according to server-side logic — highlighting the current navigation entry, for instance. The demo does this with `TWebPageInfo` in `Server.Web.Models.pas`: ```pascal TWebPageInfo = class private FTitle: string; FError: string; FSuccess: string; FBasePath: string; FActiveNav: string; public constructor Create; overload; constructor Create(const ATitle: string; const AActiveNav: string = ''); overload; property Title: string read FTitle write FTitle; property Error: string read FError write FError; property Success: string read FSuccess write FSuccess; property BasePath: string read FBasePath write FBasePath; property ActiveNav: string read FActiveNav write FActiveNav; function HasError: Boolean; function HasSuccess: Boolean; function NavStateClass(const AKey: string): string; function NavIconStateClass(const AKey: string): string; end; ``` - **`HasError` / `HasSuccess`** return `True` when a flash message is set, so the template can decide whether to render a notification banner. - **`NavStateClass(AKey)`** compares `AKey` (say, `'users'`) with the page's `ActiveNav` and returns the classes for an active or an inactive link. - **`NavIconStateClass(AKey)`** does the same for the icon beside each link. The implementation is deliberately dull — the interesting part is that the *class names* are data: ```pascal const NAV_ACTIVE_CLASS = 'bg-gray-100 text-emerald-600 dark:bg-white/5 dark:text-white'; NAV_INACTIVE_CLASS = 'text-gray-700 hover:bg-gray-100 hover:text-emerald-600 ' + 'dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-white'; function TWebPageInfo.NavStateClass(const AKey: string): string; begin if SameText(AKey, FActiveNav) then Result := NAV_ACTIVE_CLASS else Result := NAV_INACTIVE_CLASS; end; ``` A page sets its active section when it builds the model — in `Server.Resources.Web.Users.pas`: ```pascal LPage := TWebPageInfo.Create('Users', 'users'); ``` The first argument becomes the browser tab title; the second tells the navigation which entry to highlight while this page is on screen. ## Two WebStencils rules worth learning early These two catch nearly every newcomer. **A boolean property is read directly, not wrapped.** ```html @if (@user.IS_ACTIVE) { ... } @if user.Is_Active { ... } ``` **A method call with arguments needs the expression form `@( … )`.** ```html @page.NavStateClass('home') @(page.NavStateClass('home')) ``` | What you want | Wrong | Right | | --- | --- | --- | | test a boolean property | `@if (@user.IS_ACTIVE)` | `@if user.Is_Active { }` | | call a method with an argument | `@page.NavStateClass('home')` | `@(page.NavStateClass('home'))` | ## Putting it together: the sidebar A trimmed excerpt from the demo's `sidebar.html`, with both rules applied: ```html
Home ... Users ``` ![The demo's sidebar, with the Users entry highlighted as active](./images/sidebar-users.png) ## Day-to-day workflow 1. Keep `tailwindcss.exe … --watch` running while you edit templates. 2. Edit Pascal code as needed. 3. Build and run the project. 4. Restart the server so new routes and resources take effect. 5. Refresh the browser. ## Troubleshooting | Symptom | Likely cause | | --- | --- | | classes have no effect | the `output.css` link path is wrong, or the CLI never ran — check the file's timestamp | | the active-navigation highlight never changes | `@page.NavStateClass(...)` was used instead of `@(page.NavStateClass(...))` | | an `@if` on a boolean is always wrong | the property was wrapped as `@user.PROP` instead of referenced as `user.Prop` | | `No implementation found for http method GET` | the `[Path(...)]` attribute does not match the URL the template requests | | the CSS looks stale after a rebuild | the build event's paths are wrong, or `tailwindcss.exe` is not reachable from the working directory | | a dropdown or the mobile drawer does nothing | `www/js/ui.js` did not load — check the `/static/js/ui.js` request in the browser's network tab | | a class used only from Pascal or JavaScript has no effect | Tailwind never saw it: add that file to `content` in `tools/tailwind.config.js` and rebuild | ## Further reading - [HTML & Templates](/features/templates) — WebStencils, htmx and static files in MARS - [Authentication (JWT)](/features/authentication) — the token flow the demo's login builds on - [WebStencils + TailwindCSS](https://www.youtube.com/watch?v=NGYIF_CjEgo) (video) - [Embarcadero/WebStencilsDemos](https://github.com/Embarcadero/WebStencilsDemos/tree/main/FeatureDemos/Delphi/TailwindCSSBased) - [Tailwind CLI releases](https://github.com/tailwindlabs/tailwindcss/releases) --- Source: https://andrea-magni.github.io/MARS/release-notes # Release Notes What changed in each MARS-Curiosity release, newest first. Each entry is a one-liner with a link to the documentation, the demo or the issue; the GitHub release has the full notes, upgrade notes included. ## Unreleased {#unreleased} Changes on the `develop` branch, part of the next release. **New** - Linux daemon: `--foreground` (or `-f`) runs the server in the current process with logs on standard output, for systemd (`Type=simple`) and Docker. [Deployment](/guide/deployment#linux-with-systemd) - Documentation: [Deployment](/guide/deployment) guide (Windows service, systemd, Docker, reverse proxy, HTTPS, IIS/Apache/FastCGI), [Why MARS?](/guide/why-mars), [FAQ](/guide/faq); `llms.txt` and `llms-full.txt` for AI tools, sitemap. **Fixed** - Linux daemon: the log file only held its last line. - Documentation: the footer stated the wrong license (MARS is released under the Mozilla Public License 2.0). ## 1.8.1 {#v1-8-1} **7 October 2026** · [GitHub release](https://github.com/andrea-magni/MARS/releases/tag/v.1.8.1) · [changes since 1.8.0](https://github.com/andrea-magni/MARS/compare/v.1.8.0...v.1.8.1) **New** - Client logging: `OnLog`, `RegisterLogger`, `LogOptions` (content, masking), ready-made sinks, server-sent events streams. [Client logging](/client/logging) - Server JSON log: custom entries with structured data (`Log`), `JSONLogging.BuiltInEntries` ([#211](https://github.com/andrea-magni/MARS/pull/211)). [Custom entries](/features/logging#custom-entries-with-structured-data) - Shared configuration files: `[Include]` section in `.ini` files; `.ini` parameter names are case insensitive. [Shared configuration](/reference/parameters#shared-configuration-include) - OpenAPI: `[MetaRequestBody]` documents a body read by the method itself (the token resource uses it). [OpenAPI](/features/openapi) - DCS server: HTTPS without a reverse proxy (`PortSSL`, `DCS.SSL.CertFile`, `DCS.SSL.KeyFile`); `IMARSRequest.IsSecure`. [HTTPS](/server/engine#https) - Indy server: `Indy.KeepAlive` parameter, enabled in `MARSTemplate`. [Engine parameters](/reference/parameters#engine-parameters) - Templates: one `Server.ini` shared by all the server flavors; `MARSTemplateDCS` aligned with `MARSTemplate`, Windows service and Linux daemon on DCS. [MARSTemplate](/demos/#marstemplate) - MARSCmd: choice of the template (`MARSTemplate`, `MARSTemplateDCS`); new projects in `Documents\MARS Projects`. [MARSCmd](/guide/installation#bootstrap-a-new-project-with-marscmd) - Delphi-Mocks is a git submodule (`ThirdParty/Delphi-Mocks`). **Fixed** - DCS server: 404 on every request, content stream leak, query string, cookies not `HttpOnly`, static files downloaded as attachments, JSON request bodies not received. - OpenAPI: request body of methods without `[Consumes]` ([#212](https://github.com/andrea-magni/MARS/issues/212)). - Setup: the uninstaller deleted the user projects in the `Demos` folder. - MCP: OAuth metadata used `http://` for a server reached in HTTPS without a proxy. ## 1.8.0 {#v1-8-0} **5 October 2026** · [GitHub release](https://github.com/andrea-magni/MARS/releases/tag/v.1.8.0) · [changes since 1.7.1](https://github.com/andrea-magni/MARS/compare/v.1.7.1...v.1.8.0) **New** - JWT key rotation: `JWT.KeyId`, `JWT.PreviousSecret.`, custom key providers ([#82](https://github.com/andrea-magni/MARS/issues/82)). [Key rotation](/features/authentication#key-rotation) - MCP Apps: interactive HTML views for MCP tools (`[MCPToolUI]`, `[MCPAppResource]`). [MCP Apps](/features/mcp#mcp-apps-interactive-uis) · [MCPServer demo](/demos/#mcpserver) - MCP: optional tool parameters with `[MCPDefault]`. [MCP](/features/mcp) - JSON serialization options from the configuration file (`JSON.*` parameters). [From the configuration file](/features/serialization#from-the-configuration-file) - `JSON.EscapeNonASCII` parameter ([#208](https://github.com/andrea-magni/MARS/issues/208)). [Non-ASCII characters](/features/serialization#non-ascii-characters) - TMS Smart Setup: `tms install andreamagni.mars`. [Installation](/guide/installation#tms-smart-setup) - delphi-jose-jwt v4 as a git submodule. **Security** - JOSE backend: HS256 only (tokens asking for other algorithms were accepted). - `TFileSystemResource`: 8.3 short names bypassed the `[Exclude]`/`[Include]` masks ([#210](https://github.com/andrea-magni/MARS/issues/210)). **Fixed** - `MARS.DCS` package search path ([#209](https://github.com/andrea-magni/MARS/issues/209)); design-time package build; JOSE folder of the `MARSTemplateDCS` projects. **Changed** - Delphi 10.4 Sydney is the minimum supported version. ## 1.7.1 {#v1-7-1} **25 September 2026** · [GitHub release](https://github.com/andrea-magni/MARS/releases/tag/v.1.7.1) · [changes since 1.7.0](https://github.com/andrea-magni/MARS/compare/v.1.7.0...v.1.7.1) **New** - Delphi 13.2 support. **Fixed** - Large arrays of records sent by Win32 clients exhausted memory ([#205](https://github.com/andrea-magni/MARS/issues/205)). - Large arrays of records read by the server built the whole JSON tree ([#206](https://github.com/andrea-magni/MARS/issues/206)). - Applications not using JWT required `JWT.Secret` ([#207](https://github.com/andrea-magni/MARS/issues/207)). [Authentication](/features/authentication) ## 1.7.0 {#v1-7-0} **17 September 2026** · [GitHub release](https://github.com/andrea-magni/MARS/releases/tag/v.1.7.0) · [changes since 1.6.4](https://github.com/andrea-magni/MARS/compare/v.1.6.4...v.1.7.0) **New** - MCP server support: tools, resources, prompts, FireDAC tools, per-tool roles, OAuth 2.1 authorization server. [MCP servers](/features/mcp) · [MCPServer demo](/demos/#mcpserver) - Agent Skills for Claude Code and other AI coding agents. [AI Agent Skills](/guide/agent-skills) - This documentation site. - `QUERY` HTTP verb, server and client side ([#191](https://github.com/andrea-magni/MARS/issues/191)). [HTTP verbs](/server/resources#http-verbs) - `TFileSystemResource`: `HEAD` requests, `[DotSegments]` ([#204](https://github.com/andrea-magni/MARS/issues/204)), `[DirectoryListing]`. [Path safety](/features/templates#path-safety) - OpenAPI: more of the specification through attributes. [Enriching the spec](/features/openapi#enriching-the-spec) - `TMARSReqRespLoggerJSON`: JSON log files for Grafana/Loki. [File logging for Grafana](/features/logging#file-logging-for-grafana-json) - Tailwind CSS demo. [Tutorial](/demos/tailwindcss-tutorial) **Security** - Path traversal in `TFileSystemResource` ([#195](https://github.com/andrea-magni/MARS/issues/195)). - New projects get their own JWT secret ([#201](https://github.com/andrea-magni/MARS/issues/201), [#202](https://github.com/andrea-magni/MARS/issues/202)). - In-memory logger always on once its unit was included ([#197](https://github.com/andrea-magni/MARS/issues/197)). **Fixed** - Repeated query parameters ([#196](https://github.com/andrea-magni/MARS/issues/196)), directory listing ([#198](https://github.com/andrea-magni/MARS/issues/198)), JSON to record/object ([#199](https://github.com/andrea-magni/MARS/issues/199), [#200](https://github.com/andrea-magni/MARS/issues/200)), MCP OAuth behind a proxy ([#203](https://github.com/andrea-magni/MARS/issues/203)). - A malformed request body answers 400 instead of 500; wildcard routing; tokens and JWT decoding. ## 1.6.4 {#v1-6-4} **5 June 2026** · [GitHub release](https://github.com/andrea-magni/MARS/releases/tag/v.1.6.4) · [changes since 1.6.3](https://github.com/andrea-magni/MARS/compare/v1.6.3...v.1.6.4) **New** - `TMARSHttpClient`: client component with server-sent events support. [Client components](/client/components) · [Server-Sent Events](/client/resources#server-sent-events) - WebStencils integration. [WebStencils](/features/templates#webstencils) - Demos: [SSEDemo](/demos/#ssedemo), [WebStencilsDemo](/demos/#webstencilsdemo), [HtmxDemo](/demos/#htmxdemo). - JSON: `TList>` serialization; `TMARSJSONSerializationOptions` reworked. [JSON serialization](/features/serialization) - Tests for parameters, JWT and claims. ## 1.6.3 {#v1-6-3} **15 April 2026** · [GitHub release](https://github.com/andrea-magni/MARS/releases/tag/v1.6.3) · [changes since 1.6.2](https://github.com/andrea-magni/MARS/compare/v1.6.2...v1.6.3) **New** - `Access-Control-Allow-Private-Network` CORS header ([#179](https://github.com/andrea-magni/MARS/pull/179)). [CORS](/server/engine#cors) - `IMessageBodyStreamProvider`: large responses streamed without loading them in memory. [Content negotiation](/server/content-negotiation) - `[Headers]`, `[Cookies]`, `[QueryParams]`, `[PathParams]` collection binders. [Collection binders](/server/attributes#collection-binders) - Demos: [OTPDemo](/demos/#otpdemo), [TokenRenew](/demos/#tokenrenew). **Fixed** - Linux fixes. ## 1.6.2 {#v1-6-2} **22 October 2025** · [GitHub release](https://github.com/andrea-magni/MARS/releases/tag/v1.6.2) · [changes since 1.6.1](https://github.com/andrea-magni/MARS/compare/v1.6.1...v1.6.2) - Setup packages cleanup ([#172](https://github.com/andrea-magni/MARS/pull/172)); README, installation and contributing documents revised ([#173](https://github.com/andrea-magni/MARS/pull/173)–[#178](https://github.com/andrea-magni/MARS/pull/178)). ## 1.6.1 {#v1-6-1} **30 September 2025** · [GitHub release](https://github.com/andrea-magni/MARS/releases/tag/v1.6.1) · [changes since 1.6.0](https://github.com/andrea-magni/MARS/compare/v1.6.0...v1.6.1) - Setup compatible with Delphi 10.2 ([#172](https://github.com/andrea-magni/MARS/pull/172)). ## 1.6.0 {#v1-6-0} **11 September 2025** · [GitHub release](https://github.com/andrea-magni/MARS/releases/tag/v1.6.0) · [changes since 1.5.9](https://github.com/andrea-magni/MARS/compare/v1.5.9...v1.6.0) **New** - Delphi 13 Florence support ([#170](https://github.com/andrea-magni/MARS/pull/170)). - `MARSTemplateServerFCGI`: FastCGI server for nginx in `MARSTemplate`. [MARSTemplate](/demos/#marstemplate) **Fixed** - Date serialization options were ignored ([#169](https://github.com/andrea-magni/MARS/pull/169)). ## 1.5.9b {#v1-5-9b} **1 August 2025** · [GitHub release](https://github.com/andrea-magni/MARS/releases/tag/v1.5.9b) · [changes since 1.5.9](https://github.com/andrea-magni/MARS/compare/v1.5.9...v1.5.9b) **New** - `PATCH` HTTP verb ([#168](https://github.com/andrea-magni/MARS/pull/168)). [HTTP verbs](/server/resources#http-verbs) - Error objects: structured error bodies, server and client side. [Error handling](/server/error-handling) · [ErrorObjects demo](/demos/#errorobjects) **Fixed** - Custom headers of the Indy client. ## 1.5.9 {#v1-5-9} **27 June 2025** · [GitHub release](https://github.com/andrea-magni/MARS/releases/tag/v1.5.9) · [changes since 1.5](https://github.com/andrea-magni/MARS/compare/v1.5...v1.5.9) **New** - `IMARSEngine` and `IMARSApplication` interfaces throughout MARS. [Engine](/server/engine) - More JSON serialization options, with more granularity. [Serialization options](/features/serialization#serialization-options) - `AfterContextCleanup` hooks, by attribute and through `TMARSActivation`. [Request lifecycle](/server/request-lifecycle) - Setup (installer) ([#166](https://github.com/andrea-magni/MARS/pull/166)). [Installation](/guide/installation) **Fixed** - Delphi version detection ([#142](https://github.com/andrea-magni/MARS/pull/142)), `TryISO8601ToDate` on Delphi XE7 and earlier ([#145](https://github.com/andrea-magni/MARS/pull/145)), [#141](https://github.com/andrea-magni/MARS/issues/141) and others ([#146](https://github.com/andrea-magni/MARS/pull/146)), resource `ConstructorFunc` not called ([#156](https://github.com/andrea-magni/MARS/pull/156)). ## Older releases See the [GitHub releases](https://github.com/andrea-magni/MARS/releases?page=2).