Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
inspector — 从 Web UI、CLI 或 TUI 检查、调试并可视化测试 Model Context Protocol (MCP) 服务器,支持工具/资源探索、请求日志记录和 OAuth 认证。 | Kitploit
工具/GitHubGitHub/modelcontextprotocol/inspector
脚本与自动化调试器实用工具与框架身份验证
GitHubmodelcontextprotocol/inspector

inspector

从 Web UI、CLI 或 TUI 检查、调试并可视化测试 Model Context Protocol (MCP) 服务器,支持工具/资源探索、请求日志记录和 OAuth 认证。

查看仓库网站
10.7k1.5k20小时35分前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

MCP Inspector

用于检查 Model Context Protocol (MCP) 服务器的开发者工具。它作为一个单一包 @modelcontextprotocol/inspector 发布,提供了三种检查服务器的方式:

  • Web — 一个基于 Vite + React + Mantine 的单页应用,配备 Node 后端。
  • CLI — 一个可脚本化的命令行客户端,用于自动化、CI 和快速的智能体反馈循环。
  • TUI — 一个基于 Ink 构建的交互式终端 UI。

这三种方式都通过同一个全局 mcp-inspector 二进制文件运行:```bash npx @modelcontextprotocol/inspector # web UI (default) npx @modelcontextprotocol/inspector --cli # CLI npx @modelcontextprotocol/inspector --tui # TUI

root@kitploit:~
> **从 v1 升级?** 阅读 [v1 → v2 迁移指南](https://github.com/modelcontextprotocol/inspector/blob/HEAD/docs/v1-to-v2-migration.md) — CLI 标志、新的 `--config` 与 `--catalog` 拆分、Node 引擎版本升级,以及不再随附的内容。

> **仓库状态。** 这是 Inspector 的 **v2** 产品线。积极开发在 **`v2/main`** 上进行(开发分支 — 所有 v2 PR 都指向它),该分支在里程碑发布时合并到 **`main`**;`main` 是默认分支,包含最新发布的 v2,并发布到 npm 的 `latest` 标签。旧版 **v1** 产品线保留在 **`v1/main`** 上 — 仅提供安全修复,直接从该分支发布到 npm 的 `v1-latest` 标签(`npx @modelcontextprotocol/inspector@v1-latest`)。分支/看板约定请参阅 [`AGENTS.md`](https://github.com/modelcontextprotocol/inspector/blob/HEAD/AGENTS.md)。

## 项目结构

v2 **不是** npm 工作区。`clients/*` 下的每个客户端都拥有自己的 `package.json` 和 `node_modules`;共享代码位于 `core/` 中,通过 `@inspector/core` 构建时别名来使用(其自身没有 `package.json`)。在根目录执行一次 `npm install` 会级联安装到每个客户端(参见 [安装](#setup))。```
inspector/
├── clients/
│   ├── web/          # Web client (Vite + React + Mantine). src/ = browser app; server/ = Node dev/prod backend
│   ├── cli/          # CLI client (tsup bundle, @inspector/core alias)
│   ├── tui/          # TUI client (Ink + React, tsup bundle)
│   └── launcher/     # Shared launcher — provides the `mcp-inspector` bin, dispatches to web/cli/tui
├── core/             # Shared code consumed via the `@inspector/core` alias (no package.json)
│   ├── auth/         # OAuth: providers, discovery, storage, endpoint overrides, mid-session recovery (browser/node/remote backends)
│   ├── client/       # Install-level client config (`client.json`): browser-safe parse/validate + Node load/save, remote backend, secrets
│   ├── json/         # JSON + parameter/argument conversion utilities, and the nullable-union
│   │                 #   schema collapse shared by the web and TUI form builders
│   ├── logging/      # Silent pino logger singleton
│   ├── mcp/          # InspectorClient runtime, state stores, transports, config import,
│   │                 #   and the RFC 6570 URI-template helpers the web form and TUI expand through
│   ├── node/         # Node-only shared helpers: version reader, hostUrl (host normalize/canonicalize + all-interfaces/loopback detection)
│   ├── react/        # React hooks over the state stores
│   └── storage/      # File I/O helpers for the OAuth persist backends
├── test-servers/     # Composable MCP test servers + fixtures used by integration tests
├── scripts/          # Root build/verify tooling (install cascade, smokes, verify-build-gate, verify-format-coverage, verify-dep-lockstep, pack:verify)
├── docs/             # Task-oriented guides (v1→v2 migration, server configuration, MCP App review, launcher/config plan)
├── specification/    # Design/build specifications
├── AGENTS.md         # Contribution rules for agents AND humans (see below)
└── README.md         # You are here

Each client has its own README with client-specific detail: web · cli · tui · launcher.

Task-oriented guides live under docs/:

  • Migrating from v1 to v2 — the v1 → v2 map: CLI flag mapping, --config vs. --catalog semantics with before/after examples, the Node engine bump (>=22.7.5 → >=22.19.0), env-var renames, and the sub-packages that no longer ship.
  • MCP server configuration — which server(s) the Inspector connects to: --catalog vs. --config, ad-hoc targets, the -- separator, the file format and its Inspector-specific per-server fields. Shared by all three clients; the cli and tui READMEs delegate their server-options sections to it.
  • Reviewing an MCP App — the CLI-first → one-shot-web recipe for automated App-tool review: --app-info probe → deep-link navigate → rendered widget, plus OAuth handoff and proxy support.
  • Launcher and config consolidation — why the launcher runs a client in-process rather than spawning it, and how the shared config processor fits in.

Setup

Requires Node >=22.19.0.```bash npm install # root install; postinstall cascades into every client

root@kitploit:~
- **全新克隆:** 在仓库根目录运行 `npm install`。
- **拉取更新导致某个客户端的依赖发生变化后:** 在根目录重新运行 `npm install` 以重新同步所有客户端。

级联脚本(`scripts/install-clients.mjs`)仅用于开发——当该包作为依赖被安装时会提前退出,且发布的 tarball 只包含每个客户端的 `build/`,因此最终用户不受影响。设置 `INSPECTOR_SKIP_CLIENT_INSTALL=1` 可跳过此步骤。

**依赖声明的位置。** MCP SDK 包(`@modelcontextprotocol/client`、`core`、`server`、`server-legacy`、`ext-apps`)只存在于**根目录**的 `package.json` 中——绝不会出现在客户端的 `package.json` 里。Node 的解析会向上查找,因此根目录的安装位于每个客户端的依赖链上,而已发布的 tarball 本来就是依据根目录清单进行解析的。在每个客户端中分别声明它们会安装第二份副本,该副本可能与根目录中的那份发生漂移——这正是 [#1970](https://github.com/modelcontextprotocol/inspector/issues/1970) 之前依赖树中出现两个版本 `ext-apps`(以及传递依赖的 v1 `@modelcontextprotocol/sdk`)的原因;而 `client`/`core` 的第二份副本正是 `vitest.shared.mts` 携带 `dedupe` 变通方案所要解决的问题。同样的“仅根目录”放置原则也适用于所有仅通过根目录自有代码访问、且没有自身清单的包(`test-servers/src`、`core/`),`vitest.shared.mts` 会将它们别名指向仓库根目录——目前有两个这样的包:`express` 和 `yaml`,两者都经由 `test-servers/src` 访问。**此类包究竟是 `dependency` 还是 `devDependency`,取决于谁在运行时消费它,而非取决于声明位置:** 任何被 `core/` 在运行时导入的包都必须是根目录的 **`dependency`**,因为客户端构建会将 npm 包外部化,而已发布的安装会从根目录清单中解析这些包,而根目录清单中不包含 devDependencies。`express` 仅用于测试,属于 devDependency;`yaml` 目前位于 `dependencies` 中。**`vite` 和 `@vitejs/plugin-react` 出于同样的原因被列为根目录 `dependencies`,而非误配置**——它们看起来像构建工具,但 `clients/web/server/start-vite-dev-server.ts` 会在运行时为 `mcp-inspector --web --dev` 导入它们,且 `clients/web/tsup.runner.config.ts` 将两者都列为 `external`,因此已发布的安装会从根目录清单中解析它们。如果将两者移到 `devDependencies`,会破坏消费者的 `--web --dev`(以及 `ensure-web-build.ts` 中按需执行的 `vite build`),同时却仍能通过所有本地检查。这确实意味着它们会出现在 `npm audit --omit=dev` 的结果中,这其实是一个特性:它们确实位于生产依赖树中。

## 开发期间的运行方式

日常 Web 迭代时,直接从 Web 客户端运行 Vite(快速 HMR,无需构建启动器):```bash
cd clients/web && npm run dev

下面的启动器驱动脚本运行已构建的启动器,因此请先构建(npm run build):```bash npm run web # prod web launcher against clients/web/dist npm run web:dev # web launcher in --dev mode (Vite)

root@kitploit:~
## `@inspector/core` 共享包

![共享代码架构:四个客户端基于 @inspector/core 共享包](https://assets.kitploit.com/production/public/readmes/50997/12951c8cb492b9d753ea13b98a8ea2475ef2e250a9e849227b05c3cfc7c5d31c/962dd293aaae81c94af868f086686d56189155e63449bfc6b537818e8725c812-display-v1.webp)

`core/` 保存所有三个客户端共享的逻辑,使 web、CLI 和 TUI 行为一致。其入口是 **`InspectorClient`** 类(`core/mcp/`),它管理到 MCP 服务器的连接、请求/响应生命周期以及一组状态存储;`core/react/` 暴露基于这些存储的 React hooks,web 和 TUI(Ink)的 React 树都会消费这些 hooks。OAuth(`core/auth/`)被拆分为同构逻辑以及浏览器/Node/远程后端,因此相同的流程可以在浏览器、Node 和远程后端上运行。

`core/` 有意**没有 `package.json`**——它不单独发布。每个客户端通过 `@inspector/core` 别名将其打包进去:

- **CLI / TUI:** 其 `tsup.config.ts` 中的 `esbuildOptions.alias` 将 `@inspector/core` → 映射到仓库的 `core/` 目录,`noExternal: [/^@inspector\/core/]` 将其内联到 bundle 中。
- **Web:** 在 `clients/web/vite.config.ts` 中为浏览器应用和 Node 后端运行器使用相同的别名。

将 `core/` 作为独立包发布(例如供第三方在其上构建)被有意推迟——见 issue [#1636](https://github.com/modelcontextprotocol/inspector/issues/1636)。

## Web 客户端:“dumb components” + Storybook

v2 web 客户端由 **表示性(“dumb”)组件**构成——它们通过 props 接收数据和回调,只包含展示逻辑,不直接进行数据获取或持有客户端状态。状态来自 `@inspector/core` hooks,在组件树顶部附近接入。这使组件保持隔离、可测试和可文档化。

正是这种方法使 **Storybook** 在这里成为一等公民:每个屏幕和元素组件都有一个 `*.stories.tsx` 文件(96+ 个 stories),用 fixture props 渲染组件。Storybook **play 函数**兼作交互测试,在 CI 中无头运行(`npm run ci:storybook`,通过 Playwright 使用 Chromium)。

样式遵循严格的 Mantine-first 约定(优先使用主题变体和组件 props 而非 CSS 类,优先使用 `--inspector-*` CSS 自定义属性而非原始颜色字面量)。完整规则见 [`AGENTS.md`](https://github.com/modelcontextprotocol/inspector/blob/HEAD/AGENTS.md) 中的 **React 指令**——在修改 web UI 之前请先阅读。元素组件位于 `clients/web/src/components/elements/`;主题变体位于 `clients/web/src/theme/`。

## 测试服务器

`test-servers/` 提供**可组合的 MCP 服务器**,供集成测试和冒烟测试套件使用,从而让测试通过真实传输协议与真实服务器交互,而不是使用 mock。服务器由 **presets** 组装而成(`test-servers/src/preset-registry.ts` 中的 fixture 工厂——工具、资源、提示、任务、elicitation、sampling、OAuth 等),可以通过两种方式驱动:

- **进程内**——导入工厂(`createTestServerHttp`、`createEchoTool` 等)并在测试的事件循环内运行服务器(用于 HTTP 集成路径)。
- **作为子进程**——`test-servers/build/test-server-stdio.js` 作为真正的 stdio 子进程被生成(用于 CLI 冒烟测试和 stdio 集成测试)。

通过 JSON 配置声明式地配置服务器(参见 `test-servers/configs/*.json`)选择 presets,然后通过 `--config` 加载。由于服务器是以真实子进程方式生成的,所以必须先生成构建输出:```bash
npm run test-servers:build   # (from clients/web) → tsc -p test-servers, emits test-servers/build/

The Vite 别名 @modelcontextprotocol/inspector-test-server(位于 clients/web/vite.config.ts)指向 test-servers/build/index.js,因此 getTestMcpServerPath() 会解析为真实的 .js 路径。

服务现代协议时代

可流式 HTTP 服务器也可以通过 SDK 的 createMcpHandler 来服务现代(2026-07-28)协议时代:

  • 在 JSON 配置中设置 transport.modern — true 表示双时代无状态服务,或 { "legacy": "reject" } 表示仅现代严格模式。
  • 或者在 ServerConfig 上传递 modern,用于进程内的 createTestServerHttp。

正是这一点让协商 protocolEra: "auto" | "modern" 的 Inspector 连接能够触达现代端(填充了 server/discover,无会话)。参见 test-servers/configs/modern-http.json。

展示配置

下面的每个配置都是一个开箱即用的服务器,便于手动演练某一项功能。使用 --config 加载;除非另有说明,请使用 协议时代 = 现代 连接。

MCP 应用

mcp-app-http.json 提供 mcp_app_demo 工具(_meta.ui.resourceUri)及其 UI 资源 mcp_app_demo_widget,因此 Apps 选项卡有了真实的应用可渲染。它是一个普通的可流式 HTTP 服务器 — 请使用默认(legacy)协议时代连接,而不是现代。

打开 Apps 选项卡,选择 mcp_app_demo,为其指定标题并点击 打开应用:该小组件会在沙箱 iframe 内渲染,并演练宿主端 UI 协议面 — 宿主上下文渲染、size-changed、ui/message,以及向 App logs 面板写入一行日志。由于该小组件是通过沙箱代理页面提供的,因此这个配置也正是用来复现 #1859 的(缺少的 clients/web/static/sandbox_proxy.html 会在此处以 "Sandbox 未加载" 消息代替小组件呈现)— 这种故障只会在已安装的软件包中出现,在仓库中从未出现。

关于同一流程的脚本化版本(--app-info 探测 → 深链接 → 渲染后的小组件),请参阅 审查 MCP 应用。

MRTR

modern-mrtr-http.json 在现代端提供 mrtr_confirm 工具(预设 mrtr_confirm、createMrtrTool)。其处理器返回嵌入了表单征询的 inputRequired(...),因此调用它会产生一次真实的往返:input_required → 客户端完成嵌入的征询并以新的 id 重试 → complete。

Inspector 手动驱动 MRTR(inputRequired: { autoFulfill: false }),因此嵌入的征询会停留在待处理请求模态框(标记为 "input_required")处等待您作答,随后重试完成。这对于直观查看待处理请求的用户体验以及 Protocol 视图中的 MRTR 会话分组都很有用。

mrtr-showcase-http.json 在一个服务器中打包了所有 MRTR 预设:

运行 mrtr_empty 并回答其单项征询:Protocol 选项卡会将这次交互分组为一个以 COMPLETE 结束的 MRTR 会话,Results 面板显示 "空结果 — 工具调用已成功完成且未返回任何内容。" 在有缺陷的构建中,同样的结果渲染为 "尚无结果",即面板的运行前占位符(#1860)— 因此用户刚刚亲眼看到成功执行的调用,读起来却像是从未运行过。没有 structuredContent 的空 content 数组是合法的 CallToolResult,而面板只会在结果存在之后才挂载,因此占位符的措辞在那里不可能成立。(同一缺陷的另一半 — 负载仅存在于 structuredContent 中的结果 — 已由 #1908 修复。)

legacy 的 collect_elicitation 预设调用 server.elicitInput,后者在 2026-07-28 端上会报错 — 该端不允许 server→client 请求。MRTR 是现代替代方案。

Network 选项卡 — 标准化标头与错误分类

modern-network-http.json 涵盖 SEP-2243 / SEP-2575。它提供 get_weather 工具,其 city 参数带有 x-mcp-header: "City" 注解,因此现代客户端会将其镜像为 Mcp-Param-City。

它还提供四个 trigger_* 工具,现代端的规范错误注入器(transport.modern.injectSpecErrors: true)会用真实的 HTTP 状态码加上 JSON-RPC 错误体来应答:

打开 Network 选项卡,即可看到镜像的 Mcp-* 标头被高亮、哨兵值被解码,以及每个错误以不同的方式渲染。

Mcp-Param-* 镜像由 Inspector 构建,而非 SDK。 SDK 只在 client.callTool() 内部进行镜像,并在浏览器中跳过(detectProbeEnvironment() !== "browser")。Inspector 将 tools/call 通过 client.request() 路由,以便手动驱动 MRTR,因此它自行构建镜像标头(#1846)— 在每个客户端上都是如此,包括 web,因为 web 客户端的上游请求由 Node 后端发出,而非浏览器。因此 get_weather 可以通过 web、CLI 和 TUI 调用,普通形式和"以任务方式运行"形式均可。

Tools 选项卡中的 x-mcp-header

xmcpheader-modern-http.json 提供:

  • echo — 普通工具。
  • get_weather — 其 city 参数上的一个有效 x-mcp-header: "City" 注解。
  • invalid_header_tool — 使用标头名称 "Bad Header" 的注解。空格使其成为无效的 RFC 9110 标记,因此整个工具定义无效。
  • trigger_invalid_params — 以真实的 -32602 Invalid params 错误应答,其消息_并非_关于缺少工具。

打开 Tools 选项卡:get_weather 的详情面板显示一个 "镜像的请求标头(SEP-2243)" 部分(city → Mcp-Param-City),invalid_header_tool 在 "已排除(SEP-2243)" 分隔线下以删除线显示,悬停时显示原因。符合规范的 Streamable HTTP 客户端必须将其从 tools/list 中丢弃;Inspector 则展示了_原因_。

在 SDK v2 下,以 -32602 拒绝的 tools/call 会渲染为独立的错误面板,而不是 isError 结果 — 当消息指明缺少某个工具时,标题为 "未知工具",否则为 "无效参数"(运行 trigger_invalid_params 即可验证)。

逐页获取

pagination-http.json 提供 12 个工具、12 个资源和 12 个提示(预设 numbered_tools / numbered_resources / numbered_prompts,count: 12),每个的 maxPageSize 均为 4,因此每个列表都会分页为三页。

开启 "逐页获取列表"(服务器设置 — paginatedLists 设置,或列表侧边栏中的 分页 开关)后,列表仅加载第 1 页(4 项),带有一个 加载下一页 控件和 已加载 N 页 状态。每次点击都会获取接下来的 4 项并追加;刷新会重置回第 1 页。关闭该开关(默认)时,同样的列表会在连接时自动聚合全部三页。

结构化输出

structured-output-http.json 提供 list_items(嵌套的 structuredContent — 对象内数组中的对象,即 #1908 的形态)、get_temp(扁平的三键负载)和 echo(完全没有 outputSchema)。它是一个普通的可流式 HTTP 服务器 — 请使用默认(legacy)协议时代连接。

从 Tools 选项卡运行 list_items:结果面板显示 content[] 文本摘要("找到 2 个项目。")以及一个可折叠的 结构化输出 部分,将经模式校验的负载渲染为美化打印、可复制的 JSON。该部分正是 v2 所遗漏的 — 声明了 outputSchema 的工具会在那里返回真实数据,而文本块通常只是概述。运行 echo 可确认:当结果不带 structuredContent 时,该部分不存在。

重复的工具名称

duplicate-tool-names-http.json 提供 get_weather、get_temp、echo 和 add,然后在 tools/list 末尾以相同的 name 和 (duplicate) 标题重复 get_weather 和 echo(duplicateToolNames)。没有任何预设能产生这种形态 — SDK 的 registerTool 会拒绝重复的名称 — 但真实服务器可以、也确实会如此,而 Inspector 必须忠实地渲染它。

连接(默认 legacy 时代)后,打开 Tools 选项卡,并在 搜索工具 中输入 get:列表必须精确收窄为三行 get_*。在有缺陷的构建中,它保留了一行过期的 echo,因为侧边栏仅以 tool.name 作为行的键,冲突的键在协调过程中孤立了一个子项(#1957)。

重复的副本故意采用追加方式,而非放在其孪生项旁边。React 会先匹配开头一段同键的子项,因此紧邻头部的重复项恰好能对齐,缺陷便被掩盖;将两者分开才能让缺陷可见 — 这也正是现实的形态:两个工具源拼接在一起。

可空参数

nullable-fields-http.json 提供 record_shipment,其四个参数均以 Zod 的 .nullish() 声明 — "可选且显式可空"。这会编译为 anyOf: [<branch>, { "type": "null" }],因此真正的类型(以及枚举的 enum 列表)位于分支上,而非顶层。get_temp 与之并列,带有一个普通、不可空的 units 枚举以供对比。普通可流式 HTTP — 请使用默认(legacy)协议时代连接。

打开 Tools 选项卡并选择 record_shipment:direction 必须渲染为下拉选择框(envio / recebimento),带有一个可将其重置为 null 的清除按钮;reference 渲染为文本输入框;quantity 渲染为数字输入框;express 渲染为复选框。在有缺陷的构建中,它们每一个都回退到了原始 JSON 文本域,该文本域在每次按键时都会重新转义自身内容,直到该值完全无法使用(#1928)。该工具会回显收到的参数,因此结果面板会精确显示发送的内容。

TUI 也存在同样的缺陷,值得针对同一服务器进行检查(--tui,然后测试 record_shipment):direction 是下拉选择框,quantity 是整数字段,express 是布尔值。两个客户端现在共享同一个折叠步骤 — core/json/nullableUnion.ts 中的 normalizeNullableUnion — 正是为了确保它们在可渲染的模式上不会产生分歧。

RFC 6570 资源模板

rfc6570-templates-http.json 直接提供来自 #1919 的两个资源模板 — events_by_topic(foobar://events/{topic})和 events_by_query(foobar://events{?topic})— 每个模板都会回显与其匹配的 URI,外加一个普通的 foobar://events 资源(见下文)。普通可流式 HTTP;请使用默认(legacy)协议时代连接。

打开 Resources 选项卡并选择 events_by_topic,然后输入 foo/bar。请求必须以 foobar://events/foo%2Fbar 发出,结果会回显服务器匹配到的 URI。在有缺陷的构建中,该值被原样拼接,因此斜杠产生了第二个路径段,SDK 的匹配器返回 -32602 Resource not found: foobar://events/foo/bar — 正是该 issue 中的确切故障。?、#、%、空格和非 ASCII 文本也同样如此。

events_by_query 是之前不可见的那一半:旧的 /\{(\w+)\}/g 扫描无法识别带运算符的表达式,因此根本没有渲染任何 topic 输入框。现在它出现了,并标记为 可选 — RFC 6570 在变量未定义时会丢弃整个表达式,因此字段留空时读取会请求 foobar://events,填写后则请求 foobar://events?topic=foo%2Fbar。标题旁的 URI 预览会在您输入时显示部分展开的形式,未填写的表达式保持原样。

普通的 foobar://events 资源是特意注册的,而非填充。SDK 的 UriTemplate.match() 将 {?topic} 编译为必需的 \?topic=([^&]+),因此仅凭模板无法服务空白读取 — match("foobar://events") 返回 null。真实服务器会将未过滤的集合作为自己的资源公开;展示服务器也这样做,以便该步骤真正得到解析。

web 客户端和 TUI 通过同一个共享助手进行展开,即 core/mcp/uriTemplate.ts — web 直接通过 Resources 表单,TUI 则通过 InspectorClient.readResourceFromTemplate — 而且两者也从其解析器派生表单字段,这正是让这种共享真正成立的一半:表单以其渲染出的名称提交值,因此一个破坏了名称的解析器会在展开时静默丢弃该值。(CLI 不是使用者:它没有模板表单,其 resources/read 直接透传已展开的 --uri。)

SDK 的 UriTemplate 仍在使用,但仅用于验证模板(构造它正是拒绝未闭合表达式的方式)。其展开器则未被使用,因为它在五个方面不完整 — 每一项都是对照锁定的 SDK 测得的,而非推断的:

; 和 :3 这两行是用户直接可见的:在 SDK 的解析下,表单渲染出的字段会被原样标注为 ;id 和 id:3。+/# 这一行是静默损坏而非过度转义 — IPv6 字面量或已编码的路径到达服务器时已被改动。

完全无法展开的模板 — 语法外修饰符({id:abc}),或未声明任何变量的表达式({}、{a,}、{?})— 会阻止读取,而不是发送某些内容。选择 events_malformed(foobar://events/{topic:abc})即可看到效果:读取资源被禁用,原因打印在表单下方,预览按服务器声明的形式显示模板。替代方案比看起来更糟:否则 x://{} 会展开为 x:// 且不渲染任何输入,因此表单的"所有必填项均已填写"检查会空洞地通过,并读取一个并非服务器所发布模板的 URI。

字面量在展开时也会进行 pct 编码(RFC 6570 §3.1):café/{var} 发送的是 caf%C3%A9/value,而不是路径中的原始 UTF-8 — SDK 的展开器同样不这样做。模板可能使用的名称是 RFC 6570 的 varchar,外加对 - 和 ~ 的标注容差:一致性测试套件会拒绝 {default-graph-uri},但真实服务器会发布此类名称,且 SDK 的匹配器能对它们进行往返处理,因此 Inspector 会展开它们并将变量标记为 conforming: false,而不是拒绝一个已被证明可用的资源。

未定义的变量才会省略其表达式 — 定义为空字符串的变量会展开(根据 RFC 6570 §3.2.7,x{?q} 得到 x?q=,x{;q} 得到 x;q)。展开器尊重这一区别,因此 readResourceFromTemplate 之类的调用方可以请求任一 URI。将两者合并是表单层面的问题,而非模板层面的:两个客户端都会将每个已声明的变量以 "" 初始化,而文本输入框无法表达"已定义但为空",因此每个表单在提交时会丢弃其空白项(definedValues)。

必填性是表达式的属性,而非变量的属性:RFC 6570 会从多名称表达式中丢弃未定义的名称,因此只填写了 a 的 {a,b} 是可展开的,表单不得阻止它。requiredGroups 为每个不可省略的表达式返回一个条目,hasRequiredValues 要求每个表达式由其任一名称满足 — 一旦某个名称在多个表达式中重复出现,任何按变量设置的标志都无法表达这一点(填写 b 和 c 即可满足 {a,b}{a,c})。#### 声明的扩展

advertised-extensions-http.json 提供 echo(始终)和一个 get_weather 工具,由 io.modelcontextprotocol/tasks 扩展门控(extensionGatedTools):该工具已注册但初始为禁用状态,服务器仅在客户端在其 capabilities.extensions 中声明了该扩展时,才在 notifications/initialized 上启用它。

  1. 连接 — Inspector 默认声明 Tasks 扩展,因此工具列表同时显示 echo 和 get_weather。
  2. 打开 服务器设置 → 声明的扩展,取消勾选 Tasks (io.modelcontextprotocol/tasks),然后重新连接。
  3. 现在客户端不声明任何扩展,服务器永远不会启用 get_weather,工具列表只显示 echo。

这是用于调试服务器根据客户端声明的内容而合法更改工具注册的开关。仅限传统有状态一侧——现代按请求一侧没有持久的 oninitialized。

日志记录:两个时代

logging-legacy-http.json 和 logging-modern-http.json 都提供 logging: true 以及一个 send_notification 工具,该工具以所选级别发出 notifications/message。传统版本是一个普通的 streamable-HTTP 服务器;现代版本设置了 transport.modern: true。

  • Legacy — 日志标签页提供一个会话范围的设置活动级别选择器和设置按钮。调用 send_notification 会将日志流式传输到面板中。
  • Modern — 同一标签页改为显示按请求记录日志级别。选择一个级别以选择加入,客户端会在每个后续请求上盖上 _meta["io.modelcontextprotocol/logLevel"](在“网络”标签页的请求体中验证)。调用 send_notification 会通过请求的 SSE 响应流式传输日志。将其设回关闭后,同样的调用会被静默拦截——请求省略 logLevel 键,因此日志永远不会到达。

这种拦截符合规范(“服务器不得为未选择加入的请求发出 notifications/message”),因为 send_notification 通过 SDK 的请求作用域且阈值感知的 extra.log(ctx.mcpReq.log)发出消息。在现代侧,它从请求信封中读取按请求的 logLevel 选择加入设置,并在客户端未选择加入或级别低于请求的严重性阈值时丢弃消息;在传统侧,它遵循 logging/setLevel 中的会话级别。由于它是通过请求的 notify 发出的,现代响应会升级为 SSE,日志会沿着原始请求的流传输。

资源订阅:两个时代

subscriptions-legacy-http.json 和 subscriptions-modern-http.json 都提供三个 numbered_resources 资源,且 subscriptions: true。传统版本还提供一个 update_resource 工具;现代版本设置了 transport.modern: true。

  • Legacy — 在资源标签页中打开一个资源,然后点击订阅。客户端发送 resources/subscribe,订阅部分列出该 URI,且没有任何流式界面装饰。使用该 URI 调用 update_resource 后,服务器会更新内容并发出 notifications/resources/updated,更新已订阅卡片的上次更新时间。
  • Modern — 同样的订阅改为发送 subscriptions/listen(其过滤器携带 resourceSubscriptions 以及 resourcesListChanged 选择加入项),并在 notifications/subscriptions/acknowledged 时完成解析。订阅部分随后在其标题中显示流状态徽章(Connecting… → Listening),如果长连接流断开,则通过重新列出资源来重连。

现代配置故意省略了 update_resource。SDK 的现代侧是无状态/按请求的(createMcpHandler(() => createMcpServer(config))),因此该工具将在一次性的服务器实例上运行——内容更改不会为下一次 resources/read 保留,其 resources/updated 也不会到达独立的监听流。与其说有用,不如说令人困惑。

因此,实时的更新通知往返在传统(有状态会话)服务器上演示,而现代服务器用于订阅/监听/徽章行为。Inspector 的 receive 路径与时代无关,因此一个真实的有状态现代服务器,如果能把 resources/updated 路由到监听流,就能以同样的方式驱动已订阅的卡片。

任务:两个时代

Legacy(tasks-legacy-http.json)通过 simple_task / progress_task / elicitation_task 预设声明 capabilities.tasks(tasks: { list, cancel })。在开启作为任务运行的情况下运行其中一个工具,任务标签页会列出它(通过 tasks/list 填充)、轮询 tasks/get、使用阻塞式 tasks/result 获取负载,并使用 tasks/cancel 取消。

Modern(tasks-modern-http.json)设置 transport.modern: true 和 tasksExtension: true,声明 io.modelcontextprotocol/tasks 扩展(SEP-2663),并提供 modern_task / modern_input_task。任务标签页的开关取决于协商后的扩展,而不是 capabilities.tasks。

  • 作为任务运行 modern_task —— tools/call 返回一个 CreateTaskResult(resultType: "task",可在协议/网络标签页中看到),客户端轮询 tasks/get(没有 tasks/list),完成后的任务会内联其结果(没有阻塞式 tasks/result)。
  • 运行 modern_input_task —— 任务进入 input_required 状态,通过待处理请求模态框呈现内嵌的征询。回答后发送**tasks/update** 及 inputResponses,下一次轮询即完成。

SDK v2 移除了所有任务支持,并且在两侧将 tasks/* 规范方法按时代门控,使其无法进入现代时代。因此 Inspector 自行驱动该扩展——resultType: "task" 帧在传输层被重写为携带句柄的 CallToolResult,而 tasks/get / update / cancel 通过原始线路请求通道并携带完整的现代信封进行传输。测试服务器在 SDK 处理器之前通过 Express 拦截器提供 tasks/*,因为 SDK 的现代侧会用 -32601 回答它们。

任务标签页的刷新会重新轮询客户端已知的句柄——现代模式没有服务器端任务列表。

构建```bash

npm run build # builds all clients: web → cli → tui → launcher

root@kitploit:~
各个客户端:`build:web`、`build:cli`、`build:tui`、`build:launcher`。Web 构建同时生成浏览器 SPA(`clients/web/dist`,Vite)和 Node 生产服务器运行器(`clients/web/build`,tsup)。

## 测试与质量门禁

每个客户端都从自己的目录进行自我校验;根脚本将它们串联起来。**没有**聚合的根 `test` 脚本——请使用 `validate`(快速)或 `coverage`(门禁)。

| 脚本                              | 说明                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `npm run validate`                  | 首先运行三个持久性守卫——`verify:format-coverage`(每个被跟踪的源文件都受格式门禁约束)、`verify:typecheck-coverage`(每个文件都归属于某个 tsconfig 项目)、`verify:dep-lockstep`(没有任何依赖会从两个安装进入同一个 `tsc` 程序并产生版本偏差)——然后运行 `test:scripts`(守卫自身的解析器单元测试),再运行 `validate:core`(共享 `core/` 的 `format:check` + `lint` 门禁),接着对每个客户端运行:`format:check` + `lint` + **`typecheck`**(cli/tui/launcher;web 通过其 `build` 内的 `tsc -b` 进行类型检查)+ `build` + 快速单元测试。这是快速的内循环检查。 |
| `npm run coverage`                  | 在 v8 插桩下针对每个客户端的**每文件 ≥90% 门禁**(行/语句/函数/分支)。CI 强制执行。对于 web,它还会运行集成项目,并覆盖共享的 `core/` 运行时(包括 `core/json` 和 `core/client`)。 |
| `npm run smoke`                     | 通过构建后的启动器进行端到端冒烟测试(`--help` 分发 + 生产版 cli/tui/web),外加两次无头 Chromium 冒烟测试:一次启动冒烟测试,运行生产版 web 包并断言首次渲染干净(没有未捕获的错误——同步异常或未处理的拒绝,这是 Node 内置模块进入浏览器包时的表现),以及一次 **MCP Apps** 冒烟测试(`smoke:web:app`),它针对可组合的 App 服务器驱动 连接 → 打开应用 → `data-app-status="ready"`,覆盖沙箱代理和 UI 协议桥。 |
| `npm run verify:build-gate`         | 运行一次真实的 `vite build`,并强制将一个 Node 内置模块纳入浏览器依赖图,然后断言构建通过 #1769 门禁**失败**(该门禁将 Vite 的浏览器外部化警告转为硬错误)。防止在 Vite 升级时警告措辞发生变化而静默禁用该门禁。属于 `npm run ci` 的一部分。 |
| `npm run verify:format-coverage`    | 从每个 `package.json` 中解析 `format:check` 的 glob 模式(仅限从 `validate` 可达的那些),枚举所有被跟踪的源文件,并**失败**列出任何未被 glob 覆盖的文件——这是“每个第一方源文件都受格式门禁约束”不变量的持久守卫(#1792)。在 `validate` 中首先运行。 |
| `npm run test:scripts`              | 针对守卫自身的纯解析器进行表驱动单元测试(`node --test`)(`scripts/lib/npm-scripts.mjs`、`scripts/lib/tsc-program.mjs`,以及 `verify-typecheck-coverage.mjs` 和 `verify-dep-lockstep.mjs` 导出的辅助函数),它们编码的每条规则对应一个用例,外加 `scripts/lib/resolve-node-bin.test.mjs`——跨平台 bin 解析器(#1939),固定对照脚本实际启动的包的 `bin`/`exports` 真实形状。在 `validate` 中运行——而 `verify:typecheck-coverage` 反过来守卫*这个*门禁(从 `validate` 可达、测试集非空、每个测试文件都能被 `test:scripts` 的 glob 匹配),因为 `node --test` 会静默跳过 glob 未命中的文件并且仍以退出码 0 结束。 |
| `npm run verify:typecheck-coverage` | 上述检查的类型检查覆盖对应项(#1791):对于每个 Node 客户端(从磁盘自动发现——通过其 `typecheck` 脚本中的项目注册,或对于像 `clients/web` 这样的 `tsc -b` 客户端,通过其 `tsconfig.json` 的 `references`),它使用 `tsc --listFilesOnly` 运行这些项目,合并它们,并**失败**列出该客户端下任何未被任何项目覆盖的被跟踪 `.ts`/`.tsx`/`.mts`/`.cts` 文件(这样新的顶层配置/辅助文件就不会被静默地漏掉类型检查)。它还以默认拒绝的方式要求,任何客户端都不拥有的第一方 TypeScript(`test-servers/src`、根 `vitest.shared.mts`、所有 `core/`,以及任何新的顶层位置)必须落入某个客户端项目的 tsc 检查中——因此,web 的项目无法到达的 `core` 下的 `*.tsx` 也会被捕获。同时断言门禁已正确接线(每个客户端的类型检查环节——其 `typecheck` 脚本,或 web 的 `tsc -b`——可从其 `validate` 到达,并且根链运行每个客户端的 `validate`)。在 `validate` 中运行。 |
| `npm run verify:dep-lockstep`       | 守护“每个跨安装依赖只有一个版本”的不变量(#1896)。v2 不是 workspace,因此客户端的测试项目会将共享的第一方 TypeScript——`core/`、`test-servers/src`,以及根所有的 `vitest.shared.mts`(所有这些都从**根**安装解析其依赖)——与客户端自身的源码一起编译,从而让同一个包两次进入同一个 `tsc` 程序。版本相同时这无害;一旦发生版本偏差,TypeScript 就必须关联每个类型的两个结构不同的副本,这对递归泛型表面来说是指数级的(zod `4.3.6` 与 `4.4.3` 在 `clients/web` 中耗尽了 4GB 的 tsc 堆)。它从**实际进入每个程序的内容**(#1965)推导候选集——通过共享的 `scripts/lib/tsc-program.mjs` 用 `tsc --listFilesOnly` 列出每个客户端 tsconfig 项目,将每个解析到的 `node_modules` 文件映射到其所属安装,从而保留从两个安装到达同一个程序的包(一个仅通过另一个包的 `.d.ts` 才能到达其声明的包,如 `@modelcontextprotocol/sdk` 那样,对第一方导入的扫描是不可见的)。根据程序解析到的确切安装路径对应的 lockfile 条目为每个副本定价,只比较在同一个程序中相遇的安装,并且对任何不在注释的 `TOLERATED_SKEW` 允许列表(目前为空)中的不一致**默认拒绝失败**,允许列表中的包仅在*同一主版本内*被容忍。在 `validate` 中运行。 |
| `npm run ci`                        | **强制的推送前命令。** `validate` → `coverage` → `verify:build-gate` → `smoke` → Storybook。是 GitHub CI 的真正超集。 |
| `npm run pack:verify`               | 发布冒烟测试——参见 [发布](#publishing)。 |

也存在每个客户端的脚本(`validate:web`、`coverage:cli`、`smoke:tui`,……),外加针对共享 `core/` 包的根 `validate:core` / `format:core`、针对根 `scripts/` 工具链的 `format:scripts`,以及针对根“共享”表面的 `format:shared` / `lint:shared`(`test-servers/src/**`、`vitest.shared.mts`、根 `eslint.config.js`)。在提交前运行 `npm run format`——根 `format` 会修复 `core/`、根 `scripts/`、共享表面以及每个客户端;`validate` 运行不进行修复的 `format:check`,并在存在任何未格式化文件时让 CI 失败。

**Linting 是类型感知的。** 全部五个 ESLint 范围(`clients/{web,cli,tui,launcher}` 加上根 `core/` + 共享门禁)都将 `@typescript-eslint/no-floating-promises` 启用为 `error`,因此一个既未被 await、未被返回、未以 `.catch(…)` 终止、也未用 `void` 显式丢弃的 promise 会让 `lint` 失败——进而导致 `validate` 失败([#1959](https://github.com/modelcontextprotocol/inspector/issues/1959))。该规则需要类型信息,因此每个范围的配置都指定了一个解析器项目;根范围内的是 **`tsconfig.lint.json`**,这是一个仅用于 lint 的项目,覆盖 `core/**`、`test-servers/src/**` 和 `vitest.shared.mts`,这些文件本身没有自己的 tsconfig。它不产生任何输出,也不改变任何类型检查——但添加到根 lint 范围的新第一方 TypeScript 位置必须添加到其 `include` 中。关于何时可以接受 `void`,请参阅 [`AGENTS.md`](https://github.com/modelcontextprotocol/inspector/blob/HEAD/AGENTS.md) 中的 **TypeScript 说明**。

关于完整的测试规则——每文件 ≥90% 门禁、测试文件的位置、单元 vs. 集成 vs. Storybook 项目,以及 `v8 ignore` 策略——请参阅 [`AGENTS.md`](https://github.com/modelcontextprotocol/inspector/blob/HEAD/AGENTS.md)。

## 发布

根 `@modelcontextprotocol/inspector` 包以**一个 tarball 和单一版本号**的形式发布——没有单独的 `-web` / `-cli` / `-tui` / `-core` 包。`npm run build` 构建每个客户端,然后 `prepack` 在 `npm publish` 之前运行。运行时依赖声明在根 `package.json` 上;客户端构建会打包 `@inspector/core`,并将从根安装解析的 npm 包外部化。

### 发布内容与打包不变量

根 `package.json` 的 `"files"` 允许列表是 tarball 的事实来源。有一些不太明显的条目存在,因为它们是在**运行时**被读取的,或者曾被 npm 的 packlist 静默丢弃——在没有重新运行 `npm run pack:verify` 的情况下不要移除它们:

- **没有 source map。** 客户端打包器设置了 `sourcemap: false`(`clients/{cli,tui}/tsup.config.ts`、`clients/web/tsup.runner.config.ts`);Vite 和启动器的 `tsc` 本来就不会生成。source map 约占未压缩大小的一半,并且运行时并不需要——请通过 `npm run dev` 在源码上进行调试。
- **`clients/web/build` 通过 `clients/web/.npmignore` 发布。** `clients/web/.gitignore` 列出了 `build/`,而 npm 的 packlist 会让这个嵌套的 `.gitignore` 优先于根 `"files"` 允许列表——因此生产版 web 服务器运行器曾静默地从 tarball 中缺失,而 `clients/web/dist` 却漏了过去(其 `.gitignore` 只列出了 `dist-ssr`)。`clients/web/.npmignore` 在发布时覆盖 `.gitignore`,因此 `build/`(运行器)和 `dist/`(SPA)都会发布。其他客户端不需要这个——它们都没有嵌套的 `.gitignore`。
- **`clients/web/static` 发布 MCP Apps 沙箱代理。** `clients/web/static/sandbox_proxy.html` 是已提交的源文件(不是构建产物),由 `clients/web/server/sandbox-controller.ts` 在运行时从磁盘读取,路径为 `<runner dir>/../static/sandbox_proxy.html`。它曾完全不在根 `"files"` 允许列表中,因此每个已发布的构建都在 Apps 标签页上以 **“Sandbox not loaded”** 失败([#1859](https://github.com/modelcontextprotocol/inspector/issues/1859)),而在仓库中却工作正常。由于该路径是_相对于_ `clients/web/build` 解析的,该目录必须发布在确切的位置——`pack:verify` 同时断言 tarball 条目和磁盘上安装后的路径。
- **一个渲染 React 的依赖会被打包,而不是外部化。** 外部化的包会从 npm 在消费者依赖树中放置**它**的位置解析自己的 `react`,这不一定就是 bundle 解析我们的 React 的位置——npm 会将一个包放在满足*其* peer 范围的 React 旁边,而那些范围比我们的更宽松。`ink-form` 和 `ink-scroll-view` 声明了 `">=18"`,因此持有 React 18 的项目能够满足它们并将它们提升,而 Inspector 的 React 19 则嵌套在下面:两份 React 副本,于是工具测试表单或滚动视图一挂载,TUI 就会以 `TypeError: Cannot read properties of null (reading 'useState')` 崩溃([#1952](https://github.com/modelcontextprotocol/inspector/issues/1952))。因此,两者都由 `clients/tui/tsup.config.ts` 内联,并且**不是**根依赖:tarball 将它们的代码发布在 `clients/tui/build/index.js` 内部,而不是让消费者安装它们。打包还会将它们的传递依赖固定到本仓库安装所解析的版本(尤其是通过 `overrides` 固定的 `ink-select-input@6`,而 npm 对于一个作为依赖安装的包会忽略 `overrides`)。**`ink` 是唯一的例外,出于成本考虑:** 打包它可以工作,但会增加约 1.4 MB(`react-reconciler` 和 `yoga-layout` 会跟着进来,还要为内联的 CJS 加上 `createRequire` 横幅),因此它保持外部化——*不是*因为它的 `">=19"` peer 使其安全,事实并非如此。让这一点可以容忍的是根 `react` 范围:`"^19.0.0"` 刻意对整个主版本开放,这样 npm 就能将我们的 React 与消费者固定的任何 React 19 去重,使外部化的 `ink` 落在 bundle 所使用的同一副本上。**收窄该范围会为渲染器本身重新引入这个 bug**——`clients/tui/__tests__/tsupConfig.test.ts` 将其固定在 `ink` 的 peer 下限,并守护拆分方案的其余部分;参见 [TUI README](https://github.com/modelcontextprotocol/inspector/blob/HEAD/clients/tui/README.md#bundling-react-rendering-dependencies-must-be-inlined-1952)。
- **单一版本号,从根 `package.json` 读取。** Inspector 以一个包、一个版本的形式发布,因此只有**根** `package.json` 携带 `version`——四个 `clients/*/package.json` 故意都没有。每个 Node 客户端(CLI、TUI 和 web 后端)都通过 `core/node/version.ts` 中的共享读取器 `readInspectorVersion()` 解析版本,该读取器会向上查找到根清单(始终存在于 tarball 中)。运行时不会读取任何客户端 `package.json`,因此它们都不需要发布。web **浏览器**无法读取文件系统;它通过 `GET /api/config` 从后端获取版本(参见 [#1639](https://github.com/modelcontextprotocol/inspector/issues/1639))。

### `npm run pack:verify`——针对真实 tarball 的发布冒烟测试The `smoke:*` 脚本针对仓库内的构建树运行,该构建树**并非**已发布的包。`npm run pack:verify`(`scripts/pack-and-verify.mjs`)弥补了这一差距:它会执行构建,`npm pack` 生成可发布的 tarball(断言不附带任何 source map,且运行时所需的文件均存在),然后将该 tarball 安装到一个**全新的临时消费者**环境中——一个全新的临时目录,在其中执行真实的 `npm install <tgz>`(拉取运行时依赖、运行 `postinstall`),与 `npx @modelcontextprotocol/inspector` 的方式完全一致——并端到端驱动已安装的 `mcp-inspector` 可执行文件:`--help` 分发、通过 stdio 执行真实的 `--cli tools/list`,以及一次生产环境 `--web` 启动,必须从随附的 `dist` 中提供 `/` 服务。它能够捕获“在 `--dev` 下正常,但通过 `npx …` 运行即失败”的路径/打包类问题。它需要网络访问(安装过程会拉取依赖),因此它是本地/发布检查,**不属于**快速的 `validate`/`ci` 流程。

### 发布版本

发布由 [`.github/workflows/main.yml`](https://github.com/modelcontextprotocol/inspector/blob/HEAD/.github/workflows/main.yml) 中的两个 release 门控任务自动完成(`github.event_name == 'release'`,两者均 `needs: build`):

- **`publish`** — npm 包。以 `npm run pack:verify` 作为发布前门禁,断言发布标签与根 `package.json` 中的版本一致,然后执行 `npm publish --access public --provenance` — 单个 `npm publish`(v2 不是 npm workspace,因此没有 v1 风格的 `publish-all`/`--workspaces`),并通过 GitHub OIDC 生成带签名的 provenance 证明(`id-token: write`、`environment: release`、`NPM_TOKEN`)。
- **`publish-github-container-registry`** — 容器镜像(参见 [Docker](#docker))。

v2 版本从 **`main`** 发布,需先将里程碑的工作从 `v2/main` 合并到 `main` — 而不是直接从 `v2/main` 发布。(v1 线独立地从 `v1/main` 发布到 `v1-latest` 标签,并且从不触及 `main`;参见 [仓库状态](#mcp-inspector)。)

由于**只有一个版本号**(只有根 `package.json` 包含版本号 — 各客户端均不携带版本号,因此无需保持同步,也没有 `check-version` 步骤),发布流程分三步。

**1. 在 `v2/main` 上、里程碑合并之前进行版本号提升。** 版本号提升是里程碑工作的一部分,因此它属于开发分支,并随其他所有内容一起流入 `main`:

请替换下面的真实 issue 编号和发布版本 — 命令可直接复制粘贴使用(一个 `2.2.0` → `2.3.0` 的 minor 版本提升):```bash
git checkout -b v2/chore/2010-bump-2-3-0 v2/main
npm version minor --no-git-tag-version   # or major / patch; bump only, no tag
# PR → v2/main

