Sync from mcpengine monorepo — Factory V2 complete (tools + apps)

This commit is contained in:
Jake Shore 2026-02-13 01:55:49 -05:00
parent 9ceb620f2a
commit 1533030472
38 changed files with 4500 additions and 637 deletions

3
.env.example Normal file
View File

@ -0,0 +1,3 @@
HELPSCOUT_APP_ID=your_app_id_here
HELPSCOUT_APP_SECRET=your_app_secret_here
HELPSCOUT_ACCESS_TOKEN=your_oauth2_access_token_here

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
node_modules/
dist/
.env
*.log
.DS_Store

239
README.md Normal file
View File

@ -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)

View File

@ -1,637 +0,0 @@
<!DOCTYPE html>
<html lang="en" class="scroll-smooth">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Help Scout Connect — AI-Power Your Support in 2 Clicks</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
colors: {
brand: {
50: '#eff6ff',
100: '#dbeafe',
200: '#bfdbfe',
300: '#93c5fd',
400: '#60a5fa',
500: '#1292EE',
600: '#0f7cd6',
700: '#0c66be',
}
}
}
}
}
</script>
<style>
.gradient-text {
background: linear-gradient(135deg, #1292EE 0%, #60a5fa 50%, #a78bfa 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero-glow {
background: radial-gradient(ellipse 80% 50% at 50% -20%, rgba(18, 146, 238, 0.2), transparent);
}
.card-glow {
transition: all 0.3s ease;
}
.card-glow:hover {
box-shadow: 0 0 50px rgba(18, 146, 238, 0.2);
transform: translateY(-4px);
border-color: rgba(18, 146, 238, 0.4);
}
.animate-float {
animation: float 6s ease-in-out infinite;
}
.animate-float-delayed {
animation: float 6s ease-in-out infinite;
animation-delay: -3s;
}
.animate-pulse-glow {
animation: pulseGlow 3s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-20px); }
}
@keyframes pulseGlow {
0%, 100% { box-shadow: 0 0 20px rgba(18, 146, 238, 0.3); }
50% { box-shadow: 0 0 40px rgba(18, 146, 238, 0.5); }
}
@keyframes wiggle {
0%, 100% { transform: rotate(-2deg); }
50% { transform: rotate(2deg); }
}
@keyframes glow-pulse {
0%, 100% { box-shadow: 0 0 20px 0 rgba(18, 146, 238, 0.4), 0 0 40px 0 rgba(18, 146, 238, 0.2); }
50% { box-shadow: 0 0 30px 5px rgba(18, 146, 238, 0.6), 0 0 60px 10px rgba(18, 146, 238, 0.3); }
}
.sticky-btn {
animation: wiggle 2.5s ease-in-out infinite, glow-pulse 2s ease-in-out infinite;
}
.sticky-btn:hover {
animation: none;
}
.video-glow {
box-shadow: 0 0 80px rgba(18, 146, 238, 0.3), 0 25px 80px -12px rgba(0, 0, 0, 0.8);
}
.gradient-border {
background: linear-gradient(135deg, rgba(18, 146, 238, 0.5), rgba(167, 139, 250, 0.2));
padding: 1px;
border-radius: 1rem;
}
.gradient-border-inner {
background: rgb(24 24 27);
border-radius: calc(1rem - 1px);
}
.feature-icon {
transition: all 0.3s ease;
}
.card-glow:hover .feature-icon {
transform: scale(1.1);
}
@keyframes typing {
from { width: 0 }
to { width: 100% }
}
.typing-effect {
overflow: hidden;
white-space: nowrap;
animation: typing 2s steps(30, end);
}
</style>
</head>
<body class="bg-zinc-950 text-zinc-100 font-sans antialiased">
<!-- Nav -->
<nav class="fixed top-0 w-full z-50 border-b border-zinc-800/50 bg-zinc-950/80 backdrop-blur-xl">
<div class="max-w-6xl mx-auto px-6 py-4 flex items-center justify-between">
<div class="flex items-center gap-2">
<div class="w-8 h-8 rounded-lg bg-gradient-to-br from-brand-500 to-blue-400 flex items-center justify-center">
<i data-lucide="life-buoy" class="w-5 h-5 text-white"></i>
</div>
<span class="font-bold text-xl">Help Scout Connect</span>
</div>
<div class="hidden md:flex items-center gap-8">
<a href="#features" class="text-zinc-400 hover:text-white transition">Features</a>
<a href="#pricing" class="text-zinc-400 hover:text-white transition">Waitlist</a>
<a href="#faq" class="text-zinc-400 hover:text-white transition">FAQ</a>
</div>
<div class="flex items-center gap-4">
<a href="#" class="text-zinc-400 hover:text-white transition hidden sm:block">Sign In</a>
<a href="#pricing" class="px-4 py-2 bg-brand-500 hover:bg-brand-600 rounded-lg font-medium transition transform hover:scale-105">
Join Waitlist
</a>
</div>
</div>
</nav>
<!-- Hero -->
<section class="relative min-h-screen flex items-center hero-glow pt-20 overflow-hidden">
<!-- Floating background elements -->
<div class="absolute top-40 left-10 w-72 h-72 bg-brand-500/10 rounded-full blur-3xl animate-float"></div>
<div class="absolute bottom-20 right-10 w-96 h-96 bg-purple-500/10 rounded-full blur-3xl animate-float-delayed"></div>
<div class="max-w-6xl mx-auto px-6 py-20">
<div class="text-center relative z-10">
<div class="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-zinc-800/50 border border-zinc-700/50 text-sm text-zinc-300 mb-8 backdrop-blur-sm">
<span class="w-2 h-2 rounded-full bg-blue-400 animate-pulse"></span>
Open Source + Hosted
</div>
<h1 class="text-5xl md:text-6xl lg:text-7xl font-extrabold tracking-tight mb-6 leading-[1.1]">
Connect <span class="gradient-text">Help Scout</span><br>
to AI in 2 Clicks
</h1>
<p class="text-xl md:text-2xl text-zinc-400 max-w-2xl mx-auto mb-10">
The complete Help Scout MCP server. <strong class="text-white">54 tools</strong> for conversations, docs, and workflows.
No setup. No OAuth headaches. Just connect and support.
</p>
<div class="flex flex-col sm:flex-row gap-4 justify-center mb-12">
<a href="#pricing" class="px-8 py-4 bg-brand-500 hover:bg-brand-600 rounded-xl font-semibold text-lg transition transform hover:scale-105 text-center shadow-lg shadow-brand-500/25">
Join the Waitlist
</a>
<a href="#demo" class="px-8 py-4 bg-zinc-800/80 hover:bg-zinc-700 border border-zinc-700 rounded-xl font-semibold text-lg transition flex items-center justify-center gap-2">
<i data-lucide="play" class="w-5 h-5"></i>
Watch Demo
</a>
</div>
<!-- Social Proof -->
<div class="flex items-center gap-4 justify-center">
<div class="flex -space-x-3">
<img src="https://i.pravatar.cc/100?img=21" class="w-10 h-10 rounded-full border-2 border-zinc-950" alt="">
<img src="https://i.pravatar.cc/100?img=22" class="w-10 h-10 rounded-full border-2 border-zinc-950" alt="">
<img src="https://i.pravatar.cc/100?img=23" class="w-10 h-10 rounded-full border-2 border-zinc-950" alt="">
<img src="https://i.pravatar.cc/100?img=24" class="w-10 h-10 rounded-full border-2 border-zinc-950" alt="">
<div class="w-10 h-10 rounded-full border-2 border-zinc-950 bg-brand-500 flex items-center justify-center text-xs font-bold">+50</div>
</div>
<p class="text-zinc-400">
Trusted by <strong class="text-white">200+</strong> support teams
</p>
</div>
</div>
</div>
</section>
<!-- Video Demo -->
<section id="demo" class="py-20 border-t border-zinc-800/50">
<div class="max-w-5xl mx-auto px-6">
<div class="text-center mb-12">
<h2 class="text-3xl md:text-4xl font-bold mb-4">See It In Action</h2>
<p class="text-xl text-zinc-400">Watch how AI transforms your support workflow</p>
</div>
<div class="gradient-border">
<div class="gradient-border-inner p-2">
<div class="rounded-xl overflow-hidden video-glow">
<video autoplay loop muted playsinline class="w-full">
<source src="output/helpscout.mp4" type="video/mp4">
</video>
</div>
</div>
</div>
<div class="flex justify-center mt-8">
<div class="inline-flex items-center gap-6 px-6 py-3 rounded-full bg-zinc-900/80 border border-zinc-800 backdrop-blur-sm">
<div class="flex items-center gap-2">
<i data-lucide="message-circle" class="w-4 h-4 text-brand-500"></i>
<span class="text-sm text-zinc-300">Conversations</span>
</div>
<div class="w-px h-4 bg-zinc-700"></div>
<div class="flex items-center gap-2">
<i data-lucide="book-open" class="w-4 h-4 text-brand-500"></i>
<span class="text-sm text-zinc-300">Docs</span>
</div>
<div class="w-px h-4 bg-zinc-700"></div>
<div class="flex items-center gap-2">
<i data-lucide="git-merge" class="w-4 h-4 text-brand-500"></i>
<span class="text-sm text-zinc-300">Workflows</span>
</div>
</div>
</div>
</div>
</section>
<!-- Problem/Solution -->
<section class="py-24 border-t border-zinc-800/50">
<div class="max-w-6xl mx-auto px-6">
<div class="text-center mb-16">
<h2 class="text-3xl md:text-5xl font-bold mb-4">
Setting up Help Scout + AI<br>
<span class="text-zinc-500">shouldn't take a week</span>
</h2>
</div>
<div class="grid md:grid-cols-3 gap-8">
<!-- Pain Point 1 -->
<div class="relative group">
<div class="absolute inset-0 bg-gradient-to-b from-red-500/10 to-transparent rounded-2xl"></div>
<div class="relative p-8 rounded-2xl bg-zinc-900/50 border border-zinc-800 h-full">
<div class="w-12 h-12 rounded-xl bg-red-500/10 flex items-center justify-center mb-6">
<i data-lucide="x" class="w-6 h-6 text-red-400"></i>
</div>
<h3 class="text-lg font-semibold text-zinc-300 mb-3">Repetitive support queries</h3>
<p class="text-zinc-500 mb-6">Same questions, every day. Copy-paste answers get stale fast.</p>
<div class="pt-6 border-t border-zinc-800">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-brand-500/20 flex items-center justify-center">
<i data-lucide="check" class="w-4 h-4 text-brand-400"></i>
</div>
<p class="text-white font-medium">AI drafts from your docs</p>
</div>
</div>
</div>
</div>
<!-- Pain Point 2 -->
<div class="relative group">
<div class="absolute inset-0 bg-gradient-to-b from-red-500/10 to-transparent rounded-2xl"></div>
<div class="relative p-8 rounded-2xl bg-zinc-900/50 border border-zinc-800 h-full">
<div class="w-12 h-12 rounded-xl bg-red-500/10 flex items-center justify-center mb-6">
<i data-lucide="x" class="w-6 h-6 text-red-400"></i>
</div>
<h3 class="text-lg font-semibold text-zinc-300 mb-3">No customer context</h3>
<p class="text-zinc-500 mb-6">Who is this person? What happened before? Time wasted searching.</p>
<div class="pt-6 border-t border-zinc-800">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-brand-500/20 flex items-center justify-center">
<i data-lucide="check" class="w-4 h-4 text-brand-400"></i>
</div>
<p class="text-white font-medium">Full history at a glance</p>
</div>
</div>
</div>
</div>
<!-- Pain Point 3 -->
<div class="relative group">
<div class="absolute inset-0 bg-gradient-to-b from-red-500/10 to-transparent rounded-2xl"></div>
<div class="relative p-8 rounded-2xl bg-zinc-900/50 border border-zinc-800 h-full">
<div class="w-12 h-12 rounded-xl bg-red-500/10 flex items-center justify-center mb-6">
<i data-lucide="x" class="w-6 h-6 text-red-400"></i>
</div>
<h3 class="text-lg font-semibold text-zinc-300 mb-3">Manual ticket routing</h3>
<p class="text-zinc-500 mb-6">Tickets land in the wrong queue. Customers wait. CSAT drops.</p>
<div class="pt-6 border-t border-zinc-800">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-brand-500/20 flex items-center justify-center">
<i data-lucide="check" class="w-4 h-4 text-brand-400"></i>
</div>
<p class="text-white font-medium">AI assigns intelligently</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Features -->
<section id="features" class="py-24 border-t border-zinc-800/50">
<div class="max-w-6xl mx-auto px-6">
<div class="text-center mb-16">
<div class="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-brand-500/10 text-brand-400 text-sm font-medium mb-6">
<i data-lucide="sparkles" class="w-4 h-4"></i>
Full API Coverage
</div>
<h2 class="text-3xl md:text-5xl font-bold mb-4">Everything you need</h2>
<p class="text-xl text-zinc-400">Full Help Scout API access through one simple connection</p>
</div>
<div class="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
<div class="bg-zinc-900/50 rounded-2xl border border-zinc-800 p-6 card-glow">
<div class="w-14 h-14 rounded-xl bg-gradient-to-br from-brand-500/20 to-blue-500/20 flex items-center justify-center mb-5 feature-icon">
<i data-lucide="message-circle" class="w-7 h-7 text-brand-400"></i>
</div>
<h3 class="text-xl font-semibold mb-3">Conversation Management</h3>
<p class="text-zinc-400">Handle emails, chats, and messages — all unified in one place.</p>
</div>
<div class="bg-zinc-900/50 rounded-2xl border border-zinc-800 p-6 card-glow">
<div class="w-14 h-14 rounded-xl bg-gradient-to-br from-purple-500/20 to-pink-500/20 flex items-center justify-center mb-5 feature-icon">
<i data-lucide="book-open" class="w-7 h-7 text-purple-400"></i>
</div>
<h3 class="text-xl font-semibold mb-3">Docs</h3>
<p class="text-zinc-400">Search and surface knowledge base articles automatically.</p>
</div>
<div class="bg-zinc-900/50 rounded-2xl border border-zinc-800 p-6 card-glow">
<div class="w-14 h-14 rounded-xl bg-gradient-to-br from-orange-500/20 to-yellow-500/20 flex items-center justify-center mb-5 feature-icon">
<i data-lucide="user-circle" class="w-7 h-7 text-orange-400"></i>
</div>
<h3 class="text-xl font-semibold mb-3">Customer Profiles</h3>
<p class="text-zinc-400">Access history, properties, and context for every customer.</p>
</div>
<div class="bg-zinc-900/50 rounded-2xl border border-zinc-800 p-6 card-glow">
<div class="w-14 h-14 rounded-xl bg-gradient-to-br from-cyan-500/20 to-teal-500/20 flex items-center justify-center mb-5 feature-icon">
<i data-lucide="git-merge" class="w-7 h-7 text-cyan-400"></i>
</div>
<h3 class="text-xl font-semibold mb-3">Workflows</h3>
<p class="text-zinc-400">Automate tagging, assignment, and responses effortlessly.</p>
</div>
</div>
<div class="mt-12 text-center">
<p class="text-zinc-400 mb-4">+ 50 more endpoints including:</p>
<div class="flex flex-wrap justify-center gap-2">
<span class="px-3 py-1.5 bg-zinc-800/80 rounded-full text-sm hover:bg-zinc-700 transition cursor-default">Mailboxes</span>
<span class="px-3 py-1.5 bg-zinc-800/80 rounded-full text-sm hover:bg-zinc-700 transition cursor-default">Tags</span>
<span class="px-3 py-1.5 bg-zinc-800/80 rounded-full text-sm hover:bg-zinc-700 transition cursor-default">Saved Replies</span>
<span class="px-3 py-1.5 bg-zinc-800/80 rounded-full text-sm hover:bg-zinc-700 transition cursor-default">Webhooks</span>
<span class="px-3 py-1.5 bg-zinc-800/80 rounded-full text-sm hover:bg-zinc-700 transition cursor-default">Teams</span>
<span class="px-3 py-1.5 bg-zinc-800/80 rounded-full text-sm hover:bg-zinc-700 transition cursor-default">Users</span>
<span class="px-3 py-1.5 bg-zinc-800/80 rounded-full text-sm hover:bg-zinc-700 transition cursor-default">Reports</span>
<span class="px-3 py-1.5 bg-zinc-800/80 rounded-full text-sm hover:bg-zinc-700 transition cursor-default">Beacons</span>
</div>
</div>
</div>
</section>
<!-- Waitlist -->
<section id="pricing" class="py-24 border-t border-zinc-800/50">
<div class="max-w-2xl mx-auto px-6">
<div class="text-center mb-12">
<div class="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-brand-500/20 text-brand-400 text-sm font-medium mb-6">
<i data-lucide="sparkles" class="w-4 h-4"></i>
Coming Soon
</div>
<h2 class="text-3xl md:text-5xl font-bold mb-4">Join the Waitlist</h2>
<p class="text-xl text-zinc-400">Be the first to know when we launch. Early access + exclusive perks for waitlist members.</p>
</div>
<div class="relative">
<div class="absolute inset-0 bg-gradient-to-r from-brand-500/10 to-purple-500/10 rounded-3xl blur-xl"></div>
<div class="relative bg-zinc-900/80 rounded-2xl border border-zinc-800 p-8 backdrop-blur-sm">
<form id="waitlist-form" class="space-y-6">
<div>
<label for="name" class="block text-sm font-medium text-zinc-300 mb-2">Name <span class="text-brand-400">*</span></label>
<input
type="text"
id="name"
name="name"
required
placeholder="Your full name"
class="w-full px-4 py-3.5 bg-zinc-800/80 border border-zinc-700 rounded-xl text-white placeholder-zinc-500 focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20 transition"
>
</div>
<div>
<label for="phone" class="block text-sm font-medium text-zinc-300 mb-2">Phone <span class="text-brand-400">*</span></label>
<input
type="tel"
id="phone"
name="phone"
required
placeholder="Your phone number"
class="w-full px-4 py-3.5 bg-zinc-800/80 border border-zinc-700 rounded-xl text-white placeholder-zinc-500 focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20 transition"
>
</div>
<div>
<label for="email" class="block text-sm font-medium text-zinc-300 mb-2">Email</label>
<input
type="email"
id="email"
name="email"
placeholder="you@company.com"
class="w-full px-4 py-3.5 bg-zinc-800/80 border border-zinc-700 rounded-xl text-white placeholder-zinc-500 focus:outline-none focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20 transition"
>
</div>
<button
type="submit"
id="submit-btn"
class="w-full py-4 bg-gradient-to-r from-brand-500 to-purple-500 hover:from-brand-600 hover:to-purple-600 rounded-xl font-semibold text-lg transition transform hover:scale-[1.02] flex items-center justify-center gap-2 shadow-lg shadow-brand-500/25"
>
<span>Join the Waitlist</span>
<i data-lucide="arrow-right" class="w-5 h-5"></i>
</button>
</form>
<!-- Success Message (hidden by default) -->
<div id="success-message" class="hidden text-center py-8">
<div class="w-16 h-16 bg-green-500/20 rounded-full flex items-center justify-center mx-auto mb-4">
<i data-lucide="check" class="w-8 h-8 text-green-400"></i>
</div>
<h3 class="text-2xl font-bold mb-2">You're on the list!</h3>
<p class="text-zinc-400">We'll reach out as soon as we're ready for you.</p>
</div>
<p class="text-zinc-500 text-sm text-center mt-6 flex items-center justify-center gap-2">
<i data-lucide="lock" class="w-4 h-4"></i>
We respect your privacy. No spam, ever.
</p>
</div>
</div>
</div>
</section>
<script>
document.getElementById('waitlist-form').addEventListener('submit', async function(e) {
e.preventDefault();
const form = this;
const submitBtn = document.getElementById('submit-btn');
const successMsg = document.getElementById('success-message');
submitBtn.disabled = true;
submitBtn.innerHTML = '<span>Submitting...</span><svg class="animate-spin w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>';
setTimeout(() => {
form.classList.add('hidden');
successMsg.classList.remove('hidden');
}, 1000);
});
</script>
<!-- Open Source -->
<section class="py-24 border-t border-zinc-800/50">
<div class="max-w-6xl mx-auto px-6">
<div class="bg-gradient-to-br from-zinc-900 to-zinc-900/50 rounded-3xl border border-zinc-800 p-8 md:p-12 relative overflow-hidden">
<div class="absolute top-0 right-0 w-96 h-96 bg-brand-500/5 rounded-full blur-3xl"></div>
<div class="grid md:grid-cols-2 gap-12 items-center relative">
<div>
<div class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-zinc-800 text-sm mb-6">
<i data-lucide="github" class="w-4 h-4"></i>
Open Source
</div>
<h2 class="text-3xl md:text-4xl font-bold mb-4">
Self-host if you want.<br>
<span class="text-zinc-500">We won't stop you.</span>
</h2>
<p class="text-zinc-400 mb-6">
The entire MCP server is open source. Run it yourself, modify it, contribute back.
The hosted version just saves you the hassle.
</p>
<a href="https://github.com/BusyBee3333/help-scout-mcp-2026-complete" target="_blank" rel="noopener" class="inline-flex items-center gap-2 text-brand-400 hover:text-brand-300 font-medium transition">
View on GitHub
<i data-lucide="arrow-right" class="w-4 h-4"></i>
</a>
</div>
<div class="bg-zinc-950 rounded-xl p-6 font-mono text-sm border border-zinc-800">
<div class="flex items-center gap-2 text-zinc-500 mb-4">
<span class="w-3 h-3 rounded-full bg-red-500"></span>
<span class="w-3 h-3 rounded-full bg-yellow-500"></span>
<span class="w-3 h-3 rounded-full bg-green-500"></span>
<span class="ml-2">Terminal</span>
</div>
<pre class="text-zinc-300 overflow-x-auto"><code><span class="text-zinc-500">$</span> git clone https://github.com/BusyBee3333/help-scout-mcp-2026-complete.git
<span class="text-zinc-500">$</span> cd mcp && npm install
<span class="text-zinc-500">$</span> npm run build
<span class="text-zinc-500">$</span> node dist/server.js
<span class="text-green-400">✓ Help Scout MCP Server running</span>
<span class="text-green-400">✓ 54 tools loaded</span>
<span class="text-zinc-500">Listening on stdio...</span></code></pre>
</div>
</div>
</div>
</div>
</section>
<!-- FAQ -->
<section id="faq" class="py-24 border-t border-zinc-800/50">
<div class="max-w-3xl mx-auto px-6">
<div class="text-center mb-16">
<h2 class="text-3xl md:text-4xl font-bold mb-4">Frequently asked questions</h2>
</div>
<div class="space-y-4">
<details class="group bg-zinc-900/50 rounded-xl border border-zinc-800 p-6 hover:border-zinc-700 transition">
<summary class="flex items-center justify-between cursor-pointer list-none">
<span class="font-medium text-lg">What is MCP?</span>
<i data-lucide="chevron-down" class="w-5 h-5 text-zinc-400 group-open:rotate-180 transition"></i>
</summary>
<p class="mt-4 text-zinc-400 leading-relaxed">
MCP (Model Context Protocol) is a standard for connecting AI assistants to external tools and data.
It's supported by Claude, and lets AI actually take actions in your systems — not just chat about them.
</p>
</details>
<details class="group bg-zinc-900/50 rounded-xl border border-zinc-800 p-6 hover:border-zinc-700 transition">
<summary class="flex items-center justify-between cursor-pointer list-none">
<span class="font-medium text-lg">Do I need to install anything?</span>
<i data-lucide="chevron-down" class="w-5 h-5 text-zinc-400 group-open:rotate-180 transition"></i>
</summary>
<p class="mt-4 text-zinc-400 leading-relaxed">
For the hosted version, no. Just connect your Help Scout account via OAuth and add the MCP endpoint to your AI client (Claude Desktop, etc.).
If you self-host, you'll need Node.js.
</p>
</details>
<details class="group bg-zinc-900/50 rounded-xl border border-zinc-800 p-6 hover:border-zinc-700 transition">
<summary class="flex items-center justify-between cursor-pointer list-none">
<span class="font-medium text-lg">Is my data secure?</span>
<i data-lucide="chevron-down" class="w-5 h-5 text-zinc-400 group-open:rotate-180 transition"></i>
</summary>
<p class="mt-4 text-zinc-400 leading-relaxed">
Yes. We use OAuth 2.0 and never store your Help Scout API keys. Tokens are encrypted at rest and in transit.
You can revoke access anytime from your Help Scout settings.
</p>
</details>
<details class="group bg-zinc-900/50 rounded-xl border border-zinc-800 p-6 hover:border-zinc-700 transition">
<summary class="flex items-center justify-between cursor-pointer list-none">
<span class="font-medium text-lg">Can I use this with GPT or other AI models?</span>
<i data-lucide="chevron-down" class="w-5 h-5 text-zinc-400 group-open:rotate-180 transition"></i>
</summary>
<p class="mt-4 text-zinc-400 leading-relaxed">
MCP is currently best supported by Claude (Anthropic). GPT can use it via custom implementations.
As MCP adoption grows, more clients will support it natively.
</p>
</details>
</div>
</div>
</section>
<!-- CTA -->
<section class="py-24 border-t border-zinc-800/50">
<div class="max-w-4xl mx-auto px-6 text-center">
<h2 class="text-3xl md:text-5xl font-bold mb-6">
Ready to AI-power your Help Scout?
</h2>
<p class="text-xl text-zinc-400 mb-10">
Join 200+ support teams already automating with Help Scout Connect.
</p>
<div class="flex flex-col sm:flex-row items-center justify-center gap-4">
<a href="#pricing" class="w-full sm:w-auto px-8 py-4 bg-gradient-to-r from-brand-500 to-purple-500 hover:from-brand-600 hover:to-purple-600 rounded-xl font-semibold text-lg transition transform hover:scale-105 shadow-lg shadow-brand-500/25">
Join the Waitlist
</a>
<a href="#" class="w-full sm:w-auto px-8 py-4 bg-zinc-800 hover:bg-zinc-700 rounded-xl font-semibold text-lg transition">
Book a Demo
</a>
</div>
</div>
</section>
<!-- Footer -->
<footer class="border-t border-zinc-800/50 py-12">
<div class="max-w-6xl mx-auto px-6">
<div class="flex flex-col md:flex-row items-center justify-between gap-6">
<div class="flex items-center gap-2">
<div class="w-8 h-8 rounded-lg bg-gradient-to-br from-brand-500 to-blue-400 flex items-center justify-center">
<i data-lucide="life-buoy" class="w-5 h-5 text-white"></i>
</div>
<span class="font-bold text-xl">Help Scout Connect</span>
</div>
<div class="flex items-center gap-8 text-zinc-400">
<a href="https://github.com/BusyBee3333/help-scout-mcp-2026-complete" target="_blank" rel="noopener" class="hover:text-white transition">Privacy</a>
<a href="https://github.com/BusyBee3333/help-scout-mcp-2026-complete" target="_blank" rel="noopener" class="hover:text-white transition">Terms</a>
<a href="https://github.com/BusyBee3333/help-scout-mcp-2026-complete" target="_blank" rel="noopener" class="hover:text-white transition">GitHub</a>
<a href="https://github.com/BusyBee3333/help-scout-mcp-2026-complete" target="_blank" rel="noopener" class="hover:text-white transition">Twitter</a>
</div>
<p class="text-zinc-500 text-sm">© 2026 Help Scout Connect. Not affiliated with Help Scout.</p>
</div>
</div>
</footer>
<!-- Sticky Floating CTA -->
<div id="sticky-cta" class="fixed bottom-6 right-6 z-50 opacity-0 translate-y-4 transition-all duration-300 pointer-events-none">
<a
href="#pricing"
class="sticky-btn flex items-center gap-2 px-6 py-3 bg-gradient-to-r from-brand-500 to-purple-500 rounded-full font-semibold transition-all transform hover:scale-110"
>
<i data-lucide="sparkles" class="w-5 h-5"></i>
<span>Join Waitlist</span>
</a>
</div>
<script>
const stickyCta = document.getElementById('sticky-cta');
const pricingSection = document.getElementById('pricing');
function updateStickyCta() {
const scrollY = window.scrollY;
const pricingTop = pricingSection.offsetTop;
const pricingBottom = pricingTop + pricingSection.offsetHeight;
const viewportBottom = scrollY + window.innerHeight;
const shouldShow = scrollY > 300 && (viewportBottom < pricingTop || scrollY > pricingBottom);
if (shouldShow) {
stickyCta.classList.remove('opacity-0', 'translate-y-4', 'pointer-events-none');
stickyCta.classList.add('opacity-100', 'translate-y-0', 'pointer-events-auto');
} else {
stickyCta.classList.add('opacity-0', 'translate-y-4', 'pointer-events-none');
stickyCta.classList.remove('opacity-100', 'translate-y-0', 'pointer-events-auto');
}
}
window.addEventListener('scroll', updateStickyCta);
updateStickyCta();
</script>
<script>lucide.createIcons();</script>
</body>
</html>

