mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-07-07 15:12:18 +08:00
### What this PR does **Before this PR:** 1. The project used an outdated `@modelcontextprotocol/sdk` version that had security vulnerabilities (GHSA-8r9q-7v3j-jr4g). 2. In `MCPService.ts`, the code incorrectly accessed `params.message` field which did not exist in the type definition. The old SDK used `looseObject` type, so no TypeScript error was raised. 3. The `serialize.ts` utility was only available in the renderer process and could not be used in the main process (Node.js). **After this PR:** 1. Upgraded `@modelcontextprotocol/sdk` from an older version to `1.27.1` to resolve the security vulnerability. 2. Fixed the incorrect implementation in `MCPService.ts` by using the `data` field instead of `message` to get the log message, following the official MCP specification: https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/logging#log-message-notifications 3. Moved the `serialize` utility from `src/renderer/src/utils/` to `packages/shared/utils/` so it can be used by both main and renderer processes. **Refactoring:** - Previously, the `packages/shared` package only contained a single `utils.ts` file. - To add `serialize.ts` to the shared package, the existing `utils.ts` was migrated to `packages/shared/utils/index.ts`. - The new `serialize.ts` was added to `packages/shared/utils/serialize.ts`. <!-- (optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*: --> Fixes # ### Why we need it and why it was done in this way The following tradeoffs were made: 1. **Security fix**: The MCP SDK upgrade was necessary to address GHSA-8r9q-7v3j-jr4g. 2. **Type safety**: The new SDK type definitions are stricter (no longer `looseObject`), which exposed the existing bug where `params.message` was incorrectly used. This is now fixed by using the `data` field as per the official spec. 3. **Code sharing**: To safely convert the `unknown` type `data` field to string, the serialize utility needed to be accessible from both main and renderer processes. The utility was moved to the shared package since its functions (using only standard JavaScript/TypeScript features like `JSON.stringify`) are compatible with both Node.js and browser environments. The following alternatives were considered: - Keep using `message` field: Not viable as it doesn't exist in the new SDK types and the official spec recommends using `data` field. - Duplicate the serialize logic in main process: Would lead to code duplication and maintenance burden. Links to places where the discussion took place: <!-- optional: slack, other GH issue, mailinglist, ... --> ### Breaking changes - **Runtime behavior change**: MCP log messages now use the `data` field instead of `message`. A small number of older MCP servers that use the non-standard `message` field for log information will no longer be supported. However, this is the correct behavior according to the official MCP specification, so this change is considered a bug fix rather than a breaking change. ### Special notes for your reviewer - Line 701 in `MCPService.ts` uses `as` to bypass TypeScript type checking. After manual review, the types do not match correctly. A `FIXME` comment has been added to indicate this needs future attention, as the exact type mapping is still being determined. ### Checklist This checklist is not enforcing, but it's a reminder of items that could be relevant to every PR. Approvers are expected to review this list. - [x] PR: The PR description is expressive enough and will help future contributors - [x] Code: [Write code that humans can understand](https://en.wikiquote.org/wiki/Martin_Fowler#code-for-humans) and [Keep it simple](https://en.wikipedia.org/wiki/KISS_principle) - [x] Refactor: You have [left the code cleaner than you found it (Boy Scout Rule)](https://learning.oreilly.com/library/view/97-things-every/9780596809515/ch08.html) - [x] Upgrade: Impact of this change on upgrade flows was considered and addressed if required - [x] Documentation: A [user-guide update](https://docs.cherry-ai.com) was considered and is present (link) or not required. Check this only when the PR introduces or changes a user-facing feature or behavior. ### Release note ```release-note Security: Upgraded @modelcontextprotocol/sdk to 1.27.1 to fix security vulnerability (GHSA-8r9q-7v3j-jr4g) Fix: Fixed MCP server log message handling to use the standard `data` field instead of non-standard `message` field per MCP specification ```
95 lines
2.8 KiB
TypeScript
95 lines
2.8 KiB
TypeScript
import { isSerializable } from '@types'
|
||
|
||
/**
|
||
* 安全地序列化一个值为 JSON 字符串。
|
||
* 基于 `Serializable` 类型和 `isSerializable` 运行时检查。
|
||
*
|
||
* @param value 要序列化的值
|
||
* @param options 配置选项
|
||
* @returns 序列化后的字符串,或 null(如果失败且未抛错)
|
||
*/
|
||
export function safeSerialize(
|
||
value: unknown,
|
||
options: {
|
||
/**
|
||
* 处理不可序列化值的方式:
|
||
* - 'error': 抛出错误
|
||
* - 'omit': 尝试过滤掉非法字段(⚠️ 不支持深度修复,仅顶层判断)
|
||
* - 'serialize': 尝试安全转换(如 Date → ISO 字符串)
|
||
*/
|
||
onError?: 'error' | 'omit' | 'serialize'
|
||
|
||
/**
|
||
* 是否美化输出
|
||
* @default true
|
||
*/
|
||
pretty?: boolean
|
||
} = {}
|
||
): string | null {
|
||
const { onError = 'serialize', pretty = true } = options
|
||
const space = pretty ? 2 : undefined
|
||
|
||
// 1. 如果本身就是合法的 Serializable 值,直接序列化
|
||
if (isSerializable(value)) {
|
||
try {
|
||
return JSON.stringify(value, null, space)
|
||
} catch (err) {
|
||
// 理论上不会发生,但以防万一(比如极深嵌套栈溢出)
|
||
if (onError === 'error') {
|
||
throw new Error(`Failed to stringify serializable value: ${err instanceof Error ? err.message : err}`)
|
||
}
|
||
return null
|
||
}
|
||
}
|
||
|
||
// 2. 不是可序列化的,根据策略处理
|
||
switch (onError) {
|
||
case 'error':
|
||
throw new TypeError('Value is not serializable and cannot be safely serialized.')
|
||
|
||
case 'omit':
|
||
// 注意:这里不能“修复”对象,只能返回 null 表示跳过
|
||
return null
|
||
|
||
case 'serialize': {
|
||
// 宽容模式:尝试做一些安全转换
|
||
return tryLenientSerialize(value, space)
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 尽力而为地序列化一个值,即使它不符合 Serializable。
|
||
* 适用于调试、日志等非关键场景。
|
||
*/
|
||
function tryLenientSerialize(value: unknown, space?: string | number): string {
|
||
const seen = new WeakSet()
|
||
|
||
const serialized = JSON.stringify(
|
||
value,
|
||
(_, val: any) => {
|
||
// 处理循环引用
|
||
if (typeof val === 'object' && val !== null) {
|
||
if (seen.has(val)) {
|
||
return '[Circular]'
|
||
}
|
||
seen.add(val)
|
||
}
|
||
|
||
// 处理特殊类型
|
||
if (val instanceof Date) return val.toISOString()
|
||
if (val instanceof RegExp) return `{RegExp: "${val.toString()}"}`
|
||
if (typeof val === 'function') return `[Function: ${val.name || 'anonymous'}]`
|
||
if (typeof val === 'symbol') return `Symbol(${String(val.description)})`
|
||
if (val instanceof Map) return Object.fromEntries(val.entries())
|
||
if (val instanceof Set) return Array.from(val)
|
||
if (val === undefined) return '[undefined]'
|
||
|
||
return val
|
||
},
|
||
space
|
||
)
|
||
|
||
return serialized
|
||
}
|