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.
- massgen/__init__.py +142 -8
- massgen/adapters/__init__.py +29 -0
- massgen/adapters/ag2_adapter.py +483 -0
- massgen/adapters/base.py +183 -0
- massgen/adapters/tests/__init__.py +0 -0
- massgen/adapters/tests/test_ag2_adapter.py +439 -0
- massgen/adapters/tests/test_agent_adapter.py +128 -0
- massgen/adapters/utils/__init__.py +2 -0
- massgen/adapters/utils/ag2_utils.py +236 -0
- massgen/adapters/utils/tests/__init__.py +0 -0
- massgen/adapters/utils/tests/test_ag2_utils.py +138 -0
- massgen/agent_config.py +329 -55
- massgen/api_params_handler/__init__.py +10 -0
- massgen/api_params_handler/_api_params_handler_base.py +99 -0
- massgen/api_params_handler/_chat_completions_api_params_handler.py +176 -0
- massgen/api_params_handler/_claude_api_params_handler.py +113 -0
- massgen/api_params_handler/_response_api_params_handler.py +130 -0
- massgen/backend/__init__.py +39 -4
- massgen/backend/azure_openai.py +385 -0
- massgen/backend/base.py +341 -69
- massgen/backend/base_with_mcp.py +1102 -0
- massgen/backend/capabilities.py +386 -0
- massgen/backend/chat_completions.py +577 -130
- massgen/backend/claude.py +1033 -537
- massgen/backend/claude_code.py +1203 -0
- massgen/backend/cli_base.py +209 -0
- massgen/backend/docs/BACKEND_ARCHITECTURE.md +126 -0
- massgen/backend/{CLAUDE_API_RESEARCH.md → docs/CLAUDE_API_RESEARCH.md} +18 -18
- massgen/backend/{GEMINI_API_DOCUMENTATION.md → docs/GEMINI_API_DOCUMENTATION.md} +9 -9
- massgen/backend/docs/Gemini MCP Integration Analysis.md +1050 -0
- massgen/backend/docs/MCP_IMPLEMENTATION_CLAUDE_BACKEND.md +177 -0
- massgen/backend/docs/MCP_INTEGRATION_RESPONSE_BACKEND.md +352 -0
- massgen/backend/docs/OPENAI_GPT5_MODELS.md +211 -0
- massgen/backend/{OPENAI_RESPONSES_API_FORMAT.md → docs/OPENAI_RESPONSE_API_TOOL_CALLS.md} +3 -3
- massgen/backend/docs/OPENAI_response_streaming.md +20654 -0
- massgen/backend/docs/inference_backend.md +257 -0
- massgen/backend/docs/permissions_and_context_files.md +1085 -0
- massgen/backend/external.py +126 -0
- massgen/backend/gemini.py +1850 -241
- massgen/backend/grok.py +40 -156
- massgen/backend/inference.py +156 -0
- massgen/backend/lmstudio.py +171 -0
- massgen/backend/response.py +1095 -322
- massgen/chat_agent.py +131 -113
- massgen/cli.py +1560 -275
- massgen/config_builder.py +2396 -0
- massgen/configs/BACKEND_CONFIGURATION.md +458 -0
- massgen/configs/README.md +559 -216
- massgen/configs/ag2/ag2_case_study.yaml +27 -0
- massgen/configs/ag2/ag2_coder.yaml +34 -0
- massgen/configs/ag2/ag2_coder_case_study.yaml +36 -0
- massgen/configs/ag2/ag2_gemini.yaml +27 -0
- massgen/configs/ag2/ag2_groupchat.yaml +108 -0
- massgen/configs/ag2/ag2_groupchat_gpt.yaml +118 -0
- massgen/configs/ag2/ag2_single_agent.yaml +21 -0
- massgen/configs/basic/multi/fast_timeout_example.yaml +37 -0
- massgen/configs/basic/multi/gemini_4o_claude.yaml +31 -0
- massgen/configs/basic/multi/gemini_gpt5nano_claude.yaml +36 -0
- massgen/configs/{gemini_4o_claude.yaml → basic/multi/geminicode_4o_claude.yaml} +3 -3
- massgen/configs/basic/multi/geminicode_gpt5nano_claude.yaml +36 -0
- massgen/configs/basic/multi/glm_gemini_claude.yaml +25 -0
- massgen/configs/basic/multi/gpt4o_audio_generation.yaml +30 -0
- massgen/configs/basic/multi/gpt4o_image_generation.yaml +31 -0
- massgen/configs/basic/multi/gpt5nano_glm_qwen.yaml +26 -0
- massgen/configs/basic/multi/gpt5nano_image_understanding.yaml +26 -0
- massgen/configs/{three_agents_default.yaml → basic/multi/three_agents_default.yaml} +8 -4
- massgen/configs/basic/multi/three_agents_opensource.yaml +27 -0
- massgen/configs/basic/multi/three_agents_vllm.yaml +20 -0
- massgen/configs/basic/multi/two_agents_gemini.yaml +19 -0
- massgen/configs/{two_agents.yaml → basic/multi/two_agents_gpt5.yaml} +14 -6
- massgen/configs/basic/multi/two_agents_opensource_lmstudio.yaml +31 -0
- massgen/configs/basic/multi/two_qwen_vllm_sglang.yaml +28 -0
- massgen/configs/{single_agent.yaml → basic/single/single_agent.yaml} +1 -1
- massgen/configs/{single_flash2.5.yaml → basic/single/single_flash2.5.yaml} +1 -2
- massgen/configs/basic/single/single_gemini2.5pro.yaml +16 -0
- massgen/configs/basic/single/single_gpt4o_audio_generation.yaml +22 -0
- massgen/configs/basic/single/single_gpt4o_image_generation.yaml +22 -0
- massgen/configs/basic/single/single_gpt4o_video_generation.yaml +24 -0
- massgen/configs/basic/single/single_gpt5nano.yaml +20 -0
- massgen/configs/basic/single/single_gpt5nano_file_search.yaml +18 -0
- massgen/configs/basic/single/single_gpt5nano_image_understanding.yaml +17 -0
- massgen/configs/basic/single/single_gptoss120b.yaml +15 -0
- massgen/configs/basic/single/single_openrouter_audio_understanding.yaml +15 -0
- massgen/configs/basic/single/single_qwen_video_understanding.yaml +15 -0
- massgen/configs/debug/code_execution/command_filtering_blacklist.yaml +29 -0
- massgen/configs/debug/code_execution/command_filtering_whitelist.yaml +28 -0
- massgen/configs/debug/code_execution/docker_verification.yaml +29 -0
- massgen/configs/debug/skip_coordination_test.yaml +27 -0
- massgen/configs/debug/test_sdk_migration.yaml +17 -0
- massgen/configs/docs/DISCORD_MCP_SETUP.md +208 -0
- massgen/configs/docs/TWITTER_MCP_ENESCINAR_SETUP.md +82 -0
- massgen/configs/providers/azure/azure_openai_multi.yaml +21 -0
- massgen/configs/providers/azure/azure_openai_single.yaml +19 -0
- massgen/configs/providers/claude/claude.yaml +14 -0
- massgen/configs/providers/gemini/gemini_gpt5nano.yaml +28 -0
- massgen/configs/providers/local/lmstudio.yaml +11 -0
- massgen/configs/providers/openai/gpt5.yaml +46 -0
- massgen/configs/providers/openai/gpt5_nano.yaml +46 -0
- massgen/configs/providers/others/grok_single_agent.yaml +19 -0
- massgen/configs/providers/others/zai_coding_team.yaml +108 -0
- massgen/configs/providers/others/zai_glm45.yaml +12 -0
- massgen/configs/{creative_team.yaml → teams/creative/creative_team.yaml} +16 -6
- massgen/configs/{travel_planning.yaml → teams/creative/travel_planning.yaml} +16 -6
- massgen/configs/{news_analysis.yaml → teams/research/news_analysis.yaml} +16 -6
- massgen/configs/{research_team.yaml → teams/research/research_team.yaml} +15 -7
- massgen/configs/{technical_analysis.yaml → teams/research/technical_analysis.yaml} +16 -6
- massgen/configs/tools/code-execution/basic_command_execution.yaml +25 -0
- massgen/configs/tools/code-execution/code_execution_use_case_simple.yaml +41 -0
- massgen/configs/tools/code-execution/docker_claude_code.yaml +32 -0
- massgen/configs/tools/code-execution/docker_multi_agent.yaml +32 -0
- massgen/configs/tools/code-execution/docker_simple.yaml +29 -0
- massgen/configs/tools/code-execution/docker_with_resource_limits.yaml +32 -0
- massgen/configs/tools/code-execution/multi_agent_playwright_automation.yaml +57 -0
- massgen/configs/tools/filesystem/cc_gpt5_gemini_filesystem.yaml +34 -0
- massgen/configs/tools/filesystem/claude_code_context_sharing.yaml +68 -0
- massgen/configs/tools/filesystem/claude_code_flash2.5.yaml +43 -0
- massgen/configs/tools/filesystem/claude_code_flash2.5_gptoss.yaml +49 -0
- massgen/configs/tools/filesystem/claude_code_gpt5nano.yaml +31 -0
- massgen/configs/tools/filesystem/claude_code_single.yaml +40 -0
- massgen/configs/tools/filesystem/fs_permissions_test.yaml +87 -0
- massgen/configs/tools/filesystem/gemini_gemini_workspace_cleanup.yaml +54 -0
- massgen/configs/tools/filesystem/gemini_gpt5_filesystem_casestudy.yaml +30 -0
- massgen/configs/tools/filesystem/gemini_gpt5nano_file_context_path.yaml +43 -0
- massgen/configs/tools/filesystem/gemini_gpt5nano_protected_paths.yaml +45 -0
- massgen/configs/tools/filesystem/gpt5mini_cc_fs_context_path.yaml +31 -0
- massgen/configs/tools/filesystem/grok4_gpt5_gemini_filesystem.yaml +32 -0
- massgen/configs/tools/filesystem/multiturn/grok4_gpt5_claude_code_filesystem_multiturn.yaml +58 -0
- massgen/configs/tools/filesystem/multiturn/grok4_gpt5_gemini_filesystem_multiturn.yaml +58 -0
- massgen/configs/tools/filesystem/multiturn/two_claude_code_filesystem_multiturn.yaml +47 -0
- massgen/configs/tools/filesystem/multiturn/two_gemini_flash_filesystem_multiturn.yaml +48 -0
- massgen/configs/tools/mcp/claude_code_discord_mcp_example.yaml +27 -0
- massgen/configs/tools/mcp/claude_code_simple_mcp.yaml +35 -0
- massgen/configs/tools/mcp/claude_code_twitter_mcp_example.yaml +32 -0
- massgen/configs/tools/mcp/claude_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/claude_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/five_agents_travel_mcp_test.yaml +157 -0
- massgen/configs/tools/mcp/five_agents_weather_mcp_test.yaml +103 -0
- massgen/configs/tools/mcp/gemini_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test.yaml +23 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test_sharing.yaml +23 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test_single_agent.yaml +17 -0
- massgen/configs/tools/mcp/gemini_mcp_filesystem_test_with_claude_code.yaml +24 -0
- massgen/configs/tools/mcp/gemini_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/gemini_notion_mcp.yaml +52 -0
- massgen/configs/tools/mcp/gpt5_nano_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/gpt5_nano_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/gpt5mini_claude_code_discord_mcp_example.yaml +38 -0
- massgen/configs/tools/mcp/gpt_oss_mcp_example.yaml +25 -0
- massgen/configs/tools/mcp/gpt_oss_mcp_test.yaml +28 -0
- massgen/configs/tools/mcp/grok3_mini_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/grok3_mini_mcp_test.yaml +27 -0
- massgen/configs/tools/mcp/multimcp_gemini.yaml +111 -0
- massgen/configs/tools/mcp/qwen_api_mcp_example.yaml +25 -0
- massgen/configs/tools/mcp/qwen_api_mcp_test.yaml +28 -0
- massgen/configs/tools/mcp/qwen_local_mcp_example.yaml +24 -0
- massgen/configs/tools/mcp/qwen_local_mcp_test.yaml +27 -0
- massgen/configs/tools/planning/five_agents_discord_mcp_planning_mode.yaml +140 -0
- massgen/configs/tools/planning/five_agents_filesystem_mcp_planning_mode.yaml +151 -0
- massgen/configs/tools/planning/five_agents_notion_mcp_planning_mode.yaml +151 -0
- massgen/configs/tools/planning/five_agents_twitter_mcp_planning_mode.yaml +155 -0
- massgen/configs/tools/planning/gpt5_mini_case_study_mcp_planning_mode.yaml +73 -0
- massgen/configs/tools/web-search/claude_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/gemini_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/gpt5_mini_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/gpt_oss_streamable_http_test.yaml +44 -0
- massgen/configs/tools/web-search/grok3_mini_streamable_http_test.yaml +43 -0
- massgen/configs/tools/web-search/qwen_api_streamable_http_test.yaml +44 -0
- massgen/configs/tools/web-search/qwen_local_streamable_http_test.yaml +43 -0
- massgen/coordination_tracker.py +708 -0
- massgen/docker/README.md +462 -0
- massgen/filesystem_manager/__init__.py +21 -0
- massgen/filesystem_manager/_base.py +9 -0
- massgen/filesystem_manager/_code_execution_server.py +545 -0
- massgen/filesystem_manager/_docker_manager.py +477 -0
- massgen/filesystem_manager/_file_operation_tracker.py +248 -0
- massgen/filesystem_manager/_filesystem_manager.py +813 -0
- massgen/filesystem_manager/_path_permission_manager.py +1261 -0
- massgen/filesystem_manager/_workspace_tools_server.py +1815 -0
- massgen/formatter/__init__.py +10 -0
- massgen/formatter/_chat_completions_formatter.py +284 -0
- massgen/formatter/_claude_formatter.py +235 -0
- massgen/formatter/_formatter_base.py +156 -0
- massgen/formatter/_response_formatter.py +263 -0
- massgen/frontend/__init__.py +1 -2
- massgen/frontend/coordination_ui.py +471 -286
- massgen/frontend/displays/base_display.py +56 -11
- massgen/frontend/displays/create_coordination_table.py +1956 -0
- massgen/frontend/displays/rich_terminal_display.py +1259 -619
- massgen/frontend/displays/simple_display.py +9 -4
- massgen/frontend/displays/terminal_display.py +27 -68
- massgen/logger_config.py +681 -0
- massgen/mcp_tools/README.md +232 -0
- massgen/mcp_tools/__init__.py +105 -0
- massgen/mcp_tools/backend_utils.py +1035 -0
- massgen/mcp_tools/circuit_breaker.py +195 -0
- massgen/mcp_tools/client.py +894 -0
- massgen/mcp_tools/config_validator.py +138 -0
- massgen/mcp_tools/docs/circuit_breaker.md +646 -0
- massgen/mcp_tools/docs/client.md +950 -0
- massgen/mcp_tools/docs/config_validator.md +478 -0
- massgen/mcp_tools/docs/exceptions.md +1165 -0
- massgen/mcp_tools/docs/security.md +854 -0
- massgen/mcp_tools/exceptions.py +338 -0
- massgen/mcp_tools/hooks.py +212 -0
- massgen/mcp_tools/security.py +780 -0
- massgen/message_templates.py +342 -64
- massgen/orchestrator.py +1515 -241
- massgen/stream_chunk/__init__.py +35 -0
- massgen/stream_chunk/base.py +92 -0
- massgen/stream_chunk/multimodal.py +237 -0
- massgen/stream_chunk/text.py +162 -0
- massgen/tests/mcp_test_server.py +150 -0
- massgen/tests/multi_turn_conversation_design.md +0 -8
- massgen/tests/test_azure_openai_backend.py +156 -0
- massgen/tests/test_backend_capabilities.py +262 -0
- massgen/tests/test_backend_event_loop_all.py +179 -0
- massgen/tests/test_chat_completions_refactor.py +142 -0
- massgen/tests/test_claude_backend.py +15 -28
- massgen/tests/test_claude_code.py +268 -0
- massgen/tests/test_claude_code_context_sharing.py +233 -0
- massgen/tests/test_claude_code_orchestrator.py +175 -0
- massgen/tests/test_cli_backends.py +180 -0
- massgen/tests/test_code_execution.py +679 -0
- massgen/tests/test_external_agent_backend.py +134 -0
- massgen/tests/test_final_presentation_fallback.py +237 -0
- massgen/tests/test_gemini_planning_mode.py +351 -0
- massgen/tests/test_grok_backend.py +7 -10
- massgen/tests/test_http_mcp_server.py +42 -0
- massgen/tests/test_integration_simple.py +198 -0
- massgen/tests/test_mcp_blocking.py +125 -0
- massgen/tests/test_message_context_building.py +29 -47
- massgen/tests/test_orchestrator_final_presentation.py +48 -0
- massgen/tests/test_path_permission_manager.py +2087 -0
- massgen/tests/test_rich_terminal_display.py +14 -13
- massgen/tests/test_timeout.py +133 -0
- massgen/tests/test_v3_3agents.py +11 -12
- massgen/tests/test_v3_simple.py +8 -13
- massgen/tests/test_v3_three_agents.py +11 -18
- massgen/tests/test_v3_two_agents.py +8 -13
- massgen/token_manager/__init__.py +7 -0
- massgen/token_manager/token_manager.py +400 -0
- massgen/utils.py +52 -16
- massgen/v1/agent.py +45 -91
- massgen/v1/agents.py +18 -53
- massgen/v1/backends/gemini.py +50 -153
- massgen/v1/backends/grok.py +21 -54
- massgen/v1/backends/oai.py +39 -111
- massgen/v1/cli.py +36 -93
- massgen/v1/config.py +8 -12
- massgen/v1/logging.py +43 -127
- massgen/v1/main.py +18 -32
- massgen/v1/orchestrator.py +68 -209
- massgen/v1/streaming_display.py +62 -163
- massgen/v1/tools.py +8 -12
- massgen/v1/types.py +9 -23
- massgen/v1/utils.py +5 -23
- massgen-0.1.0.dist-info/METADATA +1245 -0
- massgen-0.1.0.dist-info/RECORD +273 -0
- massgen-0.1.0.dist-info/entry_points.txt +2 -0
- massgen/frontend/logging/__init__.py +0 -9
- massgen/frontend/logging/realtime_logger.py +0 -197
- massgen-0.0.3.dist-info/METADATA +0 -568
- massgen-0.0.3.dist-info/RECORD +0 -76
- massgen-0.0.3.dist-info/entry_points.txt +0 -2
- /massgen/backend/{Function calling openai responses.md → docs/Function calling openai responses.md} +0 -0
- {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/WHEEL +0 -0
- {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/licenses/LICENSE +0 -0
- {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
|