diff --git a/servers/helpscout/.env.example b/servers/helpscout/.env.example new file mode 100644 index 0000000..d56030a --- /dev/null +++ b/servers/helpscout/.env.example @@ -0,0 +1,5 @@ +# HelpScout API Credentials +# Get these from: https://secure.helpscout.net/apps/ + +HELPSCOUT_APP_ID=your_app_id_here +HELPSCOUT_APP_SECRET=your_app_secret_here diff --git a/servers/helpscout/.gitignore b/servers/helpscout/.gitignore new file mode 100644 index 0000000..4274b51 --- /dev/null +++ b/servers/helpscout/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +.env +*.log +.DS_Store diff --git a/servers/helpscout/README.md b/servers/helpscout/README.md new file mode 100644 index 0000000..5412ea6 --- /dev/null +++ b/servers/helpscout/README.md @@ -0,0 +1,239 @@ +# HelpScout MCP Server + +A comprehensive Model Context Protocol (MCP) server for HelpScout's Mailbox API v2. This server provides 47+ tools and 18 interactive MCP apps for complete customer support management. + +## Features + +### 🛠️ Tools (47) + +#### Conversations (12 tools) +- List, get, create, update, delete conversations +- Manage threads (list, create reply, create note, create phone) +- Update tags, change status, assign conversations + +#### Customers (12 tools) +- List, get, create, update, delete customers +- Manage customer emails, phones, addresses +- List customer properties + +#### Mailboxes (5 tools) +- List and get mailboxes +- List folders and custom fields + +#### Users (3 tools) +- List and get users +- Get current authenticated user + +#### Tags (4 tools) +- List, create, update, delete tags + +#### Workflows (5 tools) +- List, get workflows +- Activate/deactivate workflows +- Get workflow statistics + +#### Saved Replies (5 tools) +- List, get, create, update, delete saved replies + +#### Teams (3 tools) +- List, get teams +- List team members + +#### Webhooks (5 tools) +- List, get, create, update, delete webhooks + +#### Reporting (5 tools) +- Company overview report +- Conversations report +- Happiness report +- Productivity report +- User-specific reports + +### 📱 MCP Apps (18) + +1. **conversation-dashboard** - Dashboard view of conversations with filters +2. **conversation-detail** - Detailed single conversation view +3. **conversation-grid** - Card-based grid layout +4. **conversation-timeline** - Visual timeline of conversation history +5. **customer-grid** - Grid view of all customers +6. **customer-detail** - Detailed customer profile +7. **mailbox-overview** - Overview of all mailboxes +8. **folder-browser** - Browse and manage folders +9. **user-stats** - Individual user performance metrics +10. **tag-manager** - Manage tags with usage stats +11. **workflow-dashboard** - Workflow management dashboard +12. **workflow-detail** - Detailed workflow configuration +13. **saved-replies** - Browse and manage saved replies +14. **team-overview** - Team structure and member stats +15. **happiness-report** - Customer satisfaction metrics +16. **productivity-report** - Team productivity metrics +17. **company-report** - Overall company performance KPIs +18. **search-results** - Search interface for conversations + +## Installation + +```bash +npm install +npm run build +``` + +## Configuration + +Create a `.env` file with your HelpScout credentials: + +```env +HELPSCOUT_APP_ID=your_app_id +HELPSCOUT_APP_SECRET=your_app_secret +``` + +### Getting HelpScout Credentials + +1. Go to https://secure.helpscout.net/apps/ +2. Create a new app +3. Copy the App ID and App Secret +4. Add them to your `.env` file + +## Usage + +### With MCP Client + +Add to your MCP client configuration: + +```json +{ + "mcpServers": { + "helpscout": { + "command": "node", + "args": ["/path/to/helpscout/dist/main.js"], + "env": { + "HELPSCOUT_APP_ID": "your_app_id", + "HELPSCOUT_APP_SECRET": "your_app_secret" + } + } + } +} +``` + +### Standalone + +```bash +npm start +``` + +## API Coverage + +This server implements the HelpScout Mailbox API v2: +- **Base URL**: https://api.helpscout.net/v2/ +- **Authentication**: OAuth2 Client Credentials +- **Features**: Pagination, error handling, automatic token refresh + +## Tool Examples + +### List Active Conversations +```javascript +{ + "name": "helpscout_list_conversations", + "arguments": { + "status": "active", + "mailbox": 123456 + } +} +``` + +### Create a Conversation +```javascript +{ + "name": "helpscout_create_conversation", + "arguments": { + "subject": "Need help with billing", + "type": "email", + "mailboxId": 123456, + "customerEmail": "customer@example.com", + "status": "active" + } +} +``` + +### Get Happiness Report +```javascript +{ + "name": "helpscout_get_happiness_report", + "arguments": { + "start": "2025-01-01", + "end": "2025-01-31" + } +} +``` + +## MCP Apps Usage + +Access MCP apps as resources: + +``` +helpscout://app/conversation-dashboard +helpscout://app/customer-detail +helpscout://app/happiness-report +``` + +Each app provides a rich HTML interface for interacting with HelpScout data. + +## Development + +```bash +# Build +npm run build + +# Watch mode +npm run dev + +# Prepare for publishing +npm run prepare +``` + +## Architecture + +``` +src/ +├── api/ +│ └── client.ts # HelpScout API client with OAuth2 +├── tools/ +│ ├── conversations-tools.ts +│ ├── customers-tools.ts +│ ├── mailboxes-tools.ts +│ ├── users-tools.ts +│ ├── tags-tools.ts +│ ├── workflows-tools.ts +│ ├── saved-replies-tools.ts +│ ├── teams-tools.ts +│ ├── webhooks-tools.ts +│ └── reporting-tools.ts +├── apps/ +│ ├── conversation-dashboard.ts +│ ├── conversation-detail.ts +│ └── ... (18 apps total) +├── types/ +│ └── index.ts # TypeScript type definitions +├── server.ts # MCP server implementation +└── main.ts # Entry point +``` + +## Error Handling + +The server includes comprehensive error handling: +- API errors are caught and returned with detailed messages +- Token refresh is automatic +- Pagination handles large datasets gracefully + +## Contributing + +This is part of the MCPEngine project. For contributions, please refer to the main repository guidelines. + +## License + +MIT + +## Links + +- [HelpScout API Documentation](https://developer.helpscout.com/) +- [MCP Documentation](https://modelcontextprotocol.io/) +- [MCPEngine](https://github.com/BusyBee3333/mcpengine) diff --git a/servers/helpscout/package.json b/servers/helpscout/package.json index 555887c..a79a006 100644 --- a/servers/helpscout/package.json +++ b/servers/helpscout/package.json @@ -1,20 +1,34 @@ { - "name": "mcp-server-helpscout", + "name": "@mcpengine/helpscout-server", "version": "1.0.0", + "description": "HelpScout MCP Server - Complete Mailbox API v2 integration", "type": "module", - "main": "dist/index.js", + "main": "dist/main.js", + "bin": { + "helpscout-mcp": "./dist/main.js" + }, "scripts": { "build": "tsc", - "start": "node dist/index.js", - "dev": "tsx src/index.ts" + "dev": "tsc --watch", + "start": "node dist/main.js", + "prepare": "npm run build" }, + "keywords": [ + "mcp", + "helpscout", + "customer-support", + "mailbox", + "conversations" + ], + "author": "MCPEngine", + "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^0.5.0", - "zod": "^3.22.4" + "@modelcontextprotocol/sdk": "^1.0.4", + "axios": "^1.7.9", + "dotenv": "^16.4.7" }, "devDependencies": { - "@types/node": "^20.10.0", - "tsx": "^4.7.0", - "typescript": "^5.3.0" + "@types/node": "^22.10.2", + "typescript": "^5.7.2" } } diff --git a/servers/helpscout/src/api/client.ts b/servers/helpscout/src/api/client.ts new file mode 100644 index 0000000..02ac9df --- /dev/null +++ b/servers/helpscout/src/api/client.ts @@ -0,0 +1,138 @@ +import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'; +import type { + HelpScoutConfig, + AccessTokenResponse, + PaginatedResponse +} from '../types/index.js'; + +export class HelpScoutClient { + private config: HelpScoutConfig; + private client: AxiosInstance; + private baseURL = 'https://api.helpscout.net/v2'; + + constructor(config: HelpScoutConfig) { + this.config = config; + this.client = axios.create({ + baseURL: this.baseURL, + headers: { + 'Content-Type': 'application/json', + }, + }); + + // Add auth interceptor + this.client.interceptors.request.use(async (config) => { + const token = await this.getAccessToken(); + config.headers.Authorization = `Bearer ${token}`; + return config; + }); + + // Add error handler + this.client.interceptors.response.use( + (response) => response, + (error) => { + if (error.response) { + const { status, data } = error.response; + throw new Error( + `HelpScout API Error (${status}): ${JSON.stringify(data)}` + ); + } + throw error; + } + ); + } + + private async getAccessToken(): Promise { + // Check if we have a valid token + if (this.config.accessToken && this.config.tokenExpiry) { + if (Date.now() < this.config.tokenExpiry) { + return this.config.accessToken; + } + } + + // Get new token + const response = await axios.post( + 'https://api.helpscout.net/v2/oauth2/token', + { + grant_type: 'client_credentials', + client_id: this.config.appId, + client_secret: this.config.appSecret, + } + ); + + this.config.accessToken = response.data.access_token; + this.config.tokenExpiry = Date.now() + response.data.expires_in * 1000; + + return this.config.accessToken; + } + + async get(path: string, params?: any): Promise { + const response = await this.client.get(path, { params }); + return response.data; + } + + async post(path: string, data?: any): Promise { + const response = await this.client.post(path, data); + return response.data; + } + + async put(path: string, data?: any): Promise { + const response = await this.client.put(path, data); + return response.data; + } + + async patch(path: string, data?: any): Promise { + const response = await this.client.patch(path, data); + return response.data; + } + + async delete(path: string): Promise { + await this.client.delete(path); + } + + async *paginate( + path: string, + params?: any, + embedKey?: string + ): AsyncGenerator { + let nextUrl: string | undefined = path; + let page = 1; + + while (nextUrl) { + const fullParams = { ...params, page }; + const response = await this.get>(nextUrl, fullParams); + + // Extract items from embedded response + let items: T[] = []; + if (response._embedded && embedKey && response._embedded[embedKey]) { + items = response._embedded[embedKey]; + } else if (Array.isArray(response)) { + items = response as any; + } + + yield items; + + // Check for next page + if ( + response._links?.next && + response.page && + response.page.number < response.page.totalPages + ) { + page++; + } else { + nextUrl = undefined; + } + } + } + + async getAllPages( + path: string, + params?: any, + embedKey?: string + ): Promise { + const allItems: T[] = []; + for await (const items of this.paginate(path, params, embedKey)) { + allItems.push(...items); + } + return allItems; + } +} diff --git a/servers/helpscout/src/apps/company-report.ts b/servers/helpscout/src/apps/company-report.ts new file mode 100644 index 0000000..a62f1be --- /dev/null +++ b/servers/helpscout/src/apps/company-report.ts @@ -0,0 +1,105 @@ +export const companyReportApp = { + name: 'company-report', + description: 'Overall company performance and KPIs', + content: ` + + + + + Company Report + + + +
+

🏢 Company Report

