# dify-mcp

一个轻量 MCP Server：只调用用户在外部配置文件中手工指定的 Dify 应用。不再读取 Dify Console、不同步全部任务，也不维护 registry 文件。

## 安装与启动

```bash
npm install
npm run build
node dist/index.js
```

全局安装发布包：

```bash
npm install -g dify-tool-mcp
```

## 配置任意 MCP 客户端

在支持本地 stdio MCP Server 的客户端中添加以下标准配置。配置文件位置和顶层字段名由客户端决定，常见字段名为 `mcpServers`。

```json
{
  "mcpServers": {
    "dify-tool-mcp": {
      "command": "npx",
      "args": ["-y", "dify-tool-mcp"],
      "env": {
        "DIFY_MCP_CONFIG_PATH": "/absolute/path/to/dify-mcp-config.json",
        "DIFY_BASE_URL": "https://dify.example.com"
      }
    }
  }
}
```

Windows 路径须使用 JSON 转义反斜杠，例如 `C:\\Users\\name\\.dify-mcp\\config.json`。如果已全局安装，也可将 `command` 改为 `dify-mcp` 并移除 `args`。`DIFY_BASE_URL` 对私有 Dify 部署尤其重要；SaaS 或已经写入配置文件时可省略。

连接后，让 Agent 调用 `dify_configure_app` 并传入 API Key。该工具会验证 Key、读取应用信息和参数，再创建或更新配置文件；无需把 API Key 放入 MCP 客户端配置。

查看版本、简介与随包附带的 Agent 指南路径：

```bash
dify-mcp --version
```

## Agent Skill（可选）

发布包包含 [skills/dify-tool-mcp/SKILL.md](./skills/dify-tool-mcp/SKILL.md)。它是通用 Agent 操作指南，不依赖 Codex：将该目录安装到所用 Agent 平台的 Skill/Instructions 机制中；若平台没有 Skill 功能，可将其中的任务匹配与调用规则加入系统提示。MCP Server 本身不依赖该 Skill，也可以直接调用。

**自动安装**：连接 MCP 后调用 `dify_get_skill`，它会返回随包 skill 的绝对路径、文件清单和全部文件内容。Agent 会据此把 skill 目录复制到宿主平台的 Skill 机制中并确认安装路径——CodeBuddy 优先用户级 `~/.codebuddy/skills/`（项目级为 `<工作目录>/.codebuddy/skills/`），Claude Code 为 `~/.claude/skills/`，其他平台按各自约定。无需手动复制，也没有 postinstall 脚本。

## 手工配置要使用的 Dify 工作流

默认配置路径为 `~/.dify-mcp/config.json`。通过 `DIFY_MCP_CONFIG_PATH` 可以指定其他绝对路径。

### 使用 API Key 快速配置

也可以让 MCP 直接完成配置：调用 `dify_configure_app` 并提供 Dify Service API Key（私有部署时同时提供 `base_url`）。该工具会先请求 Dify 的 `/v1/parameters` 和 `/v1/info`，识别应用模式、名称、描述与输入参数；确认可访问后才原子写入 `config.json`。工具结果不会回显 API Key。

```json
{
  "api_key": "app-REDACTED",
  "base_url": "https://dify.example.com",
  "function_description": "分析简历并提出针对目标岗位的优化建议"
}
```

`function_description` 可选，但建议填写；它是 Agent 用来匹配用户任务与 Dify 能力的主要依据。重复提供相同 API Key 或 `app_id` 时会更新现有条目。若设定 `enabled: true`，需重启 MCP 服务后才会出现对应的独立工具；`dify_invoke` 则可立即使用。

从 [dify-mcp.config.example.json](./dify-mcp.config.example.json) 复制一份作为配置文件，并只保留需要使用的应用：

```json
{
  "version": 1,
  "base_url": "https://dify.example.com",
  "apps": [
    {
      "app_id": "resume-review",
      "name": "简历优化",
      "api_key": "app-xxxxxxxx",
      "function_description": "根据简历和目标岗位给出匹配度分析与优化建议",
      "mode": "workflow"
    },
    {
      "app_id": "knowledge-chat",
      "name": "知识库问答",
      "api_key": "app-yyyyyyyy",
      "function_description": "回答企业内部知识库问题",
      "mode": "advanced-chat"
    }
  ]
}
```

