Главная / Статьи

Приложение WeatherSG MCP: инструменты плюс интерактивная карта в ChatGPT и Claude

WeatherSG — приложение MCP на TypeScript: контракт данных, Streamable HTTP, развертывание в Cloud Run и регистрация хоста, позволяющие ChatGPT и Claude отображать актуальную карту погоды.

Издатель: REACTAPP.TOP16 мин чтения3664 слов

Источники: How to Build and Deploy an MCP App

Приложения MCP расширяют протокол Model Context Protocol за рамки инструментов, возвращающих текст: они могут предоставлять интерактивные ресурсы пользовательского интерфейса, которые хосты вроде ChatGPT или Claude могут отображать. В этом руководстве создается WeatherSG на TypeScript — контракт данных, получение информации о погоде, обычный инструмент, интерфейс к карте, объединенные в один HTML-ресурс, транспорт Streamable HTTP, развертывание в контейнере на Cloud Run и регистрация в обоих хостах.

Что такое приложение MCP

Сервер по-прежнему предоставляет инструменты и ресурсы; приложение дополнительно обеспечивает фронт-энд, который хост встраивает после вызова инструмента. Цикл выглядит так: запрос → инструмент → ресурс интерфейса → взаимодействие пользователя → дальнейшие вызовы инструментов по мере необходимости.

Почему здесь TypeScript

Один язык для серверной части и объединения интерфейса позволяет сделать единицу развертывания меньшей по размеру. Строгие типы помогают сохранять согласованность контракта данных между модулями обработки данных и интерфейсом карте.

mkdir weathersg
cd weathersg
npm init -y

npm install \
  @modelcontextprotocol/sdk \
  @modelcontextprotocol/ext-apps \
  express leaflet react react-dom zod

npm install --save-dev \
  @types/express @types/leaflet @types/node \
  @types/react @types/react-dom \
  esbuild supertest tsx typescript vitest

Храните секреты в среде платформы; никогда не встраивайте ключи API в собранный HTML.

{
  "scripts": {
    "build": "npm run build:web && tsc -p tsconfig.json",
    "build:web": "node scripts/build-web.mjs",
    "dev": "npm run build:web && tsx watch src/index.ts",
    "start": "node dist/src/index.js",
    "test": "vitest run",
    "typecheck": "tsc -p tsconfig.json --noEmit"
  },
  "engines": {
    "node": ">=20"
  }
}

Для хостов, ожидающих URL долговременной сессии MCP, предпочтительнее использовать потоковый HTTP.

weathersg/
  src/
    weather/
      schema.ts
      client.ts
    http-app.ts
    index.ts
    server.ts
  web/
    src/
      main.tsx
      styles.css
  scripts/
    build-web.mjs
  tests/
  Dockerfile
  package.json
  tsconfig.json

Проверки работоспособности контейнеров должны выполняться по конечной точке MCP, а не только по статической странице.

import { z } from "zod";

export const weatherLayerSchema = z.enum([
  "all",
  "forecast",
  "rainfall",
]);

const forecastAreaSchema = z.object({
  id: z.string(),
  name: z.string(),
  latitude: z.number(),
  longitude: z.number(),
  condition: z.string(),
  category: z.enum([
    "clear",
    "cloudy",
    "showers",
    "thunderstorms",
    "windy",
    "hazy",
    "unknown",
  ]),
});

const rainfallStationSchema = z.object({
  id: z.string(),
  name: z.string(),
  latitude: z.number(),
  longitude: z.number(),
  rainfallMm: z.number().nonnegative(),
});

