Early access: Ailimu is free while we build to 1.0. We'd love your feedback. Send feedback Get notified at 1.0
Ailimu

Reference

The tables on this page are generated from the code by pnpm docs, and CI fails if they drift. If they say something, that is what the build does. The activity log format below is written by hand, from apps/connector/src/activity.ts.

Environment variables

Only FM_USER and FM_PASS are required.

Variable What it is
FM_USER, FM_PASS REQUIRED. The FileMaker account the connector logs in as. Needs the fmodata extended privilege to read tables, plus fmrest to expose scripts. Without them discovery returns 401. Credentials are read from the environment only. They are never written to the config file, so they stay out of backups and out of the hosted tier.
FM_USER_<SLUG>, FM_PASS_<SLUG> Several FileMaker files on one connector? Each is a "profile" with its own slug, its own /mcp/<slug> endpoint and its own token. Give a profile its own account by suffixing the slug in upper snake case; anything unset falls back to FM_USER / FM_PASS above, so a single-file setup needs nothing here. profile "sales-live" -> FM_USER_SALES_LIVE / FM_PASS_SALES_LIVE The server address is set per profile in the wizard, so files on different servers (fm5.example.com and fm6.example.com) are two profiles, each with its own pair here. Changing .env means restarting the container.
FM_HOST, FM_DATABASE Optional. Prefills the wizard's Connection step. The wizard's own values win, and the published config is what the MCP endpoint actually uses at runtime. Must include the scheme and no trailing slash: it is used as a URL prefix, <FM_HOST>/fmi/data/vLatest/databases/<FM_DATABASE>
PG_USER, PG_PASSWORD, PG_USER_<SLUG>, PG_PASSWORD_<SLUG> Optional. A PostgreSQL database instead of, or beside, a FileMaker file. The account Ailimu logs in as, read from the environment only and never written to the config file. Give it only the rights you want Claude to have: the database's own permissions are the real limit. A connection with its own account uses the slug in upper snake case, and anything unset falls back to PG_USER / PG_PASSWORD. connection "shop" -> PG_USER_SHOP / PG_PASSWORD_SHOP
PG_HOST, PG_PORT, PG_DATABASE Optional. Prefills the wizard's Connection step for a PostgreSQL connection. The wizard's own values win. PG_PORT defaults to 5432.
ADMIN_TOKEN, MCP_TOKEN Optional. ADMIN_TOKEN is the setup code that creates the first owner account, and works as an owner-level bearer token for scripting /admin/api; people sign in to the wizard with their own accounts. MCP_TOKEN guards the bare /mcp endpoint. Leave blank and the connector generates them on first run, prints them, and persists them on the config volume so they survive a restart. Set them here to pin your own values.
ACTIVITY_RETAIN_DAYS, ACTIVITY_READS_RETAIN_DAYS Optional. How many days the activity log keeps entries. Changes, script runs and admin actions default to 365. Claude's reads, recorded only on connections where "Record reads" is on, default to 30: they are nearly all of the volume, roughly 0.6 MB a day for normal use and 6 MB a day for heavy use.
COMPACT_RESULTS, RESULT_MAX_KB, FIND_DEFAULT_ROWS, TEXT_CLIP_CHARS, COLUMNAR_FROM_ROWS Optional. How big a tool result may get. Every token of a result is paid for by the person using Claude, so by default results are lean: no indentation, empty fields left out, long text cut in a search, 25 rows unless Claude asks for more, and at most 40 KB (about 10,000 tokens) in one answer. The tools describe themselves briefly too: how to search is said once per table, not once per field. Setting COMPACT_RESULTS to off goes back to the earlier, larger format for both the answers and the tool descriptions. The other four each change one thing: the size of one answer in KB (5 to 1000), the rows a search returns when no limit is given (1 to 500), the characters kept of a long text in a search (0 keeps it all), and how many rows make a search a table (7 by default: the field names once, then the values, which roughly halves a long listing; 0 never does). Records fetched with get_ always keep their full text. A value that is not a number is ignored.
PORT Optional. Defaults to 8081.
PUBLIC_URL Optional. The address people open Ailimu at, e.g. http://192.168.1.20:8081 or https://ailimu.example.com. Only used to print a correct wizard link in the start-up log; inside Docker the connector cannot work it out itself.
UPDATE_CHECK Optional. Once a day Ailimu asks Docker Hub which versions of its image exist, so the wizard can tell owners and editors when a newer one is out, and then reads ailimu.net/releases.json for what it brings. Neither request carries anything about your install or your data. Set to off to stop both.
MCP_ALLOWED_ORIGINS Optional. Claude and other MCP clients send no Origin header and are never affected. A web page on another site that calls /mcp is refused. List the addresses of any browser-based tool that should be let through, separated by commas, e.g. http://localhost:6274 for the MCP inspector.
TRUST_PROXY Optional. Set this only when Ailimu sits behind a reverse proxy, so sign-in limits and secure cookies see the browser's address rather than the proxy's. "loopback" for a proxy on the same machine, the proxy's address or subnet (e.g. 172.18.0.0/16 for one in the same Docker network), or a hop count. Leave unset otherwise: a trusted proxy header can be faked by anyone who can reach Ailimu directly.
CONFIG_DIR Leave CONFIG_DIR unset. Local default is ./.ailimu-config and the Docker image sets /config itself. Setting it here would override the volume path.
FM_TIMEOUT_SECONDS Optional. Seconds to wait for FileMaker to answer one request before giving up. Default 60.
PG_TIMEOUT_SECONDS Optional. Seconds PostgreSQL may spend on one statement before it is stopped, so a search that cannot finish says so instead of holding a connection. Default 30.

