主导航

构建您的 MCP 服务器

连接工具、模板以及驱动 ChatGPT 应用的小组件运行时。

阅读本指南后,您将掌握如何将后端 MCP 服务器连接至 ChatGPT,定义工具,注册 UI 模板,并使用小组件运行时将它们串联起来。您将为 ChatGPT 应用打下坚实基础,实现返回结构化数据、渲染交互式小组件,并确保模型、服务器与 UI 保持同步。如果您想直接查看实现过程,可以直接跳转到文末的示例

使用编辑器中的 OpenAI Docs MCP 服务器,加速开发进程。

概述

MCP 服务器为您的应用所做的工作

ChatGPT 应用由三个组件构成

  • 您的 MCP 服务器:负责定义工具、执行身份验证、返回数据,并将每个工具指向相应的 UI 包。
  • 小组件/UI 包:在 ChatGPT 的 iframe 中渲染,并通过 MCP 应用 UI 桥(基于 postMessage 的 JSON-RPC)与宿主进行通信。
  • 模型:决定何时调用工具,并使用您返回的结构化数据来叙述操作体验。

稳健的服务器实现能保持组件间的清晰界限,让您能够独立迭代 UI 和数据。请记住:您负责构建 MCP 服务器并定义工具,而 ChatGPT 模型则会根据您提供的元数据决定何时调用它们。

开始之前

先决条件

  • 熟悉 TypeScript 或 Python 以及 Web 打包工具(Vite、esbuild 等)。
  • MCP 服务器可通过 HTTP 访问(本地环境即可开始)。
  • 已构建导出根脚本(React 或原生 JS)的 UI 包。

示例项目布局

your-chatgpt-app/
├─ server/
│  └─ src/index.ts          # MCP server + tool handlers
├─ web/
│  ├─ src/component.tsx     # React widget
│  └─ dist/app.{js,css}  # Bundled assets referenced by the server
└─ package.json

架构流程

  1. 用户的 Prompt 促使 ChatGPT 调用您的一种 MCP 工具。
  2. 您的服务器运行处理程序,获取权威数据,并返回 structuredContent_meta 以及 UI 元数据。
  3. ChatGPT 加载工具描述符中链接的 HTML 模板(服务类型为 text/html;profile=mcp-app),并通过 MCP 应用桥将工具输入/结果传送到 iframe(例如 ui/notifications/tool-result)。
  4. 小组件基于工具结果进行渲染,可以再次调用工具(使用 tools/call),并根据需要选择性地使用仅限 ChatGPT 的扩展功能。
  5. 模型读取 structuredContent 以叙述所发生的情况,因此请确保数据简洁且幂等——ChatGPT 可能会重试工具调用。
User prompt

ChatGPT model ──► MCP tool call ──► Your server ──► Tool response (`structuredContent`, `_meta`, `content`)
   │                                                   │
   └───── renders narration ◄──── widget iframe ◄──────┘
                              (HTML template + MCP Apps bridge)

使用 MCP 应用 UI 桥

