MCP для ИИ-агентов

Свой MCP-сервер для ИИ-агентов

У SwapZilla уже есть опубликованный MCP-сервер с открытым кодом, и для большинства агентов этого достаточно. Свой поднимают тогда, когда обмены должны атрибутироваться на ваш ключ и ограничиваться вашими правилами, а не рассуждениями агента.

  • Готовыйswapzilla-mcp · MIT · Node 18+
  • Инструментов в нём6 — от активов до статуса ордера
  • Свой добавляетваш PartnerID, лимиты, подтверждения
  • Защита от повторовIdempotency-Key на намерение

Собрано на/v1/assets/v1/partner/quotes/v1/validate-address/v1/partner/orders/v1/partner/orders/{id}

Дать ИИ-агенту SwapZilla можно двумя способами, и быстрый уже опубликован. swapzilla-mcp — открытый (MIT, Node 18+) сервер Model Context Protocol, который запускается одной строкой и не требует ключа; он живёт на /ru/mcp/. Свой сервер нужен тогда, когда обмены должны идти под вашим партнёрским ключом, с вашим белым списком и вашими лимитами.

  • Готовый: шесть инструментов, без аккаунта и без ключа, некастодиальный по конструкции.
  • Свой: ордера помечены вашим PartnerID и вашим client_order_id.
  • В обоих случаях создание ордера не двигает деньги — оно возвращает адрес.
  • Ключ, если он есть, живёт в процессе сервера и никогда — в контексте модели.

Опубликованный сервер, одной строкой

npx -y https://swapzilla.io/mcp/swapzilla-mcp-1.0.0.tgz

Зарегистрируйте его в любом MCP-клиенте — Claude Desktop, Claude Code, Cursor, Windsurf — и у агента появятся шесть инструментов:

ИнструментЧто агент им делает
swapzilla_list_assetsОпределяет точный код актива и сеть — до всего остального.
swapzilla_list_providersСмотрит, какие обменники агрегированы и включены.
swapzilla_get_quoteСравнивает живые предложения всех провайдеров; у каждого есть ID.
swapzilla_validate_addressСначала проверяет адрес получателя для нужной сети.
swapzilla_create_exchangeОткрывает обмен и возвращает DepositAddress.
swapzilla_get_orderОпрашивает статус вплоть до хеша выплаты.

