Skip to main content
Glama

HubSpot MCP

#!/usr/bin/env node import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js" import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js" import { createStatefulServer } from "@smithery/sdk/server/stateful.js" import { z } from "zod" function formatResponse(data: any) { let text = '' if (typeof data === 'string') { text = data } else if (data === null || data === undefined) { text = "No data returned" } else if (typeof data === 'object') { text = JSON.stringify(data) } else { text = String(data) } return { content: [{ type: "text", text }] } } async function makeApiRequest(apiKey: string, endpoint: string, params: Record<string, any> = {}, method = 'GET', body: Record<string, any> | null = null) { if (!apiKey) { throw new Error("HUBSPOT_ACCESS_TOKEN environment variable is not set") } const queryParams = new URLSearchParams() Object.entries(params).forEach(([key, value]) => { if (value !== undefined) queryParams.append(key, value.toString()) }) const url = `https://api.hubapi.com${endpoint}${queryParams.toString() ? `?${queryParams.toString()}` : ''}` const headers: Record<string, string> = { 'Accept': 'application/json', 'Authorization': `Bearer ${apiKey}` } if (body) headers['Content-Type'] = 'application/json' const requestOptions: RequestInit = { method, headers } if (body) requestOptions.body = JSON.stringify(body) const response = await fetch(url, requestOptions) if (!response.ok) return `Error fetching data from HubSpot: Status ${response.status}` if (response.status === 204) return `No data returned: Status ${response.status}` return await response.json() } async function makeApiRequestWithErrorHandling(apiKey: string, endpoint: string, params: Record<string, any> = {}, method = 'GET', body: Record<string, any> | null = null) { try { const data = await makeApiRequest(apiKey, endpoint, params, method, body) return formatResponse(data) } catch (error: any) { return formatResponse(`Error performing request: ${error.message}`) } } async function handleEndpoint(apiCall: () => Promise<any>) { try { return await apiCall() } catch (error: any) { return formatResponse(error.message) } } function getConfig(config: any) { return { hubspotAccessToken: config?.HUBSPOT_ACCESS_TOKEN || process.env.HUBSPOT_ACCESS_TOKEN } } function createServer({ config }: { config?: any } = {}) { const server = new McpServer({ name: "HubSpot-MCP", version: "1.7.0", description: "An extensive MCP for the HubSpot API" }) const { hubspotAccessToken } = getConfig(config) // Companies: https://developers.hubspot.com/docs/reference/api/crm/objects/companies const companyPropertiesSchema = z.object({ name: z.string().optional(), domain: z.string().optional(), website: z.string().url().optional(), description: z.string().optional(), industry: z.string().optional(), numberofemployees: z.number().optional(), annualrevenue: z.number().optional(), city: z.string().optional(), state: z.string().optional(), country: z.string().optional(), phone: z.string().optional(), address: z.string().optional(), address2: z.string().optional(), zip: z.string().optional(), type: z.string().optional(), lifecyclestage: z.enum(['lead', 'customer', 'opportunity', 'subscriber', 'other']).optional(), }).catchall(z.any()) server.tool("crm_create_company", "Create a new company with validated properties", { properties: companyPropertiesSchema, associations: z.array(z.object({ to: z.object({ id: z.string() }), types: z.array(z.object({ associationCategory: z.string(), associationTypeId: z.number() })) })).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/companies' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { properties: params.properties, associations: params.associations }) }) } ) server.tool("crm_update_company", "Update an existing company with validated properties", { companyId: z.string(), properties: companyPropertiesSchema }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/companies/${params.companyId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'PATCH', { properties: params.properties }) }) } ) server.tool("crm_get_company", "Get a single company by ID with specific properties and associations", { companyId: z.string(), properties: z.array(z.string()).optional(), associations: z.array(z.enum(['contacts', 'deals', 'tickets'])).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/companies/${params.companyId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, { properties: params.properties?.join(','), associations: params.associations?.join(',') }) }) } ) server.tool("crm_search_companies", "Search companies with company-specific filters", { filterGroups: z.array(z.object({ filters: z.array(z.object({ propertyName: z.string(), operator: z.enum(['EQ', 'NEQ', 'LT', 'LTE', 'GT', 'GTE', 'BETWEEN', 'IN', 'NOT_IN', 'HAS_PROPERTY', 'NOT_HAS_PROPERTY', 'CONTAINS_TOKEN', 'NOT_CONTAINS_TOKEN']), value: z.any() })) })), properties: z.array(z.string()).optional(), limit: z.number().min(1).max(100).optional(), after: z.string().optional(), sorts: z.array(z.object({ propertyName: z.string(), direction: z.enum(['ASCENDING', 'DESCENDING']) })).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/companies/search' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { filterGroups: params.filterGroups, properties: params.properties, limit: params.limit, after: params.after, sorts: params.sorts }) }) } ) server.tool("crm_batch_create_companies", "Create multiple companies in a single request", { inputs: z.array(z.object({ properties: companyPropertiesSchema, associations: z.array(z.object({ to: z.object({ id: z.string() }), types: z.array(z.object({ associationCategory: z.string(), associationTypeId: z.number() })) })).optional() })) }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/companies/batch/create' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.inputs }) }) } ) server.tool("crm_batch_update_companies", "Update multiple companies in a single request", { inputs: z.array(z.object({ id: z.string(), properties: companyPropertiesSchema })) }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/companies/batch/update' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.inputs }) }) } ) server.tool("crm_get_company_properties", "Get all properties for companies", { archived: z.boolean().optional(), properties: z.array(z.string()).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/properties/companies' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, { archived: params.archived, properties: params.properties?.join(',') }) }) } ) server.tool("crm_create_company_property", "Create a new company property", { name: z.string(), label: z.string(), type: z.enum(['string', 'number', 'date', 'datetime', 'enumeration', 'bool']), fieldType: z.enum(['text', 'textarea', 'select', 'radio', 'checkbox', 'number', 'date', 'file']), groupName: z.string(), description: z.string().optional(), options: z.array(z.object({ label: z.string(), value: z.string(), description: z.string().optional(), displayOrder: z.number().optional(), hidden: z.boolean().optional() })).optional(), displayOrder: z.number().optional(), hasUniqueValue: z.boolean().optional(), hidden: z.boolean().optional(), formField: z.boolean().optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/properties/companies' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', params) }) } ) // Objects: https://developers.hubspot.com/docs/reference/api/crm/objects/objects server.tool("crm_list_objects", "List CRM objects of a specific type with optional filtering and pagination", { objectType: z.enum(['companies', 'contacts', 'deals', 'tickets', 'products', 'line_items', 'quotes', 'custom']), properties: z.array(z.string()).optional(), after: z.string().optional(), limit: z.number().min(1).max(100).optional(), archived: z.boolean().optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/${params.objectType}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, { properties: params.properties?.join(','), after: params.after, limit: params.limit, archived: params.archived }) }) } ) server.tool("crm_get_object", "Get a single CRM object by ID", { objectType: z.enum(['companies', 'contacts', 'deals', 'tickets', 'products', 'line_items', 'quotes', 'custom']), objectId: z.string(), properties: z.array(z.string()).optional(), associations: z.array(z.string()).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/${params.objectType}/${params.objectId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, { properties: params.properties?.join(','), associations: params.associations?.join(',') }) }) } ) server.tool("crm_create_object", "Create a new CRM object", { objectType: z.enum(['companies', 'contacts', 'deals', 'tickets', 'products', 'line_items', 'quotes', 'custom']), properties: z.record(z.any()), associations: z.array(z.object({ to: z.object({ id: z.string() }), types: z.array(z.object({ associationCategory: z.string(), associationTypeId: z.number() })) })).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/${params.objectType}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { properties: params.properties, associations: params.associations }) }) } ) server.tool("crm_update_object", "Update an existing CRM object", { objectType: z.enum(['companies', 'contacts', 'deals', 'tickets', 'products', 'line_items', 'quotes', 'custom']), objectId: z.string(), properties: z.record(z.any()) }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/${params.objectType}/${params.objectId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'PATCH', { properties: params.properties }) }) } ) server.tool("crm_archive_object", "Archive (delete) a CRM object", { objectType: z.enum(['companies', 'contacts', 'deals', 'tickets', 'products', 'line_items', 'quotes', 'custom']), objectId: z.string() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/${params.objectType}/${params.objectId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'DELETE') }) } ) server.tool("crm_search_objects", "Search CRM objects using filters", { objectType: z.enum(['companies', 'contacts', 'deals', 'tickets', 'products', 'line_items', 'quotes', 'custom']), filterGroups: z.array(z.object({ filters: z.array(z.object({ propertyName: z.string(), operator: z.enum(['EQ', 'NEQ', 'LT', 'LTE', 'GT', 'GTE', 'BETWEEN', 'IN', 'NOT_IN', 'HAS_PROPERTY', 'NOT_HAS_PROPERTY', 'CONTAINS_TOKEN', 'NOT_CONTAINS_TOKEN']), value: z.any() })) })), properties: z.array(z.string()).optional(), limit: z.number().min(1).max(100).optional(), after: z.string().optional(), sorts: z.array(z.object({ propertyName: z.string(), direction: z.enum(['ASCENDING', 'DESCENDING']) })).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/${params.objectType}/search` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { filterGroups: params.filterGroups, properties: params.properties, limit: params.limit, after: params.after, sorts: params.sorts }) }) } ) server.tool("crm_batch_create_objects", "Create multiple CRM objects in a single request", { objectType: z.enum(['companies', 'contacts', 'deals', 'tickets', 'products', 'line_items', 'quotes', 'custom']), inputs: z.array(z.object({ properties: z.record(z.any()), associations: z.array(z.object({ to: z.object({ id: z.string() }), types: z.array(z.object({ associationCategory: z.string(), associationTypeId: z.number() })) })).optional() })) }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/${params.objectType}/batch/create` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.inputs }) }) } ) server.tool("crm_batch_read_objects", "Create multiple CRM objects in a single request", { objectType: z.enum(['companies', 'contacts', 'deals', 'tickets', 'products', 'line_items', 'quotes', 'custom']), propertiesWithHistory: z.array(z.string()).optional(), idProperty: z.string().optional(), objectIds: z.array(z.string()), properties: z.array(z.string()).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/${params.objectType}/batch/read` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { propertiesWithHistory: params.propertiesWithHistory, idProperty: params.idProperty, inputs: params.objectIds.map((id: string) => ({ id })), properties: params.properties }) }) } ) server.tool("crm_batch_update_objects", "Update multiple CRM objects in a single request", { objectType: z.enum(['companies', 'contacts', 'deals', 'tickets', 'products', 'line_items', 'quotes', 'custom']), inputs: z.array(z.object({ id: z.string(), properties: z.record(z.any()) })) }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/${params.objectType}/batch/update` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.inputs }) }) } ) server.tool("crm_batch_archive_objects", "Archive (delete) multiple CRM objects in a single request", { objectType: z.enum(['companies', 'contacts', 'deals', 'tickets', 'products', 'line_items', 'quotes', 'custom']), objectIds: z.array(z.string()), }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/${params.objectType}/batch/archive` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.objectIds.map((id: string) => ({ id })) }) }) } ) // Association Details: https://developers.hubspot.com/docs/reference/api/crm/associations/association-details server.tool("crm_list_association_types", "List all available association types for a given object type pair", { fromObjectType: z.enum(['companies', 'contacts', 'deals', 'tickets', 'products', 'line_items', 'quotes', 'custom']), toObjectType: z.enum(['companies', 'contacts', 'deals', 'tickets', 'products', 'line_items', 'quotes', 'custom']) }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v4/associations/${params.fromObjectType}/${params.toObjectType}/types` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint) }) } ) server.tool("crm_get_associations", "Get all associations of a specific type between objects", { fromObjectType: z.enum(['companies', 'contacts', 'deals', 'tickets', 'products', 'line_items', 'quotes', 'custom']), toObjectType: z.enum(['companies', 'contacts', 'deals', 'tickets', 'products', 'line_items', 'quotes', 'custom']), fromObjectId: z.string(), after: z.string().optional(), limit: z.number().min(1).max(500).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v4/objects/${params.fromObjectType}/${params.fromObjectId}/associations/${params.toObjectType}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, { after: params.after, limit: params.limit }) }) } ) server.tool("crm_create_association", "Create an association between two objects", { fromObjectType: z.enum(['companies', 'contacts', 'deals', 'tickets', 'products', 'line_items', 'quotes', 'custom']), toObjectType: z.enum(['companies', 'contacts', 'deals', 'tickets', 'products', 'line_items', 'quotes', 'custom']), fromObjectId: z.string(), toObjectId: z.string(), associationTypes: z.array(z.object({ associationCategory: z.string(), associationTypeId: z.number() })) }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v4/objects/${params.fromObjectType}/${params.fromObjectId}/associations/${params.toObjectType}/${params.toObjectId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'PUT', { types: params.associationTypes }) }) } ) server.tool("crm_archive_association", "Archive (delete) an association between two objects", { fromObjectType: z.enum(['companies', 'contacts', 'deals', 'tickets', 'products', 'line_items', 'quotes', 'custom']), toObjectType: z.enum(['companies', 'contacts', 'deals', 'tickets', 'products', 'line_items', 'quotes', 'custom']), fromObjectId: z.string(), toObjectId: z.string() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v4/objects/${params.fromObjectType}/${params.fromObjectId}/associations/${params.toObjectType}/${params.toObjectId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'DELETE') }) } ) server.tool("crm_batch_create_associations", "Create multiple associations in a single request", { fromObjectType: z.enum(['companies', 'contacts', 'deals', 'tickets', 'products', 'line_items', 'quotes', 'custom']), toObjectType: z.enum(['companies', 'contacts', 'deals', 'tickets', 'products', 'line_items', 'quotes', 'custom']), inputs: z.array(z.object({ from: z.object({ id: z.string() }), to: z.object({ id: z.string() }), types: z.array(z.object({ associationCategory: z.string(), associationTypeId: z.number() })) })) }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v4/associations/${params.fromObjectType}/${params.toObjectType}/batch/create` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.inputs }) }) } ) server.tool("crm_batch_archive_associations", "Archive (delete) multiple associations in a single request", { fromObjectType: z.enum(['companies', 'contacts', 'deals', 'tickets', 'products', 'line_items', 'quotes', 'custom']), toObjectType: z.enum(['companies', 'contacts', 'deals', 'tickets', 'products', 'line_items', 'quotes', 'custom']), inputs: z.array(z.object({ from: z.object({ id: z.string() }), to: z.object({ id: z.string() }) })) }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v4/associations/${params.fromObjectType}/${params.toObjectType}/batch/archive` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.inputs }) }) } ) // Contacts: https://developers.hubspot.com/docs/reference/api/crm/objects/contacts const contactPropertiesSchema = z.object({ email: z.string().email().optional(), firstname: z.string().optional(), lastname: z.string().optional(), phone: z.string().optional(), mobilephone: z.string().optional(), company: z.string().optional(), jobtitle: z.string().optional(), lifecyclestage: z.enum(['subscriber', 'lead', 'marketingqualifiedlead', 'salesqualifiedlead', 'opportunity', 'customer', 'evangelist', 'other']).optional(), leadstatus: z.enum(['new', 'open', 'inprogress', 'opennotcontacted', 'opencontacted', 'closedconverted', 'closednotconverted']).optional(), address: z.string().optional(), city: z.string().optional(), state: z.string().optional(), zip: z.string().optional(), country: z.string().optional(), website: z.string().url().optional(), twitterhandle: z.string().optional(), facebookfanpage: z.string().optional(), linkedinbio: z.string().optional(), }).catchall(z.any()) server.tool("crm_create_contact", "Create a new contact with validated properties", { properties: contactPropertiesSchema, associations: z.array(z.object({ to: z.object({ id: z.string() }), types: z.array(z.object({ associationCategory: z.string(), associationTypeId: z.number() })) })).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/contacts' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { properties: params.properties, associations: params.associations }) }) } ) server.tool("crm_update_contact", "Update an existing contact with validated properties", { contactId: z.string(), properties: contactPropertiesSchema }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/contacts/${params.contactId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'PATCH', { properties: params.properties }) }) } ) server.tool("crm_get_contact", "Get a single contact by ID with specific properties and associations", { contactId: z.string(), properties: z.array(z.string()).optional(), associations: z.array(z.enum(['companies', 'deals', 'tickets', 'calls', 'emails', 'meetings', 'notes'])).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/contacts/${params.contactId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, { properties: params.properties?.join(','), associations: params.associations?.join(',') }) }) } ) server.tool("crm_search_contacts", "Search contacts with contact-specific filters", { filterGroups: z.array(z.object({ filters: z.array(z.object({ propertyName: z.string(), operator: z.enum(['EQ', 'NEQ', 'LT', 'LTE', 'GT', 'GTE', 'BETWEEN', 'IN', 'NOT_IN', 'HAS_PROPERTY', 'NOT_HAS_PROPERTY', 'CONTAINS_TOKEN', 'NOT_CONTAINS_TOKEN']), value: z.any() })) })), properties: z.array(z.string()).optional(), limit: z.number().min(1).max(100).optional(), after: z.string().optional(), sorts: z.array(z.object({ propertyName: z.string(), direction: z.enum(['ASCENDING', 'DESCENDING']) })).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/contacts/search' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { filterGroups: params.filterGroups, properties: params.properties, limit: params.limit, after: params.after, sorts: params.sorts }) }) } ) server.tool("crm_batch_create_contacts", "Create multiple contacts in a single request", { inputs: z.array(z.object({ properties: contactPropertiesSchema, associations: z.array(z.object({ to: z.object({ id: z.string() }), types: z.array(z.object({ associationCategory: z.string(), associationTypeId: z.number() })) })).optional() })) }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/contacts/batch/create' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.inputs }) }) } ) server.tool("crm_batch_update_contacts", "Update multiple contacts in a single request", { inputs: z.array(z.object({ id: z.string(), properties: contactPropertiesSchema })) }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/contacts/batch/update' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.inputs }) }) } ) server.tool("crm_get_contact_properties", "Get all properties for contacts", { archived: z.boolean().optional(), properties: z.array(z.string()).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/properties/contacts' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, { archived: params.archived, properties: params.properties?.join(',') }) }) } ) server.tool("crm_create_contact_property", "Create a new contact property", { name: z.string(), label: z.string(), type: z.enum(['string', 'number', 'date', 'datetime', 'enumeration', 'bool']), fieldType: z.enum(['text', 'textarea', 'select', 'radio', 'checkbox', 'number', 'date', 'file']), groupName: z.string(), description: z.string().optional(), options: z.array(z.object({ label: z.string(), value: z.string(), description: z.string().optional(), displayOrder: z.number().optional(), hidden: z.boolean().optional() })).optional(), displayOrder: z.number().optional(), hasUniqueValue: z.boolean().optional(), hidden: z.boolean().optional(), formField: z.boolean().optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/properties/contacts' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', params) }) } ) // Leads: https://developers.hubspot.com/docs/reference/api/crm/objects/leads const leadPropertiesSchema = z.object({ email: z.string().email().optional(), firstname: z.string().optional(), lastname: z.string().optional(), phone: z.string().optional(), company: z.string().optional(), jobtitle: z.string().optional(), leadstatus: z.enum(['new', 'open', 'in_progress', 'qualified', 'unqualified', 'converted', 'lost']).optional(), leadsource: z.string().optional(), industry: z.string().optional(), annualrevenue: z.number().optional(), numberofemployees: z.number().optional(), rating: z.enum(['hot', 'warm', 'cold']).optional(), website: z.string().url().optional(), address: z.string().optional(), city: z.string().optional(), state: z.string().optional(), zip: z.string().optional(), country: z.string().optional(), notes: z.string().optional(), }).catchall(z.any()) server.tool("crm_create_lead", "Create a new lead with validated properties", { properties: leadPropertiesSchema, associations: z.array(z.object({ to: z.object({ id: z.string() }), types: z.array(z.object({ associationCategory: z.string(), associationTypeId: z.number() })) })).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/leads' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { properties: params.properties, associations: params.associations }) }) } ) server.tool("crm_update_lead", "Update an existing lead with validated properties", { leadId: z.string(), properties: leadPropertiesSchema }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/leads/${params.leadId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'PATCH', { properties: params.properties }) }) } ) server.tool("crm_get_lead", "Get a single lead by ID with specific properties and associations", { leadId: z.string(), properties: z.array(z.string()).optional(), associations: z.array(z.enum(['companies', 'contacts', 'deals', 'notes', 'tasks'])).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/leads/${params.leadId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, { properties: params.properties?.join(','), associations: params.associations?.join(',') }) }) } ) server.tool("crm_search_leads", "Search leads with lead-specific filters", { filterGroups: z.array(z.object({ filters: z.array(z.object({ propertyName: z.string(), operator: z.enum(['EQ', 'NEQ', 'LT', 'LTE', 'GT', 'GTE', 'BETWEEN', 'IN', 'NOT_IN', 'HAS_PROPERTY', 'NOT_HAS_PROPERTY', 'CONTAINS_TOKEN', 'NOT_CONTAINS_TOKEN']), value: z.any() })) })), properties: z.array(z.string()).optional(), limit: z.number().min(1).max(100).optional(), after: z.string().optional(), sorts: z.array(z.object({ propertyName: z.string(), direction: z.enum(['ASCENDING', 'DESCENDING']) })).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/leads/search' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { filterGroups: params.filterGroups, properties: params.properties, limit: params.limit, after: params.after, sorts: params.sorts }) }) } ) server.tool("crm_batch_create_leads", "Create multiple leads in a single request", { inputs: z.array(z.object({ properties: leadPropertiesSchema, associations: z.array(z.object({ to: z.object({ id: z.string() }), types: z.array(z.object({ associationCategory: z.string(), associationTypeId: z.number() })) })).optional() })) }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/leads/batch/create' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.inputs }) }) } ) server.tool("crm_batch_update_leads", "Update multiple leads in a single request", { inputs: z.array(z.object({ id: z.string(), properties: leadPropertiesSchema })) }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/leads/batch/update' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.inputs }) }) } ) server.tool("crm_get_lead_properties", "Get all properties for leads", { archived: z.boolean().optional(), properties: z.array(z.string()).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/properties/leads' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, { archived: params.archived, properties: params.properties?.join(',') }) }) } ) server.tool("crm_create_lead_property", "Create a new lead property", { name: z.string(), label: z.string(), type: z.enum(['string', 'number', 'date', 'datetime', 'enumeration', 'bool']), fieldType: z.enum(['text', 'textarea', 'select', 'radio', 'checkbox', 'number', 'date', 'file']), groupName: z.string(), description: z.string().optional(), options: z.array(z.object({ label: z.string(), value: z.string(), description: z.string().optional(), displayOrder: z.number().optional(), hidden: z.boolean().optional() })).optional(), displayOrder: z.number().optional(), hasUniqueValue: z.boolean().optional(), hidden: z.boolean().optional(), formField: z.boolean().optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/properties/leads' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', params) }) } ) // Meetings: https://developers.hubspot.com/docs/reference/api/crm/engagements/meetings server.tool("meetings_list", "List all meetings with optional filtering", { after: z.string().optional(), limit: z.number().min(1).max(100).optional(), createdAfter: z.string().optional(), createdBefore: z.string().optional(), properties: z.array(z.string()).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/meetings' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, { after: params.after, limit: params.limit, createdAfter: params.createdAfter, createdBefore: params.createdBefore, properties: params.properties?.join(',') }) }) } ) server.tool("meetings_get", "Get details of a specific meeting", { meetingId: z.string(), properties: z.array(z.string()).optional(), associations: z.array(z.enum(['contacts', 'companies', 'deals'])).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/meetings/${params.meetingId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, { properties: params.properties?.join(','), associations: params.associations?.join(',') }) }) } ) server.tool("meetings_create", "Create a new meeting", { properties: z.object({ hs_timestamp: z.string(), hs_meeting_title: z.string(), hs_meeting_body: z.string().optional(), hs_meeting_location: z.string().optional(), hs_meeting_start_time: z.string(), hs_meeting_end_time: z.string(), hs_meeting_outcome: z.enum(['SCHEDULED', 'COMPLETED', 'CANCELED']).optional(), hubspot_owner_id: z.string().optional() }), associations: z.array(z.object({ to: z.object({ id: z.string() }), types: z.array(z.object({ associationCategory: z.string(), associationTypeId: z.number() })) })).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/meetings' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { properties: params.properties, associations: params.associations }) }) } ) server.tool("meetings_update", "Update an existing meeting", { meetingId: z.string(), properties: z.object({ hs_meeting_title: z.string().optional(), hs_meeting_body: z.string().optional(), hs_meeting_location: z.string().optional(), hs_meeting_start_time: z.string().optional(), hs_meeting_end_time: z.string().optional(), hs_meeting_outcome: z.enum(['SCHEDULED', 'COMPLETED', 'CANCELED']).optional(), hubspot_owner_id: z.string().optional() }) }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/meetings/${params.meetingId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'PATCH', { properties: params.properties }) }) } ) server.tool("meetings_archive", "Archive (delete) a meeting", { meetingId: z.string() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/meetings/${params.meetingId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'DELETE') }) } ) server.tool("meetings_search", "Search meetings with specific filters", { filterGroups: z.array(z.object({ filters: z.array(z.object({ propertyName: z.string(), operator: z.enum(['EQ', 'NEQ', 'LT', 'LTE', 'GT', 'GTE', 'BETWEEN', 'IN', 'NOT_IN', 'HAS_PROPERTY', 'NOT_HAS_PROPERTY', 'CONTAINS_TOKEN', 'NOT_CONTAINS_TOKEN']), value: z.any() })) })), properties: z.array(z.string()).optional(), limit: z.number().min(1).max(100).optional(), after: z.string().optional(), sorts: z.array(z.object({ propertyName: z.string(), direction: z.enum(['ASCENDING', 'DESCENDING']) })).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/meetings/search' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { filterGroups: params.filterGroups, properties: params.properties, limit: params.limit, after: params.after, sorts: params.sorts }) }) } ) server.tool("meetings_batch_create", "Create multiple meetings in a single request", { inputs: z.array(z.object({ properties: z.object({ hs_timestamp: z.string(), hs_meeting_title: z.string(), hs_meeting_body: z.string().optional(), hs_meeting_location: z.string().optional(), hs_meeting_start_time: z.string(), hs_meeting_end_time: z.string(), hs_meeting_outcome: z.enum(['SCHEDULED', 'COMPLETED', 'CANCELED']).optional(), hubspot_owner_id: z.string().optional() }), associations: z.array(z.object({ to: z.object({ id: z.string() }), types: z.array(z.object({ associationCategory: z.string(), associationTypeId: z.number() })) })).optional() })) }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/meetings/batch/create' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.inputs }) }) } ) server.tool("meetings_batch_update", "Update multiple meetings in a single request", { inputs: z.array(z.object({ id: z.string(), properties: z.object({ hs_meeting_title: z.string().optional(), hs_meeting_body: z.string().optional(), hs_meeting_location: z.string().optional(), hs_meeting_start_time: z.string().optional(), hs_meeting_end_time: z.string().optional(), hs_meeting_outcome: z.enum(['SCHEDULED', 'COMPLETED', 'CANCELED']).optional(), hubspot_owner_id: z.string().optional() }) })) }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/meetings/batch/update' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.inputs }) }) } ) server.tool("meetings_batch_archive", "Archive (delete) multiple meetings in a single request", { meetingIds: z.array(z.string()), }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/meetings/batch/archive' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.meetingIds.map((id: string) => ({ id })) }) }) } ) // Notes: https://developers.hubspot.com/docs/reference/api/crm/engagements/notes const notePropertiesSchema = z.object({ hs_note_body: z.string(), hs_timestamp: z.string().optional(), hubspot_owner_id: z.string().optional() }).catchall(z.any()) server.tool("notes_create", "Create a new note", { properties: notePropertiesSchema, associations: z.array(z.object({ to: z.object({ id: z.string() }), types: z.array(z.object({ associationCategory: z.string(), associationTypeId: z.number() })) })).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/notes' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { properties: params.properties, associations: params.associations }) }) } ) server.tool("notes_get", "Get details of a specific note", { noteId: z.string(), properties: z.array(z.string()).optional(), associations: z.array(z.enum(['contacts', 'companies', 'deals', 'tickets'])).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/notes/${params.noteId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, { properties: params.properties?.join(','), associations: params.associations?.join(',') }) }) } ) server.tool("notes_update", "Update an existing note", { noteId: z.string(), properties: notePropertiesSchema }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/notes/${params.noteId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'PATCH', { properties: params.properties }) }) } ) server.tool("notes_archive", "Archive (delete) a note", { noteId: z.string() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/notes/${params.noteId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'DELETE') }) } ) server.tool("notes_list", "List all notes with optional filtering", { limit: z.number().min(1).max(100).optional(), after: z.string().optional(), properties: z.array(z.string()).optional(), associations: z.array(z.enum(['contacts', 'companies', 'deals', 'tickets'])).optional(), archived: z.boolean().optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/notes' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, { limit: params.limit, after: params.after, properties: params.properties?.join(','), associations: params.associations?.join(','), archived: params.archived }) }) } ) server.tool("notes_search", "Search notes with specific filters", { filterGroups: z.array(z.object({ filters: z.array(z.object({ propertyName: z.string(), operator: z.enum(['EQ', 'NEQ', 'LT', 'LTE', 'GT', 'GTE', 'BETWEEN', 'IN', 'NOT_IN', 'HAS_PROPERTY', 'NOT_HAS_PROPERTY', 'CONTAINS_TOKEN', 'NOT_CONTAINS_TOKEN']), value: z.any() })) })), properties: z.array(z.string()).optional(), limit: z.number().min(1).max(100).optional(), after: z.string().optional(), sorts: z.array(z.object({ propertyName: z.string(), direction: z.enum(['ASCENDING', 'DESCENDING']) })).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/notes/search' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { filterGroups: params.filterGroups, properties: params.properties, limit: params.limit, after: params.after, sorts: params.sorts }) }) } ) server.tool("notes_batch_create", "Create multiple notes in a single request", { inputs: z.array(z.object({ properties: notePropertiesSchema, associations: z.array(z.object({ to: z.object({ id: z.string() }), types: z.array(z.object({ associationCategory: z.string(), associationTypeId: z.number() })) })).optional() })) }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/notes/batch/create' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.inputs }) }) } ) server.tool("notes_batch_read", "Read multiple notes in a single request", { inputs: z.array(z.object({ id: z.string(), properties: z.array(z.string()).optional(), associations: z.array(z.enum(['contacts', 'companies', 'deals', 'tickets'])).optional() })) }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/notes/batch/read' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.inputs }) }) } ) server.tool("notes_batch_update", "Update multiple notes in a single request", { inputs: z.array(z.object({ id: z.string(), properties: notePropertiesSchema })) }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/notes/batch/update' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.inputs }) }) } ) server.tool("notes_batch_archive", "Archive (delete) multiple notes in a single request", { noteIds: z.array(z.string()), }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/notes/batch/archive' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.noteIds.map((id: string) => ({ id })) }) }) } ) // Tasks: https://developers.hubspot.com/docs/reference/api/crm/engagements/tasks const taskPropertiesSchema = z.object({ hs_task_body: z.string(), hs_task_priority: z.enum(['HIGH', 'MEDIUM', 'LOW']).optional(), hs_task_status: z.enum(['NOT_STARTED', 'IN_PROGRESS', 'WAITING', 'COMPLETED', 'DEFERRED']).optional(), hs_task_subject: z.string(), hs_task_type: z.string().optional(), hs_timestamp: z.string().optional(), hs_task_due_date: z.string().optional(), hubspot_owner_id: z.string().optional() }).catchall(z.any()) server.tool("tasks_create", "Create a new task", { properties: taskPropertiesSchema, associations: z.array(z.object({ to: z.object({ id: z.string() }), types: z.array(z.object({ associationCategory: z.string(), associationTypeId: z.number() })) })).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/tasks' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { properties: params.properties, associations: params.associations }) }) } ) server.tool("tasks_get", "Get details of a specific task", { taskId: z.string(), properties: z.array(z.string()).optional(), associations: z.array(z.enum(['contacts', 'companies', 'deals', 'tickets'])).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/tasks/${params.taskId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, { properties: params.properties?.join(','), associations: params.associations?.join(',') }) }) } ) server.tool("tasks_update", "Update an existing task", { taskId: z.string(), properties: taskPropertiesSchema }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/tasks/${params.taskId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'PATCH', { properties: params.properties }) }) } ) server.tool("tasks_archive", "Archive (delete) a task", { taskId: z.string() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/tasks/${params.taskId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'DELETE') }) } ) server.tool("tasks_list", "List all tasks with optional filtering", { limit: z.number().min(1).max(100).optional(), after: z.string().optional(), properties: z.array(z.string()).optional(), associations: z.array(z.enum(['contacts', 'companies', 'deals', 'tickets'])).optional(), archived: z.boolean().optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/tasks' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, { limit: params.limit, after: params.after, properties: params.properties?.join(','), associations: params.associations?.join(','), archived: params.archived }) }) } ) server.tool("tasks_search", "Search tasks with specific filters", { filterGroups: z.array(z.object({ filters: z.array(z.object({ propertyName: z.string(), operator: z.enum(['EQ', 'NEQ', 'LT', 'LTE', 'GT', 'GTE', 'BETWEEN', 'IN', 'NOT_IN', 'HAS_PROPERTY', 'NOT_HAS_PROPERTY', 'CONTAINS_TOKEN', 'NOT_CONTAINS_TOKEN']), value: z.any() })) })), properties: z.array(z.string()).optional(), limit: z.number().min(1).max(100).optional(), after: z.string().optional(), sorts: z.array(z.object({ propertyName: z.string(), direction: z.enum(['ASCENDING', 'DESCENDING']) })).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/tasks/search' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { filterGroups: params.filterGroups, properties: params.properties, limit: params.limit, after: params.after, sorts: params.sorts }) }) } ) server.tool("tasks_batch_create", "Create multiple tasks in a single request", { inputs: z.array(z.object({ properties: taskPropertiesSchema, associations: z.array(z.object({ to: z.object({ id: z.string() }), types: z.array(z.object({ associationCategory: z.string(), associationTypeId: z.number() })) })).optional() })) }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/tasks/batch/create' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.inputs }) }) } ) server.tool("tasks_batch_read", "Read multiple tasks in a single request", { inputs: z.array(z.object({ id: z.string(), properties: z.array(z.string()).optional(), associations: z.array(z.enum(['contacts', 'companies', 'deals', 'tickets'])).optional() })) }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/tasks/batch/read' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.inputs }) }) } ) server.tool("tasks_batch_update", "Update multiple tasks in a single request", { inputs: z.array(z.object({ id: z.string(), properties: taskPropertiesSchema })) }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/tasks/batch/update' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.inputs }) }) } ) server.tool("tasks_batch_archive", "Archive (delete) multiple tasks in a single request", { taskIds: z.array(z.string()), }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/tasks/batch/archive' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.taskIds.map((id: string) => ({ id })) }) }) } ) // Engagement Details: https://developers.hubspot.com/docs/reference/api/crm/engagements/engagement-details const engagementDetailsSchema = z.object({ type: z.enum(['EMAIL', 'CALL', 'MEETING', 'TASK', 'NOTE']), title: z.string(), description: z.string().optional(), owner: z.object({ id: z.string(), email: z.string().email() }).optional(), startTime: z.string().optional(), endTime: z.string().optional(), activityType: z.string().optional(), loggedAt: z.string().optional(), status: z.string().optional() }).catchall(z.any()) server.tool("engagement_details_get", "Get details of a specific engagement", { engagementId: z.string() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/engagements/v1/engagements/${params.engagementId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint) }) } ) server.tool("engagement_details_create", "Create a new engagement with details", { engagement: engagementDetailsSchema, associations: z.object({ contactIds: z.array(z.string()).optional(), companyIds: z.array(z.string()).optional(), dealIds: z.array(z.string()).optional(), ownerIds: z.array(z.string()).optional(), ticketIds: z.array(z.string()).optional() }).optional(), metadata: z.record(z.any()).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/engagements/v1/engagements' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { engagement: params.engagement, associations: params.associations, metadata: params.metadata }) }) } ) server.tool("engagement_details_update", "Update an existing engagement's details", { engagementId: z.string(), engagement: engagementDetailsSchema, metadata: z.record(z.any()).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/engagements/v1/engagements/${params.engagementId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'PATCH', { engagement: params.engagement, metadata: params.metadata }) }) } ) server.tool("engagement_details_list", "List all engagements with optional filtering", { limit: z.number().min(1).max(100).optional(), offset: z.number().optional(), startTime: z.string().optional(), endTime: z.string().optional(), activityTypes: z.array(z.string()).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/engagements/v1/engagements/paged' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, { limit: params.limit, offset: params.offset, startTime: params.startTime, endTime: params.endTime, activityTypes: params.activityTypes?.join(',') }) }) } ) server.tool("engagement_details_archive", "Archive (delete) an engagement", { engagementId: z.string() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/engagements/v1/engagements/${params.engagementId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'DELETE') }) } ) server.tool("engagement_details_get_associated", "Get all engagements associated with an object", { objectType: z.enum(['CONTACT', 'COMPANY', 'DEAL', 'TICKET']), objectId: z.string(), startTime: z.string().optional(), endTime: z.string().optional(), activityTypes: z.array(z.string()).optional(), limit: z.number().min(1).max(100).optional(), offset: z.number().optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/engagements/v1/engagements/associated/${params.objectType}/${params.objectId}/paged` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, { startTime: params.startTime, endTime: params.endTime, activityTypes: params.activityTypes?.join(','), limit: params.limit, offset: params.offset }) }) } ) // Calls: https://developers.hubspot.com/docs/reference/api/crm/engagements/calls const callPropertiesSchema = z.object({ hs_call_body: z.string(), hs_call_direction: z.enum(['INBOUND', 'OUTBOUND']).optional(), hs_call_disposition: z.string().optional(), hs_call_duration: z.number().optional(), hs_call_recording_url: z.string().url().optional(), hs_call_status: z.enum(['SCHEDULED', 'COMPLETED', 'CANCELED', 'NO_ANSWER']).optional(), hs_call_title: z.string(), hs_timestamp: z.string().optional(), hubspot_owner_id: z.string().optional() }).catchall(z.any()) server.tool("calls_create", "Create a new call record", { properties: callPropertiesSchema, associations: z.array(z.object({ to: z.object({ id: z.string() }), types: z.array(z.object({ associationCategory: z.string(), associationTypeId: z.number() })) })).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/calls' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { properties: params.properties, associations: params.associations }) }) } ) server.tool("calls_get", "Get details of a specific call", { callId: z.string(), properties: z.array(z.string()).optional(), associations: z.array(z.enum(['contacts', 'companies', 'deals', 'tickets'])).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/calls/${params.callId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, { properties: params.properties?.join(','), associations: params.associations?.join(',') }) }) } ) server.tool("calls_update", "Update an existing call record", { callId: z.string(), properties: callPropertiesSchema }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/calls/${params.callId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'PATCH', { properties: params.properties }) }) } ) server.tool("calls_archive", "Archive (delete) a call record", { callId: z.string() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/calls/${params.callId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'DELETE') }) } ) server.tool("calls_list", "List all calls with optional filtering", { limit: z.number().min(1).max(100).optional(), after: z.string().optional(), properties: z.array(z.string()).optional(), associations: z.array(z.enum(['contacts', 'companies', 'deals', 'tickets'])).optional(), archived: z.boolean().optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/calls' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, { limit: params.limit, after: params.after, properties: params.properties?.join(','), associations: params.associations?.join(','), archived: params.archived }) }) } ) server.tool("calls_search", "Search calls with specific filters", { filterGroups: z.array(z.object({ filters: z.array(z.object({ propertyName: z.string(), operator: z.enum(['EQ', 'NEQ', 'LT', 'LTE', 'GT', 'GTE', 'BETWEEN', 'IN', 'NOT_IN', 'HAS_PROPERTY', 'NOT_HAS_PROPERTY', 'CONTAINS_TOKEN', 'NOT_CONTAINS_TOKEN']), value: z.any() })) })), properties: z.array(z.string()).optional(), limit: z.number().min(1).max(100).optional(), after: z.string().optional(), sorts: z.array(z.object({ propertyName: z.string(), direction: z.enum(['ASCENDING', 'DESCENDING']) })).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/calls/search' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { filterGroups: params.filterGroups, properties: params.properties, limit: params.limit, after: params.after, sorts: params.sorts }) }) } ) server.tool("calls_batch_create", "Create multiple call records in a single request", { inputs: z.array(z.object({ properties: callPropertiesSchema, associations: z.array(z.object({ to: z.object({ id: z.string() }), types: z.array(z.object({ associationCategory: z.string(), associationTypeId: z.number() })) })).optional() })) }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/calls/batch/create' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.inputs }) }) } ) server.tool("calls_batch_read", "Read multiple call records in a single request", { inputs: z.array(z.object({ id: z.string(), properties: z.array(z.string()).optional(), associations: z.array(z.enum(['contacts', 'companies', 'deals', 'tickets'])).optional() })) }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/calls/batch/read' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.inputs }) }) } ) server.tool("calls_batch_update", "Update multiple call records in a single request", { inputs: z.array(z.object({ id: z.string(), properties: callPropertiesSchema })) }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/calls/batch/update' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.inputs }) }) } ) server.tool("calls_batch_archive", "Archive (delete) multiple call records in a single request", { callIds: z.array(z.string()), }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/calls/batch/archive' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.callIds.map((id: string) => ({ id })) }) }) } ) // Email: https://developers.hubspot.com/docs/reference/api/crm/engagements/email const emailPropertiesSchema = z.object({ hs_email_subject: z.string(), hs_email_text: z.string(), hs_email_html: z.string().optional(), hs_email_status: z.enum(['SENT', 'DRAFT', 'SCHEDULED']).optional(), hs_email_direction: z.enum(['INBOUND', 'OUTBOUND']).optional(), hs_timestamp: z.string().optional(), hs_email_headers: z.record(z.string()).optional(), hs_email_from_email: z.string().email(), hs_email_from_firstname: z.string().optional(), hs_email_from_lastname: z.string().optional(), hs_email_to_email: z.string().email(), hs_email_to_firstname: z.string().optional(), hs_email_to_lastname: z.string().optional(), hs_email_cc: z.array(z.string().email()).optional(), hs_email_bcc: z.array(z.string().email()).optional(), hubspot_owner_id: z.string().optional() }).catchall(z.any()) server.tool("emails_create", "Create a new email record", { properties: emailPropertiesSchema, associations: z.array(z.object({ to: z.object({ id: z.string() }), types: z.array(z.object({ associationCategory: z.string(), associationTypeId: z.number() })) })).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/emails' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { properties: params.properties, associations: params.associations }) }) } ) server.tool("emails_get", "Get details of a specific email", { emailId: z.string(), properties: z.array(z.string()).optional(), associations: z.array(z.enum(['contacts', 'companies', 'deals', 'tickets'])).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/emails/${params.emailId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, { properties: params.properties?.join(','), associations: params.associations?.join(',') }) }) } ) server.tool("emails_update", "Update an existing email record", { emailId: z.string(), properties: emailPropertiesSchema }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/emails/${params.emailId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'PATCH', { properties: params.properties }) }) } ) server.tool("emails_archive", "Archive (delete) an email record", { emailId: z.string() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/crm/v3/objects/emails/${params.emailId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'DELETE') }) } ) server.tool("emails_list", "List all emails with optional filtering", { limit: z.number().min(1).max(100).optional(), after: z.string().optional(), properties: z.array(z.string()).optional(), associations: z.array(z.enum(['contacts', 'companies', 'deals', 'tickets'])).optional(), archived: z.boolean().optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/emails' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, { limit: params.limit, after: params.after, properties: params.properties?.join(','), associations: params.associations?.join(','), archived: params.archived }) }) } ) server.tool("emails_search", "Search emails with specific filters", { filterGroups: z.array(z.object({ filters: z.array(z.object({ propertyName: z.string(), operator: z.enum(['EQ', 'NEQ', 'LT', 'LTE', 'GT', 'GTE', 'BETWEEN', 'IN', 'NOT_IN', 'HAS_PROPERTY', 'NOT_HAS_PROPERTY', 'CONTAINS_TOKEN', 'NOT_CONTAINS_TOKEN']), value: z.any() })) })), properties: z.array(z.string()).optional(), limit: z.number().min(1).max(100).optional(), after: z.string().optional(), sorts: z.array(z.object({ propertyName: z.string(), direction: z.enum(['ASCENDING', 'DESCENDING']) })).optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/emails/search' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { filterGroups: params.filterGroups, properties: params.properties, limit: params.limit, after: params.after, sorts: params.sorts }) }) } ) server.tool("emails_batch_create", "Create multiple email records in a single request", { inputs: z.array(z.object({ properties: emailPropertiesSchema, associations: z.array(z.object({ to: z.object({ id: z.string() }), types: z.array(z.object({ associationCategory: z.string(), associationTypeId: z.number() })) })).optional() })) }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/emails/batch/create' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.inputs }) }) } ) server.tool("emails_batch_read", "Read multiple email records in a single request", { inputs: z.array(z.object({ id: z.string(), properties: z.array(z.string()).optional(), associations: z.array(z.enum(['contacts', 'companies', 'deals', 'tickets'])).optional() })) }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/emails/batch/read' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.inputs }) }) } ) server.tool("emails_batch_update", "Update multiple email records in a single request", { inputs: z.array(z.object({ id: z.string(), properties: emailPropertiesSchema })) }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/emails/batch/update' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.inputs }) }) } ) server.tool("emails_batch_archive", "Archive (delete) multiple email records in a single request", { emailIds: z.array(z.string()), }, async (params) => { return handleEndpoint(async () => { const endpoint = '/crm/v3/objects/emails/batch/archive' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.emailIds.map((id: string) => ({ id })) }) }) } ) // Communications: https://developers.hubspot.com/docs/reference/api/crm/engagements/communications const communicationPreferencesSchema = z.object({ subscriptionId: z.string(), status: z.enum(['SUBSCRIBED', 'UNSUBSCRIBED', 'NOT_OPTED']), legalBasis: z.enum(['LEGITIMATE_INTEREST_CLIENT', 'LEGITIMATE_INTEREST_PUB', 'PERFORMANCE_OF_CONTRACT', 'CONSENT_WITH_NOTICE', 'CONSENT_WITH_NOTICE_AND_OPT_OUT']).optional(), legalBasisExplanation: z.string().optional() }).catchall(z.any()) server.tool("communications_get_preferences", "Get communication preferences for a contact", { contactId: z.string(), subscriptionId: z.string().optional() }, async (params) => { return handleEndpoint(async () => { const subscriptionEndpointPath = params.subscriptionId ? `/subscription/${params.subscriptionId}` : '' const endpoint = `/communication-preferences/v3/status/email/${params.contactId}${subscriptionEndpointPath}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint) }) } ) server.tool("communications_update_preferences", "Update communication preferences for a contact", { contactId: z.string(), subscriptionId: z.string(), preferences: communicationPreferencesSchema }, async (params) => { return handleEndpoint(async () => { const endpoint = `/communication-preferences/v3/status/email/${params.contactId}/subscription/${params.subscriptionId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'PUT', params.preferences) }) } ) server.tool("communications_unsubscribe_contact", "Unsubscribe a contact from all email communications", { contactId: z.string(), portalSubscriptionLegalBasis: z.enum(['LEGITIMATE_INTEREST_CLIENT', 'LEGITIMATE_INTEREST_PUB', 'PERFORMANCE_OF_CONTRACT', 'CONSENT_WITH_NOTICE', 'CONSENT_WITH_NOTICE_AND_OPT_OUT']).optional(), portalSubscriptionLegalBasisExplanation: z.string().optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/communication-preferences/v3/unsubscribe/email/${params.contactId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'PUT', { portalSubscriptionLegalBasis: params.portalSubscriptionLegalBasis, portalSubscriptionLegalBasisExplanation: params.portalSubscriptionLegalBasisExplanation }) }) } ) server.tool("communications_subscribe_contact", "Subscribe a contact to all email communications", { contactId: z.string(), portalSubscriptionLegalBasis: z.enum(['LEGITIMATE_INTEREST_CLIENT', 'LEGITIMATE_INTEREST_PUB', 'PERFORMANCE_OF_CONTRACT', 'CONSENT_WITH_NOTICE', 'CONSENT_WITH_NOTICE_AND_OPT_OUT']).optional(), portalSubscriptionLegalBasisExplanation: z.string().optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = `/communication-preferences/v3/subscribe/email/${params.contactId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'PUT', { portalSubscriptionLegalBasis: params.portalSubscriptionLegalBasis, portalSubscriptionLegalBasisExplanation: params.portalSubscriptionLegalBasisExplanation }) }) } ) server.tool("communications_get_subscription_definitions", "Get all subscription definitions for the portal", { archived: z.boolean().optional() }, async (params) => { return handleEndpoint(async () => { const endpoint = '/communication-preferences/v3/definitions' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, { archived: params.archived }) }) } ) server.tool("communications_get_subscription_status", "Get subscription status for multiple contacts", { subscriptionId: z.string(), contactIds: z.array(z.string()) }, async (params) => { return handleEndpoint(async () => { const endpoint = `/communication-preferences/v3/status/email/subscription/${params.subscriptionId}/bulk` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { contactIds: params.contactIds }) }) } ) server.tool("communications_update_subscription_status", "Update subscription status for multiple contacts", { subscriptionId: z.string(), updates: z.array(z.object({ contactId: z.string(), status: z.enum(['SUBSCRIBED', 'UNSUBSCRIBED', 'NOT_OPTED']), legalBasis: z.enum(['LEGITIMATE_INTEREST_CLIENT', 'LEGITIMATE_INTEREST_PUB', 'PERFORMANCE_OF_CONTRACT', 'CONSENT_WITH_NOTICE', 'CONSENT_WITH_NOTICE_AND_OPT_OUT']).optional(), legalBasisExplanation: z.string().optional() })) }, async (params) => { return handleEndpoint(async () => { const endpoint = `/communication-preferences/v3/status/email/subscription/${params.subscriptionId}/bulk` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'PUT', { updates: params.updates }) }) } ) // Products: https://developers.hubspot.com/docs/reference/api/crm/objects/products const productPropertiesSchema = z.object({ name: z.string().optional(), description: z.string().optional(), price: z.number().optional(), sku: z.string().optional(), hs_product_type: z.string().optional(), hs_recurring_billing_period: z.string().optional(), }).catchall(z.any()) server.tool("products_list", "Read a page of products. Control what is returned via the `properties` query param. `after` is the paging cursor token of the last successfully read resource will be returned as the `paging.next.after` JSON property of a paged response containing more results.", { limit: z.number().min(1).optional(), after: z.string().optional(), properties: z.array(z.string()).optional() }, async params => handleEndpoint(async () => { const endpoint = '/crm/v3/objects/products' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, { limit: params.limit, after: params.after, properties: params.properties?.join(',') }) }) ) server.tool("products_read", "Read an Object identified by ID", { productId: z.string(), properties: z.array(z.string()).optional(), associations: z.array(z.string()).optional() }, async params => handleEndpoint(async () => { const endpoint = `/crm/v3/objects/products/${params.productId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, { properties: params.properties?.join(','), associations: params.associations?.join(',') }) }) ) server.tool("products_create", "Create a product with the given properties and return a copy of the object, including the ID.", { properties: productPropertiesSchema }, async params => handleEndpoint(async () => { const endpoint = '/crm/v3/objects/products' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { properties: params.properties }) }) ) server.tool("products_update", "Perform a partial update of an Object identified by ID. Read-only and non-existent properties will result in an error. Properties values can be cleared by passing an empty string.", { productId: z.string(), properties: productPropertiesSchema }, async params => handleEndpoint(async () => { const endpoint = `/crm/v3/objects/products/${params.productId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'PATCH', { properties: params.properties }) }) ) server.tool("products_archive", "Move an Object identified by ID to the recycling bin.", { productId: z.string() }, async params => handleEndpoint(async () => { const endpoint = `/crm/v3/objects/products/${params.productId}` return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'DELETE') }) ) server.tool("products_search", "Search products", { query: z.string().optional(), limit: z.number().min(1).optional(), after: z.string().optional(), sorts: z.array(z.string()).optional(), properties: z.array(z.string()).optional(), filterGroups: z.array(z.object({ filters: z.array(z.object({ propertyName: z.string(), operator: z.enum(['EQ', 'NEQ', 'LT', 'LTE', 'GT', 'GTE', 'BETWEEN', 'IN', 'NOT_IN', 'HAS_PROPERTY', 'NOT_HAS_PROPERTY', 'CONTAINS_TOKEN', 'NOT_CONTAINS_TOKEN']), value: z.any().optional(), values: z.array(z.any()).optional() })) })), }, async params => handleEndpoint(async () => { const endpoint = '/crm/v3/objects/products/search' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { filterGroups: params.filterGroups, properties: params.properties, limit: params.limit, after: params.after, sorts: params.sorts }) }) ) server.tool("products_batch_archive", "Archive (delete) a batch of products by ID", { productIds: z.array(z.string()), }, async params => handleEndpoint(async () => { const endpoint = '/crm/v3/objects/products/batch/archive' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.productIds.map((id: string) => ({ id })) }) }) ) server.tool("products_batch_create", "Create a batch of products", { inputs: z.array(z.object({ properties: productPropertiesSchema })) }, async params => handleEndpoint(async () => { const endpoint = '/crm/v3/objects/products/batch/create' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.inputs }) }) ) server.tool("products_batch_read", "Read a batch of products by internal ID, or unique property values. Retrieve records by the `idProperty` parameter to retrieve records by a custom unique value property.", { propertiesWithHistory: z.array(z.string()), idProperty: z.string().optional(), productIds: z.array(z.string()), properties: z.array(z.string()) }, async params => handleEndpoint(async () => { const endpoint = '/crm/v3/objects/products/batch/read' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.productIds.map((id: string) => ({ id })) }) }) ) server.tool("products_batch_update", "Update a batch of products by internal ID, or unique values specified by the `idProperty` query param.", { inputs: z.array(z.object({ id: z.string(), idProperty: z.string().optional(), objectWriteTraceId: z.string().optional(), properties: productPropertiesSchema })) }, async params => handleEndpoint(async () => { const endpoint = '/crm/v3/objects/products/batch/update' return await makeApiRequestWithErrorHandling(hubspotAccessToken, endpoint, {}, 'POST', { inputs: params.inputs }) }) ) return server.server } // Stdio Server const stdioServer = createServer({}) const transport = new StdioServerTransport() await stdioServer.connect(transport) // Streamable HTTP Server const { app } = createStatefulServer(createServer) const PORT = process.env.PORT || 3000 app.listen(PORT)

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/shinzo-labs/hubspot-mcp'

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