EvolCode
треки/Claude — глубокий разбор·05 / 07
12 мин чтения

Claude как «руки» на твоём сервере

Обычный чат с Claude — это диалог: ты пишешь, он отвечает текстом. Но Claude может БОЛЬШЕ: ему можно дать «руки» — функции, которые он вызывает сам когда нужно. Скажешь «обнови цену в каталоге на 1500» — и он сам читает файл, меняет цифру, сохраняет. Это и называют «agent» или «tool use». Разберёмся как это устроено и как поставить себе.

Зачем вообще это нужно

Реальный кейс: у тебя на VPS лежит сайт, в нём JSON с товарами. Каждый раз когда поставщик меняет цену — ты заходишь по SSH, открываешь файл в nano, меняешь цифру, сохраняешь. Скучно, ошибки случаются, времени жалко. Альтернатива — Claude-агент: ты пишешь в Telegram-бота «обнови цену товара X на Y», бот шлёт это Claude, Claude вызывает функцию write_file и обновляет JSON. То же самое с базами данных, конфигами, статикой блога — любой контент, который раньше требовал ручного захода на сервер.

Простой путь: Claude Code на сервере

Если не хочешь писать своё — Anthropic уже сделал готовый CLI-агент Claude Code. Заходишь по SSH на сервер, ставишь

$ curl -fsSL https://claude.ai/install.sh | bash

переходишь в папку проекта, запускаешь claude. Дальше пишешь словами что нужно — Claude сам читает/пишет файлы, запускает команды, делает коммиты. Это лучший вариант для разработки и одноразовых правок. Для постоянной автоматизации (бот, веб-интерфейс) — нужен свой агент через API.

Свой агент: tool use через API

API Anthropic принимает параметр tools — массив с описанием функций, которые Claude может вызвать. Каждая функция: имя, описание (что делает простыми словами), JSON-схема входа. Claude в ответе отдаёт либо обычный текст, либо блок tool_use — мол, «вызови функцию write_file с такими-то аргументами». Твой код выполняет эту функцию и возвращает результат через tool_result. Цикл повторяется пока Claude не скажет «готово» (stop_reason ≠ tool_use). У OpenAI это называется function calling — концепция та же.

Главное правило: песочница и whitelist

Claude — мощный, но не идеальный. Если ты дашь ему run_command(any_command) — однажды он по ошибке вызовет

$ rm -rf /

и сервер кончится. Правила безопасности: 1) Никаких «универсальных» тулов вроде exec_shell — только узкие функции с конкретной задачей (update_price, add_product). 2) Whitelist путей — разрешаем читать/писать ТОЛЬКО ./data/, всё что вне — отказываем. 3) Логировать каждый tool_use в файл — чтобы потом расследовать что произошло. 4) Для production-данных — отдельный read_only-режим, где Claude может смотреть, но не менять. 5) Лимит на число итераций цикла — 10-20 максимум, иначе риск зацикливания.

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

Берёшь на console.anthropic.com → API Keys → Create. Кладёшь в .env на сервере: ANTHROPIC_API_KEY=sk-ant-.... Никогда не давать NEXT_PUBLIC_ или VITE_ префикс — этот ключ ТОЛЬКО серверный. Если сервер у тебя на Hetzner / DigitalOcean — заведи .env в папке проекта и убедись что .env есть в .gitignore. Для systemd-сервиса — переменные либо в

$ EnvironmentFile=/etc/myapp.env

либо через loadEnvFile в Node.js 20.6+.

примерjavascript
// agent.mjs — запусти как: node agent.mjs
// Установка: npm i @anthropic-ai/sdk
import Anthropic from '@anthropic-ai/sdk';
import { readFile, writeFile } from 'node:fs/promises';
import readline from 'node:readline/promises';

const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });

// === ОПИСАНИЕ ТУЛОВ для Claude ===
// Claude увидит эти описания и решит когда вызывать.
const tools = [
  {
    name: 'read_file',
    description: 'Прочитать файл из папки ./data/. Возвращает содержимое.',
    input_schema: {
      type: 'object',
      properties: {
        path: { type: 'string', description: 'Путь, например ./data/products.json' },
      },
      required: ['path'],
    },
  },
  {
    name: 'write_file',
    description: 'Перезаписать файл новым содержимым. Только в ./data/.',
    input_schema: {
      type: 'object',
      properties: {
        path: { type: 'string' },
        content: { type: 'string' },
      },
      required: ['path', 'content'],
    },
  },
];

// === ОБРАБОТЧИКИ — что РЕАЛЬНО делать когда Claude вызвал tool ===
const handlers = {
  read_file: async ({ path }) => {
    if (!path.startsWith('./data/')) return { error: 'Доступ только к ./data/' };
    return { content: await readFile(path, 'utf-8') };
  },
  write_file: async ({ path, content }) => {
    if (!path.startsWith('./data/')) return { error: 'Доступ только к ./data/' };
    await writeFile(path, content);
    return { ok: true };
  },
};

// === ЦИКЛ АГЕНТА ===
async function runAgent(userMessage) {
  const messages = [{ role: 'user', content: userMessage }];
  let iterations = 0;

  while (iterations++ < 10) {           // лимит — защита от зацикливания
    const r = await client.messages.create({
      model: 'claude-sonnet-4-6',
      max_tokens: 1024,
      tools,
      messages,
    });
    messages.push({ role: 'assistant', content: r.content });

    if (r.stop_reason !== 'tool_use') {
      // Claude закончил — отдаём текст пользователю
      return r.content.find((b) => b.type === 'text')?.text ?? 'готово';
    }

    // Выполняем все запрошенные тулы и возвращаем результаты
    const toolUses = r.content.filter((b) => b.type === 'tool_use');
    const results = [];
    for (const use of toolUses) {
      console.log('→ tool:', use.name, use.input);
      const handler = handlers[use.name];
      const result = handler ? await handler(use.input) : { error: 'Нет такого тула' };
      results.push({
        type: 'tool_result',
        tool_use_id: use.id,
        content: JSON.stringify(result),
      });
    }
    messages.push({ role: 'user', content: results });
  }
}

// === ДЕМО ===
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
const ask = await rl.question('Что сделать? ');
const answer = await runAgent(ask);
console.log('\n→', answer);
rl.close();

// Пример запроса:
// "В файле ./data/products.json у товара 'Молоко' измени price на 95"
// Claude САМ: 1) read_file → видит JSON 2) меняет цифру 3) write_file 4) пишет "готово"

Минимальный агент: Claude меняет JSON-конфиг через две функции read_file/write_file

главное
  • 01Claude умеет не только отвечать текстом, но и вызывать функции которые ты ему дал — это «tool use» / agent
  • 02Самый быстрый старт: SSH на сервер + установить Claude Code (`claude.ai/install.sh`)
  • 03Свой агент: API + параметр tools (имя, description, JSON-schema) + цикл вызов→tool_result→вызов до stop_reason ≠ tool_use
  • 04Главное правило безопасности — узкие тулы (update_price, не run_shell) и whitelist путей
  • 05Каждый tool_use логируйте — без логов разобраться «что натворил агент» невозможно
  • 06Лимит итераций цикла (10-20) — защита от зацикливания и неконтролируемых трат
  • 07ANTHROPIC_API_KEY — только серверный, никогда NEXT_PUBLIC_ / VITE_ префикс
проверь себя
01Что значит ответ Claude с stop_reason: "tool_use"?
02Какой из тулов БЕЗОПАСНЕЕ давать Claude в production-агенте?
03Куда положить ANTHROPIC_API_KEY на сервере с Next.js приложением?
следующий урок: Claude Code CLI — готовый агент за минуту