@lobehub/chat 1.109.1 → 1.110.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/.cursor/rules/i18n.mdc +1 -2
  2. package/CHANGELOG.md +50 -0
  3. package/README.md +1 -1
  4. package/README.zh-CN.md +1 -1
  5. package/apps/desktop/electron-builder.js +22 -0
  6. package/apps/desktop/src/main/controllers/McpInstallCtr.ts +153 -0
  7. package/apps/desktop/src/main/controllers/index.ts +19 -0
  8. package/apps/desktop/src/main/core/App.ts +46 -0
  9. package/apps/desktop/src/main/core/infrastructure/IoCContainer.ts +4 -0
  10. package/apps/desktop/src/main/core/infrastructure/ProtocolManager.ts +256 -0
  11. package/apps/desktop/src/main/types/protocol.ts +60 -0
  12. package/apps/desktop/src/main/utils/__tests__/protocol.test.ts +203 -0
  13. package/apps/desktop/src/main/utils/protocol.ts +210 -0
  14. package/changelog/v1.json +18 -0
  15. package/locales/ar/plugin.json +196 -136
  16. package/locales/bg-BG/plugin.json +204 -144
  17. package/locales/de-DE/plugin.json +176 -116
  18. package/locales/en-US/plugin.json +192 -132
  19. package/locales/es-ES/plugin.json +203 -143
  20. package/locales/fa-IR/plugin.json +155 -95
  21. package/locales/fr-FR/plugin.json +161 -101
  22. package/locales/it-IT/plugin.json +193 -133
  23. package/locales/ja-JP/plugin.json +195 -135
  24. package/locales/ko-KR/plugin.json +163 -103
  25. package/locales/nl-NL/plugin.json +211 -151
  26. package/locales/pl-PL/plugin.json +171 -111
  27. package/locales/pt-BR/plugin.json +180 -120
  28. package/locales/ru-RU/plugin.json +191 -131
  29. package/locales/tr-TR/plugin.json +187 -127
  30. package/locales/vi-VN/plugin.json +152 -92
  31. package/locales/zh-CN/plugin.json +60 -0
  32. package/locales/zh-TW/plugin.json +157 -97
  33. package/package.json +2 -1
  34. package/packages/electron-client-ipc/src/events/index.ts +5 -2
  35. package/packages/electron-client-ipc/src/events/protocol.ts +29 -0
  36. package/packages/electron-client-ipc/src/types/index.ts +1 -0
  37. package/packages/electron-client-ipc/src/types/mcpInstall.ts +19 -0
  38. package/packages/types/src/plugins/mcp.ts +38 -1
  39. package/packages/types/src/plugins/protocol.ts +166 -0
  40. package/src/app/[variants]/(main)/chat/_layout/Desktop/index.tsx +4 -1
  41. package/src/app/[variants]/(main)/discover/(detail)/mcp/[slug]/features/Sidebar/ActionButton/index.tsx +1 -2
  42. package/src/components/KeyValueEditor/index.tsx +4 -2
  43. package/src/features/ChatItem/index.tsx +25 -2
  44. package/src/features/MCP/MCPInstallProgress/index.tsx +1 -1
  45. package/src/features/PluginDevModal/MCPManifestForm/index.tsx +30 -36
  46. package/src/features/PluginStore/McpList/List/Item.tsx +1 -1
  47. package/src/features/ProtocolUrlHandler/InstallPlugin/ConfigDisplay.tsx +211 -0
  48. package/src/features/ProtocolUrlHandler/InstallPlugin/CustomPluginInstallModal.tsx +228 -0
  49. package/src/features/ProtocolUrlHandler/InstallPlugin/OfficialPluginInstallModal/Detail.tsx +44 -0
  50. package/src/features/ProtocolUrlHandler/InstallPlugin/OfficialPluginInstallModal/index.tsx +105 -0
  51. package/src/features/ProtocolUrlHandler/InstallPlugin/index.tsx +55 -0
  52. package/src/features/ProtocolUrlHandler/InstallPlugin/types.ts +45 -0
  53. package/src/features/ProtocolUrlHandler/index.tsx +30 -0
  54. package/src/locales/default/plugin.ts +60 -0
  55. package/src/store/tool/slices/mcpStore/action.ts +127 -1
  56. package/src/store/tool/slices/mcpStore/initialState.ts +8 -13
  57. package/src/store/tool/slices/mcpStore/selectors.ts +13 -0
