誰もが、AIのロゼッタ・ストーンのようにMCPを解析している。Diagrams!Whitepapers!Thought leaders making TikToks! しかし、私の親愛なるデータドラウイドは、実際のパッケージはどこにありますか? ここに私を駆り立てているものがあります: nobody's showing the actual packets. それは、動機的なポスターから手術を学ぶようにしています。確かに、理論的な枠組みは *インスピレーションです*,しかし私は血液と脳を見たい! 原色のJSONはどこにありますか? stdin / stdoutのゴミはどこにありますか? 雷が見えなくても、私はどのように私の創造を動かすべきですか? それは、動機的なポスターから手術を学ぶようにしています。確かに、理論的な枠組みは *インスピレーションです*,しかし私は血液と脳を見たい! 原色のJSONはどこにありますか? stdin / stdoutのゴミはどこにありますか? 雷が見えなくても、私はどのように私の創造を動かすべきですか? ワイヤレスダンプなしでプロトコルを学習しようとすることは、PowerPointから壁を学習しようとしているのと同じです。 Give me the electrons or give me death. 今、私たちのマントラを持っているので、掘り下げましょう! これがあなたのビジネスモデルですか? MCPビジネスプラン: MCPを学ぶ ~~~~~~~~~~~~~~~~~~ 利益 知り合いの音? もちろんそう! 鉛を金に変えるスタートアップに相当します。 Alchemy、しかし、より多くのJSONを含む。 幸い... #あなたは一人じゃない! それは、このいわゆるモデルコンテキストプロトコルにインクのリームが流されたように見えます。誰もがLLMの進化、ツール呼び出し、機能呼び出し、両方向のこれとその能力について話しています。 "What's this look like ON THE WIRE???" もちろん、プロトコル全体がドキュメンタリー化されている。 I don't want philosophy. 私は哲学を望んでいない。 I want to feel like Dr. Frankenstein tending to my golem, lightning and all. もう一つの暗いBNF-flavoredシンタクス文法のセマンティックなヘイスタックを通過しないでください。 プロトコル プロトコル.io see わたしは、啓示された者たちの間でただの居住者であって、わたしがそのように自分自身を開くまで、わたしは悲しむであろう。 フィールドからの初期観察 MCPは非常に、非常に新しいものであり、まだ出血している可能性がありますが、ある日、内部政治について、ある種のデジタル憲法条約のような本が書かれるかもしれません。 しかし、いまのところ、ひとつは何よりも目立つ: ツールコールはあなたが必要とするすべてです If we zoom way, way in on this single, critical usage case — この単一の、重要な使用事例について、 無視できるA システムの美しい骨格を見つめるまで、単純化することができます:出力のためのメッセージのタイプは4つ、入力のためのメッセージのタイプは3つです(数字が正確に一致しない理由を後で説明します)。 tool invocation ロット 初期化 初期化 ツール/リスト ツール / Call (そして3つの結果が戻ってくる) あなたはこの7つのメッセージについて知ることによって、プロトコル全体を理解することができます。 天気予報 (from ) プロトコル プロトコル.io プロトコル プロトコル.io われわれは使う — a simple MCP server example that lets you query forecast and alert data. You can find it in their GitHub here. あなたは予測と警告データをクエリすることができます。 」 weather.py https://github.com/modelcontextprotocol/quickstart-resources/blob/main/weather-server-python/weather.py あなたがこのことについて話すとき、実際に何が「ワイヤー上で」起こっているのかについて話しましょう。 まずは、MCPです。 HTTPS? Web Sockets? Named pipes? Tapping into an ancient Ethernet vampire cable? それは関係ありません。 アグネスティックな交通 実践では、最も一般的な方法は、サーバをサブプロセスとして起動し、その上で通信することです。 そして これはあなたに素敵な小さなプライベートなコミュニケーションチャンネルを与えます(技術的にはフルダプレックスですが、 「1.5duplex」と呼ぶと可愛い。 stdin stdout stderr メッセージがどのように流れるか MCP は JSON-RPC 2.0 をワイヤー上に使用します. This gives you a request/response protocol, plus notifications. それぞれのメッセージはJSONのラインごとに1つとして送信されます. それは、それぞれのラインが意味の完全なパッケージであるデジタル電報サービスのようなものです。 このスペックは? ある種の; 実際にはありません. スペックはそれを開いてしまいます. しかし、このニュージーラインに限定されたJSONは、デフォルトのイディオムです。 「Stdin / Stdout Considered Harmful」の神話 はい、あなたはこのパターンが「危険」または「信頼できない」と主張するいくつかの劇的なブログ投稿を見つけることができます。 実験室で火災を起こしたくないの? なぜ人々は、それが真実でないの? Ye Olden Timesでは、余分に長いラインがバッファの過流を引き起こす可能性があります(こんにちは、Morris Worm)。幸運なことに、ほとんどの近代的なソフトウェアはこの問題に苦しんでいません(それほど多く! 指が交差しました!)現代のJSONパッサーは高速で抵抗力があります。 256MBの天候警報を送りたいですか? それのために行ってください。 これらの警告は通常、Cでファイル記述器を手動でケーブル化する人々から来ています。 モジュールは、まさにこのタイプのタスクのために作られたものです。 subprocess # fork off an MCP subprocess import subprocess as sp # Redirect stderr to /dev/null stderr = sp.DEVNULL # Start the weather.py process process = sp.Popen( ["uv", "run", "weather.py"], stdin=sp.PIPE, stdout=sp.PIPE, stderr=stderr ) すべてネイティブPython. 追加パッケージなし. バッテリーは含まれています。 THE モジュールは子どものI/Oストリームをパイプに置き換え、あなたに完全な支配力を与えます。 subprocess 今、私たちはようやくプロトコル科学としての準備ができています。 proper digital necromancer! Plot Twist: It's Embarrassingly Simple シンプル MCPは、そのすべてのエンタープライズ・ドレス・アンド・セプター・レガリアにおいて、次のように述べている。 YOU: "Hello, I speak robot." SERVER: "Delightful. I also speak robot." YOU: "Excellent, we both can speak robot!" YOU: "What tricks can you do?" SERVER: "I can juggle and summon storms." YOU: "Storm, please!" SERVER: "⛈️ As you wish." このプロトコル全体は、誰かに何かをするように頼み、彼らがそれをしたことを確認するための非常に正式な方法に過ぎません。 残りは単なるエンタープライズグレードの儀式と、この美しくシンプルなハンドシェイクのまわりに輝く粘着剤です。 7 丁寧な交換 それがそれ。 Stitching the Monster Together: A Working Prototype シッチング・ザ・モンスター 私たちは天候サーバを生み出し、正しい狂気の科学者のようにそれを尋問するつもりです。 ヒント1:プロトコルを理解する最善の方法は、それを叫ぶまで悪用し、協力するまでパッチすることです。 ヒント1:プロトコルを理解する最善の方法は、それを叫ぶまで悪用し、協力するまでパッチすることです。 ヒント2:あなたがあなたのLLMを拷問する場合、私はAIが乗り越えるときのThe After Timesであなたに何が起こるかについて責任を負うことはありません! ヒント2:あなたがあなたのLLMを拷問する場合、私はAIが乗り越えるときのThe After Timesであなたに何が起こるかについて責任を負うことはありません! ステップ1:デジタルミニオンを呼び出す # Behold! We create life! # (fork off an MCP subprocess) import subprocess # Start the weather.py process # Use `uv run` to execute the script in the uv environment # Redirect stdin, stdout to PIPE for communication # Redirect stderr to DEVNULL to suppress error messages process = subprocess.Popen( ["uv", "run", "weather.py"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=stderr) # Behold! We create life! process = subprocess.Popen( ["uv", "run", "weather.py"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL # Silence the screams ) THE モジュールは、子どものI/Oストリームをパイプに置き換え、会話の完全なコントロールを提供します。それは美しく単純です:あなたは今、あなたのベックと呼び出しに専用の子どものプロセスを持っています。 subprocess 攻撃的になる必要がある? あなたは実際に誰がボスであるかを示すために信号を送ることができます。 通常の状況下では、親のプロセスが終了すると、子供は船とともに落ちるので、ゾンビのプロセスがあなたのシステムを混乱させていることを心配する必要はありません(よく、ほとんどの場合)。 : You will notice we are using あなたは私たちが使用していることに気づくでしょう このプロセスは、必要ではありませんが、 現代のPythonツールのゴールデンスタンダードになりつつあります. まだチェックしていない場合は、絶対にすべきです。 Quick aside uv uv あなたがまだPIPを使っているなら、私たちは話し合わなければなりません。 あなたがまだPIPを使っているなら、私たちは話し合わなければなりません。 ステップ2:不愉快な最初のデート すべての良い関係は相互の識別から始まります 私たちは、自分自身を発表することから始めます(紳士のように): # Define the initialize request init_request = { "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-03-26", "capabilities": {}, "clientInfo": { "name": "MyMCPClient", "version": "1.0.0" } } } こちらは私たちの 通信を確立するために送信する最初のパッケージは、サーバーに3つの重要なことを伝えます:私たちはMCPを話し、どのバージョンを使用しているか、そしてどのような機能をテーブルに持ち込んでいます。 SAVO オープン これは JSON-RPC 2.0 フォーマットに従い、サーバーとの会話全体を通して一貫していることに気付くでしょう。 Let's fire it off. import json def send_mcp_request(process, request): """Sends a JSON-RPC request to the subprocess's stdin.""" json_request = json.dumps(request) + '\n' # Add newline delimiter process.stdin.write(json_request.encode('utf-8')) process.stdin.flush() # Ensure the data is sent immediately # 1. Send the initialize request print("Sending initialize request...") send_mcp_request(process, init_request) この出力を見るべき: Sending initialize request... 今のところ、サーバーがこれまでのすべてについて何を言っているか聞いてみましょう: def read_mcp_response(process): """Reads a JSON-RPC response from the subprocess's stdout.""" # Assuming the server sends one JSON object per line line = process.stdout.readline().decode('utf-8') if line: print(" . . . len is", len(line)) return json.loads(line) return None print("Sending initialized request...") send_mcp_request(process, notified_request) サーバーは礼儀正しく、自分自身を紹介する: . . . len is 266 Received response after initialization:{'id': 1, 'jsonrpc': '2.0', 'result': {'capabilities': {'experimental': {}, 'prompts': {'listChanged': False}, 'resources': {'listChanged': False, 'subscribe': False}, 'tools': {'listChanged': False}}, 'protocolVersion': '2025-03-26', 'serverInfo': {'name': 'weather', 'version': '1.9.4'}}} 「こんにちは!私は天候サーバーで、会話の真ん中に私の能力を偶然に変えるつもりはないし、絶対にあなたを幽霊にするつもりはない」 Translation プロトコルバージョン情報と基本的な詳細を共有しているが、 セクションは本物の賞です。 So, what's the server actually telling us here? capabilities We are ignoring capabilities for this demo, but check this out: we 「listChanged」イベントにサブスクリプションする場合、当社のサーバーはダイナミックにツールを追加または削除するタイプでした。MCPプロトコルに隠れているスレイクな小さなパブ/サブシステムは、あらゆる種類のイベントに耳を傾けることができます。 できる 「急ぎ」と「資源」についても同じことが言えますが、我々はそれらを完全に省略しています。 すべてのアイデアは、異なるシステムが異なる懸念を処理するため、あなたはすべての車輪を再発明する必要はありません。あなたは、実装するプロトコルのどの部分を選択し、選択することができますが、あなたは他のMCPツールで素晴らしい遊びをしたい場合は、あなたは仕様に固執する方が良いでしょう。 API separation Alright, we're connected and ready to rock, right? Wrong. サーバーはまだそこに座っており、デジタル足を叩き、私たちが手を握るのを完了するのを待っています。 notified_request = { "jsonrpc": "2.0", "method": "notifications/initialized" } フィールド? それがJSON-RPCの「火と忘れ」というやり方で、予想もしくは要求される答えはありません。 ACK パッケージを送信するように考えてください: "Hey server, I'm ready to roll!" Notice the missing id notification No id は「答えないでください、私はあなたがこの情報で何をすべきかを知っていることを信じます」という意味です。 「返事しないでください、この情報に何をすればいいかわかるでしょう」という意味です。 ノー id ところが、その後、再び田舎に戻る(ストーリーに戻る!)。 ところが、その後、再び田舎に戻る(ストーリーに戻る!)。 サーバーは、合理的に礼儀正しく、すでにその能力の宣言で応答したことを覚えておいてください. We want to nod approvingly. では、確認しましょう: ほんとに、パーティーの準備はできています。 # yes we are indeed ready to party # Acknowledge the server so it knows we approve print("// Sending initialized request...") send_mcp_request(process, notified_request) 現在、サーバーはリクエストを期待し始めることを知っています。 ステップ3「Show Me What You Got」 このサーバーが遊び場に持って来たおもちゃを見る時間: tools_list_request = { "jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": { } } # 2. Send the tools/list request print("// Sending tools/list request...") send_mcp_request(process, tools_list_request) 予想していた結果が・・・完璧! // Sending tools/list request... 今、出力を読み返そう...我々が発見した宝物を見る時間: # Read the server's response to the tools/list request tools_list_response = read_mcp_response(process) print("// Received tools list response:", end='') pprint(tools_list_response) そして、私たちのサーバーは誇りを持ってその商品を表示します:予報、警告、その他の気象の悪い状況。 . . . len is 732 // Received tools list response:{'id': 2, 'jsonrpc': '2.0', 'result': {'tools': [{'description': 'Get weather alerts for a US state.\n' '\n' ' Args:\n' ' state: Two-letter US state code ' '(e.g. CA, NY)\n' ' ', 'inputSchema': {'properties': {'state': {'title': 'State', 'type': 'string'}}, 'required': ['state'], 'title': 'get_alertsArguments', 'type': 'object'}, 'name': 'get_alerts'}, {'description': 'Get weather forecast for a location.\n' '\n' ' Args:\n' ' latitude: Latitude of the ' 'location\n' ' longitude: Longitude of the ' 'location\n' ' ', 'inputSchema': {'properties': {'latitude': {'title': 'Latitude', 'type': 'number'}, 'longitude': {'title': 'Longitude', 'type': 'number'}}, 'required': ['latitude', 'longitude'], 'title': 'get_forecastArguments', 'type': 'object'}, 'name': 'get_forecast'}]}} WHOA! それは JSON! いいね! いいね! いいね! いいね! ロット それは私たちの金鉱であり、各オブジェクトはLLMが呼び出すことができるツールを表しています。 See that tools 面白い事実:「説明」フィールドは、あなたのLLMがどんな機能を呼ぶかを決定する方法です。ツールのためのTinderのように考えてください。 面白い事実:「説明」フィールドは、あなたのLLMがどんな機能を呼ぶかを決定する方法です。ツールのためのTinderのように考えてください。 オープンAIはもともとこれを「機能呼び出し」としてブランド化しようと試みたが、これは... しかし、途中どこかで、業界は集団的に「ツール」がよりクール(あるいは、もしかしたらよりアクセス可能なもの)に聞こえるようになったと決め、今ではダンス全体を「ツール呼び出し」と呼んでいます。 One interesting side note 技術 Anatomy of a tool (pay attention, this is where the magic lives): 名称: 関数の実際の名称 — 文字はここで許可されていません! これは、ツールの最終的なカノニック名です。 説明:読むためのLLMのための単純な英語(これは文字通りあなたのツールを使用するかどうかを決定するときにAIが見ているものです) inputSchema: JSON Schema defining what parameters you need. 必要なパラメータを定義するJSON Schema outputSchema: Conspicuously missing! Everything just returns a "big string" and hopes for the best. すべてが「大きな弦」を返し、最善を希望します。 名称 描写 入力プログラム 出力計画 なぜ出力スケジュールがないのか? なぜなら、我々は基本的にJSONを文字列に包装し、それがデザインの決定であるふりをするからです。 従来の関数は、任意の種類を返すことができますが、Unixコマンドラインツール(名前が意味する)はテキストを吐き出すだけです。 いくつかのモデルにはJSON出力を強制するスイッチもありますので、理論的には、あなたのLLMは毎回構造化されたデータを期待できます。また、ツールは単純なテキスト、CSV、HTML、または本当に何でも返すことができます。 大丈夫、私たちは私たちのツールボックスを充電しました! 私たちの最初のMCPツール呼び出しをしましょう! ステップ4:真実の瞬間 いずれにせよ、私たちはツールのリストを持っています、今何ですか? 一つ呼びましょう! ツールを選択しますのでお選びください。 もし私たちがGPSデータを持っていたら、私たちは緯度と長さを必要としなかったので、 より良い選択になるでしょう。 get_alerts get_forecast tools_call_request = { "jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": { "name": "get_alerts", "arguments": { "state": "TX" } } } テキサスを選んだのは、天候災害を含むすべてがそこに大きいからだ。 待って、待って―なぜ「ツール/呼び出し」ではなく「ツール/呼び出し」なのでしょうか? つまり、我々は一つのツールを呼んでいるのですよね? 1位 確かに、「tool/call」は英語でより自然に聞こえるが、他のエンドポイントとの一貫性が勝利するようだ。 すべてのデータデッキが一行に並ぶ今、私たちはビッグレッドボタンを押すことができます。 # 3. Send the tools/call request print("// Sending tools/call request...") send_mcp_request(process, tools_call_request) # Read the server's response to the tools/call request tools_call_response = read_mcp_response(process) print("// Received tools call response:", end='') pprint(tools_call_response) [BEEP BEEP BOOP BOOP] (これが赤いボタンの音です) ] [Drumroll please サーバーの思考...処理...そして... ] [And the crowd goes wild! Voilà! リアルな天気データが現実化します. 警告、洪水、竜巻、作業. すべては、あなたのセラピストが命じたように、構造化されたJSONに包装されています: (削除され、誰もJSONのゴミの11ページを見たくない) // Sending tools/call request... . . . len is 51305 // Received tools call response:{'id': 3, 'jsonrpc': '2.0', 'result': {'content': [{'text': '\n' 'Event: Flood Advisory\n' 'Area: Hidalgo, TX; Willacy, TX\n' 'Severity: Minor\n' 'Description: * WHAT...Flooding caused by ' 'excessive rainfall continues.\n' '\n' '* WHERE...A portion of Deep South Texas, ' 'including the following\n' 'counties, Hidalgo and Willacy.\n' '\n' '* WHEN...Until 245 PM CDT.\n' '\n' '* IMPACTS...Minor flooding in low-lying and ' 'poor drainage areas.\n' '\n' '* ADDITIONAL DETAILS...\n' '- At 205 PM CDT, Doppler radar indicated ' 'heavy rain due to\n' 'thunderstorms. Minor flooding is ongoing or ' 'expected to begin\n' 'shortly in the advisory area. Between 2 and ' '5 inches of rain\n' 'have fallen.\n' '- Additional rainfall amounts up to 1 inch ' 'are expected over\n' 'the area. This additional rain will result ' 'in minor flooding.\n' '- Some locations that will experience ' 'flooding include...\n' 'Harlingen, Elsa, Edcouch, La Villa, Lasara, ' 'La Villa High\n' 'School, Monte Alto, Jose Borrego Middle ' 'School, Satiago\n' 'Garcia Elementary School, Edcouch Police ' 'Department, Edcouch\n' 'City Hall, Edcouch Volunteer Fire ' 'Department, Edcouch-Elsa\n' 'High School, Laguna Seca, Carlos Truan ' 'Junior High School,\n' 'Elsa Police Department, Lyndon B Johnson ' 'Elementary School,\n' 'Elsa Public Library, Olivarez and Lasara ' 'Elementary School.\n' '- http://www.weather.gov/safety/flood\n' "Instructions: Turn around, don't drown when " 'encountering flooded roads. Most flood\n' '\n' 'The next statement will be issued Tuesday ' 'morning at 830 AM CDT.\n', 'type': 'text'}], 'isError': False}} 注意 その あなたが午前3時に謎の失敗をデバッグするのを楽しまない限り、常にこのフィールドをチェックしてください。 Victory! isError: false 見た目は良い! 我々はクリーンストレンドデータを戻す(物事を複雑にするためにストリーミングなし)、これは私たちに選択肢を与えます。我々はこの天気データを解析し、LLMのためにそれをマッサージするか、または単に原始的な応答を渡し、モデルにそれを解析させます。 しかし、高度なものを構築している場合、ツール出力の前処理は非常に強力です. あなたはそれをフォーマットし、それをフィルタリングし、他のデータソースと組み合わせ、またはあなたのアプリケーションが必要とするものに変換することができます。 私たちは、MCPサーバを登録し、接続を初期化し、ツールを呼び出し、結果を処理しました。 And that's a wrap! The Full Monty: Your Complete MCP クライアント 以下は、一つの召喚のサークルで栄光の儀式全体です。 import subprocess import json from pprint import pprint def send_mcp_request(process, request): json_request = json.dumps(request) + '\n' process.stdin.write(json_request.encode('utf-8')) process.stdin.flush() def read_mcp_response(process): line = process.stdout.readline().decode('utf-8') return json.loads(line) if line else None requests = { 'init': { "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-03-26", "capabilities": {}, "clientInfo": {"name": "MyMCPClient", "version": "1.0.0"} } }, 'initialized': { "jsonrpc": "2.0", "method": "notifications/initialized" }, 'list_tools': { "jsonrpc": "2.0", "id": 2, "method": "tools/list" }, 'call_tool': { "jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "get_alerts", "arguments": {"state": "TX"}} } } process = subprocess.Popen( ["uv", "run", "weather.py"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL ) try: send_mcp_request(process, requests['init']) pprint(read_mcp_response(process)) send_mcp_request(process, requests['initialized']) send_mcp_request(process, requests['list_tools']) tools = read_mcp_response(process) print("Available tools:", [t['name'] for t in tools['result']['tools']]) send_mcp_request(process, requests['call_tool']) result = read_mcp_response(process) print("Weather alert received:", len(result['result']['content'][0]['text']), "characters") finally: process.terminate() THE BIG REVELATION You just built an MCP client using nothing but Python's standard library. No frameworks. No external dependencies. No magic. Just subprocess pipes and JSON—the same tools you have had since Python 2.7. Is this “production-ready”? 正直に言えば? まったくないが、驚くほど近い。 あなたのデータベースと話すクロードが、GPT-4があなたのAPIを呼び出すか、あるスタートアップの「革命的なAIワークフロープラットフォーム」であろうと、すべての下に、それはこれだけです。 Spawn process. Send JSON. Read JSON. Repeat. それはオズの魔法使いが本当に良いサウンドシステムを持つ男にすぎないことを発見するようなものです。 それはオズの魔法使いが本当に良いサウンドシステムを持つ男にすぎないことを発見するようなものです。 あなたの次のステップは狂気へ あなたが獣の腹部(コードで)を見た今、あなたは: あなたのオファーを行うカスタムMCPサーバーを構築する(もう誰かが統合を書くのを待つことはありません) MCP 接続をネットワーク ネクロマンサーのようにデバッグし、最悪のタイミングで必然的に壊れる LLMsがあなたのメタデータをどのように消費するかを正確に知ることによってより良いツールを設計します。 あなたのLLMが恋に落ちるように、それほど抵抗不能に説明されたより良いツールを書く あなたがプロトコルを理解しているため、すべてのことから地獄を最適化 猫の食器を自動化するか、私は判断しない。 美しい啓示 The miracle in step 2 of your business plan? It was you, all along. あなたのビジネスプランのステップ2の奇跡?それはあなたでした。 変なものを作ろう。 このコードをアクションで見たいですか? 完全な例はここにあります: [https://gitlab.com/-/snippets/4864350] https://gitlab.com/-/snippets/4864350 https://gitlab.com/-/snippets/4864350?embedable=true