关于 d365fo-connector
d365fo-connector 是连接 Microsoft Dynamics 365 Finance & Operations 环境的 MCP 服务,供 AI 助手发现元数据、查询数据并执行操作。它支持多环境,并强制生产环境只读。
主要功能
- 01连接多个 D365 环境
- 02发现实体与架构元数据
- 03查询、聚合及分析数据
- 04导出与批量执行查询
- 05非生产环境记录增删改
- 06生产环境强制只读
适用场景
- 01检查实体字段和关系
- 02分析销售及客户数据
- 03对比不同环境的架构
- 04在开发或 UAT 环境维护记录
MCP 相关
通过 MCP 向客户端提供 D365 元数据资源及数据访问工具。支持查询和分析;写操作仅对非生产环境开放。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
npx -y @zhound/d365fo-mcp-serverD365_TENANT_IDAzure AD tenant IDD365_CLIENT_IDApp registration client IDD365_CLIENT_SECRET密钥App registration client secretD365_ENVIRONMENT_URLD365 F&O environment URL, e.g. https://contoso.operations.dynamics.com本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
工具
22收录的工具列表 · 更新于 2 周前
此工具无需参数。
详细介绍
D365 Finance & Operations MCP 服务
一个 MCP(Model Context Protocol)服务,可访问 Microsoft Dynamics 365 Finance & Operations 环境。支持 Claude 等 AI 助手探索 D365 元数据、查询数据,并在非生产环境中执行写入操作。
功能
- 多环境支持 - 连接到多个 D365 环境(生产、UAT、开发)
- 读写操作 - 可在所有环境中查询数据;仅可在非生产环境中创建、更新和删除数据
- 生产环境安全保障 - 生产环境在设计上始终为只读
- 用于架构发现和元数据探索的 MCP 资源
- 22 种专用工具,支持灵活的数据访问、聚合、批量操作和分析
- 环境仪表板 - 健康监控、API 统计和操作跟踪
- 通过 Azure AD 客户端凭据进行 安全身份验证
- 自动元数据缓存(TTL 为 24 小时,按环境分别缓存)
架构
资源
| 资源 | URI | 用途 |
|---|---|---|
| 实体列表 | d365://entities?filter=<pattern> | 列出所有实体,并可选用通配符筛选 |
| 实体架构 | d365://entity/{entityName} | 任意实体的完整架构(字段、键、导航属性) |
| 导航属性 | d365://navigation/{entityName} | 实体关系和导航属性 |
| 枚举定义 | d365://enums | 所有枚举类型及其值 |
| 已保存的查询 | d365://queries | 列出已保存的查询模板 |
| 仪表板 | d365://dashboard | 所有环境的 JSON 指标(健康状态、API 统计、近期操作) |
工具
所有工具都支持可选的 environment 参数,以指定目标 D365 环境。
| 工具 | 用途 |
|---|---|
list_environments | 列出所有已配置的 D365 环境及其连接状态 |
set_environment | 设置当前会话的工作环境 |
describe_entity | 快速查询实体架构 |
execute_odata | 执行原始 OData 路径(查询、单条记录、计数) |
aggregate | 对实体数据执行聚合(SUM、AVG、COUNT、MIN、MAX、COUNTDISTINCT、百分位数) |
get_related | 沿实体关系获取相关记录 |
export | 将查询结果导出为 CSV、JSON 或 TSV 格式 |
compare_periods | 比较同比、环比季度和环比月份,并计算变化 |
trending | 时间序列分析,包含增长率和移动平均值 |
save_query | 保存可复用的查询模板,并支持参数 |
execute_saved_query | 替换参数并执行已保存的查询模板 |
delete_saved_query | 删除已保存的查询模板 |
join_entities | 使用 $expand 或客户端联接进行跨实体联接 |
batch_query | 并行执行多个查询 |
search_entity | 使用自动回退策略进行稳健的实体搜索 |
analyze_customer | 通过单次调用进行全面的客户分析 |
create_record | 创建新记录(仅限非生产环境) |
update_record | 更新现有记录(仅限非生产环境) |
delete_record | 删除记录(仅限非生产环境) |
batch_crud | 在单个批量请求中执行多个创建、更新或删除操作(仅限非生产环境) |
compare_schemas | 比较两个环境中的实体架构,以检测架构偏差 |
dashboard | 显示环境仪表板,包含健康状态、API 统计和近期操作 |
安装
从 npm 安装(推荐)
npx @zhound/d365fo-mcp-server
或全局安装:
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
快速开始(推荐)
运行交互式设置向导:
npm run setup
向导将:
- 检查前置条件(Node.js 18+、依赖项)
- 指导你配置 D365 环境
- 测试与 D365 环境的连接
- 生成配置文件
- 配置 Claude Desktop 和/或 Claude Code
设置完成后,重启 Claude Desktop(在 macOS 上按 Cmd+Q 后重新打开,或在 Windows 上按 Ctrl+Q),或启动新的 Claude Code 会话。
配置
多环境配置(推荐)
在项目根目录或工作目录中创建 d365-environments.json 文件:
{
"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"
}
]
}
环境类型:
type: "production"- 只读访问(所有写入操作均会被阻止)type: "non-production"- 完全读写访问(启用创建、更新和删除)
可以复制 d365-environments.example.json 作为起始模板。
单环境(旧版)
服务还支持以下环境变量(未提供 JSON 配置时作为备用配置):
| 变量 | 描述 |
|---|---|
D365_TENANT_ID | Azure AD 租户 ID |
D365_CLIENT_ID | Azure AD 应用程序(客户端)ID |
D365_CLIENT_SECRET | Azure AD 客户端机密 |
D365_ENVIRONMENT_URL | D365 F&O 环境 URL(例如,https://contoso.operations.dynamics.com) |
D365_ENVIRONMENT_TYPE | 可选:"production" 或 "non-production"(为安全起见,默认为 "production") |
可选项:
| 变量 | 默认值 | 描述 |
|---|---|---|
D365_TRANSPORT | stdio | 传输模式(stdio 或 http) |
D365_HTTP_PORT | 3000 | HTTP 端口(使用 http 传输时) |
D365_LOG_LEVEL | info | 日志级别 |
D365_PAGINATION_TIMEOUT_MS | 60000 | 大型数据集分页请求的超时时间(毫秒) |
D365_CONFIG_FILE | 配置文件路径(如果不在默认位置) |
Azure AD 应用注册
第 1 步:创建 Azure AD 应用
- 前往 Azure 门户 > Azure Active Directory > 应用注册
- 点击“新注册”
- 输入名称(例如,“D365 MCP Server”)
- 选择“仅此组织目录中的帐户”
- 点击“注册”
第 2 步:配置 API 权限
- 前往“API 权限” > “添加权限”
- 选择“Dynamics 365 Finance and Operations”
- 选择“应用程序权限” >
CustomService.ReadWrite.All - 点击“代表 [your organization] 授予管理员同意”
第 3 步:创建客户端机密
- 前往“证书和机密” > “新建客户端机密”
- 添加描述并设置有效期
- 立即复制机密值(仅显示一次)
- 记下:
- 租户 ID:可在“概述”页面找到
- 客户端 ID:“概述”页面上的应用程序(客户端)ID
- 客户端机密:刚刚复制的值
第 4 步:在 D365 环境中注册应用
重要: 必须在你要连接的每个 D365 环境(Production、UAT、Dev)中完成此步骤。
-
在 D365 F&O 中,导航至: 系统管理 > 设置 > Azure Active Directory 应用程序
-
点击“新建”以添加记录:
字段 值 客户端 ID Azure AD 中的应用程序(客户端)ID 名称 描述性名称(例如,“MCP Server Integration”) 用户 ID 应用运行时使用的 D365 用户帐户 -
用户 ID 决定应用可以访问哪些数据:
- 使用分配了适当安全角色的服务帐户
- 对于只读访问:分配“查看所有数据”等角色
- 对于非生产环境的写入访问:分配允许创建、更新和删除的角色
-
对要连接的每个环境重复此操作
注意: 如果跳过此步骤,即使 Azure AD 身份验证成功,API 调用仍会因 401 Unauthorized 或 403 Forbidden 错误而失败。
设置
Claude Desktop
将以下内容添加到 Claude Desktop 配置文件中:
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"
}
}
}
}
Claude Code(CLI)
添加到 ~/.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"
}
}
}
}
添加配置后,重启 Claude Desktop 或 Claude Code。
环境可见性配置
使用多个 D365 环境时,可以配置它们在 Claude 中的显示方式:
选项 A:为每个环境配置单独的服务(推荐)
此选项会在 Claude 侧边栏中将每个环境显示为单独的 MCP 服务:
{
"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"
}
}
}
}
优点:
- 环境会立即显示在 Claude 侧边栏中
- 查询目标环境一目了然
- 在所有平台上都能可靠运行
工作原理: D365_SINGLE_ENV 环境变量会告知服务仅从 d365-environments.json 加载指定环境。D365_CONFIG_FILE 可确保无论当前工作目录是什么,都能找到配置文件。
选项 B:使用单个多环境服务
使用单个服务,并在每次查询中通过 environment 参数指定环境:
{
"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"
}
}
}
}
然后在查询中指定环境:
{ "entity": "CustomersV3", "top": 10, "environment": "uat" }
优点:
- 只需一个服务进程
- 可在一个会话中灵活查询任何环境
交互式设置脚本(node setup.js)可以为你生成任一配置。
与 Claude 对话——提示词示例
配置完成后,你可以用自然语言向 Claude 询问 D365 环境相关问题。以下是按功能分类的示例:
发现实体
你: D365 中有哪些与客户相关的实体?
Claude 会使用 d365://entities?filter=*Cust* 资源查找匹配的实体。
你: 显示 CustomersV3 实体的架构
Claude 会使用 describe_entity 或 d365://entity/CustomersV3 资源。
查询数据
你: 获取前 10 位客户的账号和名称
Claude 会使用 execute_odata,路径为 CustomersV3?$top=10&$select=CustomerAccount,CustomerName
你: 系统中有多少销售订单?
Claude 会使用 execute_odata,路径为 SalesOrderHeaders/$count
你: 查找所有属于客户组“US”且信用额度超过 50000 的客户
Claude 会自动构建 OData 筛选查询。
聚合与分析
你: 按总消费金额列出前 20 位客户
Claude 会使用 aggregate,并设置 groupBy、orderBy 和 top:
{
"entity": "SalesOrderLinesV2",
"aggregations": [{"function": "SUM", "field": "LineAmount"}],
"groupBy": ["OrderingCustomerAccountNumber"],
"orderBy": "sum_LineAmount desc",
"top": 20
}
你: 订单金额的中位数是多少?也显示第 90 和第 95 百分位数
Claude 会使用 aggregate 和百分位函数:
{
"entity": "SalesOrderLinesV2",
"aggregations": [
{"function": "P50", "field": "LineAmount", "alias": "median"},
{"function": "P90", "field": "LineAmount"},
{"function": "P95", "field": "LineAmount"}
],
"accurate": true
}
你: 按产品类别拆分总收入
Claude 会使用 aggregate 并设置 groupBy:
{
"entity": "SalesOrderLinesV2",
"aggregations": [{"function": "SUM", "field": "LineAmount"}],
"groupBy": ["ItemGroup"]
}
基于时间的分析
你: 显示过去 12 个月的月度销售趋势和增长率
Claude 会使用 trending:
{
"entity": "SalesOrderLinesV2",
"dateField": "CreatedDateTime",
"valueField": "LineAmount",
"granularity": "month",
"periods": 12,
"includeGrowthRate": true
}
你: 比较今年和去年的销售额
Claude 会使用 compare_periods 进行同比比较:
{
"entity": "SalesOrderLinesV2",
"dateField": "CreatedDateTime",
"comparisonType": "YoY",
"aggregations": [{"function": "SUM", "field": "LineAmount"}]
}
你: 第四季度的销售额与第三季度相比如何?
Claude 会使用 compare_periods 进行环比比较:
{
"entity": "SalesOrderLinesV2",
"dateField": "CreatedDateTime",
"comparisonType": "QoQ",
"aggregations": [{"function": "SUM", "field": "LineAmount"}]
}
客户洞察
你: 全面分析客户 US-001,包括档案、订单、消费额和趋势
Claude 会使用 analyze_customer,通过一次调用进行全面分析:
{
"customerAccount": "US-001",
"includeOrders": true,
"includeSpend": true,
"includeTrending": true
}
你: 查找名为“S&S Industries”的客户
Claude 会使用 search_entity,它可以处理会导致标准 OData 查询失败的特殊字符:
{
"entity": "CustomersV3",
"searchTerm": "S&S Industries",
"searchField": "CustomerName"
}
多查询与联接
你: 获取仪表板视图:客户总数、本月订单总数,以及销量最高的 5 种产品
Claude 会使用 batch_query 并行运行全部三个查询:
{
"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"}
]
}
你: 显示最近的订单及客户名称和客户组
Claude 会使用 join_entities 将订单与客户详情关联起来:
{
"primaryEntity": "SalesOrderHeadersV2",
"primaryKey": "OrderingCustomerAccountNumber",
"secondaryEntity": "CustomersV3",
"secondaryKey": "CustomerAccount",
"primarySelect": ["SalesOrderNumber", "OrderCreatedDateTime"],
"secondarySelect": ["CustomerName", "CustomerGroup"]
}
数据导出
你: 将所有信用额度超过 $100K 的客户导出为 CSV
Claude 会使用 export 并指定格式和筛选条件:
{
"entity": "CustomersV3",
"format": "csv",
"filter": "CreditLimit gt 100000",
"select": ["CustomerAccount", "CustomerName", "CreditLimit"]
}
了解枚举
你: 销售订单状态有哪些可能的值?
Claude 会检查 d365://enums 资源以查找枚举定义。
最佳效果提示
-
直接询问业务问题 - MCP 工具会为你处理复杂操作。只需提问:“按支出列出我们的前 20 大客户。”或“Q4 与 Q3 相比如何?”
-
使用自然的日期格式 - Claude 能理解“上个月”、“Q4 2024”、“过去 12 个月”,或“January 1, 2024”这类具体日期。
-
无需担心特殊字符 - 搜索“S&S Industries”或“O'Brien Corp”会自动生效。遇到会导致标准 OData 出错的字符时,工具会采用备用策略。
-
请求趋势和对比 - 内置的时间智能功能会处理复杂操作:“显示每月销售趋势及增长率”或“比较今年与去年的收入”。
-
组合多个问题 - 请求类似仪表板的视图:“获取客户总数、本月订单数和排名前 5 的产品”——查询会并行运行。
-
需要时导出数据 - 直接请求导出为 CSV、JSON 或 TSV:“将所有信用额度超过 $100K 的客户导出为 CSV”。
-
请求解释 - 如果想了解 OData 语法,可以让 Claude 解释查询:“显示 US 组中的客户,并解释 OData 查询”。
API 参考
资源
d365://entities
列出可用的 D365 实体,可选择进行筛选。
查询参数:
filter(可选):通配符模式(*匹配任意字符,?匹配单个字符)
示例:
d365://entities # List all entities
d365://entities?filter=Cust* # Entities starting with "Cust"
d365://entities?filter=*Header* # Entities containing "Header"
d365://entity/{entityName}
获取实体的完整架构。
示例:
d365://entity/CustomersV3
d365://entity/SalesOrderHeaders
响应包含:
- 实体名称和描述
- 主键字段
- 所有字段及其类型、约束和枚举引用
- 导航属性(关系)
d365://navigation/{entityName}
获取实体的导航属性(关系)。
示例:
d365://navigation/SalesOrderHeadersV2
d365://navigation/CustomersV3
响应包含:
- 导航属性名称
- 目标实体类型
- 关系基数(一对多、多对一)
d365://enums
列出所有枚举类型定义。
响应包含:
- 枚举名称和完整命名空间
- 所有成员值及其数字代码
工具
list_environments
列出所有已配置的 D365 环境,以及其连接状态和权限。
参数:
- 无需参数
示例:
{}
响应包含:
- 环境名称和显示名称
- 类型(生产/非生产)
- 连接状态
- 读/写权限
set_environment
为当前会话设置工作环境。后续工具调用默认使用此环境。
参数:
environment(字符串,必填):要设为活动环境的名称
示例:
{
"environment": "uat"
}
describe_entity
以易读格式获取实体架构。
参数:
entity(字符串,必填):实体名称
示例:
{
"entity": "CustomersV3"
}
execute_odata
针对 D365 执行原始 OData 路径。
参数:
path(字符串,必填):追加到/data/后的 OData 路径
示例:
// 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" }
aggregate
对 D365 实体数据执行聚合。简单的 COUNT 操作使用快速的 /$count,其他情况则在客户端执行聚合。
参数:
entity(字符串,必填):要聚合的实体名称aggregations(数组,必填):聚合规格数组:function:"SUM" | "AVG" | "COUNT" | "MIN" | "MAX" | "COUNTDISTINCT" | "P50" | "P90" | "P95" | "P99"field:要聚合的字段(COUNT 使用 "*")alias(可选):自定义结果名称
filter(字符串,可选):OData $filter 表达式groupBy(数组,可选):用于分组的字段accurate(布尔值,可选):获取所有记录以计算精确总数(默认值:false)sampling(布尔值,可选):对超大型数据集进行统计抽样,以快速估算(默认值:false)orderBy(字符串,可选):按聚合别名对结果排序(例如:"sum_LineAmount desc")top(数字,可选):排序后仅返回前 N 个结果
百分位函数:
P50- 中位数(第 50 百分位)P90- 第 90 百分位P95- 第 95 百分位P99- 第 99 百分位
性能说明:
- 默认模式最多处理 5K 条记录,以快速估算
accurate=true会获取所有记录,每页超时时间为 60s,并自动重试(采用指数退避,重试 2 次)sampling=true会使用约 10K 条记录的样本,对超大型数据集(100K+ 条记录)进行统计估算
示例:
// 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_related
沿实体关系检索关联记录,一次调用即可完成。
参数:
entity(字符串,必填):源实体名称key(字符串 | 对象,必填):源记录的主键relationship(字符串,必填):要沿用的导航属性名称select(字符串[],可选):要从关联实体中包含的字段filter(字符串,可选):应用于关联记录的筛选条件top(数字,可选):关联记录的最大数量(默认值:1000)
示例:
// 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" }
export
将 D365 实体数据导出为 CSV、JSON 或 TSV 格式。
参数:
entity(字符串,必填):要导出的实体format("json" | "csv" | "tsv",可选):输出格式(默认值:"json")select(字符串[],可选):要包含的字段filter(字符串,可选):OData $filter 表达式orderBy(字符串,可选):OData $orderby 表达式maxRecords(数字,可选):最大记录数(默认值:10000)includeHeaders(布尔值,可选):为 CSV/TSV 包含标题行(默认值:true)
示例:
// 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_periods
比较两个时间段的聚合结果(YoY、QoQ、MoM 或自定义范围)。
参数:
entity(字符串,必填):要分析的实体dateField(字符串,必填):用于筛选的日期/日期时间字段aggregations(数组,必填):与 aggregate 工具相同comparisonType("YoY" | "QoQ" | "MoM" | "custom",必填):比较类型referenceDate(字符串,可选):计算所用的参考日期(默认值:今天)period1、period2(对象,可选):自定义时间段范围filter(字符串,可选):附加的 OData 筛选条件groupBy(字符串[],可选):用于分组的字段
示例:
// 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"} }
trending
使用聚合、增长率和移动平均值进行时间序列分析。
参数:
entity(string,必需):要分析的实体dateField(string,必需):用于分桶的日期/日期时间字段valueField(string,必需):要聚合的数值字段aggregation("SUM" | "AVG" | "COUNT" | "MIN" | "MAX",可选):默认值:"SUM"granularity("day" | "week" | "month" | "quarter" | "year",可选):默认值:"month"periods(number,可选):要分析的周期数(默认值:12)endDate(string,可选):分析的结束日期(默认值:今天)filter(string,可选):额外的 OData 筛选条件movingAverageWindow(number,可选):计算 MA 的窗口大小includeGrowthRate(boolean,可选):包含增长率(默认值:true)
示例:
// 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_query
保存可重复使用的查询模板,以便稍后执行。使用 {{paramName}} 表示可替换参数。
参数:
name(string,必需):查询的唯一名称description(string,可选):查询描述entity(string,必需):要查询的实体select(string[],可选):要选择的字段filter(string,可选):OData $filter(使用{{paramName}}表示参数)orderBy(string,可选):OData $orderby 表达式top(number,可选):最大记录数expand(string,可选):OData $expand 表达式
示例:
// 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_query
执行先前保存的查询模板。
参数:
name(string,必需):已保存查询的名称params(object,可选):要替换的参数值fetchAll(boolean,可选):获取所有分页结果(默认值:false)maxRecords(number,可选):当 fetchAll=true 时的最大记录数(默认值:50000)
示例:
// 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_query
删除已保存的查询模板。
参数:
name(string,必需):要删除的查询名称
join_entities
使用 OData $expand 或客户端联接跨实体进行联接。
参数:
primaryEntity(string,必需):主实体名称primaryKey(string,必需):用于联接的主键字段secondaryEntity(string,必需):次实体名称secondaryKey(string,必需):用于联接的次键字段primarySelect(string[],可选):主实体中的字段secondarySelect(string[],可选):次实体中的字段primaryFilter(string,可选):主实体的筛选条件joinType("inner" | "left",可选):联接类型(默认值:"inner")maxRecords(number,可选):最大记录数(默认值:5000)
示例:
// Join orders with customers
{ "primaryEntity": "SalesOrderHeadersV2", "primaryKey": "OrderingCustomerAccountNumber", "secondaryEntity": "CustomersV3", "secondaryKey": "CustomerAccount", "primarySelect": ["SalesOrderNumber", "OrderCreatedDateTime"], "secondarySelect": ["CustomerName", "CustomerGroup"] }
batch_query
并行执行多个 D365 OData 查询,并在单个响应中返回所有结果。
参数:
queries(array,必需):查询规范数组(1-10 个查询):name(string,可选):此查询结果的标签entity(string,必需):实体名称filter(string,可选):OData $filter 表达式select(string[],可选):要包含的字段top(number,可选):记录数上限(默认值:100)orderby(string,可选):OData $orderby 表达式fetchAll(boolean,可选):自动获取所有分页结果maxRecords(number,可选):当 fetchAll=true 时的最大记录数
stopOnError(boolean,可选):遇到第一个错误时停止(默认值:false)
示例:
// 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_entity
具备自动回退策略的稳健实体搜索。可处理特殊字符(例如公司名称中的 &),避免标准 OData contains() 遇到的问题。
搜索策略(按顺序尝试):
contains()- 标准 OData 文本搜索(速度最快)startswith()- 前缀匹配(在 D365 上更可靠)exact- 字段精确匹配client_filter- 获取数据并在客户端筛选(始终有效)
参数:
entity(字符串,必填):要搜索的实体searchTerm(字符串,必填):要搜索的文本searchField(字符串,必填):要搜索的字段select(字符串数组,可选):结果中要返回的字段top(数字,可选):最大结果数(默认值:10)
示例:
// 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_customer
通过一次调用全面分析客户。并行运行查询,以收集客户资料、订单、支出和趋势数据。
功能:
- 查询客户资料(支持回退搜索策略)
- 订单统计(数量、总支出、平均订单金额)
- 订单日期范围(首笔和最近一笔订单)
- 最近订单列表
- 月度订单趋势
在订单行级别(SalesOrderLinesV2)进行高效聚合,以准确计算支出,避免标头总额为 $0 的问题。
参数:
customerAccount(字符串,可选):客户账号customerName(字符串,可选):要搜索的客户名称(可处理特殊字符)includeOrders(布尔值,可选):是否包含最近订单列表(默认值:true)includeSpend(布尔值,可选):是否包含总支出计算(默认值:true)includeTrending(布尔值,可选):是否包含月度趋势分析(默认值:true)recentOrdersLimit(数字,可选):显示的最近订单数量(默认值:10)trendPeriods(数字,可选):趋势统计的月数(默认值:12)
示例:
// 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 }
输出包括:
- 客户资料(名称、账号、组别、地址)
- 汇总统计(订单总数、总支出、平均订单金额、首笔和最近一笔订单日期)
- 最近订单列表
- 月度订单趋势表,包含订单数和收入
d365://queries
列出所有已保存查询模板的资源。
响应包括:
- 查询数量和列表
- 每个查询的名称、说明、实体和参数
- 使用说明
dashboard
显示环境仪表板,其中包含运行状况、API 统计信息和近期操作。
参数:
checkHealth(布尔值,可选):执行实时连接检查(默认值:false)
示例:
{
"checkHealth": true
}
响应包括:
- 各环境的运行状况
- API 调用统计信息(总调用次数、成功率)
- 近期操作日志
- 环境配置摘要
OData 查询语法
筛选示例
// 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
写入操作(仅限非生产环境)
写入操作仅适用于 type: "non-production" 的环境。生产环境始终为只读。
create_record
在 D365 实体中创建新记录。
参数:
entity(字符串,必填):实体名称data(对象,必填):新记录的字段值environment(字符串,可选):目标环境
示例:
{
"entity": "CustomersV3",
"data": {
"CustomerAccount": "CUST-001",
"CustomerName": "Contoso Ltd",
"CustomerGroup": "US"
},
"environment": "uat"
}
update_record
更新现有记录。
参数:
entity(字符串,必填):实体名称key(字符串 | 对象,必填):记录键data(对象,必填):要更新的字段值etag(字符串,可选):用于乐观并发控制的 ETagenvironment(字符串,可选):目标环境
示例:
{
"entity": "CustomersV3",
"key": "CUST-001",
"data": {
"CustomerName": "Contoso Corporation"
},
"environment": "dev"
}
delete_record
从实体中删除记录。
参数:
entity(字符串,必填):实体名称key(字符串 | 对象,必填):记录键etag(字符串,可选):用于乐观并发控制的 ETagenvironment(字符串,可选):目标环境
示例:
{
"entity": "CustomersV3",
"key": "CUST-001",
"environment": "dev"
}
安全性
凭据保护
本项目采用多层措施来保护你的 Azure AD 凭据:
| 保护措施 | 说明 |
|---|---|
.gitignore | .env 和 d365-environments.json 不会纳入版本控制 |
.gitattributes | 敏感文件不会包含在 git archive 导出内容中 |
| 提交前钩子 | 在允许提交前扫描暂存文件,查找机密信息模式 |
| 脱敏错误 | Azure AD 错误响应会在内部记录,但不会暴露给调用方 |
受保护的文件
以下文件包含凭据,受到保护:
.env- 环境变量(旧版单环境配置)d365-environments.json- 包含机密信息的多环境配置*.local.json- 本地配置覆盖项
安全文件(包含占位符,可以提交):
.env.example- 含占位符值的模板d365-environments.example.json- 示例配置
凭据泄露时的处理方式
如果你不慎提交或泄露凭据:
-
立即轮换 Azure AD 客户端机密:
- 前往 Azure Portal > Azure Active Directory > App registrations
- 选择你的 D365 应用注册
- 前往“Certificates & secrets”
- 创建新的客户端机密
- 使用新机密更新本地
.env或d365-environments.json - 从 Azure AD 中删除旧机密
-
查看 Azure AD 登录日志:
- 检查是否存在未经授权的访问尝试
- Azure Portal > Azure AD > Sign-in logs > 按你的应用筛选
-
如果已提交到 git:
- 即使在新提交中移除机密,它仍会保留在 git 历史记录中
- 可考虑使用
git filter-branch或 BFG Repo-Cleaner 清除历史记录 - 强制推送清理后的仓库(请与协作者协调)
轮换 Azure AD 机密
最佳做法是定期轮换机密(每 90-180 天):
- 在 Azure Portal 中创建新机密(在旧机密过期之前)
- 更新配置文件:
# Edit .env or d365-environments.json with new secret - 测试连接:
npm start # Verify authentication works - 从 Azure Portal 中删除旧机密
提交前钩子
提交前钩子会扫描以下模式:
- Azure AD 客户端机密(
clientSecret后的 30 个或更多字符组成的字符串) - 租户 ID(
tenantId后的 UUID 格式字符串) - 含机密信息的环境变量赋值
仅在误报时,可通过以下方式绕过:
git commit --no-verify
运行时保护
- 生产环境只读:在结构上禁止对生产环境执行写入操作
- 非生产环境写入权限:仅
type: "non-production"环境支持创建、更新和删除操作 - 不暴露凭据:凭据由服务端管理
- 防止 OData 注入:参数会经过适当编码
故障排除
MCP 服务未显示
-
完全重启 Claude Desktop - 在 macOS 上按 Cmd+Q(不要只关闭窗口),然后重新打开。在 Windows 上,按 Ctrl+Q 或从系统托盘退出。
-
检查服务配置 - 确认配置文件路径正确:
D365_CONFIG_FILE=./d365-environments.json D365_SINGLE_ENV=uat node dist/index.js -
验证配置路径 - 确保 Claude 配置中的
D365_CONFIG_FILE指向d365-environments.json的实际位置。 -
查看 Claude 日志 - macOS:
~/Library/Logs/Claude/;Windows:%APPDATA%\Claude\logs\
身份验证错误
- 确认租户 ID、客户端 ID 和机密正确
- 确保 Azure AD 应用具有所需的 API 权限
- 检查是否已授予管理员同意
找不到实体
- 使用
d365://entities查找可用实体 - 实体名称区分大小写
- 某些实体可能不会通过 OData 公开
超时错误
- 使用
$top和$filter缩小查询范围 - 对于大型数据集,使用
$skip进行分页 - 使用
batch_query并行运行多个查询
大型数据集聚合优化:
- 分页请求现在使用 60 秒超时,并自动重试(最多重试 2 次,采用指数退避)
- 通过
D365_PAGINATION_TIMEOUT_MS环境变量配置超时时间 - 对于超大型数据集(10 万条以上记录),在
aggregate工具中使用sampling=true,快速获取统计估算结果 - 如果在分页过程中中断,
accurate=true模式现在会报告部分结果
许可证
MIT
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。