hanzo-mcp 0.8.8__py3-none-any.whl → 0.9.0__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.

Potentially problematic release.


This version of hanzo-mcp might be problematic. Click here for more details.

Files changed (167) hide show
  1. hanzo_mcp/__init__.py +1 -3
  2. hanzo_mcp/analytics/posthog_analytics.py +4 -17
  3. hanzo_mcp/bridge.py +9 -25
  4. hanzo_mcp/cli.py +8 -17
  5. hanzo_mcp/cli_enhanced.py +5 -14
  6. hanzo_mcp/cli_plugin.py +3 -9
  7. hanzo_mcp/config/settings.py +6 -20
  8. hanzo_mcp/config/tool_config.py +2 -4
  9. hanzo_mcp/core/base_agent.py +88 -88
  10. hanzo_mcp/core/model_registry.py +238 -210
  11. hanzo_mcp/dev_server.py +5 -15
  12. hanzo_mcp/prompts/__init__.py +2 -6
  13. hanzo_mcp/prompts/project_todo_reminder.py +3 -9
  14. hanzo_mcp/prompts/tool_explorer.py +1 -3
  15. hanzo_mcp/prompts/utils.py +7 -21
  16. hanzo_mcp/server.py +6 -7
  17. hanzo_mcp/tools/__init__.py +29 -32
  18. hanzo_mcp/tools/agent/__init__.py +2 -1
  19. hanzo_mcp/tools/agent/agent.py +10 -30
  20. hanzo_mcp/tools/agent/agent_tool.py +23 -17
  21. hanzo_mcp/tools/agent/claude_desktop_auth.py +3 -9
  22. hanzo_mcp/tools/agent/cli_agent_base.py +7 -24
  23. hanzo_mcp/tools/agent/cli_tools.py +76 -75
  24. hanzo_mcp/tools/agent/code_auth.py +1 -3
  25. hanzo_mcp/tools/agent/code_auth_tool.py +2 -6
  26. hanzo_mcp/tools/agent/critic_tool.py +8 -24
  27. hanzo_mcp/tools/agent/iching_tool.py +12 -36
  28. hanzo_mcp/tools/agent/network_tool.py +7 -18
  29. hanzo_mcp/tools/agent/prompt.py +1 -5
  30. hanzo_mcp/tools/agent/review_tool.py +10 -25
  31. hanzo_mcp/tools/agent/swarm_alias.py +1 -3
  32. hanzo_mcp/tools/agent/unified_cli_tools.py +38 -38
  33. hanzo_mcp/tools/common/batch_tool.py +15 -45
  34. hanzo_mcp/tools/common/config_tool.py +9 -28
  35. hanzo_mcp/tools/common/context.py +1 -3
  36. hanzo_mcp/tools/common/critic_tool.py +1 -3
  37. hanzo_mcp/tools/common/decorators.py +2 -6
  38. hanzo_mcp/tools/common/enhanced_base.py +2 -6
  39. hanzo_mcp/tools/common/fastmcp_pagination.py +4 -12
  40. hanzo_mcp/tools/common/forgiving_edit.py +9 -28
  41. hanzo_mcp/tools/common/mode.py +1 -5
  42. hanzo_mcp/tools/common/paginated_base.py +3 -11
  43. hanzo_mcp/tools/common/paginated_response.py +10 -30
  44. hanzo_mcp/tools/common/pagination.py +3 -9
  45. hanzo_mcp/tools/common/path_utils.py +34 -0
  46. hanzo_mcp/tools/common/permissions.py +14 -13
  47. hanzo_mcp/tools/common/personality.py +983 -701
  48. hanzo_mcp/tools/common/plugin_loader.py +3 -15
  49. hanzo_mcp/tools/common/stats.py +7 -19
  50. hanzo_mcp/tools/common/thinking_tool.py +1 -3
  51. hanzo_mcp/tools/common/tool_disable.py +2 -6
  52. hanzo_mcp/tools/common/tool_list.py +2 -6
  53. hanzo_mcp/tools/common/validation.py +1 -3
  54. hanzo_mcp/tools/compiler/__init__.py +8 -0
  55. hanzo_mcp/tools/compiler/sandboxed_compiler.py +681 -0
  56. hanzo_mcp/tools/config/config_tool.py +7 -13
  57. hanzo_mcp/tools/config/index_config.py +1 -3
  58. hanzo_mcp/tools/config/mode_tool.py +5 -15
  59. hanzo_mcp/tools/database/database_manager.py +3 -9
  60. hanzo_mcp/tools/database/graph.py +1 -3
  61. hanzo_mcp/tools/database/graph_add.py +3 -9
  62. hanzo_mcp/tools/database/graph_query.py +11 -34
  63. hanzo_mcp/tools/database/graph_remove.py +3 -9
  64. hanzo_mcp/tools/database/graph_search.py +6 -20
  65. hanzo_mcp/tools/database/graph_stats.py +11 -33
  66. hanzo_mcp/tools/database/sql.py +4 -12
  67. hanzo_mcp/tools/database/sql_query.py +6 -10
  68. hanzo_mcp/tools/database/sql_search.py +2 -6
  69. hanzo_mcp/tools/database/sql_stats.py +5 -15
  70. hanzo_mcp/tools/editor/neovim_command.py +1 -3
  71. hanzo_mcp/tools/editor/neovim_session.py +7 -13
  72. hanzo_mcp/tools/environment/__init__.py +8 -0
  73. hanzo_mcp/tools/environment/environment_detector.py +594 -0
  74. hanzo_mcp/tools/filesystem/__init__.py +28 -26
  75. hanzo_mcp/tools/filesystem/ast_multi_edit.py +14 -43
  76. hanzo_mcp/tools/filesystem/ast_tool.py +3 -0
  77. hanzo_mcp/tools/filesystem/base.py +20 -12
  78. hanzo_mcp/tools/filesystem/content_replace.py +7 -12
  79. hanzo_mcp/tools/filesystem/diff.py +2 -10
  80. hanzo_mcp/tools/filesystem/directory_tree.py +285 -51
  81. hanzo_mcp/tools/filesystem/edit.py +10 -18
  82. hanzo_mcp/tools/filesystem/find.py +312 -179
  83. hanzo_mcp/tools/filesystem/git_search.py +12 -24
  84. hanzo_mcp/tools/filesystem/multi_edit.py +10 -18
  85. hanzo_mcp/tools/filesystem/read.py +14 -30
  86. hanzo_mcp/tools/filesystem/rules_tool.py +9 -17
  87. hanzo_mcp/tools/filesystem/search.py +1160 -0
  88. hanzo_mcp/tools/filesystem/watch.py +2 -4
  89. hanzo_mcp/tools/filesystem/write.py +7 -10
  90. hanzo_mcp/tools/framework/__init__.py +8 -0
  91. hanzo_mcp/tools/framework/framework_modes.py +714 -0
  92. hanzo_mcp/tools/jupyter/base.py +6 -20
  93. hanzo_mcp/tools/jupyter/jupyter.py +4 -12
  94. hanzo_mcp/tools/llm/consensus_tool.py +8 -24
  95. hanzo_mcp/tools/llm/llm_manage.py +2 -6
  96. hanzo_mcp/tools/llm/llm_tool.py +17 -58
  97. hanzo_mcp/tools/llm/llm_unified.py +18 -59
  98. hanzo_mcp/tools/llm/provider_tools.py +1 -3
  99. hanzo_mcp/tools/lsp/lsp_tool.py +621 -481
  100. hanzo_mcp/tools/mcp/mcp_add.py +3 -5
  101. hanzo_mcp/tools/mcp/mcp_remove.py +1 -1
  102. hanzo_mcp/tools/mcp/mcp_stats.py +1 -3
  103. hanzo_mcp/tools/mcp/mcp_tool.py +9 -23
  104. hanzo_mcp/tools/memory/__init__.py +33 -40
  105. hanzo_mcp/tools/memory/conversation_memory.py +636 -0
  106. hanzo_mcp/tools/memory/knowledge_tools.py +7 -25
  107. hanzo_mcp/tools/memory/memory_tools.py +7 -19
  108. hanzo_mcp/tools/search/find_tool.py +12 -34
  109. hanzo_mcp/tools/search/unified_search.py +27 -81
  110. hanzo_mcp/tools/shell/__init__.py +16 -4
  111. hanzo_mcp/tools/shell/auto_background.py +2 -6
  112. hanzo_mcp/tools/shell/base.py +1 -5
  113. hanzo_mcp/tools/shell/base_process.py +5 -7
  114. hanzo_mcp/tools/shell/bash_session.py +7 -24
  115. hanzo_mcp/tools/shell/bash_session_executor.py +5 -15
  116. hanzo_mcp/tools/shell/bash_tool.py +3 -7
  117. hanzo_mcp/tools/shell/command_executor.py +26 -79
  118. hanzo_mcp/tools/shell/logs.py +4 -16
  119. hanzo_mcp/tools/shell/npx.py +2 -8
  120. hanzo_mcp/tools/shell/npx_tool.py +1 -3
  121. hanzo_mcp/tools/shell/pkill.py +4 -12
  122. hanzo_mcp/tools/shell/process_tool.py +2 -8
  123. hanzo_mcp/tools/shell/processes.py +5 -17
  124. hanzo_mcp/tools/shell/run_background.py +1 -3
  125. hanzo_mcp/tools/shell/run_command.py +1 -3
  126. hanzo_mcp/tools/shell/run_command_windows.py +1 -3
  127. hanzo_mcp/tools/shell/run_tool.py +56 -0
  128. hanzo_mcp/tools/shell/session_manager.py +2 -6
  129. hanzo_mcp/tools/shell/session_storage.py +2 -6
  130. hanzo_mcp/tools/shell/streaming_command.py +7 -23
  131. hanzo_mcp/tools/shell/uvx.py +4 -14
  132. hanzo_mcp/tools/shell/uvx_background.py +2 -6
  133. hanzo_mcp/tools/shell/uvx_tool.py +1 -3
  134. hanzo_mcp/tools/shell/zsh_tool.py +12 -20
  135. hanzo_mcp/tools/todo/todo.py +1 -3
  136. hanzo_mcp/tools/vector/__init__.py +97 -50
  137. hanzo_mcp/tools/vector/ast_analyzer.py +6 -20
  138. hanzo_mcp/tools/vector/git_ingester.py +10 -30
  139. hanzo_mcp/tools/vector/index_tool.py +3 -9
  140. hanzo_mcp/tools/vector/infinity_store.py +11 -30
  141. hanzo_mcp/tools/vector/mock_infinity.py +159 -0
  142. hanzo_mcp/tools/vector/node_tool.py +538 -0
  143. hanzo_mcp/tools/vector/project_manager.py +4 -12
  144. hanzo_mcp/tools/vector/unified_vector.py +384 -0
  145. hanzo_mcp/tools/vector/vector.py +2 -6
  146. hanzo_mcp/tools/vector/vector_index.py +8 -8
  147. hanzo_mcp/tools/vector/vector_search.py +7 -21
  148. {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/METADATA +2 -2
  149. hanzo_mcp-0.9.0.dist-info/RECORD +191 -0
  150. hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +0 -645
  151. hanzo_mcp/tools/agent/swarm_tool.py +0 -723
  152. hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +0 -577
  153. hanzo_mcp/tools/filesystem/batch_search.py +0 -900
  154. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +0 -350
  155. hanzo_mcp/tools/filesystem/find_files.py +0 -369
  156. hanzo_mcp/tools/filesystem/grep.py +0 -467
  157. hanzo_mcp/tools/filesystem/search_tool.py +0 -767
  158. hanzo_mcp/tools/filesystem/symbols_tool.py +0 -515
  159. hanzo_mcp/tools/filesystem/tree.py +0 -270
  160. hanzo_mcp/tools/jupyter/notebook_edit.py +0 -317
  161. hanzo_mcp/tools/jupyter/notebook_read.py +0 -147
  162. hanzo_mcp/tools/todo/todo_read.py +0 -143
  163. hanzo_mcp/tools/todo/todo_write.py +0 -374
  164. hanzo_mcp-0.8.8.dist-info/RECORD +0 -192
  165. {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/WHEEL +0 -0
  166. {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/entry_points.txt +0 -0
  167. {hanzo_mcp-0.8.8.dist-info → hanzo_mcp-0.9.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,681 @@
1
+ """Sandboxed multi-language compiler and execution tool.
2
+
3
+ This tool provides safe compilation and execution of code in multiple
4
+ languages with sandboxing, resource limits, and timeout controls.
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import json
10
+ import asyncio
11
+ import tempfile
12
+ import shutil
13
+ import logging
14
+ import resource
15
+ import signal
16
+ from typing import Dict, Any, Optional, List, Tuple
17
+ from pathlib import Path
18
+ from dataclasses import dataclass
19
+ from enum import Enum
20
+
21
+ from hanzo_mcp.types import MCPResourceDocument
22
+ from hanzo_mcp.tools.common.base import BaseTool
23
+
24
+
25
+ class Language(Enum):
26
+ """Supported programming languages."""
27
+ C = "c"
28
+ CPP = "cpp"
29
+ GO = "go"
30
+ JAVASCRIPT = "javascript"
31
+ TYPESCRIPT = "typescript"
32
+ RUST = "rust"
33
+ ZIG = "zig"
34
+ PYTHON = "python"
35
+ JAVA = "java"
36
+ KOTLIN = "kotlin"
37
+ SWIFT = "swift"
38
+ RUBY = "ruby"
39
+ PHP = "php"
40
+
41
+
42
+ @dataclass
43
+ class CompilerConfig:
44
+ """Compiler configuration for a language."""
45
+ language: Language
46
+ compiler_cmd: List[str]
47
+ run_cmd: List[str]
48
+ file_extension: str
49
+ needs_compilation: bool
50
+ docker_image: Optional[str] = None
51
+ sandbox_cmd: Optional[List[str]] = None
52
+
53
+
54
+ # Language compiler configurations
55
+ COMPILER_CONFIGS = {
56
+ Language.C: CompilerConfig(
57
+ language=Language.C,
58
+ compiler_cmd=["clang", "-O2", "-o", "{output}", "{input}"],
59
+ run_cmd=["./{output}"],
60
+ file_extension=".c",
61
+ needs_compilation=True,
62
+ docker_image="gcc:latest",
63
+ sandbox_cmd=["docker", "run", "--rm", "-v", "{mount}:/code", "gcc:latest"],
64
+ ),
65
+
66
+ Language.CPP: CompilerConfig(
67
+ language=Language.CPP,
68
+ compiler_cmd=["clang++", "-std=c++17", "-O2", "-o", "{output}", "{input}"],
69
+ run_cmd=["./{output}"],
70
+ file_extension=".cpp",
71
+ needs_compilation=True,
72
+ docker_image="gcc:latest",
73
+ sandbox_cmd=["docker", "run", "--rm", "-v", "{mount}:/code", "gcc:latest"],
74
+ ),
75
+
76
+ Language.GO: CompilerConfig(
77
+ language=Language.GO,
78
+ compiler_cmd=["go", "build", "-o", "{output}", "{input}"],
79
+ run_cmd=["./{output}"],
80
+ file_extension=".go",
81
+ needs_compilation=True,
82
+ docker_image="golang:latest",
83
+ sandbox_cmd=["docker", "run", "--rm", "-v", "{mount}:/code", "golang:latest"],
84
+ ),
85
+
86
+ Language.JAVASCRIPT: CompilerConfig(
87
+ language=Language.JAVASCRIPT,
88
+ compiler_cmd=[],
89
+ run_cmd=["node", "{input}"],
90
+ file_extension=".js",
91
+ needs_compilation=False,
92
+ docker_image="node:latest",
93
+ sandbox_cmd=["docker", "run", "--rm", "-v", "{mount}:/code", "node:latest"],
94
+ ),
95
+
96
+ Language.TYPESCRIPT: CompilerConfig(
97
+ language=Language.TYPESCRIPT,
98
+ compiler_cmd=["tsc", "{input}", "--outFile", "{output}.js"],
99
+ run_cmd=["node", "{output}.js"],
100
+ file_extension=".ts",
101
+ needs_compilation=True,
102
+ docker_image="node:latest",
103
+ sandbox_cmd=["docker", "run", "--rm", "-v", "{mount}:/code", "node:latest"],
104
+ ),
105
+
106
+ Language.RUST: CompilerConfig(
107
+ language=Language.RUST,
108
+ compiler_cmd=["rustc", "-O", "-o", "{output}", "{input}"],
109
+ run_cmd=["./{output}"],
110
+ file_extension=".rs",
111
+ needs_compilation=True,
112
+ docker_image="rust:latest",
113
+ sandbox_cmd=["docker", "run", "--rm", "-v", "{mount}:/code", "rust:latest"],
114
+ ),
115
+
116
+ Language.ZIG: CompilerConfig(
117
+ language=Language.ZIG,
118
+ compiler_cmd=["zig", "build-exe", "{input}", "-femit-bin={output}"],
119
+ run_cmd=["./{output}"],
120
+ file_extension=".zig",
121
+ needs_compilation=True,
122
+ docker_image="euantorano/zig:latest",
123
+ sandbox_cmd=["docker", "run", "--rm", "-v", "{mount}:/code", "euantorano/zig:latest"],
124
+ ),
125
+
126
+ Language.PYTHON: CompilerConfig(
127
+ language=Language.PYTHON,
128
+ compiler_cmd=[],
129
+ run_cmd=["python", "{input}"],
130
+ file_extension=".py",
131
+ needs_compilation=False,
132
+ docker_image="python:latest",
133
+ sandbox_cmd=["docker", "run", "--rm", "-v", "{mount}:/code", "python:latest"],
134
+ ),
135
+
136
+ Language.JAVA: CompilerConfig(
137
+ language=Language.JAVA,
138
+ compiler_cmd=["javac", "{input}"],
139
+ run_cmd=["java", "{classname}"],
140
+ file_extension=".java",
141
+ needs_compilation=True,
142
+ docker_image="openjdk:latest",
143
+ sandbox_cmd=["docker", "run", "--rm", "-v", "{mount}:/code", "openjdk:latest"],
144
+ ),
145
+
146
+ Language.RUBY: CompilerConfig(
147
+ language=Language.RUBY,
148
+ compiler_cmd=[],
149
+ run_cmd=["ruby", "{input}"],
150
+ file_extension=".rb",
151
+ needs_compilation=False,
152
+ docker_image="ruby:latest",
153
+ sandbox_cmd=["docker", "run", "--rm", "-v", "{mount}:/code", "ruby:latest"],
154
+ ),
155
+ }
156
+
157
+
158
+ class SandboxExecutor:
159
+ """Executes code in a sandboxed environment."""
160
+
161
+ def __init__(self, use_docker: bool = False):
162
+ self.use_docker = use_docker
163
+ self.logger = logging.getLogger(__name__)
164
+
165
+ async def execute(
166
+ self,
167
+ cmd: List[str],
168
+ cwd: str,
169
+ timeout: int = 10,
170
+ memory_limit: int = 256 * 1024 * 1024, # 256 MB
171
+ stdin_data: Optional[str] = None,
172
+ ) -> Tuple[int, str, str]:
173
+ """Execute command with sandboxing."""
174
+
175
+ if self.use_docker:
176
+ return await self._execute_docker(cmd, cwd, timeout, stdin_data)
177
+ else:
178
+ return await self._execute_native(cmd, cwd, timeout, memory_limit, stdin_data)
179
+
180
+ async def _execute_native(
181
+ self,
182
+ cmd: List[str],
183
+ cwd: str,
184
+ timeout: int,
185
+ memory_limit: int,
186
+ stdin_data: Optional[str],
187
+ ) -> Tuple[int, str, str]:
188
+ """Execute with native sandboxing."""
189
+
190
+ # Create process with resource limits
191
+ try:
192
+ process = await asyncio.create_subprocess_exec(
193
+ *cmd,
194
+ stdin=asyncio.subprocess.PIPE if stdin_data else None,
195
+ stdout=asyncio.subprocess.PIPE,
196
+ stderr=asyncio.subprocess.PIPE,
197
+ cwd=cwd,
198
+ preexec_fn=lambda: self._set_limits(memory_limit) if sys.platform != "win32" else None,
199
+ )
200
+
201
+ # Send input and get output
202
+ stdout, stderr = await asyncio.wait_for(
203
+ process.communicate(stdin_data.encode() if stdin_data else None),
204
+ timeout=timeout,
205
+ )
206
+
207
+ return process.returncode, stdout.decode(), stderr.decode()
208
+
209
+ except asyncio.TimeoutError:
210
+ if process:
211
+ process.kill()
212
+ await process.wait()
213
+ return -1, "", "Execution timed out"
214
+
215
+ except Exception as e:
216
+ return -1, "", str(e)
217
+
218
+ def _set_limits(self, memory_limit: int):
219
+ """Set resource limits for process."""
220
+ # Memory limit
221
+ resource.setrlimit(resource.RLIMIT_AS, (memory_limit, memory_limit))
222
+
223
+ # CPU time limit (10 seconds)
224
+ resource.setrlimit(resource.RLIMIT_CPU, (10, 10))
225
+
226
+ # No file creation
227
+ resource.setrlimit(resource.RLIMIT_FSIZE, (0, 0))
228
+
229
+ # Limited number of processes
230
+ resource.setrlimit(resource.RLIMIT_NPROC, (10, 10))
231
+
232
+ async def _execute_docker(
233
+ self,
234
+ cmd: List[str],
235
+ cwd: str,
236
+ timeout: int,
237
+ stdin_data: Optional[str],
238
+ ) -> Tuple[int, str, str]:
239
+ """Execute with Docker sandboxing."""
240
+
241
+ # Docker execution with limits
242
+ docker_cmd = [
243
+ "docker", "run",
244
+ "--rm",
245
+ "--network", "none", # No network access
246
+ "--memory", "256m", # Memory limit
247
+ "--cpus", "0.5", # CPU limit
248
+ "--read-only", # Read-only filesystem
249
+ "-v", f"{cwd}:/code:ro", # Mount code directory as read-only
250
+ "-w", "/code",
251
+ "--timeout", str(timeout),
252
+ ]
253
+
254
+ # Add the actual command
255
+ docker_cmd.extend(cmd)
256
+
257
+ try:
258
+ process = await asyncio.create_subprocess_exec(
259
+ *docker_cmd,
260
+ stdin=asyncio.subprocess.PIPE if stdin_data else None,
261
+ stdout=asyncio.subprocess.PIPE,
262
+ stderr=asyncio.subprocess.PIPE,
263
+ )
264
+
265
+ stdout, stderr = await asyncio.wait_for(
266
+ process.communicate(stdin_data.encode() if stdin_data else None),
267
+ timeout=timeout + 2, # Give Docker a bit more time
268
+ )
269
+
270
+ return process.returncode, stdout.decode(), stderr.decode()
271
+
272
+ except asyncio.TimeoutError:
273
+ return -1, "", "Docker execution timed out"
274
+
275
+ except Exception as e:
276
+ return -1, "", str(e)
277
+
278
+
279
+ class SandboxedCompiler(BaseTool):
280
+ """Sandboxed compiler and execution tool."""
281
+
282
+ name = "compile"
283
+ description = """Compile and run code in multiple languages with sandboxing.
284
+
285
+ Actions:
286
+ - compile: Compile source code
287
+ - run: Compile and run code
288
+ - evaluate: Evaluate expression
289
+ - test: Run with test cases
290
+ - benchmark: Run performance benchmark
291
+
292
+ Supported languages: C, C++, Go, JavaScript, TypeScript, Rust, Zig, Python, Java, Ruby
293
+
294
+ Safety features:
295
+ - Memory limits (256MB default)
296
+ - CPU time limits (10s default)
297
+ - No network access in sandbox mode
298
+ - Read-only filesystem in Docker mode
299
+ """
300
+
301
+ def __init__(self, use_docker: bool = False):
302
+ super().__init__()
303
+ self.logger = logging.getLogger(__name__)
304
+ self.executor = SandboxExecutor(use_docker)
305
+ self.use_docker = use_docker
306
+
307
+ def _detect_language(self, code: str, language: Optional[str] = None) -> Optional[Language]:
308
+ """Detect language from code or hint."""
309
+
310
+ if language:
311
+ # Try to match language string
312
+ for lang in Language:
313
+ if language.lower() in [lang.value, lang.name.lower()]:
314
+ return lang
315
+
316
+ # Auto-detect from code patterns
317
+ patterns = {
318
+ Language.C: ["#include <stdio.h>", "int main("],
319
+ Language.CPP: ["#include <iostream>", "using namespace std", "std::"],
320
+ Language.GO: ["package main", "func main()", "import ("],
321
+ Language.JAVASCRIPT: ["console.log", "function ", "const ", "let ", "var "],
322
+ Language.TYPESCRIPT: ["interface ", "type ", ": string", ": number"],
323
+ Language.RUST: ["fn main()", "let mut ", "impl ", "trait "],
324
+ Language.ZIG: ["pub fn main()", "const std ="],
325
+ Language.PYTHON: ["def ", "import ", "from ", "print("],
326
+ Language.JAVA: ["public class ", "public static void main"],
327
+ Language.RUBY: ["puts ", "def ", "class ", "require "],
328
+ }
329
+
330
+ for lang, markers in patterns.items():
331
+ if any(marker in code for marker in markers):
332
+ return lang
333
+
334
+ return None
335
+
336
+ async def _compile_code(
337
+ self,
338
+ code: str,
339
+ language: Language,
340
+ output_dir: Path,
341
+ ) -> Tuple[bool, str, str]:
342
+ """Compile source code."""
343
+
344
+ config = COMPILER_CONFIGS[language]
345
+
346
+ # Write source file
347
+ source_file = output_dir / f"main{config.file_extension}"
348
+ source_file.write_text(code)
349
+
350
+ if not config.needs_compilation:
351
+ return True, "", ""
352
+
353
+ # Prepare compile command
354
+ output_file = output_dir / "program"
355
+ compile_cmd = [
356
+ part.replace("{input}", str(source_file))
357
+ .replace("{output}", str(output_file))
358
+ for part in config.compiler_cmd
359
+ ]
360
+
361
+ # Compile
362
+ returncode, stdout, stderr = await self.executor.execute(
363
+ compile_cmd,
364
+ str(output_dir),
365
+ timeout=30, # Longer timeout for compilation
366
+ )
367
+
368
+ return returncode == 0, stdout, stderr
369
+
370
+ async def _run_code(
371
+ self,
372
+ language: Language,
373
+ output_dir: Path,
374
+ stdin_data: Optional[str] = None,
375
+ timeout: int = 10,
376
+ ) -> Tuple[int, str, str]:
377
+ """Run compiled code."""
378
+
379
+ config = COMPILER_CONFIGS[language]
380
+
381
+ if config.needs_compilation:
382
+ output_file = "program"
383
+ else:
384
+ output_file = f"main{config.file_extension}"
385
+
386
+ # Prepare run command
387
+ run_cmd = [
388
+ part.replace("{input}", output_file)
389
+ .replace("{output}", output_file)
390
+ .replace("{classname}", "main") # For Java
391
+ for part in config.run_cmd
392
+ ]
393
+
394
+ # Run
395
+ return await self.executor.execute(
396
+ run_cmd,
397
+ str(output_dir),
398
+ timeout=timeout,
399
+ stdin_data=stdin_data,
400
+ )
401
+
402
+ async def _run_test_cases(
403
+ self,
404
+ code: str,
405
+ language: Language,
406
+ test_cases: List[Dict[str, str]],
407
+ ) -> List[Dict[str, Any]]:
408
+ """Run code with multiple test cases."""
409
+
410
+ results = []
411
+
412
+ with tempfile.TemporaryDirectory() as tmpdir:
413
+ output_dir = Path(tmpdir)
414
+
415
+ # Compile once
416
+ success, compile_out, compile_err = await self._compile_code(
417
+ code, language, output_dir
418
+ )
419
+
420
+ if not success:
421
+ return [{
422
+ "error": "Compilation failed",
423
+ "stdout": compile_out,
424
+ "stderr": compile_err,
425
+ }]
426
+
427
+ # Run test cases
428
+ for i, test_case in enumerate(test_cases):
429
+ stdin = test_case.get("input", "")
430
+ expected = test_case.get("expected", "")
431
+
432
+ returncode, stdout, stderr = await self._run_code(
433
+ language, output_dir, stdin
434
+ )
435
+
436
+ # Check result
437
+ passed = (
438
+ returncode == 0 and
439
+ (not expected or stdout.strip() == expected.strip())
440
+ )
441
+
442
+ results.append({
443
+ "test_case": i + 1,
444
+ "input": stdin,
445
+ "expected": expected,
446
+ "actual": stdout.strip(),
447
+ "stderr": stderr,
448
+ "passed": passed,
449
+ "exit_code": returncode,
450
+ })
451
+
452
+ return results
453
+
454
+ async def _benchmark_code(
455
+ self,
456
+ code: str,
457
+ language: Language,
458
+ iterations: int = 10,
459
+ ) -> Dict[str, Any]:
460
+ """Benchmark code performance."""
461
+
462
+ import time
463
+
464
+ with tempfile.TemporaryDirectory() as tmpdir:
465
+ output_dir = Path(tmpdir)
466
+
467
+ # Compile
468
+ success, compile_out, compile_err = await self._compile_code(
469
+ code, language, output_dir
470
+ )
471
+
472
+ if not success:
473
+ return {
474
+ "error": "Compilation failed",
475
+ "stdout": compile_out,
476
+ "stderr": compile_err,
477
+ }
478
+
479
+ # Run multiple times
480
+ times = []
481
+ for _ in range(iterations):
482
+ start = time.perf_counter()
483
+ returncode, stdout, stderr = await self._run_code(
484
+ language, output_dir
485
+ )
486
+ elapsed = time.perf_counter() - start
487
+
488
+ if returncode != 0:
489
+ return {
490
+ "error": "Execution failed",
491
+ "stdout": stdout,
492
+ "stderr": stderr,
493
+ }
494
+
495
+ times.append(elapsed)
496
+
497
+ # Calculate statistics
498
+ avg_time = sum(times) / len(times)
499
+ min_time = min(times)
500
+ max_time = max(times)
501
+
502
+ return {
503
+ "iterations": iterations,
504
+ "average_time": avg_time,
505
+ "min_time": min_time,
506
+ "max_time": max_time,
507
+ "times": times,
508
+ "unit": "seconds",
509
+ }
510
+
511
+ async def run(
512
+ self,
513
+ action: str,
514
+ code: str,
515
+ language: Optional[str] = None,
516
+ input: Optional[str] = None,
517
+ test_cases: Optional[List[Dict[str, str]]] = None,
518
+ timeout: int = 10,
519
+ iterations: int = 10,
520
+ **kwargs,
521
+ ) -> MCPResourceDocument:
522
+ """Execute compiler action."""
523
+
524
+ # Detect language
525
+ lang = self._detect_language(code, language)
526
+
527
+ if not lang:
528
+ return MCPResourceDocument(
529
+ data={
530
+ "error": "Could not detect language",
531
+ "hint": "Specify language or use clearer code patterns",
532
+ "supported_languages": [l.value for l in Language],
533
+ }
534
+ )
535
+
536
+ if action == "compile":
537
+ # Compile only
538
+ with tempfile.TemporaryDirectory() as tmpdir:
539
+ output_dir = Path(tmpdir)
540
+ success, stdout, stderr = await self._compile_code(
541
+ code, lang, output_dir
542
+ )
543
+
544
+ return MCPResourceDocument(
545
+ data={
546
+ "success": success,
547
+ "language": lang.value,
548
+ "stdout": stdout,
549
+ "stderr": stderr,
550
+ }
551
+ )
552
+
553
+ elif action == "run":
554
+ # Compile and run
555
+ with tempfile.TemporaryDirectory() as tmpdir:
556
+ output_dir = Path(tmpdir)
557
+
558
+ # Compile
559
+ success, compile_out, compile_err = await self._compile_code(
560
+ code, lang, output_dir
561
+ )
562
+
563
+ if not success:
564
+ return MCPResourceDocument(
565
+ data={
566
+ "success": False,
567
+ "language": lang.value,
568
+ "phase": "compilation",
569
+ "stdout": compile_out,
570
+ "stderr": compile_err,
571
+ }
572
+ )
573
+
574
+ # Run
575
+ returncode, stdout, stderr = await self._run_code(
576
+ lang, output_dir, input, timeout
577
+ )
578
+
579
+ return MCPResourceDocument(
580
+ data={
581
+ "success": returncode == 0,
582
+ "language": lang.value,
583
+ "exit_code": returncode,
584
+ "stdout": stdout,
585
+ "stderr": stderr,
586
+ "compile_output": compile_out,
587
+ }
588
+ )
589
+
590
+ elif action == "evaluate":
591
+ # Quick evaluation (for expressions)
592
+ # Wrap code in minimal boilerplate
593
+ if lang == Language.PYTHON:
594
+ wrapped = f"print({code})"
595
+ elif lang == Language.JAVASCRIPT:
596
+ wrapped = f"console.log({code})"
597
+ elif lang == Language.GO:
598
+ wrapped = f"package main\nimport \"fmt\"\nfunc main() {{ fmt.Println({code}) }}"
599
+ else:
600
+ wrapped = code
601
+
602
+ with tempfile.TemporaryDirectory() as tmpdir:
603
+ output_dir = Path(tmpdir)
604
+
605
+ success, compile_out, compile_err = await self._compile_code(
606
+ wrapped, lang, output_dir
607
+ )
608
+
609
+ if not success:
610
+ return MCPResourceDocument(
611
+ data={
612
+ "success": False,
613
+ "language": lang.value,
614
+ "error": compile_err,
615
+ }
616
+ )
617
+
618
+ returncode, stdout, stderr = await self._run_code(
619
+ lang, output_dir, timeout=5
620
+ )
621
+
622
+ return MCPResourceDocument(
623
+ data={
624
+ "success": returncode == 0,
625
+ "language": lang.value,
626
+ "result": stdout.strip(),
627
+ "stderr": stderr,
628
+ }
629
+ )
630
+
631
+ elif action == "test":
632
+ # Run with test cases
633
+ if not test_cases:
634
+ return MCPResourceDocument(
635
+ data={"error": "Test cases required for test action"}
636
+ )
637
+
638
+ results = await self._run_test_cases(code, lang, test_cases)
639
+
640
+ passed = sum(1 for r in results if r.get("passed", False))
641
+ total = len(results)
642
+
643
+ return MCPResourceDocument(
644
+ data={
645
+ "language": lang.value,
646
+ "passed": passed,
647
+ "total": total,
648
+ "success": passed == total,
649
+ "results": results,
650
+ }
651
+ )
652
+
653
+ elif action == "benchmark":
654
+ # Benchmark performance
655
+ result = await self._benchmark_code(code, lang, iterations)
656
+
657
+ return MCPResourceDocument(
658
+ data={
659
+ "language": lang.value,
660
+ **result,
661
+ }
662
+ )
663
+
664
+ else:
665
+ return MCPResourceDocument(
666
+ data={
667
+ "error": f"Unknown action: {action}",
668
+ "valid_actions": ["compile", "run", "evaluate", "test", "benchmark"],
669
+ }
670
+ )
671
+
672
+ async def call(self, **kwargs) -> str:
673
+ """Tool interface for MCP."""
674
+ result = await self.run(**kwargs)
675
+ return result.to_json_string()
676
+
677
+
678
+ # Factory function
679
+ def create_sandboxed_compiler(use_docker: bool = False):
680
+ """Create sandboxed compiler tool."""
681
+ return SandboxedCompiler(use_docker)