跳转至

execution-tools

第4章 · 工具 · 配套项目 chapter4/execution-tools

项目说明

Execution Tools MCP Server

An MCP (Model Context Protocol) server that provides comprehensive execution tools with built-in safety mechanisms for AI agents.

Features

Safety Mechanisms

  1. LLM-Based Approval: Irreversible operations require approval from a secondary LLM before execution
  2. Result Summarization: Execution tool outputs larger than 10,000 characters are automatically summarized by an LLM for easier processing
  3. Automatic Verification: Operations that can be verified (e.g., syntax checking) are automatically validated

Tool Categories

File System Tools

  • file_write: Write content to files with automatic syntax verification
  • file_edit: Edit existing files with diff preview and verification

Generic Execution Tools

  • code_interpreter: Execute Python code in a sandboxed environment with result analysis
  • virtual_terminal: Execute shell commands with error summarization

External System Integration Tools

  • google_calendar_add: Add events to Google Calendar
  • github_create_pr: Create GitHub Pull Requests with validation

Installation

pip install -r requirements.txt

Configuration

  1. Copy env.example to .env:

    cp env.example .env
    

  2. Configure your environment variables:

    # LLM Configuration (for safety checks and summarization)
    PROVIDER=kimi
    
    # API Keys (set the one for your provider)
    KIMI_API_KEY=your_kimi_key
    # SILICONFLOW_API_KEY=your_siliconflow_key
    # DOUBAO_API_KEY=your_doubao_key
    # OPENROUTER_API_KEY=your_openrouter_key
    
    # Model (optional, defaults to provider's default)
    # MODEL=kimi-k3
    
    # Model parameters
    TEMPERATURE=0.7
    MAX_TOKENS=4096
    
    # External Services (optional)
    GOOGLE_CALENDAR_CREDENTIALS_FILE=credentials.json
    GITHUB_TOKEN=your_github_token
    
    # Safety Settings
    REQUIRE_APPROVAL_FOR_DANGEROUS_OPS=true
    AUTO_SUMMARIZE_COMPLEX_OUTPUT=true
    AUTO_VERIFY_CODE=true
    

Supported Providers: - siliconflow: Qwen/Qwen3-235B-A22B-Thinking-2507 - doubao: doubao-seed-1-6-thinking-250715
- kimi/moonshot: kimi-k3 - openrouter: google/gemini-3.5-flash (or openai/gpt-5.6-luna, anthropic/claude-sonnet-4.6)

Universal OpenRouter fallback: when the configured PROVIDER's key is missing but OPENROUTER_API_KEY is set, the LLM steps (approval, summarization, error/syntax analysis) transparently switch to openrouter via Config.effective_provider(). Set MODEL to a provider/model id for OpenRouter, e.g. MODEL=openai/gpt-5.6-luna.

Usage

命令行入口 (CLI)

cli.py 是统一的命令行入口,用于列出、单独调用每个执行工具,并运行端到端演示。 它复用与 MCP 服务器相同的工具实现,因此行为完全一致。

# 查看总帮助与所有子命令
python cli.py --help

# 列出所有执行工具
python cli.py list

# 端到端离线演示(推荐先看这个;无需 API key 即可运行)
python cli.py demo

# 单独调用某个工具
python cli.py code --language python --code "print(2 ** 10)"
python cli.py shell "python3 --version"
python cli.py write --path notes.txt --content "hello" --overwrite
python cli.py edit --path notes.txt --search hello --replace world

全局开关(放在子命令之前):

开关 作用
--provider 覆盖 LLM 提供商(PROVIDER
--workspace 覆盖工作目录(文件操作被限制在此目录内)
--no-approval 关闭危险操作的 LLM 事前审批
--no-verify 关闭写文件/代码的自动语法校验
--no-summarize 关闭长输出的 LLM 总结(仍会截断并持久化)

离线运行listdemo 以及关闭了审批/总结/非 Python 校验的 code/shell/write/edit 均无需 API key。需要 API key 的场景为:LLM 事前审批、 长输出的 LLM 总结、非 Python 语法校验。calendarpr 还额外需要相应外部凭据。

长输出的截断与持久化:当 code_interpreter / virtual_terminal 的输出 超过阈值(默认 200 行或 10000 字符)时,工具只在上下文中保留头尾各 50 行, 完整输出落盘到临时文件,并在返回值的 stdout_file / stderr_file 字段给出路径。 该机制不依赖 LLM,可离线工作。

Running the MCP Server

python server.py

Using with MCP Client

from mcp import Client

# Connect to the MCP server
client = Client("stdio://python server.py")

# Use file write tool
result = await client.call_tool("file_write", {
    "path": "test.py",
    "content": "print('Hello, World!')"
})

# Use code interpreter
result = await client.call_tool("code_interpreter", {
    "code": "import math\nprint(math.sqrt(16))"
})

# Use virtual terminal
result = await client.call_tool("virtual_terminal", {
    "command": "ls -la"
})

Testing Individual Tools

# Test file operations
python test_file_tools.py

# Test execution tools
python test_execution_tools.py

# Test external integrations
python test_external_tools.py

Architecture

The server implements a layered architecture:

  1. Safety Layer: Intercepts dangerous operations and validates them
  2. Tool Layer: Implements individual tool logic
  3. Verification Layer: Validates outputs and provides feedback
  4. Integration Layer: Connects to external services

Examples

See examples.py for comprehensive usage examples.

实验 4-2:执行工具 MCP 服务器

本项目对应书中第 4 章「执行工具」一节的实验 4-2,聚焦执行工具的安全机制: 分层安全防护(输入验证、权限控制、LLM 事前审批)、自动语法验证与反馈闭环、 以及长输出的截断与持久化。推荐从 python cli.py demo 开始。

源代码

cli.py

#!/usr/bin/env python3
"""执行工具统一命令行入口(实验 4-2:执行工具 MCP 服务器)。

本文件提供一个 argparse 命令行界面,用于列出、单独调用每个执行工具,并运行
一个端到端的离线演示。它复用 server.py 背后的同一批工具实现,因此命令行的
行为与 MCP 服务器完全一致。

工具清单(与 server.py 一致):
  file_write        写文件(写入前自动做语法/linter 校验)
  file_edit         按“搜索-替换”编辑文件(带 diff 预览与校验)
  code_interpreter  多语言沙盒代码执行(危险操作审批、长输出截断持久化)
  virtual_terminal  Shell 命令执行(危险命令检测、长输出截断持久化)
  google_calendar_add  创建 Google 日历事件(需要凭据)
  github_create_pr     创建 GitHub Pull Request(需要 token)

安全机制(与书中“执行工具”一节对应):
  - LLM 事前审批:不可逆/危险操作在执行前交由独立 LLM 审查
  - 自动验证:Python 语法通过 compile() 本地校验,其他语言由 LLM 兜底
  - 长输出截断与持久化:超过阈值时仅保留头尾若干行,完整输出落盘到临时文件

用法示例:
  python cli.py list
  python cli.py demo
  python cli.py code --language python --code "print(2 ** 10)"
  python cli.py shell "python3 --version"
  python cli.py write --path notes.txt --content "hello" --overwrite
  python cli.py --no-approval --no-summarize shell "ls -la"

不需要 API key 的命令:list、demo(离线路径)、以及关闭了审批/总结/非 Python
校验的 code/shell/write/edit。需要 API key 的场景:LLM 审批、长输出 LLM 总结、
非 Python 语法校验。calendar 与 pr 还额外需要相应的外部凭据。
"""

import argparse
import asyncio
import json
import os
import sys
import tempfile
import textwrap


# ---------------------------------------------------------------------------
# 工具元数据(供 `list` 子命令展示)
# ---------------------------------------------------------------------------
TOOL_CATALOG = [
    ("file_write", "文件系统", "写文件,写入前自动做语法/linter 校验"),
    ("file_edit", "文件系统", "按搜索-替换编辑文件,附带 diff 预览与校验"),
    ("code_interpreter", "通用执行", "多语言沙盒代码执行(Python/JS/Go/Java/C++/Rust/PHP/Bash)"),
    ("virtual_terminal", "通用执行", "Shell 命令执行,含危险命令检测与长输出截断"),
    ("google_calendar_add", "外部系统", "创建 Google 日历事件(需要 credentials.json)"),
    ("github_create_pr", "外部系统", "创建 GitHub Pull Request(需要 GITHUB_TOKEN)"),
]


def _apply_global_env(args: argparse.Namespace) -> None:
    """把全局开关写入环境变量,供 config.py 在导入时读取。

    config.Config 在模块导入时读取环境变量,因此所有涉及配置的模块都必须在此
    函数执行之后才导入(本文件中的工具模块均为函数内延迟导入)。
    """
    if args.provider:
        os.environ["PROVIDER"] = args.provider
    if args.workspace:
        os.environ["WORKSPACE_DIR"] = os.path.abspath(args.workspace)
    if args.no_approval:
        os.environ["REQUIRE_APPROVAL_FOR_DANGEROUS_OPS"] = "false"
    if args.no_verify:
        os.environ["AUTO_VERIFY_CODE"] = "false"
    if args.no_summarize:
        os.environ["AUTO_SUMMARIZE_COMPLEX_OUTPUT"] = "false"


def _build_tools():
    """构造共享的工具实例(延迟导入,确保环境变量已就绪)。"""
    from llm_helper import LLMHelper
    from file_tools import FileTools
    from execution_tools import ExecutionTools
    from external_tools import ExternalTools

    llm_helper = LLMHelper()  # 客户端惰性创建,离线时不需要 API key
    return {
        "llm": llm_helper,
        "file": FileTools(llm_helper),
        "exec": ExecutionTools(llm_helper),
        "external": ExternalTools(llm_helper),
    }


def _print_result(result: dict) -> None:
    """统一以 JSON 打印工具返回结果。"""
    print(json.dumps(result, indent=2, ensure_ascii=False))


# ---------------------------------------------------------------------------
# 子命令实现
# ---------------------------------------------------------------------------
def cmd_list(args: argparse.Namespace) -> int:
    print("可用执行工具:\n")
    print(f"  {'工具名':<20} {'类别':<8} 说明")
    print(f"  {'-' * 20} {'-' * 8} {'-' * 40}")
    for name, category, desc in TOOL_CATALOG:
        print(f"  {name:<20} {category:<8} {desc}")
    print("\n用 `python cli.py <子命令> --help` 查看每个工具的参数。")
    print("用 `python cli.py demo` 运行端到端离线演示。")
    return 0


def cmd_code(args: argparse.Namespace) -> int:
    code = args.code
    if args.file:
        with open(args.file, "r", encoding="utf-8") as f:
            code = f.read()
    if not code:
        print("错误:请通过 --code 或 --file 提供要执行的代码。", file=sys.stderr)
        return 2

    tools = _build_tools()
    result = asyncio.run(tools["exec"].code_interpreter(
        code=code,
        language=args.language,
        timeout=args.timeout,
        stdin=args.stdin,
    ))
    _print_result(result)
    return 0 if result.get("success") else 1


def cmd_shell(args: argparse.Namespace) -> int:
    tools = _build_tools()
    result = asyncio.run(tools["exec"].virtual_terminal(
        command=args.command,
        timeout=args.timeout,
    ))
    _print_result(result)
    return 0 if result.get("success") else 1


def cmd_write(args: argparse.Namespace) -> int:
    content = args.content
    if args.content_file:
        with open(args.content_file, "r", encoding="utf-8") as f:
            content = f.read()
    if content is None:
        print("错误:请通过 --content 或 --content-file 提供文件内容。", file=sys.stderr)
        return 2

    tools = _build_tools()
    result = asyncio.run(tools["file"].write_file(
        path=args.path,
        content=content,
        overwrite=args.overwrite,
    ))
    _print_result(result)
    return 0 if result.get("success") else 1


def cmd_edit(args: argparse.Namespace) -> int:
    tools = _build_tools()
    result = asyncio.run(tools["file"].edit_file(
        path=args.path,
        search=args.search,
        replace=args.replace,
    ))
    _print_result(result)
    return 0 if result.get("success") else 1


def cmd_calendar(args: argparse.Namespace) -> int:
    tools = _build_tools()
    result = asyncio.run(tools["external"].google_calendar_add(
        summary=args.summary,
        start_time=args.start,
        end_time=args.end,
        description=args.description,
        location=args.location,
    ))
    _print_result(result)
    return 0 if result.get("success") else 1


def cmd_pr(args: argparse.Namespace) -> int:
    tools = _build_tools()
    result = asyncio.run(tools["external"].github_create_pr(
        repo_name=args.repo,
        title=args.title,
        body=args.body,
        head_branch=args.head,
        base_branch=args.base,
    ))
    _print_result(result)
    return 0 if result.get("success") else 1


def cmd_demo(args: argparse.Namespace) -> int:
    """端到端离线演示:模拟一个 Agent 用执行工具完成一个真实小任务。

    场景:Agent 需要写一个词频统计脚本、生成样本数据、运行统计、再用 shell
    校验结果。演示同时覆盖四个安全机制:linter 校验、危险命令 fail-safe 审批、
    长输出截断与持久化。整个流程默认离线运行(关闭 LLM 总结)。
    """
    # 演示放在独立临时工作区,避免污染当前目录。
    workspace = tempfile.mkdtemp(prefix="exec_tools_demo_")
    os.environ["WORKSPACE_DIR"] = workspace
    # 离线运行:关闭需要 LLM 的输出总结(截断持久化不依赖 LLM)。
    if "AUTO_SUMMARIZE_COMPLEX_OUTPUT" not in os.environ:
        os.environ["AUTO_SUMMARIZE_COMPLEX_OUTPUT"] = "false"

    tools = _build_tools()
    file_tools = tools["file"]
    exec_tools = tools["exec"]

    def section(title: str) -> None:
        print("\n" + "=" * 64)
        print(title)
        print("=" * 64)

    print(f"演示工作区:{workspace}")
    print("(离线路径,无需 API key;如已配置 key,审批/总结将走真实 LLM)")

    async def run() -> None:
        # 1. 写文件 + 自动 linter 校验(合法代码)
        section("1. file_write:写入词频统计脚本(自动语法校验)")
        script = textwrap.dedent('''\
            """统计文本文件中的词频。"""
            import sys
            from collections import Counter

            def word_count(path):
                with open(path, encoding="utf-8") as f:
                    words = f.read().split()
                return Counter(words)

            if __name__ == "__main__":
                for word, freq in word_count(sys.argv[1]).most_common(5):
                    print(f"{word}\\t{freq}")
            ''')
        r = await file_tools.write_file("wordcount.py", script, overwrite=True)
        print(f"结果:success={r['success']}, verification={r.get('verification')}")
        print(f"写入:{r.get('path')}")

        # 2. linter 拦截语法错误的代码
        section("2. file_write:写入含语法错误的代码(linter 应拦截)")
        broken = "def broken(:\n    return 1\n"
        r = await file_tools.write_file("broken.py", broken, overwrite=True)
        print(f"结果:success={r['success']}")
        print(f"校验反馈:{r.get('error')}")

        # 3. 生成样本数据
        section("3. file_write:生成样本数据文件")
        sample = "apple banana apple cherry banana apple date cherry banana apple\n"
        r = await file_tools.write_file("data.txt", sample, overwrite=True)
        print(f"结果:success={r['success']},写入 {r.get('bytes_written')} 字节")

        # 4. code_interpreter:运行统计脚本
        section("4. code_interpreter:运行统计逻辑(Python 沙盒)")
        analysis = textwrap.dedent('''\
            from collections import Counter
            text = "apple banana apple cherry banana apple date cherry banana apple"
            for word, freq in Counter(text.split()).most_common(3):
                print(f"{word}: {freq}")
            ''')
        r = await exec_tools.code_interpreter(code=analysis, language="python")
        print(f"结果:success={r['success']}, returncode={r.get('returncode')}")
        print("stdout:")
        print(textwrap.indent(r.get("stdout", ""), "  "))

        # 5. virtual_terminal:用 shell 校验数据文件
        section("5. virtual_terminal:用 shell 校验数据文件")
        r = await exec_tools.virtual_terminal(
            command=f"wc -w {workspace}/data.txt && echo '--- 词数统计完成 ---'"
        )
        print(f"结果:success={r['success']}, returncode={r.get('returncode')}")
        print("stdout:")
        print(textwrap.indent(r.get("stdout", ""), "  "))

        # 6. 长输出截断与持久化(离线,不需 LLM)
        section("6. code_interpreter:长输出自动截断并落盘")
        long_code = "for i in range(1000):\n    print(f'line {i}: ' + 'x' * 20)\n"
        r = await exec_tools.code_interpreter(code=long_code, language="python")
        stdout = r.get("stdout", "")
        print(f"上下文中保留的输出行数:{len(stdout.splitlines())}(原始 1000 行)")
        print(f"完整输出落盘文件:{r.get('stdout_file')}")
        print("上下文中输出的尾部片段:")
        print(textwrap.indent("\n".join(stdout.splitlines()[-4:]), "  "))

        # 7. 危险命令的审批(离线 fail-safe / 在线交由真实 LLM 判断)
        section("7. virtual_terminal:危险命令触发审批")
        os.environ["REQUIRE_APPROVAL_FOR_DANGEROUS_OPS"] = "true"
        # 目标是不存在的临时路径,即便被执行也无副作用。
        danger = await exec_tools.virtual_terminal(
            command="rm -rf /tmp/exec_tools_demo_nonexistent_path_xyz"
        )
        print(f"结果:success={danger['success']}")
        if danger.get("error"):
            print(f"说明:{danger.get('error')}")
            print("(审批未通过:危险命令被拦截、未执行。离线无 LLM 时按 fail-safe 拒绝,"
                  "在线时也可能被真实 LLM 判定为高风险而拒绝。)")
        else:
            print("(审批通过:已配置 API key,真实 LLM 判定该命令针对不存在路径、无副作用而放行。)")

        section("演示完成")
        print("覆盖的安全机制:自动 linter 校验、危险命令审批、长输出截断持久化。")
        print(f"演示产物位于:{workspace}")

    asyncio.run(run())
    return 0


# ---------------------------------------------------------------------------
# 参数解析
# ---------------------------------------------------------------------------
def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="cli.py",
        description="执行工具统一命令行入口(实验 4-2:执行工具 MCP 服务器)。",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=textwrap.dedent("""\
            示例:
              python cli.py list                     列出所有执行工具
              python cli.py demo                     运行端到端离线演示
              python cli.py code --code "print(6*7)"  执行 Python 代码
              python cli.py shell "ls -la"            执行 shell 命令
              python cli.py write --path a.txt --content hi --overwrite
              python cli.py --no-approval shell "echo hello"

            关闭 --no-approval / --no-summarize / --no-verify 后,
            code/shell/write/edit 等命令可完全离线运行,无需 API key。
        """),
    )

    # 全局开关
    parser.add_argument("--provider", help="LLM 提供商(覆盖 PROVIDER,如 kimi/doubao/siliconflow/openrouter)")
    parser.add_argument("--workspace", help="工作目录(覆盖 WORKSPACE_DIR,文件操作被限制在此目录内)")
    parser.add_argument("--no-approval", action="store_true", help="关闭危险操作的 LLM 事前审批")
    parser.add_argument("--no-verify", action="store_true", help="关闭写文件/代码的自动语法校验")
    parser.add_argument("--no-summarize", action="store_true", help="关闭长输出的 LLM 总结(仍会截断持久化)")

    sub = parser.add_subparsers(dest="command", metavar="<子命令>")

    p = sub.add_parser("list", help="列出所有可用的执行工具")
    p.set_defaults(func=cmd_list)

    p = sub.add_parser("demo", help="运行端到端离线演示(推荐先看这个)")
    p.set_defaults(func=cmd_demo)

    p = sub.add_parser("code", help="调用 code_interpreter 执行代码")
    p.add_argument("--code", help="要执行的代码字符串")
    p.add_argument("--file", help="从文件读取要执行的代码")
    p.add_argument("--language", default="python",
                   help="编程语言(python/javascript/typescript/go/java/cpp/rust/php/bash,默认 python)")
    p.add_argument("--timeout", type=float, default=30.0, help="执行超时秒数(默认 30)")
    p.add_argument("--stdin", help="可选的标准输入")
    p.set_defaults(func=cmd_code)

    p = sub.add_parser("shell", help="调用 virtual_terminal 执行 shell 命令")
    p.add_argument("command", help="要执行的 shell 命令")
    p.add_argument("--timeout", type=int, default=30, help="超时秒数(默认 30)")
    p.set_defaults(func=cmd_shell)

    p = sub.add_parser("write", help="调用 file_write 写文件")
    p.add_argument("--path", required=True, help="文件路径(相对工作目录或绝对路径)")
    p.add_argument("--content", help="文件内容")
    p.add_argument("--content-file", help="从文件读取要写入的内容")
    p.add_argument("--overwrite", action="store_true", help="允许覆盖已存在文件")
    p.set_defaults(func=cmd_write)

    p = sub.add_parser("edit", help="调用 file_edit 按搜索-替换编辑文件")
    p.add_argument("--path", required=True, help="文件路径")
    p.add_argument("--search", required=True, help="要搜索的文本")
    p.add_argument("--replace", required=True, help="替换文本")
    p.set_defaults(func=cmd_edit)

    p = sub.add_parser("calendar", help="调用 google_calendar_add 创建日历事件(需要凭据)")
    p.add_argument("--summary", required=True, help="事件标题")
    p.add_argument("--start", required=True, help="开始时间(ISO 8601,如 2025-10-01T10:00:00)")
    p.add_argument("--end", required=True, help="结束时间(ISO 8601)")
    p.add_argument("--description", help="事件描述")
    p.add_argument("--location", help="事件地点")
    p.set_defaults(func=cmd_calendar)

    p = sub.add_parser("pr", help="调用 github_create_pr 创建 Pull Request(需要 token)")
    p.add_argument("--repo", required=True, help="仓库名(owner/repo 格式)")
    p.add_argument("--title", required=True, help="PR 标题")
    p.add_argument("--body", required=True, help="PR 描述")
    p.add_argument("--head", required=True, help="源分支")
    p.add_argument("--base", default="main", help="目标分支(默认 main)")
    p.set_defaults(func=cmd_pr)

    return parser


