Dynamics 365 Finance Operations
STDIOProduction-ready MCP server for Microsoft Dynamics 365 Finance & Operations integration
Production-ready MCP server for Microsoft Dynamics 365 Finance & Operations integration
Production-ready Model Context Protocol (MCP) server that exposes the full capabilities of Microsoft Dynamics 365 Finance & Operations (D365 F&O) to AI assistants and other MCP-compatible tools. This enables sophisticated Dynamics 365 integration workflows through standardized protocol interactions.
🚀 One-Click Installation for VS Code:
🐳 Docker Installation for VS Code:
☁️ Deploy to Azure Container Apps:
Deploy the MCP server as a secure, internet-accessible HTTP endpoint with OAuth or API Key authentication. Perfect for web integrations and remote AI assistant access.
Option 1: Using Bash Script (Recommended)
# Download and run the deployment script curl -O https://raw.githubusercontent.com/mafzaal/d365fo-client/main/deploy-aca.sh chmod +x deploy-aca.sh # Set authentication (choose OAuth or API Key) export D365FO_MCP_AUTH_CLIENT_ID="your-client-id" export D365FO_MCP_AUTH_CLIENT_SECRET="your-client-secret" export D365FO_MCP_AUTH_TENANT_ID="your-tenant-id" # OR export D365FO_MCP_API_KEY_VALUE="your-secret-key" # Deploy ./deploy-aca.sh
Option 2: Using ARM Template
azure-deploy.jsonAlso includes a comprehensive Python client library for Microsoft Dynamics 365 Finance & Operations with OData endpoints, metadata operations, label management, and CLI tools.
The d365fo-client includes two production-ready Model Context Protocol (MCP) servers that expose the full capabilities of D365 Finance & Operations to AI assistants and other MCP-compatible tools:
d365fo-mcp-server) - Original implementation with stdio supportd365fo-fastmcp-server) - Modern implementation with multi-transport support ⭐ RecommendedBoth servers provide identical functionality but the FastMCP implementation offers enhanced performance and deployment flexibility.
D365FO_LOG_FILE environment variable for flexible log file pathsD365FO_ prefix for consistency.env files and comprehensive environment variable documentation# Install d365fo-client with MCP dependencies pip install d365fo-client # Set up environment variables export D365FO_BASE_URL="https://your-environment.dynamics.com" export D365FO_CLIENT_ID="your-client-id" # Optional with default credentials export D365FO_CLIENT_SECRET="your-client-secret" # Optional with default credentials export D365FO_TENANT_ID="your-tenant-id" # Optional with default credentials
The modern FastMCP implementation provides enhanced performance and multiple transport options:
# Development (stdio transport - default) d365fo-fastmcp-server # Production HTTP API d365fo-fastmcp-server --transport http --port 8000 --host 0.0.0.0 # Real-time Web Applications (SSE) d365fo-fastmcp-server --transport sse --port 8001 --host 0.0.0.0
Key Benefits:
The original MCP SDK implementation remains available for backward compatibility:
# Start the traditional MCP server d365fo-mcp-server
FastMCP Server with Default Credentials:
Add to your VS Code mcp.json for GitHub Copilot with MCP:
{ "servers": { "d365fo-fastmcp-server": { "type": "stdio", "command": "uvx", "args": [ "--from", "d365fo-client@latest", "d365fo-fastmcp-server" ], "env": { "D365FO_BASE_URL": "https://your-environment.dynamics.com", "D365FO_LOG_LEVEL": "INFO" } } } }
Traditional MCP Server (Alternative):
{ "servers": { "d365fo-mcp-server": { "type": "stdio", "command": "uvx", "args": [ "--from", "d365fo-client", "d365fo-mcp-server" ], "env": { "D365FO_BASE_URL": "https://your-environment.dynamics.com", "D365FO_LOG_LEVEL": "INFO" } } } }
Option 2: Explicit Credentials For environments requiring service principal authentication:
{ "servers": { "d365fo-fastmcp-server": { "type": "stdio", "command": "uvx", "args": [ "--from", "d365fo-client", "d365fo-fastmcp-server" ], "env": { "D365FO_BASE_URL": "https://your-environment.dynamics.com", "D365FO_LOG_LEVEL": "DEBUG", "D365FO_CLIENT_ID": "${input:client_id}", "D365FO_CLIENT_SECRET": "${input:client_secret}", "D365FO_TENANT_ID": "${input:tenant_id}" } } }, "inputs": [ { "id": "tenant_id", "type": "promptString", "description": "Azure AD Tenant ID for D365 F&O authentication", "password": true }, { "id": "client_id", "type": "promptString", "description": "Azure AD Client ID for D365 F&O authentication", "password": true }, { "id": "client_secret", "type": "promptString", "description": "Azure AD Client Secret for D365 F&O authentication", "password": true } ] }
Option 3: Docker Integration For containerized environments and enhanced isolation:
{ "servers": { "d365fo-mcp-server": { "type": "stdio", "command": "docker", "args": [ "run", "--rm", "-i", "-v", "d365fo-mcp:/home/mcp_user/", "-e", "D365FO_CLIENT_ID=${input:client_id}", "-e", "D365FO_CLIENT_SECRET=${input:client_secret}", "-e", "D365FO_TENANT_ID=${input:tenant_id}", "ghcr.io/mafzaal/d365fo-client:latest" ], "env": { "D365FO_LOG_LEVEL": "DEBUG", "D365FO_CLIENT_ID": "${input:client_id}", "D365FO_CLIENT_SECRET": "${input:client_secret}", "D365FO_TENANT_ID": "${input:tenant_id}" } } }, "inputs": [ { "id": "tenant_id", "type": "promptString", "description": "Azure AD Tenant ID for D365 F&O authentication", "password": true }, { "id": "client_id", "type": "promptString", "description": "Azure AD Client ID for D365 F&O authentication", "password": true }, { "id": "client_secret", "type": "promptString", "description": "Azure AD Client Secret for D365 F&O authentication", "password": true } ] }
Benefits of Docker approach:
d365fo-mcp)Prerequisites:
FastMCP Server: Add to your Claude Desktop configuration:
{ "mcpServers": { "d365fo-fastmcp": { "command": "uvx", "args": [ "--from", "d365fo-client", "d365fo-fastmcp-server" ], "env": { "D365FO_BASE_URL": "https://your-environment.dynamics.com", "D365FO_LOG_LEVEL": "INFO" } } } }
Traditional MCP Server (Alternative):
{ "mcpServers": { "d365fo": { "command": "uvx", "args": [ "--from", "d365fo-client", "d365fo-mcp-server" ], "env": { "D365FO_BASE_URL": "https://your-environment.dynamics.com", "D365FO_LOG_LEVEL": "INFO" } } } }
Benefits of uvx approach:
The FastMCP server provides HTTP and SSE transports for web application integration:
import aiohttp import json async def call_d365fo_api(): """Example: Using HTTP transport for web API integration""" # Start FastMCP server with HTTP transport # d365fo-fastmcp-server --transport http --port 8000 mcp_request = { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "d365fo_query_entities", "arguments": { "entityName": "CustomersV3", "top": 10, "select": ["CustomerAccount", "Name"] } } } async with aiohttp.ClientSession() as session: async with session.post( "http://localhost:8000/mcp", json=mcp_request, headers={"Content-Type": "application/json"} ) as response: result = await response.json() print(json.dumps(result, indent=2))
// Example: JavaScript client for real-time D365FO data // Start FastMCP server: d365fo-fastmcp-server --transport sse --port 8001 const eventSource = new EventSource('http://localhost:8001/sse'); eventSource.onmessage = function(event) { const data = JSON.parse(event.data); console.log('Received D365FO data:', data); // Handle real-time updates from D365FO if (data.method === 'notification') { updateDashboard(data.params); } }; // Send MCP requests via SSE function queryCustomers() { const request = { jsonrpc: "2.0", id: Date.now(), method: "tools/call", params: { name: "d365fo_search_entities", arguments: { pattern: "customer", limit: 50 } } }; fetch('http://localhost:8001/sse/send', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(request) }); }
from d365fo_client.mcp import D365FOMCPServer # Create and run server with custom configuration config = { "default_environment": { "base_url": "https://your-environment.dynamics.com", "use_default_credentials": True } } server = D365FOMCPServer(config) await server.run()
Connect using any MCP-compatible client library:
from mcp import Client async with Client("d365fo-mcp-server") as client: # Discover available tools tools = await client.list_tools() # Execute operations result = await client.call_tool( "d365fo_query_entities", {"entityName": "Customers", "top": 5} )
For containerized environments and production deployments:
Pull the Docker Image:
# Pull from GitHub Container Registry docker pull ghcr.io/mafzaal/d365fo-client:latest # Or pull a specific version docker pull ghcr.io/mafzaal/d365fo-client:v0.2.3
Standalone Docker Usage:
# Run MCP server with environment variables docker run --rm -i \ -e D365FO_BASE_URL="https://your-environment.dynamics.com" \ -e D365FO_CLIENT_ID="your-client-id" \ -e D365FO_CLIENT_SECRET="your-client-secret" \ -e D365FO_TENANT_ID="your-tenant-id" \ -e D365FO_LOG_LEVEL="INFO" \ -v d365fo-mcp:/home/mcp_user/ \ ghcr.io/mafzaal/d365fo-client:latest # Run CLI commands with Docker docker run --rm -it \ -e D365FO_BASE_URL="https://your-environment.dynamics.com" \ -e D365FO_CLIENT_ID="your-client-id" \ -e D365FO_CLIENT_SECRET="your-client-secret" \ -e D365FO_TENANT_ID="your-tenant-id" \ ghcr.io/mafzaal/d365fo-client:latest \ d365fo-client entities --limit 10
Docker Compose Example:
version: '3.8' services: d365fo-mcp: image: ghcr.io/mafzaal/d365fo-client:latest environment: - D365FO_BASE_URL=https://your-environment.dynamics.com - D365FO_CLIENT_ID=${D365FO_CLIENT_ID} - D365FO_CLIENT_SECRET=${D365FO_CLIENT_SECRET} - D365FO_TENANT_ID=${D365FO_TENANT_ID} - D365FO_LOG_LEVEL=INFO volumes: - d365fo-mcp:/home/mcp_user/ stdin_open: true tty: true volumes: d365fo-mcp:
Docker Benefits:
Connection Failures
# Test connectivity d365fo-client version app --base-url https://your-environment.dynamics.com # Check logs tail -f ~/.d365fo-mcp/logs/mcp-server.log
Authentication Issues
# Verify Azure CLI authentication az account show # Test with explicit credentials export D365FO_CLIENT_ID="your-client-id" # ... set other variables d365fo-mcp-server
Performance Issues
# Enable debug logging export D365FO_LOG_LEVEL="DEBUG" # Adjust connection settings export D365FO_CONNECTION_TIMEOUT="120" export D365FO_MAX_CONCURRENT_REQUESTS="5"
~/.d365fo-mcp/logs/mcp-server.log for detailed error informationd365fo_get_environment_info tool to check system statusThe server provides 49 comprehensive tools organized into functional categories:
d365fo_test_connection - Test connectivity and authentication with performance metrics and error diagnosticsd365fo_get_environment_info - Get comprehensive environment details including versions, configurations, and capabilitiesd365fo_query_entities - Simplified OData querying with 'eq' filtering, wildcard patterns, field selection, and paginationd365fo_get_entity_record - Retrieve specific records by key with expansion options and ETag supportd365fo_create_entity_record - Create new entity records with validation and business logic executiond365fo_update_entity_record - Update existing records with partial updates and optimistic concurrency controld365fo_delete_entity_record - Delete entity records with referential integrity checking and cascading rulesd365fo_call_action - Execute OData actions and functions for complex business operationsd365fo_call_json_service - Call generic JSON service endpoints with parameter support and response handlingd365fo_search_entities - Search entities by pattern with category filtering and full-text search capabilitiesd365fo_get_entity_schema - Get detailed entity schemas with properties, relationships, and label resolutiond365fo_search_actions - Search available OData actions with binding type and parameter informationd365fo_search_enumerations - Search system enumerations with keyword-based filteringd365fo_get_enumeration_fields - Get detailed enumeration member information with multi-language supportd365fo_get_installed_modules - Retrieve information about installed modules and their configurationsd365fo_get_label - Get single label text by ID with multi-language support and fallback optionsd365fo_get_labels_batch - Get multiple labels efficiently with batch processing and performance optimizationd365fo_list_profiles - List all configured D365FO environment profiles with status informationd365fo_get_profile - Get detailed configuration information for specific profilesd365fo_create_profile - Create new environment profiles with comprehensive authentication optionsd365fo_update_profile - Modify existing profile configurations with partial update supportd365fo_delete_profile - Remove environment profiles with proper cleanup and validationd365fo_set_default_profile - Designate a specific profile as the default for operationsd365fo_get_default_profile - Retrieve information about the currently configured default profiled365fo_validate_profile - Validate profile configurations for completeness and security complianced365fo_test_profile_connection - Test connectivity and authentication for specific profilesd365fo_clone_profile - Clone existing profiles with customization options for new environmentsd365fo_search_profiles - Search profiles by pattern with filtering and sorting capabilitiesd365fo_get_profile_names - Get simplified list of available profile names for quick referenced365fo_import_profiles - Import profile configurations from external sources or backupsd365fo_export_profiles - Export profile configurations for backup or deployment purposesd365fo_execute_sql_query - Execute SELECT queries against metadata database with security validationd365fo_get_database_schema - Get comprehensive database schema information including relationshipsd365fo_get_table_info - Get detailed information about specific database tables with sample datad365fo_get_database_statistics - Generate database statistics and analytics for performance monitoringd365fo_start_sync - Initiate metadata synchronization with various strategies and session trackingd365fo_get_sync_progress - Monitor detailed progress of sync sessions with time estimatesd365fo_cancel_sync - Cancel running sync sessions with graceful cleanupd365fo_list_sync_sessions - List all active sync sessions with status and progress informationd365fo_get_sync_history - Get history of completed sync sessions with success/failure status and statisticsd365fo_download_srs_report - Download SQL Server Reporting Services (SRS) reports with parameter supportd365fo_download_sales_confirmation - Download sales confirmation reports in various formatsd365fo_download_purchase_order - Download purchase order documents with formatting optionsd365fo_download_customer_invoice - Download customer invoice reports with customizationd365fo_download_free_text_invoice - Download free text invoice documentsd365fo_download_debit_credit_note - Download debit and credit note reportsd365fo_get_server_performance - Get server performance metrics and statisticsd365fo_get_server_config - Get server configuration information and system settingsd365fo_reset_performance_stats - Reset performance statistics and counters for fresh monitoring📖 For detailed information about all MCP tools including usage examples and best practices, see the Comprehensive MCP Tools Introduction.
🤖 For AI agents and assistants, see the AI Agent Guide for structured workflows, best practices, and automation patterns.
The server exposes four types of resources for discovery and access:
Access entity metadata and sample data:
d365fo://entities/CustomersV3 # Customer entity with metadata and sample data
d365fo://entities/SalesOrders # Sales order entity information
d365fo://entities/Products # Product entity details
Access system-wide metadata:
d365fo://metadata/entities # All data entities metadata (V2 cache)
d365fo://metadata/actions # Available OData actions
d365fo://metadata/enumerations # System enumerations
d365fo://metadata/labels # System labels and translations
Access environment status and information:
d365fo://environment/status # Environment health and connectivity
d365fo://environment/version # Version information (app, platform, build)
d365fo://environment/cache # Cache status and statistics V2
Access predefined and templated queries:
d365fo://queries/customers_recent # Recent customers query template
d365fo://queries/sales_summary # Sales summary query with parameters
Access metadata database queries:
d365fo://database/entities # SQL-based entity searches with FTS5
d365fo://database/actions # Action discovery with metadata
d365fo://database/statistics # Cache and performance statistics
{ "tool": "d365fo_query_entities", "arguments": { "entityName": "CustomersV3", "select": ["CustomerAccount", "Name", "Email"], "filter": "CustomerGroup eq 'VIP'", "top": 10 } }
{ "tool": "d365fo_get_entity_schema", "arguments": { "entityName": "CustomersV3", "includeProperties": true, "resolveLabels": true, "language": "en-US" } }
{ "tool": "d365fo_get_environment_info", "arguments": {} }
Uses Azure Default Credential chain (Managed Identity, Azure CLI, etc.):
export D365FO_BASE_URL="https://your-environment.dynamics.com" # No additional auth environment variables needed d365fo-mcp-server
For service principal authentication:
export D365FO_BASE_URL="https://your-environment.dynamics.com" export D365FO_CLIENT_ID="your-client-id" export D365FO_CLIENT_SECRET="your-client-secret" export D365FO_TENANT_ID="your-tenant-id" d365fo-mcp-server
For secure credential storage using Azure Key Vault:
export D365FO_BASE_URL="https://your-environment.dynamics.com" export D365FO_CREDENTIAL_SOURCE="keyvault" export D365FO_KEYVAULT_URL="https://your-keyvault.vault.azure.net/" d365fo-mcp-server
New in v0.3.0: Comprehensive environment variable management with type safety and validation using Pydantic settings.
Create a configuration file or set additional environment variables:
# === Core D365FO Connection Settings === export D365FO_BASE_URL="https://your-environment.dynamics.com" export D365FO_CLIENT_ID="your-client-id" export D365FO_CLIENT_SECRET="your-client-secret" export D365FO_TENANT_ID="your-tenant-id" # === Logging Configuration === export D365FO_LOG_LEVEL="DEBUG" # DEBUG, INFO, WARNING, ERROR, CRITICAL export D365FO_LOG_FILE="/custom/path/server.log" # Custom log file path # === MCP Server Transport Settings (v0.3.0+) === export D365FO_MCP_TRANSPORT="stdio" # stdio, sse, http, streamable-http export D365FO_MCP_HTTP_HOST="0.0.0.0" # HTTP host (default: 127.0.0.1) export D365FO_MCP_HTTP_PORT="8000" # HTTP port (default: 8000) export D365FO_MCP_HTTP_STATELESS="true" # Enable stateless mode export D365FO_MCP_HTTP_JSON="true" # Enable JSON response mode # === Cache and Performance Settings === export D365FO_CACHE_DIR="/custom/cache/path" # General cache directory export D365FO_META_CACHE_DIR="/custom/metadata/cache" # Metadata cache directory export D365FO_LABEL_CACHE="true" # Enable label caching (default: true) export D365FO_LABEL_EXPIRY="1440" # Label cache expiry in minutes (24 hours) export D365FO_USE_CACHE_FIRST="true" # Use cache before API calls # === Connection and Performance Tuning === export D365FO_TIMEOUT="60" # General timeout in seconds export D365FO_MCP_MAX_CONCURRENT_REQUESTS="10" # Max concurrent requests export D365FO_MCP_REQUEST_TIMEOUT="30" # Request timeout in seconds export D365FO_VERIFY_SSL="true" # Verify SSL certificates # === MCP Authentication Settings (Advanced) === export D365FO_MCP_AUTH_CLIENT_ID="your-mcp-client-id" export D365FO_MCP_AUTH_CLIENT_SECRET="your-mcp-client-secret" export D365FO_MCP_AUTH_TENANT_ID="your-mcp-tenant-id" export D365FO_MCP_AUTH_BASE_URL="http://localhost:8000" export D365FO_MCP_AUTH_REQUIRED_SCOPES="User.Read,email,openid,profile" # === Debug Settings === export DEBUG="true" # Enable debug mode
Environment File Support: You can also create a .env file in your project directory with these variables for development convenience.
# Install from PyPI pip install d365fo-client # Or install from source git clone https://github.com/mafzaal/d365fo-client.git cd d365fo-client uv sync # Installs with exact dependencies from uv.lock # Or use Docker (no local installation required) docker pull ghcr.io/mafzaal/d365fo-client:latest # Run with Docker docker run --rm -it \ -e D365FO_BASE_URL="https://your-environment.dynamics.com" \ -e D365FO_CLIENT_ID="your-client-id" \ -e D365FO_CLIENT_SECRET="your-client-secret" \ -e D365FO_TENANT_ID="your-tenant-id" \ -v d365fo-mcp:/home/mcp_user/ \ ghcr.io/mafzaal/d365fo-client:latest
Note: The package includes MCP (Model Context Protocol) dependencies by default, enabling AI assistant integration. Both d365fo-client CLI and d365fo-mcp-server commands will be available after installation.
Breaking Change in v0.2.3: Environment variable names have been updated for consistency:
AZURE_CLIENT_ID → D365FO_CLIENT_IDAZURE_CLIENT_SECRET → D365FO_CLIENT_SECRETAZURE_TENANT_ID → D365FO_TENANT_IDPlease update your environment variables accordingly when upgrading.
d365fo-client provides a comprehensive CLI with hierarchical commands for interacting with Dynamics 365 Finance & Operations APIs and metadata. The CLI supports all major operations including entity management, metadata discovery, and system administration.
# Use the installed CLI command d365fo-client [GLOBAL_OPTIONS] COMMAND [SUBCOMMAND] [OPTIONS] # Alternative: Module execution python -m d365fo_client.main [OPTIONS] COMMAND [ARGS]
# List entities with filtering d365fo-client entities list --pattern "customer" --limit 10 # Get entity details and schema d365fo-client entities get CustomersV3 --properties --keys --labels # CRUD operations d365fo-client entities create Customers --data '{"CustomerAccount":"US-999","Name":"Test"}' d365fo-client entities update Customers US-999 --data '{"Name":"Updated Name"}' d365fo-client entities delete Customers US-999
# Search and discover entities d365fo-client metadata entities --search "sales" --output json # Get available actions d365fo-client metadata actions --pattern "calculate" --limit 5 # Enumerate system enumerations d365fo-client metadata enums --search "status" --output table # Synchronize metadata cache d365fo-client metadata sync --force-refresh
# Get application versions d365fo-client version app d365fo-client version platform d365fo-client version build
# Resolve single label d365fo-client labels resolve "@SYS13342" # Search labels by pattern d365fo-client labels search "customer" --language "en-US"
# Call SQL diagnostic services d365fo-client service sql-diagnostic GetAxSqlExecuting d365fo-client service sql-diagnostic GetAxSqlResourceStats --since-minutes 5 d365fo-client service sql-diagnostic GetAxSqlBlocking --output json # Generic JSON service calls d365fo-client service call SysSqlDiagnosticService SysSqlDiagnosticServiceOperations GetAxSqlExecuting d365fo-client service call YourServiceGroup YourServiceName YourOperation --parameters '{"param1":"value1"}'
--base-url URL — Specify D365 F&O environment URL--profile NAME — Use named configuration profile--output FORMAT — Output format: json, table, csv, yaml (default: table)--verbose — Enable verbose output for debugging--timeout SECONDS — Request timeout (default: 30)Create reusable configurations in ~/.d365fo-client/config.yaml:
profiles: production: base_url: "https://prod.dynamics.com" use_default_credentials: true timeout: 60 development: base_url: "https://dev.dynamics.com" client_id: "${D365FO_CLIENT_ID}" client_secret: "${D365FO_CLIENT_SECRET}" tenant_id: "${D365FO_TENANT_ID}" use_cache_first: true default_profile: "development"
# Quick entity discovery d365fo-client entities list --pattern "cust.*" --output json # Get comprehensive entity information d365fo-client entities get CustomersV3 --properties --keys --labels --output yaml # Search for calculation actions d365fo-client metadata actions --pattern "calculate|compute" --output table # Test environment connectivity d365fo-client version app --verbose
For a complete command reference:
d365fo-client --help d365fo-client entities --help d365fo-client metadata --help
import asyncio from d365fo_client import D365FOClient, FOClientConfig async def main(): # Simple configuration with default credentials config = FOClientConfig( base_url="https://your-fo-environment.dynamics.com", use_default_credentials=True # Uses Azure Default Credential ) async with D365FOClient(config) as client: # Test connection if await client.test_connection(): print("✅ Connected successfully!") # Get environment information env_info = await client.get_environment_info() print(f"Environment: {env_info.application_version}") # Search for entities (uses metadata cache v2) customer_entities = await client.search_entities("customer") print(f"Found {len(customer_entities)} customer entities") # Get customers with query options from d365fo_client import QueryOptions options = QueryOptions( select=["CustomerAccount", "Name", "SalesCurrencyCode"], top=10, orderby=["Name"] ) customers = await client.get_data("/data/CustomersV3", options) print(f"Retrieved {len(customers['value'])} customers") if __name__ == "__main__": asyncio.run(main())
from d365fo_client import create_client # Quick client creation with enhanced defaults async with create_client("https://your-fo-environment.dynamics.com") as client: customers = await client.get_data("/data/CustomersV3", top=5)
The d365fo-client now includes a comprehensive Pydantic settings model for type-safe environment variable management:
from d365fo_client import D365FOSettings, get_settings # Get type-safe settings instance settings = get_settings() # Access settings with full IntelliSense support print(f"Base URL: {settings.base_url}") print(f"Log Level: {settings.log_level}") print(f"Cache Directory: {settings.cache_dir}") # Check configuration state if settings.has_client_credentials(): print("Client credentials configured") startup_mode = settings.get_startup_mode() # "profile_only", "default_auth", "client_credentials" # Convert to environment dictionary for external tools env_vars = settings.to_env_dict()
Key Benefits:
.env files in developmentfrom d365fo_client import FOClientConfig # Option 1: Default Azure credentials (recommended) config = FOClientConfig( base_url="https://your-fo-environment.dynamics.com", use_default_credentials=True ) # Option 2: Client credentials config = FOClientConfig( base_url="https://your-fo-environment.dynamics.com", client_id="your-client-id", client_secret="your-client-secret", tenant_id="your-tenant-id", use_default_credentials=False ) # Option 3: Azure Key Vault integration (New in v0.2.3) config = FOClientConfig( base_url="https://your-fo-environment.dynamics.com", credential_source="keyvault", # Use Azure Key Vault for credentials keyvault_url="https://your-keyvault.vault.azure.net/" ) # Option 4: With custom settings config = FOClientConfig( base_url="https://your-fo-environment.dynamics.com", use_default_credentials=True, verify_ssl=False, # For development environments timeout=60, # Request timeout in seconds metadata_cache_dir="./my_cache", # Custom cache directory use_label_cache=True, # Enable label caching label_cache_expiry_minutes=120 # Cache for 2 hours )
The d365fo-client automatically detects and migrates legacy configuration files:
verify_ssl, outdated field names)cache_dir → metadata_cache_dir, auth_mode → use_default_credentials# Legacy configurations are automatically migrated when FastMCP server starts # No manual intervention required - migration happens transparently
async with D365FOClient(config) as client: # CREATE - Create new customer (supports composite keys) new_customer = { "CustomerAccount": "US-999", "Name": "Test Customer", "SalesCurrencyCode": "USD" } created = await client.create_data("/data/CustomersV3", new_customer) # READ - Get single customer by key customer = await client.get_data("/data/CustomersV3('US-001')") # UPDATE - Update customer with optimistic concurrency updates = {"Name": "Updated Customer Name"} updated = await client.update_data("/data/CustomersV3('US-001')", updates) # DELETE - Delete customer success = await client.delete_data("/data/CustomersV3('US-999')") print(f"Delete successful: {success}")
from d365fo_client import QueryOptions # Complex query with multiple options options = QueryOptions( select=["CustomerAccount", "Name", "SalesCurrencyCode", "CustomerGroupId"], filter="SalesCurrencyCode eq 'USD' and contains(Name, 'Corp')", expand=["CustomerGroup"], orderby=["Name desc", "CustomerAccount"], top=50, skip=10, count=True ) result = await client.get_data("/data/CustomersV3", options) print(f"Total count: {result.get('@odata.count')}")
# Unbound action result = await client.post_data("/data/calculateTax", { "amount": 1000.00, "taxGroup": "STANDARD" }) # Bound action on entity set result = await client.post_data("/data/CustomersV3/calculateBalances", { "asOfDate": "2024-12-31" }) # Bound action on specific entity instance result = await client.post_data("/data/CustomersV3('US-001')/calculateBalance", { "asOfDate": "2024-12-31" })
# Basic JSON service call (no parameters) response = await client.post_json_service( service_group="SysSqlDiagnosticService", service_name="SysSqlDiagnosticServiceOperations", operation_name="GetAxSqlExecuting" ) if response.success: print(f"Found {len(response.data)} executing SQL statements") print(f"Status: HTTP {response.status_code}") else: print(f"Error: {response.error_message}") # JSON service call with parameters from datetime import datetime, timezone, timedelta end_time = datetime.now(timezone.utc) start_time = end_time - timedelta(minutes=10) response = await client.post_json_service( service_group="SysSqlDiagnosticService", service_name="SysSqlDiagnosticServiceOperations", operation_name="GetAxSqlResourceStats", parameters={ "start": start_time.isoformat(), "end": end_time.isoformat() } ) # Using JsonServiceRequest object for better structure from d365fo_client.models import JsonServiceRequest request = JsonServiceRequest( service_group="SysSqlDiagnosticService", service_name="SysSqlDiagnosticServiceOperations", operation_name="GetAxSqlBlocking" ) response = await client.call_json_service(request) print(f"Service endpoint: {request.get_endpoint_path()}") # Multiple SQL diagnostic operations operations = ["GetAxSqlExecuting", "GetAxSqlBlocking", "GetAxSqlLockInfo"] for operation in operations: response = await client.post_json_service( service_group="SysSqlDiagnosticService", service_name="SysSqlDiagnosticServiceOperations", operation_name=operation ) if response.success: count = len(response.data) if isinstance(response.data, list) else 1 print(f"{operation}: {count} records") # Custom service call template response = await client.post_json_service( service_group="YourServiceGroup", service_name="YourServiceName", operation_name="YourOperation", parameters={ "parameter1": "value1", "parameter2": 123, "parameter3": True } )
# Intelligent metadata synchronization (v2 system) sync_manager = await client.get_sync_manager() await sync_manager.smart_sync() # Search entities with enhanced filtering sales_entities = await client.search_entities("sales") print("Sales-related entities:", [e.name for e in sales_entities]) # Get detailed entity information with labels entity_info = await client.get_public_entity_info("CustomersV3") if entity_info: print(f"Entity: {entity_info.name}") print(f"Label: {entity_info.label_text}") print(f"Data Service Enabled: {entity_info.data_service_enabled}") # Search actions with caching calc_actions = await client.search_actions("calculate") print("Calculation actions:", [a.name for a in calc_actions]) # Get enumeration information enum_info = await client.get_public_enumeration_info("NoYes") if enum_info: print(f"Enum: {enum_info.name}") for member in enum_info.members: print(f" {member.name} = {member.value}")
# Get specific label (v2 caching system) label_text = await client.get_label_text("@SYS13342") print(f"Label text: {label_text}") # Get multiple labels efficiently labels = await client.get_labels_batch([ "@SYS13342", "@SYS9490", "@GLS63332" ]) for label_id, text in labels.items(): print(f"{label_id}: {text}") # Enhanced entity info with resolved labels entity_info = await client.get_public_entity_info_with_labels("CustomersV3") if entity_info.label_text: print(f"Entity display name: {entity_info.label_text}") # Access enhanced properties with labels for prop in entity_info.enhanced_properties[:5]: if hasattr(prop, 'label_text') and prop.label_text: print(f"{prop.name}: {prop.label_text}")
from d365fo_client import D365FOClientError, AuthenticationError, ConnectionError try: async with D365FOClient(config) as client: customer = await client.get_data("/data/CustomersV3('NON-EXISTENT')") except ConnectionError as e: print(f"Connection failed: {e}") except AuthenticationError as e: print(f"Authentication failed: {e}") except D365FOClientError as e: print(f"Client operation failed: {e}") print(f"Status code: {e.status_code}") print(f"Response: {e.response_text}")
# Clone the repository git clone https://github.com/mafzaal/d365fo-client.git cd d365fo-client # Install with development dependencies using uv uv sync --dev # Run tests uv run pytest # Run integration tests .\tests\integration\integration-test-simple.ps1 test-sandbox # Format code uv run black . uv run isort . # Type checking uv run mypy src/ # Quality checks .\make.ps1 quality-check # Windows PowerShell # or make quality-check # Unix/Linux/macOS
d365fo-client/
├── src/
│ └── d365fo_client/
│ ├── __init__.py # Public API exports
│ ├── main.py # CLI entry point
│ ├── cli.py # CLI command handlers
│ ├── client.py # Enhanced D365FOClient class
│ ├── config.py # Configuration management
│ ├── auth.py # Authentication management
│ ├── session.py # HTTP session management
│ ├── crud.py # CRUD operations
│ ├── query.py # OData query utilities
│ ├── metadata.py # Legacy metadata operations
│ ├── metadata_api.py # Metadata API client
│ ├── metadata_cache.py # Metadata caching layer V2
│ ├── metadata_sync.py # Metadata synchronization V2 with session management
│ ├── sync_session.py # Enhanced sync session management (New in v0.2.3)
│ ├── credential_manager.py # Credential source management (New in v0.2.3)
│ ├── labels.py # Label operations V2
│ ├── profiles.py # Profile data models
│ ├── profile_manager.py # Profile management
│ ├── models.py # Data models and configurations
│ ├── output.py # Output formatting
│ ├── utils.py # Utility functions
│ ├── exceptions.py # Custom exceptions
│ └── mcp/ # Model Context Protocol server
│ ├── __init__.py # MCP server exports
│ ├── main.py # MCP server entry point
│ ├── server.py # Core MCP server implementation
│ ├── client_manager.py# D365FO client connection pooling
│ ├── models.py # MCP-specific data models
│ ├── mixins/ # FastMCP tool mixins (49 tools)
│ ├── tools/ # Legacy MCP tools (deprecated)
│ │ ├── connection_tools.py
│ │ ├── crud_tools.py
│ │ ├── metadata_tools.py
│ │ └── label_tools.py
│ ├── resources/ # MCP resource handlers (4 types)
│ │ ├── entity_handler.py
│ │ ├── metadata_handler.py
│ │ ├── environment_handler.py
│ │ └── query_handler.py
│ └── prompts/ # MCP prompt templates
├── tests/ # Comprehensive test suite
│ ├── unit/ # Unit tests (pytest-based)
│ ├── integration/ # Multi-tier integration testing
│ │ ├── mock_server/ # Mock D365 F&O API server
│ │ ├── test_mock_server.py # Mock server tests
│ │ ├── test_sandbox.py # Sandbox environment tests ✅
│ │ ├── test_live.py # Live environment tests
│ │ ├── conftest.py # Shared pytest fixtures
│ │ ├── test_runner.py # Python test execution engine
│ │ └── integration-test-simple.ps1 # PowerShell automation
│ └── test_mcp_server.py # MCP server unit tests ✅
├── scripts/ # Metadata discovery scripts
│ ├── search_data_entities.ps1 # PowerShell entity search
│ ├── get_data_entity_schema.ps1 # PowerShell schema retrieval
│ ├── search_enums.py # Python enumeration search
│ ├── get_enumeration_info.py # Python enumeration info
│ ├── search_actions.ps1 # PowerShell action search
│ └── get_action_info.py # Python action information
├── docs/ # Comprehensive documentation
├── pyproject.toml # Project configuration
└── README.md # This file
| Option | Type | Default | Description |
|---|---|---|---|
base_url | str | Required | D365 F&O base URL |
client_id | str | None | Azure AD client ID |
client_secret | str | None | Azure AD client secret |
tenant_id | str | None | Azure AD tenant ID |
use_default_credentials | bool | True | Use Azure Default Credential |
credential_source | str | "environment" | Credential source: "environment", "keyvault" |
keyvault_url | str | None | Azure Key Vault URL for credential storage |
verify_ssl | bool | False | Verify SSL certificates |
timeout | int | 30 | Request timeout in seconds |
metadata_cache_dir | str | Platform-specific user cache | Metadata cache directory |
use_label_cache | bool | True | Enable label caching V2 |
label_cache_expiry_minutes | int | 60 | Label cache expiry time |
use_cache_first | bool | False | Enable cache-first mode with background sync |
By default, the client uses platform-appropriate user cache directories:
%LOCALAPPDATA%\d365fo-client (e.g., C:\Users\username\AppData\Local\d365fo-client)~/Library/Caches/d365fo-client (e.g., /Users/username/Library/Caches/d365fo-client)~/.cache/d365fo-client (e.g., /home/username/.cache/d365fo-client)You can override this by explicitly setting metadata_cache_dir:
from d365fo_client import FOClientConfig # Use custom cache directory config = FOClientConfig( base_url="https://your-fo-environment.dynamics.com", metadata_cache_dir="/custom/cache/path" ) # Or get the default cache directory programmatically from d365fo_client import get_user_cache_dir cache_dir = get_user_cache_dir("my-app") # Platform-appropriate cache dir config = FOClientConfig( base_url="https://your-fo-environment.dynamics.com", metadata_cache_dir=str(cache_dir) )
This project includes comprehensive testing at multiple levels to ensure reliability and quality.
Run standard unit tests for core functionality:
# Run all unit tests uv run pytest # Run with coverage uv run pytest --cov=d365fo_client --cov-report=html # Run specific test file uv run pytest tests/test_client.py -v
The project includes a sophisticated multi-tier integration testing framework:
# Run sandbox integration tests (recommended) .\tests\integration\integration-test-simple.ps1 test-sandbox # Run mock server tests (no external dependencies) .\tests\integration\integration-test-simple.ps1 test-mock # Run with verbose output .\tests\integration\integration-test-simple.ps1 test-sandbox -VerboseOutput
Mock Server Tests - Fast, isolated tests against a simulated D365 F&O API
Sandbox Tests ⭐ (Default) - Tests against real D365 F&O test environments
Live Tests - Optional tests against production environments
Set up integration testing with environment variables:
# Copy the template and configure cp tests/integration/.env.template tests/integration/.env # Edit .env file with your settings: INTEGRATION_TEST_LEVEL=sandbox D365FO_SANDBOX_BASE_URL=https://your-test.dynamics.com D365FO_CLIENT_ID=your-client-id D365FO_CLIENT_SECRET=your-client-secret D365FO_TENANT_ID=your-tenant-id
# Test environment setup .\tests\integration\integration-test-simple.ps1 setup # Dependency checking .\tests\integration\integration-test-simple.ps1 deps-check # Run specific test levels .\tests\integration\integration-test-simple.ps1 test-mock .\tests\integration\integration-test-simple.ps1 test-sandbox .\tests\integration\integration-test-simple.ps1 test-live # Coverage and reporting .\tests\integration\integration-test-simple.ps1 coverage # Clean up test artifacts .\tests\integration\integration-test-simple.ps1 clean
Integration tests cover:
For detailed information, see Integration Testing Documentation.
Recent sandbox integration test results:
✅ 17 passed, 0 failed, 2 warnings in 37.67s
======================================================
✅ TestSandboxConnection::test_connection_success
✅ TestSandboxConnection::test_metadata_connection_success
✅ TestSandboxVersionMethods::test_get_application_version
✅ TestSandboxVersionMethods::test_get_platform_build_version
✅ TestSandboxVersionMethods::test_get_application_build_version
✅ TestSandboxVersionMethods::test_version_consistency
✅ TestSandboxMetadataOperations::test_download_metadata
✅ TestSandboxMetadataOperations::test_search_entities
✅ TestSandboxMetadataOperations::test_get_data_entities
✅ TestSandboxMetadataOperations::test_get_public_entities
✅ TestSandboxDataOperations::test_get_available_entities
✅ TestSandboxDataOperations::test_odata_query_options
✅ TestSandboxAuthentication::test_authenticated_requests
✅ TestSandboxErrorHandling::test_invalid_entity_error
✅ TestSandboxErrorHandling::test_invalid_action_error
✅ TestSandboxPerformance::test_response_times
✅ TestSandboxPerformance::test_concurrent_operations
git checkout -b feature/amazing-feature)uv run pytest).\tests\integration\integration-test-simple.ps1 test-sandbox)uv run black . && uv run isort .)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)This project is licensed under the MIT License - see the LICENSE file for details.
See CHANGELOG.md for a list of changes and version history.