Настраивается двумя переменными окружения: SWAPZILLA_API_BASE (по умолчанию https://api.swapzilla.io) и необязательным SWAPZILLA_API_KEY, который отправляется как X-API-Key. Для публичных данных о курсах не нужен ни один из них. Инструкции по установке, блок конфигурации клиента и сам пакет — на /ru/mcp/.

Когда нужен свой сервер

Опубликованный сервер намеренно простой: это агрегатор, выставленный инструментами, для того, кто запускает агента. Партнёрской интеграции обычно нужно то, чего в нём нет, — и каждый такой пункт — повод обернуть partner API самостоятельно.

Что нужноПочему свой сервер
Чтобы обмены были вашимиВызовы с партнёрским ключом помечаются вашим PartnerID, а GET /v1/partner/orders их перечисляет: атрибуции и сверки с чужой машины не получить.
Белый список адресовАгент, который может назвать любой to_address, может заплатить кому угодно. Проверке место в инструменте, который вы контролируете, а не в промпте.
Лимиты расходовНа намерение и на день, проверяются в коде до создания ордера.
Человек в контуреВаш create_swap может ждать подтверждения, которое в продукте уже есть.
Собственные идентификаторыclient_order_id связывает обмен с намерением, тикетом или пользователем, который его вызвал.
Защита от повторовIdempotency-Key на намерение превращает повторный вызов в тот же ордер, а не во второй обмен.

Свой сервер примерно на пятьдесят строк

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const BASE = "https://api.swapzilla.io";
const KEY = process.env.SWAPZILLA_KEY!;              // остаётся в этом процессе
const server = new McpServer({ name: "acme-swaps", version: "1.0.0" });

server.tool(
  "quote_swap",
  {
    from_asset: z.string(), from_network: z.string(),
    to_asset: z.string(), to_network: z.string(),
    amount_from: z.string().optional(), amount_to: z.string().optional(),
    rate_type: z.enum(["floating", "fixed"]).default("floating"),
  },
  async (args) => {
    const r = await fetch(`${BASE}/v1/partner/quotes?${new URLSearchParams(args)}`, {
      headers: { "X-API-Key": KEY },
    });
    const { offers } = await r.json();
    // Возвращайте только то, о чём модель должна рассуждать, а не весь ответ.
    return { content: [{ type: "text", text: JSON.stringify(offers.slice(0, 5).map((o) => ({
      id: o.ID, provider: o.ProviderName, receive: o.ToAmount,
      minutes: o.EstimatedMins, kyc: o.KycRating, worse_by_percent: o.DeviationPercent,
    }))) }] };
  },
);

await server.connect(new StdioServerTransport());

Пишущий инструмент устроен так же — с ограничениями вокруг него:

server.tool(
  "create_swap",
  { offer_id: z.string(), to_address: z.string(), intent_id: z.string() },
  async ({ offer_id, to_address, intent_id }) => {
    assertAllowed(to_address);                       // ваш белый список
    await requireApproval(intent_id);                // ваш человек или ваша политика

    const r = await fetch(`${BASE}/v1/partner/orders`, {
      method: "POST",
      headers: {
        "X-API-Key": KEY, "Content-Type": "application/json",
        "Idempotency-Key": intent_id,                // одно намерение — один ордер
      },
      body: JSON.stringify({
        offer_id, to_address,
        refund_address: process.env.TREASURY_ADDRESS,
        client_order_id: intent_id,
      }),
    });
    const order = await r.json();
    return { content: [{ type: "text", text:
      `Отправьте ровно ${order.AmountFrom} ${order.FromAsset} в сети ${order.FromNetwork} ` +
      `на ${order.DepositAddress}. Ордер ${order.ID}.` }] };
  },
);

Читающие инструменты — активы, котировки, проверка адреса, статус — можно спокойно давать агенту. Ворота нужны только тому, который создаёт ордер.

Ограничения, которые стоит поставить

  • Белый список адресов получения. Ограничьте to_address адресами, которые ваша система уже знает, и отклоняйте остальные внутри инструмента.
  • Лимиты сумм. На намерение и на день, проверяются в сервере. Здравый смысл модели — не лимит расходов.
  • Одно намерение — один idempotency-ключ. Агенты повторяют вызовы; ключ и не даёт повтору стать вторым обменом.
  • Котировка и подтверждение внутри пяти минут. Предложения истекают, поэтому план, одобренный через двадцать минут, нужно пересчитать — и инструмент должен об этом сказать, а не молча переоценить.
  • Никогда не помещайте ключ в контекст модели. Он живёт в процессе сервера, не возвращается инструментами и не попадает в промпт.
  • Логируйте ProviderOrderID. Это та ссылка, которую понимает поддержка провайдера, если обмен приходится «догонять».

Самое сильное ограничение — структурное. API некастодиален: создание ордера выдаёт депозитный адрес и больше ничего. Пока тот же агент не умеет ещё и отправить депозит, худшее, что он может, — открыть ордер, который никто не оплатит и который истечёт сам.

Чего не может ни один из серверов

Чего нетЧто это значит
Push-уведомленийВебхуков нет — статус это вызов инструмента опросом, и ждущий агент должен спрашивать раз в 5–10 секунд, не чаще.
ОтменыОрдер нельзя отменить через API. Неоплаченный истекает сам.
Перемещения средствТак задумано. Любой обмен оплачивается кошельком, до которого агент не дотягивается.
ПесочницыВ продакшене её нет; для детерминированных тестов можно включить на ваш ключ провайдер mock.
Скоринга рисков кошелькаНи AML, ни анализа графов в API нет, поэтому агент не может спросить, рискованный ли адрес.

Вопросы

Есть ли у SwapZilla официальный MCP-сервер?

Да — swapzilla-mcp с открытым исходным кодом под MIT, на Node 18+, устанавливается одной строкой: npx -y https://swapzilla.io/mcp/swapzilla-mcp-1.0.0.tgz. Он даёт шесть инструментов и не требует ни аккаунта, ни API-ключа для публичных данных о курсах. Всё про установку — на /ru/mcp/; эта страница о собственном сервере, когда в контуре нужны ваш ключ и ваши лимиты.

Когда опубликованного сервера недостаточно?

Когда обмены должны атрибутироваться на ваш партнёрский id, когда адреса получения обязаны приходить из белого списка, когда расходам нужен лимит или подтверждение человека, а также когда каждый обмен должен нести ваш client_order_id для сверки. Всё это живёт в процессе сервера — значит, сервер придётся поднять свой.

Может ли ИИ-агент потратить мои деньги через этот API?

Сам по себе — нет. Обмен некастодиальный: создание ордера возвращает депозитный адрес и ничего не перемещает. Средства двигаются только когда кошелёк отправит этот депозит, а это отдельное действие вне API. Держите возможность отправлять депозит подальше от агента — и худший случай это неоплаченный ордер, который истечёт.

Как не дать агенту повторами создать два обмена?

Дайте каждому намерению идентификатор и передавайте его как Idempotency-Key при создании ордера. Повторный вызов с тем же ключом вернёт исходный ордер, а не откроет второй.

Сколько живёт котировка, на которую опирается агент?

Пять минут. После этого создание ордера по идентификатору предложения падает с 400, и инструменту приходится пересчитать котировку. Показывайте этот срок модели, чтобы долго согласовывавшийся план переоценивался, а не ломался на исполнении.

Стоит ли отдавать агенту сырые ответы API?

Лучше нет. В предложениях больше полей, чем нужно модели; урезанная форма — провайдер, сумма к получению, минуты, KYC-рейтинг, насколько хуже лучшего — удешевляет рассуждение и не превращает случайные значения в выдуманные гарантии.

Что ещё собирают на этом же ключе

Как получить ключ

Зарегистрируйтесь в партнёрском кабинете — партнёрский id и api_key для заголовка X-API-Key выпускаются сразу. Ключ становится рабочим после активации: напишите в @swapzilla_support_bot и коротко опишите интеграцию. Все эндпоинты, поля и статусы — в документации partner API.