a
Scenario 01 · Teaching Aid

Customer Support
Resolution Agent

Learn how to design and build an AI agent that resolves support tickets with 80%+ first-contact resolution, using MCP tools and smart escalation logic.

Claude Agent SDK MCP Tools Escalation Logic High Ambiguity Returns · Billing · Account

What makes this agent different?

01 🎯

High-Ambiguity Handling

Customers rarely describe problems clearly. The agent must infer intent from vague phrases like "my thing doesn't work" or "I want a refund" — mapping them to concrete backend operations.

02 🔧

MCP Tool Use

Instead of hallucinating answers, the agent calls verified backend tools: get_customer, lookup_order, process_refund, escalate_to_human.

03

First-Contact Resolution

The 80% FCR target means most issues should be solved in ONE interaction, not handed off. This changes how you design the system prompt and tool permissions.

04 🧠

Contextual Memory

The agent maintains conversation state — it knows what it looked up, what it offered, and what the customer said. Each tool call enriches the context window.

05 🚨

Smart Escalation

Escalation isn't failure — it's the right call when: emotion is high, the issue is policy-level, legal risk exists, or the agent has tried and failed.

06 🔒

Guard Rails & Safety

The agent needs hard limits: never process refunds above $X without confirmation, never delete account data, always verify identity before sharing PII.

Click a node to learn more

step 01 Intake
Parse intent
step 02 Verify Identity
get_customer()
step 03 Context Lookup
lookup_order()
step 04 Classify Issue
Routing logic
✓ Resolve → process_refund()
? Clarify → ask follow-up
↑ Escalate → escalate_to_human()

👆 Click any node above to see what happens at that step.

Click each tool to explore its interface

get_customer() READ

Fetches a customer record from your CRM by email, phone, or ID. Always call this first.

identifierstring — email, phone, or customer_id
returnsname, account_status, tier, lifetime_value, open_tickets
⚠ TipIf not found, don't assume fraud — ask for alternate ID.
# Example MCP call get_customer({ "identifier": "[email protected]" }) # Returns: { "customer_id": "cust_882", "name": "Alice Chen", "tier": "gold", "account_status": "active", "lifetime_value": 2840 }
lookup_order() READ

Retrieves full order details including status, items, fulfilment, and return eligibility.

order_idstring — order number
returnsitems, status, delivery_date, return_window, refund_amount
💡 PatternIf customer doesn't know order ID, use customer_id to get recent orders first.
lookup_order({ "order_id": "ORD-4421" }) # Returns: { "status": "delivered", "delivered_at": "2025-02-10", "return_eligible": true, "return_window_days": 5, "refund_amount": 89.99 }
process_refund() WRITE

Initiates a refund against a verified order. Must confirm eligibility and get customer consent first.

order_idstring — must match verified customer
reasonenum — DEFECTIVE | NOT_RECEIVED | CHANGED_MIND | OTHER
amountfloat — defaults to full order amount
🔒 Guard railIf amount > $200, require agent to confirm with customer before calling.
process_refund({ "order_id": "ORD-4421", "reason": "NOT_RECEIVED", "amount": 89.99 }) # Returns: { "refund_id": "REF-991", "status": "approved", "eta_days": 3 }
escalate_to_human() ESCALATE

Hands off the conversation to a human agent with full context. Last resort, not first instinct.

customer_idstring — for queue routing
reasonstring — why escalation is needed
summarystring — what the agent learned so far
priorityenum — LOW | MEDIUM | HIGH | URGENT
💡 Best practiceAlways tell the customer you're escalating AND why. Never ghost-transfer.
escalate_to_human({ "customer_id": "cust_882", "reason": "Legal dispute, customer threatening chargeback", "summary": "Order ORD-4421, refund denied due to window expiry", "priority": "HIGH" })

Watch the agent reason through real requests

What does success look like?

80%+
First-Contact Resolution
< 90s
Avg. Handle Time
4.3+
CSAT Score / 5
20%
Max Escalation Rate

