Sales Prep MCP
Allows reading email threads, drafting and sending follow-up emails, and performing mail merges from a spreadsheet, enabling pre-call briefs, call recaps, and outbound communication.
Provides OAuth authentication to Google services, enabling the server to access Google Calendar, Gmail, Google Sheets, Google Forms, Google Docs, and Google Drive for reading and writing data.
Enables reading calendar events to prepare pre-call briefs, schedule follow-up meetings, book demos, flag deals without meetings, and handle no-show recoveries.
Allows reading documents to extract line items from signed quotes for invoice creation.
Provides access to files for retrieving policies to prefill security questionnaires.
Enables reading form responses to qualify inbound demo requests and book meetings.
Allows reading and writing spreadsheet data for lead enrichment, prefilling security questionnaires, and mail merges with status updates.
Enables creating issues or projects for closed-won handoffs, such as setting up internal tasks.
Provides tools for creating and updating pages and databases, used for CRM updates, account research briefs, pipeline digests, and security questionnaire prefill.
Allows sending direct messages and posting to channels for pre-call briefs, demo booking alerts, closed-won notifications, pipeline digests, and trial alerts.
Enables creating payment links and invoices, and reading subscription/trial data for deal closure, invoicing, and trial management.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Sales Prep MCP prepare a pre-call brief for tomorrow's external meetings"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Sales Prep and Follow-up
Pre-call briefs, follow-up emails, enriched lead sheets and payment links, from your calendar.
An MCP server with 14 workflows across Google Calendar, Tavily, Firecrawl, Gmail, Slack, Granola, Notion, Google Sheets, Google Forms, Stripe, Google Docs, Linear and Google Drive. Each workflow is a prompt your agent runs as a slash command, over the 33 tools it needs and no others.
uv tool install https://github.com/r28ai/sales-prep-mcp/releases/download/v0.1.0/sales_prep_mcp-0.1.0-py3-none-any.whl
claude mcp add sales -- sales-prep-mcpIt installs with uv from this repository's release, with no git and nothing to build; nothing but Charter and the libraries it uses comes from PyPI. To update, run the install line from the latest release. If a desktop app cannot find sales-prep-mcp, give it the full path from which sales-prep-mcp (where sales-prep-mcp on Windows).
Then ask your agent to connect your apps, or run /mcp__sales__setup.
Connect your apps
Ask the agent to connect one ("connect Linear"). It tells you where to get that app's key and the command that stores it, and the next call works, with no restart. The agent never asks for a key in the chat.
Or connect everything this server uses from a terminal:
sales-prep-mcp login # each app in turn
sales-prep-mcp login tavily # just one
sales-prep-mcp status # what is connectedTokens and keys go to your operating system's keychain (macOS Keychain, Windows Credential Manager, the Secret Service on Linux), and are checked with one read-only call to the app's own API before they are kept. Every key, token and OAuth client is yours: we register no app with any of these services, and nothing passes through a server of ours, because there isn't one.
App | How it connects | Or set |
Browser sign-in, over your own OAuth client (make one). |
| |
Tavily | Your own key (get one), entered once. |
|
Firecrawl | Your own key (get one), entered once. |
|
Slack | Your own key (get one), entered once. A bot token from your own Slack app, which the guide sets up in about three minutes. |
|
Granola | Your own key (get one), entered once. In the Granola app: Settings → Connectors → API keys. Business plan or above. |
|
Notion | Your own key (get one), entered once. Then share the pages it should see with the integration. |
|
Stripe | Your own key (get one), entered once. |
|
Linear | Your own key (get one), entered once. |
|
A variable set in your client's config always wins over the keychain.
Related MCP server: Chief of Staff
Workflows
Workflow | What you get | Apps |
Pre-call brief for tomorrow's external meetings | Company news, what they sell and the last email thread, in a DM the night before. | Google Calendar, Tavily, Firecrawl, Gmail, Slack |
Call → follow-up email and next step | The recap email is drafted, the next meeting held and the CRM updated before the rep leaves the room. | Granola, Gmail, Google Calendar, Notion |
Lead list enrichment | A column of domains becomes size, product, pricing model and a recent signal per row. | Google Sheets, Firecrawl, Tavily |
Inbound demo request → booked | A qualified request gets a meeting and a researched AE within the hour. | Google Forms, Firecrawl, Google Calendar, Gmail, Slack |
Scope agreed on a call → payment link | The deal closes while the buyer still remembers saying yes. | Granola, Stripe, Gmail |
Signed quote → Stripe invoice | The quote's line items become the invoice's, with no retyping. | Google Docs, Stripe |
Closed-won handoff | Account page, customer record and internal channel exist the day the deal closes. | Stripe, Notion, Linear, Slack |
Target account research | Every account in the target list gets a cited brief on its page. | Notion, Tavily |
Pipeline digest | Deals with no meeting booked in two weeks get flagged to their owner. | Notion, Google Calendar, Slack |
Trial ending → rep alert and nudge | Trials that end in three days get a human touch instead of a silent expiry. | Stripe, Gmail, Slack |
No-show recovery | A meeting with no notes was a no-show, and it gets a reschedule email. | Google Calendar, Granola, Gmail |
Security questionnaire, prefilled | Two hundred rows answered from your own policies, for a human to review. | Google Sheets, Notion, Google Drive |
New thread from an unknown domain → CRM | Inbound that didn't come through a form still lands in the CRM with context. | Gmail, Notion, Firecrawl |
Mail merge from a sheet | Personalised drafts per row, status written back, nothing sent without review. | Google Sheets, Gmail |
Every prompt takes one optional argument, details: the repo, team, channel, customer or date range you mean, so the agent does not have to ask. In Claude Code, put it in quotes, or only its first word arrives:
/mcp__sales__pre_call_brief_for_tomorrow_s_external_meetings "tomorrow's calls only, skip internal ones"Reads run without asking. Before anything that creates, sends, changes or deletes, the prompt tells the agent to show you the call and wait.
2 of the 14 workflows need no Google or Granola credential.
Other clients
Claude Desktop: install uv if you have not, since Claude Desktop starts the server with it, then open the .mcpb from the latest release. Claude asks for any keys in its own settings and keeps them in your keychain. The first start takes a few seconds longer, while uv installs it.
VS Code (.vscode/mcp.json): VS Code asks for each key the first time the server starts and stores it securely. Leave out any you stored with login.
{
"inputs": [
{
"type": "promptString",
"id": "google-client-secret",
"description": "Google: OAuth client secret",
"password": true
},
{
"type": "promptString",
"id": "tavily-api-key",
"description": "Tavily: API key",
"password": true
},
{
"type": "promptString",
"id": "firecrawl-api-key",
"description": "Firecrawl: API key",
"password": true
},
{
"type": "promptString",
"id": "slack-bot-token",
"description": "Slack: Bot token (xoxb-\u2026)",
"password": true
},
{
"type": "promptString",
"id": "granola-api-key",
"description": "Granola: API key",
"password": true
},
{
"type": "promptString",
"id": "notion-api-key",
"description": "Notion: Integration secret (ntn_\u2026)",
"password": true
},
{
"type": "promptString",
"id": "stripe-api-key",
"description": "Stripe: Secret or restricted key",
"password": true
},
{
"type": "promptString",
"id": "linear-api-key",
"description": "Linear: Personal API key",
"password": true
}
],
"servers": {
"sales": {
"type": "stdio",
"command": "sales-prep-mcp",
"env": {
"GOOGLE_CLIENT_SECRET": "${input:google-client-secret}",
"TAVILY_API_KEY": "${input:tavily-api-key}",
"FIRECRAWL_API_KEY": "${input:firecrawl-api-key}",
"SLACK_BOT_TOKEN": "${input:slack-bot-token}",
"GRANOLA_API_KEY": "${input:granola-api-key}",
"NOTION_API_KEY": "${input:notion-api-key}",
"STRIPE_API_KEY": "${input:stripe-api-key}",
"LINEAR_API_KEY": "${input:linear-api-key}",
"GOOGLE_CLIENT_ID": ""
}
}
}
}Cursor (.cursor/mcp.json) starts it the same way:
{
"mcpServers": {
"sales": {
"command": "sales-prep-mcp"
}
}
}Codex (~/.codex/config.toml) starts a turn without waiting for a server unless it is required, and then the agent has none of its tools. required = true makes the session wait for it, and startup_readiness = "catalog" waits for its tool list rather than just its connection:
[mcp_servers.sales]
command = "sales-prep-mcp"
required = true
startup_readiness = "catalog"
startup_timeout_sec = 30Name the server sales. A host builds each tool's name from that key, and a longer one can push a tool past the 64 characters a function name allows.
Built with Charter
Every tool here is a Charter declaration: a Pydantic schema saying where each field goes on the wire. Charter's runtime builds the request, attaches and refreshes the credential, and trims the response before the model reads it. It runs in your process, with no proxy and no telemetry.
The 33 tool schemas come to 47,240 tokens.
The same tools work in your own agent, without MCP:
from charter.adapters.openai import to_openai_tools
from charter_packs_mcp import FAMILIES
tools = FAMILIES["sales"].tools()
definitions = to_openai_tools(tools) # or charter.adapters.langchainNeed an API that isn't here? Write a pack: your coding agent writes the declarations, and Charter's conformance suite checks them.
Google Calendar:
gcalendar_events_list,gcalendar_events_insertTavily:
tavily_search,tavily_research_create,tavily_research_getFirecrawl:
firecrawl_scrape,firecrawl_extractGmail:
gmail_threads_list,gmail_drafts_create,gmail_messages_sendSlack:
slack_chat_post_message,slack_conversations_createGranola:
granola_notes_get,granola_notes_listNotion:
notion_pages_update,notion_pages_create,notion_data_sources_query,notion_searchGoogle Sheets:
gsheets_spreadsheets_values_get,gsheets_spreadsheets_values_updateGoogle Forms:
gforms_forms_responses_listStripe:
stripe_prices_list,stripe_checkout_sessions_create,stripe_customers_create,stripe_invoice_items_create,stripe_invoices_create,stripe_invoices_finalize,stripe_subscriptions_list,stripe_customers_retrieveGoogle Docs:
gdocs_documents_getLinear:
linear_customer_createGoogle Drive:
gdrive_files_list,gdrive_files_export
License
Apache 2.0.
Available Tools
35 toolsconnectA
Connect one app this server uses. For an app that issues keys, says where to get one and the terminal command that stores it. For Google, once the user's own OAuth client is set, starts the browser sign-in and returns at once: the user approves in the browser and the next call works. To see which apps are connected, call connection_status. Never ask the user for a key in the chat.
Inputs: app (required).
connection_statusARead-only
See which apps this server is connected to, and how to connect each one that is not. Changes nothing.
Inputs: no named parameters.
firecrawl_extractA
Extract structured data from one or more URLs using an LLM. Poll results with extract_status.
Inputs: urls (required), prompt, schema, showSources, +6 more.
firecrawl_scrapeB
Scrape a single URL and optionally extract information. Use when the user wants to read or summarize a specific webpage. Supports markdown, HTML, screenshots, and structured JSON extraction.
Inputs: url (required), proxy, maxAge, minAge, +22 more.
gcalendar_events_insertC
Create a calendar event; returns details of the event.
Inputs: event (required), calendarId (required), sendUpdates, maxAttendees, +3 more.
gcalendar_events_listCRead-onlyIdempotent
List events matching a given search filter.
Inputs: calendarId (required), q, iCalUID, orderBy, +14 more.
gdocs_documents_getARead-onlyIdempotent
Read a document's full structural content. The document ID is the long string in its URL, between '/d/' and '/edit'. Pass include_tabs_content=true for a document with several tabs, since the default response covers only the first.
Inputs: documentId (required), includeTabsContent, suggestionsViewMode.
gdrive_files_exportARead-onlyIdempotent
Export a Google Workspace document to the requested MIME type and return the exported content. Limited to 10 MB. Common MIME types: text/plain, text/html, text/csv, application/pdf.
Inputs: fileId (required), mimeType (required).
gdrive_files_listARead-onlyIdempotent
List the user's files. Returns all files by default, including trashed files; add trashed = false to q to hide them. q is Drive's search grammar — name contains 'Q3' and mimeType = 'application/vnd.google-apps.folder'.
Inputs: q, spaces, corpora, driveId, +7 more.
gforms_forms_responses_listARead-onlyIdempotent
List a form's submitted responses, newest page first, up to 5000 per page. The only supported filter is on submission time: pass filter='timestamp >= 2026-01-01T00:00:00Z' to read what has arrived since a point in time. Answers come back keyed by questionId.
Inputs: formId (required), filter, pageSize, pageToken.
gmail_drafts_createC
Save an email draft to Gmail.
Inputs: body (required), userId.
gmail_messages_sendC
Send an email via the Gmail API.
Inputs: body (required), userId.
gmail_threads_listCRead-onlyIdempotent
List Gmail threads.
Inputs: q, userId, labelIds, pageToken, +2 more.
granola_notes_getARead-onlyIdempotent
Read one meeting note: its AI summary, the people who attended, the calendar event it was taken against, and the folders it belongs to. Pass include='transcript' to get the transcript inline as well. A transcript too large to inline answers 413 TRANSCRIPT_TOO_LARGE. Read it with notes_transcript_get instead.
Inputs: noteId (required), include.
granola_notes_listARead-onlyIdempotent
List meeting notes, filtered by when they were created or last updated and optionally narrowed to one folder and its subfolders. Returns each note's id, title, owner and timestamps, not its content. Fetch that with notes_get. Only notes that already have a generated AI summary appear here.
Inputs: cursor, folderId, pageSize, createdAfter, +2 more.
gsheets_spreadsheets_values_getCRead-onlyIdempotent
Returns a range of values from a spreadsheet.
Inputs: range (required), spreadsheetId (required), majorDimension, valueRenderOption, +1 more.
gsheets_spreadsheets_values_updateC
Sets values in a range of a spreadsheet.
Inputs: range (required), valueRange (required), spreadsheetId (required), valueInputOption (required), +3 more.
linear_customer_createB
Create a customer. Only name is required.
Inputs: variables (required).
notion_data_sources_queryB
Get the rows of a data source, optionally filtered and sorted. The filter grammar is one condition per column type, composed with and and or up to two levels deep. Read the schema first if you do not know the column names — a filter naming a column that is not there is a 400.
Inputs: dataSourceId (required), body, filterProperties.
notion_pages_createA
Create a page — as a subpage of another page, or as a row of a database by giving its data_source_id as the parent. Content comes as a markdown string Notion parses into blocks, or from a template: one or the other, never both.
Inputs: body (required), filterProperties.
notion_pages_updateA
Update a page's property values, icon, cover, or trash state. Properties not named are left alone, and a value set to null is cleared. This cannot move a page — use pages_move.
Inputs: body (required), pageId (required), filterProperties.
notion_searchA
Search the titles of pages and data sources shared with this integration. Titles only — it does not search page content — and it returns nothing that has not been shared. With no query it lists everything visible, which is how you find the IDs to start from.
Inputs: body.
slack_chat_post_messageA
Send a message to a Slack channel, private group, or DM. Provide text for a plain message; set thread_ts to reply inside an existing thread.
Inputs: channel (required), text, parse, blocks, +13 more.
slack_conversations_createB
Create a channel. Slack refuses a name that is already taken.
Inputs: name (required), isPrivate.
stripe_checkout_sessions_createA
Create a Checkout Session and get a hosted payment URL. Pass line_items with price IDs and quantities, mode='payment' for one-time or mode='subscription' for recurring.
Inputs: mode (required), uiMode, currency, customer, +9 more.
stripe_customers_createC
Create a customer.
Inputs: name, email, phone, address, +6 more.
stripe_customers_retrieveBRead-onlyIdempotent
Retrieve a single customer by ID.
Inputs: customer (required).
stripe_invoice_items_createA
Add a line to an invoice. Name an existing price as pricing.price, or give an amount directly. Without invoice the line waits for the customer's next subscription invoice.
Inputs: customer (required), amount, period, invoice, +11 more.
stripe_invoices_createA
Create a draft invoice for a customer. It bills nothing until finalised, so this is the safe half of invoicing.
Inputs: customer (required), footer, dueDate, currency, +11 more.
stripe_invoices_finalizeA
Finalise a draft invoice, making it open and payable. The amounts stop being editable at this point.
Inputs: invoice (required), autoAdvance.
stripe_prices_listARead-onlyIdempotent
List prices. Filter by product to find what a product costs.
Inputs: limit, active, product, currency, +2 more.
stripe_subscriptions_listBRead-onlyIdempotent
List subscriptions. Filter by customer or status.
Inputs: limit, price, status, customer, +2 more.
tavily_research_createA
Create an async research task that searches, analyzes sources, and generates a cited report. Poll results with research_get.
Inputs: input (required), files, model, outputLength, +4 more.
tavily_research_getARead-onlyIdempotent
Retrieve the status and results of a research task by request_id. HTTP 202 means still running; poll until HTTP 200.
Inputs: requestId (required), includeUsage.
tavily_searchA
Execute a real-time web search optimized for AI agents. Use when sources are unknown or current web context is needed. Prefer search_depth advanced with chunks_per_source 3 for stronger evidence per source.
Inputs: query (required), topic, country, endDate, +21 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 35 tools
Most tools are clearly separated by app prefix and action, e.g. gmail_messages_send vs gmail_drafts_create. Some overlap remains among tavily_search, tavily_research_create, firecrawl_scrape, and firecrawl_extract, which could be confused for general web research tasks. Overall the set is mostly distinct but not perfectly unambiguous.
Tool names follow a highly predictable snake_case pattern, usually app/service/resource/action, such as notion_pages_create, stripe_invoices_finalize, and gcalendar_events_list. Minor deviations like connect and connection_status are meta-tools and do not break the pattern. The naming convention is consistent throughout.
With 35 tools, this is well above the typical 3-15 well-scoped range and exceeds the 25+ threshold for being too many. Although the server spans many integrations, the surface feels heavy and likely includes tools that could be consolidated or conditionally exposed. The count is not well-scoped for a single sales-prep agent.
Coverage is broad across apps but shallow and has significant gaps: Gmail can list threads but not read message bodies, Calendar can list/insert but not update/delete, and Linear only creates customers. granola_notes_get even references a notes_transcript_get tool that is not present, creating a dead end. These gaps would likely cause agent failures in real sales-prep workflows.
Maintenance
Related MCP Connectors
Give your AI agent 75+ business tools: tasks, projects, CRM, forms, E-Sign, team comms, automations.
Connects your AI assistant to a living playbook distilled from your team's sales calls.
CRM, inbox, meetings, revenue intelligence, analytics and automations for AI agents.
Sales-rep jobs on your HubSpot CRM: daily brief, triage, call prep, forecast. Writes need approval.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables agents to find leads, read buying signals, write and send email and LinkedIn outreach, handle replies, and report results, with built-in safety gates, approvals, limits, and compliance controls.10Apache 2.0
- AlicenseBqualityBmaintenanceRuns 20 slash-command workflows across Google Calendar, Gmail, Linear, Slack, Granola, Google Docs, GitHub, Stripe, Notion, Google Forms, Sheets and Drive to produce morning briefs, meeting prep, action items assigned to owners and weekly updates. Reads proceed without asking, while anything that creates, sends, changes or deletes is shown for approval first.471Apache 2.0
- AlicenseBqualityBmaintenanceEnables freelancers and agencies to run eight back-office workflows across Stripe, Google Drive, Linear, Google Calendar, Gmail, GitHub, Google Docs, Granola, Google Sheets and Firecrawl, covering client onboarding, invoices from calendar and commits, status reports, scope-creep detection, site audits and overdue invoice chasers. Reads run freely, while anything that creates, sends, changes or deletes is shown for approval first, with credentials kept in your own OS keychain and no proxying through any third-party server.28Apache 2.0
- AlicenseBqualityBmaintenanceRuns 13 agent slash-command workflows across Linear, GitHub, Google Docs, Slack, Firecrawl, Sheets, Tavily, Forms, Calendar, Gmail, Stripe, Granola and Notion — turning shipped features into blog and social drafts and handling competitor pricing, mention monitoring, SEO gaps, webinars, newsletters and launch-day tracking. Every credential is your own, kept in the OS keychain or client config, and anything that writes, sends or deletes is shown for approval first.29Apache 2.0