@lobehub/lobehub 2.0.0-next.27 → 2.0.0-next.29

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 (34) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/package.json +1 -1
  4. package/packages/database/package.json +1 -1
  5. package/packages/database/src/models/message.ts +29 -2
  6. package/packages/types/src/discover/mcp.ts +6 -0
  7. package/packages/types/src/message/ui/params.ts +0 -49
  8. package/packages/types/src/plugins/mcp.ts +4 -1
  9. package/renovate.json +4 -30
  10. package/src/features/MCP/utils.test.ts +91 -0
  11. package/src/features/MCP/utils.ts +20 -2
  12. package/src/features/PluginStore/Content.tsx +2 -3
  13. package/src/features/PluginStore/McpList/index.tsx +6 -2
  14. package/src/server/routers/lambda/__tests__/integration/message.integration.test.ts +35 -35
  15. package/src/server/routers/lambda/market/index.ts +4 -2
  16. package/src/server/routers/lambda/message.ts +104 -16
  17. package/src/services/mcp.ts +40 -6
  18. package/src/services/message/index.ts +63 -35
  19. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts +17 -10
  20. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChatV2.test.ts +4 -4
  21. package/src/store/chat/slices/aiChat/actions/__tests__/helpers.ts +2 -2
  22. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +3 -2
  23. package/src/store/chat/slices/aiChat/actions/generateAIChatV2.ts +2 -2
  24. package/src/store/chat/slices/aiChat/actions/generateAIGroupChat.ts +7 -2
  25. package/src/store/chat/slices/builtinTool/actions/search.ts +3 -3
  26. package/src/store/chat/slices/message/action.test.ts +152 -15
  27. package/src/store/chat/slices/message/action.ts +70 -82
  28. package/src/store/chat/slices/plugin/action.test.ts +84 -25
  29. package/src/store/chat/slices/plugin/action.ts +52 -24
  30. package/src/store/chat/slices/thread/action.test.ts +13 -4
  31. package/src/store/chat/slices/thread/action.ts +3 -1
  32. package/src/store/tool/slices/mcpStore/action.test.ts +95 -3
  33. package/src/store/tool/slices/mcpStore/action.ts +177 -53
  34. package/src/store/tool/slices/oldStore/initialState.ts +1 -2