ChatGPT 支持用于 UI 通信的开放式 MCP 应用标准

  • 基于 postMessage 的 JSON-RPC 2.0 消息。
  • 用于宿主↔iframe UI 通信的 ui/* 方法和通知。
  • 通过 tools/call 进行的 MCP 工具调用。

从 MCP 应用桥开始,以保持您的 UI 在不同宿主间的可移植性,然后在需要 ChatGPT 特定功能时添加相关扩展。如需深入了解和映射指南,请参阅 ChatGPT 中的 MCP 应用兼容性

了解 window.openai 小组件运行时

window.openai 是一个 Apps SDK 兼容层,也是可选 ChatGPT 扩展功能的宿主。对于新应用,默认使用 MCP 应用桥,并将 window.openai 视为提供 ChatGPT 特有功能的 API。

独特功能包括

  • 文件处理(ChatGPT 扩展): uploadFileselectFilesgetFileDownloadUrl 涵盖了文件上传、选择和下载。
  • 宿主界面(ChatGPT 扩展): requestModal 可打开由宿主控制的模态框。
  • 商业(ChatGPT 扩展): requestCheckout 可打开即时结账(如果已启用)。

有关 window.openai 的完整参考,请参阅 ChatGPT UI 指南

当您需要宿主控制的覆盖层时使用 requestModal——例如,打开锚定在“添加到购物车”按钮上的结账或详情视图,以便购物者无需调整内嵌小组件的大小即可查看选项。要在模态框中显示不同的 UI 模板,请传递您注册的模板 URI(例如通过 registerAppResource)。

当这些 API 能显著改善您的 ChatGPT 体验时使用它们,但请保持您的核心 UI 桥基于 MCP 应用标准构建。有关实现模式,请参阅 构建您的 ChatGPT UI

选择一个 SDK

Apps SDK 适用于任何 MCP 实现,但官方 SDK 是最快的入门方式。它们内置了工具/模式辅助程序、HTTP 服务器脚手架、资源注册工具以及端到端类型安全,让您可以专注于业务逻辑。

安装与您后端语言匹配的 SDK,然后按照以下步骤操作。

# TypeScript / Node
npm install @modelcontextprotocol/sdk @modelcontextprotocol/ext-apps zod

# Python
pip install mcp

构建您的 MCP 服务器

步骤 1 – 注册组件模板

每个 UI 包都作为带有 MCP 应用 UI MIME 类型 (text/html;profile=mcp-app) 的 MCP 资源公开。如果您使用 @modelcontextprotocol/ext-apps/server,建议使用 RESOURCE_MIME_TYPE 而不是硬编码该字符串。

注册模板并包含有关边框、域和 CSP 规则的元数据。

// Registers the Kanban widget HTML entry point served to ChatGPT.
import {
  registerAppResource,
  RESOURCE_MIME_TYPE,
} from "@modelcontextprotocol/ext-apps/server";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { readFileSync } from "node:fs";

const server = new McpServer({ name: "kanban-server", version: "1.0.0" });
const HTML = readFileSync("web/dist/kanban.js", "utf8");
const CSS = readFileSync("web/dist/kanban.css", "utf8");

registerAppResource(
  server,
  "kanban-widget",
  "ui://widget/kanban-board.html",
  {},
  async () => ({
    contents: [
      {
        uri: "ui://widget/kanban-board.html",
        mimeType: RESOURCE_MIME_TYPE,
        text: `
<div id="kanban-root"></div>
<style>${CSS}</style>
<script type="module">${HTML}</script>
        `.trim(),
        _meta: {
          ui: {
            prefersBorder: true,
            domain: "https://myapp.example.com",
            csp: {
              connectDomains: ["https://api.myapp.example.com"], // example API domain
              resourceDomains: ["https://*.oaistatic.com"], // example CDN allowlist
              // Optional: allow embedding specific iframe origins.
              frameDomains: ["https://*.example-embed.com"],
            },
          },
        },
      },
    ],
  })
);

如果您需要在小组件内嵌入 iframe,请使用 _meta.ui.csp.frameDomains 来声明源许可名单。如果不设置 frameDomains,子框架默认被拦截。由于 iframe 内容难以检查,启用子框架的小组件会受到额外审查,可能无法获得目录分发资格。

最佳实践: 当您以破坏性的方式更改小组件的 HTML/JS/CSS 时,请给模板赋予一个新的 URI(或使用新的文件名),以便 ChatGPT 始终加载更新后的包,而不是缓存的版本。

将 URI 视为您的缓存键。当您更新标记或包时,请对 URI 进行版本控制并更新对它的所有引用(例如 registerAppResource URI、工具描述符中的 _meta.ui.resourceUri 以及模板列表中的 contents[].uri)。ChatGPT 承认 _meta["openai/outputTemplate"] 作为 OpenAI 特定的兼容性别名。

// Old
contents: [{ uri: "ui://widget/kanban-board.html" /* ... */ }];
// New
contents: [{ uri: "ui://widget/kanban-board-v2.html" /* ... */ }];

如果您频繁发布更新,请保持简短、一致的版本控制方案,以便您可以向前(或向后)滚动,而不必重复使用相同的 URI。

