Skip to main content
Glama
mwhesse

Dataverse MCP Server

by mwhesse

export_solution_schema

Export Dataverse solution schema to JSON for documenting data models, generating diagrams, and analyzing table structures with customizable filtering options.

Instructions

Exports a comprehensive JSON schema of Dataverse tables, columns, relationships, and option sets. Use this to document your data model, generate diagrams, or analyze solution structure. Supports filtering by prefixes, system/custom components, and specific tables.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
customizationPrefixesNoList of customization prefixes to include (e.g., ["new", "xyz", "its"]). If not provided and prefixOnly is true, uses solution context prefix
excludeColumnPrefixesNoList of column prefixes to exclude from export (default: ["adx_", "msa_", "msdyn_", "mspp_"])
includeAllSystemTablesNoWhether to include all system tables in the export
includeSystemColumnsNoWhether to include system columns in the export
includeSystemOptionSetsNoWhether to include system option sets in the export
includeSystemRelationshipsNoWhether to include system (non-custom) relationships in the export
outputPathNoPath where to save the schema JSON file (default: schema-export.json)
prefixOnlyNoWhether to export only tables that match the solution customization prefix (deprecated - use customizationPrefixes instead)
prettifyNoWhether to format the JSON output for readability
systemTablesToIncludeNoList of system tables to include when includeAllSystemTables is false (default: contact, account)

