massgen 0.0.3__py3-none-any.whl → 0.1.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 massgen might be problematic. Click here for more details.

Files changed (268) hide show
  1. massgen/__init__.py +142 -8
  2. massgen/adapters/__init__.py +29 -0
  3. massgen/adapters/ag2_adapter.py +483 -0
  4. massgen/adapters/base.py +183 -0
  5. massgen/adapters/tests/__init__.py +0 -0
  6. massgen/adapters/tests/test_ag2_adapter.py +439 -0
  7. massgen/adapters/tests/test_agent_adapter.py +128 -0
  8. massgen/adapters/utils/__init__.py +2 -0
  9. massgen/adapters/utils/ag2_utils.py +236 -0
  10. massgen/adapters/utils/tests/__init__.py +0 -0
  11. massgen/adapters/utils/tests/test_ag2_utils.py +138 -0
  12. massgen/agent_config.py +329 -55
  13. massgen/api_params_handler/__init__.py +10 -0
  14. massgen/api_params_handler/_api_params_handler_base.py +99 -0
  15. massgen/api_params_handler/_chat_completions_api_params_handler.py +176 -0
  16. massgen/api_params_handler/_claude_api_params_handler.py +113 -0
  17. massgen/api_params_handler/_response_api_params_handler.py +130 -0
  18. massgen/backend/__init__.py +39 -4
  19. massgen/backend/azure_openai.py +385 -0
  20. massgen/backend/base.py +341 -69
  21. massgen/backend/base_with_mcp.py +1102 -0
  22. massgen/backend/capabilities.py +386 -0
  23. massgen/backend/chat_completions.py +577 -130
  24. massgen/backend/claude.py +1033 -537
  25. massgen/backend/claude_code.py +1203 -0
  26. massgen/backend/cli_base.py +209 -0
  27. massgen/backend/docs/BACKEND_ARCHITECTURE.md +126 -0
  28. massgen/backend/{CLAUDE_API_RESEARCH.md → docs/CLAUDE_API_RESEARCH.md} +18 -18
  29. massgen/backend/{GEMINI_API_DOCUMENTATION.md → docs/GEMINI_API_DOCUMENTATION.md} +9 -9
  30. massgen/backend/docs/Gemini MCP Integration Analysis.md +1050 -0
  31. massgen/backend/docs/MCP_IMPLEMENTATION_CLAUDE_BACKEND.md +177 -0
  32. massgen/backend/docs/MCP_INTEGRATION_RESPONSE_BACKEND.md +352 -0
  33. massgen/backend/docs/OPENAI_GPT5_MODELS.md +211 -0
  34. massgen/backend/{OPENAI_RESPONSES_API_FORMAT.md → docs/OPENAI_RESPONSE_API_TOOL_CALLS.md} +3 -3
  35. massgen/backend/docs/OPENAI_response_streaming.md +20654 -0
  36. massgen/backend/docs/inference_backend.md +257 -0
  37. massgen/backend/docs/permissions_and_context_files.md +1085 -0
  38. massgen/backend/external.py +126 -0
  39. massgen/backend/gemini.py +1850 -241
  40. massgen/backend/grok.py +40 -156
  41. massgen/backend/inference.py +156 -0
  42. massgen/backend/lmstudio.py +171 -0
  43. massgen/backend/response.py +1095 -322
  44. massgen/chat_agent.py +131 -113
  45. massgen/cli.py +1560 -275
  46. massgen/config_builder.py +2396 -0
  47. massgen/configs/BACKEND_CONFIGURATION.md +458 -0
  48. massgen/configs/README.md +559 -216
  49. massgen/configs/ag2/ag2_case_study.yaml +27 -0
  50. massgen/configs/ag2/ag2_coder.yaml +34 -0
  51. massgen/configs/ag2/ag2_coder_case_study.yaml +36 -0
  52. massgen/configs/ag2/ag2_gemini.yaml +27 -0
  53. massgen/configs/ag2/ag2_groupchat.yaml +108 -0
  54. massgen/configs/ag2/ag2_groupchat_gpt.yaml +118 -0
  55. massgen/configs/ag2/ag2_single_agent.yaml +21 -0
  56. massgen/configs/basic/multi/fast_timeout_example.yaml +37 -0
  57. massgen/configs/basic/multi/gemini_4o_claude.yaml +31 -0
  58. massgen/configs/basic/multi/gemini_gpt5nano_claude.yaml +36 -0
  59. massgen/configs/{gemini_4o_claude.yaml → basic/multi/geminicode_4o_claude.yaml} +3 -3
  60. massgen/configs/basic/multi/geminicode_gpt5nano_claude.yaml +36 -0
  61. massgen/configs/basic/multi/glm_gemini_claude.yaml +25 -0
  62. massgen/configs/basic/multi/gpt4o_audio_generation.yaml +30 -0
  63. massgen/configs/basic/multi/gpt4o_image_generation.yaml +31 -0
  64. massgen/configs/basic/multi/gpt5nano_glm_qwen.yaml +26 -0
  65. massgen/configs/basic/multi/gpt5nano_image_understanding.yaml +26 -0
  66. massgen/configs/{three_agents_default.yaml → basic/multi/three_agents_default.yaml} +8 -4
  67. massgen/configs/basic/multi/three_agents_opensource.yaml +27 -0
  68. massgen/configs/basic/multi/three_agents_vllm.yaml +20 -0
  69. massgen/configs/basic/multi/two_agents_gemini.yaml +19 -0
  70. massgen/configs/{two_agents.yaml → basic/multi/two_agents_gpt5.yaml} +14 -6
  71. massgen/configs/basic/multi/two_agents_opensource_lmstudio.yaml +31 -0
  72. massgen/configs/basic/multi/two_qwen_vllm_sglang.yaml +28 -0
  73. massgen/configs/{single_agent.yaml → basic/single/single_agent.yaml} +1 -1
  74. massgen/configs/{single_flash2.5.yaml → basic/single/single_flash2.5.yaml} +1 -2
  75. massgen/configs/basic/single/single_gemini2.5pro.yaml +16 -0
  76. massgen/configs/basic/single/single_gpt4o_audio_generation.yaml +22 -0
  77. massgen/configs/basic/single/single_gpt4o_image_generation.yaml +22 -0
  78. massgen/configs/basic/single/single_gpt4o_video_generation.yaml +24 -0
  79. massgen/configs/basic/single/single_gpt5nano.yaml +20 -0
  80. massgen/configs/basic/single/single_gpt5nano_file_search.yaml +18 -0
  81. massgen/configs/basic/single/single_gpt5nano_image_understanding.yaml +17 -0
  82. massgen/configs/basic/single/single_gptoss120b.yaml +15 -0
  83. massgen/configs/basic/single/single_openrouter_audio_understanding.yaml +15 -0
  84. massgen/configs/basic/single/single_qwen_video_understanding.yaml +15 -0
  85. massgen/configs/debug/code_execution/command_filtering_blacklist.yaml +29 -0
  86. massgen/configs/debug/code_execution/command_filtering_whitelist.yaml +28 -0
  87. massgen/configs/debug/code_execution/docker_verification.yaml +29 -0
  88. massgen/configs/debug/skip_coordination_test.yaml +27 -0
  89. massgen/configs/debug/test_sdk_migration.yaml +17 -0
  90. massgen/configs/docs/DISCORD_MCP_SETUP.md +208 -0
  91. massgen/configs/docs/TWITTER_MCP_ENESCINAR_SETUP.md +82 -0
  92. massgen/configs/providers/azure/azure_openai_multi.yaml +21 -0
  93. massgen/configs/providers/azure/azure_openai_single.yaml +19 -0
  94. massgen/configs/providers/claude/claude.yaml +14 -0
  95. massgen/configs/providers/gemini/gemini_gpt5nano.yaml +28 -0
  96. massgen/configs/providers/local/lmstudio.yaml +11 -0
  97. massgen/configs/providers/openai/gpt5.yaml +46 -0
  98. massgen/configs/providers/openai/gpt5_nano.yaml +46 -0
  99. massgen/configs/providers/others/grok_single_agent.yaml +19 -0
  100. massgen/configs/providers/others/zai_coding_team.yaml +108 -0
  101. massgen/configs/providers/others/zai_glm45.yaml +12 -0
  102. massgen/configs/{creative_team.yaml → teams/creative/creative_team.yaml} +16 -6
  103. massgen/configs/{travel_planning.yaml → teams/creative/travel_planning.yaml} +16 -6
  104. massgen/configs/{news_analysis.yaml → teams/research/news_analysis.yaml} +16 -6
  105. massgen/configs/{research_team.yaml → teams/research/research_team.yaml} +15 -7
  106. massgen/configs/{technical_analysis.yaml → teams/research/technical_analysis.yaml} +16 -6
  107. massgen/configs/tools/code-execution/basic_command_execution.yaml +25 -0
  108. massgen/configs/tools/code-execution/code_execution_use_case_simple.yaml +41 -0
  109. massgen/configs/tools/code-execution/docker_claude_code.yaml +32 -0
  110. massgen/configs/tools/code-execution/docker_multi_agent.yaml +32 -0
  111. massgen/configs/tools/code-execution/docker_simple.yaml +29 -0
  112. massgen/configs/tools/code-execution/docker_with_resource_limits.yaml +32 -0
  113. massgen/configs/tools/code-execution/multi_agent_playwright_automation.yaml +57 -0
  114. massgen/configs/tools/filesystem/cc_gpt5_gemini_filesystem.yaml +34 -0
  115. massgen/configs/tools/filesystem/claude_code_context_sharing.yaml +68 -0
  116. massgen/configs/tools/filesystem/claude_code_flash2.5.yaml +43 -0
  117. massgen/configs/tools/filesystem/claude_code_flash2.5_gptoss.yaml +49 -0
  118. massgen/configs/tools/filesystem/claude_code_gpt5nano.yaml +31 -0
  119. massgen/configs/tools/filesystem/claude_code_single.yaml +40 -0
  120. massgen/configs/tools/filesystem/fs_permissions_test.yaml +87 -0
  121. massgen/configs/tools/filesystem/gemini_gemini_workspace_cleanup.yaml +54 -0
  122. massgen/configs/tools/filesystem/gemini_gpt5_filesystem_casestudy.yaml +30 -0
  123. massgen/configs/tools/filesystem/gemini_gpt5nano_file_context_path.yaml +43 -0
  124. massgen/configs/tools/filesystem/gemini_gpt5nano_protected_paths.yaml +45 -0
  125. massgen/configs/tools/filesystem/gpt5mini_cc_fs_context_path.yaml +31 -0
  126. massgen/configs/tools/filesystem/grok4_gpt5_gemini_filesystem.yaml +32 -0
  127. massgen/configs/tools/filesystem/multiturn/grok4_gpt5_claude_code_filesystem_multiturn.yaml +58 -0
  128. massgen/configs/tools/filesystem/multiturn/grok4_gpt5_gemini_filesystem_multiturn.yaml +58 -0
  129. massgen/configs/tools/filesystem/multiturn/two_claude_code_filesystem_multiturn.yaml +47 -0
  130. massgen/configs/tools/filesystem/multiturn/two_gemini_flash_filesystem_multiturn.yaml +48 -0
  131. massgen/configs/tools/mcp/claude_code_discord_mcp_example.yaml +27 -0
  132. massgen/configs/tools/mcp/claude_code_simple_mcp.yaml +35 -0
  133. massgen/configs/tools/mcp/claude_code_twitter_mcp_example.yaml +32 -0
  134. massgen/configs/tools/mcp/claude_mcp_example.yaml +24 -0
  135. massgen/configs/tools/mcp/claude_mcp_test.yaml +27 -0
  136. massgen/configs/tools/mcp/five_agents_travel_mcp_test.yaml +157 -0
  137. massgen/configs/tools/mcp/five_agents_weather_mcp_test.yaml +103 -0
  138. massgen/configs/tools/mcp/gemini_mcp_example.yaml +24 -0
  139. massgen/configs/tools/mcp/gemini_mcp_filesystem_test.yaml +23 -0
  140. massgen/configs/tools/mcp/gemini_mcp_filesystem_test_sharing.yaml +23 -0
  141. massgen/configs/tools/mcp/gemini_mcp_filesystem_test_single_agent.yaml +17 -0
  142. massgen/configs/tools/mcp/gemini_mcp_filesystem_test_with_claude_code.yaml +24 -0
  143. massgen/configs/tools/mcp/gemini_mcp_test.yaml +27 -0
  144. massgen/configs/tools/mcp/gemini_notion_mcp.yaml +52 -0
  145. massgen/configs/tools/mcp/gpt5_nano_mcp_example.yaml +24 -0
  146. massgen/configs/tools/mcp/gpt5_nano_mcp_test.yaml +27 -0
  147. massgen/configs/tools/mcp/gpt5mini_claude_code_discord_mcp_example.yaml +38 -0
  148. massgen/configs/tools/mcp/gpt_oss_mcp_example.yaml +25 -0
  149. massgen/configs/tools/mcp/gpt_oss_mcp_test.yaml +28 -0
  150. massgen/configs/tools/mcp/grok3_mini_mcp_example.yaml +24 -0
  151. massgen/configs/tools/mcp/grok3_mini_mcp_test.yaml +27 -0
  152. massgen/configs/tools/mcp/multimcp_gemini.yaml +111 -0
  153. massgen/configs/tools/mcp/qwen_api_mcp_example.yaml +25 -0
  154. massgen/configs/tools/mcp/qwen_api_mcp_test.yaml +28 -0
  155. massgen/configs/tools/mcp/qwen_local_mcp_example.yaml +24 -0
  156. massgen/configs/tools/mcp/qwen_local_mcp_test.yaml +27 -0
  157. massgen/configs/tools/planning/five_agents_discord_mcp_planning_mode.yaml +140 -0
  158. massgen/configs/tools/planning/five_agents_filesystem_mcp_planning_mode.yaml +151 -0
  159. massgen/configs/tools/planning/five_agents_notion_mcp_planning_mode.yaml +151 -0
  160. massgen/configs/tools/planning/five_agents_twitter_mcp_planning_mode.yaml +155 -0
  161. massgen/configs/tools/planning/gpt5_mini_case_study_mcp_planning_mode.yaml +73 -0
  162. massgen/configs/tools/web-search/claude_streamable_http_test.yaml +43 -0
  163. massgen/configs/tools/web-search/gemini_streamable_http_test.yaml +43 -0
  164. massgen/configs/tools/web-search/gpt5_mini_streamable_http_test.yaml +43 -0
  165. massgen/configs/tools/web-search/gpt_oss_streamable_http_test.yaml +44 -0
  166. massgen/configs/tools/web-search/grok3_mini_streamable_http_test.yaml +43 -0
  167. massgen/configs/tools/web-search/qwen_api_streamable_http_test.yaml +44 -0
  168. massgen/configs/tools/web-search/qwen_local_streamable_http_test.yaml +43 -0
  169. massgen/coordination_tracker.py +708 -0
  170. massgen/docker/README.md +462 -0
  171. massgen/filesystem_manager/__init__.py +21 -0
  172. massgen/filesystem_manager/_base.py +9 -0
  173. massgen/filesystem_manager/_code_execution_server.py +545 -0
  174. massgen/filesystem_manager/_docker_manager.py +477 -0
  175. massgen/filesystem_manager/_file_operation_tracker.py +248 -0
  176. massgen/filesystem_manager/_filesystem_manager.py +813 -0
  177. massgen/filesystem_manager/_path_permission_manager.py +1261 -0
  178. massgen/filesystem_manager/_workspace_tools_server.py +1815 -0
  179. massgen/formatter/__init__.py +10 -0
  180. massgen/formatter/_chat_completions_formatter.py +284 -0
  181. massgen/formatter/_claude_formatter.py +235 -0
  182. massgen/formatter/_formatter_base.py +156 -0
  183. massgen/formatter/_response_formatter.py +263 -0
  184. massgen/frontend/__init__.py +1 -2
  185. massgen/frontend/coordination_ui.py +471 -286
  186. massgen/frontend/displays/base_display.py +56 -11
  187. massgen/frontend/displays/create_coordination_table.py +1956 -0
  188. massgen/frontend/displays/rich_terminal_display.py +1259 -619
  189. massgen/frontend/displays/simple_display.py +9 -4
  190. massgen/frontend/displays/terminal_display.py +27 -68
  191. massgen/logger_config.py +681 -0
  192. massgen/mcp_tools/README.md +232 -0
  193. massgen/mcp_tools/__init__.py +105 -0
  194. massgen/mcp_tools/backend_utils.py +1035 -0
  195. massgen/mcp_tools/circuit_breaker.py +195 -0
  196. massgen/mcp_tools/client.py +894 -0
  197. massgen/mcp_tools/config_validator.py +138 -0
  198. massgen/mcp_tools/docs/circuit_breaker.md +646 -0
  199. massgen/mcp_tools/docs/client.md +950 -0
  200. massgen/mcp_tools/docs/config_validator.md +478 -0
  201. massgen/mcp_tools/docs/exceptions.md +1165 -0
  202. massgen/mcp_tools/docs/security.md +854 -0
  203. massgen/mcp_tools/exceptions.py +338 -0
  204. massgen/mcp_tools/hooks.py +212 -0
  205. massgen/mcp_tools/security.py +780 -0
  206. massgen/message_templates.py +342 -64
  207. massgen/orchestrator.py +1515 -241
  208. massgen/stream_chunk/__init__.py +35 -0
  209. massgen/stream_chunk/base.py +92 -0
  210. massgen/stream_chunk/multimodal.py +237 -0
  211. massgen/stream_chunk/text.py +162 -0
  212. massgen/tests/mcp_test_server.py +150 -0
  213. massgen/tests/multi_turn_conversation_design.md +0 -8
  214. massgen/tests/test_azure_openai_backend.py +156 -0
  215. massgen/tests/test_backend_capabilities.py +262 -0
  216. massgen/tests/test_backend_event_loop_all.py +179 -0
  217. massgen/tests/test_chat_completions_refactor.py +142 -0
  218. massgen/tests/test_claude_backend.py +15 -28
  219. massgen/tests/test_claude_code.py +268 -0
  220. massgen/tests/test_claude_code_context_sharing.py +233 -0
  221. massgen/tests/test_claude_code_orchestrator.py +175 -0
  222. massgen/tests/test_cli_backends.py +180 -0
  223. massgen/tests/test_code_execution.py +679 -0
  224. massgen/tests/test_external_agent_backend.py +134 -0
  225. massgen/tests/test_final_presentation_fallback.py +237 -0
  226. massgen/tests/test_gemini_planning_mode.py +351 -0
  227. massgen/tests/test_grok_backend.py +7 -10
  228. massgen/tests/test_http_mcp_server.py +42 -0
  229. massgen/tests/test_integration_simple.py +198 -0
  230. massgen/tests/test_mcp_blocking.py +125 -0
  231. massgen/tests/test_message_context_building.py +29 -47
  232. massgen/tests/test_orchestrator_final_presentation.py +48 -0
  233. massgen/tests/test_path_permission_manager.py +2087 -0
  234. massgen/tests/test_rich_terminal_display.py +14 -13
  235. massgen/tests/test_timeout.py +133 -0
  236. massgen/tests/test_v3_3agents.py +11 -12
  237. massgen/tests/test_v3_simple.py +8 -13
  238. massgen/tests/test_v3_three_agents.py +11 -18
  239. massgen/tests/test_v3_two_agents.py +8 -13
  240. massgen/token_manager/__init__.py +7 -0
  241. massgen/token_manager/token_manager.py +400 -0
  242. massgen/utils.py +52 -16
  243. massgen/v1/agent.py +45 -91
  244. massgen/v1/agents.py +18 -53
  245. massgen/v1/backends/gemini.py +50 -153
  246. massgen/v1/backends/grok.py +21 -54
  247. massgen/v1/backends/oai.py +39 -111
  248. massgen/v1/cli.py +36 -93
  249. massgen/v1/config.py +8 -12
  250. massgen/v1/logging.py +43 -127
  251. massgen/v1/main.py +18 -32
  252. massgen/v1/orchestrator.py +68 -209
  253. massgen/v1/streaming_display.py +62 -163
  254. massgen/v1/tools.py +8 -12
  255. massgen/v1/types.py +9 -23
  256. massgen/v1/utils.py +5 -23
  257. massgen-0.1.0.dist-info/METADATA +1245 -0
  258. massgen-0.1.0.dist-info/RECORD +273 -0
  259. massgen-0.1.0.dist-info/entry_points.txt +2 -0
  260. massgen/frontend/logging/__init__.py +0 -9
  261. massgen/frontend/logging/realtime_logger.py +0 -197
  262. massgen-0.0.3.dist-info/METADATA +0 -568
  263. massgen-0.0.3.dist-info/RECORD +0 -76
  264. massgen-0.0.3.dist-info/entry_points.txt +0 -2
  265. /massgen/backend/{Function calling openai responses.md → docs/Function calling openai responses.md} +0 -0
  266. {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/WHEEL +0 -0
  267. {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/licenses/LICENSE +0 -0
  268. {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,545 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Code Execution MCP Server for MassGen
5
+
6
+ This MCP server provides command line execution capabilities for agents, allowing
7
+ them to run tests, execute scripts, and perform other command-line operations.
8
+
9
+ Tools provided:
10
+ - execute_command: Execute any command line command with timeout and working directory control
11
+
12
+ Inspired by AG2's LocalCommandLineCodeExecutor sanitization patterns.
13
+ """
14
+
15
+ import argparse
16
+ import os
17
+ import re
18
+ import subprocess
19
+ import sys
20
+ import time
21
+ from pathlib import Path
22
+ from typing import Any, Dict, List, Optional
23
+
24
+ import fastmcp
25
+
26
+ # Platform detection
27
+ WIN32 = sys.platform == "win32"
28
+
29
+ # Docker integration (optional)
30
+ try:
31
+ import docker
32
+ from docker.errors import DockerException
33
+
34
+ DOCKER_AVAILABLE = True
35
+ except ImportError:
36
+ DOCKER_AVAILABLE = False
37
+ docker = None # type: ignore
38
+ DockerException = Exception # type: ignore
39
+
40
+
41
+ def _validate_path_access(path: Path, allowed_paths: List[Path]) -> None:
42
+ """
43
+ Validate that a path is within allowed directories.
44
+
45
+ Args:
46
+ path: Path to validate
47
+ allowed_paths: List of allowed base paths
48
+
49
+ Raises:
50
+ ValueError: If path is not within allowed directories
51
+ """
52
+ if not allowed_paths:
53
+ return # No restrictions
54
+
55
+ for allowed_path in allowed_paths:
56
+ try:
57
+ path.relative_to(allowed_path)
58
+ return # Path is within this allowed directory
59
+ except ValueError:
60
+ continue
61
+
62
+ raise ValueError(f"Path not in allowed directories: {path}")
63
+
64
+
65
+ def _sanitize_command(command: str) -> None:
66
+ """
67
+ Sanitize the command to prevent dangerous operations.
68
+
69
+ Adapted from AG2's LocalCommandLineCodeExecutor.sanitize_command().
70
+ This provides basic protection for users running commands outside Docker.
71
+
72
+ Args:
73
+ command: The command to sanitize
74
+
75
+ Raises:
76
+ ValueError: If dangerous command is detected
77
+ """
78
+ dangerous_patterns = [
79
+ # AG2 original patterns
80
+ (r"\brm\s+-rf\s+/", "Use of 'rm -rf /' is not allowed"),
81
+ (r"\bmv\b.*?\s+/dev/null", "Moving files to /dev/null is not allowed"),
82
+ (r"\bdd\b", "Use of 'dd' command is not allowed"),
83
+ (r">\s*/dev/sd[a-z][1-9]?", "Overwriting disk blocks directly is not allowed"),
84
+ (r":\(\)\{\s*:\|\:&\s*\};:", "Fork bombs are not allowed"),
85
+ # Additional safety patterns
86
+ (r"\bsudo\b", "Use of 'sudo' is not allowed"),
87
+ (r"\bsu\b", "Use of 'su' is not allowed"),
88
+ (r"\bchown\b", "Use of 'chown' is not allowed"),
89
+ (r"\bchmod\b", "Use of 'chmod' is not allowed"),
90
+ ]
91
+
92
+ for pattern, message in dangerous_patterns:
93
+ if re.search(pattern, command):
94
+ raise ValueError(f"Potentially dangerous command detected: {message}")
95
+
96
+
97
+ def _check_command_filters(command: str, allowed_patterns: Optional[List[str]], blocked_patterns: Optional[List[str]]) -> None:
98
+ """
99
+ Check command against whitelist/blacklist filters.
100
+
101
+ Args:
102
+ command: The command to check
103
+ allowed_patterns: Whitelist regex patterns (if provided, command MUST match one)
104
+ blocked_patterns: Blacklist regex patterns (command must NOT match any)
105
+
106
+ Raises:
107
+ ValueError: If command doesn't match whitelist or matches blacklist
108
+ """
109
+ # Check whitelist (if provided, command MUST match at least one pattern)
110
+ if allowed_patterns:
111
+ if not any(re.match(pattern, command) for pattern in allowed_patterns):
112
+ raise ValueError(
113
+ f"Command not in allowed list. Allowed patterns: {', '.join(allowed_patterns)}",
114
+ )
115
+
116
+ # Check blacklist (command must NOT match any blocked pattern)
117
+ if blocked_patterns:
118
+ for pattern in blocked_patterns:
119
+ if re.match(pattern, command):
120
+ raise ValueError(
121
+ f"Command matches blocked pattern: '{pattern}'",
122
+ )
123
+
124
+
125
+ def _prepare_environment(work_dir: Path) -> Dict[str, str]:
126
+ """
127
+ Prepare environment by auto-detecting .venv in work_dir.
128
+
129
+ This function checks for a .venv directory in the working directory and
130
+ automatically modifies PATH to use it if found. Each workspace manages
131
+ its own virtual environment independently.
132
+
133
+ Args:
134
+ work_dir: Working directory to check for .venv
135
+
136
+ Returns:
137
+ Environment variables dict with PATH modified if .venv exists
138
+ """
139
+ env = os.environ.copy()
140
+
141
+ # Auto-detect .venv in work_dir
142
+ venv_dir = work_dir / ".venv"
143
+ if venv_dir.exists():
144
+ # Determine bin directory based on platform
145
+ venv_bin = venv_dir / ("Scripts" if WIN32 else "bin")
146
+ if venv_bin.exists():
147
+ # Prepend venv bin to PATH
148
+ env["PATH"] = f"{venv_bin}{os.pathsep}{env['PATH']}"
149
+ # Set VIRTUAL_ENV for tools that check it
150
+ env["VIRTUAL_ENV"] = str(venv_dir)
151
+
152
+ return env
153
+
154
+
155
+ async def create_server() -> fastmcp.FastMCP:
156
+ """Factory function to create and configure the code execution server."""
157
+
158
+ parser = argparse.ArgumentParser(description="Code Execution MCP Server")
159
+ parser.add_argument(
160
+ "--allowed-paths",
161
+ type=str,
162
+ nargs="*",
163
+ default=[],
164
+ help="List of allowed base paths for execution (default: no restrictions)",
165
+ )
166
+ parser.add_argument(
167
+ "--timeout",
168
+ type=int,
169
+ default=60,
170
+ help="Default timeout in seconds (default: 60)",
171
+ )
172
+ parser.add_argument(
173
+ "--max-output-size",
174
+ type=int,
175
+ default=1024 * 1024, # 1MB
176
+ help="Maximum output size in bytes (default: 1MB)",
177
+ )
178
+ parser.add_argument(
179
+ "--allowed-commands",
180
+ type=str,
181
+ nargs="*",
182
+ default=None,
183
+ help="Whitelist: Only allow commands matching these regex patterns (e.g., 'python .*', 'pytest .*')",
184
+ )
185
+ parser.add_argument(
186
+ "--blocked-commands",
187
+ type=str,
188
+ nargs="*",
189
+ default=None,
190
+ help="Blacklist: Block commands matching these regex patterns (e.g., 'rm .*', 'sudo .*')",
191
+ )
192
+ parser.add_argument(
193
+ "--execution-mode",
194
+ type=str,
195
+ default="local",
196
+ choices=["local", "docker"],
197
+ help="Execution mode: local (subprocess) or docker (container isolation)",
198
+ )
199
+ parser.add_argument(
200
+ "--agent-id",
201
+ type=str,
202
+ default=None,
203
+ help="Agent ID (required for Docker mode to identify container)",
204
+ )
205
+ args = parser.parse_args()
206
+
207
+ # Create the FastMCP server
208
+ mcp = fastmcp.FastMCP("Command Execution")
209
+
210
+ # Store configuration
211
+ mcp.allowed_paths = [Path(p).resolve() for p in args.allowed_paths]
212
+ mcp.default_timeout = args.timeout
213
+ mcp.max_output_size = args.max_output_size
214
+ mcp.allowed_commands = args.allowed_commands # Whitelist patterns
215
+ mcp.blocked_commands = args.blocked_commands # Blacklist patterns
216
+ mcp.execution_mode = args.execution_mode
217
+ mcp.agent_id = args.agent_id
218
+
219
+ # Initialize Docker client if Docker mode
220
+ mcp.docker_client = None
221
+ if args.execution_mode == "docker":
222
+ if not DOCKER_AVAILABLE:
223
+ raise RuntimeError("Docker mode requested but docker library not available. Install with: pip install docker")
224
+ # Note: agent_id validation is deferred to first command execution
225
+ # This allows MCP server to start before agent_id is set by orchestrator
226
+
227
+ try:
228
+ mcp.docker_client = docker.from_env()
229
+ mcp.docker_client.ping() # Test connection
230
+ print("✅ [Docker] Connected to Docker daemon")
231
+ except DockerException as e:
232
+ raise RuntimeError(f"Failed to connect to Docker: {e}")
233
+
234
+ @mcp.tool()
235
+ def execute_command(
236
+ command: str,
237
+ timeout: Optional[int] = None,
238
+ work_dir: Optional[str] = None,
239
+ ) -> Dict[str, Any]:
240
+ """
241
+ Execute a command line command.
242
+
243
+ This tool allows executing any command line program including:
244
+ - Python: execute_command("python script.py")
245
+ - Node.js: execute_command("node app.js")
246
+ - Tests: execute_command("pytest tests/")
247
+ - Build tools: execute_command("npm run build")
248
+ - Shell commands: execute_command("ls -la")
249
+
250
+ The command is executed in a shell environment, so you can use shell features
251
+ like pipes, redirection, and environment variables. On Windows, this uses
252
+ cmd.exe; on Unix/Mac, this uses the default shell (typically bash).
253
+
254
+ Args:
255
+ command: The command to execute (required)
256
+ timeout: Maximum execution time in seconds (default: 60)
257
+ Set to None for no timeout (use with caution)
258
+ work_dir: Working directory for execution (relative to workspace)
259
+ If not specified, uses the current workspace directory
260
+
261
+ Returns:
262
+ Dictionary containing:
263
+ - success: bool - True if exit code was 0
264
+ - exit_code: int - Process exit code
265
+ - stdout: str - Standard output from the command
266
+ - stderr: str - Standard error from the command
267
+ - execution_time: float - Time taken to execute in seconds
268
+ - command: str - The command that was executed
269
+ - work_dir: str - The working directory used
270
+
271
+ Security:
272
+ - Execution is confined to allowed paths
273
+ - Timeout enforced to prevent infinite loops
274
+ - Output size limited to prevent memory exhaustion
275
+ - Basic sanitization against dangerous commands
276
+
277
+ Examples:
278
+ # Run Python script
279
+ execute_command("python test.py")
280
+
281
+ # Run tests with pytest
282
+ execute_command("pytest tests/ -v")
283
+
284
+ # Install package and run script
285
+ execute_command("pip install requests && python scraper.py")
286
+
287
+ # Check Python version
288
+ execute_command("python --version")
289
+
290
+ # List files
291
+ execute_command("ls -la") # Unix/Mac
292
+ execute_command("dir") # Windows
293
+ """
294
+ try:
295
+ # Basic command sanitization (dangerous patterns)
296
+ try:
297
+ _sanitize_command(command)
298
+ except ValueError as e:
299
+ return {
300
+ "success": False,
301
+ "exit_code": -1,
302
+ "stdout": "",
303
+ "stderr": str(e),
304
+ "execution_time": 0.0,
305
+ "command": command,
306
+ "work_dir": work_dir or str(Path.cwd()),
307
+ }
308
+
309
+ # Check whitelist/blacklist filters
310
+ try:
311
+ _check_command_filters(command, mcp.allowed_commands, mcp.blocked_commands)
312
+ except ValueError as e:
313
+ return {
314
+ "success": False,
315
+ "exit_code": -1,
316
+ "stdout": "",
317
+ "stderr": str(e),
318
+ "execution_time": 0.0,
319
+ "command": command,
320
+ "work_dir": work_dir or str(Path.cwd()),
321
+ }
322
+
323
+ # Use default timeout if not specified
324
+ if timeout is None:
325
+ timeout = mcp.default_timeout
326
+
327
+ # Resolve working directory
328
+ if work_dir:
329
+ if Path(work_dir).is_absolute():
330
+ work_path = Path(work_dir).resolve()
331
+ else:
332
+ # Relative path - resolve relative to current working directory
333
+ work_path = (Path.cwd() / work_dir).resolve()
334
+ else:
335
+ work_path = Path.cwd()
336
+
337
+ # Validate working directory is within allowed paths
338
+ _validate_path_access(work_path, mcp.allowed_paths)
339
+
340
+ # Verify working directory exists
341
+ if not work_path.exists():
342
+ return {
343
+ "success": False,
344
+ "exit_code": -1,
345
+ "stdout": "",
346
+ "stderr": f"Working directory does not exist: {work_path}",
347
+ "execution_time": 0.0,
348
+ "command": command,
349
+ "work_dir": str(work_path),
350
+ }
351
+
352
+ if not work_path.is_dir():
353
+ return {
354
+ "success": False,
355
+ "exit_code": -1,
356
+ "stdout": "",
357
+ "stderr": f"Working directory is not a directory: {work_path}",
358
+ "execution_time": 0.0,
359
+ "command": command,
360
+ "work_dir": str(work_path),
361
+ }
362
+
363
+ # Execute command based on execution mode
364
+ if mcp.execution_mode == "docker":
365
+ # Docker mode: execute in container via Docker client
366
+ if not mcp.docker_client:
367
+ return {
368
+ "success": False,
369
+ "exit_code": -1,
370
+ "stdout": "",
371
+ "stderr": "Docker mode enabled but docker_client not initialized",
372
+ "execution_time": 0.0,
373
+ "command": command,
374
+ "work_dir": str(work_path),
375
+ }
376
+
377
+ # Validate agent_id is set before executing
378
+ if not mcp.agent_id:
379
+ return {
380
+ "success": False,
381
+ "exit_code": -1,
382
+ "stdout": "",
383
+ "stderr": "Docker mode requires agent_id to be set. This should be configured by the orchestrator.",
384
+ "execution_time": 0.0,
385
+ "command": command,
386
+ "work_dir": str(work_path),
387
+ }
388
+
389
+ try:
390
+ # Get container by name
391
+ container_name = f"massgen-{mcp.agent_id}"
392
+ container = mcp.docker_client.containers.get(container_name)
393
+
394
+ # IMPORTANT: Use host paths directly in container
395
+ # Container mounts are configured to use the SAME paths as host
396
+ # This makes Docker completely transparent to the LLM
397
+
398
+ # Execute command via docker exec
399
+ exec_config = {
400
+ "cmd": ["/bin/sh", "-c", command],
401
+ "workdir": str(work_path), # Use host path directly
402
+ "stdout": True,
403
+ "stderr": True,
404
+ }
405
+
406
+ start_time = time.time()
407
+ exit_code, output = container.exec_run(**exec_config)
408
+ execution_time = time.time() - start_time
409
+
410
+ # Docker exec_run combines stdout and stderr
411
+ output_str = output.decode("utf-8") if isinstance(output, bytes) else output
412
+
413
+ # Truncate output if too large
414
+ if len(output_str) > mcp.max_output_size:
415
+ output_str = output_str[: mcp.max_output_size] + f"\n... (truncated, exceeded {mcp.max_output_size} bytes)"
416
+
417
+ return {
418
+ "success": exit_code == 0,
419
+ "exit_code": exit_code,
420
+ "stdout": output_str,
421
+ "stderr": "", # Docker exec_run combines stdout/stderr
422
+ "execution_time": execution_time,
423
+ "command": command,
424
+ "work_dir": str(work_path), # Return host path
425
+ }
426
+
427
+ except DockerException as e:
428
+ return {
429
+ "success": False,
430
+ "exit_code": -1,
431
+ "stdout": "",
432
+ "stderr": f"Docker container error: {str(e)}",
433
+ "execution_time": 0.0,
434
+ "command": command,
435
+ "work_dir": str(work_path),
436
+ }
437
+ except Exception as e:
438
+ return {
439
+ "success": False,
440
+ "exit_code": -1,
441
+ "stdout": "",
442
+ "stderr": f"Docker execution error: {str(e)}",
443
+ "execution_time": 0.0,
444
+ "command": command,
445
+ "work_dir": str(work_path),
446
+ }
447
+
448
+ else:
449
+ # Local mode: execute using subprocess (existing logic)
450
+ # Prepare environment (auto-detects .venv in work_dir)
451
+ env = _prepare_environment(work_path)
452
+
453
+ # Execute command
454
+ start_time = time.time()
455
+
456
+ try:
457
+ result = subprocess.run(
458
+ command,
459
+ shell=True,
460
+ cwd=str(work_path),
461
+ timeout=timeout,
462
+ capture_output=True,
463
+ text=True,
464
+ env=env,
465
+ )
466
+
467
+ execution_time = time.time() - start_time
468
+
469
+ # Truncate output if too large
470
+ stdout = result.stdout
471
+ stderr = result.stderr
472
+
473
+ if len(stdout) > mcp.max_output_size:
474
+ stdout = stdout[: mcp.max_output_size] + f"\n... (truncated, exceeded {mcp.max_output_size} bytes)"
475
+
476
+ if len(stderr) > mcp.max_output_size:
477
+ stderr = stderr[: mcp.max_output_size] + f"\n... (truncated, exceeded {mcp.max_output_size} bytes)"
478
+
479
+ return {
480
+ "success": result.returncode == 0,
481
+ "exit_code": result.returncode,
482
+ "stdout": stdout,
483
+ "stderr": stderr,
484
+ "execution_time": execution_time,
485
+ "command": command,
486
+ "work_dir": str(work_path),
487
+ }
488
+
489
+ except subprocess.TimeoutExpired:
490
+ execution_time = time.time() - start_time
491
+ return {
492
+ "success": False,
493
+ "exit_code": -1,
494
+ "stdout": "",
495
+ "stderr": f"Command timed out after {timeout} seconds",
496
+ "execution_time": execution_time,
497
+ "command": command,
498
+ "work_dir": str(work_path),
499
+ }
500
+
501
+ except Exception as e:
502
+ execution_time = time.time() - start_time
503
+ return {
504
+ "success": False,
505
+ "exit_code": -1,
506
+ "stdout": "",
507
+ "stderr": f"Execution error: {str(e)}",
508
+ "execution_time": execution_time,
509
+ "command": command,
510
+ "work_dir": str(work_path),
511
+ }
512
+
513
+ except ValueError as e:
514
+ # Path validation error
515
+ return {
516
+ "success": False,
517
+ "exit_code": -1,
518
+ "stdout": "",
519
+ "stderr": f"Path validation error: {str(e)}",
520
+ "execution_time": 0.0,
521
+ "command": command,
522
+ "work_dir": work_dir or str(Path.cwd()),
523
+ }
524
+
525
+ except Exception as e:
526
+ # Unexpected error
527
+ return {
528
+ "success": False,
529
+ "exit_code": -1,
530
+ "stdout": "",
531
+ "stderr": f"Unexpected error: {str(e)}",
532
+ "execution_time": 0.0,
533
+ "command": command,
534
+ "work_dir": work_dir or str(Path.cwd()),
535
+ }
536
+
537
+ print("🚀 Command Execution MCP Server started and ready")
538
+ print(f"Execution mode: {mcp.execution_mode}")
539
+ if mcp.execution_mode == "docker":
540
+ print(f"Agent ID: {mcp.agent_id}")
541
+ print(f"Default timeout: {mcp.default_timeout}s")
542
+ print(f"Max output size: {mcp.max_output_size} bytes")
543
+ print(f"Allowed paths: {[str(p) for p in mcp.allowed_paths]}")
544
+
545
+ return mcp