尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

MCP Inspector V2 存储架构:OAuth 持久化、混合状态管理与 Repository 接口模式全解析

MCP Inspector V2 存储架构:OAuth 持久化、混合状态管理与 Repository 接口模式全解析 MCP Inspector V2 存储架构OAuth 持久化、混合状态管理与 Repository 接口模式全解析【免费下载链接】inspectorVisual testing tool for MCP servers项目地址: https://gitcode.com/gh_mirrors/inspector1/inspector导读本文基于仓库 specification/v2_storage.md 技术规格系统梳理 MCP Inspector V2 的存储与状态管理架构数据如何持久化、状态存放于何处、由哪些模块负责管理。文章将深入讲解oauth.json运行时持久化的三层实现OAuthStorageBaseOAuthMemoryStoreOAuthPersistBackend、六大主流状态管理库的选型对比、最终的混合架构方案以及客户端状态包装 core 仓库接口的 Repository 模式并附上当前仓库中已经落地的源码实现证据与迁移路径。存储架构总览与设计原则Inspector V2 的存储规格回答了三个核心问题数据持久化到哪里文件 / localStorage / 内存、状态存放在哪个进程代理服务端 / 浏览器客户端、由哪些库负责管理。其整体架构遵循四条设计原则接口优先Interface-first仓库/服务接口保持与存储实现无关上层代码不感知底层是文件、内存还是远程 HTTP。core 保持 React-freemodelcontextprotocol/inspector-core不引入任何 React 依赖让 CLI / TUI / Web 客户端都能复用。混合方案Hybrid approach不同类别的状态采用不同的管理手段而不是一刀切。服务器配置经由代理Server configs via proxy依据 Discussion #1805。状态分类一份全局状态地图规格用一张分类表划分了所有状态类别明确各自的持久化方式和存放位置类别示例持久化位置OAuth 运行时状态Tokens、PKCE、scopes、IdP 会话、授权服务器元数据文件oauth.jsonOAuthStorageBase 持久化后端服务器配置URL、transport、headers文件mcp.json代理服务器用户偏好主题、日志级别、面板尺寸localStorage浏览器连接状态状态、服务器信息、错误内存React Context执行状态当前请求、待处理队列内存React Context日志展示过滤视图、暂停状态内存core/mcp/state React hooks执行表单状态选中的工具、表单值内存React 组件状态 / hooks测试配置档自定义配置档、当前选中项localStorage浏览器计划中历史数据请求/响应记录NDJSON 文件Pino代理服务器服务端存储与客户端存储的分工该规格聚焦客户端侧状态管理服务端持久化使用完全不同的技术栈层技术用途代理PinoNDJSON 文件原始历史持久化history.ndjson由 History API 解析客户端浏览器 UIlocalStorage 内存UI 偏好、展示缓冲区代理上的 Pino logger 将 MCP 请求/响应记录写入 NDJSON 格式History API 端点解析该文件并返回过滤后的 JSON。客户端侧则由core/mcp/state存储和 React hooks 负责日志如何展示过滤、暂停、自动滚动、缓存已获取的历史以提升 UI 性能、以及那些不应放在服务器上的用户偏好。Pino 的详细配置可参考 specification/v2_server.md 中 Pino 选型说明部分。在当前的仓库实现中客户端日志展示状态由 core/mcp/state/index.ts 导出的多个事件驱动管理器承载例如MessageLogState消息日志默认最多保留 1000 条、超出后按 FIFO 淘汰与FetchRequestLogStateHTTP 请求日志。这些管理器实现了日志缓冲、过滤器状态与暂停/恢复控制正是规格中Logs Display状态的实际载体。OAuth 运行时持久化~/.mcp-inspector/storage/oauth.jsonOAuth 与 EMA 的运行时状态——access/refresh token、PKCE verifier、已授权 scopes、缓存的授权服务器元数据、IdP 会话——与mcp.json分离存储默认路径为~/.mcp-inspector/storage/oauth.json。三种客户端实现客户端实现路径WebRemoteOAuthStorage→/api/storage/oauth经 Hono 后端读写同一个磁盘文件CLI / TUINodeOAuthStorage直接文件 I/O测试 / 参考BrowserOAuthStoragesessionStorage未接入 v2 Web 应用三层存储栈Issue #1549OAuth 持久化采用接口 → 内存存储 → 持久化后端的三层结构OAuthStorage 接口 └── OAuthStorageBase组合内存态 后端含并发持久化队列 ├── OAuthMemoryStore内存态servers idpSessions └── OAuthPersistBackend可插拔后端 ├── createFileOAuthPersistBackendNode 文件 ├── createRemoteOAuthPersistBackendHTTP └── createSessionOAuthPersistBackendsessionStorage对应源码为 core/auth/oauth-storage.ts、core/auth/store.ts 与 core/auth/oauth-persist.ts。写入格式磁盘上写的是纯 JSON{ servers, idpSessions }读取时兼容旧版{ state, version }信封结构并在下次写入时自动完成迁移migrate-on-write。所有 getter 均为异步setter 自动触发持久化。Web 端通过getWebRemoteOAuthStorage()共享同一个存储实例实现见 core/auth/remote/storage-remote.ts。从源码结构看core/auth/oauth-persist.ts 中的parseOAuthPersistBlob正是新旧格式双读逻辑的实现点若解析结果是带version字段的{ state, ... }信封则取state内部载荷若直接是{ servers, idpSessions }则原样采用两者都不是则返回null。源码级实现细节OAuthStorageBasecore/auth/oauth-storage.ts有几个值得注意的工程细节懒加载首次读取前通过load()从后端快照填充内存态loadPromise保证并发调用只加载一次。串行化持久化内部维护persistQueue承诺链将并发写操作排队避免多个变更交错导致后写覆盖先写。按授权服务器隔离凭证SEP-2352ServerOAuthState中以byIssuer[issuer]键控每个 AS 的 client 注册信息与 token同时维护activeIssuer作为无上下文字段读取的默认答案。访问/刷新令牌在读取时会被重新盖章issuer从而支持 SDK 的discardIfIssuerMismatch跨 AS 凭证拒用。惰性迁移顶层裸clientInformation/tokens字段是旧版无键回退旧快照反序列化后凭证落在顶层且没有issuer标记。读取时byIssuer无条目则回退到顶层第一次带issuer的保存会把凭证提升进byIssuer并清除回退字段新写入永远不会落到裸字段。Node 端路径解析顺序core/auth/node/storage-node.ts 的getStateFilePath显式传入的customPath环境变量MCP_INSPECTOR_OAUTH_STATE_PATH按文件粒度的覆盖便于测试与脚本化运行隔离环境变量MCP_STORAGE_DIR下的oauth.json注意此分支只作用于 OAuth 后端不会搬移client.json/mcp.json默认~/.mcp-inspector/storage/oauth.jsonWindows 上为%USERPROFILE%\.mcp-inspector\storage。文件写入的原子性core/storage/store-io.tswriteStoreFile使用atomically库以临时文件 rename方式写入文件权限为0o600并自动创建父目录写入承诺按路径登记在pendingWrites中测试与优雅关闭可通过flushStoreFileWrites()等待落盘完成而无需轮询文件。服务端路由代理服务器的 Hono 后端在 core/mcp/remote/node/server.ts 中实现了GET / POST / DELETE /api/storage/:storeId三个端点对应read/write/removeWeb 客户端经由此 API 与 CLI/TUI 共享同一份磁盘状态。其配套的浏览器侧测试见 web/src/lib/remoteOAuthStorage.test.ts。EMA 流程相关的持久化细节可进一步阅读 specification/v2_auth_ema.md 中的OAuth persistence (#1549)章节以及 specification/v2_auth_mid_session.md。状态管理方案选型对比规格对六种主流方案进行了系统对比评估维度包括包体积、学习曲线、样板代码、DevTools、持久化中间件、React-free 能力、TypeScript 支持与选择器优化标准ZustandRedux/RTKJotaiContextuseReducerTanStack QueryValtio包体积~1.2KB~11KB~2.2KB0内置~13KB~3KB学习曲线低中-高低低中低样板代码极少多极少中低极少DevTools有极佳有React DevTools有有持久化中间件内置RTK-persist有手动不适用有React-free 使用可以/vanilla否否否否可以TypeScript 支持极佳极佳良好良好极佳良好选择器优化内置需手动 memo自动手动自动自动各方案结论Zustand推荐用于 UI 状态样板代码极少、内置persist中间件可自动同步 localStorage、选择器式访问避免多余重渲染、zustand/vanilla的核心与 React 解耦便于 CLI/TUI 复用、TypeScript 推断出色且有 MCPJam Inspector 这一同类应用的先例。缺点是复杂状态流转下结构不如 Redux 严谨、无内置时间旅行调试、多 store 若组织不当会导致状态碎片化。Redux / Redux ToolkitDevTools 时间旅行调试极佳、更新路径高度结构化但 11KB 包体积对 Inspector 场景偏重、对单服务器连接模型是过度设计、需要react-redux绑定违背 React-free 核心结论否决。Jotai原子状态模型支持细粒度更新、天然少重渲染、适合派生状态但命令式更新不够直观、生态较小结论不错的备选但在 MCP 工具链语境下证明度不足。Context useReducer零额外依赖、动作式状态流转清晰但任何变更都会重渲染全部消费者除非精心拆分、选择器优化需手动useMemo/useCallback、多状态域下冗长、无内置持久化。结论保留给连接/执行状态机已实现。TanStack QueryReact Query服务端状态缓存与同步出色、内置缓存/后台刷新/乐观更新但设计目标是异步服务端状态而非本地 UI 状态对代理中介的数据拉取是额外复杂度。结论当仓库层走代理 API 时可考虑作为 History/Logs API 调用的补充。ValtioProxy 可变 API 自然、自动重渲染优化、可脱离 React 使用但 Proxy 的魔法对调试不友好、更新不如 Zustand 显式、社区采用度低。结论可行的备选但 Proxy 语义对团队不够熟悉。推荐架构混合方案规格的最终结论是按状态域选择最合适的工具而非统一替换状态域技术理由连接状态useReducer Context清晰的状态机disconnected → connecting → connected → error已在 McpContext 实现执行状态useReducer Context含待处理请求的复杂流转已在 ExecutionContext 实现用户偏好Zustand persist简单键值对需要持久化避免 prop drilling日志展示Zustand实时缓冲过滤状态暂停/恢复执行表单Zustand表单值选中项最近结果展示测试配置档Zustand persist用户配置当前选中项服务器配置仓库代理 API规格约束不落浏览器存储历史数据仓库接口存储实现延迟决策为什么连接状态不换掉 useReducer连接状态是边界明确的状态机disconnected - connecting - connected - error ^ | | |____________________________|__________|useReducer擅长动作式流转CONNECT_REQUEST—— 开始连接尝试CONNECT_SUCCESS—— 保存服务器信息、能力CONNECT_ERROR—— 保存错误详情DISCONNECT—— 清理并复位该模式已在McpContext.tsx中实现并运行良好无需迁移。Zustand Store 规格早期规划含最终落地说明历史说明本节记录的是早期使用 Zustand 管理浏览器 UI 状态的规划。最终发布的 Web 客户端改用core/mcp/state事件驱动存储与core/reacthooks 实现OAuth 运行时持久化使用OAuthStorageBase见上文而非 Zustand。下表对比对评估未来的 UI 状态库仍有参考价值。1. Preferences Store偏好 Store用途跨会话持久化用户偏好持久化localStorage经persist中间件localStorage keyinspector-preferences。interface PreferencesState { // 主题 theme: light | dark | system; // 日志 logLevel: LogLevel; showTimestamps: boolean; wrapLogLines: boolean; // 展示 compactMode: boolean; showAnnotations: boolean; // 布局 logsExpanded: boolean; historySidebarWidth: number; } interface PreferencesActions { setTheme: (theme: PreferencesState[theme]) void; setLogLevel: (level: LogLevel) void; toggleTimestamps: () void; toggleWrapLines: () void; toggleCompactMode: () void; toggleAnnotations: () void; setLogsExpanded: (expanded: boolean) void; setHistorySidebarWidth: (width: number) void; resetToDefaults: () void; }2. Logs Display Store日志展示 Store用途管理实时日志展示状态持久化无临时态内存上限1000 条FIFO 淘汰。interface LogsDisplayState { // 缓冲仅内存 entries: LogEntry[]; // 过滤器 minLevel: LogLevel; loggerFilter: string | null; requestIdFilter: string | null; searchQuery: string; // 控制 isPaused: boolean; isAutoScroll: boolean; } interface LogsDisplayActions { addEntry: (entry: LogEntry) void; addBatch: (entries: LogEntry[]) void; clearLogs: () void; setMinLevel: (level: LogLevel) void; setLoggerFilter: (logger: string | null) void; setRequestIdFilter: (requestId: string | null) void; setSearchQuery: (query: string) void; togglePause: () void; toggleAutoScroll: () void; }对照源码最终落地实现中core/mcp/state/messageLogState.ts 的MessageLogState以maxMessages默认 1000实现 FIFO 淘汰并借助TypedEventTarget对外派发message单条与messagesChange全量列表事件连接断开时清空日志以断开事件作为会话边界。3. Execution Form Store执行表单 Store用途追踪工具/资源/提示词执行表单状态持久化无临时态。interface ExecutionFormState { // 工具 selectedToolName: string | null; toolFormValues: Recordstring, unknown; lastToolResult: ToolResult | null; // 资源 selectedResourceUri: string | null; resourceContent: unknown | null; // 提示词 selectedPromptName: string | null; promptFormValues: Recordstring, string; promptMessages: unknown[] | null; } interface ToolResult { toolName: string; args: Recordstring, unknown; result: unknown; timestamp: string; duration: number; isError: boolean; } interface ExecutionFormActions { selectTool: (name: string | null) void; setToolFormValues: (values: Recordstring, unknown) void; setLastToolResult: (result: ToolResult | null) void; selectResource: (uri: string | null) void; setResourceContent: (content: unknown | null) void; selectPrompt: (name: string | null) void; setPromptFormValues: (values: Recordstring, string) void; setPromptMessages: (messages: unknown[] | null) void; reset: () void; }注意完整执行历史由服务端通过 Pino/NDJSON 持久化并经由 History API 访问lastToolResult仅用于即时展示最近一次结果避免与历史 store 重复存储。4. Testing Profiles Store测试配置档 Store用途管理 sampling/elicitation 响应配置持久化localStorage经persist中间件localStorage keyinspector-testing-profiles。interface TestingProfilesState { profiles: TestingProfile[]; activeProfileId: string; } interface TestingProfilesActions { setActiveProfile: (id: string) void; addProfile: (profile: OmitTestingProfile, id) TestingProfile; updateProfile: (id: string, updates: PartialTestingProfile) void; deleteProfile: (id: string) void; resetToDefaults: () void; }默认配置档Manual手动—— 手动响应请求不自动批准Auto-Approve自动批准—— 以默认响应自动批准。与 core 包的集成Repository 接口模式Zustand store 位于客户端包中包裹wrapcore 仓库接口而非取代它们// client/src/stores/historyStore.ts import type { HistoryRepository } from modelcontextprotocol/inspector-core; export function createHistoryStore(repository: HistoryRepository) { return createHistoryStoreState((set, get) ({ entries: [], isLoading: false, fetch: async () { set({ isLoading: true }); const entries await repository.list(); set({ entries, isLoading: false }); }, add: async (entry) { const added await repository.add(entry); set((s) ({ entries: [added, ...s.entries] })); return added; }, // ... 其他方法委托给 repository })); }该模式带来的收益保持 core 接口不变允许自由替换仓库实现内存、代理 API、文件为 UI 组件提供响应式状态用内存 stub 即可保持可测试性。依赖流向------------------------------------------------------------------ | 客户端包 | | | | ------------------ --------------------- | | | Zustand Stores | | Context Providers | | | | - preferences | | - McpContext | | | | - logsDisplay | | - ExecutionContext | | | | - executionForm | --------------------- | | | - testingProfiles | | | ----------------- | | | | | | | v v | | -------------------------------------------------------- | | | Core 包接口 | | | | - ServerConfigRepository - HistoryRepository | | | | - LogsRepository - TestingProfileRepository | | | ------------------------------------------------------- | ---------------------------|----------------------------------- | v ------------------------------- | Core 包React-free | | - MCP Client 生命周期 | | - Transport 创建 | | - Handler 函数 | | - 类型定义 | -------------------------------依赖方向始终保持单向客户端 store/Context → core 接口 → core 实现。core 不感知任何 React 或 UI 状态库。推荐文件结构client/src/stores/ index.ts # 再导出所有 store preferencesStore.ts # 主题、日志级别、展示偏好 logsDisplayStore.ts # 日志缓冲、过滤器、控制 executionFormStore.ts # 工具/资源/提示词表单状态 testingProfilesStore.ts # sampling/elicitation 配置档三步迁移路径阶段 1引入 Zustand无破坏安装npm install zustand创建client/src/stores/目录实现上述四个 store暂不改动任何现有组件阶段 2迁移组件用 store 替换 ExecutionContext 中的mockTestingProfiles更新依赖偏好的组件主题、日志设置日志页改用 logsDisplay storetools/resources/prompts 页改用 execution form store阶段 3接通仓库层存储实现确定后创建对应 repository 实现用 Zustand store 工厂模式包裹 repositoriesUI 组件零改动从当前仓库的实现看阶段 3 的store 工厂包裹 repository思路与 core/mcp/state/index.ts 中的ManagedToolsState、ManagedResourcesState、ManagedPromptsState、PagedToolsState等事件驱动状态管理器殊途同归——它们同样通过构造函数注入InspectorClientProtocol并在内部维护列表与分页状态客户端 React hooks如 core/react/useManagedTools.ts、core/react/useMessageLog.ts再订阅其事件对外暴露响应式数据。开放问题TanStack Query 补充当仓库层走代理 API 时是否为 History/Logs API 调用引入 TanStack Query它可提供缓存、后台刷新与乐观更新。生产环境 DevToolsZustand devtools 只在开发构建启用还是也在生产环境用于调试Store 粒度execution form store 是否应拆分为 toolsStore、resourcesStore、promptsStore 以获得更细粒度控制参考Issue #983 —— 数据规格讨论Discussion #1805 —— 服务器配置存储决策MCPJam Inspector —— 使用 Zustand 的参考实现Zustand 官方文档仓库内相关规格specification/v2_scope.md、specification/v2_servers_file.md、specification/v2_server.md、specification/v2_web_client.md、specification/v2_auth_ema.md、specification/v2_auth_mid_session.md【免费下载链接】inspectorVisual testing tool for MCP servers项目地址: https://gitcode.com/gh_mirrors/inspector1/inspector创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表