Connect AI assistants to Carbon Voice for asynchronous voice and text communication. Create, send, search, and retrieve voice messages, access transcriptions, manage conversations and folders, and use AI actions to summarize discussions and extract action items.
Encrypted at rest, isolated from the model
Resolved from an AES-256-GCM vault at the moment of the call and attached to the request — the model never sees the secrets.
Try asking
List messages with filtering by date, conversation, folder, workspace, creator or language. USE WHEN: The general-purpose message reader; full bodies incl. transcript and AI summary. Max date span 183 days. `user_ids` filters by SENDER — for messages exchanged WITH someone, pass `conversation_id` from `list_conversations`. Use presigned URLs as-is. USE INSTEAD: `get_recent_messages` for a quick look at the latest few (hard-capped at 10, no paging). `search_message_ids` for filters this cannot express — notified state, mentions or labels. FIRST: `workspace_id` comes from `get_workspaces_basic_info` (field `id`) — call it first if you don't have one. EXAMPLE: {"workspace_id":"ws-abc","start_date":"2026-09-01T00:00:00Z","size":25} RETURNS: `{page, size, sort_direction, total, results_count, has_next_page, filters, results: [{id, transcript?, ai_summary?, audio_url?, creator_id, conversation_id?, duration_ms, reply_count, status, type, created_at, ...}]}`. Page on `has_next_page`/`total` — do not guess. NARROW: pass response_fields ["total","has_next_page","results.id","results.transcript","results.created_at"] unless you need more — the full payload is much larger.
Get one message by ID, optionally expanded with related records. USE WHEN: You have a message ID. `fields` ADDS related data (`conversation`, `creator`, `labels`) — it does not narrow the response. Use `response_fields` to narrow. USE INSTEAD: `list_messages` when you do not have an ID, or need several messages — it already returns full bodies, so fetching each one again is wasted. FIRST: `id` comes from `list_messages` (field `results[].id`) — call it first if you don't have one. EXAMPLE: {"id":"msg-abc","fields":"creator"} RETURNS: `{message: {id, transcript?, ai_summary?, audio_url?, creator_id, duration_ms, status, type, attachments?, created_at, ...}}`.
Get up to 10 of the most recent messages, each with its conversation, creator and labels. USE WHEN: A quick "what just happened" glance. Pre-joined, so no follow-up calls for creator or conversation names. USE INSTEAD: `list_messages` whenever you need more than 10, any date range, paging, or a filter other than conversation and language — this tool supports none of those. EXAMPLE: {"conversation_id":"conv-abc"} RETURNS: `{results: [{message: {...}, conversation: {...}, creator: {...}, labels: [...]}]}`. No total and no paging: the cap of 10 is the whole answer.
Post a message into an existing conversation, or reply in a thread. USE WHEN: You have a `conversation_id`. Pass `parent_id` (a message ID) to reply as a thread. Either `transcript` or `links` is required — the schema marks neither individually, so both param descriptions say so. USE INSTEAD: `create_direct_message` to reach people who are not already in a conversation. `create_voicememo_message` for a standalone memo. FIRST: `id` comes from `list_conversations` (field `results[].id`) — call it first if you don't have one. EXAMPLE: {"id":"conv-abc","transcript":"Agreed, shipping Friday."} RETURNS: `{message: {id, link, transcript?, status, type, conversation_id, created_at, ...}}`. ERROR BAD_REQUEST: Neither `transcript` nor `links` was provided. — Pass at least one of them.
Send a direct message to one or more people, by user ID or email. USE WHEN: Reaching people outside an existing conversation. Address it with `to.user_ids` or `to.emails`. Requires `transcript` or `links`. USE INSTEAD: `create_conversation_message` when a conversation already exists — a DM starts a separate thread rather than joining it. FIRST: `to.user_ids` comes from `search_users` (field `id`) — call it first if you don't have one. EXAMPLE: {"to":{"user_ids":["user-abc"]},"transcript":"Quick question about the deck."} RETURNS: `{message: {id, link, transcript?, status, conversation_id, created_at, ...}}`. ERROR BAD_REQUEST: A user ID is invalid, or neither `transcript` nor `links` was provided. — Resolve people with `search_users` — never pass a display name as a user ID — and include a transcript or links.
Create a voice memo, either from text (spoken via text-to-speech) or from an audio file at a URL. USE WHEN: Pass `transcript` (2-5000 chars) to have Carbon Voice speak the text, or an https `audio_url` to upload audio, which wins over `transcript`. File it with `folder_id`, whose type must match, or a `workspace_id`. USE INSTEAD: `create_conversation_message` to post into an existing conversation, or `create_direct_message` to send to specific people. A voice memo is standalone and lives in a folder or workspace. EXAMPLE: {"transcript":"Reminder to review the pricing deck before Friday."} RETURNS: `{message: {id, link, transcript?, audio_url?, duration_ms, status, type, created_at, ...}}`. `status` is often `processing` at first. ERROR INVALID_AUDIO_URL: `audio_url` is not https, unreachable, too large, timed out, embeds credentials, or resolves to a private address. — The message gives the reason. Use a public https URL, or pass `transcript` instead. ERROR BAD_REQUEST: None of `transcript`, `audio_url` or `links` was provided, or the transcript is outside 2-5000 characters. — Provide one of the three, and keep the transcript within the length limits.
Attach one or more link URLs to an existing message. USE WHEN: Adding external links to a message that already exists. USE INSTEAD: `create_message_share_link` to share a Carbon Voice message outward — that produces a link, this consumes them. FIRST: `id` comes from `list_messages` (field `results[].id`) — call it first if you don't have one. EXAMPLE: {"id":"msg-abc","links":["https://example.com/spec"]} RETURNS: `{...}` confirmation with the resulting attachments.
Get a user's full profile by ID — names, languages, voice settings, workspace roles. USE WHEN: You already have a user ID and need complete details. USE INSTEAD: `search_user` / `search_users` to FIND someone by email, phone or name. `get_current_user` for the caller — this tool needs an explicit ID and will not default to you. FIRST: `id` comes from `search_users` (field `id`) — call it first if you don't have one. EXAMPLE: {"id":"user-abc"} RETURNS: `{id, first_name, last_name?, languages, voice_gender, workspace_ids, workspace_roles, user_type, created_at, ...}`. NARROW: pass response_fields ["id","first_name","last_name","workspace_ids"] unless you need more — the full payload is much larger.
Find a single user by email, phone or name. USE WHEN: Resolving ONE person. Supply exactly one of `email`, `phone` or `name`. Name search only matches your own contacts. USE INSTEAD: `search_users` for several people in one call — it takes arrays and saves a round trip per person. `get_user` when you already have the ID. EXAMPLE: {"email":"someone@example.com"} RETURNS: `{id, full_name, first_name, last_name?, link, image_url?, languages?, ...}`. Use `id` wherever another tool asks for a user ID. ERROR NOT_FOUND: Nobody matched, or a name search hit a non-contact. — Try an email or phone instead of a name; name search is limited to your contacts.
Resolve several users at once by emails, phones, IDs or names. USE WHEN: Turning a list of people into user IDs in one call — the right first step before any tool that takes user IDs. Name search only matches your contacts. USE INSTEAD: `search_user` for a single lookup. `get_user` for a full profile once you have the ID. EXAMPLE: {"names":["Brett"]} RETURNS: Array of `{id, full_name, first_name, last_name?, link, languages?, ...}`. If a name returns more than one candidate, ask which person was meant rather than guessing. NARROW: pass response_fields ["id","full_name"] unless you need more — the full payload is much larger.
Get the calling user's own identity, workspaces and settings. USE WHEN: Establishing who you are acting as, or finding the caller’s workspace IDs before a workspace-scoped call. Takes no arguments. USE INSTEAD: `get_user` for somebody else (it requires an explicit ID). `get_workspaces_basic_info` if you only need workspace IDs and names — it is far smaller than this response. EXAMPLE: {} RETURNS: `{success, user: {user_guid, first_name, last_name?, email_txt?, phone_txt?, workspace_guids, identities, entries, environments, lifecycle_events, notification_settings, settings, ...}, settings: {...}}`. This payload is LARGE — several unbounded arrays and an open settings map. NARROW: pass response_fields ["user.user_guid","user.first_name","user.email_txt","user.workspace_guids"] unless you need more — the full payload is much larger.
List your conversations from the last 6 months, optionally filtered by participants, type and name. USE WHEN: Finding a `conversation_id`. Filter with `user_ids` plus `match`, `types` and `name` — YOUR DM with someone is `user_ids: ["<their id>"], types: ["directMessage"]`. You are always an implicit participant, so never pass your own ID. Filters AND together; if several match, ask which was meant. USE INSTEAD: `get_conversation` when you already have an ID and want full detail — this returns only id, name, workspace_id and type. EXAMPLE: {"user_ids":["user-abc"],"types":["directMessage"]} RETURNS: `{results_count, results: [{id, name, workspace_id, type}]}` where type is `directMessage` | `customerConversation` | `namedConversation` | `asyncMeeting`. No paging: `results_count` is what is returned, after filtering. `name` adds `unfiltered_count`: rows the name was matched against, AFTER `user_ids`/`types`. ALWAYS check it before saying no such conversation exists. 0 of 47 means none matched that string (misspelt, or >6 months old); 0 of 0 means your other filters left nothing — not that the caller has none. Widen the filters or ask; never report it does not exist.
Get one conversation by ID, with full metadata. USE WHEN: You have a `conversation_id` and need its description, visibility, owner or workspace name. USE INSTEAD: `get_conversation_users` for the participant list. `list_messages` with `conversation_id` for its messages — this returns neither. FIRST: `id` comes from `list_conversations` (field `results[].id`) — call it first if you don't have one. EXAMPLE: {"id":"conv-abc"} RETURNS: `{id, name, description?, link, workspace_id, workspace_name, owner_id, type, visibility, ...}`.
List the people in a conversation. USE WHEN: Finding out who is in a conversation, or collecting participant user IDs. USE INSTEAD: `search_users` to resolve people by name or email generally — this is scoped to one conversation. FIRST: `id` comes from `list_conversations` (field `results[].id`) — call it first if you don't have one. EXAMPLE: {"id":"conv-abc"} RETURNS: Array of user objects with `id` and profile fields. NARROW: pass response_fields ["id","full_name"] unless you need more — the full payload is much larger.
Summarize a conversation by running an AI Action over its recent messages. USE WHEN: You want a conversation summarized and have a `conversation_id`. Message selection is handled for you — omit `message_ids` and the most recent messages are used. USE INSTEAD: `run_ai_action` if you already know exactly which `message_ids` to process, or want a non-summary AI Action. FIRST: `prompt_id` comes from `list_ai_actions` (field `id`) — call it first if you don't have one. FIRST: `conversation_id` comes from `list_conversations` (field `results[].id`) — call it first if you don't have one. EXAMPLE: {"conversation_id":"conv-abc","prompt_id":"prompt-abc","limit":50} RETURNS: Same as `run_ai_action`: `{id, prompt_id, message_ids, responses: [{language, text?, markdown?, ...}], ...}`. ERROR BAD_REQUEST: `prompt_id` is not a valid AI Action ID. — Call `list_ai_actions` to get valid `prompt_id` values.
List the root folders of a workspace for a given folder type. USE WHEN: Orienting in the folder tree, or finding a `folder_id`. `type` is required: `voicememo` or `prerecorded`. `include_all_tree` returns nested folders too. USE INSTEAD: `get_folder` to inspect one folder. `get_folder_with_messages` when you want a folder’s messages rather than its structure. EXAMPLE: {"type":"voicememo","workspace_id":"ws-abc"} RETURNS: `{type, workspace_id?, include_all_tree?, sort_by, sort_direction, results: [{id, name, parent_folder_id?, subfolder_ids?, total_nested_folders_count, total_nested_messages_count, ...}]}`. Not paginated — this is the complete set. NARROW: pass response_fields ["results.id","results.name","results.total_nested_messages_count"] unless you need more — the full payload is much larger.
Create a folder in a workspace, optionally nested under another. USE WHEN: Organising memos. `name`, `type` and `workspace_id` are all required; add `parent_folder_id` to nest. USE INSTEAD: `move_folder` to relocate a folder that already exists. FIRST: `workspace_id` comes from `get_workspaces_basic_info` (field `id`) — call it first if you don't have one. EXAMPLE: {"name":"Q4 planning","type":"voicememo","workspace_id":"ws-abc"} RETURNS: The created folder, same shape as `get_folder`.
Get one folder's metadata and, optionally, its immediate subfolders. USE WHEN: Inspecting a folder. Set `include_first_level_tree: true` to get subfolders. Both `date` AND `direction` are silently ignored unless you do — upstream only documented that caveat on `date`. USE INSTEAD: `get_folder_with_messages` when you want the messages inside the folder — this returns structure and counts only. FIRST: `id` comes from `get_root_folders` (field `results[].id`) — call it first if you don't have one. EXAMPLE: {"id":"folder-abc","include_first_level_tree":true} RETURNS: `{id, name, type, workspace_id, parent_folder_id?, path?, subfolder_ids?, message_ids?, total_nested_folders_count, total_nested_messages_count, subfolders?, ...}`.
Get a folder together with the messages stored directly in it. USE WHEN: Reading a folder’s contents. Only messages at that folder’s own level are returned — nested folders are not walked. USE INSTEAD: `get_folder` for structure and counts without message bodies. `list_messages` with `folder_id` when you need date filtering or paging, which this tool does not support. FIRST: `id` comes from `get_root_folders` (field `results[].id`) — call it first if you don't have one. EXAMPLE: {"id":"folder-abc"} RETURNS: `{folder: {...}, messages: [{...}]}`. NARROW: pass response_fields ["folder.id","folder.name","messages"] unless you need more — the full payload is much larger.
Rename a folder. USE WHEN: Changing only the name. `name` is the sole editable field here. USE INSTEAD: `move_folder` to change where a folder sits in the tree. FIRST: `id` comes from `get_root_folders` (field `results[].id`) — call it first if you don't have one. EXAMPLE: {"id":"folder-abc","name":"Q4 planning (final)"} RETURNS: The updated folder, same shape as `get_folder`.
Permanently delete a folder, including every nested folder and all their messages. USE WHEN: Only when the whole subtree should be destroyed. This cascades and cannot be undone. USE INSTEAD: `move_folder` to get a folder out of the way, or `move_message_to_folder` to relocate its messages first. Check `total_nested_messages_count` via `get_folder` before calling — the cascade is easy to underestimate. FIRST: `id` comes from `get_root_folders` (field `results[].id`) — call it first if you don't have one. EXAMPLE: {"id":"folder-abc"} RETURNS: Deletion confirmation.
Move a folder into another folder, or up to a workspace root. USE WHEN: Relocating a folder. Pass `folder_id` for a new parent folder, or `workspace_id` to move it to the workspace root — one or the other, not both. USE INSTEAD: `update_folder_name` to rename in place. `move_message_to_folder` for a single message rather than a folder. FIRST: `id` comes from `get_root_folders` (field `results[].id`) — call it first if you don't have one. EXAMPLE: {"id":"folder-abc","folder_id":"folder-parent"} RETURNS: The moved folder, same shape as `get_folder`. ERROR BAD_REQUEST: Both `folder_id` and `workspace_id` were given, or neither, or the move would nest a folder inside itself. — Pass exactly one destination.
Move a message into a folder, or out to a workspace. USE WHEN: Filing a memo. Only `voicememo`/`prerecorded` messages you created can be moved, and the message type must match the folder's type. Pass exactly one of `folder_id` or `workspace_id`. USE INSTEAD: `move_folder` to relocate a whole folder. `create_voicememo_message` with `folder_id` to file a memo at creation time instead of moving it after. FIRST: `message_id` comes from `list_messages` (field `results[].id`) — call it first if you don't have one. EXAMPLE: {"message_id":"msg-abc","folder_id":"folder-abc"} RETURNS: The updated message with its new placement. ERROR BAD_REQUEST: The message type is not `voicememo`/`prerecorded`, it does not match the destination folder's type, or both/neither destination was given. — Check `type` via `get_message` and the folder type via `get_folder`; they must match. Pass exactly one destination.
List every workspace you belong to, as id and name only. USE WHEN: The cheapest way to resolve a workspace name to an ID before a workspace-scoped call. Takes no arguments. USE INSTEAD: `get_current_user` if you need more than ids and names — but note that response is much larger, so prefer this one when ids suffice. EXAMPLE: {} RETURNS: Array of `{id, name}`. Nothing else, and no paging.
List the AI Actions (Prompts) available to you — each has an `id` usable as `prompt_id`. USE WHEN: Before calling `run_ai_action` or `summarize_conversation`, to find a `prompt_id`. Also to show the user which AI Actions exist. Filter by `owner_type` (`user` = your own, `workspace` = shared, `system` = Carbon Voice built-ins). USE INSTEAD: `get_ai_action_responses` if you want results that were already generated rather than the list of available actions. EXAMPLE: {"owner_type":"system"} RETURNS: Array of `{id, name, description?, prompt, owner_type, workspace_id?, response_format?, created_at, last_updated_at}`. Use `id` as `prompt_id` elsewhere. NARROW: pass response_fields ["id","name","description","owner_type"] unless you need more — the full payload is much larger.
Run an AI Action (Prompt) against one or more specific messages. USE WHEN: You have concrete `message_ids` and a `prompt_id`, and want the AI Action applied to exactly those messages. USE INSTEAD: `summarize_conversation` if you want a whole conversation summarized and would otherwise have to list its messages yourself — it does that selection for you. FIRST: `prompt_id` comes from `list_ai_actions` (field `id`) — call it first if you don't have one. FIRST: `message_ids` comes from `list_messages` (field `results[].id`) — call it first if you don't have one. EXAMPLE: {"prompt_id":"prompt-abc","message_ids":["msg-1","msg-2"],"language":"english"} RETURNS: `{id, prompt_id, message_ids, creator_id, channel_id?, workspace_id?, responses: [{language, text?, markdown?, html?, json?}], created_at}`. The generated output is in `responses[]`, one entry per language. ERROR BAD_REQUEST: `prompt_id` or one of `message_ids` is not a valid ID. — Call `list_ai_actions` for valid `prompt_id` values and `list_messages` for valid message IDs; do not retry with the same IDs.
Run an AI Action (Prompt) against one or more shared messages, addressed by share link ID. USE WHEN: You have share link IDs — from `create_message_share_link`, or a link someone gave you — and want an AI Action applied to the messages behind them. USE INSTEAD: `run_ai_action` if you have the message IDs directly; going through a share link adds nothing when you already have access. FIRST: `share_link_ids` comes from `create_message_share_link` (field `id`) — call it first if you don't have one. FIRST: `prompt_id` comes from `list_ai_actions` (field `id`) — call it first if you don't have one. EXAMPLE: {"prompt_id":"prompt-abc","share_link_ids":["share-abc"]} RETURNS: `{share_link_ids, responses: [{language, text?, markdown?, html?, json?}], ...}`. ERROR NOT_FOUND: A share link ID does not exist, or its access has been revoked or expired. — Create a fresh link with `create_message_share_link`, or verify the ID with `get_message_share_link`.
Retrieve AI Action results that were generated previously. USE WHEN: You want existing output rather than a fresh run — e.g. showing what an AI Action already produced for a message or conversation. Combine `prompt_id`, `message_id` and `channel_id` to narrow. USE INSTEAD: `run_ai_action` (or `summarize_conversation`) to generate new output; this tool only reads what already exists and returns an empty array if nothing has been generated. EXAMPLE: {"channel_id":"conv-abc","limit":10} RETURNS: Array of `{id, prompt_id, creator_id, message_ids, channel_id?, workspace_id?, responses: [{language, text?, markdown?, html?, json?}], created_at}`. NARROW: pass response_fields ["id","prompt_id","responses"] unless you need more — the full payload is much larger.
Create a shareable link to an existing message (e.g. a voice memo), and get the URL back. USE WHEN: `share_type: "link"` for a shareable URL; `"forward"` to attach the share onto another message (also needs `message_id`). Also how you get a `share_link_ids` value. USE INSTEAD: `add_attachments_to_message` to attach an external URL to a message, rather than share a Carbon Voice message outward. FIRST: `shared_message_id` comes from `list_messages` (field `results[].id`) — call it first if you don't have one. EXAMPLE: {"shared_message_id":"msg-abc","share_type":"link","access_type":"public"} RETURNS: `{id, link, share_type, access_type, created_by, specified_access?, end_access_at?, revoked_at?, shared_message: {...}}`. `link` is the URL to hand out; `id` is the `share_link_ids` value other tools take. NARROW: pass response_fields ["id","link","share_type","access_type"] unless you need more — the full payload is much larger. ERROR BAD_REQUEST: `shared_message_id` is invalid, or `share_type` is "forward" without `message_id`. — Confirm the ID with `get_message`; when forwarding, also pass `message_id`.
Look up an existing message share link by its ID, including the message behind it. USE WHEN: You have a share link ID and want its URL, access settings, or whether it is still valid — check `revoked_at` and `end_access_at` before relying on it. USE INSTEAD: `create_message_share_link` to make a new link; this only reads existing ones. `get_message` if you have the message ID and do not care about the share. FIRST: `share_link_id` comes from `create_message_share_link` (field `id`) — call it first if you don't have one. EXAMPLE: {"share_link_id":"share-abc"} RETURNS: Same shape as `create_message_share_link`: `{id, link, share_type, access_type, revoked_at?, end_access_at?, shared_message: {...}, ...}`. NARROW: pass response_fields ["id","link","revoked_at","end_access_at"] unless you need more — the full payload is much larger. ERROR NOT_FOUND: No share link with that ID, or it is no longer accessible. — Create a new one with `create_message_share_link`; do not retry the same ID.
List your action items across every conversation and folder: those assigned to you, PLUS unassigned ones you created. USE WHEN: Answering "what do I owe / what is on my plate" — but check `assigned_to` before calling something the user's own commitment: a null one is an item they raised that nobody has picked up. Filter by `status` (`todo` open, `suggested` AI-proposed, `done` complete). Page with `starting_after`. USE INSTEAD: `list_action_items` when you want one specific conversation or folder rather than everything of yours. EXAMPLE: {"status":"todo","limit":25} RETURNS: `{results: [{id, title, status, notes_text?, assigned_to?, due_date?, container_id?, container_type?, source_message_id?, creator_id, ...}], total?, results_count?, has_more?, next_cursor?, filters?}`. Keep paging while `has_more` is true, passing `next_cursor` as `starting_after`. NARROW: pass response_fields ["results.id","results.title","results.status","results.assigned_to","results.due_date"] unless you need more — the full payload is much larger.
List action items belonging to one container — a conversation, a folder, or home. USE WHEN: You have a `container_id` and want its action items. `container_type` is `channel` for a conversation, `folder` for a folder, or `home`. A conversation id is NOT a folder id — resolve `container_id` with the tool matching your `container_type`. Filter by `status` or `assigned_to` (pass the string `null` for unassigned). USE INSTEAD: `list_my_action_items` for everything assigned to you regardless of where it lives. FIRST: `container_id` comes from `list_conversations` (field `results[].id`) when `container_type` is `channel` — call it first if you don't have one. FIRST: `container_id` comes from `get_root_folders` (field `results[].id`) when `container_type` is `folder` — call it first if you don't have one. EXAMPLE: {"container_type":"channel","container_id":"conv-abc","status":"todo"} RETURNS: Same as `list_my_action_items`: `{results: [...], total?, results_count?, has_more?, next_cursor?, filters?}`. NARROW: pass response_fields ["results.id","results.title","results.status","results.assigned_to"] unless you need more — the full payload is much larger.
Get one action item by its ID, with full detail. USE WHEN: You have an action item ID and need its notes, assignee, due date, or source message. USE INSTEAD: `list_my_action_items` or `list_action_items` if you do not have an ID yet — they already return the same fields per item, so a follow-up call is usually wasted. FIRST: `id` comes from `list_my_action_items` (field `results[].id`) — call it first if you don't have one. EXAMPLE: {"id":"ai-abc"} RETURNS: `{id, title, status, notes_text?, creator_id, assigned_to?, due_date?, container_id?, container_type?, source_message_id?, last_updated_by, ...}`.
Create an action item, optionally attached to a conversation or folder. USE WHEN: Recording a task. Only `title` is required. Attach it by passing both `container_type` and `container_id`, and link it to what prompted it with `source_message_id`. USE INSTEAD: `suggest_action_items_from_messages` to have tasks extracted from message content automatically instead of writing each one yourself. EXAMPLE: {"title":"Send the pricing deck","assigned_to":"user-abc"} RETURNS: `{id, title, status, notes_text?, assigned_to?, due_date?, container_id?, container_type?, creator_id, ...}`. New items start at status `todo`. ERROR BAD_REQUEST: `assigned_to` is not a valid user ID, or `container_id` does not match `container_type`. — Resolve people with `search_users` (never pass a name) and containers with `list_conversations` or `get_root_folders`.
Change an action item's title, notes, assignee, or due date. USE WHEN: Editing item content. Send only the fields you want changed — omitted fields are left as they are. USE INSTEAD: `set_action_item_status` to move an item between `todo` / `done` / `suggested`; status is not editable here. FIRST: `id` comes from `list_my_action_items` (field `results[].id`) — call it first if you don't have one. EXAMPLE: {"id":"ai-abc","due_date":"2026-10-01"} RETURNS: The updated action item, same shape as `get_action_item`.
Move an action item between `suggested`, `todo` and `done`. USE WHEN: Completing an item (`done`), reopening it (`todo`), or accepting an AI-suggested item by promoting it from `suggested` to `todo`. USE INSTEAD: `update_action_item` for title, notes, assignee or due date; this tool only sets status. FIRST: `id` comes from `list_my_action_items` (field `results[].id`) — call it first if you don't have one. EXAMPLE: {"id":"ai-abc","status":"done"} RETURNS: The updated action item, same shape as `get_action_item`.
Permanently delete an action item. USE WHEN: The item was created in error and should not exist at all. USE INSTEAD: `set_action_item_status` with `done` to complete an item — that keeps the record. Deleting cannot be undone, so prefer it only when the item is genuinely spurious. FIRST: `id` comes from `list_my_action_items` (field `results[].id`) — call it first if you don't have one. EXAMPLE: {"id":"ai-abc"} RETURNS: Deletion confirmation for the removed item.
Extract action items from ONE message and return them immediately. USE WHEN: Turning a single message into tasks — "what did she ask me to do?". The items come back in this call, already saved with `status: "suggested"`; promote the ones you want with `set_action_item_status`. Prefer this over `suggest_action_items_from_messages` whenever there is exactly one message, since it needs no polling. USE INSTEAD: `suggest_action_items_from_messages` for two or more messages — it reasons over the whole set at once and can catch commitments that span messages, which calling this tool repeatedly cannot. `create_action_item` when you already know the task. FIRST: `message_id` comes from `list_messages` (field `results[].id`) — call it first if you don't have one. EXAMPLE: {"message_id":"msg-1"} RETURNS: Array of the created action items, each with `id`, `title`, `assigned_to`, `due_date`, `notes_text` and `status: "suggested"`. An empty array means the model found nothing actionable. Runs the extraction inline, so expect this call to take a few seconds.
Queue AI extraction of candidate action items from specific messages. Runs in the background. USE WHEN: Turning a conversation into tasks — "what did we agree to?". Pass the `message_ids` to analyse, then POLL `list_my_action_items` or `list_action_items` with `status: "suggested"` for the results, and promote the ones you want with `set_action_item_status`. USE INSTEAD: `suggest_action_items_from_message` (singular) for a SINGLE message — it returns the items directly, with no polling. `create_action_item` when you already know the task and do not need it inferred — that returns the item synchronously, with an id. FIRST: `message_ids` comes from `list_messages` (field `results[].id`) — call it first if you don't have one. EXAMPLE: {"message_ids":["msg-1","msg-2"]} RETURNS: ACKNOWLEDGEMENT ONLY — no items are returned. Extraction is queued and runs in the background, so poll a listing tool with `status: "suggested"` to see the results.
Find message IDs by notified state, mentions, labels, creator, conversation or workspace — returning IDs plus cursor metadata. USE WHEN: Any filter `list_messages` cannot express: whether you were notified (`notified_status`), whether you were tagged (`tagged_user_ids`), or by `label_ids`. Cheap in tokens because it returns IDs only — hydrate the ones you need with `get_message`. USE INSTEAD: `list_messages` when a date range, conversation or workspace filter is all you need and you want full message bodies in one call. `search_messages_by_heard_status` for unread/listened state, which this tool cannot filter on. EXAMPLE: {"notified_status":"notified","limit":50} RETURNS: `{ids: [{...}], has_more, next_cursor?}`. Keep paging while `has_more` is true, passing `next_cursor` back as `next_cursor`. ERROR BAD_REQUEST: An ID list contains names rather than IDs, or exceeds 50 entries. — Resolve people to IDs with `search_users` and conversations with `list_conversations`; split lists longer than 50.
Find messages by whether you have listened to them, and get per-conversation unheard counts. USE WHEN: "What have I not listened to yet" / "catch me up". `heardStatus: "unheard"` is the unread filter. The response also carries `unheard_counts_by_channel`, so you can prioritise conversations without fetching their messages. USE INSTEAD: `search_message_ids` for notified state, mentions or date anchors — this tool accepts no date filter (see note below). `list_messages` for plain recent history. EXAMPLE: {"heardStatus":"unheard","limit":25} RETURNS: `{messages: [{message_guid, creator_guid, creator_first_name, channel_guids, transcript_txt, message_ts, heard_status, ...}], unheard_counts_by_channel: {conversation_id: count}, success}`. Use `unheard_counts_by_channel` to decide where to look first. NARROW: pass response_fields ["unheard_counts_by_channel","messages.message_guid","messages.channel_guids","messages.creator_first_name","messages.transcript_txt","messages.message_ts"] unless you need more — the full payload is much larger.
List your inbox notifications, with a total unread count. USE WHEN: Answering "what did I miss" or "where was I mentioned" — pass `category: "mentions"` for mentions. The response includes `total_unread`, so you can report a count without paging. USE INSTEAD: `search_message_ids` with `notified_status` if you want the messages themselves rather than notification records. `search_messages_by_heard_status` for unlistened messages. EXAMPLE: {"category":"mentions","limit":25} RETURNS: `{results: [...], total_results, total_unread, filters}`. Pages with `skip`/`limit`, not cursors. NARROW: pass response_fields ["results","total_unread","total_results"] unless you need more — the full payload is much larger.
One endpoint, the same key, whichever client you use.
~/Library/Application Support/Claude/claude_desktop_config.json (Mac) · %APPDATA%\Claude\claude_desktop_config.json (Windows)
Replace API_KEY with your own key.
Already have an "mcpServers" section in your config? Just add the server entry inside it.
Discovery, routing, credentials, tool scoping and execution logs all happen at the gateway→connections stay ACTIVE with no work from you
Carbon Voice MCP runs through a gateway that holds the credentials, scopes the access and records every call.
Managed auth, hosted MCP servers, and every Gmail tool your agent needs.
Free to start.