{"name":"com.workspace369/mcp","title":"Workspace369","version":"1.0.0","description":"CRM, proposals, e-sign contracts, invoices, projects, time tracking and automations for client work.","websiteUrl":"https://workspace369.com/","documentationUrl":"https://docs.workspace369.com","remotes":[{"type":"streamable-http","url":"https://mcp.workspace369.com/mcp","authentication":{"required":true,"schemes":["oauth2","bearer"],"oauth":{"version":"2.1","protectedResourceMetadata":"https://mcp.workspace369.com/.well-known/oauth-protected-resource/mcp"},"personalAccessTokens":{"prefix":"mcp_pat_"}}}],"capabilities":{"tools":{},"prompts":{}},"tools":[{"name":"workspace_get_context","title":"Get workspace context","description":"Return the current workspace (`workspace`: id, name, plan), the caller's `permissions` and their `token_balance`. Call it first to confirm which workspace the connection is on and what the caller may do; then use workspace_schema for the data model and workspace_business_summary for headline counts. Read-only; takes no arguments.","annotations":{"title":"Get workspace context","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"workspace_schema","title":"Get workspace data model","description":"Describe the record types you can work with through this server (clients, requests, proposals, contracts, invoices, projects, tasks, calendar events, products): the tools that read and write each, the fields their create tool accepts, and this workspace's client stages, client custom field definitions, calendar categories and lead sources (definitions only, never record values). Call it after workspace_get_context and before building screens. For task statuses and custom fields use task_board_schema.","annotations":{"title":"Get workspace data model","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"clients_search","title":"Search clients","description":"List clients in the workspace, paged (up to `limit`, default 25, max 100, plus `nextCursor`). `query` matches name, email or company. `lead_source`, `client_type`, `stage` (a pipeline stage id or name) and `tags` (matches any) are applied after the page is cut, so a filtered page can be short while more matches exist; keep following `nextCursor`. Archived clients are excluded. Scoped members see only their assigned clients.","annotations":{"title":"Search clients","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"clients_get","title":"Get a client","description":"Fetch one client by id, with its `custom_fields` values (key to value). Returns `{ client: null }` if it does not exist or is outside a scoped member's assigned clients. Archived clients are returned with `archived: true`. Read-only. Find ids with clients_search; change a client with clients_update.","annotations":{"title":"Get a client","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"clients_create","title":"Create a client","description":"Create a client, or a prospect when `lifecycle_stage` is \"prospect\". A name is required: `name`, `first_name`/`last_name`, or `company`. `client_type` defaults to business for a company-only client, otherwise individual. Phone numbers are stored as E.164: give the country code; a 10-digit number is read as US/Canada and an unreadable number is refused. Use `addresses` for several addresses (exactly one is primary; the first if none is flagged), or the flat address fields for one. Addresses are checked against Google Places: an agreeing match is stored geocoded, anything else is stored as written and reported as unverified. Scoped members' new clients are assigned to them. `custom_fields` ({key: value}) is validated against the workspace's definitions; an unknown field or a bad value refuses the create. A repeat call with the same `idempotency_key` returns the first client (`deduped: true`).","annotations":{"title":"Create a client","readOnlyHint":false,"destructiveHint":true,"openWorldHint":true,"idempotentHint":false}},{"name":"clients_update","title":"Update a client","description":"Update a client by `id`. Only the fields passed change. `lifecycle_stage` \"client\" promotes a prospect, \"prospect\" demotes. `phone: \"\"` removes the primary phone. Email, phone and single-address updates replace only the primary entry and keep the others; `addresses` replaces the whole address list (the only way to remove or reorder one) and goes through the same Google Places check as on create. `custom_fields` ({key: value}, validated against the workspace's definitions; null clears a value) merges into the client's existing values. Returns the merged client, or an error if the client does not exist or is outside a scoped member's assigned clients.","annotations":{"title":"Update a client","readOnlyHint":false,"destructiveHint":true,"openWorldHint":true,"idempotentHint":true}},{"name":"clients_move_stage","title":"Move a client to a stage","description":"Move a client to a pipeline stage; use it, not clients_update (whose `lifecycle_stage` only flips prospect/client), for stages. A lead-group stage makes it a prospect, any other a client. Its current stage returns `moved: false`. Unknown stages (the error lists them), archived and out-of-scope clients are refused. Needs clients.edit.","annotations":{"title":"Move a client to a stage","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":true}},{"name":"notes_search","title":"Search notes","description":"List notes, paged (up to `limit`, default 25, max 100, plus `nextCursor`). `scope` \"workspace\" (default) returns workspace and client notes; \"personal\" returns the caller's own personal notes. Filters: `query` (title/preview), `folder_id`, `tag` (case-insensitive substring), `client_id` (notes attached to that client). Filters apply after the page is cut, so keep following `nextCursor`.","annotations":{"title":"Search notes","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"notes_get","title":"Get a note","description":"Fetch one note by id. Returns `{ note: null }` if it does not exist, is trashed, or is outside this workspace (another workspace or a user's personal notes). Read-only. Find ids with notes_search; edit with notes_update.","annotations":{"title":"Get a note","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"notes_create","title":"Create a note","description":"Create a note; it only adds (notes_update edits one, notes_search finds one). `body` takes simple markdown (headings, lists, bold, italic, links). By default it is a workspace note the team sees; `is_personal: true` makes it private to the caller. `client_ids` takes ONE client id and writes into that client's Notes tab instead (needs permission to edit clients; not with `is_personal`). `folder_id` must be a folder in that space, else it is refused; omit it for the space's default folder. A repeat `idempotency_key` returns the first note (`deduped: true`).","annotations":{"title":"Create a note","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":false}},{"name":"invoices_search","title":"Search invoices","description":"List invoices, paged (up to `limit`, default 25, max 100, plus `nextCursor`). Proposals and estimates are excluded (use proposals_search). `query` matches number, title, client name or PO number; `number` looks up one invoice by its human number (e.g. '26-6') and returns its `id`. Archived invoices are excluded unless `include_archived` is true. There is no 'Overdue' status; use each row's `is_overdue`, `due_date` and `balance_due`. `total` and `balance_due` are null without the invoices.view_prices permission. Scoped members see only their assigned clients.","annotations":{"title":"Search invoices","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"invoices_get","title":"Get an invoice","description":"Fetch one invoice by `id` or by `invoice_number` (e.g. '26-6'). Returns `{ invoice: null }` if it does not exist, is a proposal/estimate, or is outside a scoped member's clients; if two invoices share a number it refuses and asks for the `id`. Includes `line_items` (up to 100, each with the `line_number` that invoices_update_draft's `update_services` addresses by position) and the true `line_item_count`. `content_locked` / `content_lock_reason` show whether the invoice is locked by a recorded payment or credit (its items and prices then change only through a change order in the app); `change_orders_pending` counts change orders awaiting the client. Monetary fields are null without the invoices.view_prices permission.","annotations":{"title":"Get an invoice","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"invoices_create_draft","title":"Create a draft invoice","description":"Create a draft invoice for a client. It is saved as a draft and never sent. Needs `client_id` and at least one line item: `{product_id, quantity}` (catalogue product; `unit_price` overrides its price) or `{description, unit_price, quantity}` (one-off). `unit_price` is the price of one unit; line totals are computed. `details` is the line's printed description and accepts simple markdown. A draft without a `title` is named \"Invoice for <client>\". The number comes from the workspace's invoice sequence (`number` sets one explicitly). `due_in_days` (0 = due on receipt) or `due_date` (MM/DD/YYYY) set the due date; `payment_term` is the printed label only. Tax is `tax_percent` or `tax_amount`, discount is `discount_percent` or `discount_amount` (not both of either); tax applies after the discount. `notes` prints on the invoice. The workspace's Invoice Defaults (due terms, tax, discount, deposit, notes) apply unless `apply_workspace_defaults` is false, and explicit values override them (see invoices_get_defaults). The invoice inherits its visibility from the client. A repeat call with the same `idempotency_key` returns the first invoice (`deduped: true`).","annotations":{"title":"Create a draft invoice","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":false}},{"name":"invoices_send","title":"Send an invoice","description":"Send an invoice to its client by email or SMS, as the app's Send email sheet does. Identify it by `invoice_id` or `invoice_number` (e.g. '26-6'). `channel` is 'email' (default) or 'sms'; the recipient defaults to the client's email or phone. `to`, `cc` and `bcc` must be saved on the invoice's client or its contacts, or be a team member or the workspace; others are refused. Email uses the workspace's email settings and invoice template, with the invoice button and PDF attached; `template` (see email_send_options) replaces the template, and `subject` and `message` override its subject and body. If `from_inbox` is omitted, the workspace's AI & MCP inbox is used, else the only inbox the user can send from; with several and no default, nothing is sent and the result lists them. `cc`/`bcc` replace the default copies. At most 10 recipients per email from a starter inbox and 50 from the user's own domain or mailbox (To + Cc + Bcc, workspace addresses excluded); over the cap nothing is sent. SMS texts the invoice link, with `message` as a note. Refuses when the invoice has no link yet. Sends a real message and bills for it.","annotations":{"title":"Send an invoice","readOnlyHint":false,"destructiveHint":true,"openWorldHint":true,"idempotentHint":false}},{"name":"proposals_search","title":"Search proposals","description":"List proposals and estimates, paged (up to `limit`, default 25, max 100, plus `nextCursor`). Invoices are excluded (use invoices_search). `query` matches title, number or client name; archived proposals are excluded unless `include_archived` is true. Each row carries `sent_at`, `viewed_at`, `view_count`, `accepted_at`, `declined_at` and a derived `opened_by_client`; these, not `status`, show whether the client has seen it. `total` is null without the invoices.view_prices permission. Scoped members see only their assigned clients.","annotations":{"title":"Search proposals","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"proposals_get","title":"Get a proposal","description":"Fetch one proposal or estimate by id. Returns `{ proposal: null }` if it does not exist, is an invoice, or is outside a scoped member's clients. Includes line items (name, quantity, rate, total, whether client-selectable), `design` (theme, colours, cover, title placement) and `sections` (ids, types, titles, text, images, highlights); change them with proposals_update_design and proposals_edit_sections. `content_locked` / `content_lock_reason` show whether it is locked (accepted, or deposit paid) and so changes only through a change order in the app; `change_orders_pending` counts change orders awaiting the client. `total` is null without the invoices.view_prices permission.","annotations":{"title":"Get a proposal","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"proposals_create_draft","title":"Create a draft proposal","description":"Create a draft proposal for a client. It is saved as a draft and never sent. Needs `client_id` and `title`; numbered from the workspace's proposal sequence (PROP-NNN). `template_id` (see proposal_templates_list) starts from a saved template's sections, design, settings, line items and notes instead of the workspace defaults. Each `line_items` entry is `{product_id, quantity}` (catalogue product; name and price read server-side) or `{description, unit_price, quantity}` (one-off), where `unit_price` is the price of one unit; without line items the proposal quotes zero. `optional: true` marks a client-selectable add-on (only on `product_id` lines). `details` is a line's printed description and `body` seeds one text section; both accept simple markdown. `notes` is internal and not shown to the client. Totals and tax are computed. A discount cannot be set here; use proposals_update_draft. Signature, deposit and validity terms come from the proposal defaults (proposals_get_defaults). The proposal inherits its visibility from the client. A repeat call with the same `idempotency_key` returns the first proposal (`deduped: true`).","annotations":{"title":"Create a draft proposal","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":false}},{"name":"proposals_send","title":"Send a proposal","description":"Send a proposal to its client. Enqueues the send; the proposals worker mints the client link, marks the proposal sent and emails the client with the workspace's proposal email template. Requires `proposal_id`. Invoices or estimates, missing ids and proposals outside a scoped member's clients return 'not found' and nothing is sent. If `from_inbox` is omitted, the workspace's AI & MCP inbox is used, else the only inbox the user can send from; with several and no default, nothing is sent and the result lists them. The email goes to the client alone, so there is no Cc/Bcc to set. At most 10 recipients per email from a starter inbox and 50 from the user's own domain or mailbox (To + Cc + Bcc, workspace addresses excluded); over the cap nothing is sent. The result names the inbox and recipient. A repeat call with the same `idempotency_key` collapses onto one send. Sends a real email and bills for it.","annotations":{"title":"Send a proposal","readOnlyHint":false,"destructiveHint":true,"openWorldHint":true,"idempotentHint":false}},{"name":"projects_search","title":"Search projects","description":"List projects, paged (up to `limit`, default 25, max 100, plus `nextCursor`). `query` matches name or client id; `status` and `clientId` narrow the results. Scoped members see only their assigned clients' projects. To read one in full use projects_get.","annotations":{"title":"Search projects","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"projects_get","title":"Get a project","description":"Fetch one project by id. Returns `{ project: null }` if it does not exist or is outside a scoped member's assigned clients. Read-only. Find ids with projects_search; edit with projects_update.","annotations":{"title":"Get a project","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"projects_create","title":"Create a project","description":"Create a project. Needs `project_name`. `status` is planning, in_progress, on_hold, completed or cancelled (display names like \"In Progress\" are accepted). `priority` is low, medium, high or critical. `start_date`/`end_date` are ISO-8601; a date-only value is that day in the workspace time zone. With a `client_id` the project inherits that client's visibility; a client-less project is visible to full members only. A repeat call with the same `idempotency_key` returns the first project (`deduped: true`). To change one use projects_update; to find one, projects_search.","annotations":{"title":"Create a project","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":false}},{"name":"tasks_search","title":"Search tasks","description":"List tasks across the workspace's boards, paged (up to `limit`, default 25, max 100, plus `nextCursor`). Filters: `query` (title), `boardId` (from task_boards_list), `status` (names normalized, `todo` matches `To-do`), `priority` (1 low, 2 medium, 3 high), `assignee_id` (a member id), `due_before` / `due_after` (ISO-8601 dates), `tags` (matches any) and `is_starred`. Overdue work is `assignee_id` with a `due_before` of now. Filters apply after the page is cut, so a filtered page can be short; keep following `nextCursor`. Rows carry an 80-character description preview; `include_full_descriptions: true` returns complete descriptions. Scoped members see only tasks on boards they can access.","annotations":{"title":"Search tasks","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"tasks_create","title":"Create a task","description":"Create ONE task on a board. Needs `board_id` (from task_boards_list) and `title`. `status` is validated against the board's statuses (names normalized) and defaults to the board default; `priority` is 1 low, 2 medium, 3 high. `assignees` are workspace member ids. `subtasks` are checklist titles created with the task. `custom_fields` is `[{field, value}]` using the field ids, types and dropdown options from task_board_schema (dates ISO 8601, checkboxes true/false, numbers numeric). `start_date` and `due_date` are epoch milliseconds or null; `blocked_by` lists ids of tasks on the same board that must finish first. A task whose exact title (case- and whitespace-insensitive) already exists on the board is skipped and returned with `deduped: true`. `description` is plain text (markdown renders literally). Scoped members can create only on boards they can access. A repeat call with the same `idempotency_key` returns the first task.","annotations":{"title":"Create a task","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":false}},{"name":"tasks_update","title":"Update a task","description":"Update a task by `id` on its `board_id`. Only the fields passed change. `title`, `status` (validated against the board's statuses), `description` (null clears), `priority` (1-3), `estimated_hours` (null clears) and `is_starred` are replaced as given. `tags`, `assignees` and `blocked_by` REPLACE the stored list ([] clears). `custom_fields` ([{field, value}], using ids from task_board_schema) is merged into the stored values; `clear_custom_fields` lists field ids or unique names to remove (a required field cannot be cleared). `start_date`/`due_date` are epoch milliseconds (null clears). A blocker must be an existing task on the same board, and an edge that would create a dependency cycle is rejected. Subtask changes go through tasks_subtasks_update. Returns the merged task, or an error if the task does not exist or is on a board outside a scoped member's access.","annotations":{"title":"Update a task","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":true}},{"name":"tasks_comments_list","title":"Read a task's comments","description":"List the comments on a task, newest first, with author and time. Takes `board_id` and `task_id` from tasks_search. Attachments are reported as a count, not urls. Read-only. Returns `comments`; add one with tasks_comment_add.","annotations":{"title":"Read a task's comments","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"tasks_comment_add","title":"Comment on a task","description":"Add a comment to a task, attributed to the person the caller acts for; it also appears in the task's activity feed. Takes `board_id` and `task_id` from tasks_search plus `content`. @-mentions are not resolved: the text is posted as plain text. The comment is posted inside the workspace and nothing is sent to a client. Returns the new `comment`. Each call adds another comment.","annotations":{"title":"Comment on a task","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":false}},{"name":"task_boards_create","title":"Create a task board","description":"Create a task board with the standard To-do / In Progress / Done columns. If the name is taken it is numbered (\"Launch (1)\") and the response says so. It cannot rename, delete or reconfigure a board. Writes one new board and returns `board` and `renamed` (true when the name was numbered). Check task_boards_list first to avoid near-duplicates; each call creates a board.","annotations":{"title":"Create a task board","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":false}},{"name":"calendar_list_events","title":"List calendar events","description":"List calendar events (schedules), paged (up to `limit`, default 25, max 100, plus `nextCursor`). `from` / `to` (ISO dates) filter by start time: a date-only value means the whole of that day in the workspace time zone (from = to = one date lists that day); a value with a time is an exact instant. All-day events (`is_all_day`) use UTC-midnight date markers: show the date, not a local clock time. `employee_id` keeps events assigned to that team member; `client_id` keeps one client's events. Cancelled events are excluded unless `include_cancelled` is true. Each row carries `is_cancelled`, `location`, `meeting_url`, `description`, `notes` and `is_recurring`; a Meet link that failed shows `meeting_link_status` / `meeting_link_error_code`. `google` is the caller's Google connection state. Scoped members see only their assigned clients' events plus team-wide ones; members restricted to assigned schedules see only events assigned to them.","annotations":{"title":"List calendar events","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"booking_pages_list","title":"List booking pages","description":"List the workspace's booking pages. Use it whenever a client needs a booking link; never compose one. `public_link` is set only for an active page with a slug, so an inactive page has no link to share. Takes no arguments; pages are created and edited in Workspace369.","annotations":{"title":"List booking pages","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"calendar_create_event","title":"Create a calendar event","description":"Create a calendar event (schedule). Needs `title` and `starts_at` (ISO date). `ends_at` defaults to one hour after `starts_at` and must be after it. For all-day events set `is_all_day: true`, pass YYYY-MM-DD dates (no timezone conversion) and `time_zone`; the default all-day end is the following date and explicit ends are exclusive (September 11 alone ends September 12). With a `client_id` the event inherits that client's visibility; a client-less event is visible to the whole team. `category` must be an existing name from calendar_categories_list. `reminders` are alarm metadata on the event (5_minutes_before, 15_minutes_before, 30_minutes_before, 1_hour_before, 2_hours_before, 1_day_before); they do not prove a notification was scheduled. `recurrence` is daily, weekly, biweekly or monthly, with `recurrence_count` or `recurrence_end_date`. `assigned_user_ids` are ids from team_members_list (omitted: assigned to the caller; `[]`: unassigned). `services` (name + quantity) returns a catalog-priced estimate in the response (`services_preview`, `estimate_total`); nothing is stored on the event. A repeat call with the same `idempotency_key` returns the first event (`deduped: true`).","annotations":{"title":"Create a calendar event","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":false}},{"name":"time_entries_list","title":"List time entries","description":"List time entries, paged (up to `limit`, default 25, max 100, plus `nextCursor`). `startTime`/`endTime` (epoch ms) bound the entry start and are applied to each page, so keep following `nextCursor`. A scoped member sees only the entries they logged plus entries for their assigned clients. Read-only. To filter by client, project, task or billable status use time_entries_search.","annotations":{"title":"List time entries","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"time_entries_search","title":"Search time entries","description":"List time entries with filters, paged (up to `limit`, default 25, max 100, plus `nextCursor`). `clientId`, `projectId`, `taskId` and `billable` narrow the results; `startTime`/`endTime` (epoch ms) bound the entry start. Filters are applied to each page, so keep following `nextCursor`. A scoped member sees only the entries they logged plus entries for their assigned clients. Read-only. For a plain date-range listing time_entries_list is enough.","annotations":{"title":"Search time entries","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"time_entries_create","title":"Log a time entry","description":"Log a completed time entry for the calling user (entries for other members are not supported). Needs `start_time` (epoch ms, within the window the server enforces) and `duration_minutes` (0-10080). `billable` defaults to true. A billable entry resolves the workspace's own rate (project, then member, then workspace default) when `hourly_rate` is omitted; a rate other than the resolved one needs the billable-rates edit permission. A non-billable entry always has rate 0. `total_amount` is computed (duration / 60 x hourly_rate when billable, else 0). A repeat call with the same `idempotency_key` returns the first entry (`deduped: true`). To change an entry use time_entries_update; to find entries, time_entries_search.","annotations":{"title":"Log a time entry","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":false}},{"name":"time_entries_update","title":"Update a time entry","description":"Update one of the caller's own time entries by `id`. Only the fields passed change. Editable: `start_time`/`end_time` (epoch ms), `duration_minutes`, `billable`, `hourly_rate`, `currency`, `description` and `client_id`/`project_id`/`task_id` (re-pointing also re-resolves the stored display name). `total_amount` is recomputed when duration, rate or billable change. Marking an entry non-billable clears its rate; marking a $0 entry billable applies the workspace's resolved rate; a different `hourly_rate` needs the billable-rates edit permission. A timer started in the app can be stopped (`is_running: false`) or paused and resumed (`is_paused`, `total_pause_ms`, `last_pause_at`); a stopped entry cannot be restarted. Billing, timesheet-status and client-scope fields cannot be set. Invoiced, submitted, approved and period-locked entries, other members' entries, and restarting a stopped timer are refused.","annotations":{"title":"Update a time entry","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":true}},{"name":"timesheets_list","title":"List timesheet submissions","description":"List weekly timesheet submissions: member, week, approval `status` and server-computed totals (minutes, billable minutes, amount, entry count). Callers see only their own unless they can approve timesheets. `status` and `memberId` filter each page after it is read, so a page can be short; keep following `nextCursor`. For the time entries themselves use time_entries_list or time_entries_search.","annotations":{"title":"List timesheet submissions","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"inbox_send_message","title":"Send an inbox message","description":"Send an email or SMS from the workspace inbox. Recipients (`to`, `cc`, `bcc`) must already be in the workspace: a client's or contact's saved address or number (pass `client_id`), a team member, the workspace's own address, or the contact of an existing conversation (`conversation_id`); others are refused, as is a client who opted out or is blacklisted. Email: `to` (one or more addresses, each with its own copy), `subject` and `body` (markdown), or a general library `template` (see email_send_options) whose subject and body apply unless overridden. If `from_inbox` is omitted, the workspace's AI & MCP inbox is used, else the only inbox the user can send from; with several and no default, nothing is sent and the result lists them. `cc`/`bcc` replace the default copies. At most 10 recipients per email from a starter inbox and 50 from the user's own domain or mailbox (To + Cc + Bcc, workspace addresses excluded); over the cap nothing is sent. SMS: `to` is one phone number and `body` the text; it needs `client_id` or `conversation_id`, and numbers that texted STOP or are blocked are refused. Sends a real, billed message.","annotations":{"title":"Send an inbox message","readOnlyHint":false,"destructiveHint":true,"openWorldHint":true,"idempotentHint":false}},{"name":"search_workspace_messages","title":"Search inbox messages","description":"Full-text search across individual inbox messages (email, SMS, call notes), e.g. to find the message where something was said. Returns up to `limit` matches (default 25, max 50), each with its `conversation_id`, plus `nextCursor` when more exist. Callers see only the channels and clients they have access to. Read-only. Searches message text only; find people with clients_search and policies or FAQs with knowledge_search.","annotations":{"title":"Search inbox messages","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"knowledge_search","title":"Search the knowledge base","description":"Retrieve entries from the workspace's AI knowledge base (policies, procedures, pricing rules, FAQs). `query` runs a semantic search that falls back to keyword matching; `category` filters (e.g. \"Pricing\", \"FAQ\"); with neither it lists the newest entries. Returns up to `limit` entries (default 3, max 25), content truncated to 1500 characters. Read-only. Returns `entries` and the search `mode` used. For client records use clients_search; for notes, notes_search.","annotations":{"title":"Search the knowledge base","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"inbox_send_bulk","title":"Send a bulk SMS","description":"Send one SMS to many of the workspace's own clients in a single action. `recipients` is [{client_id, to?, name?}] (find ids with clients_search) and `body` is the same text for every recipient. `client_id` is required: the text goes to that client's phone on file, or to `to` only if it is a number saved on that client. Numbers that are not on a client record cannot be texted. Clients who opted out of texts or are blacklisted are excluded as the campaigns screen does and returned as `suppressed`, not `failed`. One confirmation covers the exact recipient list, and confirmable tools are rejected inside batch_apply. At most 30 recipients per call. Duplicate phone numbers are sent once and reported as duplicates. Each recipient returns its own outcome (queued, failed, duplicate or suppressed) with its conversation id; a failure does not stop later recipients. Requires the inbox SMS send permission. Sends real messages and bills for each.","annotations":{"title":"Send a bulk SMS","readOnlyHint":false,"destructiveHint":true,"openWorldHint":true,"idempotentHint":false}},{"name":"media_attach","title":"Attach a workspace file","description":"Attach an already-uploaded workspace-storage file (by `storage_path`) to a schedule, an invoice, or an expense (`entity_type: \"expense\"`, id from expenses_list; this is how a receipt is attached). No file bytes are accepted: the app uploads and registers the file first. `kind: \"photo\"` (schedules only; `is_before` marks a before/after job photo) requires an image; `kind: \"document\"` attaches any registered file. Invoices resolve by `entity_id` or `entity_number` (ambiguous numbers are refused); proposals and estimates are not valid targets. Re-attaching the same path is a no-op (`deduped`).","annotations":{"title":"Attach a workspace file","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":true}},{"name":"media_list","title":"List attached media","description":"List the files attached to a schedule, an invoice or an expense: photos and documents, with file name, description and before/after photo kind from the workspace storage registry. `kind` filters to photo, document or all (default). Returns storage paths, never file bytes. Read-only. Returns `media` and `count`; attach a file with media_attach.","annotations":{"title":"List attached media","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"expense_types_list","title":"List expense types","description":"List the workspace's configured expense categories (id and name). expenses_create and expenses_update take a category name; an unknown category is allowed but flagged. Read-only; takes no arguments. Returns `expense_types` and `count`.","annotations":{"title":"List expense types","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"expenses_create","title":"Record an expense","description":"Record a business expense. Needs a positive `amount` (dollars) and a `category` (a name from expense_types_list; an unknown one is recorded with a warning). `date` is \"MM/dd/yyyy\" or \"yyyy-MM-dd\" and defaults to today. `receipt_number` is folded into the description. `project_id` links the expense to a project (from projects_search); `schedule_id` links it to a calendar job. Dates inside a closed accounting period are refused. A repeat call with the same `idempotency_key` reports a replay with `deduped`. Returns `expense` and `deduped`; an unknown category adds `category_warning`.","annotations":{"title":"Record an expense","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":false}},{"name":"expenses_update","title":"Update an expense","description":"Correct an expense by `expense_id` (from expenses_list). Only the fields passed change: `amount`, `category`, `description`, `date` (\"MM/dd/yyyy\" or \"yyyy-MM-dd\") or `project_id` (empty string unlinks it). Refused when the expense is in a closed accounting period or would be moved into one. It cannot delete an expense or change who recorded it. Returns `expense_id`, the applied `changes` and any category note or warning.","annotations":{"title":"Update an expense","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":true}},{"name":"expenses_list","title":"List expenses","description":"List recorded expenses, paged by `limit` (default 25, max 100) and `nextCursor`. Filters: `category` (case-insensitive name), `schedule_id`, `project_id`, and `period` (today, this_week, this_month, this_year or all; UTC calendar, weeks start Monday). `page_total_amount` sums this page only. Read-only. Returns `expenses`, `page_total_amount` and `nextCursor`; record one with expenses_create.","annotations":{"title":"List expenses","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"products_list","title":"List catalog services","description":"List the services/products in the catalog with pricing, paged (up to `limit`, default 25, max 100, plus `nextCursor`). By default only active, non-deleted services are returned; `status` (draft, archived or all) includes inactive ones. Each row's `id` is the argument for products_update. Read-only.","annotations":{"title":"List catalog services","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"products_create","title":"Add a catalog service","description":"Add a service/product to the catalog. Needs `name`, `price` and `price_type` (flat_rate, per_hour, per_unit or per_area). `active: false` creates a draft. A duplicate name is refused (use products_update). `description` accepts simple markdown (`# ` headings, `- ` and `1. ` lists, **bold**, *italic*, <u>underline</u>, ~~strikethrough~~, [links](https://…)). A repeat call with the same `idempotency_key` returns the first product. Returns `product` and `deduped`.","annotations":{"title":"Add a catalog service","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":false}},{"name":"products_update","title":"Update a catalog service","description":"Update a catalog service by `id` (from products_list). Only the fields passed (`name`, `price`, `price_type`, `description`, `active`) change. `active: false` moves it to draft; `true` re-activates it. Passing `description` replaces the stored description, including its rich formatting; it accepts simple markdown (`# ` headings, `- ` and `1. ` lists, **bold**, *italic*, <u>underline</u>, ~~strikethrough~~, [links](https://…)). Returns the updated `product`. To add a service use products_create.","annotations":{"title":"Update a catalog service","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":true}},{"name":"workspace_business_summary","title":"Business summary","description":"Counts only, for an overview: clients, invoices (total, unpaid, unpaid sum), today's events and catalog services. For records use clients_search, invoices_search or calendar_list_events; one invoice's payments, invoice_payments_summary. Invoice counts include proposals and legacy estimates (same collection). A figure the caller may not view or that cannot be counted exactly is `null`, never zero.","annotations":{"title":"Business summary","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"tasks_get","title":"Get a task","description":"Get the details of one task on a board: description, status, priority, dates, tags, assignees, estimate, star state, custom-field values (resolved against the board schema) and subtasks. Needs `board_id` and the task `id` (or a `title`, accepted only when it identifies one unambiguous task; ambiguous titles are refused with the candidates).","annotations":{"title":"Get a task","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"task_boards_list","title":"List task boards","description":"List the workspace's task boards with their ordered statuses. Task writes need a `board_id` and a status name that exists on that board. Scoped members see only boards they can access. Read-only; takes no arguments. Returns `boards`; each `id` is the `board_id` the task tools take. For custom fields use task_board_schema; to add a board use task_boards_create.","annotations":{"title":"List task boards","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"task_board_schema","title":"Get a board's field schema","description":"Get the writable field schema for one task board: its statuses, built-in task fields, and board-specific custom fields with types, required state, ids and allowed dropdown options. Use it for `estimated_hours` and `custom_fields` on task writes. Board tags are listed by task_board_tags_list. Read-only. Returns `board`, `statuses`, `built_in_fields`, `custom_fields` and `defaults`. Call it before tasks_create or tasks_update when setting custom fields.","annotations":{"title":"Get a board's field schema","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"task_board_tags_list","title":"List a board's tags","description":"Search or page the tag catalog of one accessible task board, including tags not used by currently loaded tasks. Pass `next_after_id` as `after_id` with the same board and query to continue; pages are live, not a snapshot. If `catalog_ready` is false the catalog is not initialized.","annotations":{"title":"List a board's tags","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"tasks_subtasks_update","title":"Update a task's subtasks","description":"Add checklist items to an existing task and/or complete or reopen existing ones. Locate the task by `id` + `board_id`. `add` appends titles (titles already on the checklist are skipped and reported). `set_completion` ({subtasks: [titles], completed}) is all-or-nothing: every title must match exactly one existing subtask, otherwise nothing changes. Returns the full checklist. To create a task with a checklist, pass `subtasks` to tasks_create.","annotations":{"title":"Update a task's subtasks","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":true}},{"name":"calendar_categories_list","title":"List calendar categories","description":"List the calendar categories configured in the workspace. Only these names exist; a calendar event's `category` must be one of them. Read-only; takes no arguments. Returns `categories`. Call before calendar_create_event or calendar_update_event to pick a valid `category`.","annotations":{"title":"List calendar categories","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"calendar_check_availability","title":"Check team availability","description":"Check which team members are available on a date, from their working hours and that day's calendar events. The day is bounded in the workspace time zone (a member's own time zone override wins for that member) and the answer states the zone. `date` is required; `employee_ids` (from team_members_list) narrows the check. Returns available members (with working hours) and unavailable members (booked, with conflicts, or not a working day). For one person's weekly hours use team_member_availability_get; for events, calendar_list_events.","annotations":{"title":"Check team availability","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"calendar_update_event","title":"Update a calendar event","description":"Edit or reschedule an existing calendar event by `event_id` (new events: calendar_create_event; bulk client moves: calendar_events_reschedule_batch). Only the fields passed change. `starts_at` / `ends_at` are ISO dates; `starts_at` alone moves the event and keeps its duration. All-day moves use date strings with exclusive ends; set `is_all_day` to convert. `notes` replaces the Notes block and `description` the description (\"\" clears either). `add_reminders` appends, skipping any reminder already set; `clear_reminders: true` removes them all. `status: \"cancelled\"` soft-cancels (record kept). A recurring event (`is_recurring`) needs `occurrence_scope: \"all_events\"`, which changes the whole series; other scopes are done in the Calendar screen. `recurrence` (none, daily, weekly, biweekly, monthly, with `recurrence_count` or `recurrence_end_date`) makes a one-off event repeat, re-patterns a series, or with `none` makes it one event; a pattern shorter than the event is refused, and a series on specific weekdays can only have its count changed. An event someone has open in the app is refused as being edited. Linked reminders fire for the first occurrence only on biweekly series.","annotations":{"title":"Update a calendar event","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":true}},{"name":"calendar_events_reschedule_batch","title":"Reschedule a batch of events","description":"Move many calendar events at once as one reviewable action. Pass exactly one mode: `from_date` (ISO date) moves that day's client-linked events to `to_date`, or `client_id` moves that client's upcoming events to `to_date`. Days and times are local to `time_zone` (IANA; default the workspace time zone). Each event keeps its local time of day and duration; `start_time` (\"HH:MM\", 24h, local) re-anchors the day, preserving inter-event gaps unless `preserve_order: false` stacks them at the same time. At most 30 events move per call; if more match, the call refuses with the count. Recurring series and events someone has open in the app are skipped and counted. The result lists every move (old to new start).","annotations":{"title":"Reschedule a batch of events","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":true}},{"name":"team_members_list","title":"List team members","description":"List the workspace's team members: the assignable `id`, name, email, role, invitation status and active availability days of each. Ids are used for event assignees and calendar_check_availability. Read-only; takes no arguments. Returns `members`. To find one person by name or email use team_members_search.","annotations":{"title":"List team members","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"team_members_search","title":"Find a team member","description":"Find a team member by name or email. An unambiguous match returns that member (id, name, email, role, status); an ambiguous query returns up to 5 candidates. Read-only. Returns `match` (the one member, when unambiguous) or `candidates`. For the full roster use team_members_list.","annotations":{"title":"Find a team member","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"team_member_availability_get","title":"Get team member availability","description":"Read a team member's weekly working hours. `member_id` selects one member; omit it for the whole roster. All seven days are always returned; `active: false` is a non-working day. Slot times are wall-clock times in `time_zone`; `time_zone_source` is `member` (their own zone), `workspace`, or `default` (a UTC fallback nobody set). Read-only. Returns `members`; change hours with team_member_availability_set.","annotations":{"title":"Get team member availability","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"team_member_availability_set","title":"Set team member availability","description":"Change a team member's working availability, one day at a time, and/or the time zone those hours are in. Only the days passed change (days you omit are left as they are); clearing a day means naming it with `active: false` or empty `slots`, and an active day needs at least one slot. Times are 12-hour strings like \"8:00 AM\" in the member's time zone. `time_zone` must be an IANA zone naming a place (e.g. \"America/Denver\"); fixed offsets like \"+05:00\" or \"EST\" are refused. Pass `days`, `time_zone`, or both. The member is not notified. Any caller holding `team.edit_permissions` may edit any member, including the workspace owner, as in the app.","annotations":{"title":"Set team member availability","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":true}},{"name":"team_member_activity_list","title":"List a team member's activity","description":"List what a team member has recently done in the workspace (the audit trail behind Team > Member Activity): the action, the record touched, when, and the names of the fields that changed. It reports which fields changed, not what they changed to: old and new values are not served. Messages and Ask AI sessions are listed without a name, because their stored name is their content; use `target_id` to refer to them. `since` / `until` are epoch milliseconds; `limit` defaults to 25, max 100.","annotations":{"title":"List a team member's activity","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"invoices_create_from_schedule","title":"Invoice a scheduled job","description":"Create an invoice for one scheduled job (calendar event). `schedule_id` comes from calendar_list_events. The invoice takes its client and line items from the schedule and its connected estimate/proposal when one exists (a declined optional add-on is left off); otherwise it starts empty and lines are added with invoices_update_draft. Dates, due date, payment term, notes and terms come from the workspace's Invoice Defaults (invoices_get_defaults). It is created as Pending with a public pay link, linked back to the schedule, and numbered from the workspace sequence. A schedule that already has an invoice is refused. `mark_as_paid` also records a payment for the full total, using `payment_type` (Cash, Check, Credit Card, Bank Transfer or Other). This tool does not edit statuses of existing invoices; payments_record records payments. Nothing is charged and nothing is sent to the client. A recorded payment sends the invoice.* webhook events to any webhook URLs the workspace has set up. A repeat call with the same `idempotency_key` returns the first invoice.","annotations":{"title":"Invoice a scheduled job","readOnlyHint":false,"destructiveHint":true,"openWorldHint":true,"idempotentHint":false}},{"name":"invoices_batch_from_schedules","title":"Invoice scheduled jobs in a range","description":"Create invoices for every scheduled job in a date range in one action. `from_date` and optional `to_date` are ISO dates. Schedules are enumerated server-side; those that already have an invoice are skipped (`exclude_already_invoiced: false` includes them), as are cancelled appointments and schedules with no client. Each invoice gets the workspace's Invoice Defaults. At most 25 invoices are created per call (`max_invoices` lowers it), and the result reports how many eligible schedules remain. `mark_as_paid` (+ `payment_type`) records a real payment per invoice. `notify_email` and/or `notify_sms` send each client their invoice with its public pay link and bill for each message; emails go out as invoices_send sends them, with `email_message` replacing the body and `sms_message` adding a note to the text. `from_inbox` names the sending inbox; when omitted it is the workspace's inbox for AI & MCP emails or the only usable one, and with several and no default the call is refused before anything is created and the result lists them. A retry with the same `idempotency_key` reuses the invoices already created.","annotations":{"title":"Invoice scheduled jobs in a range","readOnlyHint":false,"destructiveHint":true,"openWorldHint":true,"idempotentHint":false}},{"name":"invoices_update_draft","title":"Update a draft invoice","description":"Edit an existing invoice by `invoice_id` or `invoice_number`. Changeable: `notes` (replaced), discount (`discount_percent` or `discount_amount`; 0 removes it), tax (`tax_percent` or `tax_amount`), `deposit_percent`, `invoice_date` / `due_date` (MM/DD/YYYY) or `due_in_days`, `payment_term` (printed label only), and `project_id` / `unlink_project`. Lines: `add_services` (same shape as invoices_create_draft), `remove_services` (by line name) and `update_services` (edit a line in place by `line_number`: `description`, `details`, `quantity`, `unit_price`). Line numbers are positions from invoices_get, resolved after `remove_services` and before `add_services`; an out-of-range number is refused and nothing is written. Totals, tax and paid state are re-derived. It sends nothing. Only drafts are editable by default; a sent invoice is refused unless `allow_non_draft: true`, since the edit changes what the customer sees at the public invoice link. A locked invoice (a payment or credit recorded, or any sent invoice when the workspace locks on send) is always refused. Payment state (see payments_record), status, number, client and currency never change.","annotations":{"title":"Update a draft invoice","readOnlyHint":false,"destructiveHint":true,"openWorldHint":true,"idempotentHint":false}},{"name":"invoice_payments_list","title":"List invoice payments","description":"List the payments recorded against one invoice (amount, method, date, status, reference, notes) plus the invoice total, total paid and remaining balance. Identify the invoice by `invoice_id` or `invoice_number` (e.g. '26-6'). Proposals/estimates, unknown identifiers and invoices outside a scoped member's clients all return 'not found'. Monetary fields are null without the invoices.view_prices permission. Read-only. For totals only use invoice_payments_summary; to record a payment use payments_record.","annotations":{"title":"List invoice payments","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"invoice_payments_summary","title":"Invoice payment summary","description":"Payment summary for one invoice: total received, refunded, net, payment count, amounts by method, invoice total and remaining balance. Identify the invoice by `invoice_id` or `invoice_number`. Proposals/estimates, unknown identifiers and out-of-scope invoices return 'not found'. Monetary fields are null (and by-method omitted) without the invoices.view_prices permission. Read-only. For the individual payments use invoice_payments_list.","annotations":{"title":"Invoice payment summary","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"payments_record","title":"Record an invoice payment","description":"Record a payment against an invoice (full, general or partial); this is how an invoice becomes paid. Identify the invoice by `invoice_id` or `invoice_number`. Needs a positive `amount` and a `payment_method` (Cash, Check, Credit Card, Bank Transfer or Other). `payment_date` (\"YYYY-MM-DD\") defaults to now; a date inside a closed accounting period is refused. The amount cannot exceed the outstanding balance. `partial: true` requires an amount below the invoice total. `installment_id` settles a payment-plan installment. The invoice's paid state is re-derived server-side; invoice pricing is not edited. The payment lands in the invoice's activity log and sends the invoice.* webhook events to any webhook URLs the workspace has set up, as a payment recorded in the app does. Nothing is charged and nothing is sent to the client. A repeat call with the same `idempotency_key` does not record the payment twice.","annotations":{"title":"Record an invoice payment","readOnlyHint":false,"destructiveHint":true,"openWorldHint":true,"idempotentHint":false}},{"name":"notes_update","title":"Update a note","description":"Update a note by `id` (from notes_search): `title`, `body` (replaces the whole body; markdown becomes the editor's formatting) or `tags` (REPLACES the tag list). Only the fields passed change. Works on workspace notes, client notes (needs permission to edit clients) and the caller's personal notes. Trashed notes are not editable, and a password-protected note's `body` cannot be changed here. Returns the updated `note`. To add a new note use notes_create.","annotations":{"title":"Update a note","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":true}},{"name":"projects_update","title":"Update a project","description":"Update a project by `id` (from projects_search): `name`, `description`, `status` (planning, in_progress, on_hold, completed, cancelled), `budget`, `priority` (low, medium, high, critical) or `notes`. Only the fields passed change. Progress cannot be set; it is derived from task completion. Moving to in_progress or completed stamps the actual start/end dates. Returns the updated `project`. To add a project use projects_create.","annotations":{"title":"Update a project","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":true}},{"name":"reminders_create","title":"Create a reminder","description":"Create a reminder: a notification that fires at a single point in time. It is not a calendar event, which occupies a time range with a start and an end. A standalone reminder needs a `title` and an ISO-8601 `remind_at`. For a reminder tied to a calendar event, pass the `event_id` from calendar_create_event or calendar_list_events and `minutes_before` (1440 = 24 hours); this creates a workspace reminder linked to the event and notifies the caller by default. `event_id` with `remind_at` sets an absolute time instead. For all-day events, `time_zone` applies: 24 hours before means 24 elapsed hours before midnight at the start of that date in that zone, not 9 AM. `reminder_type` is `personal` (default, notifies only the caller) or `workspace` (notifies the team members in `recipients` via `channels`, default push). Recipients are team-member names or emails; a non-member email must be saved on the client named in `client_id` or be a workspace address, otherwise the reminder is refused. Unmatched recipients are listed in `unresolved_recipients`. `recurrence` repeats it daily, weekly, monthly or yearly. A repeat call with the same `idempotency_key` is safe.","annotations":{"title":"Create a reminder","readOnlyHint":false,"destructiveHint":true,"openWorldHint":true,"idempotentHint":false}},{"name":"reminders_list","title":"List reminders","description":"List the caller's reminders, sorted by time (up to `limit`, default 25, max 100), with the ids reminders_close needs. `filter`: upcoming (default; pending, future), overdue (pending, past), completed (ticked off via reminders_close with intent `complete`) or all (everything not canceled). `scope`: personal, workspace or all (default). Each row has `done`; the status value `sent` means delivered, not done. Read-only. Returns `reminders`; to change one use reminders_update.","annotations":{"title":"List reminders","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"reminders_close","title":"Close a reminder","description":"Close a reminder by `id` (from reminders_list) so it will not fire. `intent` `complete` ticks it off (shown under Completed); `cancel` dismisses it (shown under Canceled, struck through). For a recurring reminder, `complete` closes the whole series, not only the occurrence that is due. Returns the closed `reminder`. To change a reminder instead of closing it, use reminders_update.","annotations":{"title":"Close a reminder","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":true}},{"name":"reminders_update","title":"Update a reminder","description":"Change an existing reminder by `id` (from reminders_list): `title`, `description`, `remind_at` (ISO-8601), `priority` or `channels`. Only the fields passed change. There is no separate snooze action: moving `remind_at` postpones a reminder and re-arms delivery, so one that already fired fires again at the new time. Editing keeps the reminder's id and its link to the invoice or task it came from, whereas closing and re-creating gives a new id and files the original under Canceled. A completed or canceled reminder cannot be edited. Times are ISO-8601 only.","annotations":{"title":"Update a reminder","readOnlyHint":false,"destructiveHint":true,"openWorldHint":true,"idempotentHint":false}},{"name":"proposals_update_draft","title":"Update a draft proposal","description":"Edit an existing draft proposal, identified by `proposal_id`. Changeable: `title`, internal `notes` (replaced), `add_line_items` (same grammar as proposals_create_draft), `remove_line_items` (by line name), discount (`discount_percent` or `discount_amount`; 0 removes it), signature requirement, deposit (`deposit_percent` or `deposit_amount`; `require_deposit: false` removes it) and expiry (`valid_for_days`; 0 removes it). When lines, discount or deposit change, subtotal, tax (at the proposal's rate, or a product's own rate, on the post-discount subtotal), total and deposit are re-derived server-side; other edits leave the money unchanged. Only drafts are editable: a sent, accepted or declined proposal is refused (recall a sent one to draft with proposals_update_status or in the app), and an accepted or deposit-paid proposal is locked and changes only through a change order in the app. It cannot send, accept, decline, convert, delete, change the client or tax rate, or edit layout, theme or sections. Repeating `add_line_items` adds the lines twice.","annotations":{"title":"Update a draft proposal","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":false}},{"name":"proposals_update_status","title":"Record a proposal's outcome","description":"Record what happened to a sent proposal: `intent` \"accepted\" or \"declined\" when the client answered off-system (call, meeting, email), or \"draft\" to recall it so it can be edited again. `reason` is recorded only on a decline. Only a sent proposal can change state, the same rule as the Proposals screen. Marking accepted does not store a signature or typed name; those are written only when the client accepts through the public link. A locked proposal (accepted, or deposit paid) cannot be recalled or declined. This does not send anything and does not create an invoice.","annotations":{"title":"Record a proposal's outcome","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":false}},{"name":"proposals_get_defaults","title":"Get proposal defaults","description":"Read the acceptance terms of proposals: whether accepting requires a signature, whether it requires a deposit (and how much), and when it expires. With no arguments it returns the workspace defaults that seed new proposals (including `validity_days`); with `proposal_id` it also returns that proposal's effective terms (`valid_until` is an actual date) and the deposit amount resolved against its current total. Monetary values are null without the invoices.view_prices permission; whether a deposit is required is always visible. Scoped members see only their assigned clients.","annotations":{"title":"Get proposal defaults","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"proposals_design_options","title":"List proposal design options","description":"List what a proposal's design can be set to: themes (font, default accent), accent presets, page backgrounds, cover styles, title placements, gallery layouts, section types, the 18 cover banners (id, name, preview_url) and the photo gallery categories. `gallery_category` pages through that category's photos (12 at a time; pass `cursor` = next_cursor for more); a photo's `path` is the `gallery_path` for proposals_update_design or proposals_edit_sections. It changes nothing.","annotations":{"title":"List proposal design options","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"proposals_update_design","title":"Change a proposal's design","description":"Change how a draft proposal looks (the editor's Design tab). `theme` (modern, minimal, classic or bold) sets fonts and spacing. Changing `theme` without `accent_color` resets the accent to the theme's default, as the editor does; pass both to keep a custom colour. `accent_color` is hex. `page_background_color` is the backdrop around the paper (hex or \"default\"). `title_placement`: on_image puts the title over the cover; below_image shows the image as a banner with the title under it. `cover_image`: source: \"banner\" + banner_id, source: \"gallery\" + gallery_path, source: \"workspace_file\" + storage_path, source: \"url\" + an https image link (downloaded into the workspace), or source: \"none\" to remove it. `cover_image_style`: fill, inset or fade. `cover_focal_point`: 0 (top) to 100 (bottom). Options come from proposals_design_options. Only drafts can be changed. Returns `previous_design`, so a change can be undone by sending those values back.","annotations":{"title":"Change a proposal's design","readOnlyHint":false,"destructiveHint":true,"openWorldHint":true,"idempotentHint":false}},{"name":"proposals_edit_sections","title":"Edit a proposal's sections","description":"Add, change, reorder or remove a draft proposal's sections (the editor's blocks); proposals_get returns the section ids. Four fixed sections lead every proposal (cover, header, line_items = the pricing table, totals): they cannot be removed or moved, and only the pricing table's `title` changes. Content sections: text (markdown `body`), timeline (`timeline` rows of {date, description}), terms (plain-text `body`, or `terms_id` from the terms library) and images (`images` replaces the gallery; `image_layout` grid, masonry, hero, stacked, showcase, comparison or carousel). Each image is {source, caption?, alt?} with source banner, gallery, workspace_file or url (an https link, downloaded into the workspace). `highlighted` tints a content section with the accent. `after_section_id` places an added or moved section (a fixed section's id = top of the content). `from_section_template_id` inserts a saved section (proposal_templates_list). Up to 20 operations run in order, all or nothing. Only drafts can be changed.","annotations":{"title":"Edit a proposal's sections","readOnlyHint":false,"destructiveHint":true,"openWorldHint":true,"idempotentHint":false}},{"name":"proposal_templates_list","title":"List proposal templates","description":"List the workspace's proposal templates (id, name, theme, accent, section and line counts) and saved sections (id, name, type). `kind`: proposal, section or all (default). Templates start a proposal via proposals_create_draft `template_id`, apply to a draft via proposal_templates_apply, and saved sections insert via proposals_edit_sections `from_section_template_id`. Read-only.","annotations":{"title":"List proposal templates","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"proposal_templates_save_from_proposal","title":"Save a proposal as a template","description":"Saves a proposal's sections, design, settings and line items as a new reusable proposal template and returns its `template_id`. The proposal itself is not changed. Each call creates another template. To save a single section use proposal_templates_save_section. Use this tool to keep a finished proposal as a starting point; apply a saved one with proposal_templates_apply. It creates a template and changes nothing existing. Needs the proposals.manage_templates permission.","annotations":{"title":"Save a proposal as a template","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":false}},{"name":"proposal_templates_save_section","title":"Save a proposal section as a template","description":"Saves one content section of a proposal as a reusable section template and returns its `section_template_id`. The proposal itself is not changed. Each call creates another section template. To save the whole proposal use proposal_templates_save_from_proposal. Use this tool to keep one section for reuse; apply a saved one with proposal_templates_apply. It creates a template and changes nothing existing. Needs the proposals.manage_templates permission.","annotations":{"title":"Save a proposal section as a template","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":false}},{"name":"proposal_templates_apply","title":"Apply a template to a draft proposal","description":"Replaces a draft proposal's sections, design and settings with a template's, and its line items too unless replace_line_items is false. Totals are re-derived; the client, title and notes are kept. Only drafts can be changed. Returns `proposal_id` and the re-derived `total` and `deposit_amount` (null without the invoices.view_prices permission). Overwrites the draft's content; find templates with proposal_templates_list. Needs permission to edit proposals, not to manage templates.","annotations":{"title":"Apply a template to a draft proposal","readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":false}},{"name":"proposal_templates_rename","title":"Rename a proposal template","description":"Renames a proposal template, or a saved section template when `template_kind` is section. Returns `template_id`. The template's content is unchanged. To use one on a draft call proposal_templates_apply. Needs the proposals.manage_templates permission.","annotations":{"title":"Rename a proposal template","readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":false}},{"name":"proposal_templates_duplicate","title":"Duplicate a proposal template","description":"Copies a proposal template into a new template, named `name` or after the original, and returns the copy's `template_id`. Each call makes another copy. Section templates cannot be duplicated. Needs the proposals.manage_templates permission.","annotations":{"title":"Duplicate a proposal template","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":false}},{"name":"proposal_templates_delete","title":"Delete a proposal template","description":"Deletes a proposal template or section template from the library. Proposals already made from it are not changed. Returns `deleted`. Needs the proposals.manage_templates permission.","annotations":{"title":"Delete a proposal template","readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":false}},{"name":"proposals_update_defaults","title":"Change proposal defaults","description":"Change the workspace's proposal defaults (Settings > Proposals). Only the fields passed change; pass at least one. They apply to new blank proposals only; existing proposals and templates are unchanged. proposals_get_defaults reads them; proposals_update_design restyles one draft.","annotations":{"title":"Change proposal defaults","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":true}},{"name":"invoices_get_defaults","title":"Get invoice defaults","description":"Read the workspace's Invoice Defaults, the terms applied to every new invoice: payment timing (`due_in_days` and the `due_date_preview` an invoice created today would get), the payment-term label, tax (none, a percentage, a fixed amount, or the default tax code), discount, whether a deposit is requested and at what percentage, and the default notes. invoices_create_draft applies these unless told otherwise. Existing invoices are not affected. Rates and amounts are null without the invoices.view_prices permission (`prices_visible` says which); the modes are still returned. `tax_code_unresolved: true` means tax-code mode is selected but no active default tax code is set, so new invoices are untaxed. Changing these settings is not possible from any tool.","annotations":{"title":"Get invoice defaults","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"after_payment_get","title":"Read the after-payment page","description":"Read the after-payment page (Settings > After payment) clients see once they pay an invoice, pay link, proposal deposit or booking: the note, the next steps in payer order, the redirect (seconds 0 = a Continue link), where it shows, whether a review link is set, and 30-day views and clicks per step. after_payment_update takes the step ids.","annotations":{"title":"Read the after-payment page","readOnlyHint":true,"destructiveHint":false,"openWorldHint":false,"idempotentHint":true}},{"name":"after_payment_update","title":"Change the after-payment page","description":"Change the after-payment page (Settings > After payment) with operations run in order, all or nothing; each field's description names the operations that use it. Up to 5 steps; links must be https. A review or portal step without a url uses the workspace's review link or the paying client's portal. Get step ids from after_payment_get. Owners and admins only.","annotations":{"title":"Change the after-payment page","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":false}},{"name":"requests_search","title":"Search requests","description":"List inbound leads (contact forms, Pages, Funnels, the inbound lead API), newest first. For one lead's full message and consent use requests_get; requests_forms_list names the forms; a converted lead's `converted_client_id` opens with clients_get. Funnel leads have `source` 'funnel:{id}'. `form_id` and `query` filter each page after it is read, so a page can be short or empty; keep following `nextCursor`.","annotations":{"title":"Search requests","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"requests_get","title":"Get a request","description":"Fetch one inbound request by id, including the full message and the recorded consent (SMS consent, transactional/promotional consent, terms accepted, and the exact consent wording shown). Returns `{ request: null }` if the id does not exist in this workspace. Funnel answer content, internal comments, IP address and user agent are never returned. Read-only. Find ids with requests_search.","annotations":{"title":"Get a request","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"requests_forms_list","title":"List intake forms","description":"List the workspace's inbound intake forms, so a request's `form_id` can be resolved to a name: slug (document id and public URL segment), name, template, layout, active flag and timestamps. Active forms only unless `include_inactive` is true. Creating, editing, publishing or pausing a form is not available through this API; forms are edited in Workspace369 under Requests > Settings > Forms.","annotations":{"title":"List intake forms","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"requests_update_status","title":"Update request status","description":"Mark an inbound request 'reviewed' or archive it. Archiving is terminal: there is no delete and no way back to 'new'. A converted request can be archived but not moved back to 'reviewed'. Converting is done by requests_convert_to_client. Returns `changed: false` if the request was already in the requested status. Sends nothing to the prospect; to reply use requests_respond.","annotations":{"title":"Update request status","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":true}},{"name":"requests_convert_to_client","title":"Convert a request to a client","description":"Turn an inbound request into a CRM client and mark the request converted. A new client lands in the Lead stage, owned by the request's assignee, with the lead source from the workspace's list (\"Website form · <form name>\" for a form request), the same as the Convert button in Requests. The client id is derived from the lead's email (or phone when there is no email), so converting the same person twice, or after the server auto-converted them, links to the same client (`created: false`). A request with neither an email nor a usable phone number is refused. Needs Clients write access as well as Requests write access. Creating the client can start workflow automations that run on new clients, which may message the prospect.","annotations":{"title":"Convert a request to a client","readOnlyHint":false,"destructiveHint":true,"openWorldHint":true,"idempotentHint":true}},{"name":"requests_respond","title":"Reply to a request","description":"Reply to an inbound prospect by email or SMS. The recipient is read from the request itself: the To address cannot be overridden, no files are attached, and only that prospect is messaged. Email goes out as the app's Reply via email does: from an inbox, with the workspace's from-name, reply-to, signature and default Cc/Bcc; `subject` defaults to 'Re: your request' and `body` is markdown, with `{{client.*}}` merge fields filled from the request's name, email and phone. If `from_inbox` is omitted, the workspace's AI & MCP inbox is used, else the only inbox the user can send from; with several and no default, nothing is sent and the result lists them. `cc` and `bcc` are email-only, must be team, workspace or the request's client addresses, and are final lists that replace the defaults (the inbox's archive Bcc is always added). One email goes to at most 10 recipients from a starter inbox and 50 from the user's own domain or mailbox. SMS goes only to a lead who recorded SMS consent. A request marked 'new' becomes 'reviewed' after a successful send. This sends a real message to the prospect and bills for it.","annotations":{"title":"Reply to a request","readOnlyHint":false,"destructiveHint":true,"openWorldHint":true,"idempotentHint":false}},{"name":"lead_sources_list","title":"List lead sources","description":"List the workspace's lead sources (where new business comes from) and which one is the default for new clients. Read-only; takes no arguments. Returns `lead_sources` and `default_id`; change the default with lead_sources_set_default.","annotations":{"title":"List lead sources","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"lead_sources_set_default","title":"Set the default lead source","description":"Set which lead source new clients get when none is named (an empty `lead_source_id` clears it). This changes every client created afterwards. It does not create a lead source; pass an id from lead_sources_list. Returns `default_id`, `default_name` and `changed`.","annotations":{"title":"Set the default lead source","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":true}},{"name":"automations_list","title":"List workflows","description":"List the workspace's workflows (the Workflows screen) with each one's trigger, action (its first step's action), category, step count, lifecycle (draft, active, paused), on/off state, last run's outcome and when it last fired. Each row carries `can_fire` and `blocked_reasons`, which show whether a workflow will actually run: a workflow can be stored and show as enabled yet never fire (its trigger has no live emitter, or its `status` was written as something other than a boolean).","annotations":{"title":"List workflows","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"automations_get","title":"Get a workflow","description":"Fetch one workflow's full definition: trigger, conditions, action, recipients and follow-up sequence. `definition_warnings` lists problems; conditions are ANDed and fail closed, so an unresolvable field or unknown operator makes a workflow log a run and deliver nothing. Recipient addresses are never returned, only the recipient type and name. Returns `{ automation: null }` if the id does not exist in this workspace.","annotations":{"title":"Get a workflow","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"automations_runs_list","title":"List workflow runs","description":"List workflow runs, newest first, up to `limit` (no paging): did it fire and what happened. `outcome`: delivered, failed, skipped (fired, delivered nothing: failed condition, unresolved recipient, missing mailbox), stopped (stopWorkflow condition) or waiting (queued on a wait). automations_test runs show only with `include_test_runs: true`. Bodies, recipient addresses and trigger data are never returned; automations_get shows the steps.","annotations":{"title":"List workflow runs","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"automations_disable","title":"Turn a workflow off","description":"Turn a workflow off so it stops firing, queued steps included: a step waiting on a delay re-checks the live workflow and is skipped. Needs `automations:write`. Turn it back on in the Workflows screen, or with automations_activate where listed (own `automations:activate` scope). An already-off workflow returns `already_off: true`.","annotations":{"title":"Turn a workflow off","readOnlyHint":false,"destructiveHint":true,"openWorldHint":false,"idempotentHint":true}},{"name":"automations_test","title":"Test a workflow","description":"Dry-runs a workflow and sends nothing: every step reports what it would do (who it would email or text, what it would change), on sample data or on a real record (`record_id`). If the workflow has saved changes that are not live yet, those are what is tested, as in the Workflows screen. The run is logged as a test run, readable with automations_runs_list and `include_test_runs: true`. Returns `run_id`, `outcome`, `tested` and `name`. No email or SMS is sent and no record changes; it only saves a test-run log. Use it before automations_activate.","annotations":{"title":"Test a workflow","readOnlyHint":false,"idempotentHint":false,"destructiveHint":true,"openWorldHint":false}},{"name":"contracts_search","title":"Search contracts","description":"List contracts, newest first (up to `limit`, default 25, max 50). Filter by `status_group` (draft; waiting = sent, in progress or sealing; completed; declined; voided; expired), `client_id`, and `archived` ('exclude' by default, or 'only'). `query` matches words in the title. One call scans at most 200 contracts; `scan_truncated: true` means more may match, so pass `next_cursor` to continue. Field values, signatures and signer IP addresses are never returned. Members limited to specific clients see only contracts in their book.","annotations":{"title":"Search contracts","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}},{"name":"contracts_get","title":"Get a contract","description":"Fetch one contract by `contract_id`: status, dates, verification and expiry settings, invitation subject, the sealed document's SHA-256 once completed, and each recipient's name, email, role, routing order and progress. Returns `{ contract: null }` if the id does not exist or is outside a scoped member's book. `send_request` is the latest send started with contracts_send: `queued` while it runs, `sent`, or `failed` with the `failure_code`; null when there is none. Field values, signature images and signer IP addresses are never returned.","annotations":{"title":"Get a contract","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}},{"name":"contract_templates_list","title":"List contract templates","description":"List the workspace's contract templates with their roles (key, label, signer/approver/viewer/cc, routing order, and whether the client, a fixed person or the sender fills it). `bulk_eligible`: exactly one client role and every other role fixed. `proposal_eligible`: also, the client role signs or approves first. Pass a template id to contracts_create_draft; to find contracts use contracts_search.","annotations":{"title":"List contract templates","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}},{"name":"contracts_create_draft","title":"Create a draft contract","description":"Create a DRAFT contract from a template. Pass `template_id` (see contract_templates_list) and `client_id` when the template has a client role; the client's primary email fills that role. Pass `recipients` for roles whose assignment is manual, as { role_key, name, email }; each email must be saved on the client (or its contacts), a team member or the workspace. Optional `title`, `message` (the invitation email subject and body) and `reminders` (the automatic reminder cadence: first after N days, then every N days, at most N). Nothing is sent: the draft can be reviewed in Workspace369 or sent with contracts_send. `unassigned_role_keys` lists roles that still need a person before the contract can be sent. Each call creates a new draft.","annotations":{"title":"Create a draft contract","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":false}},{"name":"contracts_send","title":"Send a contract for signature","description":"Send a draft contract for signature: the document is frozen, the first routing group is emailed and the expiry clock starts. Needs `contract_id`. It uses one of the workspace's included monthly contracts if any remain; otherwise it costs Tokens, and nothing is sent if that charge fails. This emails real recipients and cannot be undone (voiding stops it). The send runs in the background (a contract from a text template is rendered first, which can take minutes), so the result `status: \"sending\"` confirms nothing yet: contracts_get shows whether it went out (status no longer draft; `send_request.failure_code` explains a failure). Calling again while a send is in progress starts nothing new (`deduplicated: true`).","annotations":{"title":"Send a contract for signature","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":true}},{"name":"contracts_remind","title":"Remind contract recipients","description":"Email a reminder to recipients who were invited and have not finished. `recipient_id` selects one recipient; omit it to remind everyone who is due. A recipient reminded in the last 24 hours (`too_soon`) or not waiting on an emailed link, such as one signing in person (`not_remindable`), is listed under `skipped`. This sends real email and cannot be undone. Returns `reminded_recipient_ids` and `skipped`.","annotations":{"title":"Remind contract recipients","readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":true}},{"name":"contracts_void","title":"Void a contract","description":"Void an open contract (sent or in progress) so nobody can sign it. Requires `contract_id` and a `reason` of 5 to 1000 characters, recorded on the contract. A contract that is sealing, completed, declined, voided or expired cannot be voided. Recipients are emailed that it was voided. This cannot be undone; duplicate the contract in Workspace369 to start again. Returns the voided `contract`.","annotations":{"title":"Void a contract","readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":true}},{"name":"email_send_options","title":"Get email send options","description":"Call this before inbox_send_message, invoices_send or proposals_send; it sends nothing. Read how an email would go out: the inboxes the user can send from (label, address, starter or own domain/mailbox, per-email recipient cap, which is the workspace default and which the default for automatic emails), what happens when `from_inbox` is omitted (`when_from_inbox_omitted`; 'ask_user' means the send tools will not choose), the default Cc and Bcc the send tools add (automatic ones are marked, and each says whether it counts toward the cap), each inbox's compliance archive Bcc (always added, cannot be removed), from-name and reply-to overrides, whether a signature is added, the default invoice email template, and the template library (id, name, context: 'general' templates work with inbox_send_message and invoices_send, 'invoice' with invoices_send, 'proposal' templates apply to proposals automatically). In the send tools `cc` / `bcc` replace the defaults. Recipient cap: at most 10 people per email from a starter inbox and 50 from the user's own domain or mailbox, counting To + Cc + Bcc; the workspace's own addresses do not count. Requires the Send Email permission.","annotations":{"title":"Get email send options","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"follow_ups_list","title":"List client follow-ups","description":"List the workspace's client follow-ups grouped as `overdue`, `today`, `this_week` (rest of the next 7 days) and `suggested`. Each item is the client's next follow-up as computed by the server: scheduled meetings, reminders and tasks first, then detected reasons (chase_payment = invoice past due, reply_owed = client message waiting over 24 h, onboarding_step = contract/deposit/kickoff missing, chase_proposal = proposal sent 3+ days and not accepted, renewal = retainer ends within 30 days), then `suggested` check-ins (quiet longer than the stage's cadence). `amount` + `currency`, when present, is the money behind the follow-up (only for callers who may see prices). `best_time`, when present, comes from the client's notes. `scope`: everyone (default) or mine (follow-ups naming the caller, or on clients they created or are assigned). `group`: overdue, today, this_week, suggested, or due (overdue + today). Scoped members see only their assigned clients; archived clients are excluded. Changes nothing.","annotations":{"title":"List client follow-ups","readOnlyHint":true,"idempotentHint":true,"destructiveHint":false,"openWorldHint":false}},{"name":"recurring_plans_search","title":"Search recurring invoices","description":"List recurring invoice plans (an invoice that repeats on a schedule, paid by autopay or a pay link). Filters: `query` (plan or client name), `status` (draft, scheduled, active, paused, ended), `client_id`, `needs_attention` (failed payment, client must confirm with their bank, new card needed, draft to review, Stripe disconnected). Each plan has its cadence, next invoice date, autopay state and card, and any retry in progress. `summary` totals this page: active plans, plans needing attention and monthly recurring revenue (`monthly_amount` normalizes weekly/quarterly/yearly plans to a month). Amounts are null without the invoices.view_prices permission. Scoped members see only their clients. Paged: when the response has `nextCursor`, pass that value back as `cursor` for the next page; no `nextCursor` means the last page.","annotations":{"title":"Search recurring invoices","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}},{"name":"recurring_plans_get","title":"Get a recurring invoice","description":"Fetch one recurring invoice plan by `id` (from recurring_plans_search): everything a search row has, plus sending (auto or review), payment terms, how it ends, any pause or upcoming amount change, pending one-off lines, and its last few autopay charge attempts (status, decline code, next retry). Returns `{ plan: null }` if the id does not exist or is outside a scoped member's clients. Amounts are null without invoices.view_prices.","annotations":{"title":"Get a recurring invoice","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}},{"name":"retainers_search","title":"Search retainers","description":"List retainers: hour banks (`hours`), fixed monthly (`fixed`) and prepaid deposits (`prepaid`). For one retainer's ledger and extra-hour rules use retainers_get; repeating invoices are recurring_plans_search. Each row has this cycle's hours (included plus any rolled-in, approved, pending approval and left; negative = extra hours), the extra-hour rate, the prepaid balance, the next invoice date, and `billing`: automatic, review_needed (an older retainer waiting for the studio to confirm billing) or manual. Money fields are null without invoices.view_prices. Scoped members see only their clients. Paged: when the response has `nextCursor`, pass that value back as `cursor` for the next page; no `nextCursor` means the last page.","annotations":{"title":"Search retainers","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}},{"name":"retainers_get","title":"Get a retainer","description":"Fetch one retainer by `id` (from retainers_search): everything a search row has, plus how extra hours are handled (bill_next or track_only), what happens to unused hours, the prepaid top-up settings, the prepaid balance with the amount reserved for a refund in progress and what is still available (`prepaid_available` = balance - reserved), and its last 10 ledger entries in plain words. Returns `{ retainer: null }` if the id does not exist or is outside a scoped member's clients.","annotations":{"title":"Get a retainer","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false}},{"name":"recurring_plans_send_pay_link","title":"Send a recurring invoice's pay link","description":"Email the client the pay link for a recurring plan's open invoice (the one a failed autopay is retrying or waiting on, else the plan's latest unpaid invoice), as invoices_send sends an invoice: the workspace's template, signature and PDF, with the Pay button. Useful when autopay failed or the client must confirm with their bank. `plan_id` is required; `message` (markdown) replaces the default line about the card; `from_inbox` and `idempotency_key` work as in invoices_send. Refuses when the plan has no unpaid invoice. Sends a real email and uses one email credit.","annotations":{"title":"Send a recurring invoice's pay link","readOnlyHint":false,"destructiveHint":true,"openWorldHint":true,"idempotentHint":false}},{"name":"approvals_get","title":"Check an approval","description":"Check what became of a tool call that was held for the user's approval. Pass the `approval_id` the held call returned. Reports whether the user is still deciding, approved it (with the result), declined it, or let it expire. It cannot approve anything; only the user can, in the Workspace369 app. Read-only and free (no tokens charged).","annotations":{"title":"Check an approval","readOnlyHint":true,"destructiveHint":false,"openWorldHint":false,"idempotentHint":true}}],"prompts":[{"name":"build_crm_frontend","description":"Guidance for building a custom CRM front end on Workspace369.","arguments":[{"name":"business_type","required":false},{"name":"framework","required":false}]},{"name":"set_up_my_crm","description":"Set up your Workspace369 CRM: review what exists, propose stages and fields, add them after you confirm.","arguments":[{"name":"business_type","required":false}]},{"name":"import_my_clients","description":"Import a pasted list or CSV of clients, skipping duplicates, after you confirm.","arguments":[]},{"name":"weekly_pipeline_review","description":"Read-only weekly review: clients by stage, proposals awaiting reply, unpaid invoices, next actions.","arguments":[]}]}