Skip to main content
Glama
ajaystream

HubSpot MCP Server

by ajaystream

hubspot-create-engagement

Create HubSpot notes or tasks to track interactions outside the CRM, associating them with contacts, companies, deals, or tickets for updated activity reporting.

Instructions

🛡️ Guardrails:
  1. Data Modification Warning: This tool modifies HubSpot data. Only use when the user has explicitly requested to update their CRM.

🎯 Purpose:
  1. Creates a HubSpot engagement (Note or Task) associated with contacts, companies, deals, or tickets.
  2. This endpoint is useful for keeping your CRM records up-to-date on any interactions that take place outside of HubSpot.
  3. Activity reporting in the CRM also feeds off of this data.

📋 Prerequisites:
  1. Use the hubspot-get-user-details tool to get the OwnerId and UserId.

🧭 Usage Guidance:
  1. Use NOTE type for adding notes to records
  2. Use TASK type for creating tasks with subject, status, and assignment
  3. Both require relevant associations to connect them to CRM records
  4. Other types of engagements (EMAIL, CALL, MEETING) are NOT supported yet.
  5. HubSpot notes and task descriptions support HTML formatting. However headings (<h1>, <h2>, etc.) look ugly in the CRM. So use them sparingly.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
typeYesThe type of engagement to create (NOTE or TASK)
ownerIdYesThe ID of the owner of this engagement
timestampNoTimestamp for the engagement (milliseconds since epoch). Defaults to current time if not provided.
associationsYesAssociated records for this engagement
metadataYesMetadata specific to the engagement type

