Dataverse Custom API Power MCP is a metadata-driven Model Context Protocol server that automatically discovers Custom APIs from your Dataverse environment and exposes them as MCP tools for Microsoft Copilot Studio. Unlike hardcoded connectors, this solution adapts to your environment—any Custom API you create is immediately available without code changes.
- Metadata-Driven: Discovers Custom APIs from Dataverse
customapistable at runtime - Zero Configuration: No hardcoding—adapts to any environment automatically
- Smart Caching: 30-minute TTL reduces API calls while staying current
- Private API Support: Configurable filtering to include/exclude private Custom APIs
- All Binding Types: Global unbound (0), Entity-bound (1), EntityCollection-bound (2)
- Functions & Actions: Handles GET (Functions) with URL parameters and POST (Actions) with body parameters
- All 13 Parameter Types: Boolean, DateTime, Decimal, Entity, EntityCollection, EntityReference, Float, Integer, Money, Picklist, String, StringArray, Guid
- Open Types: Validates and auto-injects
@odata.typefor Entity/EntityCollection parameters withoutLogicalEntityName
- Create Custom APIs: Define new Custom APIs from Copilot Studio with full parameter support
- Add Parameters & Properties: Build request parameters and response properties dynamically
- Update & Delete: Modify Custom API metadata or remove APIs with cascading delete
- Solution Integration: Create Custom APIs directly in solutions for proper ALM
- Self-Bootstrapping: New Custom APIs immediately appear as MCP tools after creation
- List Solutions: Discover available solutions (managed/unmanaged) for API placement
- Correlation IDs: Request tracing with
x-ms-correlation-request-idheaders for debugging - OAuth Token Forwarding: Seamless authentication passthrough to Dataverse Web API
- OData 4.0 Headers: Full compliance with Dataverse Web API standards
- Comprehensive Error Handling: Detailed errors with correlation IDs for troubleshooting
- Entity Set Mapping: 35+ common entities mapped, automatic pluralization for custom entities
Single MCP protocol endpoint (/mcp) supporting JSON-RPC 2.0 methods:
| Method | Description | Returns |
|---|---|---|
initialize |
Protocol handshake with capability negotiation | Server info, protocol version, capabilities |
notifications/initialized |
Confirms initialization complete | HTTP 200 OK |
tools/list |
Discovers Custom APIs + lists 6 management tools | Array of tool definitions with inputSchema |
tools/call |
Executes Custom API or management tool | Content array with execution results |
ping |
Health check | Empty result object |
6 built-in tools for Custom API lifecycle management:
| Tool Name | Description | Required Privilege |
|---|---|---|
dataverse_management_create_custom_api |
Create new Custom API definition | prvCreateCustomAPI |
dataverse_management_create_api_parameter |
Add request parameter to Custom API | prvCreateCustomAPIRequestParameter |
dataverse_management_create_api_property |
Add response property to Custom API | prvCreateCustomAPIResponseProperty |
dataverse_management_update_custom_api |
Update Custom API metadata (description, private flag) | prvWriteCustomAPI |
dataverse_management_delete_custom_api |
Delete Custom API (cascades to params/properties) | prvDeleteCustomAPI |
dataverse_management_list_solutions |
List solutions for ALM (managed/unmanaged filter) | prvReadSolution |
Key Behaviors:
tools/list: Cached for 30 minutes per environment; discovers all Custom APIs matching filter criteria + 6 management toolstools/call: Routes management tools first, then validates Custom API tool name format (dataverse_{uniquename})- Management tools auto-invalidate cache after CRUD operations for immediate discovery
- All methods return JSON-RPC 2.0 compliant responses with
jsonrpc,id, andresultorerror
- Navigate to Azure Portal → Azure Active Directory → App registrations
- Click New registration
- Name:
Dataverse Custom API MCP(or your preferred name) - Supported account types: Accounts in any organizational directory (Any Azure AD directory - Multitenant)
- Redirect URI:
- Platform: Web
- URI:
https://global.consent.azure-apim.net/redirect
- Click Register
- Go to API permissions → Add a permission
- Select Dynamics CRM
- Choose Delegated permissions
- Check user_impersonation
- Click Add permissions
- Optional: Grant admin consent for your organization
- Client ID: Copy from the Overview page (used in
apiProperties.json) - Example:
a08ac176-6f85-4b74-b580-0dfe304ac725
environmentUrl (Required)
- Dataverse environment subdomain (e.g.,
myorgforhttps://myorg.crm.dynamics.com) - Provided when creating connection in Power Platform
- Used to construct resource URI:
https://{environmentUrl}.crm.dynamics.com
OAuth 2.0 Configuration
- Login URI:
https://login.microsoftonline.com - Tenant ID:
common(multi-tenant support) - Authorization URL:
https://login.microsoftonline.com/common/oauth2/authorize - Token URL:
https://login.microsoftonline.com/common/oauth2/token
By default, private Custom APIs (IsPrivate=true) are filtered out. To include them:
- Open
script.csx - Locate line 16:
private const bool INCLUDE_PRIVATE_APIS = false; - Change to:
private const bool INCLUDE_PRIVATE_APIS = true; - Save and update connector in Power Platform
Note: Private APIs are typically internal/system APIs. Only enable if you need to expose them to Copilot Studio.
# Install Power Platform CLI
winget install Microsoft.PowerPlatformCLI
# Authenticate
paconn logincd "Dataverse Custom API"
paconn validate --api-def apiDefinition.swagger.json- Navigate to Power Platform maker portal
- Data → Custom connectors → New custom connector → Import an OpenAPI file
- Upload
apiDefinition.swagger.json - Enable custom code on Code tab
- Paste contents of
script.csx - Create connector
- Test connection with OAuth flow
Request:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {
"name": "copilot-studio",
"version": "1.0.0"
}
}
}Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {
"tools": { "listChanged": false }
},
"serverInfo": {
"name": "dataverse-custom-api-mcp",
"version": "1.0.0"
}
}
}Request:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list"
}Response Example:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"name": "dataverse_my_CalculateDiscount",
"description": "Calculate discount for opportunity (Global unbound Function)",
"inputSchema": {
"type": "object",
"properties": {
"OpportunityId": {
"type": "string",
"format": "uuid",
"description": "Opportunity GUID"
},
"DiscountPercent": {
"type": "number",
"description": "Discount percentage"
}
},
"required": ["OpportunityId", "DiscountPercent"]
}
},
{
"name": "dataverse_my_UpdateAccountStatus",
"description": "Update account status (Bound to account entity Action)",
"inputSchema": {
"type": "object",
"properties": {
"Target": {
"type": "string",
"description": "GUID of the account record"
},
"NewStatus": {
"type": "string",
"description": "New status value"
}
},
"required": ["Target", "NewStatus"]
}
}
]
}
}Request:
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "dataverse_my_CalculateDiscount",
"arguments": {
"OpportunityId": "12345678-1234-1234-1234-123456789012",
"DiscountPercent": 15.5
}
}
}Response:
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "{\"DiscountedAmount\":8500.00,\"Message\":\"Discount applied successfully\"}"
}
]
}
}Request:
{
"jsonrpc": "2.0",
"id": 4,
"method": "tools/call",
"params": {
"name": "dataverse_my_UpdateAccountStatus",
"arguments": {
"Target": "87654321-4321-4321-4321-210987654321",
"NewStatus": "Active"
}
}
}Response:
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"content": [
{
"type": "text",
"text": "{\"Success\":true,\"UpdatedOn\":\"2026-01-06T10:30:00Z\"}"
}
]
}
}Request:
{
"jsonrpc": "2.0",
"id": 5,
"method": "tools/call",
"params": {
"name": "dataverse_my_LinkContacts",
"arguments": {
"PrimaryContact": {
"logicalName": "contact",
"id": "11111111-1111-1111-1111-111111111111"
},
"RelatedAccount": {
"logicalName": "account",
"id": "22222222-2222-2222-2222-222222222222"
}
}
}
}Request:
{
"jsonrpc": "2.0",
"id": 6,
"method": "tools/call",
"params": {
"name": "dataverse_my_CreateRecord",
"arguments": {
"Record": {
"@odata.type": "Microsoft.Dynamics.CRM.contact",
"firstname": "John",
"lastname": "Doe",
"emailaddress1": "john.doe@example.com"
}
}
}
}Note: Open type parameters (Entity/EntityCollection without LogicalEntityName) require @odata.type property. Typed parameters auto-inject @odata.type if not provided.
Request:
{
"jsonrpc": "2.0",
"id": 7,
"method": "tools/call",
"params": {
"name": "dataverse_management_list_solutions",
"arguments": {
"IncludeManaged": false
}
}
}Response:
{
"jsonrpc": "2.0",
"id": 7,
"result": {
"content": [
{
"type": "text",
"text": "Found 3 solution(s):\n\n- My Custom Solutions (MyPublisher)\n Version: 1.0.0.0\n Type: Unmanaged\n Description: Custom APIs for business logic\n\n- Common Data Services Default Solution (Default)\n Version: 1.0\n Type: Unmanaged\n..."
}
]
}
}Request:
{
"jsonrpc": "2.0",
"id": 8,
"method": "tools/call",
"params": {
"name": "dataverse_management_create_custom_api",
"arguments": {
"UniqueName": "new_ValidateEmail",
"DisplayName": "Validate Email Address",
"Description": "Validates email format and checks against blocklist",
"BindingType": 0,
"IsFunction": true,
"IsPrivate": false,
"SolutionUniqueName": "MyPublisher"
}
}
}Response:
{
"jsonrpc": "2.0",
"id": 8,
"result": {
"content": [
{
"type": "text",
"text": "Custom API 'new_ValidateEmail' created successfully. ID: 12345678-abcd-efgh-ijkl-1234567890ab"
}
]
}
}Note: After creation, call tools/list again (or wait for cache expiry) to see the new dataverse_new_ValidateEmail tool.
Request:
{
"jsonrpc": "2.0",
"id": 9,
"method": "tools/call",
"params": {
"name": "dataverse_management_create_api_parameter",
"arguments": {
"CustomAPIUniqueName": "new_ValidateEmail",
"UniqueName": "EmailAddress",
"Name": "EmailAddress",
"DisplayName": "Email Address",
"Description": "Email address to validate",
"Type": 10,
"IsOptional": false
}
}
}Response:
{
"jsonrpc": "2.0",
"id": 9,
"result": {
"content": [
{
"type": "text",
"text": "Parameter 'EmailAddress' (String) added to Custom API 'new_ValidateEmail'"
}
]
}
}Request:
{
"jsonrpc": "2.0",
"id": 10,
"method": "tools/call",
"params": {
"name": "dataverse_management_create_api_property",
"arguments": {
"CustomAPIUniqueName": "new_ValidateEmail",
"UniqueName": "IsValid",
"Name": "IsValid",
"DisplayName": "Is Valid",
"Description": "True if email is valid",
"Type": 0
}
}
}Response:
{
"jsonrpc": "2.0",
"id": 10,
"result": {
"content": [
{
"type": "text",
"text": "Response property 'IsValid' (Boolean) added to Custom API 'new_ValidateEmail'"
}
]
}
}Request:
{
"jsonrpc": "2.0",
"id": 11,
"method": "tools/call",
"params": {
"name": "dataverse_management_update_custom_api",
"arguments": {
"UniqueName": "new_ValidateEmail",
"Description": "Validates email format, checks blocklist, and verifies DNS MX records",
"IsPrivate": true
}
}
}Response:
{
"jsonrpc": "2.0",
"id": 11,
"result": {
"content": [
{
"type": "text",
"text": "Custom API 'new_ValidateEmail' updated successfully"
}
]
}
}Request:
{
"jsonrpc": "2.0",
"id": 12,
"method": "tools/call",
"params": {
"name": "dataverse_management_delete_custom_api",
"arguments": {
"UniqueName": "new_ValidateEmail"
}
}
}Response:
{
"jsonrpc": "2.0",
"id": 12,
"result": {
"content": [
{
"type": "text",
"text": "Custom API 'new_ValidateEmail' deleted successfully. All parameters and response properties were also deleted."
}
]
}
}Warning: Deletion is permanent. Ensure Custom API is not referenced by other components before deleting.
The connector retrieves Custom API metadata from Dataverse using a single optimized query:
GET /api/data/v9.2/customapis
?$select=uniquename,displayname,description,bindingtype,boundentitylogicalname,isfunction,isprivate
&$expand=CustomAPIRequestParameters($select=uniquename,name,displayname,description,type,logicalentityname,isoptional),
CustomAPIResponseProperties($select=uniquename,name,displayname,description,type,logicalentityname)
&$filter=isprivate eq false // When INCLUDE_PRIVATE_APIS=false
- 0 - Global Unbound:
/api/data/v9.2/{uniquename} - 1 - Entity-Bound:
/api/data/v9.2/{entityset}({id})/Microsoft.Dynamics.CRM.{uniquename}(auto-addsTargetparameter) - 2 - EntityCollection-Bound:
/api/data/v9.2/{entityset}/Microsoft.Dynamics.CRM.{uniquename}
| Dataverse Type | JSON Schema | Notes |
|---|---|---|
| Boolean (0) | boolean |
Lowercase in URLs (true/false) |
| DateTime (1) | string |
Format: date-time (ISO 8601) |
| Decimal (2) | number |
Precision preserved |
| Entity (3) | object |
Requires @odata.type for open types |
| EntityCollection (4) | array |
Validates @odata.type on each entity |
| EntityReference (5) | object |
Converts to {@odata.type, {logicalName}id} |
| Float (6) | number |
Standard JSON number |
| Integer (7) | integer |
Whole numbers |
| Money (8) | number |
Wrapped in {Value: x} for body params |
| Picklist (9) | integer |
Option set value |
| String (10) | string |
Quoted/escaped in URLs |
| StringArray (11) | array |
Array of strings |
| Guid (12) | string |
Format: uuid |
- Cache Key: Environment URL (e.g.,
myorg.crm.dynamics.com) - TTL: 30 minutes
- Invalidation: Automatic on expiry; no manual refresh needed
- Scope: Static dictionary (persists across requests in same runtime instance)
- Discovers Custom APIs automatically from metadata
- Executes Custom APIs with proper parameter formatting
- Creates Custom API definitions via management tools (NEW in 2.0)
- Modifies Custom API metadata (description, private flag) (NEW in 2.0)
- Deletes Custom APIs with cascading parameter/property removal (NEW in 2.0)
- Routes to correct binding type (Global/Entity/EntityCollection)
- Forwards OAuth authentication to Dataverse
- Returns response properties or full response
- Invalidates cache automatically after CRUD operations (NEW in 2.0)
This connector provides Custom API definition management and execution. It does not:
- Implement Custom API business logic (logic is in plug-ins registered on the Custom API)
- Control Custom API security privileges (
ExecutePrivilegeName) - Manage Custom API extension points (
AllowedCustomProcessingStepType) - Enable/disable Custom APIs for Power Automate workflows (
WorkflowSdkStepEnabled) - Catalog Custom APIs as business events (for Power Automate triggers)
- Register plug-in assemblies or types (use Plugin Registration Tool)
Note: Business logic, security, and workflow integration are configured on the Custom API itself in Dataverse, not in this connector.
| Custom API Feature | How Connector Handles It |
|---|---|
ExecutePrivilegeName |
Dataverse enforces privilege check; connector returns privilege error if user lacks permission |
AllowedCustomProcessingStepType |
Connector executes main operation; Dataverse handles registered plug-in steps (None/Async Only/Sync and Async) |
WorkflowSdkStepEnabled |
Not applicable (connector bypasses workflow designer, calls Web API directly) |
IsCustomizable managed property |
Connector reflects current state; respects if API locked in managed solution |
| Business Events | Connector can trigger async logic if Custom API configured with AllowedCustomProcessingStepType=Async Only |
- OAuth Token: User's token forwarded to Dataverse; user must have appropriate security roles
- Execute Privileges: Custom APIs with
ExecutePrivilegeNamerequire user to have that privilege via security role - Table Permissions: Entity-bound APIs (BindingType=1) require read access to bound entity record
- Private APIs:
INCLUDE_PRIVATE_APIS=falsehides private APIs from discovery (IsPrivate=true excluded from metadata) - Management Tools (NEW in 2.0): Creating/modifying/deleting Custom APIs requires highly privileged security roles:
prvCreateCustomAPI,prvWriteCustomAPI,prvDeleteCustomAPI(System Customizer role or higher)- Solution-aware creation requires
prvReadSolutionand write access to target solution - Best Practice: Restrict management tool usage to administrators/developers only in production environments
- Discovery: Single metadata query with $expand (optimized)
- Caching: 30-minute TTL per environment (reduces metadata queries)
- Execution: Direct Web API calls (no middleware overhead)
- Correlation: Full request tracing for debugging
- Error Handling: Correlation IDs in all errors for support troubleshooting
Every request generates a correlation ID for tracing:
- Header:
x-ms-correlation-request-idsent to Dataverse - Logging: Included in all log statements and error responses
- Usage: Search Dataverse logs or connector logs with correlation ID to trace request flow
Example Error with Correlation ID:
{
"error": {
"statusCode": 400,
"message": "Required parameter missing: OpportunityId",
"correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
}- Cause: API not discovered in metadata, or cache is stale
- Solution:
- Verify Custom API exists in environment using Power Apps
- Check
IsPrivateflag—setINCLUDE_PRIVATE_APIS=trueif needed - Wait 30 minutes for cache refresh, or restart connector to clear cache
- Cause: Entity-bound Custom API (BindingType=1) missing
Targetparameter - Solution: Include
Targetwith entity GUID inarguments:"arguments": { "Target": "12345678-1234-1234-1234-123456789012", ... }
- Cause: Entity/EntityCollection parameter without
LogicalEntityNameis missing@odata.type - Solution: Add
@odata.typeto entity object:"Record": { "@odata.type": "Microsoft.Dynamics.CRM.contact", "firstname": "Jane" }
- Cause: Custom entity with non-standard plural form
- Solution: Check
GetEntitySetName()inscript.csx(lines 584-637). Add mapping:{ "customentity", "customentities" }, // Add your mapping
Enable detailed logging in Power Platform:
- Go to connector Test tab
- Create test connection with OAuth
- Send MCP request via Test operation
- Review logs in Response section (includes correlation IDs)
Copilot Studio
↓ (MCP JSON-RPC 2.0)
Power Platform Connector Runtime
↓ (script.csx routes by method)
HandleMCPRequest()
├─ initialize → Protocol handshake
├─ tools/list → DiscoverCustomAPIsAsync()
│ ↓ (if cache miss)
│ Dataverse Web API (/api/data/v9.2/customapis)
│ ↓
│ GenerateMCPToolDefinition() × N
│ ↓
│ Return tools array
│
└─ tools/call → HandleToolsCall()
↓
ExecuteCustomAPIAsync()
↓ (BuildCustomAPIUrl by binding type)
Dataverse Web API (/{uniquename} or /{entityset}(...))
↓ (GET for Functions, POST for Actions)
ParseCustomAPIResponse()
↓
Return MCP content
- Protocol Version: 2024-11-05
- Server Name:
dataverse-custom-api-mcp - Capabilities:
tools(listChanged: false) - Supported Methods:
initialize- Client/server handshakenotifications/initialized- Initialization confirmationtools/list- Discover Custom APIs (cached 30 min)tools/call- Execute Custom API with parametersping- Health check
- Dataverse Custom APIs
- Dataverse Web API
- Custom API Parameters
- Model Context Protocol
- Power Platform Custom Connectors
- Custom Code Connectors
Initial Release
- 6 Management Tools: Create, update, delete Custom APIs from Copilot Studio
- Create Custom APIs: Full definition creation with solution integration
- Add Parameters & Properties: Dynamic schema building via MCP tools
- Solution Integration:
SolutionUniqueNameparameter for proper ALM - Auto Cache Invalidation: Newly created/modified APIs immediately available
- List Solutions: Discover managed/unmanaged solutions for API placement
- Self-Bootstrapping: Create Custom APIs that become tools without code changes
- Enhanced Security: Management tools require
prvCreate/Write/DeleteCustomAPIprivileges - Type Descriptions: Human-readable type names in success messages
- Automatic Custom API discovery from Dataverse metadata
- Support for all 3 binding types (Global/Entity/EntityCollection)
- Support for all 13 Dataverse parameter types
- Functions (GET) and Actions (POST) handling
- Open type validation with
@odata.typerequirements - 30-minute metadata caching with environment-scoped keys
- Correlation ID request tracing
- Private API filtering (configurable)
- OAuth token forwarding with OData 4.0 headers
- MCP Protocol 2024-11-05 compliance
- Entity set name mapping for 35+ common entities
Version: 2.0.0
Developer: Troy Taylor
Last Updated: January 6, 2026
Brand Color: #da3b01 (Microsoft Orange Red)
License: Use with Power Platform subscription