Skip to main content
Glama

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-mcp

It 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 connected

Tokens 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

Google

Browser sign-in, over your own OAuth client (make one).

GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET

Tavily

Your own key (get one), entered once.

TAVILY_API_KEY

Firecrawl

Your own key (get one), entered once.

FIRECRAWL_API_KEY

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.

SLACK_BOT_TOKEN

Granola

Your own key (get one), entered once. In the Granola app: Settings → Connectors → API keys. Business plan or above.

GRANOLA_API_KEY

Notion

Your own key (get one), entered once. Then share the pages it should see with the integration.

NOTION_API_KEY

Stripe

Your own key (get one), entered once.

STRIPE_API_KEY

Linear

Your own key (get one), entered once.

LINEAR_API_KEY

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 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 call_to_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 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 inbound_demo_request_to_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 scope_agreed_on_a_call_to_payment_link

The deal closes while the buyer still remembers saying yes.

Granola, Stripe, Gmail

Signed quote → Stripe invoice signed_quote_to_stripe_invoice

The quote's line items become the invoice's, with no retyping.

Google Docs, Stripe

Closed-won handoff closed_won_handoff

Account page, customer record and internal channel exist the day the deal closes.

Stripe, Notion, Linear, Slack

Target account research target_account_research

Every account in the target list gets a cited brief on its page.

Notion, Tavily

Pipeline digest 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 trial_ending_to_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 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 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 new_thread_from_an_unknown_domain_to_crm

Inbound that didn't come through a form still lands in the CRM with context.

Gmail, Notion, Firecrawl

Mail merge from a sheet 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 = 30

Name 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.langchain

Need 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_insert

  • Tavily: tavily_search, tavily_research_create, tavily_research_get

  • Firecrawl: firecrawl_scrape, firecrawl_extract

  • Gmail: gmail_threads_list, gmail_drafts_create, gmail_messages_send

  • Slack: slack_chat_post_message, slack_conversations_create

  • Granola: granola_notes_get, granola_notes_list

  • Notion: notion_pages_update, notion_pages_create, notion_data_sources_query, notion_search

  • Google Sheets: gsheets_spreadsheets_values_get, gsheets_spreadsheets_values_update

  • Google Forms: gforms_forms_responses_list

  • Stripe: 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_retrieve

  • Google Docs: gdocs_documents_get

  • Linear: linear_customer_create

  • Google Drive: gdrive_files_list, gdrive_files_export

License

Apache 2.0.

Available Tools

35 tools
connectA

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_statusA
Read-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_listC
Read-onlyIdempotent

List events matching a given search filter.

Inputs: calendarId (required), q, iCalUID, orderBy, +14 more.

gdocs_documents_getA
Read-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_exportA
Read-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_listA
Read-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_listA
Read-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_listC
Read-onlyIdempotent

List Gmail threads.

Inputs: q, userId, labelIds, pageToken, +2 more.

granola_notes_getA
Read-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_listA
Read-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_getC
Read-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.

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_retrieveB
Read-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_listA
Read-onlyIdempotent

List prices. Filter by product to find what a product costs.

Inputs: limit, active, product, currency, +2 more.

stripe_subscriptions_listB
Read-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_getA
Read-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.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.2/5.0

    Scored across 35 tools

    Disambiguation4/5

    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.

    Naming Consistency5/5

    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.

    Tool Count2/5

    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.

    Completeness2/5

    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

    ActivitySlowing
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables 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.
      10
      Apache 2.0
    • A
      license
      B
      quality
      B
      maintenance
      Runs 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.
      47
      1
      Apache 2.0
    • A
      license
      B
      quality
      B
      maintenance
      Enables 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.
      28
      Apache 2.0
    • A
      license
      B
      quality
      B
      maintenance
      Runs 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.
      29
      Apache 2.0