⚠️ --no-git-tag-version 起着承重作用。 单独的 npm version 也会打标签,而标签会落在 v2/main 的一个提交上——但发布必须从 main 切出,因此标签必须指向那里的合并提交(步骤 3)。在此处打标签会为一个永远不会发布的提交创建标签。

2. 将 v2/main → main 合并,经由通常的里程碑合并分支。它现在带有版本号提升,因此发布落在 main 上时版本号已经正确。

在步骤 1 和 2 之间,两个分支确实会不同,这是预期的,而不是漂移:v2/main 读取的是正在构建的版本,而 main 读取的仍然是当前已发布的版本。这种排序消除的是发布后的漂移——一旦里程碑合并落地,它们会再次一致,v2/main 永远不会落后于 main。如果你看到 v2/main 领先于 main,说明发布正在进行中;如果看到它落后,说明出了问题。

3. 为 main 提交打标签并起草 Release:```bash git fetch origin main git tag 2.3.0 origin/main && git push origin 2.3.0

then draft & publish a GitHub Release for that tag → triggers publish

root@kitploit:~
⚠️ **标记 `origin/main`,而不是你本地的 `HEAD`。** `git checkout main && git pull` 会根据你配置的 merge 或 rebase 策略进行解析,因此一个分叉的本地 `main` 可能会悄悄产生或重放本地提交。在那里标记 `HEAD` 会标记一个不在 `origin/main` 上的提交,而 `git push origin <tag>` 只推送标签本身——从而产生一个发布,其对应的提交从未被推送过。显式指定 `origin/main` 可以确保被标记的提交与远程分支所指向的完全一致,无论本地状态如何。

⚠️ **不要加 `v` 前缀。** 本仓库的发布标签是裸的 `x.y.z` 格式——`2.2.0`、`2.1.0`、`2.0.0`——所以请标记 `2.3.0`,而不是 `v2.3.0`。注意 npm 自身的 `tag-version-prefix` 默认为 `v`,而本仓库没有设置 `.npmrc`,因此直接运行 `npm version` 会产生一个带 `v` 前缀的标签,不符合约定。手动打标签(第 3 步)才能保持正确。工作流的 assert 步骤在比较之前会去掉开头的 `v`,因此带 `v` 前缀的标签仍然可以发布——只是会与此前的所有版本不一致。

发布的目标提交决定了哪个工作流会运行,因此只有当发布是从包含此 (v2) 工作流的提交上切出时,本工作流才会执行发布。

**为什么版本号提升要先在 `v2/main` 上进行([#2010](https://github.com/modelcontextprotocol/inspector/issues/2010))。** 过去这发生在里程碑合并分支上,而该分支是从 `main` 切出的——因此版本号提升只存在于 `v2/main` 的*下游*,没有任何机制把它带回来。在 2.1.0 和 2.2.0 两次发布期间,`v2/main` 一直停留在 `2.0.0`。这并非只是表面问题:从里程碑合并分支切出的分支会悄悄地把版本号提升带入无关的 PR 中(这在 [#2009](https://github.com/modelcontextprotocol/inspector/issues/2009) 上发生过,一个容器 bug 修复带着 `2.0.0 → 2.2.0` 的差异一起出现),而且任何在开发中读取版本号的地方——`readInspectorVersion()`、`--version`、`GET /api/config`——都会报告一个落后两个版本的版本号。

**不要**通过将 `main` 合并回 `v2/main` 来"修复"未来的漂移。`main` 携带了完整的 pre-v2 v1 历史(通过 `ec5d8e13 chore: replace main's tree with v2` 保留——约 230 个 `v2/main` 没有的提交),因此反向合并会将这些历史永久地移植到 develop 分支的日志中,而目的只是为了交付一个涉及两个文件的变更。先提升版本号,就没有什么需要反向合并的了。

