Skip to main content

From Call Transcript to Salesforce in 60 Seconds: Building a Meeting Notes Agent

· 4 min read
Metadata Morph
AI & Data Engineering Team

Sales reps spend an average of 5–6 hours per week on CRM data entry. They log call notes, update opportunity stages, create follow-up tasks, and capture next steps — after every single call. It's manual, it's inconsistent, and it's the reason CRM data is always slightly out of date.

A meeting notes agent eliminates this entirely. It processes the call transcript, extracts structured data, and updates Salesforce before the rep has finished their post-call coffee.

What the Agent Extracts

Given a sales call transcript, the agent extracts and acts on:

Extracted fieldSalesforce action
Deal stage signals ("we're ready to move forward")Update Opportunity Stage
Stated budget or contract valueUpdate Amount field
Decision maker names and titles mentionedCreate/update Contacts
Blockers or objections raisedLog to Next Steps field
Agreed follow-up actionsCreate Tasks with due dates
Competitor mentionsLog to Competitor field
Timeline signals ("we need this live by Q1")Update Close Date
Sentiment and engagement levelCustom scoring field

The agent doesn't just dump the transcript into a notes field — it extracts structured data into the right Salesforce fields so the CRM actually reflects reality.

Architecture

┌─────────────────────────────────────────────────────────────────┐
│ MEETING NOTES AGENT │
│ │
│ Trigger: new transcript lands in /transcripts/ folder │
│ │
│ 1. Read transcript via Filesystem MCP │
│ 2. Extract structured deal data with LLM │
│ 3. Look up existing Opportunity in Salesforce MCP │
│ 4. Update Opportunity fields │
│ 5. Create follow-up Tasks │
│ 6. Post summary to #deals Slack channel │
└──────────┬──────────────┬──────────────────┬────────────────────┘
│ │ │
┌──────▼──────┐ ┌─────▼──────────┐ ┌────▼──────────┐
│ Filesystem │ │ Salesforce │ │ Notification │
│ MCP │ │ MCP │ │ MCP (Slack) │
│(transcripts)│ │(opportunities, │ │ │
│ │ │ contacts,tasks)│ │ │
└─────────────┘ └────────────────┘ └───────────────┘

MCP Configuration

{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/data/transcripts"]
},
"salesforce": {
"command": "uvx",
"args": ["mcp-server-salesforce"],
"env": {
"SF_CLIENT_ID": "${SF_CLIENT_ID}",
"SF_CLIENT_SECRET": "${SF_CLIENT_SECRET}",
"SF_INSTANCE_URL": "${SF_INSTANCE_URL}"
}
},
"notifications": {
"command": "uvx",
"args": ["mcp-server-slack"],
"env": {
"SLACK_BOT_TOKEN": "${SLACK_TOKEN}"
}
}
}
}

Extraction Prompt

The extraction step uses a structured output schema to guarantee the agent returns parseable JSON, not text:

You are a sales intelligence agent. Extract structured data from the following
sales call transcript and return ONLY valid JSON matching this schema.

Schema:
{
"opportunity_updates": {
"stage": string | null, // Salesforce stage name, null if unclear
"amount": number | null, // Confirmed deal value in USD, null if not mentioned
"close_date": string | null, // ISO date if timeline mentioned, null otherwise
"next_step": string // Single sentence: most important agreed next action
},
"contacts_mentioned": [
{"name": string, "title": string | null, "role": "decision_maker" | "influencer" | "user"}
],
"tasks": [
{"description": string, "owner": "rep" | "prospect" | "internal", "due_date": string | null}
],
"blockers": [string],
"competitors_mentioned": [string],
"sentiment": "positive" | "neutral" | "negative" | "mixed",
"confidence": "high" | "medium" | "low" // agent's confidence in extraction quality
}

Rules:
- Only extract what is explicitly stated. Do not infer or assume.
- If the transcript is too short or unclear to extract reliably, set confidence to "low"
and populate only the fields you are certain about.
- Dates should be converted to ISO format (YYYY-MM-DD). Use the call date as reference.

By demanding JSON output with a defined schema, the downstream Salesforce update step becomes deterministic — no parsing ambiguity.

Confidence Gating

The agent includes a confidence field in its output. When confidence is low — transcript too short, heavy crosstalk, unclear outcome — the agent skips the Salesforce write and instead sends a Slack message to the rep asking them to review and confirm.

This prevents garbage data from entering the CRM, which is worse than no data.

result = agent.extract(transcript)

if result["confidence"] == "low":
notify_rep(
message=f"⚠️ Low-confidence extraction for {call.opportunity_name}. "
f"Please review and confirm before CRM update.",
extracted_data=result,
confirm_url=f"{INTERNAL_TOOL}/confirm/{call.id}"
)
else:
salesforce.update_opportunity(call.opportunity_id, result)
salesforce.create_tasks(call.opportunity_id, result["tasks"])

What the Rep Receives

After every call, the rep gets a Slack message:

✅ CRM Updated — Acme Corp Discovery Call

Stage: Qualification → Proposal (updated)
Next step: Send custom ROI analysis by Nov 29
Tasks created: 2
• Rep: Draft ROI analysis (due Nov 27)
• Prospect: Share current vendor contract (due Nov 26)

Contacts added: Sarah Chen (VP Engineering, decision maker)
Competitors mentioned: Vendor X

View in Salesforce → [link]

The rep reviews in 30 seconds, clicks the Salesforce link if they want to edit anything, and moves on. No manual data entry.

The CRM Data Quality Improvement

Beyond saving time, the bigger benefit is data quality. When reps enter data manually, they do it when convenient — often days after the call, from memory, with inconsistent terminology. The agent captures data immediately from the source of truth (the transcript) and writes it into standardized fields.

Pipeline reviews become reliable. Forecast accuracy improves. Sales leadership stops adjusting for "the CRM lag."

Book a strategy session to automate your sales CRM workflows.