Credentials for several files

With more than one profile, each may have its own FileMaker account, named after the profile's slug in upper snake case, falling back to the unsuffixed pair:

FM_USER_SALES_LIVE=...
FM_PASS_SALES_LIVE=...

Each profile also has its own server address, set in the wizard. So files on different servers, such as https://www5.example.com and https://www6.example.com with a different user on each, are simply two profiles with their own variables:

FM_USER_SERVER5=...
FM_PASS_SERVER5=...
FM_USER_SERVER6=...
FM_PASS_SERVER6=...

FM_HOST does not choose the server for a profile. It only pre-fills the address on a new connection. You do not need it when you use several servers.

To add another account, for example when a second FileMaker file or server needs a user with its own privileges:

  1. Add the profile in the wizard and note its slug, such as sales-live.
  2. Add FM_USER_SALES_LIVE and FM_PASS_SALES_LIVE to .env. Hyphens in the slug become underscores.
  3. Restart: docker compose up -d --force-recreate.

Each account needs the fmrest extended privilege for scripts and fmodata for tables. The Connection step tells you whether a file is using its own account or the default one, and which variable names it looks for.

Credentials are read from the environment only. They are never written to the config file and never handled by the wizard, which is why adding a profile with its own account means editing .env and restarting.

Endpoints