def main(argv=None) -> int:
    parser = build_parser()
    args = parser.parse_args(argv)

    if not getattr(args, "command", None):
        parser.print_help()
        return 0

    _apply_global_env(args)
    try:
        return args.func(args)
    except KeyboardInterrupt:
        print("\n已中断。", file=sys.stderr)
        return 130


if __name__ == "__main__":
    sys.exit(main())

config.py

"""Configuration management for the execution tools MCP server."""

import os
import sys
from pathlib import Path
from typing import Optional
from dotenv import load_dotenv

# Load environment variables
load_dotenv()


def _env_int(name: str, default: int) -> int:
    """Read an integer env var; fall back to default (with a warning) if malformed."""
    raw = os.getenv(name)
    if raw is None:
        return default
    try:
        return int(raw)
    except ValueError:
        print(f"Warning: invalid {name}={raw!r} (must be an integer); using default {default}",
              file=sys.stderr)
        return default


def _env_float(name: str, default: float) -> float:
    """Read a float env var; fall back to default (with a warning) if malformed."""
    raw = os.getenv(name)
    if raw is None:
        return default
    try:
        return float(raw)
    except ValueError:
        print(f"Warning: invalid {name}={raw!r} (must be a number); using default {default}",
              file=sys.stderr)
        return default


class Config:
    """Configuration for the MCP server."""

    # LLM Configuration
    PROVIDER: str = os.getenv("PROVIDER", "kimi")

    # API Keys
    SILICONFLOW_API_KEY: Optional[str] = os.getenv("SILICONFLOW_API_KEY")
    DOUBAO_API_KEY: Optional[str] = os.getenv("DOUBAO_API_KEY")
    KIMI_API_KEY: Optional[str] = os.getenv("KIMI_API_KEY")
    MOONSHOT_API_KEY: Optional[str] = os.getenv("MOONSHOT_API_KEY")
    OPENROUTER_API_KEY: Optional[str] = os.getenv("OPENROUTER_API_KEY")

    # Model names (optional, defaults to provider defaults)
    MODEL: Optional[str] = os.getenv("MODEL")

    # Model parameters
    TEMPERATURE: float = _env_float("TEMPERATURE", 0.7)
    MAX_TOKENS: int = _env_int("MAX_TOKENS", 4096)

    # External Services
    GOOGLE_CALENDAR_CREDENTIALS_FILE: str = os.getenv(
        "GOOGLE_CALENDAR_CREDENTIALS_FILE", 
        "credentials.json"
    )
    GITHUB_TOKEN: Optional[str] = os.getenv("GITHUB_TOKEN")

    # Safety Settings
    REQUIRE_APPROVAL_FOR_DANGEROUS_OPS: bool = (
        os.getenv("REQUIRE_APPROVAL_FOR_DANGEROUS_OPS", "true").lower() == "true"
    )
    AUTO_SUMMARIZE_COMPLEX_OUTPUT: bool = (
        os.getenv("AUTO_SUMMARIZE_COMPLEX_OUTPUT", "true").lower() == "true"
    )
    AUTO_VERIFY_CODE: bool = (
        os.getenv("AUTO_VERIFY_CODE", "true").lower() == "true"
    )
    MAX_OUTPUT_LENGTH: int = _env_int("MAX_OUTPUT_LENGTH", 1000)

    # Workspace Configuration
    WORKSPACE_DIR: Path = Path(os.getenv("WORKSPACE_DIR", os.getcwd()))

    @classmethod
    def get_api_key(cls, provider: str) -> Optional[str]:
        """Get API key for the specified provider."""
        provider = provider.lower()
        if provider == "siliconflow":
            return cls.SILICONFLOW_API_KEY
        elif provider == "doubao":
            return cls.DOUBAO_API_KEY
        elif provider in ["kimi", "moonshot"]:
            return cls.KIMI_API_KEY or cls.MOONSHOT_API_KEY
        elif provider == "openrouter":
            return cls.OPENROUTER_API_KEY
        return None

    @classmethod
    def effective_provider(cls) -> str:
        """Resolve the provider actually used, applying the OpenRouter fallback.

        Preserves default behavior when the configured provider's key is
        present. Otherwise, if an OPENROUTER_API_KEY is available, transparently
        fall back to 'openrouter' so the tools still run with only that key set.
        """
        provider = cls.PROVIDER.lower()
        if cls.get_api_key(provider):
            return provider
        if cls.OPENROUTER_API_KEY:
            return "openrouter"
        return provider

    @classmethod
    def validate(cls) -> None:
        """Validate the configuration."""
        provider = cls.effective_provider()
        api_key = cls.get_api_key(provider)

        if not api_key:
            raise ValueError(
                f"API key required for provider '{cls.PROVIDER.lower()}'. "
                f"Set one of {cls.PROVIDER.upper()}_API_KEY or OPENROUTER_API_KEY "
                f"(universal fallback)."
            )

    @classmethod
    def get_llm_config(cls) -> dict:
        """Get LLM configuration based on provider."""
        provider = cls.effective_provider()
        api_key = cls.get_api_key(provider)

        if not api_key:
            raise ValueError(
                f"API key not found for provider '{cls.PROVIDER.lower()}'. "
                f"Set {cls.PROVIDER.upper()}_API_KEY or OPENROUTER_API_KEY."
            )

        if provider == "siliconflow":
            return {
                "provider": "siliconflow",
                "api_key": api_key,
                "base_url": "https://api.siliconflow.cn/v1",
                "model": cls.MODEL or "Qwen/Qwen3-235B-A22B-Thinking-2507"
            }
        elif provider == "doubao":
            return {
                "provider": "doubao",
                "api_key": api_key,
                "base_url": "https://ark.cn-beijing.volces.com/api/v3",
                "model": cls.MODEL or "doubao-seed-1-6-thinking-250715"
            }
        elif provider in ["kimi", "moonshot"]:
            return {
                "provider": "kimi",
                "api_key": api_key,
                "base_url": "https://api.moonshot.cn/v1",
                "model": cls.MODEL or "kimi-k3"
            }
        elif provider == "openrouter":
            return {
                "provider": "openrouter",
                "api_key": api_key,
                "base_url": "https://openrouter.ai/api/v1",
                "model": cls.MODEL or "google/gemini-3.5-flash"
            }
        else:
            raise ValueError(
                f"Unsupported provider: {provider}. "
                f"Use 'siliconflow', 'doubao', 'kimi', 'moonshot', or 'openrouter'"
            )


# Note: configuration is validated lazily when the LLM is actually used
# (see LLMHelper), so that execution tools which do not require an LLM
# (file write, code run, terminal) can be used offline without an API key.

examples.py

"""Example usage of the execution tools MCP server."""

import asyncio
from llm_helper import LLMHelper
from file_tools import FileTools
from execution_tools import ExecutionTools
from external_tools import ExternalTools


async def example_file_operations():
    """Example: File operations with verification."""
    print("=== File Operations Example ===\n")

    llm_helper = LLMHelper()
    file_tools = FileTools(llm_helper)

    # Write a Python file
    print("1. Writing a Python file...")
    result = await file_tools.write_file(
        path="test_script.py",
        content="""def greet(name):
    print(f"Hello, {name}!")

if __name__ == "__main__":
    greet("World")
""",
        overwrite=True
    )
    print(f"Result: {result}\n")

    # Edit the file
    print("2. Editing the file...")
    result = await file_tools.edit_file(
        path="test_script.py",
        search='greet("World")',
        replace='greet("MCP Server")'
    )
    print(f"Result: {result}\n")


async def example_code_interpreter():
    """Example: Code interpreter with analysis."""
    print("=== Code Interpreter Example ===\n")

    llm_helper = LLMHelper()
    execution_tools = ExecutionTools(llm_helper)

    # Execute valid code
    print("1. Executing valid code...")
    result = await execution_tools.code_interpreter(
        code="""
import math

# Calculate factorial
def factorial(n):
    if n <= 1:
        return 1
    return n * factorial(n - 1)

print(f"Factorial of 5: {factorial(5)}")
print(f"Square root of 16: {math.sqrt(16)}")
"""
    )
    print(f"Result: {result}\n")

    # Execute code with error
    print("2. Executing code with error...")
    result = await execution_tools.code_interpreter(
        code="""
# This will cause an error
x = 10 / 0
"""
    )
    print(f"Result: {result}\n")


async def example_virtual_terminal():
    """Example: Virtual terminal."""
    print("=== Virtual Terminal Example ===\n")

    llm_helper = LLMHelper()
    execution_tools = ExecutionTools(llm_helper)

    # Execute simple command
    print("1. Listing current directory...")
    result = await execution_tools.virtual_terminal(
        command="ls -la"
    )
    print(f"Result: {result}\n")

    # Execute command with error
    print("2. Executing command that fails...")
    result = await execution_tools.virtual_terminal(
        command="cat nonexistent_file.txt"
    )
    print(f"Result: {result}\n")


async def example_google_calendar():
    """Example: Google Calendar integration."""
    print("=== Google Calendar Example ===\n")

    llm_helper = LLMHelper()
    external_tools = ExternalTools(llm_helper)

    print("Adding event to Google Calendar...")
    result = await external_tools.google_calendar_add(
        summary="Team Meeting",
        start_time="2025-10-01T10:00:00",
        end_time="2025-10-01T11:00:00",
        description="Quarterly planning meeting",
        location="Conference Room A"
    )
    print(f"Result: {result}\n")


async def example_github_pr():
    """Example: GitHub Pull Request creation."""
    print("=== GitHub PR Example ===\n")

    llm_helper = LLMHelper()
    external_tools = ExternalTools(llm_helper)

    print("Creating GitHub Pull Request...")
    result = await external_tools.github_create_pr(
        repo_name="owner/repository",
        title="Add new feature",
        body="This PR adds a new feature to improve performance.\n\n## Changes\n- Optimized algorithm\n- Added tests\n- Updated documentation",
        head_branch="feature/new-feature",
        base_branch="main"
    )
    print(f"Result: {result}\n")


async def main():
    """Run all examples."""
    try:
        # File operations
        await example_file_operations()

        # Code interpreter
        await example_code_interpreter()

        # Virtual terminal
        await example_virtual_terminal()

        # External tools (commented out as they require credentials)
        # await example_google_calendar()
        # await example_github_pr()

        print("All examples completed!")

    except Exception as e:
        print(f"Error running examples: {e}")
        import traceback
        traceback.print_exc()


if __name__ == "__main__":
    asyncio.run(main())

execution_tools.py

"""Generic execution tools: code interpreter and virtual terminal."""

import os
import subprocess
import sys
import io
import tempfile
import traceback
from typing import Dict, Any, Optional, Tuple
from contextlib import redirect_stdout, redirect_stderr
from llm_helper import LLMHelper
from config import Config
from multilang_executor import LanguageExecutor, ExecutionStatus

# Long-output handling thresholds (see "长输出的截断与持久化" in chapter 4).
# When output exceeds either threshold, keep the head and tail few lines in the
# context and persist the full output to a temp file for later retrieval.
MAX_OUTPUT_LINES = 200
MAX_OUTPUT_CHARS = 10000
HEAD_LINES = 50
TAIL_LINES = 50


def truncate_and_persist(
    text: str,
    tool_name: str = "execution",
    max_lines: int = MAX_OUTPUT_LINES,
    max_chars: int = MAX_OUTPUT_CHARS,
    head_lines: int = HEAD_LINES,
    tail_lines: int = TAIL_LINES,
) -> Tuple[str, Optional[str]]:
    """Truncate over-long output and persist the full text to a temp file.

    Returns a tuple of (processed_text, saved_path). When the output is within
    both thresholds, it is returned unchanged with ``saved_path`` set to None.
    Otherwise only the first ``head_lines`` and last ``tail_lines`` lines are
    kept in context, with a middle marker pointing to the saved file. This
    keeps the agent's context bounded without discarding any information and
    requires no LLM call.
    """
    if text is None:
        return text, None

    lines = text.split("\n")
    if len(text) <= max_chars and len(lines) <= max_lines:
        return text, None

    # Persist the complete output for later retrieval via read_file.
    fd, path = tempfile.mkstemp(prefix=f"{tool_name}_output_", suffix=".txt")
    with os.fdopen(fd, "w", encoding="utf-8") as f:
        f.write(text)

    head_part = lines[:head_lines]
    tail_part = lines[-tail_lines:]
    omitted = max(len(lines) - head_lines - tail_lines, 0)

    middle = f"... [省略 {omitted} 行,完整输出已保存至 {path}] ..."
    guide = f"[如需完整输出,请使用 read_file 工具读取 {path}]"
    truncated = "\n".join(head_part + [middle] + tail_part + [guide])
    return truncated, path


