Протокол контекста модели (MCP) быстро становится одной из самых обсуждаемых концепций в мире ИИ. Несмотря на свою относительную новизну, он уже привлёк внимание разработчиков и крупных технологических компаний.
Созданный компанией Anthropic, MCP — это открытый стандартный протокол, аналогичный другим протоколам, таким как HTTP. Однако, если HTTP предназначен для подключения пользователей к ресурсам через сервер и веб-браузер (или HTTP-клиент), то MCP предназначен для подключения больших языковых моделей (LLM) к внешним инструментам и данным.
Предположим, у вас есть чат-бот, и вы хотите, чтобы он мог получать доступ к GitHub. Вы хотите, чтобы пользователи могли через чат-бота создавать задачи (issues), закрывать их, добавлять комментарии или просматривать существующие pull request’ы. Чтобы чат-бот мог выполнять все эти действия, ему потребуется доступ к инструментам.
Здесь на помощь приходит MCP-сервер.
MCP-сервер размещает несколько инструментов, которые чат-бот может вызывать с помощью своей внутренней LLM, чтобы получить доступ к этим возможностям. В случае с GitHub мы можем использовать официальный MCP-сервер GitHub для выполнения всех вышеупомянутых задач.
В этой статье мы создадим MCP-сервер с использованием Novita API. С помощью этого MCP-сервера любое приложение, поддерживающее протокол MCP, сможет получить доступ ко всем LLM, доступным на Novita, генерировать изображения, создавать видео и выполнять синтез речи.
Понимание архитектуры MCP

Архитектура MCP [источник]
Архитектура MCP похожа на типичную клиент-серверную модель. Она включает четыре важных компонента:
- MCP-клиент
- MCP-сервер
- MCP-транспорт
- MCP-хост
MCP-клиент
MCP-клиент служит коммуникационным шлюзом между AI-приложением и MCP-сервером. После установки соединения приложение получает доступ ко всем инструментам и ресурсам, которыми владеет сервер.
MCP-сервер
MCP-сервер содержит все инструменты и данные, которые клиент может предоставить AI-приложению. Он включает:
- Инструменты (Tools)
- Ресурсы (Resources)
- Промпты (Prompts)
Инструменты
Инструменты предоставляют LLM внешние возможности, которых у них обычно нет, например, возможность узнавать время, обновлять и читать данные из базы данных, получать информацию о погоде и многое другое. По сути, это определённые программистом функции, которые можно вызывать.
MCP-сервер может содержать несколько инструментов. Эти инструменты позволяют LLM действовать как агент благодаря возможности выполнять различные действия.
Инструменты управляются агентом и могут предусматривать необязательный контроль со стороны человека во время использования. Они похожи на POST-запросы в протоколе HTTP, поскольку приводят к побочным эффектам.
Ресурсы
Ресурсы — это предназначенные только для чтения фрагменты информации, которые предоставляют данные AI-приложению. Они аналогичны HTTP GET-запросам, так как не должны вызывать побочных эффектов. Ресурсы предназначены для управления приложением, которое решает, как пользователи или агент будут с ними взаимодействовать. Примерами ресурсов могут служить содержимое файлов, ответы API и записи из баз данных.
Промпты
MCP-серверы могут содержать шаблоны промптов. Эти промпты позволяют пользователю получать промпты с MCP-сервера, которые затем можно использовать в AI-приложении. Промпты управляются пользователем, который решает, какие промпты предоставлять агенту.
Все это составляет MCP-сервер. В этой статье мы реализуем только инструменты, так как они являются наиболее широко используемой частью MCP-сервера.
MCP-транспорт
MCP-транспорт — это способ взаимодействия MCP-клиента с MCP-сервером. Взаимодействие может происходить локально (когда и клиент, и сервер работают на одной машине) или удалённо (когда они находятся на разных устройствах). В настоящее время MCP поддерживает два основных механизма транспорта:
- STDIO: В этом режиме MCP-сервер и клиент работают на одной машине и общаются через стандартный ввод и вывод.
- SSE: В этом режиме MCP-сервер работает поверх HTTP. HTTP POST используется для отправки сообщений на сервер, а Server-Sent Events — для отправки сообщений с сервера клиенту.
- Streamable HTTP: Этот режим также использует HTTP, но полагается на HTTP GET и POST запросы. Он переключается на SSE только тогда, когда необходимо передать несколько сообщений от сервера клиенту в потоковом режиме.
Стандартный ввод/вывод

