Skip to main content
Glama
simonl77

Salesforce MCP Server

by simonl77

salesforce_manage_field

Create or modify custom fields on Salesforce objects to extend data models, configure properties like required or unique constraints, and establish relationships between objects.

Instructions

Create new custom fields or modify existing fields on any Salesforce object:

  • Field Types: Text, Number, Date, Lookup, Master-Detail, Picklist etc.

  • Properties: Required, Unique, External ID, Length, Scale etc.

  • Relationships: Create lookups and master-detail relationships

  • Automatically grants Field Level Security to System Administrator (or specified profiles) Examples: Add Rating__c picklist to Account, Create Account lookup on Custom Object Note: Use grantAccessTo parameter to specify profiles, defaults to System Administrator

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
operationYesWhether to create new field or update existing
objectNameYesAPI name of the object to add/modify the field
fieldNameYesAPI name for the field (without __c suffix)
labelNoLabel for the field
typeNoField type (required for create)
requiredNoWhether the field is required
uniqueNoWhether the field value must be unique
externalIdNoWhether the field is an external ID
lengthNoLength for text fields
precisionNoPrecision for numeric fields
scaleNoScale for numeric fields
referenceToNoAPI name of the object to reference (for Lookup/MasterDetail)
relationshipLabelNoLabel for the relationship (for Lookup/MasterDetail)
relationshipNameNoAPI name for the relationship (for Lookup/MasterDetail)
deleteConstraintNoDelete constraint for Lookup fields
picklistValuesNoValues for Picklist/MultiselectPicklist fields
descriptionNoDescription of the field
grantAccessToNoProfile names to grant field access to (defaults to ['System Administrator'])

Implementation Reference

  • Main handler function that implements the salesforce_manage_field tool. Handles 'create' and 'update' operations for custom fields on Salesforce objects using Metadata API. Supports various field types (Text, Number, Lookup, Picklist etc.), properties (required, unique, length etc.), relationships, picklist values, and automatically grants Field Level Security.
    export async function handleManageField(conn: any, args: ManageFieldArgs) {
      const { operation, objectName, fieldName, type, grantAccessTo, ...fieldProps } = args;
    
      try {
        if (operation === 'create') {
          if (!type) {
            throw new Error('Field type is required for field creation');
          }
    
          // Prepare base metadata for the new field
          const metadata: FieldMetadataInfo = {
            fullName: `${objectName}.${fieldName}__c`,
            label: fieldProps.label || fieldName,
            type,
            ...(fieldProps.required && { required: fieldProps.required }),
            ...(fieldProps.unique && { unique: fieldProps.unique }),
            ...(fieldProps.externalId && { externalId: fieldProps.externalId }),
            ...(fieldProps.description && { description: fieldProps.description })
          };
    
          // Add type-specific properties
          switch (type) {
            case 'MasterDetail':
            case 'Lookup':
              if (fieldProps.referenceTo) {
                metadata.referenceTo = fieldProps.referenceTo;
                metadata.relationshipName = fieldProps.relationshipName;
                metadata.relationshipLabel = fieldProps.relationshipLabel || fieldProps.relationshipName;
                if (type === 'Lookup' && fieldProps.deleteConstraint) {
                  metadata.deleteConstraint = fieldProps.deleteConstraint;
                }
              }
              break;
    
            case 'TextArea':
              metadata.type = 'LongTextArea';
              metadata.length = fieldProps.length || 32768;
              metadata.visibleLines = 3;
              break;
    
            case 'Text':
              if (fieldProps.length) {
                metadata.length = fieldProps.length;
              }
              break;
    
            case 'Number':
              if (fieldProps.precision) {
                metadata.precision = fieldProps.precision;
                metadata.scale = fieldProps.scale || 0;
              }
              break;
    
            case 'Picklist':
            case 'MultiselectPicklist':
              if (fieldProps.picklistValues) {
                metadata.valueSet = {
                  valueSetDefinition: {
                    sorted: true,
                    value: fieldProps.picklistValues.map(val => ({
                      fullName: val.label,
                      default: val.isDefault || false,
                      label: val.label
                    }))
                  }
                };
              }
              break;
          }
    
          // Create the field
          const result = await conn.metadata.create('CustomField', metadata);
    
          if (result && (Array.isArray(result) ? result[0].success : result.success)) {
            let permissionMessage = '';
            
            // Grant Field Level Security (default to System Administrator if not specified)
            const profilesToGrant = grantAccessTo && grantAccessTo.length > 0 ? grantAccessTo : ['System Administrator'];
            
            // Wait a moment for field to be fully created
            await new Promise(resolve => setTimeout(resolve, 2000));
            
            const permissionResult = await grantFieldPermissions(conn, objectName, fieldName, profilesToGrant);
            permissionMessage = `\n${permissionResult.message}`;
            
            return {
              content: [{
                type: "text",
                text: `Successfully created custom field ${fieldName}__c on ${objectName}.${permissionMessage}`
              }],
              isError: false,
            };
          }
        } else {
          // For update, first get existing metadata
          const existingMetadata = await conn.metadata.read('CustomField', [`${objectName}.${fieldName}__c`]);
          const currentMetadata = Array.isArray(existingMetadata) ? existingMetadata[0] : existingMetadata;
    
          if (!currentMetadata) {
            throw new Error(`Field ${fieldName}__c not found on object ${objectName}`);
          }
    
          // Prepare update metadata
          const metadata: FieldMetadataInfo = {
            ...currentMetadata,
            ...(fieldProps.label && { label: fieldProps.label }),
            ...(fieldProps.required !== undefined && { required: fieldProps.required }),
            ...(fieldProps.unique !== undefined && { unique: fieldProps.unique }),
            ...(fieldProps.externalId !== undefined && { externalId: fieldProps.externalId }),
            ...(fieldProps.description !== undefined && { description: fieldProps.description }),
            ...(fieldProps.length && { length: fieldProps.length }),
            ...(fieldProps.precision && { precision: fieldProps.precision, scale: fieldProps.scale || 0 })
          };
    
          // Special handling for picklist values if provided
          if (fieldProps.picklistValues && 
              (currentMetadata.type === 'Picklist' || currentMetadata.type === 'MultiselectPicklist')) {
            metadata.valueSet = {
              valueSetDefinition: {
                sorted: true,
                value: fieldProps.picklistValues.map(val => ({
                  fullName: val.label,
                  default: val.isDefault || false,
                  label: val.label
                }))
              }
            };
          }
    
          // Update the field
          const result = await conn.metadata.update('CustomField', metadata);
    
          if (result && (Array.isArray(result) ? result[0].success : result.success)) {
            return {
              content: [{
                type: "text",
                text: `Successfully updated custom field ${fieldName}__c on ${objectName}`
              }],
              isError: false,
            };
          }
        }
    
        return {
          content: [{
            type: "text",
            text: `Failed to ${operation} custom field ${fieldName}__c`
          }],
          isError: true,
        };
    
      } catch (error) {
        return {
          content: [{
            type: "text",
            text: `Error ${operation === 'create' ? 'creating' : 'updating'} custom field: ${error instanceof Error ? error.message : String(error)}`
          }],
          isError: true,
        };
      }
    }
  • Tool schema definition for salesforce_manage_field, including inputSchema with all parameters, types, descriptions, and required fields.
    export const MANAGE_FIELD: Tool = {
      name: "salesforce_manage_field",
      description: `Create new custom fields or modify existing fields on any Salesforce object:
      - Field Types: Text, Number, Date, Lookup, Master-Detail, Picklist etc.
      - Properties: Required, Unique, External ID, Length, Scale etc.
      - Relationships: Create lookups and master-detail relationships
      - Automatically grants Field Level Security to System Administrator (or specified profiles)
      Examples: Add Rating__c picklist to Account, Create Account lookup on Custom Object
      Note: Use grantAccessTo parameter to specify profiles, defaults to System Administrator`,
      inputSchema: {
        type: "object",
        properties: {
          operation: {
            type: "string",
            enum: ["create", "update"],
            description: "Whether to create new field or update existing"
          },
          objectName: {
            type: "string",
            description: "API name of the object to add/modify the field"
          },
          fieldName: {
            type: "string",
            description: "API name for the field (without __c suffix)"
          },
          label: {
            type: "string",
            description: "Label for the field",
            optional: true
          },
          type: {
            type: "string",
            enum: ["Checkbox", "Currency", "Date", "DateTime", "Email", "Number", "Percent", 
                   "Phone", "Picklist", "MultiselectPicklist", "Text", "TextArea", "LongTextArea", 
                   "Html", "Url", "Lookup", "MasterDetail"],
            description: "Field type (required for create)",
            optional: true
          },
          required: {
            type: "boolean",
            description: "Whether the field is required",
            optional: true
          },
          unique: {
            type: "boolean",
            description: "Whether the field value must be unique",
            optional: true
          },
          externalId: {
            type: "boolean",
            description: "Whether the field is an external ID",
            optional: true
          },
          length: {
            type: "number",
            description: "Length for text fields",
            optional: true
          },
          precision: {
            type: "number",
            description: "Precision for numeric fields",
            optional: true
          },
          scale: {
            type: "number",
            description: "Scale for numeric fields",
            optional: true
          },
          referenceTo: {
            type: "string",
            description: "API name of the object to reference (for Lookup/MasterDetail)",
            optional: true
          },
          relationshipLabel: {
            type: "string",
            description: "Label for the relationship (for Lookup/MasterDetail)",
            optional: true
          },
          relationshipName: {
            type: "string",
            description: "API name for the relationship (for Lookup/MasterDetail)",
            optional: true
          },
          deleteConstraint: {
            type: "string",
            enum: ["Cascade", "Restrict", "SetNull"],
            description: "Delete constraint for Lookup fields",
            optional: true
          },
          picklistValues: {
            type: "array",
            items: {
              type: "object",
              properties: {
                label: { type: "string" },
                isDefault: { type: "boolean", optional: true }
              }
            },
            description: "Values for Picklist/MultiselectPicklist fields",
            optional: true
          },
          description: {
            type: "string",
            description: "Description of the field",
            optional: true
          },
          grantAccessTo: {
            type: "array",
            items: { type: "string" },
            description: "Profile names to grant field access to (defaults to ['System Administrator'])",
            optional: true
          }
        },
        required: ["operation", "objectName", "fieldName"]
      }
    };
  • src/index.ts:152-178 (registration)
    Registration and dispatch in the main server CallToolRequest handler switch statement. Validates arguments and calls handleManageField.
    case "salesforce_manage_field": {
      const fieldArgs = args as Record<string, unknown>;
      if (!fieldArgs.operation || !fieldArgs.objectName || !fieldArgs.fieldName) {
        throw new Error('operation, objectName, and fieldName are required for field management');
      }
      const validatedArgs: ManageFieldArgs = {
        operation: fieldArgs.operation as 'create' | 'update',
        objectName: fieldArgs.objectName as string,
        fieldName: fieldArgs.fieldName as string,
        label: fieldArgs.label as string | undefined,
        type: fieldArgs.type as string | undefined,
        required: fieldArgs.required as boolean | undefined,
        unique: fieldArgs.unique as boolean | undefined,
        externalId: fieldArgs.externalId as boolean | undefined,
        length: fieldArgs.length as number | undefined,
        precision: fieldArgs.precision as number | undefined,
        scale: fieldArgs.scale as number | undefined,
        referenceTo: fieldArgs.referenceTo as string | undefined,
        relationshipLabel: fieldArgs.relationshipLabel as string | undefined,
        relationshipName: fieldArgs.relationshipName as string | undefined,
        deleteConstraint: fieldArgs.deleteConstraint as 'Cascade' | 'Restrict' | 'SetNull' | undefined,
        picklistValues: fieldArgs.picklistValues as Array<{ label: string; isDefault?: boolean }> | undefined,
        description: fieldArgs.description as string | undefined,
        grantAccessTo: fieldArgs.grantAccessTo as string[] | undefined
      };
      return await handleManageField(conn, validatedArgs);
    }
  • src/index.ts:47-62 (registration)
    Tool registration in ListToolsRequestHandler, including MANAGE_FIELD in the exported tools list.
      SEARCH_OBJECTS, 
      DESCRIBE_OBJECT, 
      QUERY_RECORDS, 
      AGGREGATE_QUERY,
      DML_RECORDS,
      MANAGE_OBJECT,
      MANAGE_FIELD,
      MANAGE_FIELD_PERMISSIONS,
      SEARCH_ALL,
      READ_APEX,
      WRITE_APEX,
      READ_APEX_TRIGGER,
      WRITE_APEX_TRIGGER,
      EXECUTE_ANONYMOUS,
      MANAGE_DEBUG_LOGS
    ],
  • Helper function to grant Field Level Security permissions to profiles for the new/updated field. Called automatically after field creation.
    // Helper function to set field permissions (simplified version of the one in manageFieldPermissions.ts)
    async function grantFieldPermissions(conn: any, objectName: string, fieldName: string, profileNames: string[]): Promise<{success: boolean; message: string}> {
      try {
        const fieldApiName = fieldName.endsWith('__c') || fieldName.includes('.') ? fieldName : `${fieldName}__c`;
        const fullFieldName = `${objectName}.${fieldApiName}`;
        
        // Get profile IDs
        const profileQuery = await conn.query(`
          SELECT Id, Name 
          FROM Profile 
          WHERE Name IN (${profileNames.map(name => `'${name}'`).join(', ')})
        `);
    
        if (profileQuery.records.length === 0) {
          return { success: false, message: `No profiles found matching: ${profileNames.join(', ')}` };
        }
    
        const results: any[] = [];
        const errors: string[] = [];
    
        for (const profile of profileQuery.records) {
          try {
            // Check if permission already exists
            const existingPerm = await conn.query(`
              SELECT Id, PermissionsRead, PermissionsEdit
              FROM FieldPermissions
              WHERE ParentId IN (
                SELECT Id FROM PermissionSet 
                WHERE IsOwnedByProfile = true 
                AND ProfileId = '${profile.Id}'
              )
              AND Field = '${fullFieldName}'
              AND SobjectType = '${objectName}'
              LIMIT 1
            `);
    
            if (existingPerm.records.length > 0) {
              // Update existing permission
              await conn.sobject('FieldPermissions').update({
                Id: existingPerm.records[0].Id,
                PermissionsRead: true,
                PermissionsEdit: true
              });
              results.push(profile.Name);
            } else {
              // Get the PermissionSet ID for this profile
              const permSetQuery = await conn.query(`
                SELECT Id FROM PermissionSet 
                WHERE IsOwnedByProfile = true 
                AND ProfileId = '${profile.Id}'
                LIMIT 1
              `);
    
              if (permSetQuery.records.length > 0) {
                // Create new permission
                await conn.sobject('FieldPermissions').create({
                  ParentId: permSetQuery.records[0].Id,
                  SobjectType: objectName,
                  Field: fullFieldName,
                  PermissionsRead: true,
                  PermissionsEdit: true
                });
                results.push(profile.Name);
              } else {
                errors.push(profile.Name);
              }
            }
          } catch (error) {
            errors.push(profile.Name);
            console.error(`Error granting permission to ${profile.Name}:`, error);
          }
        }
    
        if (results.length > 0) {
          return {
            success: true,
            message: `Field Level Security granted to: ${results.join(', ')}${errors.length > 0 ? `. Failed for: ${errors.join(', ')}` : ''}`
          };
        } else {
          return {
            success: false,
            message: `Could not grant Field Level Security to any profiles.`
          };
        }
      } catch (error) {
        console.error('Error granting field permissions:', error);
        return {
          success: false,
          message: `Field Level Security configuration failed.`
        };
      }
    }