class ExecutionTools:
    """Generic execution tools with safety and result analysis."""

    def __init__(self, llm_helper: LLMHelper):
        """Initialize execution tools with LLM helper."""
        self.llm_helper = llm_helper
        self.lang_executor = LanguageExecutor(workspace_dir=Config.WORKSPACE_DIR)

    async def code_interpreter(
        self,
        code: str,
        language: str = "python",
        timeout: float = 30.0,
        stdin: Optional[str] = None,
        files: Optional[Dict[str, str]] = None
    ) -> Dict[str, Any]:
        """
        Execute code in a sandboxed environment with multi-language support.

        Args:
            code: Code to execute
            language: Programming language (python, javascript, typescript, go, java, cpp, rust, php, bash)
            timeout: Execution timeout in seconds
            stdin: Optional stdin input
            files: Optional additional files

        Returns:
            Result dictionary with output and analysis
        """
        if language is None:
            language = "python"
        language = language.lower()

        # Verify syntax first (only for Python for now)
        if Config.AUTO_VERIFY_CODE and language in ['python', 'python3']:
            is_valid, error_msg = self.llm_helper.verify_code_syntax(code, language)
            if not is_valid:
                return {
                    "success": False,
                    "error": f"Syntax error: {error_msg}",
                    "verification": "failed",
                    "language": language
                }

        # Check for dangerous operations
        if Config.REQUIRE_APPROVAL_FOR_DANGEROUS_OPS:
            dangerous_patterns = {
                'python': ['os.system', 'subprocess', 'eval', 'exec', 'open(', '__import__', 'compile'],
                'bash': ['rm -rf', 'dd if=', 'mkfs', '> /dev/', 'curl', 'wget'],
                'php': ['exec(', 'system(', 'shell_exec(', 'passthru(', 'eval('],
            }

            patterns = dangerous_patterns.get(language, [])
            detected = [p for p in patterns if p in code]

            if detected:
                approved, reason = self.llm_helper.request_approval(
                    "code_execution",
                    {
                        "code": code,
                        "language": language,
                        "detected_patterns": detected
                    }
                )

                if not approved:
                    return {
                        "success": False,
                        "error": f"Execution not approved: {reason}",
                        "language": language
                    }

        # Execute code using multi-language executor
        try:
            result = await self.lang_executor.execute_code(
                code=code,
                language=language,
                timeout=timeout,
                stdin=stdin,
                files=files
            )

            # Convert status to success flag
            success = result.get('status') == ExecutionStatus.SUCCESS

            # Long outputs: truncate head/tail and persist the full text to a
            # temp file (offline-safe), then optionally LLM-summarize whatever
            # still exceeds the char threshold.
            stdout = result.get('stdout', '')
            stderr = result.get('stderr', '')
            stdout, stdout_file = truncate_and_persist(stdout, "code_interpreter")
            stderr, stderr_file = truncate_and_persist(stderr, "code_interpreter")

            if Config.AUTO_SUMMARIZE_COMPLEX_OUTPUT and len(stdout) > MAX_OUTPUT_CHARS:
                stdout = self.llm_helper.summarize_output("code_interpreter", stdout)
            if Config.AUTO_SUMMARIZE_COMPLEX_OUTPUT and len(stderr) > MAX_OUTPUT_CHARS:
                stderr = self.llm_helper.summarize_output("code_interpreter", stderr)

            return {
                "success": success,
                "status": result.get('status'),
                "language": result.get('language', language),
                "stdout": stdout,
                "stderr": stderr,
                "stdout_file": stdout_file,
                "stderr_file": stderr_file,
                "returncode": result.get('returncode'),
                "error": result.get('error'),
                "compile_output": result.get('compile_output'),
                "phase": result.get('phase'),
                "verification": "passed" if Config.AUTO_VERIFY_CODE else "skipped"
            }

        except Exception as e:
            error_output = f"{type(e).__name__}: {str(e)}\n{traceback.format_exc()}"
            return {
                "success": False,
                "error": error_output,
                "language": language
            }

    async def virtual_terminal(
        self,
        command: str,
        timeout: int = 30
    ) -> Dict[str, Any]:
        """
        Execute shell command in a virtual terminal.

        Args:
            command: Shell command to execute
            timeout: Timeout in seconds

        Returns:
            Result dictionary with output and analysis
        """
        # Check for dangerous commands
        if Config.REQUIRE_APPROVAL_FOR_DANGEROUS_OPS:
            dangerous_commands = [
                'rm -rf', 'dd', 'mkfs', 'format',
                '> /dev/', 'chmod -R', 'chown -R'
            ]

            if any(dangerous in command for dangerous in dangerous_commands):
                approved, reason = self.llm_helper.request_approval(
                    "terminal_command",
                    {
                        "command": command,
                        "detected_patterns": [p for p in dangerous_commands if p in command]
                    }
                )

                if not approved:
                    return {
                        "success": False,
                        "error": f"Command execution not approved: {reason}"
                    }

        # Execute command
        try:
            result = subprocess.run(
                command,
                shell=True,
                capture_output=True,
                text=True,
                timeout=timeout,
                cwd=Config.WORKSPACE_DIR
            )

            stdout = result.stdout
            stderr = result.stderr

            # Long output: truncate head/tail and persist to a temp file, then
            # optionally LLM-summarize whatever still exceeds the char threshold.
            stdout, stdout_file = truncate_and_persist(stdout, "virtual_terminal")
            stderr, stderr_file = truncate_and_persist(stderr, "virtual_terminal")

            if Config.AUTO_SUMMARIZE_COMPLEX_OUTPUT:
                if len(stdout) > MAX_OUTPUT_CHARS:
                    stdout = self.llm_helper.summarize_output(
                        "virtual_terminal",
                        stdout
                    )
                if len(stderr) > MAX_OUTPUT_CHARS:
                    stderr = self.llm_helper.summarize_output(
                        "virtual_terminal",
                        stderr
                    )

            response = {
                "success": result.returncode == 0,
                "returncode": result.returncode,
                "stdout": stdout,
                "stderr": stderr,
                "stdout_file": stdout_file,
                "stderr_file": stderr_file
            }
            return response

        except subprocess.TimeoutExpired:
            return {
                "success": False,
                "error": f"Command timed out after {timeout} seconds"
            }
        except Exception as e:
            return {
                "success": False,
                "error": f"Command execution failed: {str(e)}"
            }

external_tools.py

"""External system integration tools: Google Calendar and GitHub."""

import os
import json
from typing import Dict, Any, Optional
from datetime import datetime, timedelta
from pathlib import Path

from llm_helper import LLMHelper
from config import Config

# Google Calendar imports (optional)
try:
    from google.oauth2.credentials import Credentials
    from google_auth_oauthlib.flow import InstalledAppFlow
    from google.auth.transport.requests import Request
    from googleapiclient.discovery import build
    GOOGLE_AVAILABLE = True
except ImportError:
    GOOGLE_AVAILABLE = False

# GitHub imports (optional)
try:
    from github import Github, GithubException
    GITHUB_AVAILABLE = True
except ImportError:
    GITHUB_AVAILABLE = False


class ExternalTools:
    """External system integration tools."""

    def __init__(self, llm_helper: LLMHelper):
        """Initialize external tools with LLM helper."""
        self.llm_helper = llm_helper
        self._google_service = None
        self._github_client = None

    def _get_google_calendar_service(self):
        """Get or create Google Calendar service."""
        if not GOOGLE_AVAILABLE:
            raise ImportError("Google Calendar libraries not installed")

        if self._google_service:
            return self._google_service

        SCOPES = ['https://www.googleapis.com/auth/calendar']
        creds = None

        token_path = Path('token.json')
        creds_path = Path(Config.GOOGLE_CALENDAR_CREDENTIALS_FILE)

        # Load token if exists
        if token_path.exists():
            creds = Credentials.from_authorized_user_file(str(token_path), SCOPES)

        # Refresh or get new token
        if not creds or not creds.valid:
            if creds and creds.expired and creds.refresh_token:
                creds.refresh(Request())
            else:
                if not creds_path.exists():
                    raise FileNotFoundError(f"Credentials file not found: {creds_path}")
                flow = InstalledAppFlow.from_client_secrets_file(str(creds_path), SCOPES)
                creds = flow.run_local_server(port=0)

            # Save token
            token_path.write_text(creds.to_json())

        self._google_service = build('calendar', 'v3', credentials=creds)
        return self._google_service

    def _get_github_client(self):
        """Get or create GitHub client."""
        if not GITHUB_AVAILABLE:
            raise ImportError("GitHub library not installed")

        if self._github_client:
            return self._github_client

        if not Config.GITHUB_TOKEN:
            raise ValueError("GitHub token not configured")

        self._github_client = Github(Config.GITHUB_TOKEN)
        return self._github_client

    async def google_calendar_add(
        self,
        summary: str,
        start_time: str,
        end_time: str,
        description: Optional[str] = None,
        location: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Add event to Google Calendar.

        Args:
            summary: Event title
            start_time: Start time (ISO 8601 format or natural language)
            end_time: End time (ISO 8601 format or natural language)
            description: Event description
            location: Event location

        Returns:
            Result dictionary with event details
        """
        try:
            service = self._get_google_calendar_service()
        except Exception as e:
            return {
                "success": False,
                "error": f"Failed to initialize Google Calendar: {str(e)}"
            }

        # Parse times
        try:
            start_dt = self._parse_datetime(start_time)
            end_dt = self._parse_datetime(end_time)
        except ValueError as e:
            return {
                "success": False,
                "error": f"Invalid datetime format: {str(e)}"
            }

        # Validate times
        if end_dt <= start_dt:
            return {
                "success": False,
                "error": "End time must be after start time"
            }

        # Request approval
        if Config.REQUIRE_APPROVAL_FOR_DANGEROUS_OPS:
            approved, reason = self.llm_helper.request_approval(
                "google_calendar_add",
                {
                    "summary": summary,
                    "start_time": start_dt.isoformat(),
                    "end_time": end_dt.isoformat(),
                    "description": description
                }
            )

            if not approved:
                return {
                    "success": False,
                    "error": f"Calendar event creation not approved: {reason}"
                }

        # Create event
        event = {
            'summary': summary,
            'start': {
                'dateTime': start_dt.isoformat(),
                'timeZone': 'UTC'
            },
            'end': {
                'dateTime': end_dt.isoformat(),
                'timeZone': 'UTC'
            }
        }

        if description:
            event['description'] = description
        if location:
            event['location'] = location

        try:
            created_event = service.events().insert(
                calendarId='primary',
                body=event
            ).execute()

            return {
                "success": True,
                "event_id": created_event['id'],
                "event_link": created_event.get('htmlLink'),
                "summary": summary,
                "start_time": start_dt.isoformat(),
                "end_time": end_dt.isoformat()
            }
        except Exception as e:
            return {
                "success": False,
                "error": f"Failed to create calendar event: {str(e)}"
            }

    async def github_create_pr(
        self,
        repo_name: str,
        title: str,
        body: str,
        head_branch: str,
        base_branch: str = "main"
    ) -> Dict[str, Any]:
        """
        Create a GitHub Pull Request.

        Args:
            repo_name: Repository name (format: owner/repo)
            title: PR title
            body: PR description
            head_branch: Source branch
            base_branch: Target branch

        Returns:
            Result dictionary with PR details
        """
        try:
            github = self._get_github_client()
        except Exception as e:
            return {
                "success": False,
                "error": f"Failed to initialize GitHub client: {str(e)}"
            }

        # Validate repository name
        if '/' not in repo_name:
            return {
                "success": False,
                "error": "Repository name must be in format: owner/repo"
            }

        # Request approval
        if Config.REQUIRE_APPROVAL_FOR_DANGEROUS_OPS:
            approved, reason = self.llm_helper.request_approval(
                "github_create_pr",
                {
                    "repo": repo_name,
                    "title": title,
                    "head": head_branch,
                    "base": base_branch,
                    "body_preview": body[:200]
                }
            )

            if not approved:
                return {
                    "success": False,
                    "error": f"PR creation not approved: {reason}"
                }

        try:
            # Get repository
            repo = github.get_repo(repo_name)

            # Verify branches exist
            try:
                repo.get_branch(head_branch)
                repo.get_branch(base_branch)
            except Exception as e:
                return {
                    "success": False,
                    "error": f"Branch verification failed: {str(e)}"
                }

            # Create pull request
            pr = repo.create_pull(
                title=title,
                body=body,
                head=head_branch,
                base=base_branch
            )

            return {
                "success": True,
                "pr_number": pr.number,
                "pr_url": pr.html_url,
                "title": title,
                "state": pr.state,
                "created_at": pr.created_at.isoformat()
            }

        except GithubException as e:
            return {
                "success": False,
                "error": f"GitHub API error: {e.data.get('message', str(e))}"
            }
        except Exception as e:
            return {
                "success": False,
                "error": f"Failed to create pull request: {str(e)}"
            }

    def _parse_datetime(self, time_str: str) -> datetime:
        """Parse datetime string in various formats."""
        # Try ISO 8601 format first
        formats = [
            '%Y-%m-%dT%H:%M:%S',
            '%Y-%m-%d %H:%M:%S',
            '%Y-%m-%d %H:%M',
            '%Y-%m-%d'
        ]

        for fmt in formats:
            try:
                return datetime.strptime(time_str, fmt)
            except ValueError:
                continue

        # If no format works, raise error
        raise ValueError(f"Could not parse datetime: {time_str}")

file_tools.py

"""File system tools with safety mechanisms."""

import itertools
import os
from pathlib import Path
from typing import Dict, Any
from llm_helper import LLMHelper
from config import Config


class FileTools:
    """File system tools with verification and safety checks."""

    def __init__(self, llm_helper: LLMHelper):
        """Initialize file tools with LLM helper."""
        self.llm_helper = llm_helper
        self.workspace_dir = Config.WORKSPACE_DIR

    def _resolve_path(self, path: str) -> Path:
        """Resolve path relative to workspace."""
        path_obj = Path(path)
        if not path_obj.is_absolute():
            path_obj = self.workspace_dir / path_obj
        return path_obj.resolve()

    def _is_safe_path(self, path: Path) -> bool:
        """Check if path is within workspace."""
        try:
            path.resolve().relative_to(self.workspace_dir.resolve())
            return True
        except ValueError:
            return False

    async def write_file(
        self,
        path: str,
        content: str,
        overwrite: bool = False
    ) -> Dict[str, Any]:
        """
        Write content to a file with safety checks.

        Args:
            path: File path (relative to workspace or absolute)
            content: Content to write
            overwrite: Whether to overwrite existing files

        Returns:
            Result dictionary with status and details
        """
        resolved_path = self._resolve_path(path)

        # Safety check: ensure path is within workspace
        if not self._is_safe_path(resolved_path):
            return {
                "success": False,
                "error": f"Path {path} is outside workspace directory"
            }

        # Check if file exists and overwrite is not allowed
        if resolved_path.exists() and not overwrite:
            # Request approval for overwriting
            if Config.REQUIRE_APPROVAL_FOR_DANGEROUS_OPS:
                approved, reason = self.llm_helper.request_approval(
                    "file_overwrite",
                    {
                        "path": str(resolved_path),
                        "existing_size": resolved_path.stat().st_size,
                        "new_content_size": len(content)
                    }
                )

                if not approved:
                    return {
                        "success": False,
                        "error": f"Overwrite not approved: {reason}"
                    }

        # Verify code syntax if it's a code file
        if Config.AUTO_VERIFY_CODE and resolved_path.suffix in ['.py', '.js', '.ts']:
            language = {'.py': 'python', '.js': 'javascript', '.ts': 'typescript'}[resolved_path.suffix]
            is_valid, error_msg = self.llm_helper.verify_code_syntax(content, language)

            if not is_valid:
                return {
                    "success": False,
                    "error": f"Syntax validation failed: {error_msg}",
                    "verification": "failed"
                }

        # Write the file
        try:
            resolved_path.parent.mkdir(parents=True, exist_ok=True)
            resolved_path.write_text(content)

            return {
                "success": True,
                "path": str(resolved_path),
                "bytes_written": len(content),
                "verification": "passed" if Config.AUTO_VERIFY_CODE else "skipped"
            }
        except Exception as e:
            return {
                "success": False,
                "error": f"Failed to write file: {str(e)}"
            }

    async def edit_file(
        self,
        path: str,
        search: str,
        replace: str
    ) -> Dict[str, Any]:
        """
        Edit a file by searching and replacing content.

        Args:
            path: File path
            search: Text to search for
            replace: Replacement text

        Returns:
            Result dictionary with status and details
        """
        resolved_path = self._resolve_path(path)

        # Safety check
        if not self._is_safe_path(resolved_path):
            return {
                "success": False,
                "error": f"Path {path} is outside workspace directory"
            }

        # Check if file exists
        if not resolved_path.exists():
            return {
                "success": False,
                "error": f"File {path} does not exist"
            }

        # Read current content
        try:
            current_content = resolved_path.read_text()
        except Exception as e:
            return {
                "success": False,
                "error": f"Failed to read file: {str(e)}"
            }

        # Check if search text exists
        if search not in current_content:
            return {
                "success": False,
                "error": f"Search text not found in file"
            }

        # Perform replacement
        new_content = current_content.replace(search, replace, 1)

        # Generate diff preview
        diff_preview = self._generate_diff(current_content, new_content)

        # Verify new content if it's code
        if Config.AUTO_VERIFY_CODE and resolved_path.suffix in ['.py', '.js', '.ts']:
            language = {'.py': 'python', '.js': 'javascript', '.ts': 'typescript'}[resolved_path.suffix]
            is_valid, error_msg = self.llm_helper.verify_code_syntax(new_content, language)

            if not is_valid:
                return {
                    "success": False,
                    "error": f"Syntax validation failed after edit: {error_msg}",
                    "diff_preview": diff_preview
                }

        # Write the modified content
        try:
            resolved_path.write_text(new_content)

            return {
                "success": True,
                "path": str(resolved_path),
                "diff_preview": diff_preview,
                "verification": "passed" if Config.AUTO_VERIFY_CODE else "skipped"
            }
        except Exception as e:
            return {
                "success": False,
                "error": f"Failed to write file: {str(e)}"
            }

    def _generate_diff(self, old_content: str, new_content: str) -> str:
        """Generate a simple diff preview."""
        old_lines = old_content.split('\n')
        new_lines = new_content.split('\n')

        diff_lines = []
        for i, (old, new) in enumerate(itertools.zip_longest(old_lines, new_lines, fillvalue=''), 1):
            if old != new:
                diff_lines.append(f"Line {i}:")
                diff_lines.append(f"  - {old}")
                diff_lines.append(f"  + {new}")

        return '\n'.join(diff_lines[:20])  # Limit to 20 lines

filesystem_enhanced.py

"""
Enhanced filesystem tools based on AWorld filesystem-server.
Provides comprehensive file and directory operations with safety checks.
"""
import json
import os
import shutil
import traceback
from pathlib import Path
from typing import Dict, Any, List

from config import Config


class FilesystemEnhanced:
    """Enhanced filesystem operations with safety and validation."""

    def __init__(self):
        self.workspace_dir = Path(Config.WORKSPACE_DIR).resolve()
        self.allowed_directories = [self.workspace_dir]

    def _resolve_path(self, path: str) -> Path:
        """Resolve path relative to workspace."""
        path_obj = Path(path)
        if not path_obj.is_absolute():
            path_obj = self.workspace_dir / path_obj
        return path_obj.resolve()

    def _is_safe_path(self, path: Path) -> bool:
        """Check if path is within allowed directories."""
        try:
            resolved = path.resolve()
            for allowed in self.allowed_directories:
                try:
                    resolved.relative_to(allowed.resolve())
                    return True
                except ValueError:
                    continue
            return False
        except Exception:
            return False

    async def read_text_file(
        self,
        file_path: str,
        encoding: str = "utf-8",
        max_size_mb: int = 10
    ) -> Dict[str, Any]:
        """
        Read a text file with size limits.

        Args:
            file_path: Path to the file
            encoding: File encoding
            max_size_mb: Maximum file size in MB

        Returns:
            Dictionary with file content and metadata
        """
        try:
            resolved_path = self._resolve_path(file_path)

            if not self._is_safe_path(resolved_path):
                return {
                    "success": False,
                    "error": f"Path {file_path} is outside allowed directories"
                }

            if not resolved_path.exists():
                return {
                    "success": False,
                    "error": f"File {file_path} does not exist"
                }

            # Check file size
            file_size = resolved_path.stat().st_size
            max_size_bytes = max_size_mb * 1024 * 1024

            if file_size > max_size_bytes:
                return {
                    "success": False,
                    "error": f"File too large: {file_size / (1024*1024):.2f}MB (max: {max_size_mb}MB)"
                }

            # Read file
            content = resolved_path.read_text(encoding=encoding)

            return {
                "success": True,
                "content": content,
                "file_path": str(resolved_path),
                "file_size": file_size,
                "encoding": encoding,
                "lines": len(content.splitlines())
            }

        except UnicodeDecodeError:
            return {
                "success": False,
                "error": f"File is not a valid text file with encoding {encoding}"
            }
        except Exception as e:
            return {
                "success": False,
                "error": f"Failed to read file: {str(e)}"
            }

    async def read_multiple_files(
        self,
        file_paths: List[str],
        encoding: str = "utf-8"
    ) -> Dict[str, Any]:
        """
        Read multiple files at once.

        Args:
            file_paths: List of file paths
            encoding: File encoding

        Returns:
            Dictionary with all file contents
        """
        results = {}
        errors = []

        for file_path in file_paths:
            result = await self.read_text_file(file_path, encoding)

            if result["success"]:
                results[file_path] = {
                    "content": result["content"],
                    "size": result["file_size"],
                    "lines": result["lines"]
                }
            else:
                errors.append({
                    "file": file_path,
                    "error": result["error"]
                })

        return {
            "success": len(results) > 0,
            "files_read": len(results),
            "files_failed": len(errors),
            "results": results,
            "errors": errors
        }

    async def list_directory_with_sizes(
        self,
        directory_path: str = "."
    ) -> Dict[str, Any]:
        """
        List directory contents with file sizes.

        Args:
            directory_path: Path to directory

        Returns:
            Dictionary with directory contents and sizes
        """
        try:
            resolved_path = self._resolve_path(directory_path)

            if not self._is_safe_path(resolved_path):
                return {
                    "success": False,
                    "error": f"Path {directory_path} is outside allowed directories"
                }

            if not resolved_path.exists():
                return {
                    "success": False,
                    "error": f"Directory {directory_path} does not exist"
                }

            if not resolved_path.is_dir():
                return {
                    "success": False,
                    "error": f"{directory_path} is not a directory"
                }

            # List contents with sizes
            contents = []
            total_size = 0

            for item in sorted(resolved_path.iterdir()):
                try:
                    is_dir = item.is_dir()
                    size = 0 if is_dir else item.stat().st_size
                    total_size += size

                    contents.append({
                        "name": item.name,
                        "type": "directory" if is_dir else "file",
                        "size": size,
                        "size_human": self._format_size(size),
                        "modified": item.stat().st_mtime
                    })
                except Exception as e:
                    contents.append({
                        "name": item.name,
                        "type": "unknown",
                        "error": str(e)
                    })

            return {
                "success": True,
                "directory": str(resolved_path),
                "total_items": len(contents),
                "total_size": total_size,
                "total_size_human": self._format_size(total_size),
                "contents": contents
            }

        except Exception as e:
            return {
                "success": False,
                "error": f"Failed to list directory: {str(e)}"
            }

    async def directory_tree(
        self,
        directory_path: str = ".",
        max_depth: int = 3,
        show_hidden: bool = False
    ) -> Dict[str, Any]:
        """
        Generate a tree structure of directory contents.

        Args:
            directory_path: Path to directory
            max_depth: Maximum depth to traverse
            show_hidden: Whether to show hidden files

        Returns:
            Dictionary with directory tree structure
        """
        try:
            resolved_path = self._resolve_path(directory_path)

            if not self._is_safe_path(resolved_path):
                return {
                    "success": False,
                    "error": f"Path {directory_path} is outside allowed directories"
                }

            def build_tree(path: Path, current_depth: int = 0) -> Dict[str, Any]:
                """Recursively build tree structure."""
                if current_depth >= max_depth:
                    return {"name": path.name, "type": "directory", "truncated": True}

                if not path.is_dir():
                    return {
                        "name": path.name,
                        "type": "file",
                        "size": path.stat().st_size
                    }

                children = []
                try:
                    for item in sorted(path.iterdir()):
                        # Skip hidden files if needed
                        if not show_hidden and item.name.startswith('.'):
                            continue

                        children.append(build_tree(item, current_depth + 1))
                except PermissionError:
                    return {
                        "name": path.name,
                        "type": "directory",
                        "error": "Permission denied"
                    }

                return {
                    "name": path.name,
                    "type": "directory",
                    "children": children,
                    "count": len(children)
                }

            tree = build_tree(resolved_path)

            return {
                "success": True,
                "root": str(resolved_path),
                "tree": tree,
                "max_depth": max_depth
            }

        except Exception as e:
            return {
                "success": False,
                "error": f"Failed to generate directory tree: {str(e)}"
            }

    async def search_files(
        self,
        pattern: str,
        directory_path: str = ".",
        recursive: bool = True,
        case_sensitive: bool = False
    ) -> Dict[str, Any]:
        """
        Search for files matching a pattern.

        Args:
            pattern: Glob pattern (e.g., "*.py", "test_*.txt")
            directory_path: Directory to search in
            recursive: Search recursively
            case_sensitive: Case-sensitive matching

        Returns:
            Dictionary with matching files
        """
        try:
            resolved_path = self._resolve_path(directory_path)

            if not self._is_safe_path(resolved_path):
                return {
                    "success": False,
                    "error": f"Path {directory_path} is outside allowed directories"
                }

            if not resolved_path.exists():
                return {
                    "success": False,
                    "error": f"Directory {directory_path} does not exist"
                }

            # Search for files
            if recursive:
                matches = list(resolved_path.rglob(pattern))
            else:
                matches = list(resolved_path.glob(pattern))

            # Filter to files only
            files = [m for m in matches if m.is_file()]

            results = []
            for file_path in sorted(files):
                try:
                    stat = file_path.stat()
                    results.append({
                        "path": str(file_path.relative_to(resolved_path)),
                        "absolute_path": str(file_path),
                        "size": stat.st_size,
                        "size_human": self._format_size(stat.st_size),
                        "modified": stat.st_mtime
                    })
                except Exception:
                    pass

            return {
                "success": True,
                "pattern": pattern,
                "directory": str(resolved_path),
                "recursive": recursive,
                "matches": len(results),
                "files": results
            }

        except Exception as e:
            return {
                "success": False,
                "error": f"Failed to search files: {str(e)}"
            }

    async def get_file_info(
        self,
        file_path: str
    ) -> Dict[str, Any]:
        """
        Get detailed information about a file.

        Args:
            file_path: Path to the file

        Returns:
            Dictionary with file information
        """
        try:
            resolved_path = self._resolve_path(file_path)

            if not self._is_safe_path(resolved_path):
                return {
                    "success": False,
                    "error": f"Path {file_path} is outside allowed directories"
                }

            if not resolved_path.exists():
                return {
                    "success": False,
                    "error": f"File {file_path} does not exist"
                }

            stat = resolved_path.stat()

            info = {
                "path": str(resolved_path),
                "name": resolved_path.name,
                "extension": resolved_path.suffix,
                "size": stat.st_size,
                "size_human": self._format_size(stat.st_size),
                "is_file": resolved_path.is_file(),
                "is_directory": resolved_path.is_dir(),
                "is_symlink": resolved_path.is_symlink(),
                "created": stat.st_ctime,
                "modified": stat.st_mtime,
                "accessed": stat.st_atime,
                "permissions": oct(stat.st_mode)[-3:]
            }

            # Add parent directory info
            info["parent"] = str(resolved_path.parent)

            # For text files, add line count
            if resolved_path.is_file() and resolved_path.suffix in ['.txt', '.py', '.md', '.json', '.yaml', '.yml']:
                try:
                    content = resolved_path.read_text()
                    info["lines"] = len(content.splitlines())
                    info["characters"] = len(content)
                except Exception:
                    pass

            return {
                "success": True,
                "file_info": info
            }

        except Exception as e:
            return {
                "success": False,
                "error": f"Failed to get file info: {str(e)}"
            }

    async def move_file(
        self,
        source: str,
        destination: str,
        overwrite: bool = False
    ) -> Dict[str, Any]:
        """
        Move or rename a file/directory.

        Args:
            source: Source path
            destination: Destination path
            overwrite: Whether to overwrite existing destination

        Returns:
            Dictionary with operation result
        """
        try:
            source_path = self._resolve_path(source)
            dest_path = self._resolve_path(destination)

            if not self._is_safe_path(source_path) or not self._is_safe_path(dest_path):
                return {
                    "success": False,
                    "error": "Paths must be within allowed directories"
                }

            if not source_path.exists():
                return {
                    "success": False,
                    "error": f"Source {source} does not exist"
                }

            if dest_path.exists() and not overwrite:
                return {
                    "success": False,
                    "error": f"Destination {destination} already exists. Use overwrite=True to replace."
                }

            # Perform move
            if dest_path.exists():
                if dest_path.is_dir():
                    shutil.rmtree(dest_path)
                else:
                    dest_path.unlink()

            shutil.move(str(source_path), str(dest_path))

            return {
                "success": True,
                "source": str(source_path),
                "destination": str(dest_path),
                "message": f"Moved {source} to {destination}"
            }

        except Exception as e:
            return {
                "success": False,
                "error": f"Failed to move file: {str(e)}"
            }

    async def copy_file(
        self,
        source: str,
        destination: str,
        overwrite: bool = False
    ) -> Dict[str, Any]:
        """
        Copy a file or directory.

        Args:
            source: Source path
            destination: Destination path
            overwrite: Whether to overwrite existing destination

        Returns:
            Dictionary with operation result
        """
        try:
            source_path = self._resolve_path(source)
            dest_path = self._resolve_path(destination)

            if not self._is_safe_path(source_path) or not self._is_safe_path(dest_path):
                return {
                    "success": False,
                    "error": "Paths must be within allowed directories"
                }

            if not source_path.exists():
                return {
                    "success": False,
                    "error": f"Source {source} does not exist"
                }

            if dest_path.exists() and not overwrite:
                return {
                    "success": False,
                    "error": f"Destination {destination} already exists"
                }

            # Perform copy
            if source_path.is_dir():
                if dest_path.exists():
                    shutil.rmtree(dest_path)
                shutil.copytree(source_path, dest_path)
            else:
                dest_path.parent.mkdir(parents=True, exist_ok=True)
                shutil.copy2(source_path, dest_path)

            return {
                "success": True,
                "source": str(source_path),
                "destination": str(dest_path),
                "message": f"Copied {source} to {destination}"
            }

        except Exception as e:
            return {
                "success": False,
                "error": f"Failed to copy file: {str(e)}"
            }

    async def delete_file(
        self,
        file_path: str,
        recursive: bool = False
    ) -> Dict[str, Any]:
        """
        Delete a file or directory.

        Args:
            file_path: Path to delete
            recursive: For directories, delete recursively

        Returns:
            Dictionary with operation result
        """
        try:
            resolved_path = self._resolve_path(file_path)

            if not self._is_safe_path(resolved_path):
                return {
                    "success": False,
                    "error": f"Path {file_path} is outside allowed directories"
                }

            if not resolved_path.exists():
                return {
                    "success": False,
                    "error": f"Path {file_path} does not exist"
                }

            # Delete
            if resolved_path.is_dir():
                if not recursive:
                    return {
                        "success": False,
                        "error": "Cannot delete directory without recursive=True"
                    }
                shutil.rmtree(resolved_path)
            else:
                resolved_path.unlink()

            return {
                "success": True,
                "deleted": str(resolved_path),
                "message": f"Deleted {file_path}"
            }

        except Exception as e:
            return {
                "success": False,
                "error": f"Failed to delete: {str(e)}"
            }

    async def create_directory(
        self,
        directory_path: str,
        parents: bool = True
    ) -> Dict[str, Any]:
        """
        Create a new directory.

        Args:
            directory_path: Path for new directory
            parents: Create parent directories if needed

        Returns:
            Dictionary with operation result
        """
        try:
            resolved_path = self._resolve_path(directory_path)

            if not self._is_safe_path(resolved_path):
                return {
                    "success": False,
                    "error": f"Path {directory_path} is outside allowed directories"
                }

            if resolved_path.exists():
                return {
                    "success": False,
                    "error": f"Directory {directory_path} already exists"
                }

            # Create directory
            resolved_path.mkdir(parents=parents, exist_ok=False)

            return {
                "success": True,
                "directory": str(resolved_path),
                "message": f"Created directory {directory_path}"
            }

        except Exception as e:
            return {
                "success": False,
                "error": f"Failed to create directory: {str(e)}"
            }

    async def list_allowed_directories(self) -> Dict[str, Any]:
        """
        List directories that are accessible.

        Returns:
            Dictionary with allowed directories
        """
        return {
            "success": True,
            "allowed_directories": [str(d) for d in self.allowed_directories],
            "count": len(self.allowed_directories)
        }

    def _format_size(self, size_bytes: int) -> str:
        """Format file size in human-readable format."""
        for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
            if size_bytes < 1024.0:
                return f"{size_bytes:.2f} {unit}"
            size_bytes /= 1024.0
        return f"{size_bytes:.2f} PB"

hello.py

#!/usr/bin/env python3
"""A simple greeting script."""

def main():
    name = "World"
    print(f"Hello, {name}!")

if __name__ == "__main__":
    main()

llm_helper.py

"""LLM helper for safety checks, approval, and summarization."""

import json
from typing import Optional, Dict, Any
from openai import OpenAI
from config import Config


def _reasoning_safe_temperature(model, requested=1.0):
    """Reasoning models (Kimi K3, GPT-5, ...) only accept temperature=1.
    Return 1 for those; otherwise the requested value so non-reasoning
    providers (Doubao, DeepSeek, older Moonshot) are unchanged."""
    m = str(model or "").lower().replace("/", "-")
    return 1 if ("kimi-k3" in m or "gpt-5" in m) else requested


def _parse_json_response(content):
    """Parse a JSON object out of an LLM reply, tolerating markdown fences.

    Reasoning models (notably kimi-k3) reliably return valid JSON but wrap it
    in a ```json ... ``` code fence, so a bare json.loads() fails with
    "Expecting value: line 1 column 1". Strip an optional fence and, as a last
    resort, slice from the first '{' to the last '}' before parsing."""
    text = (content or "").strip()
    if text.startswith("```"):
        # Drop the opening fence line (``` or ```json) and the closing fence.
        text = text.split("\n", 1)[1] if "\n" in text else ""
        if text.rstrip().endswith("```"):
            text = text.rstrip()[:-3]
        text = text.strip()
    try:
        return json.loads(text)
    except json.JSONDecodeError:
        start, end = text.find("{"), text.rfind("}")
        if start != -1 and end != -1 and end > start:
            return json.loads(text[start:end + 1])
        raise


class LLMHelper:
    """Helper class for LLM-based operations."""

    def __init__(self):
        """Initialize the LLM helper.

        The OpenAI-compatible client is created lazily on first use so that
        execution tools which do not need an LLM (e.g. Python code execution
        with local syntax checking, terminal commands, file writes) work
        offline without any API key configured. Methods that actually call
        the LLM (approval, summarization, non-Python syntax check) will raise
        or fail-safe if no key is available.
        """
        self.client = None
        self.model = None
        self.provider = None

    def _ensure_client(self) -> None:
        """Create the LLM client on first use (raises if no API key)."""
        if self.client is None:
            llm_config = Config.get_llm_config()
            # All providers use OpenAI-compatible API
            self.client = OpenAI(
                api_key=llm_config["api_key"],
                base_url=llm_config.get("base_url")
            )
            self.model = llm_config["model"]
            self.provider = llm_config["provider"]

    def request_approval(
        self, 
        operation: str, 
        details: Dict[str, Any]
    ) -> tuple[bool, str]:
        """
        Request LLM approval for a dangerous operation.

        Args:
            operation: The operation name
            details: Details about the operation

        Returns:
            Tuple of (approved, reason)
        """
        prompt = f"""You are a safety reviewer for an AI agent execution system.
Review the following operation and determine if it should be approved.

Operation: {operation}
Details: {json.dumps(details, indent=2)}

Analyze the operation for:
1. Potential data loss or destructive actions
2. Security risks
3. Resource consumption concerns
4. Compliance with best practices

Respond in JSON format:
{{
    "approved": true/false,
    "reason": "Brief explanation of your decision",
    "risk_level": "low/medium/high",
    "recommendations": ["List of recommendations if any"]
}}
"""

        try:
            self._ensure_client()
            response = self.client.chat.completions.create(
                model=self.model,
                messages=[
                    {
                        "role": "system",
                        "content": "You are a cautious safety reviewer. Approve operations that are safe and reject risky ones."
                    },
                    {"role": "user", "content": prompt}
                ],
                temperature=_reasoning_safe_temperature(self.model, 0.1),
                max_tokens=Config.MAX_TOKENS
            )

            result = _parse_json_response(response.choices[0].message.content)
            return result["approved"], result["reason"]

        except Exception as e:
            # If approval check fails, default to rejection for safety
            return False, f"Approval check failed: {str(e)}"

    def summarize_output(
        self, 
        tool_name: str,
        output: str
    ) -> str:
        """
        Summarize complex tool output.

        Args:
            tool_name: Name of the tool that produced the output
            output: The output to summarize

        Returns:
            Summarized output
        """

        prompt = f"""Summarize the following output from the '{tool_name}' tool.
Focus on:
1. Key results or findings
2. Errors or warnings
3. Important patterns or insights
4. Actionable information

Output to summarize:
{output[:5000]}  # Limit input to avoid token limits

Provide a concise summary that captures the essential information."""

        try:
            self._ensure_client()
            response = self.client.chat.completions.create(
                model=self.model,
                messages=[
                    {
                        "role": "system",
                        "content": "You are an expert at summarizing technical output. Be concise and focus on actionable information."
                    },
                    {"role": "user", "content": prompt}
                ],
                temperature=_reasoning_safe_temperature(self.model, 0.1),
                max_tokens=Config.MAX_TOKENS
            )

            summary = response.choices[0].message.content
            return f"[SUMMARIZED OUTPUT]\n{summary}\n\n[Original output length: {len(output)} characters]"

        except Exception as e:
            return f"[SUMMARIZATION FAILED: {str(e)}]\n\n{output[:Config.MAX_OUTPUT_LENGTH]}..."

    def analyze_error(
        self,
        tool_name: str,
        command: str,
        error_output: str
    ) -> str:
        """
        Analyze error output and provide suggestions.

        Args:
            tool_name: Name of the tool that produced the error
            command: The command or code that failed
            error_output: The error output

        Returns:
            Analysis with suggestions
        """
        prompt = f"""Analyze the following error from the '{tool_name}' tool:

Command/Code:
{command}

Error Output:
{error_output[:3000]}

Provide:
1. Root cause analysis
2. Suggested fixes
3. Prevention strategies

Be concise and practical."""

        try:
            self._ensure_client()
            response = self.client.chat.completions.create(
                model=self.model,
                messages=[
                    {
                        "role": "system",
                        "content": "You are an expert debugger. Analyze errors and provide clear, actionable solutions."
                    },
                    {"role": "user", "content": prompt}
                ],
                temperature=_reasoning_safe_temperature(self.model, 0.2),
                max_tokens=Config.MAX_TOKENS
            )

            return response.choices[0].message.content

        except Exception as e:
            return f"Error analysis failed: {str(e)}"

    def verify_code_syntax(
        self,
        code: str,
        language: str = "python"
    ) -> tuple[bool, Optional[str]]:
        """
        Verify code syntax and provide feedback.

        Args:
            code: The code to verify
            language: Programming language

        Returns:
            Tuple of (is_valid, error_message)
        """
        # For Python, we can do actual syntax checking
        if language == "python":
            try:
                compile(code, "<string>", "exec")
                return True, None
            except SyntaxError as e:
                return False, f"Syntax error at line {e.lineno}: {e.msg}"

        # For other languages, use LLM for basic validation
        prompt = f"""Check the following {language} code for syntax errors:

```{language}
{code}

Respond in JSON format: {{ "valid": true/false, "errors": ["List of syntax errors if any"], "warnings": ["List of warnings if any"] }} """

    try:
        self._ensure_client()
        response = self.client.chat.completions.create(
            model=self.model,
            messages=[
                {
                    "role": "system",
                    "content": f"You are a {language} syntax validator. Check code for syntax errors."
                },
                {"role": "user", "content": prompt}
            ],
            temperature=_reasoning_safe_temperature(self.model, 0.1),
            max_tokens=Config.MAX_TOKENS
        )

        result = _parse_json_response(response.choices[0].message.content)
        if result["valid"]:
            return True, None
        else:
            return False, "; ".join(result["errors"])

    except Exception as e:
        # If validation fails, allow the code through
        return True, None
### `multilang_executor.py`

```python
"""Multi-language code execution support inspired by SandboxFusion."""

import asyncio
import subprocess
import tempfile
import os
import shutil
import time
import base64
import psutil
from typing import Dict, Any, Optional, List
from enum import Enum
import logging

logger = logging.getLogger(__name__)


def try_decode(s: bytes) -> str:
    """Safely decode bytes to string."""
    try:
        return s.decode('utf-8', errors='replace')
    except Exception as e:
        return f'[DecodeError] {e}'


async def get_all_output(stream) -> str:
    """Read stream until EOF. Call after the process has exited or been killed."""
    if stream is None:
        return ""
    try:
        result = await stream.read()
        return try_decode(result)
    except Exception as e:
        logger.debug(f"Error reading output: {e}")
        return ""


def kill_process_tree(pid: int):
    """Kill process and all its children."""
    try:
        parent = psutil.Process(pid)
        children = parent.children(recursive=True)

        # Kill children first
        for child in children:
            try:
                child.kill()
            except psutil.NoSuchProcess:
                pass

        # Kill parent
        try:
            parent.kill()
        except psutil.NoSuchProcess:
            pass

    except psutil.NoSuchProcess:
        pass
    except Exception as e:
        logger.warning(f'Error killing process tree: {e}')


class ExecutionStatus(str, Enum):
    """Execution status."""
    SUCCESS = "success"
    FAILED = "failed"
    TIMEOUT = "timeout"
    ERROR = "error"


class LanguageExecutor:
    """Multi-language code executor."""

    def __init__(self, workspace_dir: str = None):
        """Initialize executor."""
        self.workspace_dir = workspace_dir or os.getcwd()

    async def execute_code(
        self,
        code: str,
        language: str,
        timeout: float = 30.0,
        compile_timeout: float = 10.0,
        stdin: Optional[str] = None,
        files: Optional[Dict[str, str]] = None
    ) -> Dict[str, Any]:
        """
        Execute code in the specified language.

        Args:
            code: Code to execute
            language: Programming language
            timeout: Execution timeout in seconds
            compile_timeout: Compilation timeout in seconds
            stdin: Optional stdin input
            files: Optional additional files (name -> content)

        Returns:
            Execution result dictionary
        """
        if language is None:
            language = "python"
        language = language.lower()

        # Map language to executor
        executors = {
            'python': self._run_python,
            'python3': self._run_python,
            'javascript': self._run_javascript,
            'js': self._run_javascript,
            'typescript': self._run_typescript,
            'ts': self._run_typescript,
            'go': self._run_go,
            'java': self._run_java,
            'cpp': self._run_cpp,
            'c++': self._run_cpp,
            'rust': self._run_rust,
            'php': self._run_php,
            'bash': self._run_bash,
            'shell': self._run_bash,
            'sh': self._run_bash,
            'nodejs': self._run_javascript,
            'node': self._run_javascript,
        }

        executor = executors.get(language)
        if not executor:
            return {
                "status": ExecutionStatus.ERROR,
                "error": f"Unsupported language: {language}. Supported: {', '.join(sorted(set(executors.keys())))}"
            }

        try:
            return await executor(code, timeout, compile_timeout, stdin, files or {})
        except Exception as e:
            logger.exception(f"Error executing {language} code")
            return {
                "status": ExecutionStatus.ERROR,
                "error": f"Execution failed: {str(e)}"
            }

    async def _run_command(
        self,
        command: str,
        timeout: float,
        stdin: Optional[str] = None,
        cwd: Optional[str] = None,
        shell: bool = True
    ) -> Dict[str, Any]:
        """Run a shell command and return results with proper process management."""
        process = None
        try:
            logger.debug(f'Running command: {command[:100]}...')

            process = await asyncio.create_subprocess_shell(
                command,
                stdin=asyncio.subprocess.PIPE if stdin else None,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE,
                cwd=cwd,
                executable='/bin/bash'
            )

            # Write stdin if provided
            if stdin and process.stdin:
                try:
                    process.stdin.write(stdin.encode())
                    await process.stdin.drain()
                    process.stdin.close()
                except Exception as e:
                    logger.warning(f"Failed to write stdin: {e}")

            start_time = time.time()

            try:
                # Wait for process with timeout
                await asyncio.wait_for(process.wait(), timeout=timeout)
                execution_time = time.time() - start_time

                # Read output non-blocking
                stdout = await get_all_output(process.stdout)
                stderr = await get_all_output(process.stderr)

                logger.debug(f'Command completed in {execution_time:.2f}s')

                return {
                    "status": ExecutionStatus.SUCCESS if process.returncode == 0 else ExecutionStatus.FAILED,
                    "returncode": process.returncode,
                    "stdout": stdout,
                    "stderr": stderr,
                    "execution_time": execution_time
                }

            except asyncio.TimeoutError:
                execution_time = time.time() - start_time

                # Kill first so pipes close, then drain remaining output
                if psutil.pid_exists(process.pid):
                    kill_process_tree(process.pid)
                    logger.info(f'Process {process.pid} killed due to timeout')

                stdout = await get_all_output(process.stdout)
                stderr = await get_all_output(process.stderr)

                return {
                    "status": ExecutionStatus.TIMEOUT,
                    "error": f"Execution timed out after {timeout} seconds",
                    "stdout": stdout,
                    "stderr": stderr,
                    "execution_time": execution_time
                }

        except Exception as e:
            logger.exception(f"Error running command: {command[:100]}")
            return {
                "status": ExecutionStatus.ERROR,
                "error": f"Command execution failed: {str(e)}"
            }
        finally:
            # Cleanup: ensure process is terminated
            if process and psutil.pid_exists(process.pid):
                kill_process_tree(process.pid)

    def _write_files(self, tmp_dir: str, files: Dict[str, str]):
        """Write additional files to tmp directory."""
        for filename, content in files.items():
            if not content or "IGNORE_THIS_FILE" in filename:
                continue

            filepath = os.path.join(tmp_dir, filename)
            dirpath = os.path.dirname(filepath)

            if dirpath:
                os.makedirs(dirpath, exist_ok=True)

            # Handle base64 encoded content
            try:
                if self._is_base64(content):
                    with open(filepath, 'wb') as f:
                        f.write(base64.b64decode(content))
                else:
                    with open(filepath, 'w', encoding='utf-8') as f:
                        f.write(content)
            except Exception as e:
                logger.warning(f"Failed to write file {filename}: {e}")

    def _is_base64(self, s: str) -> bool:
        """Check if string is base64 encoded."""
        try:
            if len(s) % 4 != 0 or not all(c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' for c in s):
                return False
            base64.b64decode(s, validate=True)
            return True
        except Exception:
            return False

    async def _run_python(
        self,
        code: str,
        timeout: float,
        compile_timeout: float,
        stdin: Optional[str],
        files: Dict[str, str]
    ) -> Dict[str, Any]:
        """Execute Python code."""
        with tempfile.TemporaryDirectory(prefix='python_', ignore_cleanup_errors=True) as tmp_dir:
            self._write_files(tmp_dir, files)
            code_file = os.path.join(tmp_dir, 'main.py')
            with open(code_file, 'w', encoding='utf-8') as f:
                f.write(code)

            # Use python3 with unbuffered output
            result = await self._run_command(
                f'python3 -u {code_file}',
                timeout,
                stdin,
                tmp_dir
            )
            result['language'] = 'python'
            return result

    async def _run_javascript(
        self,
        code: str,
        timeout: float,
        compile_timeout: float,
        stdin: Optional[str],
        files: Dict[str, str]
    ) -> Dict[str, Any]:
        """Execute JavaScript code with Node.js."""
        with tempfile.TemporaryDirectory(prefix='js_', ignore_cleanup_errors=True) as tmp_dir:
            self._write_files(tmp_dir, files)

            # Create package.json if not exists to enable ES modules
            if 'package.json' not in files:
                package_json = {
                    "type": "module",
                    "dependencies": {}
                }
                with open(os.path.join(tmp_dir, 'package.json'), 'w') as f:
                    import json
                    json.dump(package_json, f)

            code_file = os.path.join(tmp_dir, 'main.js')
            with open(code_file, 'w', encoding='utf-8') as f:
                f.write(code)

            result = await self._run_command(
                f'node {code_file}',
                timeout,
                stdin,
                tmp_dir
            )
            result['language'] = 'javascript'
            return result

    async def _run_typescript(
        self,
        code: str,
        timeout: float,
        compile_timeout: float,
        stdin: Optional[str],
        files: Dict[str, str]
    ) -> Dict[str, Any]:
        """Execute TypeScript code with tsx."""
        with tempfile.TemporaryDirectory(prefix='ts_', ignore_cleanup_errors=True) as tmp_dir:
            self._write_files(tmp_dir, files)
            code_file = os.path.join(tmp_dir, 'main.ts')
            with open(code_file, 'w', encoding='utf-8') as f:
                f.write(code)

            # Check if tsx is available, fallback to ts-node
            check_tsx = await self._run_command('which tsx 2>/dev/null', 1.0)
            cmd = 'tsx' if check_tsx['status'] == ExecutionStatus.SUCCESS else 'ts-node'

            result = await self._run_command(
                f'{cmd} {code_file}',
                timeout,
                stdin,
                tmp_dir
            )
            result['language'] = 'typescript'
            return result

    async def _run_go(
        self,
        code: str,
        timeout: float,
        compile_timeout: float,
        stdin: Optional[str],
        files: Dict[str, str]
    ) -> Dict[str, Any]:
        """Execute Go code."""
        with tempfile.TemporaryDirectory(prefix='go_', ignore_cleanup_errors=True) as tmp_dir:
            self._write_files(tmp_dir, files)

            # Initialize go module (ignore errors if already exists)
            await self._run_command('go mod init main 2>/dev/null || true', 2.0, cwd=tmp_dir)

            code_file = os.path.join(tmp_dir, 'main.go')
            with open(code_file, 'w', encoding='utf-8') as f:
                f.write(code)

            # Compile
            compile_result = await self._run_command(
                'go build -o main main.go',
                compile_timeout,
                cwd=tmp_dir
            )

            if compile_result['status'] != ExecutionStatus.SUCCESS:
                return {
                    "status": ExecutionStatus.FAILED,
                    "language": "go",
                    "phase": "compilation",
                    "returncode": compile_result.get('returncode', 1),
                    "stdout": compile_result.get('stdout', ''),
                    "stderr": compile_result.get('stderr', ''),
                    "error": "Compilation failed"
                }

            # Run
            result = await self._run_command(
                './main',
                timeout,
                stdin,
                tmp_dir
            )
            result['language'] = 'go'
            result['compile_stdout'] = compile_result.get('stdout', '')
            result['compile_stderr'] = compile_result.get('stderr', '')
            return result

    async def _run_java(
        self,
        code: str,
        timeout: float,
        compile_timeout: float,
        stdin: Optional[str],
        files: Dict[str, str]
    ) -> Dict[str, Any]:
        """Execute Java code."""
        with tempfile.TemporaryDirectory(prefix='java_', ignore_cleanup_errors=True) as tmp_dir:
            self._write_files(tmp_dir, files)

            # Extract class name from public class declaration
            class_name = 'Main'
            import re
            match = re.search(r'public\s+class\s+(\w+)', code)
            if match:
                class_name = match.group(1)

            code_file = os.path.join(tmp_dir, f'{class_name}.java')
            with open(code_file, 'w', encoding='utf-8') as f:
                f.write(code)

            # Prepare classpath for additional jars
            jars = [f for f in files.keys() if f.endswith('.jar')]
            classpath = '.:' + ':'.join(jars) if jars else '.'

            # Compile
            compile_result = await self._run_command(
                f'javac -cp {classpath} {class_name}.java',
                compile_timeout,
                cwd=tmp_dir
            )

            if compile_result['status'] != ExecutionStatus.SUCCESS:
                return {
                    "status": ExecutionStatus.FAILED,
                    "language": "java",
                    "phase": "compilation",
                    "returncode": compile_result.get('returncode', 1),
                    "stdout": compile_result.get('stdout', ''),
                    "stderr": compile_result.get('stderr', ''),
                    "error": "Compilation failed"
                }

            # Run with assertions enabled
            result = await self._run_command(
                f'java -cp {classpath} -ea {class_name}',
                timeout,
                stdin,
                tmp_dir
            )
            result['language'] = 'java'
            result['compile_stdout'] = compile_result.get('stdout', '')
            result['compile_stderr'] = compile_result.get('stderr', '')
            return result

    async def _run_cpp(
        self,
        code: str,
        timeout: float,
        compile_timeout: float,
        stdin: Optional[str],
        files: Dict[str, str]
    ) -> Dict[str, Any]:
        """Execute C++ code."""
        with tempfile.TemporaryDirectory(prefix='cpp_', ignore_cleanup_errors=True) as tmp_dir:
            self._write_files(tmp_dir, files)
            code_file = os.path.join(tmp_dir, 'main.cpp')
            with open(code_file, 'w', encoding='utf-8') as f:
                f.write(code)

            # Compile with commonly needed flags
            # Try with optional libraries (crypto, ssl, pthread)
            compile_flags = '-std=c++17 -O2'
            optional_libs = []

            # Check if we need pthread
            if '#include <thread>' in code or 'std::thread' in code:
                optional_libs.append('-lpthread')

            libs = ' '.join(optional_libs)
            compile_result = await self._run_command(
                f'g++ {compile_flags} main.cpp -o main {libs}',
                compile_timeout,
                cwd=tmp_dir
            )

            if compile_result['status'] != ExecutionStatus.SUCCESS:
                return {
                    "status": ExecutionStatus.FAILED,
                    "language": "cpp",
                    "phase": "compilation",
                    "returncode": compile_result.get('returncode', 1),
                    "stdout": compile_result.get('stdout', ''),
                    "stderr": compile_result.get('stderr', ''),
                    "error": "Compilation failed"
                }

            # Run
            result = await self._run_command(
                './main',
                timeout,
                stdin,
                tmp_dir
            )
            result['language'] = 'cpp'
            result['compile_stdout'] = compile_result.get('stdout', '')
            result['compile_stderr'] = compile_result.get('stderr', '')
            return result

    async def _run_rust(
        self,
        code: str,
        timeout: float,
        compile_timeout: float,
        stdin: Optional[str],
        files: Dict[str, str]
    ) -> Dict[str, Any]:
        """Execute Rust code."""
        with tempfile.TemporaryDirectory(prefix='rust_', ignore_cleanup_errors=True) as tmp_dir:
            self._write_files(tmp_dir, files)
            code_file = os.path.join(tmp_dir, 'main.rs')
            with open(code_file, 'w', encoding='utf-8') as f:
                f.write(code)

            # Compile with optimizations
            compile_result = await self._run_command(
                'rustc -O main.rs -o main',
                compile_timeout,
                cwd=tmp_dir
            )

            if compile_result['status'] != ExecutionStatus.SUCCESS:
                return {
                    "status": ExecutionStatus.FAILED,
                    "language": "rust",
                    "phase": "compilation",
                    "returncode": compile_result.get('returncode', 1),
                    "stdout": compile_result.get('stdout', ''),
                    "stderr": compile_result.get('stderr', ''),
                    "error": "Compilation failed"
                }

            # Run
            result = await self._run_command(
                './main',
                timeout,
                stdin,
                tmp_dir
            )
            result['language'] = 'rust'
            result['compile_stdout'] = compile_result.get('stdout', '')
            result['compile_stderr'] = compile_result.get('stderr', '')
            return result

    async def _run_php(
        self,
        code: str,
        timeout: float,
        compile_timeout: float,
        stdin: Optional[str],
        files: Dict[str, str]
    ) -> Dict[str, Any]:
        """Execute PHP code."""
        with tempfile.TemporaryDirectory(prefix='php_', ignore_cleanup_errors=True) as tmp_dir:
            self._write_files(tmp_dir, files)

            # Ensure PHP tags
            code_clean = code.strip()
            if not code_clean.startswith('<?php') and not code_clean.startswith('<?'):
                code = '<?php\n' + code

            code_file = os.path.join(tmp_dir, 'main.php')
            with open(code_file, 'w', encoding='utf-8') as f:
                f.write(code)

            result = await self._run_command(
                f'php -f {code_file}',
                timeout,
                stdin,
                tmp_dir
            )
            result['language'] = 'php'
            return result

    async def _run_bash(
        self,
        code: str,
        timeout: float,
        compile_timeout: float,
        stdin: Optional[str],
        files: Dict[str, str]
    ) -> Dict[str, Any]:
        """Execute Bash script."""
        with tempfile.TemporaryDirectory(prefix='bash_', ignore_cleanup_errors=True) as tmp_dir:
            self._write_files(tmp_dir, files)
            code_file = os.path.join(tmp_dir, 'script.sh')
            with open(code_file, 'w', encoding='utf-8') as f:
                # Add shebang if not present
                if not code.startswith('#!'):
                    f.write('#!/bin/bash\n')
                f.write(code)

            # Make executable
            os.chmod(code_file, 0o755)

            result = await self._run_command(
                f'bash {code_file}',
                timeout,
                stdin,
                tmp_dir
            )
            result['language'] = 'bash'
            return result

quickstart.py

"""Quick start guide for the execution tools MCP server."""

import asyncio
from llm_helper import LLMHelper
from file_tools import FileTools
from execution_tools import ExecutionTools


async def quickstart():
    """Quick demonstration of the execution tools."""
    print("=== Execution Tools MCP Server - Quick Start ===\n")

    # Initialize
    print("Initializing tools...")
    llm_helper = LLMHelper()
    file_tools = FileTools(llm_helper)
    execution_tools = ExecutionTools(llm_helper)

    # 1. File operations
    print("\n1. File Operations Demo")
    print("-" * 50)

    print("\nWriting a Python script...")
    result = await file_tools.write_file(
        path="hello.py",
        content="""#!/usr/bin/env python3
\"\"\"A simple greeting script.\"\"\"

def main():
    name = "World"
    print(f"Hello, {name}!")

if __name__ == "__main__":
    main()
""",
        overwrite=True
    )
    print(f"Status: {'✓ Success' if result['success'] else '✗ Failed'}")
    if result['success']:
        print(f"Written to: {result['path']}")
        print(f"Verification: {result['verification']}")

    # 2. Code execution
    print("\n2. Code Interpreter Demo")
    print("-" * 50)

    print("\nExecuting Python code...")
    result = await execution_tools.code_interpreter(
        code="""
# Calculate fibonacci sequence
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print("Fibonacci sequence (first 10 numbers):")
for i in range(10):
    print(f"F({i}) = {fibonacci(i)}")
"""
    )
    print(f"Status: {'✓ Success' if result['success'] else '✗ Failed'}")
    if result['success']:
        print("Output:")
        print(result['stdout'][:500])  # Print first 500 chars

    # 3. Terminal execution
    print("\n3. Virtual Terminal Demo")
    print("-" * 50)

    print("\nExecuting shell command...")
    result = await execution_tools.virtual_terminal(
        command="python --version && echo 'Current directory:' && pwd"
    )
    print(f"Status: {'✓ Success' if result['success'] else '✗ Failed'}")
    if result['success']:
        print("Output:")
        print(result['stdout'])

    # Summary
    print("\n" + "=" * 50)
    print("Quick start completed!")
    print("\nKey Features:")
    print("  • File operations with automatic syntax verification")
    print("  • Code execution with error analysis")
    print("  • Shell commands with result summarization")
    print("  • LLM-based approval for dangerous operations")
    print("  • External integrations (Google Calendar, GitHub)")
    print("\nNext Steps:")
    print("  • Run 'python examples.py' for more examples")
    print("  • Run 'python server.py' to start the MCP server")
    print("  • See README.md for full documentation")


if __name__ == "__main__":
    asyncio.run(quickstart())

server.py

"""MCP server for execution tools."""

import asyncio
import json
from typing import Any
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationOptions
import mcp.server.stdio
import mcp.types as types

from config import Config
from llm_helper import LLMHelper
from file_tools import FileTools
from execution_tools import ExecutionTools
from external_tools import ExternalTools


# Initialize server
server = Server("execution-tools")

# Initialize tools
llm_helper = LLMHelper()
file_tools = FileTools(llm_helper)
execution_tools = ExecutionTools(llm_helper)
external_tools = ExternalTools(llm_helper)


@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
    """List available tools."""
    return [
        types.Tool(
            name="file_write",
            description="Write content to a file with automatic syntax verification",
            inputSchema={
                "type": "object",
                "properties": {
                    "path": {
                        "type": "string",
                        "description": "File path (relative to workspace or absolute)"
                    },
                    "content": {
                        "type": "string",
                        "description": "Content to write"
                    },
                    "overwrite": {
                        "type": "boolean",
                        "description": "Whether to overwrite existing files",
                        "default": False
                    }
                },
                "required": ["path", "content"]
            }
        ),
        types.Tool(
            name="file_edit",
            description="Edit an existing file by searching and replacing content",
            inputSchema={
                "type": "object",
                "properties": {
                    "path": {
                        "type": "string",
                        "description": "File path"
                    },
                    "search": {
                        "type": "string",
                        "description": "Text to search for"
                    },
                    "replace": {
                        "type": "string",
                        "description": "Replacement text"
                    }
                },
                "required": ["path", "search", "replace"]
            }
        ),
        types.Tool(
            name="code_interpreter",
            description="Execute code in multiple programming languages in a sandboxed environment with result analysis. Supports: Python, JavaScript, TypeScript, Go, Java, C++, Rust, PHP, Bash",
            inputSchema={
                "type": "object",
                "properties": {
                    "code": {
                        "type": "string",
                        "description": "Code to execute"
                    },
                    "language": {
                        "type": "string",
                        "description": "Programming language (python, javascript, typescript, go, java, cpp, rust, php, bash)",
                        "default": "python"
                    },
                    "timeout": {
                        "type": "number",
                        "description": "Execution timeout in seconds",
                        "default": 30.0
                    },
                    "stdin": {
                        "type": "string",
                        "description": "Optional stdin input for the program"
                    },
                    "files": {
                        "type": "object",
                        "description": "Optional additional files (filename -> content mapping)",
                        "additionalProperties": {"type": "string"}
                    }
                },
                "required": ["code"]
            }
        ),
        types.Tool(
            name="virtual_terminal",
            description="Execute shell commands with error summarization",
            inputSchema={
                "type": "object",
                "properties": {
                    "command": {
                        "type": "string",
                        "description": "Shell command to execute"
                    },
                    "timeout": {
                        "type": "integer",
                        "description": "Timeout in seconds",
                        "default": 30
                    }
                },
                "required": ["command"]
            }
        ),
        types.Tool(
            name="google_calendar_add",
            description="Add an event to Google Calendar",
            inputSchema={
                "type": "object",
                "properties": {
                    "summary": {
                        "type": "string",
                        "description": "Event title"
                    },
                    "start_time": {
                        "type": "string",
                        "description": "Start time (ISO 8601 format, e.g., 2024-01-01T10:00:00)"
                    },
                    "end_time": {
                        "type": "string",
                        "description": "End time (ISO 8601 format)"
                    },
                    "description": {
                        "type": "string",
                        "description": "Event description"
                    },
                    "location": {
                        "type": "string",
                        "description": "Event location"
                    }
                },
                "required": ["summary", "start_time", "end_time"]
            }
        ),
        types.Tool(
            name="github_create_pr",
            description="Create a GitHub Pull Request",
            inputSchema={
                "type": "object",
                "properties": {
                    "repo_name": {
                        "type": "string",
                        "description": "Repository name (format: owner/repo)"
                    },
                    "title": {
                        "type": "string",
                        "description": "PR title"
                    },
                    "body": {
                        "type": "string",
                        "description": "PR description"
                    },
                    "head_branch": {
                        "type": "string",
                        "description": "Source branch"
                    },
                    "base_branch": {
                        "type": "string",
                        "description": "Target branch",
                        "default": "main"
                    }
                },
                "required": ["repo_name", "title", "body", "head_branch"]
            }
        )
    ]


@server.call_tool()
async def handle_call_tool(
    name: str,
    arguments: dict[str, Any] | None
) -> list[types.TextContent]:
    """Handle tool calls."""
    if arguments is None:
        arguments = {}

    try:
        # Route to appropriate tool
        if name == "file_write":
            result = await file_tools.write_file(
                path=arguments["path"],
                content=arguments["content"],
                overwrite=arguments.get("overwrite", False)
            )
        elif name == "file_edit":
            result = await file_tools.edit_file(
                path=arguments["path"],
                search=arguments["search"],
                replace=arguments["replace"]
            )
        elif name == "code_interpreter":
            result = await execution_tools.code_interpreter(
                code=arguments["code"],
                language=arguments.get("language") or "python",
                timeout=arguments.get("timeout", 30.0),
                stdin=arguments.get("stdin"),
                files=arguments.get("files")
            )
        elif name == "virtual_terminal":
            result = await execution_tools.virtual_terminal(
                command=arguments["command"],
                timeout=arguments.get("timeout", 30)
            )
        elif name == "google_calendar_add":
            result = await external_tools.google_calendar_add(
                summary=arguments["summary"],
                start_time=arguments["start_time"],
                end_time=arguments["end_time"],
                description=arguments.get("description"),
                location=arguments.get("location")
            )
        elif name == "github_create_pr":
            result = await external_tools.github_create_pr(
                repo_name=arguments["repo_name"],
                title=arguments["title"],
                body=arguments["body"],
                head_branch=arguments["head_branch"],
                base_branch=arguments.get("base_branch", "main")
            )
        else:
            raise ValueError(f"Unknown tool: {name}")

        # Format result
        return [
            types.TextContent(
                type="text",
                text=json.dumps(result, indent=2)
            )
        ]

    except Exception as e:
        return [
            types.TextContent(
                type="text",
                text=json.dumps({
                    "success": False,
                    "error": f"Tool execution failed: {str(e)}"
                }, indent=2)
            )
        ]


async def main():
    """Run the MCP server."""
    async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream,
            write_stream,
            InitializationOptions(
                server_name="execution-tools",
                server_version="1.0.0",
                capabilities=server.get_capabilities(
                    notification_options=NotificationOptions(),
                    experimental_capabilities={}
                )
            )
        )


if __name__ == "__main__":
    asyncio.run(main())

terminal_controller.py

"""
Terminal Controller with integrated file operations.
Based on AWorld terminal-controller implementation.
Provides command execution with directory navigation and file editing.
"""
import os
import subprocess
import traceback
from pathlib import Path
from typing import Dict, Any

from config import Config


class TerminalController:
    """Terminal controller with directory navigation and file operations."""

    def __init__(self):
        self.workspace_dir = Path(Config.WORKSPACE_DIR).resolve()
        self.current_directory = self.workspace_dir
        self.command_history = []
        self.max_history = 100

    def _is_safe_path(self, path: Path) -> bool:
        """Check if path is within workspace."""
        try:
            path.resolve().relative_to(self.workspace_dir)
            return True
        except ValueError:
            return False

    def _resolve_path(self, path: str) -> Path:
        """Resolve path relative to current directory."""
        path_obj = Path(path)
        if not path_obj.is_absolute():
            path_obj = self.current_directory / path_obj
        return path_obj.resolve()

    async def execute_command(
        self,
        command: str,
        timeout: int = 30
    ) -> Dict[str, Any]:
        """
        Execute a shell command in current directory.

        Args:
            command: Command to execute
            timeout: Timeout in seconds

        Returns:
            Dictionary with command output
        """
        try:
            # Add to history
            self.command_history.append(command)
            if len(self.command_history) > self.max_history:
                self.command_history.pop(0)

            # Execute command
            result = subprocess.run(
                command,
                shell=True,
                capture_output=True,
                text=True,
                timeout=timeout,
                cwd=str(self.current_directory)
            )

            return {
                "success": result.returncode == 0,
                "command": command,
                "stdout": result.stdout,
                "stderr": result.stderr,
                "returncode": result.returncode,
                "cwd": str(self.current_directory)
            }

        except subprocess.TimeoutExpired:
            return {
                "success": False,
                "error": f"Command timed out after {timeout} seconds",
                "command": command
            }
        except Exception as e:
            return {
                "success": False,
                "error": f"Command execution failed: {str(e)}",
                "command": command
            }

    async def get_current_directory(self) -> Dict[str, Any]:
        """
        Get the current working directory.

        Returns:
            Dictionary with current directory path
        """
        return {
            "success": True,
            "current_directory": str(self.current_directory),
            "workspace": str(self.workspace_dir)
        }

    async def change_directory(
        self,
        directory: str
    ) -> Dict[str, Any]:
        """
        Change the current working directory.

        Args:
            directory: Directory to change to

        Returns:
            Dictionary with new directory
        """
        try:
            new_dir = self._resolve_path(directory)

            if not self._is_safe_path(new_dir):
                return {
                    "success": False,
                    "error": f"Directory {directory} is outside workspace"
                }

            if not new_dir.exists():
                return {
                    "success": False,
                    "error": f"Directory {directory} does not exist"
                }

            if not new_dir.is_dir():
                return {
                    "success": False,
                    "error": f"{directory} is not a directory"
                }

            self.current_directory = new_dir

            return {
                "success": True,
                "current_directory": str(self.current_directory),
                "message": f"Changed to {directory}"
            }

        except Exception as e:
            return {
                "success": False,
                "error": f"Failed to change directory: {str(e)}"
            }

    async def list_directory(
        self,
        directory: str = "."
    ) -> Dict[str, Any]:
        """
        List contents of a directory.

        Args:
            directory: Directory to list (relative to current)

        Returns:
            Dictionary with directory contents
        """
        try:
            dir_path = self._resolve_path(directory)

            if not self._is_safe_path(dir_path):
                return {
                    "success": False,
                    "error": f"Directory {directory} is outside workspace"
                }

            if not dir_path.exists():
                return {
                    "success": False,
                    "error": f"Directory {directory} does not exist"
                }

            contents = []
            for item in sorted(dir_path.iterdir()):
                contents.append({
                    "name": item.name,
                    "type": "directory" if item.is_dir() else "file",
                    "size": 0 if item.is_dir() else item.stat().st_size
                })

            return {
                "success": True,
                "directory": str(dir_path),
                "contents": contents,
                "count": len(contents)
            }

        except Exception as e:
            return {
                "success": False,
                "error": f"Failed to list directory: {str(e)}"
            }

    async def read_file(
        self,
        file_path: str,
        encoding: str = "utf-8"
    ) -> Dict[str, Any]:
        """
        Read a file from current directory.

        Args:
            file_path: File path relative to current directory
            encoding: File encoding

        Returns:
            Dictionary with file content
        """
        try:
            resolved_path = self._resolve_path(file_path)

            if not self._is_safe_path(resolved_path):
                return {
                    "success": False,
                    "error": f"File {file_path} is outside workspace"
                }

            if not resolved_path.exists():
                return {
                    "success": False,
                    "error": f"File {file_path} does not exist"
                }

            content = resolved_path.read_text(encoding=encoding)

            return {
                "success": True,
                "file_path": str(resolved_path),
                "content": content,
                "size": len(content),
                "lines": len(content.splitlines())
            }

        except Exception as e:
            return {
                "success": False,
                "error": f"Failed to read file: {str(e)}"
            }

    async def write_file(
        self,
        file_path: str,
        content: str,
        mode: str = "w"
    ) -> Dict[str, Any]:
        """
        Write content to a file.

        Args:
            file_path: File path relative to current directory
            content: Content to write
            mode: Write mode ('w' for write, 'a' for append)

        Returns:
            Dictionary with write result
        """
        try:
            resolved_path = self._resolve_path(file_path)

            if not self._is_safe_path(resolved_path):
                return {
                    "success": False,
                    "error": f"File {file_path} is outside workspace"
                }

            # Create parent directories if needed
            resolved_path.parent.mkdir(parents=True, exist_ok=True)

            # Write file
            if mode == "a":
                with open(resolved_path, 'a', encoding='utf-8') as f:
                    f.write(content)
            else:
                resolved_path.write_text(content, encoding='utf-8')

            return {
                "success": True,
                "file_path": str(resolved_path),
                "bytes_written": len(content),
                "mode": mode
            }

        except Exception as e:
            return {
                "success": False,
                "error": f"Failed to write file: {str(e)}"
            }

    async def insert_file_content(
        self,
        file_path: str,
        content: str,
        line_number: int
    ) -> Dict[str, Any]:
        """
        Insert content at specific line in file.

        Args:
            file_path: File path
            content: Content to insert
            line_number: Line number to insert at (1-indexed)

        Returns:
            Dictionary with operation result
        """
        try:
            resolved_path = self._resolve_path(file_path)

            if not self._is_safe_path(resolved_path):
                return {
                    "success": False,
                    "error": f"File {file_path} is outside workspace"
                }

            if not resolved_path.exists():
                return {
                    "success": False,
                    "error": f"File {file_path} does not exist"
                }

            # Read current content
            lines = resolved_path.read_text().splitlines()

            # Insert content
            if line_number < 1 or line_number > len(lines) + 1:
                return {
                    "success": False,
                    "error": f"Line number {line_number} out of range (1-{len(lines)+1})"
                }

            lines.insert(line_number - 1, content)

            # Write back
            resolved_path.write_text('\n'.join(lines) + '\n')

            return {
                "success": True,
                "file_path": str(resolved_path),
                "line_number": line_number,
                "total_lines": len(lines)
            }

        except Exception as e:
            return {
                "success": False,
                "error": f"Failed to insert content: {str(e)}"
            }

    async def delete_file_content(
        self,
        file_path: str,
        start_line: int,
        end_line: int
    ) -> Dict[str, Any]:
        """
        Delete lines from file.

        Args:
            file_path: File path
            start_line: Start line number (1-indexed, inclusive)
            end_line: End line number (1-indexed, inclusive)

        Returns:
            Dictionary with operation result
        """
        try:
            resolved_path = self._resolve_path(file_path)

            if not self._is_safe_path(resolved_path):
                return {
                    "success": False,
                    "error": f"File {file_path} is outside workspace"
                }

            if not resolved_path.exists():
                return {
                    "success": False,
                    "error": f"File {file_path} does not exist"
                }

            # Read lines
            lines = resolved_path.read_text().splitlines()

            # Validate range
            if start_line < 1 or end_line > len(lines) or start_line > end_line:
                return {
                    "success": False,
                    "error": f"Invalid line range: {start_line}-{end_line} (file has {len(lines)} lines)"
                }

            # Delete lines
            del lines[start_line - 1:end_line]

            # Write back
            resolved_path.write_text('\n'.join(lines) + '\n' if lines else '')

            return {
                "success": True,
                "file_path": str(resolved_path),
                "deleted_lines": end_line - start_line + 1,
                "remaining_lines": len(lines)
            }

        except Exception as e:
            return {
                "success": False,
                "error": f"Failed to delete content: {str(e)}"
            }

    async def update_file_content(
        self,
        file_path: str,
        line_number: int,
        new_content: str
    ) -> Dict[str, Any]:
        """
        Update a specific line in file.

        Args:
            file_path: File path
            line_number: Line number to update (1-indexed)
            new_content: New content for the line

        Returns:
            Dictionary with operation result
        """
        try:
            resolved_path = self._resolve_path(file_path)

            if not self._is_safe_path(resolved_path):
                return {
                    "success": False,
                    "error": f"File {file_path} is outside workspace"
                }

            if not resolved_path.exists():
                return {
                    "success": False,
                    "error": f"File {file_path} does not exist"
                }

            # Read lines
            lines = resolved_path.read_text().splitlines()

            if line_number < 1 or line_number > len(lines):
                return {
                    "success": False,
                    "error": f"Line number {line_number} out of range (1-{len(lines)})"
                }

            # Update line
            old_content = lines[line_number - 1]
            lines[line_number - 1] = new_content

            # Write back
            resolved_path.write_text('\n'.join(lines) + '\n')

            return {
                "success": True,
                "file_path": str(resolved_path),
                "line_number": line_number,
                "old_content": old_content,
                "new_content": new_content
            }

        except Exception as e:
            return {
                "success": False,
                "error": f"Failed to update content: {str(e)}"
            }

    async def get_command_history(
        self,
        count: int = 10
    ) -> Dict[str, Any]:
        """
        Get recent command history.

        Args:
            count: Number of recent commands

        Returns:
            Dictionary with command history
        """
        recent = self.command_history[-count:] if self.command_history else []

        return {
            "success": True,
            "history": recent,
            "count": len(recent),
            "total": len(self.command_history)
        }

test_config_env.py

"""Regression test: malformed numeric env vars must not crash config import.

TEMPERATURE / MAX_TOKENS / MAX_OUTPUT_LENGTH were parsed with bare
float()/int() at import time, so e.g. MAX_TOKENS=abc crashed every tool with
ValueError. They now fall back to defaults with a warning.
"""
import importlib
import os
import sys

sys.path.insert(0, os.path.dirname(__file__))

# Ensure the real local config module is imported (other tests stub it).
sys.modules.pop("config", None)
import config as cfg


def test_env_int_falls_back_on_malformed(monkeypatch, capsys):
    monkeypatch.setenv("MAX_TOKENS", "abc")
    assert cfg._env_int("MAX_TOKENS", 4096) == 4096
    assert "invalid MAX_TOKENS" in capsys.readouterr().err


def test_env_int_parses_valid_value(monkeypatch):
    monkeypatch.setenv("MAX_TOKENS", "123")
    assert cfg._env_int("MAX_TOKENS", 4096) == 123


def test_env_float_falls_back_on_malformed(monkeypatch, capsys):
    monkeypatch.setenv("TEMPERATURE", "hot")
    assert cfg._env_float("TEMPERATURE", 0.7) == 0.7
    assert "invalid TEMPERATURE" in capsys.readouterr().err


def test_env_float_parses_valid_value(monkeypatch):
    monkeypatch.setenv("TEMPERATURE", "0.2")
    assert cfg._env_float("TEMPERATURE", 0.7) == 0.2


def test_module_import_survives_malformed_env(monkeypatch):
    """Import-time class attributes must not raise on malformed env values."""
    monkeypatch.setenv("MAX_OUTPUT_LENGTH", "lots")
    # Fresh import from disk (test_terminal_controller stubs sys.modules['config']).
    sys.modules.pop("config", None)
    fresh = importlib.import_module("config")
    assert fresh.Config.MAX_OUTPUT_LENGTH == 1000

test_execution_tools.py

"""Test execution tools."""

import asyncio
from llm_helper import LLMHelper
from execution_tools import ExecutionTools


async def test_code_interpreter():
    """Test code interpreter functionality."""
    print("Testing code interpreter...")

    llm_helper = LLMHelper()
    execution_tools = ExecutionTools(llm_helper)

    # Test valid code
    result = await execution_tools.code_interpreter(
        code='print("Test successful")\nresult = 2 + 2\nprint(f"2 + 2 = {result}")'
    )

    assert result["success"], f"Code execution failed: {result.get('error')}"
    assert "Test successful" in result["stdout"]
    print(f"✓ Code execution successful: {result}")

    # Test error handling
    result = await execution_tools.code_interpreter(
        code='x = 1 / 0'
    )

    assert not result["success"], "Should fail with division by zero"
    assert "error_analysis" in result
    print(f"✓ Error handling works: {result['error'][:100]}...")


async def test_virtual_terminal():
    """Test virtual terminal functionality."""
    print("\nTesting virtual terminal...")

    llm_helper = LLMHelper()
    execution_tools = ExecutionTools(llm_helper)

    # Test successful command
    result = await execution_tools.virtual_terminal(
        command='echo "Terminal test"'
    )

    assert result["success"], f"Command failed: {result.get('error')}"
    assert "Terminal test" in result["stdout"]
    print(f"✓ Command execution successful: {result}")

    # Test failed command
    result = await execution_tools.virtual_terminal(
        command='ls /nonexistent_directory_12345'
    )

    assert not result["success"], "Should fail with non-existent directory"
    assert "error_analysis" in result
    print(f"✓ Error handling works: returncode={result['returncode']}")


async def test_syntax_verification():
    """Test syntax verification."""
    print("\nTesting syntax verification...")

    llm_helper = LLMHelper()
    execution_tools = ExecutionTools(llm_helper)

    # Test syntax error detection
    result = await execution_tools.code_interpreter(
        code='print("Unclosed string'
    )

    assert not result["success"], "Should detect syntax error"
    print(f"✓ Syntax verification works: {result['error'][:100]}...")


async def main():
    """Run all tests."""
    print("=== Execution Tools Tests ===\n")

    try:
        await test_code_interpreter()
        await test_virtual_terminal()
        await test_syntax_verification()

        print("\n✓ All execution tools tests passed!")

    except AssertionError as e:
        print(f"\n✗ Test failed: {e}")
    except Exception as e:
        print(f"\n✗ Error: {e}")
        import traceback
        traceback.print_exc()


if __name__ == "__main__":
    asyncio.run(main())

test_external_tools.py

"""Test external integration tools."""

import asyncio
from llm_helper import LLMHelper
from external_tools import ExternalTools


async def test_google_calendar():
    """Test Google Calendar integration."""
    print("Testing Google Calendar...")

    llm_helper = LLMHelper()
    external_tools = ExternalTools(llm_helper)

    try:
        result = await external_tools.google_calendar_add(
            summary="Test Event",
            start_time="2025-10-01T10:00:00",
            end_time="2025-10-01T11:00:00",
            description="This is a test event"
        )

        if result["success"]:
            print(f"✓ Calendar event created: {result}")
        else:
            print(f"Calendar test skipped or failed: {result['error']}")

    except Exception as e:
        print(f"Calendar test skipped (likely missing credentials): {e}")


async def test_github_pr():
    """Test GitHub PR creation."""
    print("\nTesting GitHub PR...")

    llm_helper = LLMHelper()
    external_tools = ExternalTools(llm_helper)

    try:
        # Note: This will fail without a valid repo and token
        result = await external_tools.github_create_pr(
            repo_name="test/test-repo",
            title="Test PR",
            body="This is a test PR",
            head_branch="test-branch",
            base_branch="main"
        )

        if result["success"]:
            print(f"✓ PR created: {result}")
        else:
            print(f"PR test expected to fail (test repo): {result['error']}")

    except Exception as e:
        print(f"PR test skipped (likely missing credentials): {e}")


async def test_datetime_parsing():
    """Test datetime parsing."""
    print("\nTesting datetime parsing...")

    llm_helper = LLMHelper()
    external_tools = ExternalTools(llm_helper)

    # Test invalid datetime
    result = await external_tools.google_calendar_add(
        summary="Test",
        start_time="invalid-datetime",
        end_time="2025-10-01T11:00:00"
    )

    assert not result["success"], "Should fail with invalid datetime"
    print(f"✓ Invalid datetime handling works: {result['error']}")

    # Test end before start
    result = await external_tools.google_calendar_add(
        summary="Test",
        start_time="2025-10-01T11:00:00",
        end_time="2025-10-01T10:00:00"
    )

    assert not result["success"], "Should fail when end is before start"
    print(f"✓ Time validation works: {result['error']}")


async def main():
    """Run all tests."""
    print("=== External Tools Tests ===\n")

    try:
        await test_datetime_parsing()
        await test_google_calendar()
        await test_github_pr()

        print("\n✓ External tools tests completed!")
        print("Note: Some tests may be skipped if credentials are not configured.")

    except AssertionError as e:
        print(f"\n✗ Test failed: {e}")
    except Exception as e:
        print(f"\n✗ Error: {e}")
        import traceback
        traceback.print_exc()


if __name__ == "__main__":
    asyncio.run(main())

test_file_tools.py

"""Test file system tools."""

import asyncio
import tempfile
from pathlib import Path
from llm_helper import LLMHelper
from file_tools import FileTools
from config import Config


async def test_file_write():
    """Test file write functionality."""
    print("Testing file write...")

    llm_helper = LLMHelper()
    file_tools = FileTools(llm_helper)

    # Test writing valid Python code
    result = await file_tools.write_file(
        path="test_output.py",
        content='print("Hello, World!")\n',
        overwrite=True
    )

    assert result["success"], f"File write failed: {result.get('error')}"
    assert result["verification"] in ["passed", "skipped"]
    print(f"✓ File write successful: {result}")

    # Test syntax error detection
    result = await file_tools.write_file(
        path="test_syntax_error.py",
        content='print("Unclosed string\n',
        overwrite=True
    )

    if Config.AUTO_VERIFY_CODE:
        assert not result["success"], "Should detect syntax error"
        print(f"✓ Syntax error detected: {result['error']}")
    else:
        print("✓ Verification skipped (AUTO_VERIFY_CODE=False)")


async def test_file_edit():
    """Test file edit functionality."""
    print("\nTesting file edit...")

    llm_helper = LLMHelper()
    file_tools = FileTools(llm_helper)

    # Create a test file first
    await file_tools.write_file(
        path="test_edit.py",
        content='message = "Hello"\nprint(message)\n',
        overwrite=True
    )

    # Edit the file
    result = await file_tools.edit_file(
        path="test_edit.py",
        search='message = "Hello"',
        replace='message = "Hi there"'
    )

    assert result["success"], f"File edit failed: {result.get('error')}"
    assert "diff_preview" in result
    print(f"✓ File edit successful: {result}")


async def test_safety_checks():
    """Test safety checks for file operations."""
    print("\nTesting safety checks...")

    llm_helper = LLMHelper()
    file_tools = FileTools(llm_helper)

    # Test path outside workspace
    result = await file_tools.write_file(
        path="/tmp/outside_workspace.txt",
        content="test"
    )

    # This should fail unless /tmp is in workspace
    print(f"Path safety check result: {result}")


async def main():
    """Run all tests."""
    print("=== File Tools Tests ===\n")

    try:
        await test_file_write()
        await test_file_edit()
        await test_safety_checks()

        print("\n✓ All file tools tests passed!")

    except AssertionError as e:
        print(f"\n✗ Test failed: {e}")
    except Exception as e:
        print(f"\n✗ Error: {e}")
        import traceback
        traceback.print_exc()


if __name__ == "__main__":
    asyncio.run(main())

test_filesystem_enhanced.py

"""
Real tests for enhanced filesystem tools.
These tests perform actual file operations to verify functionality.
"""
import asyncio
import json
import pytest
import tempfile
import shutil
from pathlib import Path
import sys

# Add current directory to path
sys.path.insert(0, str(Path(__file__).parent))

# Mock Config for testing
class Config:
    WORKSPACE_DIR = Path(tempfile.mkdtemp())
    AUTO_VERIFY_CODE = False
    REQUIRE_APPROVAL_FOR_DANGEROUS_OPS = False

# Set config before import
sys.modules['config'] = type(sys)('config')
sys.modules['config'].Config = Config

from filesystem_enhanced import FilesystemEnhanced


@pytest.fixture(scope="function")
def fs():
    """Create filesystem instance with temp workspace."""
    # Create new temp dir for each test
    Config.WORKSPACE_DIR = Path(tempfile.mkdtemp())
    filesystem = FilesystemEnhanced()
    yield filesystem
    # Cleanup
    if Config.WORKSPACE_DIR.exists():
        try:
            shutil.rmtree(Config.WORKSPACE_DIR)
        except Exception:
            pass


@pytest.fixture(scope="function")
def test_files(fs):
    """Create test files for testing."""
    # Create some test files
    try:
        (Config.WORKSPACE_DIR / "test1.txt").write_text("Hello World")
        (Config.WORKSPACE_DIR / "test2.txt").write_text("Python Testing\nLine 2\nLine 3")
        (Config.WORKSPACE_DIR / "data.json").write_text('{"key": "value"}')
        (Config.WORKSPACE_DIR / "subdir").mkdir()
        (Config.WORKSPACE_DIR / "subdir" / "nested.txt").write_text("Nested file")
    except Exception as e:
        print(f"Error creating test files: {e}")
    return fs


class TestReadOperations:
    """Tests for file reading operations."""

    @pytest.mark.asyncio
    async def test_read_text_file(self, test_files):
        """Test reading a text file."""
        result = await test_files.read_text_file("test1.txt")

        assert result["success"] is True
        assert result["content"] == "Hello World"
        assert result["file_size"] > 0
        assert result["lines"] == 1

        print("✅ Read text file successfully")
        print(f"   Content: {result['content']}")

    @pytest.mark.asyncio
    async def test_read_multiline_file(self, test_files):
        """Test reading multiline file."""
        result = await test_files.read_text_file("test2.txt")

        assert result["success"] is True
        assert result["lines"] == 3
        assert "Python Testing" in result["content"]

        print("✅ Read multiline file")
        print(f"   Lines: {result['lines']}")

    @pytest.mark.asyncio
    async def test_read_nonexistent_file(self, fs):
        """Test reading non-existent file."""
        result = await fs.read_text_file("nonexistent.txt")

        assert result["success"] is False
        assert "does not exist" in result["error"]

        print("✅ Correctly handled nonexistent file")

    @pytest.mark.asyncio
    async def test_read_multiple_files(self, test_files):
        """Test reading multiple files at once."""
        result = await test_files.read_multiple_files([
            "test1.txt",
            "test2.txt",
            "data.json"
        ])

        assert result["success"] is True
        assert result["files_read"] == 3
        assert result["files_failed"] == 0
        assert "test1.txt" in result["results"]
        assert "test2.txt" in result["results"]

        print(f"✅ Read {result['files_read']} files")
        print(f"   Files: {', '.join(result['results'].keys())}")

    @pytest.mark.asyncio
    async def test_read_multiple_files_with_errors(self, test_files):
        """Test reading multiple files with some missing."""
        result = await test_files.read_multiple_files([
            "test1.txt",
            "nonexistent.txt",
            "test2.txt"
        ])

        assert result["success"] is True  # At least some succeeded
        assert result["files_read"] == 2
        assert result["files_failed"] == 1
        assert len(result["errors"]) == 1

        print(f"✅ Read {result['files_read']} files, {result['files_failed']} failed")


class TestListOperations:
    """Tests for directory listing operations."""

    @pytest.mark.asyncio
    async def test_list_directory_with_sizes(self, test_files):
        """Test listing directory with file sizes."""
        result = await test_files.list_directory_with_sizes(".")

        assert result["success"] is True
        assert result["total_items"] >= 4  # At least 3 files + 1 dir
        assert result["total_size"] > 0
        assert len(result["contents"]) >= 4

        # Check structure
        item = result["contents"][0]
        assert "name" in item
        assert "type" in item
        assert "size" in item
        assert "size_human" in item

        print(f"✅ Listed {result['total_items']} items")
        print(f"   Total size: {result['total_size_human']}")

    @pytest.mark.asyncio
    async def test_directory_tree(self, test_files):
        """Test generating directory tree."""
        result = await test_files.directory_tree(".", max_depth=3)

        assert result["success"] is True
        assert "tree" in result
        assert result["tree"]["type"] == "directory"
        assert "children" in result["tree"]
        assert len(result["tree"]["children"]) >= 4

        print(f"✅ Generated directory tree")
        print(f"   Root: {result['root']}")
        print(f"   Items: {result['tree']['count']}")

    @pytest.mark.asyncio
    async def test_directory_tree_depth_limit(self, test_files):
        """Test directory tree with depth limit."""
        # Create deeper structure
        (Config.WORKSPACE_DIR / "deep" / "level2" / "level3").mkdir(parents=True)

        result = await test_files.directory_tree(".", max_depth=2)

        assert result["success"] is True
        assert result["max_depth"] == 2

        print("✅ Directory tree respects depth limit")


class TestSearchOperations:
    """Tests for file search operations."""

    @pytest.mark.asyncio
    async def test_search_files_pattern(self, test_files):
        """Test searching files by pattern."""
        result = await test_files.search_files("*.txt", ".", recursive=True)

        assert result["success"] is True
        assert result["matches"] >= 3  # test1.txt, test2.txt, nested.txt
        assert result["pattern"] == "*.txt"

        # Check results structure
        if len(result["files"]) > 0:
            file_info = result["files"][0]
            assert "path" in file_info
            assert "size" in file_info
            assert "size_human" in file_info

        print(f"✅ Found {result['matches']} files matching *.txt")

    @pytest.mark.asyncio
    async def test_search_files_nonrecursive(self, test_files):
        """Test non-recursive search."""
        result = await test_files.search_files("*.txt", ".", recursive=False)

        assert result["success"] is True
        assert result["recursive"] is False
        # Should not find nested.txt
        paths = [f["path"] for f in result["files"]]
        assert not any("subdir" in p for p in paths)

        print(f"✅ Non-recursive search: {result['matches']} files")

    @pytest.mark.asyncio
    async def test_search_files_json(self, test_files):
        """Test searching for specific file type."""
        result = await test_files.search_files("*.json", ".", recursive=True)

        assert result["success"] is True
        assert result["matches"] >= 1

        print(f"✅ Found {result['matches']} JSON files")


class TestFileInfo:
    """Tests for file information retrieval."""

    @pytest.mark.asyncio
    async def test_get_file_info(self, test_files):
        """Test getting file information."""
        result = await test_files.get_file_info("test2.txt")

        assert result["success"] is True
        info = result["file_info"]

        assert info["name"] == "test2.txt"
        assert info["extension"] == ".txt"
        assert info["is_file"] is True
        assert info["is_directory"] is False
        assert info["lines"] == 3
        assert info["size"] > 0

        print("✅ File info retrieved")
        print(f"   Name: {info['name']}")
        print(f"   Size: {info['size_human']}")
        print(f"   Lines: {info['lines']}")

    @pytest.mark.asyncio
    async def test_get_directory_info(self, test_files):
        """Test getting directory information."""
        result = await test_files.get_file_info("subdir")

        assert result["success"] is True
        info = result["file_info"]

        assert info["is_directory"] is True
        assert info["is_file"] is False

        print("✅ Directory info retrieved")


class TestMoveOperations:
    """Tests for move and copy operations."""

    @pytest.mark.asyncio
    async def test_move_file(self, test_files):
        """Test moving a file."""
        result = await test_files.move_file("test1.txt", "moved.txt")

        assert result["success"] is True
        assert Path(Config.WORKSPACE_DIR / "moved.txt").exists()
        assert not Path(Config.WORKSPACE_DIR / "test1.txt").exists()

        print("✅ File moved successfully")
        print(f"   From: test1.txt")
        print(f"   To: moved.txt")

    @pytest.mark.asyncio
    async def test_move_file_no_overwrite(self, test_files):
        """Test move without overwrite."""
        result = await test_files.move_file("test1.txt", "test2.txt", overwrite=False)

        assert result["success"] is False
        assert "already exists" in result["error"]

        print("✅ Correctly prevented overwrite")

    @pytest.mark.asyncio
    async def test_copy_file(self, test_files):
        """Test copying a file."""
        result = await test_files.copy_file("test1.txt", "copied.txt")

        assert result["success"] is True
        assert Path(Config.WORKSPACE_DIR / "copied.txt").exists()
        assert Path(Config.WORKSPACE_DIR / "test1.txt").exists()  # Original still exists

        print("✅ File copied successfully")

    @pytest.mark.asyncio
    async def test_copy_directory(self, test_files):
        """Test copying a directory."""
        result = await test_files.copy_file("subdir", "subdir_copy")

        assert result["success"] is True
        assert Path(Config.WORKSPACE_DIR / "subdir_copy").exists()
        assert Path(Config.WORKSPACE_DIR / "subdir_copy" / "nested.txt").exists()

        print("✅ Directory copied recursively")


class TestDeleteOperations:
    """Tests for delete operations."""

    @pytest.mark.asyncio
    async def test_delete_file(self, test_files):
        """Test deleting a file."""
        result = await test_files.delete_file("test1.txt")

        assert result["success"] is True
        assert not Path(Config.WORKSPACE_DIR / "test1.txt").exists()

        print("✅ File deleted")

    @pytest.mark.asyncio
    async def test_delete_directory_recursive(self, test_files):
        """Test deleting directory recursively."""
        result = await test_files.delete_file("subdir", recursive=True)

        assert result["success"] is True
        assert not Path(Config.WORKSPACE_DIR / "subdir").exists()

        print("✅ Directory deleted recursively")

    @pytest.mark.asyncio
    async def test_delete_directory_without_recursive(self, test_files):
        """Test that directory delete requires recursive flag."""
        result = await test_files.delete_file("subdir", recursive=False)

        assert result["success"] is False
        assert "recursive" in result["error"].lower()

        print("✅ Correctly required recursive flag")


class TestCreateOperations:
    """Tests for create operations."""

    @pytest.mark.asyncio
    async def test_create_directory(self, fs):
        """Test creating a directory."""
        result = await fs.create_directory("newdir")

        assert result["success"] is True
        assert Path(Config.WORKSPACE_DIR / "newdir").exists()

        print("✅ Directory created")

    @pytest.mark.asyncio
    async def test_create_nested_directory(self, fs):
        """Test creating nested directories."""
        result = await fs.create_directory("parent/child/grandchild", parents=True)

        assert result["success"] is True
        assert Path(Config.WORKSPACE_DIR / "parent" / "child" / "grandchild").exists()

        print("✅ Nested directories created")

    @pytest.mark.asyncio
    async def test_list_allowed_directories(self, fs):
        """Test listing allowed directories."""
        result = await fs.list_allowed_directories()

        assert result["success"] is True
        assert result["count"] >= 1
        assert len(result["allowed_directories"]) >= 1

        print(f"✅ Listed {result['count']} allowed directories")


# Run tests
if __name__ == "__main__":
    print("=" * 70)
    print("Running Enhanced Filesystem Tools Tests")
    print("=" * 70)
    print()

    pytest.main([__file__, "-v", "-s"])

test_multilang.py

"""Test multi-language code execution."""

import asyncio
import sys
from multilang_executor import LanguageExecutor, ExecutionStatus


async def test_language(executor: LanguageExecutor, language: str, code: str, description: str):
    """Test a specific language."""
    print(f"\n{'='*60}")
    print(f"Testing {language}: {description}")
    print(f"{'='*60}")

    result = await executor.execute_code(code, language, timeout=10.0)

    print(f"Status: {result.get('status')}")
    if result.get('stdout'):
        print(f"Output:\n{result['stdout']}")
    if result.get('stderr'):
        print(f"Errors:\n{result['stderr']}")
    if result.get('compile_output'):
        print(f"Compile output:\n{result['compile_output']}")

    success = result.get('status') == ExecutionStatus.SUCCESS
    print(f"✅ PASSED" if success else f"❌ FAILED")
    return success


async def main():
    """Run all language tests."""
    executor = LanguageExecutor()

    tests = [
        # Python
        ("python", """
import numpy as np
import pandas as pd

data = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]})
print("Data shape:", data.shape)
print("Mean of A:", data['A'].mean())
""", "NumPy and Pandas"),

        # JavaScript
        ("javascript", """
console.log('Hello from Node.js!');
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((a, b) => a + b, 0);
console.log('Sum:', sum);
""", "Array operations"),

        # TypeScript
        ("typescript", """
interface Point {
    x: number;
    y: number;
}

const point: Point = { x: 10, y: 20 };
console.log(`Point: (${point.x}, ${point.y})`);
""", "Type-safe interfaces"),

        # Go
        ("go", """
package main

import "fmt"

func main() {
    fmt.Println("Hello from Go!")
    sum := 0
    for i := 1; i <= 10; i++ {
        sum += i
    }
    fmt.Printf("Sum of 1-10: %d\\n", sum)
}
""", "Loops and formatting"),

        # Java
        ("java", """
public class Main {
    public static void main(String[] args) {
        System.out.println("Hello from Java!");
        int sum = 0;
        for (int i = 1; i <= 10; i++) {
            sum += i;
        }
        System.out.println("Sum of 1-10: " + sum);
    }
}
""", "Basic class and loops"),

        # C++
        ("cpp", """
#include <iostream>
#include <vector>
#include <numeric>

int main() {
    std::cout << "Hello from C++!" << std::endl;
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    int sum = std::accumulate(numbers.begin(), numbers.end(), 0);
    std::cout << "Sum: " << sum << std::endl;
    return 0;
}
""", "STL vector and accumulate"),

        # Rust
        ("rust", """
fn main() {
    println!("Hello from Rust!");
    let numbers = vec![1, 2, 3, 4, 5];
    let sum: i32 = numbers.iter().sum();
    println!("Sum: {}", sum);
}
""", "Vector and iterators"),

        # PHP
        ("php", """
<?php
echo "Hello from PHP!\\n";
$numbers = [1, 2, 3, 4, 5];
$sum = array_sum($numbers);
echo "Sum: $sum\\n";
?>
""", "Array operations"),

        # Bash
        ("bash", """
echo "Hello from Bash!"
sum=0
for i in {1..10}; do
    sum=$((sum + i))
done
echo "Sum of 1-10: $sum"
""", "Shell loops"),
    ]

    print(f"\n{'#'*60}")
    print(f"# Multi-Language Code Execution Test Suite")
    print(f"{'#'*60}")

    results = []
    for language, code, description in tests:
        try:
            success = await test_language(executor, language, code, description)
            results.append((language, success))
        except Exception as e:
            print(f"❌ EXCEPTION: {e}")
            results.append((language, False))

    # Summary
    print(f"\n{'='*60}")
    print(f"Test Summary")
    print(f"{'='*60}")

    passed = sum(1 for _, success in results if success)
    total = len(results)

    for language, success in results:
        status = "✅ PASS" if success else "❌ FAIL"
        print(f"{status} - {language}")

    print(f"\nTotal: {passed}/{total} passed ({100*passed//total}%)")

    return passed == total


if __name__ == "__main__":
    success = asyncio.run(main())
    sys.exit(0 if success else 1)

test_multilang_output.py

import asyncio
import pytest
from multilang_executor import get_all_output, LanguageExecutor, ExecutionStatus


@pytest.mark.asyncio
async def test_get_all_output_reads_full_payload():
    class FakeStream:
        def __init__(self, data: bytes):
            self._data = data
            self._done = False

        async def read(self, n: int = -1):
            if self._done:
                return b""
            self._done = True
            return self._data

    payload = b"hello world" * 1000
    out = await get_all_output(FakeStream(payload))
    assert out == payload.decode()


@pytest.mark.asyncio
async def test_execute_captures_stdout():
    exe = LanguageExecutor()
    result = await exe.execute_code("print('ok-from-executor')", "python", timeout=10)
    assert result["status"] == ExecutionStatus.SUCCESS
    assert "ok-from-executor" in result["stdout"]

test_null_language.py

"""Null optional language must default to python on public paths."""
import asyncio
import tempfile
from pathlib import Path
from unittest.mock import MagicMock

from multilang_executor import LanguageExecutor
from execution_tools import ExecutionTools


def test_null_language_executor_defaults_to_python():
    le = LanguageExecutor(workspace_dir=Path(tempfile.mkdtemp()))
    result = asyncio.run(le.execute_code("print(42)", language=None, timeout=10))
    assert isinstance(result, dict)
    assert result.get("language") == "python"
    assert "42" in (result.get("stdout") or "")


def test_null_language_code_interpreter_defaults(monkeypatch):
    """Public MCP path: ExecutionTools.code_interpreter(language=None)."""
    helper = MagicMock()
    et = ExecutionTools(helper)
    seen = {}

    async def fake_exec(code, language, timeout=30.0, stdin=None, files=None):
        seen["language"] = language
        return {
            "success": True,
            "stdout": "ok\n",
            "stderr": "",
            "language": language,
            "returncode": 0,
            "status": "ok",
        }

    monkeypatch.setattr(et.lang_executor, "execute_code", fake_exec)
    import config as cfg
    monkeypatch.setattr(cfg.Config, "AUTO_VERIFY_CODE", False, raising=False)
    monkeypatch.setattr(cfg.Config, "REQUIRE_APPROVAL_FOR_DANGEROUS_OPS", False, raising=False)
    monkeypatch.setattr(cfg.Config, "AUTO_SUMMARIZE_OUTPUT", False, raising=False)

    out = asyncio.run(et.code_interpreter("print(1)", language=None))
    assert seen["language"] == "python"
    assert out["language"] == "python"
    assert out.get("error") in (None, "")

test_script.py

def greet(name):
    print(f"Hello, {name}!")

if __name__ == "__main__":
    greet("MCP Server")

test_terminal_controller.py

"""
Tests for Terminal Controller.
Tests command execution with directory navigation and file operations.
"""
import asyncio
import json
import pytest
import tempfile
import shutil
from pathlib import Path
import sys

# Mock Config for testing
class Config:
    WORKSPACE_DIR = Path(tempfile.mkdtemp())
    AUTO_VERIFY_CODE = False
    REQUIRE_APPROVAL_FOR_DANGEROUS_OPS = False

sys.modules['config'] = type(sys)('config')
sys.modules['config'].Config = Config

from terminal_controller import TerminalController


@pytest.fixture(scope="function")
def tc():
    """Create terminal controller with temp workspace."""
    Config.WORKSPACE_DIR = Path(tempfile.mkdtemp())
    controller = TerminalController()
    yield controller
    # Cleanup
    if Config.WORKSPACE_DIR.exists():
        try:
            shutil.rmtree(Config.WORKSPACE_DIR)
        except Exception:
            pass


class TestTerminalBasics:
    """Tests for basic terminal operations."""

    @pytest.mark.asyncio
    async def test_get_current_directory(self, tc):
        """Test getting current directory."""
        result = await tc.get_current_directory()

        assert result["success"] is True
        assert "current_directory" in result
        assert "workspace" in result

        print(f"✅ Current directory: {result['current_directory']}")

    @pytest.mark.asyncio
    async def test_execute_command_simple(self, tc):
        """Test executing a simple command."""
        result = await tc.execute_command("echo 'Hello World'")

        assert result["success"] is True
        assert "Hello World" in result["stdout"]
        assert result["returncode"] == 0

        print(f"✅ Command executed: {result['command']}")

    @pytest.mark.asyncio
    async def test_execute_command_ls(self, tc):
        """Test listing directory."""
        result = await tc.execute_command("ls")

        assert result["success"] is True
        print(f"✅ Directory listing completed")

    @pytest.mark.asyncio
    async def test_command_history(self, tc):
        """Test command history."""
        await tc.execute_command("echo 'test1'")
        await tc.execute_command("echo 'test2'")
        await tc.execute_command("echo 'test3'")

        result = await tc.get_command_history(count=2)

        assert result["success"] is True
        assert len(result["history"]) == 2
        assert result["total"] == 3

        print(f"✅ Command history: {result['count']} recent commands")


class TestDirectoryOperations:
    """Tests for directory navigation."""

    @pytest.mark.asyncio
    async def test_change_directory(self, tc):
        """Test changing directory."""
        # Create a subdirectory
        subdir = Config.WORKSPACE_DIR / "subdir"
        subdir.mkdir()

        result = await tc.change_directory("subdir")

        assert result["success"] is True
        assert "subdir" in result["current_directory"]

        print(f"✅ Changed to: {result['current_directory']}")

    @pytest.mark.asyncio
    async def test_list_directory(self, tc):
        """Test listing directory."""
        # Create some files
        (Config.WORKSPACE_DIR / "file1.txt").write_text("test")
        (Config.WORKSPACE_DIR / "file2.txt").write_text("test")

        result = await tc.list_directory(".")

        assert result["success"] is True
        assert result["count"] >= 2

        print(f"✅ Listed {result['count']} items")

    @pytest.mark.asyncio
    async def test_change_to_nonexistent(self, tc):
        """Test changing to nonexistent directory."""
        result = await tc.change_directory("nonexistent")

        assert result["success"] is False
        assert "does not exist" in result["error"]

        print("✅ Correctly rejected nonexistent directory")


class TestFileOperations:
    """Tests for file operations through terminal controller."""

    @pytest.mark.asyncio
    async def test_write_file(self, tc):
        """Test writing a file."""
        result = await tc.write_file("test.txt", "Hello Terminal")

        assert result["success"] is True
        assert result["bytes_written"] > 0

        # Verify file exists
        file_path = Config.WORKSPACE_DIR / "test.txt"
        assert file_path.exists()
        assert file_path.read_text() == "Hello Terminal"

        print(f"✅ Wrote {result['bytes_written']} bytes")

    @pytest.mark.asyncio
    async def test_read_file(self, tc):
        """Test reading a file."""
        # Create a file
        test_file = Config.WORKSPACE_DIR / "read_test.txt"
        test_file.write_text("Test content\nLine 2")

        result = await tc.read_file("read_test.txt")

        assert result["success"] is True
        assert result["content"] == "Test content\nLine 2"
        assert result["lines"] == 2

        print(f"✅ Read file: {result['lines']} lines")

    @pytest.mark.asyncio
    async def test_insert_file_content(self, tc):
        """Test inserting content into file."""
        # Create a file
        test_file = Config.WORKSPACE_DIR / "insert_test.txt"
        test_file.write_text("Line 1\nLine 3")

        result = await tc.insert_file_content("insert_test.txt", "Line 2", 2)

        assert result["success"] is True
        assert result["line_number"] == 2

        # Verify content
        content = test_file.read_text()
        lines = content.splitlines()
        assert lines[1] == "Line 2"

        print("✅ Inserted content at line 2")

    @pytest.mark.asyncio
    async def test_delete_file_content(self, tc):
        """Test deleting lines from file."""
        # Create a file
        test_file = Config.WORKSPACE_DIR / "delete_test.txt"
        test_file.write_text("Line 1\nLine 2\nLine 3\nLine 4")

        result = await tc.delete_file_content("delete_test.txt", 2, 3)

        assert result["success"] is True
        assert result["deleted_lines"] == 2

        # Verify content
        content = test_file.read_text()
        assert "Line 2" not in content
        assert "Line 3" not in content
        assert "Line 1" in content
        assert "Line 4" in content

        print(f"✅ Deleted {result['deleted_lines']} lines")

    @pytest.mark.asyncio
    async def test_update_file_content(self, tc):
        """Test updating a line in file."""
        # Create a file
        test_file = Config.WORKSPACE_DIR / "update_test.txt"
        test_file.write_text("Line 1\nOld Line 2\nLine 3")

        result = await tc.update_file_content("update_test.txt", 2, "New Line 2")

        assert result["success"] is True
        assert result["old_content"] == "Old Line 2"
        assert result["new_content"] == "New Line 2"

        # Verify content
        content = test_file.read_text()
        assert "New Line 2" in content
        assert "Old Line 2" not in content

        print("✅ Updated line 2")


if __name__ == "__main__":
    print("=" * 70)
    print("Running Terminal Controller Tests")
    print("=" * 70)
    print()

    pytest.main([__file__, "-v", "-s"])