每个 Dify 应用只需要：

| 字段 | 说明 |
| --- | --- |
| `api_key` | Dify Service API Key。一个 API Key 对应一个具体 Dify 应用。 |
| `function_description` | 该应用的功能描述，作为 MCP 工具描述。 |

推荐额外填写 `app_id`，作为稳定调用标识。其他可选字段为 `name`、`mode`、`tool_name`、`enabled` 和 `inputs`。

`mode` 可填 `workflow`、`advanced-chat`、`chat`、`completion` 或 `auto`。使用 `auto` 时服务会根据 `/v1/parameters` 和 `/v1/info` 推断调用方式。

## 参数发现与热加载

服务启动时读取配置文件，并为每个已配置应用请求 `GET /v1/parameters`，自动获得 Dify 的 `user_input_form`。

后续每次调用前会检查配置文件的修改时间和大小：未变更时不重复读取；文件发生变更后立即加载新的 API Key、描述、模式和参数。若 Dify 返回参数校验错误，也会重新获取一次参数定义并在结果中返回最新的 `inputs`。

配置中设定 `enabled: true` 的应用会在启动时注册为独立 MCP 工具。工具列表变化需要重启；通用调用则无需重启。

## 调用

先使用 `dify_list_configured_apps` 查看手工配置的应用，然后调用 `dify_invoke`：

```json
{
  "ref": "resume-review",
  "inputs": {
    "resume_text": "...",
    "target_role": "产品经理"
  }
}
```

聊天/文本生成应用传入 `query`：

```json
{
  "ref": "knowledge-chat",
  "inputs": {
    "query": "公司的报销流程是什么？"
  }
}
```

`dify_invoke` 默认使用流式响应，自动路由到相应端点：

| 类型 | Dify 端点 |
| --- | --- |
| `workflow` | `/v1/workflows/run` |
| `advanced-chat` / `chat` | `/v1/chat-messages` |
| `completion` | `/v1/completion-messages` |

## MCP 工具

| 工具 | 用途 |
| --- | --- |
| `dify_get_skill` | 返回随包 Agent Skill 的路径、文件清单与内容，供 Agent 自助安装到宿主平台的 Skill 机制。 |
| `dify_list_configured_apps` | 列出配置文件中手工指定的应用。 |
| `dify_config_status` | 查看配置文件路径、修改时间和参数刷新状态（含知识库配置状态）。 |
| `dify_configure_app` | 根据 API Key 先读取 Dify 应用信息和参数，再安全写入或更新外部配置。 |
| `dify_invoke` | 调用已配置的 Dify 应用，默认流式。 |
| `dify_run_workflow` | `dify_invoke` 的兼容别名。 |

## 知识库（Datasets）

知识库（数据集）使用**独立的 Datasets API Key**（`DIFY_DATASET_API_KEY`），与应用 Service API Key 不同。知识库与应用在配置文件中**分别维护**：应用放在 `apps`，知识库放在顶层 `datasets` 数组，互不混合。

### 环境变量

```bash
export DIFY_DATASET_API_KEY="dataset-xxxxxxxx"
```

也可在 `config.json` 顶层加 `dataset_api_key`（环境变量优先，且不会被 `dify_configure_app` 写入流程删除）。

### 工具

重点工具：

| 工具 | 用途 |
| --- | --- |
| `dify_dataset_maintain` | 维护知识库的统一入口，按参数自动判断动作（见下）。 |
| `dify_dataset_retrieve` | 在指定知识库检索（用 `dataset_id` 或已注册的 `name`）。支持两层 LLM Wiki 检索：`layer` 默认 `wiki`（优先查组织视图库，权重更高）；`raw` 查原始文档库；`both` 先 wiki 再 raw（取更细数据）。不传 `dataset_id`/`name` 时按 `layer` 自动选库。 |

> 其余数据集操作（列出知识库/文档、查索引状态、删除文档/知识库）仅作为内部能力，不对外暴露为 MCP 工具；如需使用请在 `dify_dataset_maintain` 流程内或通过代码扩展。

### `dify_dataset_maintain` 动作判定