步骤 2 – 描述工具

工具是模型进行推理的契约。为每个用户意图定义一个工具(例如 list_tasks, update_task)。每个描述符都应包含:

  • 机器可读的名称和人类可读的标题。
  • 参数模式。使用下方的 Node 辅助程序时,请使用 Zod 原始形状或标准模式;其他 SDK 可能公开 JSON Schema 或数据类。
  • 返回的 structuredContent 的模式 (outputSchema),以便客户端和模型了解工具结果的形状。
  • 指向模板 URI 的 _meta.ui.resourceUri
  • 可选的 _meta.ui.visibility,用于控制工具是可由模型、UI 还是两者调用。
  • 可选的 ChatGPT 扩展(例如工具运行时的短状态文本)。

模型会检查这些描述符以决定工具何时适合用户的请求,因此请将名称、描述和模式视为您用户体验 (UX) 的一部分。

设计处理程序以实现幂等性——模型可能会重试调用。

// Example app that exposes a kanban-board tool with schema, metadata, and handler.
import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
import { z } from "zod";

registerAppTool(
  server,
  "kanban-board",
  {
    title: "Show Kanban Board",
    inputSchema: { workspace: z.string() },
    outputSchema: {
      columns: z.array(
        z.object({
          id: z.string(),
          title: z.string(),
          tasks: z.array(
            z.object({
              id: z.string(),
              title: z.string(),
              status: z.string(),
            })
          ),
        })
      ),
    },
    _meta: {
      ui: { resourceUri: "ui://widget/kanban-board.html" },
      // ChatGPT extension (optional):
      // "openai/toolInvocation/invoking": "Preparing the board…",
      // "openai/toolInvocation/invoked": "Board ready.",
    },
  },
  async ({ workspace }) => {
    const board = await loadBoard(workspace);
    return {
      structuredContent: board.summary,
      content: [{ type: "text", text: `Showing board ${workspace}` }],
      _meta: board.details,
    };
  }
);

记忆与工具调用

记忆由用户控制并由模型协调:模型在选择或参数化工具调用时决定是否以及如何使用它。默认情况下,记忆在应用中是关闭的。用户可以为应用启用或禁用记忆。应用不会收到单独的记忆流;它们只能看到模型在工具输入中包含的内容。当记忆关闭时,请求会在没有记忆的模型上下文中重新评估。

Memory settings in ChatGPT

最佳实践

  • 保持工具输入明确且对正确性至关重要;不要依赖记忆来处理关键字段。
  • 将记忆视为提示而非权威;当对用户流程很重要且可能产生副作用时,请确认用户偏好。
  • 在缺少上下文时提供安全默认值或询问后续问题。
  • 使工具能够抵御重试、重新评估或缺少记忆的情况。
  • 对于写入或破坏性操作,请在当前轮次中重新确认意图和关键参数。

步骤 3 – 返回结构化数据和元数据

每个工具响应都可以包含三个同级有效负载:

  • structuredContent – 小组件使用模型读取的简洁 JSON。仅包含模型应看到的内容。
  • content – 模型响应的可选叙述(Markdown 或纯文本)。
  • _meta – 专供小组件使用的大型或敏感数据。_meta 永远不会到达模型。
// Returns concise structuredContent for the model plus rich _meta for the widget.
async function loadKanbanBoard(workspace: string) {
  const tasks = await db.fetchTasks(workspace);
  return {
    structuredContent: {
      columns: ["todo", "in-progress", "done"].map((status) => ({
        id: status,
        title: status.replace("-", " "),
        tasks: tasks.filter((task) => task.status === status).slice(0, 5),
      })),
    },
    content: [
      {
        type: "text",
        text: "Here's the latest snapshot. Drag cards in the widget to update status.",
      },
    ],
    _meta: {
      tasksById: Object.fromEntries(tasks.map((task) => [task.id, task])),
      lastSyncedAt: new Date().toISOString(),
    },
  };
}

小组件通过 MCP 应用桥(例如 ui/notifications/tool-result)接收这些有效负载,而模型仅能看到 structuredContentcontent

