@lobehub/lobehub 2.0.0-next.142 → 2.0.0-next.144

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 (58) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/apps/desktop/package.json +1 -0
  3. package/apps/desktop/src/main/core/ui/__tests__/MenuManager.test.ts +320 -0
  4. package/apps/desktop/src/main/core/ui/__tests__/Tray.test.ts +518 -0
  5. package/apps/desktop/src/main/core/ui/__tests__/TrayManager.test.ts +360 -0
  6. package/apps/desktop/src/main/menus/impls/BaseMenuPlatform.test.ts +49 -0
  7. package/apps/desktop/src/main/menus/impls/linux.test.ts +552 -0
  8. package/apps/desktop/src/main/menus/impls/macOS.test.ts +464 -0
  9. package/apps/desktop/src/main/menus/impls/windows.test.ts +429 -0
  10. package/apps/desktop/src/main/modules/fileSearch/__tests__/macOS.integration.test.ts +2 -2
  11. package/apps/desktop/src/main/services/__tests__/fileSearchSrv.test.ts +402 -0
  12. package/apps/desktop/src/main/utils/__tests__/file-system.test.ts +91 -0
  13. package/apps/desktop/src/main/utils/__tests__/logger.test.ts +229 -0
  14. package/apps/desktop/src/preload/electronApi.test.ts +142 -0
  15. package/apps/desktop/src/preload/invoke.test.ts +145 -0
  16. package/apps/desktop/src/preload/routeInterceptor.test.ts +374 -0
  17. package/apps/desktop/src/preload/streamer.test.ts +365 -0
  18. package/apps/desktop/vitest.config.mts +1 -0
  19. package/changelog/v1.json +18 -0
  20. package/locales/ar/marketAuth.json +13 -0
  21. package/locales/bg-BG/marketAuth.json +13 -0
  22. package/locales/de-DE/marketAuth.json +13 -0
  23. package/locales/en-US/marketAuth.json +13 -0
  24. package/locales/es-ES/marketAuth.json +13 -0
  25. package/locales/fa-IR/marketAuth.json +13 -0
  26. package/locales/fr-FR/marketAuth.json +13 -0
  27. package/locales/it-IT/marketAuth.json +13 -0
  28. package/locales/ja-JP/marketAuth.json +13 -0
  29. package/locales/ko-KR/marketAuth.json +13 -0
  30. package/locales/nl-NL/marketAuth.json +13 -0
  31. package/locales/pl-PL/marketAuth.json +13 -0
  32. package/locales/pt-BR/marketAuth.json +13 -0
  33. package/locales/ru-RU/marketAuth.json +13 -0
  34. package/locales/tr-TR/marketAuth.json +13 -0
  35. package/locales/vi-VN/marketAuth.json +13 -0
  36. package/locales/zh-CN/marketAuth.json +13 -0
  37. package/locales/zh-TW/marketAuth.json +13 -0
  38. package/package.json +1 -1
  39. package/packages/database/migrations/0054_better_auth_two_factor.sql +2 -0
  40. package/packages/database/src/core/migrations.json +1 -1
  41. package/packages/database/src/models/user.ts +27 -5
  42. package/packages/types/src/discover/mcp.ts +2 -1
  43. package/packages/types/src/tool/plugin.ts +2 -1
  44. package/scripts/migrateServerDB/errorHint.js +26 -0
  45. package/scripts/migrateServerDB/index.ts +5 -1
  46. package/src/app/[variants]/(main)/chat/settings/features/SmartAgentActionButton/MarketPublishButton.tsx +0 -2
  47. package/src/app/[variants]/(main)/discover/(detail)/mcp/features/Sidebar/ActionButton/index.tsx +33 -7
  48. package/src/features/PluginStore/McpList/List/Action.tsx +20 -1
  49. package/src/layout/AuthProvider/MarketAuth/MarketAuthConfirmModal.tsx +158 -0
  50. package/src/layout/AuthProvider/MarketAuth/MarketAuthProvider.tsx +130 -14
  51. package/src/libs/mcp/types.ts +8 -0
  52. package/src/locales/default/marketAuth.ts +13 -0
  53. package/src/server/routers/lambda/market/index.ts +85 -2
  54. package/src/server/services/discover/index.ts +45 -4
  55. package/src/services/discover.ts +1 -1
  56. package/src/services/mcp.ts +18 -3
  57. package/src/store/tool/slices/mcpStore/action.test.ts +141 -0
  58. package/src/store/tool/slices/mcpStore/action.ts +153 -11
