
1. 本地 Java 代码语义分析为什么值得做成 MCP 小工具如果你手上有一份几十万行的 Java 老项目想快速搞清楚「这个方法到底在干什么」「哪些函数逻辑相似可以合并」靠人肉翻代码基本不现实。我最近在做的这件事就是把 Java 代码语义分析封装成一个本地 MCP 工具用 FastMCP 暴露工具接口用 TaoToken 统一 Key 走 AI 语义提取再配合向量库做相似函数检索。整套东西跑在你自己的机器上代码不出本地只有语义提取那一步会调用远端模型。MCPModel Context Protocol你可以理解成「给 AI 客户端插工具的标准插座」。以前你想让 Cline、Claude Code 这类工具调用你自己的脚本得写一堆适配现在只要按 MCP 协议暴露几个 tool客户端就能像调用内置能力一样调用你的 Java 分析器。FastMCP 是 Python 侧最省事的实现方式几十行就能起一个带工具的服务。这篇面向的是本地有 Java 项目、想用 AI 做代码语义理解、又不想把整份源码上传到某个平台的开发者。核心链路是「FastMCP 起服务 → TaoToken 统一 Key 提供模型通道 → 客户端配置接入 → 发一个语义分析请求验证」。下面所有配置都可以直接复制改路径和 Key 就能跑。2. TaoToken 前置统一 Key 与通道准备TaoToken 在这里的角色是「统一 Key 统一 API 通道」。你的 Java 语义分析工具需要调模型如果每个模型都单独申请 Key、单独记 base_url配置会散得到处都是。用 TaoToken 之后不管底层换哪个模型你的工具代码只认一个 Key 和一个 API 地址。先拿到 Key。打开控制台在 API Keys 页面创建一个控制台入口https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewriteAPI Keys 页面https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite创建完复制那串sk-开头的 Key先存到环境变量里别硬编码进代码# Linux / macOS export TAOTOKEN_API_KEYsk-你的Key # Windows PowerShell $env:TAOTOKEN_API_KEYsk-你的KeyAPI 基础地址统一用https://taotoken.net/api注意这个地址不带任何查询参数。你的工具里所有模型调用都指向它具体走哪个模型由请求里的 model 字段决定。注意Key 只放环境变量或本地配置文件不要提交到 Git。语义分析工具会读源码配置文件里再泄露 Key 就双重翻车了。如果你后面要长期跑编码类 Agent比如让 AI 直接改 Java 代码可以了解下 Coding Plan它更适合高频调用场景Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite3. 可复制配置FastMCP 服务 客户端接入3.1 项目结构与依赖先建目录我用的结构是这样java-semantic-mcp/ ├── server.py # FastMCP 服务主文件 ├── analyzer.py # Java 解析 语义提取逻辑 ├── config.toml # 服务端配置 ├── requirements.txt └── vector_db/ # 向量库持久化目录自动生成requirements.txtfastmcp0.4.0 openai1.30.0 chromadb0.4.24 sentence-transformers2.6.0安装pip install -r requirements.txt3.2 config.toml 骨架服务端配置我放在config.toml把模型通道和向量库参数集中管理[model] # 统一走 TaoToken 通道 base_url https://taotoken.net/api api_key_env TAOTOKEN_API_KEY model_name gpt-4o-mini max_tokens 200 temperature 0.1 [embedding] model_name BAAI/bge-base-zh-v1.5 dimension 768 [vector_db] path ./vector_db collection java_functions metric cosine [server] name java-code-analyzer transport stdioapi_key_env写的是环境变量名代码运行时去读这样配置文件可以放心提交。3.3 Java 函数切分与语义提取核心analyzer.py里做两件事正则切分 Java 方法、调模型提取语义。切分部分用正则识别类和方法定义同时向上收集注释和注解import re from typing import Any CLASS_PATTERN r^\s*(public|private|protected)?\s*(static)?\s*(final)?\s*class\s(\w) METHOD_PATTERN r^\s*(public|private|protected)?\s*(static)?\s*(final)?\s*(\w(?:[^])?|void)\s(\w)\s*\([^)]*\)\s*\{? def extract_comments_before(lines: list[str], start: int) - list[str]: comments, i [], start - 1 while i 0: line lines[i].strip() if not line: i - 1 continue if line.startswith(/**) or line.startswith(/*): block, j [], i while j len(lines): block.append(lines[j].strip()) if lines[j].strip().endswith(*/): break j 1 comments.extend(reversed(block)) i j - 1 elif line.startswith(//) or line.startswith(): comments.append(line) else: break i - 1 return list(reversed(comments)) def parse_java_functions(content: str) - list[dict[str, Any]]: lines content.split(\n) results [] for i, line in enumerate(lines): m re.search(METHOD_PATTERN, line) if not m: continue brace, end 0, i for j in range(i, len(lines)): brace lines[j].count({) - lines[j].count(}) if brace 0 and { in lines[j]: end j break comments extract_comments_before(lines, i) results.append({ name: m.group(5), return_type: m.group(4), parameters: (re.search(r\(([^)]*)\), line) or [None, ])[1], line_start: i 1, line_end: end 1, code: \n.join(lines[i:end 1]), comments: [c for c in comments if not c.strip().startswith()], annotations: [c for c in comments if c.strip().startswith()], }) return results语义提取走 OpenAI 兼容接口base_url 指向 TaoTokenimport os from openai import OpenAI client OpenAI( api_keyos.environ[TAOTOKEN_API_KEY], base_urlhttps://taotoken.net/api, ) def extract_semantic(func: dict) - str: prompt f用一句中文概括以下 Java 函数的核心业务语义不超过 50 字重点说业务功能而非技术实现。 函数名: {func[name]} 返回类型: {func[return_type]} 参数: {func[parameters]} 注释: {chr(10).join(func[comments]) or 无} 代码: java {func[code]} resp client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: prompt}], max_tokens200, temperature0.1, ) return resp.choices[0].message.content.strip()3.4 FastMCP 服务骨架server.py暴露三个工具分析单文件、语义搜索、分析单函数import json from fastmcp import FastMCP from analyzer import parse_java_functions, extract_semantic from embedder import embed, store, search mcp FastMCP(java-code-analyzer) mcp.tool() def analyze_java_file(file_path: str) - str: 分析 Java 文件切分函数并提取语义存入向量库 with open(file_path, encodingutf-8) as f: content f.read() funcs parse_java_functions(content) out [] for fn in funcs: semantic extract_semantic(fn) store(fn[name], semantic, fn[code], embed(semantic)) out.append({name: fn[name], semantic: semantic, lines: f{fn[line_start]}-{fn[line_end]}}) return json.dumps(out, ensure_asciiFalse, indent2) mcp.tool() def search_similar(query: str, top_k: int 3) - str: 按语义搜索相似函数 return json.dumps(search(embed(query), top_k), ensure_asciiFalse, indent2) mcp.tool() def analyze_function(code: str, name: str) - str: 分析单个函数的语义 return extract_semantic({name: name, code: code, return_type: , parameters: , comments: [], annotations: []}) if __name__ __main__: mcp.run()3.5 客户端接入settings.json 与 Cline 配置如果你用 Claude Code 或支持 MCP 的客户端在settings.json里加{ mcpServers: { java-semantic: { command: python, args: [/绝对路径/java-semantic-mcp/server.py], env: { TAOTOKEN_API_KEY: sk-你的Key } } } }Cline 的 MCP 配置片段cline_mcp_settings.json{ mcpServers: { java-semantic: { command: python, args: [/绝对路径/java-semantic-mcp/server.py], disabled: false, autoApprove: [analyze_java_file, search_similar] } } }CC Switch 里切换配置时把上面这段贴进对应 profile 的 mcpServers 字段即可切换后重启客户端生效。4. 启动验证与语义分析请求4.1 启动服务cd java-semantic-mcp python server.pystdio 模式下终端不会打印太多东西看到进程挂起不退出就是正常。想调试可以用 MCP 自带的 dev 模式fastmcp dev server.py它会起一个本地 UI能直接点工具、填参数、看返回。4.2 发一个语义分析请求准备一个测试 Java 文件Demo.javapublic class Demo { /** * 根据用户ID查询订单列表只返回未支付订单 */ public ListOrder queryPendingOrders(Long userId) { return orderMapper.selectByUserAndStatus(userId, PENDING); } }在客户端里调用analyze_java_file参数传绝对路径。预期返回类似[ { name: queryPendingOrders, semantic: 根据用户ID查询该用户所有未支付状态的订单列表, lines: 5-7 } ]再调search_similarquery 传「查询用户订单」应该能命中刚存进去的函数similarity 在 0.8 以上。这一步通了说明「切分 → 语义提取 → 向量化 → 检索」整条链路都活了。4.3 批量分析整个项目把analyze_java_file包一层目录遍历就能批量处理from pathlib import Path mcp.tool() def analyze_folder(folder: str) - str: 批量分析文件夹下所有 Java 文件 files list(Path(folder).rglob(*.java)) total 0 for f in files: with open(f, encodingutf-8) as fp: funcs parse_java_functions(fp.read()) for fn in funcs: semantic extract_semantic(fn) store(fn[name], semantic, fn[code], embed(semantic)) total 1 return f处理 {len(files)} 个文件提取 {total} 个函数语义5. 本篇常见错排查报 401 / invalid api key九成是环境变量没生效。在服务进程里打印os.environ.get(TAOTOKEN_API_KEY)确认注意客户端配置里的env字段是给子进程用的不会继承你 shell 里 export 的变量两边都要配。base_url 写错必须是https://taotoken.net/api不要带/v1后缀也不要带任何查询参数。OpenAI SDK 会自己拼/chat/completions。函数切分漏掉方法正则对泛型返回类型、注解换行、Lambda 里的花括号比较敏感。如果发现某个方法没被切出来先看它的签名是不是跨行了。跨行签名建议先把文件做一次「合并连续签名行」预处理。向量库检索结果为空检查store和search用的是不是同一个 collection 名和同一个 embedding 模型。换过模型维度对不上检索会直接报错或返回垃圾结果。MCP 客户端连不上args里的路径必须是绝对路径相对路径在客户端启动子进程时工作目录不确定。另外确认python命令在客户端的环境变量 PATH 里不行就写 python 的绝对路径。语义提取很慢每个函数一次模型调用几百个函数就是几百次请求。可以先把函数按文件批量打包成一次请求让模型返回 JSON 数组能省不少时间。6. 继续往下走这套工具跑通之后最直接的用法是把它挂到你的编码 Agent 上让 AI 在改代码前先搜一遍「有没有语义相似的函数」避免重复造轮子。模型对话能力可以直接在网页端验证语义提取效果模型对话https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite接入文档里有完整的接口说明和参数细节遇到通道问题先翻这里接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite如果你用的是 Claude Code 这类编码工具Anthropic 兼容接入的配置方式单独有一页Claude Code 接入https://taotoken.net/claudecode-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecode-anthropicutm_campaignrewrite我自己的习惯是先把analyze_folder跑一遍全项目把语义库建起来之后每次改代码前用search_similar查一下比翻 IDE 的全局搜索准得多——毕竟搜的是「这个函数在干什么」而不是「哪个文件里有这个词」。