2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude DesktopのMCPでPythonコードを実行するまで(MCPサーバー入門)

Last updated at Posted at 2025-06-04

Claude DesktopのMCPでPythonコードを実行するまで(MCPサーバー入門)

MacbookのClaudeのMCPでPythonコードを実行したかったので試してみた。

(本記事は、https://qiita.com/quittardis/items/a3ba1cc395bcb1c29570 の内容を前提知識としています)
上の記事に従って設定後、

MCPライブラリをインストールしてプロジェクトを作成

# uvを使って直接MCPライブラリをインストールしてプロジェクトを作成
uv init my-python-server
cd my-python-server
uv add "mcp[cli]"

基本的なサーバーファイルを作成:

# サーバーファイルを作成
touch server.py

server.pyに以下の基本的なMCPサーバーコードを追加:

from mcp.server.fastmcp import FastMCP
import math
import json
from datetime import datetime

# MCPサーバーを初期化
mcp = FastMCP("MyPythonServer")

# 基本的なツール
@mcp.tool()
def hello(name: str) -> str:
    """Say hello to someone"""
    return f"Hello, {name}! Welcome to my Python MCP server."

@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two numbers"""
    return a + b

@mcp.tool()
def multiply(a: float, b: float) -> float:
    """Multiply two numbers"""
    return a * b

@mcp.tool()
def calculate_circle_area(radius: float) -> float:
    """Calculate the area of a circle given its radius"""
    return math.pi * radius ** 2

@mcp.tool()
def get_current_time() -> str:
    """Get the current date and time"""
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

@mcp.tool()
def reverse_string(text: str) -> str:
    """Reverse a given string"""
    return text[::-1]

@mcp.tool()
def word_count(text: str) -> dict:
    """Count the number of words, characters, and lines in a text"""
    words = len(text.split())
    characters = len(text)
    lines = len(text.split('\n'))
    
    return {
        "words": words,
        "characters": characters,
        "lines": lines
    }

# リソースの追加(オプション)
@mcp.resource("info://server")
def get_server_info() -> str:
    """Get information about this MCP server"""
    return json.dumps({
        "name": "MyPythonServer",
        "version": "1.0.0",
        "description": "A basic Python MCP server with common utilities",
        "tools": [
            "hello", "add", "multiply", "calculate_circle_area", 
            "get_current_time", "reverse_string", "word_count"
        ]
    }, indent=2)

if __name__ == "__main__":
    mcp.run(transport="stdio")

(このコードはテストに使うだけですが、MCPサーバーをどうやって
構築するかのいい勉強になります。)

Claude Desktopの設定ファイルを編集:

<#ユーザー名> のところは実際にユーザー名に入れ替えてください。

{
  "mcpServers": {
    "my-python-server": {
      "command": "uv",
      "args": [
        "--directory",
        "/Users/#ユーザー名/my-python-server",
        "run",
        "server.py"
      ]
    }
  }
}

Claude Desktopを終了し、再起動して、
「今の時刻を教えて」とか聞くとこの新しい serverが動作していることがわかる。
「hello」と呼びかけると

Hello! MCPサーバーの機能を使ってお挨拶させていただきました。何かお手伝いできることはありますか?
先ほど作成したmy-python-server MCPの機能も利用できます。例えば:

計算(足し算、掛け算)
円の面積計算
文字列の操作
テキストの統計

何か試してみたいことがあれば、お気軽にお声がけください!

次に、
「my-python-serverをpythonコードを実行するMCPにしたい」
と、インプットすると、python実行用のコードが生成される。

from mcp.server.fastmcp import FastMCP
import subprocess
import sys
import tempfile
import os
import json
from datetime import datetime
import traceback

# MCPサーバーを初期化
mcp = FastMCP("PythonExecutorServer")

@mcp.tool()
def execute_python_code(code: str, timeout: int = 30) -> dict:
    """
    Execute Python code safely and return the output.
    
    Args:
        code: Python code to execute
        timeout: Maximum execution time in seconds (default: 30)
    
    Returns:
        Dictionary with stdout, stderr, return_code, and execution_time
    """
    try:
        # 一時ファイルを作成してコードを保存
        with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
            f.write(code)
            temp_file = f.name
        
        start_time = datetime.now()
        
        # Pythonコードを実行
        result = subprocess.run(
            [sys.executable, temp_file],
            capture_output=True,
            text=True,
            timeout=timeout
        )
        
        end_time = datetime.now()
        execution_time = (end_time - start_time).total_seconds()
        
        # 一時ファイルを削除
        os.unlink(temp_file)
        
        return {
            "stdout": result.stdout,
            "stderr": result.stderr,
            "return_code": result.returncode,
            "execution_time": execution_time,
            "success": result.returncode == 0
        }
        
    except subprocess.TimeoutExpired:
        # タイムアウトの場合
        if 'temp_file' in locals():
            os.unlink(temp_file)
        return {
            "stdout": "",
            "stderr": f"Execution timed out after {timeout} seconds",
            "return_code": -1,
            "execution_time": timeout,
            "success": False
        }
    except Exception as e:
        # その他のエラー
        if 'temp_file' in locals():
            os.unlink(temp_file)
        return {
            "stdout": "",
            "stderr": f"Error executing code: {str(e)}",
            "return_code": -1,
            "execution_time": 0,
            "success": False
        }

@mcp.tool()
def execute_python_expression(expression: str) -> dict:
    """
    Evaluate a Python expression and return the result.
    
    Args:
        expression: Python expression to evaluate
    
    Returns:
        Dictionary with result, error, and success status
    """
    try:
        # 安全な環境で式を評価
        result = eval(expression, {"__builtins__": {}}, {
            "abs": abs, "round": round, "min": min, "max": max,
            "sum": sum, "len": len, "str": str, "int": int, "float": float,
            "list": list, "dict": dict, "tuple": tuple, "set": set,
            "range": range, "enumerate": enumerate, "zip": zip,
            "sorted": sorted, "reversed": reversed,
            "math": __import__("math"),
            "datetime": __import__("datetime"),
        })
        
        return {
            "result": str(result),
            "type": type(result).__name__,
            "success": True,
            "error": None
        }
        
    except Exception as e:
        return {
            "result": None,
            "type": None,
            "success": False,
            "error": str(e)
        }

@mcp.tool()
def install_package(package_name: str) -> dict:
    """
    Install a Python package using pip.
    
    Args:
        package_name: Name of the package to install
    
    Returns:
        Dictionary with installation status and output
    """
    try:
        result = subprocess.run(
            [sys.executable, "-m", "pip", "install", package_name],
            capture_output=True,
            text=True,
            timeout=300  # 5分のタイムアウト
        )
        
        return {
            "success": result.returncode == 0,
            "stdout": result.stdout,
            "stderr": result.stderr,
            "return_code": result.returncode,
            "package": package_name
        }
        
    except subprocess.TimeoutExpired:
        return {
            "success": False,
            "stdout": "",
            "stderr": f"Package installation timed out after 300 seconds",
            "return_code": -1,
            "package": package_name
        }
    except Exception as e:
        return {
            "success": False,
            "stdout": "",
            "stderr": f"Error installing package: {str(e)}",
            "return_code": -1,
            "package": package_name
        }

@mcp.tool()
def list_installed_packages() -> dict:
    """
    List all installed Python packages.
    
    Returns:
        Dictionary with list of installed packages
    """
    try:
        result = subprocess.run(
            [sys.executable, "-m", "pip", "list", "--format=json"],
            capture_output=True,
            text=True,
            timeout=30
        )
        
        if result.returncode == 0:
            packages = json.loads(result.stdout)
            return {
                "success": True,
                "packages": packages,
                "count": len(packages)
            }
        else:
            return {
                "success": False,
                "error": result.stderr,
                "packages": [],
                "count": 0
            }
            
    except Exception as e:
        return {
            "success": False,
            "error": str(e),
            "packages": [],
            "count": 0
        }

@mcp.tool()
def check_syntax(code: str) -> dict:
    """
    Check Python code syntax without executing it.
    
    Args:
        code: Python code to check
    
    Returns:
        Dictionary with syntax check results
    """
    try:
        compile(code, '<string>', 'exec')
        return {
            "valid": True,
            "error": None,
            "message": "Syntax is valid"
        }
    except SyntaxError as e:
        return {
            "valid": False,
            "error": "SyntaxError",
            "message": str(e),
            "line": e.lineno,
            "offset": e.offset
        }
    except Exception as e:
        return {
            "valid": False,
            "error": type(e).__name__,
            "message": str(e),
            "line": None,
            "offset": None
        }

# サーバー情報リソース
@mcp.resource("info://server")
def get_server_info() -> str:
    """Get information about this Python executor MCP server"""
    return json.dumps({
        "name": "PythonExecutorServer",
        "version": "1.0.0",
        "description": "A Python MCP server that can execute Python code safely",
        "tools": [
            "execute_python_code",
            "execute_python_expression", 
            "install_package",
            "list_installed_packages",
            "check_syntax"
        ],
        "python_version": sys.version,
        "python_executable": sys.executable
    }, indent=2)

if __name__ == "__main__":
    mcp.run(transport="stdio")

server.pyにこれをコピペします。

既存のserver.pyファイルの中身を上記のコードで置き換えてください。このアップデートされたMCPサーバーには以下の機能が含まれています:

主な機能

1. execute_python_code(code, timeout=30)

完全なPythonコードを実行
タイムアウト設定可能
標準出力・エラー出力・実行時間を返す

2. execute_python_expression(expression)

数式や簡単な式を安全に評価
制限された環境で実行(セキュリティのため)

3. install_package(package_name)

pipを使ってパッケージをインストール

4. list_installed_packages()

インストール済みパッケージの一覧表示

5. check_syntax(code)

コードの構文チェック(実行なし)

Claude Desktopを再起動すると、以下のようなことができる:

コード実行:

"次のPythonコードを実行して: print('Hello World')"

(これで、my-python-serverがPythonコード実行専用のMCPサーバーになります!)

numpyや、matplotlibをインストールする場合は、

my_python_serverフォルダに移動し、Terminalから、

uv add numpy

uv add matplotlib

uv add opencv-python-headless (ビルドするので時間がかかります)

とします。

「Pythonでmatplotlibを使ったサンプル画像を生成して」
とか指示すると、以下のような画像が生成される。

circle_art.png

flower_pattern.png

Windows版

Macbookで動作した上のコードであるが、Windowsでは、Pythonコードの実行のところでTime Outになってしまう。 ChatGPTやClaudeに相談して色々やってみたが、なかなか正解にたどりつけなかった。最後にPythonコードの実行時のWindowsの問題じゃないかと、あたりをつけ、ChatGPTに簡単なコードを生成し、それを上のコードに移植してやっと動いたのが下のコード。
この二つのコードをClaudeに比較してもらったときのメッセージによると、

「WindowsとmacOSでの動作の違いは、主にプロセス作成のコストの差にあります。ファイル1ではsubprocess.run()で新しいPythonプロセスを起動していますが、Windowsではプロセス作成のオーバーヘッドが非常に大きく、これがタイムアウトの原因となっています。一方、ファイル2ではexec()を使って同一プロセス内でコードを実行するため、プロセス作成のコストを回避できています。macOSやLinuxではfork()システムコールによる軽量なプロセス作成が可能ですが、Windowsではプロセス作成により多くの時間とリソースが必要になります。また、一時ファイルの作成・削除やPythonインタープリターの起動時間も、Windowsの方が一般的に時間がかかります。ただし、ファイル2のアプローチはセキュリティ面でリスクがあるため、本格的な用途では プラットフォーム別の最適化やハイブリッドアプローチを検討することをお勧めします。」

という御託宣でした。

windows用の改良版👇

def execute_python_codeだけを修正しています

from mcp.server.fastmcp import FastMCP
from starlette.responses import JSONResponse
import subprocess
import sys
import tempfile
import os
import json
from datetime import datetime
import traceback

# MCPサーバーを初期化
mcp = FastMCP("PythonExecutorServer")


@mcp.tool()
def execute_python_code(code: str, timeout: int = 30) -> dict:
    """
    Execute Python code safely and return the output.
    
    Args:
        code: Python code to execute
        timeout: Maximum execution time in seconds (default: 30)
    
    Returns:
        Dictionary with stdout, stderr, return_code, and execution_time
    """
    try:
        # 危険なコードの実行を避けるため、実行環境を制限
        exec_globals = {}
        exec_locals = {}

        # 安全な範囲でPythonコードを実行
        exec(code, exec_globals, exec_locals)

        return JSONResponse({
            "status": "success",
            "result": exec_locals
        })

    except Exception as e:
        return JSONResponse({
            "status": "error",
            "message": str(e)
        }, status_code=400)


@mcp.tool()
def execute_python_expression(expression: str) -> dict:
    """
    Evaluate a Python expression and return the result.
    
    Args:
        expression: Python expression to evaluate
    
    Returns:
        Dictionary with result, error, and success status
    """
    try:
        # 安全な環境で式を評価
        result = eval(expression, {"__builtins__": {}}, {
            "abs": abs, "round": round, "min": min, "max": max,
            "sum": sum, "len": len, "str": str, "int": int, "float": float,
            "list": list, "dict": dict, "tuple": tuple, "set": set,
            "range": range, "enumerate": enumerate, "zip": zip,
            "sorted": sorted, "reversed": reversed,
            "math": __import__("math"),
            "datetime": __import__("datetime"),
        })
        
        return {
            "result": str(result),
            "type": type(result).__name__,
            "success": True,
            "error": None
        }
        
    except Exception as e:
        return {
            "result": None,
            "type": None,
            "success": False,
            "error": str(e)
        }

@mcp.tool()
def install_with_uv(package_name: str):
    """
    uv pip を使用してパッケージをインストールする関数。
    uv コマンドがシステムにインストールされている必要があります。
    """
    try:
        print(f"Installing '{package_name}' using uv...")
        subprocess.check_call(["uv", "pip", "install", package_name])
        print(f"'{package_name}' installed successfully with uv.")
    except subprocess.CalledProcessError:
        print(f"Failed to install '{package_name}' using uv.")
    except FileNotFoundError:
        print("❌ 'uv' command not found. Please install uv first.")

@mcp.tool()
def list_installed_packages() -> dict:
    """
    List all installed Python packages.
    
    Returns:
        Dictionary with list of installed packages
    """
    try:
        result = subprocess.run(
            [sys.executable, "-m", "pip", "list", "--format=json"],
            capture_output=True,
            text=True,
            timeout=30
        )
        
        if result.returncode == 0:
            packages = json.loads(result.stdout)
            return {
                "success": True,
                "packages": packages,
                "count": len(packages)
            }
        else:
            return {
                "success": False,
                "error": result.stderr,
                "packages": [],
                "count": 0
            }
            
    except Exception as e:
        return {
            "success": False,
            "error": str(e),
            "packages": [],
            "count": 0
        }

@mcp.tool()
def check_syntax(code: str) -> dict:
    """
    Check Python code syntax without executing it.
    
    Args:
        code: Python code to check
    
    Returns:
        Dictionary with syntax check results
    """
    try:
        compile(code, '<string>', 'exec')
        return {
            "valid": True,
            "error": None,
            "message": "Syntax is valid"
        }
    except SyntaxError as e:
        return {
            "valid": False,
            "error": "SyntaxError",
            "message": str(e),
            "line": e.lineno,
            "offset": e.offset
        }
    except Exception as e:
        return {
            "valid": False,
            "error": type(e).__name__,
            "message": str(e),
            "line": None,
            "offset": None
        }

# サーバー情報リソース
@mcp.resource("info://server")
def get_server_info() -> str:
    """Get information about this Python executor MCP server"""
    return json.dumps({
        "name": "PythonExecutorServer",
        "version": "1.0.0",
        "description": "A Python MCP server that can execute Python code safely",
        "tools": [
            "execute_python_code",
            "execute_python_expression", 
            "install_package",
            "list_installed_packages",
            "check_syntax"
        ],
        "python_version": sys.version,
        "python_executable": sys.executable
    }, indent=2)

if __name__ == "__main__":
    mcp.run(transport="stdio")
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?