@@ -0,0 +1,158 @@
1
+ 'use client';
2
+
3
+ import { Modal } from '@lobehub/ui';
4
+ import { createStyles } from 'antd-style';
5
+ import { ArrowRight, ShieldCheck } from 'lucide-react';
6
+ import { memo } from 'react';
7
+ import { useTranslation } from 'react-i18next';
8
+
9
+ const useStyles = createStyles(({ css, token }) => ({
10
+ content: css`
11
+ .ant-modal-content {
12
+ overflow: hidden;
13
+ padding: 0;
14
+ }
15
+
16
+ .ant-modal-header {
17
+ margin-block-end: 0;
18
+ padding: 0;
19
+ border-block-end: none;
20
+ }
21
+
22
+ .ant-modal-body {
23
+ padding: 0;
24
+ }
25
+
26
+ .ant-modal-footer {
27
+ display: flex;
28
+ align-items: center;
29
+ justify-content: space-between;
30
+
31
+ margin-block-start: 0;
32
+ padding-block: 16px;
33
+ padding-inline: 24px;
34
+ border-block-start: 1px solid ${token.colorBorder};
35
+
36
+ background: ${token.colorBgContainer};
37
+
38
+ .ant-btn {
39
+ margin: 0;
40
+ }
41
+ }
42
+ `,
43
+ description: css`
44
+ font-size: 14px;
45
+ line-height: 1.5;
46
+ color: ${token.colorTextSecondary};
47
+ text-align: center;
48
+
49
+ a {
50
+ color: ${token.colorPrimary};
51
+ text-decoration: none;
52
+
53
+ &:hover {
54
+ text-decoration: underline;
55
+ }
56
+ }
57
+
58
+ .highlight {
59
+ font-weight: 500;
60
+ color: ${token.colorText};
61
+ }
62
+ `,
63
+ header: css`
64
+ padding-block: 24px 16px;
65
+ padding-inline: 24px;
66
+ text-align: center;
67
+ `,
68
+ iconWrapper: css`
69
+ display: flex;
70
+ flex-direction: column;
71
+ align-items: center;
72
+
73
+ padding-block: 32px 0;
74
+ padding-inline: 0;
75
+ `,
76
+ okButton: css`
77
+ display: flex;
78
+ gap: 8px;
79
+ align-items: center;
80
+ `,
81
+ shieldIcon: css`
82
+ display: flex;
83
+ align-items: center;
84
+ justify-content: center;
85
+
86
+ width: 64px;
87
+ height: 64px;
88
+ border-radius: 50%;
89
+
90
+ background: ${token.colorPrimaryBg};
91
+
92
+ svg {
93
+ width: 36px;
94
+ height: 36px;
95
+ color: ${token.colorPrimary};
96
+ }
97
+ `,
98
+ title: css`
99
+ margin-block-end: 24px;
100
+ font-size: 18px;
101
+ font-weight: 600;
102
+ color: ${token.colorText};
103
+ `,
104
+ }));
105
+
106
+ interface MarketAuthConfirmModalProps {
107
+ onCancel: () => void;
108
+ onConfirm: () => void;
109
+ open: boolean;
110
+ }
111
+
112
+ const MarketAuthConfirmModal = memo<MarketAuthConfirmModalProps>(
113
+ ({ open, onConfirm, onCancel }) => {
114
+ const { t } = useTranslation('marketAuth');
115
+ const { styles } = useStyles();
116
+
117
+ return (
118
+ <Modal
119
+ cancelText={t('authorize.cancel')}
120
+ className={styles.content}
121
+ okButtonProps={{
122
+ className: styles.okButton,
123
+ icon: <ArrowRight size={16} />,
124
+ }}
125
+ okText={t('authorize.confirm')}
126
+ onCancel={onCancel}
127
+ onOk={onConfirm}
128
+ open={open}
129
+ title={null}
130
+ width={440}
131
+ >
132
+ <div className={styles.iconWrapper}>
133
+ <div className={styles.shieldIcon}>
134
+ <ShieldCheck />
135
+ </div>
136
+ </div>
137
+
138
+ <div className={styles.header}>
139
+ <div className={styles.title}>{t('authorize.title')}</div>
140
+ <div className={styles.description}>
141
+ {t('authorize.description.prefix')} <span className="highlight">LobeHub</span>{' '}
142
+ <a href="https://lobehub.com/terms" rel="noopener noreferrer" target="_blank">
143
+ {t('authorize.description.terms')}
144
+ </a>{' '}
145
+ {t('authorize.description.and')}{' '}
146
+ <a href="https://lobehub.com/privacy" rel="noopener noreferrer" target="_blank">
147
+ {t('authorize.description.privacy')}
148
+ </a>
149
+ </div>
150
+ </div>
151
+ </Modal>
152
+ );
153
+ },
154
+ );
155
+
156
+ MarketAuthConfirmModal.displayName = 'MarketAuthConfirmModal';
157
+
158
+ export default MarketAuthConfirmModal;
@@ -1,11 +1,14 @@
1
1
  'use client';