38
package.json Normal file
View File

@ -0,0 +1,38 @@
{
"name": "@mcpengine/helpscout-mcp-server",
"version": "1.0.0",
"description": "Complete HelpScout Mailbox API v2 MCP Server with OAuth2 authentication",
"type": "module",
"main": "dist/main.js",
"scripts": {
"build": "tsc && npm run copy-apps",
"copy-apps": "mkdir -p dist/apps && cp -r src/apps/*.tsx dist/apps/ 2>/dev/null || true",
"dev": "tsc --watch",
"start": "node dist/main.js",
"prepare": "npm run build"
},
"keywords": [
"helpscout",
"mcp",
"support",
"customer-service",
"helpdesk",
"oauth2"
],
"author": "MCP Engine",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.4",
"axios": "^1.7.9",
"dotenv": "^16.4.7",
"react": "^18.3.1"
},
"devDependencies": {
"@types/node": "^22.10.5",
"@types/react": "^18.3.18",
"typescript": "^5.7.2"
},
"engines": {
"node": ">=18.0.0"
}
}

138
src/api/client.ts Normal file
View File

@ -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<string> {
// 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<AccessTokenResponse>(
'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<T>(path: string, params?: any): Promise<T> {
const response = await this.client.get<T>(path, { params });
return response.data;
}
async post<T>(path: string, data?: any): Promise<T> {
const response = await this.client.post<T>(path, data);
return response.data;
}
async put<T>(path: string, data?: any): Promise<T> {
const response = await this.client.put<T>(path, data);
return response.data;
}
async patch<T>(path: string, data?: any): Promise<T> {
const response = await this.client.patch<T>(path, data);
return response.data;
}
async delete(path: string): Promise<void> {
await this.client.delete(path);
}
async *paginate<T>(
path: string,
params?: any,
embedKey?: string
): AsyncGenerator<T[], void, unknown> {
let nextUrl: string | undefined = path;
let page = 1;
while (nextUrl) {
const fullParams = { ...params, page };
const response = await this.get<PaginatedResponse<T>>(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<T>(
path: string,
params?: any,
embedKey?: string
): Promise<T[]> {
const allItems: T[] = [];
for await (const items of this.paginate<T>(path, params, embedKey)) {
allItems.push(...items);
}
return allItems;
}
}

105
src/apps/company-report.ts Normal file
View File

@ -0,0 +1,105 @@
export const companyReportApp = {
name: 'company-report',
description: 'Overall company performance and KPIs',
content: `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Company Report</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
.container { max-width: 1400px; margin: 0 auto; }
h1 { font-size: 28px; color: #1f2d3d; margin-bottom: 8px; }
.subtitle { font-size: 14px; color: #6f7b8a; margin-bottom: 24px; }
.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 24px; }
.kpi-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.kpi-value { font-size: 32px; font-weight: bold; color: #1f2d3d; margin-bottom: 4px; }
.kpi-label { font-size: 13px; color: #6f7b8a; margin-bottom: 8px; }
.kpi-change { font-size: 12px; font-weight: 500; }
.kpi-change.positive { color: #10b981; }
.kpi-change.negative { color: #ef4444; }
.sections-grid { display: grid; grid-template-columns: 2fr 1fr; gap: 20px; }
.section { background: white; padding: 24px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.section-title { font-size: 18px; font-weight: 600; color: #1f2d3d; margin-bottom: 16px; }
.chart-placeholder { height: 200px; background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%); border-radius: 6px; display: flex; align-items: center; justify-content: center; color: #0369a1; font-size: 14px; }
.summary-item { padding: 12px 0; border-bottom: 1px solid #f0f2f5; display: flex; justify-content: space-between; }
.summary-item:last-child { border-bottom: none; }
.summary-label { font-size: 14px; color: #6f7b8a; }
.summary-value { font-size: 14px; font-weight: 600; color: #1f2d3d; }
</style>
</head>
<body>
<div class="container">
<h1>🏢 Company Report</h1>
<div class="subtitle">Overall performance overview for last 30 days</div>
<div class="kpi-grid">
<div class="kpi-card">
<div class="kpi-label">CONVERSATIONS CREATED</div>
<div class="kpi-value">1,524</div>
<div class="kpi-change positive"> 15% vs last month</div>
</div>
<div class="kpi-card">
<div class="kpi-label">CONVERSATIONS CLOSED</div>
<div class="kpi-value">1,389</div>
<div class="kpi-change positive"> 12% vs last month</div>
</div>
<div class="kpi-card">
<div class="kpi-label">CUSTOMERS HELPED</div>
<div class="kpi-value">847</div>
<div class="kpi-change positive"> 8% vs last month</div>
</div>
<div class="kpi-card">
<div class="kpi-label">HAPPINESS SCORE</div>
<div class="kpi-value">94%</div>
<div class="kpi-change positive"> 3% vs last month</div>
</div>
<div class="kpi-card">
<div class="kpi-label">REPLIES SENT</div>
<div class="kpi-value">3,247</div>
<div class="kpi-change positive"> 18% vs last month</div>
</div>
<div class="kpi-card">
<div class="kpi-label">AVG RESOLUTION TIME</div>
<div class="kpi-value">8.4h</div>
<div class="kpi-change negative"> 1.2h vs last month</div>
</div>
</div>
<div class="sections-grid">
<div class="section">
<div class="section-title">Conversation Volume Trend</div>
<div class="chart-placeholder">📈 7-day conversation trend chart</div>
</div>
<div class="section">
<div class="section-title">Key Metrics Summary</div>
<div class="summary-item">
<span class="summary-label">First Response Time</span>
<span class="summary-value">3.2 hours</span>
</div>
<div class="summary-item">
<span class="summary-label">Active Conversations</span>
<span class="summary-value">147</span>
</div>
<div class="summary-item">
<span class="summary-label">Pending Conversations</span>
<span class="summary-value">23</span>
</div>
<div class="summary-item">
<span class="summary-label">Team Members</span>
<span class="summary-value">12 agents</span>
</div>
<div class="summary-item">
<span class="summary-label">Active Mailboxes</span>
<span class="summary-value">3</span>
</div>
</div>
</div>
</div>
</body>
</html>
`,
};

View File

@ -0,0 +1,141 @@
export const conversationDashboardApp = {
name: 'conversation-dashboard',
description: 'Dashboard view of conversations with filters and quick actions',
content: `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Conversation Dashboard</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f7f9fc;
padding: 20px;
}
.dashboard { max-width: 1400px; margin: 0 auto; }
h1 { font-size: 28px; color: #1f2d3d; margin-bottom: 20px; }
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 24px; }
.stat-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.stat-value { font-size: 32px; font-weight: bold; color: #3197d6; }
.stat-label { font-size: 14px; color: #6f7b8a; margin-top: 4px; }
.filters { background: white; padding: 16px; border-radius: 8px; margin-bottom: 20px; display: flex; gap: 12px; flex-wrap: wrap; }
.filter-group { display: flex; flex-direction: column; gap: 4px; }
.filter-group label { font-size: 12px; color: #6f7b8a; }
.filter-group select, .filter-group input { padding: 8px; border: 1px solid #d9dee4; border-radius: 4px; font-size: 14px; }
.conversations { background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.conv-header { display: grid; grid-template-columns: 1fr 150px 120px 100px 100px; padding: 12px 16px; background: #f7f9fc; border-bottom: 1px solid #d9dee4; font-size: 12px; font-weight: 600; color: #6f7b8a; }
.conv-row { display: grid; grid-template-columns: 1fr 150px 120px 100px 100px; padding: 16px; border-bottom: 1px solid #f0f2f5; align-items: center; cursor: pointer; transition: background 0.2s; }
.conv-row:hover { background: #f7f9fc; }
.conv-subject { font-weight: 500; color: #1f2d3d; }
.conv-preview { font-size: 13px; color: #6f7b8a; margin-top: 4px; }
.conv-customer { font-size: 14px; color: #3197d6; }
.conv-assignee { font-size: 14px; color: #6f7b8a; }
.status-badge { padding: 4px 8px; border-radius: 4px; font-size: 12px; font-weight: 500; text-align: center; }
.status-active { background: #d4f4dd; color: #0a6640; }
.status-pending { background: #fff4cc; color: #b45309; }
.status-closed { background: #e5e7eb; color: #4b5563; }
.conv-date { font-size: 13px; color: #6f7b8a; }
</style>
</head>
<body>
<div class="dashboard">
<h1>📬 Conversation Dashboard</h1>
<div class="stats">
<div class="stat-card">
<div class="stat-value">147</div>
<div class="stat-label">Active Conversations</div>
</div>
<div class="stat-card">
<div class="stat-value">23</div>
<div class="stat-label">Pending</div>
</div>
<div class="stat-card">
<div class="stat-value">8</div>
<div class="stat-label">Unassigned</div>
</div>
<div class="stat-card">
<div class="stat-value">4.2h</div>
<div class="stat-label">Avg Response Time</div>
</div>
</div>
<div class="filters">
<div class="filter-group">
<label>Status</label>
<select>
<option>All</option>
<option>Active</option>
<option>Pending</option>
<option>Closed</option>
</select>
</div>
<div class="filter-group">
<label>Mailbox</label>
<select>
<option>All Mailboxes</option>
<option>Support</option>
<option>Sales</option>
</select>
</div>
<div class="filter-group">
<label>Assigned To</label>
<select>
<option>Everyone</option>
<option>Me</option>
<option>Unassigned</option>
</select>
</div>
<div class="filter-group">
<label>Search</label>
<input type="text" placeholder="Search conversations...">
</div>
</div>
<div class="conversations">
<div class="conv-header">
<div>SUBJECT</div>
<div>CUSTOMER</div>
<div>ASSIGNED</div>
<div>STATUS</div>
<div>UPDATED</div>
</div>
<div class="conv-row" onclick="alert('Open conversation detail')">
<div>
<div class="conv-subject">Payment issue with subscription</div>
<div class="conv-preview">Customer having trouble with their credit card...</div>
</div>
<div class="conv-customer">Sarah Chen</div>
<div class="conv-assignee">John Smith</div>
<div><span class="status-badge status-active">Active</span></div>
<div class="conv-date">2 hours ago</div>
</div>
<div class="conv-row" onclick="alert('Open conversation detail')">
<div>
<div class="conv-subject">Feature request: Dark mode</div>
<div class="conv-preview">Would love to have a dark mode option...</div>
</div>
<div class="conv-customer">Mike Johnson</div>
<div class="conv-assignee">Emily Davis</div>
<div><span class="status-badge status-pending">Pending</span></div>
<div class="conv-date">5 hours ago</div>
</div>
<div class="conv-row" onclick="alert('Open conversation detail')">
<div>
<div class="conv-subject">Login problems</div>
<div class="conv-preview">Cannot log in with my account credentials...</div>
</div>
<div class="conv-customer">Alex Brown</div>
<div class="conv-assignee"></div>
<div><span class="status-badge status-active">Active</span></div>
<div class="conv-date">1 day ago</div>
</div>
</div>
</div>
</body>
</html>
`,
};

View File

@ -0,0 +1,134 @@
export const conversationDetailApp = {
name: 'conversation-detail',
description: 'Detailed view of a single conversation with thread history and actions',
content: `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Conversation Detail</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f7f9fc;
display: flex;
height: 100vh;
}
.sidebar { width: 300px; background: white; border-right: 1px solid #d9dee4; padding: 20px; overflow-y: auto; }
.main { flex: 1; display: flex; flex-direction: column; }
.header { background: white; padding: 16px 24px; border-bottom: 1px solid #d9dee4; display: flex; justify-content: space-between; align-items: center; }
.subject { font-size: 20px; font-weight: 600; color: #1f2d3d; }
.actions { display: flex; gap: 8px; }
.btn { padding: 8px 16px; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; border: 1px solid #d9dee4; background: white; }
.btn:hover { background: #f7f9fc; }
.btn-primary { background: #3197d6; color: white; border: none; }
.btn-primary:hover { background: #2578af; }
.threads { flex: 1; overflow-y: auto; padding: 24px; }
.thread { background: white; border-radius: 8px; padding: 20px; margin-bottom: 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.thread-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
.thread-author { font-weight: 600; color: #1f2d3d; }
.thread-time { font-size: 13px; color: #6f7b8a; }
.thread-body { color: #3e4c59; line-height: 1.6; }
.thread-note { background: #fff9e6; border-left: 4px solid #f59e0b; }
.sidebar-section { margin-bottom: 24px; }
.sidebar-label { font-size: 12px; font-weight: 600; color: #6f7b8a; margin-bottom: 8px; text-transform: uppercase; }
.sidebar-value { font-size: 14px; color: #1f2d3d; }
.tag { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 12px; background: #e0e7ff; color: #3730a3; margin-right: 4px; margin-bottom: 4px; }
</style>
</head>
<body>
<div class="sidebar">
<div class="sidebar-section">
<div class="sidebar-label">Customer</div>
<div class="sidebar-value" style="color: #3197d6; font-weight: 500;">Sarah Chen</div>
<div style="font-size: 13px; color: #6f7b8a; margin-top: 4px;">sarah.chen@example.com</div>
</div>
<div class="sidebar-section">
<div class="sidebar-label">Status</div>
<div class="sidebar-value">
<select style="width: 100%; padding: 8px; border: 1px solid #d9dee4; border-radius: 4px;">
<option>Active</option>
<option>Pending</option>
<option>Closed</option>
</select>
</div>
</div>
<div class="sidebar-section">
<div class="sidebar-label">Assigned To</div>
<div class="sidebar-value">
<select style="width: 100%; padding: 8px; border: 1px solid #d9dee4; border-radius: 4px;">
<option>John Smith</option>
<option>Emily Davis</option>
<option>Unassigned</option>
</select>
</div>
</div>
<div class="sidebar-section">
<div class="sidebar-label">Tags</div>
<div>
<span class="tag">billing</span>
<span class="tag">urgent</span>
</div>
</div>
<div class="sidebar-section">
<div class="sidebar-label">Mailbox</div>
<div class="sidebar-value">Support</div>
</div>
<div class="sidebar-section">
<div class="sidebar-label">Created</div>
<div class="sidebar-value" style="font-size: 13px;">Jan 15, 2025 2:34 PM</div>
</div>
</div>
<div class="main">
<div class="header">
<div class="subject">Payment issue with subscription</div>
<div class="actions">
<button class="btn">Add Note</button>
<button class="btn btn-primary">Reply</button>
</div>
</div>
<div class="threads">
<div class="thread">
<div class="thread-header">
<div class="thread-author">Sarah Chen (Customer)</div>
<div class="thread-time">2 hours ago</div>
</div>
<div class="thread-body">
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?
</div>
</div>
<div class="thread thread-note">
<div class="thread-header">
<div class="thread-author">🔒 John Smith (Internal Note)</div>
<div class="thread-time">1 hour ago</div>
</div>
<div class="thread-body">
Checking with billing team - seems like a known issue with Visa cards from Canada. Should have a fix deployed by EOD.
</div>
</div>
<div class="thread">
<div class="thread-header">
<div class="thread-author">John Smith (Agent)</div>
<div class="thread-time">30 minutes ago</div>
</div>
<div class="thread-body">
Hi Sarah,<br><br>
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.<br><br>
In the meantime, if you have an alternative payment method (Mastercard or American Express), that should work without issues.<br><br>
I'll follow up once the fix is deployed!<br><br>
Best regards,<br>
John
</div>
</div>
</div>
</div>
</body>
</html>
`,
};

View File

@ -0,0 +1,104 @@
export const conversationGridApp = {
name: 'conversation-grid',
description: 'Alternative grid layout for conversations with card view',
content: `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Conversations Grid</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
.container { max-width: 1400px; margin: 0 auto; }
h1 { font-size: 28px; color: #1f2d3d; margin-bottom: 20px; }
.conv-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 16px; }
.conv-card { background: white; border-radius: 8px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); cursor: pointer; transition: transform 0.2s; }
.conv-card:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0,0,0,0.15); }
.conv-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 12px; }
.conv-number { font-size: 12px; color: #9ca3af; font-weight: 500; }
.status-badge { padding: 4px 8px; border-radius: 4px; font-size: 11px; font-weight: 500; }
.status-active { background: #d4f4dd; color: #0a6640; }
.status-pending { background: #fff4cc; color: #b45309; }
.conv-subject { font-size: 16px; font-weight: 600; color: #1f2d3d; margin-bottom: 8px; }
.conv-preview { font-size: 13px; color: #6f7b8a; line-height: 1.5; margin-bottom: 16px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.conv-footer { display: flex; justify-content: space-between; align-items: center; padding-top: 12px; border-top: 1px solid #f0f2f5; }
.customer-info { display: flex; align-items: center; gap: 8px; }
.customer-avatar { width: 28px; height: 28px; border-radius: 50%; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 11px; font-weight: 600; }
.customer-name { font-size: 13px; color: #3197d6; font-weight: 500; }
.conv-time { font-size: 12px; color: #9ca3af; }
</style>
</head>
<body>
<div class="container">
<h1>📬 Conversations</h1>
<div class="conv-grid">
<div class="conv-card" onclick="alert('Open conversation')">
<div class="conv-header">
<span class="conv-number">#1247</span>
<span class="status-badge status-active">Active</span>
</div>
<div class="conv-subject">Payment issue with subscription</div>
<div class="conv-preview">Customer having trouble with their credit card. Error appears when trying to update payment method...</div>
<div class="conv-footer">
<div class="customer-info">
<div class="customer-avatar">SC</div>
<span class="customer-name">Sarah Chen</span>
</div>
<span class="conv-time">2h ago</span>
</div>
</div>
<div class="conv-card" onclick="alert('Open conversation')">
<div class="conv-header">
<span class="conv-number">#1246</span>
<span class="status-badge status-pending">Pending</span>
</div>
<div class="conv-subject">Feature request: Dark mode</div>
<div class="conv-preview">Would love to have a dark mode option for the application. Many users work late hours...</div>
<div class="conv-footer">
<div class="customer-info">
<div class="customer-avatar" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">MJ</div>
<span class="customer-name">Mike Johnson</span>
</div>
<span class="conv-time">5h ago</span>
</div>
</div>
<div class="conv-card" onclick="alert('Open conversation')">
<div class="conv-header">
<span class="conv-number">#1245</span>
<span class="status-badge status-active">Active</span>
</div>
<div class="conv-subject">Login problems</div>
<div class="conv-preview">Cannot log in with my account credentials. Password reset doesn't seem to work either...</div>
<div class="conv-footer">
<div class="customer-info">
<div class="customer-avatar" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);">AB</div>
<span class="customer-name">Alex Brown</span>
</div>
<span class="conv-time">1d ago</span>
</div>
</div>
<div class="conv-card" onclick="alert('Open conversation')">
<div class="conv-header">
<span class="conv-number">#1244</span>
<span class="status-badge status-active">Active</span>
</div>
<div class="conv-subject">Question about API limits</div>
<div class="conv-preview">What are the rate limits for the API? We're planning to scale our integration...</div>
<div class="conv-footer">
<div class="customer-info">
<div class="customer-avatar" style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);">ED</div>
<span class="customer-name">Emily Davis</span>
</div>
<span class="conv-time">1d ago</span>
</div>
</div>
</div>
</div>
</body>
</html>
`,
};

View File

@ -0,0 +1,127 @@
export const conversationTimelineApp = {
name: 'conversation-timeline',
description: 'Visual timeline view of conversation history and events',
content: `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Conversation Timeline</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
.container { max-width: 900px; margin: 0 auto; }
.header { background: white; padding: 24px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.conv-subject { font-size: 24px; font-weight: 600; color: #1f2d3d; margin-bottom: 8px; }
.conv-meta { font-size: 14px; color: #6f7b8a; }
.timeline { position: relative; padding-left: 40px; }
.timeline::before { content: ''; position: absolute; left: 16px; top: 0; bottom: 0; width: 2px; background: #d9dee4; }
.timeline-item { position: relative; background: white; padding: 20px; border-radius: 8px; margin-bottom: 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.timeline-marker { position: absolute; left: -29px; width: 12px; height: 12px; border-radius: 50%; background: #3197d6; border: 3px solid white; box-shadow: 0 0 0 2px #3197d6; }
.timeline-marker.note { background: #f59e0b; box-shadow: 0 0 0 2px #f59e0b; }
.timeline-marker.event { background: #10b981; box-shadow: 0 0 0 2px #10b981; }
.timeline-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
.timeline-author { font-weight: 600; color: #1f2d3d; }
.timeline-type { font-size: 12px; color: #6f7b8a; margin-left: 8px; }
.timeline-time { font-size: 13px; color: #9ca3af; }
.timeline-body { color: #3e4c59; line-height: 1.6; }
.event-label { display: inline-block; padding: 6px 12px; border-radius: 4px; font-size: 13px; font-weight: 500; background: #f0fdf4; color: #166534; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="conv-subject">Payment issue with subscription</div>
<div class="conv-meta">Conversation #1247 Created 2 days ago by Sarah Chen</div>
</div>
<div class="timeline">
<div class="timeline-item">
<div class="timeline-marker"></div>
<div class="timeline-header">
<div>
<span class="timeline-author">Sarah Chen</span>
<span class="timeline-type">(Customer)</span>
</div>
<div class="timeline-time">2 days ago 2:34 PM</div>
</div>
<div class="timeline-body">
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?
</div>
</div>
<div class="timeline-item">
<div class="timeline-marker event"></div>
<div class="timeline-header">
<div>
<span class="timeline-author">System Event</span>
</div>
<div class="timeline-time">2 days ago 2:35 PM</div>
</div>
<div class="timeline-body">
<span class="event-label"> Assigned to John Smith</span>
</div>
</div>
<div class="timeline-item">
<div class="timeline-marker event"></div>
<div class="timeline-header">
<div>
<span class="timeline-author">System Event</span>
</div>
<div class="timeline-time">2 days ago 2:35 PM</div>
</div>
<div class="timeline-body">
<span class="event-label">🏷 Tags added: billing, urgent</span>
</div>
</div>
<div class="timeline-item">
<div class="timeline-marker note"></div>
<div class="timeline-header">
<div>
<span class="timeline-author">John Smith</span>
<span class="timeline-type">(Internal Note)</span>
</div>
<div class="timeline-time">2 days ago 3:15 PM</div>
</div>
<div class="timeline-body">
Checking with billing team - seems like a known issue with Visa cards from Canada. Should have a fix deployed by EOD.
</div>
</div>
<div class="timeline-item">
<div class="timeline-marker"></div>
<div class="timeline-header">
<div>
<span class="timeline-author">John Smith</span>
<span class="timeline-type">(Agent Reply)</span>
</div>
<div class="timeline-time">2 days ago 4:30 PM</div>
</div>
<div class="timeline-body">
Hi Sarah,<br><br>
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.<br><br>
In the meantime, if you have an alternative payment method (Mastercard or American Express), that should work without issues.<br><br>
I'll follow up once the fix is deployed!
</div>
</div>
<div class="timeline-item">
<div class="timeline-marker event"></div>
<div class="timeline-header">
<div>
<span class="timeline-author">System Event</span>
</div>
<div class="timeline-time">2 days ago 4:31 PM</div>
</div>
<div class="timeline-body">
<span class="event-label">Status changed: Active Pending</span>
</div>
</div>
</div>
</div>
</body>
</html>
`,
};

114
src/apps/customer-detail.ts Normal file
View File

@ -0,0 +1,114 @@
export const customerDetailApp = {
name: 'customer-detail',
description: 'Detailed customer profile with history and contact information',
content: `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Customer Detail</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; }
.container { max-width: 1200px; margin: 0 auto; padding: 20px; display: grid; grid-template-columns: 350px 1fr; gap: 20px; }
.sidebar { background: white; border-radius: 8px; padding: 24px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.main { background: white; border-radius: 8px; padding: 24px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.avatar { width: 80px; height: 80px; border-radius: 50%; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 32px; font-weight: bold; margin: 0 auto 16px; }
.customer-name { font-size: 22px; font-weight: 600; color: #1f2d3d; text-align: center; margin-bottom: 8px; }
.customer-org { font-size: 14px; color: #6f7b8a; text-align: center; margin-bottom: 24px; }
.info-section { margin-bottom: 24px; }
.info-label { font-size: 12px; font-weight: 600; color: #6f7b8a; margin-bottom: 8px; text-transform: uppercase; }
.info-value { font-size: 14px; color: #1f2d3d; margin-bottom: 8px; }
.info-value a { color: #3197d6; text-decoration: none; }
.btn { width: 100%; padding: 10px; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; border: 1px solid #d9dee4; background: white; margin-bottom: 8px; }
.btn:hover { background: #f7f9fc; }
.tabs { display: flex; border-bottom: 1px solid #d9dee4; margin-bottom: 24px; }
.tab { padding: 12px 24px; font-size: 14px; font-weight: 500; color: #6f7b8a; cursor: pointer; border-bottom: 2px solid transparent; }
.tab.active { color: #3197d6; border-bottom-color: #3197d6; }
.conversation-item { padding: 16px; border: 1px solid #e5e7eb; border-radius: 6px; margin-bottom: 12px; cursor: pointer; }
.conversation-item:hover { background: #f7f9fc; }
.conv-subject { font-weight: 500; color: #1f2d3d; margin-bottom: 4px; }
.conv-meta { font-size: 13px; color: #6f7b8a; }
.status-badge { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 11px; font-weight: 500; margin-left: 8px; }
.status-active { background: #d4f4dd; color: #0a6640; }
.status-closed { background: #e5e7eb; color: #4b5563; }
</style>
</head>
<body>
<div class="container">
<div class="sidebar">
<div class="avatar">SC</div>
<div class="customer-name">Sarah Chen</div>
<div class="customer-org">Acme Corporation</div>
<button class="btn">📧 Send Email</button>
<button class="btn">💬 Start Conversation</button>
<div class="info-section">
<div class="info-label">Email</div>
<div class="info-value"><a href="mailto:sarah.chen@example.com">sarah.chen@example.com</a></div>
</div>
<div class="info-section">
<div class="info-label">Phone</div>
<div class="info-value"><a href="tel:+15551234567">+1 (555) 123-4567</a></div>
</div>
<div class="info-section">
<div class="info-label">Job Title</div>
<div class="info-value">Product Manager</div>
</div>
<div class="info-section">
<div class="info-label">Location</div>
<div class="info-value">San Francisco, CA</div>
</div>
<div class="info-section">
<div class="info-label">Customer Since</div>
<div class="info-value">January 2024</div>
</div>
<div class="info-section">
<div class="info-label">Background</div>
<div class="info-value" style="font-size: 13px;">Long-time customer, frequently provides valuable product feedback.</div>
</div>
</div>
<div class="main">
<div class="tabs">
<div class="tab active">Conversations (12)</div>
<div class="tab">Notes (3)</div>
<div class="tab">Activity</div>
</div>
<div class="conversation-item" onclick="alert('Open conversation')">
<div class="conv-subject">
Payment issue with subscription
<span class="status-badge status-active">Active</span>
</div>
<div class="conv-meta">Last updated 2 hours ago Assigned to John Smith</div>
</div>
<div class="conversation-item" onclick="alert('Open conversation')">
<div class="conv-subject">
Feature request: Dark mode
<span class="status-badge status-closed">Closed</span>
</div>
<div class="conv-meta">Closed 3 days ago Assigned to Emily Davis</div>
</div>
<div class="conversation-item" onclick="alert('Open conversation')">
<div class="conv-subject">
Question about API limits
<span class="status-badge status-closed">Closed</span>
</div>
<div class="conv-meta">Closed 1 week ago Assigned to John Smith</div>
</div>
</div>
</div>
</body>
</html>
`,
};

85
src/apps/customer-grid.ts Normal file
View File

@ -0,0 +1,85 @@
export const customerGridApp = {
name: 'customer-grid',
description: 'Grid view of all customers with search and filtering',
content: `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Customers</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
.container { max-width: 1400px; margin: 0 auto; }
h1 { font-size: 28px; color: #1f2d3d; margin-bottom: 20px; }
.toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.search { flex: 1; max-width: 400px; }
.search input { width: 100%; padding: 10px 16px; border: 1px solid #d9dee4; border-radius: 6px; font-size: 14px; }
.btn { padding: 10px 20px; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; border: none; }
.btn-primary { background: #3197d6; color: white; }
.customer-grid { background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.grid-header { display: grid; grid-template-columns: 250px 200px 150px 1fr 120px; padding: 12px 16px; background: #f7f9fc; border-bottom: 1px solid #d9dee4; font-size: 12px; font-weight: 600; color: #6f7b8a; }
.grid-row { display: grid; grid-template-columns: 250px 200px 150px 1fr 120px; padding: 16px; border-bottom: 1px solid #f0f2f5; align-items: center; cursor: pointer; transition: background 0.2s; }
.grid-row:hover { background: #f7f9fc; }
.customer-name { font-weight: 500; color: #1f2d3d; }
.customer-email { font-size: 13px; color: #3197d6; margin-top: 2px; }
.customer-phone { font-size: 14px; color: #6f7b8a; }
.customer-org { font-size: 14px; color: #6f7b8a; }
.customer-location { font-size: 14px; color: #6f7b8a; }
.customer-count { font-size: 13px; color: #6f7b8a; text-align: center; }
</style>
</head>
<body>
<div class="container">
<h1>👥 Customers</h1>
<div class="toolbar">
<div class="search">
<input type="text" placeholder="Search customers by name, email, or organization...">
</div>
<button class="btn btn-primary">+ Add Customer</button>
</div>
<div class="customer-grid">
<div class="grid-header">
<div>NAME</div>
<div>EMAIL</div>
<div>PHONE</div>
<div>ORGANIZATION</div>
<div>CONVERSATIONS</div>
</div>
<div class="grid-row" onclick="alert('Open customer detail')">
<div>
<div class="customer-name">Sarah Chen</div>
<div class="customer-email">sarah.chen@example.com</div>
</div>
<div class="customer-email">sarah.chen@example.com</div>
<div class="customer-phone">+1 (555) 123-4567</div>
<div class="customer-org">Acme Corp</div>
<div class="customer-count">12 conversations</div>
</div>
<div class="grid-row" onclick="alert('Open customer detail')">
<div>
<div class="customer-name">Mike Johnson</div>
<div class="customer-email">mike.j@techstart.io</div>
</div>
<div class="customer-email">mike.j@techstart.io</div>
<div class="customer-phone">+1 (555) 987-6543</div>
<div class="customer-org">TechStart</div>
<div class="customer-count">8 conversations</div>
</div>
<div class="grid-row" onclick="alert('Open customer detail')">
<div>
<div class="customer-name">Emily Davis</div>
<div class="customer-email">emily@startup.com</div>
</div>
<div class="customer-email">emily@startup.com</div>
<div class="customer-phone">+1 (555) 456-7890</div>
<div class="customer-org">Startup Inc</div>
<div class="customer-count">5 conversations</div>
</div>
</div>
</div>
</body>
</html>
`,
};

116
src/apps/folder-browser.ts Normal file
View File

@ -0,0 +1,116 @@
export const folderBrowserApp = {
name: 'folder-browser',
description: 'Browse and manage mailbox folders',
content: `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Folder Browser</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
.container { max-width: 1200px; margin: 0 auto; display: grid; grid-template-columns: 300px 1fr; gap: 20px; }
.sidebar { background: white; border-radius: 8px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); height: fit-content; }
.main { background: white; border-radius: 8px; padding: 24px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.mailbox-section { margin-bottom: 24px; }
.mailbox-name { font-size: 14px; font-weight: 600; color: #6f7b8a; margin-bottom: 12px; text-transform: uppercase; }
.folder-list { display: flex; flex-direction: column; gap: 4px; }
.folder-item { padding: 10px 12px; border-radius: 6px; cursor: pointer; transition: background 0.2s; display: flex; justify-content: space-between; align-items: center; }
.folder-item:hover { background: #f7f9fc; }
.folder-item.active { background: #e0f2fe; color: #0369a1; font-weight: 500; }
.folder-name { font-size: 14px; display: flex; align-items: center; gap: 8px; }
.folder-count { font-size: 12px; padding: 2px 8px; border-radius: 10px; background: #e5e7eb; color: #4b5563; font-weight: 600; }
.folder-item.active .folder-count { background: #0369a1; color: white; }
.main-header { margin-bottom: 24px; }
.main-title { font-size: 24px; font-weight: 600; color: #1f2d3d; margin-bottom: 8px; }
.main-subtitle { font-size: 14px; color: #6f7b8a; }
.conv-list { display: flex; flex-direction: column; gap: 12px; }
.conv-item { padding: 16px; border: 1px solid #e5e7eb; border-radius: 6px; cursor: pointer; transition: all 0.2s; }
.conv-item:hover { border-color: #3197d6; background: #f7f9fc; }
.conv-subject { font-size: 15px; font-weight: 500; color: #1f2d3d; margin-bottom: 4px; }
.conv-meta { font-size: 13px; color: #6f7b8a; }
</style>
</head>
<body>
<div class="container">
<div class="sidebar">
<div class="mailbox-section">
<div class="mailbox-name">Support Mailbox</div>
<div class="folder-list">
<div class="folder-item active">
<span class="folder-name">📥 Unassigned</span>
<span class="folder-count">8</span>
</div>
<div class="folder-item">
<span class="folder-name">📋 My Conversations</span>
<span class="folder-count">23</span>
</div>
<div class="folder-item">
<span class="folder-name">📌 Drafts</span>
<span class="folder-count">2</span>
</div>
<div class="folder-item">
<span class="folder-name">👥 Assigned</span>
<span class="folder-count">47</span>
</div>
<div class="folder-item">
<span class="folder-name"> Closed</span>
<span class="folder-count">147</span>
</div>
<div class="folder-item">
<span class="folder-name">🚫 Spam</span>
<span class="folder-count">3</span>
</div>
</div>
</div>
<div class="mailbox-section">
<div class="mailbox-name">Sales Mailbox</div>
<div class="folder-list">
<div class="folder-item">
<span class="folder-name">📥 Unassigned</span>
<span class="folder-count">3</span>
</div>
<div class="folder-item">
<span class="folder-name">📋 My Conversations</span>
<span class="folder-count">12</span>
</div>
<div class="folder-item">
<span class="folder-name"> Closed</span>
<span class="folder-count">89</span>
</div>
</div>
</div>
</div>
<div class="main">
<div class="main-header">
<div class="main-title">📥 Unassigned</div>
<div class="main-subtitle">8 conversations need to be assigned</div>
</div>
<div class="conv-list">
<div class="conv-item" onclick="alert('Open conversation')">
<div class="conv-subject">Payment issue with subscription</div>
<div class="conv-meta">Sarah Chen 2 hours ago #1247</div>
</div>
<div class="conv-item" onclick="alert('Open conversation')">
<div class="conv-subject">Login problems</div>
<div class="conv-meta">Alex Brown 1 day ago #1245</div>
</div>
<div class="conv-item" onclick="alert('Open conversation')">
<div class="conv-subject">Question about API limits</div>
<div class="conv-meta">Emily Davis 1 day ago #1244</div>
</div>
<div class="conv-item" onclick="alert('Open conversation')">
<div class="conv-subject">Account upgrade request</div>
<div class="conv-meta">Mike Johnson 2 days ago #1238</div>
</div>
</div>
</div>
</div>
</body>
</html>
`,
};

View File

@ -0,0 +1,93 @@
export const happinessReportApp = {
name: 'happiness-report',
description: 'Customer satisfaction and happiness metrics',
content: `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Happiness Report</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
.container { max-width: 1200px; margin: 0 auto; }
h1 { font-size: 28px; color: #1f2d3d; margin-bottom: 8px; }
.subtitle { font-size: 14px; color: #6f7b8a; margin-bottom: 24px; }
.hero-score { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 40px; border-radius: 12px; text-align: center; margin-bottom: 24px; }
.score-value { font-size: 72px; font-weight: bold; margin-bottom: 8px; }
.score-label { font-size: 16px; opacity: 0.9; }
.score-change { font-size: 14px; margin-top: 8px; }
.score-change.positive { color: #a7f3d0; }
.metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 24px; }
.metric-card { background: white; padding: 24px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.metric-value { font-size: 32px; font-weight: bold; margin-bottom: 4px; }
.metric-label { font-size: 13px; color: #6f7b8a; }
.metric-great { color: #10b981; }
.metric-okay { color: #f59e0b; }
.metric-bad { color: #ef4444; }
.chart-section { background: white; padding: 24px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 24px; }
.chart-title { font-size: 18px; font-weight: 600; color: #1f2d3d; margin-bottom: 16px; }
.bar-chart { display: flex; flex-direction: column; gap: 12px; }
.bar-item { display: flex; align-items: center; gap: 12px; }
.bar-label { width: 100px; font-size: 14px; color: #6f7b8a; }
.bar-container { flex: 1; background: #f0f2f5; border-radius: 4px; height: 32px; position: relative; overflow: hidden; }
.bar-fill { height: 100%; border-radius: 4px; display: flex; align-items: center; padding: 0 12px; font-size: 13px; font-weight: 600; color: white; }
.bar-great { background: #10b981; }
.bar-okay { background: #f59e0b; }
.bar-bad { background: #ef4444; }
</style>
</head>
<body>
<div class="container">
<h1>😊 Happiness Report</h1>
<div class="subtitle">Customer satisfaction metrics for last 30 days</div>
<div class="hero-score">
<div class="score-value">94%</div>
<div class="score-label">Overall Happiness Score</div>
<div class="score-change positive"> 3% from previous period</div>
</div>
<div class="metrics">
<div class="metric-card">
<div class="metric-value metric-great">237</div>
<div class="metric-label">😊 Great Ratings</div>
</div>
<div class="metric-card">
<div class="metric-value metric-okay">18</div>
<div class="metric-label">😐 Okay Ratings</div>
</div>
<div class="metric-card">
<div class="metric-value metric-bad">7</div>
<div class="metric-label">😞 Not Good Ratings</div>
</div>
</div>
<div class="chart-section">
<div class="chart-title">Ratings Distribution</div>
<div class="bar-chart">
<div class="bar-item">
<div class="bar-label">Great</div>
<div class="bar-container">
<div class="bar-fill bar-great" style="width: 90%;">237 (90%)</div>
</div>
</div>
<div class="bar-item">
<div class="bar-label">Okay</div>
<div class="bar-container">
<div class="bar-fill bar-okay" style="width: 7%;">18 (7%)</div>
</div>
</div>
<div class="bar-item">
<div class="bar-label">Not Good</div>
<div class="bar-container">
<div class="bar-fill bar-bad" style="width: 3%;">7 (3%)</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
`,
};

View File

@ -0,0 +1,99 @@
export const mailboxOverviewApp = {
name: 'mailbox-overview',
description: 'Overview of all mailboxes with folder counts and activity',
content: `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Mailbox Overview</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
.container { max-width: 1200px; margin: 0 auto; }
h1 { font-size: 28px; color: #1f2d3d; margin-bottom: 20px; }
.mailboxes { display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 20px; }
.mailbox-card { background: white; border-radius: 8px; padding: 24px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); cursor: pointer; transition: transform 0.2s; }
.mailbox-card:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0,0,0,0.15); }
.mailbox-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.mailbox-name { font-size: 18px; font-weight: 600; color: #1f2d3d; }
.mailbox-email { font-size: 13px; color: #6f7b8a; margin-bottom: 20px; }
.folder-list { display: flex; flex-direction: column; gap: 12px; }
.folder-item { display: flex; justify-content: space-between; align-items: center; padding: 8px; border-radius: 4px; background: #f7f9fc; }
.folder-name { font-size: 14px; color: #3e4c59; }
.folder-count { font-size: 14px; font-weight: 600; color: #3197d6; }
</style>
</head>
<body>
<div class="container">
<h1>📮 Mailboxes</h1>
<div class="mailboxes">
<div class="mailbox-card" onclick="alert('Open mailbox detail')">
<div class="mailbox-header">
<div class="mailbox-name">Support</div>
<div style="font-size: 24px;">💬</div>
</div>
<div class="mailbox-email">support@company.com</div>
<div class="folder-list">
<div class="folder-item">
<span class="folder-name">📥 Unassigned</span>
<span class="folder-count">8</span>
</div>
<div class="folder-item">
<span class="folder-name">📋 My Conversations</span>
<span class="folder-count">23</span>
</div>
<div class="folder-item">
<span class="folder-name"> Closed</span>
<span class="folder-count">147</span>
</div>
</div>
</div>
<div class="mailbox-card" onclick="alert('Open mailbox detail')">
<div class="mailbox-header">
<div class="mailbox-name">Sales</div>
<div style="font-size: 24px;">💰</div>
</div>
<div class="mailbox-email">sales@company.com</div>
<div class="folder-list">
<div class="folder-item">
<span class="folder-name">📥 Unassigned</span>
<span class="folder-count">3</span>
</div>
<div class="folder-item">
<span class="folder-name">📋 My Conversations</span>
<span class="folder-count">12</span>
</div>
<div class="folder-item">
<span class="folder-name"> Closed</span>
<span class="folder-count">89</span>
</div>
</div>
</div>
<div class="mailbox-card" onclick="alert('Open mailbox detail')">
<div class="mailbox-header">
<div class="mailbox-name">Billing</div>
<div style="font-size: 24px;">💳</div>
</div>
<div class="mailbox-email">billing@company.com</div>
<div class="folder-list">
<div class="folder-item">
<span class="folder-name">📥 Unassigned</span>
<span class="folder-count">2</span>
</div>
<div class="folder-item">
<span class="folder-name">📋 My Conversations</span>
<span class="folder-count">7</span>
</div>
<div class="folder-item">
<span class="folder-name"> Closed</span>
<span class="folder-count">64</span>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
`,
};

View File

@ -0,0 +1,105 @@
export const productivityReportApp = {
name: 'productivity-report',
description: 'Team productivity metrics and response times',
content: `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Productivity Report</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
.container { max-width: 1200px; margin: 0 auto; }
h1 { font-size: 28px; color: #1f2d3d; margin-bottom: 8px; }
.subtitle { font-size: 14px; color: #6f7b8a; margin-bottom: 24px; }
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; margin-bottom: 24px; }
.stat-card { background: white; padding: 24px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.stat-value { font-size: 36px; font-weight: bold; color: #3197d6; margin-bottom: 4px; }
.stat-label { font-size: 14px; color: #6f7b8a; }
.stat-change { font-size: 12px; margin-top: 4px; }
.stat-change.positive { color: #10b981; }
.stat-change.negative { color: #ef4444; }
.chart-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.chart-section { background: white; padding: 24px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.chart-title { font-size: 18px; font-weight: 600; color: #1f2d3d; margin-bottom: 16px; }
.metric-item { display: flex; justify-content: space-between; padding: 12px 0; border-bottom: 1px solid #f0f2f5; }
.metric-item:last-child { border-bottom: none; }
.metric-name { font-size: 14px; color: #3e4c59; }
.metric-val { font-size: 14px; font-weight: 600; color: #1f2d3d; }
</style>
</head>
<body>
<div class="container">
<h1>📊 Productivity Report</h1>
<div class="subtitle">Team performance metrics for last 30 days</div>
<div class="stats">
<div class="stat-card">
<div class="stat-value">1,247</div>
<div class="stat-label">Replies Sent</div>
<div class="stat-change positive"> 12% from last month</div>
</div>
<div class="stat-card">
<div class="stat-value">3.2h</div>
<div class="stat-label">Avg First Response Time</div>
<div class="stat-change positive"> 0.5h from last month</div>
</div>
<div class="stat-card">
<div class="stat-value">8.4h</div>
<div class="stat-label">Avg Resolution Time</div>
<div class="stat-change negative"> 1.2h from last month</div>
</div>
<div class="stat-card">
<div class="stat-value">892</div>
<div class="stat-label">Resolved Conversations</div>
<div class="stat-change positive"> 8% from last month</div>
</div>
</div>
<div class="chart-grid">
<div class="chart-section">
<div class="chart-title">Top Performers (Replies)</div>
<div class="metric-item">
<span class="metric-name">🥇 John Smith</span>
<span class="metric-val">324 replies</span>
</div>
<div class="metric-item">
<span class="metric-name">🥈 Emily Davis</span>
<span class="metric-val">287 replies</span>
</div>
<div class="metric-item">
<span class="metric-name">🥉 Mike Johnson</span>
<span class="metric-val">219 replies</span>
</div>
<div class="metric-item">
<span class="metric-name">Sarah Wilson</span>
<span class="metric-val">198 replies</span>
</div>
</div>
<div class="chart-section">
<div class="chart-title">Response Time by Agent</div>
<div class="metric-item">
<span class="metric-name">John Smith</span>
<span class="metric-val">2.8h</span>
</div>
<div class="metric-item">
<span class="metric-name">Emily Davis</span>
<span class="metric-val">3.1h</span>
</div>
<div class="metric-item">
<span class="metric-name">Mike Johnson</span>
<span class="metric-val">3.5h</span>
</div>
<div class="metric-item">
<span class="metric-name">Sarah Wilson</span>
<span class="metric-val">4.2h</span>
</div>
</div>
</div>
</div>
</body>
</html>
`,
};

101
src/apps/saved-replies.ts Normal file
View File

@ -0,0 +1,101 @@
export const savedRepliesApp = {
name: 'saved-replies',
description: 'Manage and browse saved reply templates',
content: `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Saved Replies</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
.container { max-width: 1200px; margin: 0 auto; }
h1 { font-size: 28px; color: #1f2d3d; margin-bottom: 20px; }
.toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.search { flex: 1; max-width: 400px; }
.search input { width: 100%; padding: 10px 16px; border: 1px solid #d9dee4; border-radius: 6px; font-size: 14px; }
.btn { padding: 10px 20px; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; border: none; background: #3197d6; color: white; }
.replies-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 16px; }
.reply-card { background: white; border-radius: 8px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); cursor: pointer; transition: transform 0.2s; }
.reply-card:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0,0,0,0.15); }
.reply-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 12px; }
.reply-name { font-size: 16px; font-weight: 600; color: #1f2d3d; }
.reply-actions { display: flex; gap: 8px; }
.icon-btn { padding: 4px 8px; border-radius: 4px; border: 1px solid #d9dee4; background: white; cursor: pointer; font-size: 14px; }
.icon-btn:hover { background: #f7f9fc; }
.reply-preview { font-size: 14px; color: #6f7b8a; line-height: 1.5; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; margin-bottom: 12px; }
.reply-meta { font-size: 12px; color: #9ca3af; }
</style>
</head>
<body>
<div class="container">
<h1>💬 Saved Replies</h1>
<div class="toolbar">
<div class="search">
<input type="text" placeholder="Search saved replies...">
</div>
<button class="btn">+ Create Reply</button>
</div>
<div class="replies-grid">
<div class="reply-card" onclick="alert('Edit reply')">
<div class="reply-header">
<div class="reply-name">Welcome Message</div>
<div class="reply-actions">
<button class="icon-btn"></button>
<button class="icon-btn">🗑</button>
</div>
</div>
<div class="reply-preview">
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?
</div>
<div class="reply-meta">Support mailbox Used 47 times</div>
</div>
<div class="reply-card" onclick="alert('Edit reply')">
<div class="reply-header">
<div class="reply-name">Billing Issue Response</div>
<div class="reply-actions">
<button class="icon-btn"></button>
<button class="icon-btn">🗑</button>
</div>
</div>
<div class="reply-preview">
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.
</div>
<div class="reply-meta">Billing mailbox Used 23 times</div>
</div>
<div class="reply-card" onclick="alert('Edit reply')">
<div class="reply-header">
<div class="reply-name">Feature Request Acknowledgment</div>
<div class="reply-actions">
<button class="icon-btn"></button>
<button class="icon-btn">🗑</button>
</div>
</div>
<div class="reply-preview">
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.
</div>
<div class="reply-meta">All mailboxes Used 34 times</div>
</div>
<div class="reply-card" onclick="alert('Edit reply')">
<div class="reply-header">
<div class="reply-name">Account Access Help</div>
<div class="reply-actions">
<button class="icon-btn"></button>
<button class="icon-btn">🗑</button>
</div>
</div>
<div class="reply-preview">
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.
</div>
<div class="reply-meta">Support mailbox Used 89 times</div>
</div>
</div>
</div>
</body>
</html>
`,
};

View File

@ -0,0 +1,88 @@
export const searchResultsApp = {
name: 'search-results',
description: 'Search results interface for finding conversations and customers',
content: `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Search Results</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
.container { max-width: 1000px; margin: 0 auto; }
.search-bar { background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.search-bar input { width: 100%; padding: 12px 16px; border: 2px solid #3197d6; border-radius: 6px; font-size: 16px; }
.results-header { font-size: 14px; color: #6f7b8a; margin-bottom: 16px; }
.results-count { font-weight: 600; color: #1f2d3d; }
.tabs { display: flex; gap: 8px; margin-bottom: 20px; }
.tab { padding: 10px 20px; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; border: 1px solid #d9dee4; background: white; }
.tab.active { background: #3197d6; color: white; border-color: #3197d6; }
.result-item { background: white; padding: 20px; border-radius: 8px; margin-bottom: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); cursor: pointer; transition: transform 0.2s; }
.result-item:hover { transform: translateX(4px); box-shadow: 0 2px 6px rgba(0,0,0,0.15); }
.result-type { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; text-transform: uppercase; margin-bottom: 8px; }
.type-conversation { background: #e0f2fe; color: #0369a1; }
.type-customer { background: #fef3c7; color: #92400e; }
.result-title { font-size: 16px; font-weight: 600; color: #1f2d3d; margin-bottom: 8px; }
.result-snippet { font-size: 14px; color: #6f7b8a; line-height: 1.5; margin-bottom: 8px; }
.highlight { background: #fef08a; padding: 2px 4px; border-radius: 2px; }
.result-meta { font-size: 12px; color: #9ca3af; }
</style>
</head>
<body>
<div class="container">
<div class="search-bar">
<input type="text" placeholder="Search conversations, customers, and more..." value="payment issue">
</div>
<div class="results-header">
Found <span class="results-count">12 results</span> for "payment issue"
</div>
<div class="tabs">
<button class="tab active">All (12)</button>
<button class="tab">Conversations (8)</button>
<button class="tab">Customers (3)</button>
<button class="tab">Articles (1)</button>
</div>
<div class="result-item" onclick="alert('Open conversation')">
<span class="result-type type-conversation">Conversation #1247</span>
<div class="result-title">Payment issue with subscription</div>
<div class="result-snippet">
Customer having trouble with their credit card. Error appears when trying to update <span class="highlight">payment</span> method. The <span class="highlight">issue</span> started yesterday...
</div>
<div class="result-meta">Sarah Chen Active 2 hours ago</div>
</div>
<div class="result-item" onclick="alert('Open conversation')">
<span class="result-type type-conversation">Conversation #1189</span>
<div class="result-title">Billing problem - double charge</div>
<div class="result-snippet">
I was charged twice for my subscription this month. Can you help resolve this <span class="highlight">payment issue</span>?
</div>
<div class="result-meta">Mike Johnson Closed 3 days ago</div>
</div>
<div class="result-item" onclick="alert('Open customer')">
<span class="result-type type-customer">Customer</span>
<div class="result-title">Sarah Chen</div>
<div class="result-snippet">
Background: VIP customer, frequently reports <span class="highlight">payment issues</span>. Works at Acme Corp as Product Manager.
</div>
<div class="result-meta">sarah.chen@example.com 12 conversations</div>
</div>
<div class="result-item" onclick="alert('Open conversation')">
<span class="result-type type-conversation">Conversation #1156</span>
<div class="result-title">Can't complete checkout</div>
<div class="result-snippet">
Checkout process fails at the <span class="highlight">payment</span> step. Tried multiple cards, same <span class="highlight">issue</span>.
</div>
<div class="result-meta">Alex Brown Closed 1 week ago</div>
</div>
</div>
</body>
</html>
`,
};

97
src/apps/tag-manager.ts Normal file
View File

@ -0,0 +1,97 @@
export const tagManagerApp = {
name: 'tag-manager',
description: 'Manage tags with usage stats and color coding',
content: `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Tag Manager</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
.container { max-width: 1000px; margin: 0 auto; }
h1 { font-size: 28px; color: #1f2d3d; margin-bottom: 20px; }
.toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.btn { padding: 10px 20px; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; border: none; background: #3197d6; color: white; }
.tags-grid { background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.grid-header { display: grid; grid-template-columns: 1fr 100px 150px 120px; padding: 12px 16px; background: #f7f9fc; border-bottom: 1px solid #d9dee4; font-size: 12px; font-weight: 600; color: #6f7b8a; }
.grid-row { display: grid; grid-template-columns: 1fr 100px 150px 120px; padding: 16px; border-bottom: 1px solid #f0f2f5; align-items: center; }
.tag-display { display: inline-flex; align-items: center; gap: 8px; }
.tag-color { width: 16px; height: 16px; border-radius: 3px; }
.tag-name { font-size: 14px; font-weight: 500; color: #1f2d3d; }
.tag-count { font-size: 14px; color: #6f7b8a; text-align: center; }
.tag-created { font-size: 13px; color: #6f7b8a; }
.tag-actions { display: flex; gap: 8px; justify-content: flex-end; }
.action-btn { padding: 6px 12px; font-size: 12px; border-radius: 4px; cursor: pointer; border: 1px solid #d9dee4; background: white; }
.action-btn:hover { background: #f7f9fc; }
</style>
</head>
<body>
<div class="container">
<h1>🏷 Tag Manager</h1>
<div class="toolbar">
<div style="font-size: 14px; color: #6f7b8a;">Managing 12 tags</div>
<button class="btn">+ Create Tag</button>
</div>
<div class="tags-grid">
<div class="grid-header">
<div>TAG NAME</div>
<div style="text-align: center;">USAGE</div>
<div>CREATED</div>
<div style="text-align: right;">ACTIONS</div>
</div>
<div class="grid-row">
<div class="tag-display">
<div class="tag-color" style="background: #ef4444;"></div>
<span class="tag-name">urgent</span>
</div>
<div class="tag-count">23 conversations</div>
<div class="tag-created">Jan 2024</div>
<div class="tag-actions">
<button class="action-btn">Edit</button>
<button class="action-btn" style="color: #ef4444;">Delete</button>
</div>
</div>
<div class="grid-row">
<div class="tag-display">
<div class="tag-color" style="background: #3b82f6;"></div>
<span class="tag-name">billing</span>
</div>
<div class="tag-count">47 conversations</div>
<div class="tag-created">Dec 2023</div>
<div class="tag-actions">
<button class="action-btn">Edit</button>
<button class="action-btn" style="color: #ef4444;">Delete</button>
</div>
</div>
<div class="grid-row">
<div class="tag-display">
<div class="tag-color" style="background: #10b981;"></div>
<span class="tag-name">feature-request</span>
</div>
<div class="tag-count">31 conversations</div>
<div class="tag-created">Nov 2023</div>
<div class="tag-actions">
<button class="action-btn">Edit</button>
<button class="action-btn" style="color: #ef4444;">Delete</button>
</div>
</div>
<div class="grid-row">
<div class="tag-display">
<div class="tag-color" style="background: #f59e0b;"></div>
<span class="tag-name">bug</span>
</div>
<div class="tag-count">18 conversations</div>
<div class="tag-created">Oct 2023</div>
<div class="tag-actions">
<button class="action-btn">Edit</button>
<button class="action-btn" style="color: #ef4444;">Delete</button>
</div>
</div>
</div>
</div>
</body>
</html>
`,
};

104
src/apps/team-overview.ts Normal file
View File

@ -0,0 +1,104 @@
export const teamOverviewApp = {
name: 'team-overview',
description: 'Team structure and member statistics',
content: `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Teams</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
.container { max-width: 1200px; margin: 0 auto; }
h1 { font-size: 28px; color: #1f2d3d; margin-bottom: 20px; }
.teams { display: grid; gap: 20px; }
.team-card { background: white; border-radius: 8px; padding: 24px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.team-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.team-name { font-size: 20px; font-weight: 600; color: #1f2d3d; }
.team-count { font-size: 14px; color: #6f7b8a; }
.members-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 16px; }
.member-card { padding: 16px; border: 1px solid #e5e7eb; border-radius: 6px; display: flex; align-items: center; gap: 12px; }
.avatar { width: 48px; height: 48px; border-radius: 50%; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; color: white; font-weight: 600; font-size: 16px; }
.member-info { flex: 1; }
.member-name { font-size: 14px; font-weight: 500; color: #1f2d3d; margin-bottom: 2px; }
.member-email { font-size: 12px; color: #6f7b8a; }
.member-role { font-size: 11px; color: #9ca3af; margin-top: 2px; }
</style>
</head>
<body>
<div class="container">
<h1>👥 Teams</h1>
<div class="teams">
<div class="team-card">
<div class="team-header">
<div class="team-name">Support Team</div>
<div class="team-count">6 members</div>
</div>
<div class="members-grid">
<div class="member-card">
<div class="avatar" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">JS</div>
<div class="member-info">
<div class="member-name">John Smith</div>
<div class="member-email">john@company.com</div>
<div class="member-role">Team Lead</div>
</div>
</div>
<div class="member-card">
<div class="avatar" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">ED</div>
<div class="member-info">
<div class="member-name">Emily Davis</div>
<div class="member-email">emily@company.com</div>
<div class="member-role">Senior Agent</div>
</div>
</div>
<div class="member-card">
<div class="avatar" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);">MJ</div>
<div class="member-info">
<div class="member-name">Mike Johnson</div>
<div class="member-email">mike@company.com</div>
<div class="member-role">Agent</div>
</div>
</div>
<div class="member-card">
<div class="avatar" style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);">SW</div>
<div class="member-info">
<div class="member-name">Sarah Wilson</div>
<div class="member-email">sarah@company.com</div>
<div class="member-role">Agent</div>
</div>
</div>
</div>
</div>
<div class="team-card">
<div class="team-header">
<div class="team-name">Sales Team</div>
<div class="team-count">4 members</div>
</div>
<div class="members-grid">
<div class="member-card">
<div class="avatar" style="background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);">AB</div>
<div class="member-info">
<div class="member-name">Alex Brown</div>
<div class="member-email">alex@company.com</div>
<div class="member-role">Sales Lead</div>
</div>
</div>
<div class="member-card">
<div class="avatar" style="background: linear-gradient(135deg, #30cfd0 0%, #330867 100%);">LG</div>
<div class="member-info">
<div class="member-name">Lisa Garcia</div>
<div class="member-email">lisa@company.com</div>
<div class="member-role">Sales Rep</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
`,
};

86
src/apps/user-stats.ts Normal file
View File

@ -0,0 +1,86 @@
export const userStatsApp = {
name: 'user-stats',
description: 'Individual user performance statistics and metrics',
content: `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>User Statistics</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
.container { max-width: 1000px; margin: 0 auto; }
.profile { background: white; border-radius: 8px; padding: 32px; text-align: center; margin-bottom: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.avatar { width: 100px; height: 100px; border-radius: 50%; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 40px; font-weight: bold; margin: 0 auto 16px; }
.user-name { font-size: 24px; font-weight: 600; color: #1f2d3d; margin-bottom: 4px; }
.user-role { font-size: 14px; color: #6f7b8a; margin-bottom: 4px; }
.user-email { font-size: 13px; color: #9ca3af; }
.stats-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 20px; }
.stat-card { background: white; padding: 24px; border-radius: 8px; text-align: center; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.stat-value { font-size: 36px; font-weight: bold; color: #3197d6; margin-bottom: 8px; }
.stat-label { font-size: 13px; color: #6f7b8a; }
.metrics-section { background: white; border-radius: 8px; padding: 24px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.metrics-title { font-size: 18px; font-weight: 600; color: #1f2d3d; margin-bottom: 16px; }
.metric-row { display: flex; justify-content: space-between; padding: 12px 0; border-bottom: 1px solid #f0f2f5; }
.metric-row:last-child { border-bottom: none; }
.metric-label { font-size: 14px; color: #6f7b8a; }
.metric-value { font-size: 14px; font-weight: 600; color: #1f2d3d; }
</style>
</head>
<body>
<div class="container">
<div class="profile">
<div class="avatar">JS</div>
<div class="user-name">John Smith</div>
<div class="user-role">Support Team Lead</div>
<div class="user-email">john.smith@company.com</div>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value">324</div>
<div class="stat-label">Replies Sent (30d)</div>
</div>
<div class="stat-card">
<div class="stat-value">97%</div>
<div class="stat-label">Happiness Score</div>
</div>
<div class="stat-card">
<div class="stat-value">2.8h</div>
<div class="stat-label">Avg Response Time</div>
</div>
</div>
<div class="metrics-section">
<div class="metrics-title">Performance Metrics (Last 30 Days)</div>
<div class="metric-row">
<span class="metric-label">Conversations Created</span>
<span class="metric-value">89</span>
</div>
<div class="metric-row">
<span class="metric-label">Conversations Closed</span>
<span class="metric-value">147</span>
</div>
<div class="metric-row">
<span class="metric-label">Average Resolution Time</span>
<span class="metric-value">7.2 hours</span>
</div>
<div class="metric-row">
<span class="metric-label">First Response Time</span>
<span class="metric-value">2.8 hours</span>
</div>
<div class="metric-row">
<span class="metric-label">Active Conversations</span>
<span class="metric-value">23</span>
</div>
<div class="metric-row">
<span class="metric-label">Customer Ratings</span>
<span class="metric-value">72 ratings (97% positive)</span>
</div>
</div>
</div>
</body>
</html>
`,
};

View File

@ -0,0 +1,128 @@
export const workflowDashboardApp = {
name: 'workflow-dashboard',
description: 'Dashboard for workflows with activation status and stats',
content: `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Workflows</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
.container { max-width: 1200px; margin: 0 auto; }
h1 { font-size: 28px; color: #1f2d3d; margin-bottom: 20px; }
.workflows { background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.workflow-item { padding: 20px; border-bottom: 1px solid #f0f2f5; display: flex; justify-content: between; align-items: center; gap: 16px; }
.workflow-info { flex: 1; }
.workflow-name { font-size: 16px; font-weight: 600; color: #1f2d3d; margin-bottom: 4px; }
.workflow-meta { font-size: 13px; color: #6f7b8a; }
.workflow-type { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 12px; font-weight: 500; background: #e0e7ff; color: #3730a3; margin-right: 8px; }
.workflow-stats { display: flex; gap: 24px; align-items: center; }
.stat { text-align: center; }
.stat-value { font-size: 20px; font-weight: 600; color: #1f2d3d; }
.stat-label { font-size: 11px; color: #6f7b8a; text-transform: uppercase; }
.toggle { width: 50px; height: 26px; background: #d1d5db; border-radius: 13px; position: relative; cursor: pointer; transition: background 0.3s; }
.toggle.active { background: #10b981; }
.toggle-knob { width: 22px; height: 22px; background: white; border-radius: 50%; position: absolute; top: 2px; left: 2px; transition: left 0.3s; box-shadow: 0 1px 3px rgba(0,0,0,0.3); }
.toggle.active .toggle-knob { left: 26px; }
</style>
</head>
<body>
<div class="container">
<h1> Workflows</h1>
<div class="workflows">
<div class="workflow-item">
<div class="workflow-info">
<div class="workflow-name">
<span class="workflow-type">Automatic</span>
Auto-tag billing conversations
</div>
<div class="workflow-meta">Support mailbox Modified 2 weeks ago</div>
</div>
<div class="workflow-stats">
<div class="stat">
<div class="stat-value">247</div>
<div class="stat-label">Total Runs</div>
</div>
<div class="stat">
<div class="stat-value">245</div>
<div class="stat-label">Successful</div>
</div>
</div>
<div class="toggle active" onclick="this.classList.toggle('active')">
<div class="toggle-knob"></div>
</div>
</div>
<div class="workflow-item">
<div class="workflow-info">
<div class="workflow-name">
<span class="workflow-type" style="background: #fef3c7; color: #92400e;">Manual</span>
Escalate to senior support
</div>
<div class="workflow-meta">Support mailbox Modified 1 month ago</div>
</div>
<div class="workflow-stats">
<div class="stat">
<div class="stat-value">34</div>
<div class="stat-label">Total Runs</div>
</div>
<div class="stat">
<div class="stat-value">34</div>
<div class="stat-label">Successful</div>
</div>
</div>
<div class="toggle active" onclick="this.classList.toggle('active')">
<div class="toggle-knob"></div>
</div>
</div>
<div class="workflow-item">
<div class="workflow-info">
<div class="workflow-name">
<span class="workflow-type">Automatic</span>
Close spam conversations
</div>
<div class="workflow-meta">All mailboxes Modified 3 days ago</div>
</div>
<div class="workflow-stats">
<div class="stat">
<div class="stat-value">89</div>
<div class="stat-label">Total Runs</div>
</div>
<div class="stat">
<div class="stat-value">89</div>
<div class="stat-label">Successful</div>
</div>
</div>
<div class="toggle active" onclick="this.classList.toggle('active')">
<div class="toggle-knob"></div>
</div>
</div>
<div class="workflow-item">
<div class="workflow-info">
<div class="workflow-name">
<span class="workflow-type">Automatic</span>
Send satisfaction survey
</div>
<div class="workflow-meta">Support mailbox Modified 1 week ago</div>
</div>
<div class="workflow-stats">
<div class="stat">
<div class="stat-value">156</div>
<div class="stat-label">Total Runs</div>
</div>
<div class="stat">
<div class="stat-value">154</div>
<div class="stat-label">Successful</div>
</div>
</div>
<div class="toggle" onclick="this.classList.toggle('active')">
<div class="toggle-knob"></div>
</div>
</div>
</div>
</div>
</body>
</html>
`,
};

118
src/apps/workflow-detail.ts Normal file
View File

@ -0,0 +1,118 @@
export const workflowDetailApp = {
name: 'workflow-detail',
description: 'Detailed view of workflow configuration and execution history',
content: `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Workflow Detail</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f7f9fc; padding: 20px; }
.container { max-width: 1000px; margin: 0 auto; }
.header { background: white; padding: 24px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); display: flex; justify-content: space-between; align-items: center; }
.workflow-info { flex: 1; }
.workflow-name { font-size: 24px; font-weight: 600; color: #1f2d3d; margin-bottom: 8px; }
.workflow-meta { font-size: 14px; color: #6f7b8a; }
.workflow-type { display: inline-block; padding: 4px 10px; border-radius: 4px; font-size: 12px; font-weight: 500; background: #e0e7ff; color: #3730a3; margin-right: 8px; }
.toggle { width: 50px; height: 26px; background: #10b981; border-radius: 13px; position: relative; cursor: pointer; }
.toggle-knob { width: 22px; height: 22px; background: white; border-radius: 50%; position: absolute; top: 2px; left: 26px; box-shadow: 0 1px 3px rgba(0,0,0,0.3); }
.stats-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 20px; }
.stat-card { background: white; padding: 20px; border-radius: 8px; text-align: center; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.stat-value { font-size: 32px; font-weight: bold; color: #3197d6; margin-bottom: 4px; }
.stat-label { font-size: 13px; color: #6f7b8a; }
.section { background: white; padding: 24px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.section-title { font-size: 18px; font-weight: 600; color: #1f2d3d; margin-bottom: 16px; }
.condition-item { padding: 16px; background: #f7f9fc; border-radius: 6px; margin-bottom: 12px; border-left: 4px solid #3197d6; }
.condition-label { font-size: 12px; font-weight: 600; color: #6f7b8a; margin-bottom: 4px; text-transform: uppercase; }
.condition-value { font-size: 14px; color: #1f2d3d; }
.action-item { padding: 16px; background: #f0fdf4; border-radius: 6px; margin-bottom: 12px; border-left: 4px solid #10b981; }
.action-label { font-size: 12px; font-weight: 600; color: #166534; margin-bottom: 4px; text-transform: uppercase; }
.action-value { font-size: 14px; color: #166534; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="workflow-info">
<div class="workflow-name">
<span class="workflow-type">Automatic</span>
Auto-tag billing conversations
</div>
<div class="workflow-meta">Support mailbox Modified 2 weeks ago</div>
</div>
<div class="toggle">
<div class="toggle-knob"></div>
</div>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value">247</div>
<div class="stat-label">Total Runs</div>
</div>
<div class="stat-card">
<div class="stat-value">245</div>
<div class="stat-label">Successful</div>
</div>
<div class="stat-card">
<div class="stat-value">2</div>
<div class="stat-label">Failed</div>
</div>
</div>
<div class="section">
<div class="section-title"> Conditions (When to run)</div>
<div class="condition-item">
<div class="condition-label">Trigger</div>
<div class="condition-value">Conversation is created</div>
</div>
<div class="condition-item">
<div class="condition-label">Subject contains</div>
<div class="condition-value">payment, billing, invoice, subscription</div>
</div>
<div class="condition-item">
<div class="condition-label">Mailbox</div>
<div class="condition-value">Support</div>
</div>
</div>
<div class="section">
<div class="section-title">🎯 Actions (What to do)</div>
<div class="action-item">
<div class="action-label">Add Tags</div>
<div class="action-value">billing</div>
</div>
<div class="action-item">
<div class="action-label">Assign To</div>
<div class="action-value">Billing Team</div>
</div>
<div class="action-item">
<div class="action-label">Set Priority</div>
<div class="action-value">High</div>
</div>
</div>
<div class="section">
<div class="section-title">📊 Recent Executions</div>
<div style="padding: 16px; border: 1px solid #e5e7eb; border-radius: 6px; margin-bottom: 8px; display: flex; justify-content: space-between; align-items: center;">
<div>
<div style="font-weight: 500; color: #1f2d3d;">Conversation #1247: Payment issue</div>
<div style="font-size: 13px; color: #6f7b8a;">2 hours ago</div>
</div>
<div style="color: #10b981; font-weight: 600;"> Success</div>
</div>
<div style="padding: 16px; border: 1px solid #e5e7eb; border-radius: 6px; margin-bottom: 8px; display: flex; justify-content: space-between; align-items: center;">
<div>
<div style="font-weight: 500; color: #1f2d3d;">Conversation #1189: Billing problem</div>
<div style="font-size: 13px; color: #6f7b8a;">3 days ago</div>
</div>
<div style="color: #10b981; font-weight: 600;"> Success</div>
</div>
</div>
</div>
</body>
</html>
`,
};

11
src/main.ts Normal file
View File

@ -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);
});