步骤 4 – 本地运行

  1. 构建您的 UI 包(在 web/ 中运行 npm run build)。
  2. 启动 MCP 服务器(Node, Python 等)。
  3. 尽早并经常使用 MCP Inspector 来调用 https://:<port>/mcp、列出根目录并验证您的小组件是否正确渲染。Inspector 映射了 ChatGPT 的小组件运行时,并在部署前发现问题。

对于 TypeScript 项目,通常看起来像这样

npm run build       # compile server + widget
node dist/index.js  # start the compiled MCP server

步骤 5 – 公开 HTTPS 端点

ChatGPT 要求使用 HTTPS。在开发过程中,使用 ngrok(或类似工具)隧道连接本地主机。

ngrok http <port>
# Forwarding: https://<subdomain>.ngrok.app -> http://127.0.0.1:<port>

在 ChatGPT 开发者模式中创建连接器时使用 ngrok URL。对于生产环境,部署到低延迟的 HTTPS 宿主(Cloudflare Workers, Fly.io, Vercel, AWS 等)。

示例

这是一个精简的 TypeScript 服务器加上原生小组件。有关完整项目,请参考公开的 Apps SDK 示例

// server/src/index.ts
import {
  registerAppResource,
  registerAppTool,
  RESOURCE_MIME_TYPE,
} from "@modelcontextprotocol/ext-apps/server";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

const server = new McpServer({ name: "hello-world", version: "1.0.0" });

registerAppResource(
  server,
  "hello",
  "ui://widget/hello.html",
  {},
  async () => ({
    contents: [
      {
        uri: "ui://widget/hello.html",
        mimeType: RESOURCE_MIME_TYPE,
        text: `
<div id="root"></div>
<script type="module" src="https://example.com/hello-widget.js"></script>
      `.trim(),
      },
    ],
  })
);

registerAppTool(
  server,
  "hello_widget",
  {
    title: "Show hello widget",
    inputSchema: { name: z.string() },
    outputSchema: { message: z.string() },
    _meta: { ui: { resourceUri: "ui://widget/hello.html" } },
  },
  async ({ name }) => ({
    structuredContent: { message: `Hello ${name}!` },
    content: [{ type: "text", text: `Greeting ${name}` }],
    _meta: {},
  })
);
// hello-widget.js
const root = document.getElementById("root");
root.textContent = "Loading…";

const update = (toolResult) => {
  const message = toolResult?.structuredContent?.message ?? "Hi!";
  root.textContent = message;
};

window.addEventListener(
  "message",
  (event) => {
    if (event.source !== window.parent) return;
    const message = event.data;
    if (!message || message.jsonrpc !== "2.0") return;
    if (message.method !== "ui/notifications/tool-result") return;
    update(message.params);
  },
  { passive: true }
);

