mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-27 13:29:15 +07:00
🐛 fix(mcp): fix installation check hanging issue in desktop app (#11524)
* ✨ feat(mcp): enhance error handling and logging for MCP connections - Introduced MCPConnectionError class to capture and log stderr output during MCP connections. - Updated McpCtr to handle MCPConnectionError and provide enhanced error messages with stderr logs. - Modified MCPClient to collect stderr logs and throw enhanced errors when connection issues occur. - Improved error display in MCPManifestForm to show detailed error information when connection tests fail. - Added utility functions to parse and extract STDIO process output from error messages. Signed-off-by: Innei <tukon479@gmail.com> * 🐛 fix(mcp): remove npx check to prevent hanging during installation check - Remove `npx -y` package check that could download packages or start MCP servers - This was causing the UI to hang on "Checking installation environment" - npm packages don't require pre-installation, npx handles on-demand download --------- Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
@@ -21,7 +21,6 @@ export default defineConfig({
|
||||
},
|
||||
sourcemap: isDev ? 'inline' : false,
|
||||
},
|
||||
|
||||
define: {
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
|
||||
'process.env.UPDATE_CHANNEL': JSON.stringify(process.env.UPDATE_CHANNEL),
|
||||
|
||||
@@ -7,7 +7,7 @@ import superjson from 'superjson';
|
||||
import FileService from '@/services/fileSrv';
|
||||
import { createLogger } from '@/utils/logger';
|
||||
|
||||
import { MCPClient } from '../libs/mcp/client';
|
||||
import { MCPClient, MCPConnectionError } from '../libs/mcp/client';
|
||||
import type { MCPClientParams, ToolCallContent, ToolCallResult } from '../libs/mcp/types';
|
||||
import { ControllerModule, IpcMethod } from './index';
|
||||
|
||||
@@ -228,8 +228,9 @@ export default class McpCtr extends ControllerModule {
|
||||
type: 'stdio',
|
||||
};
|
||||
|
||||
const client = await this.createClient(params);
|
||||
let client: MCPClient | undefined;
|
||||
try {
|
||||
client = await this.createClient(params);
|
||||
const manifest = await client.listManifests();
|
||||
const identifier = input.name;
|
||||
|
||||
@@ -257,8 +258,25 @@ export default class McpCtr extends ControllerModule {
|
||||
mcpParams: params,
|
||||
type: 'mcp' as any,
|
||||
});
|
||||
} catch (error) {
|
||||
// If it's an MCPConnectionError with stderr logs, enhance the error message
|
||||
if (error instanceof MCPConnectionError && error.stderrLogs.length > 0) {
|
||||
const stderrOutput = error.stderrLogs.join('\n');
|
||||
const enhancedError = new Error(
|
||||
`${error.message}\n\n--- STDIO Process Output ---\n${stderrOutput}`,
|
||||
);
|
||||
enhancedError.name = error.name;
|
||||
logger.error('getStdioMcpServerManifest failed with STDIO logs:', {
|
||||
message: error.message,
|
||||
stderrLogs: error.stderrLogs,
|
||||
});
|
||||
throw enhancedError;
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
await client.disconnect();
|
||||
if (client) {
|
||||
await client.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,8 +331,9 @@ export default class McpCtr extends ControllerModule {
|
||||
type: 'stdio',
|
||||
};
|
||||
|
||||
const client = await this.createClient(params);
|
||||
let client: MCPClient | undefined;
|
||||
try {
|
||||
client = await this.createClient(params);
|
||||
const args = safeParseToRecord(input.args);
|
||||
|
||||
const raw = (await client.callTool(input.toolName, args)) as ToolCallResult;
|
||||
@@ -328,10 +347,25 @@ export default class McpCtr extends ControllerModule {
|
||||
success: true,
|
||||
});
|
||||
} catch (error) {
|
||||
// If it's an MCPConnectionError with stderr logs, enhance the error message
|
||||
if (error instanceof MCPConnectionError && error.stderrLogs.length > 0) {
|
||||
const stderrOutput = error.stderrLogs.join('\n');
|
||||
const enhancedError = new Error(
|
||||
`${error.message}\n\n--- STDIO Process Output ---\n${stderrOutput}`,
|
||||
);
|
||||
enhancedError.name = error.name;
|
||||
logger.error('callTool failed with STDIO logs:', {
|
||||
message: error.message,
|
||||
stderrLogs: error.stderrLogs,
|
||||
});
|
||||
throw enhancedError;
|
||||
}
|
||||
logger.error('callTool failed:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
await client.disconnect();
|
||||
if (client) {
|
||||
await client.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,8 +395,9 @@ export default class McpCtr extends ControllerModule {
|
||||
}
|
||||
|
||||
private async checkSystemDependency(dependency: any) {
|
||||
const checkCommand = dependency.checkCommand || `${dependency.name} --version`;
|
||||
|
||||
try {
|
||||
const checkCommand = dependency.checkCommand || `${dependency.name} --version`;
|
||||
const { stdout, stderr } = await execPromise(checkCommand);
|
||||
|
||||
if (stderr && !stdout) {
|
||||
@@ -444,22 +479,19 @@ export default class McpCtr extends ControllerModule {
|
||||
const packageName = details?.packageName;
|
||||
if (!packageName) return { installed: false };
|
||||
|
||||
// Only check global npm list - do NOT use npx as it may download packages
|
||||
try {
|
||||
const { stdout } = await execPromise(`npm list -g ${packageName} --depth=0`);
|
||||
if (!stdout.includes('(empty)') && stdout.includes(packageName)) return { installed: true };
|
||||
if (!stdout.includes('(empty)') && stdout.includes(packageName)) {
|
||||
return { installed: true };
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
// ignore - package not found in global list
|
||||
}
|
||||
|
||||
try {
|
||||
await execPromise(`npx -y ${packageName} --version`);
|
||||
return { installed: true };
|
||||
} catch (error) {
|
||||
return {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
installed: false,
|
||||
};
|
||||
}
|
||||
// For npm packages, we don't require pre-installation
|
||||
// npx will handle downloading and running on-demand during actual MCP connection
|
||||
return { installed: false };
|
||||
}
|
||||
|
||||
if (installationMethod === 'python') {
|
||||
@@ -553,7 +585,7 @@ export default class McpCtr extends ControllerModule {
|
||||
const bestResult = recommendedResult || firstInstallableResult || results[0];
|
||||
|
||||
const checkResult: CheckMcpInstallResult = {
|
||||
...(bestResult || {}),
|
||||
...bestResult,
|
||||
allOptions: results as any,
|
||||
platform: process.platform,
|
||||
success: true,
|
||||
|
||||
@@ -6,15 +6,31 @@ import {
|
||||
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
||||
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
||||
import type { Progress } from '@modelcontextprotocol/sdk/types.js';
|
||||
import type { Readable } from 'node:stream';
|
||||
|
||||
import { getDesktopEnv } from '@/env';
|
||||
|
||||
import type { MCPClientParams, McpPrompt, McpResource, McpTool, ToolCallResult } from './types';
|
||||
|
||||
/**
|
||||
* Custom error class for MCP connection errors that includes STDIO logs
|
||||
*/
|
||||
export class MCPConnectionError extends Error {
|
||||
readonly stderrLogs: string[];
|
||||
|
||||
constructor(message: string, stderrLogs: string[] = []) {
|
||||
super(message);
|
||||
this.name = 'MCPConnectionError';
|
||||
this.stderrLogs = stderrLogs;
|
||||
}
|
||||
}
|
||||
|
||||
export class MCPClient {
|
||||
private readonly mcp: Client;
|
||||
|
||||
private transport: Transport;
|
||||
private stderrLogs: string[] = [];
|
||||
private isStdio: boolean = false;
|
||||
|
||||
constructor(params: MCPClientParams) {
|
||||
this.mcp = new Client({ name: 'lobehub-desktop-mcp-client', version: '1.0.0' });
|
||||
@@ -40,14 +56,21 @@ export class MCPClient {
|
||||
}
|
||||
|
||||
case 'stdio': {
|
||||
this.transport = new StdioClientTransport({
|
||||
this.isStdio = true;
|
||||
const stdioTransport = new StdioClientTransport({
|
||||
args: params.args,
|
||||
command: params.command,
|
||||
env: {
|
||||
...getDefaultEnvironment(),
|
||||
...params.env,
|
||||
},
|
||||
stderr: 'pipe', // Capture stderr for better error messages
|
||||
});
|
||||
|
||||
// Listen to stderr stream to collect logs
|
||||
this.setupStderrListener(stdioTransport);
|
||||
|
||||
this.transport = stdioTransport;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -60,16 +83,45 @@ export class MCPClient {
|
||||
}
|
||||
}
|
||||
|
||||
private setupStderrListener(transport: StdioClientTransport) {
|
||||
const stderr = transport.stderr as Readable | null;
|
||||
if (stderr) {
|
||||
stderr.on('data', (chunk: Buffer) => {
|
||||
const text = chunk.toString('utf8');
|
||||
// Split by newlines and filter empty lines
|
||||
const lines = text.split('\n').filter((line) => line.trim());
|
||||
this.stderrLogs.push(...lines);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get collected stderr logs from the STDIO process
|
||||
*/
|
||||
getStderrLogs(): string[] {
|
||||
return this.stderrLogs;
|
||||
}
|
||||
|
||||
private isMethodNotFoundError(error: unknown) {
|
||||
const err = error as any;
|
||||
if (!err) return false;
|
||||
// eslint-disable-next-line unicorn/numeric-separators-style
|
||||
if (err.code === -32601) return true;
|
||||
if (typeof err.message === 'string' && err.message.includes('Method not found')) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
async initialize(options: { onProgress?: (progress: Progress) => void } = {}) {
|
||||
await this.mcp.connect(this.transport, { onprogress: options.onProgress });
|
||||
try {
|
||||
await this.mcp.connect(this.transport, { onprogress: options.onProgress });
|
||||
} catch (error) {
|
||||
// If this is a STDIO connection and we have stderr logs, enhance the error
|
||||
if (this.isStdio && this.stderrLogs.length > 0) {
|
||||
const originalMessage = error instanceof Error ? error.message : String(error);
|
||||
throw new MCPConnectionError(originalMessage, this.stderrLogs);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Button, Flexbox, Highlighter, Icon, Tag } from '@lobehub/ui';
|
||||
import { Flexbox, Highlighter, Tag } from '@lobehub/ui';
|
||||
import { cssVar } from 'antd-style';
|
||||
import { ChevronDown, ChevronRight } from 'lucide-react';
|
||||
import * as motion from 'motion/react-m';
|
||||
import { memo, useState } from 'react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { type MCPErrorInfoMetadata } from '@/types/plugins';
|
||||
@@ -12,94 +11,73 @@ const ErrorDetails = memo<{
|
||||
errorMessage?: string;
|
||||
}>(({ errorInfo, errorMessage }) => {
|
||||
const { t } = useTranslation('plugin');
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
return (
|
||||
<Flexbox gap={8}>
|
||||
<Button
|
||||
color={'default'}
|
||||
icon={<Icon icon={expanded ? ChevronDown : ChevronRight} />}
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
size="small"
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
padding: '0 4px',
|
||||
}}
|
||||
variant="filled"
|
||||
<motion.div
|
||||
animate={{ height: 'auto', opacity: 1 }}
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
style={{ overflow: 'hidden' }}
|
||||
>
|
||||
{expanded
|
||||
? t('mcpInstall.errorDetails.hideDetails')
|
||||
: t('mcpInstall.errorDetails.showDetails')}
|
||||
</Button>
|
||||
|
||||
{expanded && (
|
||||
<motion.div
|
||||
animate={{ height: 'auto', opacity: 1 }}
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
style={{ overflow: 'hidden' }}
|
||||
<Flexbox
|
||||
gap={8}
|
||||
style={{
|
||||
backgroundColor: cssVar.colorFillQuaternary,
|
||||
borderRadius: 8,
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '11px',
|
||||
padding: '8px 12px',
|
||||
}}
|
||||
>
|
||||
<Flexbox
|
||||
gap={8}
|
||||
style={{
|
||||
backgroundColor: cssVar.colorFillQuaternary,
|
||||
borderRadius: 8,
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '11px',
|
||||
padding: '8px 12px',
|
||||
}}
|
||||
>
|
||||
{errorInfo.params && (
|
||||
<Flexbox gap={4}>
|
||||
<div>
|
||||
<Tag color="blue" variant={'filled'}>
|
||||
{t('mcpInstall.errorDetails.connectionParams')}
|
||||
</Tag>
|
||||
</div>
|
||||
<div style={{ marginTop: 4, wordBreak: 'break-all' }}>
|
||||
{errorInfo.params.command && (
|
||||
<div>
|
||||
{t('mcpInstall.errorDetails.command')}: {errorInfo.params.command}
|
||||
</div>
|
||||
)}
|
||||
{errorInfo.params.args && (
|
||||
<div>
|
||||
{t('mcpInstall.errorDetails.args')}: {errorInfo.params.args.join(' ')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Flexbox>
|
||||
)}
|
||||
|
||||
{errorInfo.errorLog && (
|
||||
<Flexbox gap={4}>
|
||||
<div>
|
||||
<Tag color="red" variant={'filled'}>
|
||||
{t('mcpInstall.errorDetails.errorOutput')}
|
||||
</Tag>
|
||||
</div>
|
||||
<Highlighter
|
||||
language={'log'}
|
||||
style={{
|
||||
maxHeight: 200,
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
{errorInfo.errorLog}
|
||||
</Highlighter>
|
||||
</Flexbox>
|
||||
)}
|
||||
|
||||
{errorInfo.originalError && errorInfo.originalError !== errorMessage && (
|
||||
{errorInfo.params && (
|
||||
<Flexbox gap={4}>
|
||||
<div>
|
||||
<Tag color="orange">{t('mcpInstall.errorDetails.originalError')}</Tag>
|
||||
<div style={{ marginTop: 4, wordBreak: 'break-all' }}>
|
||||
{errorInfo.originalError}
|
||||
</div>
|
||||
<Tag color="blue" variant={'filled'}>
|
||||
{t('mcpInstall.errorDetails.connectionParams')}
|
||||
</Tag>
|
||||
</div>
|
||||
)}
|
||||
</Flexbox>
|
||||
</motion.div>
|
||||
)}
|
||||
<div style={{ marginTop: 4, wordBreak: 'break-all' }}>
|
||||
{errorInfo.params.command && (
|
||||
<div>
|
||||
{t('mcpInstall.errorDetails.command')}: {errorInfo.params.command}
|
||||
</div>
|
||||
)}
|
||||
{errorInfo.params.args && (
|
||||
<div>
|
||||
{t('mcpInstall.errorDetails.args')}: {errorInfo.params.args.join(' ')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Flexbox>
|
||||
)}
|
||||
|
||||
{errorInfo.errorLog && (
|
||||
<Flexbox gap={4}>
|
||||
<div>
|
||||
<Tag color="red" variant={'filled'}>
|
||||
{t('mcpInstall.errorDetails.errorOutput')}
|
||||
</Tag>
|
||||
</div>
|
||||
<Highlighter
|
||||
language={'log'}
|
||||
style={{
|
||||
maxHeight: 200,
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
{errorInfo.errorLog}
|
||||
</Highlighter>
|
||||
</Flexbox>
|
||||
)}
|
||||
|
||||
{errorInfo.originalError && errorInfo.originalError !== errorMessage && (
|
||||
<div>
|
||||
<Tag color="orange">{t('mcpInstall.errorDetails.originalError')}</Tag>
|
||||
<div style={{ marginTop: 4, wordBreak: 'break-all' }}>{errorInfo.originalError}</div>
|
||||
</div>
|
||||
)}
|
||||
</Flexbox>
|
||||
</motion.div>
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -6,8 +6,10 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
import KeyValueEditor from '@/components/KeyValueEditor';
|
||||
import MCPStdioCommandInput from '@/components/MCPStdioCommandInput';
|
||||
import ErrorDetails from '@/features/MCP/MCPInstallProgress/InstallError/ErrorDetails';
|
||||
import { useToolStore } from '@/store/tool';
|
||||
import { mcpStoreSelectors, pluginSelectors } from '@/store/tool/selectors';
|
||||
import { type MCPErrorInfoMetadata } from '@/types/plugins';
|
||||
|
||||
import ArgsInput from './ArgsInput';
|
||||
import CollapsibleSection from './CollapsibleSection';
|
||||
@@ -46,10 +48,12 @@ const MCPManifestForm = ({ form, isEditMode }: MCPManifestFormProps) => {
|
||||
const testState = useToolStore(mcpStoreSelectors.getMCPConnectionTestState(identifier), isEqual);
|
||||
|
||||
const [connectionError, setConnectionError] = useState<string | null>(null);
|
||||
const [errorMetadata, setErrorMetadata] = useState<MCPErrorInfoMetadata | null>(null);
|
||||
|
||||
const handleTestConnection = async () => {
|
||||
setIsTesting(true);
|
||||
setConnectionError(null);
|
||||
setErrorMetadata(null);
|
||||
|
||||
// Manually trigger validation for fields needed for the test
|
||||
let isValid = false;
|
||||
@@ -97,12 +101,29 @@ const MCPManifestForm = ({ form, isEditMode }: MCPManifestFormProps) => {
|
||||
// Be careful about overwriting user input if not desired
|
||||
form.setFieldsValue({ manifest: result.manifest });
|
||||
setConnectionError(null); // 清除本地错误状态
|
||||
setErrorMetadata(null);
|
||||
} else if (result.error) {
|
||||
// Store 已经处理了错误状态,这里可以选择显示额外的用户友好提示
|
||||
const errorMessage = t('error.testConnectionFailed', {
|
||||
error: result.error,
|
||||
});
|
||||
setConnectionError(errorMessage);
|
||||
|
||||
// Build error metadata for detailed display
|
||||
if (result.errorLog || mcpType === 'stdio') {
|
||||
setErrorMetadata({
|
||||
errorLog: result.errorLog,
|
||||
params:
|
||||
mcpType === 'stdio'
|
||||
? {
|
||||
args: mcp?.args,
|
||||
command: mcp?.command,
|
||||
type: 'stdio',
|
||||
}
|
||||
: undefined,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Handle unexpected errors
|
||||
@@ -121,7 +142,10 @@ const MCPManifestForm = ({ form, isEditMode }: MCPManifestFormProps) => {
|
||||
<QuickImportSection
|
||||
form={form}
|
||||
isEditMode={isEditMode}
|
||||
onClearConnectionError={() => setConnectionError(null)}
|
||||
onClearConnectionError={() => {
|
||||
setConnectionError(null);
|
||||
setErrorMetadata(null);
|
||||
}}
|
||||
/>
|
||||
<Form form={form} layout={'vertical'}>
|
||||
<Flexbox>
|
||||
@@ -270,9 +294,12 @@ const MCPManifestForm = ({ form, isEditMode }: MCPManifestFormProps) => {
|
||||
{(connectionError || testState.error) && (
|
||||
<Alert
|
||||
closable
|
||||
onClose={() => setConnectionError(null)}
|
||||
extra={errorMetadata ? <ErrorDetails errorInfo={errorMetadata} /> : undefined}
|
||||
onClose={() => {
|
||||
setConnectionError(null);
|
||||
setErrorMetadata(null);
|
||||
}}
|
||||
showIcon
|
||||
style={{ marginBottom: 16 }}
|
||||
title={connectionError || testState.error}
|
||||
type="error"
|
||||
/>
|
||||
|
||||
@@ -240,3 +240,34 @@ export function createMCPError(
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* STDIO Process Output separator used in enhanced error messages
|
||||
*/
|
||||
const STDIO_OUTPUT_SEPARATOR = '--- STDIO Process Output ---';
|
||||
|
||||
/**
|
||||
* Parse error message to extract STDIO process output logs
|
||||
* The enhanced error format from desktop is:
|
||||
* "Original message\n\n--- STDIO Process Output ---\nlogs..."
|
||||
*/
|
||||
export interface ParsedStdioError {
|
||||
errorLog?: string;
|
||||
originalMessage: string;
|
||||
}
|
||||
|
||||
export function parseStdioErrorMessage(errorMessage: string): ParsedStdioError {
|
||||
const separatorIndex = errorMessage.indexOf(STDIO_OUTPUT_SEPARATOR);
|
||||
|
||||
if (separatorIndex === -1) {
|
||||
return { originalMessage: errorMessage };
|
||||
}
|
||||
|
||||
const originalMessage = errorMessage.slice(0, separatorIndex).trim();
|
||||
const errorLog = errorMessage.slice(separatorIndex + STDIO_OUTPUT_SEPARATOR.length).trim();
|
||||
|
||||
return {
|
||||
errorLog: errorLog || undefined,
|
||||
originalMessage: originalMessage || errorMessage,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { gt, valid } from 'semver';
|
||||
import useSWR, { type SWRResponse } from 'swr';
|
||||
import { type StateCreator } from 'zustand/vanilla';
|
||||
|
||||
import { type MCPErrorData } from '@/libs/mcp/types';
|
||||
import { type MCPErrorData, parseStdioErrorMessage } from '@/libs/mcp/types';
|
||||
import { discoverService } from '@/services/discover';
|
||||
import { mcpService } from '@/services/mcp';
|
||||
import { pluginService } from '@/services/plugin';
|
||||
@@ -132,6 +132,8 @@ const buildCloudMcpManifest = (params: {
|
||||
// Test connection result type
|
||||
export interface TestMcpConnectionResult {
|
||||
error?: string;
|
||||
/** STDIO process output logs for debugging */
|
||||
errorLog?: string;
|
||||
manifest?: LobeChatPluginManifest;
|
||||
success: boolean;
|
||||
}
|
||||
@@ -301,8 +303,6 @@ export const createMCPPluginStoreSlice: StateCreator<
|
||||
// Check if cloudEndPoint is available: web + stdio type + haveCloudEndpoint exists
|
||||
const hasCloudEndpoint = !isDesktop && stdioOption && haveCloudEndpoint;
|
||||
|
||||
console.log('hasCloudEndpoint', hasCloudEndpoint);
|
||||
|
||||
let shouldUseHttpDeployment = !!httpOption && (!hasNonHttpDeployment || !isDesktop);
|
||||
|
||||
if (hasCloudEndpoint) {
|
||||
@@ -592,7 +592,7 @@ export const createMCPPluginStoreSlice: StateCreator<
|
||||
event: 'install',
|
||||
identifier: plugin.identifier,
|
||||
source: 'self',
|
||||
})
|
||||
});
|
||||
|
||||
discoverService.reportMcpInstallResult({
|
||||
identifier: plugin.identifier,
|
||||
@@ -653,10 +653,22 @@ export const createMCPPluginStoreSlice: StateCreator<
|
||||
};
|
||||
} else {
|
||||
// Fallback handling for normal errors
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
const rawErrorMessage = error instanceof Error ? error.message : String(error);
|
||||
|
||||
// Parse STDIO error message to extract process output logs
|
||||
const { originalMessage, errorLog } = parseStdioErrorMessage(rawErrorMessage);
|
||||
|
||||
errorInfo = {
|
||||
message: errorMessage,
|
||||
message: originalMessage,
|
||||
metadata: {
|
||||
errorLog,
|
||||
params: connection
|
||||
? {
|
||||
args: connection.args,
|
||||
command: connection.command,
|
||||
type: connection.type,
|
||||
}
|
||||
: undefined,
|
||||
step: 'installation_error',
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
@@ -800,7 +812,7 @@ export const createMCPPluginStoreSlice: StateCreator<
|
||||
event: 'activate',
|
||||
identifier: identifier,
|
||||
source: 'self',
|
||||
})
|
||||
});
|
||||
|
||||
return { manifest, success: true };
|
||||
} catch (error) {
|
||||
@@ -809,20 +821,23 @@ export const createMCPPluginStoreSlice: StateCreator<
|
||||
return { error: 'Test cancelled', success: false };
|
||||
}
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
const rawErrorMessage = error instanceof Error ? error.message : String(error);
|
||||
|
||||
// Parse STDIO error message to extract process output logs
|
||||
const { originalMessage, errorLog } = parseStdioErrorMessage(rawErrorMessage);
|
||||
|
||||
// Set error state
|
||||
set(
|
||||
produce((draft: MCPStoreState) => {
|
||||
draft.mcpTestLoading[identifier] = false;
|
||||
draft.mcpTestErrors[identifier] = errorMessage;
|
||||
draft.mcpTestErrors[identifier] = originalMessage;
|
||||
delete draft.mcpTestAbortControllers[identifier];
|
||||
}),
|
||||
false,
|
||||
n('testMcpConnection/error'),
|
||||
);
|
||||
|
||||
return { error: errorMessage, success: false };
|
||||
return { error: originalMessage, errorLog, success: false };
|
||||
}
|
||||
},
|
||||
|
||||
@@ -834,7 +849,7 @@ export const createMCPPluginStoreSlice: StateCreator<
|
||||
event: 'uninstall',
|
||||
identifier: identifier,
|
||||
source: 'self',
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
updateMCPInstallProgress: (identifier, progress) => {
|
||||
|
||||
Reference in New Issue
Block a user