Behavior3/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 does reveal important behavioral traits: that it 'Automatically grants Field Level Security to System Administrator (or specified profiles)' and includes a note about the grantAccessTo parameter default. However, it doesn't disclose other critical behaviors like whether this is a destructive operation (modifying existing fields could break dependencies), what permissions are required, rate limits, or what happens on failure. For a field management tool with zero annotation coverage, this leaves significant gaps.

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 is well-structured with bullet points and examples, making it easy to scan. It's appropriately sized for a complex tool with 18 parameters. The information is front-loaded with the core purpose first. There's minimal waste, though the bullet points could be slightly more concise. Every sentence earns its place by adding value beyond the schema.

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

Completeness3/5

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

Given the tool's complexity (18 parameters, no annotations, no output schema), the description provides a reasonable foundation but has notable gaps. It covers the purpose, usage context, and some behavioral aspects (field security granting), but doesn't address important contextual elements like what the tool returns, error conditions, dependencies, or detailed behavioral constraints. For a field management tool that can modify existing fields, more completeness would be expected.

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?

The schema description coverage is 100%, so the schema already documents all 18 parameters thoroughly. The description adds some value by grouping parameters conceptually ('Field Types', 'Properties', 'Relationships') and mentioning the grantAccessTo parameter's default behavior. However, it doesn't provide significant additional semantic context beyond what's already in the schema descriptions. The baseline of 3 is appropriate when the schema does the heavy lifting.

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 clearly states the tool's purpose: 'Create new custom fields or modify existing fields on any Salesforce object.' It specifies the verb (create/modify), resource (custom fields), and scope (any Salesforce object). It distinguishes from siblings like salesforce_manage_object (which manages objects, not fields) and salesforce_manage_field_permissions (which manages permissions, not field definitions).

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

Usage Guidelines4/5

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

The description provides clear context about when to use this tool: for creating or modifying fields with specific types and properties. It includes examples ('Add Rating__c picklist to Account, Create Account lookup on Custom Object') that illustrate appropriate use cases. However, it doesn't explicitly state when NOT to use this tool or mention alternatives among the sibling tools, such as when to use salesforce_manage_field_permissions instead for permission management.

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/simonl77/mcp-server-salesforce'

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