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を使ったサンプル画像を生成して」
とか指示すると、以下のような画像が生成される。
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")