186
src/server.ts Normal file
View File

@ -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`);
}

View File

@ -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<Conversation>(
'/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<Conversation>(`/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<Thread>(
`/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' };
},
},
];
}

View File

@ -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<Customer>(
'/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<Customer>(`/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<CustomerEmail>(
`/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<CustomerPhone>(
`/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<CustomerAddress>(
`/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<any>(
`/customers/${args.customerId}/properties`
);
return properties;
},
},
];
}

View File

@ -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<Mailbox>(
'/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<Mailbox>(`/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<Folder>(
`/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<Folder>(
`/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<CustomField>(
`/mailboxes/${args.mailboxId}/fields`,
{},
'fields'
);
return { fields, count: fields.length };
},
},
];
}

View File

@ -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<CompanyReport>(
'/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<ConversationReport>(
'/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<HappinessReport>(
'/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<ProductivityReport>(
'/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<UserReport>(
`/reports/user/${args.userId}`,
{
start: args.start,
end: args.end,
previousStart: args.previousStart,
previousEnd: args.previousEnd,
mailboxes: args.mailboxes,
tags: args.tags,
}
);
return report;
},
},
];
}

View File

@ -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<SavedReply>(
'/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<SavedReply>(`/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' };
},
},
];
}

77
src/tools/tags-tools.ts Normal file
View File