+
Overall performance overview for last 30 days
+ +
+
+
CONVERSATIONS CREATED
+
1,524
+
↑ 15% vs last month
+
+
+
CONVERSATIONS CLOSED
+
1,389
+
↑ 12% vs last month
+
+
+
CUSTOMERS HELPED
+
847
+
↑ 8% vs last month
+
+
+
HAPPINESS SCORE
+
94%
+
↑ 3% vs last month
+
+
+
REPLIES SENT
+
3,247
+
↑ 18% vs last month
+
+
+
AVG RESOLUTION TIME
+
8.4h
+
↑ 1.2h vs last month
+
+
+ +
+
+
Conversation Volume Trend
+
📈 7-day conversation trend chart
+
+ +
+
Key Metrics Summary
+
+ First Response Time + 3.2 hours +
+
+ Active Conversations + 147 +
+
+ Pending Conversations + 23 +
+
+ Team Members + 12 agents +
+
+ Active Mailboxes + 3 +
+
+
+
+ + + `, +}; diff --git a/servers/helpscout/src/apps/conversation-dashboard.ts b/servers/helpscout/src/apps/conversation-dashboard.ts new file mode 100644 index 0000000..a778430 --- /dev/null +++ b/servers/helpscout/src/apps/conversation-dashboard.ts @@ -0,0 +1,141 @@ +export const conversationDashboardApp = { + name: 'conversation-dashboard', + description: 'Dashboard view of conversations with filters and quick actions', + content: ` + + + + + + Conversation Dashboard + + + +
+

📬 Conversation Dashboard

+ +
+
+
147
+
Active Conversations
+
+
+
23
+
Pending
+
+
+
8
+
Unassigned
+
+
+
4.2h
+
Avg Response Time
+
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
SUBJECT
+
CUSTOMER
+
ASSIGNED
+
STATUS
+
UPDATED
+
+
+
+
Payment issue with subscription
+
Customer having trouble with their credit card...
+
+
Sarah Chen
+
John Smith
+
Active
+
2 hours ago
+
+
+
+
Feature request: Dark mode
+
Would love to have a dark mode option...
+
+
Mike Johnson
+
Emily Davis
+
Pending
+
5 hours ago
+
+
+
+
Login problems
+
Cannot log in with my account credentials...
+
+
Alex Brown
+
+
Active
+
1 day ago
+
+
+
+ + + `, +}; diff --git a/servers/helpscout/src/apps/conversation-detail.ts b/servers/helpscout/src/apps/conversation-detail.ts new file mode 100644 index 0000000..ce1854d --- /dev/null +++ b/servers/helpscout/src/apps/conversation-detail.ts @@ -0,0 +1,134 @@ +export const conversationDetailApp = { + name: 'conversation-detail', + description: 'Detailed view of a single conversation with thread history and actions', + content: ` + + + + + + Conversation Detail + + + + + +
+
+
Payment issue with subscription
+
+ + +
+
+ +
+
+
+
Sarah Chen (Customer)
+
2 hours ago
+
+
+ Hi, I'm having trouble updating my payment method. Every time I try to save my new credit card, I get an error message. Can you help? +
+
+ +
+
+
🔒 John Smith (Internal Note)
+
1 hour ago
+
+
+ Checking with billing team - seems like a known issue with Visa cards from Canada. Should have a fix deployed by EOD. +
+
+ +
+
+
John Smith (Agent)
+
30 minutes ago
+
+
+ Hi Sarah,

+ Thanks for reaching out! I've identified the issue - there's a temporary problem with processing Canadian Visa cards. Our engineering team is working on a fix that should be live within the next few hours.

+ In the meantime, if you have an alternative payment method (Mastercard or American Express), that should work without issues.

+ I'll follow up once the fix is deployed!

+ Best regards,
+ John +
+
+
+
+ + + `, +}; diff --git a/servers/helpscout/src/apps/conversation-grid.ts b/servers/helpscout/src/apps/conversation-grid.ts new file mode 100644 index 0000000..6d5715e --- /dev/null +++ b/servers/helpscout/src/apps/conversation-grid.ts @@ -0,0 +1,104 @@ +export const conversationGridApp = { + name: 'conversation-grid', + description: 'Alternative grid layout for conversations with card view', + content: ` + + + + + Conversations Grid + + + +
+

📬 Conversations

+
+
+
+ #1247 + Active +
+
Payment issue with subscription
+
Customer having trouble with their credit card. Error appears when trying to update payment method...
+ +
+ +
+
+ #1246 + Pending +
+
Feature request: Dark mode
+
Would love to have a dark mode option for the application. Many users work late hours...
+ +
+ +
+
+ #1245 + Active +
+
Login problems
+
Cannot log in with my account credentials. Password reset doesn't seem to work either...
+ +
+ +
+
+ #1244 + Active +
+
Question about API limits
+
What are the rate limits for the API? We're planning to scale our integration...
+ +
+
+
+ + + `, +}; diff --git a/servers/helpscout/src/apps/conversation-timeline.ts b/servers/helpscout/src/apps/conversation-timeline.ts new file mode 100644 index 0000000..6eec16a --- /dev/null +++ b/servers/helpscout/src/apps/conversation-timeline.ts @@ -0,0 +1,127 @@ +export const conversationTimelineApp = { + name: 'conversation-timeline', + description: 'Visual timeline view of conversation history and events', + content: ` + + + + + Conversation Timeline + + + +
+
+
Payment issue with subscription
+
Conversation #1247 • Created 2 days ago by Sarah Chen
+
+ +
+
+
+
+
+ Sarah Chen + (Customer) +
+
2 days ago • 2:34 PM
+
+
+ Hi, I'm having trouble updating my payment method. Every time I try to save my new credit card, I get an error message. Can you help? +
+
+ +
+
+
+
+ System Event +
+
2 days ago • 2:35 PM
+
+
+ ✓ Assigned to John Smith +
+
+ +
+
+
+
+ System Event +
+
2 days ago • 2:35 PM
+
+
+ 🏷️ Tags added: billing, urgent +
+
+ +
+
+
+
+ John Smith + (Internal Note) +
+
2 days ago • 3:15 PM
+
+
+ Checking with billing team - seems like a known issue with Visa cards from Canada. Should have a fix deployed by EOD. +
+
+ +
+
+
+
+ John Smith + (Agent Reply) +
+
2 days ago • 4:30 PM
+
+
+ Hi Sarah,

+ Thanks for reaching out! I've identified the issue - there's a temporary problem with processing Canadian Visa cards. Our engineering team is working on a fix that should be live within the next few hours.

+ In the meantime, if you have an alternative payment method (Mastercard or American Express), that should work without issues.

+ I'll follow up once the fix is deployed! +
+
+ +
+
+
+
+ System Event +
+
2 days ago • 4:31 PM
+
+
+ Status changed: Active → Pending +
+
+
+
+ + + `, +}; diff --git a/servers/helpscout/src/apps/customer-detail.ts b/servers/helpscout/src/apps/customer-detail.ts new file mode 100644 index 0000000..94b48a6 --- /dev/null +++ b/servers/helpscout/src/apps/customer-detail.ts @@ -0,0 +1,114 @@ +export const customerDetailApp = { + name: 'customer-detail', + description: 'Detailed customer profile with history and contact information', + content: ` + + + + + + Customer Detail + + + +
+ + +
+
+
Conversations (12)
+
Notes (3)
+
Activity
+
+ +
+
+ Payment issue with subscription + Active +
+
Last updated 2 hours ago • Assigned to John Smith
+
+ +
+
+ Feature request: Dark mode + Closed +
+
Closed 3 days ago • Assigned to Emily Davis
+
+ +
+
+ Question about API limits + Closed +
+
Closed 1 week ago • Assigned to John Smith
+
+
+
+ + + `, +}; diff --git a/servers/helpscout/src/apps/customer-grid.ts b/servers/helpscout/src/apps/customer-grid.ts new file mode 100644 index 0000000..4d548de --- /dev/null +++ b/servers/helpscout/src/apps/customer-grid.ts @@ -0,0 +1,85 @@ +export const customerGridApp = { + name: 'customer-grid', + description: 'Grid view of all customers with search and filtering', + content: ` + + + + + + Customers + + + +
+

👥 Customers

+
+ + +
+
+
+
NAME
+
EMAIL
+
PHONE
+
ORGANIZATION
+
CONVERSATIONS
+
+
+
+
Sarah Chen
+
sarah.chen@example.com
+
+
sarah.chen@example.com
+
+1 (555) 123-4567
+
Acme Corp
+
12 conversations
+
+
+
+
Mike Johnson
+
mike.j@techstart.io
+
+
mike.j@techstart.io
+
+1 (555) 987-6543
+
TechStart
+
8 conversations
+
+
+
+
Emily Davis
+
emily@startup.com
+
+
emily@startup.com
+
+1 (555) 456-7890
+
Startup Inc
+
5 conversations
+
+
+
+ + + `, +}; diff --git a/servers/helpscout/src/apps/folder-browser.ts b/servers/helpscout/src/apps/folder-browser.ts new file mode 100644 index 0000000..60818f6 --- /dev/null +++ b/servers/helpscout/src/apps/folder-browser.ts @@ -0,0 +1,116 @@ +export const folderBrowserApp = { + name: 'folder-browser', + description: 'Browse and manage mailbox folders', + content: ` + + + + + Folder Browser + + + +
+ + +
+
+
📥 Unassigned
+
8 conversations need to be assigned
+
+ +
+
+
Payment issue with subscription
+
Sarah Chen • 2 hours ago • #1247
+
+
+
Login problems
+
Alex Brown • 1 day ago • #1245
+
+
+
Question about API limits
+
Emily Davis • 1 day ago • #1244
+
+
+
Account upgrade request
+
Mike Johnson • 2 days ago • #1238
+
+
+
+
+ + + `, +}; diff --git a/servers/helpscout/src/apps/happiness-report.ts b/servers/helpscout/src/apps/happiness-report.ts new file mode 100644 index 0000000..69e0207 --- /dev/null +++ b/servers/helpscout/src/apps/happiness-report.ts @@ -0,0 +1,93 @@ +export const happinessReportApp = { + name: 'happiness-report', + description: 'Customer satisfaction and happiness metrics', + content: ` + + + + + Happiness Report + + + +
+

😊 Happiness Report

+
Customer satisfaction metrics for last 30 days
+ +
+
94%
+
Overall Happiness Score
+
↑ 3% from previous period
+
+ +
+
+
237
+
😊 Great Ratings
+
+
+
18
+
😐 Okay Ratings
+
+
+
7
+
😞 Not Good Ratings
+
+
+ +
+
Ratings Distribution
+
+
+
Great
+
+
237 (90%)
+
+
+
+
Okay
+
+
18 (7%)
+
+
+
+
Not Good
+
+
7 (3%)
+
+
+
+
+
+ + + `, +}; diff --git a/servers/helpscout/src/apps/mailbox-overview.ts b/servers/helpscout/src/apps/mailbox-overview.ts new file mode 100644 index 0000000..e4b294f --- /dev/null +++ b/servers/helpscout/src/apps/mailbox-overview.ts @@ -0,0 +1,99 @@ +export const mailboxOverviewApp = { + name: 'mailbox-overview', + description: 'Overview of all mailboxes with folder counts and activity', + content: ` + + + + + Mailbox Overview + + + +
+

📮 Mailboxes

+
+
+
+
Support
+
💬
+
+
support@company.com
+
+
+ 📥 Unassigned + 8 +
+
+ 📋 My Conversations + 23 +
+
+ ✅ Closed + 147 +
+
+
+
+
+
Sales
+
💰
+
+
sales@company.com
+
+
+ 📥 Unassigned + 3 +
+
+ 📋 My Conversations + 12 +
+
+ ✅ Closed + 89 +
+
+
+
+
+
Billing
+
💳
+
+
billing@company.com
+
+
+ 📥 Unassigned + 2 +
+
+ 📋 My Conversations + 7 +
+
+ ✅ Closed + 64 +
+
+
+
+
+ + + `, +}; diff --git a/servers/helpscout/src/apps/productivity-report.ts b/servers/helpscout/src/apps/productivity-report.ts new file mode 100644 index 0000000..ccd19f0 --- /dev/null +++ b/servers/helpscout/src/apps/productivity-report.ts @@ -0,0 +1,105 @@ +export const productivityReportApp = { + name: 'productivity-report', + description: 'Team productivity metrics and response times', + content: ` + + + + + Productivity Report + + + +
+