| 传入参数 | 动作 |
| --- | --- |
| 仅 `name` | 创建新知识库，并自动登记到配置 |
| `dataset_id` + `text` | 文本新增文档 |
| `dataset_id` + `file_path` | **文件新增文档**：内部读取本地文件字节，以 multipart 上传 |
| `dataset_id` + `document_id` + (`text`\|`file_path`) | 修改/替换该文档 |
| `dataset_id`（且未登记） | 向 Dify 验证该库可访问，并**自动登记进配置**（免去手改 JSON） |

### 多知识库示例

每个知识库用 `dataset_id` 区分。告诉模型"财务制度库"或"产品手册库"，即可用 `name` 或 `dataset_id` 检索：

```json
{
  "dataset_id": "4d15325b-8839-4403-bae8-c0a836240500"
}
```

调用 `dify_dataset_maintain` 传入上述 `dataset_id` 即完成验证与登记；之后 `dify_dataset_retrieve` 可直接引用。

### 实战示例：数据库规格说明库

下面是一个已落地的真实配置示例——把一批数据库表结构规格文件同步进知识库，并通过名称检索。

**1. 登记知识库**（已存在于 Dify，只需验证并写入配置）：

```json
{
  "version": 1,
  "base_url": "http://dy.gaodunwangxiao.com",
  "apps": [ ... ],
  "datasets": [
    {
      "dataset_id": "8fb1e70b-2d66-4102-bb27-f17f8c8d24d3",
      "name": "数据库规格说明",
      "description": "E:/gaodunspec/database-sail/database 下 5 个数据库实例（ADB03/P505/S133/S165/S174）的表结构规格说明。"
    }
  ]
}
```

**2. 批量上传本地目录下的所有文件**（每个文件作为一个独立文档，便于精确检索）。用脚本循环调用 `dify_dataset_maintain` 的 `file_path` 动作即可，文档名建议带相对路径以便溯源：

```json
{
  "dataset_id": "8fb1e70b-2d66-4102-bb27-f17f8c8d24d3",
  "file_path": "E:/gaodunspec/database-sail/database/P505-Pro-learning_analysis-PolarDB/learning_analysis/analysis_flink_student_course.txt",
  "name": "P505-Pro-learning_analysis-PolarDB/learning_analysis/analysis_flink_student_course.txt"
}
```

> `high_quality` 索引（含 embedding + reranking）由 Dify 在服务端后台完成，大量文档会分批就绪，就绪后即可检索。

**3. 用名称检索**（无需记 `dataset_id`，直接用登记时的 `name`）：

```json
{
  "name": "数据库规格说明",
  "query": "学生学习时间统计表有哪些字段",
  "top_k": 3
}
```

返回结果含命中文档名、分段内容与相关度评分，例如命中 `ads_eds_student_study_report_day_detail_df.txt` 并给出表字段说明。

### 文件上传

`dify_dataset_maintain` 的 `file_path` 为**服务器本地路径**，工具用 `fs.readFile` 读取后以 `multipart/form-data` 调用 `POST /datasets/{dataset_id}/document/create-by-file`。文件创建是维护动作的一部分（如 `file_path` 与 `dataset_id` 一起传入）。

### 配置文件中的知识库

登记后配置示例如下（与 `apps` 并列）：

```json
{
  "version": 1,
  "base_url": "https://dify.example.com",
  "dataset_api_key": "dataset-xxxxxxxx",
  "apps": [ { "app_id": "resume-review", "api_key": "app-xxxxxxxx", "function_description": "..." } ],
  "datasets": [
    { "dataset_id": "4d15325b-8839-4403-bae8-c0a836240500", "name": "财务制度库" }
  ]
}
```

## LLM Wiki 摄取管道（两层模型）

借鉴 Karpathy 的 LLM Wiki 思路，把知识库管理从"灌原始文件"升级为"**底层原始文档 + 上层 Wiki 组织视图**"的两层结构。**关键设计：底层原始文档保持不变（不编译、不删除），Wiki 只在上层做组织与摘要**，避免把零散表结构编译成 1:1 页面后既冗余又稀释检索质量。

