Додаток 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"
}
}
Використовуйте потоковий HTTP для хостів, які очікують URL стабільної сесії MCP.
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);
Використовуйте потоковий HTTP для хостів, які очікують URL стабільної сесії MCP.
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"],
},
},
},
}],
}),
);
Віддавайте перевагу потоковому HTTP для хостів, які очікують URL стабільної сесії MCP.
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",
});
});
Віддавайте перевагу потоковому HTTP для хостів, які очікують URL стабільної сесії MCP.
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 від чистих сервісів.
Теми інтерфейсу «темний/світлий» не повинні змінювати логіку отримання даних.
Записуйте ідентифікатори записів у журналі разом із номерами етапів чату для подальшої обробки.
Перебудовуйте індекси з урахуванням темпів змін у вихідних даних.
Відмовляйтесь від використання, якщо основні сегменти виглядають слабкими.
Зберігайте фіксати, власників та примітки про скасування змін поруч із процесами отримання та розгортання даних.
Зберігайте фіксати, власників та примітки про скасування змін поруч із процесами отримання та розгортання даних.
Зберігайте фіксати, власників та примітки про скасування змін поруч із процесами отримання та розгортання даних.
Зберігайте фіксати, власників та примітки про скасування змін поруч із процесами отримання та розгортання даних.
Зберігайте фіксати, власників та примітки про скасування змін поруч із процесами отримання та розгортання даних.
Зберігайте фіксати, власників та примітки про скасування змін поруч із процесами отримання та розгортання даних.
Зберігайте фіксати, власників та примітки про скасування змін поруч із процесами отримання та розгортання даних.
Зберігайте фіксати, власників та примітки про скасування змін поруч із процесами отримання та розгортання даних.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте фікстури, власників та примітки про скасування поруч із процесами отримання та розгортання змін.
Зберігайте інформацію про конфігурації, власників та примітки щодо скасування змін поруч із процесами впровадження та розгортання змін.