故障排除

  • 小组件未渲染 – 确保模板资源返回 mimeType: "text/html;profile=mcp-app",并且打包的 JS/CSS URL 在沙箱内可以解析。
  • 没有 ui/* 消息到达 – 宿主仅为 text/html;profile=mcp-app 资源启用 MCP 应用桥;仔细检查 MIME 类型以及小组件是否在没有 CSP 违规的情况下加载。
  • CSP 或 CORS 失败 – 使用 _meta.ui.csp 来允许您从中获取数据的确切域;沙箱会拦截其他一切。
  • 始终加载旧包 – 每当部署破坏性更改时,对模板 URI 或文件名进行缓存清除。
  • 结构化有效负载过大 – 将 structuredContent 削减到模型真正需要的内容;超大的有效负载会降低模型性能并减慢渲染速度。

高级功能

组件发起的工具调用

使用 tools/call 直接从您的 UI 调用工具。默认情况下,工具对模型和 UI 均可见。使用 _meta.ui.visibility 来限制工具的可见范围。

"_meta": {
  "ui": {
    "resourceUri": "ui://widget/kanban-board.html",
    "visibility": ["model", "app"]
  }
}

工具可见性

要使工具可从您的 UI 调用但对模型隐藏,请将 _meta.ui.visibility 设置为 ["app"]。这使得工具可通过 tools/call 对小组件可用,而不会影响模型对工具的选择。

"_meta": {
  "ui": {
    "resourceUri": "ui://widget/kanban-board.html",
    "visibility": ["app"]
  }
}

工具注释与启发

MCP 工具必须包含描述工具潜在影响工具注释 (tool annotations)。这些提示对于工具定义是必需的。

我们关注的三个提示是:

  • readOnlyHint: 对于仅检索或计算信息且不在 ChatGPT 之外创建、更新、删除或发送数据的工具(搜索、查找、预览),请设置为 true
  • openWorldHint: 对于仅影响有限目标的工具(例如在您自己的产品中“按 ID 更新任务”),请设置为 false。对于可以写入任意 URL/文件/资源的工具,请保留为 true
  • destructiveHint: 对于能够删除、覆盖或产生不可逆副作用的工具,请设置为 true

openWorldHintdestructiveHint 仅在写入操作时相关(即 readOnlyHint=false 时)。

准确设置这些提示,以便正确描述工具的影响。

如果您省略这些提示(或将它们保留为 null),请将其视为验证错误,并更新工具定义以包含它们。

示例工具描述符

{
  "name": "update_task",
  "title": "Update task",
  "annotations": {
    "readOnlyHint": false,
    "openWorldHint": false,
    "destructiveHint": false
  }
}

文件处理

ChatGPT 扩展(可选): 如果您的工具接受用户提供的文件,请使用 _meta["openai/fileParams"] 声明文件参数。该值应为应被视为文件的顶级输入模式字段列表。不支持嵌套的文件字段。

文件参数描述了文件处理的输入端:它们告诉 ChatGPT 哪些工具参数包含运行时应授权并作为文件引用传递的文件。

每个声明的文件参数接收一个具有以下形状的对象:

{
  "download_url": "https://...",
  "file_id": "file_...",
  "mime_type": "image/png",
  "file_name": "input.png"
}

download_urlfile_id 是必需的。mime_typefile_name 是可选的。download_url 是临时的,仅应在处理当前工具调用时使用。如果文件引用来自小组件上传、选定的文件或其他工具结果,则在需要从 ChatGPT 请求新的下载 URL 时使用 file_id

示例

import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
import { z } from "zod";

const imageFileSchema = z.object({
  download_url: z.string(),
  file_id: z.string(),
  mime_type: z.string().optional(),
  file_name: z.string().optional(),
});

registerAppTool(
  server,
  "process_image",
  {
    title: "process_image",
    description: "Processes an image",
    inputSchema: {
      imageToProcess: imageFileSchema,
    },
    outputSchema: {
      download_url: z.string(),
      file_id: z.string(),
      mime_type: z.string().optional(),
      file_name: z.string().optional(),
    },
    _meta: {
      ui: { resourceUri: "ui://widget/widget.html" },
      "openai/fileParams": ["imageToProcess"],
    },
  },
  async ({ imageToProcess }) => {
    return {
      content: [],
      structuredContent: {
        download_url: imageToProcess.download_url,
        file_id: imageToProcess.file_id,
        mime_type: imageToProcess.mime_type,
        file_name: imageToProcess.file_name,
      },
    };
  }
);

要从工具返回可下载文件,请在 structuredContent 中包含文件引用,通常在如 file_uri 之类的字段下。

{
  "structuredContent": {
    "file_uri": {
      "download_url": "https://...",
      "file_id": "file_...",
      "mime_type": "application/pdf",
      "file_name": "report.pdf"
    }
  }
}

这是文件处理的输出端。当结果是可下载文件时,您的工具应返回文件引用,而不是内联二进制数据或 base64 内容。ChatGPT 可以使用返回的 file_id 为小组件提供新的临时下载 URL。

ChatGPT 还支持将可下载文件作为 MCP resource_link 内容返回。请将其用于应作为下载内容公开给用户的文件输出。对于直接 Web 下载,请返回 MCP 定义的 https:// 资源 URI。ChatGPT 会在下载文件前征求用户许可。

内容安全策略 (CSP)

在小组件资源上设置 _meta.ui.csp,以便沙箱知道允许哪些域用于 connect-srcimg-srcframe-src 等。这是广泛分发前的必要条件。

"_meta": {
  "ui": {
    "csp": {
      "connectDomains": ["https://api.example.com"],
      "resourceDomains": ["https://persistent.oaistatic.com"],
      "frameDomains": ["https://*.example-embed.com"]
    }
  }
}
  • connectDomains – 您的小组件可以从中获取数据的宿主。
  • resourceDomains – 图像、字体和脚本等静态资源的宿主。
  • frameDomains – 可选;您的小组件可能嵌入为 iframe 的宿主。没有 frameDomains 的小组件无法渲染子框架。

如果您想使用 window.openai.openExternal(...) 而不看到安全链接警告,请在 openai/widgetCSP 下使用 redirect_domains 字段。

警告:不鼓励使用 frameDomains,仅应在嵌入 iframe 对您的体验至关重要时使用(例如代码编辑器或笔记本环境)。声明 frameDomains 的应用在审查时会受到更高程度的审查,并且很可能被拒绝或禁止广泛分发。

小组件域

在小组件资源模板(registerAppResource 模板)上设置 _meta.ui.domain。这是应用提交的必需项,且每个应用必须唯一。ChatGPT 在 <domain>.web-sandbox.oaiusercontent.com 下渲染小组件,这也启用了全屏弹出按钮。

"_meta": {
  "ui": {
    "csp": {
      "connectDomains": ["https://api.example.com"],
      "resourceDomains": ["https://persistent.oaistatic.com"]
    },
    "domain": "https://myapp.example.com"
  }
}

组件描述

ChatGPT 扩展(可选): 在小组件资源上设置 _meta["openai/widgetDescription"],让小组件描述自己,减少小组件下方多余的文本。

"_meta": {
  "ui": {
    "csp": {
      "connectDomains": ["https://api.example.com"],
      "resourceDomains": ["https://persistent.oaistatic.com"]
    },
    "domain": "https://myapp.example.com"
  },
  "openai/widgetDescription": "Shows an interactive zoo directory rendered by get_zoo_animals."
}

本地化内容

ChatGPT 在客户端请求中发送 _meta["openai/locale"](带有 _meta["webplus/i18n"] 作为遗留键)中的请求区域设置。使用 RFC 4647 匹配来选择最接近的支持的区域设置,并在您的响应中回传它,并据此格式化数字/日期。

客户端上下文提示

ChatGPT 还可能在客户端请求元数据中发送提示,如 _meta["openai/userAgent"]_meta["openai/userLocation"]。这些有助于定制分析或格式化,但切勿依赖它们进行授权。

一旦您的模板、工具和小组件运行时连接完毕,优化应用最快的方法是使用 ChatGPT 本身:在真实的对话中调用您的工具,查看日志,并使用浏览器开发工具调试小组件。当一切看起来正常时,将您的 MCP 服务器置于 HTTPS 之后,您的应用就准备好供用户使用了。

公司知识兼容性

ChatGPT 中的公司知识(商业、企业和教育版)可以调用应用中的任何只读工具。它倾向于 search/fetch,且只有实现了 searchfetch 工具输入签名的应用才会被包含为公司知识源。这些是连接器和深度研究所需的相同工具形状(请参阅 MCP 文档)。

在实践中,您应该:

  • searchfetch 输入模式完全实现为 MCP 模式。公司知识兼容性检查仅针对输入参数。
  • readOnlyHint: true 标记其他只读工具,以便 ChatGPT 可以安全地调用它们。

要加入,请使用 MCP 模式实现 searchfetch,并为引用返回规范的 url 值。有关资格、管理员启用和可用性的详细信息,请参阅 ChatGPT 中的公司知识 以及 构建 MCP 服务器 中的 MCP 工具模式。

虽然兼容性检查侧重于输入模式,但您仍应为 searchfetch 返回推荐的结果形状,以便 ChatGPT 可以可靠地引用来源。text 字段在您的工具响应中是 JSON 编码的字符串。

搜索结果形状(MCP 包装前的工具有效负载)

{
  "results": [
    {
      "id": "doc-1",
      "title": "Human-readable title",
      "url": "https://example.com"
    }
  ]
}

字段

  • results - 搜索结果数组。
  • results[].id - 文档或项目的唯一 ID。
  • results[].title - 人类可读的标题。
  • results[].url - 引用的规范 URL。

在 MCP 中,将此 JSON 作为 structuredContent 返回,并将相同的值作为 JSON 字符串包含在 content 中以实现兼容性。

搜索工具响应包装器

{
  "structuredContent": {
    "results": [
      {
        "id": "doc-1",
        "title": "Human-readable title",
        "url": "https://example.com"
      }
    ]
  },
  "content": [
    {
      "type": "text",
      "text": "{\"results\":[{\"id\":\"doc-1\",\"title\":\"Human-readable title\",\"url\":\"https://example.com\"}]}"
    }
  ]
}

获取结果形状(MCP 包装前的工具有效负载)

{
  "id": "doc-1",
  "title": "Human-readable title",
  "text": "Full text of the document",
  "url": "https://example.com",
  "metadata": { "source": "optional key/value pairs" }
}

字段

  • id - 文档或项目的唯一 ID。
  • title - 人类可读的标题。
  • text - 文档或项目的全文。
  • url - 引用的规范 URL。
  • metadata - 关于结果的可选键/值对。

对于 fetch,以相同方式返回文档 JSON。

获取工具响应包装器

{
  "structuredContent": {
    "id": "doc-1",
    "title": "Human-readable title",
    "text": "Full text of the document",
    "url": "https://example.com",
    "metadata": { "source": "optional key/value pairs" }
  },
  "content": [
    {
      "type": "text",
      "text": "{\"id\":\"doc-1\",\"title\":\"Human-readable title\",\"text\":\"Full text of the document\",\"url\":\"https://example.com\",\"metadata\":{\"source\":\"optional key/value pairs\"}}"
    }
  ]
}

这是一个显示 searchfetch 工具的最小 TypeScript 示例。

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

const server = new McpServer({ name: "acme-knowledge", version: "1.0.0" });

const searchOutputSchema = {
  results: z.array(
    z.object({
      id: z.string(),
      title: z.string(),
      url: z.string().url(),
    })
  ),
};

const fetchOutputSchema = {
  id: z.string(),
  title: z.string(),
  text: z.string(),
  url: z.string().url(),
  metadata: z.record(z.string(), z.string()).optional(),
};

server.registerTool(
  "search",
  {
    title: "Search knowledge",
    inputSchema: { query: z.string() },
    outputSchema: searchOutputSchema,
    annotations: { readOnlyHint: true },
  },
  async ({ query }) => {
    const structuredContent = {
      results: [{ id: "doc-1", title: "Overview", url: "https://example.com" }],
    };

    return {
      structuredContent,
      content: [{ type: "text", text: JSON.stringify(structuredContent) }],
    };
  }
);

server.registerTool(
  "fetch",
  {
    title: "Fetch document",
    inputSchema: { id: z.string() },
    outputSchema: fetchOutputSchema,
    annotations: { readOnlyHint: true },
  },
  async ({ id }) => {
    const structuredContent = {
      id,
      title: "Overview",
      text: "Full text...",
      url: "https://example.com",
      metadata: { source: "acme" },
    };

    return {
      structuredContent,
      content: [{ type: "text", text: JSON.stringify(structuredContent) }],
    };
  }
);

安全提醒

  • structuredContentcontent_meta 和小组件状态视为用户可见——切勿嵌入 API 密钥、令牌或机密。
  • 不要依赖 _meta["openai/userAgent"]_meta["openai/locale"] 或其他提示进行授权;在您的 MCP 服务器和后端 API 中实施授权。
  • 除非服务器验证了调用者的身份和意图,否则避免公开仅限管理员使用的或破坏性的工具。
© . This website operates independently and is not affiliated with or endorsed by OpenAI, Inc. All brand names, logos, and trademarks are the property of their respective owners.