export const weatherSnapshotSchema = z.object({
  layer: weatherLayerSchema,
  generatedAt: z.string(),
  forecast: z.object({
    updatedAt: z.string(),
    validFrom: z.string(),
    validTo: z.string(),
    validPeriodText: z.string(),
    areas: z.array(forecastAreaSchema),
  }).nullable(),
  rainfall: z.object({
    observedAt: z.string(),
    readingType: z.string(),
    unit: z.string(),
    stations: z.array(rainfallStationSchema),
  }).nullable(),
  summary: z.object({
    headline: z.string(),
    areasWithRain: z.number().int().nonnegative(),
    reportingRainStations: z.number().int().nonnegative(),
  }),
  warnings: z.array(z.string()),
  sources: z.array(z.object({
    label: z.string(),
    url: z.string().url(),
  })),
});

export type WeatherSnapshot = z.infer<typeof weatherSnapshotSchema>;

Регистрация хоста является частью конфигурации — повторяйте тестирование после любых изменений URL или механизмов аутентификации.

async function fetchValidated<T>(
  url: string,
  schema: z.ZodType<T>,
  sourceLabel: string,
): Promise<{ value: T; warning?: string }> {
  const cached = cache.get(url);

  try {
    const response = await fetch(url, {
      headers: { Accept: "application/json" },
      signal: AbortSignal.timeout(8_000),
    });

    if (!response.ok) {
      throw new Error(`Weather service returned HTTP ${response.status}`);
    }
    const value = schema.parse(await response.json());
    cache.set(url, {
      value,
      expiresAt: Date.now() + 60_000,
    });
    return { value };
  } catch (error) {
    if (cached) {
      return {
        value: cached.value as T,
        warning: `${sourceLabel} could not be refreshed`,
      };
    }
    throw error;
  }
}

Рассматривайте это как контрольный пункт: тесты протокола должны пройти перед подключением к чат-хосту.

const [forecastResult, rainfallResult] = await Promise.allSettled([
  fetchValidated(TWO_HOUR_FORECAST_URL, forecastSchema, "Forecast"),
  fetchValidated(RAINFALL_URL, rainfallSchema, "Rainfall"),
]);

Храните секреты в среде платформы; никогда не встраивайте ключи API в собранный HTML.

return weatherSnapshotSchema.parse(snapshot);

Для хостов, ожидающих URL долговременной сессии MCP, предпочтительнее использовать потоковый HTTP.

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { getSingaporeWeather } from "./weather/client.js";
import {
  weatherLayerSchema,
  weatherSnapshotSchema,
} from "./weather/schema.js";

const server = new McpServer(
  { name: "weathersg", version: "0.1.0" },
  {
    instructions:
      "Use get_singapore_weather for factual weather questions. " +
      "Use show_singapore_weather_map when the user asks to see, " +
      "map, compare, or explore weather across Singapore.",
  },
);

Проверки работоспособности контейнеров должны выполняться по конечной точке MCP, а не только по статической странице.

server.registerTool(
  "get_singapore_weather",
  {
    title: "Get Singapore weather",
    description:
      "Get current official two-hour forecasts and five-minute " +
      "rainfall observations across Singapore.",
    inputSchema: {
      layer: weatherLayerSchema.default("all"),
    },
    outputSchema: weatherSnapshotSchema,
    annotations: {
      readOnlyHint: true,
      destructiveHint: false,
      openWorldHint: false,
    },
  },
  async ({ layer }) => {
    const snapshot = await getSingaporeWeather(layer);
    return {
      structuredContent: snapshot,
      content: [{
        type: "text",
        text: `${snapshot.summary.headline} Snapshot generated at ${snapshot.generatedAt}.`,
      }],
    };
  },
);

Регистрация хоста является частью конфигурации — повторяйте тестирование после любых изменений URL или механизмов аутентификации.

import {
  registerAppResource,
  registerAppTool,
  RESOURCE_MIME_TYPE,
} from "@modelcontextprotocol/ext-apps/server";

export const WEATHER_APP_URI =
  "ui://weathersg/singapore-weather-map/v1.html";

Рассматривайте это как контрольный пункт: тесты протокола должны пройти перед подключением хоста чата.

