Skip to main content
Glama
sskacc

cangjie-knowledge-mcp

by sskacc

cangjie-knowledge-mcp

Cangjie 知识库 + MCP 服务器,为 Java → Cangjie 片段翻译提供 API 检索能力。

在翻译一个 Java 片段之前,先通过 MCP 工具做检索,找到片段中用到的类/方法/类型在 Cangjie 标准库中的来源(哪个库、哪个模块)完整签名官方示例代码;翻译失败 进入错误修复循环时,也可以调用本工具(error_fix_hint)根据编译错误定位相关 API 和示例。

它能回答什么问题

场景

用法

HashMap.put(key, value) 在 Cangjie 里怎么调?

search_api("map put key value") → 找到 HashMapadd(K,V)/replace(K,V)(Cangjie 没有 put)

ArrayList 有哪些方法?

get_class_members("ArrayList") → 成员及签名

读文件怎么写?

find_examples("read file bytes") → 官方 sample 代码

java.util.List 对应 Cangjie 什么?

java_to_cangjie("java.util.List") → j2cjlib 映射

cannot find symbol println 怎么修?

error_fix_hint("...") → 相关 API 文档 + 示例

一段 Java 代码怎么翻译?

resolve_java_code("<java代码>") → per-call 建议(见下文)

Related MCP server: mcp-server-moegirl-wiki

核心特性:per-call 渐进式披露检索

对代码块中的每个 API 调用独立检索,每个调用产出一个 suggest——包括构造调用 (new Xxx(...),每个构造也单独建议,含 new HashMap<>() 菱形泛型)。细粒度(api) 没有一一对应时,自动上升到语句级(statement),再不行才为整个代码块生成块级 suggest。在此之上叠加两阶段检索(类型锁定 → 方法匹配)+ 语义 rerank, 让结果从"候选列表"变成"一组可直接使用的建议"。

三层粒度

Level 1  api       最细粒度: 单个 API 调用      "map.put(k, v)"
                   → 锁 receiver 类型(HashMap) → 该类中与调用意图最匹配的成员
                   → 产出该调用的 suggest

Level 2  statement 中间粒度: 整行语句          "map.put(k, v);"
                   → NL 检索,对应 Cangjie 的一个或几个 API
                   → 用于 receiver 类型不可锁定时的降级

Level 3  function  最粗粒度: 整个代码块         "public void copyFile(...) {...}"
                   → NL "copy a file",检索整个功能模块
                   → 只生成一个块级 suggest(兜底)

per-call 工作流

resolve_java_code 的完整流程:

输入: BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
      String line = reader.readLine();
│
├─ extract_types() 拆出每个调用(含构造调用 `new Xxx(...)` 与 `new HashMap<>()`)
│    calls = [reader.readLine, new BufferedReader, new InputStreamReader, ...]
│
├─ 对每个 call 独立解析:
│    ┌─ L1 api: 锁 receiver 类型(查 x2cangjie 映射表 / BM25 类名兜底)
│    │    → 该类成员按该调用的 NL 意图重排(含 rerank)→ 返回 top-k 成员
│    │    (map.put → HashMap 的 add/contains/...;reader.readLine → StringReader 的 readln/...
│    │     new BufferedReader → StringReader 的 init(T)/lines/... ← 构造调用单独建议)
│    ├─ L2 statement: receiver 类型无法锁定(如 System.out.println)时,
│    │    用整行语句 NL 检索 → 返回候选 API 列表
│    └─ L3 function: 整个块仍无法解析时,生成一个块级 suggest(兜底)
│
└─ 输出 suggestions[]: 每个调用一个建议

关键点:

  1. 每个 API 调用都有自己的 suggest——map.putreader.readLine 是不同类型 的不同意图,各自锁各自的类、返回各自最匹配的成员。构造调用也单独建议

  2. api 级只返回 top-k 成员(默认 5)而非整个类;粗粒度(statement/function)才返回完整列表。

  3. 升降级规则:receiver 类型可锁定(含构造调用的类名)→ api;不可锁定 → statement; 整块都无法解析 → function。

  4. 类型锁定:查表(精确)+ BM25 类名兜底(相似度),泛型会被剥掉(HashMap<String,Integer>HashMap)。

