From 78fb51a31078e1489d23d0553837b4b85b6ac7bf Mon Sep 17 00:00:00 2001 From: xfy Date: Tue, 28 Jul 2026 15:04:33 +0800 Subject: [PATCH] =?UTF-8?q?feat(mcp):=20=E5=AE=A2=E6=88=B7=E7=AB=AF?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=96=B0=E5=A2=9E=20Oh-My-Pi=20=E7=89=87?= =?UTF-8?q?=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在 /admin/mcp 客户端配置展示区增加 Oh-My-Pi(omp)的 MCP 配置片段, 位于 Cline 与「通用」之间。 Oh-My-Pi 的 mcpServers 条目与 Claude Code/Cursor 的关键差异:字段名 是 transport(而非 type),值仍为 streamable-http,auth 同样走 headers.Authorization。配置文件落在 ~/.pi/agent/mcp.json(全局)或 .pi/mcp.json(项目级)。 改动: - ClientConfigs / McpClientConfigs 新增 omp_json 字段 - generate_client_configs 生成 omp 片段 - ConfigCard 增加 ConfigSnippet 行 - 新增测试 omp_json_uses_transport_field_not_type,并纳入 pretty 校验循环 格式依据:2026 pi.dev / oh-my-pi 官方文档(联网核实)。 --- src/api/mcp_tokens.rs | 3 +++ src/mcp/config.rs | 29 ++++++++++++++++++++++++++++- src/pages/admin/mcp.rs | 1 + 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/api/mcp_tokens.rs b/src/api/mcp_tokens.rs index ac0bd30..3d84078 100644 --- a/src/api/mcp_tokens.rs +++ b/src/api/mcp_tokens.rs @@ -275,6 +275,8 @@ pub struct McpClientConfigs { pub cursor_json: String, /// Cline(`cline_mcp_settings.json`)。 pub cline_json: String, + /// Oh-My-Pi(`~/.pi/agent/mcp.json` / `.pi/mcp.json`)。字段名 `transport`,非 `type`。 + pub omp_json: String, /// 通用原始 JSON(单 server entry)。 pub generic_json: String, /// Claude Code CLI 一行命令。 @@ -300,6 +302,7 @@ pub async fn get_mcp_client_configs(token: String) -> Result --header ...`。 @@ -77,6 +80,17 @@ pub fn generate_client_configs(base_url: &str, token: &str) -> ClientConfigs { } }); + // --- Oh-My-Pi:字段名是 transport(非 type),值仍为 streamable-http --- + let omp_json = serde_json::json!({ + "mcpServers": { + SERVER_NAME: { + "transport": "streamable-http", + "url": mcp_url, + "headers": { "Authorization": auth_header } + } + } + }); + // --- 通用:单个 server entry 的纯净形式 --- let generic_json = serde_json::json!({ "type": "streamable-http", @@ -94,6 +108,7 @@ pub fn generate_client_configs(base_url: &str, token: &str) -> ClientConfigs { claude_code_json: pretty_json(&claude_code_json), cursor_json: pretty_json(&cursor_json), cline_json: pretty_json(&cline_json), + omp_json: pretty_json(&omp_json), generic_json: pretty_json(&generic_json), claude_cli, } @@ -167,6 +182,18 @@ mod tests { assert_eq!(v["url"], "https://rua.plus/mcp"); } + #[test] + fn omp_json_uses_transport_field_not_type() { + let cfg = generate_client_configs(BASE, TOKEN); + let v: serde_json::Value = serde_json::from_str(&cfg.omp_json).unwrap(); + let entry = &v["mcpServers"]["yggdrasil"]; + // 关键差异:字段名是 transport(非 type)。 + assert_eq!(entry["transport"], "streamable-http"); + assert!(entry.get("type").is_none(), "omp 配置不应含 type 字段"); + assert_eq!(entry["url"], "https://rua.plus/mcp"); + assert_eq!(entry["headers"]["Authorization"], format!("Bearer {TOKEN}")); + } + #[test] fn claude_cli_one_liner_contains_url_and_header() { let cfg = generate_client_configs(BASE, TOKEN); @@ -179,7 +206,7 @@ mod tests { fn all_json_is_pretty_indented() { let cfg = generate_client_configs(BASE, TOKEN); // pretty JSON 至少含一个换行 + 缩进(非单行紧凑形式)。 - for s in [&cfg.claude_code_json, &cfg.cursor_json, &cfg.cline_json, &cfg.generic_json] { + for s in [&cfg.claude_code_json, &cfg.cursor_json, &cfg.cline_json, &cfg.omp_json, &cfg.generic_json] { assert!(s.contains('\n'), "JSON 应是 pretty-printed: {s}"); assert!(s.contains(" "), "JSON 应含 2 空格缩进: {s}"); } diff --git a/src/pages/admin/mcp.rs b/src/pages/admin/mcp.rs index e69bfe8..e1f4549 100644 --- a/src/pages/admin/mcp.rs +++ b/src/pages/admin/mcp.rs @@ -506,6 +506,7 @@ fn ConfigCard() -> Element { ConfigSnippet { title: "Claude Code(.mcp.json / ~/.claude.json)".to_string(), content: c.claude_code_json.clone() } ConfigSnippet { title: "Cursor(~/.cursor/mcp.json)".to_string(), content: c.cursor_json.clone() } ConfigSnippet { title: "Cline(cline_mcp_settings.json)".to_string(), content: c.cline_json.clone() } + ConfigSnippet { title: "Oh-My-Pi(~/.pi/agent/mcp.json 或 .pi/mcp.json)".to_string(), content: c.omp_json.clone() } ConfigSnippet { title: "通用(单 server entry)".to_string(), content: c.generic_json.clone() } ConfigSnippet { title: "Claude Code CLI".to_string(), content: c.claude_cli.clone() } }