Implementation Reference

  • The 'process' method in CreateEngagementTool class that handles the core logic: destructures input args, builds the engagement request body, calls the HubSpot API endpoint '/engagements/v1/engagements', and formats success/error responses.
    async process(args) {
        try {
            const { type, ownerId, timestamp, associations, metadata } = args;
            const engagementTimestamp = timestamp || Date.now();
            const requestBody = {
                engagement: {
                    active: true,
                    ownerId,
                    type,
                    timestamp: engagementTimestamp,
                },
                associations,
                metadata,
            };
            const response = await this.client.post('/engagements/v1/engagements', {
                body: requestBody,
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify({
                            status: 'success',
                            engagement: response,
                            message: `Successfully created ${type.toLowerCase()} engagement`,
                        }, null, 2),
                    },
                ],
            };
        }
        catch (error) {
            return {
                content: [
                    {
                        type: 'text',
                        text: `Error creating HubSpot engagement: ${error instanceof Error ? error.message : String(error)}`,
                    },
                ],
                isError: true,
            };
        }
    }
  • Zod schemas defining the input structure: ENGAGEMENT_TYPES, NoteMetadataSchema, TaskMetadataSchema, AssociationsSchema, metadataSchemas map, and the main CreateEngagementSchema with superRefine for type-specific metadata validation.
    const NoteMetadataSchema = z.object({
        body: z.string().describe('The content of the note'),
    });
    const TaskMetadataSchema = z.object({
        body: z.string().describe('The body/description of the task'),
        subject: z.string().describe('The title/subject of the task'),
        status: z.enum(['NOT_STARTED', 'IN_PROGRESS', 'COMPLETED', 'WAITING']).default('NOT_STARTED'),
        forObjectType: z.enum(['CONTACT', 'COMPANY', 'DEAL', 'TICKET']).default('CONTACT'),
    });
    export const AssociationsSchema = z.object({
        contactIds: z.array(z.number().int()).optional().default([]),
        companyIds: z.array(z.number().int()).optional().default([]),
        dealIds: z.array(z.number().int()).optional().default([]),
        ownerIds: z.array(z.number().int()).optional().default([]),
        ticketIds: z.array(z.number().int()).optional().default([]),
    });
    // Map engagement types to their metadata schemas
    const metadataSchemas = {
        NOTE: NoteMetadataSchema,
        TASK: TaskMetadataSchema,
    };
    const CreateEngagementSchema = z
        .object({
        type: z.enum(ENGAGEMENT_TYPES).describe('The type of engagement to create (NOTE or TASK)'),
        ownerId: z.number().int().positive().describe('The ID of the owner of this engagement'),
        timestamp: z
            .number()
            .int()
            .optional()
            .describe('Timestamp for the engagement (milliseconds since epoch). Defaults to current time if not provided.'),
        associations: AssociationsSchema.describe('Associated records for this engagement'),
        metadata: z.object({}).passthrough().describe('Metadata specific to the engagement type'),
    })
        .superRefine((data, ctx) => {
        const schema = metadataSchemas[data.type];
        if (!schema) {
            ctx.addIssue({
                code: z.ZodIssueCode.custom,
                message: `Unsupported engagement type: ${data.type}`,
                path: ['type'],
            });
            return;
        }
        const result = schema.safeParse(data.metadata);
        if (!result.success) {
            result.error.issues.forEach(issue => {
                ctx.addIssue({
                    ...issue,
                    path: ['metadata', ...(issue.path || [])],
                });
            });
        }
    });
  • Registers the CreateEngagementTool instance in the tools registry using the registerTool function.
    registerTool(new CreateEngagementTool());
  • ToolDefinition object containing the tool name 'hubspot-create-engagement', description, inputSchema, and annotations, passed to BaseTool constructor.
    const ToolDefinition = {
        name: 'hubspot-create-engagement',
        description: `
        🛡️ Guardrails:
          1. Data Modification Warning: This tool modifies HubSpot data. Only use when the user has explicitly requested to update their CRM.
    
        🎯 Purpose:
          1. Creates a HubSpot engagement (Note or Task) associated with contacts, companies, deals, or tickets.
          2. This endpoint is useful for keeping your CRM records up-to-date on any interactions that take place outside of HubSpot.
          3. Activity reporting in the CRM also feeds off of this data.
    
        📋 Prerequisites:
          1. Use the hubspot-get-user-details tool to get the OwnerId and UserId.
    
        🧭 Usage Guidance:
          1. Use NOTE type for adding notes to records
          2. Use TASK type for creating tasks with subject, status, and assignment
          3. Both require relevant associations to connect them to CRM records
          4. Other types of engagements (EMAIL, CALL, MEETING) are NOT supported yet.
          5. HubSpot notes and task descriptions support HTML formatting. However headings (<h1>, <h2>, etc.) look ugly in the CRM. So use them sparingly.
      `,
        inputSchema: zodToJsonSchema(CreateEngagementSchema),
        annotations: {
            title: 'Create Engagement',
            readOnlyHint: false,
            destructiveHint: false,
            idempotentHint: false,
            openWorldHint: true,
        },
    };
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already indicate this is a write operation (readOnlyHint: false) and not destructive (destructiveHint: false). The description adds valuable context beyond annotations: the data modification warning, HTML formatting support with caveats about headings, and that this feeds activity reporting in the CRM. However, it doesn't mention rate limits, error conditions, or what happens on duplicate creation attempts.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description uses clear section headers (Guardrails, Purpose, Prerequisites, Usage Guidance) that make it easy to scan. Each section contains relevant information with minimal redundancy. While not the absolute most concise possible, every sentence serves a purpose and the structure helps with comprehension.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a creation tool with 5 parameters, 100% schema coverage, and annotations covering key behavioral aspects, the description provides good additional context. It covers prerequisites, usage scenarios, limitations (unsupported engagement types), and formatting considerations. The main gap is the lack of output schema or description of return values, but given the annotations and schema coverage, this is reasonably complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all parameters well. The description adds meaningful context about parameter usage: it explains the difference between NOTE and TASK types, mentions that associations are required to connect to CRM records, and references the prerequisite hubspot-get-user-details tool for obtaining ownerId. It doesn't add syntax details but provides practical usage guidance.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description explicitly states the tool 'creates a HubSpot engagement (Note or Task) associated with contacts, companies, deals, or tickets' - a specific verb+resource combination. It distinguishes this tool from siblings like hubspot-update-engagement (updates existing engagements) and hubspot-get-engagement (reads engagements), making the purpose clear and differentiated.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit guidance on when to use this tool versus alternatives: 'Only use when the user has explicitly requested to update their CRM' (guardrail), 'Use NOTE type for adding notes to records' and 'Use TASK type for creating tasks with subject, status, and assignment' (type-specific guidance), and 'Other types of engagements (EMAIL, CALL, MEETING) are NOT supported yet' (clear exclusion). It also references the prerequisite tool hubspot-get-user-details.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

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

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ajaystream/hubspot-mcp-custom'

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