语义 rerank(可选增强)

在 BM25 召回之后,用一个可选的 LLM rerank 层对 top-k 候选做语义重排,纠正 BM25 在"词形不同但语义相同"场景下的盲区(如 Java 的 readLine → Cangjie 的 readln,readln 词法上拆不出 "read"+"line",BM25 会打 0 分,但 rerank 能理解 "读下一行"就是 readln)。思路来自 LongCodeZip 论文(条件困惑度排序比词法相似度 高 7.89%),实现上用 LLM 直接输出候选排序(避免依赖 token 级 log-prob)。

  • recall-rerank 两段式:BM25 先召回 top_k×3(粗筛),LLM 只精排几十个候选。

  • 零依赖回退:无 api_keyrerank=false 时,顺序与纯 BM25 完全一致。

  • 容错:LLM 超时/报错/输出不可解析,一律回退 BM25 顺序,检索永不因 LLM 失败而退化。

架构

cangjie-knowledge-mcp/
├── config.yaml                  # 语料路径、索引参数、LLM 配置(api_key 走环境变量)
├── Dockerfile                   # MCP server 容器化镜像
├── opencode.json                # opencode MCP 注册配置
├── src/cjkb/
│   ├── models.py                # ApiRecord / ExampleRecord / JavaMapping 数据模型
│   ├── config.py                # 配置加载(支持环境变量覆盖)
│   ├── java_types.py            # Java 类型提取器(声明/泛型/调用接收者/构造调用/强转)
│   ├── nl_generator.py          # Java 代码 → 中英双语 NL 描述(LLM 或启发式)
│   ├── layered_search.py        # per-call 解析:类型锁定 + 分层 NL + 升降级 → suggestions[]
│   ├── reranker.py              # (可选)LLM 语义 rerank 层
│   ├── collector/
│   │   ├── corpus_parser.py     # 解析 CangjieCorpus 官方文档 → API/示例记录
│   │   ├── j2cj_parser.py       # 解析 j2cjlib shim + 术语表 → Java→Cangjie 映射
│   │   └── example_writer.py    # (可选)LLM 为缺少示例的 API 生成示例
│   ├── index/
│   │   ├── bm25.py              # 纯标准库 BM25(字段加权, 驼峰/下划线/中文 unigram 分词)
│   │   └── searcher.py          # 检索 API(相似度 + 精确名 + Java 术语扩展)
│   └── mcp_server.py            # MCP stdio 服务器(零第三方依赖)
├── scripts/
│   ├── build_kb.py              # 收集 + 建索引 → data/
│   ├── import_type_mappings.py  # 导入 x2cangjie 类型翻译产物
│   ├── install_kb.py            # 一键就绪:校验数据 + 自动重建索引
│   ├── generate_examples.py     # (可选)LLM 补写缺失示例
│   └── query_demo.py            # 命令行检索演示
├── tests/                       # 单元测试 + 综合端到端测试
└── data/                        # 知识库(JSONL 入库, pkl 派生不入库)

数据流

CangjieCorpus(官方文档)   x2cangjie 类型翻译产物   j2cjlib shim + 术语表
        │                        │                      │
        v                        v                      v
  corpus_parser.py        import_type_mappings.py   j2cj_parser.py
        └────────────────────────┼──────────────────────┘
                                 v
                        KnowledgeBase(JSONL) ──> BM25 索引 ──> MCP server
                                 │                              (stdio, 9 个工具)
                                 v
               Java 代码 → extract_types → resolve_java_code(per-call)

