@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.
- package/CHANGELOG.md +50 -0
- package/apps/desktop/package.json +1 -0
- package/apps/desktop/src/main/core/ui/__tests__/MenuManager.test.ts +320 -0
- package/apps/desktop/src/main/core/ui/__tests__/Tray.test.ts +518 -0
- package/apps/desktop/src/main/core/ui/__tests__/TrayManager.test.ts +360 -0
- package/apps/desktop/src/main/menus/impls/BaseMenuPlatform.test.ts +49 -0
- package/apps/desktop/src/main/menus/impls/linux.test.ts +552 -0
- package/apps/desktop/src/main/menus/impls/macOS.test.ts +464 -0
- package/apps/desktop/src/main/menus/impls/windows.test.ts +429 -0
- package/apps/desktop/src/main/modules/fileSearch/__tests__/macOS.integration.test.ts +2 -2
- package/apps/desktop/src/main/services/__tests__/fileSearchSrv.test.ts +402 -0
- package/apps/desktop/src/main/utils/__tests__/file-system.test.ts +91 -0
- package/apps/desktop/src/main/utils/__tests__/logger.test.ts +229 -0
- package/apps/desktop/src/preload/electronApi.test.ts +142 -0
- package/apps/desktop/src/preload/invoke.test.ts +145 -0
- package/apps/desktop/src/preload/routeInterceptor.test.ts +374 -0
- package/apps/desktop/src/preload/streamer.test.ts +365 -0
- package/apps/desktop/vitest.config.mts +1 -0
- package/changelog/v1.json +18 -0
- package/locales/ar/marketAuth.json +13 -0
- package/locales/bg-BG/marketAuth.json +13 -0
- package/locales/de-DE/marketAuth.json +13 -0
- package/locales/en-US/marketAuth.json +13 -0
- package/locales/es-ES/marketAuth.json +13 -0
- package/locales/fa-IR/marketAuth.json +13 -0
- package/locales/fr-FR/marketAuth.json +13 -0
- package/locales/it-IT/marketAuth.json +13 -0
- package/locales/ja-JP/marketAuth.json +13 -0
- package/locales/ko-KR/marketAuth.json +13 -0
- package/locales/nl-NL/marketAuth.json +13 -0
- package/locales/pl-PL/marketAuth.json +13 -0
- package/locales/pt-BR/marketAuth.json +13 -0
- package/locales/ru-RU/marketAuth.json +13 -0
- package/locales/tr-TR/marketAuth.json +13 -0
- package/locales/vi-VN/marketAuth.json +13 -0
- package/locales/zh-CN/marketAuth.json +13 -0
- package/locales/zh-TW/marketAuth.json +13 -0
- package/package.json +1 -1
- package/packages/database/migrations/0054_better_auth_two_factor.sql +2 -0
- package/packages/database/src/core/migrations.json +1 -1
- package/packages/database/src/models/user.ts +27 -5
- package/packages/types/src/discover/mcp.ts +2 -1
- package/packages/types/src/tool/plugin.ts +2 -1
- package/scripts/migrateServerDB/errorHint.js +26 -0
- package/scripts/migrateServerDB/index.ts +5 -1
- package/src/app/[variants]/(main)/chat/settings/features/SmartAgentActionButton/MarketPublishButton.tsx +0 -2
- package/src/app/[variants]/(main)/discover/(detail)/mcp/features/Sidebar/ActionButton/index.tsx +33 -7
- package/src/features/PluginStore/McpList/List/Action.tsx +20 -1
- package/src/layout/AuthProvider/MarketAuth/MarketAuthConfirmModal.tsx +158 -0
- package/src/layout/AuthProvider/MarketAuth/MarketAuthProvider.tsx +130 -14
- package/src/libs/mcp/types.ts +8 -0
- package/src/locales/default/marketAuth.ts +13 -0
- package/src/server/routers/lambda/market/index.ts +85 -2
- package/src/server/services/discover/index.ts +45 -4
- package/src/services/discover.ts +1 -1
- package/src/services/mcp.ts +18 -3
- package/src/store/tool/slices/mcpStore/action.test.ts +141 -0
- 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
|
|
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
|
|
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
|
|
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
|
/**
|
package/src/libs/mcp/types.ts
CHANGED
|
@@ -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
|
-
|
|
543
|
-
|
|
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(
|
|
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> = {
|
package/src/services/discover.ts
CHANGED
|
@@ -291,7 +291,7 @@ class DiscoverService {
|
|
|
291
291
|
|
|
292
292
|
// ============================== Helpers ==============================
|
|
293
293
|
|
|
294
|
-
|
|
294
|
+
async injectMPToken() {
|
|
295
295
|
if (typeof localStorage === 'undefined') return;
|
|
296
296
|
|
|
297
297
|
// Check server-set status flag cookie
|
package/src/services/mcp.ts
CHANGED
|
@@ -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
|
|
97
|
-
if (
|
|
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
|
|