cloudflare-mcp-smart-proxy 1.0.2 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-mcp-smart-proxy",
3
- "version": "1.0.2",
3
+ "version": "1.1.1",
4
4
  "description": "Smart proxy for Cloudflare MCP - routes tools to cloud or local execution",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-mcp-smart-proxy",
3
- "version": "1.0.2",
3
+ "version": "1.1.1",
4
4
  "description": "Smart proxy for Cloudflare MCP - routes tools to cloud or local execution",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -5,6 +5,9 @@
5
5
  import { FileOperations } from './tools/file-operations.js';
6
6
  import { EditFile } from './tools/edit-file.js';
7
7
  import { CommandExecutor } from './tools/command-executor.js';
8
+ import { FileSearch } from './tools/file-search.js';
9
+ import { SymbolTools } from './tools/symbol-tools.js';
10
+ import { Diagnostics } from './tools/diagnostics.js';
8
11
 
9
12
  export class LocalToolExecutor {
10
13
  constructor(workspaceRoot) {
@@ -38,6 +41,21 @@ export class LocalToolExecutor {
38
41
  case 'execute_shell_command':
39
42
  return await CommandExecutor.execute(params, this.workspaceRoot);
40
43
 
44
+ case 'search_files':
45
+ return await FileSearch.searchFiles(params, this.workspaceRoot);
46
+
47
+ case 'search_symbols':
48
+ return await SymbolTools.searchSymbols(params, this.workspaceRoot);
49
+
50
+ case 'get_symbol_definition':
51
+ return await SymbolTools.getSymbolDefinition(params, this.workspaceRoot);
52
+
53
+ case 'get_document_symbols':
54
+ return await SymbolTools.getDocumentSymbols(params, this.workspaceRoot);
55
+
56
+ case 'get_diagnostics':
57
+ return await Diagnostics.getDiagnostics(params, this.workspaceRoot);
58
+
41
59
  default:
42
60
  throw new Error(`Unknown local tool: ${toolName}`);
43
61
  }
@@ -190,6 +208,112 @@ export class LocalToolExecutor {
190
208
  },
191
209
  required: ['command']
192
210
  }
211
+ },
212
+ {
213
+ name: 'search_files',
214
+ description: 'Search for files by name or content in local filesystem',
215
+ inputSchema: {
216
+ type: 'object',
217
+ properties: {
218
+ query: {
219
+ type: 'string',
220
+ description: 'Search query (matches filename or file content)'
221
+ },
222
+ directory: {
223
+ type: 'string',
224
+ description: 'Directory to search in (default: current directory)'
225
+ },
226
+ maxResults: {
227
+ type: 'number',
228
+ description: 'Maximum number of results (default: 50)'
229
+ },
230
+ filePattern: {
231
+ type: 'string',
232
+ description: 'File pattern to match (e.g., "*.js", "*.ts")'
233
+ },
234
+ caseSensitive: {
235
+ type: 'boolean',
236
+ description: 'Whether search is case sensitive (default: false)'
237
+ }
238
+ },
239
+ required: ['query']
240
+ }
241
+ },
242
+ {
243
+ name: 'search_symbols',
244
+ description: 'Search for code symbols (functions, classes, variables) in local filesystem',
245
+ inputSchema: {
246
+ type: 'object',
247
+ properties: {
248
+ query: {
249
+ type: 'string',
250
+ description: 'Symbol name to search for'
251
+ },
252
+ directory: {
253
+ type: 'string',
254
+ description: 'Directory to search in (default: current directory)'
255
+ },
256
+ maxResults: {
257
+ type: 'number',
258
+ description: 'Maximum number of results (default: 50)'
259
+ },
260
+ filePattern: {
261
+ type: 'string',
262
+ description: 'File pattern to match (e.g., "*.js", "*.ts")'
263
+ }
264
+ },
265
+ required: ['query']
266
+ }
267
+ },
268
+ {
269
+ name: 'get_symbol_definition',
270
+ description: 'Get the definition of a symbol in a file',
271
+ inputSchema: {
272
+ type: 'object',
273
+ properties: {
274
+ file_path: {
275
+ type: 'string',
276
+ description: 'Path to the file containing the symbol'
277
+ },
278
+ symbol: {
279
+ type: 'string',
280
+ description: 'Name of the symbol to find'
281
+ },
282
+ line: {
283
+ type: 'number',
284
+ description: 'Line number where the symbol is used (optional, helps narrow search)'
285
+ }
286
+ },
287
+ required: ['file_path', 'symbol']
288
+ }
289
+ },
290
+ {
291
+ name: 'get_document_symbols',
292
+ description: 'Get all symbols (outline) in a document',
293
+ inputSchema: {
294
+ type: 'object',
295
+ properties: {
296
+ file_path: {
297
+ type: 'string',
298
+ description: 'Path to the file to analyze'
299
+ }
300
+ },
301
+ required: ['file_path']
302
+ }
303
+ },
304
+ {
305
+ name: 'get_diagnostics',
306
+ description: 'Get code diagnostics (errors, warnings) for a file',
307
+ inputSchema: {
308
+ type: 'object',
309
+ properties: {
310
+ file_path: {
311
+ type: 'string',
312
+ description: 'Path to the file to analyze'
313
+ }
314
+ },
315
+ required: ['file_path']
316
+ }
193
317
  }
