Skip to main content
Glama
viewCodeItem.ts16.6 kB
/** * view_code_items 工具 (批量版) * 查看多个特定的代码项(类、方法或函数) * * 推荐使用场景: * 1. 已明确文件路径与目标名称,需要快速查看类/方法/函数的完整定义块。 * 2. 想对接口与实现类进行对比,确认接口声明与实现逻辑是否一致。 * 3. 需要在分析调用链之前,先精确抽取关键方法的实现代码片段。 * * 能做到的功能: * 1. 按“文件路径 + 代码项名称”批量定位并返回定义块或附近片段。 * 2. 对 Java 接口自动尝试匹配 Impl 实现类,并返回同名方法/类片段。 * 3. 提取方法片段中的字段调用,并尝试推导字段对应类型的类路径。 */ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import * as fs from "fs"; import * as path from "path"; import { pathExists, isFile } from "../utils/fileSystem.js"; /** * 默认忽略的目录名称集合。 */ const DEFAULT_IGNORE_DIRS = new Set([ ".git", ".idea", ".vscode", "node_modules", "dist", "build", "out", "target", ".gradle", ".mvn", "logs", "log", "tmp", "temp", ]); /** * 代码项提取结果。 */ type SnippetResult = { found: boolean; text: string; startLine?: number; endLine?: number; }; /** * 提取指定代码项的片段信息。 * @param filePath 文件路径 * @param itemName 代码项名称 */ function extractSnippet(filePath: string, itemName: string): SnippetResult { if (!pathExists(filePath) || !isFile(filePath)) { return { found: false, text: `--- 文件: ${filePath} (错误: 文件不存在或无效) ---` }; } try { const content = fs.readFileSync(filePath, "utf-8"); const lines = content.split("\n"); const itemIndex = lines.findIndex(line => line.includes(itemName)); if (itemIndex === -1) { return { found: false, text: `--- 文件: ${filePath} (错误: 找不到代码项 '${itemName}') ---` }; } const blockRange = findBlockRange(lines, itemIndex); if (!blockRange) { const start = Math.max(0, itemIndex - 10); const end = Math.min(lines.length, itemIndex + 60); const snippet = lines.slice(start, end).join("\n"); return { found: true, text: `--- 文件: ${filePath} (第 ${start + 1} 行至第 ${end} 行, 定位: '${itemName}') ---\n\n${snippet}`, startLine: start + 1, endLine: end, }; } const snippet = lines.slice(blockRange.start, blockRange.end + 1).join("\n"); return { found: true, text: `--- 文件: ${filePath} (第 ${blockRange.start + 1} 行至第 ${blockRange.end + 1} 行, 定位: '${itemName}') ---\n\n${snippet}`, startLine: blockRange.start + 1, endLine: blockRange.end + 1, }; } catch (error) { return { found: false, text: `--- 文件: ${filePath} (错误: 读取失败 - ${error instanceof Error ? error.message : String(error)}) ---` }; } } /** * 根据接口文件路径推导实现类文件路径。 * @param filePath 接口文件路径 */ function buildImplFilePath(filePath: string): string | null { if (!filePath.endsWith(".java")) { return null; } const fileName = path.basename(filePath); if (!fileName.endsWith("Service.java")) { return null; } const implFileName = fileName.replace("Service.java", "ServiceImpl.java"); const serviceDirToken = `${path.sep}service${path.sep}`; const implDirToken = `${path.sep}service${path.sep}impl${path.sep}`; if (filePath.includes(implDirToken)) { return path.join(path.dirname(filePath), implFileName); } if (filePath.includes(serviceDirToken)) { return filePath.replace(serviceDirToken, implDirToken).replace(fileName, implFileName); } return path.join(path.dirname(filePath), implFileName); } /** * 基于花括号匹配推导完整代码块范围。 * @param lines 文件行列表 * @param hitIndex 命中行索引 */ function findBlockRange(lines: string[], hitIndex: number): { start: number; end: number } | null { const signatureIndex = findSignatureStart(lines, hitIndex); const signatureEndIndex = findSignatureEndIndex(lines, signatureIndex); if (signatureEndIndex !== -1) { return { start: signatureIndex, end: signatureEndIndex }; } const openIndex = findOpenBraceIndex(lines, signatureIndex); if (openIndex === -1) { return null; } const endIndex = findMatchingBraceIndex(lines, openIndex); if (endIndex === -1) { return null; } return { start: signatureIndex, end: endIndex }; } /** * 向上定位方法/类签名的起始行(包含注解与注释)。 * @param lines 文件行列表 * @param hitIndex 命中行索引 */ function findSignatureStart(lines: string[], hitIndex: number): number { let index = hitIndex; while (index > 0) { const line = lines[index - 1]; const trimmed = line.trim(); if (trimmed === "") { index -= 1; continue; } if (trimmed.startsWith("@") || trimmed.startsWith("*") || trimmed.startsWith("/**") || trimmed.startsWith("*/")) { index -= 1; continue; } if (trimmed.endsWith(";") || trimmed.endsWith("}") || trimmed.startsWith("}")) { break; } break; } return index; } /** * 从指定行开始向下查找第一个左花括号位置。 * @param lines 文件行列表 * @param startIndex 起始行索引 */ function findOpenBraceIndex(lines: string[], startIndex: number): number { for (let i = startIndex; i < lines.length; i += 1) { if (lines[i].includes("{")) { return i; } } return -1; } /** * 向下查找方法签名结束位置(接口/抽象方法的分号)。 * @param lines 文件行列表 * @param startIndex 起始行索引 */ function findSignatureEndIndex(lines: string[], startIndex: number): number { for (let i = startIndex; i < lines.length; i += 1) { const line = lines[i]; if (line.includes("{")) { return -1; } if (line.includes(";")) { return i; } } return -1; } /** * 从左花括号行开始进行花括号计数匹配。 * @param lines 文件行列表 * @param openIndex 左花括号行索引 */ function findMatchingBraceIndex(lines: string[], openIndex: number): number { let depth = 0; for (let i = openIndex; i < lines.length; i += 1) { const line = lines[i]; for (const char of line) { if (char === "{") { depth += 1; } else if (char === "}") { depth -= 1; if (depth === 0) { return i; } } } } return -1; } /** * 根据文件路径推导实现类名称。 * @param filePath 文件路径 */ function buildImplClassName(filePath: string): string | null { if (!filePath.endsWith(".java")) { return null; } const baseName = path.basename(filePath, ".java"); if (baseName.endsWith("Impl")) { return baseName; } return `${baseName}Impl`; } /** * 从类文件中提取字段与类型映射。 * @param lines 文件行列表 */ function extractFieldTypeMap(lines: string[]): Map<string, string> { const map = new Map<string, string>(); const fieldRegex = /^\s*(private|protected|public)\s+(final\s+)?([A-Za-z_][\w<>]*)\s+([A-Za-z_][\w]*)\s*;/; for (const line of lines) { const match = line.match(fieldRegex); if (!match) { continue; } const rawType = match[3]; const fieldName = match[4]; const cleanType = rawType.replace(/<.*>/g, ""); map.set(fieldName, cleanType); } return map; } /** * 从方法代码中提取调用到的字段名称。 * @param snippet 代码片段 */ function extractCalledFields(snippet: string): Set<string> { const result = new Set<string>(); const callRegex = /\b([A-Za-z_][\w]*)\s*\./g; let match: RegExpExecArray | null; while ((match = callRegex.exec(snippet)) !== null) { result.add(match[1]); } return result; } /** * 生成方法调用类路径说明。 * @param filePath 文件路径 * @param snippetText 代码片段文本 */ function buildCallPathSection(filePath: string, snippetText: string): string | null { if (!filePath.endsWith(".java")) { return null; } if (!pathExists(filePath) || !isFile(filePath)) { return null; } const content = fs.readFileSync(filePath, "utf-8"); const lines = content.split("\n"); const fieldTypeMap = extractFieldTypeMap(lines); const calledFields = extractCalledFields(snippetText); const callEntries: string[] = []; calledFields.forEach(fieldName => { const typeName = fieldTypeMap.get(fieldName); if (!typeName) { return; } const classFiles = findClassFiles(process.cwd(), typeName); if (classFiles.length === 0) { callEntries.push(`${fieldName} -> ${typeName} -> 未找到路径`); return; } classFiles.forEach(classFile => { callEntries.push(`${fieldName} -> ${typeName} -> ${classFile}`); }); }); if (callEntries.length === 0) { return null; } return ["--- 方法调用类路径 ---", ...callEntries].join("\n"); } /** * 全局搜索指定类名对应的 Java 文件。 * @param rootDir 搜索根目录 * @param className 类名 */ function findClassFiles(rootDir: string, className: string): string[] { const results: string[] = []; const targetFileName = `${className}.java`; const classRegex = new RegExp(`\\b(class|interface|enum)\\s+${className}\\b`); const stack: string[] = [rootDir]; while (stack.length > 0) { const current = stack.pop(); if (!current) { continue; } let entries: fs.Dirent[]; try { entries = fs.readdirSync(current, { withFileTypes: true }); } catch (error) { continue; } for (const entry of entries) { if (entry.isDirectory()) { if (DEFAULT_IGNORE_DIRS.has(entry.name)) { continue; } stack.push(path.join(current, entry.name)); continue; } if (!entry.isFile() || entry.name !== targetFileName) { continue; } const filePath = path.join(current, entry.name); try { const content = fs.readFileSync(filePath, "utf-8"); if (classRegex.test(content)) { results.push(filePath); } } catch (error) { continue; } } } return results; } /** * 全局搜索指定实现类名对应的 Java 文件。 * @param rootDir 搜索根目录 * @param implClassName 实现类名称 */ function findImplFiles(rootDir: string, implClassName: string): string[] { const results: string[] = []; const targetFileName = `${implClassName}.java`; const classRegex = new RegExp(`\\bclass\\s+${implClassName}\\b`); const stack: string[] = [rootDir]; while (stack.length > 0) { const current = stack.pop(); if (!current) { continue; } let entries: fs.Dirent[]; try { entries = fs.readdirSync(current, { withFileTypes: true }); } catch (error) { continue; } for (const entry of entries) { if (entry.isDirectory()) { if (DEFAULT_IGNORE_DIRS.has(entry.name)) { continue; } stack.push(path.join(current, entry.name)); continue; } if (!entry.isFile() || entry.name !== targetFileName) { continue; } const filePath = path.join(current, entry.name); try { const content = fs.readFileSync(filePath, "utf-8"); if (classRegex.test(content)) { results.push(filePath); } } catch (error) { continue; } } } return results; } /** * 注册 view_code_items 工具到 MCP 服务器 * @param server MCP 服务器实例 */ export function registerViewCodeItem(server: McpServer): void { server.tool( "view_code_items", "[Precise Code Retrieval] Best for cases where the file path and item name are known. Batch returns full definition blocks for classes/methods/functions. If the input is a Java interface and implementations exist, it will attempt to traverse to Impl classes and return same-named method snippets (multiple implementations are supported and labeled). It also parses fields referenced in the method snippet to infer their type-to-class paths. Ideal for interface/implementation comparison, fast location of key methods, and precise extraction before call-chain analysis; if the path or name is unknown, use file search tools first.", { // Batch request list Items: z.array(z.object({ File: z.string().describe("Absolute path of the file"), ItemName: z.string().describe("Exact name of the code item to locate (e.g., class name 'UserService' or method name 'findUser')") })).describe("List of code items to query."), } as any, async (input: any) => { const { Items } = input; if (!Array.isArray(Items) || Items.length === 0) { return { content: [{ type: "text", text: JSON.stringify({ error: "请提供至少一个待查询的代码项" }) }], } as any; } // 并发处理所有请求 const results = await Promise.all(Items.map(async (item) => { const { File, ItemName } = item; const resultSnippets: string[] = []; const mainSnippet = extractSnippet(File, ItemName); resultSnippets.push(mainSnippet.text); const mainCallSection = buildCallPathSection(File, mainSnippet.text); if (mainCallSection) { resultSnippets.push(mainCallSection); } const implCandidates = new Set<string>(); const implFilePath = buildImplFilePath(File); if (implFilePath && implFilePath !== File) { implCandidates.add(implFilePath); } const implClassName = buildImplClassName(File); if (implClassName) { const implFiles = findImplFiles(process.cwd(), implClassName); implFiles.forEach(filePath => implCandidates.add(filePath)); } if (implCandidates.size > 0) { const implList = Array.from(implCandidates).filter(filePath => filePath !== File); let implIndex = 1; for (const filePath of implList) { const implSnippet = extractSnippet(filePath, ItemName); if (!implSnippet.found) { continue; } const implHeader = `--- Impl匹配: ${implClassName ?? "未知实现类"} (${implIndex}/${implList.length}) ---`; resultSnippets.push(`${implHeader}\n${implSnippet.text}`); const implCallSection = buildCallPathSection(filePath, implSnippet.text); if (implCallSection) { resultSnippets.push(implCallSection); } implIndex += 1; } } return resultSnippets.join("\n\n"); })); return { content: [ { type: "text", text: results.join("\n\n" + "=".repeat(30) + "\n\n"), }, ], } as any; } ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/raintear94/code-search-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server