@lobehub/lobehub 2.0.0-next.160 → 2.0.0-next.162
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/.env.example +10 -0
- package/CHANGELOG.md +42 -0
- package/changelog/v1.json +14 -0
- package/e2e/src/steps/hooks.ts +1 -0
- package/locales/ar/authError.json +40 -0
- package/locales/ar/setting.json +25 -0
- package/locales/bg-BG/authError.json +40 -0
- package/locales/bg-BG/setting.json +25 -0
- package/locales/de-DE/authError.json +40 -0
- package/locales/de-DE/setting.json +25 -0
- package/locales/en-US/authError.json +40 -0
- package/locales/en-US/setting.json +25 -0
- package/locales/es-ES/authError.json +40 -0
- package/locales/es-ES/setting.json +25 -0
- package/locales/fa-IR/authError.json +40 -0
- package/locales/fa-IR/setting.json +25 -0
- package/locales/fr-FR/authError.json +40 -0
- package/locales/fr-FR/setting.json +25 -0
- package/locales/it-IT/authError.json +40 -0
- package/locales/it-IT/setting.json +25 -0
- package/locales/ja-JP/authError.json +40 -0
- package/locales/ja-JP/setting.json +25 -0
- package/locales/ko-KR/authError.json +40 -0
- package/locales/ko-KR/setting.json +25 -0
- package/locales/nl-NL/authError.json +40 -0
- package/locales/nl-NL/setting.json +25 -0
- package/locales/pl-PL/authError.json +40 -0
- package/locales/pl-PL/setting.json +25 -0
- package/locales/pt-BR/authError.json +40 -0
- package/locales/pt-BR/setting.json +25 -0
- package/locales/ru-RU/authError.json +40 -0
- package/locales/ru-RU/setting.json +25 -0
- package/locales/tr-TR/authError.json +40 -0
- package/locales/tr-TR/setting.json +25 -0
- package/locales/vi-VN/authError.json +40 -0
- package/locales/vi-VN/setting.json +25 -0
- package/locales/zh-CN/authError.json +40 -0
- package/locales/zh-CN/setting.json +25 -0
- package/locales/zh-TW/authError.json +40 -0
- package/locales/zh-TW/setting.json +25 -0
- package/next.config.ts +13 -1
- package/package.json +3 -1
- package/packages/const/src/index.ts +1 -0
- package/packages/const/src/klavis.ts +163 -0
- package/packages/database/migrations/meta/_journal.json +1 -1
- package/packages/database/src/core/migrations.json +1 -1
- package/packages/database/src/models/plugin.ts +1 -1
- package/packages/types/src/message/common/tools.ts +9 -0
- package/packages/types/src/serverConfig.ts +1 -0
- package/packages/types/src/tool/plugin.ts +10 -0
- package/src/app/[variants]/(auth)/auth-error/page.tsx +59 -0
- package/src/auth.ts +13 -48
- package/src/config/klavis.ts +41 -0
- package/src/envs/redis.ts +1 -1
- package/src/features/ChatInput/ActionBar/Tools/KlavisServerItem.tsx +351 -0
- package/src/features/ChatInput/ActionBar/Tools/index.tsx +56 -4
- package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +174 -6
- package/src/features/ChatInput/ActionBar/components/ActionDropdown.tsx +3 -1
- package/src/helpers/toolEngineering/index.test.ts +3 -0
- package/src/helpers/toolEngineering/index.ts +13 -2
- package/src/libs/better-auth/utils/config.ts +91 -0
- package/src/libs/klavis/index.ts +36 -0
- package/src/libs/redis/manager.ts +5 -1
- package/src/libs/redis/redis.test.ts +1 -1
- package/src/libs/redis/upstash.test.ts +9 -5
- package/src/libs/redis/upstash.ts +44 -20
- package/src/locales/default/authError.ts +40 -0
- package/src/locales/default/index.ts +2 -0
- package/src/locales/default/setting.ts +25 -0
- package/src/proxy.ts +1 -0
- package/src/server/globalConfig/index.ts +2 -0
- package/src/server/routers/lambda/index.ts +2 -0
- package/src/server/routers/lambda/klavis.ts +249 -0
- package/src/server/routers/tools/index.ts +2 -0
- package/src/server/routers/tools/klavis.ts +80 -0
- package/src/server/services/mcp/index.ts +61 -15
- package/src/services/import/index.test.ts +658 -0
- package/src/services/mcp.test.ts +1 -1
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +2 -3
- package/src/store/chat/slices/plugin/action.test.ts +0 -1
- package/src/store/chat/slices/plugin/actions/internals.ts +22 -2
- package/src/store/chat/slices/plugin/actions/pluginTypes.ts +108 -0
- package/src/store/serverConfig/index.ts +1 -1
- package/src/store/serverConfig/selectors.ts +1 -0
- package/src/store/tool/initialState.ts +4 -1
- package/src/store/tool/selectors/index.ts +1 -0
- package/src/store/tool/slices/builtin/selectors.ts +25 -3
- package/src/store/tool/slices/klavisStore/action.test.ts +512 -0
- package/src/store/tool/slices/klavisStore/action.ts +375 -0
- package/src/store/tool/slices/klavisStore/index.ts +4 -0
- package/src/store/tool/slices/klavisStore/initialState.ts +25 -0
- package/src/store/tool/slices/klavisStore/selectors.test.ts +371 -0
- package/src/store/tool/slices/klavisStore/selectors.ts +123 -0
- package/src/store/tool/slices/klavisStore/types.ts +100 -0
- package/src/store/tool/slices/plugin/selectors.ts +16 -13
- package/src/store/tool/store.ts +4 -1
|
@@ -779,12 +779,37 @@ export default {
|
|
|
779
779
|
groupName: '内置插件',
|
|
780
780
|
},
|
|
781
781
|
disabled: '当前模型不支持函数调用,无法使用插件',
|
|
782
|
+
klavis: {
|
|
783
|
+
addServer: '添加服务器',
|
|
784
|
+
authCompleted: '认证完成',
|
|
785
|
+
authFailed: '认证失败',
|
|
786
|
+
authRequired: '需要认证',
|
|
787
|
+
connected: '已连接',
|
|
788
|
+
error: '错误',
|
|
789
|
+
groupName: 'Klavis 工具',
|
|
790
|
+
manage: '管理 Klavis',
|
|
791
|
+
manageTitle: '管理 Klavis 集成',
|
|
792
|
+
noServers: '暂无连接的服务器',
|
|
793
|
+
notEnabled: 'Klavis 服务未启用',
|
|
794
|
+
oauthRequired: '请在新窗口中完成 OAuth 认证',
|
|
795
|
+
pendingAuth: '待认证',
|
|
796
|
+
serverCreated: '服务器创建成功',
|
|
797
|
+
serverCreatedFailed: '服务器创建失败',
|
|
798
|
+
serverRemoved: '服务器已删除',
|
|
799
|
+
servers: '个服务器',
|
|
800
|
+
tools: '个工具',
|
|
801
|
+
verifyAuth: '我已完成认证',
|
|
802
|
+
},
|
|
782
803
|
plugins: {
|
|
783
804
|
enabled: '已启用 {{num}}',
|
|
784
805
|
groupName: '三方插件',
|
|
785
806
|
noEnabled: '暂无启用插件',
|
|
786
807
|
store: '插件商店',
|
|
787
808
|
},
|
|
809
|
+
tabs: {
|
|
810
|
+
all: '全部',
|
|
811
|
+
installed: '已启用',
|
|
812
|
+
},
|
|
788
813
|
title: '扩展插件',
|
|
789
814
|
},
|
|
790
815
|
};
|
package/src/proxy.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { klavisEnv } from '@/config/klavis';
|
|
1
2
|
import { isDesktop } from '@/const/version';
|
|
2
3
|
import { appEnv, getAppConfig } from '@/envs/app';
|
|
3
4
|
import { authEnv } from '@/envs/auth';
|
|
@@ -66,6 +67,7 @@ export const getServerGlobalConfig = async () => {
|
|
|
66
67
|
defaultAgent: {
|
|
67
68
|
config: parseAgentConfig(DEFAULT_AGENT_CONFIG),
|
|
68
69
|
},
|
|
70
|
+
enableKlavis: !!klavisEnv.KLAVIS_API_KEY,
|
|
69
71
|
enableUploadFileToServer: !!fileEnv.S3_SECRET_ACCESS_KEY,
|
|
70
72
|
enabledAccessCode: ACCESS_CODES?.length > 0,
|
|
71
73
|
|
|
@@ -20,6 +20,7 @@ import { generationTopicRouter } from './generationTopic';
|
|
|
20
20
|
import { groupRouter } from './group';
|
|
21
21
|
import { imageRouter } from './image';
|
|
22
22
|
import { importerRouter } from './importer';
|
|
23
|
+
import { klavisRouter } from './klavis';
|
|
23
24
|
import { knowledgeBaseRouter } from './knowledgeBase';
|
|
24
25
|
import { marketRouter } from './market';
|
|
25
26
|
import { messageRouter } from './message';
|
|
@@ -52,6 +53,7 @@ export const lambdaRouter = router({
|
|
|
52
53
|
healthcheck: publicProcedure.query(() => "i'm live!"),
|
|
53
54
|
image: imageRouter,
|
|
54
55
|
importer: importerRouter,
|
|
56
|
+
klavis: klavisRouter,
|
|
55
57
|
knowledgeBase: knowledgeBaseRouter,
|
|
56
58
|
market: marketRouter,
|
|
57
59
|
message: messageRouter,
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
import { PluginModel } from '@/database/models/plugin';
|
|
5
|
+
import { getKlavisClient } from '@/libs/klavis';
|
|
6
|
+
import { authedProcedure, router } from '@/libs/trpc/lambda';
|
|
7
|
+
import { serverDatabase } from '@/libs/trpc/lambda/middleware';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Klavis procedure with API key validation and database access
|
|
11
|
+
*/
|
|
12
|
+
const klavisProcedure = authedProcedure.use(serverDatabase).use(async (opts) => {
|
|
13
|
+
const client = getKlavisClient();
|
|
14
|
+
const pluginModel = new PluginModel(opts.ctx.serverDB, opts.ctx.userId);
|
|
15
|
+
|
|
16
|
+
return opts.next({
|
|
17
|
+
ctx: { ...opts.ctx, klavisClient: client, pluginModel },
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const klavisRouter = router({
|
|
22
|
+
/**
|
|
23
|
+
* Create a single MCP server instance and save to database
|
|
24
|
+
* Returns: { serverUrl, instanceId, oauthUrl?, identifier, serverName }
|
|
25
|
+
*/
|
|
26
|
+
createServerInstance: klavisProcedure
|
|
27
|
+
.input(
|
|
28
|
+
z.object({
|
|
29
|
+
/** Identifier for storage (e.g., 'google-calendar') */
|
|
30
|
+
identifier: z.string(),
|
|
31
|
+
/** Server name for Klavis API (e.g., 'Google Calendar') */
|
|
32
|
+
serverName: z.string(),
|
|
33
|
+
userId: z.string(),
|
|
34
|
+
}),
|
|
35
|
+
)
|
|
36
|
+
.mutation(async ({ input, ctx }) => {
|
|
37
|
+
const { serverName, userId, identifier } = input;
|
|
38
|
+
|
|
39
|
+
// 创建单个服务器实例
|
|
40
|
+
const response = await ctx.klavisClient.mcpServer.createServerInstance({
|
|
41
|
+
serverName: serverName as any,
|
|
42
|
+
userId,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const { serverUrl, instanceId, oauthUrl } = response;
|
|
46
|
+
|
|
47
|
+
// 获取该服务器的工具列表
|
|
48
|
+
const toolsResponse = await ctx.klavisClient.mcpServer.getTools(serverName as any);
|
|
49
|
+
const tools = toolsResponse.tools || [];
|
|
50
|
+
|
|
51
|
+
// 保存到数据库,使用传入的 identifier(格式:小写,空格替换为连字符)
|
|
52
|
+
const manifest: LobeChatPluginManifest = {
|
|
53
|
+
api: tools.map((tool: any) => ({
|
|
54
|
+
description: tool.description || '',
|
|
55
|
+
name: tool.name,
|
|
56
|
+
parameters: tool.inputSchema || { properties: {}, type: 'object' },
|
|
57
|
+
})),
|
|
58
|
+
identifier,
|
|
59
|
+
meta: {
|
|
60
|
+
avatar: '🔌',
|
|
61
|
+
description: `Klavis MCP Server: ${serverName}`,
|
|
62
|
+
title: serverName,
|
|
63
|
+
},
|
|
64
|
+
type: 'default',
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// 保存到数据库,包含 oauthUrl 和 isAuthenticated 状态
|
|
68
|
+
const isAuthenticated = !oauthUrl; // 如果没有 oauthUrl,说明不需要认证或已认证
|
|
69
|
+
await ctx.pluginModel.create({
|
|
70
|
+
customParams: {
|
|
71
|
+
klavis: {
|
|
72
|
+
instanceId,
|
|
73
|
+
isAuthenticated,
|
|
74
|
+
oauthUrl,
|
|
75
|
+
serverName,
|
|
76
|
+
serverUrl,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
identifier,
|
|
80
|
+
manifest,
|
|
81
|
+
source: 'klavis',
|
|
82
|
+
type: 'plugin',
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
identifier,
|
|
87
|
+
instanceId,
|
|
88
|
+
isAuthenticated,
|
|
89
|
+
oauthUrl,
|
|
90
|
+
serverName,
|
|
91
|
+
serverUrl,
|
|
92
|
+
};
|
|
93
|
+
}),
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Delete a server instance
|
|
97
|
+
*/
|
|
98
|
+
deleteServerInstance: klavisProcedure
|
|
99
|
+
.input(
|
|
100
|
+
z.object({
|
|
101
|
+
/** Identifier for storage (e.g., 'google-calendar') */
|
|
102
|
+
identifier: z.string(),
|
|
103
|
+
instanceId: z.string(),
|
|
104
|
+
}),
|
|
105
|
+
)
|
|
106
|
+
.mutation(async ({ input, ctx }) => {
|
|
107
|
+
// 调用 Klavis API 删除服务器实例
|
|
108
|
+
await ctx.klavisClient.mcpServer.deleteServerInstance(input.instanceId);
|
|
109
|
+
|
|
110
|
+
// 从数据库删除(使用 identifier)
|
|
111
|
+
await ctx.pluginModel.delete(input.identifier);
|
|
112
|
+
|
|
113
|
+
return { success: true };
|
|
114
|
+
}),
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Get Klavis plugins from database
|
|
118
|
+
*/
|
|
119
|
+
getKlavisPlugins: klavisProcedure.query(async ({ ctx }) => {
|
|
120
|
+
const allPlugins = await ctx.pluginModel.query();
|
|
121
|
+
// Filter plugins that have klavis customParams
|
|
122
|
+
return allPlugins.filter((plugin) => plugin.customParams?.klavis);
|
|
123
|
+
}),
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get server instance status from Klavis API
|
|
127
|
+
*/
|
|
128
|
+
getServerInstance: klavisProcedure
|
|
129
|
+
.input(
|
|
130
|
+
z.object({
|
|
131
|
+
instanceId: z.string(),
|
|
132
|
+
}),
|
|
133
|
+
)
|
|
134
|
+
.query(async ({ input, ctx }) => {
|
|
135
|
+
const response = await ctx.klavisClient.mcpServer.getServerInstance(input.instanceId);
|
|
136
|
+
return {
|
|
137
|
+
authNeeded: response.authNeeded,
|
|
138
|
+
externalUserId: response.externalUserId,
|
|
139
|
+
instanceId: response.instanceId,
|
|
140
|
+
isAuthenticated: response.isAuthenticated,
|
|
141
|
+
oauthUrl: response.oauthUrl,
|
|
142
|
+
platform: response.platform,
|
|
143
|
+
serverName: response.serverName,
|
|
144
|
+
};
|
|
145
|
+
}),
|
|
146
|
+
|
|
147
|
+
getUserIntergrations: klavisProcedure
|
|
148
|
+
.input(
|
|
149
|
+
z.object({
|
|
150
|
+
userId: z.string(),
|
|
151
|
+
}),
|
|
152
|
+
)
|
|
153
|
+
.query(async ({ input, ctx }) => {
|
|
154
|
+
const response = await ctx.klavisClient.user.getUserIntegrations(input.userId);
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
integrations: response.integrations,
|
|
158
|
+
};
|
|
159
|
+
}),
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Remove Klavis plugin from database by identifier
|
|
163
|
+
*/
|
|
164
|
+
removeKlavisPlugin: klavisProcedure
|
|
165
|
+
.input(
|
|
166
|
+
z.object({
|
|
167
|
+
/** Identifier for storage (e.g., 'google-calendar') */
|
|
168
|
+
identifier: z.string(),
|
|
169
|
+
}),
|
|
170
|
+
)
|
|
171
|
+
.mutation(async ({ input, ctx }) => {
|
|
172
|
+
await ctx.pluginModel.delete(input.identifier);
|
|
173
|
+
return { success: true };
|
|
174
|
+
}),
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Update Klavis plugin with tools and auth status in database
|
|
178
|
+
*/
|
|
179
|
+
updateKlavisPlugin: klavisProcedure
|
|
180
|
+
.input(
|
|
181
|
+
z.object({
|
|
182
|
+
/** Identifier for storage (e.g., 'google-calendar') */
|
|
183
|
+
identifier: z.string(),
|
|
184
|
+
instanceId: z.string(),
|
|
185
|
+
isAuthenticated: z.boolean(),
|
|
186
|
+
oauthUrl: z.string().optional(),
|
|
187
|
+
/** Server name for Klavis API (e.g., 'Google Calendar') */
|
|
188
|
+
serverName: z.string(),
|
|
189
|
+
serverUrl: z.string(),
|
|
190
|
+
tools: z.array(
|
|
191
|
+
z.object({
|
|
192
|
+
description: z.string().optional(),
|
|
193
|
+
inputSchema: z.any().optional(),
|
|
194
|
+
name: z.string(),
|
|
195
|
+
}),
|
|
196
|
+
),
|
|
197
|
+
}),
|
|
198
|
+
)
|
|
199
|
+
.mutation(async ({ input, ctx }) => {
|
|
200
|
+
const { identifier, serverName, serverUrl, instanceId, tools, isAuthenticated, oauthUrl } =
|
|
201
|
+
input;
|
|
202
|
+
|
|
203
|
+
// 获取现有插件(使用 identifier)
|
|
204
|
+
const existingPlugin = await ctx.pluginModel.findById(identifier);
|
|
205
|
+
|
|
206
|
+
// 构建包含所有工具的 manifest
|
|
207
|
+
const manifest: LobeChatPluginManifest = {
|
|
208
|
+
api: tools.map((tool) => ({
|
|
209
|
+
description: tool.description || '',
|
|
210
|
+
name: tool.name,
|
|
211
|
+
parameters: tool.inputSchema || { properties: {}, type: 'object' },
|
|
212
|
+
})),
|
|
213
|
+
identifier,
|
|
214
|
+
meta: existingPlugin?.manifest?.meta || {
|
|
215
|
+
avatar: '🔌',
|
|
216
|
+
description: `Klavis MCP Server: ${serverName}`,
|
|
217
|
+
title: serverName,
|
|
218
|
+
},
|
|
219
|
+
type: 'default',
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const customParams = {
|
|
223
|
+
klavis: {
|
|
224
|
+
instanceId,
|
|
225
|
+
isAuthenticated,
|
|
226
|
+
oauthUrl,
|
|
227
|
+
serverName,
|
|
228
|
+
serverUrl,
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// 更新或创建插件
|
|
233
|
+
if (existingPlugin) {
|
|
234
|
+
await ctx.pluginModel.update(identifier, { customParams, manifest });
|
|
235
|
+
} else {
|
|
236
|
+
await ctx.pluginModel.create({
|
|
237
|
+
customParams,
|
|
238
|
+
identifier,
|
|
239
|
+
manifest,
|
|
240
|
+
source: 'klavis',
|
|
241
|
+
type: 'plugin',
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return { savedCount: tools.length };
|
|
246
|
+
}),
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
export type KlavisRouter = typeof klavisRouter;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { publicProcedure, router } from '@/libs/trpc/lambda';
|
|
2
2
|
|
|
3
|
+
import { klavisRouter } from './klavis';
|
|
3
4
|
import { mcpRouter } from './mcp';
|
|
4
5
|
import { searchRouter } from './search';
|
|
5
6
|
|
|
6
7
|
export const toolsRouter = router({
|
|
7
8
|
healthcheck: publicProcedure.query(() => "i'm live!"),
|
|
9
|
+
klavis: klavisRouter,
|
|
8
10
|
mcp: mcpRouter,
|
|
9
11
|
search: searchRouter,
|
|
10
12
|
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
import { getKlavisClient } from '@/libs/klavis';
|
|
4
|
+
import { authedProcedure, router } from '@/libs/trpc/lambda';
|
|
5
|
+
import { MCPService } from '@/server/services/mcp';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Klavis procedure with client initialized in context
|
|
9
|
+
*/
|
|
10
|
+
const klavisProcedure = authedProcedure.use(async (opts) => {
|
|
11
|
+
const klavisClient = getKlavisClient();
|
|
12
|
+
|
|
13
|
+
return opts.next({
|
|
14
|
+
ctx: { ...opts.ctx, klavisClient },
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Klavis router for tools
|
|
20
|
+
* Contains callTool and listTools which call external Klavis API
|
|
21
|
+
*/
|
|
22
|
+
export const klavisRouter = router({
|
|
23
|
+
/**
|
|
24
|
+
* Call a tool on a Klavis Strata server
|
|
25
|
+
*/
|
|
26
|
+
callTool: klavisProcedure
|
|
27
|
+
.input(
|
|
28
|
+
z.object({
|
|
29
|
+
serverUrl: z.string(),
|
|
30
|
+
toolArgs: z.record(z.unknown()).optional(),
|
|
31
|
+
toolName: z.string(),
|
|
32
|
+
}),
|
|
33
|
+
)
|
|
34
|
+
.mutation(async ({ ctx, input }) => {
|
|
35
|
+
const response = await ctx.klavisClient.mcpServer.callTools({
|
|
36
|
+
serverUrl: input.serverUrl,
|
|
37
|
+
toolArgs: input.toolArgs,
|
|
38
|
+
toolName: input.toolName,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Handle error case
|
|
42
|
+
if (!response.success || !response.result) {
|
|
43
|
+
return {
|
|
44
|
+
content: response.error || 'Unknown error',
|
|
45
|
+
state: {
|
|
46
|
+
content: [{ text: response.error || 'Unknown error', type: 'text' }],
|
|
47
|
+
isError: true,
|
|
48
|
+
},
|
|
49
|
+
success: false,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Process the response using the common MCP tool call result processor
|
|
54
|
+
const processedResult = await MCPService.processToolCallResult({
|
|
55
|
+
content: (response.result.content || []) as any[],
|
|
56
|
+
isError: response.result.isError,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return processedResult;
|
|
60
|
+
}),
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* List tools available on a Klavis Strata server
|
|
64
|
+
*/
|
|
65
|
+
listTools: klavisProcedure
|
|
66
|
+
.input(
|
|
67
|
+
z.object({
|
|
68
|
+
serverUrl: z.string(),
|
|
69
|
+
}),
|
|
70
|
+
)
|
|
71
|
+
.query(async ({ ctx, input }) => {
|
|
72
|
+
const response = await ctx.klavisClient.mcpServer.listTools({
|
|
73
|
+
serverUrl: input.serverUrl,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
tools: response.tools,
|
|
78
|
+
};
|
|
79
|
+
}),
|
|
80
|
+
});
|
|
@@ -21,12 +21,60 @@ import { mcpSystemDepsCheckService } from './deps';
|
|
|
21
21
|
|
|
22
22
|
const log = debug('lobe-mcp:service');
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* MCP Tool call raw result type
|
|
26
|
+
*/
|
|
27
|
+
export interface MCPToolCallRawResult {
|
|
28
|
+
content: any[];
|
|
29
|
+
isError?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* MCP Tool call processed result type
|
|
34
|
+
*/
|
|
35
|
+
export interface MCPToolCallProcessedResult {
|
|
36
|
+
content: string;
|
|
37
|
+
error?: Error;
|
|
38
|
+
state: {
|
|
39
|
+
content: any[];
|
|
40
|
+
isError?: boolean;
|
|
41
|
+
};
|
|
42
|
+
success: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
24
45
|
// Removed MCPConnection interface as it's no longer needed
|
|
25
46
|
|
|
26
47
|
export class MCPService {
|
|
27
48
|
// Store instances of the custom MCPClient, keyed by serialized MCPClientParams
|
|
28
49
|
private clients: Map<string, MCPClient> = new Map();
|
|
29
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Process MCP tool call result with content blocks processing
|
|
53
|
+
* This is a common utility method that can be used by both internal MCP calls and external services (e.g., Klavis)
|
|
54
|
+
*/
|
|
55
|
+
static async processToolCallResult(
|
|
56
|
+
result: MCPToolCallRawResult,
|
|
57
|
+
processContentBlocksFn?: ProcessContentBlocksFn,
|
|
58
|
+
): Promise<MCPToolCallProcessedResult> {
|
|
59
|
+
// Process content blocks (upload images, etc.)
|
|
60
|
+
|
|
61
|
+
const newContent =
|
|
62
|
+
result.isError || !processContentBlocksFn
|
|
63
|
+
? result.content
|
|
64
|
+
: await processContentBlocksFn(result.content);
|
|
65
|
+
|
|
66
|
+
// Convert content blocks to string
|
|
67
|
+
const content = contentBlocksToString(newContent);
|
|
68
|
+
|
|
69
|
+
const state = { ...result, content: newContent };
|
|
70
|
+
|
|
71
|
+
if (result.isError) {
|
|
72
|
+
return { content, state, success: true };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { content, state, success: true };
|
|
76
|
+
}
|
|
77
|
+
|
|
30
78
|
private sanitizeForLogging = <T extends Record<string, any>>(obj: T): Omit<T, 'env'> => {
|
|
31
79
|
if (!obj) return obj;
|
|
32
80
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -162,7 +210,12 @@ export class MCPService {
|
|
|
162
210
|
processContentBlocks?: ProcessContentBlocksFn;
|
|
163
211
|
toolName: string;
|
|
164
212
|
}): Promise<any> {
|
|
165
|
-
const {
|
|
213
|
+
const {
|
|
214
|
+
clientParams,
|
|
215
|
+
toolName,
|
|
216
|
+
argsStr,
|
|
217
|
+
processContentBlocks: processContentBlocksFn,
|
|
218
|
+
} = options;
|
|
166
219
|
|
|
167
220
|
const client = await this.getClient(clientParams); // Get client using params
|
|
168
221
|
|
|
@@ -179,26 +232,19 @@ export class MCPService {
|
|
|
179
232
|
// Delegate the call to the MCPClient instance
|
|
180
233
|
const result = await client.callTool(toolName, args); // Pass args directly
|
|
181
234
|
|
|
182
|
-
//
|
|
183
|
-
const
|
|
184
|
-
result
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
// Convert content blocks to string
|
|
189
|
-
const content = contentBlocksToString(newContent);
|
|
190
|
-
|
|
191
|
-
const state = { ...result, content: newContent };
|
|
235
|
+
// Use the common processing method
|
|
236
|
+
const processedResult = await MCPService.processToolCallResult(
|
|
237
|
+
result,
|
|
238
|
+
processContentBlocksFn,
|
|
239
|
+
);
|
|
192
240
|
|
|
193
241
|
log(
|
|
194
242
|
`Tool "${toolName}" called successfully for params: %O, result: %O`,
|
|
195
243
|
loggableParams,
|
|
196
|
-
state,
|
|
244
|
+
processedResult.state,
|
|
197
245
|
);
|
|
198
246
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return { content, state, success: true };
|
|
247
|
+
return processedResult;
|
|
202
248
|
} catch (error) {
|
|
203
249
|
if (error instanceof McpError) {
|
|
204
250
|
const mcpError = error as McpError;
|