内容
# 智慧之家 MCP
> **这是什么** — 一个MCP(模型上下文协议)服务器,它会向多个不同的AI模型家族提出相同的问题,并将所有答案返回给你,但不进行合并。
>
> **如何阅读** — [它是什么以及它不是什么](#它是什么,它不是什么) → [快速入门](#快速入门) →
> [三种模式](#三种模式) → [两个工具](#两个工具) →
> [调用流程](#调用流程) → [安装](#安装) →
> [配置参考](#配置参考)。当你遇到问题时,请查看 [注意事项](#注意事项)。
> **要求** — Python 3.10+、`uv`/`uvx`,以及至少 **两个** 已启用模型。
>
> **代码反映截至** — 2026-07-20,提交 `7ac2449`,软件包版本 `0.6.1`。
中世纪的 [智慧之家](https://zh.wikipedia.org/wiki/%E6%99%BA%E6%89%88%E4%B9%8B%E5%AE%B6) 因为其多样性而运作良好:来自许多传统的学者、翻译家和抄写员阅读相同的问题,但通过不同的视角,读者会权衡结果。该服务器对模型执行相同的操作 — 通过 OpenRouter 的 OpenAI、Anthropic 和 Google、DeepSeek、本地 Ollama 或任何 OpenAI 兼容的端点。
---
## 它是什么,它不是什么
| 它能做到 | 它不能做到 |
| --- | --- |
| 并行启动多个模型回答同一个问题 | 合并、排名、投票或总结它们的答案 |
| 返回每个模型的完整、独立分析 | 返回单一的“委员会答案” |
| 可选地让每个模型先读取您的代码库(只读) | 永不写入、执行或网络访问超出每个模型的端点 |
| 用运行模式标记每个答案 | 告诉你哪个答案是正确的 |
**没有合成器**。调用者 — 您的 IDE 代理 — 读取每个观点并做出决定。将任何单个观点视为绝对真理会破坏设计。
**不要在每个提示上启动委员会**。它需要多个模型调用和数十秒的时间。只有当不同的模型家族看到问题可能改变结果时,才使用它。对于只需要您自己专注推理的工作,请使用单思维工具。
---
## 快速入门
```bash
# 1. 获取配置文件
curl -O https://raw.githubusercontent.com/EzzoHamdan/house-of-wisdom-mcp/master/config.example.yaml
# 2. 编辑它 — 唯一必须设置的字段是 `models`,并且至少启用 2 个。
# 3. 使用您的 MCP 客户端注册服务器(请参阅安装以获取每个客户端的语法):
# 命令:uvx
# 参数:--from git+https://github.com/EzzoHamdan/house-of-wisdom-mcp@master
# ai-council --config /绝对路径/to/config.yaml
# 4. 重启客户端,然后让您的代理调用 `list_models`。
```
如果 `list_models` 返回您的名单,则服务器已加载。然后尝试在 `scribe` 模式下进行一次 `consult` 调用 — 这是快速路径,不需要文件系统访问。
---
## 三种模式
一个参数 `mode` 决定了每个顾问有多少自由度。它是调用时更改行为的唯一旋钮。
| 模式 | 工具 | 工具预算 | 范围规则 | 使用场景 |
| --- | --- | --- | --- | --- |
| `scribe` | 无 | - | - | 您已经将相关代码粘贴到 `context` 中,或者问题是需要判断的问题,无需查询 |
| `translator` | 只读 | `max_tool_iterations`(内置 **8**) | `scope_hint` 是一个 **笼子** — 提示禁止漫游 | 您知道哪些文件相关,并希望每个模型都针对它们进行验证 |
| `scholar` | 只读 | `scholar_max_tool_iterations`(内置 **64**) | `scope_hint` 是一个 **起点** — 提示允许跟随线索 | 您不知道哪些文件相关 |
默认预算,时钟排序运行 `scribe` < `translator` < `scholar`; 绝对数字完全取决于您配置的模型以及它们是本地还是远程。但是,没有强制执行此排序 — 提高 `max_tool_iterations` 超过 `scholar_max_tool_iterations` 会逆转它。
### 有效模式的解析
优先级,最高优先顺序(`synthesis.py::collect_perspectives`):
| # | 源 | 结果 |
| --- | --- | --- |
| 1 | `mode` 参数,如果它是 `scribe` / `translator` / `scholar` 之一(不区分大小写) | 该模式 |
| 2 | `mode` 参数设置为其他任何值 | 记录警告,继续到 3 |
| 3 | 遗留 `agentic` 参数 | `false` → `scribe`,`true` → `translator` |
| 4 | `synthesizer_tools.enabled` 在配置中 | `true` → `translator`,`false` → `scribe` |
`agentic` 是一个已弃用的布尔值,用于保持旧调用者的兼容性;`mode` 优先。`scholar` 只能通过显式请求。
⚠ `synthesizer_tools.enabled` **仅在第 4 行选择默认值**。它不是一个门控:显式 `mode: "translator"` 或 `mode: "scholar"` 即使 `enabled: false` 也会运行工具循环。
---
## 两个工具
> **在 v0.5.0 中重命名**。`ai_council` → `consult` 和 `ai_council_list_models` → `list_models`。
> 旧名称仍然有效 — 它们被接受为向后兼容的别名,因此在重命名之前注册的客户端或脚本仍可正常运行。只有新名称被宣传,因此您的代理将发现并使用 `consult` / `list_models`。
### `list_models`
不带参数。直接从加载的配置中返回已配置的名单。
```json
{
"status": "success",
"data": {
"models": [
{"name": "GLM", "model_id": "glm-5.2:cloud", "provider": "custom", "enabled": true},
{"name": "Kimi", "model_id": "kimi-k2.7-code:cloud", "provider": "custom", "enabled": true}
],
"max_models": 3,
"enabled_count": 2
}
}
```
它 **不** 与任何端点联系,**不** 验证 API 密钥。`enabled: true` 表示“存在于配置中且未关闭”,而非其他。注意,`models` 列出每个已配置的条目;当 `consult` 调用未指定子集时,仅启用 `max_models` 个模型会触发,但 **任何** 已启用的模型都可以通过名称请求 — 请参阅 [实际触发哪些模型](#实际触发哪些模型)。
### `consult`
| 参数 | 类型 | 必需 | 含义 |
| --- | --- | --- | --- |
| `context` | 字符串 | **是** | 背景。不能为空,最大 **200,000** 个字符。在 `scribe` 模式下,这是模型看到的唯一内容,因此请在此粘贴文件内容。 |
| `question` | 字符串 | **是** | 不能为空,最大 **10,000** 个字符。 |
| `mode` | 字符串 | 否 | `scribe` \| `translator` \| `scholar`。请参阅 [解析顺序](#有效模式的解析)。 |
| `workspace_root` | 字符串 | 否 | 用作只读沙箱根目录的绝对路径。在 `scribe` 中忽略。默认为 `synthesizer_tools.workspace_root`,然后是 **服务器进程的工作目录** — 由您的 MCP 客户端设置,而不是您。显式传递它。 |
| `scope_hint` | 字符串 | 否 | 注入每个顾问的系统提示,例如“从 main.py 和 config.py 开始”。在 `scribe` 中忽略。 |
| `models` | 字符串数组 | 否 | 顾问 `name` 值的子集。针对 **所有** 已启用的模型解析,因此任何启用的模型都可以通过名称访问,无论 `max_models` 或其在 YAML 中的位置如何。未知名称将被静默删除;如果没有存活下来,则调用失败并返回 `NO_MATCHING_MODELS`。省略以触发默认窗口(第一个启用的 `max_models`)。 |
| `agentic` | 布尔值 | 否 | 已弃用别名:`false` → `scribe`,`true` → `translator`。被 `mode` 覆盖。 |
**成功形状**。每个已调度的模型一个条目,按名单顺序排列:
```json
{
"status": "success",
"data": {
"perspectives": [
{"label": "GLM", "model_name": "GLM", "code_name": "Alpha", "analysis": "...", "status": "ok", "mode": "translator"},
{"label": "Kimi", "model_name": "Kimi", "code_name": "Beta", "analysis": "...", "status": "error", "mode": "translator"}
],
"consensus": {"models_queried": 2, "models_succeeded": 1, "models_failed": 1}
}
}
```
- 失败的顾问以 `status: "error"` 和错误文本位于 `analysis` 中返回。整个调用仍然报告 `"status": "success"`,只要至少有一个顾问成功。
- `label` 通常是 `model_name`,或当 `anonymous_perspectives: true` 时为 `code_name`。
- `consensus` 不计算任何一致性 — 尽管名称如此,它是一个调度计数器,`models_failed` 只是 `status: "error"` 条目的数量。
**错误形状**。
```json
{"status": "error", "error": {"code": "...", "message": "...", "type": "...", "details": "..."}, "data": null}
```
| `code` | 何时触发 |
| --- | --- |
| `INVALID_INPUT` | `context` 或 `question` 为空或超过其字符上限 |
| `NO_MATCHING_MODELS` | 传递了 `models` 数组,但没有匹配启用的模型 |
| `NOT_ENOUGH_MODELS_ENABLED` | 可用的模型列表为空(启动验证通常会阻止这种情况) |
| `ALL_MODELS_FAILED` | 每个顾问都出错或整个批次超时 |
| `UNKNOWN_TOOL` | 工具名称既不是 `consult` 也不是 `list_models` |
| `INTERNAL_ERROR` | 未处理的异常;`details` 携带 Python 错误字符串 |
`data` 在除 `ALL_MODELS_FAILED` 之外的所有错误中均为 `null`,后者携带 `{"attempted_models": N, "failed_responses": N}`。请注意,没有存活分析的个别分析不会通过该路径 — 如果您需要慢批次的部分结果,请提高 `parallel_timeout` 而不是重试。
---
## 调用流程
```text
MCP 客户端(编排器)
│ consult(context, question, mode?, workspace_root?, scope_hint?, models?)
▼
main.py::_process_ai_council
├─ validate context 1..200,000 chars · question 1..10,000 chars → INVALID_INPUT
└─ roster `models` passed? yes → keep FULL enabled list entries whose name matches
nothing left? → NO_MATCHING_MODELS
no → default window = enabled[:max_models]
▼
synthesis.py::collect_perspectives
├─ mode mode arg > agentic bool > synthesizer_tools.enabled
├─ sandbox workspace_root arg > config workspace_root > process cwd
└─ budget scholar → scholar_max_tool_iterations · else max_tool_iterations
│
├── scribe ───────────► models.py::call_models_parallel
│ one chat call per model · temp 0.7 · max_tokens 8000
│ ⚠ 无并发上限 — 每个模型立即触发
│
└── translator ───────► models.py::call_models_parallel_agentic
scholar semaphore = max_concurrent_consultants
per consultant, repeat until it answers or budget runs out:
chat(tools=[read_file,list_dir,glob_search,think])
├─ tool_calls? → dispatch in sandbox → append results → loop
└─ plain text? → that is the analysis
budget exhausted → one forced "answer from what you have" turn
temp 0.4 · max_tokens 16000
▼
perspectives[] one per dispatched model, roster order, failures included
▼
MCP client weighs them. No synthesizer runs, in any mode.
```
#### 调用流程(渲染)
```mermaid
%%{init: {'themeVariables': {'lineColor': '#8b949e'}}}%%
flowchart TD
C([MCP client]) -->|consult| V{{"validate<br/>context ≤200k · question ≤10k"}}
V -->|invalid| E1["INVALID_INPUT"]
V --> S{{"models arg passed?"}}
S -->|"yes: match against ALL enabled"| R1["roster = named enabled models"]
S -->|"no: default window"| R2["roster = first max_models enabled"]
R1 -->|none match| E2["NO_MATCHING_MODELS"]
R1 --> M{{"resolve mode"}}
R2 --> M
M -->|scribe| P["call_models_parallel<br/>no tools · no semaphore"]
M -->|translator| A["call_models_parallel_agentic<br/>budget = max_tool_iterations"]
M -->|scholar| A2["call_models_parallel_agentic<br/>budget = scholar_max_tool_iterations"]
A --> L
A2 --> L
L{{"tool loop, per consultant<br/>capped by max_concurrent_consultants"}}
L -->|tool_calls| T[("sandbox: read_file · list_dir<br/>glob_search · think")]
T --> L
L -->|plain text| O["one perspective per model<br/>errors included in-band"]
P --> O
O --> C
classDef gate stroke-dasharray:4 3;
class V,S,M,L gate;
```
---
## 安装
### erequisites
| 需要 | 为什么 |
| --- | --- |
| Python 3.10+ | `requires-python = ">=3.10"` |
| [`uv` / `uvx`](https://docs.astral.sh/uv/getting-started/installation) | 服务器启动方式。验证 `uvx --version`。 |
| [Ollama](https://ollama.com) on `localhost:11434` | 仅适用于本地模型。先拉取标签 (`ollama pull glm-5.2:cloud`),确认 `ollama list`。 |
| 提供商 API 密钥 | 仅适用于付费模型(OpenAI、OpenRouter、DeepSeek 等)。 |
### 步骤 1 — 编写配置文件
从 [`config.example.yaml`](config.example.yaml) 开始,它是完全注释的:
```bash
curl -O https://raw.githubusercontent.com/EzzoHamdan/house-of-wisdom-mcp/master/config.example.yaml
mkdir -p ~/.config/ai-council && mv config.example.yaml ~/.config/ai-council/config.yaml
```
`~/.config/ai-council/config.yaml` 是服务器检查 `--config` 时的路径。任何其他位置都有效,如果您传递 `--config /绝对路径.yaml`。
⚠ 如果您传递给 `--config` 的路径不存在,它将被 **忽略而不会报错**。服务器然后使用内置默认值启动,其名单是三个 OpenRouter 模型 — 所以路径输入错误的症状通常是 `OpenRouter API key is required`,而不是“找不到文件”。
### 步骤 2 — 使用您的 MCP 客户端注册服务器
启动命令在所有地方都相同;只有周围的 JSON/TOML 不同。
```
command: uvx
args: --from git+https://github.com/EzzoHamdan/house-of-wisdom-mcp@master
ai-council
--config /绝对路径/to/config.yaml
```
> **服务器键是显示名称。** 您为其在 `mcpServers` 下提供的键(下面的 `House-of-Wisdom`)是您的 IDE 显示的工具前缀,例如 `House-of-Wisdom [consult]`。重命名它为您喜欢的任何名称 — 它纯粹是外观设置,存在于您的客户端配置中,而不是此存储库中。`ai-council` 内部的令牌是一个不同的东西(安装的控制台脚本名称),必须保持不变。
**Claude Desktop** — 设置 → 开发者 → 编辑配置,打开
`~/Library/Application Support/Claude/claude_desktop_config.json`(macOS)或
`%APPDATA%\Claude\claude_desktop_config.json`(Windows):
```json
{
"mcpServers": {
"House-of-Wisdom": {
"command": "uvx",
"args": ["--from", "git+https://github.com/EzzoHamdan/house-of-wisdom-mcp@master",
"ai-council", "--config", "/绝对路径/to/config.yaml"]
}
}
}
```
**Cursor** — 相同的 JSON,在 `.cursor/mcp.json`(项目)或通过设置 → MCP(全局)。
**Kilo Code** — 相同的 JSON,在打开的文件中
(*编辑全局 MCP* → `mcp_settings.json`,或 *编辑项目 MCP* → `.kilocode/mcp.json`)。Kilo
在模型响应太慢时会杀死服务器,因此如果您使用 `scholar` 模式,请提高其每服务器超时时间:
```json
{
"mcpServers": {
"House-of-Wisdom": {
"command": "uvx",
"args": ["--from", "git+https://github.com/EzzoHamdan/house-of-wisdom-mcp@master",
"ai-council", "--config", "/绝对路径/to/config.yaml"],
"timeout": 240
}
}
}
```
**Claude Code (CLI)** — `--` 分隔符很重要,否则 `claude` 会将其解析为自己的标志:
```bash
claude mcp add House-of-Wisdom -- uvx --from git+https://github.com/EzzoHamdan/house-of-wisdom-mcp@master \
ai-council --config /绝对路径/to/config.yaml
```
**Codex CLI** — `~/.codex/config.toml`:
```toml
[mcp_servers."House-of-Wisdom"]
command = "uvx"
args = ["--from", "git+https://github.com/EzzoHamdan/house-of-wisdom-mcp@master",
"ai-council", "--config", "/绝对路径/to/config.yaml"]
```
**其他任何地方** — 任何支持 stdio MCP 的客户端都可以运行此服务器。只有注册语法会发生变化。
### 步骤 3 — 重启客户端
配置在进程启动时读取。每个配置编辑或服务器升级都需要客户端重启。
### 步骤 4 — 验证
1. 调用 `list_models` → 您的名单返回。
2. 调用 `consult`,使用 `mode: "scribe"`,短 `context` 和短 `question` → 每个模型都回答。这将模型连接性与文件系统/沙箱问题隔离。
3. 只有这样,请尝试使用显式 `workspace_root` 的 `translator`。
---
## 配置参考
### 每个键值,包含默认值
“内置”是指键值在 YAML 中不存在时的值。“示例文件”是指
[`config.example.yaml`](config.example.yaml) 随附的内容 — 这些不同,因此请勿将示例文件视为默认值。
| 键值 | 内置 | 示例文件 | 有效范围 |
| --- | --- | --- | --- |
| `max_models` | `3` | `3` | 1–10 |
| `parallel_timeout` | `60` | `240` | 5–600(秒) |
| `log_level` | `INFO` | `INFO` | `DEBUG` `INFO` `WARNING` `ERROR` `CRITICAL` |
| `anonymous_perspectives` | `false` | `false` | 布尔值 |
| `max_concurrent_consultants` | `3` | `3` | 1–32 |
| `openai_api_key` | 未设置 | 已注释掉 | 字符串 |
| `openrouter_api_key` | 未设置 | 已注释掉 | 字符串 |
| `synthesizer_tools.enabled` | `false` | `true` | 布尔值 |
| `synthesizer_tools.workspace_root` | `null` → 进程工作目录 | `null` | 绝对路径 |
| `synthesizer_tools.max_tool_iterations` | `8` | `12` | 1–128 |
| `synthesizer_tools.scholar_max_tool_iterations` | `64` | `64` | 1–256 |
| `synthesizer_tools.allowed_tools` | 所有四个 | 所有四个 | 四个工具名称的子集 — ⚠ `[]` 意味着 **全部四个**,而不是没有 |
| `models` | 3 个 OpenRouter 模型 | 6 个启用的 Ollama + 4 个禁用的付费模型 | 2–10 个条目(10 个配置的最大值,2 个启用的最小值) |
最小有效配置只有:
```yaml
models:
- name: "GLM"
provider: "custom"
model_id: "glm-5.2:cloud"
base_url: "http://localhost:11434/v1"
api_key: "ollama"
enabled: true
- name: "Kimi"
provider: "custom"
model_id: "kimi-k2.7-code:cloud"
base_url: "http://localhost:11434/v1"
api_key: "ollama"
enabled: true
```
### 启动验证 - 服务器启动的限制
在加载时检查。这些检查在标准错误流中以 `Configuration error: …` 开头退出 - 您的 MCP 客户端通常将其显示为“服务器启动失败” - 并包装在一个 Pydantic `ValidationError` 中,因此下面的字符串作为较长消息的一部分出现,而不是单独出现。
| 规则 | 消息包含 |
| --- | --- |
| 至少 **2** 个模型必须启用 | `必须启用至少两个模型` |
| 总共配置 **10** 个模型 | `不能配置超过 10 个模型(找到 N 个)` |
| `code_name` 值必须唯一 | `模型配置中发现重复的代码名称` |
| 每个 `provider: custom` 条目都需要 `base_url` | `自定义端点 '<name>' 需要 base_url` |
| 每个 `provider: custom` 条目都需要 `api_key` | `自定义端点 '<name>' 需要 api_key` |
| `provider: openai` 需要来自某处的密钥 | `如果使用 OpenAI 模型,则需要 OpenAI API 密钥` |
| `provider: openrouter` 需要来自某处的密钥 | `如果使用 OpenRouter 模型,则需要 OpenRouter API 密钥` |
`base_url` 要求独立于 `api_key` 进行检查(v0.5.1)。以前,验证在存在 `api_key` 时会短路,因此带有密钥但没有 `base_url` 的 `custom` 条目永远不会被检查,客户端会静默地回退到 OpenAI 的默认端点 - 将提示静默发送到 `api.openai.com`。现在,不带 `base_url` 的 `custom` 条目在启动时被拒绝(并在客户端构造时受到保护)。
### 模型条目字段
| 字段 | 是否必需 | 备注 |
| --- | --- | --- |
| `name` | 是 | 人类标签。此字符串是 `models` 调用参数匹配的对象。 |
| `model_id` | 是 | 提供者的标识符:Ollama 标签、OpenRouter 斜杠、OpenAI 模型名称。 |
| `provider` | 否(默认 `openrouter`) | `openai` \| `openrouter` \| `custom` |
| `base_url` | 自定义 | OpenAI 兼容的 `/v1` 端点。当设置 `api_key` 时不强制执行 - 请参见上面的警告。 |
| `api_key` | 自定义,否则可选 | 覆盖此条目的提供者级密钥。 |
| `enabled` | 否(默认 `true`) | `false` 保留配置但禁用。 |
| `code_name` | 否 | 如果省略,则从 `Alpha, Beta, Gamma, …` 自动分配。仅在 `anonymous_perspectives: true` 时显示。 ⚠ 在接近 10 个模型的上限时手动设置可能会导致启动崩溃 - 请参见[边缘情况](#sharp-edges)。 |
### 顾问配方
**本地 Ollama** - 免费,`api_key` 可以是任何非空字符串:
```yaml
models:
- name: "GLM"
provider: "custom"
model_id: "glm-5.2:cloud" # 来自 `ollama list` 的标签
base_url: "http://localhost:11434/v1"
api_key: "ollama"
enabled: true
```
**OpenAI** - 一个共享密钥在文件顶部:
```yaml
openai_api_key: "sk-..."
models:
- name: "GPT-5.6-Terra"
provider: "openai"
model_id: "gpt-5.6-terra"
enabled: true
```
**Claude / Gemini / 大多数其他通过 OpenRouter** - 一个密钥,许多系列。`model_id` 是来自 [openrouter.ai/models](https://openrouter.ai/models) 的斜杠:
```yaml
openrouter_api_key: "sk-or-..."
models:
- name: "Claude Opus"
provider: "openrouter"
model_id: "anthropic/claude-opus-4"
enabled: true
- name: "Gemini Pro"
provider: "openrouter"
model_id: "google/gemini-2.5-pro"
enabled: true
```
**DeepSeek 直接访问,Perplexity,Groq,Together,vLLM,LM Studio** - 任何 OpenAI 兼容的都使用 `provider: custom` 及其自己的 `base_url` 和 `api_key`。使用 `${ENV_VAR}` 占位符将密钥排除在文件之外(请参见[将密钥排除在 YAML 之外](#keeping-keys-out-of-the-yaml)):
```yaml
models:
- name: "DeepSeek-Pro"
provider: "custom"
model_id: "deepseek-chat"
base_url: "https://api.deepseek.com/v1"
api_key: "${DEEPSEEK_API_KEY}" # 在加载时从环境中解析
enabled: true
```
### API 密钥 - 读取位置
按模型,第一个匹配项获胜:
1. 模型条目上的 `api_key` - 始终适用于该条目,无论提供者如何。
2. 提供者级密钥(`openai_api_key` for `provider: openai`,`openrouter_api_key` for `provider: openrouter`),它本身解析为 **CLI 标志 → YAML 顶级字段 → 环境变量**。
`provider: custom` 条目永远不会回退到提供者级密钥;它们需要自己的 `api_key`。
环境变量名称为 `AI_COUNCIL_OPENAI_API_KEY` 和 `AI_COUNCIL_OPENROUTER_API_KEY`(v0.5.1 - 字段不再声明 Pydantic 别名,因此 `AI_COUNCIL_` 前缀适用于它们,就像其他设置一样)。在您的 MCP 客户端启动的环境中,未加前缀的 `OPENAI_API_KEY` / `OPENROUTER_API_KEY` 不会被采用 - 设置前缀名称或通过 YAML 或 CLI 标志传递密钥。
所有设置都使用前缀并区分大小写:`AI_COUNCIL_OPENAI_API_KEY`,`AI_COUNCIL_OPENROUTER_API_KEY`,`AI_COUNCIL_MAX_MODELS`,`AI_COUNCIL_PARALLEL_TIMEOUT`,`AI_COUNCIL_LOG_LEVEL`,`AI_COUNCIL_MAX_CONCURRENT_CONSULTANTS`,`AI_COUNCIL_ANONYMOUS_PERSPECTIVES`。
### 将密钥排除在 YAML 之外
配置文件描述 **结构** (哪些模型、超时、模式);秘密应位于 **环境** 中。保持 `config.yaml` 无密钥意味着您可以将其交给 AI 代理进行编辑,甚至可以提交它而不会泄露密钥。有三种方法可以在不触及文件的情况下提供密钥:
1. **环境变量** - 设置带有 `AI_COUNCIL_` 前缀的名称。对于 MCP,干净的地方是客户端的 `env` 块,因此服务器在启动时继承它们:
```json
"env": {
"AI_COUNCIL_OPENAI_API_KEY": "sk-...",
"AI_COUNCIL_OPENROUTER_API_KEY": "sk-or-..."
}
```
2. **`.env` 文件** - 服务器在配置文件旁边 **和** 工作目录中读取一个,然后再解析任何密钥。复制已提交的 [.env.example](.env.example) 到 `.env`(它被 gitignore)并填写;使用相同的 `AI_COUNCIL_` 前缀名称:
```dotenv
AI_COUNCIL_OPENAI_API_KEY=sk-...
AI_COUNCIL_OPENROUTER_API_KEY=sk-or-...
```
实际环境变量(例如来自客户端的 `env` 块)始终优先于 `.env` 文件。
3. **`${ENV_VAR}` 占位符** - 任何 `api_key` 或 `base_url` 值都可以引用环境变量,它在加载时被扩展。这是 **唯一** 一个针对 `provider: custom` 条目(DeepSeek,Groq,...)的每个模型 `api_key` 的密钥排除路径,因为它们永远不会回退到提供者级密钥:
```yaml
api_key: "${DEEPSEEK_API_KEY}"
```
引用的但未设置的变量在启动时大声失败,命名缺少的变量 - 它永远不会被静默发送为空密钥。
`config.yaml`,`*.local.yaml` 和 `.env` 被 gitignore,因此填充的配置不能被意外提交。
### 谁实际触发
每个调用的 roster 以两种方式构建,然后由并发性限制:
```text
配置模型 ── enabled: true ──► 启用模型
│
没有 `models` 参数 ◄─────┴─────► 传递 `models` 参数
│ │
[:max_models] 匹配名称对抗所有启用的模型
│ │
默认窗口 此调用的 roster(任意启用的模型,任意位置)
└──────────────┬───────────────┘
▼
此调用的 roster ── semaphore ──► 一次运行 N 个(仅翻译者/学者)
```
- `max_models`(1-10)限制 **默认** 风控 - 用于调用时未指定子集的风控。窗口是 YAML 列表中按 **位置** 启用的前 `max_models` 个条目,而不是按偏好。
- 每个调用的 `models` 参数针对 **完整** 启用的列表进行解析,因此任何启用的模型都可以通过名称访问,无论 `max_models` 或其位置如何。八个启用的模型和 `max_models: 3`,按名称请求第七个模型会触发该模型。命名的子集不会被缩减为 `max_models` - 调用者选择了模型,并且 `max_concurrent_consultants` 仍然限制同时运行的模型数量。
- `max_concurrent_consultants`(1-32)限制同时运行的工具循环数量;其余的队列。将其与提供者的并发性额度相匹配(Ollama Cloud Pro = 3,Max = 10)。
⚠ 它在 `scribe` 模式下不适用 - 在那里,roster 中的每个模型都立即触发。
---
## 顾问沙盒
在 `translator` 和 `scholar` 模式下,每个顾问都有自己的只读工具循环,根目录位于 `workspace_root`。这些工具对顾问内部可见;它们永远不会暴露给您的 MCP 客户端。
| 工具 | 签名 | 行为 |
| --- | --- | --- |
| `read_file` | `path` | UTF-8 读取,截断为 200,000 字节,并带有 `...[truncated]` 标记。无法解码的字节将被替换,不会致命。 |
| `list_dir` | `path`(默认根目录) | 每行一个条目,相对根目录,目录以 `/` 后缀。 |
| `glob_search` | `pattern` | 相对根目录的 glob 搜索,例如 `**/*.py`。最多 100 个结果。 |
| `think` | `thought` | 回声思考。无 I/O。按与他人相同的条件消耗预算。 |
使用 `synthesizer_tools.allowed_tools` 限制工具集;对工具列表的调用返回错误字符串给模型而不是执行。 ⚠ 空列表被视为允许,而不是限制 - `allowed_tools: []` 广告并允许所有四个。要实际缩小范围,请命名您想要的工具,例如 `["read_file", "think"]`。
### 什么永远不会发生
- 没有写入,没有 shell,没有来自工具的网络。上述四个是整个表面(`tools.py::ToolRegistry.call`)。
- 没有 `workspace_root` 之外的读取。路径使用 `Path.resolve()` 解析并使用 `relative_to()` 检查,因此指向根目录之外的符号链接被拒绝为 `SandboxViolation`(`tools.py::ToolRegistry._resolve`)。
- `scribe` 模式下没有工具调用。
- `list_models` 没有端点联系。
- 没有视角合并,在任何模式下。
### 预算会计
当模型用纯文本回复而不是工具调用时,循环停止,或者当预算用完时 - 之后它将获得一个强制回合,从它收集的内容中回答,并如果该内容为空,则会收到第二个提示。
⚠ 预算计算 **轮次**,不是单个工具调用:一个包含多个工具调用的助理回合花费一个单位,而不是四个 - 而与其一起批处理的 `think` 是免费的。系统告诉模型每个调用的成本为一个单位,因此大多数模型表现得好像更严格的会计是真实的,但批处理模型可以合法地读取您的工作空间远多于预算数字所建议的内容。
---
## 运行范围
值在 `models.py` 中硬编码;这里列出是因为它们在外部不可见。
| 路径 | 温度 | `max_tokens` | 超时 |
| --- | --- | --- | --- |
| `scribe` (`call_model`) | 0.7 | 8,000 | `parallel_timeout`,应用于整个批次 |
| `translator` / `scholar` (`call_model_with_tools`) | 0.4 | 16,000 | `parallel_timeout`,同时应用于每个顾问和整个批次 |
空响应获得一个自动重试。返回其答案的模型在 `reasoning`、`thinking` 或 `reasoning_content` 字段中 - Ollama 云思维模型中常见 - 由 `models.py::_extract_text` 处理。
提供者请求适配(v0.5.2,`models.py::_create_completion`):对于 `provider: openai`,输出上限作为 `max_completion_tokens` 发送,而不是 `max_tokens`(较新的 OpenAI 模型拒绝后者)。如果模型仍然拒绝参数 - 例如推理模型仅接受默认 `temperature: 1` - 则会删除该参数并重试调用(最多三次剥离)。`custom` / `openrouter` 端点发送经典的 `max_tokens` / `temperature` 不变。
⚠ 因为 `parallel_timeout` 也限制整个批次,并且因为排队的顾问在该窗口内花费等待时间,所以带有超过 `max_concurrent_consultants` 的模型数量的 `scholar` 运行需要一个慷慨的值。当批次超时时,只有仍在运行的顾问被取消并标记为超时;已经完成的视角被保留(v0.5.1),因此缓慢的落后者不再将部分成功折叠为 `ALL_MODELS_FAILED`。
---
## 边缘情况
已知系统看起来像它做的那样与实际行为之间的差异。每个都在代码中验证。
| ⚠ | 详细信息 |
| --- | --- |
| `synthesizer_tools.enabled` | 默认模式开关,不是功能门 - 显式 `mode` 绕过它。 `synthesis.py:126-135` |
| 并发上限 | `max_concurrent_consultants` 仅在 `translator`/`scholar` 中受尊重。 `models.py::call_models_parallel`(`scribe` 路径)没有信号量。 |
| 报告的 `mode` | 是您**请求的模式**。如果代理设置引发,则运行静默降级为纯无工具调用,但视角仍标记为 `translator`/`scholar`。 `synthesis.py:169-180`,在 `synthesis.py:194` 处标记。 |
| `anonymous_perspectives` | 仅更改 `label`。 `model_name` 和 `code_name` 在有效载荷中始终都存在,因此这对协调器隐藏了什么。 |
| 缺失配置路径 | `--config /typo.yaml` 被静默忽略,内置默认值接管 - 包括它们的 OpenRouter 名单。没有回退到 `~/.config/ai-council/config.yaml`;只有在完全省略 `--config` 时才会探测该路径。 `config.py:274-277` |
| 工具预算 | 计算包含工具调用的助理回合,而不是单个调用。 |
> **Fixed in v0.6.1.** 每个调用的 `models` 参数现在针对完整的启用列表进行解析,因此明确命名的启用模型无论 `max_models` 或其在 YAML 中的位置如何,都可以访问。 `max_models` 再次限制默认(无子集)风控,并且 `NO_MATCHING_MODELS` 详细信息报告真正的完整启用集。 `config.py::get_enabled_models`,`main.py::_process_ai_council`。
> **Fixed in v0.5.1.** 解决了九个长期存在的边缘问题:`AI_COUNCIL_` 环境前缀现在有效(并且不采用裸 `OPENAI_API_KEY`);批处理超时保留已经完成的顾问;`status` 是显式携带的,而不是从文本中猜测;`custom` 模型没有 `base_url` 被拒绝而不是与 `api.openai.com` 通话;`allowed_tools: []` 现在允许;`code_name` 自动分配不再跳过名称或在 10 个模型处崩溃;广告版本跟踪包;`synthesis_model_selection`(已死)被删除;并且 `consult` 描述不再引用示例配置默认值。
---
## CLI 参数
| 标志 | 效果 |
| --- | --- |
| `--config PATH` | 配置文件。没有它,会尝试 `~/.config/ai-council/config.yaml`。 |
| `--max-models N` | 覆盖 `max_models`。 |
| `--parallel-timeout N` | 覆盖 `parallel_timeout`,单位为秒。 |
| `--log-level LEVEL` | `DEBUG` \| `| DEBUG` \| `WARNING` \| `ERROR`。(`CRITICAL` 在 YAML 中有效,但在 CLI 中不接受。) |
| `--openai-api-key KEY` | 覆盖 YAML 值。 |
| `--openrouter-api-key KEY` | 覆盖 YAML 值。 |
日志输出到 stderr,这是 MCP 客户端收集服务器输出的地方。 `--log-level DEBUG` 打印每个顾问进行的工具调用。
---
## 代码地图
| 关注点 | 来源 |
| --- | --- |
| MCP 接线,工具模式,请求验证,错误形状 | [`ai_council/main.py`](ai_council/main.py) |
| 模式枚举,模式解析,每个模式的提示后缀,视角组装 | [`ai_council/synthesis.py`](ai_council/synthesis.py) |
| 客户端构造,并行调度,工具循环,并发信号量 | [`ai_council/models.py`](ai_council/models.py) |
| 配置模式,默认值,启动验证,YAML/环境加载 | [`ai_council/config.py`](ai_council/config.py) |
| 沙盒,四个工具,路径解析,工具模式 | [`ai_council/tools.py`](ai_council/tools.py) |
| 结构化错误日志记录 | [`ai_council/logger.py`](ai_council/logger.py) |
在 `uv run pytest`(或在激活的虚拟环境中运行 `pytest -q`)下运行测试。它们涵盖了配置解析和沙盒路径解析;没有一个测试是实际运行模型调用的。
---
## 致谢
灵感来自 [Cognition Wheel](https://github.com/Hormold/cognition-wheel),它确立了多模型咨询的集体智慧方法。该项目与它不同之处在于完全跳过了合成步骤,添加了三个命名模式,为每个顾问提供了自己的只读调查循环,并将视角权重推回调用者。地中海中世纪智慧之家提供了名称和原理:多个镜头,由读者称重,而不是由权威合并。
## 执照
MIT - 请参阅 [LICENSE](LICENSE)。
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器