Skip to main content
Glama
mwhesse

Dataverse MCP Server

by mwhesse

create_dataverse_relationship

Create relationships between Dataverse tables to establish data connections, enable navigation, and maintain referential integrity. Supports One-to-Many and Many-to-Many relationship types.

Instructions

Creates a relationship between two Dataverse tables. Supports One-to-Many relationships (parent-child with lookup field) and Many-to-Many relationships (junction table). Use this to establish data connections between tables, enable navigation, and maintain referential integrity.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
cascadeAssignNoCascade behavior for assign operationsNoCascade
cascadeDeleteNoCascade behavior for delete operationsRemoveLink
cascadeMergeNoCascade behavior for merge operationsNoCascade
cascadeReparentNoCascade behavior for reparent operationsNoCascade
cascadeShareNoCascade behavior for share operationsNoCascade
cascadeUnshareNoCascade behavior for unshare operationsNoCascade
entity1LogicalNameNoFirst entity logical name for Many-to-Many relationships
entity2LogicalNameNoSecond entity logical name for Many-to-Many relationships
intersectEntityNameNoName for the intersect entity (auto-generated if not provided)
isHierarchicalNoWhether this is a hierarchical relationship (One-to-Many only)
isValidForAdvancedFindNoWhether the relationship is valid for Advanced Find
menuBehaviorNoHow the relationship appears in associated menusUseCollectionName
menuGroupNoMenu group for the relationshipDetails
menuLabelNoCustom label for the menu (required if menuBehavior is UseLabel)
menuOrderNoOrder in the menu
referencedEntityNoReferenced (parent) entity logical name for One-to-Many relationships
referencingAttributeDisplayNameNoDisplay name for the lookup attribute
referencingAttributeLogicalNameNoLogical name for the lookup attribute to be created
referencingEntityNoReferencing (child) entity logical name for One-to-Many relationships
relationshipTypeYesType of relationship to create
schemaNameYesSchema name for the relationship (e.g., 'new_account_contact')

Implementation Reference

  • The async handler function implementing the core logic for creating One-to-Many or Many-to-Many relationships in Dataverse by posting metadata definitions via the DataverseClient.
    async (params) => { try { if (params.relationshipType === "OneToMany") { if (!params.referencedEntity || !params.referencingEntity || !params.referencingAttributeLogicalName || !params.referencingAttributeDisplayName) { throw new Error("For One-to-Many relationships, referencedEntity, referencingEntity, referencingAttributeLogicalName, and referencingAttributeDisplayName are required"); } const cascadeConfig = { Assign: getCascadeValue(params.cascadeAssign), Delete: getCascadeValue(params.cascadeDelete), Merge: getCascadeValue(params.cascadeMerge), Reparent: getCascadeValue(params.cascadeReparent), Share: getCascadeValue(params.cascadeShare), Unshare: getCascadeValue(params.cascadeUnshare), RollupView: "NoCascade" }; const menuConfig = { Behavior: getMenuBehaviorValue(params.menuBehavior), Group: getMenuGroupValue(params.menuGroup), Label: params.menuLabel ? createLocalizedLabel(params.menuLabel) : undefined, Order: params.menuOrder }; const relationshipDefinition = { "@odata.type": "Microsoft.Dynamics.CRM.OneToManyRelationshipMetadata", SchemaName: params.schemaName, ReferencedEntity: params.referencedEntity, ReferencingEntity: params.referencingEntity, CascadeConfiguration: cascadeConfig, AssociatedMenuConfiguration: menuConfig, IsValidForAdvancedFind: params.isValidForAdvancedFind, IsHierarchical: params.isHierarchical, IsCustomRelationship: true, Lookup: { "@odata.type": "Microsoft.Dynamics.CRM.LookupAttributeMetadata", LogicalName: params.referencingAttributeLogicalName, SchemaName: params.referencingAttributeLogicalName.charAt(0).toUpperCase() + params.referencingAttributeLogicalName.slice(1), DisplayName: createLocalizedLabel(params.referencingAttributeDisplayName), RequiredLevel: { Value: "None", CanBeChanged: true, ManagedPropertyLogicalName: "canmodifyrequirementlevelsettings" }, Targets: [params.referencedEntity], IsCustomAttribute: true } }; const result = await client.postMetadata("RelationshipDefinitions", relationshipDefinition); return { content: [ { type: "text", text: `Successfully created One-to-Many relationship '${params.schemaName}' between '${params.referencedEntity}' and '${params.referencingEntity}'.\n\nResponse: ${JSON.stringify(result, null, 2)}` } ] }; } else { // ManyToMany if (!params.entity1LogicalName || !params.entity2LogicalName) { throw new Error("For Many-to-Many relationships, entity1LogicalName and entity2LogicalName are required"); } const intersectName = params.intersectEntityName || `${params.entity1LogicalName}_${params.entity2LogicalName}`; const menuConfig1 = { Behavior: getMenuBehaviorValue(params.menuBehavior), Group: getMenuGroupValue(params.menuGroup), Label: params.menuLabel ? createLocalizedLabel(params.menuLabel) : undefined, Order: params.menuOrder }; const menuConfig2 = { Behavior: getMenuBehaviorValue(params.menuBehavior), Group: getMenuGroupValue(params.menuGroup), Label: params.menuLabel ? createLocalizedLabel(params.menuLabel) : undefined, Order: params.menuOrder }; const relationshipDefinition = { "@odata.type": "Microsoft.Dynamics.CRM.ManyToManyRelationshipMetadata", SchemaName: params.schemaName, Entity1LogicalName: params.entity1LogicalName, Entity1AssociatedMenuConfiguration: menuConfig1, Entity2LogicalName: params.entity2LogicalName, Entity2AssociatedMenuConfiguration: menuConfig2, IntersectEntityName: intersectName, IsValidForAdvancedFind: params.isValidForAdvancedFind, IsCustomRelationship: true }; const result = await client.postMetadata("RelationshipDefinitions", relationshipDefinition); return { content: [ { type: "text", text: `Successfully created Many-to-Many relationship '${params.schemaName}' between '${params.entity1LogicalName}' and '${params.entity2LogicalName}'.\n\nResponse: ${JSON.stringify(result, null, 2)}` } ] }; } } catch (error) { return { content: [ { type: "text", text: `Error creating relationship: ${error instanceof Error ? error.message : 'Unknown error'}` } ], isError: true }; } }
  • Zod schema defining the input parameters for the tool, including relationship type, entity names, attributes, cascade behaviors, menu settings, and other configurations.
    inputSchema: { relationshipType: z.enum(["OneToMany", "ManyToMany"]).describe("Type of relationship to create"), schemaName: z.string().describe("Schema name for the relationship (e.g., 'new_account_contact')"), // One-to-Many specific fields referencedEntity: z.string().optional().describe("Referenced (parent) entity logical name for One-to-Many relationships"), referencingEntity: z.string().optional().describe("Referencing (child) entity logical name for One-to-Many relationships"), referencingAttributeLogicalName: z.string().optional().describe("Logical name for the lookup attribute to be created"), referencingAttributeDisplayName: z.string().optional().describe("Display name for the lookup attribute"), // Many-to-Many specific fields entity1LogicalName: z.string().optional().describe("First entity logical name for Many-to-Many relationships"), entity2LogicalName: z.string().optional().describe("Second entity logical name for Many-to-Many relationships"), intersectEntityName: z.string().optional().describe("Name for the intersect entity (auto-generated if not provided)"), // Cascade configuration for One-to-Many cascadeAssign: z.enum(["NoCascade", "Cascade", "Active", "UserOwned", "RemoveLink", "Restrict"]).default("NoCascade").describe("Cascade behavior for assign operations"), cascadeDelete: z.enum(["NoCascade", "Cascade", "Active", "UserOwned", "RemoveLink", "Restrict"]).default("RemoveLink").describe("Cascade behavior for delete operations"), cascadeMerge: z.enum(["NoCascade", "Cascade", "Active", "UserOwned", "RemoveLink", "Restrict"]).default("NoCascade").describe("Cascade behavior for merge operations"), cascadeReparent: z.enum(["NoCascade", "Cascade", "Active", "UserOwned", "RemoveLink", "Restrict"]).default("NoCascade").describe("Cascade behavior for reparent operations"), cascadeShare: z.enum(["NoCascade", "Cascade", "Active", "UserOwned", "RemoveLink", "Restrict"]).default("NoCascade").describe("Cascade behavior for share operations"), cascadeUnshare: z.enum(["NoCascade", "Cascade", "Active", "UserOwned", "RemoveLink", "Restrict"]).default("NoCascade").describe("Cascade behavior for unshare operations"), // Associated menu configuration menuBehavior: z.enum(["UseCollectionName", "UseLabel", "DoNotDisplay"]).default("UseCollectionName").describe("How the relationship appears in associated menus"), menuGroup: z.enum(["Details", "Sales", "Service", "Marketing"]).default("Details").describe("Menu group for the relationship"), menuLabel: z.string().optional().describe("Custom label for the menu (required if menuBehavior is UseLabel)"), menuOrder: z.number().optional().describe("Order in the menu"), isValidForAdvancedFind: z.boolean().default(true).describe("Whether the relationship is valid for Advanced Find"), isHierarchical: z.boolean().default(false).describe("Whether this is a hierarchical relationship (One-to-Many only)") }
  • The server.registerTool call within createRelationshipTool that registers the 'create_dataverse_relationship' tool with the MCP server, including title, description, schema, and handler.
    server.registerTool( "create_dataverse_relationship", { title: "Create Dataverse Relationship", description: "Creates a relationship between two Dataverse tables. Supports One-to-Many relationships (parent-child with lookup field) and Many-to-Many relationships (junction table). Use this to establish data connections between tables, enable navigation, and maintain referential integrity.", inputSchema: { relationshipType: z.enum(["OneToMany", "ManyToMany"]).describe("Type of relationship to create"), schemaName: z.string().describe("Schema name for the relationship (e.g., 'new_account_contact')"), // One-to-Many specific fields referencedEntity: z.string().optional().describe("Referenced (parent) entity logical name for One-to-Many relationships"), referencingEntity: z.string().optional().describe("Referencing (child) entity logical name for One-to-Many relationships"), referencingAttributeLogicalName: z.string().optional().describe("Logical name for the lookup attribute to be created"), referencingAttributeDisplayName: z.string().optional().describe("Display name for the lookup attribute"), // Many-to-Many specific fields entity1LogicalName: z.string().optional().describe("First entity logical name for Many-to-Many relationships"), entity2LogicalName: z.string().optional().describe("Second entity logical name for Many-to-Many relationships"), intersectEntityName: z.string().optional().describe("Name for the intersect entity (auto-generated if not provided)"), // Cascade configuration for One-to-Many cascadeAssign: z.enum(["NoCascade", "Cascade", "Active", "UserOwned", "RemoveLink", "Restrict"]).default("NoCascade").describe("Cascade behavior for assign operations"), cascadeDelete: z.enum(["NoCascade", "Cascade", "Active", "UserOwned", "RemoveLink", "Restrict"]).default("RemoveLink").describe("Cascade behavior for delete operations"), cascadeMerge: z.enum(["NoCascade", "Cascade", "Active", "UserOwned", "RemoveLink", "Restrict"]).default("NoCascade").describe("Cascade behavior for merge operations"), cascadeReparent: z.enum(["NoCascade", "Cascade", "Active", "UserOwned", "RemoveLink", "Restrict"]).default("NoCascade").describe("Cascade behavior for reparent operations"), cascadeShare: z.enum(["NoCascade", "Cascade", "Active", "UserOwned", "RemoveLink", "Restrict"]).default("NoCascade").describe("Cascade behavior for share operations"), cascadeUnshare: z.enum(["NoCascade", "Cascade", "Active", "UserOwned", "RemoveLink", "Restrict"]).default("NoCascade").describe("Cascade behavior for unshare operations"), // Associated menu configuration menuBehavior: z.enum(["UseCollectionName", "UseLabel", "DoNotDisplay"]).default("UseCollectionName").describe("How the relationship appears in associated menus"), menuGroup: z.enum(["Details", "Sales", "Service", "Marketing"]).default("Details").describe("Menu group for the relationship"), menuLabel: z.string().optional().describe("Custom label for the menu (required if menuBehavior is UseLabel)"), menuOrder: z.number().optional().describe("Order in the menu"), isValidForAdvancedFind: z.boolean().default(true).describe("Whether the relationship is valid for Advanced Find"), isHierarchical: z.boolean().default(false).describe("Whether this is a hierarchical relationship (One-to-Many only)") } }, async (params) => { try { if (params.relationshipType === "OneToMany") { if (!params.referencedEntity || !params.referencingEntity || !params.referencingAttributeLogicalName || !params.referencingAttributeDisplayName) { throw new Error("For One-to-Many relationships, referencedEntity, referencingEntity, referencingAttributeLogicalName, and referencingAttributeDisplayName are required"); } const cascadeConfig = { Assign: getCascadeValue(params.cascadeAssign), Delete: getCascadeValue(params.cascadeDelete), Merge: getCascadeValue(params.cascadeMerge), Reparent: getCascadeValue(params.cascadeReparent), Share: getCascadeValue(params.cascadeShare), Unshare: getCascadeValue(params.cascadeUnshare), RollupView: "NoCascade" }; const menuConfig = { Behavior: getMenuBehaviorValue(params.menuBehavior), Group: getMenuGroupValue(params.menuGroup), Label: params.menuLabel ? createLocalizedLabel(params.menuLabel) : undefined, Order: params.menuOrder }; const relationshipDefinition = { "@odata.type": "Microsoft.Dynamics.CRM.OneToManyRelationshipMetadata", SchemaName: params.schemaName, ReferencedEntity: params.referencedEntity, ReferencingEntity: params.referencingEntity, CascadeConfiguration: cascadeConfig, AssociatedMenuConfiguration: menuConfig, IsValidForAdvancedFind: params.isValidForAdvancedFind, IsHierarchical: params.isHierarchical, IsCustomRelationship: true, Lookup: { "@odata.type": "Microsoft.Dynamics.CRM.LookupAttributeMetadata", LogicalName: params.referencingAttributeLogicalName, SchemaName: params.referencingAttributeLogicalName.charAt(0).toUpperCase() + params.referencingAttributeLogicalName.slice(1), DisplayName: createLocalizedLabel(params.referencingAttributeDisplayName), RequiredLevel: { Value: "None", CanBeChanged: true, ManagedPropertyLogicalName: "canmodifyrequirementlevelsettings" }, Targets: [params.referencedEntity], IsCustomAttribute: true } }; const result = await client.postMetadata("RelationshipDefinitions", relationshipDefinition); return { content: [ { type: "text", text: `Successfully created One-to-Many relationship '${params.schemaName}' between '${params.referencedEntity}' and '${params.referencingEntity}'.\n\nResponse: ${JSON.stringify(result, null, 2)}` } ] }; } else { // ManyToMany if (!params.entity1LogicalName || !params.entity2LogicalName) { throw new Error("For Many-to-Many relationships, entity1LogicalName and entity2LogicalName are required"); } const intersectName = params.intersectEntityName || `${params.entity1LogicalName}_${params.entity2LogicalName}`; const menuConfig1 = { Behavior: getMenuBehaviorValue(params.menuBehavior), Group: getMenuGroupValue(params.menuGroup), Label: params.menuLabel ? createLocalizedLabel(params.menuLabel) : undefined, Order: params.menuOrder }; const menuConfig2 = { Behavior: getMenuBehaviorValue(params.menuBehavior), Group: getMenuGroupValue(params.menuGroup), Label: params.menuLabel ? createLocalizedLabel(params.menuLabel) : undefined, Order: params.menuOrder }; const relationshipDefinition = { "@odata.type": "Microsoft.Dynamics.CRM.ManyToManyRelationshipMetadata", SchemaName: params.schemaName, Entity1LogicalName: params.entity1LogicalName, Entity1AssociatedMenuConfiguration: menuConfig1, Entity2LogicalName: params.entity2LogicalName, Entity2AssociatedMenuConfiguration: menuConfig2, IntersectEntityName: intersectName, IsValidForAdvancedFind: params.isValidForAdvancedFind, IsCustomRelationship: true }; const result = await client.postMetadata("RelationshipDefinitions", relationshipDefinition); return { content: [ { type: "text", text: `Successfully created Many-to-Many relationship '${params.schemaName}' between '${params.entity1LogicalName}' and '${params.entity2LogicalName}'.\n\nResponse: ${JSON.stringify(result, null, 2)}` } ] }; } } catch (error) { return { content: [ { type: "text", text: `Error creating relationship: ${error instanceof Error ? error.message : 'Unknown error'}` } ], isError: true }; } } );
  • src/index.ts:25-153 (registration)
    Import of createRelationshipTool and its invocation in the main server initialization, which triggers the tool registration.
    createRelationshipTool, getRelationshipTool, deleteRelationshipTool, listRelationshipsTool } from "./tools/relationship-tools.js"; import { createOptionSetTool, getOptionSetTool, updateOptionSetTool, deleteOptionSetTool, listOptionSetsTool, getOptionSetOptionsTool } from "./tools/optionset-tools.js"; import { createPublisherTool, createSolutionTool, getSolutionTool, getPublisherTool, listSolutionsTool, listPublishersTool, setSolutionContextTool, getSolutionContextTool, clearSolutionContextTool } from "./tools/solution-tools.js"; import { createRoleTool, getRoleTool, updateRoleTool, deleteRoleTool, listRolesTool, addPrivilegesToRoleTool, removePrivilegeFromRoleTool, replaceRolePrivilegesTool, getRolePrivilegesTool, assignRoleToUserTool, removeRoleFromUserTool, assignRoleToTeamTool, removeRoleFromTeamTool } from "./tools/role-tools.js"; import { createTeamTool, getTeamTool, updateTeamTool, deleteTeamTool, listTeamsTool, addMembersToTeamTool, removeMembersFromTeamTool, getTeamMembersTool, convertOwnerTeamToAccessTeamTool } from "./tools/team-tools.js"; import { createBusinessUnitTool, getBusinessUnitTool, updateBusinessUnitTool, deleteBusinessUnitTool, listBusinessUnitsTool, getBusinessUnitHierarchyTool, setBusinessUnitParentTool, getBusinessUnitUsersTool, getBusinessUnitTeamsTool } from "./tools/businessunit-tools.js"; import { exportSolutionSchemaTool, generateMermaidDiagramTool } from "./tools/schema-tools.js"; import { generateWebAPICallTool } from "./tools/webapi-tools.js"; import { generatePowerPagesWebAPICallTool } from "./tools/powerpages-webapi-tools.js"; import { managePowerPagesWebAPIConfigTool } from "./tools/powerpages-config-tools.js"; import { createAutoNumberColumnTool, updateAutoNumberFormatTool, setAutoNumberSeedTool, getAutoNumberColumnTool, listAutoNumberColumnsTool, convertToAutoNumberTool } from "./tools/autonumber-tools.js"; import { registerWebAPIResources } from "./resources/webapi-resources.js"; import { registerPowerPagesResources } from "./resources/powerpages-resources.js"; // Environment variables for Dataverse authentication const DATAVERSE_URL = process.env.DATAVERSE_URL; const CLIENT_ID = process.env.DATAVERSE_CLIENT_ID; const CLIENT_SECRET = process.env.DATAVERSE_CLIENT_SECRET; const TENANT_ID = process.env.DATAVERSE_TENANT_ID; if (!DATAVERSE_URL || !CLIENT_ID || !CLIENT_SECRET || !TENANT_ID) { throw new Error('Missing required environment variables: DATAVERSE_URL, DATAVERSE_CLIENT_ID, DATAVERSE_CLIENT_SECRET, DATAVERSE_TENANT_ID'); } // Create MCP server const server = new McpServer({ name: "dataverse-mcp", version: "0.2.2" }); // Initialize Dataverse client const dataverseClient = new DataverseClient({ dataverseUrl: DATAVERSE_URL, clientId: CLIENT_ID, clientSecret: CLIENT_SECRET, tenantId: TENANT_ID }); // Register table tools createTableTool(server, dataverseClient); getTableTool(server, dataverseClient); updateTableTool(server, dataverseClient); deleteTableTool(server, dataverseClient); listTablesTool(server, dataverseClient); // Register column tools createColumnTool(server, dataverseClient); getColumnTool(server, dataverseClient); updateColumnTool(server, dataverseClient); deleteColumnTool(server, dataverseClient); listColumnsTool(server, dataverseClient); // Register relationship tools createRelationshipTool(server, dataverseClient);
  • Helper function to create standardized LocalizedLabel objects used in relationship metadata definitions for display names and 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" } };

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