Implementation Reference

  • The main handler function that implements the core logic of the 'export_solution_schema' tool. It fetches comprehensive metadata about tables, columns, global option sets, and relationships from the Dataverse client based on the provided parameters (filters for system/custom components, prefixes, etc.), structures it into a SolutionSchema object, and writes it as prettified JSON to the specified output path. Includes extensive logging and filtering logic.
    export async function exportSolutionSchema( client: DataverseClient, args: z.infer<typeof exportSolutionSchemaSchema> ): Promise<string> { try { const { outputPath = 'schema-export.json', includeAllSystemTables, includeSystemColumns, includeSystemOptionSets, includeSystemRelationships, prefixOnly, customizationPrefixes, systemTablesToInclude = ['contact', 'account'], excludeColumnPrefixes = ['adx_', 'msa_', 'msdyn_', 'mspp_'], prettify } = args; // Collect debug messages to return in response const debugMessages: string[] = []; const log = (message: string) => { console.log(message); debugMessages.push(message); }; // Get solution context if available const solutionContext = client.getSolutionContext(); log('Starting schema export...'); // Initialize the schema object const schema: SolutionSchema = { metadata: { exportedAt: new Date().toISOString(), includeAllSystemTables, includeSystemColumns, includeSystemOptionSets, includeSystemRelationships, prefixOnly, customizationPrefixes, systemTablesToInclude, excludeColumnPrefixes }, tables: [], globalOptionSets: [], relationships: [] }; // Add solution context to metadata if available if (solutionContext) { schema.metadata.solutionUniqueName = solutionContext.solutionUniqueName; schema.metadata.solutionDisplayName = solutionContext.solutionDisplayName; schema.metadata.publisherPrefix = solutionContext.customizationPrefix; } // Export global option sets log('Exporting global option sets...'); try { // Get all option sets (filtering is not supported on GlobalOptionSetDefinitions) log('Retrieving all global option sets...'); const optionSetsListResponse = await client.getMetadata(`GlobalOptionSetDefinitions`); log(`Found ${optionSetsListResponse.value.length} total option sets`); for (const optionSetInfo of optionSetsListResponse.value) { log(`Processing option set: ${optionSetInfo.Name} (IsCustom: ${optionSetInfo.IsCustomOptionSet}, IsManaged: ${optionSetInfo.IsManaged})`); // Apply system/custom filtering if (!includeSystemOptionSets && !optionSetInfo.IsCustomOptionSet) { log(`Skipping system option set ${optionSetInfo.Name}`); continue; } // Apply prefix filtering if enabled const prefixesToCheck = customizationPrefixes || (prefixOnly && solutionContext?.customizationPrefix ? [solutionContext.customizationPrefix] : []); if (prefixesToCheck.length > 0) { const optionSetName = optionSetInfo.Name.toLowerCase(); const matchesPrefix = prefixesToCheck.some(prefix => optionSetName.startsWith(prefix.toLowerCase() + '_')); if (!matchesPrefix) { log(`Skipping option set ${optionSetInfo.Name} (doesn't match any prefix: ${prefixesToCheck.join(', ')})`); continue; } log(`Including option set ${optionSetInfo.Name} (matches prefix filter)`); } log(`Exporting option set: ${optionSetInfo.Name}`); try { // Get detailed option set information including options // Try multiple approaches to get the full option set metadata let optionSet = null; let optionsFound = false; // Approach 1: Try with MetadataId try { log(`Trying to get option set ${optionSetInfo.Name} using MetadataId ${optionSetInfo.MetadataId}`); const response1 = await client.getMetadata(`GlobalOptionSetDefinitions(${optionSetInfo.MetadataId})`); optionSet = response1; if (optionSet.Options && optionSet.Options.length > 0) { optionsFound = true; log(`Successfully got options using MetadataId approach`); } } catch (error1) { log(`MetadataId approach failed: ${error1 instanceof Error ? error1.message : 'Unknown error'}`); } // Approach 2: Try with Name filter if first approach didn't get options if (!optionsFound) { try { log(`Trying to get option set ${optionSetInfo.Name} using Name filter`); const response2 = await client.getMetadata(`GlobalOptionSetDefinitions?$filter=Name eq '${optionSetInfo.Name}'`); if (response2.value && response2.value.length > 0) { const foundOptionSet = response2.value[0]; if (foundOptionSet.Options && foundOptionSet.Options.length > 0) { optionSet = foundOptionSet; optionsFound = true; log(`Successfully got options using Name filter approach`); } } } catch (error2) { log(`Name filter approach failed: ${error2 instanceof Error ? error2.message : 'Unknown error'}`); } } // If we still don't have an option set, use what we have from the first approach if (!optionSet) { log(`No option set retrieved, skipping ${optionSetInfo.Name}`); continue; } log(`Retrieved detailed info for ${optionSet.Name}, has ${optionSet.Options?.length || 0} options`); const optionSetSchema: OptionSetSchema = { name: optionSet.Name, displayName: optionSet.DisplayName?.UserLocalizedLabel?.Label || optionSet.Name, description: optionSet.Description?.UserLocalizedLabel?.Label, isGlobal: true, isCustomOptionSet: optionSet.IsCustomOptionSet, isManaged: optionSet.IsManaged, options: [] }; // Export options for this option set if (optionSet.Options && optionSet.Options.length > 0) { for (const option of optionSet.Options) { const optionSchema: OptionSchema = { value: option.Value, label: option.Label?.UserLocalizedLabel?.Label || `Option ${option.Value}`, description: option.Description?.UserLocalizedLabel?.Label, color: option.Color }; optionSetSchema.options.push(optionSchema); } log(`Added ${optionSetSchema.options.length} options to ${optionSet.Name}`); } schema.globalOptionSets.push(optionSetSchema); log(`Successfully added option set ${optionSet.Name} to schema`); } catch (optionSetError) { log(`Error processing option set ${optionSetInfo.Name}: ${optionSetError instanceof Error ? optionSetError.message : 'Unknown error'}`); } } log(`Completed option set export. Total exported: ${schema.globalOptionSets.length}`); } catch (error) { log(`Warning: Could not export global option sets: ${error instanceof Error ? error.message : 'Unknown error'}`); console.error('Full error:', error); } // Export tables log('Exporting tables...'); // Build table filter based on system table inclusion logic let tablesFilter = ''; if (includeAllSystemTables) { // Include all tables (both custom and system) tablesFilter = ''; log('Including all system tables (no filter applied)'); } else if (systemTablesToInclude.length > 0) { // Include custom tables and specified system tables only const systemTableFilter = systemTablesToInclude.map(table => `LogicalName eq '${table}'`).join(' or '); tablesFilter = `IsCustomEntity eq true or (${systemTableFilter})`; log(`Using filter: ${tablesFilter}`); } else { // Only custom tables tablesFilter = 'IsCustomEntity eq true'; log(`Using filter: ${tablesFilter}`); } const tablesUrl = tablesFilter ? `EntityDefinitions?$filter=${tablesFilter}&$select=LogicalName,DisplayName,DisplayCollectionName,Description,SchemaName,OwnershipType,HasActivities,HasNotes,IsAuditEnabled,IsDuplicateDetectionEnabled,IsValidForQueue,IsConnectionsEnabled,IsMailMergeEnabled,IsDocumentManagementEnabled,IsCustomEntity,IsManaged,PrimaryNameAttribute,PrimaryIdAttribute` : `EntityDefinitions?$select=LogicalName,DisplayName,DisplayCollectionName,Description,SchemaName,OwnershipType,HasActivities,HasNotes,IsAuditEnabled,IsDuplicateDetectionEnabled,IsValidForQueue,IsConnectionsEnabled,IsMailMergeEnabled,IsDocumentManagementEnabled,IsCustomEntity,IsManaged,PrimaryNameAttribute,PrimaryIdAttribute`; const tablesResponse = await client.getMetadata(tablesUrl); for (const table of tablesResponse.value) { // Apply prefix filtering if enabled const prefixesToCheck = customizationPrefixes || (prefixOnly && solutionContext?.customizationPrefix ? [solutionContext.customizationPrefix] : []); if (table.IsCustomEntity) { // Custom table - apply prefix filtering if specified if (prefixesToCheck.length > 0) { const tableName = table.LogicalName.toLowerCase(); const matchesPrefix = prefixesToCheck.some(prefix => tableName.startsWith(prefix.toLowerCase() + '_')); if (!matchesPrefix) { log(`Skipping custom table ${table.LogicalName} (doesn't match any prefix: ${prefixesToCheck.join(', ')})`); continue; } log(`Including custom table ${table.LogicalName} (matches prefix filter)`); } else { log(`Including custom table ${table.LogicalName} (no prefix filter)`); } } else { // System table - check inclusion logic if (includeAllSystemTables) { log(`Including system table ${table.LogicalName} (includeAllSystemTables is true)`); } else { // Should only be here if it was explicitly included in the filter // Double-check it's in our allowed list (defensive programming) if (!systemTablesToInclude.includes(table.LogicalName.toLowerCase())) { log(`Skipping system table ${table.LogicalName} (not in systemTablesToInclude list)`); continue; } log(`Including system table ${table.LogicalName} (in systemTablesToInclude list)`); } } log(`Exporting table: ${table.LogicalName}`); const tableSchema: TableSchema = { logicalName: table.LogicalName, displayName: table.DisplayName?.UserLocalizedLabel?.Label || table.LogicalName, displayCollectionName: table.DisplayCollectionName?.UserLocalizedLabel?.Label || table.LogicalName, description: table.Description?.UserLocalizedLabel?.Label, schemaName: table.SchemaName, ownershipType: table.OwnershipType === 1 ? 'UserOwned' : 'OrganizationOwned', hasActivities: table.HasActivities, hasNotes: table.HasNotes, isAuditEnabled: table.IsAuditEnabled, isDuplicateDetectionEnabled: table.IsDuplicateDetectionEnabled, isValidForQueue: table.IsValidForQueue, isConnectionsEnabled: table.IsConnectionsEnabled, isMailMergeEnabled: table.IsMailMergeEnabled, isDocumentManagementEnabled: table.IsDocumentManagementEnabled, isCustomEntity: table.IsCustomEntity, isManaged: table.IsManaged, primaryNameAttribute: table.PrimaryNameAttribute, primaryIdAttribute: table.PrimaryIdAttribute, columns: [] }; // Get relationship information for this table to populate navigation properties for lookup columns let relationshipMap: Map<string, string> = new Map(); // Maps referencing attribute to navigation property name try { const relationshipsUrl = `EntityDefinitions(LogicalName='${table.LogicalName}')/ManyToOneRelationships?$select=ReferencingAttribute,ReferencingEntityNavigationPropertyName`; log(` Querying ManyToOneRelationships for ${table.LogicalName}: ${relationshipsUrl}`); const relationshipsResponse = await client.getMetadata(relationshipsUrl); log(` Retrieved ${relationshipsResponse.value?.length || 0} ManyToOneRelationships for ${table.LogicalName}`); for (const relationship of relationshipsResponse.value) { log(` Processing relationship: ReferencingAttribute=${relationship.ReferencingAttribute}, NavigationProperty=${relationship.ReferencingEntityNavigationPropertyName}`); if (relationship.ReferencingAttribute && relationship.ReferencingEntityNavigationPropertyName) { relationshipMap.set(relationship.ReferencingAttribute, relationship.ReferencingEntityNavigationPropertyName); log(` Added to map: ${relationship.ReferencingAttribute} -> ${relationship.ReferencingEntityNavigationPropertyName}`); } } if (relationshipMap.size > 0) { log(` Found ${relationshipMap.size} navigation properties for lookup columns`); } else { log(` No navigation properties found for ${table.LogicalName}`); } } catch (relationshipError) { log(` Warning: Could not get relationship info for navigation properties: ${relationshipError instanceof Error ? relationshipError.message : 'Unknown error'}`); } // Export columns for this table - Remove $select to get all properties including Targets // Always include Primary Key columns (IsPrimaryId eq true) even when system columns are excluded const columnsFilter = includeSystemColumns ? '' : 'IsCustomAttribute eq true or IsPrimaryId eq true'; const columnsUrl = columnsFilter ? `EntityDefinitions(LogicalName='${table.LogicalName}')/Attributes?$filter=${columnsFilter}` : `EntityDefinitions(LogicalName='${table.LogicalName}')/Attributes`; const columnsResponse = await client.getMetadata(columnsUrl); for (const column of columnsResponse.value) { // Apply column prefix exclusion filtering, but never exclude Primary Key columns const columnName = column.LogicalName.toLowerCase(); const shouldExcludeColumn = !column.IsPrimaryId && excludeColumnPrefixes.some(prefix => columnName.startsWith(prefix.toLowerCase()) ); if (shouldExcludeColumn) { log(` Excluding column ${column.LogicalName} (matches exclusion prefix)`); continue; } const columnSchema: ColumnSchema = { logicalName: column.LogicalName, displayName: column.DisplayName?.UserLocalizedLabel?.Label || column.LogicalName, description: column.Description?.UserLocalizedLabel?.Label, schemaName: column.SchemaName, attributeType: column.AttributeType, requiredLevel: column.RequiredLevel?.Value || 'None', isAuditEnabled: column.IsAuditEnabled, isValidForAdvancedFind: column.IsValidForAdvancedFind, isValidForCreate: column.IsValidForCreate, isValidForUpdate: column.IsValidForUpdate, isCustomAttribute: column.IsCustomAttribute, isManaged: column.IsManaged, isPrimaryId: column.IsPrimaryId, isPrimaryName: column.IsPrimaryName }; // Add targets and navigation property for Lookup columns if (column.AttributeType === 'Lookup') { // Add navigation property from relationship map const navigationProperty = relationshipMap.get(column.LogicalName); if (navigationProperty) { columnSchema.navigationProperty = navigationProperty; } // Approach 1: Direct Targets property if (column.Targets && Array.isArray(column.Targets)) { columnSchema.targets = column.Targets; log(` Found lookup column ${column.LogicalName} with targets: ${column.Targets.join(', ')}${navigationProperty ? `, navigationProperty: ${navigationProperty}` : ''}`); } else { // Approach 2: Try to get relationship information try { // Look for relationships where this column is the referencing attribute const relationshipsUrl = `EntityDefinitions(LogicalName='${table.LogicalName}')/OneToManyRelationships?$filter=ReferencingAttribute eq '${column.LogicalName}'`; const relationshipsResponse = await client.getMetadata(relationshipsUrl); if (relationshipsResponse.value && relationshipsResponse.value.length > 0) { const targets = relationshipsResponse.value.map((rel: any) => rel.ReferencedEntity); if (targets.length > 0) { columnSchema.targets = targets; log(` Found lookup column ${column.LogicalName} with targets from relationships: ${targets.join(', ')}${navigationProperty ? `, navigationProperty: ${navigationProperty}` : ''}`); } } else { log(` Lookup column ${column.LogicalName} has no relationship information${navigationProperty ? `, but has navigationProperty: ${navigationProperty}` : ''}`); } } catch (relationshipError) { log(` Could not get relationship info for ${column.LogicalName}: ${relationshipError instanceof Error ? relationshipError.message : 'Unknown error'}${navigationProperty ? `, but has navigationProperty: ${navigationProperty}` : ''}`); } } } // Add other type-specific properties that are now available if (column.MaxLength !== undefined) { columnSchema.maxLength = column.MaxLength; } if (column.Format !== undefined) { columnSchema.format = column.Format; } if (column.MinValue !== undefined) { columnSchema.minValue = column.MinValue; } if (column.MaxValue !== undefined) { columnSchema.maxValue = column.MaxValue; } if (column.Precision !== undefined) { columnSchema.precision = column.Precision; } if (column.DateTimeFormat !== undefined) { columnSchema.dateTimeFormat = column.DateTimeFormat; } if (column.DefaultValue !== undefined) { columnSchema.defaultValue = column.DefaultValue; } tableSchema.columns.push(columnSchema); } schema.tables.push(tableSchema); } // Export relationships - only include relationships between exported tables log('Exporting relationships...'); try { const exportedTableNames = schema.tables.map(t => t.logicalName); const exportedTableSet = new Set(exportedTableNames); log(`Filtering relationships to only include those between exported tables: ${exportedTableNames.join(', ')}`); // Use a more efficient approach to avoid 414 URI Too Long errors // Fetch relationships with minimal server-side filtering and apply comprehensive client-side filtering // Export OneToMany relationships using cast syntax log('Exporting OneToMany relationships...'); const oneToManyFilters = []; // Only apply system/custom filtering on server side to avoid URI length issues if (!includeSystemRelationships) { oneToManyFilters.push("IsCustomRelationship eq true"); } const oneToManyParams: Record<string, any> = { $select: "SchemaName,RelationshipType,IsCustomRelationship,IsManaged,IsValidForAdvancedFind,ReferencedEntity,ReferencingEntity,ReferencingAttribute,ReferencedEntityNavigationPropertyName,ReferencingEntityNavigationPropertyName,IsHierarchical,CascadeConfiguration" }; if (oneToManyFilters.length > 0) { oneToManyParams.$filter = oneToManyFilters.join(" and "); } const oneToManyResult = await client.getMetadata( "RelationshipDefinitions/Microsoft.Dynamics.CRM.OneToManyRelationshipMetadata", oneToManyParams ); log(`Retrieved ${oneToManyResult.value.length} OneToMany relationships from server`); let oneToManyFiltered = 0; for (const relationship of oneToManyResult.value) { // CRITICAL: Only include relationships where BOTH entities are in the exported tables if (!exportedTableSet.has(relationship.ReferencedEntity) || !exportedTableSet.has(relationship.ReferencingEntity)) { continue; } // Apply additional client-side filtering based on parameters if (prefixOnly || (customizationPrefixes && customizationPrefixes.length > 0)) { // Check if relationship involves tables with the specified prefixes const prefixesToCheck = customizationPrefixes || (prefixOnly && solutionContext?.customizationPrefix ? [solutionContext.customizationPrefix] : []); if (prefixesToCheck.length > 0) { const referencedMatches = prefixesToCheck.some(prefix => relationship.ReferencedEntity.toLowerCase().startsWith(prefix.toLowerCase() + '_') ); const referencingMatches = prefixesToCheck.some(prefix => relationship.ReferencingEntity.toLowerCase().startsWith(prefix.toLowerCase() + '_') ); // Include if either entity matches the prefix (relationships can cross prefix boundaries) if (!referencedMatches && !referencingMatches) { continue; } } } const relationshipSchema: RelationshipSchema = { schemaName: relationship.SchemaName, relationshipType: "OneToMany", referencedEntity: relationship.ReferencedEntity, referencingEntity: relationship.ReferencingEntity, referencingAttribute: relationship.ReferencingAttribute, referencedEntityNavigationPropertyName: relationship.ReferencedEntityNavigationPropertyName, referencingEntityNavigationPropertyName: relationship.ReferencingEntityNavigationPropertyName, cascadeConfiguration: relationship.CascadeConfiguration, isCustomRelationship: relationship.IsCustomRelationship, isManaged: relationship.IsManaged }; schema.relationships.push(relationshipSchema); oneToManyFiltered++; } log(`Filtered to ${oneToManyFiltered} OneToMany relationships involving exported tables`); // Export ManyToMany relationships using cast syntax log('Exporting ManyToMany relationships...'); const manyToManyFilters = []; // Only apply system/custom filtering on server side to avoid URI length issues if (!includeSystemRelationships) { manyToManyFilters.push("IsCustomRelationship eq true"); } const manyToManyParams: Record<string, any> = { $select: "SchemaName,RelationshipType,IsCustomRelationship,IsManaged,IsValidForAdvancedFind,Entity1LogicalName,Entity2LogicalName,IntersectEntityName" }; if (manyToManyFilters.length > 0) { manyToManyParams.$filter = manyToManyFilters.join(" and "); } const manyToManyResult = await client.getMetadata( "RelationshipDefinitions/Microsoft.Dynamics.CRM.ManyToManyRelationshipMetadata", manyToManyParams ); log(`Retrieved ${manyToManyResult.value.length} ManyToMany relationships from server`); let manyToManyFiltered = 0; for (const relationship of manyToManyResult.value) { // CRITICAL: Only include relationships where BOTH entities are in the exported tables if (!exportedTableSet.has(relationship.Entity1LogicalName) || !exportedTableSet.has(relationship.Entity2LogicalName)) { continue; } // Apply additional client-side filtering based on parameters if (prefixOnly || (customizationPrefixes && customizationPrefixes.length > 0)) { // Check if relationship involves tables with the specified prefixes const prefixesToCheck = customizationPrefixes || (prefixOnly && solutionContext?.customizationPrefix ? [solutionContext.customizationPrefix] : []); if (prefixesToCheck.length > 0) { const entity1Matches = prefixesToCheck.some(prefix => relationship.Entity1LogicalName.toLowerCase().startsWith(prefix.toLowerCase() + '_') ); const entity2Matches = prefixesToCheck.some(prefix => relationship.Entity2LogicalName.toLowerCase().startsWith(prefix.toLowerCase() + '_') ); // Include if either entity matches the prefix (relationships can cross prefix boundaries) if (!entity1Matches && !entity2Matches) { continue; } } } const relationshipSchema: RelationshipSchema = { schemaName: relationship.SchemaName, relationshipType: "ManyToMany", entity1LogicalName: relationship.Entity1LogicalName, entity2LogicalName: relationship.Entity2LogicalName, intersectEntityName: relationship.IntersectEntityName, isCustomRelationship: relationship.IsCustomRelationship, isManaged: relationship.IsManaged }; schema.relationships.push(relationshipSchema); manyToManyFiltered++; } log(`Filtered to ${manyToManyFiltered} ManyToMany relationships involving exported tables`); log(`Completed relationship export. Total exported: ${schema.relationships.length}`); } catch (error) { log(`Warning: Could not export relationships: ${error instanceof Error ? error.message : 'Unknown error'}`); console.error('Full relationship export error:', error); } // Write the schema to file const jsonOutput = prettify ? JSON.stringify(schema, null, 2) : JSON.stringify(schema); // Ensure the directory exists const outputDir = path.dirname(outputPath); if (outputDir !== '.' && !fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } fs.writeFileSync(outputPath, jsonOutput, 'utf8'); const stats = { tables: schema.tables.length, totalColumns: schema.tables.reduce((sum, table) => sum + table.columns.length, 0), globalOptionSets: schema.globalOptionSets.length, relationships: schema.relationships.length }; log('Schema export completed successfully!'); return `Schema export completed successfully! ๐Ÿ” **Debug Output:** ${debugMessages.map(msg => ` ${msg}`).join('\n')} ๐Ÿ“Š **Export Summary:** - **Tables:** ${stats.tables} - **Columns:** ${stats.totalColumns} - **Global Option Sets:** ${stats.globalOptionSets} - **Relationships:** ${stats.relationships} ๐Ÿ“ **Output:** ${outputPath} ๐Ÿ“ **File Size:** ${(fs.statSync(outputPath).size / 1024).toFixed(2)} KB ${solutionContext ? `๐Ÿ”ง **Solution Context:** ${solutionContext.solutionDisplayName} (${solutionContext.solutionUniqueName})` : ''} The schema has been exported as ${prettify ? 'formatted' : 'minified'} JSON and includes: - Complete table definitions with all properties - All columns with type-specific metadata - Global and local option sets with all options - One-to-Many and Many-to-Many relationships - Cascade configurations and relationship metadata ${includeAllSystemTables ? 'โš ๏ธ All system tables included' : (systemTablesToInclude.length > 0 ? `โš ๏ธ Selected system tables included: ${systemTablesToInclude.join(', ')}` : 'โœ… Custom tables only')} ${includeSystemColumns ? 'โš ๏ธ System columns included' : 'โœ… Custom columns only'} ${includeSystemOptionSets ? 'โš ๏ธ System option sets included' : 'โœ… Custom option sets only'} ${includeSystemRelationships ? 'โš ๏ธ System relationships included' : 'โœ… Custom relationships only'} ${prefixOnly && solutionContext?.customizationPrefix ? `๐ŸŽฏ Filtered to ${solutionContext.customizationPrefix}_ prefix only` : ''}`; } catch (error: any) { console.error('Schema export failed:', error); throw new Error(`Failed to export solution schema: ${error.message}`); } }
  • Zod schema defining the input parameters and validation for the 'export_solution_schema' tool, including options for filtering system vs custom components, prefixes, exclusions, and output formatting.
    export const exportSolutionSchemaSchema = z.object({ outputPath: z.string().optional().describe('Path where to save the schema JSON file (default: schema-export.json)'), includeAllSystemTables: z.boolean().optional().default(false).describe('Whether to include all system tables in the export'), includeSystemColumns: z.boolean().optional().default(false).describe('Whether to include system columns in the export'), includeSystemOptionSets: z.boolean().optional().default(false).describe('Whether to include system option sets in the export'), includeSystemRelationships: z.boolean().optional().default(false).describe('Whether to include system (non-custom) relationships in the export'), prefixOnly: z.boolean().optional().default(false).describe('Whether to export only tables that match the solution customization prefix (deprecated - use customizationPrefixes instead)'), customizationPrefixes: z.array(z.string()).optional().describe('List of customization prefixes to include (e.g., ["new", "xyz", "its"]). If not provided and prefixOnly is true, uses solution context prefix'), systemTablesToInclude: z.array(z.string()).optional().default(['contact', 'account']).describe('List of system tables to include when includeAllSystemTables is false (default: contact, account)'), excludeColumnPrefixes: z.array(z.string()).optional().default(['adx_', 'msa_', 'msdyn_', 'mspp_']).describe('List of column prefixes to exclude from export (default: ["adx_", "msa_", "msdyn_", "mspp_"])'), prettify: z.boolean().optional().default(true).describe('Whether to format the JSON output for readability') });
  • The registration function for the MCP tool that registers 'export_solution_schema' with the McpServer, providing title, description, inputSchema (mirroring the Zod schema), and the async handler that calls the main exportSolutionSchema function.
    export function exportSolutionSchemaTool(server: McpServer, client: DataverseClient): void { server.registerTool( 'export_solution_schema', { title: "Export Solution Schema", description: "Exports a comprehensive JSON schema of Dataverse tables, columns, relationships, and option sets. Use this to document your data model, generate diagrams, or analyze solution structure. Supports filtering by prefixes, system/custom components, and specific tables.", inputSchema: { outputPath: z.string().optional().describe('Path where to save the schema JSON file (default: schema-export.json)'), includeAllSystemTables: z.boolean().optional().default(false).describe('Whether to include all system tables in the export'), includeSystemColumns: z.boolean().optional().default(false).describe('Whether to include system columns in the export'), includeSystemOptionSets: z.boolean().optional().default(false).describe('Whether to include system option sets in the export'), includeSystemRelationships: z.boolean().optional().default(false).describe('Whether to include system (non-custom) relationships in the export'), prefixOnly: z.boolean().optional().default(false).describe('Whether to export only tables that match the solution customization prefix (deprecated - use customizationPrefixes instead)'), customizationPrefixes: z.array(z.string()).optional().describe('List of customization prefixes to include (e.g., ["new", "xyz", "its"]). If not provided and prefixOnly is true, uses solution context prefix'), systemTablesToInclude: z.array(z.string()).optional().default(['contact', 'account']).describe('List of system tables to include when includeAllSystemTables is false (default: contact, account)'), excludeColumnPrefixes: z.array(z.string()).optional().default(['adx_', 'msa_', 'msdyn_', 'mspp_']).describe('List of column prefixes to exclude from export (default: ["adx_", "msa_", "msdyn_", "mspp_"])'), prettify: z.boolean().optional().default(true).describe('Whether to format the JSON output for readability') } }, async (args) => { try { const result = await exportSolutionSchema(client, args); return { content: [ { type: "text", text: result } ] }; } catch (error) { return { content: [ { type: "text", text: `Error exporting schema: ${error instanceof Error ? error.message : 'Unknown error'}` } ], isError: true }; } } ); }
  • src/index.ts:225-225 (registration)
    The call in the main index file that invokes the registration function to register the tool with the MCP server instance.
    exportSolutionSchemaTool(server, dataverseClient);

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

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