- **底层：Raw Source（原始文档层）**：磁盘上的 `.txt`/`.md` 表结构文件原样上传到知识库，文件名带相对路径以保留层级。不可变、不删除、不重编译。本仓库 `数据库规格说明` 库即此层（430 个文档）。
- **上层：Wiki 组织视图（Org View）**：由专门的 `wiki_compiler` chat 应用（默认 `app-VeX2lYKaK6uj1ZbBrNhcFhPR` / "自管理LLM"）**按数据库实例（顶层目录）** 编译出一篇「实例总览」Wiki 页面——YAML frontmatter（`type: instance_overview` / `instance` / `generated_by`）+ `## 实例定位` / `## 核心表与设计` / `## 表间关系` / `## 相关原始文档`（列出该实例全部原始文件名，便于回源）。上传到**独立的知识库** `数据库规格说明-组织视图`（5 篇页面，对应 5 个实例）。
- **Schema（契约）**：命名约定即规则，`dify_wiki_lint` 负责体检。

> 为什么拆成两个知识库？Dify 无法在单个知识库内为不同文档设置检索权重。把组织视图放在独立知识库后，可在编排层（agent / 应用）**优先检索组织视图**（命中率高、噪音低），未命中或需要细节时再回原始文档层取正文，从而让"组织页权重更大"。

### 命名约定（单一事实来源）

| 类型 | 知识库 | 文档名 |
| --- | --- | --- |
| 原始文档（底层） | `数据库规格说明` | `<db_instance>/<submodule>/<file>`（如 `P505-Pro-learning_analysis-PolarDB/learning_analysis/analysis_flink_...txt`） |
| 实例总览（上层） | `数据库规格说明-组织视图` | `wiki/<db_instance>-总览.md` |

实例总览的 frontmatter `instance` 对应底层实例目录名，`## 相关原始文档` 列出该实例全部原始文件名，`dify_wiki_lint` 可据此做孤儿/缺失检测。

### 配置 wiki compiler

在配置文件（或环境变量）提供编译器应用的 Service API Key，置于顶层、与 `datasets` 并列：

```json
{ "wiki_compiler_api_key": "app-xxxxxxxx" }
```

### 按知识库使用不同的编译器提示词（per-dataset prompt）

编译提示词（喂给 `wiki_compiler` 应用的 `sys_prompt`）现在**写到配置里、按知识库自动选用**，从而同一套 `dify_wiki_build` / `dify_wiki_compile_one` 流水线可以编译不同类型的事实（数据库表结构、源代码、API 文档……），而不必改代码。

**优先级链**（从高到低）：
1. 工具调用时显式传 `sys_prompt`（一次性覆盖，便于试验）。
2. 目标数据集条目的 `compiler_prompt`（每个数据集自己的提示词）。
3. 配置顶层 `default_wiki_compiler_prompt`（全局默认）。
4. 内置默认提示词（面向数据库表结构 `WIKI_COMPILER_SYS_PROMPT`）。

```json
{
  "default_wiki_compiler_prompt": "你是一个通用文档编译器……（作为兜底）",
  "datasets": [
    { "dataset_id": "…", "name": "数据库规格说明", "role": "raw",
      "org_view_dataset_id": "…", "compiler_prompt": "你是一个数据库规格说明编译器……（表结构专用）" },
    { "dataset_id": "…", "name": "源码事实库", "role": "raw",
      "org_view_dataset_id": "…", "compiler_prompt": "你是一个代码事实抽取编译器……（源码专用，含 ## 公开接口与符号 / ## 调用关系 等章节）" }
  ]
}
```

内置还提供了一套**面向代码事实**的提示词模板 `WIKI_COMPILER_SYS_PROMPT_CODE`（导出自 `src/wiki.ts`），可直接复制进某个数据集的 `compiler_prompt` 使用，或作为自定义提示词的起点。调用时另可传 `kind`（如 `"源代码文件"`），仅用于把查询正文里的"原始X文件"措辞调整得更贴切——真正决定抽取什么的是 `compiler_prompt`。

### 工具

| 工具 | 用途 |
| --- | --- |
| `dify_wiki_build` | LLM Wiki Ingest：递归把每个 Raw Source 文件编译为 Wiki 页面并上传。支持 `dry_run`、`batch`（默认 4）、`force_rebuild`、`keep_raw`，以及 `sys_prompt`（覆盖编译器提示词）、`kind`（源类型措辞）。编译器提示词按数据集 `compiler_prompt` 自动选用。**下层组织场景建议改用按实例的「实例总览」方式（见下）。** |
| `dify_wiki_lint` | 审计知识库：命名违规、索引未就绪、重复名、孤儿 Wiki（源缺失）、未编译原始文件。 |
| `dify_wiki_compile_one` | 单文件编译/重摄取，复用同一核心，适合定向修复。 |

