CherryHQ/cherry-studio · error · McpError
InternalError
InternalError
Error message
Python execution failed: ${errorMessage} What it means
Catch-all at the end of the python server's CallTool handler. Every exception inside the try-block — including a zod InvalidParams McpError (see 348) and any failure from `PythonService.executeScript` (Pyodide) — is logged and re-thrown as McpError InternalError. Because the catch does not re-throw McpError (unlike memory.ts), genuine validation failures and genuine execution failures are indistinguishable to the client.
Source
Thrown at src/main/ai/mcp/servers/python.ts:115
const timeout = Math.min(Math.max(parsed.data.timeout, MIN_TIMEOUT_MS), MAX_TIMEOUT_MS)
logger.debug('Executing Python code via Pyodide')
const result = await application.get('PythonService').executeScript(code, context, timeout)
return {
content: [
{
type: 'text',
text: result
}
]
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
logger.error(`Python execution error: ${errorMessage}`)
throw new McpError(ErrorCode.InternalError, `Python execution failed: ${errorMessage}`)
}
})
}
}
export default PythonServer
View on GitHub (pinned to 726446b54c)
Solutions
- Read the wrapped message — it preserves the underlying Python traceback/cause verbatim.
- For dependency errors, declare packages via PEP 723 `# /// script` metadata at the top of `code`.
- For timeouts, pass a larger `timeout` (clamped to MAX_TIMEOUT_MS = 10 min) or reduce work.
- Server-side: add `if (error instanceof McpError) throw error` before wrapping so validation errors keep their real code.
Defensive patterns
Strategy: try-catch
Validate before calling
// Reduce runtime failures: declare third-party deps via PEP 723 and keep code under the timeout.
function wrapPythonCode(userCode: string, deps: string[] = []): string {
const meta = deps.length
? `# /// script\n# dependencies = ${JSON.stringify(deps)}\n# ///\n`
: ''
return `${meta}${userCode}`
} Type guard
const isMcpInternalError = (e: unknown): boolean => e instanceof Error && (e as any).code === ErrorCode.InternalError
Try / catch
try {
await client.callTool({ name: 'python_execute', arguments: { code, timeout: 30000 } })
} catch (e) {
if (e instanceof McpError && e.code === ErrorCode.InternalError) {
// e.message preserves the Python traceback / zod message verbatim — classify by content
if (/Timeout|timed out/i.test(e.message)) {/* shorten code or raise timeout (<= 600000) */}
else if (/ModuleNotFoundError|ImportError/.test(e.message)) {/* add PEP 723 dep */}
else {/* genuine Python exception — fix the submitted code */}
}
throw e
} Prevention
- Declare every third-party import via PEP 723 script metadata.
- Keep submitted code bounded; timeout is clamped to [1000ms, 600000ms].
- Inspect the wrapped message — it carries the real Python cause.
- Server-side: re-throw McpError before wrapping so validation vs runtime errors are distinguishable.
When it happens
Trigger: Pyodide raises a Python exception (`SyntaxError`, `NameError`, import failure, timeout); the sandboxed runtime crashes; a zod validation failure (re-wrapped here); a dependency listed via PEP 723 fails to install.
Common situations: Buggy submitted code; missing third-party package not declared via PEP 723 metadata; execution exceeds the clamped timeout; non-string `code` slipping past validation.
Related errors
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/425946061c0d9b24.
Report an issue: GitHub.