| name | mcp-response-optimization |
| description | Prevent massive MCP responses from WordPress, Playwright, Notion, and other MCPs that can kill conversations on first call. Sophisticated token management for all MCP tool usage. |
MCP Response Optimization
Core Principle
REQUEST MINIMAL, DISPLAY NOTHING, SAVE EVERYTHING
Single MCP calls can kill entire conversations. This skill ensures every MCP call is filtered, limited, silent, extracted, and saved for maximum token efficiency.
When to Activate
ALWAYS activate before ANY MCP tool call, especially:
- WordPress MCP operations (list posts, get pages, media queries)
- Playwright browser automation (DOM queries, screenshots)
- Notion API queries (search, database queries)
- Any search or list operations across MCPs
- Large data retrievals from any source
This skill is MANDATORY for all MCP usage in Claude Desktop.
The Critical Problem
One MCP call = conversation dead:
- WordPress query returns 50 full pages → 100,000+ tokens
- Playwright DOM query returns entire page HTML → 80,000+ tokens
- Notion search returns 100 full pages → 120,000+ tokens
- Result: Conversation ends on first message
MCP Integration
This skill optimizes these MCPs:
- wordpress-mcp: WordPress content management
- playwright: Browser automation and testing
- notion / notionApi: Notion workspace integration
- filesystem: File system operations (for saving large responses)
- gmail: Email management
- supabase: Database queries
- firecrawl-mcp: Web scraping
WordPress MCP - The Worst Offender
The Problem
WordPress returns EVERYTHING by default:
- Full post content (50KB per post)
- All metadata, revisions, images (base64)
- Author details, comments, categories
- One query = conversation killer
The Solution: Aggressive Filtering
Rule 1: Always Use Field Filters
// ❌ NEVER:
wordpress.list_posts({ per_page: 100 })
// Returns: 5MB of data, 1,250,000 tokens
// ✅ ALWAYS:
wordpress.list_posts({
per_page: 10,
fields: 'id,title,date,status',
context: 'view'
})
// Returns: 5KB of data, 1,250 tokens
Rule 2: Limit Results Aggressively
// Default limits:
per_page: 10 // NEVER use 100
page: 1 // Paginate if needed
Rule 3: Request Specific Fields Only
For Posts:
// Minimal (when listing):
fields: 'id,title,status,date'
// Add only when needed:
fields: 'id,title,status,date,excerpt' // For preview
fields: 'id,title,content' // Only when editing
For Pages:
fields: 'id,title,status,parent'
For Media:
// NEVER load full images
fields: 'id,title,source_url' // URL only, not base64
Rule 4: Use Targeted Queries
// ❌ BAD:
wordpress.list_posts({ search: 'conference' })
// ✅ GOOD:
wordpress.list_posts({
search: 'conference',
per_page: 5,
fields: 'id,title,date',
status: 'publish',
orderby: 'date',
order: 'desc'
})
Rule 5: Never Display Full Responses
// ❌ DON'T:
console.log(response)
// ✅ DO:
const ids = response.map(post => ({ id: post.id, title: post.title }))
console.log(`Found ${ids.length} posts`)
WordPress Response Patterns
Pattern: List Posts
const posts = wordpress.list_posts({
per_page: 10,
fields: 'id,title,status,date',
status: 'publish'
})
const post_ids = posts.map(p => p.id)
`Found ${posts.length} posts: ${post_ids.join(', ')}`
Pattern: Get Single Post
const post = wordpress.get_post({
id: 123,
fields: 'id,title,content,status'
})
`Retrieved post: ${post.title} (${post.id})`
// Save full content to file if needed
Pattern: Search and Filter
const results = wordpress.list_posts({
search: 'keyword',
per_page: 5,
fields: 'id,title,date'
})
const filtered = results.filter(/* criteria */)
`Found ${filtered.length} matches: [${filtered.map(p => p.id)}]`
Playwright MCP - DOM and Screenshot Killer
The Problem
- Full page HTML (200KB)
- Complete DOM tree with attributes
- Base64 screenshots (500KB-2MB)
- One screenshot = conversation over
The Solution: Selective Queries
Rule 1: Use CSS Selectors, Not Full DOM
// ❌ NEVER:
playwright.evaluate("document.documentElement.outerHTML")
// Returns: 50,000+ tokens
// ✅ ALWAYS:
playwright.locator('.product-title').textContent()
// Returns: 10 tokens
Rule 2: Target Specific Elements
const data = {
title: await page.locator('h1').textContent(),
price: await page.locator('.price').textContent(),
available: await page.locator('.stock-status').textContent()
}
// DON'T extract full page content
Rule 3: Screenshots - Save, Never Display
// ❌ NEVER include in response:
const screenshot = await page.screenshot()
// 500KB-2MB base64 = 125,000+ tokens
// ✅ SAVE screenshot:
await page.screenshot({ path: '/outputs/screenshot.png' })
`Screenshot saved: /outputs/screenshot.png`
// 20 tokens vs 125,000 tokens
Rule 4: Limit Element Extraction
const products = await page.locator('.product-card').all()
const product_data = []
for (const product of products.slice(0, 10)) {
product_data.push({
name: await product.locator('.name').textContent(),
price: await product.locator('.price').textContent()
})
}
fs.writeFileSync('/outputs/products.json', JSON.stringify(product_data))
`Extracted ${product_data.length} products → /outputs/products.json`
Playwright Response Patterns
Pattern: Page Scraping
await page.goto(url)
const data = {
title: await page.title(),
h1: await page.locator('h1').first().textContent(),
meta: await page.locator('meta[name="description"]').getAttribute('content')
}
fs.writeFileSync('/outputs/page_data.json', JSON.stringify(data))
`Page data saved: /outputs/page_data.json`
Pattern: Element Analysis
const count = await page.locator('.product-item').count()
`Found ${count} products`
const sample = await page.locator('.product-item').first().textContent()
`Sample product: ${sample}`
Pattern: Screenshots for User
await page.screenshot({
path: '/mnt/user-data/outputs/page.png',
fullPage: false // Viewport only
})
`[View screenshot](computer:///mnt/user-data/outputs/page.png)`
Notion MCP - Large Page Objects
The Problem
Notion returns full page objects with all blocks - can be massive.
The Solution
// ❌ DON'T:
notion.search({ query: 'project' }) // 100 full pages
// ✅ DO:
notion.search({
query: 'project',
page_size: 10,
filter: { property: 'Status', select: { equals: 'Active' } }
})
const page_ids = results.map(r => r.id)
`Found ${page_ids.length} pages: ${page_ids}`
Universal MCP Rules
Rule 1: Pre-Call Token Budget
Before EVERY MCP call:
- What's minimum data I actually need?
- Can I filter to specific fields?
- Can I limit result count?
- Will response fit in 2,000 tokens?
If answer to last question is NO → Adjust parameters
Rule 2: Pagination Over Bulk
// ❌ NEVER:
get_all_items({ limit: 1000 })
// ✅ ALWAYS:
get_items({ limit: 10, page: 1 })
Rule 3: Save Large Responses
// Any response >5KB → Save to file
// ❌ DON'T:
console.log(large_response)
// ✅ DO:
fs.writeFileSync('/outputs/response.json', JSON.stringify(large_response))
`Response saved: /outputs/response.json`
Rule 4: ID-Based Workflows
// Step 1: Get IDs only
const ids = get_minimal_list()
// Step 2: User picks which ID
`Found ${ids.length} items: ${ids}`
// Step 3: Fetch full details for THAT ONE
const full_item = get_item_by_id(selected_id)
Rule 5: Never Display Raw MCP Responses
// ❌ FATAL:
`Here's what I found: ${JSON.stringify(mcp_response)}`
// ✅ CORRECT:
const summary = {
count: mcp_response.length,
items: mcp_response.map(i => i.id)
}
`Found ${summary.count} items: ${summary.items.join(', ')}`
Token Estimation Guide
| Operation | Bad Approach | Good Approach |
|---|---|---|
| List 100 WP posts | 1,000,000 tokens | 2,500 tokens |
| Get WP page content | 50,000 tokens | 200 tokens |
| Playwright full DOM | 100,000 tokens | 500 tokens |
| Playwright screenshot | 125,000 tokens | 50 tokens |
| Notion page search | 80,000 tokens | 1,000 tokens |
Pre-Call Checklist
Before EVERY MCP tool call:
- ✅ Minimum fields requested?
- ✅ Result limit set to <20?
- ✅ Estimated response <2,000 tokens?
- ✅ Plan to save, not display?
- ✅ ID-based workflow possible?
- ✅ Pagination strategy ready?
If all YES → Proceed If any NO → Adjust parameters
Emergency Patterns
If MCP Response Already Too Large
// Response came back huge (>50,000 tokens)
// 1. Save full response to file
fs.writeFileSync('/outputs/large_response.json', JSON.stringify(response))
// 2. Extract minimal summary
const summary = {
count: response.length,
first_id: response[0]?.id,
last_id: response[response.length - 1]?.id
}
// 3. Return summary only
`Response saved to /outputs/large_response.json`
`Summary: ${summary.count} items (${summary.first_id} to ${summary.last_id})`
Success Checklist
This skill is working correctly when:
- ✅ No MCP call exceeds 2,000 tokens in response
- ✅ WordPress queries use field filters 100% of time
- ✅ Playwright saves screenshots, never displays
- ✅ Raw MCP responses NEVER appear in conversation
- ✅ ID-based workflows used consistently
- ✅ Large datasets saved to files automatically
The Golden Rules
- Request minimal fields - Only what you need
- Limit results aggressively - 10 items max initially
- Save, don't display - Files, not conversation
- IDs, not content - Work with identifiers
- Paginate on demand - Never bulk load
- Estimate before call - Budget tokens first
Integration with Other Skills
Works with:
- curv-design-system - On-brand outputs with token efficiency
- dashboard-auto-generation - Data dashboards without token bloat
Deployment: Import .zip to Claude Desktop → Settings → Skills Impact: Prevents 50,000-100,000+ token MCP disasters Result: Full conversation capacity instead of 1-message death