Skip to main content
Glama

Dataverse MCP Server

by mwhesse
table-tools.ts17.9 kB
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { DataverseClient } from "../dataverse-client.js"; import { EntityMetadata, ODataResponse, LocalizedLabel } from "../types.js"; // Helper function to create localized 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" } }; } // Helper function to generate logical name from display name and prefix function generateLogicalName(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 generateSchemaName(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}`; } // Helper function to generate display collection name from display name function generateDisplayCollectionName(displayName: string): string { // Simple pluralization - add 's' if it doesn't end with 's', 'es' if it ends with 's', 'x', 'z', 'ch', 'sh' const name = displayName.trim(); if (name.endsWith('s') || name.endsWith('x') || name.endsWith('z') || name.endsWith('ch') || name.endsWith('sh')) { return `${name}es`; } else if (name.endsWith('y') && name.length > 1 && !'aeiou'.includes(name[name.length - 2])) { return `${name.slice(0, -1)}ies`; } else { return `${name}s`; } } export function createTableTool(server: McpServer, client: DataverseClient) { server.registerTool( "create_dataverse_table", { title: "Create Dataverse Table", description: "Creates a new custom table in Dataverse with the specified configuration. Use this when you need to create a new entity to store business data. Requires a solution context to be set first.", inputSchema: { displayName: z.string().describe("Display name for the table (e.g., 'Test Table')"), description: z.string().optional().describe("Description of the table"), ownershipType: z.enum(["UserOwned", "OrganizationOwned"]).default("UserOwned").describe("Ownership type of the table"), hasActivities: z.boolean().default(false).describe("Whether the table can have activities"), hasNotes: z.boolean().default(false).describe("Whether the table can have notes"), isAuditEnabled: z.boolean().default(false).describe("Whether auditing is enabled"), isDuplicateDetectionEnabled: z.boolean().default(false).describe("Whether duplicate detection is enabled"), isValidForQueue: z.boolean().default(false).describe("Whether records can be added to queues"), isConnectionsEnabled: z.boolean().default(false).describe("Whether connections are enabled"), isMailMergeEnabled: z.boolean().default(false).describe("Whether mail merge is enabled"), isDocumentManagementEnabled: z.boolean().default(false).describe("Whether document management is enabled"), primaryNameAttribute: z.string().optional().describe("Logical name of the primary name attribute (will be auto-generated if not provided)"), primaryNameAutoNumberFormat: z.string().optional().describe("AutoNumber format for the primary name column using placeholders like 'PREFIX-{SEQNUM:4}-{RANDSTRING:3}-{DATETIMEUTC:yyyyMMdd}'. If specified, the primary name column will be created as an AutoNumber column.") } }, 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, schema name, and display collection name const logicalName = generateLogicalName(params.displayName, prefix); const schemaName = generateSchemaName(params.displayName, prefix); const displayCollectionName = generateDisplayCollectionName(params.displayName); const ownershipTypeValue = params.ownershipType === "UserOwned" ? "UserOwned" : "OrganizationOwned"; // Generate primary name attribute logical name if not provided const primaryNameLogicalName = params.primaryNameAttribute || `${logicalName}_name`; const primaryNameSchemaName = generateSchemaName(params.primaryNameAttribute || `${params.displayName} Name`, prefix); const entityDefinition = { "@odata.type": "Microsoft.Dynamics.CRM.EntityMetadata", LogicalName: logicalName, SchemaName: schemaName, DisplayName: createLocalizedLabel(params.displayName), DisplayCollectionName: createLocalizedLabel(displayCollectionName), Description: params.description ? createLocalizedLabel(params.description) : undefined, OwnershipType: ownershipTypeValue, HasActivities: params.hasActivities, HasNotes: params.hasNotes, IsActivity: false, IsCustomEntity: true, Attributes: [ { "@odata.type": "Microsoft.Dynamics.CRM.StringAttributeMetadata", LogicalName: primaryNameLogicalName, SchemaName: primaryNameSchemaName, DisplayName: createLocalizedLabel("Name"), Description: createLocalizedLabel(params.primaryNameAutoNumberFormat ? "Primary name attribute (AutoNumber)" : "Primary name attribute"), RequiredLevel: { Value: "ApplicationRequired", CanBeChanged: false, ManagedPropertyLogicalName: "canmodifyrequirementlevelsettings" }, MaxLength: params.primaryNameAutoNumberFormat ? 200 : 100, // Increase max length for AutoNumber to allow for format expansion Format: "Text", IsPrimaryName: true, IsCustomAttribute: true, ...(params.primaryNameAutoNumberFormat && { AutoNumberFormat: params.primaryNameAutoNumberFormat }) } ] }; const result = await client.postMetadata("EntityDefinitions", entityDefinition); return { content: [ { type: "text", text: `Successfully created table '${logicalName}' with display name '${params.displayName}'.\n\nGenerated names:\n- Logical Name: ${logicalName}\n- Schema Name: ${schemaName}\n- Display Collection Name: ${displayCollectionName}\n- Primary Name Attribute: ${primaryNameLogicalName}${params.primaryNameAutoNumberFormat ? `\n- AutoNumber Format: ${params.primaryNameAutoNumberFormat}` : ''}\n\nResponse: ${JSON.stringify(result, null, 2)}` } ] }; } catch (error) { return { content: [ { type: "text", text: `Error creating table: ${error instanceof Error ? error.message : 'Unknown error'}` } ], isError: true }; } } ); } export function getTableTool(server: McpServer, client: DataverseClient) { server.registerTool( "get_dataverse_table", { title: "Get Dataverse Table", description: "Retrieves detailed information about a specific Dataverse table including its metadata, properties, and configuration. Use this to inspect table definitions and understand table structure.", inputSchema: { logicalName: z.string().describe("Logical name of the table to retrieve") } }, async (params) => { try { const result = await client.getMetadata<EntityMetadata>( `EntityDefinitions(LogicalName='${params.logicalName}')` ); return { content: [ { type: "text", text: `Table information for '${params.logicalName}':\n\n${JSON.stringify(result, null, 2)}` } ] }; } catch (error) { return { content: [ { type: "text", text: `Error retrieving table: ${error instanceof Error ? error.message : 'Unknown error'}` } ], isError: true }; } } ); } export function updateTableTool(server: McpServer, client: DataverseClient) { server.registerTool( "update_dataverse_table", { title: "Update Dataverse Table", description: "Updates the properties and configuration of an existing Dataverse table. Use this to modify table settings like display names, descriptions, or feature enablement (activities, notes, auditing, etc.). Changes are published automatically.", inputSchema: { logicalName: z.string().describe("Logical name of the table to update"), displayName: z.string().optional().describe("New display name for the table"), displayCollectionName: z.string().optional().describe("New display collection name for the table"), description: z.string().optional().describe("New description of the table"), hasActivities: z.boolean().optional().describe("Whether the table can have activities"), hasNotes: z.boolean().optional().describe("Whether the table can have notes"), isAuditEnabled: z.boolean().optional().describe("Whether auditing is enabled"), isDuplicateDetectionEnabled: z.boolean().optional().describe("Whether duplicate detection is enabled"), isValidForQueue: z.boolean().optional().describe("Whether records can be added to queues"), isConnectionsEnabled: z.boolean().optional().describe("Whether connections are enabled"), isMailMergeEnabled: z.boolean().optional().describe("Whether mail merge is enabled"), isDocumentManagementEnabled: z.boolean().optional().describe("Whether document management is enabled") } }, async (params) => { try { // First, retrieve the current entity definition const currentEntity = await client.getMetadata<EntityMetadata>( `EntityDefinitions(LogicalName='${params.logicalName}')` ); // Create the updated entity definition by merging current with new values const updatedEntity: any = { ...currentEntity, "@odata.type": "Microsoft.Dynamics.CRM.EntityMetadata" }; // Update only the specified properties if (params.displayName) { updatedEntity.DisplayName = createLocalizedLabel(params.displayName); } if (params.displayCollectionName) { updatedEntity.DisplayCollectionName = createLocalizedLabel(params.displayCollectionName); } if (params.description) { updatedEntity.Description = createLocalizedLabel(params.description); } if (params.hasActivities !== undefined) { updatedEntity.HasActivities = params.hasActivities; } if (params.hasNotes !== undefined) { updatedEntity.HasNotes = params.hasNotes; } if (params.isAuditEnabled !== undefined) { updatedEntity.IsAuditEnabled = { Value: params.isAuditEnabled, CanBeChanged: true, ManagedPropertyLogicalName: "canmodifyauditsettings" }; } if (params.isDuplicateDetectionEnabled !== undefined) { updatedEntity.IsDuplicateDetectionEnabled = { Value: params.isDuplicateDetectionEnabled, CanBeChanged: true, ManagedPropertyLogicalName: "canmodifyduplicatedetectionsettings" }; } if (params.isValidForQueue !== undefined) { updatedEntity.IsValidForQueue = { Value: params.isValidForQueue, CanBeChanged: true, ManagedPropertyLogicalName: "canmodifyqueuesettings" }; } if (params.isConnectionsEnabled !== undefined) { updatedEntity.IsConnectionsEnabled = { Value: params.isConnectionsEnabled, CanBeChanged: true, ManagedPropertyLogicalName: "canmodifyconnectionsettings" }; } if (params.isMailMergeEnabled !== undefined) { updatedEntity.IsMailMergeEnabled = { Value: params.isMailMergeEnabled, CanBeChanged: true, ManagedPropertyLogicalName: "canmodifymailmergesettings" }; } if (params.isDocumentManagementEnabled !== undefined) { updatedEntity.IsDocumentManagementEnabled = params.isDocumentManagementEnabled; } // Use PUT method with MSCRM.MergeLabels header as per Microsoft documentation await client.putMetadata(`EntityDefinitions(LogicalName='${params.logicalName}')`, updatedEntity, { 'MSCRM.MergeLabels': 'true' }); // Publish the changes as required by the API await client.callAction('PublishXml', { ParameterXml: `<importexportxml><entities><entity>${params.logicalName}</entity></entities></importexportxml>` }); return { content: [ { type: "text", text: `Successfully updated table '${params.logicalName}' and published changes.` } ] }; } catch (error) { return { content: [ { type: "text", text: `Error updating table: ${error instanceof Error ? error.message : 'Unknown error'}` } ], isError: true }; } } ); } export function deleteTableTool(server: McpServer, client: DataverseClient) { server.registerTool( "delete_dataverse_table", { title: "Delete Dataverse Table", description: "Permanently deletes a custom table from Dataverse. WARNING: This action cannot be undone and will remove all data in the table. Use with extreme caution and only for tables that are no longer needed.", inputSchema: { logicalName: z.string().describe("Logical name of the table to delete") } }, async (params) => { try { await client.deleteMetadata(`EntityDefinitions(LogicalName='${params.logicalName}')`); return { content: [ { type: "text", text: `Successfully deleted table '${params.logicalName}'.` } ] }; } catch (error) { return { content: [ { type: "text", text: `Error deleting table: ${error instanceof Error ? error.message : 'Unknown error'}` } ], isError: true }; } } ); } export function listTablesTool(server: McpServer, client: DataverseClient) { server.registerTool( "list_dataverse_tables", { title: "List Dataverse Tables", description: "Retrieves a list of tables in the Dataverse environment with filtering options. Use this to discover available tables, find custom tables, or get an overview of the data model. Supports filtering by custom/system tables and managed/unmanaged status.", inputSchema: { customOnly: z.boolean().default(false).describe("Whether to list only custom tables"), includeManaged: z.boolean().default(false).describe("Whether to include managed tables"), filter: z.string().optional().describe("OData filter expression") } }, async (params) => { try { let queryParams: Record<string, any> = { $select: "LogicalName,DisplayName,DisplayCollectionName,IsCustomEntity,IsManaged,OwnershipType,HasActivities,HasNotes" }; let filters: string[] = []; if (params.customOnly) { filters.push("IsCustomEntity eq true"); } if (!params.includeManaged) { filters.push("IsManaged eq false"); } if (params.filter) { filters.push(params.filter); } if (filters.length > 0) { queryParams.$filter = filters.join(" and "); } const result = await client.getMetadata<ODataResponse<EntityMetadata>>("EntityDefinitions", queryParams); const tableList = result.value.map(entity => ({ logicalName: entity.LogicalName, displayName: entity.DisplayName?.UserLocalizedLabel?.Label || entity.LogicalName, displayCollectionName: entity.DisplayCollectionName?.UserLocalizedLabel?.Label || "", isCustom: entity.IsCustomEntity, isManaged: entity.IsManaged, ownershipType: entity.OwnershipType, hasActivities: entity.HasActivities, hasNotes: entity.HasNotes })); return { content: [ { type: "text", text: `Found ${tableList.length} tables:\n\n${JSON.stringify(tableList, null, 2)}` } ] }; } catch (error) { return { content: [ { type: "text", text: `Error listing tables: ${error instanceof Error ? error.message : 'Unknown error'}` } ], isError: true }; } } ); }

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