@lobehub/lobehub 2.0.0-next.250 → 2.0.0-next.251

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 CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.251](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.250...v2.0.0-next.251)
6
+
7
+ <sup>Released on **2026-01-09**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **community**: Support to report for agent & mcp plugin interaction for recommendation.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **community**: Support to report for agent & mcp plugin interaction for recommendation, closes [#11289](https://github.com/lobehub/lobe-chat/issues/11289) ([6f98792](https://github.com/lobehub/lobe-chat/commit/6f98792))
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
+
5
30
  ## [Version 2.0.0-next.250](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.249...v2.0.0-next.250)
6
31
 
7
32
  <sup>Released on **2026-01-09**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,9 @@
1
1
  [
2
+ {
3
+ "children": {},
4
+ "date": "2026-01-09",
5
+ "version": "2.0.0-next.251"
6
+ },
2
7
  {
3
8
  "children": {},
4
9
  "date": "2026-01-09",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.250",
3
+ "version": "2.0.0-next.251",
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",
@@ -18,7 +18,7 @@ export enum McpCategory {
18
18
  Tools = 'tools',
19
19
  TravelTransport = 'travel-transport',
20
20
  Weather = 'weather',
21
- WebSearch = 'web-search',
21
+ WebSearch = 'web-search'
22
22
  }
23
23
 
24
24
  export enum McpSorts {
@@ -83,11 +83,17 @@ const AddAgent = memo<{ mobile?: boolean }>(({ mobile }) => {
83
83
  // Report agent installation to marketplace if it has a market identifier
84
84
  if (identifier) {
85
85
  discoverService.reportAgentInstall(identifier);
86
+ discoverService.reportAgentEvent({
87
+ event: 'add',
88
+ identifier,
89
+ source: location.pathname,
90
+ })
86
91
  }
87
92
 
88
93
  if (shouldNavigate) {
89
94
  console.log(shouldNavigate);
90
95
  }
96
+
91
97
  return result;
92
98
  };
93
99
 
@@ -10,6 +10,7 @@ import urlJoin from 'url-join';
10
10
  import PublishedTime from '@/components/PublishedTime';
11
11
  import { useQuery } from '@/hooks/useQuery';
12
12
  import { type AssistantMarketSource, type DiscoverAssistantItem } from '@/types/discover';
13
+ import { discoverService } from '@/services/discover';
13
14
 
14
15
  import TokenTag from './TokenTag';
15
16
 
@@ -91,14 +92,22 @@ const AssistantItem = memo<DiscoverAssistantItem>(
91
92
  [userName, navigate],
92
93
  );
93
94
 
95
+ const handleClick = useCallback(() => {
96
+ discoverService.reportAgentEvent({
97
+ event: 'click',
98
+ identifier,
99
+ source: location.pathname,
100
+ }).catch(() => {});
101
+
102
+ navigate(link);
103
+ }, [identifier, link, navigate]);
104
+
94
105
  return (
95
106
  <Block
96
107
  clickable
97
108
  data-testid="assistant-item"
98
109
  height={'100%'}
99
- onClick={() => {
100
- navigate(link);
101
- }}
110
+ onClick={handleClick}
102
111
  style={{
103
112
  overflow: 'hidden',
104
113
  position: 'relative',
@@ -5,7 +5,7 @@ import { ActionIcon, Avatar, Block, Flexbox, Icon, Tag, Text, Tooltip } from '@l
5
5
  import { Spotlight } from '@lobehub/ui/awesome';
6
6
  import { createStaticStyles, cssVar } from 'antd-style';
7
7
  import { ClockIcon } from 'lucide-react';
8
- import { memo } from 'react';
8
+ import { memo, useCallback } from 'react';
9
9
  import { useTranslation } from 'react-i18next';
10
10
  import { Link, useNavigate } from 'react-router-dom';
11
11
  import urlJoin from 'url-join';
@@ -14,6 +14,7 @@ import InstallationIcon from '@/components/MCPDepsIcon';
14
14
  import OfficialIcon from '@/components/OfficialIcon';
15
15
  import PublishedTime from '@/components/PublishedTime';
16
16
  import Scores from '@/features/MCP/Scores';
17
+ import { discoverService } from '@/services/discover';
17
18
  import { type DiscoverMcpItem } from '@/types/discover';
18
19
 
19
20
  import ConnectionTypeTag from './ConnectionTypeTag';
@@ -77,14 +78,23 @@ const McpItem = memo<DiscoverMcpItem>(
77
78
  const { t } = useTranslation('discover');
78
79
  const navigate = useNavigate();
79
80
  const link = urlJoin('/community/mcp', identifier);
81
+
82
+ const handleClick = useCallback(() => {
83
+ discoverService.reportMcpEvent({
84
+ event: 'click',
85
+ identifier,
86
+ source: location.pathname,
87
+ }).catch(() => {});
88
+
89
+ navigate(link);
90
+ }, [identifier, link, navigate]);
91
+
80
92
  return (
81
93
  <Block
82
94
  clickable
83
95
  data-testid="mcp-item"
84
96
  height={'100%'}
85
- onClick={() => {
86
- navigate(link);
87
- }}
97
+ onClick={handleClick}
88
98
  style={{
89
99
  overflow: 'hidden',
90
100
  position: 'relative',
@@ -1,7 +1,7 @@
1
1
  import { useEffect } from 'react';
2
2
 
3
- import { AsyncTaskStatus } from '@/types/asyncTask';
4
3
  import { revalidateResources } from '@/store/file/slices/resource/hooks';
4
+ import { AsyncTaskStatus } from '@/types/asyncTask';
5
5
  import { type FileListItem } from '@/types/files';
6
6
 
7
7
  export const useCheckTaskStatus = (data: FileListItem[] | undefined) => {
@@ -167,7 +167,7 @@ export const marketRouter = router({
167
167
  }),
168
168
 
169
169
  // ============================== MCP Market ==============================
170
- getMcpCategories: marketProcedure
170
+ getMcpCategories: marketProcedure
171
171
  .input(
172
172
  z
173
173
  .object({
@@ -351,7 +351,7 @@ export const marketRouter = router({
351
351
  }),
352
352
 
353
353
  // ============================== Plugin Market ==============================
354
- getPluginCategories: marketProcedure
354
+ getPluginCategories: marketProcedure
355
355
  .input(
356
356
  z
357
357
  .object({
@@ -439,7 +439,7 @@ export const marketRouter = router({
439
439
  }),
440
440
 
441
441
  // ============================== Providers ==============================
442
- getProviderDetail: marketProcedure
442
+ getProviderDetail: marketProcedure
443
443
  .input(
444
444
  z.object({
445
445
  identifier: z.string(),
@@ -503,7 +503,7 @@ export const marketRouter = router({
503
503
  }),
504
504
 
505
505
  // ============================== User Profile ==============================
506
- getUserInfo: marketProcedure
506
+ getUserInfo: marketProcedure
507
507
  .input(
508
508
  z.object({
509
509
  locale: z.string().optional(),
@@ -598,6 +598,26 @@ export const marketRouter = router({
598
598
  }
599
599
  }),
600
600
 
601
+ reportAgentEvent: marketProcedure
602
+ .input(
603
+ z.object({
604
+ event: z.enum(['add', 'chat', 'click']),
605
+ identifier: z.string(),
606
+ source: z.string().optional(),
607
+ }),
608
+ )
609
+ .mutation(async ({ input, ctx }) => {
610
+ log('createAgentEvent input: %O', input);
611
+
612
+ try {
613
+ await ctx.discoverService.createAgentEvent(input);
614
+ return { success: true };
615
+ } catch (error) {
616
+ console.error('Error reporting Agent event: %O', error);
617
+ return { success: false };
618
+ }
619
+ }),
620
+
601
621
  reportAgentInstall: marketProcedure
602
622
  .input(
603
623
  z.object({
@@ -655,6 +675,27 @@ export const marketRouter = router({
655
675
  }
656
676
  }),
657
677
 
678
+
679
+ reportMcpEvent: marketProcedure
680
+ .input(
681
+ z.object({
682
+ event: z.enum(['click', 'install', 'activate', 'uninstall']),
683
+ identifier: z.string(),
684
+ source: z.string().optional(),
685
+ }),
686
+ )
687
+ .mutation(async ({ input, ctx }) => {
688
+ log('createMcpEvent input: %O', input);
689
+
690
+ try {
691
+ await ctx.discoverService.createPluginEvent(input);
692
+ return { success: true };
693
+ } catch (error) {
694
+ console.error('Error reporting MCP event: %O', error);
695
+ return { success: false };
696
+ }
697
+ }),
698
+
658
699
  reportMcpInstallResult: marketProcedure
659
700
  .input(
660
701
  z.object({
@@ -25,13 +25,15 @@ import {
25
25
  type DiscoverProviderItem,
26
26
  type DiscoverUserProfile,
27
27
  type IdentifiersResponse,
28
+ McpCategory,
28
29
  type McpListResponse,
29
30
  type McpQueryParams,
31
+ McpSorts,
30
32
  type ModelListResponse,
31
33
  type ModelQueryParams,
32
34
  ModelSorts,
33
35
  type PluginListResponse,
34
- type PluginQueryParams,
36
+ type PluginQueryParams as PluginQueryParams,
35
37
  PluginSorts,
36
38
  type ProviderListResponse,
37
39
  type ProviderQueryParams,
@@ -48,7 +50,12 @@ import {
48
50
  MarketSDK,
49
51
  type UserInfoResponse,
50
52
  } from '@lobehub/market-sdk';
51
- import { type CallReportRequest, type InstallReportRequest } from '@lobehub/market-types';
53
+ import {
54
+ AgentEventRequest,
55
+ type CallReportRequest,
56
+ type InstallReportRequest,
57
+ type PluginEventRequest,
58
+ } from '@lobehub/market-types';
52
59
  import dayjs from 'dayjs';
53
60
  import debug from 'debug';
54
61
  import { cloneDeep, countBy, isString, merge, uniq, uniqBy } from 'es-toolkit/compat';
@@ -850,12 +857,16 @@ export class DiscoverService {
850
857
 
851
858
  getMcpList = async (params: McpQueryParams = {}): Promise<McpListResponse> => {
852
859
  log('getMcpList: params=%O', params);
853
- const { locale } = params;
860
+ const { category, locale, sort } = params;
854
861
  const normalizedLocale = normalizeLocale(locale);
862
+ const isDiscoverCategory = category === McpCategory.Discover;
863
+
855
864
  const result = await this.market.plugins.getPluginList(
856
865
  {
857
866
  ...params,
867
+ category: isDiscoverCategory ? undefined : category,
858
868
  locale: normalizedLocale,
869
+ sort: isDiscoverCategory ? McpSorts.Recommended : sort,
859
870
  },
860
871
  {
861
872
  next: {
@@ -897,6 +908,21 @@ export class DiscoverService {
897
908
  await this.market.plugins.reportInstallation(params);
898
909
  };
899
910
 
911
+ /**
912
+ * record Agent plugin event
913
+ */
914
+ createAgentEvent = async (params: AgentEventRequest) => {
915
+ await this.market.agents.createEvent(params);
916
+ };
917
+
918
+ /**
919
+ * record MCP plugin event
920
+ */
921
+ createPluginEvent = async (params: PluginEventRequest) => {
922
+ await this.market.plugins.createEvent(params);
923
+ };
924
+
925
+
900
926
  /**
901
927
  * report plugin call result to marketplace
902
928
  */
@@ -1,5 +1,10 @@
1
1
  import { type CategoryItem, type CategoryListQuery, type PluginManifest } from '@lobehub/market-sdk';
2
- import { type CallReportRequest, type InstallReportRequest } from '@lobehub/market-types';
2
+ import {
3
+ AgentEventRequest,
4
+ type CallReportRequest,
5
+ type InstallReportRequest,
6
+ type PluginEventRequest,
7
+ } from '@lobehub/market-types';
3
8
 
4
9
  import { lambdaClient } from '@/libs/trpc/client';
5
10
  import { globalHelpers } from '@/store/global/helpers';
@@ -195,6 +200,22 @@ class DiscoverService {
195
200
  });
196
201
  };
197
202
 
203
+ reportMcpEvent = async (eventData: PluginEventRequest) => {
204
+ const allow = userGeneralSettingsSelectors.telemetry(useUserStore.getState());
205
+ if (!allow) return;
206
+
207
+ await this.injectMPToken();
208
+
209
+ const payload = cleanObject({
210
+ ...eventData,
211
+ source: eventData.source ?? 'community/mcp',
212
+ });
213
+
214
+ lambdaClient.market.reportMcpEvent.mutate(payload).catch((error) => {
215
+ console.warn('Failed to report MCP event:', error);
216
+ });
217
+ };
218
+
198
219
  /**
199
220
  * Report agent installation to increase install count
200
221
  */
@@ -211,6 +232,22 @@ class DiscoverService {
211
232
  });
212
233
  };
213
234
 
235
+ reportAgentEvent = async (eventData: AgentEventRequest) => {
236
+ const allow = userGeneralSettingsSelectors.telemetry(useUserStore.getState());
237
+ if (!allow) return;
238
+
239
+ await this.injectMPToken();
240
+
241
+ const payload = cleanObject({
242
+ ...eventData,
243
+ source: eventData.source ?? 'community/agent',
244
+ });
245
+
246
+ lambdaClient.market.reportAgentEvent.mutate(payload).catch((error) => {
247
+ console.warn('Failed to report Agent event:', error);
248
+ });
249
+ };
250
+
214
251
  // ============================== Models ==============================
215
252
 
216
253
  getModelCategories = async (params: CategoryListQuery = {}): Promise<CategoryItem[]> => {
@@ -12,6 +12,37 @@ import { CheckMcpInstallResult, MCPInstallStep } from '@/types/plugins';
12
12
 
13
13
  import { useToolStore } from '../../store';
14
14
 
15
+ vi.mock('@/libs/trpc/client', () => ({
16
+ asyncClient: {},
17
+ lambdaClient: {
18
+ market: {
19
+ getMcpCategories: { query: vi.fn() },
20
+ getMcpDetail: { query: vi.fn() },
21
+ getMcpList: { query: vi.fn() },
22
+ getMcpManifest: { query: vi.fn() },
23
+ registerClientInMarketplace: {
24
+ mutate: vi.fn().mockResolvedValue({
25
+ clientId: 'test-client-id',
26
+ clientSecret: 'test-client-secret',
27
+ }),
28
+ },
29
+ registerM2MToken: { query: vi.fn().mockResolvedValue({ success: true }) },
30
+ reportCall: { mutate: vi.fn().mockResolvedValue(undefined) },
31
+ reportMcpEvent: { mutate: vi.fn().mockResolvedValue(undefined) },
32
+ reportMcpInstallResult: { mutate: vi.fn().mockResolvedValue(undefined) },
33
+ },
34
+ },
35
+ toolsClient: {
36
+ market: {
37
+ callCloudMcpEndpoint: { mutate: vi.fn() },
38
+ },
39
+ mcp: {
40
+ callTool: { mutate: vi.fn() },
41
+ getStreamableMcpServerManifest: { query: vi.fn() },
42
+ },
43
+ },
44
+ }));
45
+
15
46
  // Keep zustand mock as it's needed globally
16
47
  vi.mock('zustand/traditional');
17
48
 
@@ -61,6 +92,13 @@ const bootstrapToolStoreWithDesktop = async (isDesktopEnv: boolean) => {
61
92
  beforeEach(() => {
62
93
  vi.clearAllMocks();
63
94
 
95
+ vi.spyOn(discoverService, 'injectMPToken').mockResolvedValue(undefined);
96
+ vi.spyOn(discoverService, 'registerClient').mockResolvedValue({
97
+ clientId: 'test-client-id',
98
+ clientSecret: 'test-client-secret',
99
+ });
100
+ vi.spyOn(discoverService, 'reportMcpEvent').mockResolvedValue(undefined as any);
101
+
64
102
  // Reset store state
65
103
  act(() => {
66
104
  useToolStore.setState(
@@ -588,6 +588,12 @@ export const createMCPPluginStoreSlice: StateCreator<
588
588
  // Calculate installation duration
589
589
  const installDurationMs = Date.now() - installStartTime;
590
590
 
591
+ discoverService.reportMcpEvent({
592
+ event: 'install',
593
+ identifier: plugin.identifier,
594
+ source: 'self',
595
+ })
596
+
591
597
  discoverService.reportMcpInstallResult({
592
598
  identifier: plugin.identifier,
593
599
  installDurationMs,
@@ -790,6 +796,12 @@ export const createMCPPluginStoreSlice: StateCreator<
790
796
  n('testMcpConnection/success'),
791
797
  );
792
798
 
799
+ discoverService.reportMcpEvent({
800
+ event: 'activate',
801
+ identifier: identifier,
802
+ source: 'self',
803
+ })
804
+
793
805
  return { manifest, success: true };
794
806
  } catch (error) {
795
807
  // Silently handle errors caused by cancellation
@@ -817,6 +829,12 @@ export const createMCPPluginStoreSlice: StateCreator<
817
829
  uninstallMCPPlugin: async (identifier) => {
818
830
  await pluginService.uninstallPlugin(identifier);
819
831
  await get().refreshPlugins();
832
+
833
+ discoverService.reportMcpEvent({
834
+ event: 'uninstall',
835
+ identifier: identifier,
836
+ source: 'self',
837
+ })
820
838
  },
821
839
 
822
840
  updateMCPInstallProgress: (identifier, progress) => {