2
2
 
3
+ import { App } from 'antd';
3
4
  import { ReactNode, createContext, useContext, useEffect, useState } from 'react';
5
+ import { useTranslation } from 'react-i18next';
4
6
 
5
7
  import { MARKET_OIDC_ENDPOINTS } from '@/services/_url';
6
8
  import { useUserStore } from '@/store/user';
7
9
  import { settingsSelectors } from '@/store/user/slices/settings/selectors/settings';
8
10
 
11
+ import MarketAuthConfirmModal from './MarketAuthConfirmModal';
9
12
  import { MarketAuthError } from './errors';
10
13
  import { MarketOIDC } from './oidc';
11
14
  import { MarketAuthContextType, MarketAuthSession, MarketUserInfo, OIDCConfig } from './types';
@@ -165,10 +168,20 @@ const refreshToken = async (): Promise<boolean> => {
165
168
  * Market 授权上下文提供者
166
169
  */
167
170
  export const MarketAuthProvider = ({ children, isDesktop }: MarketAuthProviderProps) => {
171
+ const { message } = App.useApp();
172
+ const { t } = useTranslation('marketAuth');
173
+
168
174
  const [session, setSession] = useState<MarketAuthSession | null>(null);
169
175
  const [status, setStatus] = useState<'loading' | 'authenticated' | 'unauthenticated'>('loading');
170
176
  const [oidcClient, setOidcClient] = useState<MarketOIDC | null>(null);
171
177
  const [shouldReauthorize, setShouldReauthorize] = useState(false);
178
+ const [showConfirmModal, setShowConfirmModal] = useState(false);
179
+ const [pendingSignInResolve, setPendingSignInResolve] = useState<
180
+ ((value: number | null) => void) | null
181
+ >(null);
182
+ const [pendingSignInReject, setPendingSignInReject] = useState<((reason?: any) => void) | null>(
183
+ null,
184
+ );
172
185
 
173
186
  // 初始化 OIDC 客户端(仅在客户端)
174
187
  useEffect(() => {
@@ -196,8 +209,56 @@ export const MarketAuthProvider = ({ children, isDesktop }: MarketAuthProviderPr
196
209
  */
197
210
  const restoreSession = () => {
198
211
  console.log('[MarketAuth] Attempting to restore session');
199
- const token = getTokenFromCookie();
200
212
 
213
+ // 优先级 1: 从 DB 中获取 token(优先级最高)
214
+ const dbTokens = getMarketTokensFromDB();
215
+ if (dbTokens?.accessToken && dbTokens?.expiresAt) {
216
+ // 检查 DB 中的 token 是否过期
217
+ if (dbTokens.expiresAt > Date.now()) {
218
+ console.log('[MarketAuth] Session restored from DB');
219
+
220
+ // 尝试从 sessionStorage 获取用户信息(如果有的话)
221
+ let userInfo: MarketUserInfo | undefined;
222
+ const userInfoData = sessionStorage.getItem('market_user_info');
223
+ if (userInfoData) {
224
+ try {
225
+ userInfo = JSON.parse(userInfoData);
226
+ } catch (error) {
227
+ console.error('[MarketAuth] Failed to parse stored user info:', error);
228
+ }
229
+ }
230
+
231
+ // 创建会话对象
232
+ const restoredSession: MarketAuthSession = {
233
+ accessToken: dbTokens.accessToken,
234
+ expiresAt: dbTokens.expiresAt,
235
+ expiresIn: Math.floor((dbTokens.expiresAt - Date.now()) / 1000),
236
+ scope: 'openid profile email',
237
+ tokenType: 'Bearer',
238
+ userInfo,
239
+ };
240
+
241
+ // 同步到 cookie 和 sessionStorage
242
+ setTokenToCookie(dbTokens.accessToken, restoredSession.expiresIn);
243
+ sessionStorage.setItem('market_auth_session', JSON.stringify(restoredSession));
244
+
245
+ setSession(restoredSession);
246
+ setStatus('authenticated');
247
+ return;
248
+ } else {
249
+ console.log('[MarketAuth] DB token has expired, will trigger re-authorization');
250
+ // 清理过期的 DB tokens
251
+ clearMarketTokensFromDB();
252
+ sessionStorage.removeItem('market_auth_session');
253
+ removeTokenFromCookie();
254
+ // 标记需要重新授权,等待 oidcClient 准备好
255
+ setShouldReauthorize(true);
256
+ return;
257
+ }
258
+ }
259
+
260
+ // 优先级 2: 从 cookie 和 sessionStorage 中获取(DB 中没有时的备选方案)
261
+ const token = getTokenFromCookie();
201
262
  if (token) {
202
263
  // 从 sessionStorage 中获取完整的会话信息
203
264
  const sessionData = sessionStorage.getItem('market_auth_session');
@@ -207,14 +268,7 @@ export const MarketAuthProvider = ({ children, isDesktop }: MarketAuthProviderPr
207
268
 
208
269
  // 检查 token 是否过期
209
270
  if (parsedSession.expiresAt > Date.now()) {
210
- console.log('[MarketAuth] Session restored from storage');
211
-
212
- console.log('parsedSession', parsedSession);
213
- console.log('parsedSession.userInfo', parsedSession.userInfo);
214
- console.log(
215
- "sessionStorage.getItem('market_user_info')",
216
- sessionStorage.getItem('market_user_info'),
217
- );
271
+ console.log('[MarketAuth] Session restored from cookie/sessionStorage');
218
272
 
219
273
  // 如果 session 中没有 userInfo,尝试从单独的存储中获取
220
274
  if (!parsedSession.userInfo) {
@@ -230,6 +284,9 @@ export const MarketAuthProvider = ({ children, isDesktop }: MarketAuthProviderPr
230
284
  }
231
285
  }
232
286
 
287
+ // 同步到 DB
288
+ saveMarketTokensToDB(parsedSession.accessToken, undefined, parsedSession.expiresAt);
289
+
233
290
  setSession(parsedSession);
234
291
  setStatus('authenticated');
235
292
  return;
@@ -254,9 +311,9 @@ export const MarketAuthProvider = ({ children, isDesktop }: MarketAuthProviderPr
254
311
  };
255
312
 
256
313
  /**
257
- * 登录方法
314
+ * 实际执行登录的方法(内部使用)
258
315
  */
259
- const signIn = async (): Promise<number | null> => {
316
+ const handleActualSignIn = async (): Promise<number | null> => {
260
317
  console.log('[MarketAuth] Starting sign in process');
261
318
 
262
319
  if (!oidcClient) {
@@ -311,15 +368,65 @@ export const MarketAuthProvider = ({ children, isDesktop }: MarketAuthProviderPr
311
368
  setSession(newSession);
312
369
  setStatus('authenticated');
313
370
 
314
- console.log('[MarketAuth] Sign in completed successfully');
315
371
  return userInfo?.accountId ?? null;
316
372
  } catch (error) {
317
- console.error('[MarketAuth] Sign in failed:', error);
318
373
  setStatus('unauthenticated');
374
+
375
+ // 根据错误类型显示不同的错误消息
376
+ if (error instanceof MarketAuthError) {
377
+ message.error(t(`errors.${error.code}`) || t('errors.general'));
378
+ } else {
379
+ message.error(t('errors.general'));
380
+ }
381
+
319
382
  throw error;
320
383
  }
321
384
  };
322
385
 
386
+ /**
387
+ * 登录方法(会先弹出确认对话框)
388
+ */
389
+ const signIn = async (): Promise<number | null> => {
390
+ return new Promise<number | null>((resolve, reject) => {
391
+ setPendingSignInResolve(() => resolve);
392
+ setPendingSignInReject(() => reject);
393
+ setShowConfirmModal(true);
394
+ });
395
+ };
396
+
397
+ /**
398
+ * 处理确认授权
399
+ */
400
+ const handleConfirmAuth = async () => {
401
+ setShowConfirmModal(false);
402
+ try {
403
+ const result = await handleActualSignIn();
404
+ if (pendingSignInResolve) {
405
+ pendingSignInResolve(result);
406
+ setPendingSignInResolve(null);
407
+ setPendingSignInReject(null);
408
+ }
409
+ } catch (error) {
410
+ if (pendingSignInReject) {
411
+ pendingSignInReject(error);
412
+ setPendingSignInResolve(null);
413
+ setPendingSignInReject(null);
414
+ }
415
+ }
416
+ };
417
+
418
+ /**
419
+ * 处理取消授权
420
+ */
421
+ const handleCancelAuth = () => {
422
+ setShowConfirmModal(false);
423
+ if (pendingSignInReject) {
424
+ pendingSignInReject(new Error('User cancelled authorization'));
425
+ setPendingSignInResolve(null);
426
+ setPendingSignInReject(null);
427
+ }
428
+ };
429
+
323
430
  /**
324
431
  * 登出方法
325
432
  */
@@ -468,7 +575,16 @@ export const MarketAuthProvider = ({ children, isDesktop }: MarketAuthProviderPr
468
575
  status,
469
576
  };
470
577
 
471
- return <MarketAuthContext.Provider value={contextValue}>{children}</MarketAuthContext.Provider>;
578
+ return (
579
+ <MarketAuthContext.Provider value={contextValue}>
580
+ {children}
581
+ <MarketAuthConfirmModal
582
+ onCancel={handleCancelAuth}
583
+ onConfirm={handleConfirmAuth}
584
+ open={showConfirmModal}
585
+ />
586
+ </MarketAuthContext.Provider>
587
+ );
472
588
  };
473
589
 
474
590
  /**
@@ -149,6 +149,14 @@ export interface StdioMCPParams {
149
149
  type: 'stdio';
150
150
  }
151
151
 
152
+ export interface CloudMCPParams {
153
+ auth?: AuthConfig;
154
+ headers?: Record<string, string>;
155
+ name: string;
156
+ type: 'cloud';
157
+ url: string;
158
+ }
159
+
152
160
  export type MCPClientParams = HttpMCPClientParams | StdioMCPParams;
153
161
 
154
162
  export type MCPErrorType =
@@ -1,4 +1,15 @@
1
1
  export default {
2
+ authorize: {
3
+ cancel: '取消',
4
+ confirm: '授权使用',
5
+ description: {
6
+ and: '和',
7
+ prefix: '点击授权使用即视为同意',
8
+ privacy: '隐私协议',
9
+ terms: '服务条款',
10
+ },
11
+ title: '确认授权',
12
+ },
2
13
  callback: {
3
14
  buttons: {
4
15
  close: '关闭窗口',
@@ -33,8 +44,10 @@ export default {
33
44
  stateMissing: '未找到授权状态,请重试。',
34
45
  },
35
46
  messages: {
47
+ authorized: 'LobeHub 服务授权成功',
36
48
  loading: '正在启动授权流程...',
37
49
  success: {
50
+ cloudMcpInstall: '授权成功!现在可以安装 Cloud MCP 插件了。',
38
51
  submit: '授权成功!现在可以发布助手了。',
39
52
  upload: '授权成功!现在可以发布新版本了。',
40
53
  },
@@ -4,8 +4,14 @@ import { serialize } from 'cookie';
4
4
  import debug from 'debug';
5
5
  import { z } from 'zod';
6
6
 
7
- import { publicProcedure, router } from '@/libs/trpc/lambda';
7
+ import { authedProcedure, publicProcedure, router } from '@/libs/trpc/lambda';
8
+ import { serverDatabase } from '@/libs/trpc/lambda/middleware';
8
9
  import { DiscoverService } from '@/server/services/discover';
10
+ import { FileService } from '@/server/services/file';
11
+ import {
12
+ contentBlocksToString,
13
+ processContentBlocks,
14
+ } from '@/server/services/mcp/contentProcessor';
9
15
  import {
10
16
  AssistantSorts,
11
17
  McpConnectionType,
@@ -27,7 +33,85 @@ const marketProcedure = publicProcedure.use(async ({ ctx, next }) => {
27
33
  });
28
34
  });
29
35
 
36
+ // Procedure with user authentication for operations requiring user access token
37
+ const authedMarketProcedure = authedProcedure.use(serverDatabase).use(async ({ ctx, next }) => {
38
+ const { UserModel } = await import('@/database/models/user');
39
+ const userModel = new UserModel(ctx.serverDB, ctx.userId);
40
+
41
+ return next({
42
+ ctx: {
43
+ discoverService: new DiscoverService({ accessToken: ctx.marketAccessToken }),
44
+ fileService: new FileService(ctx.serverDB, ctx.userId),
45
+ userModel,
46
+ },
47
+ });
48
+ });
49
+
30
50
  export const marketRouter = router({
51
+ // ============================== Cloud MCP Gateway ==============================
52
+ callCloudMcpEndpoint: authedMarketProcedure
53
+ .input(
54
+ z.object({
55
+ apiParams: z.record(z.any()),
56
+ identifier: z.string(),
57
+ toolName: z.string(),
58
+ }),
59
+ )
60
+ .mutation(async ({ input, ctx }) => {
61
+ log('callCloudMcpEndpoint input: %O', input);
62
+
63
+ try {
64
+ // Query user_settings to get market.accessToken
65
+ const userState = await ctx.userModel.getUserState(async () => ({}));
66
+ const userAccessToken = userState.settings?.market?.accessToken;
67
+
68
+ log('callCloudMcpEndpoint: userAccessToken exists=%s', !!userAccessToken);
69
+
70
+ if (!userAccessToken) {
71
+ throw new TRPCError({
72
+ code: 'UNAUTHORIZED',
73
+ message: 'User access token not found. Please sign in to Market first.',
74
+ });
75
+ }
76
+
77
+ const cloudResult = await ctx.discoverService.callCloudMcpEndpoint({
78
+ apiParams: input.apiParams,
79
+ identifier: input.identifier,
80
+ toolName: input.toolName,
81
+ userAccessToken,
82
+ });
83
+
84
+ // Format the cloud result to MCPToolCallResult format
85
+ // Process content blocks (upload images, etc.)
86
+ const newContent =
87
+ cloudResult?.isError || !ctx.fileService
88
+ ? cloudResult?.content
89
+ : await processContentBlocks(cloudResult?.content, ctx.fileService);
90
+
91
+ // Convert content blocks to string
92
+ const content = contentBlocksToString(newContent);
93
+ const state = { ...cloudResult, content: newContent };
94
+
95
+ if (cloudResult?.isError) {
96
+ return { content, state, success: true };
97
+ }
98
+
99
+ return { content, state, success: true };
100
+ } catch (error) {
101
+ log('Error calling cloud MCP endpoint: %O', error);
102
+
103
+ // Re-throw TRPCError as-is
104
+ if (error instanceof TRPCError) {
105
+ throw error;
106
+ }
107
+
108
+ throw new TRPCError({
109
+ code: 'INTERNAL_SERVER_ERROR',
110
+ message: 'Failed to call cloud MCP endpoint',
111
+ });
112
+ }
113
+ }),
114
+
31
115
  // ============================== Assistant Market ==============================
32
116
  getAssistantCategories: marketProcedure
33
117
  .input(
@@ -558,7 +642,6 @@ export const marketRouter = router({
558
642
  }),
559
643
 
560
644
  // ============================== Analytics ==============================
561
-
562
645
  reportCall: marketProcedure
563
646
  .input(
564
647
  z.object({
@@ -119,6 +119,44 @@ export class DiscoverService {
119
119
  };
120
120
  }
121
121
 
122
+ // ============================== Call Cloud Mcp Endpoint Methods ==============================
123
+
124
+ async callCloudMcpEndpoint(params: {
125
+ apiParams: Record<string, any>;
126
+ identifier: string;
127
+ toolName: string;
128
+ userAccessToken: string;
129
+ }) {
130
+ log('callCloudMcpEndpoint: params=%O', {
131
+ apiParams: params.apiParams,
132
+ hasUserAccessToken: !!params.userAccessToken,
133
+ identifier: params.identifier,
134
+ toolName: params.toolName,
135
+ });
136
+
137
+ try {
138
+ // Call cloud gateway with user access token in Authorization header
139
+ const result = await this.market.plugins.callCloudGateway(
140
+ {
141
+ apiParams: params.apiParams,
142
+ identifier: params.identifier,
143
+ toolName: params.toolName,
144
+ },
145
+ {
146
+ headers: {
147
+ Authorization: `Bearer ${params.userAccessToken}`,
148
+ },
149
+ },
150
+ );
151
+
152
+ log('callCloudMcpEndpoint: success, result=%O', result);
153
+ return result;
154
+ } catch (error) {
155
+ log('callCloudMcpEndpoint: error=%O', error);
156
+ throw error;
157
+ }
158
+ }
159
+
122
160
  // ============================== Helper Methods ==============================
123
161
 
124
162
  /**
@@ -539,9 +577,9 @@ export class DiscoverService {
539
577
  description: (data as any).description || data.summary,
540
578
  examples: Array.isArray((data as any).examples)
541
579
  ? (data as any).examples.map((example: any) => ({
542
- content: typeof example === 'string' ? example : example.content || '',
543
- role: example.role || 'user',
544
- }))
580
+ content: typeof example === 'string' ? example : example.content || '',
581
+ role: example.role || 'user',
582
+ }))
545
583
  : [],
546
584
  homepage:
547
585
  (data as any).homepage ||
@@ -886,7 +924,10 @@ export class DiscoverService {
886
924
  const all = await this._getPluginList(locale);
887
925
  let raw = all.find((item) => item.identifier === identifier);
888
926
  if (!raw) {
889
- log('getPluginDetail: plugin not found in default store for identifier=%s, trying MCP plugin', identifier);
927
+ log(
928
+ 'getPluginDetail: plugin not found in default store for identifier=%s, trying MCP plugin',
929
+ identifier,
930
+ );
890
931
  try {
891
932
  const mcpDetail = await this.getMcpDetail({ identifier, locale });
892
933
  const convertedMcp: Partial<DiscoverPluginDetail> = {
@@ -291,7 +291,7 @@ class DiscoverService {
291
291
 
292
292
  // ============================== Helpers ==============================
293
293
 
294
- private async injectMPToken() {
294
+ async injectMPToken() {
295
295
  if (typeof localStorage === 'undefined') return;
296
296
 
297
297
  // Check server-set status flag cookie
@@ -5,7 +5,7 @@ import { PluginManifest } from '@lobehub/market-sdk';
5
5
  import { CallReportRequest } from '@lobehub/market-types';
6
6
 
7
7
  import { MCPToolCallResult } from '@/libs/mcp';
8
- import { desktopClient, toolsClient } from '@/libs/trpc/client';
8
+ import { desktopClient, lambdaClient, toolsClient } from '@/libs/trpc/client';
9
9
 
10
10
  import { discoverService } from './discover';
11
11
 
@@ -84,6 +84,7 @@ class MCPService {
84
84
  };
85
85
 
86
86
  const isStdio = plugin?.customParams?.mcp?.type === 'stdio';
87
+ const isCloud = plugin?.customParams?.mcp?.type === 'cloud';
87
88
 
88
89
  // Record call start time
89
90
  const callStartTime = Date.now();
@@ -93,10 +94,24 @@ class MCPService {
93
94
  let result: MCPToolCallResult | undefined;
94
95
 
95
96
  try {
96
- // For desktop and stdio, use the desktopClient
97
- if (isDesktop && isStdio) {
97
+ // For cloud type, call via cloud gateway
98
+ if (isCloud) {
99
+ // Parse args
100
+ const apiParams = safeParseJSON(args) || {};
101
+
102
+ // Call cloud gateway via lambda market endpoint
103
+ // Server will automatically get user access token from database
104
+ // and format the result to MCPToolCallResult
105
+ result = await lambdaClient.market.callCloudMcpEndpoint.mutate({
106
+ apiParams,
107
+ identifier,
108
+ toolName: apiName,
109
+ });
110
+ } else if (isDesktop && isStdio) {
111
+ // For desktop and stdio, use the desktopClient
98
112
  result = await desktopClient.mcp.callTool.mutate(data, { signal });
99
113
  } else {
114
+ // For other types, use the toolsClient
100
115
  result = await toolsClient.mcp.callTool.mutate(data, { signal });
101
116
  }
102
117