Skip to main content
Glama
opportunity-tools.ts21.1 kB
/** * MCP Opportunity Tools for GoHighLevel Integration * Exposes opportunity management capabilities to Claude Desktop */ import { Tool } from '@modelcontextprotocol/sdk/types.js'; import { GHLApiClient } from '../clients/ghl-api-client.js'; import { MCPSearchOpportunitiesParams, MCPCreateOpportunityParams, MCPUpdateOpportunityParams, MCPUpsertOpportunityParams, MCPAddOpportunityFollowersParams, MCPRemoveOpportunityFollowersParams, GHLOpportunity, GHLSearchOpportunitiesResponse, GHLGetPipelinesResponse, GHLUpsertOpportunityResponse } from '../types/ghl-types.js'; /** * Opportunity Tools Class * Implements MCP tools for opportunity management */ export class OpportunityTools { constructor(private ghlClient: GHLApiClient) {} /** * Get all opportunity tool definitions for MCP server */ getToolDefinitions(): Tool[] { return [ { name: 'search_opportunities', description: 'Search for opportunities in GoHighLevel CRM using various filters like pipeline, stage, contact, status, etc.', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'General search query (searches name, contact info)' }, pipelineId: { type: 'string', description: 'Filter by specific pipeline ID' }, pipelineStageId: { type: 'string', description: 'Filter by specific pipeline stage ID' }, contactId: { type: 'string', description: 'Filter by specific contact ID' }, status: { type: 'string', description: 'Filter by opportunity status', enum: ['open', 'won', 'lost', 'abandoned', 'all'] }, assignedTo: { type: 'string', description: 'Filter by assigned user ID' }, limit: { type: 'number', description: 'Maximum number of opportunities to return (default: 20, max: 100)', minimum: 1, maximum: 100, default: 20 } } } }, { name: 'get_pipelines', description: 'Get all sales pipelines configured in GoHighLevel', inputSchema: { type: 'object', properties: {} } }, { name: 'get_opportunity', description: 'Get detailed information about a specific opportunity by ID', inputSchema: { type: 'object', properties: { opportunityId: { type: 'string', description: 'The unique ID of the opportunity to retrieve' } }, required: ['opportunityId'] } }, { name: 'create_opportunity', description: 'Create a new opportunity in GoHighLevel CRM', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Name/title of the opportunity' }, pipelineId: { type: 'string', description: 'ID of the pipeline this opportunity belongs to' }, contactId: { type: 'string', description: 'ID of the contact associated with this opportunity' }, status: { type: 'string', description: 'Initial status of the opportunity (default: open)', enum: ['open', 'won', 'lost', 'abandoned'], default: 'open' }, monetaryValue: { type: 'number', description: 'Monetary value of the opportunity in dollars' }, assignedTo: { type: 'string', description: 'User ID to assign this opportunity to' } }, required: ['name', 'pipelineId', 'contactId'] } }, { name: 'update_opportunity_status', description: 'Update the status of an opportunity (won, lost, etc.)', inputSchema: { type: 'object', properties: { opportunityId: { type: 'string', description: 'The unique ID of the opportunity' }, status: { type: 'string', description: 'New status for the opportunity', enum: ['open', 'won', 'lost', 'abandoned'] } }, required: ['opportunityId', 'status'] } }, { name: 'delete_opportunity', description: 'Delete an opportunity from GoHighLevel CRM', inputSchema: { type: 'object', properties: { opportunityId: { type: 'string', description: 'The unique ID of the opportunity to delete' } }, required: ['opportunityId'] } }, { name: 'update_opportunity', description: 'Update an existing opportunity with new details (full update)', inputSchema: { type: 'object', properties: { opportunityId: { type: 'string', description: 'The unique ID of the opportunity to update' }, name: { type: 'string', description: 'Updated name/title of the opportunity' }, pipelineId: { type: 'string', description: 'Updated pipeline ID' }, pipelineStageId: { type: 'string', description: 'Updated pipeline stage ID' }, status: { type: 'string', description: 'Updated status of the opportunity', enum: ['open', 'won', 'lost', 'abandoned'] }, monetaryValue: { type: 'number', description: 'Updated monetary value in dollars' }, assignedTo: { type: 'string', description: 'Updated assigned user ID' } }, required: ['opportunityId'] } }, { name: 'upsert_opportunity', description: 'Create or update an opportunity based on contact and pipeline (smart merge)', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Name/title of the opportunity' }, pipelineId: { type: 'string', description: 'ID of the pipeline this opportunity belongs to' }, contactId: { type: 'string', description: 'ID of the contact associated with this opportunity' }, status: { type: 'string', description: 'Status of the opportunity', enum: ['open', 'won', 'lost', 'abandoned'], default: 'open' }, pipelineStageId: { type: 'string', description: 'Pipeline stage ID' }, monetaryValue: { type: 'number', description: 'Monetary value of the opportunity in dollars' }, assignedTo: { type: 'string', description: 'User ID to assign this opportunity to' } }, required: ['pipelineId', 'contactId'] } }, { name: 'add_opportunity_followers', description: 'Add followers to an opportunity for notifications and tracking', inputSchema: { type: 'object', properties: { opportunityId: { type: 'string', description: 'The unique ID of the opportunity' }, followers: { type: 'array', items: { type: 'string' }, description: 'Array of user IDs to add as followers' } }, required: ['opportunityId', 'followers'] } }, { name: 'remove_opportunity_followers', description: 'Remove followers from an opportunity', inputSchema: { type: 'object', properties: { opportunityId: { type: 'string', description: 'The unique ID of the opportunity' }, followers: { type: 'array', items: { type: 'string' }, description: 'Array of user IDs to remove as followers' } }, required: ['opportunityId', 'followers'] } } ]; } /** * Execute opportunity tool based on tool name and arguments */ async executeTool(name: string, args: any): Promise<any> { switch (name) { case 'search_opportunities': return this.searchOpportunities(args as MCPSearchOpportunitiesParams); case 'get_pipelines': return this.getPipelines(); case 'get_opportunity': return this.getOpportunity(args.opportunityId); case 'create_opportunity': return this.createOpportunity(args as MCPCreateOpportunityParams); case 'update_opportunity_status': return this.updateOpportunityStatus(args.opportunityId, args.status); case 'delete_opportunity': return this.deleteOpportunity(args.opportunityId); case 'update_opportunity': return this.updateOpportunity(args as MCPUpdateOpportunityParams); case 'upsert_opportunity': return this.upsertOpportunity(args as MCPUpsertOpportunityParams); case 'add_opportunity_followers': return this.addOpportunityFollowers(args as MCPAddOpportunityFollowersParams); case 'remove_opportunity_followers': return this.removeOpportunityFollowers(args as MCPRemoveOpportunityFollowersParams); default: throw new Error(`Unknown opportunity tool: ${name}`); } } /** * SEARCH OPPORTUNITIES */ private async searchOpportunities(params: MCPSearchOpportunitiesParams): Promise<{ success: boolean; opportunities: GHLOpportunity[]; meta: any; message: string }> { try { // Build search parameters with correct API naming (underscores) const searchParams: any = { location_id: this.ghlClient.getConfig().locationId, limit: params.limit || 20 }; // Only add parameters if they have values if (params.query && params.query.trim()) { searchParams.q = params.query.trim(); } if (params.pipelineId) { searchParams.pipeline_id = params.pipelineId; } if (params.pipelineStageId) { searchParams.pipeline_stage_id = params.pipelineStageId; } if (params.contactId) { searchParams.contact_id = params.contactId; } if (params.status) { searchParams.status = params.status; } if (params.assignedTo) { searchParams.assigned_to = params.assignedTo; } process.stderr.write(`[GHL MCP] Calling searchOpportunities with params: ${JSON.stringify(searchParams, null, 2)}\n`); const response = await this.ghlClient.searchOpportunities(searchParams); if (!response.success || !response.data) { const errorMsg = response.error?.message || 'Unknown API error'; throw new Error(`API request failed: ${errorMsg}`); } const data = response.data as GHLSearchOpportunitiesResponse; const opportunities = Array.isArray(data.opportunities) ? data.opportunities : []; return { success: true, opportunities, meta: data.meta, message: `Found ${opportunities.length} opportunities (${data.meta?.total || opportunities.length} total)` }; } catch (error) { process.stderr.write(`[GHL MCP] Search opportunities error: ${JSON.stringify(error, null, 2)}\n`); throw new Error(`Failed to search opportunities: ${error instanceof Error ? error.message : String(error)}`); } } /** * GET PIPELINES */ private async getPipelines(): Promise<{ success: boolean; pipelines: any[]; message: string }> { try { const response = await this.ghlClient.getPipelines(); if (!response.success || !response.data) { const errorMsg = response.error?.message || 'Unknown API error'; throw new Error(`API request failed: ${errorMsg}`); } const data = response.data as GHLGetPipelinesResponse; const pipelines = Array.isArray(data.pipelines) ? data.pipelines : []; return { success: true, pipelines, message: `Retrieved ${pipelines.length} pipelines` }; } catch (error) { throw new Error(`Failed to get pipelines: ${error instanceof Error ? error.message : String(error)}`); } } /** * GET OPPORTUNITY BY ID */ private async getOpportunity(opportunityId: string): Promise<{ success: boolean; opportunity: GHLOpportunity; message: string }> { try { const response = await this.ghlClient.getOpportunity(opportunityId); if (!response.success || !response.data) { const errorMsg = response.error?.message || 'Unknown API error'; throw new Error(`API request failed: ${errorMsg}`); } return { success: true, opportunity: response.data, message: 'Opportunity retrieved successfully' }; } catch (error) { throw new Error(`Failed to get opportunity: ${error instanceof Error ? error.message : String(error)}`); } } /** * CREATE OPPORTUNITY */ private async createOpportunity(params: MCPCreateOpportunityParams): Promise<{ success: boolean; opportunity: GHLOpportunity; message: string }> { try { const opportunityData = { locationId: this.ghlClient.getConfig().locationId, name: params.name, pipelineId: params.pipelineId, contactId: params.contactId, status: params.status || 'open' as const, pipelineStageId: params.pipelineStageId, monetaryValue: params.monetaryValue, assignedTo: params.assignedTo, customFields: params.customFields }; const response = await this.ghlClient.createOpportunity(opportunityData); if (!response.success || !response.data) { const errorMsg = response.error?.message || 'Unknown API error'; throw new Error(`API request failed: ${errorMsg}`); } return { success: true, opportunity: response.data, message: `Opportunity created successfully with ID: ${response.data.id}` }; } catch (error) { throw new Error(`Failed to create opportunity: ${error instanceof Error ? error.message : String(error)}`); } } /** * UPDATE OPPORTUNITY STATUS */ private async updateOpportunityStatus(opportunityId: string, status: string): Promise<{ success: boolean; message: string }> { try { const response = await this.ghlClient.updateOpportunityStatus(opportunityId, status as any); if (!response.success || !response.data) { const errorMsg = response.error?.message || 'Unknown API error'; throw new Error(`API request failed: ${errorMsg}`); } return { success: true, message: `Opportunity status updated to ${status}` }; } catch (error) { throw new Error(`Failed to update opportunity status: ${error instanceof Error ? error.message : String(error)}`); } } /** * DELETE OPPORTUNITY */ private async deleteOpportunity(opportunityId: string): Promise<{ success: boolean; message: string }> { try { const response = await this.ghlClient.deleteOpportunity(opportunityId); if (!response.success || !response.data) { const errorMsg = response.error?.message || 'Unknown API error'; throw new Error(`API request failed: ${errorMsg}`); } return { success: true, message: 'Opportunity deleted successfully' }; } catch (error) { throw new Error(`Failed to delete opportunity: ${error instanceof Error ? error.message : String(error)}`); } } /** * UPDATE OPPORTUNITY (FULL UPDATE) */ private async updateOpportunity(params: MCPUpdateOpportunityParams): Promise<{ success: boolean; opportunity: GHLOpportunity; message: string }> { try { const updateData: any = {}; // Only include fields that are provided if (params.name) updateData.name = params.name; if (params.pipelineId) updateData.pipelineId = params.pipelineId; if (params.pipelineStageId) updateData.pipelineStageId = params.pipelineStageId; if (params.status) updateData.status = params.status; if (params.monetaryValue !== undefined) updateData.monetaryValue = params.monetaryValue; if (params.assignedTo) updateData.assignedTo = params.assignedTo; const response = await this.ghlClient.updateOpportunity(params.opportunityId, updateData); if (!response.success || !response.data) { const errorMsg = response.error?.message || 'Unknown API error'; throw new Error(`API request failed: ${errorMsg}`); } return { success: true, opportunity: response.data, message: `Opportunity updated successfully` }; } catch (error) { throw new Error(`Failed to update opportunity: ${error instanceof Error ? error.message : String(error)}`); } } /** * UPSERT OPPORTUNITY */ private async upsertOpportunity(params: MCPUpsertOpportunityParams): Promise<{ success: boolean; opportunity: GHLOpportunity; isNew: boolean; message: string }> { try { const upsertData = { locationId: this.ghlClient.getConfig().locationId, pipelineId: params.pipelineId, contactId: params.contactId, name: params.name, status: params.status || 'open' as const, pipelineStageId: params.pipelineStageId, monetaryValue: params.monetaryValue, assignedTo: params.assignedTo }; const response = await this.ghlClient.upsertOpportunity(upsertData); if (!response.success || !response.data) { const errorMsg = response.error?.message || 'Unknown API error'; throw new Error(`API request failed: ${errorMsg}`); } const data = response.data as GHLUpsertOpportunityResponse; return { success: true, opportunity: data.opportunity, isNew: data.new, message: `Opportunity ${data.new ? 'created' : 'updated'} successfully` }; } catch (error) { throw new Error(`Failed to upsert opportunity: ${error instanceof Error ? error.message : String(error)}`); } } /** * ADD OPPORTUNITY FOLLOWERS */ private async addOpportunityFollowers(params: MCPAddOpportunityFollowersParams): Promise<{ success: boolean; followers: string[]; followersAdded: string[]; message: string }> { try { const response = await this.ghlClient.addOpportunityFollowers(params.opportunityId, params.followers); if (!response.success || !response.data) { const errorMsg = response.error?.message || 'Unknown API error'; throw new Error(`API request failed: ${errorMsg}`); } return { success: true, followers: response.data.followers || [], followersAdded: response.data.followersAdded || [], message: `Added ${response.data.followersAdded?.length || 0} followers to opportunity` }; } catch (error) { throw new Error(`Failed to add opportunity followers: ${error instanceof Error ? error.message : String(error)}`); } } /** * REMOVE OPPORTUNITY FOLLOWERS */ private async removeOpportunityFollowers(params: MCPRemoveOpportunityFollowersParams): Promise<{ success: boolean; followers: string[]; followersRemoved: string[]; message: string }> { try { const response = await this.ghlClient.removeOpportunityFollowers(params.opportunityId, params.followers); if (!response.success || !response.data) { const errorMsg = response.error?.message || 'Unknown API error'; throw new Error(`API request failed: ${errorMsg}`); } return { success: true, followers: response.data.followers || [], followersRemoved: response.data.followersRemoved || [], message: `Removed ${response.data.followersRemoved?.length || 0} followers from opportunity` }; } catch (error) { throw new Error(`Failed to remove opportunity followers: ${error instanceof Error ? error.message : String(error)}`); } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/clickmediapropy/gohighlevel-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server