Server data from the Official MCP Registry
Query and manage Microsoft Dynamics 365 Finance & Operations via MCP
Query and manage Microsoft Dynamics 365 Finance & Operations via MCP
Valid MCP server (1 strong, 1 medium validity signals). 10 known CVEs in dependencies (1 critical, 4 high severity) Package registry verified. Imported from the Official MCP Registry.
3 files analyzed · 11 issues found
Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.
Unverified package source
We couldn't verify that the installable package matches the reviewed source code. Proceed with caution.
Set these up before or after installing:
Environment variable: D365_TENANT_ID
Environment variable: D365_CLIENT_ID
Environment variable: D365_CLIENT_SECRET
Environment variable: D365_ENVIRONMENT_URL
Add this to your MCP configuration file:
{
"mcpServers": {
"io-github-zhound420-d365fo-connector": {
"env": {
"D365_CLIENT_ID": "your-d365-client-id-here",
"D365_TENANT_ID": "your-d365-tenant-id-here",
"D365_CLIENT_SECRET": "your-d365-client-secret-here",
"D365_ENVIRONMENT_URL": "your-d365-environment-url-here"
},
"args": [
"-y",
"@zhound/d365fo-mcp-server"
],
"command": "npx"
}
}
}From the project's GitHub README.
An MCP (Model Context Protocol) server that provides access to Microsoft Dynamics 365 Finance & Operations environments. Enables AI assistants like Claude to explore D365 metadata, query data, and perform write operations on non-production environments.
| Resource | URI | Purpose |
|---|---|---|
| Entities List | d365://entities?filter=<pattern> | List all entities with optional wildcard filtering |
| Entity Schema | d365://entity/{entityName} | Full schema for any entity (fields, keys, navigation properties) |
| Navigation Properties | d365://navigation/{entityName} | Entity relationships and navigation properties |
| Enum Definitions | d365://enums | All enum types with their values |
| Saved Queries | d365://queries | List saved query templates |
| Dashboard | d365://dashboard | JSON metrics for all environments (health, API stats, recent operations) |
All tools support an optional environment parameter to target specific D365 environments.
| Tool | Purpose |
|---|---|
list_environments | List all configured D365 environments with connection status |
set_environment | Set the working environment for the current session |
describe_entity | Quick schema lookup for an entity |
execute_odata | Execute raw OData paths (queries, single records, counts) |
aggregate | Perform aggregations (SUM, AVG, COUNT, MIN, MAX, COUNTDISTINCT, percentiles) on entity data |
get_related | Follow entity relationships to retrieve related records |
export | Export query results to CSV, JSON, or TSV format |
compare_periods | YoY, QoQ, MoM period comparisons with change calculations |
trending | Time series analysis with growth rates and moving averages |
save_query | Save reusable query templates with parameter support |
execute_saved_query | Execute saved query templates with parameter substitution |
delete_saved_query | Delete saved query templates |
join_entities | Cross-entity joins using $expand or client-side join |
batch_query | Execute multiple queries in parallel |
search_entity | Robust entity search with automatic fallback strategies |
analyze_customer | Comprehensive single-call customer analysis |
create_record | Create new records (non-production environments only) |
update_record | Update existing records (non-production environments only) |
delete_record | Delete records (non-production environments only) |
batch_crud | Execute multiple create/update/delete operations in a single batch request (non-production only) |
compare_schemas | Compare entity schemas between two environments to detect schema drift |
dashboard | Display environment dashboard with health status, API statistics, and recent operations |
npx @zhound/d365fo-mcp-server
Or install globally:
npm install -g @zhound/d365fo-mcp-server
d365fo-mcp
git clone https://github.com/zhound420/D365FO-claude-connector.git
cd D365FO-claude-connector
npm install
npm run build
Run the interactive setup wizard:
npm run setup
The wizard will:
After setup, restart Claude Desktop (Cmd+Q then reopen on macOS, or Ctrl+Q on Windows) or start a new Claude Code session.
Create a d365-environments.json file in the project root or working directory:
{
"environments": [
{
"name": "production",
"displayName": "Production",
"type": "production",
"tenantId": "your-tenant-id",
"clientId": "your-client-id",
"clientSecret": "your-client-secret",
"environmentUrl": "https://your-company.operations.dynamics.com",
"default": true
},
{
"name": "uat",
"displayName": "UAT (Tier 2)",
"type": "non-production",
"tenantId": "your-tenant-id",
"clientId": "your-client-id",
"clientSecret": "your-client-secret",
"environmentUrl": "https://your-company-uat.sandbox.operations.dynamics.com"
},
{
"name": "dev",
"displayName": "Dev Sandbox",
"type": "non-production",
"tenantId": "your-tenant-id",
"clientId": "your-client-id",
"clientSecret": "your-client-secret",
"environmentUrl": "https://your-company-dev.sandbox.operations.dynamics.com"
}
]
}
Environment Types:
type: "production" - Read-only access (all write operations are blocked)type: "non-production" - Full read/write access (create, update, delete enabled)Copy d365-environments.example.json as a starting point.
The server also supports the following environment variables (fallback if no JSON config):
| Variable | Description |
|---|---|
D365_TENANT_ID | Azure AD tenant ID |
D365_CLIENT_ID | Azure AD application (client) ID |
D365_CLIENT_SECRET | Azure AD client secret |
D365_ENVIRONMENT_URL | D365 F&O environment URL (e.g., https://contoso.operations.dynamics.com) |
D365_ENVIRONMENT_TYPE | Optional: "production" or "non-production" (defaults to "production" for safety) |
Optional:
| Variable | Default | Description |
|---|---|---|
D365_TRANSPORT | stdio | Transport mode (stdio or http) |
D365_HTTP_PORT | 3000 | HTTP port (when using http transport) |
D365_LOG_LEVEL | info | Logging level |
D365_PAGINATION_TIMEOUT_MS | 60000 | Timeout (ms) for paginated requests on large datasets |
D365_CONFIG_FILE | Path to config file if not in default location |
CustomService.ReadWrite.AllImportant: This step must be done in each D365 environment (Production, UAT, Dev) you want to connect to.
In D365 F&O, navigate to: System Administration > Setup > Azure Active Directory applications
Click "New" to add a record:
| Field | Value |
|---|---|
| Client ID | The Application (client) ID from Azure AD |
| Name | Descriptive name (e.g., "MCP Server Integration") |
| User ID | A D365 user account for the app to run as |
The User ID determines what data the app can access:
Repeat for each environment you want to connect to
Note: If you skip this step, API calls will fail with 401 Unauthorized or 403 Forbidden errors even though Azure AD authentication succeeded.
Add to your Claude Desktop config file:
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"Microsoft D365": {
"command": "node",
"args": ["/path/to/d365fo-mcp-server/dist/index.js"],
"env": {
"D365_TENANT_ID": "your-tenant-id",
"D365_CLIENT_ID": "your-client-id",
"D365_CLIENT_SECRET": "your-client-secret",
"D365_ENVIRONMENT_URL": "https://your-env.operations.dynamics.com"
}
}
}
}
Add to ~/.claude/settings.json:
{
"mcpServers": {
"Microsoft D365": {
"command": "node",
"args": ["/path/to/d365fo-mcp-server/dist/index.js"],
"env": {
"D365_TENANT_ID": "your-tenant-id",
"D365_CLIENT_ID": "your-client-id",
"D365_CLIENT_SECRET": "your-client-secret",
"D365_ENVIRONMENT_URL": "https://your-env.operations.dynamics.com"
}
}
}
}
After adding the configuration, restart Claude Desktop or Claude Code.
When using multiple D365 environments, you can configure how they appear in Claude:
This option shows each environment as a separate MCP server in Claude's sidebar:
{
"mcpServers": {
"D365-production": {
"command": "node",
"args": ["/path/to/d365fo-mcp-server/dist/index.js"],
"env": {
"D365_CONFIG_FILE": "/path/to/d365fo-mcp-server/d365-environments.json",
"D365_SINGLE_ENV": "production"
}
},
"D365-uat": {
"command": "node",
"args": ["/path/to/d365fo-mcp-server/dist/index.js"],
"env": {
"D365_CONFIG_FILE": "/path/to/d365fo-mcp-server/d365-environments.json",
"D365_SINGLE_ENV": "uat"
}
},
"D365-dev": {
"command": "node",
"args": ["/path/to/d365fo-mcp-server/dist/index.js"],
"env": {
"D365_CONFIG_FILE": "/path/to/d365fo-mcp-server/d365-environments.json",
"D365_SINGLE_ENV": "dev"
}
}
}
}
Pros:
How it works: The D365_SINGLE_ENV environment variable tells the server to load only that specific environment from d365-environments.json. The D365_CONFIG_FILE ensures the config is found regardless of working directory.
Use a single server with an environment parameter on each query:
{
"mcpServers": {
"d365": {
"command": "node",
"args": ["/path/to/d365fo-mcp-server/dist/index.js"],
"env": {
"D365_CONFIG_FILE": "/path/to/d365fo-mcp-server/d365-environments.json"
}
}
}
}
Then specify the environment in queries:
{ "entity": "CustomersV3", "top": 10, "environment": "uat" }
Pros:
The interactive setup script (node setup.js) can generate either configuration for you.
Once configured, you can ask Claude natural language questions about your D365 environment. Here are examples organized by capability:
You: What customer-related entities are available in D365?
Claude will use the d365://entities?filter=*Cust* resource to find matching entities.
You: Show me the schema for the CustomersV3 entity
Claude will use describe_entity or the d365://entity/CustomersV3 resource.
You: Get me the first 10 customers with their account numbers and names
Claude will use execute_odata with path CustomersV3?$top=10&$select=CustomerAccount,CustomerName
You: How many sales orders are in the system?
Claude will use execute_odata with path SalesOrderHeaders/$count
You: Find all customers in customer group "US" with credit limit over 50000
Claude will construct an OData filter query automatically.
You: Who are our top 20 customers by total spend?
Claude will use aggregate with groupBy, orderBy, and top:
{
"entity": "SalesOrderLinesV2",
"aggregations": [{"function": "SUM", "field": "LineAmount"}],
"groupBy": ["OrderingCustomerAccountNumber"],
"orderBy": "sum_LineAmount desc",
"top": 20
}
You: What's the median order value? Show me the 90th and 95th percentiles too
Claude will use aggregate with percentile functions:
{
"entity": "SalesOrderLinesV2",
"aggregations": [
{"function": "P50", "field": "LineAmount", "alias": "median"},
{"function": "P90", "field": "LineAmount"},
{"function": "P95", "field": "LineAmount"}
],
"accurate": true
}
You: Break down total revenue by product category
Claude will use aggregate with groupBy:
{
"entity": "SalesOrderLinesV2",
"aggregations": [{"function": "SUM", "field": "LineAmount"}],
"groupBy": ["ItemGroup"]
}
You: Show me the monthly sales trend for the past 12 months with growth rates
Claude will use trending:
{
"entity": "SalesOrderLinesV2",
"dateField": "CreatedDateTime",
"valueField": "LineAmount",
"granularity": "month",
"periods": 12,
"includeGrowthRate": true
}
You: Compare this year's sales to last year
Claude will use compare_periods with YoY comparison:
{
"entity": "SalesOrderLinesV2",
"dateField": "CreatedDateTime",
"comparisonType": "YoY",
"aggregations": [{"function": "SUM", "field": "LineAmount"}]
}
You: How did Q4 sales compare to Q3?
Claude will use compare_periods with QoQ comparison:
{
"entity": "SalesOrderLinesV2",
"dateField": "CreatedDateTime",
"comparisonType": "QoQ",
"aggregations": [{"function": "SUM", "field": "LineAmount"}]
}
You: Give me a complete analysis of customer US-001 - profile, orders, spend, and trends
Claude will use analyze_customer for comprehensive single-call analysis:
{
"customerAccount": "US-001",
"includeOrders": true,
"includeSpend": true,
"includeTrending": true
}
You: Find the customer named "S&S Industries"
Claude will use search_entity which handles special characters that break standard OData:
{
"entity": "CustomersV3",
"searchTerm": "S&S Industries",
"searchField": "CustomerName"
}
You: Get me a dashboard view: total customers, total orders this month, and top 5 products by sales
Claude will use batch_query to run all three queries in parallel:
{
"queries": [
{"name": "total_customers", "entity": "CustomersV3", "top": 1},
{"name": "orders_this_month", "entity": "SalesOrderHeadersV2", "filter": "OrderCreatedDateTime ge 2024-01-01"},
{"name": "top_products", "entity": "SalesOrderLinesV2", "top": 5, "orderby": "LineAmount desc"}
]
}
You: Show me recent orders with customer names and their customer groups
Claude will use join_entities to correlate orders with customer details:
{
"primaryEntity": "SalesOrderHeadersV2",
"primaryKey": "OrderingCustomerAccountNumber",
"secondaryEntity": "CustomersV3",
"secondaryKey": "CustomerAccount",
"primarySelect": ["SalesOrderNumber", "OrderCreatedDateTime"],
"secondarySelect": ["CustomerName", "CustomerGroup"]
}
You: Export all customers with credit limit over $100K to CSV
Claude will use export with format and filter:
{
"entity": "CustomersV3",
"format": "csv",
"filter": "CreditLimit gt 100000",
"select": ["CustomerAccount", "CustomerName", "CreditLimit"]
}
You: What are the possible values for sales order status?
Claude will check the d365://enums resource to find enum definitions.
Ask business questions directly - The MCP tools handle complexity for you. Just ask: "Who are our top 20 customers by spend?" or "How did Q4 compare to Q3?"
Use natural date formats - Claude understands "last month", "Q4 2024", "past 12 months", or specific dates like "January 1, 2024"
Don't worry about special characters - Searching for "S&S Industries" or "O'Brien Corp" works automatically. The tools have fallback strategies for characters that break standard OData.
Request trends and comparisons - Built-in time intelligence handles the complexity: "Show monthly sales trend with growth rates" or "Compare this year's revenue to last year"
Combine multiple questions - Ask for dashboard-style views: "Get me total customers, orders this month, and top 5 products" - queries run in parallel.
Export data when needed - Request CSV, JSON, or TSV exports directly: "Export all customers with credit limit over $100K to CSV"
Ask for explanations - If you want to learn OData syntax, ask Claude to explain the query: "Show customers in group US and explain the OData query"
d365://entitiesList available D365 entities with optional filtering.
Query Parameters:
filter (optional): Wildcard pattern (* for any chars, ? for single char)Examples:
d365://entities # List all entities
d365://entities?filter=Cust* # Entities starting with "Cust"
d365://entities?filter=*Header* # Entities containing "Header"
d365://entity/{entityName}Get the full schema for an entity.
Examples:
d365://entity/CustomersV3
d365://entity/SalesOrderHeaders
Response includes:
d365://navigation/{entityName}Get navigation properties (relationships) for an entity.
Examples:
d365://navigation/SalesOrderHeadersV2
d365://navigation/CustomersV3
Response includes:
d365://enumsList all enum type definitions.
Response includes:
list_environmentsList all configured D365 environments with their connection status and permissions.
Parameters:
Example:
{}
Response includes:
set_environmentSet the working environment for the current session. Subsequent tool calls will use this environment by default.
Parameters:
environment (string, required): Name of the environment to set as activeExample:
{
"environment": "uat"
}
describe_entityGet entity schema in a human-readable format.
Parameters:
entity (string, required): Entity nameExample:
{
"entity": "CustomersV3"
}
execute_odataExecute a raw OData path against D365.
Parameters:
path (string, required): OData path appended to /data/Examples:
// Query with parameters
{ "path": "CustomersV3?$top=5&$select=CustomerAccount,CustomerName" }
// Single record by key
{ "path": "CustomersV3('US-001')" }
// Compound key
{ "path": "CustomersV3(DataAreaId='usmf',CustomerAccount='US-001')" }
// Count
{ "path": "CustomersV3/$count" }
// Filtered count
{ "path": "CustomersV3/$count?$filter=CustomerGroup eq 'US'" }
// With expansion
{ "path": "SalesOrderHeaders?$expand=SalesOrderLines&$top=3" }
aggregatePerform aggregations on D365 entity data. Uses fast /$count for simple COUNT operations, client-side aggregation otherwise.
Parameters:
entity (string, required): Entity name to aggregateaggregations (array, required): Array of aggregation specs:
function: "SUM" | "AVG" | "COUNT" | "MIN" | "MAX" | "COUNTDISTINCT" | "P50" | "P90" | "P95" | "P99"field: Field to aggregate (use "*" for COUNT)alias (optional): Custom result namefilter (string, optional): OData $filter expressiongroupBy (array, optional): Fields to group byaccurate (boolean, optional): Fetch ALL records for exact totals (default: false)sampling (boolean, optional): Use statistical sampling for fast estimates on very large datasets (default: false)orderBy (string, optional): Sort results by aggregation alias (e.g., "sum_LineAmount desc")top (number, optional): Return only top N results after sortingPercentile functions:
P50 - Median (50th percentile)P90 - 90th percentileP95 - 95th percentileP99 - 99th percentilePerformance notes:
accurate=true fetches ALL records with 60s timeout per page and automatic retry (2 retries with exponential backoff)sampling=true uses ~10K record sample for statistical estimates on very large datasets (100K+ records)Examples:
// Count all customers
{ "entity": "CustomersV3", "aggregations": [{"function": "COUNT", "field": "*"}] }
// Sum with filter
{ "entity": "SalesOrderLines", "aggregations": [{"function": "SUM", "field": "LineAmount"}], "filter": "SalesOrderNumber eq 'SO-001'" }
// Accurate mode for exact totals
{ "entity": "SalesOrderLines", "aggregations": [{"function": "SUM", "field": "LineAmount"}], "accurate": true }
// Group by
{ "entity": "SalesOrderLines", "aggregations": [{"function": "SUM", "field": "LineAmount"}], "groupBy": ["ItemNumber"] }
// Median order value (requires accurate=true for percentiles)
{ "entity": "SalesOrderLines", "aggregations": [{"function": "P50", "field": "LineAmount"}], "accurate": true }
// Fast estimate on very large dataset (100K+ records)
{ "entity": "BatchJobs", "aggregations": [{"function": "COUNT", "field": "*"}], "sampling": true }
// Top 20 customers by spend
{ "entity": "SalesOrderLines", "aggregations": [{"function": "SUM", "field": "LineAmount"}], "groupBy": ["CustomerAccount"], "orderBy": "sum_LineAmount desc", "top": 20 }
get_relatedFollow entity relationships to retrieve related records in a single call.
Parameters:
entity (string, required): Source entity namekey (string | object, required): Primary key of source recordrelationship (string, required): Navigation property name to followselect (string[], optional): Fields to include from related entityfilter (string, optional): Filter to apply to related recordstop (number, optional): Maximum related records (default: 1000)Examples:
// Get order lines for an order
{ "entity": "SalesOrderHeaders", "key": "SO-001", "relationship": "SalesOrderLines" }
// With compound key
{ "entity": "SalesOrderHeaders", "key": {"DataAreaId": "usmf", "SalesOrderNumber": "SO-001"}, "relationship": "SalesOrderLines" }
// With field selection and filter
{ "entity": "SalesOrderHeaders", "key": "SO-001", "relationship": "SalesOrderLines", "select": ["ItemNumber", "LineAmount"], "filter": "LineAmount gt 1000" }
exportExport D365 entity data to CSV, JSON, or TSV format.
Parameters:
entity (string, required): Entity to exportformat ("json" | "csv" | "tsv", optional): Output format (default: "json")select (string[], optional): Fields to includefilter (string, optional): OData $filter expressionorderBy (string, optional): OData $orderby expressionmaxRecords (number, optional): Maximum records (default: 10000)includeHeaders (boolean, optional): Include header row for CSV/TSV (default: true)Examples:
// JSON export with field selection
{ "entity": "CustomersV3", "format": "json", "select": ["CustomerAccount", "CustomerName"] }
// CSV export with filter
{ "entity": "SalesOrderLines", "format": "csv", "filter": "SalesOrderNumber eq 'SO-001'" }
// TSV with ordering and limit
{ "entity": "Products", "format": "tsv", "orderBy": "ProductName asc", "maxRecords": 500 }
compare_periodsCompare aggregations between two time periods (YoY, QoQ, MoM, or custom ranges).
Parameters:
entity (string, required): Entity to analyzedateField (string, required): Date/datetime field for filteringaggregations (array, required): Same as aggregate toolcomparisonType ("YoY" | "QoQ" | "MoM" | "custom", required): Type of comparisonreferenceDate (string, optional): Reference date for calculations (default: today)period1, period2 (objects, optional): Custom period rangesfilter (string, optional): Additional OData filtergroupBy (string[], optional): Fields to group byExamples:
// Year-over-Year comparison
{ "entity": "SalesOrderLines", "dateField": "CreatedDateTime", "comparisonType": "YoY", "aggregations": [{"function": "SUM", "field": "LineAmount"}] }
// Month-over-Month with grouping
{ "entity": "SalesOrderLines", "dateField": "CreatedDateTime", "comparisonType": "MoM", "aggregations": [{"function": "COUNT", "field": "*"}], "groupBy": ["ItemGroup"] }
// Custom date ranges
{ "entity": "SalesOrderLines", "dateField": "CreatedDateTime", "comparisonType": "custom", "aggregations": [{"function": "SUM", "field": "LineAmount"}], "period1": {"start": "2024-01-01", "end": "2024-03-31"}, "period2": {"start": "2023-01-01", "end": "2023-03-31"} }
trendingTime series analysis with aggregation, growth rates, and moving averages.
Parameters:
entity (string, required): Entity to analyzedateField (string, required): Date/datetime field for bucketingvalueField (string, required): Numeric field to aggregateaggregation ("SUM" | "AVG" | "COUNT" | "MIN" | "MAX", optional): Default: "SUM"granularity ("day" | "week" | "month" | "quarter" | "year", optional): Default: "month"periods (number, optional): Number of periods to analyze (default: 12)endDate (string, optional): End date for analysis (default: today)filter (string, optional): Additional OData filtermovingAverageWindow (number, optional): Window size for MA calculationincludeGrowthRate (boolean, optional): Include growth rates (default: true)Examples:
// Monthly revenue trend
{ "entity": "SalesOrderLines", "dateField": "CreatedDateTime", "valueField": "LineAmount", "granularity": "month", "periods": 12 }
// Weekly order count with moving average
{ "entity": "SalesOrderHeaders", "dateField": "OrderDate", "valueField": "*", "aggregation": "COUNT", "granularity": "week", "movingAverageWindow": 4 }
// Quarterly with filter
{ "entity": "SalesOrderLines", "dateField": "CreatedDateTime", "valueField": "LineAmount", "granularity": "quarter", "filter": "ItemGroup eq 'Electronics'" }
save_querySave a reusable query template for later execution. Use {{paramName}} for substitutable parameters.
Parameters:
name (string, required): Unique name for the querydescription (string, optional): Description of the queryentity (string, required): Entity to queryselect (string[], optional): Fields to selectfilter (string, optional): OData $filter (use {{paramName}} for parameters)orderBy (string, optional): OData $orderby expressiontop (number, optional): Maximum recordsexpand (string, optional): OData $expand expressionExamples:
// Basic query
{ "name": "active_customers", "entity": "CustomersV3", "filter": "IsActive eq true" }
// With parameters
{ "name": "customer_orders", "entity": "SalesOrderHeaders", "filter": "CustomerAccount eq '{{customerId}}'" }
// Complex query with description
{ "name": "recent_sales", "description": "Recent sales for analysis", "entity": "SalesOrderLines", "select": ["ItemNumber", "LineAmount"], "filter": "CreatedDateTime ge {{startDate}}", "orderBy": "CreatedDateTime desc", "top": 100 }
execute_saved_queryExecute a previously saved query template.
Parameters:
name (string, required): Name of the saved queryparams (object, optional): Parameter values to substitutefetchAll (boolean, optional): Fetch all pages (default: false)maxRecords (number, optional): Max records when fetchAll=true (default: 50000)Examples:
// Simple execution
{ "name": "active_customers" }
// With parameters
{ "name": "customer_orders", "params": {"customerId": "US-001"} }
// Multiple parameters with pagination
{ "name": "date_range_sales", "params": {"startDate": "2024-01-01", "endDate": "2024-12-31"}, "fetchAll": true }
delete_saved_queryDelete a saved query template.
Parameters:
name (string, required): Name of the query to deletejoin_entitiesCross-entity joins using OData $expand or client-side join.
Parameters:
primaryEntity (string, required): Primary entity nameprimaryKey (string, required): Primary key field to join onsecondaryEntity (string, required): Secondary entity namesecondaryKey (string, required): Secondary key field to join onprimarySelect (string[], optional): Fields from primary entitysecondarySelect (string[], optional): Fields from secondary entityprimaryFilter (string, optional): Filter for primary entityjoinType ("inner" | "left", optional): Join type (default: "inner")maxRecords (number, optional): Maximum records (default: 5000)Examples:
// Join orders with customers
{ "primaryEntity": "SalesOrderHeadersV2", "primaryKey": "OrderingCustomerAccountNumber", "secondaryEntity": "CustomersV3", "secondaryKey": "CustomerAccount", "primarySelect": ["SalesOrderNumber", "OrderCreatedDateTime"], "secondarySelect": ["CustomerName", "CustomerGroup"] }
batch_queryExecute multiple D365 OData queries in parallel, returning all results in a single response.
Parameters:
queries (array, required): Array of query specs (1-10 queries):
name (string, optional): Label for this query resultentity (string, required): Entity namefilter (string, optional): OData $filter expressionselect (string[], optional): Fields to includetop (number, optional): Limit records (default: 100)orderby (string, optional): OData $orderby expressionfetchAll (boolean, optional): Auto-paginate all pagesmaxRecords (number, optional): Max records when fetchAll=truestopOnError (boolean, optional): Stop on first failure (default: false)Examples:
// Multiple parallel queries
{
"queries": [
{ "name": "recent_orders", "entity": "SalesOrderHeadersV2", "top": 10, "orderby": "CreatedDateTime desc" },
{ "name": "customers", "entity": "CustomersV3", "filter": "CustomerGroup eq 'US'", "select": ["CustomerAccount", "CustomerName"] },
{ "name": "all_invoices", "entity": "SalesInvoiceHeadersV2", "fetchAll": true, "maxRecords": 1000 }
]
}
search_entityRobust entity search with automatic fallback strategies. Handles special characters (like & in company names) that cause issues with standard OData contains().
Search Strategies (tried in order):
contains() - Standard OData text search (fastest)startswith() - Prefix matching (more reliable on D365)exact - Exact field matchclient_filter - Fetch + client-side filter (always works)Parameters:
entity (string, required): Entity to searchsearchTerm (string, required): Text to search forsearchField (string, required): Field to search inselect (string[], optional): Fields to return in resultstop (number, optional): Maximum results (default: 10)Examples:
// Search customers with special characters
{ "entity": "CustomersV3", "searchTerm": "S&S", "searchField": "CustomerName" }
// Search with specific fields
{ "entity": "CustomersV3", "searchTerm": "Contoso", "searchField": "CustomerName", "select": ["CustomerAccount", "CustomerName", "CustomerGroup"], "top": 5 }
// Search vendors
{ "entity": "VendorsV3", "searchTerm": "Microsoft", "searchField": "VendorName" }
analyze_customerComprehensive customer analysis in a single call. Runs parallel queries to gather profile, orders, spend, and trending data.
Features:
Uses efficient aggregation at the line level (SalesOrderLinesV2) for accurate spend calculation, avoiding the $0 header total issue.
Parameters:
customerAccount (string, optional): Customer account numbercustomerName (string, optional): Customer name to search (handles special characters)includeOrders (boolean, optional): Include recent orders list (default: true)includeSpend (boolean, optional): Include total spend calculation (default: true)includeTrending (boolean, optional): Include monthly trend analysis (default: true)recentOrdersLimit (number, optional): Number of recent orders to show (default: 10)trendPeriods (number, optional): Number of months for trend (default: 12)Examples:
// Analyze by account number
{ "customerAccount": "SS0011" }
// Analyze by name (handles special characters like &)
{ "customerName": "S&S" }
// Quick analysis without trending (faster)
{ "customerAccount": "US-001", "includeTrending": false }
// Full analysis with custom periods
{ "customerName": "Contoso", "recentOrdersLimit": 20, "trendPeriods": 24 }
Output includes:
d365://queriesResource that lists all saved query templates.
Response includes:
dashboardDisplay environment dashboard with health status, API statistics, and recent operations.
Parameters:
checkHealth (boolean, optional): Perform live connectivity check (default: false)Example:
{
"checkHealth": true
}
Response includes:
// Equality
$filter=CustomerAccount eq 'US-001'
// Comparison
$filter=CreditLimit gt 10000
// String functions
$filter=startswith(CustomerName, 'Contoso')
$filter=contains(CustomerName, 'Inc')
// Logical operators
$filter=CustomerGroup eq 'US' and CreditLimit gt 5000
// Enum values
$filter=Status eq Microsoft.Dynamics.DataEntities.SalesStatus'Invoiced'
// Date comparison
$filter=OrderDate gt 2024-01-01
// Select specific fields
$select=CustomerAccount,CustomerName,CreditLimit
// Expand navigation property
$expand=SalesOrderLines
// Expand with nested select
$expand=SalesOrderLines($select=ItemId,Quantity)
// Sort ascending
$orderby=CustomerName asc
// Sort descending
$orderby=OrderDate desc
// Multiple sort columns
$orderby=CustomerGroup asc,CustomerName asc
// Pagination
$top=50&$skip=100
# Build
npm run build
# Watch mode
npm run dev
# Run directly (requires environment variables)
npm start
# Run tests
npm test
# Run tests in watch mode
npm run test:watch
src/
├── index.ts # Entry point and server setup
├── config-loader.ts # Configuration loading (JSON + env var fallback)
├── environment-manager.ts # Multi-environment management and write guards
├── auth.ts # Azure AD OAuth2 authentication
├── d365-client.ts # D365 OData API client with read/write methods
├── metadata-cache.ts # EDMX metadata parser and cache (24h TTL)
├── progress.ts # Progress reporting for long operations
├── types.ts # TypeScript type definitions
├── metrics/
│ ├── index.ts # Metrics module exports
│ ├── metrics-tracker.ts # API call statistics tracking
│ ├── health-checker.ts # Environment connectivity health checks
│ └── operation-log.ts # Operation history tracking
├── resources/
│ ├── index.ts # Resource registration
│ ├── entities.ts # d365://entities resource
│ ├── entity.ts # d365://entity/{name} resource
│ ├── navigation.ts # d365://navigation/{name} resource
│ ├── enums.ts # d365://enums resource
│ ├── queries.ts # d365://queries resource
│ └── dashboard.ts # d365://dashboard resource
├── utils/
│ ├── date-utils.ts # Date period calculations
│ ├── csv-utils.ts # CSV/TSV formatting
│ ├── env-utils.ts # Environment variable parsing with validation
│ └── pagination.ts # Shared pagination utilities (fetchPageWithRetry, paginatedFetch)
└── tools/
├── index.ts # Tool registration
├── common.ts # Shared tool utilities and error formatting
├── list-environments.ts
├── set-environment.ts
├── describe-entity.ts
├── execute-odata.ts
├── aggregate.ts
├── get-related.ts
├── export.ts
├── compare-periods.ts
├── trending.ts
├── saved-queries.ts # save/execute/delete query templates
├── join-entities.ts
├── batch-query.ts
├── batch-crud.ts # Batch create/update/delete via $batch (non-production only)
├── compare-schemas.ts # Cross-environment schema comparison
├── search-entity.ts
├── analyze-customer.ts
├── create-record.ts # Write operation (non-production only)
├── update-record.ts # Write operation (non-production only)
├── delete-record.ts # Write operation (non-production only)
└── dashboard.ts
tests/
├── auth.test.ts # Token caching, refresh dedup, invalidation
├── d365-client.test.ts # Retry logic, key formatting, CRUD operations
├── config-loader.test.ts # JSON loading, env var fallback, validation
├── pagination.test.ts # Shared pagination utilities
└── env-utils.test.ts # parseInt validation utility
Write operations are only available on environments with type: "non-production". Production environments are always read-only.
create_recordCreate a new record in a D365 entity.
Parameters:
entity (string, required): Entity namedata (object, required): Field values for the new recordenvironment (string, optional): Target environmentExample:
{
"entity": "CustomersV3",
"data": {
"CustomerAccount": "CUST-001",
"CustomerName": "Contoso Ltd",
"CustomerGroup": "US"
},
"environment": "uat"
}
update_recordUpdate an existing record.
Documentation truncated — see the full README on GitHub.
Be the first to review this server!
by Modelcontextprotocol · Developer Tools
Read, search, and manipulate Git repositories programmatically
by Toleno · Developer Tools
Toleno Network MCP Server — Manage your Toleno mining account with Claude AI using natural language.
by mcp-marketplace · Developer Tools
Create, build, and publish Python MCP servers to PyPI — conversationally.
by Microsoft · Content & Media
Convert files (PDF, Word, Excel, images, audio) to Markdown for LLM consumption
by mcp-marketplace · Developer Tools
Scaffold, build, and publish TypeScript MCP servers to npm — conversationally
by mcp-marketplace · Finance
Free stock data and market news for any MCP-compatible AI assistant.