#!/usr/bin/env python3
|
"""Standalone translation MCP. Only exposes translate. Does not call ConnectionHandler.chat."""
|
|
from __future__ import annotations
|
|
import os
|
from pathlib import Path
|
|
import httpx
|
import yaml
|
from mcp.server.fastmcp import FastMCP
|
|
ROOT = Path(__file__).resolve().parents[2]
|
TRANSLATOR_PROMPT = (
|
"你是一台翻译机。只输出译文,不要解释、不要拼音课、不要追问、不要闲聊。"
|
"未指定语言时:中文译成英文,英文译成中文;其它语言默认译成中文。"
|
)
|
|
mcp = FastMCP("english-translate")
|
|
|
def _read_yaml(path: Path) -> dict:
|
if not path.is_file():
|
return {}
|
with path.open("r", encoding="utf-8") as f:
|
data = yaml.safe_load(f) or {}
|
return data if isinstance(data, dict) else {}
|
|
|
def _load_llm_config() -> dict:
|
default_cfg = _read_yaml(ROOT / "config.yaml")
|
custom_cfg = _read_yaml(ROOT / "data" / ".config.yaml")
|
|
def merge(base, overlay):
|
out = dict(base)
|
for k, v in overlay.items():
|
if isinstance(v, dict) and isinstance(out.get(k), dict):
|
out[k] = merge(out[k], v)
|
else:
|
out[k] = v
|
return out
|
|
cfg = merge(default_cfg, custom_cfg)
|
selected = (cfg.get("selected_module") or {}).get("LLM")
|
llm_map = cfg.get("LLM") or {}
|
if selected and selected in llm_map:
|
item = dict(llm_map[selected])
|
item["_name"] = selected
|
return item
|
if llm_map:
|
name, item = next(iter(llm_map.items()))
|
data = dict(item)
|
data["_name"] = name
|
return data
|
return {}
|
|
|
def _chat_complete(text: str, source_lang: str, target_lang: str) -> str:
|
llm = _load_llm_config()
|
api_key = llm.get("api_key") or os.environ.get("OPENAI_API_KEY") or ""
|
if api_key and ("你的" in str(api_key) or "key" == str(api_key).lower()):
|
api_key = os.environ.get("OPENAI_API_KEY") or api_key
|
base_url = llm.get("base_url") or llm.get("url") or "https://api.openai.com/v1"
|
model = llm.get("model_name") or llm.get("model") or "gpt-4o-mini"
|
if not api_key or "你的" in str(api_key):
|
raise RuntimeError("翻译 MCP 未配置可用的 LLM api_key")
|
|
extra = ""
|
if source_lang or target_lang:
|
extra = f"\n源语言:{source_lang or 'auto'} 目标语言:{target_lang or 'auto'}"
|
user = f"翻译下面内容,只输出译文:{extra}\n{text}"
|
|
url = str(base_url).rstrip("/")
|
if not url.endswith("/chat/completions"):
|
if url.endswith("/v1"):
|
url = url + "/chat/completions"
|
else:
|
url = url + "/v1/chat/completions"
|
|
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
payload = {
|
"model": model,
|
"temperature": 0.1,
|
"messages": [
|
{"role": "system", "content": TRANSLATOR_PROMPT},
|
{"role": "user", "content": user},
|
],
|
}
|
with httpx.Client(timeout=60.0) as client:
|
resp = client.post(url, headers=headers, json=payload)
|
resp.raise_for_status()
|
data = resp.json()
|
choices = data.get("choices") or []
|
if not choices:
|
raise RuntimeError("翻译接口没有返回内容")
|
msg = (choices[0].get("message") or {}).get("content") or ""
|
return str(msg).strip()
|
|
|
@mcp.tool()
|
def translate(text: str, source_lang: str = "", target_lang: str = "") -> str:
|
"""Translate text. Chinese->English, English->Chinese unless languages are set. Return translation only."""
|
if not text or not str(text).strip():
|
return ""
|
return _chat_complete(str(text).strip(), source_lang or "", target_lang or "")
|
|
|
if __name__ == "__main__":
|
os.chdir(ROOT)
|
mcp.run(transport="stdio")
|