@tasklite/mcp
TaskLite MCP server: build a full backend (projects, boards, data, REST endpoints), deploy a frontend onto it, and get a ready-made admin, from Claude Code and other MCP clients
npm · fingerprint cdb9d76d5da1fa7564552bd4 · repository · RSS
What changed
Changed 2026-10-21 11:55 UTC
2 descriptions rewritten, 4 schema or annotation changes with the description left byte-identical, 17 tools added.
--- pinned/build_backend +++ observed/build_backend { "annotations": { "destructiveHint": false, - "description": "\"owner\" (default): each user sees and edits only the rows they created. \"shared\": every user authorized on the app (on its access list, or an owner, admin or editor of the organization) sees and edits every row; the mode for an admin screen or a team system. Anyone else who signs in is refused with 403, so put the people who run the app on the list with add_app_user (their phone and a role; it works before their first sign-in). \"phone\": a user signed in with a code sent to their phone sees and edits only the rows whose phoneColumn holds that phone, e.g. a directory where each person keeps their own card, or the Coaches board itself in a coaching app (each coach reads and edits his own row; a POST writes his verified phone into the row whatever the body says). \"relation\": each user reaches the rows whose relationColumn points at the row that stands for them, e.g. a Trainees board whose \"Coach\" relation column points at the Coaches board: a coach sees only his trainees. A relation endpoint never returns the row that IS the user, only rows pointing at it, so expose the identity board itself through a second endpoint in mode \"phone\". In relation mode a new row is linked to the caller by the server, the link cannot be moved with PATCH, and a user who stands for no row gets an empty list on GET and 403 on POST", + "description": "\"owner\" (default): each user sees and edits only the rows they created. \"shared\": every user authorized on the app (on its access list, or an owner, admin or editor of the organization) sees and edits every row; the mode for an admin screen or a team system. Anyone else who signs in is refused with 403, so put the people who run the app on the list with add_app_user (their phone and a role; it works before their first sign-in). \"phone\": a user signed in with a code sent to their phone sees and edits only the rows whose phoneColumn holds that phone, e.g. a directory where each person keeps their own card, or the Coaches board itself in a coaching app (each coach reads and edits his own row; a POST writes his verified phone into the row whatever the body says). \"relation\": each user reaches the rows whose relationColumn points at the row that stands for them, e.g. a Trainees board whose \"Coach\" relation column points at the Coaches board: a coach sees only his trainees. A relation endpoint never returns the row that IS the user, only rows pointing at it, so expose the identity board itself through a second endpoint in mode \"phone\". In relation mode a new row is linked to the caller by the server, the link cannot be moved with PATCH, and a user who stands for no row gets an empty list on GET and 403 on POST. \"policy\": the endpoint follows the access policy of its board (set_board_policy): business roles (define_app_role and grant_app_role, or set_policy_identity), row scope per role, per-column read and write rules. It takes no other rowLevelSecurity field; the board needs a policy and the users need roles, so test with explain_access and simulate_request", "enum": [ "owner", "shared", - "relation" + "relation", + "policy" ], "type": "string" },
--- pinned/create_app_api_key +++ observed/create_app_api_key { "annotations": { "destructiveHint": false, - "description": "Permissions recorded on the key: [\"read\"] or [\"read\",\"write\"]. Omit to match the app: write when any endpoint accepts POST, PATCH or DELETE.", + "description": "Permissions on the key: \"read\", \"write\", \"email\". Omit to match the app: write when any endpoint accepts POST, PATCH or DELETE. Sending email from the app server (POST /apps/<slug>/api/_email/send) needs the key to include \"email\" or \"write\"; a read-only key is refused with 403 APP_KEY_SCOPE_REQUIRED.", "items": { "enum": [ "read", - "write" + "write", + "email" ], "type": "string" },
--- pinned/create_app_endpoint +++ observed/create_app_endpoint { "annotations": { "destructiveHint": false, - "description": "\"owner\" (default): each user sees and edits only the rows they created. \"shared\": every user authorized on the app (on its access list, or an owner, admin or editor of the organization) sees and edits every row; the mode for an admin screen or a team system. Anyone else who signs in is refused with 403, so put the people who run the app on the list with add_app_user (their phone and a role; it works before their first sign-in). \"phone\": a user signed in with a code sent to their phone sees and edits only the rows whose phoneColumn holds that phone, e.g. a directory where each person keeps their own card, or the Coaches board itself in a coaching app (each coach reads and edits his own row; a POST writes his verified phone into the row whatever the body says). \"relation\": each user reaches the rows whose relationColumn points at the row that stands for them, e.g. a Trainees board whose \"Coach\" relation column points at the Coaches board: a coach sees only his trainees. A relation endpoint never returns the row that IS the user, only rows pointing at it, so expose the identity board itself through a second endpoint in mode \"phone\". In relation mode a new row is linked to the caller by the server, the link cannot be moved with PATCH, and a user who stands for no row gets an empty list on GET and 403 on POST", + "description": "\"owner\" (default): each user sees and edits only the rows they created. \"shared\": every user authorized on the app (on its access list, or an owner, admin or editor of the organization) sees and edits every row; the mode for an admin screen or a team system. Anyone else who signs in is refused with 403, so put the people who run the app on the list with add_app_user (their phone and a role; it works before their first sign-in). \"phone\": a user signed in with a code sent to their phone sees and edits only the rows whose phoneColumn holds that phone, e.g. a directory where each person keeps their own card, or the Coaches board itself in a coaching app (each coach reads and edits his own row; a POST writes his verified phone into the row whatever the body says). \"relation\": each user reaches the rows whose relationColumn points at the row that stands for them, e.g. a Trainees board whose \"Coach\" relation column points at the Coaches board: a coach sees only his trainees. A relation endpoint never returns the row that IS the user, only rows pointing at it, so expose the identity board itself through a second endpoint in mode \"phone\". In relation mode a new row is linked to the caller by the server, the link cannot be moved with PATCH, and a user who stands for no row gets an empty list on GET and 403 on POST. \"policy\": the endpoint follows the access policy of its board (set_board_policy): business roles (define_app_role and grant_app_role, or set_policy_identity), row scope per role, per-column read and write rules. It takes no other rowLevelSecurity field; the board needs a policy and the users need roles, so test with explain_access and simulate_request", "enum": [ "owner", "shared", - "relation" + "relation", + "policy" ], "type": "string" },
--- pinned/create_automation +++ observed/create_automation { "annotations": { "destructiveHint": false, - "description": "Create an automation on a board: when something happens, do something. The most useful action here is http_request, which calls an external API and writes the answer back into columns, pair it with the \"scheduled\" trigger and the board keeps itself up to date (prices, exchange rates, shipment status, weather). Triggers: item_created, status_changed, column_value_changed, date_approaching, scheduled. Actions: http_request, send_notification, send_email, send_push, change_status, set_column_value, create_cross_board_item, send_webhook. The scheduled trigger has no cron expression: the server checks once an hour, and triggerConfig takes { intervalHours, runAtHour } only (intervalHours: at least this many hours between runs, default 24; runAtHour: 0-23, run only during that hour of the day in UTC, not in the user's time zone, so convert: 08:00 in Israel is runAtHour 5 in summer and 6 in winter). A scheduled run executes the actions once for every row of the board that passes conditions, so filter with conditions. send_push sends a notification to the phones of app users through the app's own Firebase project (set up once per app: push_status; try it with send_test_push). Its config: { title, body, data, userIds, recipientsFromRelation, includeRowOwner, sound, badge, dataOnly }. title and body take {{column name}} placeholders, title defaults to the row title. Recipients are the union of: the row's owner under row-level security (includeRowOwner, default true; false leaves them out), userIds (fixed user ids, as in list_app_users), and recipientsFromRelation, a dotted path of column NAMES such as \"Client.Coach.Coach user\" that walks relation columns from this row and whose LAST segment must be a people column holding user ids (a single segment is a people column on this board); it does not resolve the user linked to a row by an invite or by phone, so without such a people column use userIds or the row owner. data is a flat object of strings the app reads to decide where to open (itemId and boardId are added). sound: \"default\" or the file name of a sound bundled in the app; badge: 0 to 99999, 0 clears; dataOnly: true shows nothing and delivers only data (no title, body, sound or badge; iOS may throttle or drop it). With no recipient the action is skipped, not failed. Two more things every action list can use: a { type: \"delay\", config: { minutes | hours | days } } action pauses the run and resumes the actions after it later (reminders, follow-ups); and any network action (http_request, send_webhook, send_email, send_whatsapp) may carry config.retry: { attempts (1-5), delaySeconds (1-60) }. send_webhook accepts config.secret for an HMAC signature. create_cross_board_item copies a new row to another board: config { targetBoardId, title?: \"{{item.title}}\", columnValues: { \"<column id on the TARGET board>\": \"{{<column NAME on this board>}}\" } }; there is no mapping field, and a column left out is not copied. To act only on some rows pass conditions, e.g. [{ field: \"<column id on this board>\", operator: \"equals\", value: \"New seller\" }]. Change an automation later with update_automation; call list_automations first so you do not add a second one that does the same thing.", + "description": "Create an automation on a board: when something happens, do something. The most useful action here is http_request, which calls an external API and writes the answer back into columns, pair it with the \"scheduled\" trigger and the board keeps itself up to date (prices, exchange rates, shipment status, weather). Triggers: item_created, status_changed, column_value_changed, date_approaching, scheduled. Actions: http_request, send_notification, send_email, send_push, change_status, set_column_value, create_cross_board_item, send_webhook. The scheduled trigger has no cron expression: the server checks once an hour, and triggerConfig takes { intervalHours, runAtHour } only (intervalHours: at least this many hours between runs, default 24; runAtHour: 0-23, run only during that hour of the day in UTC, not in the user's time zone, so convert: 08:00 in Israel is runAtHour 5 in summer and 6 in winter). A scheduled run executes the actions once for every row of the board that passes conditions, so filter with conditions. send_push sends a notification to the phones of app users through the app's own Firebase project (set up once per app: push_status; try it with send_test_push). Its config: { title, body, data, userIds, recipientsFromRelation, includeRowOwner, sound, badge, dataOnly }. title and body take {{column name}} placeholders, title defaults to the row title. Recipients are the union of: the row's owner under row-level security (includeRowOwner, default true; false leaves them out), userIds (fixed user ids, as in list_app_users), and recipientsFromRelation, a dotted path of column NAMES such as \"Client.Coach.Coach user\" that walks relation columns from this row and whose LAST segment must be a people column holding user ids (a single segment is a people column on this board); it does not resolve the user linked to a row by an invite or by phone, so without such a people column use userIds or the row owner. data is a flat object of strings the app reads to decide where to open (itemId and boardId are added). sound: \"default\" or the file name of a sound bundled in the app; badge: 0 to 99999, 0 clears; dataOnly: true shows nothing and delivers only data (no title, body, sound or badge; iOS may throttle or drop it). With no recipient the action is skipped, not failed. Two more things every action list can use: a { type: \"delay\", config: { minutes | hours | days } } action pauses the run and resumes the actions after it later (reminders, follow-ups); and any network action (http_request, send_webhook, send_email, send_whatsapp) may carry config.retry: { attempts (1-5), delaySeconds (1-60) }. send_webhook accepts config.secret for an HMAC signature. send_email sends mail from the organization to the people on a row. Its config: { to, subject, body, columnEmailField, fromName, replyTo, kind, attachments }. kind is \"transactional\" (the default: orders, receipts, notifications) or \"marketing\" (promotional mail: it needs the Pro plan or higher and a verified sender domain, and it gets an unsubscribe footer; people who unsubscribed are skipped). attachments is an array of FILE column ids on the triggering row (or {{column.<id>}}): the stored files in those cells are attached, up to 5MB in total, and a missing file is skipped instead of failing the action. Email is counted per month by plan: Free 300, Plus 3000, Pro 20000 (read the numbers with get_email_usage); a message held back by the quota is recorded as skipped in the communications log and can be resent from there. To send email from an app's own server instead of an automation, POST /apps/<slug>/api/_email/send with the app server key (X-API-Key, never a user token; the key needs the \"email\" or \"write\" scope, see create_app_api_key): { to: [up to 50 addresses], subject, html or text, replyTo, kind, attachmentIds: [stored file ids] }; each app may send 500 a day (429 APP_EMAIL_DAILY_LIMIT). create_cross_board_item copies a new row to another board: config { targetBoardId, title?: \"{{item.title}}\", columnValues: { \"<column id on the TARGET board>\": \"{{<column NAME on this board>}}\" } }; there is no mapping field, and a column left out is not copied. To act only on some rows pass conditions, e.g. [{ field: \"<column id on this board>\", operator: \"equals\", value: \"New seller\" }]. Change an automation later with update_automation; call list_automations first so you do not add a second one that does the same thing.", "execution": { "taskSupport": "forbidden" }, - "description": "Action-specific config, e.g. { url, method, headers, responseMapping } for http_request, or { title, body, recipientsFromRelation, includeRowOwner, userIds, data, sound, badge, dataOnly } for send_push", + "description": "Action-specific config, e.g. { url, method, headers, responseMapping } for http_request, { title, body, recipientsFromRelation, includeRowOwner, userIds, data, sound, badge, dataOnly } for send_push, or { to, subject, body, kind: \"transactional\" | \"marketing\", attachments: [\"<file column id>\"] } for send_email", "type": "object" }, "type": {
+ tool added: define_app_role
+ tool added: delete_app_role
+ tool added: delete_board_policy
+ tool added: explain_access
+ tool added: get_board_policy
+ tool added: get_cells
+ tool added: get_email_usage
+ tool added: get_item_history
+ tool added: get_policy_identity
+ tool added: grant_app_role
+ tool added: list_app_role_grants
+ tool added: list_app_roles
+ tool added: revoke_app_role
+ tool added: set_board_policy
+ tool added: set_cells_if_unchanged
+ tool added: set_policy_identity
+ tool added: simulate_request
--- pinned/update_app_endpoint +++ observed/update_app_endpoint { "annotations": { "destructiveHint": false, - "description": "\"owner\" (default): each user sees and edits only the rows they created. \"shared\": every user authorized on the app (on its access list, or an owner, admin or editor of the organization) sees and edits every row; the mode for an admin screen or a team system. Anyone else who signs in is refused with 403, so put the people who run the app on the list with add_app_user (their phone and a role; it works before their first sign-in). \"phone\": a user signed in with a code sent to their phone sees and edits only the rows whose phoneColumn holds that phone, e.g. a directory where each person keeps their own card, or the Coaches board itself in a coaching app (each coach reads and edits his own row; a POST writes his verified phone into the row whatever the body says). \"relation\": each user reaches the rows whose relationColumn points at the row that stands for them, e.g. a Trainees board whose \"Coach\" relation column points at the Coaches board: a coach sees only his trainees. A relation endpoint never returns the row that IS the user, only rows pointing at it, so expose the identity board itself through a second endpoint in mode \"phone\". In relation mode a new row is linked to the caller by the server, the link cannot be moved with PATCH, and a user who stands for no row gets an empty list on GET and 403 on POST", + "description": "\"owner\" (default): each user sees and edits only the rows they created. \"shared\": every user authorized on the app (on its access list, or an owner, admin or editor of the organization) sees and edits every row; the mode for an admin screen or a team system. Anyone else who signs in is refused with 403, so put the people who run the app on the list with add_app_user (their phone and a role; it works before their first sign-in). \"phone\": a user signed in with a code sent to their phone sees and edits only the rows whose phoneColumn holds that phone, e.g. a directory where each person keeps their own card, or the Coaches board itself in a coaching app (each coach reads and edits his own row; a POST writes his verified phone into the row whatever the body says). \"relation\": each user reaches the rows whose relationColumn points at the row that stands for them, e.g. a Trainees board whose \"Coach\" relation column points at the Coaches board: a coach sees only his trainees. A relation endpoint never returns the row that IS the user, only rows pointing at it, so expose the identity board itself through a second endpoint in mode \"phone\". In relation mode a new row is linked to the caller by the server, the link cannot be moved with PATCH, and a user who stands for no row gets an empty list on GET and 403 on POST. \"policy\": the endpoint follows the access policy of its board (set_board_policy): business roles (define_app_role and grant_app_role, or set_policy_identity), row scope per role, per-column read and write rules. It takes no other rowLevelSecurity field; the board needs a policy and the users need roles, so test with explain_access and simulate_request", "enum": [ "owner", "shared", - "relation" + "relation", + "policy" ], "type": "string" },
--- pinned/update_automation +++ observed/update_automation { "annotations": { "destructiveHint": false, - "description": "Change an existing automation: its actions, conditions, trigger config or name, or switch it off with isActive false. Fields left out stay as they are; actions and conditions, when given, replace the whole list. Use it to fix an automation instead of creating a second one next to it. Get the id from list_automations.", + "description": "Change an existing automation: its actions, conditions, trigger config or name, or switch it off with isActive false. Fields left out stay as they are; actions and conditions, when given, replace the whole list. Use it to fix an automation instead of creating a second one next to it. Get the id from list_automations. For send_email actions, config.kind is transactional (default) or marketing (Pro plan or higher, verified sender domain, unsubscribe footer) and config.attachments is an array of file column ids on the row; see create_automation.", "execution": { "taskSupport": "forbidden" },
Changed 2026-10-15 11:34 UTC
3 descriptions rewritten, 5 schema or annotation changes with the description left byte-identical, 3 tools added.
+ tool added: add_app_user
--- pinned/build_backend +++ observed/build_backend { "annotations": { "destructiveHint": false, - "description": "\"owner\" (default): each user sees and edits only the rows they created. \"shared\": every user authorized on the app (listed on it, or an owner/admin of the organization) sees and edits every row; the mode for an admin screen or a team system. \"phone\": a user signed in with a code sent to their phone sees and edits only the rows whose phoneColumn holds that phone, e.g. a directory where each person keeps their own card, or the Coaches board itself in a coaching app (each coach reads and edits his own row; a POST writes his verified phone into the row whatever the body says). \"relation\": each user reaches the rows whose relationColumn points at the row that stands for them, e.g. a Trainees board whose \"Coach\" relation column points at the Coaches board: a coach sees only his trainees. A relation endpoint never returns the row that IS the user, only rows pointing at it, so expose the identity board itself through a second endpoint in mode \"phone\". In relation mode a new row is linked to the caller by the server, the link cannot be moved with PATCH, and a user who stands for no row gets an empty list on GET and 403 on POST", + "description": "\"owner\" (default): each user sees and edits only the rows they created. \"shared\": every user authorized on the app (on its access list, or an owner, admin or editor of the organization) sees and edits every row; the mode for an admin screen or a team system. Anyone else who signs in is refused with 403, so put the people who run the app on the list with add_app_user (their phone and a role; it works before their first sign-in). \"phone\": a user signed in with a code sent to their phone sees and edits only the rows whose phoneColumn holds that phone, e.g. a directory where each person keeps their own card, or the Coaches board itself in a coaching app (each coach reads and edits his own row; a POST writes his verified phone into the row whatever the body says). \"relation\": each user reaches the rows whose relationColumn points at the row that stands for them, e.g. a Trainees board whose \"Coach\" relation column points at the Coaches board: a coach sees only his trainees. A relation endpoint never returns the row that IS the user, only rows pointing at it, so expose the identity board itself through a second endpoint in mode \"phone\". In relation mode a new row is linked to the caller by the server, the link cannot be moved with PATCH, and a user who stands for no row gets an empty list on GET and 403 on POST", "enum": [ "owner", "shared",
--- pinned/configure_external_access +++ observed/configure_external_access { "annotations": { "destructiveHint": false, - "description": "Read or change how EXTERNAL users (people who sign up to your app through TaskLite auth) get into an organization. They have two ways in, and both obey the policy below: email and password (POST /auth/register-external with this organizationId, then POST /auth/login), or Google (POST /auth/google-external with a Google ID token and this organizationId). Either way the answer carries a token the app sends as Authorization: Bearer on every App API call. registrationPolicy: \"open\", in at once; \"approval\", an organization admin approves each signup (TaskLite mails the admins on every signup, and the person once approved; unapproved users are never billed); \"invite\", a new person gets in only with an invite code (create_app_invite, or a code a user of the app minted on a relation endpoint with allowInvites): the app sends it as inviteCode with POST /auth/otp/verify, /auth/register-external or /auth/google-external, a sign-up without one is refused with 403 INVITE_REQUIRED and a bad code with 403 INVITE_INVALID, while people who are already members keep signing in as before; \"closed\" (the default), self-signup refused, external users are created by an admin. Under every policy a valid invite code admits its holder as if the organization were open, so \"invite\" is the policy for an app where people join only through someone who invited them (a coach inviting trainees). appLoginUrl: the page of YOUR app where these users log in, it becomes the \"Log in\" button in the approval email, so set it whenever you deploy an app that uses this flow; pass \"\" to clear. Call with no changes to just read the current settings. Requires organization admin.", + "description": "Read or change how EXTERNAL users (people who sign up to your app through TaskLite auth) get into an organization. This policy governs sign-up by email and password and by Google. Sign-in by a phone code (POST /auth/otp/send, then /auth/otp/verify) obeys only \"invite\": under \"closed\", \"approval\" and \"open\" any phone that verifies a code becomes an external member of the organization, who reaches nothing until an endpoint's row-level security or the app's access list (add_app_user) gives them something; under \"invite\" a new phone needs an invite code or an add_app_user grant. The two ways in that obey the whole policy: email and password (POST /auth/register-external with this organizationId, then POST /auth/login), or Google (POST /auth/google-external with a Google ID token and this organizationId). Either way the answer carries a token the app sends as Authorization: Bearer on every App API call. registrationPolicy: \"open\", in at once; \"approval\", an organization admin approves each signup (TaskLite mails the admins on every signup, and the person once approved; unapproved users are never billed); \"invite\", a new person gets in only with an invite code (create_app_invite, or a code a user of the app minted on a relation endpoint with allowInvites): the app sends it as inviteCode with POST /auth/otp/verify, /auth/register-external or /auth/google-external, a sign-up without one is refused with 403 INVITE_REQUIRED and a bad code with 403 INVITE_INVALID, while people who are already members keep signing in as before; \"closed\" (the default), self-signup refused, external users are created by an admin. Under every policy a valid invite code admits its holder as if the organization were open, so \"invite\" is the policy for an app where people join only through someone who invited them (a coach inviting trainees). appLoginUrl: the page of YOUR app where these users log in, it becomes the \"Log in\" button in the approval email, so set it whenever you deploy an app that uses this flow; pass \"\" to clear. Call with no changes to just read the current settings. Requires organization admin.", "execution": { "taskSupport": "forbidden" },
--- pinned/create_app_endpoint +++ observed/create_app_endpoint { "annotations": { "destructiveHint": false, - "description": "\"owner\" (default): each user sees and edits only the rows they created. \"shared\": every user authorized on the app (listed on it, or an owner/admin of the organization) sees and edits every row; the mode for an admin screen or a team system. \"phone\": a user signed in with a code sent to their phone sees and edits only the rows whose phoneColumn holds that phone, e.g. a directory where each person keeps their own card, or the Coaches board itself in a coaching app (each coach reads and edits his own row; a POST writes his verified phone into the row whatever the body says). \"relation\": each user reaches the rows whose relationColumn points at the row that stands for them, e.g. a Trainees board whose \"Coach\" relation column points at the Coaches board: a coach sees only his trainees. A relation endpoint never returns the row that IS the user, only rows pointing at it, so expose the identity board itself through a second endpoint in mode \"phone\". In relation mode a new row is linked to the caller by the server, the link cannot be moved with PATCH, and a user who stands for no row gets an empty list on GET and 403 on POST", + "description": "\"owner\" (default): each user sees and edits only the rows they created. \"shared\": every user authorized on the app (on its access list, or an owner, admin or editor of the organization) sees and edits every row; the mode for an admin screen or a team system. Anyone else who signs in is refused with 403, so put the people who run the app on the list with add_app_user (their phone and a role; it works before their first sign-in). \"phone\": a user signed in with a code sent to their phone sees and edits only the rows whose phoneColumn holds that phone, e.g. a directory where each person keeps their own card, or the Coaches board itself in a coaching app (each coach reads and edits his own row; a POST writes his verified phone into the row whatever the body says). \"relation\": each user reaches the rows whose relationColumn points at the row that stands for them, e.g. a Trainees board whose \"Coach\" relation column points at the Coaches board: a coach sees only his trainees. A relation endpoint never returns the row that IS the user, only rows pointing at it, so expose the identity board itself through a second endpoint in mode \"phone\". In relation mode a new row is linked to the caller by the server, the link cannot be moved with PATCH, and a user who stands for no row gets an empty list on GET and 403 on POST", "enum": [ "owner", "shared",
--- pinned/create_item +++ observed/create_item { "annotations": { "destructiveHint": false, - "description": "Status value (todo, in_progress, done, or a value from the board's status options)", + "description": "Task status of the row on a task board: todo, in_progress, review or done. Refused on a data board, where it would only write a hidden task field: set that board's status column with cells (create_item) or set_cell (update_item) instead", "type": "string" }, "tags": {
--- pinned/list_app_users +++ observed/list_app_users { "annotations": { "destructiveHint": false, - "description": "List the end users of an app: everyone who ever called it with X-App-User or signed in to it with a code. Each has userId (the id link_app_user, list_app_user_links and send_test_push take), externalId (the developer's own id for them, or null for a user who signed in by code), name, source (api or otp), role (viewer, editor or submitter; editor when never set), isActive, lastSeenAt and itemCount (rows they own on the app's boards). Use it to find the userId of the person to link to a row.", + "description": "List the people of an app, as { users, pending }. users: everyone who ever called it with X-App-User or signed in to it with a code. Each has userId (the id set_app_user_role, remove_app_user, link_app_user, list_app_user_links and send_test_push take), externalId (the developer's own id for them, or null for a user who signed in by code), name, phone (the phone they proved with a code, or null), source (api or otp), role (viewer, editor or submitter), isActive, hasAccessRow, lastSeenAt and itemCount (rows they own on the app's boards). hasAccessRow says whether they are on the app's access list: when it is false, role and isActive are only defaults, and an endpoint in row-level security mode \"shared\" refuses them with 403 until add_app_user or set_app_user_role puts them on the list. pending: the phones add_app_user is still holding access for, each with id, phone, role, createdAt, expiresAt and status (pending, or expired); they get in on their first sign-in with a code. Use it to see who can use the app and to find the userId of a person.", "execution": { "taskSupport": "forbidden" },
--- pinned/query_items +++ observed/query_items { "annotations": { "destructiveHint": false, - "description": "Only rows whose status is NOT one of these, comma separated, resolved like status. The way to ask for everything that is not done: excludeStatus \"done\" (task board) or \"Completed\" (a data board with that option). Rows with no status at all are kept", + "description": "Only rows whose status is NOT one of these, comma separated, resolved like status. The way to ask for everything that is not done: excludeStatus \"done\" (task board) or \"Completed\" (a data board with that option). Rows with no status at all are kept. A board without a status column is handled as for status: an error, or a filter here on a dropdown named status/סטטוס/מצב", "type": "string" }, "limit": { - "description": "Only rows whose status is one of these, comma separated. Matched against the board's status column by option value or label, case-insensitive: \"todo,in_progress\" on a task board, \"Received,In Repair\" on a data board whose status column lists those (get_board_schema shows the options)", + "description": "Only rows whose status is one of these, comma separated. Matched against the board's status column by option value or label, case-insensitive: \"todo,in_progress\" on a task board, \"Received,In Repair\" on a data board whose status column lists those (get_board_schema shows the options). Needs a status column. A board with none (no column of type status) gets an error that lists its dropdown columns, instead of unfiltered rows. A board whose status is a dropdown named status/סטטוס/מצב is filtered here on that dropdown, after fetching, so an explicit limit can give a page with fewer rows (the answer says so)", "type": "string" } },
+ tool added: remove_app_user
+ tool added: set_app_user_role
--- pinned/update_app_endpoint +++ observed/update_app_endpoint { "annotations": { "destructiveHint": false, - "description": "\"owner\" (default): each user sees and edits only the rows they created. \"shared\": every user authorized on the app (listed on it, or an owner/admin of the organization) sees and edits every row; the mode for an admin screen or a team system. \"phone\": a user signed in with a code sent to their phone sees and edits only the rows whose phoneColumn holds that phone, e.g. a directory where each person keeps their own card, or the Coaches board itself in a coaching app (each coach reads and edits his own row; a POST writes his verified phone into the row whatever the body says). \"relation\": each user reaches the rows whose relationColumn points at the row that stands for them, e.g. a Trainees board whose \"Coach\" relation column points at the Coaches board: a coach sees only his trainees. A relation endpoint never returns the row that IS the user, only rows pointing at it, so expose the identity board itself through a second endpoint in mode \"phone\". In relation mode a new row is linked to the caller by the server, the link cannot be moved with PATCH, and a user who stands for no row gets an empty list on GET and 403 on POST", + "description": "\"owner\" (default): each user sees and edits only the rows they created. \"shared\": every user authorized on the app (on its access list, or an owner, admin or editor of the organization) sees and edits every row; the mode for an admin screen or a team system. Anyone else who signs in is refused with 403, so put the people who run the app on the list with add_app_user (their phone and a role; it works before their first sign-in). \"phone\": a user signed in with a code sent to their phone sees and edits only the rows whose phoneColumn holds that phone, e.g. a directory where each person keeps their own card, or the Coaches board itself in a coaching app (each coach reads and edits his own row; a POST writes his verified phone into the row whatever the body says). \"relation\": each user reaches the rows whose relationColumn points at the row that stands for them, e.g. a Trainees board whose \"Coach\" relation column points at the Coaches board: a coach sees only his trainees. A relation endpoint never returns the row that IS the user, only rows pointing at it, so expose the identity board itself through a second endpoint in mode \"phone\". In relation mode a new row is linked to the caller by the server, the link cannot be moved with PATCH, and a user who stands for no row gets an empty list on GET and 403 on POST", "enum": [ "owner", "shared",
--- pinned/update_item +++ observed/update_item { "annotations": { "destructiveHint": false, - "description": "Update item fields (title, description, status, priority, dueDate, tags).", + "description": "Update item fields (title, description, status, priority, dueDate, tags). On a data board the task fields (status, priority, dueDate, tags) are hidden: its columns, status included, are set with set_cell.", "execution": { "taskSupport": "forbidden" }, - "description": "Status value (todo, in_progress, done, or a value from the board's status options)", + "description": "Task status of the row on a task board: todo, in_progress, review or done. Refused on a data board, where it would only write a hidden task field: set that board's status column with cells (create_item) or set_cell (update_item) instead", "type": "string" }, "tags": {
Changed 2026-10-09 13:23 UTC
4 descriptions rewritten, 2 schema or annotation changes with the description left byte-identical, 10 tools added.
--- pinned/build_backend +++ observed/build_backend { "annotations": { "destructiveHint": false, - "description": "Build a whole backend in one call from a spec you compose: the project, its boards, their typed columns (including relations between the boards), optional sample rows, and optionally a published REST API with one endpoint per board and a server-side key. Use it whenever the user describes a system (\"a backend for my repair shop: customers, orders, payments\") instead of calling create_project, create_board, create_column, create_app, publish_app, create_app_endpoint and create_app_api_key one by one. You do the design, pick column types by meaning (phone, date, currency, dropdown/status with options for closed choices), link boards with a relation column (type \"relation\", relatedBoard: \"<board name in this spec>\", relationType: many_to_one for an order→customer link), and this tool executes it and returns one compact summary. API field names are derived from column names and never collide with reserved item fields, so there is nothing to retry. Boards are created as plain data tables (kind \"data\": only the columns you define, no task fields); set kind \"tasks\" on a board where people track work to do and want status, priority, assignee and due date built in. Every row still has a title.", + "description": "Build a whole backend in one call from a spec you compose: the project, its boards, their typed columns (including relations between the boards), optional sample rows, and optionally a published REST API with one endpoint per board and a server-side key. Use it whenever the user describes a system (\"a backend for my repair shop: customers, orders, payments\") instead of calling create_project, create_board, create_column, create_app, publish_app, create_app_endpoint and create_app_api_key one by one. You do the design, pick column types by meaning (phone, date, currency, dropdown/status with options for closed choices), link boards with a relation column (type \"relation\", relatedBoard: \"<board name in this spec>\", relationType: many_to_one for an order→customer link), and this tool executes it and returns one compact summary. API field names are derived from column names and never collide with reserved item fields, so there is nothing to retry. Boards are created as plain data tables (kind \"data\": only the columns you define, no task fields); set kind \"tasks\" on a board where people track work to do and want status, priority, assignee and due date built in. Every row still has a title. When the API has its own users, api.rowLevelSecurity: true gives every endpoint mode \"owner\"; a board may carry its own rowLevelSecurity instead (mode shared, phone or relation, with columns named as in this spec), e.g. Coaches { mode: \"phone\", phoneColumn: \"Phone\" } and Trainees { mode: \"relation\", relationColumn: \"Coach\", identityPhoneColumn: \"Phone\", allowInvites: true }.", "execution": { "taskSupport": "forbidden" }, - "description": "true when the app has its own users and each may see only their rows (the caller then sends X-App-User)", + "description": "true when the app has its own users and each may see only their rows (the caller then sends X-App-User): every endpoint gets mode \"owner\", except a board that carries its own rowLevelSecurity. For shared, phone or relation modes set rowLevelSecurity on the board itself", "type": "boolean" } }, + "rowLevelSecurity": { + "additionalProperties": false, + "description": "Row-level security of this board's endpoint, used only when \"api\" is given; overrides api.rowLevelSecurity for this board. Columns are given by NAME as written in this spec (the ids do not exist yet). Only the keys listed here exist; an unknown key is refused, not ignored", + "properties": { + "allowInvites": { + "description": "mode \"relation\" only, default false: true lets a user of the app mint an invite code for a row they reach through this endpoint (POST /apps/{appSlug}/api/{endpointSlug}/{itemId}/invites with body { maxUses?, expiresInDays?, label? }; the endpoint must also allow POST). A coach invites a trainee to the trainee's own row; whoever signs up with the code is linked to that row. Such an invite never grants an access role. Without it only an owner or admin creates invites (create_app_invite)", + "type": "boolean" + }, + "editOwnOnly": { + "description": "mode \"relation\" only, default false: true lets everyone the relation reaches READ a row, while only the user who created it may PATCH or DELETE it, or change its files (others get 403 with code ROW_NOT_YOURS). For rows two people both see but only one of them owns, e.g. a trainee's own log that her coach reads", + "type": "boolean" + }, + "emailColumn": { + "description": "mode \"phone\" only, optional, next to phoneColumn: a column that holds each row's email. A user whose email was proved by a login code also reaches the rows that carry that address; for people whose number takes no SMS (outside Israel the code goes by email, which proves the email, not the phone). The NAME of a column of this board in the spec", + "type": "string" + }, + "enabled": { + "description": "Defaults to true; false leaves this endpoint without row-level security", + "type": "boolean" + }, + "identityEmailColumn": { + "description": "mode \"relation\" only, optional: like identityPhoneColumn, for an email column and a user whose email was verified by a login code. The NAME of a column of that board in the spec", + "type": "string" + }, + "identityPhoneColumn": { + "description": "mode \"relation\" only, optional: a phone column on the board where the caller's own items live (the board relationColumn points to, or with viaColumn the board viaColumn points to). A user whose VERIFIED phone (sign-in by a code sent to that phone) is written in a row there stands for that row, with no invite and no link. Example: Coaches.\"Phone\", so a coach who signed in by phone is his own Coaches row at once. Whoever may write that column decides who the row is, so expose it readOnly wherever it must not move. The NAME of a column of that board in the spec", + "type": "string" + }, + "mode": { + "description": "\"owner\" (default): each user sees and edits only the rows they created. \"shared\": every user authorized on the app (listed on it, or an owner/admin of the organization) sees and edits every row; the mode for an admin screen or a team system. \"phone\": a user signed in with a code sent to their phone sees and edits only the rows whose phoneColumn holds that phone, e.g. a directory where each person keeps their own card, or the Coaches board itself in a coaching app (each coach reads and edits his own row; a POST writes his verified phone into the row whatever the body says). \"relation\": each user reaches the rows whose relationColumn points at the row that stands for them, e.g. a Trainees board whose \"Coach\" relation column points at the Coaches board: a coach sees only his trainees. A relation endpoint never returns the row that IS the user, only rows pointing at it, so expose the identity board itself through a second endpoint in mode \"phone\". In relation mode a new row is linked to the caller by the server, the link cannot be moved with PATCH, and a user who stands for no row gets an empty list on GET and 403 on POST", + "enum": [ + "owner", + "shared", + "phone", + "relation" + ], + "type": "string" + }, + "phoneColumn": { + "description": "mode \"phone\" only, required there: the column that holds each row's phone number. The NAME of a column of this board in the spec", + "type": "string" + }, + "relationColumn": { + "description": "mode \"relation\" only, required there: a RELATION column on this endpoint's board. A row belongs to the caller when this column points at an item that stands for the caller (or, with viaColumn, at an item that points at one). Example: on the Trainees board, the \"Coach\" column. The NAME of a relation column of this board in the spec", + "type": "string" + }, + "singleLink": { + "description": "mode \"relation\" only, default false: true gives each row of this endpoint's board to ONE linked user. Once someone is linked to a row, another person redeeming an invite code for the same row is refused (the sign-up answers inviteRedeemed: false with inviteError \"INVITE_ROW_TAKEN\"), so a coach cannot become a trainee's row from a second account. Whoever is already linked is not affected, and the app owner can still link by hand (link_app_user). Set it on the endpoint of the board whose rows are invited to, e.g. Trainees", + "type": "boolean" + }, + "viaColumn": { + "description": "mode \"relation\" only, optional: one more hop. A RELATION column on the board that relationColumn points to, leading to the board of the caller's own items. Example: a Weigh-ins endpoint with relationColumn = Weigh-ins.\"Trainee\" and viaColumn = Trainees.\"Coach\" lets a coach reach the weigh-ins of all his trainees. For the trainee herself, make a second endpoint on the same board with relationColumn = \"Trainee\" and no viaColumn. The NAME of a relation column of the board relationColumn points to", + "type": "string" + } + }, + "type": "object" + }, "rows": { "description": "Optional sample rows keyed by column name, e.g. [{ \"Customer\": \"Sam Miller\", \"Phone\": \"052-555-0142\", \"Price\": 80 }]. \"title\" sets the row title; without it the first plain text value is used, and failing that the row is named after its board and position. A row that other boards reference must carry a title (or a text value) so the reference can be resolved. A relation cell takes the title(s) of rows in the related board (any order in the spec; rows are created in dependency order).", "items": {
+ tool added: check_invite_code
+ tool added: configure_app_settings
--- pinned/configure_external_access +++ observed/configure_external_access { "annotations": { "destructiveHint": false, - "description": "Read or change how EXTERNAL users (people who sign up to your app through TaskLite auth) get into an organization. They have two ways in, and both obey the policy below: email and password (POST /auth/register-external with this organizationId, then POST /auth/login), or Google (POST /auth/google-external with a Google ID token and this organizationId). Either way the answer carries a token the app sends as Authorization: Bearer on every App API call. registrationPolicy: \"open\", in at once; \"approval\", an organization admin approves each signup (TaskLite mails the admins on every signup, and the person once approved; unapproved users are never billed); \"closed\", invite only, self-signup refused. appLoginUrl: the page of YOUR app where these users log in, it becomes the \"Log in\" button in the approval email, so set it whenever you deploy an app that uses this flow; pass \"\" to clear. Call with no changes to just read the current settings. Requires organization admin.", + "description": "Read or change how EXTERNAL users (people who sign up to your app through TaskLite auth) get into an organization. They have two ways in, and both obey the policy below: email and password (POST /auth/register-external with this organizationId, then POST /auth/login), or Google (POST /auth/google-external with a Google ID token and this organizationId). Either way the answer carries a token the app sends as Authorization: Bearer on every App API call. registrationPolicy: \"open\", in at once; \"approval\", an organization admin approves each signup (TaskLite mails the admins on every signup, and the person once approved; unapproved users are never billed); \"invite\", a new person gets in only with an invite code (create_app_invite, or a code a user of the app minted on a relation endpoint with allowInvites): the app sends it as inviteCode with POST /auth/otp/verify, /auth/register-external or /auth/google-external, a sign-up without one is refused with 403 INVITE_REQUIRED and a bad code with 403 INVITE_INVALID, while people who are already members keep signing in as before; \"closed\" (the default), self-signup refused, external users are created by an admin. Under every policy a valid invite code admits its holder as if the organization were open, so \"invite\" is the policy for an app where people join only through someone who invited them (a coach inviting trainees). appLoginUrl: the page of YOUR app where these users log in, it becomes the \"Log in\" button in the approval email, so set it whenever you deploy an app that uses this flow; pass \"\" to clear. Call with no changes to just read the current settings. Requires organization admin.", "execution": { "taskSupport": "forbidden" }, - "description": "How external sign-ups are admitted: open, approval or closed", + "description": "How external sign-ups are admitted: open, approval, invite (only with an invite code) or closed", "enum": [ "closed", "approval", - "open" + "open", + "invite" ], "type": "string" }
--- pinned/create_app_endpoint +++ observed/create_app_endpoint { "annotations": { "destructiveHint": false, - "description": "Expose a board as a REST endpoint of an app: /apps/{appSlug}/api/{slug}. exposedColumns limits which columns are readable/writable. rowLevelSecurity.enabled makes the endpoint per-user: the developer's server sends `X-App-User: <their user id>` next to the API key, and the endpoint returns, updates and deletes ONLY that user's rows (401 without the header). Use it whenever the app has its own users.", + "description": "Expose a board as a REST endpoint of an app: /apps/{appSlug}/api/{slug}. exposedColumns limits which columns are readable/writable. rowLevelSecurity.enabled makes the endpoint per-user: the developer's server sends `X-App-User: <their user id>` next to the API key, and the endpoint returns, updates and deletes ONLY that user's rows (401 without the header). Use it whenever the app has its own users. rowLevelSecurity.mode chooses which rows a user reaches: owner (the rows they created), shared (all rows, for authorized users), phone (the rows that carry their verified phone) or relation (the rows linked through a relation column to the row that stands for them). An app with two kinds of people in one organization, coaches and their trainees, is built from these: the Coaches board in mode phone (phoneColumn = its phone column), so each coach reads and edits his own row; Trainees in mode relation with relationColumn = its Coach column and identityPhoneColumn = the Coaches phone column, so a coach who signed in by phone sees only his trainees; boards one hop further (weigh-ins of a trainee) in mode relation with relationColumn = the Trainee column and viaColumn = Trainees.Coach for the coach, plus a second endpoint on the same board without viaColumn for the trainee herself. A trainee becomes her row through an invite code (create_app_invite, or allowInvites so the coach mints it in the app) or link_app_user.", "execution": { "taskSupport": "forbidden" }, - "description": "Row-level security. Off: whoever holds the app key reads everything. On: every request must name a signed-in user, and mode decides which rows they reach", + "description": "Row-level security. Off: whoever holds the app key reads everything. On: every request must name a signed-in user, and mode decides which rows they reach. Only the keys listed here exist; an unknown key is refused, not ignored", "properties": { + "allowInvites": { + "description": "mode \"relation\" only, default false: true lets a user of the app mint an invite code for a row they reach through this endpoint (POST /apps/{appSlug}/api/{endpointSlug}/{itemId}/invites with body { maxUses?, expiresInDays?, label? }; the endpoint must also allow POST). A coach invites a trainee to the trainee's own row; whoever signs up with the code is linked to that row. Such an invite never grants an access role. Without it only an owner or admin creates invites (create_app_invite)", + "type": "boolean" + }, + "editOwnOnly": { + "description": "mode \"relation\" only, default false: true lets everyone the relation reaches READ a row, while only the user who created it may PATCH or DELETE it, or change its files (others get 403 with code ROW_NOT_YOURS). For rows two people both see but only one of them owns, e.g. a trainee's own log that her coach reads", + "type": "boolean" + }, + "emailColumn": { + "description": "mode \"phone\" only, optional, next to phoneColumn: a column that holds each row's email. A user whose email was proved by a login code also reaches the rows that carry that address; for people whose number takes no SMS (outside Israel the code goes by email, which proves the email, not the phone). A column id (get_board_schema)", + "type": "string" + }, "enabled": { "description": "true: every request must carry a signed-in user (the token from sign-in, or the app key with X-App-User)", "type": "boolean" + "identityEmailColumn": { + "description": "mode \"relation\" only, optional: like identityPhoneColumn, for an email column and a user whose email was verified by a login code. A column id (get_board_schema of that board)", + "type": "string" + }, + "identityPhoneColumn": { + "description": "mode \"relation\" only, optional: a phone column on the board where the caller's own items live (the board relationColumn points to, or with viaColumn the board viaColumn points to). A user whose VERIFIED phone (sign-in by a code sent to that phone) is written in a row there stands for that row, with no invite and no link. Example: Coaches.\"Phone\", so a coach who signed in by phone is his own Coaches row at once. Whoever may write that column decides who the row is, so expose it readOnly wherever it must not move. A column id (get_board_schema of that board)", + "type": "string" + }, "mode": { - "description": "\"owner\" (default): each user sees and edits only the rows they created. \"shared\": every user authorized on the app (listed on it, or an owner/admin of the organization) sees and edits every row; the mode for an admin screen or a team system. \"phone\": a user signed in with a code sent to their phone sees and edits only the rows whose phoneColumn holds that phone, e.g. a directory where each person keeps their own card", + "description": "\"owner\" (default): each user sees and edits only the rows they created. \"shared\": every user authorized on the app (listed on it, or an owner/admin of the organization) sees and edits every row; the mode for an admin screen or a team system. \"phone\": a user signed in with a code sent to their phone sees and edits only the rows whose phoneColumn holds that phone, e.g. a directory where each person keeps their own card, or the Coaches board itself in a coaching app (each coach reads and edits his own row; a POST writes his verified phone into the row whatever the body says). \"relation\": each user reaches the rows whose relationColumn points at the row that stands for them, e.g. a Trainees board whose \"Coach\" relation column points at the Coaches board: a coach sees only his trainees. A relation endpoint never returns the row that IS the user, only rows pointing at it, so expose the identity board itself through a second endpoint in mode \"phone\". In relation mode a new row is linked to the caller by the server, the link cannot be moved with PATCH, and a user who stands for no row gets an empty list on GET and 403 on POST", "enum": [ "owner", "shared", - "phone" + "phone", + "relation" ], "type": "string" }, - "description": "mode \"phone\" only: id of the column that holds each row's phone number (get_board_schema)", + "description": "mode \"phone\" only, required there: the column that holds each row's phone number. A column id (get_board_schema)", "type": "string" + }, + "relationColumn": { + "description": "mode \"relation\" only, required there: a RELATION column on this endpoint's board. A row belongs to the caller when this column points at an item that stands for the caller (or, with viaColumn, at an item that points at one). Example: on the Trainees board, the \"Coach\" column. A column id (get_board_schema)", + "type": "string" + }, + "singleLink": { + "description": "mode \"relation\" only, default false: true gives each row of this endpoint's board to ONE linked user. Once someone is linked to a row, another person redeeming an invite code for the same row is refused (the sign-up answers inviteRedeemed: false with inviteError \"INVITE_ROW_TAKEN\"), so a coach cannot become a trainee's row from a second account. Whoever is already linked is not affected, and the app owner can still link by hand (link_app_user). Set it on the endpoint of the board whose rows are invited to, e.g. Trainees", + "type": "boolean" + }, + "viaColumn": { + "description": "mode \"relation\" only, optional: one more hop. A RELATION column on the board that relationColumn points to, leading to the board of the caller's own items. Example: a Weigh-ins endpoint with relationColumn = Weigh-ins.\"Trainee\" and viaColumn = Trainees.\"Coach\" lets a coach reach the weigh-ins of all his trainees. For the trainee herself, make a second endpoint on the same board with relationColumn = \"Trainee\" and no viaColumn. A column id (get_board_schema of that other board)", + "type": "string" } }, "required": [
+ tool added: create_app_invite
--- pinned/create_automation +++ observed/create_automation { "annotations": { "destructiveHint": false, - "description": "Create an automation on a board: when something happens, do something. The most useful action here is http_request, which calls an external API and writes the answer back into columns, pair it with the \"scheduled\" trigger and the board keeps itself up to date (prices, exchange rates, shipment status, weather). Triggers: item_created, status_changed, column_value_changed, date_approaching, scheduled. Actions: http_request, send_notification, send_email, change_status, set_column_value, create_cross_board_item, send_webhook. Two more things every action list can use: a { type: \"delay\", config: { minutes | hours | days } } action pauses the run and resumes the actions after it later (reminders, follow-ups); and any network action (http_request, send_webhook, send_email, send_whatsapp) may carry config.retry: { attempts (1-5), delaySeconds (1-60) }. send_webhook accepts config.secret for an HMAC signature. create_cross_board_item copies a new row to another board: config { targetBoardId, title?: \"{{item.title}}\", columnValues: { \"<column id on the TARGET board>\": \"{{<column NAME on this board>}}\" } }; there is no mapping field, and a column left out is not copied. To act only on some rows pass conditions, e.g. [{ field: \"<column id on this board>\", operator: \"equals\", value: \"New seller\" }]. Change an automation later with update_automation; call list_automations first so you do not add a second one that does the same thing.", + "description": "Create an automation on a board: when something happens, do something. The most useful action here is http_request, which calls an external API and writes the answer back into columns, pair it with the \"scheduled\" trigger and the board keeps itself up to date (prices, exchange rates, shipment status, weather). Triggers: item_created, status_changed, column_value_changed, date_approaching, scheduled. Actions: http_request, send_notification, send_email, send_push, change_status, set_column_value, create_cross_board_item, send_webhook. The scheduled trigger has no cron expression: the server checks once an hour, and triggerConfig takes { intervalHours, runAtHour } only (intervalHours: at least this many hours between runs, default 24; runAtHour: 0-23, run only during that hour of the day in UTC, not in the user's time zone, so convert: 08:00 in Israel is runAtHour 5 in summer and 6 in winter). A scheduled run executes the actions once for every row of the board that passes conditions, so filter with conditions. send_push sends a notification to the phones of app users through the app's own Firebase project (set up once per app: push_status; try it with send_test_push). Its config: { title, body, data, userIds, recipientsFromRelation, includeRowOwner, sound, badge, dataOnly }. title and body take {{column name}} placeholders, title defaults to the row title. Recipients are the union of: the row's owner under row-level security (includeRowOwner, default true; false leaves them out), userIds (fixed user ids, as in list_app_users), and recipientsFromRelation, a dotted path of column NAMES such as \"Client.Coach.Coach user\" that walks relation columns from this row and whose LAST segment must be a people column holding user ids (a single segment is a people column on this board); it does not resolve the user linked to a row by an invite or by phone, so without such a people column use userIds or the row owner. data is a flat object of strings the app reads to decide where to open (itemId and boardId are added). sound: \"default\" or the file name of a sound bundled in the app; badge: 0 to 99999, 0 clears; dataOnly: true shows nothing and delivers only data (no title, body, sound or badge; iOS may throttle or drop it). With no recipient the action is skipped, not failed. Two more things every action list can use: a { type: \"delay\", config: { minutes | hours | days } } action pauses the run and resumes the actions after it later (reminders, follow-ups); and any network action (http_request, send_webhook, send_email, send_whatsapp) may carry config.retry: { attempts (1-5), delaySeconds (1-60) }. send_webhook accepts config.secret for an HMAC signature. create_cross_board_item copies a new row to another board: config { targetBoardId, title?: \"{{item.title}}\", columnValues: { \"<column id on the TARGET board>\": \"{{<column NAME on this board>}}\" } }; there is no mapping field, and a column left out is not copied. To act only on some rows pass conditions, e.g. [{ field: \"<column id on this board>\", operator: \"equals\", value: \"New seller\" }]. Change an automation later with update_automation; call list_automations first so you do not add a second one that does the same thing.", "execution": { "taskSupport": "forbidden" }, - "description": "Action-specific config, e.g. { url, method, headers, responseMapping } for http_request", + "description": "Action-specific config, e.g. { url, method, headers, responseMapping } for http_request, or { title, body, recipientsFromRelation, includeRowOwner, userIds, data, sound, badge, dataOnly } for send_push", "type": "object" }, "type": { - "description": "Column type: text, rich_text, number, status, date, datetime, duration, people, checkbox, dropdown, label, priority, link, email, phone, relation, lookup, rollup, rating, currency, file", + "description": "Action type: http_request, send_notification, send_email, send_push, change_status, set_column_value, create_cross_board_item, send_webhook, send_whatsapp, delay", "type": "string" } }, - "description": "Event that starts the automation: item_created, status_changed, column_value_changed, date_approaching, or scheduled (cron)", + "description": "Event that starts the automation: item_created, status_changed, column_value_changed, date_approaching, or scheduled (checked hourly; an interval in hours and an optional hour of the day in UTC, not a cron expression)", "enum": [ "item_created", "status_changed", - "description": "e.g. { cron: \"0 8 * * *\" } for scheduled, { columnName } for column_value_changed", + "description": "For scheduled: { intervalHours?: number (default 24), runAtHour?: 0-23 in UTC }, e.g. { intervalHours: 1 } for every hour, or { runAtHour: 5, intervalHours: 23 } for once a day at 05:00 UTC. The interval is measured from the previous run, which ends a little after the hour, so with runAtHour use 23, not 24: with 24 the next day's check falls just short and the run slips a day. A cron field is not read. For column_value_changed: { columnName }", "type": "object" } },
+ tool added: delete_app_endpoint
+ tool added: link_app_user
+ tool added: list_app_invites
+ tool added: list_app_user_links
+ tool added: list_app_users
+ tool added: revoke_app_invite
+ tool added: unlink_app_user
--- pinned/update_app_endpoint +++ observed/update_app_endpoint { "annotations": { "destructiveHint": false, + "allowInvites": { + "description": "mode \"relation\" only, default false: true lets a user of the app mint an invite code for a row they reach through this endpoint (POST /apps/{appSlug}/api/{endpointSlug}/{itemId}/invites with body { maxUses?, expiresInDays?, label? }; the endpoint must also allow POST). A coach invites a trainee to the trainee's own row; whoever signs up with the code is linked to that row. Such an invite never grants an access role. Without it only an owner or admin creates invites (create_app_invite)", + "type": "boolean" + }, + "editOwnOnly": { + "description": "mode \"relation\" only, default false: true lets everyone the relation reaches READ a row, while only the user who created it may PATCH or DELETE it, or change its files (others get 403 with code ROW_NOT_YOURS). For rows two people both see but only one of them owns, e.g. a trainee's own log that her coach reads", + "type": "boolean" + }, + "emailColumn": { + "description": "mode \"phone\" only, optional, next to phoneColumn: a column that holds each row's email. A user whose email was proved by a login code also reaches the rows that carry that address; for people whose number takes no SMS (outside Israel the code goes by email, which proves the email, not the phone). A column id (get_board_schema)", + "type": "string" + }, "enabled": { "description": "true: every request must carry a signed-in user (the token from sign-in, or the app key with X-App-User)", "type": "boolean" + "identityEmailColumn": { + "description": "mode \"relation\" only, optional: like identityPhoneColumn, for an email column and a user whose email was verified by a login code. A column id (get_board_schema of that board)", + "type": "string" + }, + "identityPhoneColumn": { + "description": "mode \"relation\" only, optional: a phone column on the board where the caller's own items live (the board relationColumn points to, or with viaColumn the board viaColumn points to). A user whose VERIFIED phone (sign-in by a code sent to that phone) is written in a row there stands for that row, with no invite and no link. Example: Coaches.\"Phone\", so a coach who signed in by phone is his own Coaches row at once. Whoever may write that column decides who the row is, so expose it readOnly wherever it must not move. A column id (get_board_schema of that board)", + "type": "string" + }, "mode": { - "description": "\"owner\" (default): each user sees and edits only the rows they created. \"shared\": every user authorized on the app (listed on it, or an owner/admin of the organization) sees and edits every row; the mode for an admin screen or a team system. \"phone\": a user signed in with a code sent to their phone sees and edits only the rows whose phoneColumn holds that phone, e.g. a directory where each person keeps their own card", + "description": "\"owner\" (default): each user sees and edits only the rows they created. \"shared\": every user authorized on the app (listed on it, or an owner/admin of the organization) sees and edits every row; the mode for an admin screen or a team system. \"phone\": a user signed in with a code sent to their phone sees and edits only the rows whose phoneColumn holds that phone, e.g. a directory where each person keeps their own card, or the Coaches board itself in a coaching app (each coach reads and edits his own row; a POST writes his verified phone into the row whatever the body says). \"relation\": each user reaches the rows whose relationColumn points at the row that stands for them, e.g. a Trainees board whose \"Coach\" relation column points at the Coaches board: a coach sees only his trainees. A relation endpoint never returns the row that IS the user, only rows pointing at it, so expose the identity board itself through a second endpoint in mode \"phone\". In relation mode a new row is linked to the caller by the server, the link cannot be moved with PATCH, and a user who stands for no row gets an empty list on GET and 403 on POST", "enum": [ "owner", "shared", - "phone" + "phone", + "relation" ], "type": "string" }, - "description": "mode \"phone\" only: id of the column that holds each row's phone number (get_board_schema)", + "description": "mode \"phone\" only, required there: the column that holds each row's phone number. A column id (get_board_schema)", "type": "string" + }, + "relationColumn": { + "description": "mode \"relation\" only, required there: a RELATION column on this endpoint's board. A row belongs to the caller when this column points at an item that stands for the caller (or, with viaColumn, at an item that points at one). Example: on the Trainees board, the \"Coach\" column. A column id (get_board_schema)", + "type": "string" + }, + "singleLink": { + "description": "mode \"relation\" only, default false: true gives each row of this endpoint's board to ONE linked user. Once someone is linked to a row, another person redeeming an invite code for the same row is refused (the sign-up answers inviteRedeemed: false with inviteError \"INVITE_ROW_TAKEN\"), so a coach cannot become a trainee's row from a second account. Whoever is already linked is not affected, and the app owner can still link by hand (link_app_user). Set it on the endpoint of the board whose rows are invited to, e.g. Trainees", + "type": "boolean" + }, + "viaColumn": { + "description": "mode \"relation\" only, optional: one more hop. A RELATION column on the board that relationColumn points to, leading to the board of the caller's own items. Example: a Weigh-ins endpoint with relationColumn = Weigh-ins.\"Trainee\" and viaColumn = Trainees.\"Coach\" lets a coach reach the weigh-ins of all his trainees. For the trainee herself, make a second endpoint on the same board with relationColumn = \"Trainee\" and no viaColumn. A column id (get_board_schema of that other board)", + "type": "string" } }, "required": [ - "description": "Replacement row-level security (same shape as create_app_endpoint); null turns it off" + "description": "Replacement row-level security (same shape as create_app_endpoint); null turns it off. The object replaces the stored one whole, it is not merged: to change one field, read the current value with list_app_endpoints and send every field again (enabled, mode and the columns of that mode), or the ones left out are lost" }, "slug": { "description": "URL slug: lowercase letters, digits and dashes",
--- pinned/update_automation +++ observed/update_automation { "annotations": { "destructiveHint": false, - "description": "New trigger config", + "description": "New trigger config, same shape as in create_automation (scheduled: { intervalHours, runAtHour in UTC }, no cron)", "type": "object" } },
Current tools
Show all 83 tool fingerprints
Watch this server yourself
If you run this server, put the proxy in front of it. It pins these exact fingerprints on first connect and stops the session if they move.
npx -y mcp-pin@0.2.3 -- <your @tasklite/mcp command>
Or subscribe to this page's RSS feed to be told when it changes.
Badge
The badge states one fact about time and nothing else. It never claims a server is safe.
[](https://mcp-pin.gautamkhosla.com/servers/6601db2da8c3febb.html)