@@ -0,0 +1,60 @@
1
+ /**
2
+ * MCP Schema - stdio 配置类型
3
+ */
4
+ export interface McpStdioConfig {
5
+ args?: string[];
6
+ command: string;
7
+ env?: Record<string, string>;
8
+ type: 'stdio';
9
+ }
10
+
11
+ /**
12
+ * MCP Schema - http 配置类型
13
+ */
14
+ export interface McpHttpConfig {
15
+ headers?: Record<string, string>;
16
+ type: 'http';
17
+ url: string;
18
+ }
19
+
20
+ /**
21
+ * MCP Schema 配置类型
22
+ */
23
+ export type McpConfig = McpStdioConfig | McpHttpConfig;
24
+
25
+ /**
26
+ * MCP Schema 对象
27
+ * 符合 RFC 0001 定义
28
+ */
29
+ export interface McpSchema {
30
+ /** 插件作者 */
31
+ author: string;
32
+ /** 插件配置 */
33
+ config: McpConfig;
34
+ /** 插件描述 */
35
+ description: string;
36
+ /** 插件主页 */
37
+ homepage?: string;
38
+ /** 插件图标 */
39
+ icon?: string;
40
+ /** 插件唯一标识符,必须与URL中的id参数匹配 */
41
+ identifier: string;
42
+ /** 插件名称 */
43
+ name: string;
44
+ /** 插件版本 (semver) */
45
+ version: string;
46
+ }
47
+
48
+ /**
49
+ * 协议URL解析结果
50
+ */
51
+ export interface ProtocolUrlParsed {
52
+ /** 操作类型 (如: 'install') */
53
+ action: string;
54
+ /** 原始URL */
55
+ originalUrl: string;
56
+ /** 解析后的所有查询参数 */
57
+ params: Record<string, string>;
58
+ /** URL类型 (如: 'plugin') */
59
+ urlType: string;
60
+ }
@@ -0,0 +1,203 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { McpSchema } from '../../types/protocol';
4
+ import { generateRFCProtocolUrl, parseProtocolUrl } from '../protocol';
5
+
6
+ describe('Protocol', () => {
7
+ describe('generateRFCProtocolUrl', () => {
8
+ it('should generate valid RFC protocol URL for stdio type', () => {
9
+ const schema: McpSchema = {
10
+ identifier: 'edgeone-mcp',
11
+ name: 'EdgeOne MCP',
12
+ author: 'Higress Team',
13
+ description: 'EdgeOne API integration for LobeChat',
14
+ version: '1.0.0',
15
+ homepage: 'https://github.com/higress/edgeone-mcp',
16
+ config: {
17
+ type: 'stdio',
18
+ command: 'npx',
19
+ args: ['-y', '@higress/edgeone-mcp'],
20
+ env: { NODE_ENV: 'production' },
21
+ },
22
+ };
23
+
24
+ const url = generateRFCProtocolUrl({
25
+ id: 'edgeone-mcp',
26
+ schema,
27
+ marketId: 'higress',
28
+ });
29
+
30
+ expect(url).toMatch(/^lobehub:\/\/plugin\/install\?/);
31
+ expect(url).toContain('id=edgeone-mcp');
32
+ expect(url).toContain('marketId=higress');
33
+
34
+ // Verify schema is URL encoded
35
+ const urlObj = new URL(url);
36
+ const schemaParam = urlObj.searchParams.get('schema');
37
+ expect(schemaParam).toBeTruthy();
38
+ // URLSearchParams.get() 自动解码,所以这里得到的是解码后的JSON
39
+ expect(schemaParam).toContain('"'); // 解码后的引号
40
+ });
41
+
42
+ it('should generate valid RFC protocol URL for http type', () => {
43
+ const schema: McpSchema = {
44
+ identifier: 'awesome-api',
45
+ name: 'Awesome API',
46
+ author: 'Smithery',
47
+ description: 'Awesome API integration',
48
+ version: '2.0.0',
49
+ config: {
50
+ type: 'http',
51
+ url: 'https://api.smithery.ai/v1/mcp',
52
+ headers: {
53
+ 'Authorization': 'Bearer token123',
54
+ 'X-Custom-Header': 'value',
55
+ },
56
+ },
57
+ };
58
+
59
+ const url = generateRFCProtocolUrl({
60
+ id: 'awesome-api',
61
+ schema,
62
+ marketId: 'smithery',
63
+ });
64
+
65
+ expect(url).toMatch(/^lobehub:\/\/plugin\/install\?/);
66
+ expect(url).toContain('id=awesome-api');
67
+ expect(url).toContain('marketId=smithery');
68
+ });
69
+
70
+ it('should throw error if schema identifier does not match id', () => {
71
+ const schema: McpSchema = {
72
+ identifier: 'wrong-id',
73
+ name: 'Test',
74
+ author: 'Test',
75
+ description: 'Test',
76
+ version: '1.0.0',
77
+ config: { type: 'stdio', command: 'test' },
78
+ };
79
+
80
+ expect(() => generateRFCProtocolUrl({ id: 'different-id', schema })).toThrowError(
81
+ 'Schema identifier must match the id parameter',
82
+ );
83
+ });
84
+ });
85
+
86
+ describe('parseProtocolUrl', () => {
87
+ it('should parse RFC protocol URL correctly', () => {
88
+ const schema: McpSchema = {
89
+ identifier: 'test-mcp',
90
+ name: 'Test MCP',
91
+ author: 'Test Author',
92
+ description: 'Test Description',
93
+ version: '1.0.0',
94
+ config: {
95
+ type: 'stdio',
96
+ command: 'test',
97
+ args: ['arg1', 'arg2'],
98
+ },
99
+ };
100
+
101
+ const url = generateRFCProtocolUrl({
102
+ id: 'test-mcp',
103
+ schema,
104
+ marketId: 'lobehub',
105
+ });
106
+
107
+ const parsed = parseProtocolUrl(url);
108
+
109
+ expect(parsed).toBeTruthy();
110
+ expect(parsed?.urlType).toBe('plugin');
111
+ expect(parsed?.action).toBe('install');
112
+ expect(parsed?.params.type).toBe('mcp');
113
+ expect(parsed?.params.id).toBe('test-mcp');
114
+ expect(parsed?.params.marketId).toBe('lobehub');
115
+ expect(parsed?.originalUrl).toBe(url);
116
+
117
+ // 验证 schema 可以被解析
118
+ const parsedSchema = JSON.parse(parsed?.params.schema || '{}');
119
+ expect(parsedSchema).toEqual(schema);
120
+ });
121
+
122
+ it('should return null for invalid protocol', () => {
123
+ const result = parseProtocolUrl('http://example.com');
124
+ expect(result).toBeNull();
125
+ });
126
+
127
+ it('should parse URLs with any action', () => {
128
+ const result = parseProtocolUrl('lobehub://plugin/configure?id=test');
129
+ expect(result).toBeTruthy();
130
+ expect(result?.urlType).toBe('plugin');
131
+ expect(result?.action).toBe('configure');
132
+ expect(result?.params.id).toBe('test');
133
+ });
134
+
135
+ it('should parse URLs with any query parameters', () => {
136
+ const result = parseProtocolUrl('lobehub://plugin/install?custom=value&another=param');
137
+ expect(result).toBeTruthy();
138
+ expect(result?.urlType).toBe('plugin');
139
+ expect(result?.action).toBe('install');
140
+ expect(result?.params.custom).toBe('value');
141
+ expect(result?.params.another).toBe('param');
142
+ });
143
+
144
+ it('should handle URLs without query parameters', () => {
145
+ const result = parseProtocolUrl('lobehub://plugin/install');
146
+ expect(result).toBeTruthy();
147
+ expect(result?.urlType).toBe('plugin');
148
+ expect(result?.action).toBe('install');
149
+ expect(Object.keys(result?.params || {})).toHaveLength(0);
150
+ });
151
+
152
+ it('should return null for URLs without action', () => {
153
+ const result = parseProtocolUrl('lobehub://plugin/');
154
+ expect(result).toBeNull();
155
+ });
156
+ });
157
+
158
+ describe('URL encoding/decoding', () => {
159
+ it('should handle special characters correctly', () => {
160
+ const schema: McpSchema = {
161
+ identifier: 'special-chars',
162
+ name: '特殊字符 ñ 🚀',
163
+ author: 'Test <test@example.com>',
164
+ description: 'Description with "quotes" and \'apostrophes\'',
165
+ version: '1.0.0',
166
+ config: {
167
+ type: 'stdio',
168
+ command: 'cmd',
169
+ args: ['arg with spaces', 'arg/with/slashes'],
170
+ },
171
+ };
172
+
173
+ const url = generateRFCProtocolUrl({ id: 'special-chars', schema });
174
+ const parsed = parseProtocolUrl(url);
175
+
176
+ expect(parsed).toBeTruthy();
177
+ expect(parsed?.params.id).toBe('special-chars');
178
+ expect(parsed?.params.type).toBe('mcp');
179
+
180
+ // 验证 schema 可以正确解析
181
+ const parsedSchema = JSON.parse(parsed?.params.schema || '{}');
182
+ expect(parsedSchema).toEqual(schema);
183
+ });
184
+
185
+ it('should handle different protocol schemes', () => {
186
+ const testCases = [
187
+ 'lobehub://plugin/install?test=value',
188
+ 'lobehub-dev://plugin/install?test=value',
189
+ 'lobehub-beta://plugin/install?test=value',
190
+ 'lobehub-nightly://plugin/install?test=value',
191
+ ];
192
+
193
+ testCases.forEach((url) => {
194
+ const parsed = parseProtocolUrl(url);
195
+ expect(parsed).toBeTruthy();
196
+ expect(parsed?.urlType).toBe('plugin');
197
+ expect(parsed?.action).toBe('install');
198
+ expect(parsed?.params.test).toBe('value');
199
+ expect(parsed?.originalUrl).toBe(url);
200
+ });
201
+ });
202
+ });
203
+ });
@@ -0,0 +1,210 @@
1
+ import { app } from 'electron';
2
+
3
+ import { McpSchema, ProtocolUrlParsed } from '../types/protocol';
4
+
5
+ export type AppChannel = 'stable' | 'beta' | 'nightly';
6
+
7
+ export const getProtocolScheme = (): string => {
8
+ // 在 Electron 环境中可以通过多种方式判断版本
9
+ const bundleId = app.name;
10
+ const appPath = app.getPath('exe');
11
+
12
+ // 通过 bundle identifier 判断
13
+ if (bundleId?.toLowerCase().includes('nightly')) return 'lobehub-nightly';
14
+ if (bundleId?.toLowerCase().includes('beta')) return 'lobehub-beta';
15
+ if (bundleId?.includes('dev')) return 'lobehub-dev';
16
+
17
+ // 通过可执行文件路径判断
18
+ if (appPath?.toLowerCase().includes('nightly')) return 'lobehub-nightly';
19
+ if (appPath?.toLowerCase().includes('beta')) return 'lobehub-beta';
20
+ if (appPath?.includes('dev')) return 'lobehub-dev';
21
+
22
+ return 'lobehub';
23
+ };
24
+
25
+ export const getVersionInfo = (): { channel: AppChannel; protocolScheme: string } => {
26
+ const protocolScheme = getProtocolScheme();
27
+
28
+ let appChannel: AppChannel = 'stable';
29
+ if (protocolScheme.includes('nightly')) {
30
+ appChannel = 'nightly';
31
+ } else if (protocolScheme.includes('beta')) {
32
+ appChannel = 'beta';
33
+ }
34
+
35
+ return {
36
+ channel: appChannel,
37
+ protocolScheme,
38
+ };
39
+ };
40
+
41
+ /**
42
+ * 验证 MCP Schema 对象结构
43
+ * @param schema 待验证的对象
44
+ * @returns 是否为有效的 MCP Schema
45
+ */
46
+ function validateMcpSchema(schema: any): schema is McpSchema {
47
+ if (!schema || typeof schema !== 'object') return false;
48
+
49
+ // 必填字段验证
50
+ if (typeof schema.identifier !== 'string' || !schema.identifier) return false;
51
+ if (typeof schema.name !== 'string' || !schema.name) return false;
52
+ if (typeof schema.author !== 'string' || !schema.author) return false;
53
+ if (typeof schema.description !== 'string' || !schema.description) return false;
54
+ if (typeof schema.version !== 'string' || !schema.version) return false;
55
+
56
+ // 可选字段验证
57
+ if (schema.homepage !== undefined && typeof schema.homepage !== 'string') return false;
58
+ if (schema.icon !== undefined && typeof schema.icon !== 'string') return false;
59
+
60
+ // config 字段验证
61
+ if (!schema.config || typeof schema.config !== 'object') return false;
62
+ const config = schema.config;
63
+
64
+ if (config.type === 'stdio') {
65
+ if (typeof config.command !== 'string' || !config.command) return false;
66
+ if (config.args !== undefined && !Array.isArray(config.args)) return false;
67
+ if (config.env !== undefined && typeof config.env !== 'object') return false;
68
+ } else if (config.type === 'http') {
69
+ if (typeof config.url !== 'string' || !config.url) return false;
70
+ try {
71
+ new URL(config.url); // 验证URL格式
72
+ } catch {
73
+ return false;
74
+ }
75
+ if (config.headers !== undefined && typeof config.headers !== 'object') return false;
76
+ } else {
77
+ return false; // 未知的 config type
78
+ }
79
+
80
+ return true;
81
+ }
82
+
83
+ /**
84
+ * 解析 lobehub:// 协议 URL (支持多版本协议)
85
+ *
86
+ * 支持的URL格式:
87
+ * - lobehub://plugin/install?id=figma&schema=xxx&marketId=lobehub
88
+ * - lobehub://plugin/configure?id=xxx&...
89
+ * - lobehub-bet://plugin/install?id=figma&schema=xxx&marketId=lobehub
90
+ * - lobehub-nightly://plugin/install?id=figma&schema=xxx&marketId=lobehub
91
+ * - lobehub-dev://plugin/install?id=figma&schema=xxx&marketId=lobehub
92
+ *
93
+ * @param url 协议 URL
94
+ * @returns 解析结果,包含基本结构和所有查询参数
95
+ */
96
+ export const parseProtocolUrl = (url: string): ProtocolUrlParsed | null => {
97
+ try {
98
+ const parsedUrl = new URL(url);
99
+
100
+ // 支持多种协议 scheme
101
+ const validProtocols = ['lobehub:', 'lobehub-dev:', 'lobehub-nightly:', 'lobehub-beta:'];
102
+ if (!validProtocols.includes(parsedUrl.protocol)) {
103
+ return null;
104
+ }
105
+
106
+ // 对于自定义协议,URL 解析后:
107
+ // lobehub://plugin/install -> hostname: "plugin", pathname: "/install"
108
+ const urlType = parsedUrl.hostname; // "plugin"
109
+ const pathParts = parsedUrl.pathname.split('/').filter(Boolean); // ["install"]
110
+
111
+ if (pathParts.length < 1) {
112
+ return null;
113
+ }
114
+
115
+ const action = pathParts[0]; // "install"
116
+
117
+ // 解析所有查询参数
118
+ const params: Record<string, string> = {};
119
+ const searchParams = new URLSearchParams(parsedUrl.search);
120
+
121
+ for (const [key, value] of searchParams.entries()) {
122
+ params[key] = value;
123
+ }
124
+
125
+ return {
126
+ action,
127
+ originalUrl: url,
128
+ params,
129
+ urlType,
130
+ };
131
+ } catch (error) {
132
+ console.error('Failed to parse protocol URL:', error);
133
+ return null;
134
+ }
135
+ };
136
+
137
+ /**
138
+ * 生成符合 RFC 0001 的协议 URL
139
+ *
140
+ * @param params 协议参数
141
+ * @returns 生成的协议URL
142
+ */
143
+ export function generateRFCProtocolUrl(params: {
144
+ /** 插件唯一标识符 */
145
+ id: string;
146
+ /** Marketplace ID */
147
+ marketId?: string;
148
+ /** MCP Schema 对象 */
149
+ schema: McpSchema;
150
+ /** 协议 scheme (默认: lobehub) */
151
+ scheme?: string;
152
+ }): string {
153
+ const { id, schema, marketId, scheme = 'lobehub' } = params;
154
+
155
+ // 验证 schema.identifier 与 id 匹配
156
+ if (schema.identifier !== id) {
157
+ throw new Error('Schema identifier must match the id parameter');
158
+ }
159
+
160
+ // 验证 schema 结构
161
+ if (!validateMcpSchema(schema)) {
162
+ throw new Error('Invalid MCP Schema structure');
163
+ }
164
+
165
+ // 构建基础 URL
166
+ const baseUrl = `${scheme}://plugin/install`;
167
+
168
+ // 构建查询参数
169
+ const searchParams = new URLSearchParams();
170
+
171
+ // 必需参数
172
+ searchParams.set('type', 'mcp');
173
+ searchParams.set('id', id);
174
+
175
+ // 编码 schema - 直接传 JSON 字符串,让 URLSearchParams 自动编码
176
+ const schemaJson = JSON.stringify(schema);
177
+ searchParams.set('schema', schemaJson);
178
+
179
+ // 可选参数
180
+ if (marketId) {
181
+ searchParams.set('marketId', marketId);
182
+ }
183
+
184
+ return `${baseUrl}?${searchParams.toString()}`;
185
+ }
186
+
187
+ /**
188
+ * 生成协议 URL 示例
189
+ *
190
+ * @example
191
+ * ```typescript
192
+ * const url = generateRFCProtocolUrl({
193
+ * id: 'edgeone-mcp',
194
+ * schema: {
195
+ * identifier: 'edgeone-mcp',
196
+ * name: 'EdgeOne MCP',
197
+ * author: 'Higress Team',
198
+ * description: 'EdgeOne API integration for LobeChat',
199
+ * version: '1.0.0',
200
+ * config: {
201
+ * type: 'stdio',
202
+ * command: 'npx',
203
+ * args: ['-y', '@higress/edgeone-mcp']
204
+ * }
205
+ * },
206
+ * marketId: 'higress'
207
+ * });
208
+ * // Result: lobehub://plugin/install?id=edgeone-mcp&schema=%7B%22identifier%22%3A...&marketId=higress
209
+ * ```
210
+ */
package/changelog/v1.json CHANGED
@@ -1,4 +1,22 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Fix remote avatar broken in desktop again."
6
+ ]
7
+ },
8
+ "date": "2025-08-06",
9
+ "version": "1.110.1"
10
+ },
11
+ {
12
+ "children": {
13
+ "features": [
14
+ "Support mcp plugin install from web."
15
+ ]
16
+ },
17
+ "date": "2025-08-06",
18
+ "version": "1.110.0"
19
+ },
2
20
  {
3
21
  "children": {
4
22
  "fixes": [