### 实战：430 个表结构文件的两层组织

**1. 底层原始文档**（已在 `数据库规格说明` 库，430 个文档，文件名带实例/子模块路径，未做 1:1 编译、保留原文）。

**2. 上层组织视图**：用 `wiki_compiler` 按 5 个实例各生成一篇「实例总览」，上传到独立库 `数据库规格说明-组织视图`：

```json
{
  "source_dir": "E:/gaodunspec/database-sail/database",
  "org_dataset_id": "b1e66498-f865-4433-9029-c3a11a0096ac",
  "filter_instances": ["ADB03-...", "P505-...", "S133-...", "S165-...", "S174-..."],
  "indexing_technique": "high_quality",
  "doc_language": "Chinese"
}
```

每篇总览含 `## 相关原始文档`，列出该实例全部表结构文件名，检索命中后可据此回源到 `数据库规格说明` 库取细节。

**3. 检索优先级（让组织页权重更大）**：`dify_dataset_retrieve` 已内置两层策略——不传 `dataset_id`/`name` 时，默认 `layer: "wiki"` 优先查该原始库配对的组织视图库；需要字段级细节时传 `layer: "both"`（先 wiki 再 raw，合并返回）或 `layer: "raw"` 只查原始文档库。若显式传 `dataset_id`/`name` 则在该数据集配对内按层检索（向后兼容）。

**多组知识库的配对模型**：每组原始库各配一个组织视图库，在配置中**显式声明**配对关系，而非全局唯一：

```json
{
  "dataset_id": "8fb1e70b-...", "name": "数据库规格说明", "role": "raw",
  "org_view_dataset_id": "b1e66498-..."          // 指向本组的组织视图库
},
{ "dataset_id": "b1e66498-...", "name": "数据库规格说明-组织视图", "role": "wiki_org_view" }
```

- `layer=wiki`/`both` 时，工具读取原始库的 `org_view_dataset_id` 定位组织视图库，多组之间互不串库。
- **若原始库未配置 `org_view_dataset_id`**：`layer=wiki` 会返回 `ask_create_org_view: true` 的提示（含建议库名与创建方式），并**跳过**组织视图查询；`layer=both` 同样提示，但仍查询原始库取细节。是否创建由调用方决定——可用 `dify_dataset_maintain({ name: "<原始库名>-组织视图" })` 创建并把返回的 `dataset_id` 填回 `org_view_dataset_id`，之后即可命中。

```json
{ "query": "学习平台有哪些核心表", "layer": "wiki" }   // 默认，查配对的组织视图库
{ "query": "study 表具体字段定义", "layer": "both" }    // 组织视图 + 原始文档取细节
```

> 磁盘上的源文件**不会被改动**。上层总览只是对原始文档的编译产物，可随时基于底层原文重新生成。

## 环境变量

| 变量 | 说明 |
| --- | --- |
| `DIFY_MCP_CONFIG_PATH` | 外部 JSON 配置文件路径。 |
| `DIFY_BASE_URL` | 配置文件没有 `base_url` 时的默认 Dify 地址。 |
| `DIFY_DEFAULT_USER` | Dify `user` 字段默认值，默认 `dify-mcp`。 |
| `DIFY_MCP_HOME` | 默认配置文件目录。 |
| `DIFY_DATASET_API_KEY` | **知识库（datasets）专用 API Key**，与每个应用的 Service API Key 不同。配置知识库工具（新增/维护/检索）前必须设置。也可用配置文件顶层 `dataset_api_key` 字段，但环境变量优先且不落盘。 |
| `DIFY_WIKI_COMPILER_API_KEY` | **LLM Wiki 编译器**应用的 Service API Key（默认 "自管理LLM" chat 应用）。驱动 `dify_wiki_build`/`dify_wiki_compile_one` 把原始文件编译为结构化 Wiki 页面。也可用配置文件顶层 `wiki_compiler_api_key` 字段，环境变量优先且不落盘。 |

配置文件保存 API Key，请限制为当前用户可读且不要提交到版本库。后续配置页面可直接读写该文件。