194
318
  ];
195
319
  }
package/src/router.js CHANGED
@@ -129,7 +129,24 @@ export class SmartRouter {
129
129
  const result = await response.json();
130
130
 
131
131
  if (result.error) {
132
- throw new Error(result.error.message || 'Cloud tool execution failed');
132
+ // MCP 标准格式:详细错误信息在 data 字段中
133
+ // 如果 data 是对象,尝试提取更多信息
134
+ let errorMessage = result.error.data || result.error.message || 'Cloud tool execution failed';
135
+
136
+ // 如果 data 是对象,尝试提取 message 或格式化整个对象
137
+ if (typeof errorMessage === 'object') {
138
+ errorMessage = errorMessage.message || JSON.stringify(errorMessage, null, 2);
139
+ }
140
+
141
+ // 记录详细错误信息用于调试
142
+ console.error(`[SmartRouter] Cloud tool error for ${toolName}:`, {
143
+ code: result.error.code,
144
+ message: result.error.message,
145
+ data: result.error.data,
146
+ fullError: result.error
147
+ });
148
+
149
+ throw new Error(errorMessage);
133
150
  }
134
151
 
135
152
  // 提取结果内容
@@ -137,7 +154,16 @@ export class SmartRouter {
137
154
  // MCP 标准格式
138
155
  const content = result.result.content;
139
156
  if (Array.isArray(content) && content.length > 0) {
140
- return content[0].text;
157
+ const text = content[0].text;
158
+ // 尝试解析 JSON 字符串,如果失败则返回原始字符串
159
+ try {
160
+ const parsed = JSON.parse(text);
161
+ // 如果解析成功且是对象,返回对象;否则返回原始字符串
162
+ return typeof parsed === 'object' && parsed !== null ? parsed : text;
163
+ } catch {
164
+ // 不是 JSON,直接返回字符串
165
+ return text;
166
+ }
141
167
  }
142
168
  return content;
143
169
  } else if (result.result) {
@@ -146,6 +172,18 @@ export class SmartRouter {
146
172
  return result;
147
173
  }
148
174
  } catch (error) {
175
+ // 记录完整错误信息用于调试
176
+ console.error(`[SmartRouter] Error calling cloud tool ${toolName}:`, {
177
+ error: error.message,
178
+ stack: error.stack,
179
+ params: params
180
+ });
181
+
182
+ // 如果错误信息已经包含 "Cloud tool call failed",直接抛出
183
+ // 否则添加前缀
184
+ if (error.message && error.message.includes('Cloud tool call failed')) {
185
+ throw error;
186
+ }
149
187
  throw new Error(`Cloud tool call failed: ${error.message}`);
150
188
  }
151
189
  }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Diagnostics - 代码诊断工具
