Приложение WeatherSG MCP: инструменты плюс интерактивная карта в ChatGPT и Claude
WeatherSG — приложение MCP на TypeScript: контракт данных, Streamable HTTP, развертывание в Cloud Run и регистрация хоста, позволяющие ChatGPT и Claude отображать актуальную карту погоды.
Источники: 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 от чистых сервисов.
Темы интерфейса тёмного/светлого цвета не должны изменять логику получения данных.
Записывайте идентификаторы записей в журнале вместе с информацией о ходах чата для последующего анализа.
Пересоздавайте индексы с такой частотой, которая соответствует скорости изменения исходных данных.
Отклоняйте запросы, если ключевые сегменты выглядят слабыми.
Храните фикстуры, ответственных лиц и записи о откатах рядом с изменениями, внесёнными при импорте и развертывании.
Храните фикстуры, ответственных лиц и записи о откатах рядом с изменениями, внесёнными при импорте и развертывании.
Храните фикстуры, ответственных лиц и записи о откатах рядом с изменениями, внесёнными при импорте и развертывании.
Храните фикстуры, ответственных лиц и записи о откатах рядом с изменениями, внесёнными при импорте и развертывании.
Храните фикстуры, ответственных лиц и записи о откатах рядом с изменениями, внесёнными при импорте и развертывании.
Храните фикстуры, ответственных лиц и записи о откатах рядом с изменениями, внесёнными при импорте и развертывании.
Храните фикстуры, ответственных лиц и записи о откатах рядом с изменениями, внесёнными при импорте и развертывании.
Храните фикстуры, ответственных лиц и записи о откатах рядом с изменениями, внесёнными при импорте и развертывании.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните фикстчеры, ответственных лиц и записи о возврате к предыдущему состоянию рядом с процессами ввода и развертывания изменений.
Храните информацию о компонентах, владельцах и записях об откатах рядом с процессами внесения и развертывания изменений.