Clarify
Connect Clarify and work with your CRM in natural language. Retrieve, create, and update records, analyze your pipeline, and generate insights—all through conversation. Query deals, companies, people, and meetings without switching apps. Find and import new leads that match your ICP directly from the conversation. Create tasks, lists, and outbound sequences automatically. Build and configure Clarify Agents from ChatGPT to automate your entire GTM motion. Your Clarify data is retrieved in real-time, and permissions are respected. Stop clicking through interfaces. Just ask what you need.
- Integration type
- Plugin
- Verification status
- Not applicable
- Platform
- ChatGPT
- Primary Subcategory
- B2B Sales CRM Platforms
- Secondary Subcategories
- Business Data & Low-Code AppsCRM Systems
- Brand
- Clarify
- Access
- Account required
- First tracked
- 2026-04-24
- Tool count
- 29
- Geography
- US
The Primary Subcategory used for this profile’s headline score.
Other Subcategories where the Integration is visible.
ChatGPT Plugin Discoverability Score
ChatGPT organic discovery is not live yet
Clarify is tracked in the ChatGPT Plugin registry. Public organic-discovery measurement is not live for ChatGPT yet, so there is no score to publish today.
Get notified when your score goes live
Enter your work email and we’ll notify you when ChatGPT Plugin organic discovery scoring launches.
No spam. Unsubscribe any time.
Competing in ChatGPT B2B Sales CRM Platforms
View CategoryHow the Discoverability Score works
Organic discovery scoring for Clarify on ChatGPT is not live yet. The score will use measured agent conversations when it launches.
Organic discovery scoring is pending. Your Plugin score will appear on this scale when measurement goes live.
FoundDiagnostic
Whether Claude found your Plugin in connector search. It must be Found before it can reach the picker, but the score counts picker appearances—not search results.
PickedMain score
How often your Plugin appeared in the picker, or Claude invoked it directly, across contested conversations. This percentage is the Discoverability Score; the headline number is rounded.
PositionedDiagnostic
What position your Plugin appeared in when it was shown in the picker. This shows prominence, but it does not affect the score.
29 tools agents can invoke
Add a comment to a supported entity. ## Important notes After adding a comment, tell the user what was commented on in a friendly, conversational way.
add-comment
Create a new email campaign (sequence) in DRAFT mode. To edit an existing campaign, use the update-campaign tool instead. 📖 **For comprehensive campaign rules and examples**: Use `read-context` with context: "campaign-docs" ## When to use this tool - When the user asks to create a new email campaign or sequence - When duplicating a campaign (first fetch it with `get-campaigns`, then call this tool with the email steps) ## Structuring the campaign Structure email steps with subjects, bodies (HTML), and timing. Campaigns start in DRAFT mode. When drafting campaign emails: - Keep subject lines under 50 characters, conversational, not overly formal - Use a warm, conversational tone while remaining professional - Be concise and include a clear call to action if appropriate - Start with a friendly greeting and end with a warm closing ## Essential rules 1. **Email/Delay Pattern**: Emails and delays MUST alternate. Always specify `delay_after_days` on each step 2. **First email**: `delay_after_days: 0`. Subsequent emails: minimum 1 day 3. **Campaigns are Workflows**: Use this tool for campaigns, not generic workflow tools 4. Campaign emails are templates sent to multiple people 5. Use variable placeholders: {{path||fallback}} with fallback, or {{path}} without fallback 6. Only use the variables listed below (do not invent variables) ## Threading: send_as_reply Set `send_as_reply: true` on a step to thread it as a reply under the previous step (uses the prior message id and "Re: <previous subject>"). Use it when a follow-up bumps the prior email and the recipient should see it inline: - **Use** `send_as_reply: true` for content like "circling back", "just bumping this", "wanted to follow up", or any step that explicitly references the prior email's call to action without a new pitch. - **Leave it off (default)** when the step introduces a new angle, case study, or call to action; recipients triage by subject, and a fresh subject signals fresh content. Rules: - Step 1 always starts a new thread (`send_as_reply` must be false / omitted). - When `send_as_reply: true`, the step's `subject` is ignored at send time (the previous step's subject is reused with "Re: " prepended), but you must still provide a sensible `subject` for storage. Fallback rules (fallbacks make emails feel natural when data is missing): - ALWAYS include fallbacks for human-identifiable information: → Person names (first_name, last_name, full_name) → use "there", "Friend", etc. → Company/organization names → use "your company", "your organization", "your team" → Job titles and roles → use "your role", etc. → Location/city names → use "your area", "your region", etc. - Skip fallbacks ONLY for technical/structured data: → URLs, email addresses, phone numbers → Dates, timestamps, IDs → Numerical values and metrics ## Examples <example> Create a campaign: { "campaign_name": "New Leads Nurture", "email_steps": [ { "subject": "Welcome, {{person.name.first_name||there}}", "body": "<p>Hi {{person.name.first_name||there}},</p><p>Thanks for your interest!</p>", "delay_after_days": 0 }, { "subject": "Resources for {{person.company_id.name||your team}}", "body": "<p>Hey {{person.name.first_name||there}},</p><p>Here are some helpful resources...</p>", "delay_after_days": 3 } ] } </example> ## Important notes - List is optional for drafts (can be selected later in UI before activation) - Email account connection is optional for drafts (required before activating) - Campaigns are created in DRAFT mode (enabled: false) - delay_after_days: 0 = immediate, 3 = day 3, 7 = day 7 - Minimum 1 day enforced between consecutive emails automatically - Body should be formatted as HTML. Put each paragraph in its own <p> tag; paragraphs render with a blank line between them. Use <br/> only for a hard line break within a paragraph (e.g. between signature lines) - When describing timing to users, say "immediately" for delay_after_days = 0, "on day 3" for delay_after_days = 3
create-campaign
Create or update an agent, an autonomous AI assistant. ## When to use this tool - When the user asks you to create or update an agent ## Before you build: is an agent the right tool? Run these checks before planning. If one fails, say so and propose the better path rather than building a degraded agent. **1. Can the platform do it?** The triggers below are the only events an agent can respond to. Notably, there is no delay trigger: "do X three days after event Y" must be re-expressed as a recurring schedule that queries for records that have reached that state. Also confirm the request fits the cadence, delivery-channel, and data-access limits documented in the planning and trigger sections below. **2. Is an agent the right fit?** An agent is right whenever a task needs to run automatically on a trigger and involves any reasoning, including per-record classification like "categorize as ICP" or "classify meeting type." One ask isn't an agent: "keep a collection of records matching criteria" is a dynamic list. Scope every agent tightly (see "Narrow the scope with filters" below): ask how the relevant records enter Clarify, and if they share a label or marker, suggest a dynamic list and an `On add to list` trigger so the agent runs only for that subset rather than every record. For enrichment, use `find-leads`/`import-leads` for net-new prospects and a filtered agent for existing records. ## Before creating a new agent Do not immediately create an agent when the user asks. First, have a brief conversation to understand their goal and tailor the agent to their needs. Ask the user clarifying questions. 1. **Check for existing agents**: Use `get-agents` to see what agents already exist in the workspace. This is a silent background check; if no agents exist, do not acknowledge it; just continue. If an agent already covers the requested use case, point the user to it instead of creating a duplicate. If one is close, mention it and ask whether they'd like to update the existing agent or create a separate one. You can only update or delete an agent you created. `get-agents` shows each agent's `Owner` (name and email); you own an agent when its Owner is you. Never pass the `agentId` of an agent owned by someone else; updating it will be rejected. When the only close match belongs to another person, create a new agent for this user instead of trying to update theirs. 2. **Understand the use case**: Ask what they want the agent to do, what triggers it, and what the expected outcome is. Dig into vague requirements, e.g., if the user says "categorize people as ICP", ask what their ICP definition is (unless the org description already defines it). If they say "update fields after meetings", ask which fields. 3. **Narrow the scope, or batch on a schedule**: When a trigger would fire on every record of a type (e.g., all person creates, all deal updates), confirm the user actually wants that broad scope. Each agent run uses resources, so reducing unnecessary runs matters. Two levers: - **Filters** (specific lists, field conditions, record types) keep the per-event trigger but skip runs that don't match. - **A schedule trigger** replaces many per-event runs with one recurring run that queries for the records needing work and processes them in a batch — far fewer runs. Prefer this only when the work tolerates a delay (up to the schedule interval) and the target records can be found by a query (they've reached some state), not when the agent must react immediately (e.g., alert the moment a deal is won) or needs the change event's before/after values. 4. **Warn about data visibility**: Agents run with the creating user's permissions. They can only see emails the user sent, received, or was CC'd on, and meetings the user is a participant of, unless teammates have shared access to meetings and emails via Settings. Tell the user this when creating agents that process emails or meetings, and suggest they ask teammates to share access via Settings if the agent needs broader visibility. 5. **Suggest complementary agents**: If the agent they want would benefit from a prerequisite agent (e.g., they want post-meeting CRM updates but don't have an agent that classifies meeting types first), mention that and offer to create both. 6. **Consider delivery channel**: If the agent produces output that the user needs to see (e.g., pipeline reviews, daily summaries, alerts when deals change stage), ask how they want it delivered. The only supported delivery channels are: - **Slack** (via Slack MCP connector): post to a channel or DM. Most common for scheduled reports and alerts. - **Email**: send a summary email to the user or a distribution list. Do not suggest delivery channels that don't exist (e.g., posting in chat, push notifications, in-app alerts). Only ask about delivery when the agent produces user-facing output, not for agents that silently update CRM fields. 7. **Select tools and connectors**: Based on the agent's purpose, set `tools` and `mcp_servers` to explicitly list every tool the agent needs. Only listed tools are available; omitted tools cannot be used. For each tool, set its permission: - `always_allow`: the agent can use this tool without asking - `needs_approval`: the agent must ask the user before using this tool Set read-only tools (e.g., `query-data`, `get-records`) to `always_allow` by default. **Current state vs. history**: `query-data` reads the live CRM (PostgreSQL): current field values, open deals, this week's pipeline. It cannot see how a record changed over time. For any question about change history or point-in-time state (stage transitions, deals that moved to a later stage this week, time-in-stage, records created or updated per day, who changed a field and when) grant `query-analytics`, which queries the analytics event log of every CRM change (including historical stage transitions). Never have an agent approximate history from `_updated_at` or claim stage transitions aren't stored; they are recorded and queryable via `query-analytics`. For write tools (e.g., `create-or-update-records`, `send-email`), always ask the user whether they want the agent to use them freely or require approval each time. Present it concisely: "Should the agent update records automatically, or ask you first?" If the agent needs an MCP connector (e.g., Slack for posting messages, Linear for issue tracking), add it to `mcp_servers` and list the specific tools needed with their permissions. Server IDs come from existing agent configurations (via `get-agents`); calling with an unknown server ID returns an error listing the workspace's available MCP servers and their IDs. **Connect connectors before saving**: an agent can only use an MCP connector the user has actually connected. Before calling this tool with any `mcp_servers`, first check their status with `get-mcp-servers`. For any required connector that is not connected, call `connect-mcp-server` and wait for the user to finish connecting it (re-check with `get-mcp-servers`) before saving the agent. Never save the agent while a required connector is still unconnected; saving will fail validation, and the user should connect everything first. If the user must connect multiple connectors, get them all connected before the single create/update call. **Replacement semantics**: both `tools` and `mcp_servers` are full replacements, not merges. When updating an agent, always include the complete set of tools and servers the agent needs, not just the ones being added. Omitting a previously configured tool removes it. To keep existing tools unchanged, omit the `tools` / `mcp_servers` field entirely. When adding a tool to an existing agent, first use `get-agents` to read the current configuration, then pass the full set including the new addition. Whenever you change an agent's tools, review its instructions in the same update and revise any step that no longer matches the available tools, so instructions and tools never drift apart. **Tool dependencies**: some tools require other tools to function. If you include any tool on the left, you must also include the tool(s) on the right: - `query-data` → `get-schema` (the agent needs to load CRM schema before writing SQL) - `query-analytics` → `get-schema` (the agent needs CRM field names to read them out of the event log) - `create-or-update-records` → `get-schema` (the agent needs to discover writable field names) - `create-or-update-list` → `get-schema` (lists are defined by SQL queries that need schema knowledge) - `import-leads` → `find-leads` (importing requires search context from a find-leads search) **Write instructions in plain language**: describe what the agent should do ("look up the person record", "update the company", "post to the channel"), never the literal tool name or MCP tool ID. Tool names can change, and naming them in the instructions wastes a tool-search step and can confuse the agent. The `tools` and `mcp_servers` fields decide what the agent can use; the instructions only describe the goal. 8. **Choose a model**: Set `model` from the task's complexity yourself. Use `fast` for simple, narrow, deterministic work (classification, field extraction, tagging/routing, short summaries) and high-volume triggers — it is faster and meaningfully cheaper. Use `smart` (the default) for multi-step reasoning, judgment, or quality-sensitive writing like emails and summaries. Only ask the user to pick when the task's complexity is genuinely ambiguous. Skip this planning step when the user provides a fully detailed specification upfront (trigger, instructions, and expected behavior). When updating an existing agent, skip planning unless the request is vague and you need more context to make the right change. ## Backfill existing records Users often want an agent to process existing records, not just future ones. When they mention "backfill", "run on existing data", or "process all current records": Do not suggest creating a separate backfill agent. Instead, add backfill capability to the same agent by including an hourly schedule trigger alongside the event-based triggers. The agent's instructions should include a backfill section that: 1. Queries for unprocessed records in batches each run. 2. Tracks progress by filtering for records that still need processing. 3. When all records are processed, the agent edits its own instructions and triggers to remove the backfill logic and the schedule trigger, keeping only the event-based triggers for ongoing processing. 4. Notifies the user via Slack that backfill is complete. ## Popular agent ideas When the user is exploring what agents can do or has a vague request, suggest ideas from this list if they are relevant to the user's business context. These are just starting points; only suggest ones that make sense for the user's situation: - **Post-meeting CRM update**: After a meeting transcript is ready, extract key takeaways, update deal stage, next steps, and relevant fields on the associated records. - **Meeting type classification**: When a meeting is created, classify it (e.g., discovery, demo, negotiation, check-in) based on attendees, title, and context, then set the meeting type field. - **Post-email CRM update**: After an important email is received or sent, update relevant deal or contact fields (e.g., sentiment, next action). - **Weekly pipeline review** (Slack): Summarize pipeline changes, stale deals, and upcoming closes, then post to a Slack channel. - **Deal stage change alert** (Slack): When a deal moves to a key stage (e.g., closed-won, closed-lost), post a summary to Slack. ## After creating or updating an agent Once the agent is fully configured and ready to use, suggest a test run to the user. Do this after both creating and updating an agent. If you are still configuring it across multiple steps, wait until it is complete before suggesting a run. When you tell the user the agent is ready, also tell them which model tier it runs on (`fast` or `smart`) and why you chose it, so the model choice is never silent. On an update, mention the tier only when you changed it. When you have just created a new agent, also give the user a brief heads-up that running an agent consumes variable credits with a 1-credit minimum, and link the pricing docs: https://docs.clarify.ai/en/articles/15193255-agents#h_bbf4c7aa36. For an existing agent that has run at least once, use `get-agents` to inspect its stats and use Avg Credits/Run as its typical per-run cost. If an agent has no run history, say there is no cost history yet and do not estimate or provide a cost range. ## When the user reports an agent issue If the user says an agent isn't working or asks why it did something unexpected, do not immediately change the agent's instructions. First, consider: 1. Check whether the agent's current instructions already address the issue. If they don't, you can update them directly. 2. If the instructions look correct, use `get-agent-runs` to fetch the most recent run transcript and diagnose what actually happened. 3. After diagnosing, explain your findings and suggest targeted fixes. Then offer to rerun. When the reported problem is a wrong or broken record URL, do not repair or guess a corrected URL from the existing instructions; that text is not a source of truth for URLs. Take the correct URL from tool output, such as the `Link:` in the run transcript (via `get-agent-runs`), and copy it exactly, or rewrite the instructions to have the agent use the `Link:` its own tools return.
create-or-update-agent
Create new fields or update existing field metadata (name, options, AI config) on any entity (company, person, deal, meeting, or custom object). Does not support changing an existing field's type or nullable/required status. Also known as custom fields. Rename fields, add enum/select options (existing options are preserved; pass an empty options array to clear all), change option colors, and configure AI autofill. Supports all field types: text, number, currency, date, enum, multi-select, markdown, attachment, and relationships. You must use `read-context` with context="field-docs" before creating or updating fields to load field type guidelines, AI prompt writing instructions, and examples. ## Important notes - Field deletion is permanent. Clarify cannot restore or recover a deleted field. Never tell a user a deleted field can be brought back. - If a user asks to restore or undelete a field, explain it can't be recovered and offer to create a new field with that name instead: it will be a fresh, separate field. ## Board / Kanban views group by an existing field A board (Kanban) view groups records into columns by one specific enum field, usually the object's built-in `status` field, or the field configured for that board. Creating a new enum field (e.g. a custom `stage`) does NOT change which field the board groups by, and you cannot see a view's board-grouping configuration from here. - When a user wants records to move between board columns, set values on the field the board already groups by (commonly `status`), or ask the user which field the board should group by. Do not create a parallel new enum and tell the user the board will use it. - If records don't land in the expected column but the data reads back correctly, do not blame a display, refresh, or caching issue. The likely cause is that the board groups by a different field than the one you set.
create-or-update-fields
Create a new custom object type or update an existing one in the workspace. ## When to use this tool - When the user asks to create a new custom object type - When the user asks to update the name, plural label, description, or avatar (icon and background color) of an existing custom object ## How it works - Without entity: creates a new custom object (name, plural, and description are required) - With entity: updates the specified custom object's metadata, including its avatar icon and background color ## Recommended workflow for create 1. Discuss with the user what the custom object represents 2. Ask for the object name and suggest 2-3 plausible names based on the conversation (e.g., "Contract", "Ticket", "Project"). Confirm plural and description with the user as well. 3. Call this tool to create the object 4. Immediately call `create-or-update-fields` with the returned entity identifier to add fields ## Description guidance The description field is stored as AI context for this object type, directly influencing how AI understands and works with records of this type. A good description captures what business concept this object represents and how the team uses it. Before creating a new object, if the user has not provided a description, ask them for one. Work with them to craft a description that captures the object's purpose. Only proceed without a description if the user explicitly declines.
create-or-update-custom-object
Create or update a dynamic list: a saved view whose membership is defined by a SQL query. Dynamic lists are the only list type Clarify supports today. ## When to use this tool - To create a new list: omit list_id and provide title + sql - To update an existing list: provide list_id along with the fields to change ## If the user asks for a "static list" or to manually curate a list Static lists are deprecated. Clarify no longer offers them as a list type, although some legacy static lists may still exist in the workspace. Do not call this tool to create one. Tell the user static lists are no longer supported, then offer the workaround: add a label to the records they want to track, and create a dynamic list that filters on that label. All built-in entities (companies, people, deals) have a built-in `labels` field with color-coded user-defined values, so the user can add or remove the label to control list membership manually. ## SQL restrictions This tool supports a restricted SQL subset. The query is used for both display and counting (via COUNT(*) wrapping), so certain patterns are incompatible: - **SELECT ***: Use explicit, table-qualified column names (e.g., "SELECT person.name, person.email FROM person") - **DISTINCT ON**: Not compatible with COUNT(*) wrapping. Use GROUP BY for deduplication instead. - **CTEs (WITH ... AS)**: Not supported. Rewrite using JOINs or WHERE clauses. - **Subqueries in FROM**: SELECT ... FROM (subquery) is not supported. Query the table directly with WHERE/JOIN clauses. - **LIMIT / OFFSET**: Not supported. A dynamic list is a continuously-evaluated membership filter, not a ranked snapshot; the LIMIT is dropped during evaluation, so a "top N" query silently matches every qualifying record. To bound a list, narrow membership with a WHERE clause (e.g. a recency window like "meeting.start > NOW() - INTERVAL '30 days'") or have the user add a label and filter on it. ORDER BY is fine; it sets the default sort, not membership. - Column names must be table-qualified (e.g., "person.name" not just "name") - Aggregate functions in ORDER BY or HAVING must also appear in the SELECT clause - **Filtering labels / multi-select fields**: these store JSONB as `{items: string[]}`. Filter membership with the array-overlap operator, e.g. `(person.labels -> 'items') ?| ARRAY['BDR Prospect']`. Do not use the `@>` containment operator; it works in the database but the list UI cannot render or edit a filter built with it. ## Filtering by the current user To make a list relative to whoever is viewing it (e.g. "my tasks", "deals I own"), compare against current_setting('app.current_user_id', TRUE), which resolves to the viewing user's ID at query time so the list works for every viewer. - Use it directly in a WHERE clause: WHERE task.assignee_id = current_setting('app.current_user_id', TRUE) - Do not hardcode a specific user's ID, and do not use template tokens like {{current_user_id}}; they are not substituted and the list will match nothing. ## Important notes - Only the fields you provide will be updated; omitted fields remain unchanged - The tool validates both the main query and count query to ensure the list will work when opened - Saving applies and publishes the list automatically; there is no separate publish step, so if the user asks to "publish" a list you built, the save already did it. Only if the tool response says the changes were left as a pending draft, direct the user to the "Publish" / "Apply changes" control on the list page using the link from the response. Never construct or surface a list-version URL as a publish action. ## Column choice for list views Lists render one row per root record, so every SELECT column must belong to the root entity. - The record-title column (the primary field, aliased `<entity>:__object__`) is always shown and cannot be removed through the list query; the backend re-adds it on save. Do not tell the user you removed the title column. Which columns are visible and their order is a per-view UI setting the user controls from the list header's column menu, not something this tool changes. - Prefer scalar fields on the root entity (e.g. `person.name`, `person.email`). - Do not select the record's `_id` (shown as "Record ID") column unless the user explicitly asks for it. The record identity is always available on the row; surfacing the raw UUID as a column is rarely useful and clutters the list. - For relationships, only include `*_id` foreign-key columns that exist on the root table (e.g. `person.company_id`). - Do not SELECT columns from many-to-many related tables. Joining through a join table produces duplicate rows, and the list view can't render an m2m relationship as a single cell. - If the user explicitly asks to display a many-to-many field as a column, explain that lists only show direct fields on the root record, and the full relationship is available on the record detail page. After creating or updating a list: 1. Tell the user what was created or updated in a friendly, conversational way 2. Provide a clickable markdown link to open the list (the tool response will include this) 3. If the tool response includes a prefilled Lead Finder link (returned when a new company or person list matches 0 records), relay it: offer it as the way to fill the list, using the exact markdown link from the response 4. Keep your response concise
create-or-update-list
Create or update records in Clarify. Supports bulk operations of up to 25 records per call. - **Without id**: Create a new record - **With id**: Update an existing record by ID This tool also links and unlinks relationships between records, not just attributes -- see the Relationships section below for trigger words and examples. All records in a single call must be the same operation: either all creates (no `id`) or all updates (with `id`). Split mixed batches into two separate calls. Each call is atomic: if any record in the batch fails, the entire batch is rolled back and no records are written. When working with user-supplied IDs (CSVs, pasted lists) verify the IDs exist with query-data first, or use smaller batches to limit the blast radius of a single bad ID. ## Before creating or updating records - Writable fields are not included in this tool's description. You must call get-schema with format "write" and only the entities you need before creating or updating records, unless the write schema for those entities was already loaded earlier in this conversation. Do not guess field names. - Never tell a user a field can be set before confirming it appears in the write schema. Computed / read-only fields (system-managed, e.g. interaction dates) are deliberately omitted from the write schema: they cannot be set here, and a field missing from the write schema is read-only or nonexistent, not something to recreate as a custom field. Do not offer to set such a field or to create a custom field of the same name. Explain that it updates automatically (from meetings and emails) and offer the real workaround, such as logging a meeting. - Before creating a company, deal, or person, use the query-data tool to search for existing records by name (ILIKE). Use a broad search, not a narrow ID filter. - If the search returns potential matches, present them to the user and ask whether to use an existing record or create a new one. - If no matches are found, proceed to create without asking for confirmation. - When creating a meeting, provide `title` (string), `start` (ISO 8601 datetime), and `end` (ISO 8601 datetime) in attributes. Set attendees via the `participants` attribute using the format `{ set: [{ email: "alice@example.com", name: "Alice" }, ...] }`. People, companies, and associations are created automatically from participants. - Meetings also support `summary` (markdown string for meeting summary/recap) and `notes` (markdown string for meeting notes or transcript). Pass these as plain markdown strings in attributes. - If the meeting data includes a timezone, pass it as the `timezone` attribute (IANA timezone string like "America/Chicago"). Naive datetimes in `start`/`end` will be interpreted in that timezone. If no timezone is provided, the user's preferred timezone is used. UTC datetimes (with Z or offset) are stored as-is. ## Relationships Pass all relationships (to-one and to-many) via the `relationships` parameter using this format: `[{ "relationshipFieldName": "<field>", "operation": "link" | "unlink", "targets": [{ "entity": "<entity>", "_id": "<id>" }] }]` Use `operation: "link"` or `operation: "unlink"` when the user says link, unlink, connect, disconnect, associate, dissociate, or relate -- any create/remove of a relationship between two records, for example: - "Link this deal to Acme Corp" -> link - "Unlink this contact from the deal" -> unlink - "Connect this deal to Acme Corp" -> link - "Dissociate this contact from the deal" -> unlink A relationship-only call is valid: `attributes` can be omitted (it defaults to `{}`) when only `relationships` is populated, e.g. to link or unlink a record without changing any other field. The specific relationship fields available per entity are returned by get-schema with format "write". ## Creating deals and tasks When creating a deal or a task, do not create a bare record with only a name. Fill in the fields that make it useful: inferring what you can and asking the user only for what you genuinely cannot. - **Owner / assignee defaults to the current user.** Unless the user names someone else, set a new deal's `owner_id` (and a new task's `assignee_id`) to the current user. Pass it via `relationships`, e.g. `{ "relationshipFieldName": "owner_id", "operation": "link", "targets": [{ "entity": "user", "_id": "<current user id>" }] }` (use `assignee_id` for tasks). The current user's record ID is in the "Information about the user" section of your context; MCP clients can get it from get-current-user. - **Infer before asking.** Before creating a deal, try to fill `amount`, `close_date`, and `stage`, and to link the relevant company and people, from context already available to you: recent meetings and emails with that company, and existing related records (use query-data and get-records). Link the named company (creating it first if it does not exist, per the notes below) and any clearly-relevant people. - **Ask only for the gaps.** If a field that matters can't be confidently inferred, ask the user for it in one short round rather than silently creating an empty record, and let them skip. Do not block creation if the user clearly just wants a quick record. - **Amount is a committed actual.** Never invent or estimate a deal's `amount`. If you don't have a committed, agreed value, ask the user or leave it unset; do not guess from projections. ## Collection fields (labels, multi-select, emails, etc.) Collection fields require an explicit operation. Do not pass raw arrays or { "items": [...] }. - **Append** (add values): `{ "append": ["value1", "value2"] }` - **Remove** (remove values): `{ "remove": ["value1"] }` - **Set** (replace all values): `{ "set": ["value1", "value2"] }` - **Append + Remove** (do both in one call): `{ "append": ["new"], "remove": ["old"] }` "set" cannot be combined with "append" or "remove". Use "set" alone to replace all values or clear them with `{ "set": [] }`. When creating records, use "set" for initial values: `{ "labels": { "set": ["Enterprise"] } }`. When updating records, choose the operation that matches the user's intent. If the user says "add label X", use "append". If they say "remove label X", use "remove". Before using "set" on an existing record, first query the record's current collection values so you know what you're replacing. "set" overwrites the entire collection; if you use it without checking, you will silently discard existing values the user wants to keep. ## Updating records When updating, you are modifying an existing record, not creating one. Do not re-run create-time logic or re-estimate values. - Only include a field when you have an explicit, new signal its value changed. No new signal for a field means leave it out of the call; do not re-send or re-estimate it. - Before overwriting a field that already has a value, read its current value and change it only if it genuinely changed. - Treat amount/value fields as actuals: set them only to committed, agreed values, never projections or estimates. - Do not move a deal's stage to a winning or final stage, or backward, without a clear signal the change actually happened. - For calendar-synced meetings, only the "type", "summary", "prep", and "notes" fields can be updated. Other fields (title, start, end, etc.) can only be updated on ad-hoc meetings created in Clarify. - Meetings support `summary` (markdown string for meeting summary/recap) and `notes` (markdown string for meeting notes or transcript). Pass these as plain markdown strings in attributes. These can be set on any meeting (ad-hoc or calendar-synced). ## Upsert mode (match_on) When creating records that may already exist, pass `match_on` with a unique field to match on (marked unique in the schema, e.g. `domains` for companies, `email_addresses` for people) to upsert instead of failing on duplicates. For each record, if exactly one existing record already has the same value for that field, the incoming attributes are merged into it and its ID is returned; records with no match are created. A value that matches multiple existing records errors; merge those records first. Scalar unique fields match exactly; collection fields match case-insensitively; non-unique fields are rejected. The whole batch still succeeds atomically. This avoids the re-query-and-retry dance after a duplicate error. `match_on` only applies to creates; do not combine it with records that have an `id`. Collection field operations (`set`/`append`/`remove`) are honored against the matched record, not flattened into a plain replace. ## Important notes - Rich-text fields shown as `markdown` in the write schema (for example a task's `description`, a deal's `summary`, or a meeting's `summary`) accept a plain markdown string in `attributes` and are converted to rich text automatically. Never open any rich-text/markdown field (`summary`, `prep`, `notes`, `description`, and any custom one) with a heading that restates or mirrors the field name; the field is already labeled in the UI; start directly with the content. - When setting numeric values (like deal amount), store the exact value the user provides. Never multiply or divide to convert between units (e.g. MRR to ARR). - Relationship fields (like `company_id`, `owner_id`, `people`) must be passed via `relationships`, not inside `attributes`. Use `attributes` only for scalar/non-relationship fields. - All relationships are optional. Records like tasks, deals, and people can be created or updated without linking to other records. Never require the user to provide a related record (e.g., a deal) before creating or updating a task. - When the user references a person or company that doesn't exist in the CRM, create the person/company record first using this tool, then link it to the main record. Do not ask the user to manually create the contact or provide unrelated context like a deal. - If you need to process more than 25 records, call this tool multiple times with batches of up to 25. - For a single record, use a single-element array. - After creating or updating records: 1. Tell the user what was done in a friendly, conversational way 2. Refer to records by name only. 3. Never include a record URL in prose; the tool result already carries each record's link.
create-or-update-records
Delete an agent by its ID. ## When to use this tool - When the user asks to delete or remove an agent they created ## Important notes - This action is permanent and cannot be undone - Only the creator of an agent can delete it
delete-agent
Delete a campaign by its ID. ## When to use this tool - When the user asks to delete or remove a campaign ## Important notes - This action is permanent and cannot be undone - The campaign must be disabled (draft) before it can be deleted - Only the campaign owner or an admin can delete a campaign
delete-campaign
Delete a custom object type by its entity identifier. ## When to use this tool - When the user asks to delete or remove a custom object type ## Important notes - This action is permanent and cannot be undone - All records of this type will be deleted - Only custom objects (entity starting with "c_") can be deleted
delete-custom-object
Delete one or more custom fields from an existing object (built-in entities like person, company, deal, or custom objects like c_my_object). ## When to use this tool - When a user wants to remove custom fields they no longer need - When cleaning up unused or obsolete fields from any object ## Important - This is a destructive operation: deleted fields and their data cannot be recovered - Always confirm the deletion plan with the user before calling this tool - Protected (system) fields cannot be deleted - Relationship fields are deleted on both sides automatically
delete-fields
Delete a list (saved view) by its ID. ## When to use this tool - When the user asks to delete or remove a list they created ## Important notes - This action is permanent and cannot be undone - Default lists cannot be deleted - The last remaining list for an entity cannot be deleted
delete-list
Delete one or more records by their IDs. Supports bulk deletion of up to 25 records per call. ## When to use this tool - When the user asks to delete or remove specific records ## Important notes - This action is permanent and cannot be undone - All relationships involving the deleted records are also removed - Use only if you already have the IDs for the records - If you need to delete more than 25 records, call this tool multiple times with batches of up to 25.
delete-records
Search Clarify's built-in prospect database of companies and people to find new leads matching criteria like industry, location, headcount, job title, seniority, and funding stage, and save (publish) a search the user wants to keep. Use this for prospecting and lead discovery on any page where leads, prospects, or outbound targets are discussed. Under the hood, a search is created as a draft (or draft version) from a custom-dialect SQL query - follow the rules and examples below exactly. ## Modes: draft vs publish This tool has two modes, set by the `mode` parameter (default `draft`): - `draft`: run a SQL query to create a new draft search, or a draft refinement of an existing one, and preview the matches. This is the default and covers all prospecting and refinement. - `publish`: save an existing draft search so it appears under the user's "My saved searches". Call with `mode: "publish"`, the search's `search_id`, and (when the search has an unapplied refinement) its `version_id`. No `sql` is needed to publish. ## Draft vs saved searches A `draft` result is **not** a saved search: it doesn't appear under the user's "My saved searches" until it's published. Never tell the user a search you just created is "saved"; describe it as a draft. If the user asks whether it's saved, say it's a draft and offer to save it. When the user asks to save (or "publish") a search, call this tool again with `mode: "publish"` and the search's `search_id`. ## Interpreting the result count The number returned is an upper bound on matching rows in the prospect database. Some of those rows may already exist in the user's CRM and some may overlap with each other. The actual number of net-new importable records can be lower and is only known at import time. Never present the match count as "unique importable leads" or use it to estimate credit spend — use it only to describe how many rows match the filters. ## Important Rules - Parameters like version_id and search_id should preferably come from the document context, which is the source of truth for identifiers. If the document context does not include a version_id, you may use the most recent version_id from the conversation if it is contextually relevant. ## When to use this tool Use this tool when a user wants to find leads matching specific criteria and preview them as a draft search: - Construct a SQL query following the rules below - Provide a `search_label` parameter (e.g., "SF Companies with 50+ employees") - The tool creates a draft search or draft version that the user can see and interact with (see "Draft vs saved searches" above) ## When not to use this tool Do not call this tool when the user's intent is ambiguous between existing CRM data and new leads. When the user searches for companies or people without using the word "lead" or "prospect", and the conversation does not already establish context, you must ask the user to clarify before calling any tool. Ask whether they want to find new leads/prospects to add to the CRM, or look up existing companies/people already in the CRM. Only call this tool after the user confirms they want new leads. ## Versioning vs Creating Searches When the user wants to update/modify a search: 1. Always use the SQL from the referenced version as the base query and only modify what the user explicitly asks to change. 2. Keep the same entity type and update the SQL query and search_label Update (provide search_id) when user says: "no", "actually", "refine", "filter", "change", "update", "modify" or refers to "it"/"that search"/"the search" Create (omit search_id) when user explicitly asks for a "new search" or different entity type (company vs person) ## SQL Rules 1. Use ILIKE, all matching is case-insensitive. 2. Database may convert ILIKE `'%foo%'` into `'foo%'` pattern (prefix-only pattern matching) depending on field type (full text search vs keyword matching). Names are processed with prefix matching only. 3. No SELECT * — explicitly list all columns. 4. Always have the first column be the primary object field with the alias `table_name:__object__`. 5. Never select _id or id fields — they are not queryable. 6. Always prefix columns with table names: `tam_company.name`, not `name`. 7. Only select top-level columns — nested JSONB fields are not allowed in SELECT clauses. 8. All selected columns require aliases in the format "table_name:column_name". 9. For JSONB array fields (industries, domains, etc.), use this exact syntax to filter: `(table.field -> 'items') ?| ARRAY['Value1', 'Value2']` Do not use CONTAINS, the `@>` containment operator, or standard SQL array syntax - only the exact pattern above works. 10. Before applying filters to enum fields, review the allowed values listed in "Available Tables and Columns" and include all relevant matches in a single WHERE clause. 11. When filtering by role or title, prefer `job_title ILIKE '%keyword%'`; it matches the person's actual title and covers most queries (e.g., "founders", "CTOs", "account executives", "sales managers", "engineers"). The `seniority` and `function` fields are coarse categorical enums with very few values; only use them when the user's query maps exactly to an enum value and a broad categorical filter is clearly intended. 12. Never use `ILIKE ANY(ARRAY[...])` or `= ANY(ARRAY[...])` — ARRAY/ANY is not valid here. To match any of several title keywords, OR together separate `job_title ILIKE '%keyword%'` conditions; to match any of several exact enum values, use `IN (...)` on that column. 13. Never include `ORDER BY` in find-leads SQL. The TAM database returns errors when ORDER BY is present, even on sortable fields. If the user uses words like "recently", "latest", or "top", interpret them as filters (e.g. `latest_role_change_at >= '2026-01-01'`), not as a sort instruction. Never write `ORDER BY`, regardless of which column the user mentions. 14. If the user asks to sort or rank explicitly, explain that find-leads cannot sort results and offer to filter by date or another field instead. 15. NOT with JSONB array operators (?|) is unreliable and returns zero results. Use positive filters instead of negation (e.g., filter for the industries you want rather than excluding ones you don't). 16. Never use OR to group multiple `->>'key'` conditions — it corrupts parentheses around JSONB access. Instead, use `IN` on a single key. Correct: `WHERE table.primary_location->>'city' IN ('San Francisco', 'New York', 'Boston')` Incorrect: `WHERE (table.primary_location->>'city' = 'San Francisco' AND ...) OR (table.primary_location->>'city' = 'New York' AND ...)` This applies to all JSONB object fields (primary_location, company_location, name), not just locations. 17. Tech-stack / tooling criteria (e.g. "uses Kubernetes", "on PostgreSQL") are a COMPANY attribute: filter `tam_company.tech_stack_products` with the JSONB-array syntax `(tam_company.tech_stack_products -> 'items') ?| ARRAY['Kubernetes']`. Never filter tech stack via a person's `skills` field. Because queries are single-table (no JOINs), a tech-stack criterion means the query must target tam_company; you cannot filter tam_person by a company's tech stack. 18. Never filter on `smart_tags` — it is not a reliable structured filter. Map every criterion to a structured column (job_title, seniority, function, employee_range / company_employee_range, tech_stack_products, industries, primary_location). 19. Single-table queries only — JOINs are not supported. 20. When creating a new search, use the default query for the entity as the base and only add a WHERE clause. When refining an existing search, preserve the existing SELECT columns unless the user explicitly asks to add or remove columns. 21. Never filter by tam_person email fields (email_addresses, personal_email_addresses) in WHERE clauses — these fields are masked until leads are imported into the CRM, so filtering by them would produce unreliable results. You may still SELECT them for display. ## Company Name Matching The TAM database stores **parent company names**, not subsidiary or regional office names. When users provide subsidiary names (e.g., "Globex France S.A.R.L.", "Initech Solutions Japan K.K."), extract the core company name and use prefix matching: Correct: `tam_person.company_name LIKE 'Globex%'`: matches "Globex", "Globex Inc.", "Globex France S.A.R.L." Incorrect: `tam_person.company_name = 'Globex France S.A.R.L.'` — exact match will likely miss the record Incorrect: `tam_person.company_name IN ('Globex France S.A.R.L.', ...)` — exact subsidiary names rarely exist in the database When matching multiple companies, use OR with LIKE prefix patterns on the core name: ```sql WHERE ( tam_person.company_name LIKE 'Globex%' OR tam_person.company_name LIKE 'Initech%' OR tam_person.company_name LIKE 'Stark Industries%' ) ``` **Important**: Because only prefix matching is available, short company names (e.g., "Nova") may produce false positives (e.g., matching "Novartis"). Use the most distinctive prefix possible. If the user provides both a short name and a more specific variant, prefer the longer one (e.g., `'Nova Dynamics%'` over `'Nova%'`). ## Examples Follow the SQL patterns in these examples exactly. The lead finder database uses custom JSONB syntax that differs from standard SQL. Example: Find me companies in SF with > 50 people ```sql SELECT tam_company.name AS "tam_company:__object__", tam_company.domains AS "tam_company:domains", tam_company.employee_range AS "tam_company:employee_range", tam_company.industries AS "tam_company:industries", tam_company.description AS "tam_company:description", tam_company.primary_location AS "tam_company:primary_location" FROM tam_company WHERE tam_company.primary_location->>'city' = 'San Francisco' AND tam_company.primary_location->>'state' = 'California' AND tam_company.employee_range IN ('51-250', '251-1K', '1K-5K', '5K-10K', '10K-50K', '50K-100K', '100K+') ``` Example: Find me SaaS company leads in New York ```sql SELECT tam_company.name AS "tam_company:__object__", tam_company.domains AS "tam_company:domains", tam_company.employee_range AS "tam_company:employee_range", tam_company.industries AS "tam_company:industries", tam_company.description AS "tam_company:description", tam_company.primary_location AS "tam_company:primary_location", tam_company.website AS "tam_company:website" FROM tam_company WHERE tam_company.primary_location->>'city' = 'New York' AND tam_company.primary_location->>'state' = 'New York' AND (tam_company.industries -> 'items') ?| ARRAY['SaaS'] ``` Example: Find me people who work in sales at tech companies ```sql SELECT tam_person.name AS "tam_person:__object__", tam_person.email_addresses AS "tam_person:email_addresses", tam_person.job_title AS "tam_person:job_title", tam_person.company_name AS "tam_person:company_name", tam_person.company_industries AS "tam_person:company_industries", tam_person.primary_location AS "tam_person:primary_location", tam_person.seniority AS "tam_person:seniority" FROM tam_person WHERE tam_person.function = 'Sales & Business Development' AND (tam_person.company_industries -> 'items') ?| ARRAY['Software Development', 'Information Technology & Services'] ``` Example: Find me founders in San Francisco Note: "founder" is a specific title — use `job_title ILIKE`, not seniority. ```sql SELECT tam_person.name AS "tam_person:__object__", tam_person.email_addresses AS "tam_person:email_addresses", tam_person.job_title AS "tam_person:job_title", tam_person.company_name AS "tam_person:company_name", tam_person.primary_location AS "tam_person:primary_location", tam_person.seniority AS "tam_person:seniority" FROM tam_person WHERE tam_person.job_title ILIKE '%founder%' AND tam_person.primary_location->>'city' = 'San Francisco' AND tam_person.primary_location->>'state' = 'California' ``` Example: Find me companies in San Francisco, New York, and Boston ```sql SELECT tam_company.name AS "tam_company:__object__", tam_company.domains AS "tam_company:domains", tam_company.employee_range AS "tam_company:employee_range", tam_company.industries AS "tam_company:industries", tam_company.description AS "tam_company:description", tam_company.primary_location AS "tam_company:primary_location" FROM tam_company WHERE tam_company.primary_location->>'city' IN ('San Francisco', 'New York', 'Boston') ``` Example of filtering by `tam_person.company_employee_range`: ```sql SELECT tam_person.name AS "tam_person:__object__", tam_person.email_addresses AS "tam_person:email_addresses", tam_person.company_employee_range AS "tam_person:company_employee_range", tam_person.company_location AS "tam_person:company_location" FROM tam_person WHERE tam_person.company_employee_range IN ('51-250', '251-1K') ``` ## Importing or adding leads to the CRM If user asks to add or import leads to the CRM, use the `import-leads` tool and not the create-or-update-records tool.
find-leads
List an agent's runs, executions, invocations, and past run history, or fetch one run with its full transcript. Use this tool for any question about what an agent has done, when it ran, or how often it has fired. ## When to use this tool - "List runs for this agent" / "show me the agent's runs" - "Agent history" / "past runs" / "recent runs" / "last run" - "How many times has this agent run" / "count runs for an agent" - Debugging why an agent behaved a certain way: fetch the run to see the messages, tool calls, and responses that happened - The user provides an agent id and asks "show me what it did" ## Not the same as get-workflow-runs Agents and workflows both have runs but they live in different tables. Use this tool (not `get-workflow-runs`) whenever the subject is an agent or an agent id. Passing an agent id to `get-workflow-runs` silently returns nothing. ## How it works - Without `run_id`: returns a paginated list of recent runs for the agent - With `run_id`: returns the full run including its message transcript - A "run" is the chat session an agent produced when one of its triggers fired — user and assistant messages, tool calls, results, and errors ## Pagination - When the user asks for "more" runs / next page / older runs, call this tool again with the same agent_id and a larger `offset` ## Proactive debugging When the user reports that an agent isn't working as expected, proactively use this tool to fetch the most recent run transcript before suggesting changes. Compare what happened in the run against the agent's instructions to diagnose the root cause. After diagnosing, explain what you found with specifics from the transcript. Suggest targeted fixes via `create-or-update-agent`, then offer to rerun.
get-agent-runs
List agents visible to the current user, or fetch a single agent by ID. ## When to use this tool - When the user asks to see their agents or list available agents - When you need to look up an agent's instructions or configuration - When the user asks about a specific agent by name or ID ## How it works - Without agentId: returns all visible agents with description only - With agentId: returns the full agent details including complete instructions and configured triggers
get-agents
List the individual people enrolled in a campaign, with their per-person engagement — who opened, who clicked, who replied, and when. ## When to use this tool - Answer "who clicked", "who opened", "who replied", "who hasn't responded" - Build a follow-up or retarget audience from the people who engaged with a campaign - Check where a specific person is in a campaign, or why their run stopped Use `get-campaigns` instead when you need campaign-level totals and rates rather than the people behind them. Get the `campaign_id` from that tool first. 📖 **For campaign rules and examples**: Use read-context with context: "campaign-docs" ## Returns One entry per recipient: person name, email, person ID, delivery status, opened/clicked/replied flags, and the timestamp of their most recent open, click, and reply. ## Pagination - When the user asks for "more" results, use the EXACT same inputs and only change the offset parameter - The tool will tell you the next offset value to use ## Examples <example> { "campaign_id": "0195f2c1-...", "event": ["clicked"] } </example> <example> { "campaign_id": "0195f2c1-...", "status": "completed", "limit": 25 } </example>
get-campaign-recipients
List campaigns in the workspace, or fetch a single campaign by ID with full details. ## When to use this tool - Without campaign_id: discover existing campaigns, search by name, get campaign IDs - With campaign_id: read a campaign's full email content, inspect subjects/bodies/timing, check performance metrics, get details before duplicating with `create-campaign` 📖 **For comprehensive campaign rules and examples**: Use `read-context` with context: "campaign-docs" ## Returns **List mode** (no campaign_id): - Campaign name, ID, description, status, email step count, target list, created/updated info **Detail mode** (with campaign_id): - Full campaign details including all email steps with subjects, bodies (HTML with variables), and timing - Campaign-level performance: open rate, click-through rate, response rate, bounce rate - Per-email-step engagement: sent, opened, clicked, replied, bounced counts All engagement here is aggregate counts only. To find out *which people* opened, clicked, or replied — and when — use `get-campaign-recipients`. ## Pagination (list mode only) - When user asks for "more" results, use the EXACT same search inputs and only change the offset parameter - The tool will tell you the next offset value to use ## Duplicating a campaign To duplicate a campaign, call this tool with campaign_id first, then pass the returned email steps and settings to `create-campaign` with a new name like "Copy of <original>". Copy the `list_id` so the duplicate targets the same audience. Do NOT copy from_name. Let the new campaign use the current user's sender identity. If the original sender differs from the current user, also remove the original sender's personal sign-off from email bodies.
get-campaigns
Get information about the current authenticated user, including their timezone. ## When to use this tool Use this to understand who "me", "my", and "I" refer to in user queries. ## Important notes When interpreting relative dates (e.g., "last 2 weeks", "yesterday"): - Calculate dates in the user's timezone returned by this tool - Convert to UTC and ISO format for database queries
get-current-user
Get list metadata (saved views) for an entity type, or fetch a single list by ID. Only dynamic lists (membership driven by a SQL query) are returned. Legacy static and default lists are filtered out. ## When to use this tool - Without list_id: discover existing lists, search by name or description - With list_id: fetch full details for a specific list Supports case-insensitive substring search when no list_id is provided. ## Returns - List name (title) - Description - SQL query - Layout (table, board) ## Pagination - When user asks for "more" results, use the EXACT same search inputs and only change the offset parameter - The tool will tell you the next offset value to use (e.g., "Call this tool again with offset=25 to continue")
get-lists
Retrieve detailed information about specific records by their IDs. This tool provides comprehensive context about records, including relationships, AI summaries, and other details. Use this tool when you need to: - Understand the current state of specific records before answering questions or taking actions - Read meeting recording transcripts with full speaker attribution - Read email content, subject, body, and participants - Get rich context about records found via query-data Typical workflow: Use query-data to find record IDs, then pass them here for full details. Meeting-specific guidance: - For meeting: returns metadata, summary (AI-generated), notes (user-written), and associated recordings. If summary is null, check notes before concluding no content exists - For meeting_recording: returns the full transcript with speaker names and timestamps - When the user asks for exact quotes, verbatim wording, or "what exactly was said", fetch the meeting_recording transcript via get-records. The meeting summary is paraphrased and does not preserve the original words.
get-records
Get the schema for Clarify entities. ## When to use this tool Use the read format when working with SQL queries and the query-data tool. Use the write format when creating or updating records with the create-or-update-records tool. Pass the specific entities you need. Read defaults to the CRM database, and omitting entities returns every CRM entity, a large result that is re-sent on every later turn, so only omit it when you genuinely need the whole model. For lead columns (funding stage, employee range, industries, location), request the tam_company or tam_person entities to get the schema for the find-leads tool. The analytics event log schema comes with the query-analytics tool directly, so you do not need this tool for it. ## Databases Clarify has three databases: - CRM database (company, person, deal, meeting, user, task): your workspace data. Use with the query-data tool. Read returns these by default. - Leads database (tam_company, tam_person): prospecting data for finding new leads. Request these entities explicitly. Use with the find-leads tool. - Analytics database (ClickHouse, single `analytics.event` table): historical event log of every CRM change: point-in-time and change-history questions. Its schema comes with the query-analytics tool. ## Formats - read: schema for reading data via SQL queries with the query-data tool, or lead columns for the find-leads tool. Returns all columns, types (including JSONB structure), relationships, join tables, and custom fields. Defaults to CRM entities. - write: writable fields for the create-or-update-records tool. Returns fields that can be set when creating or updating records.
get-schema
Import leads from a tam_company or tam_person search into Clarify. ## Important Rules - Parameters like versionId and searchId should preferably come from the document context, which is the source of truth for identifiers. If the document context does not include a versionId, you may use the most recent versionId from the conversation if it is contextually relevant. - When a versionId is provided, the version will be applied to the search and the search will be published before starting the import - Always provide a descriptive searchTitle that reflects the current filters/query of the search (e.g., "SF Companies with 50+ employees") - Always provide a searchEmoji — a single emoji that represents the theme of the search (e.g., "🏢" for companies, "🌉" for SF-related searches) When the user asks to import leads without specifying a count: - For tam_company searches, import all leads without asking — company imports never consume credits regardless of size. - For tam_person searches with fewer than 100 leads, import them all without asking. - For tam_person searches with 100 or more leads, ask whether they want to import a small sample (e.g. 10) to test first or all leads from the search. Inform the user that importing people costs 1 credit(s) per net-new person, so importing all N people will cost up to N × 1 credits total. ## How charges work Credits are charged only for net-new tam_person records that get inserted. Records that already exist in the CRM are skipped by the system and never charged. tam_company imports are always free. When a batch returns 0 net-new records (all matches already exist in the CRM), nothing is charged. Never tell the user they have "burned credits on duplicates" or suggest a refund in that case; there is nothing to refund. Never explain internal decision logic, thresholds, or tool behavior to the user. For example, do not say things like "Since there are fewer than 100, I'll import them all." Just perform the action and describe what you did in user-friendly terms. ## Extra fields This tool can import any field from the TAM database — not just the core fields. When the user asks for additional data (e.g., "import with industries and tech stack", "I want ownership status too", "add seniority"), use the extraFields parameter. The import automatically creates CRM fields that don't exist yet and populates them with TAM data. Do not create fields manually — this tool handles it. This also works for records that already exist in the CRM. If the user wants to backfill a field (e.g., "add industries to my existing companies"), re-run the import with extraFields and the existing records will be updated with the new field values. Available extra fields for companies: alternative_names (Alternative names), website (Website), type (Type), industries (Industries), specialities (Specialities), workforce_headcount (Workforce headcount), summary (Summary), funding_last_round_type (Last funding round type), funding_last_round_amount_usd (Last funding round amount (USD)), funding_last_round_date (Last funding round date), investments (Investments), financing_profile_status (Financing status), financing_profile_ipo_date (IPO date), financing_profile_market_cap (Market cap), ownership_status (Ownership status), ownership_status_detailed (Ownership status (detailed)), stock_exchange (Stock exchange), customer_types (Customer types), tech_stack_products (Tech stack products), patent_count (Patent count), contact_info_email (Contact email), contact_info_phone (Contact phone), contact_info_url (Contact URL), identifiers_stock_ticker (Stock ticker), identifiers_naics_code (NAICS code), identifiers_duns_code (DUNS code), identifiers_cage_code (CAGE code), is_acquired (Is acquired), is_exited (Is exited), is_government (Is government), is_non_profit (Is non-profit), is_shut_down (Is shut down), is_stealth (Is stealth). Available extra fields for people: personal_email_addresses (Personal email addresses), headline (Headline), function (Function), seniority (Seniority), hiring (Hiring), open_to_work (Open to work), skills (Skills), smart_tags (Smart tags), investor_data_type (Investor type), investor_data_geo_focus (Investor geo focus), investor_data_industry_focus (Investor industry focus). If the user says "import all fields" or "include everything", pass all available field names for the entity type. If the user doesn't mention extra fields, omit the parameter.
import-leads
Merge two or more records into a single primary record in Clarify. All data from the source records (fields, relationships, list memberships, notes) will be merged into the primary record. Source records are deleted after merging.
merge-records
Execute read-only ClickHouse SQL against the analytics event log. ## When to use this tool Use `query-analytics` for **historical / point-in-time** questions about CRM records: anything that requires looking at the sequence of changes over time, not just the current state. Examples: - "What was the sequence of stage changes for this deal?" - "How long did this deal sit in 'In progress'?" - "What was the total amount of all deals in each stage on a specific date?" - "How many deals were created per day last week?" - "Who last edited this record, and when?" ## When NOT to use this tool For **current state** questions ("which deals are open?", "what's this company's domain?"), use the `query-data` tool: it queries PostgreSQL directly and is faster for live values. Only fall back to `query-analytics` when you need history. ## How it works - The query runs against ClickHouse, not PostgreSQL. Use ClickHouse SQL syntax (`JSONExtractString`, `toDate`, `argMax`, etc.). - When the user hasn't asked for a specific time frame, scope the query to the last 30 days with `AND timestamp >= now() - INTERVAL 30 DAY` to keep it fast and the results relevant. - Statements are read-only: INSERT / UPDATE / DELETE / DDL are rejected by ClickHouse. - Results are capped to the `maxResultRows` parameter (default 1024). Rows beyond the cap are dropped server-side, even if your SQL omits a `LIMIT`. # Analytics database (ClickHouse) Historical event log of every change to the CRM. **Read-only.** Use this with the `query-analytics` tool to answer point-in-time and change-history questions like: - "What was the sequence of events that led to the current stage of this deal?" - "How long did this deal sit in the 'In progress' stage?" - "How many deals were created per day last week?" ## Table: `analytics.event` Schema is inspired by PostHog: a single wide event table with a JSON-stringified `properties` payload column plus a structured `actor` column. Hot keys are promoted to materialized columns (see below); everything else is read with `JSONExtract*` against `properties`. - `_id` (String) — Event id - `workspace_slug` (String) — Workspace (tenant) slug - `entity_type` (String) — Subject type: `deal`, `company`, `person` - `entity_id` (String) — Subject record id - `type` (String) — Event type (see enum) - `timestamp` (DateTime64(6,'UTC')) — Event time; use `toDate(timestamp)` for day grouping - `properties` (String/JSON) — Object state at event time; read via `JSONExtract*` - `actor` (Tuple) — `actor._id`, `actor.anonymous_id`, `actor.entity`, `actor.source_id` (all String); usable in `GROUP BY`/`ORDER BY` - `m_stage` (String) — Materialized `JSONExtractString(properties,'stage')`; empty if absent - `m_amount` (Float64) — Materialized `JSONExtractFloat(properties,'amount')`; `0` if absent ## Sort key (write fast queries) The table is a `ReplacingMergeTree` ordered by `(workspace_slug, entity_type, entity_id, timestamp, _id)`. Queries are fastest when their `WHERE` clause matches this key from the left, so filter in that order — `workspace_slug` first, then `entity_type`, then `entity_id`, then `timestamp`: - Always include `workspace_slug = '<your workspace>'` — it's the leading sort-key column. Use the **Workspace** value from the current user context; if your context doesn't include it, call the `get-current-user` tool to get it before querying. - Always filter by `entity_type` when you can — it lets ClickHouse skip almost the entire table. Add `entity_id` too for single-record questions. - Add a `timestamp` range to narrow further (and bound open-ended history scans). - Filtering only by columns outside the sort key (`type`, `m_stage`, `m_amount`, or any `JSONExtract*` value) forces a full scan — pair them with the sort-key columns above whenever possible. ## Allowed `type` values - `CdcEventType` — `clarify:create`, `clarify:update`, `clarify:merge`, `clarify:delete`, `clarify:add-to-list`, `clarify:remove-from-list`, `clarify:set-relationship`, `clarify:unset-relationship`, `clarify:grant-access`, `clarify:update-access`, `clarify:revoke-access`, `clarify:meeting` ## Materialized columns A small set of frequently-used keys from `properties` are promoted to typed, indexed columns. **Prefer them over `JSONExtract*` when the key matches** — they're faster and skip a JSON parse per row. - `m_stage` (`String`) — `properties.stage` - `m_amount` (`Float64`) — `properties.amount` These are populated from whatever `properties.stage` / `properties.amount` happens to be on each row, regardless of `entity_type`. If a custom object also stores a `stage` or `amount` field, the materialized columns work for it too. Empty / `0` means the event simply didn't carry that key. For any other property key, fall back to `JSONExtractString(properties, '<key>')` / `JSONExtractFloat(properties, '<key>')` etc. ## Common patterns - **Single-record history is cheapest**: when the question is about one specific record, filter by `entity_type` AND `entity_id` together — this hits the sort key prefix and reads a tiny slice of the table. - **Read JSON fields**: prefer materialized columns when the key matches (`m_stage`, `m_amount` — see above). Otherwise use `JSONExtractString(properties, 'priority')`, `JSONExtractFloat(properties, 'mrr')`, etc. - **Latest properties per entity**: `argMax(properties, timestamp)` over a window grouped by `entity_id`. - **Time-in-stage queries**: take consecutive `clarify:update` rows ordered by `timestamp`, group by `entity_id`, then diff timestamps where the extracted stage transitions. - **Day grouping**: `SELECT toDate(timestamp) AS day, count() ... GROUP BY day`. - **Date literals**: prefer relative helpers — `now() - INTERVAL 1 MONTH`, `toStartOfMonth(now())`, `today() - 7` — over hardcoded literals, since you have no reliable wall clock and a hardcoded "this month" boundary will silently drift. When you must use an absolute literal, note that ClickHouse's implicit cast in comparisons like `timestamp >= '2026-04-01T00:00:00Z'` rejects ISO 8601 with `T`/`Z`. Three patterns work: (1) space-separated `'2026-04-01 00:00:00'`, (2) wrap explicitly `parseDateTime64BestEffort('2026-04-01T00:00:00Z')`, (3) explicit `CAST('2026-04-01T00:00:00Z' AS DateTime64(6, 'UTC'))`. - **Actor filter**: `actor._id`, `actor.entity`. Native JSON paths, no `JSONExtract` needed. ## Examples <example> -- Deals created per day in the last 7 days SELECT toDate(timestamp) AS day, count() AS created FROM analytics.event WHERE workspace_slug = '<your workspace>' AND entity_type = 'deal' AND type = 'clarify:create' AND timestamp >= now() - INTERVAL 7 DAY GROUP BY day ORDER BY day </example> <example> -- Sequence of stage changes for a single deal SELECT timestamp, m_stage AS stage, actor._id AS actor_id FROM analytics.event WHERE workspace_slug = '<your workspace>' AND entity_type = 'deal' AND entity_id = '<deal-id>' AND type IN ('clarify:create', 'clarify:update') ORDER BY timestamp </example> <example> -- Stage-change events grouped by day across all deals SELECT toDate(timestamp) AS day, count() AS stage_changes FROM analytics.event WHERE workspace_slug = '<your workspace>' AND entity_type = 'deal' AND type = 'clarify:update' AND m_stage != '' GROUP BY day ORDER BY day </example>
query-analytics
Execute PostgreSQL queries to retrieve data from Clarify. ## When to use this tool - Counting records (e.g., "How many deals are open?") - Aggregations (SUM, AVG, COUNT, MAX, MIN, GROUP BY) - List queries (e.g., "Show me 50 recent meetings") - Filtering by timestamps (e.g., deals created in Q4, meetings last week) - Searching/filtering large datasets (e.g., "Show all deals from Q4" or "Find records updated this week") ## For detailed record context, use get-records After finding record IDs with this tool, use get-records for: - Full record details, relationships, and AI summaries - Meeting recording transcripts with speaker attribution - Email content, subject, body, and participants - Example: query-data to find IDs, then get-records to read details ## When not to use this tool This tool queries existing CRM records. When users want to find new companies or people to add to the CRM (leads/prospects), search for the lead finder tools instead. - Do not use this tool when the user mentions "leads" or "prospects" and wants to source new contacts (e.g., by industry, location, employee count, job title). Search for the lead finder tools that query the lead database instead. - Still use this tool when the user mentions "leads" or "prospects" but the context clearly refers to existing CRM records (e.g., "leads in my pipeline", "prospects with last product activity in last 30 days"). Activity history, emails, meetings, and pipeline stages only exist for CRM records, not in the lead database. But the user is still asking about leads/prospects, so we still use this tool. - Ask the user to clarify when they search for companies or people without using the word "lead" or "prospect", and the conversation does not already establish a CRM context. Ask whether they want to find new leads/prospects to add to the CRM, or look up existing companies/people already in the CRM. If the conversation already establishes CRM context, use this tool without asking. ## Before writing SQL - This tool does not include any schema. Workspaces have custom fields and JSONB columns that vary per workspace, so you cannot know the column names without loading the schema first. Always call get-schema with format "read" and the entities you need before writing SQL, unless the schema for those entities was already returned by get-schema earlier in this conversation. - Call get-current-user if the query involves "me", "my", or "I", if the tool is available - Always prefix columns with table names (e.g., `person.name`, not `name`) - Use ILIKE for case-insensitive matching (not LIKE) - Query timestamps using UTC/ISO 8601 format (e.g., '2025-12-22T00:00:00Z') - Results will have timestamps formatted in the user's timezone for readability - For many-to-many relationships, use INNER JOIN with join tables ## Working with JSONB Columns Many columns store data as JSONB objects. Check the schema returned by get-schema to identify JSONB columns. Only use JSONB operators (->, ->>) on columns explicitly marked as "JSONB with format" in the schema. ### JSONB Access Operators - `->` - Extract JSON object (returns JSONB): `column->'key'` - `->>` - Extract text value (returns TEXT): `column->>'key'` - `jsonb_array_elements(column->'items')` - Iterate over JSONB arrays ### JSONB Column Patterns Object with nested fields (e.g., `JSONB with format {first_name: string, last_name: string}`): - Access nested field: `column->>'first_name'` - Concatenate fields: `CONCAT(column->>'first_name', ' ', column->>'last_name')` - Filter: `column->>'first_name' ILIKE '%john%'` Array of items (e.g., `JSONB with format {items: string[]}`): - Get first item: `column->'items'->>0` - Iterate items: `jsonb_array_elements(column->'items') as item` - Access nested in item: `(item->>'email')` ### Filtering multi-select and label arrays For columns with format `{items: string[]}` (multi-select enums, the built-in `labels` field, etc.), filter membership with the array-overlap operator `?|`: - Has any of: `(person.labels -> 'items') ?| ARRAY['BDR Prospect', 'ICP']` Do not use the `@>` containment operator or `CONTAINS`. `@>` works in the database, but a list built with it cannot be rendered or edited in the list UI; always use the `?|` pattern above. ### Checking if JSONB collection fields are empty For columns with format `{items: string[]}` (multi-select enums, email addresses, etc.), an empty field can be either NULL or `{"items": []}`. Always check for both: - Is empty: `(column IS NULL OR (column->'items') = '[]'::JSONB)` - Is not empty: `(column IS NOT NULL AND (column->'items') <> '[]'::JSONB)` Do not use only `IS NULL` or `IS NOT NULL` for these fields; that misses records with empty arrays. ## Time-Based Queries Use the current time from `get-current-user` tool or system context as reference. ### "Last" vs "Next" Terminology - "last meeting" = Most recent past meeting: `WHERE meeting.start < NOW() ORDER BY meeting.start DESC LIMIT 1` - "next meeting" = Soonest future meeting: `WHERE meeting.start > NOW() ORDER BY meeting.start ASC LIMIT 1` - "recent meetings" = Meetings in the past, not upcoming ones - "upcoming meetings" = Meetings in the future ### Time Period Interpretation When user mentions "Q4 2025", "last quarter", or similar: - Activity in that period: Filter by date: `WHERE meeting.start >= '2025-10-01' AND meeting.start < '2026-01-01'` - Content mentioning that period (e.g., "meetings where Q4 was discussed"): Search transcripts/summaries, not date filter ## User-Scoped Queries When querying data related to "me", "my", or "I": - Use the current user's ID to filter records - Remember: `user` and `person` are different tables - `user` = internal workspace members, `person` = external contacts - For meetings: `INNER JOIN user_meeting ON meeting._id = user_meeting.meeting_id WHERE user_meeting.user_id = '<user-id>'` - External meetings have attendees who are not workspace users. Check `person_meeting` but exclude persons whose email matches a user: `EXISTS (SELECT 1 FROM person_meeting pm JOIN person p ON p._id = pm.person_id WHERE pm.meeting_id = meeting._id AND NOT EXISTS (SELECT 1 FROM "user" u WHERE u.email IN (SELECT jsonb_array_elements_text(p.email_addresses->'items'))))` - Internal meetings have no truly external attendees (inverse of above) - For tasks: Filter by `assignee_id`: `WHERE task.assignee_id = '<user-id>'` - For deals: Filter by `owner_id`: `WHERE deal.owner_id = '<user-id>'` ## Deal Analytics ### Comparison operators for thresholds When users reference a threshold amount: - "above $300", "over $300", "at least $300": `deal.amount >= 300` - "below $500", "under $500": `deal.amount < 500` - "more than $300", "fewer than 5": use strict comparison (`>` / `<`) ### Time-bounded deal metrics When users ask about deals in a specific time period (e.g., "January deals", "Q4 closed deals", "deals closed this month"), always filter by `close_date` within that period in addition to any stage filter. Do not filter only by stage -- that returns all historically closed deals, not deals closed in the requested period. Only add a closed stage filter when the user is asking about closed or won deals specifically. Do not add a closed stage filter for queries about open or in-progress deals. - Example: "deals closed in January 2026" requires `deal.stage IN (<closed stages from schema>) AND deal.close_date >= '2026-01-01' AND deal.close_date < '2026-02-01'` - Example: "deals expected to close in Q1" should only filter by `close_date` without a closed stage constraint ## Association Queries When finding emails or meetings "related to" a company or entity, join through the relationship graph instead of searching message body text. - Emails related to a company: JOIN person_message with person WHERE person.company_id = <company_id>. Do not use raw_body ILIKE '%company name%' -- body text matching picks up incidental mentions and links unrelated contacts. - Meetings related to a company: JOIN person_meeting with person WHERE person.company_id = <company_id>. - Emails for a specific person: JOIN person_message WHERE person_message.person_id = <person_id>. - Only use body/subject text search when the user explicitly asks to search email content (e.g., "find emails mentioning Project X"). ## Activity Counting Activity includes: meetings, messages, tasks, and comments. To count activity: - Comments: Query `comment` table with `entity = '<entity-type>'` and `owner_id = <entity>._id`. Use `_created_at` as timestamp. - Meetings: Join through `person_meeting` join table. If entity relates to `person` via join table (e.g., `person_deal`) or foreign key (e.g., `person.company_id`), join: entity → person → `person_meeting` → `meeting`. Use `meeting.start` as timestamp. - Messages: Join through `person_message` join table. Same pattern as meetings: entity → person → `person_message` → `message`. Use `message.received_at` as timestamp. - Tasks: Join via foreign key `task.deal_id`. For deals: direct join. For companies: join `deal` first (via `deal.company_id`), then `task`. Use `task._created_at` as timestamp. Pattern: Create CTEs for each activity type, UNION ALL them, then LEFT JOIN to your entity table. Use COUNT() and MAX() for totals and last activity date. ## Comments Comments are notes left on records. The comment table has no schema in get-schema: use these columns directly: - `_id`: UUID primary key - `entity`: parent entity type (e.g., 'company', 'deal', 'person', 'meeting', 'task') - `owner_id`: parent record ID - `message`: JSONB rich text; use `message::text` for raw content - `_created_by`: user ID of the comment author. JOIN with `"user"` on `_created_by = "user"._id` for author name - `_created_at`: timestamp ## Existence Checking Before creating a record, search broadly by name to check if it already exists. Duplicate records with the same name but different IDs are common. - Use ILIKE name matching: WHERE deal.name ILIKE '%Snowplow%' or WHERE company.name ILIKE '%Snowplow%'. Do not rely solely on a single foreign key (e.g., WHERE deal.company_id = '<id>') -- this misses records linked to duplicate/variant company records. - When checking for existing deals, search by both the deal name and the related company name. ## Name search strategies When a name search returns no results, try variations before asking the user to clarify. Records are often stored differently than expected (e.g., "BrightLoop" vs "Bright Loop"): - Remove spaces: `ILIKE '%brightloop%'` - Search individual keywords: `name ILIKE '%bright%' OR name ILIKE '%loop%'` - If a broad search returns many results, pick the closest match rather than asking the user to choose Before matching a name column with ILIKE, check its type in the get-schema output. A name stored as a JSONB object (shown as `JSONB with format {...}`) is not text: a bare `name ILIKE` on it fails with `operator does not exist: jsonb ~~*`. This is common for the display name on people-type entities (for example a person or workspace user whose name is `{first_name, last_name}`). Extract the text first, e.g. `name->>'first_name' ILIKE '%...%'` or `CONCAT(name->>'first_name', ' ', name->>'last_name') ILIKE '%...%'` (see "Working with JSONB Columns" above). Only columns the schema marks scalar (e.g. a plain `string` email) work with a direct ILIKE. ## Meeting Recordings & Transcripts Meeting recordings are linked to meetings via meeting_recording.meeting_id. The meeting_recording table is queryable via SQL (use entity=meeting for the query): - Find recordings: SELECT meeting_recording._id FROM meeting_recording WHERE meeting_recording.meeting_id = '<meeting-id>' - Read transcript: use get-records with entity=meeting_recording and the recording IDs A meeting can have multiple recordings. To avoid duplicate rows, filter with an EXISTS subquery instead of JOINing meeting_recording directly: SELECT meeting._id, meeting.title FROM meeting WHERE EXISTS (SELECT 1 FROM meeting_recording WHERE meeting_recording.meeting_id = meeting._id) When listing or showing meetings (e.g., "show me my last 5 recorded meetings"): - Always include summary and/or notes in the SELECT columns, not just metadata (title, date, status) - Present the summary content inline so the user sees what each meeting was about - If summaries are available, offer to show full transcripts for any specific meeting When answering questions about a single meeting (summary, follow-up email, action items): - Check for recordings and read the transcript via get-records before responding When answering questions across multiple meetings: - Prefer using the summary and notes fields first (available on the meeting record) - Only read individual transcripts if the user explicitly asks or summaries are insufficient Meeting content fields: - "summary": AI-generated meeting summary (on meeting record) - "notes": User-written meeting notes (on meeting record, separate from summary) - If summary is null, check notes before concluding the meeting has no content A substring match inside a summary is not evidence that a topic was requested or discussed. Summaries often enumerate ABSENT topics ("No mention of dialers, enrichment, or reporting tools"), so an ILIKE '%dialer%' hit can be a negation. Before attributing a request or interest to a person or meeting from a summary match, read the matching summary text and confirm the term appears in an affirmative context. Do not count a negated mention as a positive signal, and do not report a corrected count as fact until you have re-read the source. ## Emails & Messages The message table contains sent and received emails. Use entity=message to query directly. Sensitive columns (raw_body, subject) are protected by database-level RLS; only messages the current user has access to will return content. Use get-records with entity=message for full formatted email content with participant details. Filter by user_id for the current user's emails, received_at for time ranges, and person_message join for specific contacts. Never claim you cannot access emails -- sent and received emails are queryable. Unsent drafts are not. ## Pagination - When user asks for "more" results, use the EXACT same SQL query and only change the offset parameter - The tool will tell you the next offset value to use (e.g., "Call this tool again with offset=25 to continue") ## Data Limitations Tables contain current field values only. You cannot determine what a field's value was at a past date with this tool. What you can answer: - Deals currently in stage X: `WHERE deal.stage = 'In progress'` - Deals created/updated in a period: `WHERE deal._created_at >= '2025-10-01'` - Deals closed in a period: `WHERE deal.stage IN (<closed stages from schema>) AND deal.close_date >= '2025-10-01' AND deal.close_date < '2025-11-01'` (see "Time-bounded deal metrics" above) What requires the query-analytics tool instead: - "How many deals were in stage X on December 1st?" (requires historical snapshots) - "What was the pipeline value entering Q4?" (requires reconstructing past state) - "Which deals changed stage last month?" (requires stage transition history) For historical or point-in-time questions like these (past field values, stage transitions, change history, trends over time), use the query-analytics tool. It queries the analytics event log of every CRM change and can reconstruct past state.
query-data
Read Clarify product knowledge and documentation. Use this whenever you need to understand a feature, apply best practices, or reference detailed technical documentation. Available contexts: - field-docs: Field type guidelines, AI prompt writing instructions, available relationship data, and examples for creating or updating custom fields - campaign-docs: Complete guide to email campaigns including structure, variables, enrollment, exit conditions, and best practices
read-context
Submit concise feature requests or feedback about MCP tools. Use when: - Missing functionality prevents completing a user's request - User requests a new feature or reports a limitation - User suggests improvements Keep feedback brief and focused on what's needed or what could be improved.
submit-feedback
Update an existing email campaign: rename it, change its target list, adjust the sender, edit its email steps, or configure send time windows. To create a new campaign, use the create-campaign tool instead. Requires a `campaign_id` (get it from the `get-campaigns` tool). 📖 **For comprehensive campaign rules and examples**: Use `read-context` with context: "campaign-docs" ## When to use this tool - When the user asks to rename a campaign, change its target list, adjust the sender, or edit email steps (subjects, bodies, timing) - When the user asks to configure when a campaign's emails can be sent (send time windows) ## Editing email steps Each email_step must specify an operation: - **update**: Modify an existing step (requires step_number, optional: subject, body) - **remove**: Delete a step (requires step_number) - **insert**: Add a new step (requires step_number, subject, body, delay_after_days) To append at the end, use step_number equal to the current number of steps + 1. ## Essential rules 1. **Email/Delay Pattern**: Emails and delays MUST alternate. When inserting an email, always specify `delay_after_days` 2. **First email**: `delay_after_days: 0`. Subsequent emails: minimum 1 day 3. **Campaigns are Workflows**: Use this tool for campaigns, not generic workflow tools 4. Campaign emails are templates sent to multiple people 5. Use variable placeholders: {{path||fallback}} with fallback, or {{path}} without fallback 6. Only use the variables listed below (do not invent variables) 7. Campaigns that have already been sent cannot be modified ## Threading: send_as_reply Set `send_as_reply: true` on a step to thread it as a reply under the previous step (uses the prior message id and "Re: <previous subject>"). Use it when a follow-up bumps the prior email and the recipient should see it inline: - **Use** `send_as_reply: true` for content like "circling back", "just bumping this", "wanted to follow up", or any step that explicitly references the prior email's call to action without a new pitch. - **Leave it off (default)** when the step introduces a new angle, case study, or call to action; recipients triage by subject, and a fresh subject signals fresh content. Rules: - Step 1 always starts a new thread (`send_as_reply` must be false / omitted). - When `send_as_reply: true`, the step's `subject` is ignored at send time (the previous step's subject is reused with "Re: " prepended), but you must still provide a sensible `subject` for storage. Fallback rules (fallbacks make emails feel natural when data is missing): - ALWAYS include fallbacks for human-identifiable information: → Person names (first_name, last_name, full_name) → use "there", "Friend", etc. → Company/organization names → use "your company", "your organization", "your team" → Job titles and roles → use "your role", etc. → Location/city names → use "your area", "your region", etc. - Skip fallbacks ONLY for technical/structured data: → URLs, email addresses, phone numbers → Dates, timestamps, IDs → Numerical values and metrics ## Examples <example> Update a campaign step: { "campaign_id": "abc-123-def", "email_steps": [ { "operation": "update", "step_number": 2, "subject": "Quick follow-up, {{person.name.first_name||there}}" } ] } </example> <example> Insert a new step: { "campaign_id": "abc-123-def", "email_steps": [ { "operation": "insert", "step_number": 2, "subject": "Quick check-in", "body": "<p>Hi {{person.name.first_name||there}},</p><p>Just wanted to follow up...</p>", "delay_after_days": 3 } ] } </example> ## Send Time Windows Use `send_windows` to control when campaign emails can be sent. Emails scheduled outside the configured windows are held until the next open window in the creator's timezone. - Pass `null` to disable windows (emails can be sent at any time). - Pass a `{ windows: { <day>: { start, end } | null } }` object to set windows. Days map to lowercase names: `sunday`, `monday`, `tuesday`, `wednesday`, `thursday`, `friday`, `saturday`. A `null` value for a day means no sending that day. `start` and `end` are 24-hour `"HH:mm"` strings with `start < end` (same-day windows only). - Omit `send_windows` entirely to leave existing windows unchanged. <example> Set business-hours-only send windows: { "campaign_id": "abc-123-def", "send_windows": { "windows": { "sunday": null, "monday": { "start": "09:00", "end": "17:00" }, "tuesday": { "start": "09:00", "end": "17:00" }, "wednesday": { "start": "09:00", "end": "17:00" }, "thursday": { "start": "09:00", "end": "17:00" }, "friday": { "start": "09:00", "end": "17:00" }, "saturday": null } } } </example> ## Important notes - delay_after_days: 0 = immediate, 3 = day 3, 7 = day 7 - Minimum 1 day enforced between consecutive emails automatically - Body should be formatted as HTML. Put each paragraph in its own <p> tag; paragraphs render with a blank line between them. Use <br/> only for a hard line break within a paragraph (e.g. between signature lines) - When describing timing to users, say "immediately" for delay_after_days = 0, "on day 3" for delay_after_days = 3 - Use get-campaigns tool to find campaign IDs for updates
update-campaign
How do I improve a ChatGPT Plugin's discoverability?
The levers are the listing surface agents actually read: names, descriptions, keywords, tool metadata, and registry health. Which lever matters depends on where discovery breaks, which is what continuous measurement shows.
What are Clarify alternatives on ChatGPT?
As of 2026-08-14, Clarify competes with Allianz, Asbie, Attio, Breakcold, BROSH AI CRM, CarClever, Cirra AI, Clearskies, Close, Coevera, decisionpoint.io, Guides, Haaretz, HighLevel, HubSpot, Hypha, Idea To Prototype Beta, item, Journaling with Medome, Levitate, LicitaLAB, Nash Agent, OnePageCRM, PortfolioIQ, Rallies, RealEstateIQ, RemindMeIQ, Rox, Salesflare, Streak, Talonic, Ticketek, Twenty, VoiceBit, WeightWise, YNAB: Get Good At Money, YouEx.ai, Zoho CRM in ChatGPT B2B Sales CRM Platforms, ranked by public Discoverability Score.
Where is this profile measured?
This profile uses the geography attached to the latest public registry snapshot: US. Locale tags are intentionally omitted.