Методы сохранения и передачи контекста

Когда мы создаём ИИ-агента, важно, чтобы он помнил, о чём вы говорили ранее. Без сохранения контекста каждый запрос будет для него как первый — будто он забывает всё каждые 5 секунд. Это не просто неудобно, это делает агента бесполезным в реальных задачах. Сегодня мы разберём, как сохранять и передавать контекст между запросами — основу «памяти» ИИ-агента в архитектуре MCP.

Вы уже знаете, как устроена базовая структура проекта MCP-сервера на TypeScript. Теперь пришло время научить агента не терять данные между запросами. Мы рассмотрим два основных подхода: файловое хранилище и Redis, покажем, как формируется контекстный объект, и почему выбор метода хранения влияет на стабильность, безопасность и масштабируемость.

Что такое контекст в MCP?

В архитектуре MCP контекст — это структурированный объект, который передаётся в запросе /prompt и содержит всю необходимую информацию для LLM: историю диалога, текущее состояние, метаданные и данные от инструментов. Это не просто «память», а формализованный JSON, который сервер должен корректно обновлять и возвращать.

Пример контекстного объекта:

{
  "sessionId": "user_123",
  "history": [
    {"role": "user", "content": "Создай курс по Python"},
    {"role": "assistant", "content": "Какой уровень у аудитории?"}
  ],
  "state": {
    "courseTopic": "Python",
    "audienceLevel": null
  },
  "metadata": {
    "createdAt": "2026-04-05T10:00:00Z",
    "lastActive": "2026-04-05T10:05:00Z"
  }
}

Важно: MCP не хранит контекст за вас. Сервер должен сам решить, где и как его сохранять. Клиент присылает sessionId, а сервер по ней извлекает, обновляет и возвращает контекст.

Метод 1: Файловое хранилище

Файловое хранилище — это простой и понятный способ хранения контекста, особенно на этапе разработки. Мы будем сохранять контекст в .json-файлах, используя sessionId как имя файла.

Пример реализации на TypeScript:

import fs from 'fs';
import path from 'path';

const CONTEXT_DIR = path.join(__dirname, 'contexts');

// Сохранение контекста
function saveContext(sessionId: string, context: any) {
  const filePath = path.join(CONTEXT_DIR, `${sessionId}.json`);
  fs.writeFileSync(filePath, JSON.stringify(context, null, 2), 'utf-8');
}

// Загрузка контекста
function loadContext(sessionId: string): any {
  const filePath = path.join(CONTEXT_DIR, `${sessionId}.json`);
  if (fs.existsSync(filePath)) {
    const data = fs.readFileSync(filePath, 'utf-8');
    return JSON.parse(data);
  }
  return null; // Новый сеанс
}

Плюсы:

  • Простота понимания и отладки
  • Не требует внешних зависимостей
  • Подходит для прототипирования

Минусы:

  • Не масштабируется при высокой нагрузке
  • Риск конфликтов при одновременной записи
  • Нет автоматической очистки устаревших данных

Совет: Файловое хранилище отлично подходит для локальной разработки, но не для продакшена. В реальных условиях оно может стать узким местом.

Метод 2: Redis — промышленный стандарт

Redis — это in-memory ключ-значение хранилище, идеально подходящее для хранения контекста. Он быстро работает, поддерживает TTL (время жизни ключа), кластеризацию и отлично интегрируется с Docker и облачными платформами.

Установка и подключение:

npm install redis

Пример работы с Redis:

import { createClient } from 'redis';

const client = createClient({
  url: 'redis://localhost:6379'
});

await client.connect();

// Сохранение с TTL = 1 час
async function saveContext(sessionId: string, context: any) {
  await client.setEx(
    `context:${sessionId}`,
    3600, // TTL в секундах
    JSON.stringify(context)
  );
}

// Загрузка
async function loadContext(sessionId: string): Promise<any> {
  const data = await client.get(`context:${sessionId}`);
  return data ? JSON.parse(data) : null;
}

Плюсы:

  • Высокая производительность
  • Поддержка TTL — автоматическая очистка
  • Масштабируемость и отказоустойчивость
  • Совместимость с Docker и облачными средами

Минусы:

  • Требует отдельного сервера или контейнера
  • Нужно настраивать безопасность и резервное копирование

Особенно актуально в России: Redis часто используется в локальных дата-центрах и частных облаках из-за требований к локализации данных. Это делает его предпочтительным выбором для продакшн-систем в 2026 году.

Сравнение методов

КритерийФайловое хранилищеRedis
СкоростьСредняяВысокая
МасштабируемостьНизкаяВысокая
БезопасностьЗависит от файловой системыТребует настройки (пароли, шифрование)
TTL (автоочистка)НетДа
Поддержка в DockerДаДа
Подходит для продакшенаНетДа

Практический пример: сохранение истории диалога

Представим, что наш агент помогает пользователю создать курс. Мы хотим, чтобы он помнил тему и уровень аудитории.

// В обработчике /prompt
app.post('/prompt', async (req, res) => {
  const { sessionId, prompt } = req.body;
  
  // Загружаем контекст
  let context = await loadContext(sessionId) || {
    history: [],
    state: {},
    metadata: { createdAt: new Date().toISOString() }
  };

  // Обновляем историю
  context.history.push({ role: 'user', content: prompt });

  // Обновляем метаданные
  context.metadata.lastActive = new Date().toISOString();

  // Сохраняем
  await saveContext(sessionId, context);

  // Передаём в LLM
  const response = await callLLM(prompt, context);
  
  context.history.push({ role: 'assistant', content: response });
  await saveContext(sessionId, context);

  res.json({ response, context });
});

Ошибка новичка: не обновлять контекст после ответа LLM. Без этого агент «забудет» свою последнюю реплику и не сможет поддерживать диалог.

Что дальше?

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

Контекст — это основа, но память — это уже система: она знает, что важно, что можно забыть, и как быстро отвечать, не перегружая LLM. Именно это превращает агента из «бота» в «помощника».

Готовы к следующему шагу? Вперед — к созданию по-настоящему умных ИИ-агентов.