Method Path Auth What
GET /healthz none Liveness. Returns {ok:true} and nothing about your data.
GET / none Redirects to /admin, so the bare address opens the setup wizard.
GET /docs none This manual, for the version you are running.
GET /admin none The setup wizard. It asks you to sign in, or to create the owner account on first run.
POST /mcp mcp token MCP endpoint. Serves the only profile when the connector holds exactly one.
POST /mcp/:profile profile token MCP endpoint for one profile, with its own token.
GET /admin/auth/me none Whether to show first-run setup, sign-in, or the wizard.
POST /admin/auth/setup none First run only: create the owner account, with the setup code (ADMIN_TOKEN).
POST /admin/auth/login none Sign in with email and password. Five misses lock that email out for fifteen minutes.
POST /admin/auth/logout none Sign out this browser.
GET /admin/auth/link/:secret none Check a one-time invite or reset link without using it.
POST /admin/auth/link/:secret none Use a one-time link: choose a password and sign in.
POST /admin/auth/restore none First run only: restore a backup instead of creating an owner, with the setup code (ADMIN_TOKEN) and the backup's password.
GET /admin/api/status viewer Credential readiness, defaults, the profile list and who you are.
GET /admin/api/profiles viewer Every profile this connector serves. Tokens are left out for viewers.
GET /admin/api/profiles/:slug viewer One profile's published config.
GET /admin/api/profiles/:slug/schema viewer The cached schema, so opening a profile costs no FileMaker read.
GET /admin/api/profiles/:slug/erd editor The relationships between this connection's tables, for the ERD tab: each table's key fields and the links FileMaker declares or the field names suggest. Read from the cache, so no FileMaker read.
GET /admin/api/profiles/:slug/claude-desktop editor The Claude Desktop extension (.mcpb) for this profile, with its address and token filled in.
PUT /admin/api/profiles/:slug/fill-mode editor How the wizard fills in a table that has nothing exposed yet: essentials or empty. Saved without publishing.
DELETE /admin/api/profiles/:slug editor Delete a profile. Its endpoint stops immediately; FileMaker is untouched.
POST /admin/api/databases editor Files this FileMaker server exposes to the account.
POST /admin/api/test-connection editor Check the host, file and privileges before discovering.
POST /admin/api/discover editor Read the schema: tables, row counts, scripts and layouts.
POST /admin/api/sample editor Read a bounded row sample for named tables, to find unused columns.
POST /admin/api/preview viewer The tools a configuration would produce, without saving it.
POST /admin/api/try editor Run one read tool from an unsaved configuration against live data.
GET /admin/api/config viewer The current config and the tools it generates.
PUT /admin/api/config editor Publish. Takes effect immediately, with no restart, and records who published.
GET /admin/api/activity editor Search the activity log by date, keyword, connection, who and kind; outcome=error shows failures only. The first page also returns per-kind counts and the people list. format=csv downloads every match.
GET /admin/api/people owner Everyone with an account, and pending invite and reset links.
PUT /admin/api/people/:id/access owner Give a person access to one connection with their own key, change it to read-only or back, or take it away.
POST /admin/api/people/:id/access/:slug/reset owner A new key for one person on one connection. Their old key stops working at once.
GET /admin/api/my-access signed in The connections you may use with Claude. With reveal=1, your own keys for them (recorded in the activity log).
GET /admin/api/my-access/:slug/claude-desktop signed in Your own Claude Desktop extension for one connection, with your key inside.
GET /admin/api/server-keys owner The setup code (ADMIN_TOKEN) and the token for the older /mcp address. Recorded in the activity log.
POST /admin/api/backup owner A password-protected backup of every connection, token and account, and the activity log if asked.
POST /admin/api/restore owner Replace everything with a backup. Everyone is signed out.
POST /admin/api/people owner Invite a person with a role. Returns a one-time link to pass on; no email is sent.
PATCH /admin/api/people/:id owner Change someone's role. The last owner cannot be demoted.
DELETE /admin/api/people/:id owner Remove a person and sign them out everywhere.
POST /admin/api/people/:id/link owner A fresh one-time link: a password reset, or a re-sent invite.
DELETE /admin/api/links/:id owner Cancel a pending invite or reset link.
POST /admin/api/account/password signed in Change your own password. Signs you out everywhere else.

/healthz and /docs are deliberately unauthenticated: one is a liveness probe that reveals nothing, the other is this manual.

Tools

A table called invoices, a saved search called unpaid and a script exposed as recalculate_totals produce:

Tool What it does
get_invoices One record by its identifier. Every readable field.
find_invoices Search. Returns the identifier plus the searchable fields, and the total match count.
describe_invoices The fields a search cannot filter on, and how this table links to others.
update_invoices Change a record. Only exists when writes are enabled and read-only is off.
search_unpaid One saved search: a question set up in advance, with its own conditions and blanks. Read-only, and exists even when read-only is on.
recalculate_totals One exposed FileMaker script. Only exists when read-only is off.

Limits

Limit Value Why
Rows sampled per table 50 A few of the oldest rows and more of the newest, so a field added to the file later is not mistaken for an unused one.
Sample cell budget 5000 FileMaker's OData returns malformed JSON past roughly 280KB, so a wide table gets fewer rows. A 208-column table samples 24 rows.
Concurrent FileMaker requests 8 Discovery is one request per table; unbounded would hammer the server.
Tool result budget 40KB Whole rows are dropped to fit, and the tool says how many. About 10,000 tokens. Change it with RESULT_MAX_KB; the earlier format allowed 200KB.
Rows per search 25 (at most 500) What a search returns when no limit is given. Change the default with FIND_DEFAULT_ROWS.
Long lists as a table 7 rows or more A search this long is written with the field names once, then each row as a list of values. Change it with COLUMNAR_FROM_ROWS; 0 never does.
Long text in a search 500 characters A longer text is cut and says where the whole of it is: get_ returns it in full. Change it with TEXT_CLIP_CHARS.
Tables per sample request 500 A malformed request cannot turn into an unbounded scan.