package/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.29](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.28...v2.0.0-next.29)
6
+
7
+ <sup>Released on **2025-11-04**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **misc**: Refactor chat message model to speed up.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Code refactoring
19
+
20
+ - **misc**: Refactor chat message model to speed up, closes [#10053](https://github.com/lobehub/lobe-chat/issues/10053) ([035994f](https://github.com/lobehub/lobe-chat/commit/035994f))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ## [Version 2.0.0-next.28](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.27...v2.0.0-next.28)
31
+
32
+ <sup>Released on **2025-11-04**</sup>
33
+
34
+ #### ✨ Features
35
+
36
+ - **misc**: Support install sreamable http mcp server on web.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### What's improved
44
+
45
+ - **misc**: Support install sreamable http mcp server on web, closes [#10044](https://github.com/lobehub/lobe-chat/issues/10044) [#9916](https://github.com/lobehub/lobe-chat/issues/9916) ([85454c5](https://github.com/lobehub/lobe-chat/commit/85454c5))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ## [Version 2.0.0-next.27](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.26...v2.0.0-next.27)
6
56
 
7
57
  <sup>Released on **2025-11-04**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,22 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "improvements": [
5
+ "Refactor chat message model to speed up."
6
+ ]
7
+ },
8
+ "date": "2025-11-04",
9
+ "version": "2.0.0-next.29"
10
+ },
11
+ {
12
+ "children": {
13
+ "features": [
14
+ "Support install sreamable http mcp server on web."
15
+ ]
16
+ },
17
+ "date": "2025-11-04",
18
+ "version": "2.0.0-next.28"
19
+ },
2
20
  {
3
21
  "children": {
4
22
  "improvements": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.27",
3
+ "version": "2.0.0-next.29",
4
4
  "description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -24,7 +24,7 @@
24
24
  "peerDependencies": {
25
25
  "@electric-sql/pglite": "^0.2.17",
26
26
  "dayjs": ">=1.11.18",
27
- "drizzle-orm": ">=0.44.6",
27
+ "drizzle-orm": ">=0.44.7",
28
28
  "nanoid": ">=5.1.5",
29
29
  "pg": ">=8.16.3"
30
30
  }
@@ -603,6 +603,7 @@ export class MessageModel {
603
603
  id: string,
604
604
  { imageList, ...message }: Partial<UpdateMessageParams>,
605
605
  options?: {
606
+ groupAssistantMessages?: boolean;
606
607
  postProcessUrl?: (path: string | null, file: { fileType: string }) => Promise<string>;
607
608
  sessionId?: string | null;
608
609
  topicId?: string | null;
@@ -633,6 +634,7 @@ export class MessageModel {
633
634
  topicId: options.topicId,
634
635
  },
635
636
  {
637
+ groupAssistantMessages: options.groupAssistantMessages ?? false,
636
638
  postProcessUrl: options.postProcessUrl,
637
639
  },
638
640
  );
@@ -660,16 +662,41 @@ export class MessageModel {
660
662
  .where(and(eq(messages.userId, this.userId), eq(messages.id, id)));
661
663
  };
662
664
 
663
- updatePluginState = async (id: string, state: Record<string, any>) => {
665
+ updatePluginState = async (
666
+ id: string,
667
+ state: Record<string, any>,
668
+ options?: {
669
+ groupAssistantMessages?: boolean;
670
+ postProcessUrl?: (path: string | null, file: { fileType: string }) => Promise<string>;
671
+ sessionId?: string | null;
672
+ topicId?: string | null;
673
+ },
674
+ ): Promise<UpdateMessageResult> => {
664
675
  const item = await this.db.query.messagePlugins.findFirst({
665
676
  where: eq(messagePlugins.id, id),
666
677
  });
667
678
  if (!item) throw new Error('Plugin not found');
668
679
 
669
- return this.db
680
+ await this.db
670
681
  .update(messagePlugins)
671
682
  .set({ state: merge(item.state || {}, state) })
672
683
  .where(eq(messagePlugins.id, id));
684
+
685
+ // Return updated messages if sessionId or topicId is provided
686
+ if (options?.sessionId !== undefined || options?.topicId !== undefined) {
687
+ const messageList = await this.query(
688
+ {
689
+ sessionId: options.sessionId,
690
+ topicId: options.topicId,
691
+ },
692
+ {
693
+ groupAssistantMessages: options.groupAssistantMessages ?? false,
694
+ postProcessUrl: options.postProcessUrl,
695
+ },
696
+ );
697
+ return { messages: messageList, success: true };
698
+ }
699
+ return { success: true };
673
700
  };
674
701
 
675
702
  updateMessagePlugin = async (id: string, value: Partial<MessagePluginItem>) => {
@@ -39,10 +39,16 @@ export enum McpNavKey {
39
39
  Version = 'version',
40
40
  }
41
41
 
42
+ export enum McpConnectionType {
43
+ http = 'http',
44
+ stdio = 'stdio'
45
+ }
46
+
42
47
  export type DiscoverMcpItem = PluginItem;
43
48
 
44
49
  export interface McpQueryParams {
45
50
  category?: string;
51
+ connectionType?: McpConnectionType;
46
52
  locale?: string;
47
53
  order?: 'asc' | 'desc';
48
54
  page?: number;
@@ -120,55 +120,6 @@ const ChatPluginPayloadSchema = z.object({
120
120
  type: z.string(),
121
121
  });
122
122
 
123
- export const CreateMessageParamsSchema = z
124
- .object({
125
- content: z.string(),
126
- role: UIMessageRoleTypeSchema,
127
- sessionId: z.string().nullable().optional(),
128
- error: ChatMessageErrorSchema.nullable().optional(),
129
- fileChunks: z.array(SemanticSearchChunkSchema).optional(),
130
- files: z.array(z.string()).optional(),
131
- fromModel: z.string().optional(),
132
- fromProvider: z.string().optional(),
133
- groupId: z.string().nullable().optional(),
134
- targetId: z.string().nullable().optional(),
135
- threadId: z.string().nullable().optional(),
136
- topicId: z.string().nullable().optional(),
137
- traceId: z.string().optional(),
138
- // Allow additional fields from UIChatMessage (many can be null)
139
- agentId: z.string().optional(),
140
- children: z.any().optional(),
141
- chunksList: z.any().optional(),
142
- createdAt: z.number().optional(),
143
- extra: z.any().optional(),
144
- favorite: z.boolean().optional(),
145
- fileList: z.any().optional(),
146
- id: z.string().optional(),
147
- imageList: z.any().optional(),
148
- meta: z.any().optional(),
149
- metadata: z.any().nullable().optional(),
150
- model: z.string().nullable().optional(),
151
- observationId: z.string().optional(),
152
- parentId: z.string().optional(),
153
- performance: z.any().optional(),
154
- plugin: z.any().optional(),
155
- pluginError: z.any().optional(),
156
- pluginState: z.any().optional(),
157
- provider: z.string().nullable().optional(),
158
- quotaId: z.string().optional(),
159
- ragQuery: z.string().nullable().optional(),
160
- ragQueryId: z.string().nullable().optional(),
161
- reasoning: z.any().optional(),
162
- search: z.any().optional(),
163
- tool_call_id: z.string().optional(),
164
- toolCalls: z.any().optional(),
165
- tools: z.any().optional(),
166
- translate: z.any().optional(),
167
- tts: z.any().optional(),
168
- updatedAt: z.number().optional(),
169
- })
170
- .passthrough();
171
-
172
123
  export const CreateNewMessageParamsSchema = z
173
124
  .object({
174
125
  // Required fields
@@ -3,6 +3,7 @@ import { z } from 'zod';
3
3
 
4
4
  import { MCPErrorType } from '@/libs/mcp';
5
5
 
6
+ import { McpConnectionType } from '../discover/mcp';
6
7
  import { CustomPluginMetadata } from '../tool/plugin';
7
8
 
8
9
  /* eslint-disable typescript-sort-keys/string-enum */
@@ -110,7 +111,9 @@ export interface CheckMcpInstallResult {
110
111
  }>;
111
112
  }
112
113
 
113
- export type MCPPluginListParams = Pick<PluginQueryParams, 'locale' | 'pageSize' | 'page' | 'q'>;
114
+ export type MCPPluginListParams = Pick<PluginQueryParams, 'locale' | 'pageSize' | 'page' | 'q'> & {
115
+ connectionType?: McpConnectionType;
116
+ };
114
117
 
115
118
  export interface MCPErrorInfoMetadata {
116
119
  errorLog?: string;
package/renovate.json CHANGED
@@ -21,44 +21,18 @@
21
21
  {
22
22
  "description": "Isolate PRs for pinned deps (exact x.y.z)",
23
23
  "matchManagers": ["npm", "pnpm", "yarn", "bun"],
24
- "matchDepTypes": [
25
- "dependencies",
26
- "devDependencies",
27
- "optionalDependencies",
28
- "peerDependencies"
29
- ],
30
24
  "matchCurrentValue": "^\\d+\\.\\d+\\.\\d+([+-][0-9A-Za-z.-]+)?$",
31
25
  "groupName": null,
32
26
  "separateMinorPatch": true,
33
27
  "separateMajorMinor": true
34
28
  },
35
- // 2a) Non-pinned deps: override splitting so patch+minor can be combined
29
+ // 2) Non-pinned deps: Patch versions, grouped together
36
30
  {
37
- "description": "Non-pinned deps: allow patch+minor to group; keep majors separate",
31
+ "description": "Group patch versions together for non-pinned deps",
38
32
  "matchManagers": ["npm", "pnpm", "yarn", "bun"],
39
- "matchDepTypes": [
40
- "dependencies",
41
- "devDependencies",
42
- "optionalDependencies",
43
- "peerDependencies"
44
- ],
45
33
  "matchCurrentValue": "/(^[~^]|[<>=| -])/", // anything that looks like a range
46
- "separateMinorPatch": false,
47
- "separateMajorMinor": true
48
- },
49
- // 2b) Non-pinned deps: actually group patch+minor together
50
- {
51
- "description": "Non-pinned deps: group non-major updates",
52
- "matchManagers": ["npm", "pnpm", "yarn", "bun"],
53
- "matchDepTypes": [
54
- "dependencies",
55
- "devDependencies",
56
- "optionalDependencies",
57
- "peerDependencies"
58
- ],
59
- "matchCurrentValue": "/(^[~^]|[<>=| -])/",
60
- "matchUpdateTypes": ["minor", "patch"], // only non-majors
61
- "groupName": "deps (non-major)"
34
+ "groupName": "patch dependencies",
35
+ "matchUpdateTypes": ["patch"]
62
36
  }
63
37
  ],
64
38
  "postUpdateOptions": ["yarnDedupeHighest"],
@@ -0,0 +1,91 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { genServerConfig } from './utils';
4
+
5
+ describe('genServerConfig', () => {
6
+ it('should generate HTTP MCP server config with url', () => {
7
+ const result = genServerConfig('context7', {
8
+ type: 'http',
9
+ url: 'https://mcp.context7.com/mcp',
10
+ } as any);
11
+
12
+ const config = JSON.parse(result);
13
+
14
+ expect(config).toEqual({
15
+ mcpServers: {
16
+ context7: {
17
+ url: 'https://mcp.context7.com/mcp',
18
+ },
19
+ },
20
+ });
21
+ });
22
+
23
+ it('should generate stdio MCP server config with command and args', () => {
24
+ const result = genServerConfig('github', {
25
+ args: ['-y', '@modelcontextprotocol/server-github'],
26
+ command: 'npx',
27
+ type: 'stdio',
28
+ } as any);
29
+
30
+ const config = JSON.parse(result);
31
+
32
+ expect(config).toEqual({
33
+ mcpServers: {
34
+ github: {
35
+ args: ['-y', '@modelcontextprotocol/server-github'],
36
+ command: 'npx',
37
+ },
38
+ },
39
+ });
40
+ });
41
+
42
+ it('should handle empty connection config', () => {
43
+ const result = genServerConfig('test-plugin', {} as any);
44
+
45
+ const config = JSON.parse(result);
46
+
47
+ expect(config).toEqual({
48
+ mcpServers: {
49
+ 'test-plugin': {
50
+ args: [],
51
+ command: {},
52
+ },
53
+ },
54
+ });
55
+ });
56
+
57
+ it('should handle undefined connection', () => {
58
+ const result = genServerConfig('test-plugin', undefined);
59
+
60
+ const config = JSON.parse(result);
61
+
62
+ expect(config).toEqual({
63
+ mcpServers: {
64
+ 'test-plugin': {
65
+ args: [],
66
+ command: {},
67
+ },
68
+ },
69
+ });
70
+ });
71
+
72
+ it('should prioritize url over command/args when both exist', () => {
73
+ const result = genServerConfig('hybrid', {
74
+ args: ['arg1'],
75
+ command: 'cmd',
76
+ type: 'http',
77
+ url: 'https://example.com/mcp',
78
+ } as any);
79
+
80
+ const config = JSON.parse(result);
81
+
82
+ // Should only include url, not command/args
83
+ expect(config).toEqual({
84
+ mcpServers: {
85
+ hybrid: {
86
+ url: 'https://example.com/mcp',
87
+ },
88
+ },
89
+ });
90
+ });
91
+ });
@@ -1,7 +1,24 @@
1
1
  import { ConnectionConfig, DeploymentOption } from '@lobehub/market-types';
2
2
 
3
- export const genServerConfig = (identifier?: string, connection?: ConnectionConfig) =>
4
- JSON.stringify(
3
+ export const genServerConfig = (identifier?: string, connection?: ConnectionConfig) => {
4
+ // 检查是否为 HTTP 类型
5
+ if (connection?.url) {
6
+ // HTTP 类型配置
7
+ return JSON.stringify(
8
+ {
9
+ mcpServers: {
10
+ [String(identifier)]: {
11
+ url: connection.url,
12
+ },
13
+ },
14
+ },
15
+ null,
16
+ 2,
17
+ );
18
+ }
19
+
20
+ // stdio 类型配置
21
+ return JSON.stringify(
5
22
  {
6
23
  mcpServers: {
7
24
  [String(identifier)]: {
@@ -13,6 +30,7 @@ export const genServerConfig = (identifier?: string, connection?: ConnectionConf
13
30
  null,
14
31
  2,
15
32
  );
33
+ };
16
34
 
17
35
  export const getRecommendedDeployment = (deploymentOptions: DeploymentOption[]) =>
18
36
  deploymentOptions?.find((item) => item.isRecommended) || deploymentOptions?.[0];
@@ -4,7 +4,6 @@ import { memo, useState } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import { Flexbox } from 'react-layout-kit';
6
6
 
7
- import { isDesktop } from '@/const/version';
8
7
  import { useServerConfigStore } from '@/store/serverConfig';
9
8
  import { useToolStore } from '@/store/tool';
10
9
  import { PluginStoreTabs } from '@/store/tool/slices/oldStore';
@@ -22,7 +21,7 @@ export const Content = memo(() => {
22
21
  const [keywords] = useState<string>();
23
22
 
24
23
  const options = [
25
- isDesktop ? { label: t('store.tabs.mcp'), value: PluginStoreTabs.MCP } : undefined,
24
+ { label: t('store.tabs.mcp'), value: PluginStoreTabs.MCP },
26
25
  { label: t('store.tabs.old'), value: PluginStoreTabs.Plugin },
27
26
  { label: t('store.tabs.installed'), value: PluginStoreTabs.Installed },
28
27
  ].filter(Boolean) as SegmentedOptions;
@@ -45,7 +44,7 @@ export const Content = memo(() => {
45
44
  value={listType}
46
45
  variant={'filled'}
47
46
  />
48
- <AddPluginButton />
47
+ {mobile ? null : <AddPluginButton />}
49
48
  </Flexbox>
50
49
  <Search />
51
50
  </Flexbox>
@@ -5,7 +5,7 @@ import { memo, useRef } from 'react';
5
5
  import { Flexbox } from 'react-layout-kit';
6
6
 
7
7
  import { useToolStore } from '@/store/tool';
8
-
8
+ import { useServerConfigStore } from '@/store/serverConfig';
9
9
  import DetailLoading from './Detail/Loading';
10
10
  import List from './List';
11
11
 
@@ -15,6 +15,8 @@ export const MCPPluginList = memo(() => {
15
15
  const ref = useRef<HTMLDivElement>(null);
16
16
  const theme = useTheme();
17
17
 
18
+ const mobile = useServerConfigStore((s) => s.isMobile);
19
+
18
20
  return (
19
21
  <Flexbox
20
22
  height={'75vh'}
@@ -26,7 +28,9 @@ export const MCPPluginList = memo(() => {
26
28
  }}
27
29
  width={'100%'}
28
30
  >
29
- <DraggablePanel maxWidth={1024} minWidth={420} placement={'left'}>
31
+ {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
32
+ {/* @ts-ignore */}
33
+ <DraggablePanel maxWidth={1024} minWidth={mobile ? '100vw' : 420} placement={'left'}>
30
34
  <List
31
35
  setIdentifier={(identifier) => {
32
36
  useToolStore.setState({ activeMCPIdentifier: identifier });