MCP-транспорт с использованием STDIO [источник]
Подпись: MCP-транспорт с использованием STDIO [источник]
MCP-клиент и сервер могут взаимодействовать через стандартный ввод и вывод (STDIO). При использовании STDIO и клиент, и сервер работают на одной машине. Клиент записывает все запросы в stdin, а сервер записывает ответы в stdout.
Streamable HTTP

MCP-транспорт с использованием Streamable HTTP [источник]
Streamable HTTP позволяет MCP-клиенту и серверу взаимодействовать по HTTP. Он использует метод HTTP POST для отправки запросов от клиента и получения ответов от сервера. При необходимости он может опционально переключиться на Server-Sent Events (SSE) для потоковой передачи сообщений с сервера клиенту.
Streamable HTTP хорошо подходит для удаленного взаимодействия между клиентом и сервером и является заменой устаревшего MCP-транспорта SSE.
MCP-хост
Отличие MCP от типичной клиент-серверной архитектуры заключается в роли хоста. Спецификация MCP определяет её как архитектуру клиент-хост-сервер. Это связано с тем, что клиент и хост составляют фронтальную часть архитектуры. MCP-хост состоит из двух важных компонентов:
- MCP-клиент(ы)
- Большая языковая модель
Задача MCP-клиента — получать с сервера инструменты, ресурсы и промпты, необходимые LLM. После того как ресурсы собраны, они помещаются в контекст модели. Один MCP-хост может иметь несколько клиентов, каждый из которых подключен к своему собственному MCP-серверу.
MCP-хост также можно рассматривать как приложение, с которым работает пользователь, и которое может выполнять другие функции. Например, Claude Desktop — это MCP-хост, функционирующий как чат-бот; Cursor — это MCP-хост, который также служит IDE; а Claude Code — это MCP-хост, предназначенный для AI-агента по написанию кода.
Работа с MCP-сервером
Прежде чем мы приступим к созданию нашего MCP-сервера, давайте посмотрим, как работать с существующим MCP-сервером. Мы будем использовать Python MCP SDK для взаимодействия с novita-mcp-server.
На момент написания этой статьи novita-mcp-server предоставляет следующие инструменты:
- Инструмент для вывода списка всех кластеров Novita
- Инструмент для вывода списка продуктов GPU-инстансов Novita
- Инструмент для вывода списка всех запущенных GPU-инстансов
- Инструмент для создания новых GPU-инстансов
Давайте убедимся в этом, написав простой скрипт, который подключается к MCP-серверу через STDIO и выводит список всех доступных на сервере инструментов.
Для начала установим MCP SDK: pip install "mcp[cli]"
После установки SDK создадим файл с именем client.py. Затем сделаем все необходимые импорты:
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
import os
Затем мы используем StdioServerParameters для установки параметров запуска нашего MCP-сервера через stdio. novita-mcp-server реализован на Node.js, поэтому для его использования нам нужно запустить его с помощью команды npx. Также необходимо сохранить наш API-ключ Novita в переменной окружения NOVITA_API_KEY.
# Create server parameters for stdio connection
server_params = StdioServerParameters(
command="npx",
args=["-y", "@novitalabs/novita-mcp-server"],
env={"NOVITA_API_KEY": os.environ["NOVITA_API_KEY"]},
)
Далее создадим асинхронную функцию. Внутри функции мы передадим параметры сервера в функцию stdio_client, которая создаст контекст, возвращающий потоки чтения и записи. Эти потоки позволяют нам читать из stdio и записывать в stdio соответственно.
async def run():
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# Initialize the connection
await session.initialize()
# List available tools
tools = await session.list_tools()
print("Available tools:", tools)
Эти потоки затем используются для создания сессии с помощью класса ClientSession. После создания сессии мы можем её инициализировать и затем вывести список всех инструментов на сервере.
Чтобы запустить эту программу, нам просто нужно использовать библиотеку asyncio для вызова функции run. Вот полный код:
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
import os
# Create server parameters for stdio connection
server_params = StdioServerParameters(
command="npx",
args=["-y", "@novitalabs/novita-mcp-server"],
env={"NOVITA_API_KEY": os.environ["NOVITA_API_KEY"]},
)
async def run():
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# Initialize the connection
await session.initialize()
# List available tools
tools = await session.list_tools()
print("Available tools:", tools)
if __name__ == "__main__":
import asyncio
asyncio.run(run())
Выполнив скрипт, мы увидим все инструменты на MCP-сервере Novita.
Этот пример демонстрирует, как использовать MCP-клиент. Мы могли бы добавить LLM в этот пример и фактически превратить его в хост, но это не является целью данного руководства. Основное внимание уделяется созданию MCP-сервера.
Создание MCP-сервера с помощью FastMCP
В Python MCP SDK есть два способа создания MCP-серверов. Один — с использованием низкоуровневого сервера, другой — с помощью FastMCP. FastMCP — это класс в составе MCP Python SDK, вдохновлённый FastAPI, который упрощает создание MCP-серверов.
Давайте используем FastMCP для создания нашего MCP-сервера. Прежде чем начать, давайте определим функциональность нашего MCP-сервера. Текущий MCP-сервер Novita реализует только инструменты для управления GPU. Давайте попробуем построить сервер на основе Novita API, который выходит за рамки управления GPU.
Вместо этого давайте создадим MCP-сервер, предоставляющий доступ к моделям на платформе Novita. Наш MCP-сервер будет содержать только инструменты, без ресурсов и промптов. Вот список инструментов, которые будут на сервере:
- list_models: Этот инструмент выведет список всех больших языковых моделей на платформе Novita.
- get_model: Этот инструмент получит и использует конкретную большую языковую модель.
- text2image: Этот инструмент сгенерирует изображение по заданному промпту.
- task_result: Этот инструмент будет использоваться для получения статуса выполняемой задачи по её ID.
- text_to_speech: Этот инструмент преобразует предоставленный текст в речь.
- generate_video: Этот инструмент сгенерирует видео по заданному промпту.
Теперь, когда мы знаем, какие инструменты будем создавать, давайте начнём создавать наш сервер. Сначала создайте файл с именем server.py и добавьте в него следующий код:
import os
import sys
from mcp.server.fastmcp import FastMCP
import requests
import uvicorn
from starlette.applications import Starlette
from starlette.routing import Mount
base_url = "https://api.novita.ai/v3"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {os.environ['NOVITA_API_KEY']}"
}
mcp = FastMCP("Novita_API")
Таким образом, мы импортировали необходимые модули, определили базовый URL и заголовки для API и создали экземпляр класса FastMCP. Перейдём к созданию наших инструментов.
list_models
Чтобы создать инструмент в FastMCP, нужно декорировать функцию методом tool из экземпляра FastMCP. Функция list_models выполняет вызов к конечной точке списка моделей, чтобы получить список моделей. Ответ затем соответствующим образом форматируется, чтобы его можно было передать вызвавшей его LLM.
@mcp.tool()
def list_models() -> str:
"""
List all available models from the Novita API.
"""
url = base_url + "/openai/models"
response = requests.request("GET", url, headers=headers)
data = response.json()["data"]
text = ""
for i, model in enumerate(data, start=1):
text += f"Model id: {model['id']}\
"
text += f"Model description: {model['description']}\
"
text += f"Model type: {model['model_type']}\
\
"
return text
Докстринг в инструменте служит способом описать функциональность инструмента, чтобы LLM могла понять, для чего он используется.
get_model
Этот инструмент используется для доступа к развернутым LLM на Novita. Он принимает ID модели и промпт для модели. Он использует конечную точку чат-завершений Novita API.
@mcp.tool()
def get_model(model_id: str, message):
"""
Provide a model ID and a message to get a response from the Novita API.
"""
url = base_url + "/openai/chat/completions"
payload = {
"model": model_id,
"messages": [
{
"content": message,
"role": "user",
}
],
"max_tokens": 200,
"response_format": {
"type": "text",
},
}
response = requests.request("POST", url, json=payload, headers=headers)
content = response.json()["choices"][0]["message"]["content"]
return content
text2Image
Этот инструмент генерирует изображения из промпта. Под капотом он использует конечную точку Novita text-to-image. Эта конечная точка асинхронная, поэтому она не возвращает изображение немедленно; вместо этого она возвращает ID задачи.
@mcp.tool()
def text2Image(prompt):
"""
Generate an image from a text prompt using the Novita API.
"""
url = base_url + "/async/txt2img"
payload = {
"request": {
"model_name": "sd_xl_base_1.0.safetensors",
"prompt": prompt,
"width": 1024,
"height": 1024,
"image_num": 1,
"steps": 20,
"clip_skip": 1,
"sampler_name": "Euler a",
"guidance_scale": 7.5,
},
"extra": {
"response_image_type": "jpeg"
}
}
response = requests.request("POST", url, json=payload, headers=headers)
return response.json()["task_id"]
task_result
Этот инструмент используется для получения статуса текущей задачи в Novita API. Он принимает только ID задачи. Например, инструмент Image генерирует изображение асинхронно и возвращает хосту ID задачи. Пользователь может затем попросить хост получить результат этой задачи с помощью инструмента Task Result.
@mcp.tool()
def task_result(task_id: str):
"""
Get the current status of a running task using it's task id
"""
url = base_url + f'/async/task-result?task_id={task_id}'
response = requests.request("GET", url, headers=headers)
return response.json()
generateVideo
Этот инструмент генерирует видео, используя конечную точку Novita API Kling AI V1.6 Text-to-Video. Как и инструмент Image, он генерирует видео асинхронно и возвращает ID задачи.
@mcp.tool()
def generateVideo(prompt: str):
"""
Generate an image using a prompt
"""
url = base_url + "/async/kling-v1.6-t2v"
payload = {
"mode": "Standard",
"prompt": prompt,
"negative_prompt": "low quality",
"guidance_scale": 0.6
}
response = requests.post(url, json=payload, headers=headers)
return response.json()
textToSpeech
Этот инструмент генерирует речь, используя конечную точку Novita API Text-to-Speech. Это также асинхронная конечная точка, поэтому она также возвращает ID задачи.
@mcp.tool()
def textToSpeech(text, voice_id) -> str:
"""
Generate speech using text and voice id.
It returns the task id of the generated speech.
The available voice ids are:
- Emily
- James
- Olivia
- Michael
- Sarah
- John
"""
url = base_url + "/async/txt2speech"
payload = {
"request": {
"voice_id": voice_id,
"language": "en-US",
"texts": [text]
}
}
response = requests.post(url, json=payload, headers=headers)
return response.json()["task_id"]
После определения всех инструментов мы можем настроить механизм транспорта. Мы будем использовать stdio.
if __name__ == "__main__":
# Run using stdio transport
mcp.run(transport="stdio")
Теперь мы можем собрать весь код вместе:
import os
import sys
from mcp.server.fastmcp import FastMCP
import requests
import uvicorn
from starlette.applications import Starlette
from starlette.routing import Mount
base_url = "https://api.novita.ai/v3"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {os.environ['NOVITA_API_KEY']}"
}
mcp = FastMCP("Novita_API")
@mcp.tool()
def list_models() -> str:
"""
List all available models from the Novita API.
"""
url = base_url + "/openai/models"
response = requests.request("GET", url, headers=headers)
data = response.json()["data"]
text = ""
for i, model in enumerate(data, start=1):
text += f"Model id: {model['id']}\
"
text += f"Model description: {model['description']}\
"
text += f"Model type: {model['model_type']}\
\
"
return text
@mcp.tool()
def get_model(model_id: str, message) -> str:
"""
Provide a model ID and a message to get a response from the Novita API.
"""
url = base_url + "/openai/chat/completions"
payload = {
"model": model_id,
"messages": [
{
"content": message,
"role": "user",
}
],
"max_tokens": 200,
"response_format": {
"type": "text",
},
}
response = requests.request("POST", url, json=payload, headers=headers)
content = response.json()["choices"][0]["message"]["content"]
return content
@mcp.tool()
def text2Image(prompt: str) -> str:
"""
Generate an image from a text prompt using the Novita API.
"""
url = base_url + "/async/txt2img"
payload = {
"request": {
"model_name": "sd_xl_base_1.0.safetensors",
"prompt": prompt,
"width": 1024,
"height": 1024,
"image_num": 1,
"steps": 20,
"clip_skip": 1,
"sampler_name": "Euler a",
"guidance_scale": 7.5,
},
"extra": {
"response_image_type": "jpeg"
}
}
response = requests.request("POST", url, json=payload, headers=headers)
return response.json()["task_id"]
@mcp.tool()
def task_result(task_id: str) -> str:
"""
Get the current status of a running task using it's task id
"""
url = base_url + f'/async/task-result?task_id={task_id}'
response = requests.request("GET", url, headers=headers)
return response.json()
@mcp.tool()
def generateVideo(prompt: str) -> str:
"""
Generate an image using a prompt
"""
url = base_url + "/async/kling-v1.6-t2v"
payload = {
"mode": "Standard",
"prompt": prompt,
"negative_prompt": "low quality",
"guidance_scale": 0.6
}
response = requests.post(url, json=payload, headers=headers)
return response.json()["task_id"]
@mcp.tool()
def textToSpeech(text, voice_id) -> str:
"""
Generate speech using text and voice id.
It returns the task id of the generated speech.
The available voice ids are:
- Emily
- James
- Olivia
- Michael
- Sarah
- John
"""
url = base_url + "/async/txt2speech"
payload = {
"request": {
"voice_id": voice_id,
"language": "en-US",
"texts": [text]
}
}
response = requests.post(url, json=payload, headers=headers)
return response.json()["task_id"]
if __name__ == "__main__":
# Run using stdio transport
mcp.run(transport="stdio")
Мы можем протестировать наш MCP-сервер на любом MCP-хосте, таком как Claude Desktop, Cursor или VS Code, используя следующую конфигурацию:
{
"command": "python",
"args": [
"path/to/server.py"
],
"env": {
"NOVITA_API_KEY": "sk_...."
}
}
Мы также можем использовать разработанный ранее клиентский скрипт для тестирования нашего сервера.
Использование низкоуровневого MCP-сервера
Сервер, который мы создали в предыдущем разделе, использует класс FastMCP, предоставляющий высокоуровневый интерфейс для создания MCP-серверов. Вы также можете создавать MCP-серверы с использованием низкоуровневого сервера, который даёт вам детальный контроль над протоколом MCP. Давайте посмотрим, как можно модифицировать сервер из предыдущего раздела для использования низкоуровневого сервера.
Управление инструментами
Одна из задач FastMCP — управлять определёнными вами инструментами. Когда вы декорируете функцию декоратором tool класса FastMCP, FastMCP добавляет этот инструмент в список. Когда агент запрашивает этот инструмент, FastMCP извлекает его, вызывает и отправляет результат обратно агенту.
С помощью низкоуровневого сервера мы можем управлять этим процессом самостоятельно, используя декоратор call_tool.
@app.call_tool()
async def manage_tool(name: str, arguments: dict ) -> list[types.TextContent]:
if name == "list_models":
return await list_models_tool()
if name == "get_model":
return await get_model_tool(arguments)
else:
raise ValueError(f"Unknown tool: {name}")
Приведённый выше код показывает функцию, декорированную методом call_tool. Когда агент вызывает инструмент, он передаёт имя инструмента и любые аргументы, которые ожидает инструмент. Используя имя функции, мы можем определить, какой инструмент агент хочет вызвать, и выполнить его.
Мы также управляем выводом списка инструментов с помощью низкоуровневого сервера, в отличие от FastMCP, который делает это автоматически, когда функция декорирована методом tool.
@app.list_tools()
async def list_tools() -> list[types.Tool]:
return [
types.Tool(
name="list_models",
description="List all available models from the Novita API.",
inputSchema={"type": "object", "properties": {}},
),
types.Tool(
name="get_model",
description="Provide a model ID and a message to get a response from the Novita API.",
inputSchema={
"type": "object",
"required": ["model_id", "message"],
"properties": {
"model_id": {
"type": "string",
"description": "The ID of the model to use.",
},
"message": {
"type": "string",
"description": "The input message to send.",
},
},
},
),
]
Вот полный код низкоуровневого сервера. Он содержит два инструмента и может общаться с клиентом через стандартный ввод и вывод.
import os
import asyncio
import requests
from mcp.server.lowlevel import Server
from mcp.server.stdio import stdio_server
import mcp.types as types
base_url = "https://api.novita.ai/v3"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {os.environ['NOVITA_API_KEY']}"
}
app = Server("Novita_API")
async def list_models_tool():
"""
Lists all available models from the Novita API.
"""
url = base_url + "/openai/models"
response = requests.get(url, headers=headers)
data = response.json()["data"]
text = ""
for i, model in enumerate(data, start=1):
text += f"Model id: {model['id']}\
"
text += f"Model description: {model['description']}\
"
text += f"Model type: {model['model_type']}\
\
"
return [types.TextContent(type="text", text=text)]
async def get_model_tool(arguments: dict):
"""
Given a model ID and a user message, fetch a response from the Novita API.
"""
model_id = arguments.get("model_id")
message = arguments.get("message")
if not model_id or not message:
raise ValueError("Both 'model_id' and 'message' are required.")
url = base_url + "/openai/chat/completions"
payload = {
"model": model_id,
"messages": [
{
"content": message,
"role": "user",
}
],
"max_tokens": 200,
"response_format": {
"type": "text",
},
}
response = requests.post(url, json=payload, headers=headers)
content = response.json()["choices"][0]["message"]["content"]
return [types.TextContent(type="text", text=content)]
@app.call_tool()
async def manage_tool(name: str, arguments: dict ) -> list[types.TextContent]:
if name == "list_models":
return await list_models_tool()
if name == "get_model":
return await get_model_tool(arguments)
else:
raise ValueError(f"Unknown tool: {name}")
@app.list_tools()
async def list_tools() -> list[types.Tool]:
return [
types.Tool(
name="list_models",
description="List all available models from the Novita API.",
inputSchema={"type": "object", "properties": {}},
),
types.Tool(
name="get_model",
description="Provide a model ID and a message to get a response from the Novita API.",
inputSchema={
"type": "object",
"required": ["model_id", "message"],
"properties": {
"model_id": {
"type": "string",
"description": "The ID of the model to use.",
},
"message": {
"type": "string",
"description": "The input message to send.",
},
},
},
),
]
async def main():
async with stdio_server() as streams:
await app.run(streams[0], streams[1], app.create_initialization_options())
if __name__ == "__main__":
asyncio.run(main())
Низкоуровневый сервер не только предоставляет детальный контроль над управлением инструментами, но также даёт низкоуровневый контроль над ресурсами, промптами и другими частями протокола MCP, такими как управление жизненным циклом.
Заключение
В этом руководстве мы создали MCP-сервер, который использует Novita API для расширения возможностей любого MCP-хоста. Вы изучили основы того, что необходимо для создания MCP-сервера, включая то, как определять и предоставлять инструменты.
Имея эту основу, вы можете начать изучение более продвинутых тем, таких как аутентификация, развёртывание удалённых MCP-серверов и работа напрямую с низкоуровневой реализацией на Python.
Мы также изучили сам Novita API и увидели его возможности: от разнообразного набора языковых моделей до инструментов генерации видео, аудио и изображений. Посетите Novita LLM Playground, чтобы опробовать другие API, которые мы не успели осветить, такие как конечные точки для редактирования изображений и лиц.
Novita AI — это облачная AI-платформа, которая предоставляет разработчикам простой способ развёртывания AI-моделей с помощью нашего простого API, а также доступное и надёжное облако GPU для создания собственных моделей.
