Timill Platform Documentation

MCP Server

Model Context Protocol server reference for Timill Platform — connection, authentication, and the full tool catalog for AI agents.

Timill ships a Model Context Protocol server bundled inside the platform itself, so AI agents (Claude, and any MCP-capable host) can read and write your Timill data the same way the REST API does — e.g. “Create a goal and a few tasks for John, blocked by the Q2 launch.”

It is served over Streamable HTTP at the path /mcp on your instance, and exposes a small set of generic tools (no hardcoded item types — they adapt to your group’s schema). Every call is authenticated per-user and enforced by the same RBAC that governs the rest of the app: an agent can only ever see and do what the authenticating user can.

This page is written for agents and the people configuring them. If you are looking for the HTTP/JSON surface, see the REST API.


What is Timill? (Read this before assuming a domain)#

Timill is a configurable work platform, not a fixed CRM. Do not assume it tracks leads, deals, or contacts — that is only true if this particular instance was set up that way.

The model is deliberately generic:

  • A group is a workspace. Each group defines its own item types, custom fields, statuses, and lists — its own schema.
  • An item is any record of one of those types: a sales lead, a purchase order, a restaurant table reservation, a support ticket, a manufacturing work order, a recipe, a maintenance job, a job applicant — whatever the group was configured for.

So the same tools (query_items, create_items, …) operate on a CRM in one group, an ERP/inventory system in another, and a restaurant or clinic booking system in a third. There are no built-in concepts of “lead”, “deal”, “product”, or “customer” — those, if they exist at all, are just item types and fields a human defined for that group.

Because of this, never guess the schema — discover it. A request like “find my open leads” may map to a Lead type in one instance, an Opportunity type in another, or nothing at all. Always:

  1. list_groups → see which workspaces exist and pick the relevant one.
  2. get_group_config(groupId) → learn that group’s actual item types, field ids, statuses, and members.
  3. Then build your query_items / create_items calls against those real ids.

If nothing in the group’s schema matches what the user asked for, say so — don’t invent a “leads” concept that this instance doesn’t have.


Which server URL do I use? (Read this first)#

There is no single global MCP endpoint. Each Timill instance runs its own MCP server at its own base URL. You must point your agent at the URL of the instance that holds your data.

Your deploymentYour MCP URL
Timill Cloud (shared SaaS)https://app.timill.com/mcpthe default public server
Dedicated cloud instancehttps://<your-subdomain>.timill.com/mcp (your dedicated host)
Self-hosted / private instancehttps://<your-platform-host>/mcp

https://app.timill.com/mcp is only the default public server for the shared Timill Cloud. It can only reach data that lives on app.timill.com. If you are on a private (self-hosted) or dedicated cloud instance, the public server cannot see your groups, items, or users at all — and its OAuth login is a different account namespace.

To get full support against your own data, you must connect to your own instance’s /mcp URL. The MCP server, its OAuth authorization server, and the discovery endpoints are all hosted on that same instance, so everything (login, consent, tokens, tools) is self-contained per instance.

Operators: the MCP server is gated by an instance-level Enable MCP toggle (Instance Admin → Settings, or the TIMILL_ENABLE_MCP env var; default on). When disabled, /mcp and all OAuth endpoints return 404 — the feature is simply not advertised. No restart is needed to flip it.


Connecting#

Most MCP hosts take a server URL and handle the OAuth login for you. Minimal client config:

{
  "mcpServers": {
    "timill": {
      "url": "https://your-instance-host/mcp"
    }
  }
}

Replace the host with your instance (see the table above). On first connect the host opens a browser, you log in to Timill and consent, and the agent is issued a token automatically — no manual token paste required.


Authentication#

The /mcp endpoint accepts a bearer token in the standard header:

Authorization: Bearer <token>

There are two supported ways to obtain that token:

Timill acts as a full OAuth 2.1 Authorization Server for its own MCP server. MCP hosts discover and complete the flow automatically — you only log in and click Allow. Under the hood:

  • The user authenticates to Timill however they normally do (password, Google, Microsoft, or OIDC) and explicitly consents.
  • Timill mints its own opaque access + refresh tokens, scoped to the single mcp scope. (The underlying login method is irrelevant to the token.)
  • Access tokens last 1 hour; refresh tokens last 90 days. The host refreshes silently.

On a missing/invalid token the server returns 401 with a WWW-Authenticate challenge pointing at the discovery document, so compliant hosts can start the flow themselves.

