Skip to main content
Glama
mwhesse

Dataverse MCP Server

by mwhesse
autonumber-tools.ts22.1 kB
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from 'zod'; import { DataverseClient } from '../dataverse-client.js'; // Helper function to create localized labels function createLocalizedLabel(text: string, languageCode: number = 1033) { 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" } }; } // AutoNumber format validation schema const autoNumberFormatSchema = z.string().refine((format) => { // Validate AutoNumber format placeholders const validPlaceholders = /^[^{}]*(\{(SEQNUM|RANDSTRING|DATETIMEUTC):[0-9]+\}[^{}]*)*$/; const hasValidPlaceholders = validPlaceholders.test(format); // Check RANDSTRING length constraints (1-6) const randStringMatches = format.match(/\{RANDSTRING:(\d+)\}/g); if (randStringMatches) { for (const match of randStringMatches) { const length = parseInt(match.match(/\{RANDSTRING:(\d+)\}/)![1]); if (length < 1 || length > 6) { return false; } } } // Check SEQNUM length constraints (minimum 1) const seqNumMatches = format.match(/\{SEQNUM:(\d+)\}/g); if (seqNumMatches) { for (const match of seqNumMatches) { const length = parseInt(match.match(/\{SEQNUM:(\d+)\}/)![1]); if (length < 1) { return false; } } } return hasValidPlaceholders; }, { message: "Invalid AutoNumber format. Use placeholders like {SEQNUM:4}, {RANDSTRING:3} (1-6), {DATETIMEUTC:yyyyMMdd}" }); // 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}`; } // Create AutoNumber column tool export function createAutoNumberColumnTool(server: McpServer, client: DataverseClient) { server.registerTool( 'create_autonumber_column', { title: 'Create AutoNumber Column', description: 'Creates a new AutoNumber column in a Dataverse table with specified format. AutoNumber columns automatically generate alphanumeric strings using sequential numbers, random strings, and datetime placeholders. Requires a solution context to be set first.', inputSchema: { entityLogicalName: z.string().describe('Logical name of the table to add the AutoNumber column to'), displayName: z.string().describe('Display name for the AutoNumber column (e.g., "Serial Number")'), schemaName: z.string().optional().describe('Schema name for the column (auto-generated if not provided)'), description: z.string().optional().describe('Description of the AutoNumber column'), autoNumberFormat: autoNumberFormatSchema.describe('AutoNumber format using placeholders like "PREFIX-{SEQNUM:4}-{RANDSTRING:3}-{DATETIMEUTC:yyyyMMdd}"'), requiredLevel: z.enum(['None', 'SystemRequired', 'ApplicationRequired', 'Recommended']).default('None').describe('Required level of the column'), maxLength: z.number().min(1).max(4000).default(100).describe('Maximum length for the column (default: 100, ensure enough room for format expansion)'), 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') } }, 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 schema name if not provided const schemaName = params.schemaName || generateColumnSchemaName(params.displayName, prefix); // Prepare the column metadata const columnMetadata = { "@odata.type": "Microsoft.Dynamics.CRM.StringAttributeMetadata", "AttributeType": "String", "SchemaName": schemaName, "DisplayName": createLocalizedLabel(params.displayName), "Format": "Text", // Required for AutoNumber columns "AutoNumberFormat": params.autoNumberFormat, "RequiredLevel": { "Value": params.requiredLevel, "CanBeChanged": true, "ManagedPropertyLogicalName": "canmodifyrequirementlevelsettings" }, "MaxLength": params.maxLength, "IsCustomAttribute": true, ...(params.description && { "Description": createLocalizedLabel(params.description) }), ...(params.isAuditEnabled !== undefined && { "IsAuditEnabled": { "Value": params.isAuditEnabled, "CanBeChanged": true, "ManagedPropertyLogicalName": "canmodifyauditsettings" } }), ...(params.isValidForAdvancedFind !== undefined && { "IsValidForAdvancedFind": params.isValidForAdvancedFind }), ...(params.isValidForCreate !== undefined && { "IsValidForCreate": params.isValidForCreate }), ...(params.isValidForUpdate !== undefined && { "IsValidForUpdate": params.isValidForUpdate }) }; const result = await client.postMetadata( `EntityDefinitions(LogicalName='${params.entityLogicalName}')/Attributes`, columnMetadata ); return { content: [ { type: "text", text: `Successfully created AutoNumber column '${schemaName}' with display name '${params.displayName}' in table '${params.entityLogicalName}'.\n\nAutoNumber Format: ${params.autoNumberFormat}\nMax Length: ${params.maxLength}\nRequired Level: ${params.requiredLevel}\n\nResponse: ${JSON.stringify(result, null, 2)}` } ] }; } catch (error: any) { // Provide specific error messages for common issues let errorMessage = `Error creating AutoNumber column: ${error instanceof Error ? error.message : 'Unknown error'}`; if (error.message?.includes('Invalid Argument')) { errorMessage += '\n\nTip: Check AutoNumber format syntax. Use {SEQNUM:length}, {RANDSTRING:1-6}, {DATETIMEUTC:format}'; } return { content: [ { type: "text", text: errorMessage } ], isError: true }; } } ); } // Update AutoNumber column format tool export function updateAutoNumberFormatTool(server: McpServer, client: DataverseClient) { server.registerTool( 'update_autonumber_format', { title: 'Update AutoNumber Format', description: 'Updates the AutoNumberFormat of an existing AutoNumber column. This changes how future values will be generated but does not affect existing records.', inputSchema: { entityLogicalName: z.string().describe('Logical name of the table containing the AutoNumber column'), columnLogicalName: z.string().describe('Logical name of the AutoNumber column to update'), autoNumberFormat: autoNumberFormatSchema.describe('New AutoNumber format using placeholders like "PREFIX-{SEQNUM:4}-{RANDSTRING:3}-{DATETIMEUTC:yyyyMMdd}"'), displayName: z.string().optional().describe('New display name for the column'), description: z.string().optional().describe('New description for the column'), maxLength: z.number().min(1).max(4000).optional().describe('New maximum length (ensure enough room for format expansion)') } }, async (params) => { try { // First, retrieve the current attribute definition const currentAttribute = await client.getMetadata( `EntityDefinitions(LogicalName='${params.entityLogicalName}')/Attributes(LogicalName='${params.columnLogicalName}')` ); // Create the updated attribute definition const updatedAttribute: any = { ...currentAttribute, "@odata.type": "Microsoft.Dynamics.CRM.StringAttributeMetadata", "AutoNumberFormat": params.autoNumberFormat }; // Add optional fields if provided if (params.displayName) { updatedAttribute.DisplayName = createLocalizedLabel(params.displayName); } if (params.description) { updatedAttribute.Description = createLocalizedLabel(params.description); } if (params.maxLength) { updatedAttribute.MaxLength = params.maxLength; } // Use PUT method with MSCRM.MergeLabels header await client.putMetadata( `EntityDefinitions(LogicalName='${params.entityLogicalName}')/Attributes(LogicalName='${params.columnLogicalName}')`, updatedAttribute, { 'MSCRM.MergeLabels': 'true' } ); return { content: [ { type: "text", text: `Successfully updated AutoNumber format for column '${params.columnLogicalName}' in table '${params.entityLogicalName}'.\n\nNew AutoNumber Format: ${params.autoNumberFormat}${params.displayName ? `\nNew Display Name: ${params.displayName}` : ''}${params.maxLength ? `\nNew Max Length: ${params.maxLength}` : ''}` } ] }; } catch (error: any) { let errorMessage = `Error updating AutoNumber format: ${error instanceof Error ? error.message : 'Unknown error'}`; if (error.message?.includes('Invalid Argument')) { errorMessage += '\n\nTip: Check AutoNumber format syntax. Use {SEQNUM:length}, {RANDSTRING:1-6}, {DATETIMEUTC:format}'; } return { content: [ { type: "text", text: errorMessage } ], isError: true }; } } ); } // Set AutoNumber seed tool export function setAutoNumberSeedTool(server: McpServer, client: DataverseClient) { server.registerTool( 'set_autonumber_seed', { title: 'Set AutoNumber Seed', description: 'Sets the seed value for an AutoNumber column\'s sequential segment using the SetAutoNumberSeed action. This controls the starting number for future records. Note: Seed values are environment-specific and not included in solutions.', inputSchema: { entityLogicalName: z.string().describe('Logical name of the table containing the AutoNumber column'), columnLogicalName: z.string().describe('Logical name of the AutoNumber column'), seedValue: z.number().int().min(1).describe('Next sequential number to use (e.g., 10000 to start from 10000)') } }, async (params) => { try { // Use the SetAutoNumberSeed action const actionData = { "EntityLogicalName": params.entityLogicalName, "AttributeLogicalName": params.columnLogicalName, "Value": params.seedValue }; await client.post('SetAutoNumberSeed', actionData); return { content: [ { type: "text", text: `Successfully set AutoNumber seed for column '${params.columnLogicalName}' in table '${params.entityLogicalName}'.\n\nSeed Value: ${params.seedValue}\n\nNote: Seed value only affects future records and is environment-specific (not included in solutions).` } ] }; } catch (error: any) { return { content: [ { type: "text", text: `Error setting AutoNumber seed: ${error instanceof Error ? error.message : 'Unknown error'}` } ], isError: true }; } } ); } // Get AutoNumber column info tool export function getAutoNumberColumnTool(server: McpServer, client: DataverseClient) { server.registerTool( 'get_autonumber_column', { title: 'Get AutoNumber Column', description: 'Retrieves detailed information about an AutoNumber column including its current format, properties, and configuration.', inputSchema: { entityLogicalName: z.string().describe('Logical name of the table'), columnLogicalName: z.string().describe('Logical name of the AutoNumber column to retrieve') } }, async (params) => { try { const column = await client.getMetadata( `EntityDefinitions(LogicalName='${params.entityLogicalName}')/Attributes(LogicalName='${params.columnLogicalName}')` ); // Check if it's an AutoNumber column if (column.AttributeType !== 'String' || !column.AutoNumberFormat) { return { content: [ { type: "text", text: `The specified column '${params.columnLogicalName}' is not an AutoNumber column.\n\nAttribute Type: ${column.AttributeType}\nHas AutoNumber Format: ${!!column.AutoNumberFormat}` } ], isError: true }; } const columnInfo = { logicalName: column.LogicalName, schemaName: column.SchemaName, displayName: column.DisplayName?.UserLocalizedLabel?.Label || column.DisplayName?.LocalizedLabels?.[0]?.Label, description: column.Description?.UserLocalizedLabel?.Label || column.Description?.LocalizedLabels?.[0]?.Label, autoNumberFormat: column.AutoNumberFormat, attributeType: column.AttributeType, format: column.Format, maxLength: column.MaxLength, requiredLevel: column.RequiredLevel?.Value, isAuditEnabled: column.IsAuditEnabled?.Value, isValidForAdvancedFind: column.IsValidForAdvancedFind?.Value, isValidForCreate: column.IsValidForCreate?.Value, isValidForUpdate: column.IsValidForUpdate?.Value, isCustomAttribute: column.IsCustomAttribute?.Value, isManaged: column.IsManaged?.Value, metadataId: column.MetadataId }; return { content: [ { type: "text", text: `AutoNumber column information for '${params.columnLogicalName}' in table '${params.entityLogicalName}':\n\n${JSON.stringify(columnInfo, null, 2)}` } ] }; } catch (error: any) { return { content: [ { type: "text", text: `Error retrieving AutoNumber column: ${error instanceof Error ? error.message : 'Unknown error'}` } ], isError: true }; } } ); } // List AutoNumber columns tool export function listAutoNumberColumnsTool(server: McpServer, client: DataverseClient) { server.registerTool( 'list_autonumber_columns', { title: 'List AutoNumber Columns', description: 'Lists all AutoNumber columns in a specific table or across all tables in the environment. Helps identify existing AutoNumber implementations.', inputSchema: { entityLogicalName: z.string().optional().describe('Logical name of specific table (if not provided, searches all tables)'), customOnly: z.boolean().default(true).describe('Whether to list only custom AutoNumber columns'), includeManaged: z.boolean().default(false).describe('Whether to include managed AutoNumber columns') } }, async (params) => { try { let queryParams: Record<string, any> = { $select: "LogicalName,SchemaName,DisplayName,AutoNumberFormat,MaxLength,RequiredLevel,IsCustomAttribute,IsManaged,EntityLogicalName" }; let filters: string[] = [ "AttributeType eq Microsoft.Dynamics.CRM.AttributeTypeCode'String'", "AutoNumberFormat ne null" ]; if (!params.includeManaged) { filters.push("IsManaged eq false"); } if (params.customOnly) { filters.push("IsCustomAttribute eq true"); } queryParams.$filter = filters.join(" and "); let endpoint: string; if (params.entityLogicalName) { endpoint = `EntityDefinitions(LogicalName='${params.entityLogicalName}')/Attributes`; } else { endpoint = `AttributeMetadata`; } const result = await client.getMetadata(endpoint, queryParams); const columns = result.value || []; const autoNumberColumns = columns.map((column: any) => ({ entityLogicalName: column.EntityLogicalName, logicalName: column.LogicalName, schemaName: column.SchemaName, displayName: column.DisplayName?.UserLocalizedLabel?.Label || column.DisplayName?.LocalizedLabels?.[0]?.Label, autoNumberFormat: column.AutoNumberFormat, maxLength: column.MaxLength, requiredLevel: column.RequiredLevel?.Value, isCustomAttribute: column.IsCustomAttribute?.Value, isManaged: column.IsManaged?.Value })); return { content: [ { type: "text", text: `Found ${autoNumberColumns.length} AutoNumber column(s)${params.entityLogicalName ? ` in table '${params.entityLogicalName}'` : ''}:\n\n${JSON.stringify(autoNumberColumns, null, 2)}` } ] }; } catch (error: any) { return { content: [ { type: "text", text: `Error listing AutoNumber columns: ${error instanceof Error ? error.message : 'Unknown error'}` } ], isError: true }; } } ); } // Convert existing column to AutoNumber tool export function convertToAutoNumberTool(server: McpServer, client: DataverseClient) { server.registerTool( 'convert_to_autonumber', { title: 'Convert to AutoNumber', description: 'Converts an existing text column to an AutoNumber column by adding an AutoNumberFormat. The column must be a String type with Text format and should be empty or contain compatible data.', inputSchema: { entityLogicalName: z.string().describe('Logical name of the table containing the column'), columnLogicalName: z.string().describe('Logical name of the existing text column to convert'), autoNumberFormat: autoNumberFormatSchema.describe('AutoNumber format to apply using placeholders like "PREFIX-{SEQNUM:4}-{RANDSTRING:3}"'), maxLength: z.number().min(1).max(4000).optional().describe('New maximum length if needed (ensure enough room for format expansion)') } }, async (params) => { try { // First, get the current column to validate it can be converted const currentColumn = await client.getMetadata( `EntityDefinitions(LogicalName='${params.entityLogicalName}')/Attributes(LogicalName='${params.columnLogicalName}')` ); // Validate the column can be converted if (currentColumn.AttributeType !== 'String') { return { content: [ { type: "text", text: `Cannot convert column to AutoNumber. Only String type columns can be converted.\n\nCurrent Attribute Type: ${currentColumn.AttributeType}` } ], isError: true }; } if (currentColumn.Format !== 'Text') { return { content: [ { type: "text", text: `Cannot convert column to AutoNumber. Only Text format columns can be converted.\n\nCurrent Format: ${currentColumn.Format}` } ], isError: true }; } if (currentColumn.AutoNumberFormat) { return { content: [ { type: "text", text: `Column is already an AutoNumber column.\n\nCurrent AutoNumber Format: ${currentColumn.AutoNumberFormat}` } ], isError: true }; } // Prepare the update payload const updateData: any = { ...currentColumn, "@odata.type": "Microsoft.Dynamics.CRM.StringAttributeMetadata", "AutoNumberFormat": params.autoNumberFormat }; if (params.maxLength) { updateData.MaxLength = params.maxLength; } // Use PUT method with MSCRM.MergeLabels header await client.putMetadata( `EntityDefinitions(LogicalName='${params.entityLogicalName}')/Attributes(LogicalName='${params.columnLogicalName}')`, updateData, { 'MSCRM.MergeLabels': 'true' } ); return { content: [ { type: "text", text: `Successfully converted column '${params.columnLogicalName}' to AutoNumber in table '${params.entityLogicalName}'.\n\nAutoNumber Format: ${params.autoNumberFormat}\nPrevious Format: ${currentColumn.Format}${params.maxLength ? `\nNew Max Length: ${params.maxLength}` : ''}\n\nWarning: Existing data in the column will remain unchanged. New records will use the AutoNumber format.` } ] }; } catch (error: any) { return { content: [ { type: "text", text: `Error converting column to AutoNumber: ${error instanceof Error ? error.message : 'Unknown error'}` } ], isError: true }; } } ); }

Implementation Reference

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/mwhesse/dataverse-mcp'

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