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 + +

+ + Join Waitlist + +

+ +--- + +*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 [![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template?template=https://github.com/mastanley13/GoHighLevel-MCP) [![Donate to the Project](https://img.shields.io/badge/Donate_to_the_Project-💝_Support_Development-ff69b4?style=for-the-badge&logo=stripe&logoColor=white)](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}`); + } + } +}