首页 / 文章

WeatherSG MCP应用:ChatGPT与Claude中的工具增强型交互式地图

WeatherSG,一款基于 TypeScript 的 MCP 应用:通过数据契约、Streamable HTTP 技术,结合 Cloud Run 部署与主机注册功能,让 ChatGPT 和 Claude 能够显示实时天气地图。

发布者:REACTAPP.TOP阅读约 16 分钟3664 词

来源: How to Build and Deploy an MCP App

MCP 应用将模型上下文协议的应用范围扩展到了那些仅返回文本的工具之外:它们能够提供交互式 UI 资源,供 ChatGPT 或 Claude 等平台展示。本教程将使用 TypeScript 构建 WeatherSG——包括数据契约、天气数据获取功能、普通工具、打包为单个 HTML 资源的地图界面、流式 HTTP 传输机制、部署到 Cloud Run 的容器,以及在这两个平台上的注册流程。

什么是 MCP 应用

服务器依然会提供工具和资源;而应用则额外提供一个前端界面,供平台在调用某工具后嵌入使用。其工作流程为:提示词 → 工具 → UI 资源 → 用户交互 → 根据需要再次调用工具。

为何选择 TypeScript

使用同一种语言来处理服务器逻辑和 UI 打包,能够保持部署单元的简洁性。严格的类型系统有助于确保数据契约在数据标准化处理与地图显示环节之间保持一致性。

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"
  }
}

对于需要持久MCP会话URL的主机,建议使用可流式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);

对于需要持久MCP会话URL的主机,建议使用可流式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"],
          },
        },
      },
    }],
  }),
);

对于需要持久MCP会话URL的主机,建议使用可流式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",
  });
});

对于需要持久MCP会话URL的主机,建议使用可流式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

对于需要持久MCP会话URL的主机,建议使用可流式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

对于需要持久MCP会话URL的主机,建议使用可流式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连接器指向带有所需认证头的公共MCP URL。在TLS连接终止后再次验证功能交互。

总结

MCP应用实际上是一个完整的产品体系:包括契约、工具、UI资源、传输机制、容器以及主机配置。只要缺少任意一个环节,演示功能在其他人的客户端中就无法正常运行。

在嵌入器标识旁标注 API 接口版本。

为单元测试将 HTTP 处理程序与纯服务分离。

深色/浅色 UI 主题不得拆分数据检索逻辑。

记录数据获取编号及对话轮次,以便后续分类处理。

根据数据源变化频率定期重建索引。

当核心数据段表现不佳时拒绝处理。

在嵌入器标识旁标注 API 接口版本。

为单元测试将 HTTP 处理程序与纯服务分离。

深色/浅色 UI 主题不得拆分数据检索逻辑。

记录数据获取编号及对话轮次,以便后续分类处理。

根据数据源变化频率定期重建索引。

当核心数据段表现不佳时拒绝处理。

在嵌入器标识旁标注 API 接口版本。

为单元测试将 HTTP 处理程序与纯服务分离。

深色/浅色 UI 主题不得拆分数据检索逻辑。

记录数据获取编号及对话轮次,以便后续分类处理。

按照数据源变化频率定期重建索引。

当核心模块表现不佳时拒绝处理请求。

在嵌入器标识旁标注API合约版本号。

为单元测试将HTTP处理程序与纯服务分离。

深色/浅色UI主题不得改变数据检索逻辑。

将获取标识与聊天轮次一同记录,以便后续分类处理。

按照数据源变化频率定期重建索引。

当核心模块表现不佳时拒绝处理请求。

在嵌入器标识旁标注API合约版本号。

为单元测试将HTTP处理程序与纯服务分离。

深色/浅色UI主题不得改变数据检索逻辑。

将获取标识与聊天轮次一同记录,以便后续分类处理。

按照数据源变化频率定期重建索引。

当核心模块表现不佳时拒绝处理请求。

在嵌入器标识旁标注API合约版本号。

为单元测试将HTTP处理程序与纯服务分离。

深色/浅色 UI 主题不得拆分检索逻辑。

将获取的 ID 与聊天轮次一起记录,以便日后分类处理。

按照数据源变化频率定期重建索引。

当核心部分表现不佳时拒绝处理。

在嵌入器 ID 旁边标注 API 接口版本。

为单元测试将 HTTP 处理程序与纯服务分离。

深色/浅色 UI 主题不得拆分检索逻辑。

将获取的 ID 与聊天轮次一起记录,以便日后分类处理。

按照数据源变化频率定期重建索引。

当核心部分表现不佳时拒绝处理。

在嵌入器 ID 旁边标注 API 接口版本。

为单元测试将 HTTP 处理程序与纯服务分离。

深色/浅色 UI 主题不得拆分检索逻辑。

将获取的 ID 与聊天轮次一起记录,以便日后分类处理。

按照数据源变化频率定期重建索引。

当核心部分表现不佳时拒绝处理。

在嵌入器标识旁标注 API 接口版本。

为单元测试将 HTTP 处理程序与纯服务分离。

深色/浅色 UI 主题不得拆分数据检索逻辑。

记录数据获取编号及对话轮次,以便后续分类处理。

根据数据源变化频率定期重建索引。

当核心数据段表现不佳时拒绝处理。

在嵌入器标识旁标注 API 接口版本。

为单元测试将 HTTP 处理程序与纯服务分离。

深色/浅色 UI 主题不得拆分数据检索逻辑。

记录数据获取编号及对话轮次,以便后续分类处理。

根据数据源变化频率定期重建索引。

当核心数据段表现不佳时拒绝处理。

在嵌入器标识旁标注 API 接口版本。

为单元测试将 HTTP 处理程序与纯服务分离。

深色/浅色 UI 主题不得拆分数据检索逻辑。

记录数据获取编号及对话轮次,以便后续分类处理。

按照数据源变化频率定期重建索引。

当核心模块表现不佳时拒绝处理请求。

在嵌入器标识旁标注API合约版本号。

为单元测试将HTTP处理程序与纯服务分离。

深色/浅色UI主题不得改变数据检索逻辑。

将获取标识与聊天轮次一同记录,以便后续分类处理。

按照数据源变化频率定期重建索引。

当核心模块表现不佳时拒绝处理请求。

在嵌入器标识旁标注API合约版本号。

为单元测试将HTTP处理程序与纯服务分离。

深色/浅色UI主题不得改变数据检索逻辑。

将获取标识与聊天轮次一同记录,以便后续分类处理。

按照数据源变化频率定期重建索引。

当核心模块表现不佳时拒绝处理请求。

在嵌入器标识旁标注API合约版本号。

为单元测试将HTTP处理程序与纯服务分离。

深色/浅色界面主题不得改变数据获取逻辑。

将日志获取编号与聊天轮次记录下来,以便日后分类处理。

根据数据源的变化频率定期重建索引。

当核心数据段表现不佳时拒绝处理。

在导入和部署变更时,同时保留相关配置、负责人及回滚说明。

在导入和部署变更时,同时保留相关配置、负责人及回滚说明。

在导入和部署变更时,同时保留相关配置、负责人及回滚说明。

在导入和部署变更时,同时保留相关配置、负责人及回滚说明。

在导入和部署变更时,同时保留相关配置、负责人及回滚说明。

在导入和部署变更时,同时保留相关配置、负责人及回滚说明。

在导入和部署变更时,同时保留相关配置、负责人及回滚说明。

在导入和部署变更时,同时保留相关配置、负责人及回滚说明。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需将相关配置项、负责人以及回滚说明一并保存。

在导入和部署变更时,需同时记录相关配置、负责人以及回滚说明。