Why these numbers are hard to hit — and how the agent helps

FCR is hard because customers give incomplete info, backend systems have gaps, and agents over-escalate out of caution. Claude solves this by using MCP tools to get real data before making any decision.

Handle time drops because the agent doesn't ask questions it can already answer with a tool call.

CSAT goes up when customers feel heard and resolved fast. The agent should acknowledge emotion before jumping to solutions.

Escalation rate must be monitored — too low means the agent is handling things it shouldn't, too high kills the ROI.

Reference implementation using the Claude Agent SDK

system_prompt.txt
You are a customer support resolution agent for Acme Corp. ## Identity Your name is Aria. You are empathetic, efficient, and empowered to resolve most issues without escalation. ## Capabilities You have access to 4 tools: - get_customer(identifier) → Look up customer record - lookup_order(order_id) → Get order details & eligibility - process_refund(order_id, ...) → Issue a refund - escalate_to_human(...) → Hand off to human agent ## Resolution Rules 1. Always call get_customer() first to verify identity 2. Call lookup_order() before discussing any order issue 3. You MAY process refunds if: - Order is within return window - Amount is ≤ $200 - Customer is not flagged for abuse 4. For amounts over $200, confirm with customer before processing 5. Acknowledge customer emotion BEFORE jumping to solutions ## MUST Escalate When: - Customer mentions legal action or chargebacks - Account is flagged for fraud investigation - Issue requires policy exception beyond your authority - Customer has expressed anger 3+ times in conversation - You cannot resolve after 2 clarifying attempts ## Tone Warm but efficient. Never defensive. Apologize for experience, not necessarily for company fault. Be specific about ETAs. ## Never - Share another customer's data - Make promises outside policy - Process refunds for fraud-flagged accounts - Reveal internal system details
agent.py
import anthropic from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client client = anthropic.Anthropic() async def run_support_agent(customer_message: str): server_params = StdioServerParameters( command="python", args=["-m", "support_mcp_server"] ) async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools = await session.list_tools() claude_tools = [ {"name": t.name, "description": t.description, "input_schema": t.inputSchema} for t in tools.tools ] messages = [{"role": "user", "content": customer_message}] while True: response = client.messages.create( model="claude-opus-4-5", max_tokens=4096, system=SYSTEM_PROMPT, tools=claude_tools, messages=messages ) if response.stop_reason == "end_turn": break if response.stop_reason == "tool_use": tool_results = [] for block in response.content: if block.type == "tool_use": result = await session.call_tool( block.name, block.input ) tool_results.append({ "type": "tool_result", "tool_use_id": block.id, "content": result.content[0].text }) messages.append({"role": "assistant", "content": response.content}) messages.append({"role": "user", "content": tool_results}) return extract_text(response.content)
mcp_tool_schema.json
{ "tools": [ { "name": "get_customer", "description": "Look up a customer by email, phone, or ID", "inputSchema": { "type": "object", "properties": { "identifier": { "type": "string", "description": "Email, phone number, or customer_id" } }, "required": ["identifier"] } }, { "name": "lookup_order", "description": "Get full details of an order including return eligibility", "inputSchema": { "type": "object", "properties": { "order_id": { "type": "string" } }, "required": ["order_id"] } }, { "name": "process_refund", "description": "Issue a refund for an eligible order", "inputSchema": { "type": "object", "properties": { "order_id": { "type": "string" }, "reason": { "type": "string", "enum": ["DEFECTIVE", "NOT_RECEIVED", "CHANGED_MIND", "OTHER"] }, "amount": { "type": "number" } }, "required": ["order_id", "reason"] } }, { "name": "escalate_to_human", "description": "Transfer conversation to a human support agent", "inputSchema": { "type": "object", "properties": { "customer_id": { "type": "string" }, "reason": { "type": "string" }, "summary": { "type": "string" }, "priority": { "type": "string", "enum": ["LOW", "MEDIUM", "HIGH", "URGENT"] } }, "required": ["customer_id", "reason", "summary"] } } ] }

Test your understanding