|
|
@@ -0,0 +1,706 @@
|
|
|
+---
|
|
|
+title: 'Novel Translator MCP Server'
|
|
|
+slug: 'novel-translator-mcp'
|
|
|
+created: '2026-03-15'
|
|
|
+status: 'ready-for-development'
|
|
|
+stepsCompleted: ['understand', 'investigate', 'generate', 'review']
|
|
|
+tech_stack: ['python>=3.10', 'fastmcp>=0.1', 'pydantic>=2.0', 'uvicorn>=0.30', 'mcp>=1.0']
|
|
|
+files_to_modify:
|
|
|
+ - 'src/mcp_server/__init__.py'
|
|
|
+ - 'src/mcp_server/server.py'
|
|
|
+ - 'src/mcp_server/tools/*.py'
|
|
|
+ - 'requirements.txt'
|
|
|
+ - 'setup.py'
|
|
|
+code_patterns: []
|
|
|
+test_patterns: ['pytest', 'mcp client testing']
|
|
|
+---
|
|
|
+
|
|
|
+# Tech-Spec: Novel Translator MCP Server
|
|
|
+
|
|
|
+**Created:** 2026-03-15
|
|
|
+**Status:** Ready for Development
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Overview
|
|
|
+
|
|
|
+### Problem Statement
|
|
|
+
|
|
|
+当前序灵 Matrix 翻译助手缺乏标准的 API 接口,导致以下问题:
|
|
|
+
|
|
|
+1. **无法直接集成到 AI 工作流**:Claude、ChatGPT 等 AI 助手无法直接调用翻译功能
|
|
|
+2. **缺乏标准化接口**:各模块分散,需要单独导入和配置
|
|
|
+3. **难以远程调用**:所有功能限制在本地 Python 进程内
|
|
|
+4. **缺乏进度反馈机制**:长时间翻译任务无法实时报告进度
|
|
|
+
|
|
|
+### Solution
|
|
|
+
|
|
|
+使用 **FastMCP** 框架创建一个 **MCP (Model Context Protocol)** 服务,将现有模块封装为标准 MCP 工具:
|
|
|
+
|
|
|
+```
|
|
|
+Claude AI Client <--MCP--> Novel Translator MCP Server <--内部调用--> 现有模块
|
|
|
+```
|
|
|
+
|
|
|
+**核心设计决策**:
|
|
|
+- 使用 FastMCP 框架(轻量级、类型安全)
|
|
|
+- HTTP 传输模式(支持远程调用)
|
|
|
+- Pydantic Schema 验证(确保输入输出安全)
|
|
|
+- 进度报告回调(长时间任务实时反馈)
|
|
|
+
|
|
|
+### Scope
|
|
|
+
|
|
|
+**In Scope:**
|
|
|
+1. MCP 服务框架搭建(FastMCP + HTTP)
|
|
|
+2. 10 个核心 MCP 工具封装
|
|
|
+ - `translate_text` - 单文本翻译
|
|
|
+ - `translate_batch` - 批量文本翻译
|
|
|
+ - `translate_file` - 文件翻译(完整流水线)
|
|
|
+ - `clean_file` - 文件清洗
|
|
|
+ - `split_chapters` - 章节分割
|
|
|
+ - `glossary_add` - 添加术语
|
|
|
+ - `glossary_list` - 列出术语表
|
|
|
+ - `glossary_clear` - 清空术语表
|
|
|
+ - `check_duplicate` - 文件指纹查重
|
|
|
+ - `get_fingerprint` - 获取文件指纹
|
|
|
+3. Pydantic 输入/输出 Schema 定义
|
|
|
+4. 进度报告机制(via MCP resources)
|
|
|
+5. 错误处理和日志记录
|
|
|
+
|
|
|
+**Out of Scope:**
|
|
|
+- 修改现有核心模块代码
|
|
|
+- 用户认证/授权
|
|
|
+- 数据库迁移
|
|
|
+- UI 重构
|
|
|
+- WebSocket 实时推送(使用 MCP resources 轮询)
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Context for Development
|
|
|
+
|
|
|
+### Codebase Patterns
|
|
|
+
|
|
|
+**项目结构**:
|
|
|
+```
|
|
|
+src/
|
|
|
+├── translator/ # 翻译引擎
|
|
|
+│ ├── engine.py # TranslationEngine (单文本、批量翻译)
|
|
|
+│ └── pipeline.py # TranslationPipeline (术语+翻译+后处理)
|
|
|
+├── cleaning/ # 文件清洗
|
|
|
+│ └── pipeline.py # CleaningPipeline (读取、清洗、分割)
|
|
|
+├── glossary/ # 术语管理
|
|
|
+│ ├── models.py # Glossary, GlossaryEntry
|
|
|
+│ └── pipeline.py # GlossaryPipeline
|
|
|
+├── fingerprint/ # 文件指纹
|
|
|
+│ └── service.py # FingerprintService
|
|
|
+└── uploader/ # 上传管理(暂不封装)
|
|
|
+```
|
|
|
+
|
|
|
+**关键模式**:
|
|
|
+- **Pipeline 模式**:`CleaningPipeline.process()`, `TranslationPipeline.translate()`
|
|
|
+- **Fluent API**:`Glossary.add()`, `TranslationEngine.translate()`
|
|
|
+- **Path 类型**:所有文件路径接受 `Path | str`
|
|
|
+- **异常处理**:自定义异常类(`*Error`)
|
|
|
+
|
|
|
+### Files to Reference
|
|
|
+
|
|
|
+| File | Purpose |
|
|
|
+| ---- | ------- |
|
|
|
+| `src/translator/engine.py` | 翻译引擎,`translate()`, `translate_batch()` |
|
|
|
+| `src/translator/pipeline.py` | 完整翻译流水线,`translate()`, `translate_batch()` |
|
|
|
+| `src/cleaning/pipeline.py` | 清洗流水线,`process()`, `process_to_result()` |
|
|
|
+| `src/glossary/models.py` | 术语数据模型,`Glossary`, `GlossaryEntry` |
|
|
|
+| `src/glossary/pipeline.py` | 术语处理流水线 |
|
|
|
+| `src/fingerprint/service.py` | 指纹服务,`check_before_import()`, `get_fingerprint()` |
|
|
|
+| `requirements.txt` | 添加 FastMCP 依赖 |
|
|
|
+
|
|
|
+### Technical Decisions
|
|
|
+
|
|
|
+| Decision | Rationale |
|
|
|
+| -------- | --------- |
|
|
|
+| **FastMCP 框架** | 轻量级、类型安全、与 Pydantic 原生集成 |
|
|
|
+| **HTTP 传输模式** | 支持远程调用,调试方便 |
|
|
|
+| **单例 Glossary** | 术语表在服务生命周期内共享 |
|
|
|
+| **异步支持** | 使用 `async def` 处理长时间任务 |
|
|
|
+| **进度资源** | 使用 MCP Resources 暴露进度状态 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Implementation Plan
|
|
|
+
|
|
|
+### Tasks
|
|
|
+
|
|
|
+#### Task 1: 项目结构和依赖配置
|
|
|
+
|
|
|
+**文件**: `requirements.txt`, `setup.py`
|
|
|
+
|
|
|
+**操作**:
|
|
|
+1. 在 `requirements.txt` 添加 MCP 依赖:
|
|
|
+ ```
|
|
|
+ fastmcp>=0.1.0
|
|
|
+ mcp>=1.0.0
|
|
|
+ pydantic>=2.0
|
|
|
+ uvicorn>=0.30.0
|
|
|
+ ```
|
|
|
+
|
|
|
+2. 在 `setup.py` 添加 `mcp_server` 包入口点:
|
|
|
+ ```python
|
|
|
+ entry_points={
|
|
|
+ "console_scripts": [
|
|
|
+ "novel-translator-mcp=src.mcp_server:main",
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ ```
|
|
|
+
|
|
|
+#### Task 2: MCP 服务框架搭建
|
|
|
+
|
|
|
+**文件**: `src/mcp_server/__init__.py`, `src/mcp_server/server.py`
|
|
|
+
|
|
|
+**操作**:
|
|
|
+1. 创建 `src/mcp_server/` 目录
|
|
|
+2. 实现 `server.py` 主服务类:
|
|
|
+ ```python
|
|
|
+ from fastmcp import FastMCP
|
|
|
+ from src.translator.engine import TranslationEngine
|
|
|
+ from src.translator.pipeline import TranslationPipeline
|
|
|
+ from src.glossary.models import Glossary
|
|
|
+ from src.cleaning.pipeline import CleaningPipeline
|
|
|
+ from src.fingerprint.service import FingerprintService
|
|
|
+ from src.repository import Repository
|
|
|
+
|
|
|
+ mcp = FastMCP("novel-translator")
|
|
|
+
|
|
|
+ # 全局状态
|
|
|
+ _engine: TranslationEngine | None = None
|
|
|
+ _pipeline: TranslationPipeline | None = None
|
|
|
+ _glossary = Glossary()
|
|
|
+ _cleaning_pipeline = CleaningPipeline()
|
|
|
+ _repository = Repository()
|
|
|
+ _fingerprint_service = FingerprintService(_repository)
|
|
|
+ _progress_state = {}
|
|
|
+ ```
|
|
|
+
|
|
|
+3. 实现 `main()` 启动函数:
|
|
|
+ ```python
|
|
|
+ def main():
|
|
|
+ import mcp.server.cli
|
|
|
+ mcp.server.cli.run()
|
|
|
+ ```
|
|
|
+
|
|
|
+#### Task 3: 工具 1-2 - 翻译工具
|
|
|
+
|
|
|
+**文件**: `src/mcp_server/tools/translation.py`
|
|
|
+
|
|
|
+**Schema 定义**:
|
|
|
+```python
|
|
|
+from pydantic import BaseModel, Field
|
|
|
+from typing import Optional, List
|
|
|
+
|
|
|
+class TranslateTextInput(BaseModel):
|
|
|
+ text: str = Field(description="要翻译的文本")
|
|
|
+ src_lang: str = Field(default="zh", description="源语言代码 (zh, en, etc.)")
|
|
|
+ tgt_lang: str = Field(default="en", description="目标语言代码")
|
|
|
+ max_length: Optional[int] = Field(default=200, description="最大生成长度")
|
|
|
+
|
|
|
+class TranslateTextInput(BaseModel):
|
|
|
+ texts: List[str] = Field(description="要翻译的文本列表")
|
|
|
+ src_lang: str = Field(default="zh", description="源语言代码")
|
|
|
+ tgt_lang: str = Field(default="en", description="目标语言代码")
|
|
|
+ batch_size: int = Field(default=4, description="批处理大小")
|
|
|
+ max_length: Optional[int] = Field(default=200, description="最大生成长度")
|
|
|
+
|
|
|
+class TranslationOutput(BaseModel):
|
|
|
+ success: bool
|
|
|
+ translated: Optional[str] = None
|
|
|
+ translations: Optional[List[str]] = None
|
|
|
+ error: Optional[str] = None
|
|
|
+ terms_used: Optional[List[str]] = None
|
|
|
+```
|
|
|
+
|
|
|
+**工具实现**:
|
|
|
+```python
|
|
|
+@mcp.tool()
|
|
|
+async def translate_text(input: TranslateTextInput) -> TranslationOutput:
|
|
|
+ """翻译单段文本,支持术语表预处理和后处理"""
|
|
|
+ global _pipeline
|
|
|
+ if _pipeline is None:
|
|
|
+ _initialize_pipeline()
|
|
|
+ try:
|
|
|
+ result = _pipeline.translate(input.text, return_details=True)
|
|
|
+ return TranslationOutput(
|
|
|
+ success=True,
|
|
|
+ translated=result.translated,
|
|
|
+ terms_used=result.terms_used
|
|
|
+ )
|
|
|
+ except Exception as e:
|
|
|
+ return TranslationOutput(success=False, error=str(e))
|
|
|
+
|
|
|
+@mcp.tool()
|
|
|
+async def translate_batch(input: TranslateBatchInput) -> TranslationOutput:
|
|
|
+ """批量翻译多段文本"""
|
|
|
+ # 类似实现,使用 translate_batch
|
|
|
+```
|
|
|
+
|
|
|
+#### Task 4: 工具 3 - 文件翻译
|
|
|
+
|
|
|
+**文件**: `src/mcp_server/tools/translation.py`
|
|
|
+
|
|
|
+**Schema 定义**:
|
|
|
+```python
|
|
|
+class TranslateFileInput(BaseModel):
|
|
|
+ file_path: str = Field(description="要翻译的文件路径")
|
|
|
+ src_lang: str = Field(default="zh", description="源语言代码")
|
|
|
+ tgt_lang: str = Field(default="en", description="目标语言代码")
|
|
|
+ output_path: Optional[str] = Field(default=None, description="输出文件路径(默认添加 _en 后缀)")
|
|
|
+
|
|
|
+class TranslationProgress(BaseModel):
|
|
|
+ task_id: str
|
|
|
+ status: str # pending, processing, completed, failed
|
|
|
+ current_chapter: int
|
|
|
+ total_chapters: int
|
|
|
+ progress_percent: float
|
|
|
+```
|
|
|
+
|
|
|
+**工具实现**:
|
|
|
+```python
|
|
|
+@mcp.tool()
|
|
|
+async def translate_file(input: TranslateFileInput) -> TranslationOutput:
|
|
|
+ """翻译整个 TXT 文件(清洗 -> 分割 -> 翻译 -> 保存)"""
|
|
|
+ task_id = str(uuid.uuid4())
|
|
|
+ _progress_state[task_id] = {
|
|
|
+ "status": "pending",
|
|
|
+ "current": 0,
|
|
|
+ "total": 0,
|
|
|
+ "percent": 0.0
|
|
|
+ }
|
|
|
+
|
|
|
+ try:
|
|
|
+ # 1. 读取和清洗文件
|
|
|
+ chapters = _cleaning_pipeline.process(input.file_path)
|
|
|
+
|
|
|
+ # 2. 更新进度
|
|
|
+ _progress_state[task_id].update({
|
|
|
+ "status": "processing",
|
|
|
+ "total": len(chapters)
|
|
|
+ })
|
|
|
+
|
|
|
+ # 3. 翻译每一章
|
|
|
+ translated_chapters = []
|
|
|
+ for i, chapter in enumerate(chapters):
|
|
|
+ translated = _pipeline.translate(chapter.content)
|
|
|
+ translated_chapters.append({
|
|
|
+ "index": i,
|
|
|
+ "title": chapter.title,
|
|
|
+ "content": translated
|
|
|
+ })
|
|
|
+ # 更新进度
|
|
|
+ _progress_state[task_id].update({
|
|
|
+ "current": i + 1,
|
|
|
+ "percent": (i + 1) / len(chapters) * 100
|
|
|
+ })
|
|
|
+
|
|
|
+ # 4. 保存结果
|
|
|
+ output = input.output_path or _add_suffix(input.file_path, "_en")
|
|
|
+ _save_translated(output, translated_chapters)
|
|
|
+
|
|
|
+ _progress_state[task_id]["status"] = "completed"
|
|
|
+
|
|
|
+ return TranslationOutput(
|
|
|
+ success=True,
|
|
|
+ translated=output,
|
|
|
+ terms_used=[] # TODO: 收集所有使用的术语
|
|
|
+ )
|
|
|
+ except Exception as e:
|
|
|
+ _progress_state[task_id]["status"] = "failed"
|
|
|
+ return TranslationOutput(success=False, error=str(e))
|
|
|
+```
|
|
|
+
|
|
|
+#### Task 5: 工具 4-5 - 清洗和分割
|
|
|
+
|
|
|
+**文件**: `src/mcp_server/tools/cleaning.py`
|
|
|
+
|
|
|
+**Schema 定义**:
|
|
|
+```python
|
|
|
+class CleanFileInput(BaseModel):
|
|
|
+ file_path: str = Field(description="要清洗的文件路径")
|
|
|
+ output_path: Optional[str] = Field(default=None, description="输出路径")
|
|
|
+ enable_cleaning: bool = Field(default=True, description="是否启用清洗")
|
|
|
+ enable_splitting: bool = Field(default=True, description="是否启用章节分割")
|
|
|
+
|
|
|
+class SplitChaptersInput(BaseModel):
|
|
|
+ text: str = Field(description="要分割的文本")
|
|
|
+ min_chapter_length: int = Field(default=100, description="最小章节长度")
|
|
|
+
|
|
|
+class ChapterInfo(BaseModel):
|
|
|
+ index: int
|
|
|
+ title: str
|
|
|
+ char_count: int
|
|
|
+
|
|
|
+class CleaningOutput(BaseModel):
|
|
|
+ success: bool
|
|
|
+ chapters: Optional[List[ChapterInfo]] = None
|
|
|
+ cleaned_text: Optional[str] = None
|
|
|
+ error: Optional[str] = None
|
|
|
+```
|
|
|
+
|
|
|
+**工具实现**:
|
|
|
+```python
|
|
|
+@mcp.tool()
|
|
|
+async def clean_file(input: CleanFileInput) -> CleaningOutput:
|
|
|
+ """清洗 TXT 文件(去除无效字符、标准化格式、可选章节分割)"""
|
|
|
+ try:
|
|
|
+ pipeline = CleaningPipeline(
|
|
|
+ enable_cleaning=input.enable_cleaning,
|
|
|
+ enable_splitting=input.enable_splitting
|
|
|
+ )
|
|
|
+ chapters = pipeline.process(input.file_path)
|
|
|
+
|
|
|
+ return CleaningOutput(
|
|
|
+ success=True,
|
|
|
+ chapters=[
|
|
|
+ ChapterInfo(
|
|
|
+ index=c.index,
|
|
|
+ title=c.title,
|
|
|
+ char_count=c.char_count
|
|
|
+ )
|
|
|
+ for c in chapters
|
|
|
+ ]
|
|
|
+ )
|
|
|
+ except Exception as e:
|
|
|
+ return CleaningOutput(success=False, error=str(e))
|
|
|
+
|
|
|
+@mcp.tool()
|
|
|
+async def split_chapters(input: SplitChaptersInput) -> CleaningOutput:
|
|
|
+ """将文本分割为章节"""
|
|
|
+ from src.cleaning.splitter import ChapterSplitter
|
|
|
+ splitter = ChapterSplitter(
|
|
|
+ min_chapter_length=input.min_chapter_length
|
|
|
+ )
|
|
|
+ chapters = splitter.split(input.text)
|
|
|
+ # 返回章节列表...
|
|
|
+```
|
|
|
+
|
|
|
+#### Task 6: 工具 6-8 - 术语表管理
|
|
|
+
|
|
|
+**文件**: `src/mcp_server/tools/glossary.py`
|
|
|
+
|
|
|
+**Schema 定义**:
|
|
|
+```python
|
|
|
+class GlossaryAddInput(BaseModel):
|
|
|
+ source: str = Field(description="源语言术语")
|
|
|
+ target: str = Field(description="目标语言术语")
|
|
|
+ category: str = Field(default="other", description="术语类别 (character, skill, location, item, organization, other)")
|
|
|
+ context: str = Field(default="", description="上下文说明")
|
|
|
+
|
|
|
+class GlossaryEntryOutput(BaseModel):
|
|
|
+ source: str
|
|
|
+ target: str
|
|
|
+ category: str
|
|
|
+ context: str
|
|
|
+
|
|
|
+class GlossaryListOutput(BaseModel):
|
|
|
+ success: bool
|
|
|
+ entries: Optional[List[GlossaryEntryOutput]] = None
|
|
|
+ count: Optional[int] = None
|
|
|
+ error: Optional[str] = None
|
|
|
+```
|
|
|
+
|
|
|
+**工具实现**:
|
|
|
+```python
|
|
|
+@mcp.tool()
|
|
|
+async def glossary_add(input: GlossaryAddInput) -> Dict[str, Any]:
|
|
|
+ """添加术语到术语表"""
|
|
|
+ from src.glossary.models import GlossaryEntry, TermCategory
|
|
|
+
|
|
|
+ try:
|
|
|
+ category = TermCategory(input.category)
|
|
|
+ entry = GlossaryEntry(
|
|
|
+ source=input.source,
|
|
|
+ target=input.target,
|
|
|
+ category=category,
|
|
|
+ context=input.context
|
|
|
+ )
|
|
|
+ _glossary.add(entry)
|
|
|
+
|
|
|
+ # 更新流水线
|
|
|
+ if _pipeline:
|
|
|
+ _pipeline.update_glossary(_glossary)
|
|
|
+
|
|
|
+ return {"success": True, "message": f"Added: {input.source} -> {input.target}"}
|
|
|
+ except Exception as e:
|
|
|
+ return {"success": False, "error": str(e)}
|
|
|
+
|
|
|
+@mcp.tool()
|
|
|
+async def glossary_list() -> GlossaryListOutput:
|
|
|
+ """列出术语表所有条目"""
|
|
|
+ entries = [
|
|
|
+ GlossaryEntryOutput(
|
|
|
+ source=e.source,
|
|
|
+ target=e.target,
|
|
|
+ category=e.category.value,
|
|
|
+ context=e.context
|
|
|
+ )
|
|
|
+ for e in _glossary.get_all()
|
|
|
+ ]
|
|
|
+ return GlossaryListOutput(success=True, entries=entries, count=len(entries))
|
|
|
+
|
|
|
+@mcp.tool()
|
|
|
+async def glossary_clear() -> Dict[str, Any]:
|
|
|
+ """清空术语表"""
|
|
|
+ global _glossary
|
|
|
+ _glossary = Glossary()
|
|
|
+ if _pipeline:
|
|
|
+ _pipeline.update_glossary(_glossary)
|
|
|
+ return {"success": True, "message": "Glossary cleared"}
|
|
|
+```
|
|
|
+
|
|
|
+#### Task 7: 工具 9-10 - 指纹服务
|
|
|
+
|
|
|
+**文件**: `src/mcp_server/tools/fingerprint.py`
|
|
|
+
|
|
|
+**Schema 定义**:
|
|
|
+```python
|
|
|
+class CheckDuplicateInput(BaseModel):
|
|
|
+ file_path: str = Field(description="要检查的文件路径")
|
|
|
+
|
|
|
+class GetFingerprintInput(BaseModel):
|
|
|
+ file_path: str = Field(description="要获取指纹的文件路径")
|
|
|
+
|
|
|
+class DuplicateCheckOutput(BaseModel):
|
|
|
+ success: bool
|
|
|
+ is_duplicate: Optional[bool] = None
|
|
|
+ existing_work_id: Optional[str] = None
|
|
|
+ fingerprint: Optional[str] = None
|
|
|
+ error: Optional[str] = None
|
|
|
+```
|
|
|
+
|
|
|
+**工具实现**:
|
|
|
+```python
|
|
|
+@mcp.tool()
|
|
|
+async def check_duplicate(input: CheckDuplicateInput) -> DuplicateCheckOutput:
|
|
|
+ """检查文件是否已翻译(基于 MD5 指纹)"""
|
|
|
+ try:
|
|
|
+ is_dup, work_id = _fingerprint_service.check_before_import(input.file_path)
|
|
|
+ fingerprint = _fingerprint_service.get_fingerprint(input.file_path)
|
|
|
+
|
|
|
+ return DuplicateCheckOutput(
|
|
|
+ success=True,
|
|
|
+ is_duplicate=is_dup,
|
|
|
+ existing_work_id=work_id,
|
|
|
+ fingerprint=fingerprint
|
|
|
+ )
|
|
|
+ except Exception as e:
|
|
|
+ return DuplicateCheckOutput(success=False, error=str(e))
|
|
|
+
|
|
|
+@mcp.tool()
|
|
|
+async def get_fingerprint(input: GetFingerprintInput) -> Dict[str, Any]:
|
|
|
+ """获取文件的 MD5 指纹和元数据"""
|
|
|
+ try:
|
|
|
+ info = _fingerprint_service.get_file_info(input.file_path)
|
|
|
+ return {"success": True, **info}
|
|
|
+ except Exception as e:
|
|
|
+ return {"success": False, "error": str(e)}
|
|
|
+```
|
|
|
+
|
|
|
+#### Task 8: 进度资源
|
|
|
+
|
|
|
+**文件**: `src/mcp_server/resources.py`
|
|
|
+
|
|
|
+**实现**:
|
|
|
+```python
|
|
|
+@mcp.resource("progress://{task_id}")
|
|
|
+async def get_progress(task_id: str) -> str:
|
|
|
+ """获取翻译任务的进度状态"""
|
|
|
+ state = _progress_state.get(task_id)
|
|
|
+ if state:
|
|
|
+ return json.dumps(state, ensure_ascii=False)
|
|
|
+ return json.dumps({"error": "Task not found"})
|
|
|
+```
|
|
|
+
|
|
|
+#### Task 9: 初始化和辅助函数
|
|
|
+
|
|
|
+**文件**: `src/mcp_server/server.py`
|
|
|
+
|
|
|
+**实现**:
|
|
|
+```python
|
|
|
+def _initialize_pipeline():
|
|
|
+ """初始化翻译流水线"""
|
|
|
+ global _engine, _pipeline
|
|
|
+
|
|
|
+ model_path = os.getenv("M2M100_MODEL_PATH", "/mnt/code/223-236-template-6/models/m2m100")
|
|
|
+ _engine = TranslationEngine(model_path=model_path)
|
|
|
+ _pipeline = TranslationPipeline(
|
|
|
+ engine=_engine,
|
|
|
+ glossary=_glossary,
|
|
|
+ src_lang="zh",
|
|
|
+ tgt_lang="en"
|
|
|
+ )
|
|
|
+
|
|
|
+def _add_suffix(path: str, suffix: str) -> str:
|
|
|
+ """给文件路径添加后缀"""
|
|
|
+ p = Path(path)
|
|
|
+ return str(p.with_stem(p.stem + suffix))
|
|
|
+
|
|
|
+def _save_translated(path: str, chapters: List[Dict]):
|
|
|
+ """保存翻译结果"""
|
|
|
+ # 实现保存逻辑...
|
|
|
+```
|
|
|
+
|
|
|
+#### Task 10: 单元测试
|
|
|
+
|
|
|
+**文件**: `tests/test_mcp_server.py`
|
|
|
+
|
|
|
+**测试覆盖**:
|
|
|
+```python
|
|
|
+import pytest
|
|
|
+from src.mcp_server.server import mcp
|
|
|
+from src.mcp_server.tools.translation import translate_text
|
|
|
+from src.mcp_server.tools.glossary import glossary_add, glossary_list
|
|
|
+
|
|
|
+@pytest.mark.asyncio
|
|
|
+async def test_translate_text():
|
|
|
+ # 测试翻译功能
|
|
|
+ result = await translate_text(
|
|
|
+ TranslateTextInput(text="你好世界", src_lang="zh", tgt_lang="en")
|
|
|
+ )
|
|
|
+ assert result.success
|
|
|
+ assert result.translated
|
|
|
+
|
|
|
+@pytest.mark.asyncio
|
|
|
+async def test_glossary_add():
|
|
|
+ # 测试添加术语
|
|
|
+ result = await glossary_add(
|
|
|
+ GlossaryAddInput(source="林风", target="Lin Feng", category="character")
|
|
|
+ )
|
|
|
+ assert result["success"]
|
|
|
+
|
|
|
+ # 验证术语已添加
|
|
|
+ list_result = await glossary_list()
|
|
|
+ assert "林风" in [e.source for e in list_result.entries]
|
|
|
+```
|
|
|
+
|
|
|
+### Acceptance Criteria
|
|
|
+
|
|
|
+#### AC1: MCP 服务可以启动
|
|
|
+
|
|
|
+**Given** Python 环境已安装所有依赖
|
|
|
+**When** 执行 `python -m src.mcp_server`
|
|
|
+**Then** 服务成功启动,日志显示 "MCP server running on stdio"
|
|
|
+
|
|
|
+#### AC2: 翻译工具正常工作
|
|
|
+
|
|
|
+**Given** MCP 服务已启动
|
|
|
+**When** 调用 `translate_text(text="你好世界", src_lang="zh", tgt_lang="en")`
|
|
|
+**Then** 返回 `{"success": true, "translated": "Hello world", ...}`
|
|
|
+
|
|
|
+#### AC3: 术语表功能正常
|
|
|
+
|
|
|
+**Given** MCP 服务已启动
|
|
|
+**When** 执行以下操作:
|
|
|
+1. `glossary_add(source="林风", target="Lin Feng", category="character")`
|
|
|
+2. `translate_text(text="林风是主角")`
|
|
|
+**Then** 翻译结果中包含 "Lin Feng" 而非 "Lin wind"
|
|
|
+
|
|
|
+#### AC4: 文件翻译完整流程
|
|
|
+
|
|
|
+**Given** 有一个 TXT 文件 `novel.txt`
|
|
|
+**When** 调用 `translate_file(file_path="novel.txt")`
|
|
|
+**Then**:
|
|
|
+1. 返回成功
|
|
|
+2. 生成 `novel_en.txt` 文件
|
|
|
+3. 内容为翻译后的英文
|
|
|
+4. 进度资源可查询
|
|
|
+
|
|
|
+#### AC5: 指纹查重正常
|
|
|
+
|
|
|
+**Given** 已翻译过 `novel.txt`
|
|
|
+**When** 调用 `check_duplicate(file_path="novel.txt")`
|
|
|
+**Then** 返回 `{"is_duplicate": true, "existing_work_id": "..."}`
|
|
|
+
|
|
|
+#### AC6: 错误处理正确
|
|
|
+
|
|
|
+**Given** MCP 服务已启动
|
|
|
+**When** 调用 `translate_text(text="")`
|
|
|
+**Then** 返回 `{"success": false, "error": "Text cannot be empty"}`
|
|
|
+
|
|
|
+#### AC7: 单元测试覆盖 >= 80%
|
|
|
+
|
|
|
+**Given** 所有代码已实现
|
|
|
+**When** 运行 `pytest --cov=src/mcp_server`
|
|
|
+**Then** 代码覆盖率 >= 80%
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Additional Context
|
|
|
+
|
|
|
+### Dependencies
|
|
|
+
|
|
|
+**新增依赖**:
|
|
|
+```txt
|
|
|
+# MCP Server
|
|
|
+fastmcp>=0.1.0
|
|
|
+mcp>=1.0.0
|
|
|
+pydantic>=2.0
|
|
|
+uvicorn>=0.30.0
|
|
|
+
|
|
|
+# 测试
|
|
|
+pytest-asyncio>=0.23.0
|
|
|
+```
|
|
|
+
|
|
|
+**现有依赖(复用)**:
|
|
|
+- `torch==2.5.1` - 翻译引擎
|
|
|
+- `transformers==4.49.0` - m2m100 模型
|
|
|
+- `transitions==0.9.0` - 状态机
|
|
|
+- `PyQt6==6.8.0` - UI(MCP 服务不需要)
|
|
|
+
|
|
|
+### Testing Strategy
|
|
|
+
|
|
|
+1. **单元测试**: 每个工具独立测试
|
|
|
+2. **集成测试**: 测试完整流程(术语 -> 翻译 -> 验证)
|
|
|
+3. **MCP 客户端测试**: 使用 MCP Inspector 验证工具调用
|
|
|
+4. **性能测试**: 批量翻译性能验证
|
|
|
+
|
|
|
+### Notes
|
|
|
+
|
|
|
+1. **模型路径**: 默认使用 `/mnt/code/223-236-template-6/models/m2m100`,可通过环境变量 `M2M100_MODEL_PATH` 配置
|
|
|
+2. **术语表生命周期**: 术语表在服务重启后清空(可后续添加持久化)
|
|
|
+3. **进度报告**: 使用 MCP Resources 而非实时推送(简化实现)
|
|
|
+4. **异步支持**: 所有工具使用 `async def`,支持并发调用
|
|
|
+5. **错误处理**: 所有异常捕获后返回 `{"success": false, "error": "..."}`
|
|
|
+
|
|
|
+### MCP 客户端配置示例
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "mcpServers": {
|
|
|
+ "novel-translator": {
|
|
|
+ "command": "python",
|
|
|
+ "args": ["-m", "src.mcp_server"],
|
|
|
+ "env": {
|
|
|
+ "M2M100_MODEL_PATH": "/path/to/models/m2m100"
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 附录:完整工具列表
|
|
|
+
|
|
|
+| 工具名 | 功能描述 | 输入 | 输出 |
|
|
|
+|--------|----------|------|------|
|
|
|
+| `translate_text` | 单文本翻译 | text, src_lang, tgt_lang | translated, terms_used |
|
|
|
+| `translate_batch` | 批量文本翻译 | texts, src_lang, tgt_lang | translations |
|
|
|
+| `translate_file` | 文件翻译 | file_path, output_path | output_path |
|
|
|
+| `clean_file` | 文件清洗 | file_path, enable_cleaning | chapters |
|
|
|
+| `split_chapters` | 章节分割 | text, min_chapter_length | chapters |
|
|
|
+| `glossary_add` | 添加术语 | source, target, category | success |
|
|
|
+| `glossary_list` | 列出术语 | - | entries, count |
|
|
|
+| `glossary_clear` | 清空术语 | - | success |
|
|
|
+| `check_duplicate` | 检查重复 | file_path | is_duplicate, work_id |
|
|
|
+| `get_fingerprint` | 获取指纹 | file_path | fingerprint, metadata |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+**文档状态**: Ready for Development
|
|
|
+**下一步**: 执行 `bmad-quick-dev` 实现此规范
|