@ -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<Tag>(
'/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' };
},
},
];
}

60
src/tools/teams-tools.ts Normal file
View File

@ -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<Team>(
'/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<Team>(`/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<User>(
`/teams/${args.teamId}/members`,
{ page: args.page },
'members'
);
return { members, count: members.length };
},
},
];
}

53
src/tools/users-tools.ts Normal file
View File

@ -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<User>(
'/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<User>(`/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<User>('/users/me');
return user;
},
},
];
}

109
src/tools/webhooks-tools.ts Normal file
View File

@ -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<Webhook>(
'/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<Webhook>(`/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' };
},
},
];
}

View File

@ -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<Workflow>(
'/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<Workflow>(`/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<WorkflowStats>(
`/workflows/${args.id}/stats`
);
return stats;
},
},
];
}

500
src/types/index.ts Normal file
View File

@ -0,0 +1,500 @@
// HelpScout Mailbox API v2 Types
export interface HelpScoutConfig {
appId?: string;
appSecret?: string;
accessToken: string;
}
export interface APIError {
message: string;
logref?: string;
_links?: {
about?: { href: string };
};
}
export interface PagedResponse<T> {
_embedded: T;
_links?: {
self?: { href: string };
first?: { href: string };
last?: { href: string };
next?: { href: string };
prev?: { href: string };
page?: { href: string };
};
page?: {
size: number;
totalElements: number;
totalPages: number;
number: number;
};
}
// Conversations
export interface Conversation {
id: number;
number: number;
threads: number;
type: 'email' | 'chat' | 'phone';
folderId: number;
status: 'active' | 'closed' | 'open' | 'pending' | 'spam';
state: 'deleted' | 'draft' | 'published';
subject: string;
preview: string;
mailboxId: number;
assignee?: Person;
createdBy: Person;
createdAt: string;
closedBy?: number;
closedByUser?: Person;
closedAt?: string;
userUpdatedAt: string;
customerWaitingSince?: {
time: string;
friendly: string;
};
source: {
type: string;
via: 'user' | 'customer';
};
tags?: Tag[];
cc?: string[];
bcc?: string[];
primaryCustomer: Customer;
snooze?: {
snoozedBy: number;
snoozedUntil: string;
unsnoozeOnCustomerReply: boolean;
};
nextEvent?: {
time: string;
eventType: 'snooze' | 'scheduled';
userId: number;
cancelOnCustomerReply: boolean;
};
customFields?: CustomField[];
_embedded?: {
threads?: Thread[];
};
_links?: Record<string, { href: string }>;
}
export interface Thread {
id: number;
type: 'note' | 'message' | 'customer' | 'lineitem' | 'chat' | 'phone';
status: 'active' | 'nochange' | 'pending';
state: 'published' | 'draft' | 'deleted' | 'hidden';
action?: {
type: string;
text: string;
};
body: string;
source: {
type: string;
via: string;
};
customer?: Customer;
createdBy: Person;
assignedTo?: Person;
savedReplyId?: number;
to?: string[];
cc?: string[];
bcc?: string[];
createdAt: string;
openedAt?: string;
_embedded?: {
attachments?: Attachment[];
};
}
export interface CreateThread {
type: 'note' | 'message' | 'customer' | 'reply' | 'forwardCustomer' | 'forwardParent';
text: string;
user?: number;
customer?: number;
imported?: boolean;
createdAt?: string;
status?: 'active' | 'nochange' | 'pending';
to?: string[];
cc?: string[];
bcc?: string[];
attachments?: CreateAttachment[];
draft?: boolean;
}
export interface Attachment {
id: number;
mimeType: string;
filename: string;
size: number;
width?: number;
height?: number;
url: string;
_links?: {
data?: { href: string };
};
}
export interface CreateAttachment {
fileName: string;
mimeType: string;
data: string; // base64
}
// Customers
export interface Customer {
id: number;
type?: 'customer';
first?: string;
last?: string;
email: string;
phone?: string;
photoUrl?: string;
photoType?: 'twitter' | 'facebook' | 'gravatar' | 'google' | 'unknown';
gender?: 'male' | 'female' | 'unknown';
age?: string;
organization?: string;
jobTitle?: string;
location?: string;
createdAt?: string;
updatedAt?: string;
background?: string;
address?: Address;
socialProfiles?: SocialProfile[];
emails?: CustomerEmail[];
phones?: CustomerPhone[];
chats?: CustomerChat[];
websites?: CustomerWebsite[];
_embedded?: {
entries?: CustomerEntry[];
};
_links?: Record<string, { href: string }>;
}
export interface Address {
id?: number;
lines: string[];
city: string;
state: string;
postalCode: string;
country: string;
createdAt?: string;
updatedAt?: string;
}
export interface SocialProfile {
id?: number;
type: 'twitter' | 'facebook' | 'linkedin' | 'aboutme' | 'google' | 'googleplus' | 'tungleme' | 'quora' | 'foursquare' | 'youtube' | 'flickr' | 'other';
value: string;
createdAt?: string;
updatedAt?: string;
}
export interface CustomerEmail {
id?: number;
type: 'home' | 'work' | 'other';
value: string;
createdAt?: string;
updatedAt?: string;
}
export interface CustomerPhone {
id?: number;
type: 'home' | 'work' | 'mobile' | 'fax' | 'pager' | 'other';
value: string;
createdAt?: string;
updatedAt?: string;
}
export interface CustomerChat {
id?: number;
type: 'aim' | 'gtalk' | 'icq' | 'xmpp' | 'msn' | 'skype' | 'yahoo' | 'qq' | 'other';
value: string;
createdAt?: string;
updatedAt?: string;
}
export interface CustomerWebsite {
id?: number;
value: string;
createdAt?: string;
updatedAt?: string;
}
export interface CustomerEntry {
id: number;
type: 'email' | 'phone' | 'chat' | 'website';
value: string;
}
export interface CreateCustomer {
firstName: string;
lastName: string;
email?: string;
phone?: string;
photoUrl?: string;
photoType?: 'twitter' | 'facebook' | 'gravatar' | 'google' | 'unknown';
gender?: 'male' | 'female' | 'unknown';
age?: string;
organization?: string;
jobTitle?: string;
location?: string;
background?: string;
address?: Omit<Address, 'id' | 'createdAt' | 'updatedAt'>;
socialProfiles?: Omit<SocialProfile, 'id' | 'createdAt' | 'updatedAt'>[];
emails?: Omit<CustomerEmail, 'id' | 'createdAt' | 'updatedAt'>[];
phones?: Omit<CustomerPhone, 'id' | 'createdAt' | 'updatedAt'>[];
chats?: Omit<CustomerChat, 'id' | 'createdAt' | 'updatedAt'>[];
websites?: Omit<CustomerWebsite, 'id' | 'createdAt' | 'updatedAt'>[];
}
// Mailboxes
export interface Mailbox {
id: number;
name: string;
slug: string;
email: string;
createdAt: string;
updatedAt: string;
_links?: Record<string, { href: string }>;
}
export interface MailboxFields {
id: number;
name: string;
type: 'SINGLE_LINE' | 'MULTI_LINE' | 'DATE' | 'NUMBER' | 'DROPDOWN';
order: number;
required: boolean;
options?: string[];
_links?: Record<string, { href: string }>;
}
export interface Folder {
id: number;
name: string;
type: 'mytickets' | 'unassigned' | 'drafts' | 'assigned' | 'closed' | 'spam' | 'deleted' | 'mine';
userId?: number;
totalCount: number;
activeCount: number;
updatedAt: string;
_links?: Record<string, { href: string }>;
}
// Users
export interface User {
id: number;
type: 'user' | 'team';
first?: string;
last?: string;
email: string;
role: 'owner' | 'admin' | 'user';
timezone: string;
photoUrl?: string;
createdAt: string;
updatedAt: string;
_links?: Record<string, { href: string }>;
}
export interface Person {
id: number;
type: 'user' | 'customer' | 'team';
first?: string;
last?: string;
email?: string;
photoUrl?: string;
}
// Teams
export interface Team {
id: number;
name: string;
createdAt: string;
updatedAt: string;
_links?: Record<string, { href: string }>;
}
export interface TeamMember {
id: number;
first: string;
last: string;
email: string;
role: 'owner' | 'admin' | 'user';
photoUrl?: string;
_links?: Record<string, { href: string }>;
}
// Tags
export interface Tag {
id: number;
tag: string;
color: string;
createdAt?: string;
updatedAt?: string;
_links?: Record<string, { href: string }>;
}
// Workflows
export interface Workflow {
id: number;
mailboxId: number;
type: 'manual' | 'automatic';
status: 'active' | 'inactive' | 'invalid';
order: number;
name: string;
createdAt: string;
modifiedAt: string;
_links?: Record<string, { href: string }>;
}
// Saved Replies
export interface SavedReply {
id: number;
text: string;
name: string;
_links?: Record<string, { href: string }>;
}
// Webhooks
export interface Webhook {
id: number;
url: string;
state: 'enabled' | 'disabled';
events: string[];
notification: boolean;
payloadVersion: 'V1' | 'V2';
label?: string;
secret?: string;
_links?: Record<string, { href: string }>;
}
export interface CreateWebhook {
url: string;
events: string[];
secret?: string;
}
// Reports
export interface Report {
filterTags?: string[];
}
export interface ConversationReport {
current: ReportMetrics;
previous?: ReportMetrics;
deltas?: {
[key: string]: {
value: number;
percent: number;
};
};
}
export interface ReportMetrics {
startDate: string;
endDate: string;
conversations: number;
conversationsCreated: number;
newConversations?: number;
customers?: number;
resolved?: number;
replies?: number;
repliesSent?: number;
resolvedOnFirstReply?: number;
responseTime?: TimeMetrics;
resolutionTime?: TimeMetrics;
firstResponseTime?: TimeMetrics;
}
export interface TimeMetrics {
friendly: string;
seconds: number;
}
export interface UserReport {
user: Person;
current: UserMetrics;
previous?: UserMetrics;
deltas?: Record<string, { value: number; percent: number }>;
}
export interface UserMetrics {
startDate: string;
endDate: string;
totalConversations: number;
conversationsCreated: number;
conversationsResolved: number;
repliesSent: number;
resolvedOnFirstReply?: number;
responseTime?: TimeMetrics;
resolutionTime?: TimeMetrics;
percentResolved?: number;
happiness?: {
score: number;
};
}
export interface HappinessReport {
current: HappinessMetrics;
previous?: HappinessMetrics;
}
export interface HappinessMetrics {
startDate: string;
endDate: string;
happinessScore: number;
ratingsCount: number;
}
// Custom Fields
export interface CustomField {
id: number;
name: string;
value: string | number;
text?: string;
}
// Notes
export interface Note {
text: string;
}
// Search
export interface SearchConversation {
id: number;
number: number;
mailboxid: number;
subject: string;
status: string;
threadCount: number;
preview: string;
customerId: number;
customerEmail: string;
customerName: string;
updatedAt: string;
url?: string;
}
export interface SearchCustomer {
id: number;
firstName: string;
lastName: string;
email: string;
phone?: string;
photoUrl?: string;
url?: string;
}
// Ratings
export interface Rating {
id: number;
customerId: number;
userId?: number;
threadId: number;
rating: 'great' | 'okay' | 'bad';
comments?: string;
createdAt: string;
modifiedAt?: string;
_links?: Record<string, { href: string }>;
}

21
tsconfig.json Normal file
View File

@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"jsx": "react"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}