create_dataverse_column
Add custom fields to Dataverse tables with specific data types including text, numbers, dates, lookups, and choice lists to store structured data.
Instructions
Creates a new column (field) in a Dataverse table with the specified data type and configuration. Supports various column types including text, numbers, dates, lookups, and choice lists. Use this to add new fields to store specific data in your tables. Requires a solution context to be set first.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| columnType | Yes | Type of the column | |
| dateTimeFormat | No | Format for datetime columns | |
| defaultValue | No | Default value for the column | |
| description | No | Description of the column | |
| displayName | Yes | Display name for the column (e.g., 'Customer Email') | |
| entityLogicalName | Yes | Logical name of the table to add the column to | |
| falseOptionLabel | No | Label for false option in boolean columns (default: 'No') | |
| format | No | Format for string columns | |
| isAuditEnabled | No | Whether auditing is enabled for this column | |
| isValidForAdvancedFind | No | Whether the column appears in Advanced Find | |
| isValidForCreate | No | Whether the column can be set during create | |
| isValidForUpdate | No | Whether the column can be updated | |
| maxLength | No | Maximum length for string columns (default: 100) | |
| maxValue | No | Maximum value for integer/decimal columns | |
| minValue | No | Minimum value for integer/decimal columns | |
| optionSetName | No | Name of the option set for picklist columns | |
| options | No | Options for picklist columns | |
| precision | No | Precision for decimal columns (default: 2) | |
| requiredLevel | No | Required level of the column | None |
| targetEntity | No | Target entity for lookup columns | |
| trueOptionLabel | No | Label for true option in boolean columns (default: 'Yes') |
Implementation Reference
- src/tools/column-tools.ts:87-256 (handler)The asynchronous handler function that implements the core logic for creating a Dataverse column. It generates logical/schema names, constructs attribute metadata based on the specified columnType (e.g., String, Picklist, Lookup), handles type-specific properties, and posts the metadata to the EntityDefinitions/Attributes endpoint.async (params) => { try { // Get the customization prefix from the solution context const prefix = client.getCustomizationPrefix(); if (!prefix) { throw new Error('No customization prefix available. Please set a solution context using set_solution_context tool first.'); } // Generate the logical name and schema name const logicalName = generateColumnLogicalName(params.displayName, prefix); const schemaName = generateColumnSchemaName(params.displayName, prefix); let attributeDefinition: any = { LogicalName: logicalName, SchemaName: schemaName, DisplayName: createLocalizedLabel(params.displayName), Description: params.description ? createLocalizedLabel(params.description) : undefined, RequiredLevel: { Value: params.requiredLevel, CanBeChanged: true, ManagedPropertyLogicalName: "canmodifyrequirementlevelsettings" }, IsCustomAttribute: true }; // Set type-specific properties switch (params.columnType) { case "String": attributeDefinition["@odata.type"] = "Microsoft.Dynamics.CRM.StringAttributeMetadata"; attributeDefinition.MaxLength = params.maxLength || 100; // Remove Format property for now to avoid enum issues if (params.defaultValue && typeof params.defaultValue === "string") { attributeDefinition.DefaultValue = params.defaultValue; } break; case "Integer": attributeDefinition["@odata.type"] = "Microsoft.Dynamics.CRM.IntegerAttributeMetadata"; // Remove Format property for now to avoid enum issues if (params.minValue !== undefined) attributeDefinition.MinValue = params.minValue; if (params.maxValue !== undefined) attributeDefinition.MaxValue = params.maxValue; // Note: IntegerAttributeMetadata doesn't support DefaultValue property break; case "Decimal": attributeDefinition["@odata.type"] = "Microsoft.Dynamics.CRM.DecimalAttributeMetadata"; attributeDefinition.Precision = params.precision || 2; if (params.minValue !== undefined) attributeDefinition.MinValue = params.minValue; if (params.maxValue !== undefined) attributeDefinition.MaxValue = params.maxValue; if (params.defaultValue && typeof params.defaultValue === "number") { attributeDefinition.DefaultValue = params.defaultValue; } break; case "Money": attributeDefinition["@odata.type"] = "Microsoft.Dynamics.CRM.MoneyAttributeMetadata"; attributeDefinition.Precision = params.precision || 2; if (params.minValue !== undefined) attributeDefinition.MinValue = params.minValue; if (params.maxValue !== undefined) attributeDefinition.MaxValue = params.maxValue; break; case "Boolean": attributeDefinition["@odata.type"] = "Microsoft.Dynamics.CRM.BooleanAttributeMetadata"; attributeDefinition.OptionSet = { "@odata.type": "Microsoft.Dynamics.CRM.BooleanOptionSetMetadata", TrueOption: { Value: 1, Label: createLocalizedLabel(params.trueOptionLabel || "Yes") }, FalseOption: { Value: 0, Label: createLocalizedLabel(params.falseOptionLabel || "No") } }; if (params.defaultValue && typeof params.defaultValue === "boolean") { attributeDefinition.DefaultValue = params.defaultValue; } break; case "DateTime": attributeDefinition["@odata.type"] = "Microsoft.Dynamics.CRM.DateTimeAttributeMetadata"; // Remove Format property for now to avoid enum issues attributeDefinition.DateTimeBehavior = { Value: "UserLocal" }; break; case "Memo": attributeDefinition["@odata.type"] = "Microsoft.Dynamics.CRM.MemoAttributeMetadata"; attributeDefinition.MaxLength = params.maxLength || 2000; break; case "Double": attributeDefinition["@odata.type"] = "Microsoft.Dynamics.CRM.DoubleAttributeMetadata"; if (params.minValue !== undefined) attributeDefinition.MinValue = params.minValue; if (params.maxValue !== undefined) attributeDefinition.MaxValue = params.maxValue; attributeDefinition.Precision = params.precision || 2; break; case "BigInt": attributeDefinition["@odata.type"] = "Microsoft.Dynamics.CRM.BigIntAttributeMetadata"; break; case "Lookup": if (!params.targetEntity) { throw new Error("targetEntity is required for Lookup columns"); } attributeDefinition["@odata.type"] = "Microsoft.Dynamics.CRM.LookupAttributeMetadata"; attributeDefinition.Targets = [params.targetEntity]; break; case "Picklist": attributeDefinition["@odata.type"] = "Microsoft.Dynamics.CRM.PicklistAttributeMetadata"; if (params.optionSetName) { // Reference an existing global option set using the MetadataId // First get the option set to retrieve its MetadataId try { const globalOptionSet = await client.getMetadata( `GlobalOptionSetDefinitions(Name='${params.optionSetName}')` ); attributeDefinition["GlobalOptionSet@odata.bind"] = `/GlobalOptionSetDefinitions(${globalOptionSet.MetadataId})`; } catch (error) { throw new Error(`Global option set '${params.optionSetName}' not found: ${error instanceof Error ? error.message : 'Unknown error'}`); } } else if (params.options && params.options.length > 0) { // Create a new local option set attributeDefinition.OptionSet = { "@odata.type": "Microsoft.Dynamics.CRM.OptionSetMetadata", Name: `${params.entityLogicalName}_${logicalName}`, DisplayName: createLocalizedLabel(`${params.displayName} Options`), IsGlobal: false, OptionSetType: "Picklist", // Use string value instead of numeric Options: params.options.map(option => ({ Value: option.value, Label: createLocalizedLabel(option.label), Description: option.description ? createLocalizedLabel(option.description) : undefined })) }; } else { throw new Error("Either optionSetName (for global option set) or options array (for local option set) is required for Picklist columns"); } break; default: throw new Error(`Unsupported column type: ${params.columnType}`); } const result = await client.postMetadata( `EntityDefinitions(LogicalName='${params.entityLogicalName}')/Attributes`, attributeDefinition ); return { content: [ { type: "text", text: `Successfully created column '${logicalName}' with display name '${params.displayName}' of type '${params.columnType}' in table '${params.entityLogicalName}'.\n\nGenerated names:\n- Logical Name: ${logicalName}\n- Schema Name: ${schemaName}\n\nResponse: ${JSON.stringify(result, null, 2)}` } ] }; } catch (error) { return { content: [ { type: "text", text: `Error creating column: ${error instanceof Error ? error.message : 'Unknown error'}` } ], isError: true }; } }
- src/tools/column-tools.ts:49-85 (schema)The inputSchema using Zod that validates and describes parameters for the tool, including required fields like entityLogicalName, displayName, columnType, and optional type-specific options like maxLength, options for picklists, etc.inputSchema: { entityLogicalName: z.string().describe("Logical name of the table to add the column to"), displayName: z.string().describe("Display name for the column (e.g., 'Customer Email')"), description: z.string().optional().describe("Description of the column"), columnType: z.enum([ "String", "Integer", "Decimal", "Money", "Boolean", "DateTime", "Picklist", "Lookup", "Memo", "Double", "BigInt" ]).describe("Type of the column"), requiredLevel: z.enum(["None", "SystemRequired", "ApplicationRequired", "Recommended"]).default("None").describe("Required level of the column"), isAuditEnabled: z.boolean().optional().describe("Whether auditing is enabled for this column"), isValidForAdvancedFind: z.boolean().optional().describe("Whether the column appears in Advanced Find"), isValidForCreate: z.boolean().optional().describe("Whether the column can be set during create"), isValidForUpdate: z.boolean().optional().describe("Whether the column can be updated"), // String-specific options maxLength: z.number().optional().describe("Maximum length for string columns (default: 100)"), format: z.enum(["Email", "Text", "TextArea", "Url", "Phone"]).optional().describe("Format for string columns"), // Integer-specific options minValue: z.number().optional().describe("Minimum value for integer/decimal columns"), maxValue: z.number().optional().describe("Maximum value for integer/decimal columns"), // Decimal-specific options precision: z.number().optional().describe("Precision for decimal columns (default: 2)"), // DateTime-specific options dateTimeFormat: z.enum(["DateOnly", "DateAndTime"]).optional().describe("Format for datetime columns"), // Boolean-specific options trueOptionLabel: z.string().optional().describe("Label for true option in boolean columns (default: 'Yes')"), falseOptionLabel: z.string().optional().describe("Label for false option in boolean columns (default: 'No')"), defaultValue: z.union([z.string(), z.number(), z.boolean()]).optional().describe("Default value for the column"), // Lookup-specific options targetEntity: z.string().optional().describe("Target entity for lookup columns"), // Picklist-specific options optionSetName: z.string().optional().describe("Name of the option set for picklist columns"), options: z.array(z.object({ value: z.number(), label: z.string(), description: z.string().optional() })).optional().describe("Options for picklist columns") }
- src/tools/column-tools.ts:43-257 (registration)The exported createColumnTool function that registers the 'create_dataverse_column' tool with the MCP server, providing title, description, inputSchema, and the handler.export function createColumnTool(server: McpServer, client: DataverseClient) { server.registerTool( "create_dataverse_column", { title: "Create Dataverse Column", description: "Creates a new column (field) in a Dataverse table with the specified data type and configuration. Supports various column types including text, numbers, dates, lookups, and choice lists. Use this to add new fields to store specific data in your tables. Requires a solution context to be set first.", inputSchema: { entityLogicalName: z.string().describe("Logical name of the table to add the column to"), displayName: z.string().describe("Display name for the column (e.g., 'Customer Email')"), description: z.string().optional().describe("Description of the column"), columnType: z.enum([ "String", "Integer", "Decimal", "Money", "Boolean", "DateTime", "Picklist", "Lookup", "Memo", "Double", "BigInt" ]).describe("Type of the column"), requiredLevel: z.enum(["None", "SystemRequired", "ApplicationRequired", "Recommended"]).default("None").describe("Required level of the column"), isAuditEnabled: z.boolean().optional().describe("Whether auditing is enabled for this column"), isValidForAdvancedFind: z.boolean().optional().describe("Whether the column appears in Advanced Find"), isValidForCreate: z.boolean().optional().describe("Whether the column can be set during create"), isValidForUpdate: z.boolean().optional().describe("Whether the column can be updated"), // String-specific options maxLength: z.number().optional().describe("Maximum length for string columns (default: 100)"), format: z.enum(["Email", "Text", "TextArea", "Url", "Phone"]).optional().describe("Format for string columns"), // Integer-specific options minValue: z.number().optional().describe("Minimum value for integer/decimal columns"), maxValue: z.number().optional().describe("Maximum value for integer/decimal columns"), // Decimal-specific options precision: z.number().optional().describe("Precision for decimal columns (default: 2)"), // DateTime-specific options dateTimeFormat: z.enum(["DateOnly", "DateAndTime"]).optional().describe("Format for datetime columns"), // Boolean-specific options trueOptionLabel: z.string().optional().describe("Label for true option in boolean columns (default: 'Yes')"), falseOptionLabel: z.string().optional().describe("Label for false option in boolean columns (default: 'No')"), defaultValue: z.union([z.string(), z.number(), z.boolean()]).optional().describe("Default value for the column"), // Lookup-specific options targetEntity: z.string().optional().describe("Target entity for lookup columns"), // Picklist-specific options optionSetName: z.string().optional().describe("Name of the option set for picklist columns"), options: z.array(z.object({ value: z.number(), label: z.string(), description: z.string().optional() })).optional().describe("Options for picklist columns") } }, async (params) => { try { // Get the customization prefix from the solution context const prefix = client.getCustomizationPrefix(); if (!prefix) { throw new Error('No customization prefix available. Please set a solution context using set_solution_context tool first.'); } // Generate the logical name and schema name const logicalName = generateColumnLogicalName(params.displayName, prefix); const schemaName = generateColumnSchemaName(params.displayName, prefix); let attributeDefinition: any = { LogicalName: logicalName, SchemaName: schemaName, DisplayName: createLocalizedLabel(params.displayName), Description: params.description ? createLocalizedLabel(params.description) : undefined, RequiredLevel: { Value: params.requiredLevel, CanBeChanged: true, ManagedPropertyLogicalName: "canmodifyrequirementlevelsettings" }, IsCustomAttribute: true }; // Set type-specific properties switch (params.columnType) { case "String": attributeDefinition["@odata.type"] = "Microsoft.Dynamics.CRM.StringAttributeMetadata"; attributeDefinition.MaxLength = params.maxLength || 100; // Remove Format property for now to avoid enum issues if (params.defaultValue && typeof params.defaultValue === "string") { attributeDefinition.DefaultValue = params.defaultValue; } break; case "Integer": attributeDefinition["@odata.type"] = "Microsoft.Dynamics.CRM.IntegerAttributeMetadata"; // Remove Format property for now to avoid enum issues if (params.minValue !== undefined) attributeDefinition.MinValue = params.minValue; if (params.maxValue !== undefined) attributeDefinition.MaxValue = params.maxValue; // Note: IntegerAttributeMetadata doesn't support DefaultValue property break; case "Decimal": attributeDefinition["@odata.type"] = "Microsoft.Dynamics.CRM.DecimalAttributeMetadata"; attributeDefinition.Precision = params.precision || 2; if (params.minValue !== undefined) attributeDefinition.MinValue = params.minValue; if (params.maxValue !== undefined) attributeDefinition.MaxValue = params.maxValue; if (params.defaultValue && typeof params.defaultValue === "number") { attributeDefinition.DefaultValue = params.defaultValue; } break; case "Money": attributeDefinition["@odata.type"] = "Microsoft.Dynamics.CRM.MoneyAttributeMetadata"; attributeDefinition.Precision = params.precision || 2; if (params.minValue !== undefined) attributeDefinition.MinValue = params.minValue; if (params.maxValue !== undefined) attributeDefinition.MaxValue = params.maxValue; break; case "Boolean": attributeDefinition["@odata.type"] = "Microsoft.Dynamics.CRM.BooleanAttributeMetadata"; attributeDefinition.OptionSet = { "@odata.type": "Microsoft.Dynamics.CRM.BooleanOptionSetMetadata", TrueOption: { Value: 1, Label: createLocalizedLabel(params.trueOptionLabel || "Yes") }, FalseOption: { Value: 0, Label: createLocalizedLabel(params.falseOptionLabel || "No") } }; if (params.defaultValue && typeof params.defaultValue === "boolean") { attributeDefinition.DefaultValue = params.defaultValue; } break; case "DateTime": attributeDefinition["@odata.type"] = "Microsoft.Dynamics.CRM.DateTimeAttributeMetadata"; // Remove Format property for now to avoid enum issues attributeDefinition.DateTimeBehavior = { Value: "UserLocal" }; break; case "Memo": attributeDefinition["@odata.type"] = "Microsoft.Dynamics.CRM.MemoAttributeMetadata"; attributeDefinition.MaxLength = params.maxLength || 2000; break; case "Double": attributeDefinition["@odata.type"] = "Microsoft.Dynamics.CRM.DoubleAttributeMetadata"; if (params.minValue !== undefined) attributeDefinition.MinValue = params.minValue; if (params.maxValue !== undefined) attributeDefinition.MaxValue = params.maxValue; attributeDefinition.Precision = params.precision || 2; break; case "BigInt": attributeDefinition["@odata.type"] = "Microsoft.Dynamics.CRM.BigIntAttributeMetadata"; break; case "Lookup": if (!params.targetEntity) { throw new Error("targetEntity is required for Lookup columns"); } attributeDefinition["@odata.type"] = "Microsoft.Dynamics.CRM.LookupAttributeMetadata"; attributeDefinition.Targets = [params.targetEntity]; break; case "Picklist": attributeDefinition["@odata.type"] = "Microsoft.Dynamics.CRM.PicklistAttributeMetadata"; if (params.optionSetName) { // Reference an existing global option set using the MetadataId // First get the option set to retrieve its MetadataId try { const globalOptionSet = await client.getMetadata( `GlobalOptionSetDefinitions(Name='${params.optionSetName}')` ); attributeDefinition["GlobalOptionSet@odata.bind"] = `/GlobalOptionSetDefinitions(${globalOptionSet.MetadataId})`; } catch (error) { throw new Error(`Global option set '${params.optionSetName}' not found: ${error instanceof Error ? error.message : 'Unknown error'}`); } } else if (params.options && params.options.length > 0) { // Create a new local option set attributeDefinition.OptionSet = { "@odata.type": "Microsoft.Dynamics.CRM.OptionSetMetadata", Name: `${params.entityLogicalName}_${logicalName}`, DisplayName: createLocalizedLabel(`${params.displayName} Options`), IsGlobal: false, OptionSetType: "Picklist", // Use string value instead of numeric Options: params.options.map(option => ({ Value: option.value, Label: createLocalizedLabel(option.label), Description: option.description ? createLocalizedLabel(option.description) : undefined })) }; } else { throw new Error("Either optionSetName (for global option set) or options array (for local option set) is required for Picklist columns"); } break; default: throw new Error(`Unsupported column type: ${params.columnType}`); } const result = await client.postMetadata( `EntityDefinitions(LogicalName='${params.entityLogicalName}')/Attributes`, attributeDefinition ); return { content: [ { type: "text", text: `Successfully created column '${logicalName}' with display name '${params.displayName}' of type '${params.columnType}' in table '${params.entityLogicalName}'.\n\nGenerated names:\n- Logical Name: ${logicalName}\n- Schema Name: ${schemaName}\n\nResponse: ${JSON.stringify(result, null, 2)}` } ] }; } catch (error) { return { content: [ { type: "text", text: `Error creating column: ${error instanceof Error ? error.message : 'Unknown error'}` } ], isError: true }; } } );
- src/tools/column-tools.ts:7-23 (helper)createLocalizedLabel helper: Creates standardized LocalizedLabel objects used for DisplayName, Description, and Option labels.function createLocalizedLabel(text: string, languageCode: number = 1033): LocalizedLabel { return { LocalizedLabels: [ { Label: text, LanguageCode: languageCode, IsManaged: false, MetadataId: "00000000-0000-0000-0000-000000000000" } ], UserLocalizedLabel: { Label: text, LanguageCode: languageCode, IsManaged: false, MetadataId: "00000000-0000-0000-0000-000000000000" } };
- src/tools/column-tools.ts:27-41 (helper)generateColumnLogicalName and generateColumnSchemaName helpers: Generate unique logical/schema names from displayName and solution prefix.function generateColumnLogicalName(displayName: string, prefix: string): string { // Convert display name to lowercase, remove spaces and special characters const cleanName = displayName.toLowerCase() .replace(/[^a-z0-9\s]/g, '') // Remove special characters except spaces .replace(/\s+/g, ''); // Remove all spaces return `${prefix}_${cleanName}`; } // Helper function to generate schema name from display name and prefix function generateColumnSchemaName(displayName: string, prefix: string): string { // Remove whitespaces and special characters, but preserve original case const cleanName = displayName.replace(/\s+/g, '').replace(/[^a-zA-Z0-9]/g, ''); return `${prefix}_${cleanName}`; }