diff --git a/.env.example b/.env.example
index 3b059f6..08cb1a8 100644
Binary files a/.env.example and b/.env.example differ
diff --git a/README.md b/README.md
index e2a213e..0d21f30 100644
--- a/README.md
+++ b/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!**
https://www.strategixagents.com/
# 🚀 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
+
+
+
+
+
+
+
+---
+
+*Prefer to self-host? Keep reading below for the full open-source setup guide.*
+
+---
+
+>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
## 🚨 **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).**
@@ -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://buy.stripe.com/28E14o1hT7JAfstfvqdZ60y)
+<<<<<<< HEAD
> **🔥 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
@@ -743,7 +867,11 @@ This project is licensed under the **ISC License** - see the [LICENSE](LICENSE)
This comprehensive MCP server delivers:
+<<<<<<< HEAD
### ✅ **461 Operational Tools** across 38 categories
+=======
+### ✅ **461 Operational Tools** across 19 categories
+>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
### ✅ **Real-time GoHighLevel Integration** with full API coverage
### ✅ **Production-Ready Deployment** on multiple platforms
### ✅ **Enterprise-Grade Architecture** with comprehensive error handling
diff --git a/package-lock.json b/package-lock.json
index f733e19..d8dfdfe 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,11 +1,19 @@
{
+<<<<<<< HEAD
"name": "ghl-mcp",
+=======
+ "name": "@mastanley13/ghl-mcp-server",
+>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
+<<<<<<< HEAD
"name": "ghl-mcp",
+=======
+ "name": "@mastanley13/ghl-mcp-server",
+>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
"version": "1.0.0",
"license": "ISC",
"dependencies": {
@@ -17,6 +25,12 @@
"dotenv": "^16.5.0",
"express": "^5.1.0"
},
+<<<<<<< HEAD
+=======
+ "bin": {
+ "ghl-mcp-server": "dist/server.js"
+ },
+>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
"devDependencies": {
"@types/jest": "^29.5.14",
"@types/node": "^22.15.29",
@@ -25,6 +39,12 @@
"ts-jest": "^29.3.4",
"ts-node": "^10.9.2",
"typescript": "^5.8.3"
+<<<<<<< HEAD
+=======
+ },
+ "engines": {
+ "node": ">=18.0.0"
+>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
}
},
"node_modules/@ampproject/remapping": {
@@ -68,6 +88,10 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz",
"integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==",
"dev": true,
+<<<<<<< HEAD
+=======
+ "peer": true,
+>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1",
@@ -1078,6 +1102,10 @@
"version": "22.15.29",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.29.tgz",
"integrity": "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==",
+<<<<<<< HEAD
+=======
+ "peer": true,
+>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
"dependencies": {
"undici-types": "~6.21.0"
}
@@ -1459,6 +1487,10 @@
"url": "https://github.com/sponsors/ai"
}
],
+<<<<<<< HEAD
+=======
+ "peer": true,
+>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
"dependencies": {
"caniuse-lite": "^1.0.30001718",
"electron-to-chromium": "^1.5.160",
@@ -2123,6 +2155,10 @@
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
+<<<<<<< HEAD
+=======
+ "peer": true,
+>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.0",
@@ -2842,6 +2878,10 @@
"resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
"dev": true,
+<<<<<<< HEAD
+=======
+ "peer": true,
+>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
"dependencies": {
"@jest/core": "^29.7.0",
"@jest/types": "^29.6.3",
@@ -4651,6 +4691,10 @@
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"dev": true,
+<<<<<<< HEAD
+=======
+ "peer": true,
+>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
@@ -4728,6 +4772,10 @@
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"dev": true,
+<<<<<<< HEAD
+=======
+ "peer": true,
+>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -4946,6 +4994,10 @@
"version": "3.25.51",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.51.tgz",
"integrity": "sha512-TQSnBldh+XSGL+opiSIq0575wvDPqu09AqWe1F7JhUMKY+M91/aGlK4MhpVNO7MgYfHcVCB1ffwAUTJzllKJqg==",
+<<<<<<< HEAD
+=======
+ "peer": true,
+>>>>>>> 422de92c1c7a69e2ca2b7045d9142636bc3e321d
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
diff --git a/src/apps/index.ts b/src/apps/index.ts
index 0b8162c..b37b25c 100644
--- a/src/apps/index.ts
+++ b/src/apps/index.ts
@@ -11,15 +11,7 @@ import { fileURLToPath } from 'url';
export interface AppToolResult {
content: Array<{ type: 'text'; text: string }>;
- structuredContent?: {
- type: 'resource';
- resource: {
- uri: string;
- mimeType: string;
- text?: string;
- blob?: string;
- };
- };
+ structuredContent?: Record;
[key: string]: unknown;
}
@@ -33,21 +25,22 @@ export interface AppResourceHandler {
* MCP Apps Manager class
* Registers app tools and handles structuredContent responses
*/
-// Note: We use process.cwd() based path resolution to find UI dist
-// This works when running from the project root directory
+// Resolve UI build path - works regardless of working directory
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');
if (fs.existsSync(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
- return appUiPath;
+ return fromDist;
}
export class MCPAppsManager {
@@ -306,6 +299,22 @@ export class MCPAppsManager {
_meta: {
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_contact_timeline',
'view_workflow_status',
- 'view_dashboard'
+ 'view_dashboard',
+ 'update_opportunity'
];
}
@@ -365,6 +375,14 @@ export class MCPAppsManager {
return await this.viewWorkflowStatus(args.workflowId);
case 'view_dashboard':
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:
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 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 = {
pipeline,
- opportunities,
+ opportunities: simplifiedOpportunities,
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 {
+ 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
*/
@@ -702,19 +781,11 @@ export class MCPAppsManager {
htmlContent: string,
data: any
): AppToolResult {
- // Inject the data into the HTML
- const htmlWithData = this.injectDataIntoHTML(htmlContent, data);
-
+ // structuredContent is the data object that gets passed to ontoolresult
+ // The UI accesses it via result.structuredContent
return {
content: [{ type: 'text', text: textSummary }],
- structuredContent: {
- type: 'resource',
- resource: {
- uri: resourceUri,
- mimeType: mimeType,
- text: htmlWithData
- }
- }
+ structuredContent: data
};
}
diff --git a/src/clients/ghl-api-client.ts b/src/clients/ghl-api-client.ts
index 5560351..d5a307c 100644
--- a/src/clients/ghl-api-client.ts
+++ b/src/clients/ghl-api-client.ts
@@ -575,12 +575,12 @@ export class GHLApiClient {
const filters: any = {};
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();
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();
hasFilters = true;
}
@@ -1558,6 +1558,36 @@ export class GHLApiClient {
return { ...this.config };
}
+ /**
+ * Generic request method for new endpoints
+ * Used by new tool modules that don't have specific client methods yet
+ */
+ async makeRequest(method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', path: string, body?: Record): Promise> {
+ 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
*/
diff --git a/src/tools/affiliates-tools.ts b/src/tools/affiliates-tools.ts
new file mode 100644
index 0000000..a71615c
--- /dev/null
+++ b/src/tools/affiliates-tools.ts
@@ -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): Promise {
+ 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 = { 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 = { 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}`);
+ }
+ }
+}
diff --git a/src/tools/businesses-tools.ts b/src/tools/businesses-tools.ts
new file mode 100644
index 0000000..a1e12bd
--- /dev/null
+++ b/src/tools/businesses-tools.ts
@@ -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): Promise {
+ 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 = {
+ 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 = { 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}`);
+ }
+ }
+}
diff --git a/src/tools/campaigns-tools.ts b/src/tools/campaigns-tools.ts
new file mode 100644
index 0000000..1076c94
--- /dev/null
+++ b/src/tools/campaigns-tools.ts
@@ -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): Promise {
+ 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 = { 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}`);
+ }
+ }
+}
diff --git a/src/tools/companies-tools.ts b/src/tools/companies-tools.ts
new file mode 100644
index 0000000..c51c45c
--- /dev/null
+++ b/src/tools/companies-tools.ts
@@ -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): Promise {
+ 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 = {
+ 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 = {};
+ 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}`);
+ }
+ }
+}
diff --git a/src/tools/courses-tools.ts b/src/tools/courses-tools.ts
new file mode 100644
index 0000000..de639c5
--- /dev/null
+++ b/src/tools/courses-tools.ts
@@ -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): Promise {
+ 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = {
+ 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 = { 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}`);
+ }
+ }
+}
diff --git a/src/tools/forms-tools.ts b/src/tools/forms-tools.ts
new file mode 100644
index 0000000..9ae005f
--- /dev/null
+++ b/src/tools/forms-tools.ts
@@ -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): Promise {
+ 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}`);
+ }
+ }
+}
diff --git a/src/tools/funnels-tools.ts b/src/tools/funnels-tools.ts
new file mode 100644
index 0000000..b964319
--- /dev/null
+++ b/src/tools/funnels-tools.ts
@@ -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): Promise {
+ 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 = {
+ 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 = { 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}`);
+ }
+ }
+}
diff --git a/src/tools/links-tools.ts b/src/tools/links-tools.ts
new file mode 100644
index 0000000..c490909
--- /dev/null
+++ b/src/tools/links-tools.ts
@@ -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): Promise {
+ 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 = {
+ 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 = { 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}`);
+ }
+ }
+}
diff --git a/src/tools/oauth-tools.ts b/src/tools/oauth-tools.ts
new file mode 100644
index 0000000..86c601d
--- /dev/null
+++ b/src/tools/oauth-tools.ts
@@ -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): Promise {
+ 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}`);
+ }
+ }
+}
diff --git a/src/tools/phone-tools.ts b/src/tools/phone-tools.ts
new file mode 100644
index 0000000..89fd292
--- /dev/null
+++ b/src/tools/phone-tools.ts
@@ -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): Promise {
+ 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 = { 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 = { 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 = { 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 = { 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}`);
+ }
+ }
+}
diff --git a/src/tools/reporting-tools.ts b/src/tools/reporting-tools.ts
new file mode 100644
index 0000000..22e9f0e
--- /dev/null
+++ b/src/tools/reporting-tools.ts
@@ -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): Promise {
+ 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}`);
+ }
+ }
+}
diff --git a/src/tools/reputation-tools.ts b/src/tools/reputation-tools.ts
new file mode 100644
index 0000000..f50707d
--- /dev/null
+++ b/src/tools/reputation-tools.ts
@@ -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): Promise {
+ 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 = { 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 = { 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}`);
+ }
+ }
+}
diff --git a/src/tools/saas-tools.ts b/src/tools/saas-tools.ts
new file mode 100644
index 0000000..899b553
--- /dev/null
+++ b/src/tools/saas-tools.ts
@@ -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): Promise {
+ 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 = { 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 = { 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}`);
+ }
+ }
+}
diff --git a/src/tools/smartlists-tools.ts b/src/tools/smartlists-tools.ts
new file mode 100644
index 0000000..3ab8dad
--- /dev/null
+++ b/src/tools/smartlists-tools.ts
@@ -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): Promise {
+ 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 = { 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}`);
+ }
+ }
+}
diff --git a/src/tools/snapshots-tools.ts b/src/tools/snapshots-tools.ts
new file mode 100644
index 0000000..afba7e1
--- /dev/null
+++ b/src/tools/snapshots-tools.ts
@@ -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): Promise {
+ 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 = {
+ 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 = {
+ 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}`);
+ }
+ }
+}
diff --git a/src/tools/templates-tools.ts b/src/tools/templates-tools.ts
new file mode 100644
index 0000000..0c460ed
--- /dev/null
+++ b/src/tools/templates-tools.ts
@@ -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): Promise {
+ 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 = { 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 = { 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}`);
+ }
+ }
+}
diff --git a/src/tools/triggers-tools.ts b/src/tools/triggers-tools.ts
new file mode 100644
index 0000000..442d0f5
--- /dev/null
+++ b/src/tools/triggers-tools.ts
@@ -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): Promise {
+ 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 = { 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}`);
+ }
+ }
+}
diff --git a/src/tools/users-tools.ts b/src/tools/users-tools.ts
new file mode 100644
index 0000000..cd008a7
--- /dev/null
+++ b/src/tools/users-tools.ts
@@ -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): Promise {
+ 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 = {
+ 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 = {};
+ 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}`);
+ }
+ }
+}
diff --git a/src/tools/webhooks-tools.ts b/src/tools/webhooks-tools.ts
new file mode 100644
index 0000000..bc3d39e
--- /dev/null
+++ b/src/tools/webhooks-tools.ts
@@ -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): Promise {
+ 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 = { 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}`);
+ }
+ }
+}