检索原理

  1. 分词:驼峰拆分(getOrThrowget or throw)、下划线拆分(read_file_bytesread file bytes)、中文 unigram 单字切分(中文无空格词边界,单字切分让 BM25 能对中文查询/描述打分,无需引入分词器)。

  2. BM25 字段加权:name × 4 > signature × 3 > module × 2 > tags × 1.5 > description × 1,让"按名检索"比"按描述检索"更准。

  3. Java 术语扩展:查询 token 先查 Java→Cangjie 映射表,把 Java 词汇展开成 Cangjie 同义词再检索(解决 Thread vs 线程 的匹配问题)。

  4. 精确名索引:get_api_details / get_class_members 走精确名索引,不依赖相似度。

MCP 工具(9 个)

工具

说明

典型调用

search_api

API 相似度检索,返回签名/模块/来源/描述

search_api("HashMap put key value")

get_api_details

按名精确查函数/类/接口

get_api_details("add", module="std.collection")

get_class_members

类的全部成员(init/prop/func)

get_class_members("ArrayList")

find_examples

检索示例代码

find_examples("read file lines")

java_to_cangjie

Java 符号 → Cangjie 等价物

java_to_cangjie("java.util.List")

error_fix_hint

编译错误 → 相关 API + 示例

error_fix_hint("cannot find symbol println")

list_modules

列出知识库中所有模块

list_modules()

resolve_java_code

per-call 渐进式披露:拆成每个 API 调用,逐个锁类型+分层检索,返回 suggestions[]

resolve_java_code("map.put(key, value);")

describe_java_code

只生成 Java 代码的中英双语 NL 描述(不检索)

describe_java_code("String line = reader.readLine();")

快速开始

知识库数据(JSONL)已随仓库托管在 GitHub——clone 即用,不需要在本机重新构建。 BM25 索引(.pkl)是派生产物,首次运行时自动重建。

# 1. clone + 安装依赖(仅 PyYAML;索引核心零依赖)
git clone https://github.com/sskacc/cangjie-knowledge-mcp.git
pip install -r requirements.txt

# 2. 一键就绪:校验数据 + 自动重建索引(首次运行会自动完成,可跳过)
python scripts/install_kb.py

# 3. 命令行试一下
python scripts/query_demo.py "HashMap put key value"
python scripts/query_demo.py --class-members ArrayList
python scripts/query_demo.py --java "java.util.List"

# 4. 启动 MCP 服务器(stdio)
python -m cjkb.mcp_server --data-dir data

首次启动 MCP 服务器时,如果 data/ 里只有 JSONL 没有 .pkl(刚 clone 的状态), Searcher.load 会自动重建 BM25 索引(约 1 秒),无需手动干预。

Docker 方式(无需本机 Python)

宿主机没有 Python 时,用 Docker 镜像运行:

# 构建镜像(打包源码 + 知识库 + PyYAML)
docker build -t cangjie-knowledge-mcp .

# 直接运行 MCP server(stdio)
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test"}}}' \
  | docker run -i --rm cangjie-knowledge-mcp

在 opencode 中注册 MCP

项目根目录已附带 opencode.json(opencode 会自动加载项目级配置),它通过 Docker 镜像注册本 MCP server:

// opencode.json(已随仓库提供)
{
  "mcp": {
    "cangjie-knowledge": {
      "type": "local",
      "command": ["docker", "run", "-i", "--rm", "cangjie-knowledge-mcp"],
      "enabled": true
    }
  }
}

若本机有 Python(无需 Docker),可改为:

{
  "mcp": {
    "cangjie-knowledge": {
      "type": "local",
      "command": ["python", "-m", "cjkb.mcp_server"],
      "enabled": true
    }
  }
}

注册后,agent 在片段翻译和错误修复时可以直接调用上述 9 个工具。

手动维护知识库(推荐工作流)

data/ 的 JSONL 托管进 git 的原因:JSONL 是人类可读、可 diff、可 review 的 源数据,pkl 是机器生成的派生品。维护流程:

