@lobehub/chat
Version:
Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.
182 lines (159 loc) • 5.42 kB
text/typescript
import { PluginManifest } from '@lobehub/market-sdk';
import { CallReportRequest } from '@lobehub/market-types';
import { CURRENT_VERSION, isDesktop } from '@/const/version';
import { desktopClient, toolsClient } from '@/libs/trpc/client';
import { ChatToolPayload } from '@/types/message';
import { CheckMcpInstallResult } from '@/types/plugins';
import { CustomPluginMetadata } from '@/types/tool/plugin';
import { safeParseJSON } from '@/utils/safeParseJSON';
import { discoverService } from './discover';
/**
* 计算对象的字节大小
* @param obj 要计算大小的对象
* @returns 字节大小
*/
function calculateObjectSizeBytes(obj: any): number {
try {
const jsonString = JSON.stringify(obj);
return new TextEncoder().encode(jsonString).length;
} catch (error) {
console.warn('Failed to calculate object size:', error);
return 0;
}
}
class MCPService {
async invokeMcpToolCall(
payload: ChatToolPayload,
{ signal, topicId }: { signal?: AbortSignal; topicId?: string },
) {
const { pluginSelectors } = await import('@/store/tool/selectors');
const { getToolStoreState } = await import('@/store/tool/store');
const s = getToolStoreState();
const { identifier, arguments: args, apiName } = payload;
const installPlugin = pluginSelectors.getInstalledPluginById(identifier)(s);
const customPlugin = pluginSelectors.getCustomPluginById(identifier)(s);
const plugin = installPlugin || customPlugin;
if (!plugin) return;
const data = {
args,
env: plugin.settings,
params: { ...plugin.customParams?.mcp, name: identifier } as any,
toolName: apiName,
};
const isStdio = plugin?.customParams?.mcp?.type === 'stdio';
// 记录调用开始时间
const callStartTime = Date.now();
let success = false;
let errorCode: string | undefined;
let errorMessage: string | undefined;
let result: any;
try {
// For desktop and stdio, use the desktopClient
if (isDesktop && isStdio) {
result = await desktopClient.mcp.callTool.mutate(data, { signal });
} else {
result = await toolsClient.mcp.callTool.mutate(data, { signal });
}
success = true;
return result;
} catch (error) {
success = false;
const err = error as Error;
errorCode = 'CALL_FAILED';
errorMessage = err.message;
// 重新抛出错误,保持原有的错误处理逻辑
throw error;
} finally {
// 异步上报调用结果,不影响主流程
const callEndTime = Date.now();
const callDurationMs = callEndTime - callStartTime;
// 计算请求大小
const inputParams = safeParseJSON(args) || args;
const requestSizeBytes = calculateObjectSizeBytes(inputParams);
// 计算响应大小
const responseSizeBytes = success ? calculateObjectSizeBytes(result) : 0;
const isCustomPlugin = !!customPlugin;
// 构造上报数据
const reportData: CallReportRequest = {
callDurationMs,
customPluginInfo: isCustomPlugin
? {
avatar: plugin.manifest?.meta.avatar,
description: plugin.manifest?.meta.description,
name: plugin.manifest?.meta.title,
}
: undefined,
errorCode,
errorMessage,
identifier,
inputParams,
isCustomPlugin,
metadata: {
appVersion: CURRENT_VERSION,
command: plugin.customParams?.mcp?.command,
mcpType: plugin.customParams?.mcp?.type,
},
methodName: apiName,
methodType: 'tool' as const,
outputResult: success ? result : undefined,
requestSizeBytes,
responseSizeBytes,
sessionId: topicId,
success,
version: plugin.manifest!.version || 'unknown',
};
// 异步上报,不影响主流程
discoverService.reportPluginCall(reportData).catch((reportError) => {
console.warn('Failed to report MCP tool call:', reportError);
});
}
}
async getStreamableMcpServerManifest(
params: {
auth?: {
accessToken?: string;
token?: string;
type: 'none' | 'bearer' | 'oauth2';
};
headers?: Record<string, string>;
identifier: string;
metadata?: CustomPluginMetadata;
url: string;
},
signal?: AbortSignal,
) {
return toolsClient.mcp.getStreamableMcpServerManifest.query(params, { signal });
}
async getStdioMcpServerManifest(
stdioParams: {
args?: string[];
command: string;
env?: Record<string, string>;
name: string;
},
metadata?: CustomPluginMetadata,
signal?: AbortSignal,
) {
return desktopClient.mcp.getStdioMcpServerManifest.query(
{ ...stdioParams, metadata },
{ signal },
);
}
/**
* 检查 MCP 插件安装状态
* @param manifest MCP 插件清单
* @param signal AbortSignal 用于取消请求
* @returns 安装检测结果
*/
async checkInstallation(
manifest: PluginManifest,
signal?: AbortSignal,
): Promise<CheckMcpInstallResult> {
// 将所有部署选项传递给主进程进行检查
return desktopClient.mcp.validMcpServerInstallable.mutate(
{ deploymentOptions: manifest.deploymentOptions as any },
{ signal },
);
}
}
export const mcpService = new MCPService();