registerAppTool(
  server,
  "show_singapore_weather_map",
  {
    title: "Show Singapore weather map",
    description:
      "Show an interactive map of official forecasts and rainfall " +
      "observations across Singapore.",
    inputSchema: {
      layer: weatherLayerSchema.default("all"),
    },
    outputSchema: weatherSnapshotSchema,
    annotations: {
      readOnlyHint: true,
      destructiveHint: false,
      openWorldHint: false,
    },
    _meta: {
      ui: { resourceUri: WEATHER_APP_URI },
      "openai/toolInvocation/invoking": "Reading Singapore weather…",
      "openai/toolInvocation/invoked": "Singapore weather map ready",
    },
  },
  async ({ layer }) => {
    const snapshot = await getSingaporeWeather(layer);
    return {
      structuredContent: snapshot,
      content: [{
        type: "text",
        text: snapshot.summary.headline,
      }],
    };
  },
);

Храните секреты в среде платформы; никогда не включайте ключи API в собранный HTML-код.

registerAppResource(
  server,
  "Singapore weather map",
  WEATHER_APP_URI,
  { description: "Interactive map of Singapore weather" },
  async () => ({
    contents: [{
      uri: WEATHER_APP_URI,
      mimeType: RESOURCE_MIME_TYPE,
      text: loadWeatherAppHtml(),
      _meta: {
        ui: {
          prefersBorder: true,
          csp: {
            connectDomains: [],
            resourceDomains: ["https://www.onemap.gov.sg"],
          },
        },
      },
    }],
  }),
);

Для хостов, ожидающих URL долговечной сессии MCP, предпочтительнее использовать потоковый HTTP.

import { App } from "@modelcontextprotocol/ext-apps";
import { createRoot } from "react-dom/client";

const app = new App(
  { name: "WeatherSG map", version: "0.1.0" },
  {},
  { autoResize: true },
);

function parseSnapshot(value: unknown): WeatherSnapshot | null {
  if (!value || typeof value !== "object") return null;
  const candidate = value as Partial<WeatherSnapshot>;
  if (!candidate.summary || !Array.isArray(candidate.sources)) return null;
  if (!candidate.forecast && !candidate.rainfall) return null;
  return candidate as WeatherSnapshot;
}

app.ontoolresult = (result) => {
  const snapshot = parseSnapshot(result.structuredContent);
  if (snapshot) deliverSnapshot(snapshot);
};

createRoot(document.getElementById("root")!).render(<WeatherApp />);

void app.connect().catch((error) => {
  console.error("Unable to connect to the host", error);
});

Проверки работоспособности контейнеров должны направляться к конечной точке MCP, а не только на статическую страницу.

await build({
  entryPoints: ["web/src/main.tsx"],
  bundle: true,
  minify: true,
  format: "esm",
  platform: "browser",
  write: false,
  outdir: "dist/web-bundle",
});

Регистрация хоста является частью конфигурации — повторяйте тесты после любых изменений URL или механизмов аутентификации.

npm run build

Рассматривайте это как контрольный пункт: тесты протокола должны пройти перед подключением хоста чата.

import { createMcpExpressApp } from
  "@modelcontextprotocol/sdk/server/express.js";
import { StreamableHTTPServerTransport } from
  "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { createWeatherMcpServer } from "./server.js";

const app = createMcpExpressApp();

app.post("/mcp", async (request, response) => {
  response.setHeader("Cache-Control", "no-store");

  const server = createWeatherMcpServer();
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: undefined,
  });

  await server.connect(transport);

  response.on("close", () => {
    void transport.close();
    void server.close();
  });

  await transport.handleRequest(request, response, request.body);
});

Храните секреты в среде платформы; никогда не включайте ключи API в собранный HTML-код.

app.get("/health", (_request, response) => {
  response.json({ status: "ok", service: "weathersg" });
});

app.get("/ready", (_request, response) => {
  const ready = existsSync("dist/web/weather-app.html");
  response.status(ready ? 200 : 503).json({
    status: ready ? "ready" : "not_ready",
  });
});

Для хостов, ожидающих URL долговечной сессии MCP, предпочтительнее использовать потоковый HTTP.

const port = Number(process.env.PORT ?? 3000);
const app = createWeatherHttpApp();

app.listen(port, "0.0.0.0", () => {
  console.log(`WeatherSG listening on ${port}`);
});

Проверки работоспособности контейнеров должны направляться к конечной точке MCP, а не только на статическую страницу.

npm run dev

Регистрация хоста — это процесс настройки; повторяйте тестирование после любых изменений URL или механизмов аутентификации.

npx @modelcontextprotocol/inspector@latest

Рассматривайте это как контрольный пункт: тесты протокола должны пройти перед подключением хоста к чату.

git clone https://github.com/modelcontextprotocol/ext-apps.git
cd ext-apps/examples/basic-host
npm install
SERVERS='["http://localhost:3000/mcp"]' npm start

Храните конфиденциальные данные в среде платформы; никогда не встраивайте ключи API в собранный HTML-код.

npm run typecheck
npm test
npm run build

Для хостов, ожидающих URL долговременной сессии MCP, предпочтительнее использовать потоковый HTTP.

FROM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s \
  CMD ["node", "-e", "fetch('http://127.0.0.1:'+(process.env.PORT||3000)+'/ready').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"]
CMD ["node", "dist/src/index.js"]

Проверки работоспособности контейнеров должны выполняться с адреса конечной точки MCP, а не только со статической страницы.

docker build -t weathersg .
docker run --rm -p 3000:3000 weathersg

Регистрация хоста — это процесс настройки; повторяйте тестирование после любых изменений URL или механизмов аутентификации.

gcloud auth login
gcloud config set project YOUR_PROJECT_ID
gcloud services enable \
  run.googleapis.com \
  cloudbuild.googleapis.com \
  artifactregistry.googleapis.com

Рассматривайте это как контрольный пункт: тесты протокола должны пройти перед подключением хоста к чату.

gcloud run deploy weathersg \
  --source . \
  --region asia-southeast1 \
  --allow-unauthenticated \
  --set-env-vars PUBLISHER_NAME=WeatherSG

Храните конфиденциальные данные в среде платформы; никогда не встраивайте ключи API в собранный HTML-код.

https://YOUR_SERVICE_HOST/mcp

Для хостов, ожидающих URL долговременной сессии MCP, предпочтительнее использовать потоковый HTTP.

curl -fsS https://YOUR_SERVICE_HOST/health
curl -fsS https://YOUR_SERVICE_HOST/ready

Проверки состояния контейнера должны выполняться с использованием конечной точки MCP, а не только статической страницы.

Is it raining anywhere in Singapore right now?

Show me the Singapore weather map.

Now show only rainfall stations.

Регистрация хоста является частью конфигурации — проводите повторные тесты после любых изменений URL или механизмов аутентификации.

https://YOUR_SERVICE_HOST/mcp

Рассматривайте это как контрольный пункт: тесты протокола должны пройти перед подключением чат-хоста.

Тестирование перед реализацией интерфейса чата

Используйте инструменты и ресурсы вместе с инспектором MCP или скриптовым клиентом. Чат-хосты усугубляют сбои; наличие стабильного локального протокола экономит время.

Развертывание и регистрация

Отправьте контейнер на Cloud Run (или аналогичную платформу), затем направьте подключения ChatGPT и Claude к публичному URL MCP с необходимыми заголовками аутентификации. Повторно проверьте взаимодействие после завершения передачи данных по протоколу TLS.

Заключение

MCP-приложение представляет собой целостный продукт: контракт, инструменты, ресурсы интерфейса, механизмы передачи данных, контейнер и настройки хоста. Если пропустить любой из этих элементов, демо-версия не будет работать в клиенте другого пользователя.

Версии контрактов API должны указываться рядом с идентификаторами встраивателей.