📊 Productivity Report

+
Team performance metrics for last 30 days
+ +
+
+
1,247
+
Replies Sent
+
↑ 12% from last month
+
+
+
3.2h
+
Avg First Response Time
+
↓ 0.5h from last month
+
+
+
8.4h
+
Avg Resolution Time
+
↑ 1.2h from last month
+
+
+
892
+
Resolved Conversations
+
↑ 8% from last month
+
+
+ +
+
+
Top Performers (Replies)
+
+ 🥇 John Smith + 324 replies +
+
+ 🥈 Emily Davis + 287 replies +
+
+ 🥉 Mike Johnson + 219 replies +
+
+ Sarah Wilson + 198 replies +
+
+ +
+
Response Time by Agent
+
+ John Smith + 2.8h +
+
+ Emily Davis + 3.1h +
+
+ Mike Johnson + 3.5h +
+
+ Sarah Wilson + 4.2h +
+
+
+
+ + + `, +}; diff --git a/servers/helpscout/src/apps/saved-replies.ts b/servers/helpscout/src/apps/saved-replies.ts new file mode 100644 index 0000000..a7b8f58 --- /dev/null +++ b/servers/helpscout/src/apps/saved-replies.ts @@ -0,0 +1,101 @@ +export const savedRepliesApp = { + name: 'saved-replies', + description: 'Manage and browse saved reply templates', + content: ` + + + + + Saved Replies + + + +
+

💬 Saved Replies

+
+ + +
+
+
+
+
Welcome Message
+
+ + +
+
+
+ Hi there! Thanks for reaching out to our support team. We're here to help! Could you please provide more details about the issue you're experiencing? +
+
Support mailbox • Used 47 times
+
+ +
+
+
Billing Issue Response
+
+ + +
+
+
+ I understand you're experiencing a billing issue. I've escalated this to our billing team and they'll review your account within the next 24 hours. You'll receive an email update shortly. +
+
Billing mailbox • Used 23 times
+
+ +
+
+
Feature Request Acknowledgment
+
+ + +
+
+
+ Thank you for this feature suggestion! We really appreciate feedback from our users. I've passed this along to our product team for consideration in future updates. +
+
All mailboxes • Used 34 times
+
+ +
+
+
Account Access Help
+
+ + +
+
+
+ Let me help you regain access to your account. Please try resetting your password using the "Forgot Password" link on the login page. If that doesn't work, I can manually send you a reset link. +
+
Support mailbox • Used 89 times
+
+
+
+ + + `, +}; diff --git a/servers/helpscout/src/apps/search-results.ts b/servers/helpscout/src/apps/search-results.ts new file mode 100644 index 0000000..1f3ad35 --- /dev/null +++ b/servers/helpscout/src/apps/search-results.ts @@ -0,0 +1,88 @@ +export const searchResultsApp = { + name: 'search-results', + description: 'Search results interface for finding conversations and customers', + content: ` + + + + + Search Results + + + +
+ + +
+ Found 12 results for "payment issue" +
+ +
+ + + + +
+ +
+ Conversation #1247 +
Payment issue with subscription
+
+ Customer having trouble with their credit card. Error appears when trying to update payment method. The issue started yesterday... +
+
Sarah Chen • Active • 2 hours ago
+
+ +
+ Conversation #1189 +
Billing problem - double charge
+
+ I was charged twice for my subscription this month. Can you help resolve this payment issue? +
+
Mike Johnson • Closed • 3 days ago
+
+ +
+ Customer +
Sarah Chen
+
+ Background: VIP customer, frequently reports payment issues. Works at Acme Corp as Product Manager. +
+
sarah.chen@example.com • 12 conversations
+
+ +
+ Conversation #1156 +
Can't complete checkout
+
+ Checkout process fails at the payment step. Tried multiple cards, same issue. +
+
Alex Brown • Closed • 1 week ago
+
+
+ + + `, +}; diff --git a/servers/helpscout/src/apps/tag-manager.ts b/servers/helpscout/src/apps/tag-manager.ts new file mode 100644 index 0000000..7fc51e6 --- /dev/null +++ b/servers/helpscout/src/apps/tag-manager.ts @@ -0,0 +1,97 @@ +export const tagManagerApp = { + name: 'tag-manager', + description: 'Manage tags with usage stats and color coding', + content: ` + + + + + Tag Manager + + + +
+

🏷️ Tag Manager

+
+
Managing 12 tags
+ +
+
+
+
TAG NAME
+
USAGE
+
CREATED
+
ACTIONS
+
+
+
+
+ urgent +
+
23 conversations
+
Jan 2024
+
+ + +
+
+
+
+
+ billing +
+
47 conversations
+
Dec 2023
+
+ + +
+
+
+
+
+ feature-request +
+
31 conversations
+
Nov 2023
+
+ + +
+
+
+
+
+ bug +
+
18 conversations
+
Oct 2023
+
+ + +
+
+
+
+ + + `, +}; diff --git a/servers/helpscout/src/apps/team-overview.ts b/servers/helpscout/src/apps/team-overview.ts new file mode 100644 index 0000000..dcbc5ec --- /dev/null +++ b/servers/helpscout/src/apps/team-overview.ts @@ -0,0 +1,104 @@ +export const teamOverviewApp = { + name: 'team-overview', + description: 'Team structure and member statistics', + content: ` + + + + + Teams + + + +
+

👥 Teams

+ +
+
+
+
Support Team
+
6 members
+
+
+
+
JS
+
+
John Smith
+
john@company.com
+
Team Lead
+
+
+
+
ED
+
+
Emily Davis
+
emily@company.com
+
Senior Agent
+
+
+
+
MJ
+
+
Mike Johnson
+
mike@company.com
+
Agent
+
+
+
+
SW
+
+
Sarah Wilson
+
sarah@company.com
+
Agent
+
+
+
+
+ +
+
+
Sales Team
+
4 members
+
+
+
+
AB
+
+
Alex Brown
+
alex@company.com
+
Sales Lead
+
+
+
+
LG
+
+
Lisa Garcia
+
lisa@company.com
+
Sales Rep
+
+
+
+
+
+
+ + + `, +}; diff --git a/servers/helpscout/src/apps/user-stats.ts b/servers/helpscout/src/apps/user-stats.ts new file mode 100644 index 0000000..7a5a493 --- /dev/null +++ b/servers/helpscout/src/apps/user-stats.ts @@ -0,0 +1,86 @@ +export const userStatsApp = { + name: 'user-stats', + description: 'Individual user performance statistics and metrics', + content: ` + + + + + User Statistics + + + +
+
+
JS
+
John Smith
+
Support Team Lead
+
john.smith@company.com
+
+ +
+
+
324
+
Replies Sent (30d)
+
+
+
97%
+
Happiness Score
+
+
+
2.8h
+
Avg Response Time
+
+
+ +
+
Performance Metrics (Last 30 Days)
+
+ Conversations Created + 89 +
+
+ Conversations Closed + 147 +
+
+ Average Resolution Time + 7.2 hours +
+
+ First Response Time + 2.8 hours +
+
+ Active Conversations + 23 +
+
+ Customer Ratings + 72 ratings (97% positive) +
+
+
+ + + `, +}; diff --git a/servers/helpscout/src/apps/workflow-dashboard.ts b/servers/helpscout/src/apps/workflow-dashboard.ts new file mode 100644 index 0000000..d8550a1 --- /dev/null +++ b/servers/helpscout/src/apps/workflow-dashboard.ts @@ -0,0 +1,128 @@ +export const workflowDashboardApp = { + name: 'workflow-dashboard', + description: 'Dashboard for workflows with activation status and stats', + content: ` + + + + + Workflows + + + +
+

⚡ Workflows