# 1. 编辑 data/*.jsonl(增删 API 记录 / 示例 / Java 映射)
#    直接编辑 jsonl 即可,不必重跑 build_kb.py(重跑会覆盖手动改动)

# 2. 重建索引(改了 jsonl 后必须做,否则检索用旧索引)
python scripts/install_kb.py    # 或直接启动 MCP,load 时检测到 jsonl 更新会自动重建

# 3. 提交推送
git add data/*.jsonl
git commit -m "kb: add HashMap add/replace examples"
git push

规则:JSONL 是数据,进 git;pkl 是派生品,不进 git。新机器 clone 后 install_kb.py / MCP 首启都会自动重建 pkl,无需手动处理。

从零重建(只在语料变化时用)

当 Cangjie 官方语料更新,或需要重新收集时。语料来自 x2cangjie 项目 (一个 Java→Cangjie 翻译流水线,本知识库是它的配套检索工具),路径按实际 clone 位置填写:

python scripts/build_kb.py \
  --corpus <x2cangjie路径>/misc/CangjieCorpus \
  --j2cjlib <x2cangjie路径>/misc/j2cjlib \
  --terms <x2cangjie路径>/configs/java_cangjie_terms.yaml

# 重建后导入 x2cangjie 类型翻译产物(类型锁定靠它)
python scripts/import_type_mappings.py \
  --type-resolution <x2cangjie路径>/data/java/type_resolution

⚠️ build_kb.py覆盖 data/*.jsonl。若 jsonl 已有手动改动, 重跑前先 git commit(可回滚),或先备份手动改动。

在片段翻译流程中使用

以 x2cangjie 的翻译流程为例,推荐的调用时机:

  1. 翻译前:把待翻译片段丢给 resolve_java_code → 得到每个 API 调用的建议, 取 API 签名和示例注入 prompt;细节确认用 get_class_members / java_to_cangjie / find_examples

  2. 错误修复循环(cjpm build 失败后):把编译错误传给 error_fix_hint → 返回相关 API 签名和示例,拼进错误反馈 prompt。

  3. 类型解析 RAG 兜底:把 search_api 结果作为额外证据注入类型映射 prompt。

说明:本项目是独立于 x2cangjie 的新项目,不修改 x2cangjie 的代码。接入方式: (a) 在 agent(MCP 客户端)侧注册上面的 server,让 agent 直接调用; (b) 在 x2cangjie 的 Python 代码中 import cjkb 直接调用 Searcher(程序化 API)。

程序化调用(不经过 MCP)

import sys
sys.path.insert(0, "src")
from cjkb.config import load_config
from cjkb.index.searcher import Searcher

cfg = load_config("config.yaml")
s = Searcher.load(cfg["output"]["data_dir"], cfg)

for r in s.search_api("HashMap put key value"):
    print(r.name, r.module, r.signature)

for m in s.java_to_cangjie("java.util.List"):
    print(m.java_symbol, "->", m.cangjie_symbol)

LLM 配置(可选,用于四处)

配置 config.yamlllm 段或环境变量 OPENAI_API_KEY / OPENAI_BASE_URL / OPENAI_MODEL。LLM 用于四处:

  1. NL 描述生成(resolve_java_code / describe_java_code):LLM 把 Java 代码转成 中英双语 NL 描述。未配置时自动退回启发式(驼峰拆分 + 术语映射),检索不受影响。

  2. 语义 rerank(search_api / resolve_java_code 等检索出口):LLM 对 BM25 top-k 重排。未配置或 rerank=false 时退回纯 BM25 顺序

  3. 补写缺失示例:为没有官方示例的 API 生成示例。

  4. 编译错误修复建议(error_fix_hint):LLM 直接根据编译错误生成中文修复建议 (解释错误含义 + 修复方法)。未配置时退回关键词检索(见下文"变更记录")。

export OPENAI_API_KEY="..."
export OPENAI_BASE_URL="https://api.deepseek.com"    # 可选
export OPENAI_MODEL="deepseek-v4-flash"               # 可选

可选:LLM 生成缺失示例

知识库中没有官方示例的 API,可以用 LLM 自动补写(每条标记 generated=true, 与官方示例区分)。用独立脚本 scripts/generate_examples.py:

export OPENAI_API_KEY="..."

python scripts/generate_examples.py --dry-run      # 看有多少 API 缺示例
python scripts/generate_examples.py --limit 50     # 生成前 50 条(断点续跑)
python scripts/generate_examples.py --limit 0      # 生成全部缺失的

特点:

  • 断点续跑:自动跳过已生成的 title,重跑不重复

  • 失败容忍:LLM 空响应自动重试 2 次

  • 自动重建索引:生成后立即更新 BM25 索引

测试

# 单元测试 + 综合端到端测试(需 Docker,或本机 Python + PyYAML)
docker run --rm -v "$(pwd):/app" -w /app cjkb-test:latest python -m pytest -q
# 或本机:
pip install pytest
python -m pytest -q

测试覆盖:tokenize(含中文 unigram)、BM25、parser、searcher、MCP 协议、NL 生成、 类型提取(含构造调用/菱形泛型)、per-call 分层检索、降级路径、rerank 回退与解析、 9 个工具端到端、错误处理。

变更记录

2026-09-01:修复 Java 静态调用解析 + error_fix_hint 检索失配

本次修复两个影响检索质量的问题(均在 src/cjkb/ 内):

1. java_types.py — 静态调用 receiver 丢首字符

_CALL_RE 正则原要求 receiver 以小写字母开头([a-z_]),只能匹配变量调用 (map.put(...)),无法匹配静态调用(Character.valueOf(...))。静态调用会被从大写类名 的第二个字符开始错误匹配,导致 Character 被解析成 haracter(丢首字母),进而检索到 无关 API。

修复:正则加 \b 词边界并允许大写开头;extract_types 的 enrich 逻辑对"大写开头的 receiver 且无变量声明"视为静态调用,把 receiver 本身当作类型(declared_type)。

2. mcp_server.pyerror_fix_hint 检索失配

error_fix_hint 原来把编译错误文本(_clean_error 提关键词)直接拿去 BM25 搜 API 文档, 但编译错误术语(mismatched typesexpected expressionundeclared identifier)与 Cangjie API 文档词汇是两套不相交的词表,导致返回的都是 hashCodestd.fs.init 等 无关 API。

修复:新增 _llm_error_hint(LLM 配置后生效),让 LLM 直接把编译错误解释为中文修复 建议(错误含义 + 修复方法,如 mismatched types → "可用 as 关键字转换");同时新增 _llm_error_query 把错误转成检索查询词作为检索的兜底。error_fix_hint 返回体新增 hint(LLM 修复建议)与 query(检索查询词)字段。未配置 LLM 时退回原有的关键词 检索,行为不变。

注意:config.yamlllm.api_key 属敏感信息,不要提交实际密钥;用环境变量 OPENAI_API_KEY 注入即可(见上文"LLM 配置")。

2026-09-01:resolve_java_code 对无映射类型不返回误导建议

resolve_java_code 对"没有 Cangjie 对应"的 Java 类型(包装类 CharacterNumberStringBuilderStringBuffer 等)原来仍会走类型锁定 + NL 检索,结果锁到无关的 KB 类 (如 Characterstd.core.Object),返回误导性建议,反而干扰翻译。

修复(layered_search.py):

  • _GENERIC_TYPES 扩展,加入 CharacterNumberStringBuilderStringBuffer 等无可靠 Cangjie 对应的 Java 类型。

  • resolve_java_code 先把这些无映射类型的调用直接剔除(resolvable_calls),不做 L1/L2 检索;仅当存在可解析调用但都未产出建议时才触发 L3 块级兜底。当所有调用都 无映射时,返回空的 suggestions(不再硬塞一个块级兜底建议)。

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers