skills/managed-identity/SKILL.md
Set up and manage Dataverse Managed Identities for plugin assemblies — create UAMIs, compute federated credentials, register managed identity records in Dataverse, associate assemblies, and configure CI/CD signing. Use when asked "set up managed identity", "configure plugin managed identity", "connect plugin to Key Vault", "federated credential", "sign plugin assembly".
npx skillsauth add nickmeron/Dataverse-MCP-Server skills/managed-identityInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
The user wants to set up or manage Dataverse Managed Identities for plugin assemblies to securely access Azure resources (Key Vault, Storage, APIs, etc.) without storing credentials.
Argument provided: $ARGUMENTS
Managed Identity allows Dataverse plugins to acquire Azure AD tokens for Azure resources (Key Vault, Azure SQL, Storage, custom APIs) without embedding secrets. It uses User-Assigned Managed Identities (UAMI) with Federated Identity Credentials.
Architecture:
Plugin Code → IManagedIdentityService.AcquireToken() → Azure AD → Federated Credential → UAMI → Azure Resource
Collect from the user:
To get the Dataverse environment ID, use:
get_org_settings → the organizationid value
Or the user can find it in Power Platform Admin Center → Environments → Environment Details.
One UAMI per environment (or shared if acceptable). Guide the user through Azure CLI or Portal:
# Create UAMI for each environment
az identity create --name "mi-dataverse-dev" --resource-group "rg-dataverse" --location "westeurope"
az identity create --name "mi-dataverse-int" --resource-group "rg-dataverse" --location "westeurope"
az identity create --name "mi-dataverse-test" --resource-group "rg-dataverse" --location "westeurope"
az identity create --name "mi-dataverse-uat" --resource-group "rg-dataverse" --location "westeurope"
az identity create --name "mi-dataverse-prod" --resource-group "rg-dataverse" --location "westeurope"
Save each UAMI's Client ID (Application ID) from the output.
⚠️ CRITICAL: The plugin assembly must be signed with a certificate. The certificate's SHA-256 hash is used in the federated credential subject.
# PowerShell — Generate self-signed certificate
$cert = New-SelfSignedCertificate `
-Subject "CN=DataversePluginSigning" `
-CertStoreLocation "Cert:\CurrentUser\My" `
-KeyExportPolicy Exportable `
-KeySpec Signature `
-KeyLength 2048 `
-KeyAlgorithm RSA `
-HashAlgorithm SHA256 `
-NotAfter (Get-Date).AddYears(5)
# Export as PFX (with private key)
$password = ConvertTo-SecureString -String "YourPassword" -Force -AsPlainText
Export-PfxCertificate -Cert $cert -FilePath ".\plugin-signing.pfx" -Password $password
# Get the SHA-256 hash (hex format — CRITICAL: must be hex, not base64url)
$hash = $cert.GetCertHash("SHA256")
$hexHash = ($hash | ForEach-Object { $_.ToString("x2") }) -join ""
Write-Host "Certificate SHA-256 Hex Hash: $hexHash"
Use the compute_federated_credential_subject MCP tool for each environment. The subject format is:
/eid1/c/pub/t/{encodedTenantId}/a/{encodedAppId}/n/plugin/e/{environmentId}/h/{certHashHex}
Key encoding rules:
+→-, /→_)The MCP tool handles all encoding automatically.
For each environment:
az identity federated-credential create \
--identity-name "mi-dataverse-dev" \
--resource-group "rg-dataverse" \
--name "dataverse-dev-credential" \
--issuer "https://login.microsoftonline.com/{tenantId}/v2.0" \
--subject "/eid1/c/pub/t/{encodedTenantId}/a/{encodedAppId}/n/plugin/e/{envId}/h/{certHashHex}" \
--audiences "api://AzureADTokenExchange"
Repeat for each environment — only the environmentId changes in the subject.
Example for Key Vault:
# Grant each UAMI access to read secrets
az keyvault set-policy --name "kv-myapp" \
--object-id "$(az identity show -n mi-dataverse-dev -g rg-dataverse --query principalId -o tsv)" \
--secret-permissions get list
Or for RBAC-based Key Vault:
az role assignment create \
--assignee-object-id "$(az identity show -n mi-dataverse-dev -g rg-dataverse --query principalId -o tsv)" \
--role "Key Vault Secrets User" \
--scope "/subscriptions/{subId}/resourceGroups/rg-dataverse/providers/Microsoft.KeyVault/vaults/kv-myapp"
Use the create_managed_identity MCP tool for each environment:
Select environment → DEV
create_managed_identity:
application_id: "{UAMI_CLIENT_ID_FOR_DEV}"
managed_identity_id: "{CONSISTENT_GUID}" ← use same GUID across all envs!
tenant_id: "{TENANT_ID}"
⚠️ IMPORTANT: Use the same managedidentityid GUID across all environments. This allows the solution to be promoted without changing the ID.
The API call:
POST /api/data/v9.2/managedidentities
{
"applicationid": "{UAMI_CLIENT_ID}",
"managedidentityid": "{CONSISTENT_GUID}",
"credentialsource": 2,
"subjectscope": 1,
"tenantid": "{TENANT_ID}",
"version": 1
}
Use the associate_assembly_managed_identity MCP tool:
list_plugin_assemblies → find your assembly ID
associate_assembly_managed_identity:
assembly_id: "{PLUGIN_ASSEMBLY_ID}"
managed_identity_id: "{MANAGED_IDENTITY_ID}"
The API call:
PATCH /api/data/v9.2/pluginassemblies({assemblyId})
{
"[email protected]": "/managedidentities({managedIdentityId})"
}
# Sign the DLL
signtool sign /f plugin-signing.pfx /p "YourPassword" /fd SHA256 MyPlugin.dll
# Verify
signtool verify /pa MyPlugin.dll
Deploy via Plugin Registration Tool, pac plugin push, or solution import.
public void Execute(IServiceProvider serviceProvider)
{
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
var tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
// Get Managed Identity Service
var miService = (IManagedIdentityService)serviceProvider.GetService(typeof(IManagedIdentityService));
// Acquire token for Key Vault (MUST use .default suffix)
string token = miService.AcquireToken(new[] { "https://vault.azure.net/.default" });
tracingService.Trace("Token acquired: {0}...", token.Substring(0, 20));
// Use token
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = client.GetAsync("https://kv-myapp.vault.azure.net/secrets/my-secret?api-version=7.4").Result;
tracingService.Trace("Key Vault response: {0}", response.StatusCode);
}
}
⚠️ AcquireToken gotchas:
/.default suffix: "https://vault.azure.net/.default", NOT "https://vault.azure.net"IEnumerable<string>, so pass: new[] { scope } (not a plain string)- name: Sign Plugin Assembly
run: |
# Decode PFX from secret
echo "${{ secrets.PLUGIN_CERT_PFX_BASE64 }}" | base64 -d > cert.pfx
# Sign both DLLs
signtool sign /f cert.pfx /p "${{ secrets.CERT_PASSWORD }}" /fd SHA256 MyPlugin/bin/Release/net462/MyPlugin.dll
# Clean up
rm cert.pfx
- name: Post-Import — Associate Assembly with Managed Identity
run: |
# Dynamic lookup — find the assembly GUID in the target environment
ASSEMBLY_ID=$(curl -s -H "Authorization: Bearer $TOKEN" \
"$ENV_URL/api/data/v9.2/pluginassemblies?\$filter=name eq 'MyPlugin'&\$select=pluginassemblyid" \
| jq -r '.value[0].pluginassemblyid')
# Associate with managed identity
curl -X PATCH -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
"$ENV_URL/api/data/v9.2/pluginassemblies($ASSEMBLY_ID)" \
-d '{"[email protected]": "/managedidentities(YOUR_MI_GUID)"}'
GitHub master → CI/CD (sign DLL) → INT → Dataverse pipeline → Test → UAT → Prod
signtool sign /f cert.pfx /p "password" /fd SHA256 MyPlugin.dllassociate_assembly_managed_identity to link assembly → managed identity/.defaultapi://AzureADTokenExchangetesting
Create, monitor, and manage bulk deletion jobs in Dynamics 365. Use when asked "bulk delete", "delete all records of type X", "create a bulk delete job", "check bulk delete status", "cancel bulk delete", "why did bulk delete fail".
data-ai
Produce a business-readable summary of a Dynamics 365 record or set of records. Uses metadata to understand the schema before querying.
testing
Investigate users, security roles, teams, and permissions in Dynamics 365. Use when asked "who has access to...", "what roles does X have?", "compare roles", or "show me users".
data-ai
Query Dynamics 365 records using natural language. Translates questions into OData queries with metadata-aware field selection.