# Directory Structure
```
├── .gitignore
├── Dockerfile
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── src
│ ├── hubspot-client.ts
│ └── index.ts
├── tsconfig.json
└── yarn.lock
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# Dependency directories
node_modules/
# Build output
dist/
# Environment variables
.env
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# HubSpot MCP Server
[](https://www.typescriptlang.org/)
[](https://developers.hubspot.com/docs/api/overview)
[](https://github.com/modelcontextprotocol/sdk)
[](https://opensource.org/licenses/MIT)
A powerful Model Context Protocol (MCP) server implementation for seamless HubSpot CRM integration, enabling AI assistants to interact with your HubSpot data.
## Overview
This MCP server provides a comprehensive set of tools for interacting with the HubSpot CRM API, allowing AI assistants to:
- Create and manage contacts and companies in your HubSpot CRM
- Retrieve detailed company activity history and engagement timelines
- Access recent engagement data across your entire HubSpot instance
- Get lists of recently active companies and contacts
- Perform CRM operations without leaving your AI assistant interface
## Why Use This MCP Server?
- **Seamless AI Integration**: Connect your AI assistants directly to your HubSpot CRM data
- **Simplified CRM Operations**: Perform common HubSpot tasks through natural language commands
- **Real-time Data Access**: Get up-to-date information from your HubSpot instance
- **Secure Authentication**: Uses HubSpot's secure API token authentication
- **Extensible Design**: Easily add more HubSpot API capabilities as needed
## Installation
```bash
# Clone the repository
git clone https://github.com/lkm1developer/hubspot-mcp-server.git
cd hubspot-mcp-server
# Install dependencies
npm install
# Build the project
npm run build
```
## Configuration
The server requires a HubSpot API access token. You can obtain one by:
1. Going to your [HubSpot Developer Account](https://developers.hubspot.com/)
2. Creating a private app with the necessary scopes (contacts, companies, engagements)
3. Copying the generated access token
You can provide the token in two ways:
1. As an environment variable:
```
HUBSPOT_ACCESS_TOKEN=your-access-token
```
2. As a command-line argument:
```
npm start -- --access-token=your-access-token
```
For development, create a `.env` file in the project root to store your environment variables:
```
HUBSPOT_ACCESS_TOKEN=your-access-token
```
## Usage
### Starting the Server
```bash
# Start the server
npm start
# Or with a specific access token
npm start -- --access-token=your-access-token
# Run the SSE server with authentication
npx mcp-proxy-auth node dist/index.js
```
### Implementing Authentication in SSE Server
The SSE server uses the [mcp-proxy-auth](https://www.npmjs.com/package/mcp-proxy-auth) package for authentication. To implement authentication:
1. Install the package:
```bash
npm install mcp-proxy-auth
```
2. Set the `AUTH_SERVER_URL` environment variable to point to your API key verification endpoint:
```bash
export AUTH_SERVER_URL=https://your-auth-server.com/verify
```
3. Run the SSE server with authentication:
```bash
npx mcp-proxy-auth node dist/index.js
```
4. The SSE URL will be available at:
```
localhost:8080/sse?apiKey=apikey
```
Replace `apikey` with your actual API key for authentication.
The `mcp-proxy-auth` package acts as a proxy that:
- Intercepts requests to your SSE server
- Verifies API keys against your authentication server
- Only allows authenticated requests to reach your SSE endpoint
### Integrating with AI Assistants
This MCP server is designed to work with AI assistants that support the Model Context Protocol. Once running, the server exposes a set of tools that can be used by compatible AI assistants to interact with your HubSpot CRM data.
### Available Tools
The server exposes the following powerful HubSpot integration tools:
1. **hubspot_create_contact**
- Create a new contact in HubSpot with duplicate checking
- Parameters:
- `firstname` (string, required): Contact's first name
- `lastname` (string, required): Contact's last name
- `email` (string, optional): Contact's email address
- `properties` (object, optional): Additional contact properties like company, phone, etc.
- Example:
```json
{
"firstname": "John",
"lastname": "Doe",
"email": "[email protected]",
"properties": {
"company": "Acme Inc",
"phone": "555-123-4567",
"jobtitle": "Software Engineer"
}
}
```
2. **hubspot_create_company**
- Create a new company in HubSpot with duplicate checking
- Parameters:
- `name` (string, required): Company name
- `properties` (object, optional): Additional company properties
- Example:
```json
{
"name": "Acme Corporation",
"properties": {
"domain": "acme.com",
"industry": "Technology",
"phone": "555-987-6543",
"city": "San Francisco",
"state": "CA"
}
}
```
3. **hubspot_get_company_activity**
- Get comprehensive activity history for a specific company
- Parameters:
- `company_id` (string, required): HubSpot company ID
- Returns detailed engagement data including emails, calls, meetings, notes, and tasks
4. **hubspot_get_recent_engagements**
- Get recent engagement activities across all contacts and companies
- Parameters:
- `days` (number, optional, default: 7): Number of days to look back
- `limit` (number, optional, default: 50): Maximum number of engagements to return
- Returns a chronological list of all recent CRM activities
5. **hubspot_get_active_companies**
- Get most recently active companies from HubSpot
- Parameters:
- `limit` (number, optional, default: 10): Maximum number of companies to return
- Returns companies sorted by last modified date
6. **hubspot_get_active_contacts**
- Get most recently active contacts from HubSpot
- Parameters:
- `limit` (number, optional, default: 10): Maximum number of contacts to return
- Returns contacts sorted by last modified date
7. **hubspot_update_contact**
- Update an existing contact in HubSpot (ignores if contact does not exist)
- Parameters:
- `contact_id` (string, required): HubSpot contact ID to update
- `properties` (object, required): Contact properties to update
- Example:
```json
{
"contact_id": "12345",
"properties": {
"email": "[email protected]",
"phone": "555-987-6543",
"jobtitle": "Senior Software Engineer"
}
}
```
8. **hubspot_update_company**
- Update an existing company in HubSpot (ignores if company does not exist)
- Parameters:
- `company_id` (string, required): HubSpot company ID to update
- `properties` (object, required): Company properties to update
- Example:
```json
{
"company_id": "67890",
"properties": {
"domain": "updated-domain.com",
"phone": "555-123-4567",
"industry": "Software",
"city": "New York",
"state": "NY"
}
}
```
## Extending the Server
The server is designed to be easily extensible. To add new HubSpot API capabilities:
1. Add new methods to the `HubSpotClient` class in `src/hubspot-client.ts`
2. Register new tools in the `setupToolHandlers` method in `src/index.ts`
3. Rebuild the project with `npm run build`
## License
This project is licensed under the MIT License - see the LICENSE file for details.
## Keywords
HubSpot, CRM, Model Context Protocol, MCP, AI Assistant, TypeScript, API Integration, HubSpot API, CRM Integration, Contact Management, Company Management, Engagement Tracking, AI Tools
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"strict": true,
"outDir": "dist",
"sourceMap": true,
"declaration": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
```
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM node:22.12-alpine AS builder
COPY . /app
WORKDIR /app
RUN npm install
FROM node:22-alpine AS release
WORKDIR /app
COPY --from=builder /app/build /app/build
COPY --from=builder /app/package.json /app/package.json
COPY --from=builder /app/package-lock.json /app/package-lock.json
ENV NODE_ENV=production
RUN npm ci --ignore-scripts --omit-dev
EXPOSE 8080
ENTRYPOINT ["npx", "mcp-proxy", "node", "/app/dist/index.js"]
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "hubspot-mcp-server",
"version": "0.1.0",
"description": "A powerful Model Context Protocol (MCP) server implementation for seamless HubSpot CRM integration, enabling AI assistants to interact with your HubSpot data",
"main": "dist/index.js",
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "tsx --watch src/index.ts",
"stdio": "node dist/index.js"
},
"keywords": [
"mcp",
"hubspot",
"crm",
"model-context-protocol",
"ai-assistant",
"hubspot-api",
"hubspot-integration",
"crm-integration",
"typescript",
"contact-management",
"company-management",
"engagement-tracking",
"ai-tools"
],
"author": "lakhvinder singh",
"license": "MIT",
"dependencies": {
"@hubspot/api-client": "^12.0.1",
"@modelcontextprotocol/sdk": "^1.8.0",
"dotenv": "^16.4.7",
"mcp-proxy-auth": "^1.0.1",
"zod": "^3.24.2"
},
"devDependencies": {
"@types/node": "^20.10.5",
"tsx": "^4.7.0",
"typescript": "^5.3.3"
}
}
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
Tool
} from '@modelcontextprotocol/sdk/types.js';
import { HubSpotClient } from './hubspot-client.js';
import dotenv from 'dotenv';
import { parseArgs } from 'node:util';
// Load environment variables
dotenv.config();
// Parse command line arguments
const { values } = parseArgs({
options: {
'access-token': { type: 'string' }
}
});
// Initialize HubSpot client
const accessToken = values['access-token'] || process.env.HUBSPOT_ACCESS_TOKEN;
if (!accessToken) {
throw new Error('HUBSPOT_ACCESS_TOKEN environment variable is required');
}
class HubSpotServer {
// Core server properties
private server: Server;
private hubspot: HubSpotClient;
constructor() {
this.server = new Server(
{
name: 'hubspot-manager',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.hubspot = new HubSpotClient(accessToken);
this.setupToolHandlers();
this.setupErrorHandling();
}
private setupErrorHandling(): void {
this.server.onerror = (error) => {
console.error('[MCP Error]', error);
};
process.on('SIGINT', async () => {
await this.server.close();
process.exit(0);
});
process.on('uncaughtException', (error) => {
console.error('Uncaught exception:', error);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled rejection at:', promise, 'reason:', reason);
});
}
private setupToolHandlers(): void {
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
// Define available tools
const tools: Tool[] = [
{
name: 'hubspot_create_contact',
description: 'Create a new contact in HubSpot',
inputSchema: {
type: 'object',
properties: {
firstname: {
type: 'string',
description: "Contact's first name"
},
lastname: {
type: 'string',
description: "Contact's last name"
},
email: {
type: 'string',
description: "Contact's email address"
},
properties: {
type: 'object',
description: 'Additional contact properties',
additionalProperties: true
}
},
required: ['firstname', 'lastname']
}
},
{
name: 'hubspot_create_company',
description: 'Create a new company in HubSpot',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Company name'
},
properties: {
type: 'object',
description: 'Additional company properties',
additionalProperties: true
}
},
required: ['name']
}
},
{
name: 'hubspot_get_company_activity',
description: 'Get activity history for a specific company',
inputSchema: {
type: 'object',
properties: {
company_id: {
type: 'string',
description: 'HubSpot company ID'
}
},
required: ['company_id']
}
},
{
name: 'hubspot_get_recent_engagements',
description: 'Get recent engagement activities across all contacts and companies',
inputSchema: {
type: 'object',
properties: {
days: {
type: 'number',
description: 'Number of days to look back (default: 7)',
default: 7
},
limit: {
type: 'number',
description: 'Maximum number of engagements to return (default: 50)',
default: 50
}
}
}
},
{
name: 'hubspot_get_active_companies',
description: 'Get most recently active companies from HubSpot',
inputSchema: {
type: 'object',
properties: {
limit: {
type: 'number',
description: 'Maximum number of companies to return (default: 10)',
default: 10
}
}
}
},
{
name: 'hubspot_get_active_contacts',
description: 'Get most recently active contacts from HubSpot',
inputSchema: {
type: 'object',
properties: {
limit: {
type: 'number',
description: 'Maximum number of contacts to return (default: 10)',
default: 10
}
}
}
},
{
name: 'hubspot_update_contact',
description: 'Update an existing contact in HubSpot (ignores if contact does not exist)',
inputSchema: {
type: 'object',
properties: {
contact_id: {
type: 'string',
description: 'HubSpot contact ID to update'
},
properties: {
type: 'object',
description: 'Contact properties to update',
additionalProperties: true
}
},
required: ['contact_id', 'properties']
}
},
{
name: 'hubspot_update_company',
description: 'Update an existing company in HubSpot (ignores if company does not exist)',
inputSchema: {
type: 'object',
properties: {
company_id: {
type: 'string',
description: 'HubSpot company ID to update'
},
properties: {
type: 'object',
description: 'Company properties to update',
additionalProperties: true
}
},
required: ['company_id', 'properties']
}
}
];
return { tools };
});
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
const args = request.params.arguments ?? {};
switch (request.params.name) {
case 'hubspot_create_contact': {
const result = await this.hubspot.createContact(
args.firstname as string,
args.lastname as string,
args.email as string | undefined,
args.properties as Record<string, any> | undefined
);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
case 'hubspot_create_company': {
const result = await this.hubspot.createCompany(
args.name as string,
args.properties as Record<string, any> | undefined
);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
case 'hubspot_get_company_activity': {
const result = await this.hubspot.getCompanyActivity(args.company_id as string);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
case 'hubspot_get_recent_engagements': {
const result = await this.hubspot.getRecentEngagements(
args.days as number | undefined,
args.limit as number | undefined
);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
case 'hubspot_get_active_companies': {
const result = await this.hubspot.getRecentCompanies(args.limit as number | undefined);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
case 'hubspot_get_active_contacts': {
const result = await this.hubspot.getRecentContacts(args.limit as number | undefined);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
case 'hubspot_update_contact': {
const result = await this.hubspot.updateContact(
args.contact_id as string,
args.properties as Record<string, any>
);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
case 'hubspot_update_company': {
const result = await this.hubspot.updateCompany(
args.company_id as string,
args.properties as Record<string, any>
);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
default:
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
);
}
} catch (error: any) {
console.error(`Error executing tool ${request.params.name}:`, error);
return {
content: [{
type: 'text',
text: `HubSpot API error: ${error.message}`
}],
isError: true,
};
}
});
}
async run(): Promise<void> {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.log('HubSpot MCP server started');
}
}
export async function serve(): Promise<void> {
const client = new HubSpotServer();
await client.run();
}
const server = new HubSpotServer();
server.run().catch(console.error);
```
--------------------------------------------------------------------------------
/src/hubspot-client.ts:
--------------------------------------------------------------------------------
```typescript
import { Client } from '@hubspot/api-client';
import dotenv from 'dotenv';
dotenv.config();
// Convert any datetime objects to ISO strings
export function convertDatetimeFields(obj: any): any {
if (obj === null || obj === undefined) {
return obj;
}
if (typeof obj === 'object') {
if (obj instanceof Date) {
return obj.toISOString();
}
if (Array.isArray(obj)) {
return obj.map(item => convertDatetimeFields(item));
}
const result: Record<string, any> = {};
for (const [key, value] of Object.entries(obj)) {
result[key] = convertDatetimeFields(value);
}
return result;
}
return obj;
}
export class HubSpotClient {
private client: Client;
constructor(accessToken?: string) {
const token = accessToken || process.env.HUBSPOT_ACCESS_TOKEN;
if (!token) {
throw new Error('HUBSPOT_ACCESS_TOKEN environment variable is required');
}
this.client = new Client({ accessToken: token });
}
async getRecentCompanies(limit: number = 10): Promise<any> {
try {
// Create search request with sort by lastmodifieddate
const searchRequest = {
sorts: ['lastmodifieddate:desc'],
limit,
properties: ['name', 'domain', 'website', 'phone', 'industry', 'hs_lastmodifieddate']
};
// Execute the search
const searchResponse = await this.client.crm.companies.searchApi.doSearch(searchRequest);
// Convert the response to a dictionary
const companiesDict = searchResponse.results;
return convertDatetimeFields(companiesDict);
} catch (error: any) {
console.error('Error getting recent companies:', error);
return { error: error.message };
}
}
async getRecentContacts(limit: number = 10): Promise<any> {
try {
// Create search request with sort by lastmodifieddate
const searchRequest = {
sorts: ['lastmodifieddate:desc'],
limit,
properties: ['firstname', 'lastname', 'email', 'phone', 'company', 'hs_lastmodifieddate', 'lastmodifieddate']
};
// Execute the search
const searchResponse = await this.client.crm.contacts.searchApi.doSearch(searchRequest);
// Convert the response to a dictionary
const contactsDict = searchResponse.results;
return convertDatetimeFields(contactsDict);
} catch (error: any) {
console.error('Error getting recent contacts:', error);
return { error: error.message };
}
}
async getCompanyActivity(companyId: string): Promise<any> {
try {
// Step 1: Get all engagement IDs associated with the company using CRM Associations v4 API
const associatedEngagements = await this.client.crm.associations.v4.basicApi.getPage(
'companies',
companyId,
'engagements',
undefined,
500
);
// Extract engagement IDs from the associations response
const engagementIds: string[] = [];
if (associatedEngagements.results) {
for (const result of associatedEngagements.results) {
engagementIds.push(result.toObjectId);
}
}
// Step 2: Get detailed information for each engagement
const activities = [];
for (const engagementId of engagementIds) {
const engagementResponse = await this.client.apiRequest({
method: 'GET',
path: `/engagements/v1/engagements/${engagementId}`
});
// Ensure we have a proper response body
const responseBody = engagementResponse.body as any;
const engagementData = responseBody.engagement || {};
const metadata = responseBody.metadata || {};
// Format the engagement
const formattedEngagement: Record<string, any> = {
id: engagementData.id,
type: engagementData.type,
created_at: engagementData.createdAt,
last_updated: engagementData.lastUpdated,
created_by: engagementData.createdBy,
modified_by: engagementData.modifiedBy,
timestamp: engagementData.timestamp,
associations: (engagementResponse.body as any).associations || {}
};
// Add type-specific metadata formatting
if (engagementData.type === 'NOTE') {
formattedEngagement.content = metadata.body || '';
} else if (engagementData.type === 'EMAIL') {
formattedEngagement.content = {
subject: metadata.subject || '',
from: {
raw: metadata.from?.raw || '',
email: metadata.from?.email || '',
firstName: metadata.from?.firstName || '',
lastName: metadata.from?.lastName || ''
},
to: (metadata.to || []).map((recipient: any) => ({
raw: recipient.raw || '',
email: recipient.email || '',
firstName: recipient.firstName || '',
lastName: recipient.lastName || ''
})),
cc: (metadata.cc || []).map((recipient: any) => ({
raw: recipient.raw || '',
email: recipient.email || '',
firstName: recipient.firstName || '',
lastName: recipient.lastName || ''
})),
bcc: (metadata.bcc || []).map((recipient: any) => ({
raw: recipient.raw || '',
email: recipient.email || '',
firstName: recipient.firstName || '',
lastName: recipient.lastName || ''
})),
sender: {
email: metadata.sender?.email || ''
},
body: metadata.text || metadata.html || ''
};
} else if (engagementData.type === 'TASK') {
formattedEngagement.content = {
subject: metadata.subject || '',
body: metadata.body || '',
status: metadata.status || '',
for_object_type: metadata.forObjectType || ''
};
} else if (engagementData.type === 'MEETING') {
formattedEngagement.content = {
title: metadata.title || '',
body: metadata.body || '',
start_time: metadata.startTime,
end_time: metadata.endTime,
internal_notes: metadata.internalMeetingNotes || ''
};
} else if (engagementData.type === 'CALL') {
formattedEngagement.content = {
body: metadata.body || '',
from_number: metadata.fromNumber || '',
to_number: metadata.toNumber || '',
duration_ms: metadata.durationMilliseconds,
status: metadata.status || '',
disposition: metadata.disposition || ''
};
}
activities.push(formattedEngagement);
}
// Convert any datetime fields and return
return convertDatetimeFields(activities);
} catch (error: any) {
console.error('Error getting company activity:', error);
return { error: error.message };
}
}
async getRecentEngagements(days: number = 7, limit: number = 50): Promise<any> {
try {
// Calculate the date range (past N days)
const endTime = new Date();
const startTime = new Date(endTime);
startTime.setDate(startTime.getDate() - days);
// Format timestamps for API call
const startTimestamp = Math.floor(startTime.getTime());
const endTimestamp = Math.floor(endTime.getTime());
// Get all recent engagements
const engagementsResponse = await this.client.apiRequest({
method: 'GET',
path: '/engagements/v1/engagements/recent/modified',
qs: {
count: limit,
since: startTimestamp,
offset: 0
}
});
// Format the engagements similar to company_activity
const formattedEngagements = [];
// Ensure we have a proper response body
const responseBody = engagementsResponse.body as any;
for (const engagement of responseBody.results || []) {
const engagementData = engagement.engagement || {};
const metadata = engagement.metadata || {};
const formattedEngagement: Record<string, any> = {
id: engagementData.id,
type: engagementData.type,
created_at: engagementData.createdAt,
last_updated: engagementData.lastUpdated,
created_by: engagementData.createdBy,
modified_by: engagementData.modifiedBy,
timestamp: engagementData.timestamp,
associations: engagement.associations || {}
};
// Add type-specific metadata formatting identical to company_activity
if (engagementData.type === 'NOTE') {
formattedEngagement.content = metadata.body || '';
} else if (engagementData.type === 'EMAIL') {
formattedEngagement.content = {
subject: metadata.subject || '',
from: {
raw: metadata.from?.raw || '',
email: metadata.from?.email || '',
firstName: metadata.from?.firstName || '',
lastName: metadata.from?.lastName || ''
},
to: (metadata.to || []).map((recipient: any) => ({
raw: recipient.raw || '',
email: recipient.email || '',
firstName: recipient.firstName || '',
lastName: recipient.lastName || ''
})),
cc: (metadata.cc || []).map((recipient: any) => ({
raw: recipient.raw || '',
email: recipient.email || '',
firstName: recipient.firstName || '',
lastName: recipient.lastName || ''
})),
bcc: (metadata.bcc || []).map((recipient: any) => ({
raw: recipient.raw || '',
email: recipient.email || '',
firstName: recipient.firstName || '',
lastName: recipient.lastName || ''
})),
sender: {
email: metadata.sender?.email || ''
},
body: metadata.text || metadata.html || ''
};
} else if (engagementData.type === 'TASK') {
formattedEngagement.content = {
subject: metadata.subject || '',
body: metadata.body || '',
status: metadata.status || '',
for_object_type: metadata.forObjectType || ''
};
} else if (engagementData.type === 'MEETING') {
formattedEngagement.content = {
title: metadata.title || '',
body: metadata.body || '',
start_time: metadata.startTime,
end_time: metadata.endTime,
internal_notes: metadata.internalMeetingNotes || ''
};
} else if (engagementData.type === 'CALL') {
formattedEngagement.content = {
body: metadata.body || '',
from_number: metadata.fromNumber || '',
to_number: metadata.toNumber || '',
duration_ms: metadata.durationMilliseconds,
status: metadata.status || '',
disposition: metadata.disposition || ''
};
}
formattedEngagements.push(formattedEngagement);
}
// Convert any datetime fields and return
return convertDatetimeFields(formattedEngagements);
} catch (error: any) {
console.error('Error getting recent engagements:', error);
return { error: error.message };
}
}
async createContact(
firstname: string,
lastname: string,
email?: string,
properties?: Record<string, any>
): Promise<any> {
try {
// Search for existing contacts with same name and company
const company = properties?.company;
// Use type assertion to satisfy the HubSpot API client types
const searchRequest = {
filterGroups: [{
filters: [
{
propertyName: 'firstname',
operator: 'EQ',
value: firstname
} as any,
{
propertyName: 'lastname',
operator: 'EQ',
value: lastname
} as any
]
}]
} as any;
// Add company filter if provided
if (company) {
searchRequest.filterGroups[0].filters.push({
propertyName: 'company',
operator: 'EQ',
value: company
} as any);
}
const searchResponse = await this.client.crm.contacts.searchApi.doSearch(searchRequest);
if (searchResponse.total > 0) {
// Contact already exists
return {
message: 'Contact already exists',
contact: searchResponse.results[0]
};
}
// If no existing contact found, proceed with creation
const contactProperties: Record<string, any> = {
firstname,
lastname
};
// Add email if provided
if (email) {
contactProperties.email = email;
}
// Add any additional properties
if (properties) {
Object.assign(contactProperties, properties);
}
// Create contact
const apiResponse = await this.client.crm.contacts.basicApi.create({
properties: contactProperties
});
return apiResponse;
} catch (error: any) {
console.error('Error creating contact:', error);
throw new Error(`HubSpot API error: ${error.message}`);
}
}
async createCompany(name: string, properties?: Record<string, any>): Promise<any> {
try {
// Search for existing companies with same name
// Use type assertion to satisfy the HubSpot API client types
const searchRequest = {
filterGroups: [{
filters: [
{
propertyName: 'name',
operator: 'EQ',
value: name
} as any
]
}]
} as any;
const searchResponse = await this.client.crm.companies.searchApi.doSearch(searchRequest);
if (searchResponse.total > 0) {
// Company already exists
return {
message: 'Company already exists',
company: searchResponse.results[0]
};
}
// If no existing company found, proceed with creation
const companyProperties: Record<string, any> = {
name
};
// Add any additional properties
if (properties) {
Object.assign(companyProperties, properties);
}
// Create company
const apiResponse = await this.client.crm.companies.basicApi.create({
properties: companyProperties
});
return apiResponse;
} catch (error: any) {
console.error('Error creating company:', error);
throw new Error(`HubSpot API error: ${error.message}`);
}
}
async updateContact(
contactId: string,
properties: Record<string, any>
): Promise<any> {
try {
// Check if contact exists
try {
await this.client.crm.contacts.basicApi.getById(contactId);
} catch (error: any) {
// If contact doesn't exist, return a message
if (error.statusCode === 404) {
return {
message: 'Contact not found, no update performed',
contactId
};
}
// For other errors, throw them to be caught by the outer try/catch
throw error;
}
// Update the contact
const apiResponse = await this.client.crm.contacts.basicApi.update(contactId, {
properties
});
return {
message: 'Contact updated successfully',
contactId,
properties
};
} catch (error: any) {
console.error('Error updating contact:', error);
throw new Error(`HubSpot API error: ${error.message}`);
}
}
async updateCompany(
companyId: string,
properties: Record<string, any>
): Promise<any> {
try {
// Check if company exists
try {
await this.client.crm.companies.basicApi.getById(companyId);
} catch (error: any) {
// If company doesn't exist, return a message
if (error.statusCode === 404) {
return {
message: 'Company not found, no update performed',
companyId
};
}
// For other errors, throw them to be caught by the outer try/catch
throw error;
}
// Update the company
const apiResponse = await this.client.crm.companies.basicApi.update(companyId, {
properties
});
return {
message: 'Company updated successfully',
companyId,
properties
};
} catch (error: any) {
console.error('Error updating company:', error);
throw new Error(`HubSpot API error: ${error.message}`);
}
}
}
```