Skip to main content
Glama
kesslerio

Attio MCP Server

by kesslerio

add-record-to-list

Add companies or people to CRM lists like sales pipelines and lead lists to organize customer data and track business relationships.

Instructions

Add a company or person to a CRM list (sales pipeline, lead list, etc.)

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
initialValuesNoInitial values for the list entry (e.g., {"stage": "Prospect"})
listIdYesID of the list to add the record to
objectTypeYesType of record (e.g., "companies", "people")
recordIdYesID of the record to add to the list

Implementation Reference

  • Core handler that implements the logic to add a record to a list via Attio API, including input validation, generic API attempt with fallback to direct POST /lists/{listId}/entries, extensive logging, and validation error handling.
    export async function addRecordToList(
      listId: string,
      recordId: string,
      objectType: string,
      initialValues?: ListEntryValues
    ): Promise<AttioListEntry> {
      if (!listId || typeof listId !== 'string') {
        throw new Error('Invalid list ID: Must be a non-empty string');
      }
    
      if (!recordId || typeof recordId !== 'string') {
        throw new Error('Invalid record ID: Must be a non-empty string');
      }
    
      if (!objectType || typeof objectType !== 'string') {
        throw new Error(
          'Object type is required: Must be a non-empty string (e.g., "companies", "people")'
        );
      }
    
      if (!Object.values(ResourceType).includes(objectType as ResourceType)) {
        const validTypes = Object.values(ResourceType).join(', ');
        throw new Error(
          `Invalid object type: "${objectType}". Must be one of: ${validTypes}`
        );
      }
    
      const resourceType = objectType as ResourceType;
    
      try {
        return await addGenericRecordToList(
          listId,
          recordId,
          resourceType,
          initialValues
        );
      } catch (error: unknown) {
        if (process.env.NODE_ENV === 'development') {
          const log = createScopedLogger('objects.lists', 'addRecordToList');
          log.warn(
            'Generic addRecordToList failed; falling back to direct implementation',
            {
              listId,
              recordId,
              message: error instanceof Error ? error.message : 'Unknown error',
            }
          );
        }
    
        const api = getLazyAttioClient();
        const path = `/lists/${listId}/entries`;
    
        const payload: ListEntryCreatePayload = {
          data: {
            parent_record_id: recordId,
            parent_object: resourceType,
          },
        };
    
        if (initialValues && Object.keys(initialValues).length > 0) {
          payload.data.entry_values = initialValues;
        }
    
        if (process.env.NODE_ENV === 'development') {
          const log = createScopedLogger('objects.lists', 'addRecordToList');
          log.info('Fallback request payload', { path, payload });
          log.debug('Object Type', { objectType });
          if (initialValues) {
            log.debug('Initial Values', { initialValues });
          }
        }
    
        try {
          const response = await api.post(path, payload);
    
          if (process.env.NODE_ENV === 'development') {
            const log = createScopedLogger('objects.lists', 'addRecordToList');
            log.info('Fallback success response', {
              data: response.data || {},
            });
          }
    
          return extract<AttioListEntry>(response);
        } catch (fallbackError: unknown) {
          if (process.env.NODE_ENV === 'development') {
            const log = createScopedLogger('objects.lists', 'addRecordToList');
            log.warn('Error adding record to list (fallback path)', {
              listId,
              recordId,
              message:
                fallbackError instanceof Error
                  ? fallbackError.message
                  : 'Unknown error',
              status: hasErrorResponse(fallbackError)
                ? fallbackError.response?.status
                : undefined,
              data: hasErrorResponse(fallbackError)
                ? fallbackError.response?.data || {}
                : undefined,
              validationErrors: hasErrorResponse(fallbackError)
                ? fallbackError.response?.data?.validation_errors
                : undefined,
            });
          }
    
          if (
            hasErrorResponse(fallbackError) &&
            fallbackError.response?.status === 400
          ) {
            const validationErrors =
              fallbackError.response?.data?.validation_errors || [];
            const errorDetails = validationErrors
              .map((validationError) => {
                return `${validationError.path?.join('.') || 'unknown'}: ${
                  validationError.message || 'unknown'
                }`;
              })
              .join('; ');
    
            throw new Error(
              `Validation error adding record to list: ${
                errorDetails || getErrorMessage(fallbackError) || 'Unknown error'
              }`
            );
          }
    
          throw fallbackError;
        }
      }
    }
  • Tool registration configuration including wrapper handler with UUID validation, ID params for approval, and result formatter that JSON.stringifies the AttioListEntry.
    addRecordToList: {
      name: 'add-record-to-list',
      handler: async (
        listId: string,
        recordId: string,
        objectType: string,
        values?: Record<string, unknown>
      ) => {
        // UUID validation - hard fail for invalid list IDs
        if (!isValidUUID(listId)) {
          return {
            isError: true,
            content: [
              {
                type: 'text',
                text: `Invalid list_id: must be a UUID. Got: ${listId}`,
              },
            ],
          };
        }
        return await addRecordToList(listId, recordId, objectType, values);
      },
      idParams: ['listId', 'recordId'],
      formatResult: (
        result:
          | AttioListEntry
          | { isError: boolean; content: Array<Record<string, unknown>> }
      ) => {
        // Handle validation error response
        if (result && typeof result === 'object' && 'isError' in result) {
          return 'Error: Invalid list ID';
        }
        // Return JSON string
        return JSON.stringify(result);
      },
    } as ToolConfig,
  • Tool schema definition including inputSchema with properties for listId, recordId, objectType (enum companies/people), optional initialValues, required fields, and formatted description.
    {
      name: 'add-record-to-list',
      description: formatToolDescription({
        capability: 'Add company or person to list with optional initial values.',
        boundaries: 'create records; record must exist first.',
        requiresApproval: true,
        constraints: 'Requires list UUID, record UUID, object type.',
        recoveryHint: 'If not found, create record first with create-record.',
      }),
      inputSchema: {
        type: 'object',
        properties: {
          listId: {
            type: 'string',
            description: 'UUID of the list to add the record to',
            example: '550e8400-e29b-41d4-a716-446655440000',
          },
          recordId: {
            type: 'string',
            description: 'UUID of the record to add to the list',
            example: '660e8400-e29b-41d4-a716-446655440001',
          },
          objectType: {
            type: 'string',
            description: 'Type of record (e.g., "companies", "people")',
            enum: ['companies', 'people'],
            example: 'companies',
          },
          initialValues: {
            type: 'object',
            description:
              'Initial values for the list entry (e.g., {"stage": "Prospect"})',
            example: { stage: 'Qualified' },
          },
        },
        required: ['listId', 'recordId', 'objectType'],
        additionalProperties: false,
      },
    },
  • Global tool registry where listsToolConfigs (containing add-record-to-list) is registered under ResourceType.LISTS in both universal and legacy TOOL_CONFIGS modes.
    export const TOOL_CONFIGS = USE_UNIVERSAL_TOOLS_ONLY
      ? {
          // Universal tools for consolidated operations (Issue #352)
          UNIVERSAL: universalToolConfigs,
          // Lists are relationship containers - always expose them (Issue #470)
          [ResourceType.LISTS]: listsToolConfigs,
          // Workspace members for user discovery (Issue #684)
          [ResourceType.WORKSPACE_MEMBERS]: workspaceMembersToolConfigs,
        }
      : {
          // Legacy resource-specific tools (deprecated, use DISABLE_UNIVERSAL_TOOLS=true to enable)
          [ResourceType.COMPANIES]: companyToolConfigs,
          [ResourceType.PEOPLE]: peopleToolConfigs,
          [ResourceType.DEALS]: dealToolConfigs,
          [ResourceType.LISTS]: listsToolConfigs,
          [ResourceType.TASKS]: tasksToolConfigs,
          [ResourceType.RECORDS]: recordToolConfigs,
          [ResourceType.WORKSPACE_MEMBERS]: workspaceMembersToolConfigs,
          GENERAL: generalToolConfigs,
          // Add other resource types as needed
        };

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/kesslerio/attio-mcp-server'

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