#
tokens: 8131/50000 7/7 files
lines: off (toggle) GitHub
raw markdown copy
# 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

[![TypeScript](https://img.shields.io/badge/TypeScript-4.9.5-blue.svg)](https://www.typescriptlang.org/)
[![HubSpot API](https://img.shields.io/badge/HubSpot%20API-v3-orange.svg)](https://developers.hubspot.com/docs/api/overview)
[![MCP SDK](https://img.shields.io/badge/MCP%20SDK-1.8.0-green.svg)](https://github.com/modelcontextprotocol/sdk)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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}`);
    }
  }
}

```