Discovery & OAuth endpoints (all on your instance’s base URL, public, CORS-enabled):

EndpointPurpose
GET /.well-known/oauth-protected-resourceRFC 9728 — names the authorization server protecting /mcp
GET /.well-known/oauth-authorization-serverRFC 8414 — authorization-server metadata
GET /.well-known/openid-configurationAlias of the above (some hosts probe this)
POST /oauth/registerRFC 7591 — dynamic client registration
GET /oauth/authorizeAuthorization endpoint (login + consent)
POST /oauth/tokenToken endpoint (authorization_code, refresh_token)
GET /oauth/jwksEmpty key set (tokens are opaque, validated by lookup)

The protected-resource identifier is exactly <base-url>/mcp — it must match the URL the client connects to.

2. Personal API token (manual / scripted clients)#

The same tim_… bearer tokens used by the REST API also work on /mcp. Generate one in the app under User Settings → API Keys, then supply it via your client’s auth/header config:

Authorization: Bearer tim_xxxxxxxxxxxxxxxxxxxxxxxx

This path is handy for scripts and non-interactive agents that can’t run a browser OAuth flow.

Permissions & RBAC#

Whichever token is used, the agent acts as that user. There is no elevation — instance-admin context is never applied to MCP calls. RBAC is enforced inside the platform’s core logic on every call:

  • Group access requires membership (deleted/inaccessible groups are hidden).
  • query_items applies row-level RBAC filters — agents only see items their role allows.
  • Every create/update/delete/comment re-checks the relevant permission and field-level rules.

Per-tool permission requirements:

PermissionTools
(none — self-scoped)whoami
GROUP_READlist_groups, get_group_config, get_list, resolve_field_options
ITEM_READquery_items, get_item, list_comments
ITEM_CREATEcreate_items
ITEM_UPDATEupdate_items
ITEM_DELETEdelete_item
ITEM_COMMENTadd_comment
GROUP_ADMINISTRATIONadd_list_options

Tool catalog#

Thirteen tools, in three groups. Inputs and outputs are schema-typed — the host is forced to send conforming arguments and receives structured results.

Discovery & schema#

ToolPurposeKey inputs
whoamiIdentify the authenticated caller — returns {userId, name}. Use to resolve “me”/“my” (e.g. assign to self, or filter query_items by your own id).(none)
list_groupsList the groups the user can access — [{id, name}]. Start here to discover which group to work in.(none)
get_group_configThe “read the config” call. Lean snapshot of a group’s schema: per item type its custom fields ({id, name, type, listId?, options?, optionsTruncated?, ref?}) and statuses ({id, name}), plus a light member roster ({userId, name}) for resolving names like “John”. List-backed dropdown options are inlined per field (capped at 50). Run before querying or creating items.groupId
get_listFetch the complete option set [{id, name}] of one custom list. Only needed when a field reported optionsTruncated: true.groupId, listId
resolve_field_optionsThe smart linking tool. Returns candidate [{id, name}] for a reference or dropdown field, search-filtered and capped. Handles ItemREF/UserREF/dropdown, cross-group scope, and dynamic queries server-side. Use it to find the id to set on a reference field (e.g. link a task to “the launch item”).groupId, itemTypeId, fieldId, itemId?, search?, limit? (default 25, max 100)
add_list_optionsAppend new options to an existing custom list (e.g. add a missing dropdown value before setting it). Returns the created options with server-generated ids. (Creating/deleting whole lists is out of scope.)groupId, listId, options: [{name, color?}]

Items#

ToolPurposeKey inputs
query_itemsThe workhorse read. Two modes — pick one per call: (1) Structured (query DSL + type/status/assignee filters, sort/order, limit/offset) for precise, sortable, paginated results; (2) Search (search) for fuzzy, relevance-ranked keyword lookup over title/description (when search is set the structured filters and sort/offset are ignored). Default output is a compact row {id, idInGroup, title, type, status, assignee} plus {total, limit, offset, hasMore}. Pass fields (custom/built-in ids, or ["all"]) to attach a raw {fieldId: value} map per row and skip per-row get_item.groupId, plus query?, type?, status?, assignee?, search?, sort?, order?, limit? (default 50, max 100), offset?, fields?
get_itemOne item in full detail: all built-in fields plus every custom field with datatype metadata. Prefer query_items with fields to read many items at once.groupId, itemId
create_itemsBatch create (e.g. a goal + several tasks in one call). title and type required per item; items are created independently — one failure doesn’t abort the rest. Returns a per-item result with ok/error and per-field violations.groupId, items: [{title, type, description?, status?, assignee?, dueDate?, customFields?}]
update_itemsBatch partial update. Each patch is keyed by itemId; only included fields change, customFields are merged. Applied independently; check each result.groupId, items: [{itemId, title?, description?, status?, assignee?, dueDate?, customFields?}]
delete_itemSoft-delete — moves the item to the group’s trash (hidden from default queries, recoverable in the app). Destructive; confirm the itemId first.groupId, itemId

