deepcode-hku 1.0.1__py3-none-any.whl
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.
- cli/__init__.py +18 -0
- cli/cli_app.py +296 -0
- cli/cli_interface.py +744 -0
- cli/cli_launcher.py +155 -0
- cli/main_cli.py +243 -0
- cli/workflows/__init__.py +11 -0
- cli/workflows/cli_workflow_adapter.py +336 -0
- deepcode.py +219 -0
- deepcode_hku-1.0.1.dist-info/METADATA +695 -0
- deepcode_hku-1.0.1.dist-info/RECORD +44 -0
- deepcode_hku-1.0.1.dist-info/WHEEL +5 -0
- deepcode_hku-1.0.1.dist-info/entry_points.txt +2 -0
- deepcode_hku-1.0.1.dist-info/licenses/LICENSE +21 -0
- deepcode_hku-1.0.1.dist-info/top_level.txt +6 -0
- tools/__init__.py +0 -0
- tools/code_implementation_server.py +1045 -0
- tools/code_indexer.py +1657 -0
- tools/code_reference_indexer.py +486 -0
- tools/command_executor.py +324 -0
- tools/git_command.py +356 -0
- tools/pdf_converter.py +640 -0
- tools/pdf_downloader.py +1370 -0
- tools/pdf_utils.py +52 -0
- ui/__init__.py +43 -0
- ui/app.py +13 -0
- ui/components.py +1450 -0
- ui/handlers.py +773 -0
- ui/layout.py +106 -0
- ui/streamlit_app.py +38 -0
- ui/styles.py +2116 -0
- utils/__init__.py +17 -0
- utils/cli_interface.py +459 -0
- utils/dialogue_logger.py +671 -0
- utils/file_processor.py +426 -0
- utils/simple_llm_logger.py +198 -0
- workflows/__init__.py +31 -0
- workflows/agent_orchestration_engine.py +1371 -0
- workflows/agents/__init__.py +13 -0
- workflows/agents/code_implementation_agent.py +1093 -0
- workflows/agents/memory_agent_concise.py +923 -0
- workflows/agents/memory_agent_concise_index.py +935 -0
- workflows/code_implementation_workflow.py +924 -0
- workflows/code_implementation_workflow_index.py +931 -0
- workflows/codebase_index_workflow.py +726 -0
|
@@ -0,0 +1,1045 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Code Implementation MCP Server
|
|
4
|
+
|
|
5
|
+
This MCP server provides core functions needed for paper code reproduction:
|
|
6
|
+
1. File read/write operations
|
|
7
|
+
2. Code execution and testing
|
|
8
|
+
3. Code search and analysis
|
|
9
|
+
4. Iterative improvement support
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
python tools/code_implementation_server.py
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import subprocess
|
|
17
|
+
import json
|
|
18
|
+
import sys
|
|
19
|
+
import io
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
import re
|
|
22
|
+
from typing import Dict, Any
|
|
23
|
+
import tempfile
|
|
24
|
+
import shutil
|
|
25
|
+
import logging
|
|
26
|
+
from datetime import datetime
|
|
27
|
+
|
|
28
|
+
# Set standard output encoding to UTF-8
|
|
29
|
+
if sys.stdout.encoding != "utf-8":
|
|
30
|
+
try:
|
|
31
|
+
if hasattr(sys.stdout, "reconfigure"):
|
|
32
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
33
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
34
|
+
else:
|
|
35
|
+
sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding="utf-8")
|
|
36
|
+
sys.stderr = io.TextIOWrapper(sys.stderr.detach(), encoding="utf-8")
|
|
37
|
+
except Exception as e:
|
|
38
|
+
print(f"Warning: Could not set UTF-8 encoding: {e}")
|
|
39
|
+
|
|
40
|
+
# Import MCP related modules
|
|
41
|
+
from mcp.server.fastmcp import FastMCP
|
|
42
|
+
|
|
43
|
+
# Setup logging
|
|
44
|
+
logging.basicConfig(level=logging.INFO)
|
|
45
|
+
logger = logging.getLogger(__name__)
|
|
46
|
+
|
|
47
|
+
# Create FastMCP server instance
|
|
48
|
+
mcp = FastMCP("code-implementation-server")
|
|
49
|
+
|
|
50
|
+
# Global variables: workspace directory and operation history
|
|
51
|
+
WORKSPACE_DIR = None
|
|
52
|
+
OPERATION_HISTORY = []
|
|
53
|
+
CURRENT_FILES = {}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def initialize_workspace(workspace_dir: str = None):
|
|
57
|
+
"""
|
|
58
|
+
Initialize workspace
|
|
59
|
+
|
|
60
|
+
By default, the workspace will be set by the workflow via the set_workspace tool to:
|
|
61
|
+
{plan_file_parent}/generate_code
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
workspace_dir: Optional workspace directory path
|
|
65
|
+
"""
|
|
66
|
+
global WORKSPACE_DIR
|
|
67
|
+
if workspace_dir is None:
|
|
68
|
+
# Default to generate_code directory under current directory, but don't create immediately
|
|
69
|
+
# This default value will be overridden by workflow via set_workspace tool
|
|
70
|
+
WORKSPACE_DIR = Path.cwd() / "generate_code"
|
|
71
|
+
# logger.info(f"Workspace initialized (default value, will be overridden by workflow): {WORKSPACE_DIR}")
|
|
72
|
+
# logger.info("Note: Actual workspace will be set by workflow via set_workspace tool to {plan_file_parent}/generate_code")
|
|
73
|
+
else:
|
|
74
|
+
WORKSPACE_DIR = Path(workspace_dir).resolve()
|
|
75
|
+
# Only create when explicitly specified
|
|
76
|
+
WORKSPACE_DIR.mkdir(parents=True, exist_ok=True)
|
|
77
|
+
logger.info(f"Workspace initialized: {WORKSPACE_DIR}")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def ensure_workspace_exists():
|
|
81
|
+
"""Ensure workspace directory exists"""
|
|
82
|
+
global WORKSPACE_DIR
|
|
83
|
+
if WORKSPACE_DIR is None:
|
|
84
|
+
initialize_workspace()
|
|
85
|
+
|
|
86
|
+
# Create workspace directory (if it doesn't exist)
|
|
87
|
+
if not WORKSPACE_DIR.exists():
|
|
88
|
+
WORKSPACE_DIR.mkdir(parents=True, exist_ok=True)
|
|
89
|
+
logger.info(f"Workspace directory created: {WORKSPACE_DIR}")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def validate_path(path: str) -> Path:
|
|
93
|
+
"""Validate if path is within workspace"""
|
|
94
|
+
if WORKSPACE_DIR is None:
|
|
95
|
+
initialize_workspace()
|
|
96
|
+
|
|
97
|
+
full_path = (WORKSPACE_DIR / path).resolve()
|
|
98
|
+
if not str(full_path).startswith(str(WORKSPACE_DIR)):
|
|
99
|
+
raise ValueError(f"Path {path} is outside workspace scope")
|
|
100
|
+
return full_path
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def log_operation(action: str, details: Dict[str, Any]):
|
|
104
|
+
"""Log operation history"""
|
|
105
|
+
OPERATION_HISTORY.append(
|
|
106
|
+
{"timestamp": datetime.now().isoformat(), "action": action, "details": details}
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# ==================== File Operation Tools ====================
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@mcp.tool()
|
|
114
|
+
async def read_file(
|
|
115
|
+
file_path: str, start_line: int = None, end_line: int = None
|
|
116
|
+
) -> str:
|
|
117
|
+
"""
|
|
118
|
+
Read file content, supports specifying line number range
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
file_path: File path, relative to workspace
|
|
122
|
+
start_line: Starting line number (1-based, optional)
|
|
123
|
+
end_line: Ending line number (1-based, optional)
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
JSON string of file content or error message
|
|
127
|
+
"""
|
|
128
|
+
try:
|
|
129
|
+
full_path = validate_path(file_path)
|
|
130
|
+
|
|
131
|
+
if not full_path.exists():
|
|
132
|
+
result = {"status": "error", "message": f"File does not exist: {file_path}"}
|
|
133
|
+
log_operation(
|
|
134
|
+
"read_file_error", {"file_path": file_path, "error": "file_not_found"}
|
|
135
|
+
)
|
|
136
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
137
|
+
|
|
138
|
+
with open(full_path, "r", encoding="utf-8") as f:
|
|
139
|
+
lines = f.readlines()
|
|
140
|
+
|
|
141
|
+
# 处理行号范围
|
|
142
|
+
if start_line is not None or end_line is not None:
|
|
143
|
+
start_idx = (start_line - 1) if start_line else 0
|
|
144
|
+
end_idx = end_line if end_line else len(lines)
|
|
145
|
+
lines = lines[start_idx:end_idx]
|
|
146
|
+
|
|
147
|
+
content = "".join(lines)
|
|
148
|
+
|
|
149
|
+
result = {
|
|
150
|
+
"status": "success",
|
|
151
|
+
"content": content,
|
|
152
|
+
"file_path": file_path,
|
|
153
|
+
"total_lines": len(lines),
|
|
154
|
+
"size_bytes": len(content.encode("utf-8")),
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
log_operation(
|
|
158
|
+
"read_file",
|
|
159
|
+
{
|
|
160
|
+
"file_path": file_path,
|
|
161
|
+
"start_line": start_line,
|
|
162
|
+
"end_line": end_line,
|
|
163
|
+
"lines_read": len(lines),
|
|
164
|
+
},
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
168
|
+
|
|
169
|
+
except Exception as e:
|
|
170
|
+
result = {
|
|
171
|
+
"status": "error",
|
|
172
|
+
"message": f"Failed to read file: {str(e)}",
|
|
173
|
+
"file_path": file_path,
|
|
174
|
+
}
|
|
175
|
+
log_operation("read_file_error", {"file_path": file_path, "error": str(e)})
|
|
176
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@mcp.tool()
|
|
180
|
+
async def write_file(
|
|
181
|
+
file_path: str, content: str, create_dirs: bool = True, create_backup: bool = False
|
|
182
|
+
) -> str:
|
|
183
|
+
"""
|
|
184
|
+
Write content to file
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
file_path: File path, relative to workspace
|
|
188
|
+
content: Content to write to file
|
|
189
|
+
create_dirs: Whether to create directories if they don't exist
|
|
190
|
+
create_backup: Whether to create backup file if file already exists
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
JSON string of operation result
|
|
194
|
+
"""
|
|
195
|
+
try:
|
|
196
|
+
full_path = validate_path(file_path)
|
|
197
|
+
|
|
198
|
+
# Create directories (if needed)
|
|
199
|
+
if create_dirs:
|
|
200
|
+
full_path.parent.mkdir(parents=True, exist_ok=True)
|
|
201
|
+
|
|
202
|
+
# Backup existing file (only when explicitly requested)
|
|
203
|
+
backup_created = False
|
|
204
|
+
if full_path.exists() and create_backup:
|
|
205
|
+
backup_path = full_path.with_suffix(full_path.suffix + ".backup")
|
|
206
|
+
shutil.copy2(full_path, backup_path)
|
|
207
|
+
backup_created = True
|
|
208
|
+
|
|
209
|
+
# Write file
|
|
210
|
+
with open(full_path, "w", encoding="utf-8") as f:
|
|
211
|
+
f.write(content)
|
|
212
|
+
|
|
213
|
+
# Update current file record
|
|
214
|
+
CURRENT_FILES[file_path] = {
|
|
215
|
+
"last_modified": datetime.now().isoformat(),
|
|
216
|
+
"size_bytes": len(content.encode("utf-8")),
|
|
217
|
+
"lines": len(content.split("\n")),
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
result = {
|
|
221
|
+
"status": "success",
|
|
222
|
+
"message": f"File written successfully: {file_path}",
|
|
223
|
+
"file_path": file_path,
|
|
224
|
+
"size_bytes": len(content.encode("utf-8")),
|
|
225
|
+
"lines_written": len(content.split("\n")),
|
|
226
|
+
"backup_created": backup_created,
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
log_operation(
|
|
230
|
+
"write_file",
|
|
231
|
+
{
|
|
232
|
+
"file_path": file_path,
|
|
233
|
+
"size_bytes": len(content.encode("utf-8")),
|
|
234
|
+
"lines": len(content.split("\n")),
|
|
235
|
+
"backup_created": backup_created,
|
|
236
|
+
},
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
240
|
+
|
|
241
|
+
except Exception as e:
|
|
242
|
+
result = {
|
|
243
|
+
"status": "error",
|
|
244
|
+
"message": f"Failed to write file: {str(e)}",
|
|
245
|
+
"file_path": file_path,
|
|
246
|
+
}
|
|
247
|
+
log_operation("write_file_error", {"file_path": file_path, "error": str(e)})
|
|
248
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
# ==================== Code Execution Tools ====================
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@mcp.tool()
|
|
255
|
+
async def execute_python(code: str, timeout: int = 30) -> str:
|
|
256
|
+
"""
|
|
257
|
+
Execute Python code and return output
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
code: Python code to execute
|
|
261
|
+
timeout: Timeout in seconds
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
JSON string of execution result
|
|
265
|
+
"""
|
|
266
|
+
try:
|
|
267
|
+
# Create temporary file
|
|
268
|
+
with tempfile.NamedTemporaryFile(
|
|
269
|
+
mode="w", suffix=".py", delete=False, encoding="utf-8"
|
|
270
|
+
) as f:
|
|
271
|
+
f.write(code)
|
|
272
|
+
temp_file = f.name
|
|
273
|
+
|
|
274
|
+
try:
|
|
275
|
+
# Ensure workspace directory exists
|
|
276
|
+
ensure_workspace_exists()
|
|
277
|
+
|
|
278
|
+
# Execute Python code
|
|
279
|
+
result = subprocess.run(
|
|
280
|
+
[sys.executable, temp_file],
|
|
281
|
+
cwd=WORKSPACE_DIR,
|
|
282
|
+
capture_output=True,
|
|
283
|
+
text=True,
|
|
284
|
+
timeout=timeout,
|
|
285
|
+
encoding="utf-8",
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
execution_result = {
|
|
289
|
+
"status": "success" if result.returncode == 0 else "error",
|
|
290
|
+
"return_code": result.returncode,
|
|
291
|
+
"stdout": result.stdout,
|
|
292
|
+
"stderr": result.stderr,
|
|
293
|
+
"timeout": timeout,
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if result.returncode != 0:
|
|
297
|
+
execution_result["message"] = "Python code execution failed"
|
|
298
|
+
else:
|
|
299
|
+
execution_result["message"] = "Python code execution successful"
|
|
300
|
+
|
|
301
|
+
log_operation(
|
|
302
|
+
"execute_python",
|
|
303
|
+
{
|
|
304
|
+
"return_code": result.returncode,
|
|
305
|
+
"stdout_length": len(result.stdout),
|
|
306
|
+
"stderr_length": len(result.stderr),
|
|
307
|
+
},
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
return json.dumps(execution_result, ensure_ascii=False, indent=2)
|
|
311
|
+
|
|
312
|
+
finally:
|
|
313
|
+
# Clean up temporary file
|
|
314
|
+
os.unlink(temp_file)
|
|
315
|
+
|
|
316
|
+
except subprocess.TimeoutExpired:
|
|
317
|
+
result = {
|
|
318
|
+
"status": "error",
|
|
319
|
+
"message": f"Python code execution timeout ({timeout}秒)",
|
|
320
|
+
"timeout": timeout,
|
|
321
|
+
}
|
|
322
|
+
log_operation("execute_python_timeout", {"timeout": timeout})
|
|
323
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
324
|
+
|
|
325
|
+
except Exception as e:
|
|
326
|
+
result = {
|
|
327
|
+
"status": "error",
|
|
328
|
+
"message": f"Python code execution failed: {str(e)}",
|
|
329
|
+
}
|
|
330
|
+
log_operation("execute_python_error", {"error": str(e)})
|
|
331
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
@mcp.tool()
|
|
335
|
+
async def execute_bash(command: str, timeout: int = 30) -> str:
|
|
336
|
+
"""
|
|
337
|
+
Execute bash command
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
command: Bash command to execute
|
|
341
|
+
timeout: Timeout in seconds
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
JSON string of execution result
|
|
345
|
+
"""
|
|
346
|
+
try:
|
|
347
|
+
# 安全检查:禁止危险命令
|
|
348
|
+
dangerous_commands = ["rm -rf", "sudo", "chmod 777", "mkfs", "dd if="]
|
|
349
|
+
if any(dangerous in command.lower() for dangerous in dangerous_commands):
|
|
350
|
+
result = {
|
|
351
|
+
"status": "error",
|
|
352
|
+
"message": f"Dangerous command execution prohibited: {command}",
|
|
353
|
+
}
|
|
354
|
+
log_operation(
|
|
355
|
+
"execute_bash_blocked",
|
|
356
|
+
{"command": command, "reason": "dangerous_command"},
|
|
357
|
+
)
|
|
358
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
359
|
+
|
|
360
|
+
# Ensure workspace directory exists
|
|
361
|
+
ensure_workspace_exists()
|
|
362
|
+
|
|
363
|
+
# Execute command
|
|
364
|
+
result = subprocess.run(
|
|
365
|
+
command,
|
|
366
|
+
shell=True,
|
|
367
|
+
cwd=WORKSPACE_DIR,
|
|
368
|
+
capture_output=True,
|
|
369
|
+
text=True,
|
|
370
|
+
timeout=timeout,
|
|
371
|
+
encoding="utf-8",
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
execution_result = {
|
|
375
|
+
"status": "success" if result.returncode == 0 else "error",
|
|
376
|
+
"return_code": result.returncode,
|
|
377
|
+
"stdout": result.stdout,
|
|
378
|
+
"stderr": result.stderr,
|
|
379
|
+
"command": command,
|
|
380
|
+
"timeout": timeout,
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if result.returncode != 0:
|
|
384
|
+
execution_result["message"] = "Bash command execution failed"
|
|
385
|
+
else:
|
|
386
|
+
execution_result["message"] = "Bash command execution successful"
|
|
387
|
+
|
|
388
|
+
log_operation(
|
|
389
|
+
"execute_bash",
|
|
390
|
+
{
|
|
391
|
+
"command": command,
|
|
392
|
+
"return_code": result.returncode,
|
|
393
|
+
"stdout_length": len(result.stdout),
|
|
394
|
+
"stderr_length": len(result.stderr),
|
|
395
|
+
},
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
return json.dumps(execution_result, ensure_ascii=False, indent=2)
|
|
399
|
+
|
|
400
|
+
except subprocess.TimeoutExpired:
|
|
401
|
+
result = {
|
|
402
|
+
"status": "error",
|
|
403
|
+
"message": f"Bash command execution timeout ({timeout} seconds)",
|
|
404
|
+
"command": command,
|
|
405
|
+
"timeout": timeout,
|
|
406
|
+
}
|
|
407
|
+
log_operation("execute_bash_timeout", {"command": command, "timeout": timeout})
|
|
408
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
409
|
+
|
|
410
|
+
except Exception as e:
|
|
411
|
+
result = {
|
|
412
|
+
"status": "error",
|
|
413
|
+
"message": f"Failed to execute bash command: {str(e)}",
|
|
414
|
+
"command": command,
|
|
415
|
+
}
|
|
416
|
+
log_operation("execute_bash_error", {"command": command, "error": str(e)})
|
|
417
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
@mcp.tool()
|
|
421
|
+
async def read_code_mem(file_path: str) -> str:
|
|
422
|
+
"""
|
|
423
|
+
Check if file summary exists in implement_code_summary.md
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
file_path: File path to check for summary information in implement_code_summary.md
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
Summary information if available
|
|
430
|
+
"""
|
|
431
|
+
try:
|
|
432
|
+
if not file_path:
|
|
433
|
+
result = {"status": "error", "message": "file_path parameter is required"}
|
|
434
|
+
log_operation("read_code_mem_error", {"error": "missing_file_path"})
|
|
435
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
436
|
+
|
|
437
|
+
# Ensure workspace exists
|
|
438
|
+
ensure_workspace_exists()
|
|
439
|
+
|
|
440
|
+
# Look for implement_code_summary.md in the workspace
|
|
441
|
+
current_path = Path(WORKSPACE_DIR)
|
|
442
|
+
summary_file_path = current_path.parent / "implement_code_summary.md"
|
|
443
|
+
|
|
444
|
+
if not summary_file_path.exists():
|
|
445
|
+
result = {
|
|
446
|
+
"status": "no_summary",
|
|
447
|
+
"file_path": file_path,
|
|
448
|
+
"message": "No summary file found.",
|
|
449
|
+
# "recommendation": f"read_file(file_path='{file_path}')"
|
|
450
|
+
}
|
|
451
|
+
log_operation(
|
|
452
|
+
"read_code_mem", {"file_path": file_path, "status": "no_summary_file"}
|
|
453
|
+
)
|
|
454
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
455
|
+
|
|
456
|
+
# Read the summary file
|
|
457
|
+
with open(summary_file_path, "r", encoding="utf-8") as f:
|
|
458
|
+
summary_content = f.read()
|
|
459
|
+
|
|
460
|
+
if not summary_content.strip():
|
|
461
|
+
result = {
|
|
462
|
+
"status": "no_summary",
|
|
463
|
+
"file_path": file_path,
|
|
464
|
+
"message": "Summary file is empty.",
|
|
465
|
+
# "recommendation": f"read_file(file_path='{file_path}')"
|
|
466
|
+
}
|
|
467
|
+
log_operation(
|
|
468
|
+
"read_code_mem", {"file_path": file_path, "status": "empty_summary"}
|
|
469
|
+
)
|
|
470
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
471
|
+
|
|
472
|
+
# Extract file-specific section from summary
|
|
473
|
+
file_section = _extract_file_section_from_summary(summary_content, file_path)
|
|
474
|
+
|
|
475
|
+
if file_section:
|
|
476
|
+
result = {
|
|
477
|
+
"status": "summary_found",
|
|
478
|
+
"file_path": file_path,
|
|
479
|
+
"summary_content": file_section,
|
|
480
|
+
"message": f"Summary information found for {file_path} in implement_code_summary.md",
|
|
481
|
+
}
|
|
482
|
+
log_operation(
|
|
483
|
+
"read_code_mem",
|
|
484
|
+
{
|
|
485
|
+
"file_path": file_path,
|
|
486
|
+
"status": "summary_found",
|
|
487
|
+
"section_length": len(file_section),
|
|
488
|
+
},
|
|
489
|
+
)
|
|
490
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
491
|
+
else:
|
|
492
|
+
result = {
|
|
493
|
+
"status": "no_summary",
|
|
494
|
+
"file_path": file_path,
|
|
495
|
+
"message": f"No summary found for {file_path} in implement_code_summary.md",
|
|
496
|
+
# "recommendation": f"Use read_file tool to read the actual file: read_file(file_path='{file_path}')"
|
|
497
|
+
}
|
|
498
|
+
log_operation(
|
|
499
|
+
"read_code_mem", {"file_path": file_path, "status": "no_match"}
|
|
500
|
+
)
|
|
501
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
502
|
+
|
|
503
|
+
except Exception as e:
|
|
504
|
+
result = {
|
|
505
|
+
"status": "error",
|
|
506
|
+
"message": f"Failed to check code memory: {str(e)}",
|
|
507
|
+
"file_path": file_path,
|
|
508
|
+
# "recommendation": "Use read_file tool instead"
|
|
509
|
+
}
|
|
510
|
+
log_operation("read_code_mem_error", {"file_path": file_path, "error": str(e)})
|
|
511
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def _extract_file_section_from_summary(
|
|
515
|
+
summary_content: str, target_file_path: str
|
|
516
|
+
) -> str:
|
|
517
|
+
"""
|
|
518
|
+
Extract the specific section for a file from the summary content
|
|
519
|
+
|
|
520
|
+
Args:
|
|
521
|
+
summary_content: Full summary content
|
|
522
|
+
target_file_path: Path of the target file
|
|
523
|
+
|
|
524
|
+
Returns:
|
|
525
|
+
File-specific section or None if not found
|
|
526
|
+
"""
|
|
527
|
+
import re
|
|
528
|
+
|
|
529
|
+
# Normalize the target path for comparison
|
|
530
|
+
normalized_target = _normalize_file_path(target_file_path)
|
|
531
|
+
|
|
532
|
+
# Pattern to match implementation sections with separator lines
|
|
533
|
+
section_pattern = r"={80}\s*\n## IMPLEMENTATION File ([^;]+); ROUND \d+\s*\n={80}(.*?)(?=\n={80}|\Z)"
|
|
534
|
+
|
|
535
|
+
matches = re.findall(section_pattern, summary_content, re.DOTALL)
|
|
536
|
+
|
|
537
|
+
for file_path_in_summary, section_content in matches:
|
|
538
|
+
file_path_in_summary = file_path_in_summary.strip()
|
|
539
|
+
section_content = section_content.strip()
|
|
540
|
+
|
|
541
|
+
# Normalize the path from summary for comparison
|
|
542
|
+
normalized_summary_path = _normalize_file_path(file_path_in_summary)
|
|
543
|
+
|
|
544
|
+
# Check if paths match using multiple strategies
|
|
545
|
+
if _paths_match(
|
|
546
|
+
normalized_target,
|
|
547
|
+
normalized_summary_path,
|
|
548
|
+
target_file_path,
|
|
549
|
+
file_path_in_summary,
|
|
550
|
+
):
|
|
551
|
+
# Return the complete section with proper formatting
|
|
552
|
+
file_section = f"""================================================================================
|
|
553
|
+
## IMPLEMENTATION File {file_path_in_summary}; ROUND [X]
|
|
554
|
+
================================================================================
|
|
555
|
+
|
|
556
|
+
{section_content}
|
|
557
|
+
|
|
558
|
+
---
|
|
559
|
+
*Extracted from implement_code_summary.md*"""
|
|
560
|
+
return file_section
|
|
561
|
+
|
|
562
|
+
# If no section-based match, try alternative parsing method
|
|
563
|
+
return _extract_file_section_alternative(summary_content, target_file_path)
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def _normalize_file_path(file_path: str) -> str:
|
|
567
|
+
"""Normalize file path for comparison"""
|
|
568
|
+
# Remove leading/trailing slashes and convert to lowercase
|
|
569
|
+
normalized = file_path.strip("/").lower()
|
|
570
|
+
# Replace backslashes with forward slashes
|
|
571
|
+
normalized = normalized.replace("\\", "/")
|
|
572
|
+
|
|
573
|
+
# Remove common prefixes to make matching more flexible
|
|
574
|
+
common_prefixes = ["rice/", "src/", "./rice/", "./src/", "./"]
|
|
575
|
+
for prefix in common_prefixes:
|
|
576
|
+
if normalized.startswith(prefix):
|
|
577
|
+
normalized = normalized[len(prefix) :]
|
|
578
|
+
break
|
|
579
|
+
|
|
580
|
+
return normalized
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
def _paths_match(
|
|
584
|
+
normalized_target: str,
|
|
585
|
+
normalized_summary: str,
|
|
586
|
+
original_target: str,
|
|
587
|
+
original_summary: str,
|
|
588
|
+
) -> bool:
|
|
589
|
+
"""Check if two file paths match using multiple strategies"""
|
|
590
|
+
|
|
591
|
+
# Strategy 1: Exact normalized match
|
|
592
|
+
if normalized_target == normalized_summary:
|
|
593
|
+
return True
|
|
594
|
+
|
|
595
|
+
# Strategy 2: Basename match (filename only)
|
|
596
|
+
target_basename = os.path.basename(original_target)
|
|
597
|
+
summary_basename = os.path.basename(original_summary)
|
|
598
|
+
if target_basename == summary_basename and len(target_basename) > 4:
|
|
599
|
+
return True
|
|
600
|
+
|
|
601
|
+
# Strategy 3: Suffix match (remove common prefixes and compare)
|
|
602
|
+
target_suffix = _remove_common_prefixes(normalized_target)
|
|
603
|
+
summary_suffix = _remove_common_prefixes(normalized_summary)
|
|
604
|
+
if target_suffix == summary_suffix:
|
|
605
|
+
return True
|
|
606
|
+
|
|
607
|
+
# Strategy 4: Ends with match
|
|
608
|
+
if normalized_target.endswith(normalized_summary) or normalized_summary.endswith(
|
|
609
|
+
normalized_target
|
|
610
|
+
):
|
|
611
|
+
return True
|
|
612
|
+
|
|
613
|
+
# Strategy 5: Contains match for longer paths
|
|
614
|
+
if len(normalized_target) > 10 and normalized_target in normalized_summary:
|
|
615
|
+
return True
|
|
616
|
+
if len(normalized_summary) > 10 and normalized_summary in normalized_target:
|
|
617
|
+
return True
|
|
618
|
+
|
|
619
|
+
return False
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def _remove_common_prefixes(file_path: str) -> str:
|
|
623
|
+
"""Remove common prefixes from file path"""
|
|
624
|
+
prefixes_to_remove = ["rice/", "src/", "core/", "./"]
|
|
625
|
+
path = file_path
|
|
626
|
+
|
|
627
|
+
for prefix in prefixes_to_remove:
|
|
628
|
+
if path.startswith(prefix):
|
|
629
|
+
path = path[len(prefix) :]
|
|
630
|
+
|
|
631
|
+
return path
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
def _extract_file_section_alternative(
|
|
635
|
+
summary_content: str, target_file_path: str
|
|
636
|
+
) -> str:
|
|
637
|
+
"""Alternative method to extract file section using simpler pattern matching"""
|
|
638
|
+
|
|
639
|
+
# Get the basename for fallback matching
|
|
640
|
+
target_basename = os.path.basename(target_file_path)
|
|
641
|
+
|
|
642
|
+
# Split by separator lines to get individual sections
|
|
643
|
+
sections = summary_content.split("=" * 80)
|
|
644
|
+
|
|
645
|
+
for i, section in enumerate(sections):
|
|
646
|
+
if "## IMPLEMENTATION File" in section:
|
|
647
|
+
# Extract the file path from the header
|
|
648
|
+
lines = section.strip().split("\n")
|
|
649
|
+
for line in lines:
|
|
650
|
+
if "## IMPLEMENTATION File" in line:
|
|
651
|
+
# Extract file path between "File " and "; ROUND"
|
|
652
|
+
try:
|
|
653
|
+
file_part = line.split("File ")[1].split("; ROUND")[0].strip()
|
|
654
|
+
|
|
655
|
+
# Check if this matches our target
|
|
656
|
+
if (
|
|
657
|
+
_normalize_file_path(target_file_path)
|
|
658
|
+
== _normalize_file_path(file_part)
|
|
659
|
+
or target_basename == os.path.basename(file_part)
|
|
660
|
+
or target_file_path in file_part
|
|
661
|
+
or file_part.endswith(target_file_path)
|
|
662
|
+
):
|
|
663
|
+
# Get the next section which contains the content
|
|
664
|
+
if i + 1 < len(sections):
|
|
665
|
+
content_section = sections[i + 1].strip()
|
|
666
|
+
return f"""================================================================================
|
|
667
|
+
## IMPLEMENTATION File {file_part}
|
|
668
|
+
================================================================================
|
|
669
|
+
|
|
670
|
+
{content_section}
|
|
671
|
+
|
|
672
|
+
---
|
|
673
|
+
*Extracted from implement_code_summary.md using alternative method*"""
|
|
674
|
+
except (IndexError, AttributeError):
|
|
675
|
+
continue
|
|
676
|
+
|
|
677
|
+
return None
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
# ==================== Code Search Tools ====================
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
@mcp.tool()
|
|
684
|
+
async def search_code(
|
|
685
|
+
pattern: str,
|
|
686
|
+
file_pattern: str = "*.json",
|
|
687
|
+
use_regex: bool = False,
|
|
688
|
+
search_directory: str = None,
|
|
689
|
+
) -> str:
|
|
690
|
+
"""
|
|
691
|
+
Search patterns in code files
|
|
692
|
+
|
|
693
|
+
Args:
|
|
694
|
+
pattern: Search pattern
|
|
695
|
+
file_pattern: File pattern (e.g., '*.py')
|
|
696
|
+
use_regex: Whether to use regular expressions
|
|
697
|
+
search_directory: Specify search directory (optional, uses WORKSPACE_DIR if not specified)
|
|
698
|
+
|
|
699
|
+
Returns:
|
|
700
|
+
JSON string of search results
|
|
701
|
+
"""
|
|
702
|
+
try:
|
|
703
|
+
# Determine search directory
|
|
704
|
+
if search_directory:
|
|
705
|
+
# If search directory is specified, use the specified directory
|
|
706
|
+
if os.path.isabs(search_directory):
|
|
707
|
+
search_path = Path(search_directory)
|
|
708
|
+
else:
|
|
709
|
+
# Relative path, relative to current working directory
|
|
710
|
+
search_path = Path.cwd() / search_directory
|
|
711
|
+
else:
|
|
712
|
+
# 如果没有指定Search directory,使用默认的WORKSPACE_DIR
|
|
713
|
+
ensure_workspace_exists()
|
|
714
|
+
search_path = WORKSPACE_DIR
|
|
715
|
+
|
|
716
|
+
# 检查Search directory是否存在
|
|
717
|
+
if not search_path.exists():
|
|
718
|
+
result = {
|
|
719
|
+
"status": "error",
|
|
720
|
+
"message": f"Search directory不存在: {search_path}",
|
|
721
|
+
"pattern": pattern,
|
|
722
|
+
}
|
|
723
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
724
|
+
|
|
725
|
+
import glob
|
|
726
|
+
|
|
727
|
+
# Get matching files
|
|
728
|
+
file_paths = glob.glob(str(search_path / "**" / file_pattern), recursive=True)
|
|
729
|
+
|
|
730
|
+
matches = []
|
|
731
|
+
total_files_searched = 0
|
|
732
|
+
|
|
733
|
+
for file_path in file_paths:
|
|
734
|
+
try:
|
|
735
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
736
|
+
lines = f.readlines()
|
|
737
|
+
|
|
738
|
+
total_files_searched += 1
|
|
739
|
+
relative_path = os.path.relpath(file_path, search_path)
|
|
740
|
+
|
|
741
|
+
for line_num, line in enumerate(lines, 1):
|
|
742
|
+
if use_regex:
|
|
743
|
+
if re.search(pattern, line):
|
|
744
|
+
matches.append(
|
|
745
|
+
{
|
|
746
|
+
"file": relative_path,
|
|
747
|
+
"line_number": line_num,
|
|
748
|
+
"line_content": line.strip(),
|
|
749
|
+
"match_type": "regex",
|
|
750
|
+
}
|
|
751
|
+
)
|
|
752
|
+
else:
|
|
753
|
+
if pattern.lower() in line.lower():
|
|
754
|
+
matches.append(
|
|
755
|
+
{
|
|
756
|
+
"file": relative_path,
|
|
757
|
+
"line_number": line_num,
|
|
758
|
+
"line_content": line.strip(),
|
|
759
|
+
"match_type": "substring",
|
|
760
|
+
}
|
|
761
|
+
)
|
|
762
|
+
|
|
763
|
+
except Exception as e:
|
|
764
|
+
logger.warning(f"Error searching file {file_path}: {e}")
|
|
765
|
+
continue
|
|
766
|
+
|
|
767
|
+
result = {
|
|
768
|
+
"status": "success",
|
|
769
|
+
"pattern": pattern,
|
|
770
|
+
"file_pattern": file_pattern,
|
|
771
|
+
"use_regex": use_regex,
|
|
772
|
+
"search_directory": str(search_path),
|
|
773
|
+
"total_matches": len(matches),
|
|
774
|
+
"total_files_searched": total_files_searched,
|
|
775
|
+
"matches": matches[:50], # 限制返回前50个匹配
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
if len(matches) > 50:
|
|
779
|
+
result["note"] = f"显示前50个匹配,总共找到{len(matches)}个匹配"
|
|
780
|
+
|
|
781
|
+
log_operation(
|
|
782
|
+
"search_code",
|
|
783
|
+
{
|
|
784
|
+
"pattern": pattern,
|
|
785
|
+
"file_pattern": file_pattern,
|
|
786
|
+
"use_regex": use_regex,
|
|
787
|
+
"search_directory": str(search_path),
|
|
788
|
+
"total_matches": len(matches),
|
|
789
|
+
"files_searched": total_files_searched,
|
|
790
|
+
},
|
|
791
|
+
)
|
|
792
|
+
|
|
793
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
794
|
+
|
|
795
|
+
except Exception as e:
|
|
796
|
+
result = {
|
|
797
|
+
"status": "error",
|
|
798
|
+
"message": f"Code search failed: {str(e)}",
|
|
799
|
+
"pattern": pattern,
|
|
800
|
+
}
|
|
801
|
+
log_operation("search_code_error", {"pattern": pattern, "error": str(e)})
|
|
802
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
# ==================== File Structure Tools ====================
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
@mcp.tool()
|
|
809
|
+
async def get_file_structure(directory: str = ".", max_depth: int = 5) -> str:
|
|
810
|
+
"""
|
|
811
|
+
Get directory file structure
|
|
812
|
+
|
|
813
|
+
Args:
|
|
814
|
+
directory: Directory path, relative to workspace
|
|
815
|
+
max_depth: 最大遍历深度
|
|
816
|
+
|
|
817
|
+
Returns:
|
|
818
|
+
JSON string of file structure
|
|
819
|
+
"""
|
|
820
|
+
try:
|
|
821
|
+
ensure_workspace_exists()
|
|
822
|
+
|
|
823
|
+
if directory == ".":
|
|
824
|
+
target_dir = WORKSPACE_DIR
|
|
825
|
+
else:
|
|
826
|
+
target_dir = validate_path(directory)
|
|
827
|
+
|
|
828
|
+
if not target_dir.exists():
|
|
829
|
+
result = {
|
|
830
|
+
"status": "error",
|
|
831
|
+
"message": f"Directory does not exist: {directory}",
|
|
832
|
+
}
|
|
833
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
834
|
+
|
|
835
|
+
def scan_directory(path: Path, current_depth: int = 0) -> Dict[str, Any]:
|
|
836
|
+
"""Recursively scan directory"""
|
|
837
|
+
if current_depth >= max_depth:
|
|
838
|
+
return {"type": "directory", "name": path.name, "truncated": True}
|
|
839
|
+
|
|
840
|
+
items = []
|
|
841
|
+
try:
|
|
842
|
+
for item in sorted(path.iterdir()):
|
|
843
|
+
relative_path = os.path.relpath(item, WORKSPACE_DIR)
|
|
844
|
+
|
|
845
|
+
if item.is_file():
|
|
846
|
+
file_info = {
|
|
847
|
+
"type": "file",
|
|
848
|
+
"name": item.name,
|
|
849
|
+
"path": relative_path,
|
|
850
|
+
"size_bytes": item.stat().st_size,
|
|
851
|
+
"extension": item.suffix,
|
|
852
|
+
}
|
|
853
|
+
items.append(file_info)
|
|
854
|
+
elif item.is_dir() and not item.name.startswith("."):
|
|
855
|
+
dir_info = scan_directory(item, current_depth + 1)
|
|
856
|
+
dir_info["path"] = relative_path
|
|
857
|
+
items.append(dir_info)
|
|
858
|
+
except PermissionError:
|
|
859
|
+
pass
|
|
860
|
+
|
|
861
|
+
return {
|
|
862
|
+
"type": "directory",
|
|
863
|
+
"name": path.name,
|
|
864
|
+
"items": items,
|
|
865
|
+
"item_count": len(items),
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
structure = scan_directory(target_dir)
|
|
869
|
+
|
|
870
|
+
# 统计信息
|
|
871
|
+
def count_items(node):
|
|
872
|
+
if node["type"] == "file":
|
|
873
|
+
return {"files": 1, "directories": 0}
|
|
874
|
+
else:
|
|
875
|
+
counts = {"files": 0, "directories": 1}
|
|
876
|
+
for item in node.get("items", []):
|
|
877
|
+
item_counts = count_items(item)
|
|
878
|
+
counts["files"] += item_counts["files"]
|
|
879
|
+
counts["directories"] += item_counts["directories"]
|
|
880
|
+
return counts
|
|
881
|
+
|
|
882
|
+
counts = count_items(structure)
|
|
883
|
+
|
|
884
|
+
result = {
|
|
885
|
+
"status": "success",
|
|
886
|
+
"directory": directory,
|
|
887
|
+
"max_depth": max_depth,
|
|
888
|
+
"structure": structure,
|
|
889
|
+
"summary": {
|
|
890
|
+
"total_files": counts["files"],
|
|
891
|
+
"total_directories": counts["directories"]
|
|
892
|
+
- 1, # Exclude root directory
|
|
893
|
+
},
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
log_operation(
|
|
897
|
+
"get_file_structure",
|
|
898
|
+
{
|
|
899
|
+
"directory": directory,
|
|
900
|
+
"max_depth": max_depth,
|
|
901
|
+
"total_files": counts["files"],
|
|
902
|
+
"total_directories": counts["directories"] - 1,
|
|
903
|
+
},
|
|
904
|
+
)
|
|
905
|
+
|
|
906
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
907
|
+
|
|
908
|
+
except Exception as e:
|
|
909
|
+
result = {
|
|
910
|
+
"status": "error",
|
|
911
|
+
"message": f"Failed to get file structure: {str(e)}",
|
|
912
|
+
"directory": directory,
|
|
913
|
+
}
|
|
914
|
+
log_operation(
|
|
915
|
+
"get_file_structure_error", {"directory": directory, "error": str(e)}
|
|
916
|
+
)
|
|
917
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
# ==================== Workspace Management Tools ====================
|
|
921
|
+
|
|
922
|
+
|
|
923
|
+
@mcp.tool()
|
|
924
|
+
async def set_workspace(workspace_path: str) -> str:
|
|
925
|
+
"""
|
|
926
|
+
Set workspace directory
|
|
927
|
+
|
|
928
|
+
Called by workflow to set workspace to: {plan_file_parent}/generate_code
|
|
929
|
+
This ensures all file operations are executed relative to the correct project directory
|
|
930
|
+
|
|
931
|
+
Args:
|
|
932
|
+
workspace_path: Workspace path (Usually {plan_file_parent}/generate_code)
|
|
933
|
+
|
|
934
|
+
Returns:
|
|
935
|
+
JSON string of operation result
|
|
936
|
+
"""
|
|
937
|
+
try:
|
|
938
|
+
global WORKSPACE_DIR
|
|
939
|
+
new_workspace = Path(workspace_path).resolve()
|
|
940
|
+
|
|
941
|
+
# Create directory (if it does not exist)
|
|
942
|
+
new_workspace.mkdir(parents=True, exist_ok=True)
|
|
943
|
+
|
|
944
|
+
old_workspace = WORKSPACE_DIR
|
|
945
|
+
WORKSPACE_DIR = new_workspace
|
|
946
|
+
|
|
947
|
+
logger.info(f"New Workspace: {WORKSPACE_DIR}")
|
|
948
|
+
|
|
949
|
+
result = {
|
|
950
|
+
"status": "success",
|
|
951
|
+
"message": f"Workspace setup successful: {workspace_path}",
|
|
952
|
+
"new_workspace": str(WORKSPACE_DIR),
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
log_operation(
|
|
956
|
+
"set_workspace",
|
|
957
|
+
{
|
|
958
|
+
"old_workspace": str(old_workspace) if old_workspace else None,
|
|
959
|
+
"new_workspace": str(WORKSPACE_DIR),
|
|
960
|
+
"workspace_alignment": "plan_file_parent/generate_code",
|
|
961
|
+
},
|
|
962
|
+
)
|
|
963
|
+
|
|
964
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
965
|
+
|
|
966
|
+
except Exception as e:
|
|
967
|
+
result = {
|
|
968
|
+
"status": "error",
|
|
969
|
+
"message": f"Failed to set workspace: {str(e)}",
|
|
970
|
+
"workspace_path": workspace_path,
|
|
971
|
+
}
|
|
972
|
+
log_operation(
|
|
973
|
+
"set_workspace_error", {"workspace_path": workspace_path, "error": str(e)}
|
|
974
|
+
)
|
|
975
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+
@mcp.tool()
|
|
979
|
+
async def get_operation_history(last_n: int = 10) -> str:
|
|
980
|
+
"""
|
|
981
|
+
Get operation history
|
|
982
|
+
|
|
983
|
+
Args:
|
|
984
|
+
last_n: Return the last N operations
|
|
985
|
+
|
|
986
|
+
Returns:
|
|
987
|
+
JSON string of operation history
|
|
988
|
+
"""
|
|
989
|
+
try:
|
|
990
|
+
recent_history = (
|
|
991
|
+
OPERATION_HISTORY[-last_n:] if last_n > 0 else OPERATION_HISTORY
|
|
992
|
+
)
|
|
993
|
+
|
|
994
|
+
result = {
|
|
995
|
+
"status": "success",
|
|
996
|
+
"total_operations": len(OPERATION_HISTORY),
|
|
997
|
+
"returned_operations": len(recent_history),
|
|
998
|
+
"workspace": str(WORKSPACE_DIR) if WORKSPACE_DIR else None,
|
|
999
|
+
"history": recent_history,
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
1003
|
+
|
|
1004
|
+
except Exception as e:
|
|
1005
|
+
result = {
|
|
1006
|
+
"status": "error",
|
|
1007
|
+
"message": f"Failed to get operation history: {str(e)}",
|
|
1008
|
+
}
|
|
1009
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
1010
|
+
|
|
1011
|
+
|
|
1012
|
+
# ==================== Server Initialization ====================
|
|
1013
|
+
|
|
1014
|
+
|
|
1015
|
+
def main():
|
|
1016
|
+
"""Start MCP server"""
|
|
1017
|
+
print("🚀 Code Implementation MCP Server")
|
|
1018
|
+
print(
|
|
1019
|
+
"📝 Paper Code Implementation Tool Server / Paper Code Implementation Tool Server"
|
|
1020
|
+
)
|
|
1021
|
+
print("")
|
|
1022
|
+
print("Available tools / Available tools:")
|
|
1023
|
+
# print(" • read_file - Read file contents / Read file contents")
|
|
1024
|
+
print(
|
|
1025
|
+
" • read_code_mem - Read code summary from implement_code_summary.md / Read code summary from implement_code_summary.md"
|
|
1026
|
+
)
|
|
1027
|
+
print(" • write_file - Write file contents / Write file contents")
|
|
1028
|
+
print(" • execute_python - Execute Python code / Execute Python code")
|
|
1029
|
+
print(" • execute_bash - Execute bash command / Execute bash commands")
|
|
1030
|
+
print(" • search_code - Search code patterns / Search code patterns")
|
|
1031
|
+
print(" • get_file_structure - Get file structure / Get file structure")
|
|
1032
|
+
print(" • set_workspace - Set workspace / Set workspace")
|
|
1033
|
+
print(" • get_operation_history - Get operation history / Get operation history")
|
|
1034
|
+
print("")
|
|
1035
|
+
print("🔧 Server starting...")
|
|
1036
|
+
|
|
1037
|
+
# Initialize default workspace
|
|
1038
|
+
initialize_workspace()
|
|
1039
|
+
|
|
1040
|
+
# Start server
|
|
1041
|
+
mcp.run()
|
|
1042
|
+
|
|
1043
|
+
|
|
1044
|
+
if __name__ == "__main__":
|
|
1045
|
+
main()
|