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
| Name | Required | Description | Default |
|---|---|---|---|
| customizationPrefixes | No | List of customization prefixes to include (e.g., ["new", "xyz", "its"]). If not provided and prefixOnly is true, uses solution context prefix | |
| excludeColumnPrefixes | No | List of column prefixes to exclude from export (default: ["adx_", "msa_", "msdyn_", "mspp_"]) | |
| includeAllSystemTables | No | Whether to include all system tables in the export | |
| includeSystemColumns | No | Whether to include system columns in the export | |
| includeSystemOptionSets | No | Whether to include system option sets in the export | |
| includeSystemRelationships | No | Whether to include system (non-custom) relationships in the export | |
| outputPath | No | Path where to save the schema JSON file (default: schema-export.json) | |
| prefixOnly | No | Whether to export only tables that match the solution customization prefix (deprecated - use customizationPrefixes instead) | |
| prettify | No | Whether to format the JSON output for readability | |
| systemTablesToInclude | No | List of system tables to include when includeAllSystemTables is false (default: contact, account) |
Implementation Reference
- src/tools/schema-tools.ts:135-718 (handler)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}`); } }
- src/tools/schema-tools.ts:8-19 (schema)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') });
- src/tools/schema-tools.ts:1017-1060 (registration)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);