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
        };
Behavior2/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It states the tool adds records to lists, implying a mutation operation, but doesn't describe permissions required, whether the operation is idempotent, error handling, or what happens if the record is already in the list. This leaves significant gaps for a mutation tool with zero annotation coverage.

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

Conciseness5/5

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

The description is a single, efficient sentence that front-loads the core purpose ('Add a company or person to a CRM list') and includes helpful examples ('sales pipeline, lead list, etc.') without unnecessary details. Every word earns its place, making it easy to scan and understand quickly.

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

Completeness2/5

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

Given this is a mutation tool with no annotations and no output schema, the description is incomplete. It lacks behavioral details like side effects, error conditions, or return values, and doesn't provide usage context relative to siblings. While concise, it fails to address key aspects needed for safe and effective tool invocation in a complex CRM environment.

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

Parameters3/5

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

Schema description coverage is 100%, so the schema fully documents all 4 parameters (listId, recordId, objectType, initialValues) with descriptions and an enum for objectType. The description adds no parameter-specific information beyond what's in the schema, such as format examples or constraints, so it meets the baseline for high schema coverage without compensating value.

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

Purpose4/5

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

The description clearly states the action ('Add') and resource ('a company or person to a CRM list'), with examples like 'sales pipeline, lead list' providing context. It distinguishes from siblings like 'remove-record-from-list' by specifying addition, but doesn't explicitly differentiate from 'update-list-entry' or 'create-record' beyond the 'add to list' focus.

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

Usage Guidelines2/5

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

No explicit guidance on when to use this tool versus alternatives is provided. The description implies usage for adding records to lists, but doesn't mention prerequisites, when to choose this over 'create-record' (which might create a new record vs. adding an existing one), or when to use 'update-list-entry' instead. It lacks context on appropriate scenarios or exclusions.

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

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