Files
2025-11-30 08:49:50 +08:00

412 lines
13 KiB
Markdown

# Agent-Centric Design for MXCP Tools
**Designing MXCP tools that LLMs can effectively use with zero prior context.**
## Overview
When building MXCP servers, remember: **LLMs are your primary users**. Your tools must enable LLMs to accomplish real-world tasks effectively. This guide provides principles for designing tools that work well for AI agents.
## Core Principles
### 1. Build for Workflows, Not Just Data Access
**Don't simply expose database tables or API endpoints - design tools around complete workflows.**
#### ❌ Poor Design: Raw Data Access
```yaml
# tools/get_user.yml
tool:
name: get_user
description: "Get user by ID"
parameters:
- name: user_id
type: integer
source:
code: SELECT * FROM users WHERE id = $user_id
# tools/get_orders.yml
tool:
name: get_orders
description: "Get orders by user"
parameters:
- name: user_id
type: integer
source:
code: SELECT * FROM orders WHERE user_id = $user_id
```
**Problem**: LLM needs multiple tool calls to answer "What did user 123 buy?"
#### ✅ Good Design: Workflow-Oriented
```yaml
# tools/get_user_purchase_summary.yml
tool:
name: get_user_purchase_summary
description: "Get complete purchase history for a user including orders, products, and total spending. Use this to understand a user's buying behavior and preferences."
parameters:
- name: user_id
type: integer
description: "User identifier"
- name: date_range
type: string
description: "Optional date range: 'last_30_days', 'last_year', or 'all_time'"
default: "all_time"
return:
type: object
properties:
user_info: { type: object, description: "Basic user information" }
order_count: { type: integer, description: "Total number of orders" }
total_spent: { type: number, description: "Total amount spent in USD" }
top_products: { type: array, description: "Most frequently purchased products" }
source:
code: |
WITH user_orders AS (
SELECT o.*, p.name as product_name, p.category
FROM orders o
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id
WHERE o.user_id = $user_id
AND ($date_range = 'all_time'
OR ($date_range = 'last_30_days' AND o.created_at > CURRENT_DATE - INTERVAL 30 DAY)
OR ($date_range = 'last_year' AND o.created_at > CURRENT_DATE - INTERVAL 1 YEAR))
)
SELECT
json_object(
'user_info', (SELECT json_object('id', id, 'name', name) FROM users WHERE id = $user_id),
'order_count', COUNT(DISTINCT id),
'total_spent', SUM(total_amount),
'top_products', (
SELECT json_group_array(json_object('product', product_name, 'count', count))
FROM (SELECT product_name, COUNT(*) as count FROM user_orders GROUP BY product_name ORDER BY count DESC LIMIT 5)
)
) as result
FROM user_orders
```
**Benefit**: Single tool call answers complete questions about user behavior.
### 2. Optimize for Limited Context
**LLMs have constrained context windows - make every token count.**
#### Design for Concise Responses
```yaml
tool:
name: search_products
parameters:
- name: query
type: string
description: "Search query"
- name: detail_level
type: string
description: "Response detail level"
enum: ["minimal", "standard", "full"]
default: "standard"
examples:
- "minimal: Only ID, name, price"
- "standard: Basic info + category + stock"
- "full: All fields including descriptions"
source:
code: |
SELECT
CASE $detail_level
WHEN 'minimal' THEN json_object('id', id, 'name', name, 'price', price)
WHEN 'standard' THEN json_object('id', id, 'name', name, 'price', price, 'category', category, 'stock', stock)
ELSE json_object('id', id, 'name', name, 'price', price, 'category', category, 'stock', stock, 'description', description, 'specs', specs)
END as product
FROM products
WHERE name LIKE '%' || $query || '%'
```
**Principle**: Default to high-signal information, provide options for more detail.
#### Use Human-Readable Identifiers
```yaml
# ✅ GOOD: Return names alongside IDs
return:
type: object
properties:
customer_id: { type: string, description: "Customer ID (e.g., 'CUST_12345')" }
customer_name: { type: string, description: "Customer display name" }
assigned_to_id: { type: string, description: "Assigned user ID" }
assigned_to_name: { type: string, description: "Assigned user name" }
# ❌ BAD: Only return opaque IDs
return:
type: object
properties:
customer_id: { type: integer }
assigned_to: { type: integer }
```
**Benefit**: LLM can understand relationships without additional lookups.
### 3. Design Actionable Error Messages
**Error messages should guide LLMs toward correct usage patterns.**
#### ✅ Good Error Messages (Python Tools)
```python
def search_large_dataset(query: str, limit: int = 100) -> dict:
"""Search with intelligent error guidance"""
# Validate inputs
if not query or len(query) < 3:
return {
"success": False,
"error": "Query must be at least 3 characters. Provide a more specific search term to get better results.",
"error_code": "QUERY_TOO_SHORT",
"suggestion": "Try adding more keywords or using specific product names"
}
if limit > 1000:
return {
"success": False,
"error": f"Limit of {limit} exceeds maximum allowed (1000). Use filters to narrow your search: add 'category' or 'price_range' parameters.",
"error_code": "LIMIT_EXCEEDED",
"max_limit": 1000,
"suggestion": "Try using category='electronics' or price_range='0-100' to reduce results"
}
# Execute search
results = db.execute(
"SELECT * FROM products WHERE name LIKE $query LIMIT $limit",
{"query": f"%{query}%", "limit": limit}
)
if not results:
return {
"success": False,
"error": f"No products found matching '{query}'. Try broader terms or check spelling.",
"error_code": "NO_RESULTS",
"suggestion": "Use 'list_categories' tool to see available product categories"
}
return {
"success": True,
"count": len(results),
"results": results
}
```
**Principle**: Every error should suggest a specific next action.
### 4. Follow Natural Task Subdivisions
**Tool names should reflect how humans think about tasks, not just database structure.**
#### ✅ Good: Task-Oriented Naming
```
get_customer_purchase_history # What users want to know
analyze_sales_by_region # Natural analysis task
check_inventory_status # Action-oriented
schedule_report_generation # Complete workflow
```
#### ❌ Poor: Database-Oriented Naming
```
select_from_orders # Database operation
join_users_and_purchases # Technical operation
aggregate_by_column # Generic operation
```
**Use consistent prefixes for discoverability**:
```yaml
# Customer operations
- get_customer_details
- get_customer_orders
- get_customer_analytics
# Product operations
- search_products
- get_product_details
- check_product_availability
# Analytics operations
- analyze_sales_trends
- analyze_customer_behavior
- analyze_inventory_turnover
```
### 5. Provide Comprehensive Documentation
**Every field must have a description that helps LLMs understand usage.**
See **references/llm-friendly-documentation.md** for complete documentation guidelines.
**Quick checklist**:
- [ ] Tool description explains WHAT, returns WHAT, WHEN to use
- [ ] Every parameter has description with examples
- [ ] Return type properties all have descriptions
- [ ] Cross-references to related tools
- [ ] Examples show realistic usage
## MXCP-Specific Best Practices
### Use SQL for Workflow Consolidation
**SQL is powerful for combining multiple data sources in one query:**
```yaml
tool:
name: get_order_fulfillment_status
description: "Get complete order fulfillment information including shipping, payments, and inventory status. Use this to answer questions about order status and estimated delivery."
source:
code: |
SELECT
o.id as order_id,
o.status as order_status,
u.name as customer_name,
s.carrier,
s.tracking_number,
s.estimated_delivery,
p.status as payment_status,
json_group_array(
json_object(
'product', prod.name,
'quantity', oi.quantity,
'in_stock', prod.stock >= oi.quantity
)
) as items
FROM orders o
JOIN users u ON o.user_id = u.id
LEFT JOIN shipments s ON o.id = s.order_id
LEFT JOIN payments p ON o.id = p.order_id
JOIN order_items oi ON o.id = oi.order_id
JOIN products prod ON oi.product_id = prod.id
WHERE o.id = $order_id
GROUP BY o.id
```
**Single tool call provides complete fulfillment picture.**
### Use Python for Complex Workflows
```python
async def analyze_customer_churn_risk(customer_id: str) -> dict:
"""
Comprehensive churn risk analysis combining multiple data sources.
Returns risk score, contributing factors, and recommended actions.
Use this to identify customers who may leave and take preventive action.
"""
# Get customer history
orders = db.execute(
"SELECT * FROM orders WHERE customer_id = $cid ORDER BY created_at DESC",
{"cid": customer_id}
)
support_tickets = db.execute(
"SELECT * FROM support_tickets WHERE customer_id = $cid",
{"cid": customer_id}
)
# Calculate risk factors
days_since_last_order = (datetime.now() - orders[0]["created_at"]).days if orders else 999
unresolved_tickets = len([t for t in support_tickets if t["status"] != "resolved"])
total_spent = sum(o["total_amount"] for o in orders)
# Determine risk level
risk_score = 0
factors = []
if days_since_last_order > 90:
risk_score += 30
factors.append("No purchases in 90+ days")
if unresolved_tickets > 0:
risk_score += 20 * unresolved_tickets
factors.append(f"{unresolved_tickets} unresolved support tickets")
if total_spent < 100:
risk_score += 10
factors.append("Low lifetime value")
# Generate recommendations
recommendations = []
if days_since_last_order > 90:
recommendations.append("Send re-engagement email with discount")
if unresolved_tickets > 0:
recommendations.append("Prioritize resolution of open support tickets")
return {
"success": True,
"customer_id": customer_id,
"risk_score": min(risk_score, 100),
"risk_level": "high" if risk_score > 60 else "medium" if risk_score > 30 else "low",
"contributing_factors": factors,
"recommendations": recommendations,
"days_since_last_order": days_since_last_order,
"unresolved_tickets": unresolved_tickets
}
```
### Leverage MXCP Policies for Context-Aware Tools
```yaml
tool:
name: get_employee_compensation
description: "Get employee compensation details. Returns salary and benefits information based on user permissions."
parameters:
- name: employee_id
type: string
description: "Employee identifier"
return:
type: object
properties:
employee_id: { type: string }
name: { type: string }
salary: { type: number, description: "Annual salary (admin only)" }
benefits: { type: array, description: "Benefits package" }
policies:
output:
- condition: "user.role != 'hr_manager' && user.role != 'admin'"
action: filter_fields
fields: ["salary"]
reason: "Salary information restricted to HR managers and admins"
source:
code: |
SELECT
employee_id,
name,
salary,
benefits
FROM employees
WHERE employee_id = $employee_id
```
**LLM can call same tool, MXCP automatically filters based on user context.**
## Testing Agent-Centric Design
### Create Realistic Evaluation Scenarios
See **references/mxcp-evaluation-guide.md** for complete evaluation guidelines.
**Quick validation**:
1. Can an LLM answer complex multi-step questions using your tools?
2. Do tool descriptions clearly indicate when to use each tool?
3. Do error messages guide the LLM toward correct usage?
4. Can common tasks be completed with minimal tool calls?
## Summary
**Agent-centric design principles for MXCP**:
1.**Build for workflows** - Consolidate related operations
2.**Optimize for context** - Provide detail level options, use readable identifiers
3.**Actionable errors** - Guide LLMs with specific suggestions
4.**Natural naming** - Task-oriented, not database-oriented
5.**Comprehensive docs** - Every parameter and field documented
**MXCP advantages**:
- SQL enables powerful workflow consolidation
- Python handles complex multi-step logic
- Policies provide automatic context-aware filtering
- Type system ensures clear contracts
**Remember**: Design for the LLM as your user, not the human. Humans configure tools, LLMs use them.