Add MCP apps UI system, 19 new tool modules, merge upstream changes
This commit is contained in:
commit
c1fbbdd95b
BIN
.env.example
BIN
.env.example
Binary file not shown.
128
README.md
128
README.md
@ -1,9 +1,87 @@
|
|||||||
|
<<<<<<< HEAD
|
||||||
**Instead of trying to tackle this ---- use our hosted version --- GHL Agent Framework, One Click to Sign in!**
|
**Instead of trying to tackle this ---- use our hosted version --- GHL Agent Framework, One Click to Sign in!**
|
||||||
|
|
||||||
https://www.strategixagents.com/
|
https://www.strategixagents.com/
|
||||||
|
|
||||||
# 🚀 GoHighLevel MCP Server
|
# 🚀 GoHighLevel MCP Server
|
||||||
|
|
||||||
|
=======
|
||||||
|
> **🚀 Don't want to self-host?** [Join the waitlist for our fully managed solution →](https://mcp.localbosses.org)
|
||||||
|
>
|
||||||
|
> Zero setup. Zero maintenance. Just connect and automate.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🙏 Credits
|
||||||
|
|
||||||
|
**Original Creator:** [@mastanley13](https://github.com/mastanley13) — Built the foundation for this MCP server.
|
||||||
|
|
||||||
|
**Extended by:** [@BusyBee3333](https://github.com/BusyBee3333) — Expanded to 461+ tools covering the entire GHL API.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 🚀 GoHighLevel MCP Server
|
||||||
|
|
||||||
|
## 💡 What This Unlocks
|
||||||
|
|
||||||
|
**This MCP server gives AI direct access to your entire GoHighLevel CRM.** Instead of clicking through menus, you just *tell* it what you want.
|
||||||
|
|
||||||
|
### 🎯 GHL-Native Power Moves
|
||||||
|
|
||||||
|
| Just say... | What happens |
|
||||||
|
|-------------|--------------|
|
||||||
|
| *"Find everyone who filled out a form this week but hasn't been contacted"* | Searches contacts, filters by source and last activity, returns a ready-to-call list |
|
||||||
|
| *"Create an opportunity for John Smith, $15k deal, add to Enterprise pipeline"* | Creates the opp, assigns pipeline stage, links to contact — done |
|
||||||
|
| *"Schedule a discovery call with Sarah for Tuesday 2pm and send her a confirmation"* | Checks calendar availability, books the slot, fires off an SMS |
|
||||||
|
| *"Draft a blog post about our new service and schedule it for Friday"* | Creates the post in your GHL blog, SEO-ready, scheduled to publish |
|
||||||
|
| *"Send a payment link for Invoice #1042 to the client via text"* | Generates text2pay link, sends SMS with payment URL |
|
||||||
|
|
||||||
|
### 🔗 The Real Power: Combining Tools
|
||||||
|
|
||||||
|
When you pair this MCP with other tools (web search, email, spreadsheets, Slack, etc.), things get *wild*:
|
||||||
|
|
||||||
|
| Combo | What you can build |
|
||||||
|
|-------|-------------------|
|
||||||
|
| **GHL + Calendar + SMS** | "Every morning, text me a summary of today's appointments and any leads that went cold" |
|
||||||
|
| **GHL + Web Search + Email** | "Research this prospect's company, then draft a personalized outreach email and add them as a contact" |
|
||||||
|
| **GHL + Slack + Opportunities** | "When a deal closes, post a celebration to #wins with the deal value and rep name" |
|
||||||
|
| **GHL + Spreadsheet + Invoices** | "Import this CSV of clients, create contacts, and generate invoices for each one" |
|
||||||
|
| **GHL + AI + Conversations** | "Analyze the last 50 customer conversations and tell me what objections keep coming up" |
|
||||||
|
|
||||||
|
> **This isn't just API access — it's your CRM on autopilot, controlled by natural language.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎁 Don't Want to Self-Host? We've Got You.
|
||||||
|
|
||||||
|
**Not everyone wants to manage servers, deal with API keys, or troubleshoot deployments.** We get it.
|
||||||
|
|
||||||
|
👉 **[Join the waitlist for our fully managed solution](https://mcp.localbosses.org)**
|
||||||
|
|
||||||
|
**What you get:**
|
||||||
|
- ✅ **Zero setup** — We handle everything
|
||||||
|
- ✅ **Always up-to-date** — Latest features and security patches automatically
|
||||||
|
- ✅ **Priority support** — Real humans who know GHL and AI
|
||||||
|
- ✅ **Enterprise-grade reliability** — 99.9% uptime, monitored 24/7
|
||||||
|
|
||||||
|
**Perfect for:**
|
||||||
|
- Agencies who want to focus on clients, not infrastructure
|
||||||
|
- Teams without dedicated DevOps resources
|
||||||
|
- Anyone who values their time over tinkering with configs
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://mcp.localbosses.org">
|
||||||
|
<img src="https://img.shields.io/badge/Join_Waitlist-Get_Early_Access-0ea5e9?style=for-the-badge&logo=rocket&logoColor=white" alt="Join Waitlist">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Prefer to self-host? Keep reading below for the full open-source setup guide.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
|
||||||
## 🚨 **IMPORTANT: FOUNDATIONAL PROJECT NOTICE**
|
## 🚨 **IMPORTANT: FOUNDATIONAL PROJECT NOTICE**
|
||||||
|
|
||||||
> **⚠️ This is a BASE-LEVEL foundational project designed to connect the GoHighLevel community with AI automation through MCP (Model Context Protocol).**
|
> **⚠️ This is a BASE-LEVEL foundational project designed to connect the GoHighLevel community with AI automation through MCP (Model Context Protocol).**
|
||||||
@ -83,7 +161,53 @@ This project was a 'time-taker' but I felt it was important. Feel free to donate
|
|||||||
[](https://railway.app/new/template?template=https://github.com/mastanley13/GoHighLevel-MCP)
|
[](https://railway.app/new/template?template=https://github.com/mastanley13/GoHighLevel-MCP)
|
||||||
[](https://buy.stripe.com/28E14o1hT7JAfstfvqdZ60y)
|
[](https://buy.stripe.com/28E14o1hT7JAfstfvqdZ60y)
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
> **🔥 Transform Claude Desktop into a complete GoHighLevel CRM powerhouse with 461+ powerful tools across 38+ categories**
|
> **🔥 Transform Claude Desktop into a complete GoHighLevel CRM powerhouse with 461+ powerful tools across 38+ categories**
|
||||||
|
=======
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🤖 Recommended Setup Options
|
||||||
|
|
||||||
|
#### Option 1: Clawdbot (Easiest — Full AI Assistant)
|
||||||
|
|
||||||
|
**[Clawdbot](https://clawd.bot)** is the easiest way to run this MCP server. It's an AI assistant platform that handles all the MCP configuration, environment setup, and integration automatically.
|
||||||
|
|
||||||
|
**Why Clawdbot?**
|
||||||
|
- ✅ **Zero-config MCP setup** — Just add your GHL API key and go
|
||||||
|
- ✅ **Multi-channel AI** — Use your GHL tools via Discord, Slack, iMessage, WhatsApp, and more
|
||||||
|
- ✅ **Built-in automation** — Schedule tasks, create workflows, and chain tools together
|
||||||
|
- ✅ **Always-on assistant** — Runs 24/7 so your GHL automation never sleeps
|
||||||
|
|
||||||
|
**Quick start:**
|
||||||
|
```bash
|
||||||
|
npm install -g clawdbot
|
||||||
|
clawdbot init
|
||||||
|
clawdbot config set skills.entries.ghl-mcp.apiKey "your_private_integrations_key"
|
||||||
|
```
|
||||||
|
|
||||||
|
Learn more at [docs.clawd.bot](https://docs.clawd.bot) or join the [community Discord](https://discord.com/invite/clawd).
|
||||||
|
|
||||||
|
#### Option 2: mcporter (Lightweight CLI)
|
||||||
|
|
||||||
|
**[mcporter](https://github.com/cyanheads/mcporter)** is a lightweight CLI tool for managing and calling MCP servers directly from the command line. Perfect if you want to test tools, debug integrations, or build your own automation scripts.
|
||||||
|
|
||||||
|
**Why mcporter?**
|
||||||
|
- ✅ **Direct MCP access** — Call any MCP tool from the terminal
|
||||||
|
- ✅ **Config management** — Easy server setup and auth handling
|
||||||
|
- ✅ **Great for scripting** — Pipe MCP tools into shell scripts and automations
|
||||||
|
- ✅ **Debugging friendly** — Inspect requests/responses in real-time
|
||||||
|
|
||||||
|
**Quick start:**
|
||||||
|
```bash
|
||||||
|
npm install -g mcporter
|
||||||
|
mcporter config add ghl-mcp --transport stdio --command "node /path/to/ghl-mcp-server/dist/server.js"
|
||||||
|
mcporter call ghl-mcp search_contacts --params '{"query": "test"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> **🔥 Transform Claude Desktop into a complete GoHighLevel CRM powerhouse with 461+ powerful tools across 19+ categories**
|
||||||
|
>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
|
||||||
|
|
||||||
## 🎯 What This Does
|
## 🎯 What This Does
|
||||||
|
|
||||||
@ -743,7 +867,11 @@ This project is licensed under the **ISC License** - see the [LICENSE](LICENSE)
|
|||||||
|
|
||||||
This comprehensive MCP server delivers:
|
This comprehensive MCP server delivers:
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
### ✅ **461 Operational Tools** across 38 categories
|
### ✅ **461 Operational Tools** across 38 categories
|
||||||
|
=======
|
||||||
|
### ✅ **461 Operational Tools** across 19 categories
|
||||||
|
>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
|
||||||
### ✅ **Real-time GoHighLevel Integration** with full API coverage
|
### ✅ **Real-time GoHighLevel Integration** with full API coverage
|
||||||
### ✅ **Production-Ready Deployment** on multiple platforms
|
### ✅ **Production-Ready Deployment** on multiple platforms
|
||||||
### ✅ **Enterprise-Grade Architecture** with comprehensive error handling
|
### ✅ **Enterprise-Grade Architecture** with comprehensive error handling
|
||||||
|
|||||||
52
package-lock.json
generated
52
package-lock.json
generated
@ -1,11 +1,19 @@
|
|||||||
{
|
{
|
||||||
|
<<<<<<< HEAD
|
||||||
"name": "ghl-mcp",
|
"name": "ghl-mcp",
|
||||||
|
=======
|
||||||
|
"name": "@mastanley13/ghl-mcp-server",
|
||||||
|
>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
|
<<<<<<< HEAD
|
||||||
"name": "ghl-mcp",
|
"name": "ghl-mcp",
|
||||||
|
=======
|
||||||
|
"name": "@mastanley13/ghl-mcp-server",
|
||||||
|
>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -17,6 +25,12 @@
|
|||||||
"dotenv": "^16.5.0",
|
"dotenv": "^16.5.0",
|
||||||
"express": "^5.1.0"
|
"express": "^5.1.0"
|
||||||
},
|
},
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
"bin": {
|
||||||
|
"ghl-mcp-server": "dist/server.js"
|
||||||
|
},
|
||||||
|
>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
"@types/node": "^22.15.29",
|
"@types/node": "^22.15.29",
|
||||||
@ -25,6 +39,12 @@
|
|||||||
"ts-jest": "^29.3.4",
|
"ts-jest": "^29.3.4",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.8.3"
|
"typescript": "^5.8.3"
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ampproject/remapping": {
|
"node_modules/@ampproject/remapping": {
|
||||||
@ -68,6 +88,10 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz",
|
||||||
"integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==",
|
"integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
"peer": true,
|
||||||
|
>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.2.0",
|
"@ampproject/remapping": "^2.2.0",
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
@ -1078,6 +1102,10 @@
|
|||||||
"version": "22.15.29",
|
"version": "22.15.29",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.29.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.29.tgz",
|
||||||
"integrity": "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==",
|
"integrity": "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==",
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
"peer": true,
|
||||||
|
>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.21.0"
|
"undici-types": "~6.21.0"
|
||||||
}
|
}
|
||||||
@ -1459,6 +1487,10 @@
|
|||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
"peer": true,
|
||||||
|
>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"caniuse-lite": "^1.0.30001718",
|
"caniuse-lite": "^1.0.30001718",
|
||||||
"electron-to-chromium": "^1.5.160",
|
"electron-to-chromium": "^1.5.160",
|
||||||
@ -2123,6 +2155,10 @@
|
|||||||
"version": "5.1.0",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
|
||||||
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
|
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
"peer": true,
|
||||||
|
>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "^2.0.0",
|
"accepts": "^2.0.0",
|
||||||
"body-parser": "^2.2.0",
|
"body-parser": "^2.2.0",
|
||||||
@ -2842,6 +2878,10 @@
|
|||||||
"resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
|
||||||
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
|
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
"peer": true,
|
||||||
|
>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jest/core": "^29.7.0",
|
"@jest/core": "^29.7.0",
|
||||||
"@jest/types": "^29.6.3",
|
"@jest/types": "^29.6.3",
|
||||||
@ -4651,6 +4691,10 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
||||||
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
"peer": true,
|
||||||
|
>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cspotcode/source-map-support": "^0.8.0",
|
"@cspotcode/source-map-support": "^0.8.0",
|
||||||
"@tsconfig/node10": "^1.0.7",
|
"@tsconfig/node10": "^1.0.7",
|
||||||
@ -4728,6 +4772,10 @@
|
|||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
"peer": true,
|
||||||
|
>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@ -4946,6 +4994,10 @@
|
|||||||
"version": "3.25.51",
|
"version": "3.25.51",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.51.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.51.tgz",
|
||||||
"integrity": "sha512-TQSnBldh+XSGL+opiSIq0575wvDPqu09AqWe1F7JhUMKY+M91/aGlK4MhpVNO7MgYfHcVCB1ffwAUTJzllKJqg==",
|
"integrity": "sha512-TQSnBldh+XSGL+opiSIq0575wvDPqu09AqWe1F7JhUMKY+M91/aGlK4MhpVNO7MgYfHcVCB1ffwAUTJzllKJqg==",
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
"peer": true,
|
||||||
|
>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,15 +11,7 @@ import { fileURLToPath } from 'url';
|
|||||||
|
|
||||||
export interface AppToolResult {
|
export interface AppToolResult {
|
||||||
content: Array<{ type: 'text'; text: string }>;
|
content: Array<{ type: 'text'; text: string }>;
|
||||||
structuredContent?: {
|
structuredContent?: Record<string, unknown>;
|
||||||
type: 'resource';
|
|
||||||
resource: {
|
|
||||||
uri: string;
|
|
||||||
mimeType: string;
|
|
||||||
text?: string;
|
|
||||||
blob?: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,21 +25,22 @@ export interface AppResourceHandler {
|
|||||||
* MCP Apps Manager class
|
* MCP Apps Manager class
|
||||||
* Registers app tools and handles structuredContent responses
|
* Registers app tools and handles structuredContent responses
|
||||||
*/
|
*/
|
||||||
// Note: We use process.cwd() based path resolution to find UI dist
|
// Resolve UI build path - works regardless of working directory
|
||||||
// This works when running from the project root directory
|
|
||||||
function getUIBuildPath(): string {
|
function getUIBuildPath(): string {
|
||||||
// First try dist/app-ui (where MCP Apps are built)
|
// When compiled, this file is at dist/apps/index.js
|
||||||
|
// UI files are at dist/app-ui/
|
||||||
|
// Use __dirname which is available in CommonJS
|
||||||
|
const fromDist = path.resolve(__dirname, '..', 'app-ui');
|
||||||
|
if (fs.existsSync(fromDist)) {
|
||||||
|
return fromDist;
|
||||||
|
}
|
||||||
|
// Fallback: try process.cwd() based paths
|
||||||
const appUiPath = path.join(process.cwd(), 'dist', 'app-ui');
|
const appUiPath = path.join(process.cwd(), 'dist', 'app-ui');
|
||||||
if (fs.existsSync(appUiPath)) {
|
if (fs.existsSync(appUiPath)) {
|
||||||
return appUiPath;
|
return appUiPath;
|
||||||
}
|
}
|
||||||
// Fallback to src/ui/dist for legacy UI components
|
|
||||||
const fromCwd = path.join(process.cwd(), 'src', 'ui', 'dist');
|
|
||||||
if (fs.existsSync(fromCwd)) {
|
|
||||||
return fromCwd;
|
|
||||||
}
|
|
||||||
// Default fallback
|
// Default fallback
|
||||||
return appUiPath;
|
return fromDist;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MCPAppsManager {
|
export class MCPAppsManager {
|
||||||
@ -306,6 +299,22 @@ export class MCPAppsManager {
|
|||||||
_meta: {
|
_meta: {
|
||||||
ui: { resourceUri: 'ui://ghl/mcp-app' }
|
ui: { resourceUri: 'ui://ghl/mcp-app' }
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// 12. Update Opportunity - action tool for UI to update opportunities
|
||||||
|
{
|
||||||
|
name: 'update_opportunity',
|
||||||
|
description: 'Update an opportunity (move to stage, change value, status, etc.)',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
opportunityId: { type: 'string', description: 'Opportunity ID to update' },
|
||||||
|
pipelineStageId: { type: 'string', description: 'New stage ID (for moving)' },
|
||||||
|
name: { type: 'string', description: 'Opportunity name' },
|
||||||
|
monetaryValue: { type: 'number', description: 'Monetary value' },
|
||||||
|
status: { type: 'string', enum: ['open', 'won', 'lost', 'abandoned'], description: 'Opportunity status' }
|
||||||
|
},
|
||||||
|
required: ['opportunityId']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -325,7 +334,8 @@ export class MCPAppsManager {
|
|||||||
'view_agent_stats',
|
'view_agent_stats',
|
||||||
'view_contact_timeline',
|
'view_contact_timeline',
|
||||||
'view_workflow_status',
|
'view_workflow_status',
|
||||||
'view_dashboard'
|
'view_dashboard',
|
||||||
|
'update_opportunity'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -365,6 +375,14 @@ export class MCPAppsManager {
|
|||||||
return await this.viewWorkflowStatus(args.workflowId);
|
return await this.viewWorkflowStatus(args.workflowId);
|
||||||
case 'view_dashboard':
|
case 'view_dashboard':
|
||||||
return await this.viewDashboard();
|
return await this.viewDashboard();
|
||||||
|
case 'update_opportunity':
|
||||||
|
return await this.updateOpportunity(args as {
|
||||||
|
opportunityId: string;
|
||||||
|
pipelineStageId?: string;
|
||||||
|
name?: string;
|
||||||
|
monetaryValue?: number;
|
||||||
|
status?: 'open' | 'won' | 'lost' | 'abandoned';
|
||||||
|
});
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown app tool: ${toolName}`);
|
throw new Error(`Unknown app tool: ${toolName}`);
|
||||||
}
|
}
|
||||||
@ -415,9 +433,26 @@ export class MCPAppsManager {
|
|||||||
const pipeline = pipelinesResponse.data?.pipelines?.find((p: any) => p.id === pipelineId);
|
const pipeline = pipelinesResponse.data?.pipelines?.find((p: any) => p.id === pipelineId);
|
||||||
const opportunities = opportunitiesResponse.data?.opportunities || [];
|
const opportunities = opportunitiesResponse.data?.opportunities || [];
|
||||||
|
|
||||||
|
// Simplify opportunity data to only include fields the UI needs (reduces payload size)
|
||||||
|
const simplifiedOpportunities = opportunities.map((opp: any) => ({
|
||||||
|
id: opp.id,
|
||||||
|
name: opp.name || 'Untitled',
|
||||||
|
pipelineStageId: opp.pipelineStageId,
|
||||||
|
status: opp.status || 'open',
|
||||||
|
monetaryValue: opp.monetaryValue || 0,
|
||||||
|
contact: opp.contact ? {
|
||||||
|
name: opp.contact.name || 'Unknown',
|
||||||
|
email: opp.contact.email,
|
||||||
|
phone: opp.contact.phone
|
||||||
|
} : { name: 'Unknown' },
|
||||||
|
updatedAt: opp.updatedAt || opp.createdAt,
|
||||||
|
createdAt: opp.createdAt,
|
||||||
|
source: opp.source
|
||||||
|
}));
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
pipeline,
|
pipeline,
|
||||||
opportunities,
|
opportunities: simplifiedOpportunities,
|
||||||
stages: pipeline?.stages || []
|
stages: pipeline?.stages || []
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -692,6 +727,50 @@ export class MCPAppsManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update opportunity (action tool for UI)
|
||||||
|
*/
|
||||||
|
private async updateOpportunity(args: {
|
||||||
|
opportunityId: string;
|
||||||
|
pipelineStageId?: string;
|
||||||
|
name?: string;
|
||||||
|
monetaryValue?: number;
|
||||||
|
status?: 'open' | 'won' | 'lost' | 'abandoned';
|
||||||
|
}): Promise<AppToolResult> {
|
||||||
|
const { opportunityId, ...updates } = args;
|
||||||
|
|
||||||
|
// Build the update payload
|
||||||
|
const updatePayload: any = {};
|
||||||
|
if (updates.pipelineStageId) updatePayload.pipelineStageId = updates.pipelineStageId;
|
||||||
|
if (updates.name) updatePayload.name = updates.name;
|
||||||
|
if (updates.monetaryValue !== undefined) updatePayload.monetaryValue = updates.monetaryValue;
|
||||||
|
if (updates.status) updatePayload.status = updates.status;
|
||||||
|
|
||||||
|
process.stderr.write(`[MCP Apps] Updating opportunity ${opportunityId}: ${JSON.stringify(updatePayload)}\n`);
|
||||||
|
|
||||||
|
const response = await this.ghlClient.updateOpportunity(opportunityId, updatePayload);
|
||||||
|
|
||||||
|
if (!response.success) {
|
||||||
|
throw new Error(response.error?.message || 'Failed to update opportunity');
|
||||||
|
}
|
||||||
|
|
||||||
|
const opportunity = response.data;
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [{ type: 'text', text: `Updated opportunity: ${opportunity?.name || opportunityId}` }],
|
||||||
|
structuredContent: {
|
||||||
|
success: true,
|
||||||
|
opportunity: {
|
||||||
|
id: opportunity?.id,
|
||||||
|
name: opportunity?.name,
|
||||||
|
pipelineStageId: opportunity?.pipelineStageId,
|
||||||
|
monetaryValue: opportunity?.monetaryValue,
|
||||||
|
status: opportunity?.status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create app tool result with structuredContent
|
* Create app tool result with structuredContent
|
||||||
*/
|
*/
|
||||||
@ -702,19 +781,11 @@ export class MCPAppsManager {
|
|||||||
htmlContent: string,
|
htmlContent: string,
|
||||||
data: any
|
data: any
|
||||||
): AppToolResult {
|
): AppToolResult {
|
||||||
// Inject the data into the HTML
|
// structuredContent is the data object that gets passed to ontoolresult
|
||||||
const htmlWithData = this.injectDataIntoHTML(htmlContent, data);
|
// The UI accesses it via result.structuredContent
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: [{ type: 'text', text: textSummary }],
|
content: [{ type: 'text', text: textSummary }],
|
||||||
structuredContent: {
|
structuredContent: data
|
||||||
type: 'resource',
|
|
||||||
resource: {
|
|
||||||
uri: resourceUri,
|
|
||||||
mimeType: mimeType,
|
|
||||||
text: htmlWithData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -575,12 +575,12 @@ export class GHLApiClient {
|
|||||||
const filters: any = {};
|
const filters: any = {};
|
||||||
let hasFilters = false;
|
let hasFilters = false;
|
||||||
|
|
||||||
if (searchParams.filters.email && searchParams.filters.email.trim()) {
|
if (searchParams.filters.email && typeof searchParams.filters.email === 'string' && searchParams.filters.email.trim()) {
|
||||||
filters.email = searchParams.filters.email.trim();
|
filters.email = searchParams.filters.email.trim();
|
||||||
hasFilters = true;
|
hasFilters = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchParams.filters.phone && searchParams.filters.phone.trim()) {
|
if (searchParams.filters.phone && typeof searchParams.filters.phone === 'string' && searchParams.filters.phone.trim()) {
|
||||||
filters.phone = searchParams.filters.phone.trim();
|
filters.phone = searchParams.filters.phone.trim();
|
||||||
hasFilters = true;
|
hasFilters = true;
|
||||||
}
|
}
|
||||||
@ -1558,6 +1558,36 @@ export class GHLApiClient {
|
|||||||
return { ...this.config };
|
return { ...this.config };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic request method for new endpoints
|
||||||
|
* Used by new tool modules that don't have specific client methods yet
|
||||||
|
*/
|
||||||
|
async makeRequest<T = any>(method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', path: string, body?: Record<string, unknown>): Promise<GHLApiResponse<T>> {
|
||||||
|
try {
|
||||||
|
let response;
|
||||||
|
switch (method) {
|
||||||
|
case 'GET':
|
||||||
|
response = await this.axiosInstance.get(path);
|
||||||
|
break;
|
||||||
|
case 'POST':
|
||||||
|
response = await this.axiosInstance.post(path, body);
|
||||||
|
break;
|
||||||
|
case 'PUT':
|
||||||
|
response = await this.axiosInstance.put(path, body);
|
||||||
|
break;
|
||||||
|
case 'PATCH':
|
||||||
|
response = await this.axiosInstance.patch(path, body);
|
||||||
|
break;
|
||||||
|
case 'DELETE':
|
||||||
|
response = await this.axiosInstance.delete(path);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return this.wrapResponse(response.data);
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OPPORTUNITIES API METHODS
|
* OPPORTUNITIES API METHODS
|
||||||
*/
|
*/
|
||||||
|
|||||||
395
src/tools/affiliates-tools.ts
Normal file
395
src/tools/affiliates-tools.ts
Normal file
@ -0,0 +1,395 @@
|
|||||||
|
/**
|
||||||
|
* GoHighLevel Affiliates Tools
|
||||||
|
* Tools for managing affiliate marketing program
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { GHLApiClient } from '../clients/ghl-api-client.js';
|
||||||
|
|
||||||
|
export class AffiliatesTools {
|
||||||
|
constructor(private ghlClient: GHLApiClient) {}
|
||||||
|
|
||||||
|
getToolDefinitions() {
|
||||||
|
return [
|
||||||
|
// Affiliate Campaigns
|
||||||
|
{
|
||||||
|
name: 'get_affiliate_campaigns',
|
||||||
|
description: 'Get all affiliate campaigns',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
status: { type: 'string', enum: ['active', 'inactive', 'all'], description: 'Campaign status filter' },
|
||||||
|
limit: { type: 'number', description: 'Max results' },
|
||||||
|
offset: { type: 'number', description: 'Pagination offset' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_affiliate_campaign',
|
||||||
|
description: 'Get a specific affiliate campaign',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
campaignId: { type: 'string', description: 'Affiliate Campaign ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['campaignId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_affiliate_campaign',
|
||||||
|
description: 'Create a new affiliate campaign',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
name: { type: 'string', description: 'Campaign name' },
|
||||||
|
description: { type: 'string', description: 'Campaign description' },
|
||||||
|
commissionType: { type: 'string', enum: ['percentage', 'fixed'], description: 'Commission type' },
|
||||||
|
commissionValue: { type: 'number', description: 'Commission value (percentage or fixed amount)' },
|
||||||
|
cookieDays: { type: 'number', description: 'Cookie tracking duration in days' },
|
||||||
|
productIds: { type: 'array', items: { type: 'string' }, description: 'Product IDs for this campaign' }
|
||||||
|
},
|
||||||
|
required: ['name', 'commissionType', 'commissionValue']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_affiliate_campaign',
|
||||||
|
description: 'Update an affiliate campaign',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
campaignId: { type: 'string', description: 'Campaign ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
name: { type: 'string', description: 'Campaign name' },
|
||||||
|
description: { type: 'string', description: 'Campaign description' },
|
||||||
|
commissionType: { type: 'string', enum: ['percentage', 'fixed'], description: 'Commission type' },
|
||||||
|
commissionValue: { type: 'number', description: 'Commission value' },
|
||||||
|
status: { type: 'string', enum: ['active', 'inactive'], description: 'Campaign status' }
|
||||||
|
},
|
||||||
|
required: ['campaignId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete_affiliate_campaign',
|
||||||
|
description: 'Delete an affiliate campaign',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
campaignId: { type: 'string', description: 'Campaign ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['campaignId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Affiliates
|
||||||
|
{
|
||||||
|
name: 'get_affiliates',
|
||||||
|
description: 'Get all affiliates',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
campaignId: { type: 'string', description: 'Filter by campaign' },
|
||||||
|
status: { type: 'string', enum: ['pending', 'approved', 'rejected', 'all'], description: 'Status filter' },
|
||||||
|
limit: { type: 'number', description: 'Max results' },
|
||||||
|
offset: { type: 'number', description: 'Pagination offset' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_affiliate',
|
||||||
|
description: 'Get a specific affiliate',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
affiliateId: { type: 'string', description: 'Affiliate ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['affiliateId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_affiliate',
|
||||||
|
description: 'Create/add a new affiliate',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
contactId: { type: 'string', description: 'Contact ID to make affiliate' },
|
||||||
|
campaignId: { type: 'string', description: 'Campaign to assign to' },
|
||||||
|
customCode: { type: 'string', description: 'Custom affiliate code' },
|
||||||
|
status: { type: 'string', enum: ['pending', 'approved'], description: 'Initial status' }
|
||||||
|
},
|
||||||
|
required: ['contactId', 'campaignId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_affiliate',
|
||||||
|
description: 'Update an affiliate',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
affiliateId: { type: 'string', description: 'Affiliate ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
status: { type: 'string', enum: ['pending', 'approved', 'rejected'], description: 'Status' },
|
||||||
|
customCode: { type: 'string', description: 'Custom affiliate code' }
|
||||||
|
},
|
||||||
|
required: ['affiliateId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'approve_affiliate',
|
||||||
|
description: 'Approve a pending affiliate',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
affiliateId: { type: 'string', description: 'Affiliate ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['affiliateId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'reject_affiliate',
|
||||||
|
description: 'Reject/deny a pending affiliate',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
affiliateId: { type: 'string', description: 'Affiliate ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
reason: { type: 'string', description: 'Rejection reason' }
|
||||||
|
},
|
||||||
|
required: ['affiliateId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete_affiliate',
|
||||||
|
description: 'Remove an affiliate',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
affiliateId: { type: 'string', description: 'Affiliate ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['affiliateId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Commissions & Payouts
|
||||||
|
{
|
||||||
|
name: 'get_affiliate_commissions',
|
||||||
|
description: 'Get commissions for an affiliate',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
affiliateId: { type: 'string', description: 'Affiliate ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
status: { type: 'string', enum: ['pending', 'approved', 'paid', 'all'], description: 'Status filter' },
|
||||||
|
startDate: { type: 'string', description: 'Start date' },
|
||||||
|
endDate: { type: 'string', description: 'End date' },
|
||||||
|
limit: { type: 'number', description: 'Max results' },
|
||||||
|
offset: { type: 'number', description: 'Pagination offset' }
|
||||||
|
},
|
||||||
|
required: ['affiliateId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_affiliate_stats',
|
||||||
|
description: 'Get affiliate performance statistics',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
affiliateId: { type: 'string', description: 'Affiliate ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
startDate: { type: 'string', description: 'Start date' },
|
||||||
|
endDate: { type: 'string', description: 'End date' }
|
||||||
|
},
|
||||||
|
required: ['affiliateId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_payout',
|
||||||
|
description: 'Create a payout for affiliate',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
affiliateId: { type: 'string', description: 'Affiliate ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
amount: { type: 'number', description: 'Payout amount' },
|
||||||
|
commissionIds: { type: 'array', items: { type: 'string' }, description: 'Commission IDs to include' },
|
||||||
|
note: { type: 'string', description: 'Payout note' }
|
||||||
|
},
|
||||||
|
required: ['affiliateId', 'amount']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_payouts',
|
||||||
|
description: 'Get affiliate payouts',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
affiliateId: { type: 'string', description: 'Filter by affiliate' },
|
||||||
|
status: { type: 'string', enum: ['pending', 'completed', 'failed', 'all'], description: 'Status filter' },
|
||||||
|
limit: { type: 'number', description: 'Max results' },
|
||||||
|
offset: { type: 'number', description: 'Pagination offset' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Referrals
|
||||||
|
{
|
||||||
|
name: 'get_referrals',
|
||||||
|
description: 'Get referrals (leads/sales) from affiliates',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
affiliateId: { type: 'string', description: 'Filter by affiliate' },
|
||||||
|
campaignId: { type: 'string', description: 'Filter by campaign' },
|
||||||
|
type: { type: 'string', enum: ['lead', 'sale', 'all'], description: 'Referral type' },
|
||||||
|
limit: { type: 'number', description: 'Max results' },
|
||||||
|
offset: { type: 'number', description: 'Pagination offset' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleToolCall(toolName: string, args: Record<string, unknown>): Promise<unknown> {
|
||||||
|
const config = this.ghlClient.getConfig();
|
||||||
|
const locationId = (args.locationId as string) || config.locationId;
|
||||||
|
|
||||||
|
switch (toolName) {
|
||||||
|
// Campaigns
|
||||||
|
case 'get_affiliate_campaigns': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.status) params.append('status', String(args.status));
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.offset) params.append('offset', String(args.offset));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/affiliates/campaigns?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'get_affiliate_campaign': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/affiliates/campaigns/${args.campaignId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'create_affiliate_campaign': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/affiliates/campaigns`, {
|
||||||
|
locationId,
|
||||||
|
name: args.name,
|
||||||
|
description: args.description,
|
||||||
|
commissionType: args.commissionType,
|
||||||
|
commissionValue: args.commissionValue,
|
||||||
|
cookieDays: args.cookieDays,
|
||||||
|
productIds: args.productIds
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case 'update_affiliate_campaign': {
|
||||||
|
const body: Record<string, unknown> = { locationId };
|
||||||
|
if (args.name) body.name = args.name;
|
||||||
|
if (args.description) body.description = args.description;
|
||||||
|
if (args.commissionType) body.commissionType = args.commissionType;
|
||||||
|
if (args.commissionValue) body.commissionValue = args.commissionValue;
|
||||||
|
if (args.status) body.status = args.status;
|
||||||
|
return this.ghlClient.makeRequest('PUT', `/affiliates/campaigns/${args.campaignId}`, body);
|
||||||
|
}
|
||||||
|
case 'delete_affiliate_campaign': {
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/affiliates/campaigns/${args.campaignId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Affiliates
|
||||||
|
case 'get_affiliates': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.campaignId) params.append('campaignId', String(args.campaignId));
|
||||||
|
if (args.status) params.append('status', String(args.status));
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.offset) params.append('offset', String(args.offset));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/affiliates/?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'get_affiliate': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/affiliates/${args.affiliateId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'create_affiliate': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/affiliates/`, {
|
||||||
|
locationId,
|
||||||
|
contactId: args.contactId,
|
||||||
|
campaignId: args.campaignId,
|
||||||
|
customCode: args.customCode,
|
||||||
|
status: args.status
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case 'update_affiliate': {
|
||||||
|
const body: Record<string, unknown> = { locationId };
|
||||||
|
if (args.status) body.status = args.status;
|
||||||
|
if (args.customCode) body.customCode = args.customCode;
|
||||||
|
return this.ghlClient.makeRequest('PUT', `/affiliates/${args.affiliateId}`, body);
|
||||||
|
}
|
||||||
|
case 'approve_affiliate': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/affiliates/${args.affiliateId}/approve`, { locationId });
|
||||||
|
}
|
||||||
|
case 'reject_affiliate': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/affiliates/${args.affiliateId}/reject`, {
|
||||||
|
locationId,
|
||||||
|
reason: args.reason
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case 'delete_affiliate': {
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/affiliates/${args.affiliateId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commissions
|
||||||
|
case 'get_affiliate_commissions': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.status) params.append('status', String(args.status));
|
||||||
|
if (args.startDate) params.append('startDate', String(args.startDate));
|
||||||
|
if (args.endDate) params.append('endDate', String(args.endDate));
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.offset) params.append('offset', String(args.offset));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/affiliates/${args.affiliateId}/commissions?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'get_affiliate_stats': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.startDate) params.append('startDate', String(args.startDate));
|
||||||
|
if (args.endDate) params.append('endDate', String(args.endDate));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/affiliates/${args.affiliateId}/stats?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'create_payout': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/affiliates/${args.affiliateId}/payouts`, {
|
||||||
|
locationId,
|
||||||
|
amount: args.amount,
|
||||||
|
commissionIds: args.commissionIds,
|
||||||
|
note: args.note
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case 'get_payouts': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.affiliateId) params.append('affiliateId', String(args.affiliateId));
|
||||||
|
if (args.status) params.append('status', String(args.status));
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.offset) params.append('offset', String(args.offset));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/affiliates/payouts?${params.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Referrals
|
||||||
|
case 'get_referrals': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.affiliateId) params.append('affiliateId', String(args.affiliateId));
|
||||||
|
if (args.campaignId) params.append('campaignId', String(args.campaignId));
|
||||||
|
if (args.type) params.append('type', String(args.type));
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.offset) params.append('offset', String(args.offset));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/affiliates/referrals?${params.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown tool: ${toolName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
232
src/tools/businesses-tools.ts
Normal file
232
src/tools/businesses-tools.ts
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
/**
|
||||||
|
* GoHighLevel Businesses Tools
|
||||||
|
* Tools for managing businesses (multi-business support)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { GHLApiClient } from '../clients/ghl-api-client.js';
|
||||||
|
|
||||||
|
export class BusinessesTools {
|
||||||
|
constructor(private ghlClient: GHLApiClient) {}
|
||||||
|
|
||||||
|
getToolDefinitions() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'get_businesses',
|
||||||
|
description: 'Get all businesses for a location. Businesses represent different entities within a sub-account.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_business',
|
||||||
|
description: 'Get a specific business by ID',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
businessId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The business ID to retrieve'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['businessId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_business',
|
||||||
|
description: 'Create a new business for a location',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Business name'
|
||||||
|
},
|
||||||
|
phone: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Business phone number'
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Business email address'
|
||||||
|
},
|
||||||
|
website: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Business website URL'
|
||||||
|
},
|
||||||
|
address: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Business street address'
|
||||||
|
},
|
||||||
|
city: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Business city'
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Business state'
|
||||||
|
},
|
||||||
|
postalCode: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Business postal/zip code'
|
||||||
|
},
|
||||||
|
country: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Business country'
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Business description'
|
||||||
|
},
|
||||||
|
logoUrl: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'URL to business logo image'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['name']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_business',
|
||||||
|
description: 'Update an existing business',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
businessId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The business ID to update'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Business name'
|
||||||
|
},
|
||||||
|
phone: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Business phone number'
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Business email address'
|
||||||
|
},
|
||||||
|
website: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Business website URL'
|
||||||
|
},
|
||||||
|
address: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Business street address'
|
||||||
|
},
|
||||||
|
city: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Business city'
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Business state'
|
||||||
|
},
|
||||||
|
postalCode: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Business postal/zip code'
|
||||||
|
},
|
||||||
|
country: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Business country'
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Business description'
|
||||||
|
},
|
||||||
|
logoUrl: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'URL to business logo image'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['businessId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete_business',
|
||||||
|
description: 'Delete a business from a location',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
businessId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The business ID to delete'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['businessId']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleToolCall(toolName: string, args: Record<string, unknown>): Promise<unknown> {
|
||||||
|
const config = this.ghlClient.getConfig();
|
||||||
|
const locationId = (args.locationId as string) || config.locationId;
|
||||||
|
|
||||||
|
switch (toolName) {
|
||||||
|
case 'get_businesses': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/businesses/?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'get_business': {
|
||||||
|
const businessId = args.businessId as string;
|
||||||
|
return this.ghlClient.makeRequest('GET', `/businesses/${businessId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'create_business': {
|
||||||
|
const body: Record<string, unknown> = {
|
||||||
|
locationId,
|
||||||
|
name: args.name
|
||||||
|
};
|
||||||
|
const optionalFields = ['phone', 'email', 'website', 'address', 'city', 'state', 'postalCode', 'country', 'description', 'logoUrl'];
|
||||||
|
optionalFields.forEach(field => {
|
||||||
|
if (args[field]) body[field] = args[field];
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.ghlClient.makeRequest('POST', `/businesses/`, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'update_business': {
|
||||||
|
const businessId = args.businessId as string;
|
||||||
|
const body: Record<string, unknown> = { locationId };
|
||||||
|
const optionalFields = ['name', 'phone', 'email', 'website', 'address', 'city', 'state', 'postalCode', 'country', 'description', 'logoUrl'];
|
||||||
|
optionalFields.forEach(field => {
|
||||||
|
if (args[field]) body[field] = args[field];
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.ghlClient.makeRequest('PUT', `/businesses/${businessId}`, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'delete_business': {
|
||||||
|
const businessId = args.businessId as string;
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/businesses/${businessId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown tool: ${toolName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
243
src/tools/campaigns-tools.ts
Normal file
243
src/tools/campaigns-tools.ts
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
/**
|
||||||
|
* GoHighLevel Campaigns Tools
|
||||||
|
* Tools for managing email and SMS campaigns
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { GHLApiClient } from '../clients/ghl-api-client.js';
|
||||||
|
|
||||||
|
export class CampaignsTools {
|
||||||
|
constructor(private ghlClient: GHLApiClient) {}
|
||||||
|
|
||||||
|
getToolDefinitions() {
|
||||||
|
return [
|
||||||
|
// Campaign Management
|
||||||
|
{
|
||||||
|
name: 'get_campaigns',
|
||||||
|
description: 'Get all campaigns (email/SMS) for a location',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
status: { type: 'string', enum: ['draft', 'scheduled', 'running', 'completed', 'paused'], description: 'Filter by campaign status' },
|
||||||
|
limit: { type: 'number', description: 'Max results' },
|
||||||
|
offset: { type: 'number', description: 'Pagination offset' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_campaign',
|
||||||
|
description: 'Get a specific campaign by ID',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
campaignId: { type: 'string', description: 'Campaign ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['campaignId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_campaign',
|
||||||
|
description: 'Create a new campaign',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
name: { type: 'string', description: 'Campaign name' },
|
||||||
|
type: { type: 'string', enum: ['email', 'sms', 'voicemail'], description: 'Campaign type' },
|
||||||
|
status: { type: 'string', enum: ['draft', 'scheduled'], description: 'Initial status' }
|
||||||
|
},
|
||||||
|
required: ['name', 'type']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_campaign',
|
||||||
|
description: 'Update a campaign',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
campaignId: { type: 'string', description: 'Campaign ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
name: { type: 'string', description: 'Campaign name' },
|
||||||
|
status: { type: 'string', enum: ['draft', 'scheduled', 'paused'], description: 'Campaign status' }
|
||||||
|
},
|
||||||
|
required: ['campaignId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete_campaign',
|
||||||
|
description: 'Delete a campaign',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
campaignId: { type: 'string', description: 'Campaign ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['campaignId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Campaign Actions
|
||||||
|
{
|
||||||
|
name: 'start_campaign',
|
||||||
|
description: 'Start/launch a campaign',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
campaignId: { type: 'string', description: 'Campaign ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['campaignId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pause_campaign',
|
||||||
|
description: 'Pause a running campaign',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
campaignId: { type: 'string', description: 'Campaign ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['campaignId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'resume_campaign',
|
||||||
|
description: 'Resume a paused campaign',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
campaignId: { type: 'string', description: 'Campaign ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['campaignId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Campaign Stats
|
||||||
|
{
|
||||||
|
name: 'get_campaign_stats',
|
||||||
|
description: 'Get statistics for a campaign (opens, clicks, bounces, etc.)',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
campaignId: { type: 'string', description: 'Campaign ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['campaignId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_campaign_recipients',
|
||||||
|
description: 'Get all recipients of a campaign',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
campaignId: { type: 'string', description: 'Campaign ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
status: { type: 'string', enum: ['sent', 'delivered', 'opened', 'clicked', 'bounced', 'unsubscribed'], description: 'Filter by recipient status' },
|
||||||
|
limit: { type: 'number', description: 'Max results' },
|
||||||
|
offset: { type: 'number', description: 'Pagination offset' }
|
||||||
|
},
|
||||||
|
required: ['campaignId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Scheduled Messages
|
||||||
|
{
|
||||||
|
name: 'get_scheduled_messages',
|
||||||
|
description: 'Get all scheduled messages in campaigns',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
contactId: { type: 'string', description: 'Filter by contact ID' },
|
||||||
|
campaignId: { type: 'string', description: 'Filter by campaign ID' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cancel_scheduled_campaign_message',
|
||||||
|
description: 'Cancel a scheduled campaign message for a contact',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
messageId: { type: 'string', description: 'Scheduled message ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['messageId']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleToolCall(toolName: string, args: Record<string, unknown>): Promise<unknown> {
|
||||||
|
const config = this.ghlClient.getConfig();
|
||||||
|
const locationId = (args.locationId as string) || config.locationId;
|
||||||
|
|
||||||
|
switch (toolName) {
|
||||||
|
case 'get_campaigns': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.status) params.append('status', String(args.status));
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.offset) params.append('offset', String(args.offset));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/campaigns/?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'get_campaign': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/campaigns/${args.campaignId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'create_campaign': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/campaigns/`, {
|
||||||
|
locationId,
|
||||||
|
name: args.name,
|
||||||
|
type: args.type,
|
||||||
|
status: args.status || 'draft'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case 'update_campaign': {
|
||||||
|
const body: Record<string, unknown> = { locationId };
|
||||||
|
if (args.name) body.name = args.name;
|
||||||
|
if (args.status) body.status = args.status;
|
||||||
|
return this.ghlClient.makeRequest('PUT', `/campaigns/${args.campaignId}`, body);
|
||||||
|
}
|
||||||
|
case 'delete_campaign': {
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/campaigns/${args.campaignId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'start_campaign': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/campaigns/${args.campaignId}/start`, { locationId });
|
||||||
|
}
|
||||||
|
case 'pause_campaign': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/campaigns/${args.campaignId}/pause`, { locationId });
|
||||||
|
}
|
||||||
|
case 'resume_campaign': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/campaigns/${args.campaignId}/resume`, { locationId });
|
||||||
|
}
|
||||||
|
case 'get_campaign_stats': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/campaigns/${args.campaignId}/stats?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'get_campaign_recipients': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.status) params.append('status', String(args.status));
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.offset) params.append('offset', String(args.offset));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/campaigns/${args.campaignId}/recipients?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'get_scheduled_messages': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.contactId) params.append('contactId', String(args.contactId));
|
||||||
|
if (args.campaignId) params.append('campaignId', String(args.campaignId));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/campaigns/scheduled-messages?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'cancel_scheduled_campaign_message': {
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/campaigns/scheduled-messages/${args.messageId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown tool: ${toolName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
304
src/tools/companies-tools.ts
Normal file
304
src/tools/companies-tools.ts
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
/**
|
||||||
|
* GoHighLevel Companies Tools
|
||||||
|
* Tools for managing company records (B2B CRM functionality)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { GHLApiClient } from '../clients/ghl-api-client.js';
|
||||||
|
|
||||||
|
export class CompaniesTools {
|
||||||
|
constructor(private ghlClient: GHLApiClient) {}
|
||||||
|
|
||||||
|
getToolDefinitions() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'get_companies',
|
||||||
|
description: 'Get all companies for a location. Companies represent business entities in B2B scenarios.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
},
|
||||||
|
skip: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Number of records to skip for pagination'
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Maximum number of companies to return'
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Search query to filter companies'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_company',
|
||||||
|
description: 'Get a specific company by ID',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
companyId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The company ID to retrieve'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['companyId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_company',
|
||||||
|
description: 'Create a new company record',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Company name'
|
||||||
|
},
|
||||||
|
phone: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Company phone number'
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Company email address'
|
||||||
|
},
|
||||||
|
website: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Company website URL'
|
||||||
|
},
|
||||||
|
address1: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Street address line 1'
|
||||||
|
},
|
||||||
|
address2: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Street address line 2'
|
||||||
|
},
|
||||||
|
city: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'City'
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'State/Province'
|
||||||
|
},
|
||||||
|
postalCode: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Postal/ZIP code'
|
||||||
|
},
|
||||||
|
country: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Country'
|
||||||
|
},
|
||||||
|
industry: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Industry/vertical'
|
||||||
|
},
|
||||||
|
employeeCount: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Number of employees'
|
||||||
|
},
|
||||||
|
annualRevenue: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Annual revenue'
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Company description'
|
||||||
|
},
|
||||||
|
customFields: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
id: { type: 'string' },
|
||||||
|
key: { type: 'string' },
|
||||||
|
value: { type: 'string' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
description: 'Custom field values'
|
||||||
|
},
|
||||||
|
tags: {
|
||||||
|
type: 'array',
|
||||||
|
items: { type: 'string' },
|
||||||
|
description: 'Tags to apply to the company'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['name']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_company',
|
||||||
|
description: 'Update an existing company record',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
companyId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The company ID to update'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Company name'
|
||||||
|
},
|
||||||
|
phone: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Company phone number'
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Company email address'
|
||||||
|
},
|
||||||
|
website: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Company website URL'
|
||||||
|
},
|
||||||
|
address1: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Street address line 1'
|
||||||
|
},
|
||||||
|
city: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'City'
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'State/Province'
|
||||||
|
},
|
||||||
|
postalCode: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Postal/ZIP code'
|
||||||
|
},
|
||||||
|
country: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Country'
|
||||||
|
},
|
||||||
|
industry: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Industry/vertical'
|
||||||
|
},
|
||||||
|
employeeCount: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Number of employees'
|
||||||
|
},
|
||||||
|
annualRevenue: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Annual revenue'
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Company description'
|
||||||
|
},
|
||||||
|
customFields: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
id: { type: 'string' },
|
||||||
|
key: { type: 'string' },
|
||||||
|
value: { type: 'string' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
description: 'Custom field values'
|
||||||
|
},
|
||||||
|
tags: {
|
||||||
|
type: 'array',
|
||||||
|
items: { type: 'string' },
|
||||||
|
description: 'Tags to apply to the company'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['companyId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete_company',
|
||||||
|
description: 'Delete a company record',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
companyId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The company ID to delete'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['companyId']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleToolCall(toolName: string, args: Record<string, unknown>): Promise<unknown> {
|
||||||
|
const config = this.ghlClient.getConfig();
|
||||||
|
const locationId = (args.locationId as string) || config.locationId;
|
||||||
|
|
||||||
|
switch (toolName) {
|
||||||
|
case 'get_companies': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.skip) params.append('skip', String(args.skip));
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.query) params.append('query', String(args.query));
|
||||||
|
|
||||||
|
return this.ghlClient.makeRequest('GET', `/companies/?${params.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'get_company': {
|
||||||
|
const companyId = args.companyId as string;
|
||||||
|
return this.ghlClient.makeRequest('GET', `/companies/${companyId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'create_company': {
|
||||||
|
const body: Record<string, unknown> = {
|
||||||
|
locationId,
|
||||||
|
name: args.name
|
||||||
|
};
|
||||||
|
const optionalFields = ['phone', 'email', 'website', 'address1', 'address2', 'city', 'state', 'postalCode', 'country', 'industry', 'employeeCount', 'annualRevenue', 'description', 'customFields', 'tags'];
|
||||||
|
optionalFields.forEach(field => {
|
||||||
|
if (args[field] !== undefined) body[field] = args[field];
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.ghlClient.makeRequest('POST', `/companies/`, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'update_company': {
|
||||||
|
const companyId = args.companyId as string;
|
||||||
|
const body: Record<string, unknown> = {};
|
||||||
|
const optionalFields = ['name', 'phone', 'email', 'website', 'address1', 'address2', 'city', 'state', 'postalCode', 'country', 'industry', 'employeeCount', 'annualRevenue', 'description', 'customFields', 'tags'];
|
||||||
|
optionalFields.forEach(field => {
|
||||||
|
if (args[field] !== undefined) body[field] = args[field];
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.ghlClient.makeRequest('PUT', `/companies/${companyId}`, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'delete_company': {
|
||||||
|
const companyId = args.companyId as string;
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/companies/${companyId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown tool: ${toolName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
674
src/tools/courses-tools.ts
Normal file
674
src/tools/courses-tools.ts
Normal file
@ -0,0 +1,674 @@
|
|||||||
|
/**
|
||||||
|
* GoHighLevel Courses/Memberships Tools
|
||||||
|
* Tools for managing courses, products, and memberships
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { GHLApiClient } from '../clients/ghl-api-client.js';
|
||||||
|
|
||||||
|
export class CoursesTools {
|
||||||
|
constructor(private ghlClient: GHLApiClient) {}
|
||||||
|
|
||||||
|
getToolDefinitions() {
|
||||||
|
return [
|
||||||
|
// Course Importers
|
||||||
|
{
|
||||||
|
name: 'get_course_importers',
|
||||||
|
description: 'Get list of all course import jobs/processes',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID (uses default if not provided)' },
|
||||||
|
limit: { type: 'number', description: 'Max results to return' },
|
||||||
|
offset: { type: 'number', description: 'Offset for pagination' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_course_importer',
|
||||||
|
description: 'Create a new course import job to import courses from external sources',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
name: { type: 'string', description: 'Import job name' },
|
||||||
|
sourceUrl: { type: 'string', description: 'Source URL to import from' },
|
||||||
|
type: { type: 'string', description: 'Import type' }
|
||||||
|
},
|
||||||
|
required: ['name']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Course Products
|
||||||
|
{
|
||||||
|
name: 'get_course_products',
|
||||||
|
description: 'Get all course products (purchasable course bundles)',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
limit: { type: 'number', description: 'Max results' },
|
||||||
|
offset: { type: 'number', description: 'Pagination offset' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_course_product',
|
||||||
|
description: 'Get a specific course product by ID',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
productId: { type: 'string', description: 'Course product ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['productId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_course_product',
|
||||||
|
description: 'Create a new course product',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
title: { type: 'string', description: 'Product title' },
|
||||||
|
description: { type: 'string', description: 'Product description' },
|
||||||
|
imageUrl: { type: 'string', description: 'Product image URL' },
|
||||||
|
statementDescriptor: { type: 'string', description: 'Payment statement descriptor' }
|
||||||
|
},
|
||||||
|
required: ['title']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_course_product',
|
||||||
|
description: 'Update a course product',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
productId: { type: 'string', description: 'Course product ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
title: { type: 'string', description: 'Product title' },
|
||||||
|
description: { type: 'string', description: 'Product description' },
|
||||||
|
imageUrl: { type: 'string', description: 'Product image URL' }
|
||||||
|
},
|
||||||
|
required: ['productId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete_course_product',
|
||||||
|
description: 'Delete a course product',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
productId: { type: 'string', description: 'Course product ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['productId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Categories
|
||||||
|
{
|
||||||
|
name: 'get_course_categories',
|
||||||
|
description: 'Get all course categories',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
limit: { type: 'number', description: 'Max results' },
|
||||||
|
offset: { type: 'number', description: 'Pagination offset' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_course_category',
|
||||||
|
description: 'Create a new course category',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
title: { type: 'string', description: 'Category title' }
|
||||||
|
},
|
||||||
|
required: ['title']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_course_category',
|
||||||
|
description: 'Update a course category',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
categoryId: { type: 'string', description: 'Category ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
title: { type: 'string', description: 'Category title' }
|
||||||
|
},
|
||||||
|
required: ['categoryId', 'title']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete_course_category',
|
||||||
|
description: 'Delete a course category',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
categoryId: { type: 'string', description: 'Category ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['categoryId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Courses
|
||||||
|
{
|
||||||
|
name: 'get_courses',
|
||||||
|
description: 'Get all courses',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
limit: { type: 'number', description: 'Max results' },
|
||||||
|
offset: { type: 'number', description: 'Pagination offset' },
|
||||||
|
categoryId: { type: 'string', description: 'Filter by category' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_course',
|
||||||
|
description: 'Get a specific course by ID',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
courseId: { type: 'string', description: 'Course ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['courseId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_course',
|
||||||
|
description: 'Create a new course',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
title: { type: 'string', description: 'Course title' },
|
||||||
|
description: { type: 'string', description: 'Course description' },
|
||||||
|
thumbnailUrl: { type: 'string', description: 'Course thumbnail URL' },
|
||||||
|
visibility: { type: 'string', enum: ['published', 'draft'], description: 'Course visibility' },
|
||||||
|
categoryId: { type: 'string', description: 'Category ID to place course in' }
|
||||||
|
},
|
||||||
|
required: ['title']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_course',
|
||||||
|
description: 'Update a course',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
courseId: { type: 'string', description: 'Course ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
title: { type: 'string', description: 'Course title' },
|
||||||
|
description: { type: 'string', description: 'Course description' },
|
||||||
|
thumbnailUrl: { type: 'string', description: 'Course thumbnail URL' },
|
||||||
|
visibility: { type: 'string', enum: ['published', 'draft'], description: 'Course visibility' }
|
||||||
|
},
|
||||||
|
required: ['courseId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete_course',
|
||||||
|
description: 'Delete a course',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
courseId: { type: 'string', description: 'Course ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['courseId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Instructors
|
||||||
|
{
|
||||||
|
name: 'get_course_instructors',
|
||||||
|
description: 'Get all instructors for a course',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
courseId: { type: 'string', description: 'Course ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['courseId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'add_course_instructor',
|
||||||
|
description: 'Add an instructor to a course',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
courseId: { type: 'string', description: 'Course ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
userId: { type: 'string', description: 'User ID of instructor' },
|
||||||
|
name: { type: 'string', description: 'Instructor display name' },
|
||||||
|
bio: { type: 'string', description: 'Instructor bio' }
|
||||||
|
},
|
||||||
|
required: ['courseId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Posts/Lessons
|
||||||
|
{
|
||||||
|
name: 'get_course_posts',
|
||||||
|
description: 'Get all posts/lessons in a course',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
courseId: { type: 'string', description: 'Course ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
limit: { type: 'number', description: 'Max results' },
|
||||||
|
offset: { type: 'number', description: 'Pagination offset' }
|
||||||
|
},
|
||||||
|
required: ['courseId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_course_post',
|
||||||
|
description: 'Get a specific course post/lesson',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
courseId: { type: 'string', description: 'Course ID' },
|
||||||
|
postId: { type: 'string', description: 'Post/Lesson ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['courseId', 'postId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_course_post',
|
||||||
|
description: 'Create a new course post/lesson',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
courseId: { type: 'string', description: 'Course ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
title: { type: 'string', description: 'Post/lesson title' },
|
||||||
|
contentType: { type: 'string', enum: ['video', 'text', 'quiz', 'assignment'], description: 'Content type' },
|
||||||
|
content: { type: 'string', description: 'Post content (text/HTML)' },
|
||||||
|
videoUrl: { type: 'string', description: 'Video URL (if video type)' },
|
||||||
|
visibility: { type: 'string', enum: ['published', 'draft'], description: 'Visibility' }
|
||||||
|
},
|
||||||
|
required: ['courseId', 'title']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_course_post',
|
||||||
|
description: 'Update a course post/lesson',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
courseId: { type: 'string', description: 'Course ID' },
|
||||||
|
postId: { type: 'string', description: 'Post/Lesson ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
title: { type: 'string', description: 'Post/lesson title' },
|
||||||
|
content: { type: 'string', description: 'Post content' },
|
||||||
|
videoUrl: { type: 'string', description: 'Video URL' },
|
||||||
|
visibility: { type: 'string', enum: ['published', 'draft'], description: 'Visibility' }
|
||||||
|
},
|
||||||
|
required: ['courseId', 'postId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete_course_post',
|
||||||
|
description: 'Delete a course post/lesson',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
courseId: { type: 'string', description: 'Course ID' },
|
||||||
|
postId: { type: 'string', description: 'Post/Lesson ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['courseId', 'postId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Offers
|
||||||
|
{
|
||||||
|
name: 'get_course_offers',
|
||||||
|
description: 'Get all offers (pricing tiers) for a course product',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
productId: { type: 'string', description: 'Course product ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['productId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_course_offer',
|
||||||
|
description: 'Create a new offer for a course product',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
productId: { type: 'string', description: 'Course product ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
name: { type: 'string', description: 'Offer name' },
|
||||||
|
price: { type: 'number', description: 'Price in cents' },
|
||||||
|
currency: { type: 'string', description: 'Currency code (e.g., USD)' },
|
||||||
|
type: { type: 'string', enum: ['one-time', 'subscription'], description: 'Payment type' },
|
||||||
|
interval: { type: 'string', enum: ['month', 'year'], description: 'Subscription interval (if subscription)' }
|
||||||
|
},
|
||||||
|
required: ['productId', 'name', 'price']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_course_offer',
|
||||||
|
description: 'Update a course offer',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
productId: { type: 'string', description: 'Course product ID' },
|
||||||
|
offerId: { type: 'string', description: 'Offer ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
name: { type: 'string', description: 'Offer name' },
|
||||||
|
price: { type: 'number', description: 'Price in cents' }
|
||||||
|
},
|
||||||
|
required: ['productId', 'offerId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete_course_offer',
|
||||||
|
description: 'Delete a course offer',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
productId: { type: 'string', description: 'Course product ID' },
|
||||||
|
offerId: { type: 'string', description: 'Offer ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['productId', 'offerId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Student/Enrollment Management
|
||||||
|
{
|
||||||
|
name: 'get_course_enrollments',
|
||||||
|
description: 'Get all enrollments for a course',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
courseId: { type: 'string', description: 'Course ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
limit: { type: 'number', description: 'Max results' },
|
||||||
|
offset: { type: 'number', description: 'Pagination offset' }
|
||||||
|
},
|
||||||
|
required: ['courseId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'enroll_contact_in_course',
|
||||||
|
description: 'Enroll a contact in a course',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
courseId: { type: 'string', description: 'Course ID' },
|
||||||
|
contactId: { type: 'string', description: 'Contact ID to enroll' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['courseId', 'contactId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'remove_course_enrollment',
|
||||||
|
description: 'Remove a contact from a course',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
courseId: { type: 'string', description: 'Course ID' },
|
||||||
|
contactId: { type: 'string', description: 'Contact ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['courseId', 'contactId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Progress tracking
|
||||||
|
{
|
||||||
|
name: 'get_student_progress',
|
||||||
|
description: 'Get a student\'s progress in a course',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
courseId: { type: 'string', description: 'Course ID' },
|
||||||
|
contactId: { type: 'string', description: 'Contact/Student ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['courseId', 'contactId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_lesson_completion',
|
||||||
|
description: 'Mark a lesson as complete/incomplete for a student',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
courseId: { type: 'string', description: 'Course ID' },
|
||||||
|
postId: { type: 'string', description: 'Post/Lesson ID' },
|
||||||
|
contactId: { type: 'string', description: 'Contact/Student ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
completed: { type: 'boolean', description: 'Whether lesson is completed' }
|
||||||
|
},
|
||||||
|
required: ['courseId', 'postId', 'contactId', 'completed']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleToolCall(toolName: string, args: Record<string, unknown>): Promise<unknown> {
|
||||||
|
const config = this.ghlClient.getConfig();
|
||||||
|
const locationId = (args.locationId as string) || config.locationId;
|
||||||
|
|
||||||
|
switch (toolName) {
|
||||||
|
// Course Importers
|
||||||
|
case 'get_course_importers': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.offset) params.append('offset', String(args.offset));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/courses/courses-exporter?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'create_course_importer': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/courses/courses-exporter`, {
|
||||||
|
locationId,
|
||||||
|
name: args.name,
|
||||||
|
sourceUrl: args.sourceUrl,
|
||||||
|
type: args.type
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Course Products
|
||||||
|
case 'get_course_products': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.offset) params.append('offset', String(args.offset));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/courses/courses-exporter/products?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'get_course_product': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/courses/courses-exporter/products/${args.productId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'create_course_product': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/courses/courses-exporter/products`, {
|
||||||
|
locationId,
|
||||||
|
title: args.title,
|
||||||
|
description: args.description,
|
||||||
|
imageUrl: args.imageUrl,
|
||||||
|
statementDescriptor: args.statementDescriptor
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case 'update_course_product': {
|
||||||
|
const body: Record<string, unknown> = { locationId };
|
||||||
|
if (args.title) body.title = args.title;
|
||||||
|
if (args.description) body.description = args.description;
|
||||||
|
if (args.imageUrl) body.imageUrl = args.imageUrl;
|
||||||
|
return this.ghlClient.makeRequest('PUT', `/courses/courses-exporter/products/${args.productId}`, body);
|
||||||
|
}
|
||||||
|
case 'delete_course_product': {
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/courses/courses-exporter/products/${args.productId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Categories
|
||||||
|
case 'get_course_categories': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.offset) params.append('offset', String(args.offset));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/courses/categories?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'create_course_category': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/courses/categories`, { locationId, title: args.title });
|
||||||
|
}
|
||||||
|
case 'update_course_category': {
|
||||||
|
return this.ghlClient.makeRequest('PUT', `/courses/categories/${args.categoryId}`, { locationId, title: args.title });
|
||||||
|
}
|
||||||
|
case 'delete_course_category': {
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/courses/categories/${args.categoryId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Courses
|
||||||
|
case 'get_courses': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.offset) params.append('offset', String(args.offset));
|
||||||
|
if (args.categoryId) params.append('categoryId', String(args.categoryId));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/courses?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'get_course': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/courses/${args.courseId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'create_course': {
|
||||||
|
const body: Record<string, unknown> = { locationId, title: args.title };
|
||||||
|
if (args.description) body.description = args.description;
|
||||||
|
if (args.thumbnailUrl) body.thumbnailUrl = args.thumbnailUrl;
|
||||||
|
if (args.visibility) body.visibility = args.visibility;
|
||||||
|
if (args.categoryId) body.categoryId = args.categoryId;
|
||||||
|
return this.ghlClient.makeRequest('POST', `/courses`, body);
|
||||||
|
}
|
||||||
|
case 'update_course': {
|
||||||
|
const body: Record<string, unknown> = { locationId };
|
||||||
|
if (args.title) body.title = args.title;
|
||||||
|
if (args.description) body.description = args.description;
|
||||||
|
if (args.thumbnailUrl) body.thumbnailUrl = args.thumbnailUrl;
|
||||||
|
if (args.visibility) body.visibility = args.visibility;
|
||||||
|
return this.ghlClient.makeRequest('PUT', `/courses/${args.courseId}`, body);
|
||||||
|
}
|
||||||
|
case 'delete_course': {
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/courses/${args.courseId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instructors
|
||||||
|
case 'get_course_instructors': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/courses/${args.courseId}/instructors?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'add_course_instructor': {
|
||||||
|
const body: Record<string, unknown> = { locationId };
|
||||||
|
if (args.userId) body.userId = args.userId;
|
||||||
|
if (args.name) body.name = args.name;
|
||||||
|
if (args.bio) body.bio = args.bio;
|
||||||
|
return this.ghlClient.makeRequest('POST', `/courses/${args.courseId}/instructors`, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Posts/Lessons
|
||||||
|
case 'get_course_posts': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.offset) params.append('offset', String(args.offset));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/courses/${args.courseId}/posts?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'get_course_post': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/courses/${args.courseId}/posts/${args.postId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'create_course_post': {
|
||||||
|
const body: Record<string, unknown> = { locationId, title: args.title };
|
||||||
|
if (args.contentType) body.contentType = args.contentType;
|
||||||
|
if (args.content) body.content = args.content;
|
||||||
|
if (args.videoUrl) body.videoUrl = args.videoUrl;
|
||||||
|
if (args.visibility) body.visibility = args.visibility;
|
||||||
|
return this.ghlClient.makeRequest('POST', `/courses/${args.courseId}/posts`, body);
|
||||||
|
}
|
||||||
|
case 'update_course_post': {
|
||||||
|
const body: Record<string, unknown> = { locationId };
|
||||||
|
if (args.title) body.title = args.title;
|
||||||
|
if (args.content) body.content = args.content;
|
||||||
|
if (args.videoUrl) body.videoUrl = args.videoUrl;
|
||||||
|
if (args.visibility) body.visibility = args.visibility;
|
||||||
|
return this.ghlClient.makeRequest('PUT', `/courses/${args.courseId}/posts/${args.postId}`, body);
|
||||||
|
}
|
||||||
|
case 'delete_course_post': {
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/courses/${args.courseId}/posts/${args.postId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offers
|
||||||
|
case 'get_course_offers': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/courses/courses-exporter/products/${args.productId}/offers?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'create_course_offer': {
|
||||||
|
const body: Record<string, unknown> = {
|
||||||
|
locationId,
|
||||||
|
name: args.name,
|
||||||
|
price: args.price
|
||||||
|
};
|
||||||
|
if (args.currency) body.currency = args.currency;
|
||||||
|
if (args.type) body.type = args.type;
|
||||||
|
if (args.interval) body.interval = args.interval;
|
||||||
|
return this.ghlClient.makeRequest('POST', `/courses/courses-exporter/products/${args.productId}/offers`, body);
|
||||||
|
}
|
||||||
|
case 'update_course_offer': {
|
||||||
|
const body: Record<string, unknown> = { locationId };
|
||||||
|
if (args.name) body.name = args.name;
|
||||||
|
if (args.price) body.price = args.price;
|
||||||
|
return this.ghlClient.makeRequest('PUT', `/courses/courses-exporter/products/${args.productId}/offers/${args.offerId}`, body);
|
||||||
|
}
|
||||||
|
case 'delete_course_offer': {
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/courses/courses-exporter/products/${args.productId}/offers/${args.offerId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enrollments
|
||||||
|
case 'get_course_enrollments': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.offset) params.append('offset', String(args.offset));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/courses/${args.courseId}/enrollments?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'enroll_contact_in_course': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/courses/${args.courseId}/enrollments`, {
|
||||||
|
locationId,
|
||||||
|
contactId: args.contactId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case 'remove_course_enrollment': {
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/courses/${args.courseId}/enrollments/${args.contactId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Progress
|
||||||
|
case 'get_student_progress': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/courses/${args.courseId}/progress/${args.contactId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'update_lesson_completion': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/courses/${args.courseId}/posts/${args.postId}/completion`, {
|
||||||
|
locationId,
|
||||||
|
contactId: args.contactId,
|
||||||
|
completed: args.completed
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown tool: ${toolName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
134
src/tools/forms-tools.ts
Normal file
134
src/tools/forms-tools.ts
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
/**
|
||||||
|
* GoHighLevel Forms Tools
|
||||||
|
* Tools for managing forms and form submissions
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { GHLApiClient } from '../clients/ghl-api-client.js';
|
||||||
|
|
||||||
|
export class FormsTools {
|
||||||
|
constructor(private ghlClient: GHLApiClient) {}
|
||||||
|
|
||||||
|
getToolDefinitions() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'get_forms',
|
||||||
|
description: 'Get all forms for a location. Forms are used to collect leads and information from contacts.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
},
|
||||||
|
skip: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Number of records to skip for pagination'
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Maximum number of forms to return (default: 20, max: 100)'
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Filter by form type (e.g., "form", "survey")'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_form_submissions',
|
||||||
|
description: 'Get all submissions for a specific form. Returns lead data collected through the form.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
formId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Form ID to get submissions for'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
},
|
||||||
|
startAt: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Start date for filtering submissions (ISO 8601 format)'
|
||||||
|
},
|
||||||
|
endAt: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'End date for filtering submissions (ISO 8601 format)'
|
||||||
|
},
|
||||||
|
skip: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Number of records to skip for pagination'
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Maximum number of submissions to return (default: 20, max: 100)'
|
||||||
|
},
|
||||||
|
page: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Page number for pagination'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['formId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_form_by_id',
|
||||||
|
description: 'Get a specific form by its ID',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
formId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The form ID to retrieve'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['formId']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleToolCall(toolName: string, args: Record<string, unknown>): Promise<unknown> {
|
||||||
|
const config = this.ghlClient.getConfig();
|
||||||
|
const locationId = (args.locationId as string) || config.locationId;
|
||||||
|
|
||||||
|
switch (toolName) {
|
||||||
|
case 'get_forms': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.skip) params.append('skip', String(args.skip));
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.type) params.append('type', String(args.type));
|
||||||
|
|
||||||
|
return this.ghlClient.makeRequest('GET', `/forms/?${params.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'get_form_submissions': {
|
||||||
|
const formId = args.formId as string;
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.startAt) params.append('startAt', String(args.startAt));
|
||||||
|
if (args.endAt) params.append('endAt', String(args.endAt));
|
||||||
|
if (args.skip) params.append('skip', String(args.skip));
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.page) params.append('page', String(args.page));
|
||||||
|
|
||||||
|
return this.ghlClient.makeRequest('GET', `/forms/submissions?formId=${formId}&${params.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'get_form_by_id': {
|
||||||
|
const formId = args.formId as string;
|
||||||
|
return this.ghlClient.makeRequest('GET', `/forms/${formId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown tool: ${toolName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
311
src/tools/funnels-tools.ts
Normal file
311
src/tools/funnels-tools.ts
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
/**
|
||||||
|
* GoHighLevel Funnels Tools
|
||||||
|
* Tools for managing funnels and funnel pages
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { GHLApiClient } from '../clients/ghl-api-client.js';
|
||||||
|
|
||||||
|
export class FunnelsTools {
|
||||||
|
constructor(private ghlClient: GHLApiClient) {}
|
||||||
|
|
||||||
|
getToolDefinitions() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'get_funnels',
|
||||||
|
description: 'Get all funnels for a location. Funnels are multi-page sales/marketing flows.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
},
|
||||||
|
skip: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Number of records to skip for pagination'
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Maximum number of funnels to return (default: 10)'
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Filter by funnel name (partial match)'
|
||||||
|
},
|
||||||
|
category: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Filter by category'
|
||||||
|
},
|
||||||
|
parentId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Filter by parent folder ID'
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: 'string',
|
||||||
|
enum: ['funnel', 'website'],
|
||||||
|
description: 'Filter by type (funnel or website)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_funnel',
|
||||||
|
description: 'Get a specific funnel by ID with all its pages',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
funnelId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The funnel ID to retrieve'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['funnelId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_funnel_pages',
|
||||||
|
description: 'Get all pages for a specific funnel',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
funnelId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The funnel ID to get pages for'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
},
|
||||||
|
skip: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Number of records to skip'
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Maximum number of pages to return'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['funnelId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'count_funnel_pages',
|
||||||
|
description: 'Get the total count of pages for a funnel',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
funnelId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The funnel ID'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['funnelId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_funnel_redirect',
|
||||||
|
description: 'Create a URL redirect for a funnel',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
funnelId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The funnel ID'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Target URL to redirect to'
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
type: 'string',
|
||||||
|
enum: ['funnel', 'website', 'url', 'all'],
|
||||||
|
description: 'Redirect action type'
|
||||||
|
},
|
||||||
|
pathName: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Source path for the redirect'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['funnelId', 'target', 'action']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_funnel_redirect',
|
||||||
|
description: 'Update an existing funnel redirect',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
funnelId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The funnel ID'
|
||||||
|
},
|
||||||
|
redirectId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The redirect ID to update'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Target URL to redirect to'
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
type: 'string',
|
||||||
|
enum: ['funnel', 'website', 'url', 'all'],
|
||||||
|
description: 'Redirect action type'
|
||||||
|
},
|
||||||
|
pathName: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Source path for the redirect'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['funnelId', 'redirectId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete_funnel_redirect',
|
||||||
|
description: 'Delete a funnel redirect',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
funnelId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The funnel ID'
|
||||||
|
},
|
||||||
|
redirectId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The redirect ID to delete'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['funnelId', 'redirectId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_funnel_redirects',
|
||||||
|
description: 'List all redirects for a funnel',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
funnelId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The funnel ID'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
},
|
||||||
|
skip: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Number of records to skip'
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Maximum number of redirects to return'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['funnelId']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleToolCall(toolName: string, args: Record<string, unknown>): Promise<unknown> {
|
||||||
|
const config = this.ghlClient.getConfig();
|
||||||
|
const locationId = (args.locationId as string) || config.locationId;
|
||||||
|
|
||||||
|
switch (toolName) {
|
||||||
|
case 'get_funnels': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.skip) params.append('skip', String(args.skip));
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.name) params.append('name', String(args.name));
|
||||||
|
if (args.category) params.append('category', String(args.category));
|
||||||
|
if (args.parentId) params.append('parentId', String(args.parentId));
|
||||||
|
if (args.type) params.append('type', String(args.type));
|
||||||
|
|
||||||
|
return this.ghlClient.makeRequest('GET', `/funnels/?${params.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'get_funnel': {
|
||||||
|
const funnelId = args.funnelId as string;
|
||||||
|
return this.ghlClient.makeRequest('GET', `/funnels/${funnelId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'get_funnel_pages': {
|
||||||
|
const funnelId = args.funnelId as string;
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.skip) params.append('skip', String(args.skip));
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
|
||||||
|
return this.ghlClient.makeRequest('GET', `/funnels/${funnelId}/pages?${params.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'count_funnel_pages': {
|
||||||
|
const funnelId = args.funnelId as string;
|
||||||
|
return this.ghlClient.makeRequest('GET', `/funnels/${funnelId}/pages/count?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'create_funnel_redirect': {
|
||||||
|
const funnelId = args.funnelId as string;
|
||||||
|
const body: Record<string, unknown> = {
|
||||||
|
locationId,
|
||||||
|
target: args.target,
|
||||||
|
action: args.action
|
||||||
|
};
|
||||||
|
if (args.pathName) body.pathName = args.pathName;
|
||||||
|
|
||||||
|
return this.ghlClient.makeRequest('POST', `/funnels/${funnelId}/redirect`, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'update_funnel_redirect': {
|
||||||
|
const funnelId = args.funnelId as string;
|
||||||
|
const redirectId = args.redirectId as string;
|
||||||
|
const body: Record<string, unknown> = { locationId };
|
||||||
|
if (args.target) body.target = args.target;
|
||||||
|
if (args.action) body.action = args.action;
|
||||||
|
if (args.pathName) body.pathName = args.pathName;
|
||||||
|
|
||||||
|
return this.ghlClient.makeRequest('PATCH', `/funnels/${funnelId}/redirect/${redirectId}`, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'delete_funnel_redirect': {
|
||||||
|
const funnelId = args.funnelId as string;
|
||||||
|
const redirectId = args.redirectId as string;
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/funnels/${funnelId}/redirect/${redirectId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'get_funnel_redirects': {
|
||||||
|
const funnelId = args.funnelId as string;
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.skip) params.append('skip', String(args.skip));
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
|
||||||
|
return this.ghlClient.makeRequest('GET', `/funnels/${funnelId}/redirect?${params.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown tool: ${toolName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
188
src/tools/links-tools.ts
Normal file
188
src/tools/links-tools.ts
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
/**
|
||||||
|
* GoHighLevel Links (Trigger Links) Tools
|
||||||
|
* Tools for managing trigger links and link tracking
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { GHLApiClient } from '../clients/ghl-api-client.js';
|
||||||
|
|
||||||
|
export class LinksTools {
|
||||||
|
constructor(private ghlClient: GHLApiClient) {}
|
||||||
|
|
||||||
|
getToolDefinitions() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'get_links',
|
||||||
|
description: 'Get all trigger links for a location. Trigger links are trackable URLs that can trigger automations.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
},
|
||||||
|
skip: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Number of records to skip for pagination'
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Maximum number of links to return'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_link',
|
||||||
|
description: 'Get a specific trigger link by ID',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
linkId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The link ID to retrieve'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['linkId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_link',
|
||||||
|
description: 'Create a new trigger link',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Link name for identification'
|
||||||
|
},
|
||||||
|
redirectTo: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Target URL to redirect to when clicked'
|
||||||
|
},
|
||||||
|
fieldKey: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Custom field key to update on click'
|
||||||
|
},
|
||||||
|
fieldValue: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Value to set for the custom field'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['name', 'redirectTo']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_link',
|
||||||
|
description: 'Update an existing trigger link',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
linkId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The link ID to update'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Link name for identification'
|
||||||
|
},
|
||||||
|
redirectTo: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Target URL to redirect to when clicked'
|
||||||
|
},
|
||||||
|
fieldKey: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Custom field key to update on click'
|
||||||
|
},
|
||||||
|
fieldValue: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Value to set for the custom field'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['linkId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete_link',
|
||||||
|
description: 'Delete a trigger link',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
linkId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The link ID to delete'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['linkId']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleToolCall(toolName: string, args: Record<string, unknown>): Promise<unknown> {
|
||||||
|
const config = this.ghlClient.getConfig();
|
||||||
|
const locationId = (args.locationId as string) || config.locationId;
|
||||||
|
|
||||||
|
switch (toolName) {
|
||||||
|
case 'get_links': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.skip) params.append('skip', String(args.skip));
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
|
||||||
|
return this.ghlClient.makeRequest('GET', `/links/?${params.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'get_link': {
|
||||||
|
const linkId = args.linkId as string;
|
||||||
|
return this.ghlClient.makeRequest('GET', `/links/${linkId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'create_link': {
|
||||||
|
const body: Record<string, unknown> = {
|
||||||
|
locationId,
|
||||||
|
name: args.name,
|
||||||
|
redirectTo: args.redirectTo
|
||||||
|
};
|
||||||
|
if (args.fieldKey) body.fieldKey = args.fieldKey;
|
||||||
|
if (args.fieldValue) body.fieldValue = args.fieldValue;
|
||||||
|
|
||||||
|
return this.ghlClient.makeRequest('POST', `/links/`, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'update_link': {
|
||||||
|
const linkId = args.linkId as string;
|
||||||
|
const body: Record<string, unknown> = { locationId };
|
||||||
|
if (args.name) body.name = args.name;
|
||||||
|
if (args.redirectTo) body.redirectTo = args.redirectTo;
|
||||||
|
if (args.fieldKey) body.fieldKey = args.fieldKey;
|
||||||
|
if (args.fieldValue) body.fieldValue = args.fieldValue;
|
||||||
|
|
||||||
|
return this.ghlClient.makeRequest('PUT', `/links/${linkId}`, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'delete_link': {
|
||||||
|
const linkId = args.linkId as string;
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/links/${linkId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown tool: ${toolName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
200
src/tools/oauth-tools.ts
Normal file
200
src/tools/oauth-tools.ts
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
/**
|
||||||
|
* GoHighLevel OAuth/Auth Tools
|
||||||
|
* Tools for managing OAuth apps, tokens, and integrations
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { GHLApiClient } from '../clients/ghl-api-client.js';
|
||||||
|
|
||||||
|
export class OAuthTools {
|
||||||
|
constructor(private ghlClient: GHLApiClient) {}
|
||||||
|
|
||||||
|
getToolDefinitions() {
|
||||||
|
return [
|
||||||
|
// OAuth Apps
|
||||||
|
{
|
||||||
|
name: 'get_oauth_apps',
|
||||||
|
description: 'Get all OAuth applications/integrations for a location',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
companyId: { type: 'string', description: 'Company ID for agency-level apps' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_oauth_app',
|
||||||
|
description: 'Get a specific OAuth application by ID',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
appId: { type: 'string', description: 'OAuth App ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['appId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_installed_locations',
|
||||||
|
description: 'Get all locations where an OAuth app is installed',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
appId: { type: 'string', description: 'OAuth App ID' },
|
||||||
|
companyId: { type: 'string', description: 'Company ID' },
|
||||||
|
skip: { type: 'number', description: 'Records to skip' },
|
||||||
|
limit: { type: 'number', description: 'Max results' },
|
||||||
|
query: { type: 'string', description: 'Search query' },
|
||||||
|
isInstalled: { type: 'boolean', description: 'Filter by installation status' }
|
||||||
|
},
|
||||||
|
required: ['appId', 'companyId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Access Tokens
|
||||||
|
{
|
||||||
|
name: 'get_access_token_info',
|
||||||
|
description: 'Get information about the current access token',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_location_access_token',
|
||||||
|
description: 'Get an access token for a specific location (agency use)',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
companyId: { type: 'string', description: 'Company/Agency ID' },
|
||||||
|
locationId: { type: 'string', description: 'Target Location ID' }
|
||||||
|
},
|
||||||
|
required: ['companyId', 'locationId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Connected Integrations
|
||||||
|
{
|
||||||
|
name: 'get_connected_integrations',
|
||||||
|
description: 'Get all connected third-party integrations for a location',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'disconnect_integration',
|
||||||
|
description: 'Disconnect a third-party integration',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
integrationId: { type: 'string', description: 'Integration ID to disconnect' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['integrationId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// API Keys
|
||||||
|
{
|
||||||
|
name: 'get_api_keys',
|
||||||
|
description: 'List all API keys for a location',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_api_key',
|
||||||
|
description: 'Create a new API key',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
name: { type: 'string', description: 'API key name/label' },
|
||||||
|
scopes: {
|
||||||
|
type: 'array',
|
||||||
|
items: { type: 'string' },
|
||||||
|
description: 'Permission scopes for the key'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['name']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete_api_key',
|
||||||
|
description: 'Delete/revoke an API key',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
keyId: { type: 'string', description: 'API Key ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['keyId']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleToolCall(toolName: string, args: Record<string, unknown>): Promise<unknown> {
|
||||||
|
const config = this.ghlClient.getConfig();
|
||||||
|
const locationId = (args.locationId as string) || config.locationId;
|
||||||
|
|
||||||
|
switch (toolName) {
|
||||||
|
case 'get_oauth_apps': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (locationId) params.append('locationId', locationId);
|
||||||
|
if (args.companyId) params.append('companyId', String(args.companyId));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/oauth/apps?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'get_oauth_app': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/oauth/apps/${args.appId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'get_installed_locations': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('appId', String(args.appId));
|
||||||
|
params.append('companyId', String(args.companyId));
|
||||||
|
if (args.skip) params.append('skip', String(args.skip));
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.query) params.append('query', String(args.query));
|
||||||
|
if (args.isInstalled !== undefined) params.append('isInstalled', String(args.isInstalled));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/oauth/installedLocations?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'get_access_token_info': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/oauth/locationToken`);
|
||||||
|
}
|
||||||
|
case 'get_location_access_token': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/oauth/locationToken`, {
|
||||||
|
companyId: args.companyId,
|
||||||
|
locationId: args.locationId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case 'get_connected_integrations': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/integrations/connected?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'disconnect_integration': {
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/integrations/${args.integrationId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'get_api_keys': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/oauth/api-keys?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'create_api_key': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/oauth/api-keys`, {
|
||||||
|
locationId,
|
||||||
|
name: args.name,
|
||||||
|
scopes: args.scopes
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case 'delete_api_key': {
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/oauth/api-keys/${args.keyId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown tool: ${toolName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
417
src/tools/phone-tools.ts
Normal file
417
src/tools/phone-tools.ts
Normal file
@ -0,0 +1,417 @@
|
|||||||
|
/**
|
||||||
|
* GoHighLevel Phone System Tools
|
||||||
|
* Tools for managing phone numbers, call settings, and IVR
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { GHLApiClient } from '../clients/ghl-api-client.js';
|
||||||
|
|
||||||
|
export class PhoneTools {
|
||||||
|
constructor(private ghlClient: GHLApiClient) {}
|
||||||
|
|
||||||
|
getToolDefinitions() {
|
||||||
|
return [
|
||||||
|
// Phone Numbers
|
||||||
|
{
|
||||||
|
name: 'get_phone_numbers',
|
||||||
|
description: 'Get all phone numbers for a location',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_phone_number',
|
||||||
|
description: 'Get a specific phone number by ID',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
phoneNumberId: { type: 'string', description: 'Phone Number ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['phoneNumberId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'search_available_numbers',
|
||||||
|
description: 'Search for available phone numbers to purchase',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
country: { type: 'string', description: 'Country code (e.g., US, CA)' },
|
||||||
|
areaCode: { type: 'string', description: 'Area code to search' },
|
||||||
|
contains: { type: 'string', description: 'Number pattern to search for' },
|
||||||
|
type: { type: 'string', enum: ['local', 'tollfree', 'mobile'], description: 'Number type' }
|
||||||
|
},
|
||||||
|
required: ['country']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'purchase_phone_number',
|
||||||
|
description: 'Purchase a phone number',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
phoneNumber: { type: 'string', description: 'Phone number to purchase' },
|
||||||
|
name: { type: 'string', description: 'Friendly name for the number' }
|
||||||
|
},
|
||||||
|
required: ['phoneNumber']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_phone_number',
|
||||||
|
description: 'Update phone number settings',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
phoneNumberId: { type: 'string', description: 'Phone Number ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
name: { type: 'string', description: 'Friendly name' },
|
||||||
|
forwardingNumber: { type: 'string', description: 'Number to forward calls to' },
|
||||||
|
callRecording: { type: 'boolean', description: 'Enable call recording' },
|
||||||
|
whisperMessage: { type: 'string', description: 'Whisper message played to agent' }
|
||||||
|
},
|
||||||
|
required: ['phoneNumberId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'release_phone_number',
|
||||||
|
description: 'Release/delete a phone number',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
phoneNumberId: { type: 'string', description: 'Phone Number ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['phoneNumberId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Call Forwarding
|
||||||
|
{
|
||||||
|
name: 'get_call_forwarding_settings',
|
||||||
|
description: 'Get call forwarding configuration',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
phoneNumberId: { type: 'string', description: 'Phone Number ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['phoneNumberId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_call_forwarding',
|
||||||
|
description: 'Update call forwarding settings',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
phoneNumberId: { type: 'string', description: 'Phone Number ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
enabled: { type: 'boolean', description: 'Enable forwarding' },
|
||||||
|
forwardTo: { type: 'string', description: 'Number to forward to' },
|
||||||
|
ringTimeout: { type: 'number', description: 'Ring timeout in seconds' },
|
||||||
|
voicemailEnabled: { type: 'boolean', description: 'Enable voicemail on no answer' }
|
||||||
|
},
|
||||||
|
required: ['phoneNumberId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// IVR/Call Menu
|
||||||
|
{
|
||||||
|
name: 'get_ivr_menus',
|
||||||
|
description: 'Get all IVR/call menus',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_ivr_menu',
|
||||||
|
description: 'Create an IVR/call menu',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
name: { type: 'string', description: 'Menu name' },
|
||||||
|
greeting: { type: 'string', description: 'Greeting message (text or URL)' },
|
||||||
|
options: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
digit: { type: 'string', description: 'Digit to press (0-9, *, #)' },
|
||||||
|
action: { type: 'string', description: 'Action type' },
|
||||||
|
destination: { type: 'string', description: 'Action destination' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
description: 'Menu options'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['name', 'greeting']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_ivr_menu',
|
||||||
|
description: 'Update an IVR menu',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
menuId: { type: 'string', description: 'IVR Menu ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
name: { type: 'string', description: 'Menu name' },
|
||||||
|
greeting: { type: 'string', description: 'Greeting message' },
|
||||||
|
options: { type: 'array', description: 'Menu options' }
|
||||||
|
},
|
||||||
|
required: ['menuId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete_ivr_menu',
|
||||||
|
description: 'Delete an IVR menu',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
menuId: { type: 'string', description: 'IVR Menu ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['menuId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Voicemail
|
||||||
|
{
|
||||||
|
name: 'get_voicemail_settings',
|
||||||
|
description: 'Get voicemail settings',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_voicemail_settings',
|
||||||
|
description: 'Update voicemail settings',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
enabled: { type: 'boolean', description: 'Enable voicemail' },
|
||||||
|
greeting: { type: 'string', description: 'Voicemail greeting (text or URL)' },
|
||||||
|
transcriptionEnabled: { type: 'boolean', description: 'Enable transcription' },
|
||||||
|
notificationEmail: { type: 'string', description: 'Email for voicemail notifications' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_voicemails',
|
||||||
|
description: 'Get voicemail messages',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
phoneNumberId: { type: 'string', description: 'Filter by phone number' },
|
||||||
|
status: { type: 'string', enum: ['new', 'read', 'archived'], description: 'Filter by status' },
|
||||||
|
limit: { type: 'number', description: 'Max results' },
|
||||||
|
offset: { type: 'number', description: 'Pagination offset' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete_voicemail',
|
||||||
|
description: 'Delete a voicemail message',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
voicemailId: { type: 'string', description: 'Voicemail ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['voicemailId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Caller ID
|
||||||
|
{
|
||||||
|
name: 'get_caller_ids',
|
||||||
|
description: 'Get verified caller IDs',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'add_caller_id',
|
||||||
|
description: 'Add a caller ID for verification',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
phoneNumber: { type: 'string', description: 'Phone number to verify' },
|
||||||
|
name: { type: 'string', description: 'Friendly name' }
|
||||||
|
},
|
||||||
|
required: ['phoneNumber']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'verify_caller_id',
|
||||||
|
description: 'Submit verification code for caller ID',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
callerIdId: { type: 'string', description: 'Caller ID record ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
code: { type: 'string', description: 'Verification code' }
|
||||||
|
},
|
||||||
|
required: ['callerIdId', 'code']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete_caller_id',
|
||||||
|
description: 'Delete a caller ID',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
callerIdId: { type: 'string', description: 'Caller ID record ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['callerIdId']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleToolCall(toolName: string, args: Record<string, unknown>): Promise<unknown> {
|
||||||
|
const config = this.ghlClient.getConfig();
|
||||||
|
const locationId = (args.locationId as string) || config.locationId;
|
||||||
|
|
||||||
|
switch (toolName) {
|
||||||
|
// Phone Numbers
|
||||||
|
case 'get_phone_numbers': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/phone-numbers/?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'get_phone_number': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/phone-numbers/${args.phoneNumberId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'search_available_numbers': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
params.append('country', String(args.country));
|
||||||
|
if (args.areaCode) params.append('areaCode', String(args.areaCode));
|
||||||
|
if (args.contains) params.append('contains', String(args.contains));
|
||||||
|
if (args.type) params.append('type', String(args.type));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/phone-numbers/available?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'purchase_phone_number': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/phone-numbers/`, {
|
||||||
|
locationId,
|
||||||
|
phoneNumber: args.phoneNumber,
|
||||||
|
name: args.name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case 'update_phone_number': {
|
||||||
|
const body: Record<string, unknown> = { locationId };
|
||||||
|
if (args.name) body.name = args.name;
|
||||||
|
if (args.forwardingNumber) body.forwardingNumber = args.forwardingNumber;
|
||||||
|
if (args.callRecording !== undefined) body.callRecording = args.callRecording;
|
||||||
|
if (args.whisperMessage) body.whisperMessage = args.whisperMessage;
|
||||||
|
return this.ghlClient.makeRequest('PUT', `/phone-numbers/${args.phoneNumberId}`, body);
|
||||||
|
}
|
||||||
|
case 'release_phone_number': {
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/phone-numbers/${args.phoneNumberId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call Forwarding
|
||||||
|
case 'get_call_forwarding_settings': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/phone-numbers/${args.phoneNumberId}/forwarding?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'update_call_forwarding': {
|
||||||
|
const body: Record<string, unknown> = { locationId };
|
||||||
|
if (args.enabled !== undefined) body.enabled = args.enabled;
|
||||||
|
if (args.forwardTo) body.forwardTo = args.forwardTo;
|
||||||
|
if (args.ringTimeout) body.ringTimeout = args.ringTimeout;
|
||||||
|
if (args.voicemailEnabled !== undefined) body.voicemailEnabled = args.voicemailEnabled;
|
||||||
|
return this.ghlClient.makeRequest('PUT', `/phone-numbers/${args.phoneNumberId}/forwarding`, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
// IVR
|
||||||
|
case 'get_ivr_menus': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/phone-numbers/ivr?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'create_ivr_menu': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/phone-numbers/ivr`, {
|
||||||
|
locationId,
|
||||||
|
name: args.name,
|
||||||
|
greeting: args.greeting,
|
||||||
|
options: args.options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case 'update_ivr_menu': {
|
||||||
|
const body: Record<string, unknown> = { locationId };
|
||||||
|
if (args.name) body.name = args.name;
|
||||||
|
if (args.greeting) body.greeting = args.greeting;
|
||||||
|
if (args.options) body.options = args.options;
|
||||||
|
return this.ghlClient.makeRequest('PUT', `/phone-numbers/ivr/${args.menuId}`, body);
|
||||||
|
}
|
||||||
|
case 'delete_ivr_menu': {
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/phone-numbers/ivr/${args.menuId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Voicemail
|
||||||
|
case 'get_voicemail_settings': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/phone-numbers/voicemail/settings?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'update_voicemail_settings': {
|
||||||
|
const body: Record<string, unknown> = { locationId };
|
||||||
|
if (args.enabled !== undefined) body.enabled = args.enabled;
|
||||||
|
if (args.greeting) body.greeting = args.greeting;
|
||||||
|
if (args.transcriptionEnabled !== undefined) body.transcriptionEnabled = args.transcriptionEnabled;
|
||||||
|
if (args.notificationEmail) body.notificationEmail = args.notificationEmail;
|
||||||
|
return this.ghlClient.makeRequest('PUT', `/phone-numbers/voicemail/settings`, body);
|
||||||
|
}
|
||||||
|
case 'get_voicemails': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.phoneNumberId) params.append('phoneNumberId', String(args.phoneNumberId));
|
||||||
|
if (args.status) params.append('status', String(args.status));
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.offset) params.append('offset', String(args.offset));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/phone-numbers/voicemail?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'delete_voicemail': {
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/phone-numbers/voicemail/${args.voicemailId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Caller ID
|
||||||
|
case 'get_caller_ids': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/phone-numbers/caller-id?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'add_caller_id': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/phone-numbers/caller-id`, {
|
||||||
|
locationId,
|
||||||
|
phoneNumber: args.phoneNumber,
|
||||||
|
name: args.name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case 'verify_caller_id': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/phone-numbers/caller-id/${args.callerIdId}/verify`, {
|
||||||
|
locationId,
|
||||||
|
code: args.code
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case 'delete_caller_id': {
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/phone-numbers/caller-id/${args.callerIdId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown tool: ${toolName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
310
src/tools/reporting-tools.ts
Normal file
310
src/tools/reporting-tools.ts
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
/**
|
||||||
|
* GoHighLevel Reporting/Analytics Tools
|
||||||
|
* Tools for accessing reports and analytics
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { GHLApiClient } from '../clients/ghl-api-client.js';
|
||||||
|
|
||||||
|
export class ReportingTools {
|
||||||
|
constructor(private ghlClient: GHLApiClient) {}
|
||||||
|
|
||||||
|
getToolDefinitions() {
|
||||||
|
return [
|
||||||
|
// Attribution Reports
|
||||||
|
{
|
||||||
|
name: 'get_attribution_report',
|
||||||
|
description: 'Get attribution/source tracking report showing where leads came from',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
startDate: { type: 'string', description: 'Start date (YYYY-MM-DD)' },
|
||||||
|
endDate: { type: 'string', description: 'End date (YYYY-MM-DD)' }
|
||||||
|
},
|
||||||
|
required: ['startDate', 'endDate']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Call Reports
|
||||||
|
{
|
||||||
|
name: 'get_call_reports',
|
||||||
|
description: 'Get call activity reports including call duration, outcomes, etc.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
startDate: { type: 'string', description: 'Start date (YYYY-MM-DD)' },
|
||||||
|
endDate: { type: 'string', description: 'End date (YYYY-MM-DD)' },
|
||||||
|
userId: { type: 'string', description: 'Filter by user ID' },
|
||||||
|
type: { type: 'string', enum: ['inbound', 'outbound', 'all'], description: 'Call type filter' }
|
||||||
|
},
|
||||||
|
required: ['startDate', 'endDate']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Appointment Reports
|
||||||
|
{
|
||||||
|
name: 'get_appointment_reports',
|
||||||
|
description: 'Get appointment activity reports',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
startDate: { type: 'string', description: 'Start date (YYYY-MM-DD)' },
|
||||||
|
endDate: { type: 'string', description: 'End date (YYYY-MM-DD)' },
|
||||||
|
calendarId: { type: 'string', description: 'Filter by calendar ID' },
|
||||||
|
status: { type: 'string', enum: ['booked', 'confirmed', 'showed', 'noshow', 'cancelled'], description: 'Appointment status filter' }
|
||||||
|
},
|
||||||
|
required: ['startDate', 'endDate']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Pipeline/Opportunity Reports
|
||||||
|
{
|
||||||
|
name: 'get_pipeline_reports',
|
||||||
|
description: 'Get pipeline/opportunity performance reports',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
pipelineId: { type: 'string', description: 'Filter by pipeline ID' },
|
||||||
|
startDate: { type: 'string', description: 'Start date (YYYY-MM-DD)' },
|
||||||
|
endDate: { type: 'string', description: 'End date (YYYY-MM-DD)' },
|
||||||
|
userId: { type: 'string', description: 'Filter by assigned user' }
|
||||||
|
},
|
||||||
|
required: ['startDate', 'endDate']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Email/SMS Reports
|
||||||
|
{
|
||||||
|
name: 'get_email_reports',
|
||||||
|
description: 'Get email performance reports (deliverability, opens, clicks)',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
startDate: { type: 'string', description: 'Start date (YYYY-MM-DD)' },
|
||||||
|
endDate: { type: 'string', description: 'End date (YYYY-MM-DD)' }
|
||||||
|
},
|
||||||
|
required: ['startDate', 'endDate']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_sms_reports',
|
||||||
|
description: 'Get SMS performance reports',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
startDate: { type: 'string', description: 'Start date (YYYY-MM-DD)' },
|
||||||
|
endDate: { type: 'string', description: 'End date (YYYY-MM-DD)' }
|
||||||
|
},
|
||||||
|
required: ['startDate', 'endDate']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Funnel Reports
|
||||||
|
{
|
||||||
|
name: 'get_funnel_reports',
|
||||||
|
description: 'Get funnel performance reports (page views, conversions)',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
funnelId: { type: 'string', description: 'Filter by funnel ID' },
|
||||||
|
startDate: { type: 'string', description: 'Start date (YYYY-MM-DD)' },
|
||||||
|
endDate: { type: 'string', description: 'End date (YYYY-MM-DD)' }
|
||||||
|
},
|
||||||
|
required: ['startDate', 'endDate']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Google/Facebook Ad Reports
|
||||||
|
{
|
||||||
|
name: 'get_ad_reports',
|
||||||
|
description: 'Get advertising performance reports (Google/Facebook ads)',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
platform: { type: 'string', enum: ['google', 'facebook', 'all'], description: 'Ad platform' },
|
||||||
|
startDate: { type: 'string', description: 'Start date (YYYY-MM-DD)' },
|
||||||
|
endDate: { type: 'string', description: 'End date (YYYY-MM-DD)' }
|
||||||
|
},
|
||||||
|
required: ['startDate', 'endDate']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Agent Performance
|
||||||
|
{
|
||||||
|
name: 'get_agent_reports',
|
||||||
|
description: 'Get agent/user performance reports',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
userId: { type: 'string', description: 'Filter by user ID' },
|
||||||
|
startDate: { type: 'string', description: 'Start date (YYYY-MM-DD)' },
|
||||||
|
endDate: { type: 'string', description: 'End date (YYYY-MM-DD)' }
|
||||||
|
},
|
||||||
|
required: ['startDate', 'endDate']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Dashboard Stats
|
||||||
|
{
|
||||||
|
name: 'get_dashboard_stats',
|
||||||
|
description: 'Get main dashboard statistics overview',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
dateRange: { type: 'string', enum: ['today', 'yesterday', 'last7days', 'last30days', 'thisMonth', 'lastMonth', 'custom'], description: 'Date range preset' },
|
||||||
|
startDate: { type: 'string', description: 'Start date for custom range' },
|
||||||
|
endDate: { type: 'string', description: 'End date for custom range' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Conversion Reports
|
||||||
|
{
|
||||||
|
name: 'get_conversion_reports',
|
||||||
|
description: 'Get conversion tracking reports',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
startDate: { type: 'string', description: 'Start date (YYYY-MM-DD)' },
|
||||||
|
endDate: { type: 'string', description: 'End date (YYYY-MM-DD)' },
|
||||||
|
source: { type: 'string', description: 'Filter by source' }
|
||||||
|
},
|
||||||
|
required: ['startDate', 'endDate']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Revenue Reports
|
||||||
|
{
|
||||||
|
name: 'get_revenue_reports',
|
||||||
|
description: 'Get revenue/payment reports',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
startDate: { type: 'string', description: 'Start date (YYYY-MM-DD)' },
|
||||||
|
endDate: { type: 'string', description: 'End date (YYYY-MM-DD)' },
|
||||||
|
groupBy: { type: 'string', enum: ['day', 'week', 'month'], description: 'Group results by' }
|
||||||
|
},
|
||||||
|
required: ['startDate', 'endDate']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleToolCall(toolName: string, args: Record<string, unknown>): Promise<unknown> {
|
||||||
|
const config = this.ghlClient.getConfig();
|
||||||
|
const locationId = (args.locationId as string) || config.locationId;
|
||||||
|
|
||||||
|
switch (toolName) {
|
||||||
|
case 'get_attribution_report': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
params.append('startDate', String(args.startDate));
|
||||||
|
params.append('endDate', String(args.endDate));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/reporting/attribution?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'get_call_reports': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
params.append('startDate', String(args.startDate));
|
||||||
|
params.append('endDate', String(args.endDate));
|
||||||
|
if (args.userId) params.append('userId', String(args.userId));
|
||||||
|
if (args.type) params.append('type', String(args.type));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/reporting/calls?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'get_appointment_reports': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
params.append('startDate', String(args.startDate));
|
||||||
|
params.append('endDate', String(args.endDate));
|
||||||
|
if (args.calendarId) params.append('calendarId', String(args.calendarId));
|
||||||
|
if (args.status) params.append('status', String(args.status));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/reporting/appointments?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'get_pipeline_reports': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
params.append('startDate', String(args.startDate));
|
||||||
|
params.append('endDate', String(args.endDate));
|
||||||
|
if (args.pipelineId) params.append('pipelineId', String(args.pipelineId));
|
||||||
|
if (args.userId) params.append('userId', String(args.userId));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/reporting/pipelines?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'get_email_reports': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
params.append('startDate', String(args.startDate));
|
||||||
|
params.append('endDate', String(args.endDate));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/reporting/emails?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'get_sms_reports': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
params.append('startDate', String(args.startDate));
|
||||||
|
params.append('endDate', String(args.endDate));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/reporting/sms?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'get_funnel_reports': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
params.append('startDate', String(args.startDate));
|
||||||
|
params.append('endDate', String(args.endDate));
|
||||||
|
if (args.funnelId) params.append('funnelId', String(args.funnelId));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/reporting/funnels?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'get_ad_reports': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
params.append('startDate', String(args.startDate));
|
||||||
|
params.append('endDate', String(args.endDate));
|
||||||
|
if (args.platform) params.append('platform', String(args.platform));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/reporting/ads?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'get_agent_reports': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
params.append('startDate', String(args.startDate));
|
||||||
|
params.append('endDate', String(args.endDate));
|
||||||
|
if (args.userId) params.append('userId', String(args.userId));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/reporting/agents?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'get_dashboard_stats': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.dateRange) params.append('dateRange', String(args.dateRange));
|
||||||
|
if (args.startDate) params.append('startDate', String(args.startDate));
|
||||||
|
if (args.endDate) params.append('endDate', String(args.endDate));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/reporting/dashboard?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'get_conversion_reports': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
params.append('startDate', String(args.startDate));
|
||||||
|
params.append('endDate', String(args.endDate));
|
||||||
|
if (args.source) params.append('source', String(args.source));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/reporting/conversions?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'get_revenue_reports': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
params.append('startDate', String(args.startDate));
|
||||||
|
params.append('endDate', String(args.endDate));
|
||||||
|
if (args.groupBy) params.append('groupBy', String(args.groupBy));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/reporting/revenue?${params.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown tool: ${toolName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
322
src/tools/reputation-tools.ts
Normal file
322
src/tools/reputation-tools.ts
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
/**
|
||||||
|
* GoHighLevel Reputation/Reviews Tools
|
||||||
|
* Tools for managing reviews, reputation, and business listings
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { GHLApiClient } from '../clients/ghl-api-client.js';
|
||||||
|
|
||||||
|
export class ReputationTools {
|
||||||
|
constructor(private ghlClient: GHLApiClient) {}
|
||||||
|
|
||||||
|
getToolDefinitions() {
|
||||||
|
return [
|
||||||
|
// Reviews
|
||||||
|
{
|
||||||
|
name: 'get_reviews',
|
||||||
|
description: 'Get all reviews for a location from various platforms',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
platform: { type: 'string', enum: ['google', 'facebook', 'yelp', 'all'], description: 'Filter by platform' },
|
||||||
|
rating: { type: 'number', description: 'Filter by minimum rating (1-5)' },
|
||||||
|
status: { type: 'string', enum: ['replied', 'unreplied', 'all'], description: 'Filter by reply status' },
|
||||||
|
startDate: { type: 'string', description: 'Start date (YYYY-MM-DD)' },
|
||||||
|
endDate: { type: 'string', description: 'End date (YYYY-MM-DD)' },
|
||||||
|
limit: { type: 'number', description: 'Max results' },
|
||||||
|
offset: { type: 'number', description: 'Pagination offset' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_review',
|
||||||
|
description: 'Get a specific review by ID',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
reviewId: { type: 'string', description: 'Review ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['reviewId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'reply_to_review',
|
||||||
|
description: 'Reply to a review',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
reviewId: { type: 'string', description: 'Review ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
reply: { type: 'string', description: 'Reply text' }
|
||||||
|
},
|
||||||
|
required: ['reviewId', 'reply']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_review_reply',
|
||||||
|
description: 'Update a review reply',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
reviewId: { type: 'string', description: 'Review ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
reply: { type: 'string', description: 'Updated reply text' }
|
||||||
|
},
|
||||||
|
required: ['reviewId', 'reply']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete_review_reply',
|
||||||
|
description: 'Delete a review reply',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
reviewId: { type: 'string', description: 'Review ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['reviewId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Review Stats
|
||||||
|
{
|
||||||
|
name: 'get_review_stats',
|
||||||
|
description: 'Get review statistics/summary',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
platform: { type: 'string', enum: ['google', 'facebook', 'yelp', 'all'], description: 'Platform filter' },
|
||||||
|
startDate: { type: 'string', description: 'Start date' },
|
||||||
|
endDate: { type: 'string', description: 'End date' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Review Requests
|
||||||
|
{
|
||||||
|
name: 'send_review_request',
|
||||||
|
description: 'Send a review request to a contact',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
contactId: { type: 'string', description: 'Contact ID to request review from' },
|
||||||
|
platform: { type: 'string', enum: ['google', 'facebook', 'yelp'], description: 'Platform to request review on' },
|
||||||
|
method: { type: 'string', enum: ['sms', 'email', 'both'], description: 'Delivery method' },
|
||||||
|
message: { type: 'string', description: 'Custom message (optional)' }
|
||||||
|
},
|
||||||
|
required: ['contactId', 'platform', 'method']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_review_requests',
|
||||||
|
description: 'Get sent review requests',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
contactId: { type: 'string', description: 'Filter by contact' },
|
||||||
|
status: { type: 'string', enum: ['sent', 'clicked', 'reviewed', 'all'], description: 'Status filter' },
|
||||||
|
limit: { type: 'number', description: 'Max results' },
|
||||||
|
offset: { type: 'number', description: 'Pagination offset' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Connected Platforms
|
||||||
|
{
|
||||||
|
name: 'get_connected_review_platforms',
|
||||||
|
description: 'Get connected review platforms (Google, Facebook, etc.)',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'connect_google_business',
|
||||||
|
description: 'Initiate Google Business Profile connection',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'disconnect_review_platform',
|
||||||
|
description: 'Disconnect a review platform',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
platform: { type: 'string', enum: ['google', 'facebook', 'yelp'], description: 'Platform to disconnect' }
|
||||||
|
},
|
||||||
|
required: ['platform']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Review Links
|
||||||
|
{
|
||||||
|
name: 'get_review_links',
|
||||||
|
description: 'Get direct review links for platforms',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_review_links',
|
||||||
|
description: 'Update custom review links',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
googleLink: { type: 'string', description: 'Custom Google review link' },
|
||||||
|
facebookLink: { type: 'string', description: 'Custom Facebook review link' },
|
||||||
|
yelpLink: { type: 'string', description: 'Custom Yelp review link' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Review Widgets
|
||||||
|
{
|
||||||
|
name: 'get_review_widget_settings',
|
||||||
|
description: 'Get review widget embed settings',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_review_widget_settings',
|
||||||
|
description: 'Update review widget settings',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
enabled: { type: 'boolean', description: 'Enable widget' },
|
||||||
|
minRating: { type: 'number', description: 'Minimum rating to display' },
|
||||||
|
platforms: { type: 'array', items: { type: 'string' }, description: 'Platforms to show' },
|
||||||
|
layout: { type: 'string', enum: ['grid', 'carousel', 'list'], description: 'Widget layout' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleToolCall(toolName: string, args: Record<string, unknown>): Promise<unknown> {
|
||||||
|
const config = this.ghlClient.getConfig();
|
||||||
|
const locationId = (args.locationId as string) || config.locationId;
|
||||||
|
|
||||||
|
switch (toolName) {
|
||||||
|
// Reviews
|
||||||
|
case 'get_reviews': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.platform) params.append('platform', String(args.platform));
|
||||||
|
if (args.rating) params.append('rating', String(args.rating));
|
||||||
|
if (args.status) params.append('status', String(args.status));
|
||||||
|
if (args.startDate) params.append('startDate', String(args.startDate));
|
||||||
|
if (args.endDate) params.append('endDate', String(args.endDate));
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.offset) params.append('offset', String(args.offset));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/reputation/reviews?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'get_review': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/reputation/reviews/${args.reviewId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'reply_to_review': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/reputation/reviews/${args.reviewId}/reply`, {
|
||||||
|
locationId,
|
||||||
|
reply: args.reply
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case 'update_review_reply': {
|
||||||
|
return this.ghlClient.makeRequest('PUT', `/reputation/reviews/${args.reviewId}/reply`, {
|
||||||
|
locationId,
|
||||||
|
reply: args.reply
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case 'delete_review_reply': {
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/reputation/reviews/${args.reviewId}/reply?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stats
|
||||||
|
case 'get_review_stats': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.platform) params.append('platform', String(args.platform));
|
||||||
|
if (args.startDate) params.append('startDate', String(args.startDate));
|
||||||
|
if (args.endDate) params.append('endDate', String(args.endDate));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/reputation/stats?${params.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Review Requests
|
||||||
|
case 'send_review_request': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/reputation/review-requests`, {
|
||||||
|
locationId,
|
||||||
|
contactId: args.contactId,
|
||||||
|
platform: args.platform,
|
||||||
|
method: args.method,
|
||||||
|
message: args.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case 'get_review_requests': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.contactId) params.append('contactId', String(args.contactId));
|
||||||
|
if (args.status) params.append('status', String(args.status));
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.offset) params.append('offset', String(args.offset));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/reputation/review-requests?${params.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Platforms
|
||||||
|
case 'get_connected_review_platforms': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/reputation/platforms?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'connect_google_business': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/reputation/platforms/google/connect`, { locationId });
|
||||||
|
}
|
||||||
|
case 'disconnect_review_platform': {
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/reputation/platforms/${args.platform}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Links
|
||||||
|
case 'get_review_links': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/reputation/links?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'update_review_links': {
|
||||||
|
const body: Record<string, unknown> = { locationId };
|
||||||
|
if (args.googleLink) body.googleLink = args.googleLink;
|
||||||
|
if (args.facebookLink) body.facebookLink = args.facebookLink;
|
||||||
|
if (args.yelpLink) body.yelpLink = args.yelpLink;
|
||||||
|
return this.ghlClient.makeRequest('PUT', `/reputation/links`, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Widgets
|
||||||
|
case 'get_review_widget_settings': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/reputation/widget?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'update_review_widget_settings': {
|
||||||
|
const body: Record<string, unknown> = { locationId };
|
||||||
|
if (args.enabled !== undefined) body.enabled = args.enabled;
|
||||||
|
if (args.minRating) body.minRating = args.minRating;
|
||||||
|
if (args.platforms) body.platforms = args.platforms;
|
||||||
|
if (args.layout) body.layout = args.layout;
|
||||||
|
return this.ghlClient.makeRequest('PUT', `/reputation/widget`, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown tool: ${toolName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
220
src/tools/saas-tools.ts
Normal file
220
src/tools/saas-tools.ts
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
/**
|
||||||
|
* GoHighLevel SaaS/Agency Tools
|
||||||
|
* Tools for agency-level operations (company/agency management)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { GHLApiClient } from '../clients/ghl-api-client.js';
|
||||||
|
|
||||||
|
export class SaasTools {
|
||||||
|
constructor(private ghlClient: GHLApiClient) {}
|
||||||
|
|
||||||
|
getToolDefinitions() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'get_saas_locations',
|
||||||
|
description: 'Get all sub-accounts/locations for a SaaS agency. Requires agency-level access.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
companyId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Company/Agency ID'
|
||||||
|
},
|
||||||
|
skip: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Number of records to skip'
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Maximum number of locations to return (default: 10, max: 100)'
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
type: 'string',
|
||||||
|
enum: ['asc', 'desc'],
|
||||||
|
description: 'Sort order'
|
||||||
|
},
|
||||||
|
isActive: {
|
||||||
|
type: 'boolean',
|
||||||
|
description: 'Filter by active status'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['companyId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_saas_location',
|
||||||
|
description: 'Get a specific sub-account/location by ID at the agency level',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
companyId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Company/Agency ID'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID to retrieve'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['companyId', 'locationId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_saas_subscription',
|
||||||
|
description: 'Update SaaS subscription settings for a location',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
companyId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Company/Agency ID'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID'
|
||||||
|
},
|
||||||
|
subscriptionId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Subscription ID'
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: 'string',
|
||||||
|
enum: ['active', 'paused', 'cancelled'],
|
||||||
|
description: 'Subscription status'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['companyId', 'locationId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pause_saas_location',
|
||||||
|
description: 'Pause a SaaS sub-account/location',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
companyId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Company/Agency ID'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID to pause'
|
||||||
|
},
|
||||||
|
paused: {
|
||||||
|
type: 'boolean',
|
||||||
|
description: 'Whether to pause (true) or unpause (false)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['companyId', 'locationId', 'paused']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'enable_saas_location',
|
||||||
|
description: 'Enable or disable SaaS features for a location',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
companyId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Company/Agency ID'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID'
|
||||||
|
},
|
||||||
|
enabled: {
|
||||||
|
type: 'boolean',
|
||||||
|
description: 'Whether to enable (true) or disable (false) SaaS'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['companyId', 'locationId', 'enabled']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'rebilling_update',
|
||||||
|
description: 'Update rebilling configuration for agency',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
companyId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Company/Agency ID'
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Product to configure rebilling for'
|
||||||
|
},
|
||||||
|
markup: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Markup percentage'
|
||||||
|
},
|
||||||
|
enabled: {
|
||||||
|
type: 'boolean',
|
||||||
|
description: 'Whether rebilling is enabled'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['companyId']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleToolCall(toolName: string, args: Record<string, unknown>): Promise<unknown> {
|
||||||
|
const companyId = args.companyId as string;
|
||||||
|
|
||||||
|
switch (toolName) {
|
||||||
|
case 'get_saas_locations': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('companyId', companyId);
|
||||||
|
if (args.skip) params.append('skip', String(args.skip));
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.order) params.append('order', String(args.order));
|
||||||
|
if (args.isActive !== undefined) params.append('isActive', String(args.isActive));
|
||||||
|
|
||||||
|
return this.ghlClient.makeRequest('GET', `/saas-api/public-api/locations?${params.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'get_saas_location': {
|
||||||
|
const locationId = args.locationId as string;
|
||||||
|
return this.ghlClient.makeRequest('GET', `/saas-api/public-api/locations/${locationId}?companyId=${companyId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'update_saas_subscription': {
|
||||||
|
const locationId = args.locationId as string;
|
||||||
|
const body: Record<string, unknown> = { companyId };
|
||||||
|
if (args.subscriptionId) body.subscriptionId = args.subscriptionId;
|
||||||
|
if (args.status) body.status = args.status;
|
||||||
|
|
||||||
|
return this.ghlClient.makeRequest('PUT', `/saas-api/public-api/locations/${locationId}/subscription`, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'pause_saas_location': {
|
||||||
|
const locationId = args.locationId as string;
|
||||||
|
return this.ghlClient.makeRequest('POST', `/saas-api/public-api/locations/${locationId}/pause`, {
|
||||||
|
companyId,
|
||||||
|
paused: args.paused
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'enable_saas_location': {
|
||||||
|
const locationId = args.locationId as string;
|
||||||
|
return this.ghlClient.makeRequest('POST', `/saas-api/public-api/locations/${locationId}/enable`, {
|
||||||
|
companyId,
|
||||||
|
enabled: args.enabled
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'rebilling_update': {
|
||||||
|
const body: Record<string, unknown> = { companyId };
|
||||||
|
if (args.product) body.product = args.product;
|
||||||
|
if (args.markup !== undefined) body.markup = args.markup;
|
||||||
|
if (args.enabled !== undefined) body.enabled = args.enabled;
|
||||||
|
|
||||||
|
return this.ghlClient.makeRequest('PUT', `/saas-api/public-api/rebilling`, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown tool: ${toolName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
185
src/tools/smartlists-tools.ts
Normal file
185
src/tools/smartlists-tools.ts
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
/**
|
||||||
|
* GoHighLevel Smart Lists Tools
|
||||||
|
* Tools for managing smart lists (saved contact segments)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { GHLApiClient } from '../clients/ghl-api-client.js';
|
||||||
|
|
||||||
|
export class SmartListsTools {
|
||||||
|
constructor(private ghlClient: GHLApiClient) {}
|
||||||
|
|
||||||
|
getToolDefinitions() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'get_smart_lists',
|
||||||
|
description: 'Get all smart lists (saved contact filters/segments)',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
limit: { type: 'number', description: 'Max results' },
|
||||||
|
offset: { type: 'number', description: 'Pagination offset' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_smart_list',
|
||||||
|
description: 'Get a specific smart list by ID',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
smartListId: { type: 'string', description: 'Smart List ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['smartListId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_smart_list',
|
||||||
|
description: 'Create a new smart list with filter criteria',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
name: { type: 'string', description: 'Smart list name' },
|
||||||
|
filters: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
field: { type: 'string', description: 'Field to filter on' },
|
||||||
|
operator: { type: 'string', description: 'Comparison operator (equals, contains, etc.)' },
|
||||||
|
value: { type: 'string', description: 'Filter value' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
description: 'Filter conditions'
|
||||||
|
},
|
||||||
|
filterOperator: { type: 'string', enum: ['AND', 'OR'], description: 'How to combine filters' }
|
||||||
|
},
|
||||||
|
required: ['name', 'filters']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_smart_list',
|
||||||
|
description: 'Update a smart list',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
smartListId: { type: 'string', description: 'Smart List ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
name: { type: 'string', description: 'Smart list name' },
|
||||||
|
filters: { type: 'array', description: 'Filter conditions' },
|
||||||
|
filterOperator: { type: 'string', enum: ['AND', 'OR'], description: 'How to combine filters' }
|
||||||
|
},
|
||||||
|
required: ['smartListId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete_smart_list',
|
||||||
|
description: 'Delete a smart list',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
smartListId: { type: 'string', description: 'Smart List ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['smartListId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_smart_list_contacts',
|
||||||
|
description: 'Get contacts that match a smart list\'s criteria',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
smartListId: { type: 'string', description: 'Smart List ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
limit: { type: 'number', description: 'Max results' },
|
||||||
|
offset: { type: 'number', description: 'Pagination offset' }
|
||||||
|
},
|
||||||
|
required: ['smartListId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_smart_list_count',
|
||||||
|
description: 'Get the count of contacts matching a smart list',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
smartListId: { type: 'string', description: 'Smart List ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['smartListId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'duplicate_smart_list',
|
||||||
|
description: 'Duplicate/clone a smart list',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
smartListId: { type: 'string', description: 'Smart List ID to duplicate' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
name: { type: 'string', description: 'Name for the duplicate' }
|
||||||
|
},
|
||||||
|
required: ['smartListId']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleToolCall(toolName: string, args: Record<string, unknown>): Promise<unknown> {
|
||||||
|
const config = this.ghlClient.getConfig();
|
||||||
|
const locationId = (args.locationId as string) || config.locationId;
|
||||||
|
|
||||||
|
switch (toolName) {
|
||||||
|
case 'get_smart_lists': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.offset) params.append('offset', String(args.offset));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/contacts/smart-lists?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'get_smart_list': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/contacts/smart-lists/${args.smartListId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'create_smart_list': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/contacts/smart-lists`, {
|
||||||
|
locationId,
|
||||||
|
name: args.name,
|
||||||
|
filters: args.filters,
|
||||||
|
filterOperator: args.filterOperator || 'AND'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case 'update_smart_list': {
|
||||||
|
const body: Record<string, unknown> = { locationId };
|
||||||
|
if (args.name) body.name = args.name;
|
||||||
|
if (args.filters) body.filters = args.filters;
|
||||||
|
if (args.filterOperator) body.filterOperator = args.filterOperator;
|
||||||
|
return this.ghlClient.makeRequest('PUT', `/contacts/smart-lists/${args.smartListId}`, body);
|
||||||
|
}
|
||||||
|
case 'delete_smart_list': {
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/contacts/smart-lists/${args.smartListId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'get_smart_list_contacts': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.offset) params.append('offset', String(args.offset));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/contacts/smart-lists/${args.smartListId}/contacts?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'get_smart_list_count': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/contacts/smart-lists/${args.smartListId}/count?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'duplicate_smart_list': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/contacts/smart-lists/${args.smartListId}/duplicate`, {
|
||||||
|
locationId,
|
||||||
|
name: args.name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown tool: ${toolName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
223
src/tools/snapshots-tools.ts
Normal file
223
src/tools/snapshots-tools.ts
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
/**
|
||||||
|
* GoHighLevel Snapshots Tools
|
||||||
|
* Tools for managing snapshots (location templates/backups)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { GHLApiClient } from '../clients/ghl-api-client.js';
|
||||||
|
|
||||||
|
export class SnapshotsTools {
|
||||||
|
constructor(private ghlClient: GHLApiClient) {}
|
||||||
|
|
||||||
|
getToolDefinitions() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'get_snapshots',
|
||||||
|
description: 'Get all snapshots for a company/agency. Snapshots are templates that can be used to set up new locations.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
companyId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Company/Agency ID'
|
||||||
|
},
|
||||||
|
skip: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Number of records to skip'
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Maximum number of snapshots to return'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['companyId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_snapshot',
|
||||||
|
description: 'Get a specific snapshot by ID',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
snapshotId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The snapshot ID to retrieve'
|
||||||
|
},
|
||||||
|
companyId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Company/Agency ID'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['snapshotId', 'companyId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_snapshot',
|
||||||
|
description: 'Create a new snapshot from a location (backs up location settings)',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
companyId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Company/Agency ID'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Source location ID to create snapshot from'
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Name for the snapshot'
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Description of the snapshot'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['companyId', 'locationId', 'name']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_snapshot_push_status',
|
||||||
|
description: 'Check the status of a snapshot push operation',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
snapshotId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The snapshot ID'
|
||||||
|
},
|
||||||
|
companyId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Company/Agency ID'
|
||||||
|
},
|
||||||
|
pushId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The push operation ID'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['snapshotId', 'companyId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_latest_snapshot_push',
|
||||||
|
description: 'Get the latest snapshot push for a location',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
snapshotId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The snapshot ID'
|
||||||
|
},
|
||||||
|
companyId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Company/Agency ID'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Target location ID'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['snapshotId', 'companyId', 'locationId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'push_snapshot_to_subaccounts',
|
||||||
|
description: 'Push/deploy a snapshot to one or more sub-accounts',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
snapshotId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The snapshot ID to push'
|
||||||
|
},
|
||||||
|
companyId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Company/Agency ID'
|
||||||
|
},
|
||||||
|
locationIds: {
|
||||||
|
type: 'array',
|
||||||
|
items: { type: 'string' },
|
||||||
|
description: 'Array of location IDs to push the snapshot to'
|
||||||
|
},
|
||||||
|
override: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
workflows: { type: 'boolean', description: 'Override existing workflows' },
|
||||||
|
campaigns: { type: 'boolean', description: 'Override existing campaigns' },
|
||||||
|
funnels: { type: 'boolean', description: 'Override existing funnels' },
|
||||||
|
websites: { type: 'boolean', description: 'Override existing websites' },
|
||||||
|
forms: { type: 'boolean', description: 'Override existing forms' },
|
||||||
|
surveys: { type: 'boolean', description: 'Override existing surveys' },
|
||||||
|
calendars: { type: 'boolean', description: 'Override existing calendars' },
|
||||||
|
automations: { type: 'boolean', description: 'Override existing automations' },
|
||||||
|
triggers: { type: 'boolean', description: 'Override existing triggers' }
|
||||||
|
},
|
||||||
|
description: 'What to override vs skip'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['snapshotId', 'companyId', 'locationIds']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleToolCall(toolName: string, args: Record<string, unknown>): Promise<unknown> {
|
||||||
|
const companyId = args.companyId as string;
|
||||||
|
|
||||||
|
switch (toolName) {
|
||||||
|
case 'get_snapshots': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('companyId', companyId);
|
||||||
|
if (args.skip) params.append('skip', String(args.skip));
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
|
||||||
|
return this.ghlClient.makeRequest('GET', `/snapshots/?${params.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'get_snapshot': {
|
||||||
|
const snapshotId = args.snapshotId as string;
|
||||||
|
return this.ghlClient.makeRequest('GET', `/snapshots/${snapshotId}?companyId=${companyId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'create_snapshot': {
|
||||||
|
const body: Record<string, unknown> = {
|
||||||
|
companyId,
|
||||||
|
locationId: args.locationId,
|
||||||
|
name: args.name
|
||||||
|
};
|
||||||
|
if (args.description) body.description = args.description;
|
||||||
|
|
||||||
|
return this.ghlClient.makeRequest('POST', `/snapshots/`, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'get_snapshot_push_status': {
|
||||||
|
const snapshotId = args.snapshotId as string;
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('companyId', companyId);
|
||||||
|
if (args.pushId) params.append('pushId', String(args.pushId));
|
||||||
|
|
||||||
|
return this.ghlClient.makeRequest('GET', `/snapshots/${snapshotId}/push?${params.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'get_latest_snapshot_push': {
|
||||||
|
const snapshotId = args.snapshotId as string;
|
||||||
|
const locationId = args.locationId as string;
|
||||||
|
return this.ghlClient.makeRequest('GET', `/snapshots/${snapshotId}/push/${locationId}?companyId=${companyId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'push_snapshot_to_subaccounts': {
|
||||||
|
const snapshotId = args.snapshotId as string;
|
||||||
|
const body: Record<string, unknown> = {
|
||||||
|
companyId,
|
||||||
|
locationIds: args.locationIds
|
||||||
|
};
|
||||||
|
if (args.override) body.override = args.override;
|
||||||
|
|
||||||
|
return this.ghlClient.makeRequest('POST', `/snapshots/${snapshotId}/push`, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown tool: ${toolName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
373
src/tools/templates-tools.ts
Normal file
373
src/tools/templates-tools.ts
Normal file
@ -0,0 +1,373 @@
|
|||||||
|
/**
|
||||||
|
* GoHighLevel Templates Tools
|
||||||
|
* Tools for managing SMS, Email, and other message templates
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { GHLApiClient } from '../clients/ghl-api-client.js';
|
||||||
|
|
||||||
|
export class TemplatesTools {
|
||||||
|
constructor(private ghlClient: GHLApiClient) {}
|
||||||
|
|
||||||
|
getToolDefinitions() {
|
||||||
|
return [
|
||||||
|
// SMS Templates
|
||||||
|
{
|
||||||
|
name: 'get_sms_templates',
|
||||||
|
description: 'Get all SMS templates',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
limit: { type: 'number', description: 'Max results' },
|
||||||
|
offset: { type: 'number', description: 'Pagination offset' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_sms_template',
|
||||||
|
description: 'Get a specific SMS template',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
templateId: { type: 'string', description: 'SMS Template ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['templateId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_sms_template',
|
||||||
|
description: 'Create a new SMS template',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
name: { type: 'string', description: 'Template name' },
|
||||||
|
body: { type: 'string', description: 'SMS message body (can include merge fields like {{contact.first_name}})' }
|
||||||
|
},
|
||||||
|
required: ['name', 'body']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_sms_template',
|
||||||
|
description: 'Update an SMS template',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
templateId: { type: 'string', description: 'SMS Template ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
name: { type: 'string', description: 'Template name' },
|
||||||
|
body: { type: 'string', description: 'SMS message body' }
|
||||||
|
},
|
||||||
|
required: ['templateId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete_sms_template',
|
||||||
|
description: 'Delete an SMS template',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
templateId: { type: 'string', description: 'SMS Template ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['templateId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Voicemail Drop Templates
|
||||||
|
{
|
||||||
|
name: 'get_voicemail_templates',
|
||||||
|
description: 'Get all voicemail drop templates',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_voicemail_template',
|
||||||
|
description: 'Create a voicemail drop template',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
name: { type: 'string', description: 'Template name' },
|
||||||
|
audioUrl: { type: 'string', description: 'URL to audio file' }
|
||||||
|
},
|
||||||
|
required: ['name', 'audioUrl']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete_voicemail_template',
|
||||||
|
description: 'Delete a voicemail template',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
templateId: { type: 'string', description: 'Template ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['templateId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Social Templates
|
||||||
|
{
|
||||||
|
name: 'get_social_templates',
|
||||||
|
description: 'Get social media post templates',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
limit: { type: 'number', description: 'Max results' },
|
||||||
|
offset: { type: 'number', description: 'Pagination offset' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_social_template',
|
||||||
|
description: 'Create a social media post template',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
name: { type: 'string', description: 'Template name' },
|
||||||
|
content: { type: 'string', description: 'Post content' },
|
||||||
|
mediaUrls: { type: 'array', items: { type: 'string' }, description: 'Media URLs' },
|
||||||
|
platforms: { type: 'array', items: { type: 'string' }, description: 'Target platforms' }
|
||||||
|
},
|
||||||
|
required: ['name', 'content']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete_social_template',
|
||||||
|
description: 'Delete a social template',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
templateId: { type: 'string', description: 'Template ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['templateId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// WhatsApp Templates
|
||||||
|
{
|
||||||
|
name: 'get_whatsapp_templates',
|
||||||
|
description: 'Get WhatsApp message templates (must be pre-approved)',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
status: { type: 'string', enum: ['approved', 'pending', 'rejected', 'all'], description: 'Template status' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_whatsapp_template',
|
||||||
|
description: 'Create a WhatsApp template (submits for approval)',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
name: { type: 'string', description: 'Template name' },
|
||||||
|
category: { type: 'string', enum: ['marketing', 'utility', 'authentication'], description: 'Template category' },
|
||||||
|
language: { type: 'string', description: 'Language code (e.g., en_US)' },
|
||||||
|
components: { type: 'array', description: 'Template components (header, body, footer, buttons)' }
|
||||||
|
},
|
||||||
|
required: ['name', 'category', 'language', 'components']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete_whatsapp_template',
|
||||||
|
description: 'Delete a WhatsApp template',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
templateId: { type: 'string', description: 'Template ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['templateId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Snippet/Canned Response Templates
|
||||||
|
{
|
||||||
|
name: 'get_snippets',
|
||||||
|
description: 'Get canned response snippets',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
type: { type: 'string', enum: ['sms', 'email', 'all'], description: 'Snippet type' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_snippet',
|
||||||
|
description: 'Create a canned response snippet',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
name: { type: 'string', description: 'Snippet name' },
|
||||||
|
shortcut: { type: 'string', description: 'Keyboard shortcut (e.g., /thanks)' },
|
||||||
|
content: { type: 'string', description: 'Snippet content' },
|
||||||
|
type: { type: 'string', enum: ['sms', 'email', 'both'], description: 'Snippet type' }
|
||||||
|
},
|
||||||
|
required: ['name', 'content']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_snippet',
|
||||||
|
description: 'Update a snippet',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
snippetId: { type: 'string', description: 'Snippet ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
name: { type: 'string', description: 'Snippet name' },
|
||||||
|
shortcut: { type: 'string', description: 'Keyboard shortcut' },
|
||||||
|
content: { type: 'string', description: 'Snippet content' }
|
||||||
|
},
|
||||||
|
required: ['snippetId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete_snippet',
|
||||||
|
description: 'Delete a snippet',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
snippetId: { type: 'string', description: 'Snippet ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['snippetId']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleToolCall(toolName: string, args: Record<string, unknown>): Promise<unknown> {
|
||||||
|
const config = this.ghlClient.getConfig();
|
||||||
|
const locationId = (args.locationId as string) || config.locationId;
|
||||||
|
|
||||||
|
switch (toolName) {
|
||||||
|
// SMS Templates
|
||||||
|
case 'get_sms_templates': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.offset) params.append('offset', String(args.offset));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/templates/sms?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'get_sms_template': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/templates/sms/${args.templateId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'create_sms_template': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/templates/sms`, {
|
||||||
|
locationId,
|
||||||
|
name: args.name,
|
||||||
|
body: args.body
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case 'update_sms_template': {
|
||||||
|
const body: Record<string, unknown> = { locationId };
|
||||||
|
if (args.name) body.name = args.name;
|
||||||
|
if (args.body) body.body = args.body;
|
||||||
|
return this.ghlClient.makeRequest('PUT', `/templates/sms/${args.templateId}`, body);
|
||||||
|
}
|
||||||
|
case 'delete_sms_template': {
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/templates/sms/${args.templateId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Voicemail Templates
|
||||||
|
case 'get_voicemail_templates': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/templates/voicemail?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'create_voicemail_template': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/templates/voicemail`, {
|
||||||
|
locationId,
|
||||||
|
name: args.name,
|
||||||
|
audioUrl: args.audioUrl
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case 'delete_voicemail_template': {
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/templates/voicemail/${args.templateId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Social Templates
|
||||||
|
case 'get_social_templates': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.offset) params.append('offset', String(args.offset));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/templates/social?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'create_social_template': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/templates/social`, {
|
||||||
|
locationId,
|
||||||
|
name: args.name,
|
||||||
|
content: args.content,
|
||||||
|
mediaUrls: args.mediaUrls,
|
||||||
|
platforms: args.platforms
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case 'delete_social_template': {
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/templates/social/${args.templateId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// WhatsApp Templates
|
||||||
|
case 'get_whatsapp_templates': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.status) params.append('status', String(args.status));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/templates/whatsapp?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'create_whatsapp_template': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/templates/whatsapp`, {
|
||||||
|
locationId,
|
||||||
|
name: args.name,
|
||||||
|
category: args.category,
|
||||||
|
language: args.language,
|
||||||
|
components: args.components
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case 'delete_whatsapp_template': {
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/templates/whatsapp/${args.templateId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snippets
|
||||||
|
case 'get_snippets': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.type) params.append('type', String(args.type));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/templates/snippets?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'create_snippet': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/templates/snippets`, {
|
||||||
|
locationId,
|
||||||
|
name: args.name,
|
||||||
|
shortcut: args.shortcut,
|
||||||
|
content: args.content,
|
||||||
|
type: args.type
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case 'update_snippet': {
|
||||||
|
const body: Record<string, unknown> = { locationId };
|
||||||
|
if (args.name) body.name = args.name;
|
||||||
|
if (args.shortcut) body.shortcut = args.shortcut;
|
||||||
|
if (args.content) body.content = args.content;
|
||||||
|
return this.ghlClient.makeRequest('PUT', `/templates/snippets/${args.snippetId}`, body);
|
||||||
|
}
|
||||||
|
case 'delete_snippet': {
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/templates/snippets/${args.snippetId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown tool: ${toolName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
266
src/tools/triggers-tools.ts
Normal file
266
src/tools/triggers-tools.ts
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
/**
|
||||||
|
* GoHighLevel Triggers Tools
|
||||||
|
* Tools for managing automation triggers
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { GHLApiClient } from '../clients/ghl-api-client.js';
|
||||||
|
|
||||||
|
export class TriggersTools {
|
||||||
|
constructor(private ghlClient: GHLApiClient) {}
|
||||||
|
|
||||||
|
getToolDefinitions() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'get_triggers',
|
||||||
|
description: 'Get all automation triggers for a location',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
type: { type: 'string', description: 'Filter by trigger type' },
|
||||||
|
status: { type: 'string', enum: ['active', 'inactive', 'all'], description: 'Status filter' },
|
||||||
|
limit: { type: 'number', description: 'Max results' },
|
||||||
|
offset: { type: 'number', description: 'Pagination offset' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_trigger',
|
||||||
|
description: 'Get a specific trigger by ID',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
triggerId: { type: 'string', description: 'Trigger ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['triggerId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_trigger',
|
||||||
|
description: 'Create a new automation trigger',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
name: { type: 'string', description: 'Trigger name' },
|
||||||
|
type: {
|
||||||
|
type: 'string',
|
||||||
|
enum: [
|
||||||
|
'contact_created', 'contact_tag_added', 'contact_tag_removed',
|
||||||
|
'form_submitted', 'appointment_booked', 'appointment_cancelled',
|
||||||
|
'opportunity_created', 'opportunity_status_changed', 'opportunity_stage_changed',
|
||||||
|
'invoice_paid', 'order_placed', 'call_completed', 'email_opened',
|
||||||
|
'email_clicked', 'sms_received', 'webhook'
|
||||||
|
],
|
||||||
|
description: 'Trigger type/event'
|
||||||
|
},
|
||||||
|
filters: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
field: { type: 'string', description: 'Field to filter' },
|
||||||
|
operator: { type: 'string', description: 'Comparison operator' },
|
||||||
|
value: { type: 'string', description: 'Filter value' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
description: 'Conditions that must be met'
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
type: { type: 'string', description: 'Action type' },
|
||||||
|
config: { type: 'object', description: 'Action configuration' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
description: 'Actions to perform when triggered'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['name', 'type']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_trigger',
|
||||||
|
description: 'Update an existing trigger',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
triggerId: { type: 'string', description: 'Trigger ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
name: { type: 'string', description: 'Trigger name' },
|
||||||
|
filters: { type: 'array', description: 'Filter conditions' },
|
||||||
|
actions: { type: 'array', description: 'Actions to perform' },
|
||||||
|
status: { type: 'string', enum: ['active', 'inactive'], description: 'Trigger status' }
|
||||||
|
},
|
||||||
|
required: ['triggerId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete_trigger',
|
||||||
|
description: 'Delete a trigger',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
triggerId: { type: 'string', description: 'Trigger ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['triggerId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'enable_trigger',
|
||||||
|
description: 'Enable/activate a trigger',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
triggerId: { type: 'string', description: 'Trigger ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['triggerId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'disable_trigger',
|
||||||
|
description: 'Disable/deactivate a trigger',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
triggerId: { type: 'string', description: 'Trigger ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['triggerId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_trigger_types',
|
||||||
|
description: 'Get all available trigger types and their configurations',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_trigger_logs',
|
||||||
|
description: 'Get execution logs for a trigger',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
triggerId: { type: 'string', description: 'Trigger ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
status: { type: 'string', enum: ['success', 'failed', 'all'], description: 'Execution status filter' },
|
||||||
|
startDate: { type: 'string', description: 'Start date' },
|
||||||
|
endDate: { type: 'string', description: 'End date' },
|
||||||
|
limit: { type: 'number', description: 'Max results' },
|
||||||
|
offset: { type: 'number', description: 'Pagination offset' }
|
||||||
|
},
|
||||||
|
required: ['triggerId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'test_trigger',
|
||||||
|
description: 'Test a trigger with sample data',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
triggerId: { type: 'string', description: 'Trigger ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
testData: { type: 'object', description: 'Sample data to test with' }
|
||||||
|
},
|
||||||
|
required: ['triggerId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'duplicate_trigger',
|
||||||
|
description: 'Duplicate/clone a trigger',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
triggerId: { type: 'string', description: 'Trigger ID to duplicate' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
name: { type: 'string', description: 'Name for the duplicate' }
|
||||||
|
},
|
||||||
|
required: ['triggerId']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleToolCall(toolName: string, args: Record<string, unknown>): Promise<unknown> {
|
||||||
|
const config = this.ghlClient.getConfig();
|
||||||
|
const locationId = (args.locationId as string) || config.locationId;
|
||||||
|
|
||||||
|
switch (toolName) {
|
||||||
|
case 'get_triggers': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.type) params.append('type', String(args.type));
|
||||||
|
if (args.status) params.append('status', String(args.status));
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.offset) params.append('offset', String(args.offset));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/triggers/?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'get_trigger': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/triggers/${args.triggerId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'create_trigger': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/triggers/`, {
|
||||||
|
locationId,
|
||||||
|
name: args.name,
|
||||||
|
type: args.type,
|
||||||
|
filters: args.filters,
|
||||||
|
actions: args.actions
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case 'update_trigger': {
|
||||||
|
const body: Record<string, unknown> = { locationId };
|
||||||
|
if (args.name) body.name = args.name;
|
||||||
|
if (args.filters) body.filters = args.filters;
|
||||||
|
if (args.actions) body.actions = args.actions;
|
||||||
|
if (args.status) body.status = args.status;
|
||||||
|
return this.ghlClient.makeRequest('PUT', `/triggers/${args.triggerId}`, body);
|
||||||
|
}
|
||||||
|
case 'delete_trigger': {
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/triggers/${args.triggerId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'enable_trigger': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/triggers/${args.triggerId}/enable`, { locationId });
|
||||||
|
}
|
||||||
|
case 'disable_trigger': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/triggers/${args.triggerId}/disable`, { locationId });
|
||||||
|
}
|
||||||
|
case 'get_trigger_types': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/triggers/types?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'get_trigger_logs': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.status) params.append('status', String(args.status));
|
||||||
|
if (args.startDate) params.append('startDate', String(args.startDate));
|
||||||
|
if (args.endDate) params.append('endDate', String(args.endDate));
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.offset) params.append('offset', String(args.offset));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/triggers/${args.triggerId}/logs?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'test_trigger': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/triggers/${args.triggerId}/test`, {
|
||||||
|
locationId,
|
||||||
|
testData: args.testData
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case 'duplicate_trigger': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/triggers/${args.triggerId}/duplicate`, {
|
||||||
|
locationId,
|
||||||
|
name: args.name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown tool: ${toolName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
291
src/tools/users-tools.ts
Normal file
291
src/tools/users-tools.ts
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
/**
|
||||||
|
* GoHighLevel Users Tools
|
||||||
|
* Tools for managing users and team members
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { GHLApiClient } from '../clients/ghl-api-client.js';
|
||||||
|
|
||||||
|
export class UsersTools {
|
||||||
|
constructor(private ghlClient: GHLApiClient) {}
|
||||||
|
|
||||||
|
getToolDefinitions() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'get_users',
|
||||||
|
description: 'Get all users/team members for a location. Returns team members with their roles and permissions.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
},
|
||||||
|
skip: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Number of records to skip for pagination'
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Maximum number of users to return (default: 25, max: 100)'
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Filter by user type'
|
||||||
|
},
|
||||||
|
role: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Filter by role (e.g., "admin", "user")'
|
||||||
|
},
|
||||||
|
ids: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Comma-separated list of user IDs to filter'
|
||||||
|
},
|
||||||
|
sort: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Sort field'
|
||||||
|
},
|
||||||
|
sortDirection: {
|
||||||
|
type: 'string',
|
||||||
|
enum: ['asc', 'desc'],
|
||||||
|
description: 'Sort direction'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_user',
|
||||||
|
description: 'Get a specific user by their ID',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
userId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The user ID to retrieve'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['userId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_user',
|
||||||
|
description: 'Create a new user/team member for a location',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
},
|
||||||
|
firstName: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'User first name'
|
||||||
|
},
|
||||||
|
lastName: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'User last name'
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'User email address'
|
||||||
|
},
|
||||||
|
phone: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'User phone number'
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'User type (e.g., "account")'
|
||||||
|
},
|
||||||
|
role: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'User role (e.g., "admin", "user")'
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
type: 'object',
|
||||||
|
description: 'User permissions object'
|
||||||
|
},
|
||||||
|
scopes: {
|
||||||
|
type: 'array',
|
||||||
|
items: { type: 'string' },
|
||||||
|
description: 'OAuth scopes for the user'
|
||||||
|
},
|
||||||
|
scopesAssignedToOnly: {
|
||||||
|
type: 'array',
|
||||||
|
items: { type: 'string' },
|
||||||
|
description: 'Scopes only assigned to this user'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['firstName', 'lastName', 'email']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_user',
|
||||||
|
description: 'Update an existing user/team member',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
userId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The user ID to update'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
},
|
||||||
|
firstName: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'User first name'
|
||||||
|
},
|
||||||
|
lastName: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'User last name'
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'User email address'
|
||||||
|
},
|
||||||
|
phone: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'User phone number'
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'User type'
|
||||||
|
},
|
||||||
|
role: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'User role'
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
type: 'object',
|
||||||
|
description: 'User permissions object'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['userId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete_user',
|
||||||
|
description: 'Delete a user/team member from a location',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
userId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The user ID to delete'
|
||||||
|
},
|
||||||
|
locationId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Location ID (uses default if not provided)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ['userId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'search_users',
|
||||||
|
description: 'Search for users across a company/agency by email, name, or other criteria',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
companyId: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Company ID to search within'
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Search query string'
|
||||||
|
},
|
||||||
|
skip: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Records to skip'
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Max records to return'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleToolCall(toolName: string, args: Record<string, unknown>): Promise<unknown> {
|
||||||
|
const config = this.ghlClient.getConfig();
|
||||||
|
const locationId = (args.locationId as string) || config.locationId;
|
||||||
|
|
||||||
|
switch (toolName) {
|
||||||
|
case 'get_users': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.skip) params.append('skip', String(args.skip));
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.type) params.append('type', String(args.type));
|
||||||
|
if (args.role) params.append('role', String(args.role));
|
||||||
|
if (args.ids) params.append('ids', String(args.ids));
|
||||||
|
if (args.sort) params.append('sort', String(args.sort));
|
||||||
|
if (args.sortDirection) params.append('sortDirection', String(args.sortDirection));
|
||||||
|
|
||||||
|
return this.ghlClient.makeRequest('GET', `/users/?${params.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'get_user': {
|
||||||
|
const userId = args.userId as string;
|
||||||
|
return this.ghlClient.makeRequest('GET', `/users/${userId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'create_user': {
|
||||||
|
const body: Record<string, unknown> = {
|
||||||
|
locationId,
|
||||||
|
firstName: args.firstName,
|
||||||
|
lastName: args.lastName,
|
||||||
|
email: args.email
|
||||||
|
};
|
||||||
|
if (args.phone) body.phone = args.phone;
|
||||||
|
if (args.type) body.type = args.type;
|
||||||
|
if (args.role) body.role = args.role;
|
||||||
|
if (args.permissions) body.permissions = args.permissions;
|
||||||
|
if (args.scopes) body.scopes = args.scopes;
|
||||||
|
if (args.scopesAssignedToOnly) body.scopesAssignedToOnly = args.scopesAssignedToOnly;
|
||||||
|
|
||||||
|
return this.ghlClient.makeRequest('POST', `/users/`, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'update_user': {
|
||||||
|
const userId = args.userId as string;
|
||||||
|
const body: Record<string, unknown> = {};
|
||||||
|
if (args.firstName) body.firstName = args.firstName;
|
||||||
|
if (args.lastName) body.lastName = args.lastName;
|
||||||
|
if (args.email) body.email = args.email;
|
||||||
|
if (args.phone) body.phone = args.phone;
|
||||||
|
if (args.type) body.type = args.type;
|
||||||
|
if (args.role) body.role = args.role;
|
||||||
|
if (args.permissions) body.permissions = args.permissions;
|
||||||
|
|
||||||
|
return this.ghlClient.makeRequest('PUT', `/users/${userId}`, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'delete_user': {
|
||||||
|
const userId = args.userId as string;
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/users/${userId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'search_users': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (args.companyId) params.append('companyId', String(args.companyId));
|
||||||
|
if (args.query) params.append('query', String(args.query));
|
||||||
|
if (args.skip) params.append('skip', String(args.skip));
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
|
||||||
|
return this.ghlClient.makeRequest('GET', `/users/search?${params.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown tool: ${toolName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
194
src/tools/webhooks-tools.ts
Normal file
194
src/tools/webhooks-tools.ts
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
/**
|
||||||
|
* GoHighLevel Webhooks Tools
|
||||||
|
* Tools for managing webhooks and event subscriptions
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { GHLApiClient } from '../clients/ghl-api-client.js';
|
||||||
|
|
||||||
|
export class WebhooksTools {
|
||||||
|
constructor(private ghlClient: GHLApiClient) {}
|
||||||
|
|
||||||
|
getToolDefinitions() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'get_webhooks',
|
||||||
|
description: 'Get all webhooks for a location',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_webhook',
|
||||||
|
description: 'Get a specific webhook by ID',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
webhookId: { type: 'string', description: 'Webhook ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['webhookId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_webhook',
|
||||||
|
description: 'Create a new webhook subscription',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
name: { type: 'string', description: 'Webhook name' },
|
||||||
|
url: { type: 'string', description: 'Webhook URL to receive events' },
|
||||||
|
events: {
|
||||||
|
type: 'array',
|
||||||
|
items: { type: 'string' },
|
||||||
|
description: 'Events to subscribe to (e.g., contact.created, opportunity.updated)'
|
||||||
|
},
|
||||||
|
secret: { type: 'string', description: 'Secret key for webhook signature verification' }
|
||||||
|
},
|
||||||
|
required: ['name', 'url', 'events']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'update_webhook',
|
||||||
|
description: 'Update a webhook',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
webhookId: { type: 'string', description: 'Webhook ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
name: { type: 'string', description: 'Webhook name' },
|
||||||
|
url: { type: 'string', description: 'Webhook URL' },
|
||||||
|
events: {
|
||||||
|
type: 'array',
|
||||||
|
items: { type: 'string' },
|
||||||
|
description: 'Events to subscribe to'
|
||||||
|
},
|
||||||
|
active: { type: 'boolean', description: 'Whether webhook is active' }
|
||||||
|
},
|
||||||
|
required: ['webhookId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete_webhook',
|
||||||
|
description: 'Delete a webhook',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
webhookId: { type: 'string', description: 'Webhook ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['webhookId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_webhook_events',
|
||||||
|
description: 'Get list of all available webhook event types',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'get_webhook_logs',
|
||||||
|
description: 'Get webhook delivery logs/history',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
webhookId: { type: 'string', description: 'Webhook ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
limit: { type: 'number', description: 'Max results' },
|
||||||
|
offset: { type: 'number', description: 'Pagination offset' },
|
||||||
|
status: { type: 'string', enum: ['success', 'failed', 'pending'], description: 'Filter by delivery status' }
|
||||||
|
},
|
||||||
|
required: ['webhookId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'retry_webhook',
|
||||||
|
description: 'Retry a failed webhook delivery',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
webhookId: { type: 'string', description: 'Webhook ID' },
|
||||||
|
logId: { type: 'string', description: 'Webhook log entry ID to retry' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' }
|
||||||
|
},
|
||||||
|
required: ['webhookId', 'logId']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'test_webhook',
|
||||||
|
description: 'Send a test event to a webhook',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
webhookId: { type: 'string', description: 'Webhook ID' },
|
||||||
|
locationId: { type: 'string', description: 'Location ID' },
|
||||||
|
eventType: { type: 'string', description: 'Event type to test' }
|
||||||
|
},
|
||||||
|
required: ['webhookId', 'eventType']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleToolCall(toolName: string, args: Record<string, unknown>): Promise<unknown> {
|
||||||
|
const config = this.ghlClient.getConfig();
|
||||||
|
const locationId = (args.locationId as string) || config.locationId;
|
||||||
|
|
||||||
|
switch (toolName) {
|
||||||
|
case 'get_webhooks': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/webhooks/?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'get_webhook': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/webhooks/${args.webhookId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'create_webhook': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/webhooks/`, {
|
||||||
|
locationId,
|
||||||
|
name: args.name,
|
||||||
|
url: args.url,
|
||||||
|
events: args.events,
|
||||||
|
secret: args.secret
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case 'update_webhook': {
|
||||||
|
const body: Record<string, unknown> = { locationId };
|
||||||
|
if (args.name) body.name = args.name;
|
||||||
|
if (args.url) body.url = args.url;
|
||||||
|
if (args.events) body.events = args.events;
|
||||||
|
if (args.active !== undefined) body.active = args.active;
|
||||||
|
return this.ghlClient.makeRequest('PUT', `/webhooks/${args.webhookId}`, body);
|
||||||
|
}
|
||||||
|
case 'delete_webhook': {
|
||||||
|
return this.ghlClient.makeRequest('DELETE', `/webhooks/${args.webhookId}?locationId=${locationId}`);
|
||||||
|
}
|
||||||
|
case 'get_webhook_events': {
|
||||||
|
return this.ghlClient.makeRequest('GET', `/webhooks/events`);
|
||||||
|
}
|
||||||
|
case 'get_webhook_logs': {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('locationId', locationId);
|
||||||
|
if (args.limit) params.append('limit', String(args.limit));
|
||||||
|
if (args.offset) params.append('offset', String(args.offset));
|
||||||
|
if (args.status) params.append('status', String(args.status));
|
||||||
|
return this.ghlClient.makeRequest('GET', `/webhooks/${args.webhookId}/logs?${params.toString()}`);
|
||||||
|
}
|
||||||
|
case 'retry_webhook': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/webhooks/${args.webhookId}/logs/${args.logId}/retry`, { locationId });
|
||||||
|
}
|
||||||
|
case 'test_webhook': {
|
||||||
|
return this.ghlClient.makeRequest('POST', `/webhooks/${args.webhookId}/test`, {
|
||||||
|
locationId,
|
||||||
|
eventType: args.eventType
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown tool: ${toolName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user