+
+
+
+
+ Automatic + Auto-tag billing conversations +
+
Support mailbox • Modified 2 weeks ago
+
+
+
+
247
+
Total Runs
+
+
+
245
+
Successful
+
+
+
+
+
+
+
+
+
+ Manual + Escalate to senior support +
+
Support mailbox • Modified 1 month ago
+
+
+
+
34
+
Total Runs
+
+
+
34
+
Successful
+
+
+
+
+
+
+
+
+
+ Automatic + Close spam conversations +
+
All mailboxes • Modified 3 days ago
+
+
+
+
89
+
Total Runs
+
+
+
89
+
Successful
+
+
+
+
+
+
+
+
+
+ Automatic + Send satisfaction survey +
+
Support mailbox • Modified 1 week ago
+
+
+
+
156
+
Total Runs
+
+
+
154
+
Successful
+
+
+
+
+
+
+
+
+ + + `, +}; diff --git a/servers/helpscout/src/apps/workflow-detail.ts b/servers/helpscout/src/apps/workflow-detail.ts new file mode 100644 index 0000000..5113851 --- /dev/null +++ b/servers/helpscout/src/apps/workflow-detail.ts @@ -0,0 +1,118 @@ +export const workflowDetailApp = { + name: 'workflow-detail', + description: 'Detailed view of workflow configuration and execution history', + content: ` + + + + + Workflow Detail + + + +
+
+
+
+ Automatic + Auto-tag billing conversations +
+
Support mailbox • Modified 2 weeks ago
+
+
+
+
+
+ +
+
+
247
+
Total Runs
+
+
+
245
+
Successful
+
+
+
2
+
Failed
+
+
+ +
+
⚙️ Conditions (When to run)
+
+
Trigger
+
Conversation is created
+
+
+
Subject contains
+
payment, billing, invoice, subscription
+
+
+
Mailbox
+
Support
+
+
+ +
+
🎯 Actions (What to do)
+
+
Add Tags
+
billing
+
+
+
Assign To
+
Billing Team
+
+
+
Set Priority
+
High
+
+
+ +
+
📊 Recent Executions
+
+
+
Conversation #1247: Payment issue
+
2 hours ago
+
+
✓ Success
+
+
+
+
Conversation #1189: Billing problem
+
3 days ago
+
+
✓ Success
+
+
+
+ + + `, +}; diff --git a/servers/helpscout/src/index.ts b/servers/helpscout/src/index.ts deleted file mode 100644 index 285b57d..0000000 --- a/servers/helpscout/src/index.ts +++ /dev/null @@ -1,333 +0,0 @@ -#!/usr/bin/env node -import { Server } from "@modelcontextprotocol/sdk/server/index.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { - CallToolRequestSchema, - ListToolsRequestSchema, -} from "@modelcontextprotocol/sdk/types.js"; - -// ============================================ -// CONFIGURATION -// ============================================ -const MCP_NAME = "helpscout"; -const MCP_VERSION = "1.0.0"; -const API_BASE_URL = "https://api.helpscout.net/v2"; - -// ============================================ -// API CLIENT (OAuth 2.0) -// ============================================ -class HelpScoutClient { - private accessToken: string; - private baseUrl: string; - - constructor(accessToken: string) { - this.accessToken = accessToken; - this.baseUrl = API_BASE_URL; - } - - async request(endpoint: string, options: RequestInit = {}) { - const url = `${this.baseUrl}${endpoint}`; - const response = await fetch(url, { - ...options, - headers: { - "Authorization": `Bearer ${this.accessToken}`, - "Content-Type": "application/json", - ...options.headers, - }, - }); - - if (!response.ok) { - const error = await response.text(); - throw new Error(`Help Scout API error: ${response.status} - ${error}`); - } - - // Some endpoints return 201/204 with no body - const text = await response.text(); - return text ? JSON.parse(text) : { success: true }; - } - - async get(endpoint: string, params: Record = {}) { - const url = new URL(`${this.baseUrl}${endpoint}`); - for (const [key, value] of Object.entries(params)) { - if (value !== undefined && value !== null) { - url.searchParams.set(key, String(value)); - } - } - return this.request(url.pathname + url.search, { method: "GET" }); - } - - async post(endpoint: string, data: any) { - return this.request(endpoint, { - method: "POST", - body: JSON.stringify(data), - }); - } -} - -// ============================================ -// TOOL DEFINITIONS -// ============================================ -const tools = [ - { - name: "list_conversations", - description: "List conversations (tickets) from Help Scout. Returns paginated list with embedded conversation data.", - inputSchema: { - type: "object" as const, - properties: { - mailbox: { type: "number", description: "Filter by mailbox ID" }, - status: { - type: "string", - description: "Filter by status", - enum: ["active", "open", "closed", "pending", "spam"] - }, - tag: { type: "string", description: "Filter by tag" }, - assigned_to: { type: "number", description: "Filter by assigned user ID" }, - folder: { type: "number", description: "Filter by folder ID" }, - page: { type: "number", description: "Page number (default 1)" }, - sortField: { type: "string", description: "Sort field (createdAt, modifiedAt, number)" }, - sortOrder: { type: "string", enum: ["asc", "desc"], description: "Sort order" }, - }, - }, - }, - { - name: "get_conversation", - description: "Get a specific conversation by ID with full thread details", - inputSchema: { - type: "object" as const, - properties: { - id: { type: "number", description: "Conversation ID" }, - }, - required: ["id"], - }, - }, - { - name: "create_conversation", - description: "Create a new conversation (ticket) in Help Scout", - inputSchema: { - type: "object" as const, - properties: { - mailboxId: { type: "number", description: "Mailbox ID (required)" }, - subject: { type: "string", description: "Conversation subject (required)" }, - customer: { - type: "object", - description: "Customer object with email (required): {email: 'customer@example.com'}", - }, - type: { - type: "string", - enum: ["email", "phone", "chat"], - description: "Conversation type (default: email)" - }, - status: { - type: "string", - enum: ["active", "closed", "pending"], - description: "Initial status (default: active)" - }, - threads: { - type: "array", - description: "Initial threads [{type: 'customer', text: 'message content'}]", - items: { type: "object" } - }, - tags: { - type: "array", - items: { type: "string" }, - description: "Tags to apply" - }, - assignTo: { type: "number", description: "User ID to assign to" }, - }, - required: ["mailboxId", "subject", "customer"], - }, - }, - { - name: "reply_conversation", - description: "Reply to an existing conversation", - inputSchema: { - type: "object" as const, - properties: { - conversationId: { type: "number", description: "Conversation ID to reply to (required)" }, - text: { type: "string", description: "Reply text/HTML content (required)" }, - user: { type: "number", description: "User ID sending reply (required for agent replies)" }, - customer: { - type: "object", - description: "Customer object for customer replies: {email: 'customer@example.com'}" - }, - type: { - type: "string", - enum: ["reply", "note"], - description: "Thread type (reply=visible to customer, note=internal)" - }, - status: { - type: "string", - enum: ["active", "closed", "pending"], - description: "Set conversation status after reply" - }, - draft: { type: "boolean", description: "Save as draft" }, - cc: { type: "array", items: { type: "string" }, description: "CC email addresses" }, - bcc: { type: "array", items: { type: "string" }, description: "BCC email addresses" }, - }, - required: ["conversationId", "text"], - }, - }, - { - name: "list_customers", - description: "List customers from Help Scout", - inputSchema: { - type: "object" as const, - properties: { - email: { type: "string", description: "Filter by email address" }, - firstName: { type: "string", description: "Filter by first name" }, - lastName: { type: "string", description: "Filter by last name" }, - query: { type: "string", description: "Search query" }, - page: { type: "number", description: "Page number" }, - sortField: { type: "string", description: "Sort field (firstName, lastName, modifiedAt)" }, - sortOrder: { type: "string", enum: ["asc", "desc"], description: "Sort order" }, - }, - }, - }, - { - name: "list_mailboxes", - description: "List all mailboxes accessible to the authenticated user", - inputSchema: { - type: "object" as const, - properties: { - page: { type: "number", description: "Page number (default 1)" }, - }, - }, - }, - { - name: "search", - description: "Search conversations using Help Scout's search syntax", - inputSchema: { - type: "object" as const, - properties: { - query: { - type: "string", - description: "Search query (required). Supports: subject:, customer:, status:, tag:, mailbox:, etc." - }, - page: { type: "number", description: "Page number" }, - sortField: { type: "string", description: "Sort field" }, - sortOrder: { type: "string", enum: ["asc", "desc"], description: "Sort order" }, - }, - required: ["query"], - }, - }, -]; - -// ============================================ -// TOOL HANDLERS -// ============================================ -async function handleTool(client: HelpScoutClient, name: string, args: any) { - switch (name) { - case "list_conversations": { - const params: Record = {}; - if (args.mailbox) params.mailbox = args.mailbox; - if (args.status) params.status = args.status; - if (args.tag) params.tag = args.tag; - if (args.assigned_to) params["assigned_to"] = args.assigned_to; - if (args.folder) params.folder = args.folder; - if (args.page) params.page = args.page; - if (args.sortField) params.sortField = args.sortField; - if (args.sortOrder) params.sortOrder = args.sortOrder; - return await client.get("/conversations", params); - } - case "get_conversation": { - const { id } = args; - return await client.get(`/conversations/${id}`); - } - case "create_conversation": { - const payload: any = { - mailboxId: args.mailboxId, - subject: args.subject, - customer: args.customer, - type: args.type || "email", - status: args.status || "active", - }; - if (args.threads) payload.threads = args.threads; - if (args.tags) payload.tags = args.tags; - if (args.assignTo) payload.assignTo = args.assignTo; - return await client.post("/conversations", payload); - } - case "reply_conversation": { - const { conversationId, ...threadData } = args; - const payload: any = { - text: threadData.text, - type: threadData.type || "reply", - }; - if (threadData.user) payload.user = threadData.user; - if (threadData.customer) payload.customer = threadData.customer; - if (threadData.status) payload.status = threadData.status; - if (threadData.draft) payload.draft = threadData.draft; - if (threadData.cc) payload.cc = threadData.cc; - if (threadData.bcc) payload.bcc = threadData.bcc; - return await client.post(`/conversations/${conversationId}/reply`, payload); - } - case "list_customers": { - const params: Record = {}; - if (args.email) params.email = args.email; - if (args.firstName) params.firstName = args.firstName; - if (args.lastName) params.lastName = args.lastName; - if (args.query) params.query = args.query; - if (args.page) params.page = args.page; - if (args.sortField) params.sortField = args.sortField; - if (args.sortOrder) params.sortOrder = args.sortOrder; - return await client.get("/customers", params); - } - case "list_mailboxes": { - return await client.get("/mailboxes", { page: args.page }); - } - case "search": { - const params: Record = { query: args.query }; - if (args.page) params.page = args.page; - if (args.sortField) params.sortField = args.sortField; - if (args.sortOrder) params.sortOrder = args.sortOrder; - return await client.get("/conversations/search", params); - } - default: - throw new Error(`Unknown tool: ${name}`); - } -} - -// ============================================ -// SERVER SETUP -// ============================================ -async function main() { - const accessToken = process.env.HELPSCOUT_ACCESS_TOKEN; - if (!accessToken) { - console.error("Error: HELPSCOUT_ACCESS_TOKEN environment variable required"); - console.error("Obtain via OAuth 2.0 flow at https://developer.helpscout.com/mailbox-api/overview/authentication/"); - process.exit(1); - } - - const client = new HelpScoutClient(accessToken); - - const server = new Server( - { name: `${MCP_NAME}-mcp`, version: MCP_VERSION }, - { capabilities: { tools: {} } } - ); - - server.setRequestHandler(ListToolsRequestSchema, async () => ({ - tools, - })); - - server.setRequestHandler(CallToolRequestSchema, async (request) => { - const { name, arguments: args } = request.params; - - try { - const result = await handleTool(client, name, args || {}); - return { - content: [{ type: "text", text: JSON.stringify(result, null, 2) }], - }; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return { - content: [{ type: "text", text: `Error: ${message}` }], - isError: true, - }; - } - }); - - const transport = new StdioServerTransport(); - await server.connect(transport); - console.error(`${MCP_NAME} MCP server running on stdio`); -} - -main().catch(console.error); diff --git a/servers/helpscout/src/main.ts b/servers/helpscout/src/main.ts new file mode 100644 index 0000000..db108b8 --- /dev/null +++ b/servers/helpscout/src/main.ts @@ -0,0 +1,11 @@ +#!/usr/bin/env node +import { runServer } from './server.js'; +import dotenv from 'dotenv'; + +// Load environment variables from .env file if it exists +dotenv.config(); + +runServer().catch((error) => { + console.error('Fatal error:', error); + process.exit(1); +}); diff --git a/servers/helpscout/src/server.ts b/servers/helpscout/src/server.ts new file mode 100644 index 0000000..2c04633 --- /dev/null +++ b/servers/helpscout/src/server.ts @@ -0,0 +1,186 @@ +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { + ListResourcesRequestSchema, + ReadResourceRequestSchema, + ListToolsRequestSchema, + CallToolRequestSchema, +} from '@modelcontextprotocol/sdk/types.js'; +import { HelpScoutClient } from './api/client.js'; +import { registerConversationTools } from './tools/conversations-tools.js'; +import { registerCustomerTools } from './tools/customers-tools.js'; +import { registerMailboxTools } from './tools/mailboxes-tools.js'; +import { registerUserTools } from './tools/users-tools.js'; +import { registerTagTools } from './tools/tags-tools.js'; +import { registerWorkflowTools } from './tools/workflows-tools.js'; +import { registerSavedReplyTools } from './tools/saved-replies-tools.js'; +import { registerTeamTools } from './tools/teams-tools.js'; +import { registerWebhookTools } from './tools/webhooks-tools.js'; +import { registerReportingTools } from './tools/reporting-tools.js'; + +// Import MCP apps +import { conversationDashboardApp } from './apps/conversation-dashboard.js'; +import { conversationDetailApp } from './apps/conversation-detail.js'; +import { conversationGridApp } from './apps/conversation-grid.js'; +import { conversationTimelineApp } from './apps/conversation-timeline.js'; +import { customerGridApp } from './apps/customer-grid.js'; +import { customerDetailApp } from './apps/customer-detail.js'; +import { mailboxOverviewApp } from './apps/mailbox-overview.js'; +import { folderBrowserApp } from './apps/folder-browser.js'; +import { userStatsApp } from './apps/user-stats.js'; +import { tagManagerApp } from './apps/tag-manager.js'; +import { workflowDashboardApp } from './apps/workflow-dashboard.js'; +import { workflowDetailApp } from './apps/workflow-detail.js'; +import { savedRepliesApp } from './apps/saved-replies.js'; +import { teamOverviewApp } from './apps/team-overview.js'; +import { happinessReportApp } from './apps/happiness-report.js'; +import { productivityReportApp } from './apps/productivity-report.js'; +import { companyReportApp } from './apps/company-report.js'; +import { searchResultsApp } from './apps/search-results.js'; + +export async function runServer() { + const appId = process.env.HELPSCOUT_APP_ID; + const appSecret = process.env.HELPSCOUT_APP_SECRET; + + if (!appId || !appSecret) { + throw new Error( + 'HELPSCOUT_APP_ID and HELPSCOUT_APP_SECRET environment variables are required' + ); + } + + const client = new HelpScoutClient({ appId, appSecret }); + + const server = new Server( + { + name: 'helpscout-server', + version: '1.0.0', + }, + { + capabilities: { + resources: {}, + tools: {}, + }, + } + ); + + // Register all tools + const allTools = [ + ...registerConversationTools(client), + ...registerCustomerTools(client), + ...registerMailboxTools(client), + ...registerUserTools(client), + ...registerTagTools(client), + ...registerWorkflowTools(client), + ...registerSavedReplyTools(client), + ...registerTeamTools(client), + ...registerWebhookTools(client), + ...registerReportingTools(client), + ]; + + // Register all MCP apps as resources + const allApps = [ + conversationDashboardApp, + conversationDetailApp, + conversationGridApp, + conversationTimelineApp, + customerGridApp, + customerDetailApp, + mailboxOverviewApp, + folderBrowserApp, + userStatsApp, + tagManagerApp, + workflowDashboardApp, + workflowDetailApp, + savedRepliesApp, + teamOverviewApp, + happinessReportApp, + productivityReportApp, + companyReportApp, + searchResultsApp, + ]; + + // Handle list_resources + server.setRequestHandler(ListResourcesRequestSchema, async () => { + return { + resources: allApps.map((app) => ({ + uri: `helpscout://app/${app.name}`, + name: app.name, + description: app.description, + mimeType: 'text/html', + })), + }; + }); + + // Handle read_resource + server.setRequestHandler(ReadResourceRequestSchema, async (request) => { + const uri = request.params.uri; + const appName = uri.replace('helpscout://app/', ''); + const app = allApps.find((a) => a.name === appName); + + if (!app) { + throw new Error(`App not found: ${appName}`); + } + + return { + contents: [ + { + uri, + mimeType: 'text/html', + text: app.content, + }, + ], + }; + }); + + // Handle list_tools + server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: allTools.map((tool) => ({ + name: tool.name, + description: tool.description, + inputSchema: tool.inputSchema, + })), + }; + }); + + // Handle call_tool + server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + const tool = allTools.find((t) => t.name === name); + + if (!tool) { + throw new Error(`Tool not found: ${name}`); + } + + try { + const result = await tool.handler(args || {}); + return { + content: [ + { + type: 'text', + text: JSON.stringify(result, null, 2), + }, + ], + }; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + return { + content: [ + { + type: 'text', + text: JSON.stringify({ error: errorMessage }, null, 2), + }, + ], + isError: true, + }; + } + }); + + const transport = new StdioServerTransport(); + await server.connect(transport); + + console.error('HelpScout MCP Server running on stdio'); + console.error(`Registered ${allTools.length} tools`); + console.error(`Registered ${allApps.length} apps`); +} diff --git a/servers/helpscout/src/tools/conversations-tools.ts b/servers/helpscout/src/tools/conversations-tools.ts new file mode 100644 index 0000000..5ca41b0 --- /dev/null +++ b/servers/helpscout/src/tools/conversations-tools.ts @@ -0,0 +1,308 @@ +import type { HelpScoutClient } from '../api/client.js'; +import type { Conversation, Thread } from '../types/index.js'; + +export function registerConversationTools(client: HelpScoutClient) { + return [ + { + name: 'helpscout_list_conversations', + description: 'List conversations with optional filters (mailbox, folder, status, tag, assignee, customer)', + inputSchema: { + type: 'object', + properties: { + mailbox: { type: 'number', description: 'Filter by mailbox ID' }, + folder: { type: 'number', description: 'Filter by folder ID' }, + status: { + type: 'string', + enum: ['active', 'pending', 'closed', 'spam'], + description: 'Filter by status', + }, + tag: { type: 'string', description: 'Filter by tag name' }, + assignedTo: { type: 'number', description: 'Filter by assigned user ID' }, + customerId: { type: 'number', description: 'Filter by customer ID' }, + query: { type: 'string', description: 'Search query' }, + page: { type: 'number', description: 'Page number (default: 1)' }, + sortField: { type: 'string', description: 'Field to sort by' }, + sortOrder: { type: 'string', enum: ['asc', 'desc'] }, + }, + }, + handler: async (args: any) => { + const conversations = await client.getAllPages( + '/conversations', + args, + 'conversations' + ); + return { conversations, count: conversations.length }; + }, + }, + { + name: 'helpscout_get_conversation', + description: 'Get a conversation by ID with full details', + inputSchema: { + type: 'object', + properties: { + id: { type: 'number', description: 'Conversation ID' }, + }, + required: ['id'], + }, + handler: async (args: { id: number }) => { + const conversation = await client.get(`/conversations/${args.id}`); + return conversation; + }, + }, + { + name: 'helpscout_create_conversation', + description: 'Create a new conversation (email, chat, or phone)', + inputSchema: { + type: 'object', + properties: { + subject: { type: 'string', description: 'Conversation subject' }, + type: { + type: 'string', + enum: ['email', 'chat', 'phone'], + description: 'Conversation type', + }, + mailboxId: { type: 'number', description: 'Mailbox ID' }, + status: { + type: 'string', + enum: ['active', 'pending', 'closed'], + description: 'Initial status', + }, + customerId: { type: 'number', description: 'Customer ID' }, + customerEmail: { type: 'string', description: 'Customer email (if no customerId)' }, + assignTo: { type: 'number', description: 'User ID to assign to' }, + tags: { + type: 'array', + items: { type: 'string' }, + description: 'Tags to apply', + }, + threads: { + type: 'array', + items: { type: 'object' }, + description: 'Initial threads', + }, + }, + required: ['subject', 'type', 'mailboxId'], + }, + handler: async (args: any) => { + const response = await client.post<{ id: number }>( + '/conversations', + args + ); + return response; + }, + }, + { + name: 'helpscout_update_conversation', + description: 'Update conversation properties (subject, status, assignee, mailbox, etc)', + inputSchema: { + type: 'object', + properties: { + id: { type: 'number', description: 'Conversation ID' }, + op: { + type: 'string', + enum: ['replace', 'remove'], + description: 'Operation type', + }, + path: { + type: 'string', + description: 'Property path (e.g., /subject, /status, /assignTo)', + }, + value: { description: 'New value for the property' }, + }, + required: ['id', 'op', 'path'], + }, + handler: async (args: any) => { + const { id, op, path, value } = args; + await client.patch(`/conversations/${id}`, { op, path, value }); + return { success: true, message: 'Conversation updated' }; + }, + }, + { + name: 'helpscout_delete_conversation', + description: 'Delete a conversation permanently', + inputSchema: { + type: 'object', + properties: { + id: { type: 'number', description: 'Conversation ID' }, + }, + required: ['id'], + }, + handler: async (args: { id: number }) => { + await client.delete(`/conversations/${args.id}`); + return { success: true, message: 'Conversation deleted' }; + }, + }, + { + name: 'helpscout_list_conversation_threads', + description: 'List all threads in a conversation', + inputSchema: { + type: 'object', + properties: { + conversationId: { type: 'number', description: 'Conversation ID' }, + }, + required: ['conversationId'], + }, + handler: async (args: { conversationId: number }) => { + const threads = await client.getAllPages( + `/conversations/${args.conversationId}/threads`, + {}, + 'threads' + ); + return { threads, count: threads.length }; + }, + }, + { + name: 'helpscout_create_conversation_reply', + description: 'Create a reply thread in a conversation', + inputSchema: { + type: 'object', + properties: { + conversationId: { type: 'number', description: 'Conversation ID' }, + text: { type: 'string', description: 'Reply text (HTML supported)' }, + type: { + type: 'string', + enum: ['message', 'reply'], + description: 'Thread type', + }, + status: { + type: 'string', + enum: ['active', 'pending', 'closed'], + description: 'Conversation status after reply', + }, + user: { type: 'number', description: 'User ID sending the reply' }, + attachments: { + type: 'array', + items: { type: 'object' }, + description: 'Attachments', + }, + imported: { type: 'boolean', description: 'Mark as imported (no notifications)' }, + }, + required: ['conversationId', 'text', 'type'], + }, + handler: async (args: any) => { + const { conversationId, ...threadData } = args; + const response = await client.post( + `/conversations/${conversationId}/threads`, + threadData + ); + return response; + }, + }, + { + name: 'helpscout_create_conversation_note', + description: 'Create a private note in a conversation', + inputSchema: { + type: 'object', + properties: { + conversationId: { type: 'number', description: 'Conversation ID' }, + text: { type: 'string', description: 'Note text (HTML supported)' }, + user: { type: 'number', description: 'User ID creating the note' }, + }, + required: ['conversationId', 'text'], + }, + handler: async (args: any) => { + const { conversationId, text, user } = args; + const response = await client.post( + `/conversations/${conversationId}/threads`, + { + text, + type: 'note', + user, + } + ); + return response; + }, + }, + { + name: 'helpscout_create_conversation_phone', + description: 'Create a phone thread in a conversation', + inputSchema: { + type: 'object', + properties: { + conversationId: { type: 'number', description: 'Conversation ID' }, + text: { type: 'string', description: 'Phone call notes' }, + user: { type: 'number', description: 'User ID' }, + phone: { type: 'string', description: 'Phone number' }, + }, + required: ['conversationId', 'text'], + }, + handler: async (args: any) => { + const { conversationId, ...threadData } = args; + const response = await client.post( + `/conversations/${conversationId}/threads`, + { + ...threadData, + type: 'phone', + } + ); + return response; + }, + }, + { + name: 'helpscout_update_conversation_tags', + description: 'Update tags on a conversation (add or remove)', + inputSchema: { + type: 'object', + properties: { + id: { type: 'number', description: 'Conversation ID' }, + tags: { + type: 'array', + items: { type: 'string' }, + description: 'Tag names to set', + }, + }, + required: ['id', 'tags'], + }, + handler: async (args: { id: number; tags: string[] }) => { + await client.put(`/conversations/${args.id}/tags`, { + tags: args.tags, + }); + return { success: true, message: 'Tags updated' }; + }, + }, + { + name: 'helpscout_change_conversation_status', + description: 'Change conversation status (active, pending, closed, spam)', + inputSchema: { + type: 'object', + properties: { + id: { type: 'number', description: 'Conversation ID' }, + status: { + type: 'string', + enum: ['active', 'pending', 'closed', 'spam'], + description: 'New status', + }, + }, + required: ['id', 'status'], + }, + handler: async (args: { id: number; status: string }) => { + await client.patch(`/conversations/${args.id}`, { + op: 'replace', + path: '/status', + value: args.status, + }); + return { success: true, message: `Status changed to ${args.status}` }; + }, + }, + { + name: 'helpscout_assign_conversation', + description: 'Assign a conversation to a user', + inputSchema: { + type: 'object', + properties: { + id: { type: 'number', description: 'Conversation ID' }, + userId: { type: 'number', description: 'User ID to assign to' }, + }, + required: ['id', 'userId'], + }, + handler: async (args: { id: number; userId: number }) => { + await client.patch(`/conversations/${args.id}`, { + op: 'replace', + path: '/assignTo', + value: args.userId, + }); + return { success: true, message: 'Conversation assigned' }; + }, + }, + ]; +} diff --git a/servers/helpscout/src/tools/customers-tools.ts b/servers/helpscout/src/tools/customers-tools.ts new file mode 100644 index 0000000..ae32f53 --- /dev/null +++ b/servers/helpscout/src/tools/customers-tools.ts @@ -0,0 +1,268 @@ +import type { HelpScoutClient } from '../api/client.js'; +import type { Customer, CustomerEmail, CustomerPhone, CustomerAddress } from '../types/index.js'; + +export function registerCustomerTools(client: HelpScoutClient) { + return [ + { + name: 'helpscout_list_customers', + description: 'List customers with optional filters (email, first name, last name, query)', + inputSchema: { + type: 'object', + properties: { + email: { type: 'string', description: 'Filter by email' }, + firstName: { type: 'string', description: 'Filter by first name' }, + lastName: { type: 'string', description: 'Filter by last name' }, + query: { type: 'string', description: 'Search query' }, + mailbox: { type: 'number', description: 'Filter by mailbox ID' }, + page: { type: 'number', description: 'Page number (default: 1)' }, + }, + }, + handler: async (args: any) => { + const customers = await client.getAllPages( + '/customers', + args, + 'customers' + ); + return { customers, count: customers.length }; + }, + }, + { + name: 'helpscout_get_customer', + description: 'Get a customer by ID with full details', + inputSchema: { + type: 'object', + properties: { + id: { type: 'number', description: 'Customer ID' }, + }, + required: ['id'], + }, + handler: async (args: { id: number }) => { + const customer = await client.get(`/customers/${args.id}`); + return customer; + }, + }, + { + name: 'helpscout_create_customer', + description: 'Create a new customer', + inputSchema: { + type: 'object', + properties: { + firstName: { type: 'string', description: 'First name' }, + lastName: { type: 'string', description: 'Last name' }, + email: { type: 'string', description: 'Primary email' }, + phone: { type: 'string', description: 'Primary phone' }, + organization: { type: 'string', description: 'Organization name' }, + jobTitle: { type: 'string', description: 'Job title' }, + photoUrl: { type: 'string', description: 'Photo URL' }, + background: { type: 'string', description: 'Background/notes' }, + location: { type: 'string', description: 'Location' }, + age: { type: 'string', description: 'Age' }, + gender: { type: 'string', description: 'Gender' }, + }, + required: ['firstName', 'lastName'], + }, + handler: async (args: any) => { + const response = await client.post<{ id: number }>( + '/customers', + args + ); + return response; + }, + }, + { + name: 'helpscout_update_customer', + description: 'Update customer properties', + inputSchema: { + type: 'object', + properties: { + id: { type: 'number', description: 'Customer ID' }, + op: { + type: 'string', + enum: ['replace', 'remove'], + description: 'Operation type', + }, + path: { + type: 'string', + description: 'Property path (e.g., /firstName, /email)', + }, + value: { description: 'New value' }, + }, + required: ['id', 'op', 'path'], + }, + handler: async (args: any) => { + const { id, op, path, value } = args; + await client.patch(`/customers/${id}`, { op, path, value }); + return { success: true, message: 'Customer updated' }; + }, + }, + { + name: 'helpscout_delete_customer', + description: 'Delete a customer permanently', + inputSchema: { + type: 'object', + properties: { + id: { type: 'number', description: 'Customer ID' }, + }, + required: ['id'], + }, + handler: async (args: { id: number }) => { + await client.delete(`/customers/${args.id}`); + return { success: true, message: 'Customer deleted' }; + }, + }, + { + name: 'helpscout_list_customer_emails', + description: 'List all emails for a customer', + inputSchema: { + type: 'object', + properties: { + customerId: { type: 'number', description: 'Customer ID' }, + }, + required: ['customerId'], + }, + handler: async (args: { customerId: number }) => { + const emails = await client.getAllPages( + `/customers/${args.customerId}/emails`, + {}, + 'emails' + ); + return { emails, count: emails.length }; + }, + }, + { + name: 'helpscout_create_customer_email', + description: 'Add an email address to a customer', + inputSchema: { + type: 'object', + properties: { + customerId: { type: 'number', description: 'Customer ID' }, + value: { type: 'string', description: 'Email address' }, + type: { + type: 'string', + enum: ['work', 'home', 'other'], + description: 'Email type', + }, + location: { type: 'string', description: 'Location label' }, + }, + required: ['customerId', 'value'], + }, + handler: async (args: any) => { + const { customerId, ...emailData } = args; + const response = await client.post( + `/customers/${customerId}/emails`, + emailData + ); + return response; + }, + }, + { + name: 'helpscout_list_customer_phones', + description: 'List all phone numbers for a customer', + inputSchema: { + type: 'object', + properties: { + customerId: { type: 'number', description: 'Customer ID' }, + }, + required: ['customerId'], + }, + handler: async (args: { customerId: number }) => { + const phones = await client.getAllPages( + `/customers/${args.customerId}/phones`, + {}, + 'phones' + ); + return { phones, count: phones.length }; + }, + }, + { + name: 'helpscout_create_customer_phone', + description: 'Add a phone number to a customer', + inputSchema: { + type: 'object', + properties: { + customerId: { type: 'number', description: 'Customer ID' }, + value: { type: 'string', description: 'Phone number' }, + type: { + type: 'string', + enum: ['work', 'home', 'mobile', 'fax', 'other'], + description: 'Phone type', + }, + location: { type: 'string', description: 'Location label' }, + }, + required: ['customerId', 'value'], + }, + handler: async (args: any) => { + const { customerId, ...phoneData } = args; + const response = await client.post( + `/customers/${customerId}/phones`, + phoneData + ); + return response; + }, + }, + { + name: 'helpscout_list_customer_addresses', + description: 'List all addresses for a customer', + inputSchema: { + type: 'object', + properties: { + customerId: { type: 'number', description: 'Customer ID' }, + }, + required: ['customerId'], + }, + handler: async (args: { customerId: number }) => { + const addresses = await client.getAllPages( + `/customers/${args.customerId}/addresses`, + {}, + 'addresses' + ); + return { addresses, count: addresses.length }; + }, + }, + { + name: 'helpscout_create_customer_address', + description: 'Add an address to a customer', + inputSchema: { + type: 'object', + properties: { + customerId: { type: 'number', description: 'Customer ID' }, + city: { type: 'string', description: 'City' }, + state: { type: 'string', description: 'State/Province' }, + postalCode: { type: 'string', description: 'Postal code' }, + country: { type: 'string', description: 'Country code (ISO 3166-1 alpha-2)' }, + lines: { + type: 'array', + items: { type: 'string' }, + description: 'Address lines', + }, + }, + required: ['customerId', 'city', 'country'], + }, + handler: async (args: any) => { + const { customerId, ...addressData } = args; + const response = await client.post( + `/customers/${customerId}/addresses`, + addressData + ); + return response; + }, + }, + { + name: 'helpscout_list_customer_properties', + description: 'List all custom properties for a customer', + inputSchema: { + type: 'object', + properties: { + customerId: { type: 'number', description: 'Customer ID' }, + }, + required: ['customerId'], + }, + handler: async (args: { customerId: number }) => { + const properties = await client.get( + `/customers/${args.customerId}/properties` + ); + return properties; + }, + }, + ]; +} diff --git a/servers/helpscout/src/tools/mailboxes-tools.ts b/servers/helpscout/src/tools/mailboxes-tools.ts new file mode 100644 index 0000000..0dd83fb --- /dev/null +++ b/servers/helpscout/src/tools/mailboxes-tools.ts @@ -0,0 +1,97 @@ +import type { HelpScoutClient } from '../api/client.js'; +import type { Mailbox, Folder, CustomField } from '../types/index.js'; + +export function registerMailboxTools(client: HelpScoutClient) { + return [ + { + name: 'helpscout_list_mailboxes', + description: 'List all mailboxes', + inputSchema: { + type: 'object', + properties: { + page: { type: 'number', description: 'Page number (default: 1)' }, + }, + }, + handler: async (args: any) => { + const mailboxes = await client.getAllPages( + '/mailboxes', + args, + 'mailboxes' + ); + return { mailboxes, count: mailboxes.length }; + }, + }, + { + name: 'helpscout_get_mailbox', + description: 'Get a mailbox by ID with full details', + inputSchema: { + type: 'object', + properties: { + id: { type: 'number', description: 'Mailbox ID' }, + }, + required: ['id'], + }, + handler: async (args: { id: number }) => { + const mailbox = await client.get(`/mailboxes/${args.id}`); + return mailbox; + }, + }, + { + name: 'helpscout_list_mailbox_folders', + description: 'List all folders in a mailbox', + inputSchema: { + type: 'object', + properties: { + mailboxId: { type: 'number', description: 'Mailbox ID' }, + page: { type: 'number', description: 'Page number (default: 1)' }, + }, + required: ['mailboxId'], + }, + handler: async (args: any) => { + const folders = await client.getAllPages( + `/mailboxes/${args.mailboxId}/folders`, + { page: args.page }, + 'folders' + ); + return { folders, count: folders.length }; + }, + }, + { + name: 'helpscout_get_mailbox_folder', + description: 'Get a specific folder by ID', + inputSchema: { + type: 'object', + properties: { + mailboxId: { type: 'number', description: 'Mailbox ID' }, + folderId: { type: 'number', description: 'Folder ID' }, + }, + required: ['mailboxId', 'folderId'], + }, + handler: async (args: { mailboxId: number; folderId: number }) => { + const folder = await client.get( + `/mailboxes/${args.mailboxId}/folders/${args.folderId}` + ); + return folder; + }, + }, + { + name: 'helpscout_list_mailbox_fields', + description: 'List custom fields for a mailbox', + inputSchema: { + type: 'object', + properties: { + mailboxId: { type: 'number', description: 'Mailbox ID' }, + }, + required: ['mailboxId'], + }, + handler: async (args: { mailboxId: number }) => { + const fields = await client.getAllPages( + `/mailboxes/${args.mailboxId}/fields`, + {}, + 'fields' + ); + return { fields, count: fields.length }; + }, + }, + ]; +} diff --git a/servers/helpscout/src/tools/reporting-tools.ts b/servers/helpscout/src/tools/reporting-tools.ts new file mode 100644 index 0000000..b7d6e76 --- /dev/null +++ b/servers/helpscout/src/tools/reporting-tools.ts @@ -0,0 +1,244 @@ +import type { HelpScoutClient } from '../api/client.js'; +import type { + CompanyReport, + ConversationReport, + HappinessReport, + ProductivityReport, + UserReport, +} from '../types/index.js'; + +export function registerReportingTools(client: HelpScoutClient) { + return [ + { + name: 'helpscout_get_company_report', + description: 'Get company overview report (conversations, customers, happiness, response times)', + inputSchema: { + type: 'object', + properties: { + start: { + type: 'string', + description: 'Start date (YYYY-MM-DD)', + }, + end: { + type: 'string', + description: 'End date (YYYY-MM-DD)', + }, + previousStart: { + type: 'string', + description: 'Previous period start (for comparison)', + }, + previousEnd: { + type: 'string', + description: 'Previous period end (for comparison)', + }, + mailboxes: { + type: 'array', + items: { type: 'number' }, + description: 'Filter by mailbox IDs', + }, + tags: { + type: 'array', + items: { type: 'string' }, + description: 'Filter by tags', + }, + }, + required: ['start', 'end'], + }, + handler: async (args: any) => { + const report = await client.get( + '/reports/company', + args + ); + return report; + }, + }, + { + name: 'helpscout_get_conversations_report', + description: 'Get conversations report (volume, trends, busiest times)', + inputSchema: { + type: 'object', + properties: { + start: { + type: 'string', + description: 'Start date (YYYY-MM-DD)', + }, + end: { + type: 'string', + description: 'End date (YYYY-MM-DD)', + }, + previousStart: { + type: 'string', + description: 'Previous period start (for comparison)', + }, + previousEnd: { + type: 'string', + description: 'Previous period end (for comparison)', + }, + mailboxes: { + type: 'array', + items: { type: 'number' }, + description: 'Filter by mailbox IDs', + }, + tags: { + type: 'array', + items: { type: 'string' }, + description: 'Filter by tags', + }, + folders: { + type: 'array', + items: { type: 'number' }, + description: 'Filter by folder IDs', + }, + }, + required: ['start', 'end'], + }, + handler: async (args: any) => { + const report = await client.get( + '/reports/conversations', + args + ); + return report; + }, + }, + { + name: 'helpscout_get_happiness_report', + description: 'Get happiness report (ratings, scores, sentiment)', + inputSchema: { + type: 'object', + properties: { + start: { + type: 'string', + description: 'Start date (YYYY-MM-DD)', + }, + end: { + type: 'string', + description: 'End date (YYYY-MM-DD)', + }, + previousStart: { + type: 'string', + description: 'Previous period start (for comparison)', + }, + previousEnd: { + type: 'string', + description: 'Previous period end (for comparison)', + }, + mailboxes: { + type: 'array', + items: { type: 'number' }, + description: 'Filter by mailbox IDs', + }, + tags: { + type: 'array', + items: { type: 'string' }, + description: 'Filter by tags', + }, + }, + required: ['start', 'end'], + }, + handler: async (args: any) => { + const report = await client.get( + '/reports/happiness', + args + ); + return report; + }, + }, + { + name: 'helpscout_get_productivity_report', + description: 'Get productivity report (replies sent, resolution time, response time)', + inputSchema: { + type: 'object', + properties: { + start: { + type: 'string', + description: 'Start date (YYYY-MM-DD)', + }, + end: { + type: 'string', + description: 'End date (YYYY-MM-DD)', + }, + previousStart: { + type: 'string', + description: 'Previous period start (for comparison)', + }, + previousEnd: { + type: 'string', + description: 'Previous period end (for comparison)', + }, + mailboxes: { + type: 'array', + items: { type: 'number' }, + description: 'Filter by mailbox IDs', + }, + tags: { + type: 'array', + items: { type: 'string' }, + description: 'Filter by tags', + }, + }, + required: ['start', 'end'], + }, + handler: async (args: any) => { + const report = await client.get( + '/reports/productivity', + args + ); + return report; + }, + }, + { + name: 'helpscout_get_user_report', + description: 'Get report for a specific user (performance, activity)', + inputSchema: { + type: 'object', + properties: { + userId: { + type: 'number', + description: 'User ID to report on', + }, + start: { + type: 'string', + description: 'Start date (YYYY-MM-DD)', + }, + end: { + type: 'string', + description: 'End date (YYYY-MM-DD)', + }, + previousStart: { + type: 'string', + description: 'Previous period start (for comparison)', + }, + previousEnd: { + type: 'string', + description: 'Previous period end (for comparison)', + }, + mailboxes: { + type: 'array', + items: { type: 'number' }, + description: 'Filter by mailbox IDs', + }, + tags: { + type: 'array', + items: { type: 'string' }, + description: 'Filter by tags', + }, + }, + required: ['userId', 'start', 'end'], + }, + handler: async (args: any) => { + const report = await client.get( + `/reports/user/${args.userId}`, + { + start: args.start, + end: args.end, + previousStart: args.previousStart, + previousEnd: args.previousEnd, + mailboxes: args.mailboxes, + tags: args.tags, + } + ); + return report; + }, + }, + ]; +} diff --git a/servers/helpscout/src/tools/saved-replies-tools.ts b/servers/helpscout/src/tools/saved-replies-tools.ts new file mode 100644 index 0000000..1e72cc0 --- /dev/null +++ b/servers/helpscout/src/tools/saved-replies-tools.ts @@ -0,0 +1,102 @@ +import type { HelpScoutClient } from '../api/client.js'; +import type { SavedReply } from '../types/index.js'; + +export function registerSavedReplyTools(client: HelpScoutClient) { + return [ + { + name: 'helpscout_list_saved_replies', + description: 'List saved replies with optional filters', + inputSchema: { + type: 'object', + properties: { + mailboxId: { type: 'number', description: 'Filter by mailbox ID' }, + userId: { type: 'number', description: 'Filter by user ID' }, + page: { type: 'number', description: 'Page number (default: 1)' }, + }, + }, + handler: async (args: any) => { + const replies = await client.getAllPages( + '/saved-replies', + args, + 'replies' + ); + return { replies, count: replies.length }; + }, + }, + { + name: 'helpscout_get_saved_reply', + description: 'Get a saved reply by ID', + inputSchema: { + type: 'object', + properties: { + id: { type: 'number', description: 'Saved reply ID' }, + }, + required: ['id'], + }, + handler: async (args: { id: number }) => { + const reply = await client.get(`/saved-replies/${args.id}`); + return reply; + }, + }, + { + name: 'helpscout_create_saved_reply', + description: 'Create a new saved reply', + inputSchema: { + type: 'object', + properties: { + name: { type: 'string', description: 'Reply name/title' }, + text: { type: 'string', description: 'Reply text (HTML supported)' }, + mailboxId: { + type: 'number', + description: 'Mailbox ID (for mailbox-specific reply)', + }, + userId: { + type: 'number', + description: 'User ID (for user-specific reply)', + }, + }, + required: ['name', 'text'], + }, + handler: async (args: any) => { + const response = await client.post<{ id: number }>( + '/saved-replies', + args + ); + return response; + }, + }, + { + name: 'helpscout_update_saved_reply', + description: 'Update a saved reply', + inputSchema: { + type: 'object', + properties: { + id: { type: 'number', description: 'Saved reply ID' }, + name: { type: 'string', description: 'New name' }, + text: { type: 'string', description: 'New text' }, + }, + required: ['id'], + }, + handler: async (args: any) => { + const { id, ...updates } = args; + await client.put(`/saved-replies/${id}`, updates); + return { success: true, message: 'Saved reply updated' }; + }, + }, + { + name: 'helpscout_delete_saved_reply', + description: 'Delete a saved reply', + inputSchema: { + type: 'object', + properties: { + id: { type: 'number', description: 'Saved reply ID' }, + }, + required: ['id'], + }, + handler: async (args: { id: number }) => { + await client.delete(`/saved-replies/${args.id}`); + return { success: true, message: 'Saved reply deleted' }; + }, + }, + ]; +} diff --git a/servers/helpscout/src/tools/tags-tools.ts b/servers/helpscout/src/tools/tags-tools.ts new file mode 100644 index 0000000..7a1b7d5 --- /dev/null +++ b/servers/helpscout/src/tools/tags-tools.ts @@ -0,0 +1,77 @@ +import type { HelpScoutClient } from '../api/client.js'; +import type { Tag } from '../types/index.js'; + +export function registerTagTools(client: HelpScoutClient) { + return [ + { + name: 'helpscout_list_tags', + description: 'List all tags in the account', + inputSchema: { + type: 'object', + properties: { + page: { type: 'number', description: 'Page number (default: 1)' }, + }, + }, + handler: async (args: any) => { + const tags = await client.getAllPages( + '/tags', + args, + 'tags' + ); + return { tags, count: tags.length }; + }, + }, + { + name: 'helpscout_create_tag', + description: 'Create a new tag', + inputSchema: { + type: 'object', + properties: { + name: { type: 'string', description: 'Tag name' }, + color: { + type: 'string', + description: 'Tag color (hex code, e.g., #FF5733)', + }, + }, + required: ['name'], + }, + handler: async (args: { name: string; color?: string }) => { + const response = await client.post<{ id: number }>('/tags', args); + return response; + }, + }, + { + name: 'helpscout_update_tag', + description: 'Update a tag (rename or change color)', + inputSchema: { + type: 'object', + properties: { + id: { type: 'number', description: 'Tag ID' }, + name: { type: 'string', description: 'New tag name' }, + color: { type: 'string', description: 'New tag color (hex code)' }, + }, + required: ['id'], + }, + handler: async (args: any) => { + const { id, ...updates } = args; + await client.put(`/tags/${id}`, updates); + return { success: true, message: 'Tag updated' }; + }, + }, + { + name: 'helpscout_delete_tag', + description: 'Delete a tag', + inputSchema: { + type: 'object', + properties: { + id: { type: 'number', description: 'Tag ID' }, + }, + required: ['id'], + }, + handler: async (args: { id: number }) => { + await client.delete(`/tags/${args.id}`); + return { success: true, message: 'Tag deleted' }; + }, + }, + ]; +} diff --git a/servers/helpscout/src/tools/teams-tools.ts b/servers/helpscout/src/tools/teams-tools.ts new file mode 100644 index 0000000..b10ab06 --- /dev/null +++ b/servers/helpscout/src/tools/teams-tools.ts @@ -0,0 +1,60 @@ +import type { HelpScoutClient } from '../api/client.js'; +import type { Team, User } from '../types/index.js'; + +export function registerTeamTools(client: HelpScoutClient) { + return [ + { + name: 'helpscout_list_teams', + description: 'List all teams', + inputSchema: { + type: 'object', + properties: { + page: { type: 'number', description: 'Page number (default: 1)' }, + }, + }, + handler: async (args: any) => { + const teams = await client.getAllPages( + '/teams', + args, + 'teams' + ); + return { teams, count: teams.length }; + }, + }, + { + name: 'helpscout_get_team', + description: 'Get a team by ID with full details', + inputSchema: { + type: 'object', + properties: { + id: { type: 'number', description: 'Team ID' }, + }, + required: ['id'], + }, + handler: async (args: { id: number }) => { + const team = await client.get(`/teams/${args.id}`); + return team; + }, + }, + { + name: 'helpscout_list_team_members', + description: 'List all members of a team', + inputSchema: { + type: 'object', + properties: { + teamId: { type: 'number', description: 'Team ID' }, + page: { type: 'number', description: 'Page number (default: 1)' }, + }, + required: ['teamId'], + }, + handler: async (args: any) => { + const members = await client.getAllPages( + `/teams/${args.teamId}/members`, + { page: args.page }, + 'members' + ); + return { members, count: members.length }; + }, + }, + ]; +} diff --git a/servers/helpscout/src/tools/users-tools.ts b/servers/helpscout/src/tools/users-tools.ts new file mode 100644 index 0000000..a392481 --- /dev/null +++ b/servers/helpscout/src/tools/users-tools.ts @@ -0,0 +1,53 @@ +import type { HelpScoutClient } from '../api/client.js'; +import type { User } from '../types/index.js'; + +export function registerUserTools(client: HelpScoutClient) { + return [ + { + name: 'helpscout_list_users', + description: 'List all users in the account', + inputSchema: { + type: 'object', + properties: { + page: { type: 'number', description: 'Page number (default: 1)' }, + email: { type: 'string', description: 'Filter by email' }, + }, + }, + handler: async (args: any) => { + const users = await client.getAllPages( + '/users', + args, + 'users' + ); + return { users, count: users.length }; + }, + }, + { + name: 'helpscout_get_user', + description: 'Get a user by ID with full details', + inputSchema: { + type: 'object', + properties: { + id: { type: 'number', description: 'User ID' }, + }, + required: ['id'], + }, + handler: async (args: { id: number }) => { + const user = await client.get(`/users/${args.id}`); + return user; + }, + }, + { + name: 'helpscout_get_current_user', + description: 'Get the resource owner (current authenticated user)', + inputSchema: { + type: 'object', + properties: {}, + }, + handler: async () => { + const user = await client.get('/users/me'); + return user; + }, + }, + ]; +} diff --git a/servers/helpscout/src/tools/webhooks-tools.ts b/servers/helpscout/src/tools/webhooks-tools.ts new file mode 100644 index 0000000..f4964dc --- /dev/null +++ b/servers/helpscout/src/tools/webhooks-tools.ts @@ -0,0 +1,109 @@ +import type { HelpScoutClient } from '../api/client.js'; +import type { Webhook } from '../types/index.js'; + +export function registerWebhookTools(client: HelpScoutClient) { + return [ + { + name: 'helpscout_list_webhooks', + description: 'List all webhooks', + inputSchema: { + type: 'object', + properties: { + page: { type: 'number', description: 'Page number (default: 1)' }, + }, + }, + handler: async (args: any) => { + const webhooks = await client.getAllPages( + '/webhooks', + args, + 'webhooks' + ); + return { webhooks, count: webhooks.length }; + }, + }, + { + name: 'helpscout_get_webhook', + description: 'Get a webhook by ID', + inputSchema: { + type: 'object', + properties: { + id: { type: 'string', description: 'Webhook ID' }, + }, + required: ['id'], + }, + handler: async (args: { id: string }) => { + const webhook = await client.get(`/webhooks/${args.id}`); + return webhook; + }, + }, + { + name: 'helpscout_create_webhook', + description: 'Create a new webhook', + inputSchema: { + type: 'object', + properties: { + url: { type: 'string', description: 'Webhook URL' }, + events: { + type: 'array', + items: { type: 'string' }, + description: 'Events to subscribe to (e.g., conversation.created)', + }, + secret: { + type: 'string', + description: 'Webhook secret for signature verification', + }, + }, + required: ['url', 'events'], + }, + handler: async (args: any) => { + const response = await client.post<{ id: string }>( + '/webhooks', + args + ); + return response; + }, + }, + { + name: 'helpscout_update_webhook', + description: 'Update a webhook', + inputSchema: { + type: 'object', + properties: { + id: { type: 'string', description: 'Webhook ID' }, + url: { type: 'string', description: 'New webhook URL' }, + events: { + type: 'array', + items: { type: 'string' }, + description: 'New events list', + }, + state: { + type: 'string', + enum: ['enabled', 'disabled'], + description: 'Webhook state', + }, + }, + required: ['id'], + }, + handler: async (args: any) => { + const { id, ...updates } = args; + await client.put(`/webhooks/${id}`, updates); + return { success: true, message: 'Webhook updated' }; + }, + }, + { + name: 'helpscout_delete_webhook', + description: 'Delete a webhook', + inputSchema: { + type: 'object', + properties: { + id: { type: 'string', description: 'Webhook ID' }, + }, + required: ['id'], + }, + handler: async (args: { id: string }) => { + await client.delete(`/webhooks/${args.id}`); + return { success: true, message: 'Webhook deleted' }; + }, + }, + ]; +} diff --git a/servers/helpscout/src/tools/workflows-tools.ts b/servers/helpscout/src/tools/workflows-tools.ts new file mode 100644 index 0000000..955b578 --- /dev/null +++ b/servers/helpscout/src/tools/workflows-tools.ts @@ -0,0 +1,96 @@ +import type { HelpScoutClient } from '../api/client.js'; +import type { Workflow, WorkflowStats } from '../types/index.js'; + +export function registerWorkflowTools(client: HelpScoutClient) { + return [ + { + name: 'helpscout_list_workflows', + description: 'List all workflows with optional filters', + inputSchema: { + type: 'object', + properties: { + mailboxId: { type: 'number', description: 'Filter by mailbox ID' }, + page: { type: 'number', description: 'Page number (default: 1)' }, + }, + }, + handler: async (args: any) => { + const workflows = await client.getAllPages( + '/workflows', + args, + 'workflows' + ); + return { workflows, count: workflows.length }; + }, + }, + { + name: 'helpscout_get_workflow', + description: 'Get a workflow by ID with full details', + inputSchema: { + type: 'object', + properties: { + id: { type: 'number', description: 'Workflow ID' }, + }, + required: ['id'], + }, + handler: async (args: { id: number }) => { + const workflow = await client.get(`/workflows/${args.id}`); + return workflow; + }, + }, + { + name: 'helpscout_activate_workflow', + description: 'Activate a workflow', + inputSchema: { + type: 'object', + properties: { + id: { type: 'number', description: 'Workflow ID' }, + }, + required: ['id'], + }, + handler: async (args: { id: number }) => { + await client.patch(`/workflows/${args.id}`, { + op: 'replace', + path: '/status', + value: 'active', + }); + return { success: true, message: 'Workflow activated' }; + }, + }, + { + name: 'helpscout_deactivate_workflow', + description: 'Deactivate a workflow', + inputSchema: { + type: 'object', + properties: { + id: { type: 'number', description: 'Workflow ID' }, + }, + required: ['id'], + }, + handler: async (args: { id: number }) => { + await client.patch(`/workflows/${args.id}`, { + op: 'replace', + path: '/status', + value: 'inactive', + }); + return { success: true, message: 'Workflow deactivated' }; + }, + }, + { + name: 'helpscout_get_workflow_stats', + description: 'Get statistics for a workflow (run counts)', + inputSchema: { + type: 'object', + properties: { + id: { type: 'number', description: 'Workflow ID' }, + }, + required: ['id'], + }, + handler: async (args: { id: number }) => { + const stats = await client.get( + `/workflows/${args.id}/stats` + ); + return stats; + }, + }, + ]; +} diff --git a/servers/helpscout/src/types/index.ts b/servers/helpscout/src/types/index.ts new file mode 100644 index 0000000..58e7d11 --- /dev/null +++ b/servers/helpscout/src/types/index.ts @@ -0,0 +1,320 @@ +// HelpScout API Types + +export interface HelpScoutConfig { + appId: string; + appSecret: string; + accessToken?: string; + tokenExpiry?: number; +} + +export interface AccessTokenResponse { + access_token: string; + expires_in: number; + token_type: string; +} + +export interface PaginatedResponse { + _embedded?: { [key: string]: T[] }; + _links?: { + first?: { href: string }; + last?: { href: string }; + next?: { href: string }; + prev?: { href: string }; + self?: { href: string }; + }; + page?: { + number: number; + size: number; + totalElements: number; + totalPages: number; + }; +} + +export interface Conversation { + id: number; + number: number; + threads: number; + type: 'email' | 'chat' | 'phone'; + folderId: number; + status: 'active' | 'pending' | 'closed' | 'spam'; + state: 'draft' | 'published' | 'deleted'; + subject: string; + preview: string; + mailboxId: number; + assignee?: User; + createdBy: User | Customer; + createdAt: string; + closedAt?: string; + closedBy?: User; + updatedAt?: string; + userUpdatedAt?: string; + customerWaitingSince?: CustomerWaitingSince; + source?: ConversationSource; + tags?: Tag[]; + cc?: string[]; + bcc?: string[]; + primaryCustomer: Customer; + customFields?: CustomField[]; +} + +export interface Thread { + id: number; + assignedTo?: User; + status: 'active' | 'nochange' | 'pending' | 'closed'; + createdAt: string; + createdBy: User | Customer; + source?: ThreadSource; + type: 'note' | 'message' | 'reply' | 'customer' | 'lineitem' | 'forwardparent' | 'forwardchild' | 'phone'; + state: 'draft' | 'published' | 'deleted' | 'hidden'; + customer?: Customer; + fromMailbox?: Mailbox; + body?: string; + to?: string[]; + cc?: string[]; + bcc?: string[]; + attachments?: Attachment[]; + savedReplyId?: number; + openedAt?: string; +} + +export interface Customer { + id: number; + firstName: string; + lastName: string; + email: string; + phone?: string; + photoUrl?: string; + photoType?: string; + gender?: string; + age?: string; + organization?: string; + jobTitle?: string; + location?: string; + background?: string; + address?: CustomerAddress; + socialProfiles?: SocialProfile[]; + chats?: Chat[]; + websites?: Website[]; + emails?: CustomerEmail[]; + phones?: CustomerPhone[]; + createdAt: string; + updatedAt: string; +} + +export interface CustomerAddress { + id: number; + city: string; + state: string; + postalCode: string; + country: string; + lines: string[]; + createdAt: string; +} + +export interface CustomerEmail { + id: number; + value: string; + type: string; + location: string; +} + +export interface CustomerPhone { + id: number; + value: string; + type: string; + location: string; +} + +export interface SocialProfile { + type: string; + value: string; +} + +export interface Chat { + id: number; + type: string; + value: string; +} + +export interface Website { + id: number; + value: string; +} + +export interface Mailbox { + id: number; + name: string; + slug: string; + email: string; + createdAt: string; + updatedAt: string; +} + +export interface Folder { + id: number; + name: string; + type: 'mine' | 'drafts' | 'assigned' | 'closed' | 'spam' | 'open'; + userId: number; + totalCount: number; + activeCount: number; + updatedAt: string; +} + +export interface User { + id: number; + firstName: string; + lastName: string; + email: string; + role: string; + timezone: string; + photoUrl?: string; + createdAt: string; + updatedAt: string; + type?: 'user' | 'team'; +} + +export interface Tag { + id?: number; + name?: string; + color?: string; + createdAt?: string; + updatedAt?: string; + ticketCount?: number; +} + +export interface Workflow { + id: number; + mailboxId: number; + type: 'manual' | 'automatic'; + status: 'active' | 'inactive' | 'invalid'; + order: number; + name: string; + createdAt: string; + modifiedAt: string; +} + +export interface WorkflowStats { + totalRuns: number; + successfulRuns: number; + failedRuns: number; +} + +export interface SavedReply { + id: number; + name: string; + text: string; + mailboxId?: number; + userId?: number; + createdAt: string; + updatedAt: string; +} + +export interface Team { + id: number; + name: string; + createdAt: string; + updatedAt: string; + memberCount?: number; +} + +export interface Webhook { + id: string; + url: string; + secret: string; + events: string[]; + state: 'enabled' | 'disabled'; + createdAt: string; +} + +export interface CustomField { + id: number; + name: string; + value: any; + type: 'TEXT' | 'NUMBER' | 'DATE' | 'DROPDOWN'; +} + +export interface ConversationSource { + type: string; + via: string; +} + +export interface ThreadSource { + type: string; + via: string; +} + +export interface CustomerWaitingSince { + time: string; + friendly: string; + latestReplyFrom: 'customer' | 'user'; +} + +export interface Attachment { + id: number; + mimeType: string; + filename: string; + size: number; + width?: number; + height?: number; + url: string; +} + +export interface Report { + filterTags?: any; + current?: any; + previous?: any; + deltas?: any; +} + +export interface CompanyReport extends Report { + current?: { + conversationsCreated: number; + conversationsClosed: number; + customersHelped: number; + happinessScore: number; + repliesSent: number; + resolutionTime: number; + firstResponseTime: number; + }; +} + +export interface ConversationReport extends Report { + current?: { + newConversations: number; + totalConversations: number; + conversationsBusiest: any; + conversationsVolumeByDay: any[]; + }; +} + +export interface HappinessReport extends Report { + current?: { + happinessScore: number; + ratingsCount: number; + greatCount: number; + okayCount: number; + notGoodCount: number; + }; +} + +export interface ProductivityReport extends Report { + current?: { + repliesSent: number; + resolutionTime: number; + firstResponseTime: number; + responseTime: number; + resolvedConversations: number; + }; +} + +export interface UserReport extends Report { + current?: { + user: User; + repliesSent: number; + conversationsCreated: number; + conversationsClosed: number; + happinessScore: number; + resolutionTime: number; + firstResponseTime: number; + }; +} diff --git a/servers/helpscout/tsconfig.json b/servers/helpscout/tsconfig.json index de6431e..156b6d5 100644 --- a/servers/helpscout/tsconfig.json +++ b/servers/helpscout/tsconfig.json @@ -1,14 +1,19 @@ { "compilerOptions": { "target": "ES2022", - "module": "NodeNext", - "moduleResolution": "NodeNext", + "module": "Node16", + "moduleResolution": "Node16", + "lib": ["ES2022"], "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, - "declaration": true + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"]