Comments#

ToolPurposeKey inputs
list_commentsAn item’s comment thread ({id, author, content, created}), newest first, paginated.groupId, itemId, limit? (default 50, max 100), offset?
add_commentPost a comment on an item. Auto-subscribes the author and any @mentioned members and notifies them.groupId, itemId, body

The query_items query DSL#

Used in the query argument (structured mode). Predicates combine with AND / OR / NOT and ( ) grouping; adjacent terms are implicitly AND-ed.

PatternMeaning
field:valueEquality on selection fields; case-insensitive contains on free-text (title/description)
field:~text / field:^text / field:text$contains / starts-with / ends-with
field:/regex/ / field:*.pdfregex / wildcard
field:>5 >=5 <5 <=5comparisons
field:1..10 / dueDate:2026-01-01..2026-03-01inclusive range
status:(open pending closed)IN list (any of)
field:$empty / field:$filledpresence check
created:$today $yesterday $thisweek $thismonth $thisyearrelative dates
dueDate:$today-7daystrailing window

Built-in field keys: title, description, status, assignee, author, type, dueDate, completedDate, created, updated, idInGroup (sub-fields assignee.email, author.email). Custom fields are referenced by their field id (from get_group_config); dropdown/reference values by option/target id — e.g. <fieldId>:<optionId>.

Sortable fields (structured mode only): created, updated, title, status, assignee, dueDate, completedDate, idInGroup. Custom fields cannot be sorted.

status:open AND assignee:<userId> AND dueDate:<2026-05-01
(status:open OR status:pending) AND dueDate:$filled
NOT status:closed AND created:$thismonth
<dropdownFieldId>:<optionId> AND <dateFieldId>:>=2026-01-01

Custom-field value encoding#

When writing customFields (in create_items / update_items), key the map by custom-field id and encode the value by field type:

Field typeValue
Dropdown / listthe option id
ItemREFthe target item id; ItemsREF → array of item ids
UserREFthe user id; UsersREF → array of user ids
DateYYYY-MM-DD string
Number / String / Booleanthe scalar

Resolve the needed ids with get_group_config (dropdown options are inlined) or resolve_field_options. Built-in fields (title, description, status, assignee, dueDate) are set directly, not via customFields.

Ambiguous wall-clock dates resolve against the token user’s timezone (default UTC).

Batch result shape & error handling#

create_items / update_items never fail the whole batch on one bad item — they return a per-item result so the agent can self-correct:

{ "results": [
  { "index": 0, "ok": true,  "id": "0199…", "idInGroup": 42 },
  { "index": 1, "ok": false, "error": "unknown item type \"tsak\"",
    "fields": [ { "fieldId": "…", "kind": "required-field-missing", "message": "…" } ] }
] }

Check each ok/error; on validation failure the fields array names exactly which field failed and why.


Typical agent workflow#

“Create a goal and some tasks for John”

  1. list_groups → pick the group (or take it from context).
  2. get_group_config(groupId) → get John’s userId from the roster, the goal/task type ids, and required field ids (dropdown options are inlined).
  3. create_items(groupId, [...]) → batch the goal + tasks in one call.

That’s ~2–3 calls. Linking a task to another item adds one resolve_field_options(..., search: "launch") to fetch the target id. Reading many items’ fields at once? Use query_items with fields instead of looping get_item.


Notes & limits#

  • Rate limiting: /mcp shares the platform’s API rate limiter.
  • Search cap: the underlying relevance search caps its result set; limit further trims the page.
  • Reference resolution during creation: when resolving reference candidates for an item that doesn’t exist yet, dynamic queries that depend on the item’s other (not-yet-set) field values may be incomplete — rely on the search filter in resolve_field_options.
  • Out of scope (today): creating/deleting whole lists, and item-type / group / role mutation. Web search is provided by the agent host, not Timill.