3
+ * 提供基础的代码检查功能
4
+ */
5
+
6
+ import fs from 'fs/promises';
7
+ import path from 'path';
8
+ import { FileOperations } from './file-operations.js';
9
+
10
+ export class Diagnostics {
11
+ /**
12
+ * 获取文件诊断信息
13
+ */
14
+ static async getDiagnostics(params, workspaceRoot) {
15
+ const { file_path } = params;
16
+
17
+ if (!file_path) {
18
+ throw new Error('file_path parameter is required');
19
+ }
20
+
21
+ const filePath = FileOperations.validatePath(file_path, workspaceRoot);
22
+ const diagnostics = [];
23
+
24
+ try {
25
+ const stats = await fs.stat(filePath);
26
+
27
+ // 只处理代码文件
28
+ const codeExtensions = ['.js', '.ts', '.jsx', '.tsx', '.py', '.java', '.cpp', '.c'];
29
+ const ext = path.extname(file_path).toLowerCase();
30
+
31
+ if (!codeExtensions.includes(ext)) {
32
+ return {
33
+ file: file_path,
34
+ diagnostics: [],
35
+ total: 0
36
+ };
37
+ }
38
+
39
+ const content = await fs.readFile(filePath, 'utf-8');
40
+ const lines = content.split('\n');
41
+
42
+ // 基础语法检查(简单规则)
43
+ lines.forEach((line, index) => {
44
+ const lineNum = index + 1;
45
+ const trimmed = line.trim();
46
+
47
+ // 检查未闭合的括号
48
+ const openParens = (line.match(/\(/g) || []).length;
49
+ const closeParens = (line.match(/\)/g) || []).length;
50
+ const openBraces = (line.match(/\{/g) || []).length;
51
+ const closeBraces = (line.match(/\}/g) || []).length;
52
+ const openBrackets = (line.match(/\[/g) || []).length;
53
+ const closeBrackets = (line.match(/\]/g) || []).length;
54
+
55
+ // 检查未闭合的字符串
56
+ const singleQuotes = (line.match(/'/g) || []).length;
57
+ const doubleQuotes = (line.match(/"/g) || []).length;
58
+ const backticks = (line.match(/`/g) || []).length;
59
+
60
+ // 简单的警告(不是真正的语法检查,只是模式匹配)
61
+ if (trimmed && !trimmed.startsWith('//') && !trimmed.startsWith('*') && !trimmed.startsWith('/*')) {
62
+ // 检查常见的潜在问题
63
+ if (trimmed.includes('console.log') && !trimmed.includes('//')) {
64
+ diagnostics.push({
65
+ line: lineNum,
66
+ column: line.indexOf('console.log') + 1,
67
+ severity: 'warning',
68
+ message: 'console.log found - consider removing in production',
69
+ source: 'basic-linter'
70
+ });
71
+ }
72
+
73
+ // 检查未使用的变量模式(简单检查)
74
+ if (trimmed.match(/^(const|let|var)\s+\w+\s*=\s*[^;]+;\s*$/)) {
75
+ // 这是一个简单的变量声明,但无法真正判断是否使用
76
+ // 这里只是示例,实际需要 AST 解析
77
+ }
78
+
79
+ // 检查可能的语法错误模式
80
+ if (trimmed.endsWith(',') && !trimmed.includes('//')) {
81
+ // 可能是尾随逗号(在某些情况下是合法的)
82
+ }
83
+ }
84
+ });
85
+
86
+ // 检查文件编码问题
87
+ try {
88
+ // 尝试检测 BOM
89
+ if (content.charCodeAt(0) === 0xFEFF) {
90
+ diagnostics.push({
91
+ line: 1,
92
+ column: 1,
93
+ severity: 'warning',
94
+ message: 'File contains BOM (Byte Order Mark)',
95
+ source: 'encoding-check'
96
+ });
97
+ }
98
+ } catch (e) {
99
+ // 忽略
100
+ }
101
+
102
+ // 检查行尾一致性
103
+ const hasCRLF = content.includes('\r\n');
104
+ const hasLF = content.includes('\n') && !hasCRLF;
105
+ if (hasCRLF && hasLF) {
106
+ diagnostics.push({
107
+ line: 1,
108
+ column: 1,
109
+ severity: 'info',
110
+ message: 'File contains mixed line endings',
111
+ source: 'line-ending-check'
112
+ });
113
+ }
114
+
115
+ return {
116
+ file: file_path,
117
+ diagnostics: diagnostics.slice(0, 100), // 限制诊断数量
118
+ total: diagnostics.length
119
+ };
120
+ } catch (error) {
121
+ if (error.code === 'ENOENT') {
122
+ throw new Error(`File not found: ${file_path}`);
123
+ }
124
+ throw new Error(`Failed to get diagnostics: ${error.message}`);
125
+ }
126
+ }
127
+ }
128
+
@@ -0,0 +1,133 @@
1
+ /**
2
+ * File Search - 文件搜索工具
3
+ */
4
+
5
+ import fs from 'fs/promises';
6
+ import path from 'path';
7
+ import { FileOperations } from './file-operations.js';
8
+
9
+ export class FileSearch {
10
+ /**
11
+ * 搜索文件
12
+ */
13
+ static async searchFiles(params, workspaceRoot) {
14
+ const {
15
+ query,
16
+ directory = '.',
17
+ maxResults = 50,
18
+ filePattern = null,
19
+ caseSensitive = false
20
+ } = params;
21
+
22
+ if (!query) {
23
+ throw new Error('query parameter is required');
24
+ }
25
+
26
+ const searchDir = FileOperations.validatePath(directory, workspaceRoot);
27
+ const results = [];
28
+ const searchRegex = new RegExp(
29
+ caseSensitive ? query : query.toLowerCase(),
30
+ caseSensitive ? 'g' : 'gi'
31
+ );
32
+
33
+ // 文件模式匹配
34
+ let filePatternRegex = null;
35
+ if (filePattern) {
36
+ // 简单的 glob 转正则(支持 * 和 ?)
37
+ const pattern = filePattern
38
+ .replace(/\./g, '\\.')
39
+ .replace(/\*/g, '.*')
40
+ .replace(/\?/g, '.');
41
+ filePatternRegex = new RegExp(pattern, caseSensitive ? '' : 'i');
42
+ }
43
+
44
+ /**
45
+ * 递归搜索文件
46
+ */
47
+ async function searchDirectory(currentDir) {
48
+ try {
49
+ const entries = await fs.readdir(currentDir, { withFileTypes: true });
50
+
51
+ for (const entry of entries) {
52
+ const fullPath = path.join(currentDir, entry.name);
53
+ const relativePath = path.relative(workspaceRoot, fullPath);
54
+
55
+ // 跳过 node_modules, .git 等常见目录
56
+ if (entry.name.startsWith('.') && entry.name !== '.') {
57
+ continue;
58
+ }
59
+ if (entry.name === 'node_modules' || entry.name === '.git') {
60
+ continue;
61
+ }
62
+
63
+ if (entry.isDirectory()) {
64
+ await searchDirectory(fullPath);
65
+ } else if (entry.isFile()) {
66
+ // 检查文件模式
67
+ if (filePatternRegex && !filePatternRegex.test(entry.name)) {
68
+ continue;
69
+ }
70
+
71
+ // 搜索文件名
72
+ const fileNameMatch = searchRegex.test(entry.name);
73
+
74
+ // 搜索文件内容(仅文本文件)
75
+ let contentMatches = [];
76
+ if (fileNameMatch || !filePattern) {
77
+ try {
78
+ const stats = await fs.stat(fullPath);
79
+ // 只搜索小于 1MB 的文件
80
+ if (stats.size < 1024 * 1024) {
81
+ const content = await fs.readFile(fullPath, 'utf-8');
82
+ const lines = content.split('\n');
83
+
84
+ lines.forEach((line, index) => {
85
+ if (searchRegex.test(line)) {
86
+ contentMatches.push({
87
+ line: index + 1,
88
+ content: line.trim().substring(0, 200) // 限制长度
89
+ });
90
+ }
91
+ });
92
+
93
+ // 重置正则(因为 test 会改变 lastIndex)
94
+ searchRegex.lastIndex = 0;
95
+ }
96
+ } catch (error) {
97
+ // 忽略无法读取的文件(二进制文件等)
98
+ }
99
+ }
100
+
101
+ // 如果文件名或内容匹配,添加到结果
102
+ if (fileNameMatch || contentMatches.length > 0) {
103
+ results.push({
104
+ file: relativePath,
105
+ fileName: entry.name,
106
+ fileNameMatch,
107
+ contentMatches: contentMatches.slice(0, 10), // 限制每个文件的匹配数
108
+ matchCount: contentMatches.length
109
+ });
110
+
111
+ if (results.length >= maxResults) {
112
+ return; // 达到最大结果数,停止搜索
113
+ }
114
+ }
115
+ }
116
+ }
117
+ } catch (error) {
118
+ // 忽略权限错误,继续搜索其他目录
119
+ console.error(`Error searching ${currentDir}:`, error.message);
120
+ }
121
+ }
122
+
123
+ await searchDirectory(searchDir);
124
+
125
+ return {
126
+ query,
127
+ directory,
128
+ results: results.slice(0, maxResults),
129
+ total: results.length
130
+ };
131
+ }
132
+ }
133
+
@@ -0,0 +1,329 @@
1
+ /**
2
+ * Symbol Tools - 符号搜索和定义工具
3
+ */
4
+
5
+ import fs from 'fs/promises';
6
+ import path from 'path';
7
+ import { FileOperations } from './file-operations.js';
8
+
9
+ export class SymbolTools {
10
+ /**
11
+ * 搜索符号(函数、类、变量等)
12
+ */
13
+ static async searchSymbols(params, workspaceRoot) {
14
+ const {
15
+ query,
16
+ directory = '.',
17
+ maxResults = 50,
18
+ filePattern = null
19
+ } = params;
20
+
21
+ if (!query) {
22
+ throw new Error('query parameter is required');
23
+ }
24
+
25
+ const searchDir = FileOperations.validatePath(directory, workspaceRoot);
26
+ const results = [];
27
+
28
+ // 文件模式匹配
29
+ let filePatternRegex = null;
30
+ if (filePattern) {
31
+ const pattern = filePattern
32
+ .replace(/\./g, '\\.')
33
+ .replace(/\*/g, '.*')
34
+ .replace(/\?/g, '.');
35
+ filePatternRegex = new RegExp(pattern, 'i');
36
+ }
37
+
38
+ // 符号定义模式(支持多种语言)
39
+ const symbolPatterns = [
40
+ // JavaScript/TypeScript
41
+ /^(export\s+)?(function|class|const|let|var|interface|type|enum)\s+(\w+)/,
42
+ /^(export\s+)?(async\s+)?function\s+(\w+)/,
43
+ /^(export\s+)?(const|let|var)\s+(\w+)\s*[:=]/,
44
+ // Python
45
+ /^(def|class)\s+(\w+)/,
46
+ // Java/C++
47
+ /^(public|private|protected)?\s*(static)?\s*(class|interface|enum|void|int|String|boolean)\s+(\w+)/,
48
+ ];
49
+
50
+ /**
51
+ * 递归搜索符号
52
+ */
53
+ async function searchDirectory(currentDir) {
54
+ try {
55
+ const entries = await fs.readdir(currentDir, { withFileTypes: true });
56
+
57
+ for (const entry of entries) {
58
+ const fullPath = path.join(currentDir, entry.name);
59
+ const relativePath = path.relative(workspaceRoot, fullPath);
60
+
61
+ // 跳过常见目录
62
+ if (entry.name.startsWith('.') && entry.name !== '.') {
63
+ continue;
64
+ }
65
+ if (entry.name === 'node_modules' || entry.name === '.git') {
66
+ continue;
67
+ }
68
+
69
+ if (entry.isDirectory()) {
70
+ await searchDirectory(fullPath);
71
+ } else if (entry.isFile()) {
72
+ // 检查文件模式
73
+ if (filePatternRegex && !filePatternRegex.test(entry.name)) {
74
+ continue;
75
+ }
76
+
77
+ // 只搜索代码文件
78
+ const codeExtensions = ['.js', '.ts', '.jsx', '.tsx', '.py', '.java', '.cpp', '.c', '.h', '.hpp'];
79
+ const ext = path.extname(entry.name).toLowerCase();
80
+ if (!codeExtensions.includes(ext)) {
81
+ continue;
82
+ }
83
+
84
+ try {
85
+ const stats = await fs.stat(fullPath);
86
+ // 只搜索小于 500KB 的文件
87
+ if (stats.size > 500 * 1024) {
88
+ continue;
89
+ }
90
+
91
+ const content = await fs.readFile(fullPath, 'utf-8');
92
+ const lines = content.split('\n');
93
+
94
+ lines.forEach((line, index) => {
95
+ for (const pattern of symbolPatterns) {
96
+ const match = line.match(pattern);
97
+ if (match) {
98
+ const symbolName = match[match.length - 1]; // 最后一个捕获组是符号名
99
+
100
+ // 检查是否匹配查询(模糊匹配)
101
+ if (symbolName.toLowerCase().includes(query.toLowerCase())) {
102
+ // 确定符号类型
103
+ let kind = 'unknown';
104
+ if (line.includes('function') || line.includes('def')) {
105
+ kind = 'function';
106
+ } else if (line.includes('class')) {
107
+ kind = 'class';
108
+ } else if (line.includes('interface') || line.includes('type')) {
109
+ kind = 'interface';
110
+ } else if (line.includes('enum')) {
111
+ kind = 'enum';
112
+ } else if (line.includes('const') || line.includes('let') || line.includes('var')) {
113
+ kind = 'variable';
114
+ }
115
+
116
+ results.push({
117
+ name: symbolName,
118
+ kind,
119
+ file: relativePath,
120
+ line: index + 1,
121
+ content: line.trim().substring(0, 150)
122
+ });
123
+
124
+ if (results.length >= maxResults) {
125
+ return;
126
+ }
127
+ }
128
+ }
129
+ }
130
+ });
131
+ } catch (error) {
132
+ // 忽略无法读取的文件
133
+ }
134
+
135
+ if (results.length >= maxResults) {
136
+ return;
137
+ }
138
+ }
139
+ }
140
+ } catch (error) {
141
+ console.error(`Error searching ${currentDir}:`, error.message);
142
+ }
143
+ }
144
+
145
+ await searchDirectory(searchDir);
146
+
147
+ return {
148
+ query,
149
+ symbols: results.slice(0, maxResults),
150
+ total: results.length
151
+ };
152
+ }
153
+
154
+ /**
155
+ * 获取符号定义
156
+ */
157
+ static async getSymbolDefinition(params, workspaceRoot) {
158
+ const { file_path, symbol, line } = params;
159
+
160
+ if (!file_path || !symbol) {
161
+ throw new Error('file_path and symbol parameters are required');
162
+ }
163
+
164
+ const filePath = FileOperations.validatePath(file_path, workspaceRoot);
165
+
166
+ try {
167
+ const content = await fs.readFile(filePath, 'utf-8');
168
+ const lines = content.split('\n');
169
+
170
+ // 如果提供了行号,从该行开始搜索
171
+ const startLine = line ? Math.max(0, line - 1) : 0;
172
+ const searchRange = line ? 20 : lines.length; // 如果提供了行号,只搜索附近20行
173
+
174
+ // 符号定义模式
175
+ const definitionPatterns = [
176
+ new RegExp(`(export\\s+)?(function|class|const|let|var|interface|type|enum)\\s+${symbol}\\b`),
177
+ new RegExp(`(export\\s+)?(async\\s+)?function\\s+${symbol}\\b`),
178
+ new RegExp(`(export\\s+)?(const|let|var)\\s+${symbol}\\s*[:=]`),
179
+ new RegExp(`(def|class)\\s+${symbol}\\b`),
180
+ ];
181
+
182
+ let definitionLine = -1;
183
+ let definitionContent = '';
184
+
185
+ // 从指定行向上搜索
186
+ for (let i = startLine; i >= 0 && i >= startLine - searchRange; i--) {
187
+ for (const pattern of definitionPatterns) {
188
+ if (pattern.test(lines[i])) {
189
+ definitionLine = i + 1;
190
+ definitionContent = lines[i];
191
+ break;
192
+ }
193
+ }
194
+ if (definitionLine !== -1) break;
195
+ }
196
+
197
+ // 如果向上没找到,向下搜索
198
+ if (definitionLine === -1) {
199
+ for (let i = startLine; i < lines.length && i < startLine + searchRange; i++) {
200
+ for (const pattern of definitionPatterns) {
201
+ if (pattern.test(lines[i])) {
202
+ definitionLine = i + 1;
203
+ definitionContent = lines[i];
204
+ break;
205
+ }
206
+ }
207
+ if (definitionLine !== -1) break;
208
+ }
209
+ }
210
+
211
+ if (definitionLine === -1) {
212
+ throw new Error(`Symbol definition not found: ${symbol}`);
213
+ }
214
+
215
+ // 提取 JSDoc/注释(向上查找)
216
+ let documentation = '';
217
+ for (let i = definitionLine - 2; i >= 0 && i >= definitionLine - 10; i--) {
218
+ const trimmed = lines[i].trim();
219
+ if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/**') || trimmed.startsWith('*/')) {
220
+ documentation = lines[i] + '\n' + documentation;
221
+ } else if (trimmed === '' && documentation) {
222
+ // 空行,继续查找注释
223
+ continue;
224
+ } else {
225
+ break;
226
+ }
227
+ }
228
+
229
+ // 确定符号类型
230
+ let kind = 'unknown';
231
+ if (definitionContent.includes('function') || definitionContent.includes('def')) {
232
+ kind = 'function';
233
+ } else if (definitionContent.includes('class')) {
234
+ kind = 'class';
235
+ } else if (definitionContent.includes('interface') || definitionContent.includes('type')) {
236
+ kind = 'interface';
237
+ } else if (definitionContent.includes('enum')) {
238
+ kind = 'enum';
239
+ } else if (definitionContent.includes('const') || definitionContent.includes('let') || definitionContent.includes('var')) {
240
+ kind = 'variable';
241
+ }
242
+
243
+ return {
244
+ symbol,
245
+ kind,
246
+ file: file_path,
247
+ line: definitionLine,
248
+ content: definitionContent.trim(),
249
+ documentation: documentation.trim()
250
+ };
251
+ } catch (error) {
252
+ if (error.code === 'ENOENT') {
253
+ throw new Error(`File not found: ${file_path}`);
254
+ }
255
+ throw new Error(`Failed to get symbol definition: ${error.message}`);
256
+ }
257
+ }
258
+
259
+ /**
260
+ * 获取文档符号(文件大纲)
261
+ */
262
+ static async getDocumentSymbols(params, workspaceRoot) {
263
+ const { file_path } = params;
264
+
265
+ if (!file_path) {
266
+ throw new Error('file_path parameter is required');
267
+ }
268
+
269
+ const filePath = FileOperations.validatePath(file_path, workspaceRoot);
270
+
271
+ try {
272
+ const content = await fs.readFile(filePath, 'utf-8');
273
+ const lines = content.split('\n');
274
+ const symbols = [];
275
+
276
+ // 符号定义模式
277
+ const symbolPatterns = [
278
+ // 类
279
+ { pattern: /^(export\s+)?class\s+(\w+)/, kind: 'class' },
280
+ { pattern: /^class\s+(\w+)/, kind: 'class' },
281
+ // 函数
282
+ { pattern: /^(export\s+)?(async\s+)?function\s+(\w+)/, kind: 'function' },
283
+ { pattern: /^def\s+(\w+)/, kind: 'function' },
284
+ // 接口/类型
285
+ { pattern: /^(export\s+)?interface\s+(\w+)/, kind: 'interface' },
286
+ { pattern: /^(export\s+)?type\s+(\w+)/, kind: 'type' },
287
+ // 枚举
288
+ { pattern: /^(export\s+)?enum\s+(\w+)/, kind: 'enum' },
289
+ // 变量/常量
290
+ { pattern: /^(export\s+)?const\s+(\w+)/, kind: 'variable' },
291
+ { pattern: /^(export\s+)?let\s+(\w+)/, kind: 'variable' },
292
+ ];
293
+
294
+ lines.forEach((line, index) => {
295
+ for (const { pattern, kind } of symbolPatterns) {
296
+ const match = line.match(pattern);
297
+ if (match) {
298
+ const symbolName = match[match.length - 1]; // 最后一个捕获组
299
+ const indent = line.match(/^(\s*)/)?.[1]?.length || 0;
300
+
301
+ symbols.push({
302
+ name: symbolName,
303
+ kind,
304
+ line: index + 1,
305
+ indent,
306
+ content: line.trim().substring(0, 100)
307
+ });
308
+ break; // 只匹配第一个模式
309
+ }
310
+ }
311
+ });
312
+
313
+ // 按行号排序
314
+ symbols.sort((a, b) => a.line - b.line);
315
+
316
+ return {
317
+ file: file_path,
318
+ symbols,
319
+ total: symbols.length
320
+ };
321
+ } catch (error) {
322
+ if (error.code === 'ENOENT') {
323
+ throw new Error(`File not found: ${file_path}`);
324
+ }
325
+ throw new Error(`Failed to get document symbols: ${error.message}`);
326
+ }
327
+ }
328
+ }
329
+