手を動かして学ぶ！MCPステップバイステップ実践ガイド for Beginners - Vol.6 データをサーバーへ！MCPモデル情報の登録(POST)＆更新(PUT)

## はじめに

皆さん、こんにちは！「手を動かして学ぶ！MCPステップバイステップ実践ガイド for Beginners」へようこそ。このシリーズでは、Model Context Protocol (MCP) という仕組みを、Pythonというプログラミング言語を使って、実際に手を動かしながら学んでいきます。

前回 ([Vol.5 ねらいうち！URLで指定したMCPモデル情報だけを取得する](https://qiita.com/QueryPie/items/9790cd1efaeb9a20f3ab)) は、特定のMCPデバイスの情報だけをピンポイントで取得する方法を学びましたね。サーバーに「このIDのデバイス情報をください！」とお願いして、その情報だけを手に入れることができました。

今回は、これまでとは逆のステップに進みます。これまではサーバーから情報を「もらう」(GET) のがメインでしたが、今回はクライアント側からサーバーへ情報を「送る」方法、具体的には新しいMCPモデル情報（デバイス情報）をサーバーに登録したり、既存の情報を更新したりする方法を学びます。

そのために活躍するのが、**POST** と **PUT** というHTTPメソッドです。なんだか難しそうに聞こえるかもしれませんが、大丈夫。今回も身近な例え話を交えながら、わかりやすく解説していきますので、一緒にMCPの世界をさらに探求していきましょう！

## 1. データを「送る」ってどういうこと？HTTPメソッド POST と PUT 📮🔄

前回までは、WebブラウザでWebサイトを見るときのように、サーバーに「この情報が欲しい！」とリクエストを送り、情報を受け取る「GET」という方法を使ってきました。これは、図書館に行って司書さんに「この本を探しています」とお願いして、本（情報）を受け取るのに似ていますね。

しかし、MCPでは情報を取得するだけでなく、新しい情報を追加したり、既にある情報を変更したりすることも重要です。例えば、新しいスマートデバイスをシステムに追加したり、設置場所が変わったデバイスの情報を更新したりする場合などです。

このような「情報をサーバーに送る」操作のために、HTTPにはいくつかの **メソッド (methods)** が用意されています。メソッドとは、サーバーに「何をしてほしいか」を伝える動詞のようなものです。今回はその中でも特に重要な **POST** と **PUT** について学んでいきましょう。

**POSTとは？ (新しいデータを「登録する」) 📮**

**POST**メソッドは、主に新しいデータをサーバーに送信し、新しい **リソース (resource)** を作成するようお願いする時に使います。リソースとは、この文脈ではサーバーが管理している情報の一単位、例えば私たちのMCPシステムで言えば、個々のデバイス情報のことです。

例えるなら、あなたが新しい本を図書館に寄贈するようなものです。
1. あなたは寄贈したい本（新しいデータ）を図書館の受付に持っていきます。
1. 受付の司書さん（サーバー）は、その本を受け取り、図書館の蔵書リストに新しい項目として登録します。
1. 場合によっては、司書さんが新しい管理番号を本に割り振るかもしれません。

POSTリクエストを送ると、サーバーは受け取ったデータを使って新しい情報を作り、通常は「登録できましたよ」という応答を返します。

**PUTとは？ (既存のデータを「置き換える」または「更新する」) 🔄**

**PUT** メソッドは、主に既存のデータをサーバーに送信し、特定のリソースを丸ごと置き換える（更新する）ようお願いする時に使います。どのリソースを更新したいかは、通常URLで指定します。

例えるなら、図書館にある本の情報カード（例えば、本の概要や著者名が書かれたカード）の内容が古くなったので、あなたが新しい情報カードを作成し、司書さんに「この本の情報カードを、この新しいものに差し替えてください」とお願いするようなものです。

1. あなたは更新したい本の情報カード（特定のリソース）と、新しい情報カード（更新データ）を司書さんに渡します。
1. 司書さん（サーバー）は、指定された古い情報カードを見つけ出し、新しい情報カードと完全に差し替えます。

PUTリクエストを送ると、サーバーは指定されたリソースを見つけて、受け取ったデータでそのリソース全体を更新し、「更新できましたよ」という応答を返します。もし指定されたリソースが存在しない場合、サーバーの作りによっては新しく作成することもありますが、基本的には「置き換える」役割が強いメソッドです。

**POSTとPUTの使い分けのポイント**

- **新しい情報を登録したい場合: POST** を使います。例えば、システムに全く新しいデバイスを追加する場合などです。
- **既存の情報を丸ごと更新したい場合: PUT** を使います。例えば、あるデバイスの設置場所や設定が変わり、そのデバイスの情報を全面的に最新化する場合などです。PUTでは、対象の情報を特定するためのID（私たちの例では```deviceId```）が必要です。

今回は、POSTを使って新しいデバイス情報をサーバーに登録し、PUTを使って既存のデバイス情報を更新する機能を、実際にPythonコードを書きながら実装していきます。
 
## 2. サーバー側の準備：データを受け取る窓口を作ろう (```app.py``` の変更) 🛠️

まずは、クライアントから送られてくる新しいデバイス情報や更新情報を受け取って処理できるように、サーバー側のプログラム ```app.py``` を改造しましょう。

**必要なもの**
- **Python:** バージョン3.x系がインストールされていること。
- **Flask:** PythonのWebフレームワークです。インストールされていない場合は、ターミナルやコマンドプロンプトで以下のコマンドを実行してインストールしてください。 

```
pip install Flask
```
(macOSやLinuxで ```pip3``` を使っている方は ```pip3 install Flask``` としてください。)

**```app.py``` の変更点**

[Vol.5](https://qiita.com/QueryPie/items/9790cd1efaeb9a20f3ab)の ```app.py``` をベースに、以下の点を変更・追加していきます。

**1\. ```request``` オブジェクトのインポート:** クライアントから送信されたデータ（ペイロードと言います）にアクセスするために、Flaskの ```request``` という部品をインポートします。

**2\. 新しいデバイスを登録する処理 (POST /devices):**
- ```/devices``` というURLに対してPOSTリクエストが来た場合の処理を追加します。
- クライアントからJSON形式で送られてくる新しいデバイス情報を ```request.json``` で受け取ります。
- 新しいデバイスの ```deviceId``` が既存のものと重複していないかチェックします。
- 問題なければ、デバイスリスト ```all_devices_data``` に追加します。
- 成功した証として、HTTPステータスコード ```201 Created``` (作成成功) と共に、追加したデバイス情報を返します。

**3\. 既存デバイス情報を更新する処理 (PUT ```/devices/<device_id>```):** 
- ```/devices/<device_id>``` というURLに対してPUTリクエストが来た場合の処理を追加します。
- クライアントからJSON形式で送られてくる更新用デバイス情報を ```request.json``` で受け取ります。
- URLで指定された ```device_id``` を持つデバイスを探します。
- 見つかれば、そのデバイスの情報を丸ごと新しい情報で置き換えます。
- 成功した証として、HTTPステータスコード ```200 OK``` (成功) と共に、更新後のデバイス情報を返します。
- もし指定された ```device_id``` のデバイスが見つからなければ、```404 Not Found``` (見つかりません) エラーを返します。

**```app.py``` (Vol.6 版)**

```Python
# app.py (Vol.6 版)
from flask import Flask, jsonify, request # request をインポート

app = Flask(__name__)

# サンプルのMCPデバイスデータ (リスト形式) - Vol.5 と同じ
all_devices_data = [
    {
        "modelName": "Smart Thermostat X1000",
        "deviceId": "THERMO-001-A",
        "location": "Living Room",
        "status": {"currentTemperature": 23.5, "targetTemperature": 24.0, "unit": "Celsius", "isActive": True, "mode": "auto"},
        "supportedModes": ["auto", "cool", "heat", "off"]
    },
    {
        "modelName": "Smart Light L200",
        "deviceId": "LIGHT-002-B",
        "location": "Bedroom",
        "status": {"brightness": 80, "color": "warm_white", "unit": "percent", "isActive": True},
        "supportedModes": ["on", "off", "dim"]
    },
    {
        "modelName": "Security Camera C300",
        "deviceId": "CAM-003-C",
        "location": "Entrance",
        "status": {"isRecording": False, "detectionMode": "motion", "isActive": True},
        "supportedEvents": ["motion_detected", "sound_detected"]
    }
]

# 全デバイス情報を返すエンドポイント (GET) と新しいデバイスを登録するエンドポイント (POST)
@app.route('/devices', methods=['GET', 'POST']) # POSTメソッドを許可
def handle_devices():
    if request.method == 'POST':
        # クライアントから送信されたJSONデータを取得
        new_device_data = request.json
        if not new_device_data:
            return jsonify({"error": "No data provided"}), 400 # 400 Bad Request

        # deviceId の存在チェックと重複チェック
        new_device_id = new_device_data.get("deviceId")
        if not new_device_id:
            return jsonify({"error": "deviceId is required"}), 400

        for device in all_devices_data:
            if device.get("deviceId") == new_device_id:
                return jsonify({"error": "Device with this ID already exists", "deviceId": new_device_id}), 409 # 409 Conflict
        
        # 新しいデバイスデータをリストに追加 (ここでは簡単なバリデーションのみ)
        all_devices_data.append(new_device_data)
        # 201 Created ステータスコードと共に、追加されたデバイス情報を返す
        return jsonify(new_device_data), 201
    
    # GETリクエストの場合は、これまで通り全デバイス情報を返す
    return jsonify({"devices": all_devices_data})

# 特定のデバイスIDに基づいて情報を返す (GET) / 更新する (PUT) エンドポイント
@app.route('/devices/<device_id>', methods=['GET', 'PUT']) # PUTメソッドを許可
def handle_specific_device(device_id):
    found_device_index = -1
    for i, device in enumerate(all_devices_data):
        if device.get("deviceId") == device_id:
            found_device_index = i
            break

    if request.method == 'PUT':
        if found_device_index != -1:
            # クライアントから送信されたJSONデータを取得
            updated_data = request.json
            if not updated_data:
                return jsonify({"error": "No data provided for update"}), 400
            
            # 本来はここでデータのバリデーションを行うべき
            # 今回はシンプルに受け取ったデータで既存のデータを置き換える
            # deviceId の変更は通常許可しないか、慎重に扱うべきだが、ここでは簡略化
            all_devices_data[found_device_index] = updated_data
            # 更新されたdeviceIdが元のものと一致するか、または新しいデータにdeviceIdが含まれているか確認
            if updated_data.get("deviceId") != device_id:
                # もしクライアントがペイロード内でdeviceIdを変更しようとした場合、
                # それをエラーとするか、URLのdevice_idを正とするか設計による。
                # ここでは、URLのdevice_idで識別されたリソースを更新したことを示す。
                # 更新後のデータには、ペイロードで送られたdeviceIdが含まれる。
                # 混乱を避けるため、ペイロードのdeviceIdもdevice_idと一致させるのが望ましい。
                 return jsonify({"warning": "Device ID in URL and payload mismatch. Resource updated based on URL.", "updated_device": updated_data}), 200
            return jsonify(updated_data), 200 # 200 OK
        else:
            # PUTで対象が見つからない場合、404を返す (作成はしない)
            return jsonify({"error": "Device not found, cannot update", "requested_id": device_id}), 404
    
    # GETリクエストの場合
    if found_device_index != -1:
        return jsonify(all_devices_data[found_device_index])
    else:
        return jsonify({"error": "Device not found", "requested_id": device_id}), 404

if __name__ == '__main__':
    print("MCP Server (Vol.6) is running on http://127.0.0.1:5000")
    print("Access all devices at: http://127.0.0.1:5000/devices (GET)")
    print("Register a new device at: http://127.0.0.1:5000/devices (POST)")
    print("Access/Update a specific device, e.g., THERMO-001-A at: http://127.0.0.1:5000/devices/THERMO-001-A (GET/PUT)")
    print("To stop the server, press CTRL+C")
    # macOSやLinuxで python3 を使っている場合は、python3 app.py で実行してください。
    app.run(debug=True, port=5000)
```

**コードのポイント:**

- ```@app.route('/devices', methods=['GET', 'POST']): /devices``` というURL（エンドポイントと言います）が、GETリクエストとPOSTリクエストの両方を受け付けることを示しています。
- ```if request.method == 'POST'::``` 現在のリクエストがPOSTメソッドかどうかを判断しています。
- ```request.json:``` クライアントがPOSTリクエストやPUTリクエストの本体（ボディ）に含めて送ってきたJSONデータをPythonの辞書として取り出します。
- ```jsonify(new_device_data), 201```: JSON形式でレスポンスを返しつつ、HTTPステータスコードとして```201 Created``` を指定しています。これは「リソースの作成が成功した」ことを意味します。
- ```409 Conflict```: POSTで新しいデバイスを登録しようとした際に、同じ```deviceId```が既に存在した場合に返すエラーです。「競合が発生した」ことを意味します。
- ```@app.route('/devices/<device_id>', methods=['GET', 'PUT'])```: 特定のデバイスID (```<device_id>```) を含むURLが、GETリクエストとPUTリクエストの両方を受け付けることを示しています。
- PUT処理では、まずデバイスが存在するかどうかを確認し、存在すれば更新、しなければ404エラーを返します。

**サーバーの起動**

上記の ```app.py``` を保存したら、ターミナルやコマンドプロンプトでそのファイルがあるディレクトリに移動し、以下のコマンドでサーバーを起動します。

```
python app.py
```

macOSやLinuxで ```python3``` を使っている方は ```python3 app.py``` と入力してください。

成功すれば、```MCP Server (Vol.6) is running on http://127.0.0.1:5000``` といったメッセージが表示されます。
 
## 3. クライアント側の挑戦：サーバーにデータを送ってみよう (```client.py``` の変更) 📤

サーバー側の準備が整ったので、次はクライアント側のプログラム ```client.py``` を変更して、実際にサーバーにデータを送信する機能を実装しましょう。

**```client.py``` の変更点**

[Vol.5](https://qiita.com/QueryPie/items/9790cd1efaeb9a20f3ab)の ```client.py``` をベースに、以下の関数を新しく追加・変更します。

**1\. 新しいデバイスを登録する関数 (```add_new_device```):**
- 登録したいデバイスの情報（Pythonの辞書形式）を引数として受け取ります。
- ```requests.post()``` を使って、サーバーの ```/devices``` エンドポイントにPOSTリクエストを送信します。
- ```json=```引数を使って、Pythonの辞書をJSON形式で送信します。
- サーバーからの応答（成功かエラーか）を表示します。

**2\. 既存デバイス情報を更新する関数 (```update_device_info```):**
- 更新対象の ```device_id``` と、更新後のデバイス情報（Pythonの辞書形式）を引数として受け取ります。
- ```requests.put()``` を使って、サーバーの ```/devices/<device_id>``` エンドポイントにPUTリクエストを送信します。
- こちらも ```json=```引数でデータを送信します。
- サーバーからの応答を表示します。

**```client.py``` (Vol.6 版)**

```Python
# client.py (Vol.6 版)
import requests
import json # JSONを整形して表示するためにインポート
from requests.exceptions import JSONDecodeError

BASE_SERVER_URL = "http://127.0.0.1:5000/devices" # サーバーの基本URL

# display_device_info関数はVol.5から変更なし (ここでは省略、前回のコードを流用してください)
def display_device_info(device_info):
    """デバイス情報を整形して表示するヘルパー関数"""
    print(f"  モデル名: {device_info.get('modelName', '情報なし')}")
    print(f"  デバイスID: {device_info.get('deviceId', '情報なし')}")
    print(f"  設置場所: {device_info.get('location', '情報なし')}")

    status_info = device_info.get('status', {})
    if status_info:
        print("  ステータス:")
        for key, value in status_info.items():
            if isinstance(value, bool):
                print(f"    {key.replace('is', '').capitalize() if 'is' in key else key.capitalize()}: {'オン' if value else 'オフ'}")
            else:
                print(f"    {key.capitalize()}: {value} {device_info.get('status', {}).get('unit', '') if key == 'currentTemperature' or key == 'targetTemperature' or key == 'brightness' else ''}")
    else:
        print("  ステータス: 情報なし")

    if "supportedModes" in device_info and device_info["supportedModes"]:
        print(f"  対応モード: {', '.join(device_info['supportedModes'])}")
    if "supportedEvents" in device_info and device_info["supportedEvents"]:
        print(f"  対応イベント: {', '.join(device_info['supportedEvents'])}")
    print("-" * 30)

# get_specific_device_info関数はVol.5から変更なし (ここでは省略、前回のコードを流用してください)
def get_specific_device_info(device_id):
    target_url = f"{BASE_SERVER_URL}/{device_id}"
    print(f"\n--- MCPデバイスID '{device_id}' の情報を取得します ---")
    print(f"アクセスするURL: {target_url}")

    try:
        response = requests.get(target_url, timeout=5)

        if response.status_code == 200:
            device_data = response.json()
            print("--- 取得成功 ---")
            display_device_info(device_data)
        elif response.status_code == 404:
            try:
                error_data = response.json()
                print("--- 取得失敗 (404 Not Found) ---")
                print(f"  エラーメッセージ: {error_data.get('error', 'サーバーからのエラー詳細なし')}")
                print(f"  リクエストしたID: {error_data.get('requested_id', device_id)}")
            except JSONDecodeError:
                print("--- 取得失敗 (404 Not Found) ---")
                print(f"  サーバーからのエラーメッセージはJSON形式ではありませんでした。応答: {response.text[:200]}...")
        else:
            print(f"--- エラー (ステータスコード: {response.status_code}) ---")
            try:
                error_details = response.json()
                print("  サーバーからの詳細:")
                print(json.dumps(error_details, indent=2, ensure_ascii=False))
            except JSONDecodeError:
                print(f"  サーバーからの応答はJSON形式ではありませんでした。内容: {response.text[:200]}...")

    except requests.exceptions.ConnectionError:
        print(f"エラー: サーバー ({target_url}) への接続に失敗しました。サーバーが起動しているか確認してください。")
    except requests.exceptions.Timeout:
        print(f"エラー: サーバー ({target_url}) への接続がタイムアウトしました。")
    except JSONDecodeError:
        print(f"エラー: サーバーからの応答 ({target_url}) が正しいJSON形式ではありません。")
        if 'response' in locals() and response is not None:
             print(f"  応答内容(先頭200文字): {response.text[:200]}...")
    except requests.exceptions.RequestException as e:
        print(f"エラー: リクエスト中に問題が発生しました ({target_url}): {e}")


# get_all_device_infos関数はVol.5から変更なし (ここでは省略、前回のコードを流用してください)
def get_all_device_infos():
    target_url = BASE_SERVER_URL # これは /devices エンドポイント
    print(f"\n--- 全てのMCPデバイス情報を取得します ---")
    print(f"アクセスするURL: {target_url}")
    try:
        response = requests.get(target_url, timeout=5)
        response.raise_for_status() # 200番台以外ならエラーを発生

        data = response.json()
        devices = data.get("devices", [])
        if devices:
            print(f"--- 取得成功 (全 {len(devices)} デバイス) ---")
            for device in devices:
                display_device_info(device)
        else:
            print("デバイス情報が見つかりませんでした。")

    except requests.exceptions.ConnectionError:
        print(f"エラー: サーバー ({target_url}) への接続に失敗しました。")
    except requests.exceptions.Timeout:
        print(f"エラー: サーバー ({target_url}) からの応答がタイムアウトしました。")
    except requests.exceptions.HTTPError as e:
        print(f"エラー: HTTPエラーが発生しました。ステータスコード: {e.response.status_code}")
        try:
            error_details = e.response.json()
            print(f"  サーバーからの詳細: {json.dumps(error_details, indent=2, ensure_ascii=False)}")
        except JSONDecodeError:
            print(f"  サーバーからの応答はJSON形式ではありませんでした。内容: {e.response.text[:200]}...")
    except JSONDecodeError:
        print(f"エラー: サーバーからの応答 ({target_url}) が正しいJSON形式ではありません。")
        if 'response' in locals() and response is not None:
             print(f"  応答内容(先頭200文字): {response.text[:200]}...")
    except requests.exceptions.RequestException as e:
        print(f"エラー: リクエスト中に予期せぬ問題が発生しました ({target_url}): {e}")


def add_new_device(device_data):
    """新しいデバイス情報をサーバーに登録する (POST)"""
    target_url = BASE_SERVER_URL # POST先は /devices
    print(f"\n--- 新しいデバイスを登録します ---")
    print(f"送信先URL: {target_url}")
    print(f"送信データ:")
    print(json.dumps(device_data, indent=2, ensure_ascii=False))

    try:
        response = requests.post(target_url, json=device_data, timeout=5)

        if response.status_code == 201: # 201 Created
            print("--- 登録成功 (201 Created) ---")
            new_device = response.json()
            display_device_info(new_device)
        elif response.status_code == 409: # 409 Conflict (deviceId重複など)
            error_data = response.json()
            print("--- 登録失敗 (409 Conflict) ---")
            print(f"  エラーメッセージ: {error_data.get('error', 'サーバーからのエラー詳細なし')}")
            print(f"  競合したID: {error_data.get('deviceId', '情報なし')}")
        elif response.status_code == 400: # 400 Bad Request (データ不備など)
            error_data = response.json()
            print("--- 登録失敗 (400 Bad Request) ---")
            print(f"  エラーメッセージ: {error_data.get('error', 'サーバーからのエラー詳細なし')}")
        else:
            print(f"--- エラー (ステータスコード: {response.status_code}) ---")
            try:
                error_details = response.json()
                print("  サーバーからの詳細:")
                print(json.dumps(error_details, indent=2, ensure_ascii=False))
            except JSONDecodeError:
                print(f"  サーバーからの応答はJSON形式ではありませんでした。内容: {response.text[:200]}...")
    
    except requests.exceptions.ConnectionError:
        print(f"エラー: サーバー ({target_url}) への接続に失敗しました。")
    except requests.exceptions.Timeout:
        print(f"エラー: サーバー ({target_url}) への接続がタイムアウトしました。")
    except requests.exceptions.RequestException as e:
        print(f"エラー: リクエスト中に問題が発生しました ({target_url}): {e}")


def update_device_info(device_id, updated_data):
    """既存のデバイス情報を更新する (PUT)"""
    target_url = f"{BASE_SERVER_URL}/{device_id}" # PUT先は /devices/{device_id}
    print(f"\n--- デバイスID '{device_id}' の情報を更新します ---")
    print(f"送信先URL: {target_url}")
    print(f"送信データ:")
    print(json.dumps(updated_data, indent=2, ensure_ascii=False))

    try:
        response = requests.put(target_url, json=updated_data, timeout=5)

        if response.status_code == 200: # 200 OK
            print("--- 更新成功 (200 OK) ---")
            device_info = response.json()
            # warningが含まれているかチェック (app.pyのdeviceIdミスマッチ警告用)
            if "warning" in device_info:
                print(f"  サーバーからの警告: {device_info['warning']}")
                display_device_info(device_info.get("updated_device", {}))
            else:
                display_device_info(device_info)
        elif response.status_code == 404: # 404 Not Found
            error_data = response.json()
            print("--- 更新失敗 (404 Not Found) ---")
            print(f"  エラーメッセージ: {error_data.get('error', 'サーバーからのエラー詳細なし')}")
            print(f"  リクエストしたID: {error_data.get('requested_id', device_id)}")
        elif response.status_code == 400: # 400 Bad Request
            error_data = response.json()
            print("--- 更新失敗 (400 Bad Request) ---")
            print(f"  エラーメッセージ: {error_data.get('error', 'サーバーからのエラー詳細なし')}")
        else:
            print(f"--- エラー (ステータスコード: {response.status_code}) ---")
            try:
                error_details = response.json()
                print("  サーバーからの詳細:")
                print(json.dumps(error_details, indent=2, ensure_ascii=False))
            except JSONDecodeError:
                print(f"  サーバーからの応答はJSON形式ではありませんでした。内容: {response.text[:200]}...")

    except requests.exceptions.ConnectionError:
        print(f"エラー: サーバー ({target_url}) への接続に失敗しました。")
    except requests.exceptions.Timeout:
        print(f"エラー: サーバー ({target_url}) への接続がタイムアウトしました。")
    except requests.exceptions.RequestException as e:
        print(f"エラー: リクエスト中に問題が発生しました ({target_url}): {e}")


if __name__ == '__main__':
    print("MCPクライアント (Vol.6) を実行します。")

    # --- 1. まず現在の全デバイス情報を取得 ---
    get_all_device_infos()

    # --- 2. 新しいデバイスを登録してみる ---
    new_smart_speaker = {
        "modelName": "Smart Speaker S500",
        "deviceId": "SPEAKER-004-D", # 新しいユニークなID
        "location": "Kitchen",
        "status": {"volume": 50, "isPlaying": False, "isActive": True},
        "supportedModes": ["play", "pause", "volume_control"]
    }
    add_new_device(new_smart_speaker)

    # --- 3. 登録されたか確認するために、再度全デバイス情報を取得 ---
    get_all_device_infos()

    # --- 4. 既存のデバイス (LIGHT-002-B) の情報を更新してみる ---
    updated_light_info = {
        "modelName": "Smart Light L200 (Upgraded)", # モデル名を少し変更
        "deviceId": "LIGHT-002-B", # IDは同じ
        "location": "Bedroom (Study Corner)", # 場所を変更
        "status": {"brightness": 60, "color": "cool_white", "unit": "percent", "isActive": True}, # ステータスも変更
        "supportedModes": ["on", "off", "dim", "color_change"] # 対応モードも追加
    }
    update_device_info("LIGHT-002-B", updated_light_info)
    
    # --- 5. 更新されたか確認するために、該当デバイスの情報を個別に取得 ---
    get_specific_device_info("LIGHT-002-B")

    # --- 6. 存在しないデバイスIDで更新を試みる ---
    non_existent_device_update = {
        "modelName": "Phantom Device",
        "deviceId": "NON-EXISTENT-001",
        "location": "Nowhere",
        "status": {"isActive": False}
    }
    update_device_info("NON-EXISTENT-001", non_existent_device_update)

    # --- 7. 同じdeviceIdで再度POSTを試みる (エラーになるはず) ---
    add_new_device(new_smart_speaker) # SPEAKER-004-D は既に登録済みのはず
```

**コードのポイント:**

- ```requests.post(target_url, json=device_data, timeout=5):``` 
    - 第一引数に送信先のURL (```target_url```)。
    - ```json=device_data```: これが重要です。Pythonの辞書 (```device_data```) を渡すと、```requests```ライブラリが自動的にJSON形式の文字列に変換し、リクエストのヘッダーに ```Content-Type: application/json``` を設定して送信してくれます。非常に便利ですね！
    - ```timeout=5```: 5秒待ってもサーバーから応答がなければタイムアウトエラーとします。
- ```requests.put(target_url, json=updated_data, timeout=5)```: 
    - ```post``` と同様に、URL、JSONデータ、タイムアウトを指定します。
- ```if __name__ == '__main__'```: ブロックでは、実際に新しいデバイスを登録する処理、登録後の全件取得、既存デバイスの更新処理、更新後の個別取得、そしてエラーケースの試行（存在しないデバイスの更新、重複IDでの登録）を行っています。これにより、POSTとPUTの動作を具体的に確認できます。
 
## 4. 実際に動かして理解を深めよう！ 🚀

さあ、準備は整いました！実際にサーバーとクライアントを動かして、データの登録と更新がどのように行われるかを見てみましょう。

**手順：**

**1\. サーバーの起動:**
```app.py``` (Vol.6 版) があるディレクトリで、ターミナル（またはコマンドプロンプト）を開き、以下のコマンドを実行します。

```
python app.py
```

（macOSやLinuxの方は ```python3 app.py```）

サーバーが起動し、リクエストを待ち受け始めます。

**2\. クライアントの実行:**
**別の**ターミナル（またはコマンドプロンプト）を開き、```client.py``` (Vol.6 版) があるディレクトリで、以下のコマンドを実行します。

```
python client.py
```

（macOSやLinuxの方は ```python3 client.py```）

**実行結果の例 (クライアント側の出力の一部):**

クライアントを実行すると、まず現在のデバイス一覧が表示され、次に新しいデバイス（Smart Speaker S500）が登録され、その結果が表示されます。そして再度デバイス一覧を取得すると、新しいスピーカーが追加されているのが確認できるはずです。

その後、ライトの情報が更新され、その更新後の情報が表示されます。 存在しないデバイスIDで更新しようとした場合や、既に存在するIDで再度登録しようとした場合は、サーバーからエラーメッセージが返ってくることも確認できます。

例えば、新しいデバイス登録成功時の出力は以下のようになるでしょう。

```
--- 新しいデバイスを登録します ---
送信先URL: http://127.0.0.1:5000/devices
送信データ:
{
  "modelName": "Smart Speaker S500",
  "deviceId": "SPEAKER-004-D",
  "location": "Kitchen",
  "status": {
    "volume": 50,
    "isPlaying": false,
    "isActive": true
  },
  "supportedModes": [
    "play",
    "pause",
    "volume_control"
  ]
}
--- 登録成功 (201 Created) ---
  モデル名: Smart Speaker S500
  デバイスID: SPEAKER-004-D
  設置場所: Kitchen
  ステータス:
    Volume: 50 
    Playing: オフ
    Active: オン
  対応モード: play, pause, volume_control
------------------------------
```

そして、既存のライト情報更新成功時はこのようになります。

```
--- デバイスID 'LIGHT-002-B' の情報を更新します ---
送信先URL: http://127.0.0.1:5000/devices/LIGHT-002-B
送信データ:
{
  "modelName": "Smart Light L200 (Upgraded)",
  "deviceId": "LIGHT-002-B",
  "location": "Bedroom (Study Corner)",
  "status": {
    "brightness": 60,
    "color": "cool_white",
    "unit": "percent",
    "isActive": true
  },
  "supportedModes": [
    "on",
    "off",
    "dim",
    "color_change"
  ]
}
--- 更新成功 (200 OK) ---
  モデル名: Smart Light L200 (Upgraded)
  デバイスID: LIGHT-002-B
  設置場所: Bedroom (Study Corner)
  ステータス:
    Brightness: 60 percent
    Color: cool_white 
    Unit: percent 
    Active: オン
  対応モード: on, off, dim, color_change
------------------------------
```

**サーバー側のログの確認:**

サーバーを実行しているターミナルには、クライアントからのアクセスログが表示されます。 POSTリクエストやPUTリクエストが来ていること、そしてサーバーがどのようなステータスコード (201, 200, 404, 409など) を返したかが確認できます。

```
127.0.0.1 - - [日/月/年 時:分:秒] "GET /devices HTTP/1.1" 200 -
127.0.0.1 - - [日/月/年 時:分:秒] "POST /devices HTTP/1.1" 201 -
127.0.0.1 - - [日/月/年 時:分:秒] "GET /devices HTTP/1.1" 200 -
127.0.0.1 - - [日/月/年 時:分:秒] "PUT /devices/LIGHT-002-B HTTP/1.1" 200 -
127.0.0.1 - - [日/月/年 時:分:秒] "GET /devices/LIGHT-002-B HTTP/1.1" 200 -
127.0.0.1 - - [日/月/年 時:分:秒] "PUT /devices/NON-EXISTENT-001 HTTP/1.1" 404 -
127.0.0.1 - - [日/月/年 時:分:秒] "POST /devices HTTP/1.1" 409 -
```

（実際の日付と時刻は実行タイミングによって変わります）

**試してみよう！**

- ```client.py``` の ```if __name__ == '__main__':``` ブロック内で、登録するデバイス情報や更新する情報を自由に変えてみましょう。
- 新しいデバイスをいくつか追加してみてください。
- PUTリクエストで、送信するデータからいくつかのキー（例えば ```location```）を削除したらどうなるか、サーバー側の処理（今回は全置き換え）と合わせて考えてみましょう。

実際に手を動かして試すことで、POSTとPUTの動き、そしてクライアントとサーバー間のデータの流れがより深く理解できるはずです。
 
## 5. まとめと次回予告

今回は、MCPモデル情報（デバイス情報）をサーバーに **登録 (POST)** したり、**更新 (PUT)** したりする方法を学びました。

**今回の学びのポイント:**
- **HTTPメソッド:** GET (取得) だけでなく、POST (新規作成) や PUT (置換/更新) といったメソッドがあることを理解しました。
- **POST:** 新しいリソースをサーバーに作成する際に使用します。図書館に新しい本を寄贈するイメージです。
- **PUT:** 既存のリソースを丸ごと置き換える際に使用します。情報カードを新しいものに差し替えるイメージです。
- **Flaskでのデータ受信:** サーバー側(```app.py```)で ```request.json``` を使って、クライアントから送られてきたJSONデータを取得する方法を学びました。また、```methods=['POST', 'PUT']``` のようにして、ルートが対応するHTTPメソッドを指定しました。
- **requestsライブラリでのデータ送信:** クライアント側(```client.py```)で ```requests.post(url, json=data```) や ```requests.put(url, json=data```) を使って、簡単にJSONデータをサーバーに送信できることを体験しました。
- ステータスコード: 成功時 (200 OK, 201 Created) やエラー時 (400 Bad Request, 404 Not Found, 409 Conflict) に、サーバーが適切なHTTPステータスコードを返すことの重要性の一端に触れました。

これで、MCPの基本的なCRUD (Create, Read, Update, Delete の頭文字。今回はCreate, Read, Update) 操作のうち、情報を「作る」「読む」「更新する」ことができるようになりましたね！（Delete はまた別の機会に）

さて次回、**Vol.7** のテーマは **「転ばぬ先の杖！MCP通信のエラー処理 (try...except) を学ぼう」** です。

これまでのコードでも少しエラー処理に触れてきましたが、ネットワーク通信には予期せぬエラーがつきものです。サーバーが応答しない、送ったデータ形式が違う、などなど。次回は、そのようなエラーが発生したときにプログラムがクラッシュせず、うまく対応するための「エラーハンドリング」というテクニックについて、クライアント側とサーバー側の両面から詳しく見ていきます。

どうぞお楽しみに！