MCP server for Max, the AI sales agent from Digital Crew. Max is built for lead qualification, cold outreach, and pipeline growth—see the product story on max.digitalcrew.tech.
This repo hosts a Next.js app that exposes MCP tools over Streamable HTTP so Max (or any MCP-capable client) can read and update workspace profile settings against the Digital Crew backend API.
The server registers MCP tools that proxy to the Digital Crew max-agent API.
Generated by pnpm docs:tools — do not edit by hand.
158 tools registered.
| Tool | Purpose |
|---|---|
add_prospects_to_list |
Add prospects to a prospect list by their UUIDs. |
agent_draft_create |
Stage an action (e.g. send a nudge, upsert a CRM contact, export a CSV) as a PENDING draft in the workspace for the human to approve. The draft is persisted in max-agent's agent_action_drafts table; nothing runs until the human approves. Returns { draft_id, state:'pending', review_url }. AFTER CREATING, POST THE RETURNED review_url (or a link to the agent drafts view in the workspace UI) TO THE USER'S CHAT CHANNEL SO THEY CAN APPROVE. Use this whenever an agent wants to take a write action on the user's behalf but the user hasn't pre-authorized the specific instance. |
agent_draft_get |
Fetch one agent action draft by id, including its full payload, current state, audit trail, and any execution result. Use to inspect a specific draft before recommending approve/reject to the user. |
agent_draft_list |
List agent action drafts in the workspace, optionally filtered by state (pending/approved/rejected/executed/failed/canceled) and/or action_type. Paginated via { limit, cursor }. Returns { data: [...], next_cursor? }. Use to show the user what's awaiting their approval or to audit recently-executed actions. |
apollo_add_more |
Append more leads to an existing Apollo list (async). Re-runs the saved search for additional results. |
apollo_create_list |
Create an Apollo-backed prospect list (async). Starts people search → ingestion. Poll the list status (or use wait_for_prospect_list) for progress. Auto-generates an idempotency_key if not provided so retries are safe. |
approve_inbox_draft |
Approve a drafted reply and send it in-thread via Unipile, setting the action's status to 'approved'. Optionally pass body to send an edited reply instead of the stored draft. Returns the updated {data: InboxAutopilotAction}. Fails with 404 (draft not found), 409 (action not in 'draft' status), or 400 SendReplyError (code CHAT_NOT_FOUND | ACCOUNT_NOT_FOUND | ACCOUNT_NOT_CONNECTED | NO_PROSPECT_EMAIL | SEND_FAILED). |
approve_proposal |
Approve a pending proposal and launch its draft campaign. SYNCHRONOUS — max-agent builds the prospect list, creates the campaign, and launches it (may take a while). Optionally pass modifications (titles, target_prospect_ids, campaign_name, campaign_description) to override the recommendation before launch; omit them to approve as-is. On success returns {data: SignalProposal (status 'launched', draft_campaign_id set), campaign_id}. Can fail with 402 (enrichment quota exceeded — proposal reverts to pending), 409 (not pending / already decided / expired), or 422 (no target prospects / no connected account). |
archive_campaign |
Archive a campaign (soft delete) — hidden from default views but restorable. |
archive_chat |
Archive a chat (soft delete). Messages remain, use archived=true filter to see them. |
book_meeting |
Create a Cal.com booking and record it as a meeting. event_type_id defaults to the connection's default event type; start is a required ISO8601 UTC time; attendee_name, attendee_email, and attendee_time_zone (IANA) describe the attendee. Optionally pass prospect_id (advances that prospect to 'replied'), campaign_id, and a flat string metadata map. Returns the created {data: meeting row} (calcom_booking_uid, meeting_url, status 'scheduled', etc.). Fails with 409 (no calendar connected), 400 (invalid body / no default event type), or 4xx/502 CALCOM_ERROR (e.g. slot conflict / instance unreachable). |
bulk_delete_organizations |
Delete multiple organizations. Set deleteProspects=true to cascade-delete linked prospects. |
bulk_delete_prospects |
Delete multiple prospects by IDs. |
bulk_enrich |
Queue many prospects and/or organizations for background enrichment by the cron worker (does NOT run inline). Provide prospect_ids and/or organization_ids. Returns {accepted} — how many rows were queued. Use this instead of calling enrich_prospect in a loop when enriching more than a couple of records. |
bulk_import_organizations |
Import multiple organizations. Deduplicates by domain. Returns imported/existing/failed counts. |
bulk_import_prospects |
Import multiple prospects at once. Deduplicates by email. Returns imported/existing/failed counts. |
calendar_status |
Return the workspace's Cal.com connection status. Returns {data: {connected: false}} when no instance is connected, or {data: CalendarStatus} (baseUrl, username, label, defaultEventTypeId, eventTypes) when connected. eventTypes may be [] if the instance is currently unreachable. The api_key is never returned. |
cancel_meeting |
Cancel a recorded meeting by its meetings.id UUID. If the meeting has a Cal.com booking uid it is cancelled on Cal.com first, then the row is marked cancelled. Optionally pass a reason. Returns the updated {data: meeting row} with status 'cancelled'. Fails with 404 (unknown / not in this workspace), 409 (no calendar connected, only when a Cal.com cancel is needed), or 4xx/502 CALCOM_ERROR. |
claire_deep_research |
Multi-source background research on a named person or company. Returns recent activity, news, role context, company highlights, and proof points. Synchronous (may take 30-90s). Call this BEFORE crafting personalized outreach so the message reflects who the prospect actually is. |
claire_extract_prospects_from_url |
Fetch a public URL (conference attendee list, team / about page, press release, panel announcement, etc.) and extract structured prospects (people / contacts) from it via Claire. Returns { prospects:[{ name, title, company, linkedin_url?, email?, ... }], source_url, extracted_count, claire_request_id? }. Use when the user pastes a URL and asks to 'find leads here' / 'build a list from this page'. Pair with create_prospect or import_prospect_list_csv to persist the result. |
claire_find_competitors |
Identify direct competitors of a company by URL. Returns a list of competing companies with descriptions and sources. Synchronous (typically 1-3 min). Use to expand a prospect list with similar companies or to ground competitive positioning in outreach. |
claire_market_watch |
Run a market-watch pass on a URL, optionally filtered by criteria (e.g. 'pricing', 'hiring', 'funding'). Returns a snapshot of detected signals. Use to inform outreach timing (e.g. just-funded companies) or to spot competitive moves. |
claire_search |
Free-text research query against Claire's hub. Use for quick lookups like industry trends, funding news, or any topic where you'd otherwise google. Synchronous — waits for Claire to finish and returns the result. Use mode='lite' for fast first-pass (default), 'full' for deeper multi-source. |
clear_failed_requests |
Drain the dead-letter queue. Returns the number of entries removed. Use after manually replaying or after resolving the upstream issue. |
connect_calendar |
Connect the workspace's self-hosted Cal.com instance so Max can read availability and book meetings. Validates the credentials by calling Cal.com listEventTypes before storing them. Pass base_url (the instance API base, e.g. https://cal.example.com or .../api/v2), api_key, and optionally default_event_type_id. Returns {data: CalendarStatus} with connected:true, baseUrl, username, label, defaultEventTypeId, and the list of eventTypes. The api_key is never returned. |
create_campaign |
Create a new campaign in draft state. Requires name, included_lists, and accounts. Won't send until launched. |
create_intent_trigger |
Set up a trigger that watches a target URL for a buying signal (funding, hiring, tech_stack, news, job_change, or custom). Optionally scope it to an organization or prospect and set how often it re-polls. Returns the created {data: IntentTrigger}. Used to start monitoring for intent before any signals exist. |
create_organization |
Create a new organization/company record. |
create_prospect |
Create a single prospect. Deduplicates by email — returns existing row if email already exists. |
create_prospect_list |
Create an empty platform prospect list. For Apollo-sourced lists, use apollo_create_list instead. |
crm_assign_prospects |
Assign prospects to HubSpot owners using agent_settings.assignment_rules (or assignment_rules_override). First matching rule wins; no match → owner_id null, reason 'no rule matched', priority 'low'. Priority otherwise derives from prospect.score (>=75 high, 50–74 med, <50 low) else 'med'. Returns { assignments: [{ id|email, owner_id, owner_name, owner_email, reasoning, priority }] }. |
crm_detect_forecast_changes |
Compare current open HubSpot deals against the workspace's deal snapshot from window_days ago (read from max-agent's crm_deal_snapshots via GET /api/v1/crm/deal-snapshots). Flags amount changes (abs delta pct > threshold), stage moves (forward/backward via pipeline displayOrder), close-date slips (> threshold days), new deals since last snapshot, and deals that disappeared. Returns { window_days, baseline_iso, current_iso, changes:[{ deal_id, dealname, owner_id, owner_name, amount, prior_amount, amount_delta_pct, stage, prior_stage, stage_movement, close_date, prior_close_date, close_date_slip_days, flag_reasons[] }] }. |
crm_export_import_csv |
Build a HubSpot-import CSV (base64-encoded) from prospects. When dedup_against_hubspot (default true), drops prospects whose email already exists in HubSpot (concurrency <=5). Columns: Email, First Name, Last Name, Job Title, Company, Company Domain, Country, Industry, Number of Employees [, HubSpot Owner ID when include_assignment]. Returns { csv_base64, row_count, deduped_count, deduped:[{ email, existing_id }] }. |
crm_get_contact |
Fetch a single CRM contact by email (the dedup identity). Returns null if not found. |
crm_get_deal |
Fetch a single HubSpot deal by id, including its full properties and associated company/contact ids. Returns null if not found. |
crm_list_activities |
List HubSpot engagements (call/email/meeting/note/task) with optional filters (deal, contact, owner, types, since). Per-type queries are merged sorted by timestamp desc. Returns id, type, timestamp, ownerId, dealId, contactId, subject, body. |
crm_list_deals |
List deals from HubSpot with optional filters (stage, owner, pipeline, amount range, close-date range, modified-after). Returns id, dealname, amount, ownerId, stage, pipeline, closeDate, lastModified, lastActivityDate, nextStep, associated company/contact ids. |
crm_list_owners |
List HubSpot owners (sales reps) for the workspace. Returns id, email, firstName, lastName, teams. Use to map deals/assignments to people. |
crm_list_pipeline_stages |
List deal pipeline stages (optionally scoped to one pipeline). Returns id, label, displayOrder, pipelineId, isWonStage, isLostStage. |
crm_pipeline_risk_scan |
Scan open HubSpot deals for risk: days inactive, days-to-close, missing fields (amount/owner/next_step/last_activity), close-date slipping, and high-value-low-activity. Computes a transparent weighted risk_score (0–100) and a PRIVATE suggested nudge draft to the owner (never sent). Uses agent_settings.risk_thresholds. Returns { scanned_count, flagged:[...] }. |
crm_score_prospects |
Score prospects 0–100 against the workspace ICP rules (agent_settings.icp_rules): country 25, industry 25, employee-in-range 20, any title keyword 30. With no rules configured, every prospect scores 50 (reason no_icp_rules_configured). Returns { scored: [{ id|email, score, matched[], missed[] }] }. |
crm_search_contacts |
Search the connected CRM (HubSpot) for contacts by free text (name, email, company). Returns matching contacts. Use before creating a contact to check if one already exists. |
crm_status |
Report whether HubSpot is connected for this workspace. Attempts a cheap HubSpot read; returns { connected: bool, ... }. |
crm_upsert_company |
Create-or-update a company in the connected CRM, matched by domain — never creates a duplicate. Requires a read + write HubSpot connection. |
crm_upsert_contact |
Create-or-update a contact in the connected CRM, matched by email — never creates a duplicate. Use when the user asks to add or update a contact in HubSpot. Requires a read + write HubSpot connection. |
crm_weekly_brief_compose |
Compose a structured weekly sales brief from last week's activities, current open deals, the pipeline risk scan, and per-rep aggregates. Pure data (no writes). Returns { week_ending, last_week_summary, this_week_priorities, stale_deals, deals_without_next_step, top_risks, per_rep_questions, suggested_bj_notes, action_items }. Pass the result to notion_publish_weekly_brief to draft it in Notion. |
delete_campaign |
Permanently delete a campaign and all its workflow executions. Prefer archive for soft removal. |
delete_organization |
Delete an organization. Linked prospects get organization_id = null. |
delete_prospect |
Delete a prospect permanently. |
delete_prospect_list |
Delete a prospect list (prospects themselves are NOT deleted). |
disable_trigger |
Disable an intent trigger (sets active=false) so it stops re-polling. Returns the updated {data: IntentTrigger}, or a 404 error if the trigger is not in this workspace. |
disconnect_account |
Disconnect a LinkedIn or email account from the workspace. Use hosted_auth_link to reconnect. |
enrich_organization |
Run Claire deep-research on an organization and save the result onto the record. SYNCHRONOUS — waits for Claire (may take 30-180s) and returns {status} ('complete' with the research, 'skipped' if the org has no name, or 'busy' if another enrichment is already running). Counts against the workspace's daily enrichment quota; returns a 429 quota error when exhausted. Set force=true to re-enrich an already-complete record. |
enrich_prospect |
Run Claire deep-research on a prospect and save the result onto the record. SYNCHRONOUS — waits for Claire (may take 30-180s) and returns {status} ('complete' with the research, 'skipped' if the prospect has no name, or 'busy' if another enrichment is already running). Counts against the workspace's daily enrichment quota; returns a 429 quota error when exhausted. Set force=true to re-enrich an already-complete record. Call this BEFORE crafting personalized outreach. |
explorium_add_more |
Append more leads to an existing Explorium list (async). Re-runs the saved search for additional results. Poll the list (or use wait_for_prospect_list) for progress. |
explorium_create_company_list |
Create an Explorium-backed company (organization) list (async). Runs a business search with the given filters → enrichment → ingestion. Returns a pending list DTO; poll its status (or use wait_for_prospect_list) until completed. Auto-generates an idempotency_key if not provided so retries are safe. |
explorium_create_list |
Create an Explorium-backed prospect (people) list (async). Runs a people search with the given filters → enrichment → ingestion. Returns a pending list DTO; poll its status (or use wait_for_prospect_list) until completed. Auto-generates an idempotency_key if not provided so retries are safe. |
generate_message_preview |
Use AI to generate a personalized message for a prospect. Charges credits. Specify channel and prompt. |
generate_workflow |
Use AI to generate a campaign workflow from natural language. Charges credits. Provide a prompt or structured fields. |
get_account |
Get full details of a connected account — provider, channel, config, sync status. |
get_account_rate_limits |
Get daily/weekly sending limits and current usage for a specific account. |
get_availability |
Fetch open booking slots for an event type, grouped by date. event_type_id defaults to the connection's default event type; start defaults to now and end to now + 14 days; time_zone is an optional IANA string to localize slots. Returns {data: {eventTypeId, slotsByDate: {: [{start, end?}]}}}. Fails with 409 if no calendar is connected, or 400 if no event type id is available. |
get_campaign |
Get full details of a campaign by ID — workflow, scheduling, accounts, prospect lists, stats. |
get_campaign_engagement_summary |
Engagement summary for a campaign: open/click/reply/bounce rates plus per-link click detail (top links, distinct URLs clicked). |
get_campaign_lead_analytics |
Per-prospect breakdown — where each lead is in the workflow and message event history. |
get_campaign_memory |
Read Max's durable memory for a campaign (ICP, decisions, notes). Recall this when working on one of several simultaneous campaigns so you keep them straight. |
get_campaign_node_run_counts |
Map of workflow node ID → execution count. Useful for funnel visualization. |
get_campaign_stats |
Aggregate performance stats — email open/reply rates, LinkedIn connection/reply rates, execution counts. |
get_chat |
Get full details of a single Unibox chat/conversation thread. |
get_circuit_status |
Inspect the circuit-breaker state for each upstream host the MCP server has called. Shows whether requests are being fast-failed (state=open), probing (half-open), or flowing normally (closed). |
get_dashboard_kpis |
Workspace-wide aggregate stats — execution counts, email/LinkedIn rates, completion percentage. |
get_email_tracking_events |
Raw per-event email tracking rows for a prospect (opens, clicks, replies, bounces) with url/ip/user_agent detail. Newest first. Optionally filter by event_types. |
get_enrichment_credits |
Return the workspace's daily enrichment quota usage: {cap, used_today, remaining}. Check this before a large bulk enrichment to confirm there's headroom. |
get_enrichment_status |
Check the enrichment state of a single prospect or organization. Provide exactly one of prospect_id or organization_id. Returns {enrichment_status, enrichment_updated_at, has_research}. |
get_inbox_autopilot_status |
Return the workspace's current inbox autopilot setting: {enabled, mode ('auto_safe'|'draft_all'|'off'), daily_cap}. Use this to confirm whether autopilot is active and how it is configured before changing it or reviewing drafts. |
get_link_click_details |
Link clicks for a campaign grouped by url, with total click counts and unique-prospect counts. Sorted by clicks desc. |
get_organization |
Get full details of an organization — domain, industry, employee count, funding, social URLs. |
get_prospect |
Get full profile of a prospect — name, title, company, LinkedIn, email, location, enrichment data. |
get_prospect_campaign_activity |
Chronological log of message events for a prospect across all campaigns (newest first). |
get_prospect_engagement_timeline |
Chronological email engagement timeline for a prospect (oldest first) — every open, click, reply, and bounce. |
get_prospect_list |
Get full details of a prospect list by ID — status, search config, result counts, timestamps. |
get_signal_history |
Return the detected SignalEvent rows for a single trigger — each event records whether the poll found changes, a summary, and the raw scrape. Returns {data: SignalEvent[]}. |
get_signal_proposal |
Fetch a single signal proposal by id, including its full recommendation (campaign name/description, workflow_config, target_prospect_ids, estimated contacts/credits, matched ICP, and entity). Returns {data: SignalProposal}, or a 404 error if not found. |
get_upcoming_meetings |
List upcoming non-cancelled meetings ordered by start time ascending. from defaults to now and to defaults to now + 30 days (both ISO8601). Returns {data: meeting row[]} (status one of scheduled|rescheduled|completed|no_show). |
get_workspace_profile |
Fetch workspace profile settings (company info) for the authenticated workspace via DigitalCrew API. Uses the MCP connection Authorization: Bearer token when set. |
hosted_auth_link |
Generate a short-lived URL for the user to connect a LinkedIn or email account via Unipile hosted auth. |
import_prospect_list_csv |
Create a new prospect list and import prospects in one call. Each row needs an email (for dedup). |
launch_campaign |
Launch a draft campaign — transitions draft → active and creates workflow executions for each prospect. |
linkedin_cancel_invitation |
Cancel/withdraw a sent LinkedIn invitation by its invitation_id (from list_invitations_sent). |
linkedin_comment_on_post |
Comment on a LinkedIn post. |
linkedin_create_post |
Create a LinkedIn post from the connected account. |
linkedin_find_profile |
Find a LinkedIn profile by name, company and/or title. Searches LinkedIn directly — always use this before any invitation or message. If multiple people share the name, returns candidates with ambiguous:true; show them to the user and ask which one. Never guess a slug or proceed when ambiguous. |
linkedin_get_all_messages |
Get all recent LinkedIn messages across all conversations. |
linkedin_get_company_profile |
Get a LinkedIn company profile by its identifier or slug. |
linkedin_get_conversation_messages |
Get messages in a specific LinkedIn conversation. |
linkedin_get_own_profile |
Get the profile of the connected LinkedIn account. Useful to confirm the account is active. |
linkedin_get_profile |
Get a full LinkedIn profile by slug (public identifier). Returns provider_id and profile data. |
linkedin_get_user_posts |
Get recent posts by a LinkedIn user. |
linkedin_list_connections |
List first-degree LinkedIn connections of the connected account. |
linkedin_list_conversations |
List LinkedIn conversations (inbox). |
linkedin_list_invitations_received |
List pending LinkedIn invitations received from others. |
linkedin_list_invitations_sent |
List pending LinkedIn invitations you have sent. |
linkedin_react_to_post |
React to a LinkedIn post. |
linkedin_reply_in_chat |
Reply in an existing LinkedIn conversation. |
linkedin_search_people |
Search LinkedIn for people by keywords. |
linkedin_send_invitation |
Send a LinkedIn connection request. ALWAYS call find_profile first to get provider_id. |
linkedin_send_message |
Send a LinkedIn direct message to start a new conversation. Requires existing connection or InMail credits. |
list_accounts |
List all connected LinkedIn and email accounts — name, email, status, daily limits. |
list_campaigns |
List all outreach campaigns. Filter by status, search by name, paginate and sort. |
list_chat_messages |
Get all messages in a conversation — body, direction (in/out), timestamp, status. |
list_chats |
List LinkedIn and email conversations — filter by channel, prospect, account, or archived status. |
list_failed_requests |
Inspect the dead-letter queue — write requests (POST/PATCH/DELETE) that exhausted all retries. Useful for forensics or manual replay. Backed by Redis if REDIS_URL is set, otherwise an in-memory ring buffer of up to 500 entries. |
list_inbox_drafts |
List the autopilot-generated reply drafts awaiting review (status='draft') for the workspace, newest first. Returns {data: InboxAutopilotAction[]}, each row including intent, sentiment, confidence, and the proposed reply_body. Review these before calling approve_inbox_draft or reject_inbox_draft. |
list_intent_signals |
List the workspace's intent triggers (optionally filtered by active state) so you can see what is being monitored and review recent signal activity. Returns {data: IntentTrigger[]}, each row including last_run_at and last_error. Use get_signal_history to drill into the detected events for a specific trigger. |
list_organizations |
List all organizations/companies — search by name or domain, filter by industry/country. |
list_prospect_list_members |
List all prospects in a specific list — paginated, searchable, sortable. |
list_prospect_lists |
List all prospect lists — name, status, result counts, and search criteria. |
list_prospects |
List prospects with rich filtering — search, status, org, titles, countries, industries, pagination, sorting. |
list_signal_proposals |
List the AI-generated campaign proposals produced from detected signals, optionally filtered by status (pending, approved, rejected, modified, launched, expired). An unknown status is ignored and all proposals are returned. Returns {data: SignalProposal[]}. Review pending proposals before approving them. |
meetings |
Read the workspace's meetings from the meeting hub. The workspace comes from your bearer token — prospect_id filters within it. |
modify_proposal |
Adjust a pending proposal WITHOUT launching it: pass modifications (titles, target_prospect_ids, campaign_name, campaign_description) to re-select prospects and regenerate the campaign workflow. The proposal stays pending so it can be reviewed and approved later. Returns the updated {data: SignalProposal}. Fails with 409 if the proposal is not pending, or 404 if not found. |
notion_append_blocks |
Append an array of Notion block JSON objects to an existing page (chunked into ≤100-block requests). Requires agent_settings.allow_notion_writes (default true). Returns { appended }. |
notion_create_page |
Create a new Notion page under a parent page, with an optional body of Notion block JSON. Requires agent_settings.allow_notion_writes (default true). Returns { id, url }. |
notion_get_page |
Fetch a Notion page object plus all of its child blocks (paginated). Returns { page, blocks }. |
notion_publish_weekly_brief |
Render a crm_weekly_brief_compose output as a DRAFT Notion page (H1 title + H2 section per part) under the workspace's Drafts/the assistant parent. Write-gated by agent_settings.allow_notion_writes. Defaults parent to agent_settings.notion_drafts_parent_id and template to agent_settings.notion_weekly_template_id. Returns { page_id, url, status:'draft', partial_success? }. |
notion_search_pages |
Search the connected Notion workspace for pages matching a free-text query. Returns [{ id, title, url }]. |
pause_campaign |
Pause an active campaign — stops dequeueing new actions (in-flight calls finish). |
propose_times |
Return the n soonest available slots as a flat list — handy for offering a prospect a few concrete times. count is 1-50 (default 3); event_type_id defaults to the connection's default event type; time_zone is an optional IANA string. Returns {data: {eventTypeId, slots: [{start, end?}]}}. Fails with 409 if no calendar is connected. |
prospect_list_meetings |
List the meetings involving one prospect, newest first — the prospect meeting feed. Same filters and shape as the meetings group's list action, with prospect_id required. Returns {data: MeetingSessionSummaryDto[], nextCursor}. Paginated: echo nextCursor back as cursor; null means the last page. prospect_id filters within your authenticated workspace. |
prospect_list_tasks |
List the tasks about one prospect, newest first. Same filters and shape as the tasks group's list action, with prospect_id required. Returns {data: TaskDto[], nextCursor}. Paginated: echo nextCursor back as cursor; null means the last page. prospect_id filters within your authenticated workspace. |
reject_inbox_draft |
Reject a drafted reply so it will not be sent, setting the action's status to 'rejected'. No message is sent. Returns the updated {data: InboxAutopilotAction}. Fails with 404 (draft not found) or 409 (action not in 'draft' status). |
reject_proposal |
Reject a pending proposal so it will not be launched. Returns the updated {data: SignalProposal (status 'rejected')}. Fails with 409 if the proposal is not pending. |
remove_prospects_from_list |
Remove prospects from a prospect list by their UUIDs. |
restore_campaign |
Restore an archived campaign back to draft status. |
resume_campaign |
Resume a paused campaign — transitions paused → active. |
search_prospect_lists |
Preview filter results without creating a list — search by titles, countries, industries, employee count, etc. |
send_booking_link |
Compose the rep's public Cal.com booking link so it can be shared with a prospect. Does NOT call Cal.com. Optionally pass prospect_id for context. Returns {data: {url, username}}. Fails with 409 if no calendar is connected. |
send_chat_message |
Send a manual reply in an existing Unibox chat. Channel (email/LinkedIn) is inferred from the chat. |
send_new_email |
Compose and send a brand-new email to any recipient, outside any chat or campaign. Sends from the workspace's connected email account. Use send_chat_message instead to reply within an existing conversation. |
set_inbox_autopilot |
Set the workspace's inbox autopilot configuration. enabled is the master kill switch (false = do nothing). When enabled, the autopilot reads each inbound email reply, drafts a suggested reply in the rep's voice, and NOTIFIES the user to review/approve it — it NEVER auto-sends; a human approves every reply (via list_inbox_drafts + approve_inbox_draft). mode: 'off' suppresses entirely; 'auto_safe' and 'draft_all' both draft-and-notify (auto-send is disabled in v1). daily_cap is reserved for a future auto-send mode. Returns the saved {data: inboxAutopilot}. |
simulate_account_connected |
Fire a Unipile account-connected webhook event. Use to test the handler that creates/updates an account record when Unipile finishes connecting. |
simulate_account_status |
Fire a Unipile account-status webhook. Use to test status changes (OK, CREDENTIALS, ERROR, CONNECTING, STOPPED). Useful for verifying that disconnected accounts are flagged correctly. |
simulate_email_tracking |
Fire a Unipile mail_opened or mail_link_clicked tracking event. Pass label as 'execution_state_id:node_id' to link the event to a campaign execution and increment open/click counts. |
simulate_linkedin_messaging |
Fire a Unipile LinkedIn messaging event (message_received, message_read, message_delivered, etc.). Use to test reply detection and campaign execution advancement on LinkedIn. |
simulate_new_email |
Fire a Unipile mail_received webhook. Use to test inbound email handling — reply detection, chat thread creation, and campaign execution advancement. |
simulate_new_relation |
Fire a Unipile new_relation event (LinkedIn invitation accepted). Use to test that the connection record is updated and any waiting campaign executions are advanced. |
stop_campaign |
Stop an active or paused campaign permanently — cannot be resumed. |
tasks |
Read tasks and propose new ones. Agents propose, humans dispose: you can create suggestions and drive accepted work forward, but only a human can approve or reject a suggestion. The workspace comes from your bearer token. |
update_account |
Update account sender name, timezone, and working hours. |
update_account_rate_limit |
Update daily or weekly sending cap for a specific rate-limit row. |
update_campaign |
Partial update of a campaign — name, description, workflow, lists, accounts, scheduling. |
update_campaign_memory |
Record/update Max's durable memory for a campaign. Pass a partial 'memory' object (top-level keys merge; send the full array to change decisions/notes). Use it to remember ICP, decisions, and progress per campaign. |
update_chat |
Update chat metadata — title, read state, archived status, prospect link. |
update_organization |
Update an organization's fields (partial update). |
update_prospect |
Update a prospect's fields (partial update). |
update_prospect_list |
Update a prospect list (only list_name and status are editable). |
update_workspace_profile |
Create or update workspace profile settings for the authenticated workspace via DigitalCrew API (PUT upsert). Uses the MCP connection Authorization: Bearer token when set. |
wait_for_prospect_list |
Poll a prospect list (typically Apollo-backed) until its status becomes completed, failed, or cancelled — or the timeout elapses. Use this after apollo_create_list / apollo_add_more so the agent doesn't need to manage polling itself. |
Tool definitions and validation use Zod per domain under features/pilot-tools/<domain>/schema.ts.
- Node.js (see Next.js 15 requirements)
- pnpm (see
packageManagerinpackage.json)
Set these environment variables (e.g. in .env.local for local dev, or in your host’s env for production):
| Variable | Required | Description |
|---|---|---|
DIGITALCREW_API_BASE_URL |
Yes | Base URL of the Digital Crew API (no trailing slash) |
MCP_GATEWAY_SECRET |
Yes (prod) | Shared secret; callers send X-MCP-Gateway-Key on /mcp and /chat |
MCP_ADMIN_GATEWAY_KEY |
No | Separate key required for admin tools when ENABLE_ADMIN_TOOLS=true |
ALLOW_ENV_TOKEN_FALLBACK |
No | Set true only for legacy scripts that cannot send per-request tokens |
DIGITALCREW_API_TOKEN or DIGITALCREW_BEARER_TOKEN |
No* | Used only when ALLOW_ENV_TOKEN_FALLBACK=true |
ENABLE_ADMIN_TOOLS |
No | Set true to expose dead-letter / circuit-breaker admin tools |
ENABLE_WEBHOOK_SIMULATORS |
No | Set true to expose webhook simulation tools (dev/staging only) |
CHAT_RATE_LIMIT_PER_MINUTE |
No | Per-gateway-key chat limit (default 20) |
CHAT_DAILY_REQUEST_CAP |
No | Daily OpenRouter call cap per key (default 500) |
REDIS_URL |
No | Shared dead-letter queue and circuit-breaker state across instances |
*Preferred auth: Authorization: Bearer <token> on the MCP HTTP request, or bearer_token on a tool call. Precedence: tool bearer_token → MCP Authorization → env (only if fallback enabled).
pnpm install
# Create .env.local with DIGITALCREW_API_BASE_URL (and optional token vars)
pnpm devThe MCP endpoint is:
- Local:
http://localhost:3000/mcp - Production:
https://<your-deployment>/mcp
Point your MCP client at that URL using Streamable HTTP transport.
scripts/test-streamable-http-client.mjs lists tools via Streamable HTTP. It expects the @modelcontextprotocol/sdk client package available in your environment (install it if the script is not runnable yet):
node scripts/test-streamable-http-client.mjs http://localhost:3000Note: SSE is currently disabled in
app/mcp/route.ts(disableSse: true). The olderscripts/test-client.mjs(SSE) is not aligned with the default setup unless you enable SSE and Redis per Vercel’s MCP pattern.
- Next.js App Router
mcp-handler(Vercel MCP adapter)- Zod for tool input schemas
- Enable Fluid compute for efficient execution.
- Adjust
maxDurationinapp/mcp/route.tsif your plan allows (e.g. up to 800s on Pro/Enterprise). - SSE: If you switch
disableSsetofalse, attach Redis and setREDIS_URLas required by the adapter. See also the Next.js MCP template.
- Digital Crew — AI workforce platform
- Max — Outreach control center — Max product site
- Model Context Protocol — MCP specification
Private project ("private": true in package.json). Use and deployment are governed by your Digital Crew agreements.