Config file

One JSON file per profile, under profiles/ on the config volume. You should not need to edit it by hand, but it is plain JSON so you can read it and keep it in version control if you want to.

Field Type Default
tenantId string required
slug string optional
displayName string required
connection object required
connection.host string required
connection.database string required
connection.apiVersion string "vLatest"
connection.backend dataapi | odata | postgres "dataapi"
connection.port number optional
connection.schema string optional
connection.ssl off | require | verify optional
connection.scriptDatabase string optional
readOnly boolean true
resources array []
scripts array []
savedSearches array []
audit object {}
audit.enabled boolean true
audit.logWrites boolean true
audit.logReads boolean false
updatedAt string optional
updatedBy string optional
lastDiscovered string optional
fillMode essentials | empty optional

Activity log format

What the Activity screen searches is plain text on the config volume, so you can also read it on the server, back it up, or feed it to your own monitoring or log system.

/config/activity/
  2026-09-28.jsonl          changes, script runs and admin actions, one file per day
  2026-09-27.jsonl
  reads/
    2026-09-28.jsonl        Claude's reads, only for connections that record them

Fields

Field In What
ts every entry When it happened, UTC.
kind every entry change, script, read or admin.
connection most entries The connection's slug, as in /mcp/<slug>.
actor every entry Claude for tool calls; a person's email for admin actions; admin token for API calls made with ADMIN_TOKEN.
tool change, script, read The MCP tool that was called.
outcome change, script, read ok or error.
error failed calls What FileMaker or Ailimu said, up to 500 characters.
ms change, script, read How long the call took, in milliseconds.
record change The ID of the record that was changed.
fields change The fields that were set, with the values sent.
fmScript script The FileMaker script's own name.
args script, read What Claude passed: script parameters, or search criteria.
matches, returned read For a search: how many records matched, and how many came back.
bytes change, script, read How much text Claude was handed for the call, in bytes. Roughly four bytes to a token, so it shows which tools cost the most. Entries written before this was added do not have it.
action admin What the person did, in words.
person, role admin Who an action on people was about, and their role.
migrated imported entries true on entries carried over from an old audit.log.

What a read never contains: the rows that came back. A read records the question and the count, so the log does not become a second copy of your data. A change does contain the values written, which is why Activity is for editors and owners only.

One entry of each kind

A change:

{"ts":"2026-09-28T09:05:12.441Z","kind":"change","connection":"sales","actor":"Claude","tool":"update_invoices","record":"INV-70114","fields":{"payment_status":"Paid"},"outcome":"ok","ms":312}

A script run that FileMaker refused:

{"ts":"2026-09-28T09:06:40.118Z","kind":"script","connection":"sales","actor":"Claude","tool":"recalculate_totals","fmScript":"Recalculate Totals","args":{"invoice_id":"INV-70114"},"outcome":"error","error":"FileMaker script Recalculate Totals returned error 401: no records matched the request.","ms":540}

A search, on a connection that records reads:

{"ts":"2026-09-28T09:04:58.003Z","kind":"read","connection":"sales","actor":"Claude","tool":"find_invoices","args":{"payment_status":"Overdue"},"outcome":"ok","returned":50,"matches":400,"ms":188}

An admin action:

{"ts":"2026-09-28T08:59:30.772Z","kind":"admin","actor":"[email protected]","action":"published a connection","connection":"sales","readOnly":false,"logReads":true,"tables":4,"scripts":1}

Searching on the server

# Everything that touched one record
docker compose exec ailimu sh -c 'grep -h INV-70114 /config/activity/*.jsonl'

# Every failed call in the last day's file, with jq on the host
docker compose exec ailimu cat /config/activity/2026-09-28.jsonl | jq -c 'select(.outcome == "error")'

Known FileMaker behaviour

Things the FileMaker APIs do that Ailimu works around. They are listed here because they look like Ailimu bugs and are not: