Model Context Protocol (MCP) のビッグウェーブが来ていますが、まだ乗れていなかったので乗ってみます。
今回やること
- PythonでPCからArduinoにシリアル通信を行い、内蔵LEDを点灯・消灯するMCPサーバーを構築する
- MCPサーバーをMCPクライアントから直接叩いてLチカする
- Claude Desktopを使ってMCP経由でLEDを操作する
- OpenAI Agents SDKを使ってAIエージェントにMCP経由でLEDを操作してもらってLチカする
以下手順の紹介です。
環境構築
Windows上に適当なPython環境が構築されていることを前提としますが、他のOSでもだいたい同じだと思います。
uvのインストール
この頃はやりのPythonパッケージマネージャーです。MCP関係の記事だとuvが使われている場合が多いように思います。 GitHubのドキュメントに従ってインストールします。
pipを使う場合は以下の通り。
pip install uv
必要なライブラリのインストール
適当なディレクトリを作って以下を実行します。
この記事では C:\Users\kivantium\mcp_arduino 内で作業するものとします。
cd C:\Users\kivantium\mcp_arduino uv init uv add fastmcp pyserial
FastMCPに関する注意
現在FastMCPと呼ばれているものには2種類あります。
- 公式のMCP Python SDKに含まれるfastmcpモジュール
- 有志開発のFastMCP v2系列
二つはimport文で見分けることができます。
import mcp.server.fastmcpでインポートしていたら公式SDKimport fastmcpでインポートしていたらv2系列
両者にはある程度の互換性があるものの、細かい部分には違いがあるのでどちらのFastMCPを使っているかには注意する必要があります。この記事はv2系列のFastMCPを利用しています。
Arduinoの準備
適当なArduinoを買って、PCにはArduino IDEをインストールしておきます。
Arduinoスケッチ
以下のコードをArduinoに書き込みます。シリアル通信で 1 を送信したら内蔵LEDが点灯し、0 を送信したら消灯します。
void setup() { pinMode(LED_BUILTIN, OUTPUT); Serial.begin(9600); } void loop() { char key; if (Serial.available()) { key = Serial.read(); switch(key) { case '1': digitalWrite(LED_BUILTIN, HIGH); break; case '0': digitalWrite(LED_BUILTIN, LOW); break; } } }
MCP サーバー
Pythonからシリアル通信で1または0を送信するだけのプログラムserver.pyを作ります。
import serial from fastmcp import FastMCP # Arduinoとのシリアル通信を行うクラス class ArduinoLEDController: def __init__(self, port: str): try: self.ser = serial.Serial( port=port, baudrate=9600, parity=serial.PARITY_NONE, timeout=1 ) except serial.SerialException as e: raise RuntimeError(f"Failed to open serial port {port}: {e}") from e def write_str(self, data: str) -> None: try: self.ser.write(data.encode('utf-8')) self.ser.flush() except serial.SerialException as e: print(f"Serial write error: {e}") def led_on(self) -> None: """Turn on LED""" self.write_str('1') def led_off(self) -> None: """Turn off LED""" self.write_str('0') def close(self): if self.ser.is_open: self.ser.close() ctrl = ArduinoLEDController("COM3") # クラスのメンバ関数をMCPサーバー化 # https://gofastmcp.com/v2/patterns/decorating-methods mcp = FastMCP("Arduino LED controller") mcp.tool(ctrl.led_on) mcp.tool(ctrl.led_off) if __name__ == "__main__": try: mcp.run() finally: ctrl.close()
以下を実行して正常に起動することを確認します。
uv run server.py
MCPクライアント
動作確認として、LチカするためのMCPクライアントの例を示します。
import asyncio from fastmcp import Client from fastmcp.client.transports import StdioTransport async def main(): # 標準入出力でMCPサーバーと通信するときの書き方 # https://gofastmcp.com/clients/transports transport = StdioTransport( command="uv", args=["run", "server.py"] ) async with Client(transport) as client: # ツール一覧を表示 tools = await client.list_tools() print(tools) # LEDを1秒間隔で点滅 print("Start blinking LED... (Ctrl+C to stop)") while True: await client.call_tool("led_on") await asyncio.sleep(1) await client.call_tool("led_off") await asyncio.sleep(1) asyncio.run(main())
これを uv run client.py のように実行すると、以下のツール一覧が表示された後にLEDが点滅します。
[Tool(name='led_on', title=None, description='Turn on LED', inputSchema={'properties': {}, 'type': 'object'}, outputSchema=None, icons=None, annotations=None, meta={'_fastmcp': {'tags': []}}, execution=None), Tool(name='led_off', title=None, description='Turn off LED', inputSchema={'properties': {}, 'type': 'object'}, outputSchema=None, icons=None, annotations=None, meta={'_fastmcp': {'tags': []}}, execution=None)]
Claude Desktopとの連携
Claude Desktopをダウンロードして適宜アカウントを設定したら、「設定 (Ctrl+,)」→「開発者」→「設定を編集」を選んでMCPの設定ファイルを開きます。
今回のケースでは claude_desktop_config.json に以下の内容を書き込みます。
パスは環境に応じて適宜変更してください。
{ "mcpServers": { "led_control": { "command": "C:\\Users\\kivantium\\.local\\bin\\uv.exe", "args": [ "--directory", "C:\\Users\\kivantium\\mcp_arduino", "run", "server.py" ] } } }
設定を終えたらClaude Desktopを再起動(重要:単にウィンドウを閉じるだけでなくシステムトレイのアイコンを右クリックして「終了」を選んで完全に終了すること)すると、MCPサーバーがClaude Desktopに認識されるはずです。

この状態でClaude DesktopにLEDの点灯を依頼すると、MCPサーバー経由でツールを実行してくれます。

OpenAI Agents SDKからのMCPサーバー呼び出し
AIエージェントからのMCPサーバー呼び出しの例として、OpenAI Agents SDKを使うコードを示します。
インストール
uv add openai-agents
コード例
import asyncio from agents import Agent, Runner from agents.mcp import MCPServerStdio async def main(): async with MCPServerStdio( name="led_control", params={ "command": "uv", "args": ["run", "server.py"], } ) as server: agent = Agent( name="Assistant", instructions="You are a helpful assistant", model="gpt-4o-mini", mcp_servers=[server], ) while True: result = await Runner.run(agent, "Please turn on the LED.") print(result.final_output) result = await Runner.run(agent, "Please turn off the LED.") print(result.final_output) if __name__ == "__main__": asyncio.run(main())
上のコードをclient_agent.pyという名前で保存して、環境変数 OPENAI_API_KEY にOpenAIのAPIキーを設定してから実行します。
uv run client_agent.py
ArduinoのLEDが点滅し、ターミナルには次のようなメッセージが表示されます。
The LED has been turned on. The LED has been turned off. The LED has been turned on. If you need anything else, just let me know! The LED has been turned off. If you need anything else, just let me know! The LED has been turned on. If you need anything else, just let me know! The LED is now turned off.
点滅させるためのロジックを自分で書いてしまってはエージェントの意味がないので、AIが自分で考えてLチカできるようにします。
まず、MCPサーバーに遅延関数を加えます。diffを示します。
@@ -31,6 +31,10 @@ """Turn off LED""" self.write_str('0') + def wait_1s(self) -> None: + """Wait for 1 second""" + time.sleep(1) + def close(self): if self.ser.is_open: self.ser.close() @@ -42,6 +46,7 @@ mcp = FastMCP("Arduino LED controller") mcp.tool(ctrl.led_on) mcp.tool(ctrl.led_off) +mcp.tool(ctrl.wait_1s) if __name__ == "__main__": try:
次にエージェントに指示を与えるループを以下の単一の指示に書き換えます。
result = await Runner.run(agent, "Please blink the LED 3 times at 1 second intervals.")
すると、以下の動画に示すように無事にLチカが実行されました。
LEDを点滅させるためだけにOpenAI社のサーバーでGPUがうなる富豪的Lチカが完成しました。めでたしめでたし。
解決していない問題点
上で示したコードを実行すると以下のようなエラーが表示されます。
Error invoking MCP tool led_off: Timed out while waiting for response to ClientRequest. Waited 5.0 seconds. Error invoking MCP tool led_off: Timed out while waiting for response to ClientRequest. Waited 5.0 seconds. Error invoking MCP tool wait_1s: Timed out while waiting for response to ClientRequest. Waited 5.0 seconds.
検索するとタイムアウト時間を延ばせば良いという解決策が出てきますが、長いタイムアウト時間を設定するとLEDの点滅が3回で終わらなくなりました。
何が起きているのか調べる必要があります。

