Для тестирования на единицы необходимо разделять обработчики HTTP от чистых сервисов.

Темы интерфейса «темная/светлая» не должны изменять логику получения данных.

Записывайте идентификаторы запросов вместе с номерами сообщений в чате для последующей обработки.

Пересоздавайте индексы с такой же частотой, как и изменение исходных данных.

Отклоняйте запросы, если ключевые сегменты выглядят слабыми.

Версии контрактов API должны указываться рядом с идентификаторами встраивателей.

Для тестирования на единицы необходимо разделять обработчики HTTP от чистых сервисов.

Темы интерфейса «темная/светлая» не должны изменять логику получения данных.

Записывайте идентификаторы запросов вместе с номерами сообщений в чате для последующей обработки.

Пересоздавайте индексы с такой же частотой, как и изменение исходных данных.

Отклоняйте запросы, если ключевые сегменты выглядят слабыми.

Версии контрактов API должны указываться рядом с идентификаторами встраивателей.

Для тестирования на единицы необходимо разделять обработчики HTTP от чистых сервисов.

Темы интерфейса «темная/светлая» не должны изменять логику получения данных.

Записывайте идентификаторы запросов вместе с номерами сообщений в чате для последующей обработки.

Пересоздавайте индексы с такой частотой, которая соответствует изменчивости исходных данных.

Отклоняйте запросы, если лидирующие сегменты выглядят слабыми.

Версионируйте контракты API рядом с идентификаторами встраивателей.

Для тестирования на единицы отделяйте обработчики HTTP от чистых сервисов.

Темы интерфейса «темная/светлая» не должны изменять логику получения данных.

Записывайте идентификаторы запросов вместе с этапами чата для последующей обработки.

Пересоздавайте индексы с такой частотой, которая соответствует изменчивости исходных данных.

Отклоняйте запросы, если лидирующие сегменты выглядят слабыми.

Версионируйте контракты API рядом с идентификаторами встраивателей.

Для тестирования на единицы отделяйте обработчики HTTP от чистых сервисов.

Темы интерфейса «темная/светлая» не должны изменять логику получения данных.

Записывайте идентификаторы запросов вместе с этапами чата для последующей обработки.

Пересоздавайте индексы с такой частотой, которая соответствует изменчивости исходных данных.

Отклоняйте запросы, если лидирующие сегменты выглядят слабыми.

Версионируйте контракты API рядом с идентификаторами встраивателей.

Для тестирования на единицы отделяйте обработчики HTTP от чистых сервисов.

Темы интерфейса тёмного/светлого режима не должны изменять логику получения данных.

Записывайте идентификаторы запросов вместе с номерами ходов в чате для последующей обработки.

Пересоздавайте индексы с такой частотой, которая соответствует изменчивости исходных данных.

Отклоняйте запросы, если лучшие сегменты выглядят слабыми.

Указывайте версии контрактов API рядом с идентификаторами встраиваемых компонентов.

Для тестирования на единицы разделяйте обработчики HTTP от чистых сервисов.

Темы интерфейса тёмного/светлого режима не должны изменять логику получения данных.

Записывайте идентификаторы запросов вместе с номерами ходов в чате для последующей обработки.

Пересоздавайте индексы с такой частотой, которая соответствует изменчивости исходных данных.

Отклоняйте запросы, если лучшие сегменты выглядят слабыми.

Указывайте версии контрактов API рядом с идентификаторами встраиваемых компонентов.

Для тестирования на единицы разделяйте обработчики HTTP от чистых сервисов.

Темы интерфейса тёмного/светлого режима не должны изменять логику получения данных.

Записывайте идентификаторы запросов вместе с номерами ходов в чате для последующей обработки.

Пересоздавайте индексы с такой частотой, которая соответствует изменчивости исходных данных.

Отклоняйте запросы, если лучшие сегменты выглядят слабыми.

Версии контрактов API должны указываться рядом с идентификаторами встраивателей.

