From helpscout-navigator
Guides correct sequencing and tool selection for HelpScout MCP tools to search tickets, inboxes, conversations, customers, and organizations. Handles credential setup and prevents common search errors.
npx claudepluginhub drewburchfield/help-scout-mcp-server --plugin helpscout-navigatorThis skill uses the workspace's default tool permissions.
Guide for correctly using HelpScout MCP tools. Prevents common mistakes and ensures complete search results.
Automates Freshdesk helpdesk operations including tickets, contacts, companies, notes, and replies via Rube MCP (Composio) toolkit. Requires active connection; always search tools first for schemas.
Automates Freshdesk helpdesk operations including tickets, contacts, companies, notes, and replies via Rube MCP (Composio) tools. Useful for customer support workflows; always search tools first for schemas.
Conducts multi-source research on customer questions, bugs, account history, or topics from internal docs, CRM, support tickets, chats, and web with attribution and confidence scoring.
Share bugs, ideas, or general feedback.
Guide for correctly using HelpScout MCP tools. Prevents common mistakes and ensures complete search results.
Follow these steps IN ORDER. Do not skip ahead.
Look for these tools in your available tools:
mcp__helpscout__searchInboxesmcp__helpscout__searchConversationsmcp__helpscout__comprehensiveConversationSearchIf tools ARE available: ✅ Skip to "Critical Rules" section. You're ready to go.
If tools are NOT available: Continue to Step 2.
Run this command:
echo "HELPSCOUT_APP_ID: ${HELPSCOUT_APP_ID:+[SET]}" && echo "HELPSCOUT_APP_SECRET: ${HELPSCOUT_APP_SECRET:+[SET]}"
If both show [SET]: Credentials exist but MCP didn't start. Go to Step 4.
If either is blank: Credentials are missing. Go to Step 3.
Tell the user:
HelpScout credentials are not configured.
Get your credentials:
- Go to HelpScout → Your Profile → My Apps
- Create a new app (or use existing)
- Copy the App ID and App Secret
Add to your shell profile (
~/.zshrcor~/.bashrc):export HELPSCOUT_APP_ID="your-app-id-here" export HELPSCOUT_APP_SECRET="your-app-secret-here"Then go to Step 4.
⚠️ This is where most people get stuck.
The MCP server inherits environment variables from Claude Code's process. If Claude Code was started before the credentials were set, it won't have them.
Tell the user:
You must restart BOTH your terminal AND Claude Code:
- Quit Claude Code completely (not just close the window)
- Close your terminal completely (not just the tab)
- Open a new terminal (this loads your updated
.zshrc)- Start Claude Code from this new terminal
claudeThe HelpScout MCP server will now start with the correct credentials.
Do not proceed with HelpScout operations until the MCP tools are available.
The HelpScout MCP server provides 17 tools for searching and retrieving support conversations. However, using them incorrectly leads to missed tickets and incomplete results. This skill ensures you use the right tool in the right order.
Core problems this skill solves:
searchInboxes first (required for inbox-scoped searches)searchConversations defaults to "active" status only (misses closed/pending)When a user mentions an inbox by name, you MUST look up the inbox ID before searching.
| User Says | You MUST Do First |
|---|---|
| "Search the support inbox" | searchInboxes(query: "support") |
| "Find tickets in sales mailbox" | searchInboxes(query: "sales") |
| "Check the billing inbox" | searchInboxes(query: "billing") |
Why: All inbox-scoped searches require an inbox ID (numeric), not a name.
DANGER: searchConversations without a status parameter only returns active tickets.
| What You Want | WRONG Tool | RIGHT Tool |
|---|---|---|
| "Find tickets about billing" | searchConversations(query: "billing") | comprehensiveConversationSearch(searchTerms: ["billing"]) |
| "Search for refund requests" | searchConversations(query: "refund") | comprehensiveConversationSearch(searchTerms: ["refund"]) |
Use searchConversations ONLY when:
Use comprehensiveConversationSearch when:
This tool requires IDs from previous searches. Do NOT use for initial searches.
| Use Case | Correct Approach |
|---|---|
| Find ticket #42839 | structuredConversationFilter(conversationNumber: 42839) |
| Find John's assigned tickets | First: get John's ID. Then: structuredConversationFilter(assignedTo: <johnId>) |
| Find customer 12345's history | structuredConversationFilter(customerIds: [12345]) |
digraph decision {
rankdir=TB;
node [shape=box, style=rounded];
start [label="Start", shape=ellipse];
inbox_q [label="User mentions\ninbox name?", shape=diamond];
searchInboxes [label="searchInboxes\n(ALWAYS FIRST)", style="bold,filled", fillcolor="#ffcccc"];
keyword_q [label="Keyword\nsearch?", shape=diamond];
comprehensive [label="comprehensiveConversationSearch\n(multi-status)", style="bold,filled", fillcolor="#ccffcc"];
listing_q [label="Listing by\ntime/status?", shape=diamond];
searchConv [label="searchConversations"];
complex_q [label="Complex filters?\n(domain, tags)", shape=diamond];
advanced [label="advancedConversationSearch"];
has_ids [label="Have IDs from\nprior search?", shape=diamond];
structured [label="structuredConversationFilter"];
thread_q [label="Need full\nthread?", shape=diamond];
getThreads [label="getThreads"];
summary_q [label="Need quick\nsummary?", shape=diamond];
getSummary [label="getConversationSummary"];
start -> inbox_q;
inbox_q -> searchInboxes [label="yes"];
inbox_q -> keyword_q [label="no"];
searchInboxes -> keyword_q;
keyword_q -> comprehensive [label="yes"];
keyword_q -> listing_q [label="no"];
listing_q -> searchConv [label="yes"];
listing_q -> complex_q [label="no"];
complex_q -> advanced [label="yes"];
complex_q -> has_ids [label="no"];
has_ids -> structured [label="yes"];
has_ids -> thread_q [label="no"];
thread_q -> getThreads [label="yes"];
thread_q -> summary_q [label="no"];
summary_q -> getSummary [label="yes"];
}
| I want to... | Use this tool | Required first? |
|---|---|---|
| Search by keywords | comprehensiveConversationSearch | searchInboxes if inbox mentioned |
| List recent tickets (single status) | searchConversations | searchInboxes if inbox mentioned |
| List recent tickets (ALL statuses) | structuredConversationFilter(sortBy: "waitingSince", status: "all") | searchInboxes if inbox mentioned |
| Find tickets by email domain | advancedConversationSearch | searchInboxes if inbox mentioned |
| Look up ticket #12345 | structuredConversationFilter | None |
| Get all tickets from customer X | structuredConversationFilter | Need customer ID from prior search |
| Read full conversation | getThreads | Need conversation ID |
| Get quick overview | getConversationSummary | Need conversation ID |
| List all inboxes | listAllInboxes | None |
| Get current server time | getServerTime | None |
| Look up a customer by email | searchCustomersByEmail | None |
| Browse customers | listCustomers | None |
| Get full customer profile | getCustomer | Need customer ID |
| Get customer contact details | getCustomerContacts | Need customer ID |
| Browse organizations | listOrganizations | None |
| Get organization details | getOrganization | Need organization ID |
| See who is in an organization | getOrganizationMembers | Need organization ID |
| See org's support history | getOrganizationConversations | Need organization ID |
| Tool | Purpose | Key Limitation |
|---|---|---|
searchInboxes | Get inbox ID from name | ALWAYS call first when inbox mentioned |
listAllInboxes | List all available inboxes | Helper for discovery |
searchConversations | List tickets by time/status | Defaults to ACTIVE ONLY |
comprehensiveConversationSearch | Keyword search across statuses | Preferred for content search |
advancedConversationSearch | Complex filters (domain, tags) | For boolean logic |
structuredConversationFilter | ID-based lookup | Requires IDs from prior search |
getConversationSummary | Quick overview | Needs conversation ID |
getThreads | Full message history | Needs conversation ID |
getServerTime | Current timestamp | For time-relative searches |
listCustomers | Browse customers by name or query | Page-based v2 API |
searchCustomersByEmail | Find customer by email | Uses v3 API with cursor pagination |
getCustomer | Full customer profile with contacts | Includes embedded sub-resources |
getCustomerContacts | All contact channels for a customer | Parallel sub-resource lookups |
listOrganizations | Browse organizations | Sortable by activity, size, name |
getOrganization | Organization profile with counts | Optional customer/conversation counts |
getOrganizationMembers | Customers in an organization | 50 per page |
getOrganizationConversations | Support history for an organization | 50 per page |
See references/tool-reference.md for complete parameter documentation.
User: "Search the support inbox for billing issues"
Steps:
Look up inbox ID:
searchInboxes(query: "support")
Result: { id: 359402, name: "Support" }
Search with inbox scope:
comprehensiveConversationSearch(
searchTerms: ["billing"],
inboxId: "359402"
)
User: "Show me recent tickets in the sales inbox"
Steps:
Look up inbox ID:
searchInboxes(query: "sales")
List recent (no keyword = use searchConversations):
searchConversations(
inboxId: "359402",
sort: "createdAt",
order: "desc",
limit: 20
)
User: "Show me ticket 12345"
Steps:
Direct lookup (no inbox lookup needed):
structuredConversationFilter(conversationNumber: 12345)
Get details:
getConversationSummary(conversationId: "<id from step 1>")
User: "Find tickets from @acme.com"
Steps:
advancedConversationSearch(emailDomain: "acme.com")
User: "Show me recent tickets from the last 30 days" (no specific status mentioned)
Steps:
structuredConversationFilter with a unique sortBy value:
structuredConversationFilter(
sortBy: "waitingSince", // Required: unique sortBy enables all-status queries
status: "all", // Includes active, pending, closed, spam
sortOrder: "desc",
limit: 50,
createdAfter: "2024-01-01T00:00:00Z" // Optional: date filter
)
Why this works:
structuredConversationFilter supports status: "all" (unlike searchConversations)sortBy: "waitingSince" satisfies thiscustomerName, customerEmailCommon mistake: Using searchConversations(status: "all") - this FAILS because searchConversations only accepts specific statuses (active/pending/closed/spam), not "all".
User: "Show me the full thread for conversation 12345678"
Steps:
getThreads(conversationId: "12345678", limit: 200)
User: "Look up the customer jane@acme.com and show their history"
Steps:
Find customer by email:
searchCustomersByEmail(email: "jane@acme.com")
Result: { id: 12345, firstName: "Jane", organizationId: 456 }
Get full profile:
getCustomer(customerId: "12345")
Get their conversations:
structuredConversationFilter(customerIds: [12345], status: "all", sortBy: "createdAt")
User: "Show me everything about the Acme Corp account"
Steps:
Find the organization:
listOrganizations(sortField: "name")
Find "Acme Corp" in results, get org ID.
Get organization details:
getOrganization(organizationId: "456", includeCounts: true)
See who is in the org:
getOrganizationMembers(organizationId: "456")
See their support history:
getOrganizationConversations(organizationId: "456")
| Mistake | Why It Fails | Correct Approach |
|---|---|---|
searchConversations(query: "billing") without status | Returns active only, misses 80%+ of tickets | comprehensiveConversationSearch(searchTerms: ["billing"]) |
searchConversations(inboxId: "Support") | Inbox ID must be numeric, not name | First: searchInboxes(query: "Support") |
searchConversations(status: "all") | "all" is NOT a valid status for this tool | Use structuredConversationFilter(sortBy: "waitingSince", status: "all") |
structuredConversationFilter as first search | Requires IDs you don't have yet | Start with comprehensiveConversationSearch |
structuredConversationFilter without unique field | Tool requires a unique field to work | Add sortBy: "waitingSince" or provide assignedTo/customerIds/conversationNumber |
Skipping searchInboxes when user mentions inbox | API requires numeric inbox ID | ALWAYS lookup first |
Using searchConversations for keyword search | Misses closed/pending tickets | Use comprehensiveConversationSearch |
See references/common-mistakes.md for more anti-patterns.
# STEP 1: Always get inbox ID first (when inbox mentioned)
searchInboxes(query: "support") # Returns inbox ID
# STEP 2a: Keyword search (multi-status)
comprehensiveConversationSearch(
searchTerms: ["billing", "refund"],
inboxId: "359402",
timeframeDays: 60
)
# STEP 2b: List recent (single status)
searchConversations(
inboxId: "359402",
status: "active", # Required: active, pending, closed, or spam (NOT "all")
sort: "createdAt",
order: "desc"
)
# STEP 2c: List recent (ALL statuses)
structuredConversationFilter(
sortBy: "waitingSince", # Required: unique sortBy enables status: "all"
status: "all",
sortOrder: "desc",
limit: 50
)
# Direct ticket lookup
structuredConversationFilter(conversationNumber: 12345)
# Email domain search
advancedConversationSearch(emailDomain: "acme.com")
# Full thread
getThreads(conversationId: "12345678")
# Quick summary
getConversationSummary(conversationId: "12345678")
# Customer lookup
searchCustomersByEmail(email: "jane@acme.com")
getCustomer(customerId: "12345")
getCustomerContacts(customerId: "12345")
# Organization traversal
listOrganizations(sortField: "conversationCount", sortOrder: "desc")
getOrganization(organizationId: "456", includeCounts: true)
getOrganizationMembers(organizationId: "456")
getOrganizationConversations(organizationId: "456")
Before executing a HelpScout search, verify:
searchInboxes first?comprehensiveConversationSearch (not searchConversations)?searchConversations?structuredConversationFilter(sortBy: "waitingSince", status: "all") (NOT searchConversations)?structuredConversationFilter? → Have unique field (conversationNumber, assignedTo, customerIds, folderId, or sortBy with waitingSince/customerName/customerEmail)?searchCustomersByEmail (not conversation search)?getCustomer after finding ID?listOrganizations or getOrganization?