内容
# terra — make-it-in-germany MCP CORE
**主机无关的MCP CORE服务器** (软件包 `mig_mcp`)通过[模型上下文协议](https://modelcontextprotocol.io)提供**真实的德国熟练工人数据**,以及**重新实施的、基于法规的快速检查引擎**。
它提供:
- 来自 **联邦就业机构(Bundesagentur für Arbeit,简称BA)** 的 **实时数据**,包括职位发布、职业与KldB-2010映射、薪资范围、学徒职位、AVGS辅导服务以及通过GovData/Destatis开放数据发现的外国资格认可统计数据。
- 一个 **确定性、基于法规的快速检查引擎**,重新实现了机会卡(Chancenkarte)积分自我检查和意图 → 签证路线候选人查找器,基于主要法规(§20a AufenthG)。
它故意 **不**:
- 包含任何LLM SDK(不捆绑或调用第三方模型);
- 提供任何make-it-in-germany **门户文本**(该门户是机器人隔离的,其内容不在范围内 - 此CORE提供 *数据*,而非门户文本);
- 进行任何具有约束力的法律确定(每个快速检查结果都是非约束性的自我评估,带有验证标志和法规引用)。
每个面向外部的结果都带有 **出处**:源标签、规范引用URL、`source_status`,以及(适用时)许可证和新鲜度/过期注意。
---
## 5个承载约束
该CORE的设计使得兼容的主机(自托管的Integreat Ollama RAG堆栈)可以满足这些约束。CORE在其内在能力范围内强制执行这些约束;其余约束存在于主机中,并在文档中诚实地记录。
1. **自托管 / 无第三方LLM**。CORE **不包含LLM SDK**,也不调用任何模型。这是内在保证 - 主机不能使CORE调用第三方模型。
2. **每个事实都有依据且可引证**。每个工具和资源结果都带有 `provenance`,包括 `source_url` 和 `source_status`。行为良好的主机可以依据答案并运行 `FACT_CHECK`;CORE提供依据材料,但不能 **强制** 给定主机实际进行事实检查。
3. **设备上数据最小化(仅枚举)**。钱包导出的 `personalContext` 和快速检查枚举属于特殊类别。它们仅限于用户同意的设备上Integreat路径。通用主机适配器 **拒绝** 携带 `personalContext` 或快速检查枚举键的任何有效载荷(参见安全性和GDPR)。
4. **≤3句子的HTML / BCP-47答案合约存在于主机中**。该CORE **不生成答案正文**,不选择答案长度,也不进行本地化。它返回结构化数据 + 出处;主机现有的生成步骤以用户的语言编写答案。
5. **附加 / 向后兼容**。CORE添加MCP基元而不更改现有主机行为。Integreat包装路径是附加的:当没有CORE结果(或它降级)时,主机的答案与以前一样来自CMS语料库。
---
## 快速入门
需要Python ≥ 3.10和[uv](https://docs.astral.sh/uv/)。
```bash
# 安装(带有开发/测试额外功能)
uv sync --extra dev
# 通过stdio运行MCP CORE(默认 - 用于同位置的Integreat主机)
uv run terra-mig
# 通过Streamable HTTP运行(仅限内部演示 - 127.0.0.1:8080 + 源白名单)
uv run terra-mig --transport http
```
`terra-mig`控制台脚本在`pyproject.toml`(`mig_mcp.server:main`)中定义。您也可以运行 `python -m mig_mcp.server --transport http`。
### 将其添加到MCP主机(stdio)
大多数MCP主机接受命令 + 参数。示例主机配置条目:
```jsonc
{
"mcpServers": {
"terra-mig": {
"command": "uv",
"args": ["run", "terra-mig"],
// 可选地将工作目录设置为此仓库,并通过env传递MIG_*
"env": {
"MIG_BA_JOBSUCHE_API_KEY": "jobboerse-jobsuche"
}
}
}
}
```
无需任何配置,服务器仍然会启动,并且所有BA支持的工具都会优雅地降级(它返回一个空结果,带有`source_status` `unavailable`/`degraded`,而不是崩溃)。
---
## 配置
所有配置均来自环境变量,前缀为 `MIG_`(参见 [`.env.example`](.env.example),它是变量名称的真实来源)。复制它到 `.env`(gitignored)并填写所需的内容。
| 变量 | 默认值 | 用途 |
| --- | --- | --- |
| `MIG_BA_JOBSUCHE_API_KEY` | *(空)* | BA Jobsuche的X-API-Key |
| `MIG_BA_BERUFENET_API_KEY` | *(空)* | BERUFENET的X-API-Key |
| `MIG_BA_ENTGELTATLAS_API_KEY` | *(空)* | Entgeltatlas的X-API-Key |
| `MIG_BA_AUSBILDUNGSSUCHE_API_KEY` | *(空)* | Ausbildungssuche的X-API-Key |
| `MIG_BA_COACHING_API_KEY` | *(空)* | Coachingangebote(AVGS)的X-API-Key |
| `MIG_BA_WEITERBILDUNG_API_KEY` | *(空)* | Weiterbildungssuche的X-API-Key(默认禁用) |
| `MIG_HTTP_TIMEOUT_SECONDS` | `10` | 上游请求超时(1–60) |
| `MIG_HTTP_USER_AGENT` | `terra-mig-mcp/0.1 (+…)` | 每个上游请求中发送的用户代理 |
| `MIG_CACHE_TTL_REFERENCE_SECONDS` | `86400` | 引用数据(职业、薪水、统计数据)的TTL |
| `MIG_CACHE_TTL_VOLATILE_SECONDS` | `300` | 实时/波动数据(职位列表)的TTL |
| `MIG_HTTP_HOST` | `127.0.0.1` | Streamable HTTP绑定地址(本地主机) |
| `MIG_HTTP_PORT` | `8080` | Streamable HTTP端口 |
| `MIG_ALLOWED_ORIGINS` | `http://127.0.0.1,http://localhost` | 源白名单(DNS重绑定保护) |
> **BA API密钥不是秘密**。联邦就业机构(Bundesagentur für Arbeit,简称BA)API是 **非官方/社区反向工程** 的(bund.dev / bundesAPI)。它们的“密钥”是众所周知的 **公共** 客户端标识符,从机构自己的前端提取。它们不具有保密价值,但 **可能会在任何时候被撤销**。留空是安全的 - 服务器会降级运行。规范公共标识符在 <https://bund.dev> 上发布。
---
## 工具 / 资源 / 提示目录
10种工具(全部注释为`readOnlyHint=true`、`openWorldHint=true`),5种资源模板和3种提示。完整参考:[`docs/TOOLS.md`](docs/TOOLS.md)。
### 工具
| 工具 | 支持来源 | 状态 |
| --- | --- | --- |
| `search_jobs` | BA Jobsuche | 实时 |
| `get_job_detail` | BA Jobsuche(详情) | 实时 |
| `map_profession_to_occupation` | BERUFENET | 实时 |
| `get_salary_band` | Entgeltatlas | 实时 |
| `search_apprenticeships` | BA Ausbildungssuche | 实时 |
| `search_coaching` | BA Coachingangebote(AVGS) | 实时 |
| `query_recognition_stats` | GovData CKAN / Destatis | **未验证** (分布尚未确认) |
| `search_continuing_education` | BA Weiterbildungssuche | **禁用/降级** (上游404) |
| `quickcheck_opportunity_card` | 法规(§20a AufenthG)+临时权重 | **临时** |
| `quickcheck_visa_route` | 法规+意图→路线词汇 | **未验证的存根** |
### 资源模板(RFC 6570)
| URI模板 | 描述 |
| --- | --- |
| `country://iso3/{code}` | ISO-3166 alpha-3 → 国家名称 |
| `country://iso3` | 完整的嵌入式ISO-3166 alpha-3 → 名称映射 |
| `occupation://berufenet/{query}` | BERUFENET职业(带有KldB代码)用于查询 |
| `salary://kldb/{code}` | 缓存的KldB-2010代码的薪资范围 |
| `stats://recognition/{year}` | 某年的识别统计数据(Destatis;dl-de/by-2-0) |
### 提示
| 提示 | 用途 |
| --- | --- |
| `assess_visa_options` | 为意图+国籍链接 `quickcheck_visa_route` |
| `check_opportunity_card` | 收集个人资料并运行 `quickcheck_opportunity_card` |
| `find_jobs_for_profession` | 映射职业→KldB,然后搜索工作(+可选薪水) |
---
## 诚实:验证内容与作为存根/临时内容的内容
真实数据核心(BA + GovData)返回带有出处验证的实时上游数据。**快速检查引擎作为临时/存根内容**,并在每个结果中标明。 在呈现结果时要准确:
- **验证**
- 机会卡 **6分门槛** 及其在 **§20a AufenthG** 中的编码。
- **非积分路径**:具有德国认可的职业资格或认可/等效的大学学位的申请人 **无论积分总数如何** 都有资格(`pathway = "recognized_qualification"`,`eligible = true`)。
- **临时** (`verification_status = "provisional"`)
- 机会卡的 **每个标准积分权重**。实际的每答案值在服务器端运行于Radware机器人墙后无法提取。发布的权重是明确标记的占位符(小0-4整数范围),仅存在เพื่อให้引擎具有确定性形状。每个分解都标记为 `breakdown_status = "provisional"`。**总值不具有法律效力**。
- **未验证的存根** (`verification_status = "unverified_stub"`)
- **每个国籍的签证路线映射**。`quickcheck_visa_route`仅返回意图级别的候选路线, **从不按国籍分支** (该映射尚不存在为验证输入)。`nationality_iso3`和`facts`被回声返回以便向前兼容,但不影响结果。
- **未验证的数据源** (`source_status = "unverified"`)
- `query_recognition_stats`通过GovData CKAN发现Destatis识别数据集。即使成功的CKAN响应也标记为`unverified`,因为支持实际计数的具体CSV/XML分布 **尚未确认实时** - 行仅携带数据集 **标签** 且 **从不制造计数**。
### 法律效力 & 过时
`fetched_at` 跟踪 *检索*,而非法律变更。法规衍生制品另外携带一个 **法律效力封套**:`statute_version`、`review_by` 和一个命名的 `owner`。当今天的日期超过 `review_by` 时,引擎会添加一个显著的 `staleness_caution` 到免责声明中。在发货时,`statute_version` 的确切修订日期和法律 `owner` 是 **占位符,待分配** - 在 `src/mig_mcp/rules/quickcheck_tables.py` 中标记。
每个快速检查结果都带有 **非约束性免责声明**,引导用户前往外交事务局/德国驻外使馆,或当地的 Ausländerbehörde,作为唯一具有约束力的决定机构。
---
## 安全性和GDPR立场
- **零信任:上游内容是数据**。连接器解析JSON;它们从不执行、评估或将获取的文本插入shell。Integreat适配器仅 **格式化** 文本并容忍形状漂移(异常类型的行被跳过,从不引发)。
- **Streamable HTTP已强化,仅用于演示**。当使用 `--transport http` 时,服务器绑定到 `127.0.0.1` 并启用DNS重绑定保护,带有 `Origin` / `Host` 白名单(Streamable-HTTP 必须)。此接口 **不提供FACT_CHECK** ,可能会驱动第三方LLM,因此 **不是合规产品接口** - 它仅用于内部演示。
- **代码或日志中没有秘密**。API密钥从不硬编码(仅从 `MIG_*` 环境变量中获取)。包含秘密的标头(`X-API-Key`、`Authorization`、`Cookie`,...)在任何日志行之前都会被删除。日志记录仅输出到 **stderr**,以便stdout保持干净的JSON-RPC通道用于stdio传输。
- **GDPR数据最小化保护**。通用主机适配器(`mig_mcp.adapters.generic`)**拒绝** 携带顶级 `personalContext` 键或任何快速检查枚举键(`qualification`、`german_level`、`english_level`、`nationality_iso3`、`ties_to_germany`、`age_band`、`work_experience_years`、`intent`)的任何传入有效载荷,引发 `PersonalContextRejected`。这些枚举属性仅限于用户同意的Integreat路径;通用主机是外部上下文,不应接收它们。
---
## 测试
测试套件使用 `pytest` + `pytest-asyncio`(`asyncio_mode = "auto"`)和 `respx` 模拟 `httpx`,因此它离线运行且确定性强(无实际网络调用)。
```bash
uv run --project . pytest -q
```
当前套件:**135个测试,全都通过** - 分跨连接器(60)、引擎 + 适配器(49)和服务器 + 服务(26)。覆盖范围保护负载承载合约:每个上游故障模式下的优雅降级(401/403 → 不可用,404 → 降级,超时/传输/非JSON → 不可用/降级),快速检查完整性(6分门槛,认可资格路径,容忍枚举强制转换,JSON序列化,过时警告),GDPR保护,Integreat合成文档包装和MCP线路(10个只读工具,资源模板,3个提示)。
---
## 架构
参见 [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) 了解CORE/适配器分层、数据路径(连接器 → 规范化 → 缓存 → MCP基元)、Integreat合成文档包装路径、传输和新鲜度/法律效力策略。
---
## 许可证 & 归属
- **代码:** MIT(参见 `pyproject.toml`)。
- **GovData / Destatis开放数据** (识别统计数据)获得 **`dl-de/by-2-0`** (Datenlizenz Deutschland – Namensnennung – Version 2.0)许可,并相应地在每个结果的出处中归属。
- **法规文本** 引用主要来源 [gesetze-im-internet.de](https://www.gesetze-im-internet.de/aufenthg_2004/__20a.html) (§20a AufenthG),而不是摘要页面。
- **联邦就业机构(Bundesagentur für Arbeit,简称BA)API** 通过 [bund.dev](https://bund.dev) 中记录的非官方/社区标识符访问;参见上面的配置说明。