Для тестирования на единицы необходимо разделять обработчики HTTP от чистых сервисов.

Темы интерфейса «темная/светлая» не должны изменять логику получения данных.

Записывайте идентификаторы запросов вместе с номерами сообщений в чате для последующей обработки.

Пересоздавайте индексы с такой же частотой, как и изменение исходных данных.

Отклоняйте запросы, если основные сегменты кажутся слабыми.

Версии контрактов API должны указываться рядом с идентификаторами встраивателей.

Для тестирования на единицы необходимо разделять обработчики HTTP от чистых сервисов.

Темы интерфейса «темная/светлая» не должны изменять логику получения данных.

Записывайте идентификаторы запросов вместе с номерами сообщений в чате для последующей обработки.

Пересоздавайте индексы с такой же частотой, как и изменение исходных данных.

Отклоняйте запросы, если основные сегменты кажутся слабыми.

Версии контрактов API должны указываться рядом с идентификаторами встраивателей.

Для тестирования на единицы необходимо разделять обработчики HTTP от чистых сервисов.

Темы интерфейса «темная/светлая» не должны изменять логику получения данных.

Записывайте идентификаторы запросов вместе с номерами сообщений в чате для последующей обработки.

Пересоздавайте индексы с такой частотой, которая соответствует изменчивости исходных данных.

Отклоняйте запросы, если лучшие сегменты выглядят слабыми.

Версионируйте контракты API рядом с идентификаторами встраивателей.

Для тестирования на единицы отделяйте обработчики HTTP от чистых сервисов.

Темы интерфейса «темная/светлая» не должны изменять логику получения данных.

Записывайте идентификаторы запросов вместе с этапами чата для последующей обработки.

Пересоздавайте индексы с такой частотой, которая соответствует изменчивости исходных данных.

Отклоняйте запросы, если лучшие сегменты выглядят слабыми.

Версионируйте контракты API рядом с идентификаторами встраивателей.

Для тестирования на единицы отделяйте обработчики HTTP от чистых сервисов.

Темы интерфейса «темная/светлая» не должны изменять логику получения данных.

Записывайте идентификаторы запросов вместе с этапами чата для последующей обработки.

Пересоздавайте индексы с такой частотой, которая соответствует изменчивости исходных данных.

Отклоняйте запросы, если лучшие сегменты выглядят слабыми.

Версионируйте контракты API рядом с идентификаторами встраивателей.

Для тестирования на единицы отделяйте обработчики HTTP от чистых сервисов.

Темы интерфейса тёмного/светлого цвета не должны изменять логику получения данных.

Записывайте идентификаторы записей в журнале вместе с информацией о ходах чата для последующего анализа.

Пересоздавайте индексы с такой частотой, которая соответствует скорости изменения исходных данных.

Отклоняйте запросы, если ключевые сегменты выглядят слабыми.

Храните фикстуры, ответственных лиц и записи о откатах рядом с изменениями, внесёнными при импорте и развертывании.

Храните фикстуры, ответственных лиц и записи о откатах рядом с изменениями, внесёнными при импорте и развертывании.

Храните фикстуры, ответственных лиц и записи о откатах рядом с изменениями, внесёнными при импорте и развертывании.

Храните фикстуры, ответственных лиц и записи о откатах рядом с изменениями, внесёнными при импорте и развертывании.

Храните фикстуры, ответственных лиц и записи о откатах рядом с изменениями, внесёнными при импорте и развертывании.

Храните фикстуры, ответственных лиц и записи о откатах рядом с изменениями, внесёнными при импорте и развертывании.

Храните фикстуры, ответственных лиц и записи о откатах рядом с изменениями, внесёнными при импорте и развертывании.

Храните фикстуры, ответственных лиц и записи о откатах рядом с изменениями, внесёнными при импорте и развертывании.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.

Храните информацию о компонентах, владельцах и записях об откатах рядом с процессами внесения и развертывания изменений.