Skip to main content
Glama
README.md
# 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

A4.2/5.0

Scored across 7 tools

Disambiguation5/5

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.

Naming Consistency4/5

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.

Tool Count5/5

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.

Completeness4/5

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.

Maintenance

ActivityStale
ResponsivenessNo issues