### Docker

发布工作流会将容器镜像发布到 GHCR(`ghcr.io/modelcontextprotocol/inspector`,`linux/amd64` + `linux/arm64`)。[`Dockerfile`](https://github.com/modelcontextprotocol/inspector/blob/HEAD/Dockerfile) 是一个两阶段构建:第一阶段安装并对可发布的 tarball 执行 `npm pack`;第二阶段对该 tarball 执行 `npm install -g`,因此镜像所携带的产物与 npm 发布的产物完全相同,并带有干净的 `mcp-inspector` bin。```bash
# run the web UI (reads the auth token from the container logs)
docker run --rm -p 127.0.0.1:6274:6274 ghcr.io/modelcontextprotocol/inspector

# or build the image locally
docker build -t mcp-inspector .
docker run --rm -p 127.0.0.1:6274:6274 mcp-inspector

使用 Apps 标签页?也请一并发布 6275。 MCP Apps 沙箱是浏览器直接访问的第二个监听器,位于 MCP_SANDBOX_PORT(默认 6275)。其他组件都不需要它,因此上述单端口命令在常规检查时没有问题——但没有它,Apps 标签页会渲染出空白小部件:```bash docker run --rm -p 127.0.0.1:6274:6274 -p 127.0.0.1:6275:6275
ghcr.io/modelcontextprotocol/inspector

root@kitploit:~
在容器内外使用**相同的端口号**发布。沙盒 URL 会通过 `/api/config` 以 `http://localhost:<container port>/sandbox` 的形式交给浏览器,因此重新映射端口(`-p 9000:6275`)会公布一个浏览器无法访问的端口;请改用 `-e MCP_SANDBOX_PORT=9000 -p 127.0.0.1:9000:9000`。

**在发布的端口上保留 `127.0.0.1:` 前缀。** 裸写 `-p 6274:6274` 会在**每个主机接口**上发布,将 Inspector 暴露到你的本地网络中。容器的 `HOST=0.0.0.0` 是另一回事——它管理的是 _容器的_ 接口,而非主机的接口——因此在容器外防止通配符绑定的 `DANGEROUSLY_BIND_ALL_INTERFACES` 选择加入机制并不覆盖这种情况。这一点在这里比普通 Web 应用更为重要:后端会在请求时生成进程,`GET /` 会将 API 令牌嵌入到提供的 HTML 中,而一个**不带** `Origin` 头的请求会完全跳过来源允许列表——因此对于任何非浏览器客户端来说,API 令牌是唯一的防护。更广泛的发布需要在 Inspector 前方设置真正的访问控制边界——一个进行身份验证的反向代理、一条 SSH 隧道或一个私有网络。设置自己的 `MCP_INSPECTOR_API_TOKEN` 并**不能**替代:`GET /` 会披露正在使用的任何令牌,因此自定义令牌和生成的令牌一样容易被窃取。

**保留你添加的服务器。** Inspector 会将你的服务器列表保存到 `$HOME/.mcp-inspector/mcp.json`,在镜像中该路径为 `/home/node/.mcp-inspector/mcp.json`——位于容器的可写层内,因此 `--rm` 会将其丢弃,每次运行都会从空列表开始。在此处挂载一个卷以保留它:```bash
docker run --rm -p 127.0.0.1:6274:6274 \
  -v mcp-inspector-data:/home/node/.mcp-inspector \
  ghcr.io/modelcontextprotocol/inspector

相同的卷也会持久保存 OAuth 令牌和存储的状态,因此已授权的服务器在多次运行之间保持授权。使用 -e MCP_CATALOG_PATH=/some/other/path.json 将目录放到其他位置——挂载覆盖您指定目录的卷。如果您绑定挂载主机目录而不是命名卷(-v "$PWD/inspector-data:/home/node/.mcp-inspector"),该目录会保留主机所有权,因此在 Linux 上请添加 --user "$(id -u):$(id -g)" 或将其 chown 为 uid 1000——否则非 root 的 node 用户无法写入,添加服务器会因 EACCES 失败。

从修复前的镜像升级? 早期的镜像没有创建 /home/node/.mcp-inspector,因此 Docker 以 root 身份创建了卷的挂载点,非 root 的 node 用户无法写入。空的卷会在当前镜像首次运行时自我修复(Docker 会将镜像目录的所有权应用到空卷上),但已有文件的卷会保留旧的 root 所有权,并且仍然以 EACCES 失败。只需修复一次:```bash docker run --rm -u 0 --entrypoint chown
-v mcp-inspector-data:/data ghcr.io/modelcontextprotocol/inspector
-R node:node /data

root@kitploit:~
The image defaults to `--web` bound to `0.0.0.0:6274` with browser auto-open disabled; override the args to run another mode (`docker run --rm ghcr.io/modelcontextprotocol/inspector --cli …`). Pass `-e MCP_INSPECTOR_API_TOKEN=…` to set a known token (otherwise one is generated and printed in the logs), or `-e DANGEROUSLY_OMIT_AUTH=true` to disable auth. Binding `0.0.0.0` (all network interfaces) is refused by default outside a container — it exposes the process-spawning backend to the local network — so the image opts in explicitly with `DANGEROUSLY_BIND_ALL_INTERFACES=true` (already set in the `Dockerfile`); a bare `HOST=0.0.0.0` without that flag exits with an error. If you **remap the published port** (`-p 127.0.0.1:8080:6274`), the browser's origin (`http://localhost:8080`) no longer matches the in-container port, so set `-e ALLOWED_ORIGINS=http://localhost:8080,http://127.0.0.1:8080` (or run `-e CLIENT_PORT=8080 -p 127.0.0.1:8080:8080`) or connects will 403. `ALLOWED_ORIGINS` **replaces** the default list rather than merging, so list every loopback form you'll browse from (see the [web README](https://github.com/modelcontextprotocol/inspector/blob/HEAD/clients/web/README.md#host-binding--the-origin-allow-list)). The image runs as the non-root `node` user and has a `HEALTHCHECK` that probes the web UI — it assumes the default `--web` mode, so add `--no-healthcheck` when running `--cli`/`--tui` (which have no web server).

## Contributing — `AGENTS.md` and `CLAUDE.md`

**[`AGENTS.md`](https://github.com/modelcontextprotocol/inspector/blob/HEAD/AGENTS.md) is the contract for changing this codebase, and it applies to humans and AI agents alike.** It is not agent-only boilerplate — it holds the project's real conventions: the issue-and-board workflow, branch/label rules, the TypeScript and Mantine/React standards, the testing and coverage requirements, and the mandatory pre-push gate. Read it before making changes, and keep it up to date when you change structure, tooling, or rules.

`CLAUDE.md` is the entry point the [Claude Code](https://claude.com/claude-code) agent loads automatically; it simply includes `AGENTS.md` and this README, so both agents and humans work from the same source of truth. If you use a different agent that reads `AGENTS.md`, you get the same rules.

A key rule worth surfacing here: **all work is issue-driven.** Before starting, find or create a tracking issue on the v2 project board; open PRs against `v2/main` with `Closes #<issue>`. The exact recipes (labels, board IDs, statuses) are in `AGENTS.md`.

## License

MIT.
下载工具
配置演示内容关联问题
mcp-app-http.json (legacy era)Apps 选项卡中的一个 MCP 应用(UI 资源 + 应用工具)#1859
modern-mrtr-http.json单个 MRTR 往返—
mrtr-showcase-http.json一个服务器中包含所有 MRTR 预设#1860
modern-network-http.jsonNetwork 选项卡:Mcp-* 标头 + 错误分类#1628
xmcpheader-modern-http.jsonTools 选项卡:x-mcp-header 镜像与排除#1632
pagination-http.json逐页获取列表#1721
structured-output-http.jsonTools 选项卡:结果的 structuredContent 部分#1908
duplicate-tool-names-http.json重复工具名称的 tools/list#1957
nullable-fields-http.jsonTools 选项卡:可空(anyOf + null)参数#1928
rfc6570-templates-http.jsonResources 选项卡:RFC 6570 资源模板展开#1919
advertised-extensions-http.json工具注册受已通告扩展门控#1739
logging-{legacy,modern}-http.json日志记录,两种时代#1629
subscriptions-{legacy,modern}-http.json资源订阅,两种时代#1630
tasks-{legacy,modern}-http.json任务,两种时代#1631
预设行为
mrtr_confirm单轮
mrtr_two_step通过 requestState 进行两轮征询
mrtr_sample嵌入的采样 → Sampling 面板
mrtr_roots嵌入的 roots/list,从配置的根中静默自动应答(无模态框)
mrtr_edge先是一轮仅 inputRequests,然后是一轮仅 requestState
mrtr_empty以空结果完成 — 无 content、无 structuredContent
mrtr_loop永不完成 → 触发 MRTR_MAX_ROUNDS 上限
工具响应
trigger_header_mismatch400 / -32020
trigger_missing_capability400 / -32021
trigger_unsupported_version400 / -32022(带有 data.supported)
trigger_method_not_found404 / -32601
形态SDK 行为
{a,b}原始拼接各值 — 无编码,运算符前缀被丢弃
{;id}; 不在其运算符列表中,因此变量会被解析为 ;id
{id:3}前缀修饰符被并入名称,得到 id:3
{+v} / {#v}encodeURI 会破坏保留字符 [/]([::1] → %5B::1%5D)并双重编码 pct 三元组(%2F → %252F)
{v}encodeURIComponent 会让子分隔符 !'()* 保持未编码,而 RFC 6570 要求对其进行编码