resume-repo-sync
# resume-repo-sync
基于 **官方 Python MCP SDK(FastMCP)** 的本地 MCP Server:
根据 GitHub 仓库的**客观信号**自动生成简历「项目经历」bullet,合并进已有 PDF 简历的结构化数据,并用 HTML 模板 + WeasyPrint 导出新 PDF。
> **设计原则**
> 1. 工具逻辑与 MCP 协议层分离(核心函数可脱离 MCP 单测)
> 2. 依赖自包含:直接 `requests` 调 GitHub REST,不套第三方 GitHub MCP
> 3. 只写可验证事实,禁止虚构「性能提升 50%」类指标
> 4. 不持久化用户简历 / token;临时文件可过期清理
---
## 功能一览
| # | Tool | 说明 |
|---|------|------|
| 1 | `parse_resume` | PDF → 结构化 `ResumeData` |
| 2 | `fetch_repo_info` | GitHub REST → `RepoInfo`(含 `topics`、`contributor_stats`) |
| 3 | `analyze_repo_for_resume` | 客观技术要点 + `contribution_level` + `absolute_contribution_signal` |
| 4 | `generate_bullet_points` | 按贡献级别 / 绝对提交量收敛措辞;negligible 可跳过整段项目 |
| 5 | `merge_into_resume` | 插入/覆盖项目(冲突 → `project_already_exists`) |
| 6 | `diff_resume_versions` | 项目粒度人类可读 diff |
| 7 | `render_pdf` | Jinja2 模板 + WeasyPrint → PDF(`modern` / `compact`) |
推荐调用顺序:
```
parse_resume
→ fetch_repo_info(github_username?)
→ analyze_repo_for_resume(github_username?, target_role?)
→ generate_bullet_points # 若 skip_project_entry 则不要 merge
→ merge_into_resume
→ diff_resume_versions
→ render_pdf(template=modern|compact)
```
---
## 架构
```text
┌─────────────────────────────────────┐
│ MCP Client (Claude Desktop / │
│ Inspector / 其他 LLM Host) │
└─────────────────┬───────────────────┘
│ stdio
┌─────────────────▼───────────────────┐
│ server.py (FastMCP Tool 注册) │
│ 仅做参数校验 + envelope 序列化 │
└─────────────────┬───────────────────┘
│ 调用纯 Python 函数
┌──────────────┬──────────────┼──────────────┬──────────────┐
▼ ▼ ▼ ▼ ▼
resume/ github/ generation/ cleanup.py config.py
parser fetcher bullet_writer TTL / rmtree GITHUB_TOKEN
merge analyzer
diff
renderer ──► templates/{modern,compact}.html ──► WeasyPrint PDF
```
**分层收益**:业务模块无 `mcp` import → `pytest` 直接测;换 transport(未来 HTTP)不必动分析/渲染逻辑。
---
## 项目结构
```
resume-repo-sync/
├── src/resume_repo_sync/
│ ├── server.py # FastMCP + Tool 注册(stdio)
│ ├── config.py # 环境变量 / TTL
│ ├── cleanup.py # 临时文件清理
│ ├── resume/
│ │ ├── schema.py # Pydantic 模型 + ToolErrorCode
│ │ ├── parser.py # pdfplumber 解析
│ │ ├── merge.py / diff.py
│ │ └── renderer.py # Jinja2 + WeasyPrint
│ ├── github/
│ │ ├── fetcher.py # REST:meta / languages / README /
│ │ │ # commits / contributors / deps
│ │ └── analyzer.py # 客观分析 + 贡献分级
│ └── generation/
│ └── bullet_writer.py # bullet 措辞(含 negligible 跳过)
├── templates/
│ ├── modern.html # 单栏宽松
│ └── compact.html # 双栏紧凑
├── scripts/e2e_demo.py
├── tests/
├── pyproject.toml
├── .env.example
└── README.md
```
---
## 快速开始
### 1. 安装
需要 Python ≥ 3.11,推荐 [uv](https://github.com/astral-sh/uv):
```bash
cd resume-repo-sync
uv sync --extra dev
```
#### WeasyPrint(Windows)
```bash
winget install --id tschoonj.GTKForWindows --accept-package-agreements
# bin 通常在: C:\Program Files\GTK3-Runtime Win64\bin
# renderer 会自动探测;也可设 WEASYPRINT_GTK_BIN
```
中文字体栈(两套模板共用):`Microsoft YaHei` / `Noto Sans SC` / `SimSun`。
本机验证:PDF 嵌入 **Microsoft YaHei**,`pdfplumber` 可提取「张三」「清华大学」等中文。
### 2. 配置
```bash
cp .env.example .env
```
| 变量 | 说明 |
|------|------|
| `GITHUB_TOKEN` | 私有仓 / 提高 API 限额(可选) |
| `RESUME_TMP_DIR` | managed 临时 PDF 根目录 |
| `PDF_TMP_TTL_HOURS` | managed PDF 过期小时数(默认 24;`0`=每次清空) |
| `WEASYPRINT_GTK_BIN` | Windows GTK `bin` 覆盖 |
| `LOG_LEVEL` | 默认 `INFO`(仅操作状态,无简历正文/token) |
### 3. 测试 / 演示 / 启动
```bash
uv run pytest -q
# 端到端(内置样例简历 + 真实公开仓)
uv run python scripts/e2e_demo.py
# MCP stdio
uv run resume-repo-sync
# Inspector
uv run mcp dev src/resume_repo_sync/server.py
```
### 4. Claude Desktop(可选)
`%APPDATA%\Claude\claude_desktop_config.json`:
```json
{
"mcpServers": {
"resume-repo-sync": {
"command": "uv",
"args": [
"--directory",
"C:\\Users\\admin\\Desktop\\resume-repo-sync",
"run",
"resume-repo-sync"
],
"env": { "GITHUB_TOKEN": "ghp_xxxxxxxx" }
}
}
}
```
---
## PDF 模板对比
两套模板都是 **Jinja2 + 中文字体栈**,通过 `render_pdf(..., template="...")` 切换;
未知名称返回 **`template_not_found`**(列出可用模板),**不会**静默回退默认模板。
| 维度 | `modern`(默认) | `compact` |
|------|------------------|-----------|
| 布局 | **单栏**纵向流式 | **双栏**:左轨(联系/技能/教育)+ 右主栏(工作/项目) |
| 页边距 | 约 1.6–1.8cm,留白较多 | 约 1.0–1.1cm,信息密度更高 |
| 字号层级 | 姓名 ~20pt,章节 ~12pt,正文 ~10.5pt | 姓名 ~16pt,章节小写标题 ~8.5pt,正文 ~8.5–9pt |
| 章节样式 | 粗下划线 `h2` 分区 | 大写细线 `block-title`,条目更紧 |
| 项目展示 | 每条 bullet 间距宽松,技术栈单独一行 | `ul.dense` 紧凑列表,技术栈斜体一行塞入 |
| 适合场景 | 一页内项目不多、希望阅读舒适 | 内容多、希望压到一页 / ATS 友好密集版 |
调用示例:
```text
render_pdf(resume_data, template="modern")
render_pdf(resume_data, template="compact")
render_pdf(resume_data, template="nope") → error_code=template_not_found
```
在 `templates/` 下新增 `*.html` 即可扩展;`list_templates()` / 错误详情会自动发现。
---
## 端到端示例
```text
1) parse_resume(pdf_path)
2) fetch_repo_info(repo_url, github_username?)
3) analyze_repo_for_resume(repo_info, github_username?, target_role?)
→ contribution_level + absolute_contribution_signal
4) generate_bullet_points(analysis)
→ 若 skip_project_entry=true:不要 merge,按 recommendation 处理
5) merge_into_resume(...)
6) diff_resume_versions(old, new) # 人工确认
7) render_pdf(..., template="compact") # 或 modern
```
实测(样例简历 + `modelcontextprotocol/python-sdk`)会在 diff 中看到类似:
```text
+ 新增项目 [0] python-sdk
• 参与… / 使用 Python… / 项目获得 N GitHub Stars
```
---
## 贡献分级(简历诚信)
**占比** → `contribution_level`:
| level | 条件 |
|-------|------|
| `primary` | ≥ 50% listed contributions |
| `contributor` | 20% – <50% |
| `minor` | < 20% |
| `unknown` | 无数据 |
**绝对次数** → `absolute_contribution_signal`(用于细分 **minor** 措辞):
| signal | 绝对 listed contributions | minor 时 bullet |
|--------|---------------------------|-----------------|
| `negligible` | < 3 | **不生成**完整项目条;`skip_project_entry` + recommendation |
| `moderate` | 3–19 | 「参与贡献 / 协助」 |
| `substantial` | ≥ 20 | 写明具体提交次数;仍禁止「主导/独立完成」 |
| `unknown` | 无数据 | 中性措辞 |
大仓里「占比低但提交多」与「几乎没贡献」不再混为一谈。
---
## 隐私与数据保留
| 数据 | 落盘? | 保留 |
|------|--------|------|
| 用户简历 PDF | 只读,不复制 | 用户自管 |
| 解析 workdir | 系统 temp 私有目录 | **返回前必删**(含异常 `finally`) |
| `ResumeData` / `RepoInfo` | 仅内存 / MCP 响应 | 进程结束即消失 |
| `render_pdf` 默认输出 | `{system_temp}/resume-repo-sync/resume-*.pdf` | TTL(默认 24h)自动删 |
| 显式 `output_path` | 调用方路径 | **永不自动删** |
| `GITHUB_TOKEN` | 环境变量 / `.env` | 不写日志 |
日志只记 ok/fail、basename、计数——**不**记简历正文、bullet、token。
`.gitignore` 已屏蔽 `.env`、`*.pdf`、`uploads/`、`output/`、`tmp/` 等。
---
## 设计决策(展示 / 面试)
1. **自包含 GitHub REST,而不是嵌一层 GitHub MCP**
错误语义可控(私有仓 / 限流 / 404 → 稳定 `ToolErrorCode`),部署少一个进程。
2. **业务不写在 `@mcp.tool` 里**
普通函数可 `pytest`;MCP 层只做 IO envelope。
3. **解析用启发式,不调 LLM**
行为可预测、零额外 API 成本;极端版式用 `parse_warnings` / `raw_text` 交给上层模型兜底。
4. **WeasyPrint + Jinja2**
排版与逻辑分离;加模板 = 加 HTML 文件,无需改 Python 分支。
5. **贡献分级驱动措辞**
避免把「大仓里提过几个 PR」写成「独立主导」,保证可验证与诚信。
---
## MCP Inspector 速查
```bash
uv run mcp dev src/resume_repo_sync/server.py
```
- **模板切换**:`render_pdf` 的 `template` 分别试 `modern`、`compact`;再试 `does-not-exist` → `template_not_found`。
- **negligible**:公开大仓 + 不存在的 `github_username` → `skip_project_entry=true`。
- **冲突**:`merge_into_resume` 同名且 `overwrite=false` → `project_already_exists`。
---
## License
MIT
TDQS
Scored across 7 tools
Each tool has a clearly distinct purpose and phase in the pipeline: parse extracts from PDF, fetch/analyze/generate handle repo→bullet transformation, merge inserts into resume, render outputs PDF, diff compares versions. The workflow is linear and each step is unambiguous.
All tools follow a clear verb_noun snake_case pattern (parse_resume, fetch_repo_info, analyze_repo_for_resume, generate_bullet_points, merge_into_resume, render_pdf, diff_resume_versions). The only minor deviation is that some verbs are single words (parse, fetch, render) while others are multi-word or conceptual (analyze_repo_for_resume, generate_bullet_points), but the pattern is otherwise highly consistent.
Seven tools is well-scoped for a resume-to-repo-sync pipeline. Each tool represents a distinct and necessary stage of the workflow, and none feel redundant or padding. The count is comfortably within the ideal range.
The pipeline covers the full lifecycle: parse input (parse_resume), external data ingestion (fetch_repo_info), analysis (analyze_repo_for_resume), content generation (generate_bullet_points), merging (merge_into_resume), verification (diff_resume_versions), and output (render_pdf). Minor gaps include no way to edit other resume sections (education/work) or remove projects, but the core sync workflow is complete.