Все рассекают MCP, как будто это камень Розетты ИИ. Диаграммы! Белые бумаги! Лидеры мыслей, делающие TikToks! Но где, мои дорогие друиды данных, есть реальные пакеты? nobody's showing the actual packets. Это как попытка научиться хирургическому вмешательству из мотивационного плаката. Конечно, теоретическая рамка *вдохновляет*, но я хочу увидеть кровь и кишечник! Где сырой JSON? Это как попытка научиться хирургическому вмешательству из мотивационного плаката. Конечно, теоретическая рамка *вдохновляет*, но я хочу увидеть кровь и кишечник! Где сырой JSON? Попытка выучить протокол без проволоки похожа на попытку выучить ограждение из PowerPoint. Give me the electrons or give me death. Теперь, когда у нас есть мантра, давайте копаем! Это ваша бизнес-модель? Ваш бизнес-план MCP: Учитесь MCP ??? (...чтобы чудо произошло...) прибыль Звучит знакомо? Конечно, это так! Это эквивалент стартапа превращения свинца в золото. Алхимия, но с гораздо большим количеством JSON. К счастью... # Ты не один! Казалось бы, что над этим так называемым Протоколом Контекста Модели были пролиты нити чернила. Все говорят о развивающихся LLM, вызове инструментов, вызове функций, двунаправленном этом и возможности, что. я, конечно, не исключение. но одна вещь продолжала клонировать у моего ствола мозга, когда я гулял по этому радостному тростнику теории и абстракции: "What's this look like ON THE WIRE???" Конечно, весь протокол задокументирован на Но я не хочу философии, я хочу Я хочу чувствовать себя, как доктор Франкенштейн, склонный к моему голему, молнии и всему. Образец протокола.io see Горе мне, простому пребывающему среди просвещенных, пока я не разорвал себя таким образом! Первоначальные наблюдения с поля MCP очень, очень новый. Кровянистый край. Возможно, все еще кровоточит. Может быть, однажды, целые книги будут написаны о его внутренней политике, как какой-то цифровой конституционной конвенции. Но на данный момент одна вещь выделяется прежде всего: Инструментальный звонок - это все, что вам нужно Если мы увеличиваем путь, путь в этот один, критический случай использования — Мы можем игнорировать a Мы можем упростить, пока не посмотрим на красивый скелет системы: всего четыре типа сообщений для выхода, и три для ввода. (Мы объясним, почему цифры не совпадают точно немного позже.) tool invocation Лот Инициализировать ИНИЦИАЛИЗАЦИЯ Инструменты / Список Инструменты / Call (и 3 результата, которые возвращаются) Вы можете понять весь протокол, зная об этих семи сообщениях. Пример погоды (от ) Образец протокола.io Образец протокола.io Мы будем использовать — простой пример MCP-сервера, который позволяет запросить прогноз и данные оповещения. Вы можете найти его в их GitHub здесь: [ ¦ ¦ ¦ weather.py https://github.com/modelcontextprotocol/quickstart-resources/blob/main/weather-server-python/weather.py Давайте поговорим о том, что на самом деле происходит «на проводе», когда вы говорите об этом. Первое место: MCP Это означает, что спецификации не заботятся о том, как вы подключаетесь к серверу. HTTPS? Веб-сокеты? Названные трубы? Включение в древний вампирский кабель Ethernet? Транспортная агностика . На практике наиболее распространенным методом является запуск сервера в качестве подпроцесса и коммуникация через сервер. и Это дает вам хороший небольшой частный канал связи. (Технически полный дуплекс, но если вы добавите , мы будем называть его 1.5-duplex, чтобы быть милым). stdin stdout stderr Как передаются сообщения MCP использует JSON-RPC 2.0 через проволоку. Это дает вам протокол запроса/ответа, плюс уведомления. Каждое сообщение отправляется как одно на строку JSON. Это как цифровая телеграмма, где каждая строка представляет собой полный пакет значений. Это в спектре? Нечто вроде; не на самом деле. спектр оставляет его открытым. Но этот новый JSON с разграничением линий является по умолчанию идиомом. Миф о том, что «стдин/стдоут считается вредным» Да, вы можете найти некоторые драматические сообщения в блоге, утверждающие, что этот шаблон «опасный» или «ненадежный». Мы не хотим начинать пожар в лаборатории, не так ли? почему люди скажут это, если это не правда? Вы видите, в Ye Olden Times, дополнительно длинные линии могут вызвать перетоки буфера (здравствуйте, Morris Worm). К счастью, большинство современного программного обеспечения не страдает от этой проблемы (как много! пальцы пересечены!). Современные JSON-апсеры быстры и устойчивы. Хотите отправить предупреждение о погоде 256 МБ? Идите за ним. Просто... может быть, не делайте это каждую секунду. Эти предупреждения обычно приходят от людей, которые вручную проводят описатели файлов в 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. Нет дополнительных пакетов. Батареи включены. ТЭ Модуль заменяет потоки 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." Весь этот протокол - это просто очень формальный способ попросить кого-то сделать что-то и получить подтверждение того, что они это сделали. Все остальное - это просто церемония корпоративного уровня и глянцевое клей вокруг этого красиво простого рукопожатия. Семь вежливых обменников. Вот это и есть. Соединение монстров: рабочий прототип Мы собираемся порождать метеорологический сервер и допрашивать его, как правильный сумасшедший ученый. Совет #1: Лучший способ понять протокол — злоупотреблять им до тех пор, пока он не кричит, а затем исправлять его до тех пор, пока он не будет сотрудничать. Совет #1: Лучший способ понять протокол — злоупотреблять им до тех пор, пока он не кричит, а затем исправлять его до тех пор, пока он не будет сотрудничать. Совет #2: Если вы пытайте свои LLM, я не буду нести ответственность за то, что происходит с вами в The After Times When AI Taketh Over! Совет #2: Если вы пытайте свои LLM, я не буду нести ответственность за то, что происходит с вами в The After Times When AI Taketh Over! Шаг 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 ) ТЭ Модуль заменяет потоки I/O ребенка трубами, давая вам полный контроль над разговором.Это красиво просто: у вас теперь есть специальный детский процесс на вашем вызове, готовый выполнить все, что вы бросите на него. subprocess Вы даже можете отправлять сигналы, чтобы действительно показать, кто босс. В нормальных условиях, когда родительский процесс выходит, ребенок спускается с корабля, так что вам не нужно беспокоиться о зомби-процессах, запутывающих вашу систему (хорошо, в основном). Вы заметите, что мы используем для того, чтобы вызвать процесс.Это не требуется, но быстро становится золотым стандартом для современных инструментов 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" } } } Это наша Он сообщает серверу три важнейшие вещи: мы говорим о MCP, какую версию мы используем, и какие возможности мы приносим на стол. Открытие Salvo Вы заметите, что это следует формату 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 Мы игнорируем возможности для этого демо, но проверьте это: мы Подписывайтесь на события "listChanged", если наш сервер был типом, чтобы добавлять или удалять инструменты динамически.На самом деле в протоколе MCP скрывается крошечная маленькая система pub/sub — вы можете слушать все виды событий. Наш weather.py сервер слишком прост для любой из этих фантастических вещей, но он там, если вам это нужно. могла То же самое относится к «поспешникам» и «ресурсам» — мы полностью их пропускаем. Вся идея заключается в том, что разные системы справляются с разными проблемами, поэтому вам не нужно изобретать каждое колесо.Вы можете выбрать и выбрать, какие части протокола реализовать, но если вы хотите играть хорошо с другими инструментами MCP, вам лучше придерживаться спецификации. API separation Alright, we're connected and ready to rock, right? Wrong. Сервер все еще сидит там, ступая на цифровую ногу, ожидая, что мы закончим рукопожатие. notified_request = { "jsonrpc": "2.0", "method": "notifications/initialized" } Это способ JSON-RPC сказать «огонь и забудь» — никакого ответа, ожидаемого или требуемого. Подумайте об этом, как об отправке пакета ACK: «Эй сервер, я готов закручиваться!» Notice the missing id notification Нет идентификатора означает: «Не отвечай, я буду верить, что ты знаешь, что делать с этой информацией». Это означает: «Не отвечай, я буду верить, что ты знаешь, что делать с этой информацией». Нет id Тем временем... обратно на ранчо... (возвращаясь к истории!) Тем временем... обратно на ранчо... (возвращаясь к истории!) Помните, что сервер, будучи достаточно вежливым, уже ответил с манифестом своих способностей. Так что давайте подтвердим: Да, мы действительно готовы к вечеринке. # 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: «Покажите мне, что вы получили» Время посмотреть, какие игрушки этот сервер принес на площадку: 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! это а Джейсон: Давайте погрузимся в хорошие вещи. Лот Это наша золотая руда - каждый объект представляет собой инструмент, который LLM может применять. (Плотный поворот: мы также можем назвать их, что именно то, что мы делаем прямо сейчас!) See that tools Забавный факт: поля «описание» - это то, как ваш LLM решает, какую функцию вызвать. думайте об этом как о Tinder для инструментов, с вашим ИИ, смотрящим на его телефон, пытаясь решить, прокрутить влево или вправо Забавный факт: поля «описание» - это то, как ваш LLM решает, какую функцию вызвать. думайте об этом как о Tinder для инструментов, с вашим ИИ, смотрящим на его телефон, пытаясь решить, прокрутить влево или вправо OpenAI первоначально пытался назвать это «призывом к функциям» — что... Но где-то по пути, отрасль коллективно решила, что «инструменты» звучат прохладнее (или, может быть, более доступно?), и теперь мы называем весь танец «призывом инструментов». One interesting side note Технически Anatomy of a tool (pay attention, this is where the magic lives): Название: Фактическое название функции — здесь нет разрешенных типов! Это окончательное, каноническое название инструмента; мы будем использовать его для ссылки на этот инструмент, когда мы его назовем. Описание: Простой английский для LLM для чтения (это буквально то, на что смотрит ИИ при решении, использовать ли ваш инструмент) inputSchema: JSON Schema определяет, какие параметры вам нужны outputSchema: Conspicuously missing! Everything just returns a "big string" и надеется на лучшее Имя Описание Вступительная схема Выпускная схема Потому что мы все в основном возвращаем 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" } } } Я выбрал Техас, потому что там все больше, включая погодные катастрофы. Подождите, держитесь — почему это «инструменты/призыв», а не «инструмент/призыв»? Один Хорошо, конечно, «инструмент / звонок» звучит более естественно на английском языке, но, по-видимому, согласованность с другими конечными точками выигрывает. Теперь, когда все наши утки данных в очереди, мы можем нажать большую красную кнопку. # 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! Реальные данные о погоде материализуются. Предупреждения, наводнения, торнадо, работы. Все упаковано в структурированный JSON, точно так же, как ваш терапевт заказал: (отрезано, никто не хочет видеть 11 страниц JSON отходов) // 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! Full Monty: Ваш полный клиент 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() Большое Откровение You just built an MCP client using nothing but Python's standard library. Нет фреймворков. Нет внешних зависимостей. Нет магии. Просто субпроцессные трубы и JSON — те же инструменты, которые у вас были с Python 2.7. «Готовы ли вы к производству?» Честно говоря? Не совсем — но это удивительно близко. Независимо от того, разговаривает ли Клод с вашей базой данных, GPT-4 называет ваши API, или какая-то стартап «революционная платформа рабочего процесса ИИ» — под всем этим, это только это: Spawn process. Send JSON. Read JSON. Repeat. Это как обнаружить волшебника из Оза - это просто парень с действительно хорошей звуковой системой. Это как обнаружить волшебника из Оза - это просто парень с действительно хорошей звуковой системой. Следующий шаг к безумию Теперь, когда вы увидели живот зверя (в коде), вы можете: Создайте персонализированные серверы MCP, которые выполняют ваши предложения (не ждите, пока кто-то другой напишет интеграцию) Дебютирование MCP-соединений как сетевой некроманс, когда они неизбежно ломаются в худший возможный момент Проектируйте лучшие инструменты, зная точно, как LLM потребляют ваши метаданные Напишите лучшие инструменты, так неотразимо описанные, что ваши 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