| name | mcp-tool-builder |
| description | Build channel-aware MCP tools for the Customer Success FTE system. This skill should be
used when creating, modifying, or reviewing MCP server tools that must accept channel_source
metadata and format responses per channel (Gmail, WhatsApp, Web Form). Provides the canonical
pattern for tool input schemas, channel adaptation, and the required tool execution order.
|
MCP Tool Builder
Build channel-aware MCP tools following the Customer Success FTE canonical patterns.
Before Implementation
Gather context to ensure correct integration:
| Source | Gather |
|---|
| Codebase | Existing tools in production/agent/tools.py, current schemas |
| Conversation | Which tool to build, specific requirements |
| AGENTS.md | Tool execution order, guardrails, channel constraints |
| Skill References | Tool patterns from references/tool-patterns.md |
Channel-Aware Tool Pattern
Every MCP tool in this system MUST be channel-aware. This means:
- Accept
channel_source in the input schema
- Pass channel context through the tool chain
- Format output according to channel constraints
Input Schema Pattern
Every tool input includes channel metadata:
from pydantic import BaseModel, Field
from enum import Enum
class Channel(str, Enum):
GMAIL = "gmail"
WHATSAPP = "whatsapp"
WEB_FORM = "web_form"
class ChannelMetadata(BaseModel):
channel_source: Channel
customer_id: str
conversation_id: str | None = None
thread_id: str | None = None
class ToolNameInput(BaseModel):
"""Tool-specific fields + channel metadata."""
query: str
channel: ChannelMetadata
Output Formatting Per Channel
Tools that produce customer-facing text MUST respect channel limits:
| Channel | Max Length | Style | Signature |
|---|
| Gmail | 500 words | Formal, detailed | "Best regards, TechCorp AI Support Team" |
| WhatsApp | 300 chars preferred, 1600 max | Conversational, concise | None |
| Web Form | 300 words | Semi-formal | "Need more help?" footer |
Response Adapter Pattern
def adapt_response(raw_response: str, channel: Channel) -> str:
"""Adapt tool output to channel constraints."""
if channel == Channel.WHATSAPP:
return truncate_and_split(raw_response, preferred=300, max_per_msg=1600)
elif channel == Channel.GMAIL:
return format_email_response(raw_response, max_words=500)
elif channel == Channel.WEB_FORM:
return format_web_response(raw_response, max_words=300)
Required Tool Execution Order
The agent MUST call tools in this order. Enforce in tool design:
create_ticket → get_customer_history → search_knowledge_base → send_response
create_ticket is ALWAYS the first tool call
send_response is ALWAYS the last tool call
- Tools in between can vary but order above is the default flow
Canonical Tool Definitions
The system requires 5+ MCP tools. Each follows the pattern below:
1. search_knowledge_base
class KnowledgeSearchInput(BaseModel):
query: str
max_results: int = Field(default=5, le=5)
category: str | None = None
channel: ChannelMetadata
2. create_ticket
class TicketInput(BaseModel):
customer_id: str
issue: str
priority: str = "medium"
category: str | None = None
channel: ChannelMetadata
3. get_customer_history
class CustomerHistoryInput(BaseModel):
customer_id: str
channel: ChannelMetadata
4. escalate_to_human
class EscalationInput(BaseModel):
ticket_id: str
reason: str
urgency: str = "normal"
channel: ChannelMetadata
5. send_response
class ResponseInput(BaseModel):
ticket_id: str
message: str
channel: ChannelMetadata
Building a New Tool
Follow this checklist when adding a new MCP tool:
- Define input schema with
ChannelMetadata included
- Implement core logic — the channel-agnostic business logic
- Add channel adaptation — format output per channel constraints
- Register in tool manifest — add to agent's tool list
- Enforce execution order — document where it fits in the tool chain
- Add guardrail checks — respect agent guardrails (no pricing, no competitor mentions)
- Write edge-case tests — use
test-first-iteration skill
Tool Implementation Template
from agents import function_tool
@function_tool
async def tool_name(input: ToolNameInput) -> str:
"""Tool description for the agent.
Channel-aware: accepts channel_source, formats output per channel.
"""
result = await core_logic(input)
return adapt_response(result, input.channel.channel_source)
Must Follow
Must Avoid
- Tools that ignore
channel_source and return uniform responses
- Breaking the tool execution order
- Hardcoding channel-specific logic outside the response adapter
- Tools that access the database without going through
production/database/queries.py
- Returning raw data without channel-appropriate formatting
Scaffolding a New Tool
Run the scaffold script to generate a tool file + test stub:
python scripts/scaffold-tool.py --name check_sentiment --description "Analyze customer message sentiment"
python scripts/scaffold-tool.py --name check_sentiment --description "Analyze sentiment" --output production/agent/tools/
Or copy assets/tool-template.py as a starting point and fill in the [PLACEHOLDERS].
Reference Files
| File | When to Read |
|---|
references/tool-patterns.md | Full implementation examples for all 5 canonical tools, error handling, testing patterns, and anti-patterns to avoid |
references/channel-adaptation.md | Deep dive on response formatting per channel — splitting algorithm, before/after examples, guardrail checks in adapter |
scripts/scaffold-tool.py | Run to generate a new tool file + test stub with all boilerplate pre-filled |
assets/tool-template.py | Copy as starting point for a new tool — has all sections commented and ready to customize |