























@@ -30,7 +30,28 @@ const MCP_TOOL_APPROVAL_CONNECTOR_NAME_KEY = "connector_name";
3030const MCP_TOOL_APPROVAL_TOOL_TITLE_KEY = "tool_title";
3131const MCP_TOOL_APPROVAL_TOOL_DESCRIPTION_KEY = "tool_description";
3232const MCP_TOOL_APPROVAL_TOOL_PARAMS_DISPLAY_KEY = "tool_params_display";
33+const MAX_DISPLAY_PARAM_ENTRIES = 8;
3334const MAX_DISPLAY_PARAM_VALUE_LENGTH = 120;
35+const MAX_DISPLAY_VALUE_ARRAY_ITEMS = 8;
36+const MAX_DISPLAY_VALUE_OBJECT_KEYS = 8;
37+const MAX_DISPLAY_VALUE_DEPTH = 3;
38+const DISPLAY_TEXT_SCAN_MAX_LENGTH = 4096;
39+const ANSI_OSC_SEQUENCE_RE = new RegExp(
40+String.raw`(?:\u001b]|\u009d)[^\u001b\u009c\u0007]*(?:\u0007|\u001b\\|\u009c)`,
41+"g",
42+);
43+const ANSI_CONTROL_SEQUENCE_RE = new RegExp(
44+String.raw`(?:\u001b\[[0-?]*[ -/]*[@-~]|\u009b[0-?]*[ -/]*[@-~]|\u001b[@-Z\\-_])`,
45+"g",
46+);
47+const CONTROL_CHARACTER_RE = new RegExp(String.raw`[\u0000-\u001f\u007f-\u009f]+`, "g");
48+const INVISIBLE_FORMATTING_CONTROL_RE = new RegExp(
49+String.raw`[\u00ad\u034f\u061c\u200b-\u200f\u202a-\u202e\u2060-\u206f\ufeff\ufe00-\ufe0f\u{e0100}-\u{e01ef}]`,
50+"gu",
51+);
52+const DANGLING_TERMINAL_SEQUENCE_SUFFIX_RE = new RegExp(
53+String.raw`(?:\u001b\][^\u001b\u009c\u0007]*|\u009d[^\u001b\u009c\u0007]*|\u001b\[[0-?]*[ -/]*|\u009b[0-?]*[ -/]*|\u001b)$`,
54+);
34553556export async function handleCodexAppServerElicitationRequest(params: {
3657requestParams: JsonValue | undefined;
@@ -97,14 +118,15 @@ function readBridgeableApprovalElicitation(
97118return undefined;
98119}
99120100-const title = readString(requestParams, "message") ?? "Codex MCP tool approval";
121+const title =
122+sanitizeDisplayText(readString(requestParams, "message") ?? "") || "Codex MCP tool approval";
101123return {
102124 title,
103125description: buildApprovalDescription({
104126 title,
105127meta: requestParams._meta,
106128 requestedSchema,
107-serverName: readString(requestParams, "serverName"),
129+serverName: sanitizeOptionalDisplayText(readString(requestParams, "serverName")),
108130}),
109131 requestedSchema,
110132meta: requestParams._meta,
@@ -117,13 +139,20 @@ function buildApprovalDescription(params: {
117139requestedSchema: JsonObject;
118140serverName: string | undefined;
119141}): string {
142+const connectorName = sanitizeOptionalDisplayText(
143+readString(params.meta, MCP_TOOL_APPROVAL_CONNECTOR_NAME_KEY),
144+);
145+const toolTitle = sanitizeOptionalDisplayText(
146+readString(params.meta, MCP_TOOL_APPROVAL_TOOL_TITLE_KEY),
147+);
148+const toolDescription = sanitizeOptionalDisplayText(
149+readString(params.meta, MCP_TOOL_APPROVAL_TOOL_DESCRIPTION_KEY),
150+);
120151const summaryLines = [
121-readString(params.meta, MCP_TOOL_APPROVAL_CONNECTOR_NAME_KEY) &&
122-`App: ${readString(params.meta, MCP_TOOL_APPROVAL_CONNECTOR_NAME_KEY)}`,
123-readString(params.meta, MCP_TOOL_APPROVAL_TOOL_TITLE_KEY) &&
124-`Tool: ${readString(params.meta, MCP_TOOL_APPROVAL_TOOL_TITLE_KEY)}`,
152+connectorName && `App: ${connectorName}`,
153+toolTitle && `Tool: ${toolTitle}`,
125154params.serverName && `MCP server: ${params.serverName}`,
126-readString(params.meta, MCP_TOOL_APPROVAL_TOOL_DESCRIPTION_KEY),
155+toolDescription,
127156].filter((line): line is string => Boolean(line));
128157const paramLines = readDisplayParamLines(params.meta);
129158const propertyLines = readPropertyDescriptionLines(params.requestedSchema);
@@ -145,8 +174,11 @@ function readPropertyDescriptionLines(requestedSchema: JsonObject): string[] {
145174if (!schema) {
146175return undefined;
147176}
148-const propTitle = readString(schema, "title") ?? name;
149-const description = readString(schema, "description");
177+const propTitle =
178+sanitizeDisplayText(readString(schema, "title") ?? "") ||
179+sanitizeDisplayText(name) ||
180+"field";
181+const description = sanitizeOptionalDisplayText(readString(schema, "description"));
150182return description ? `- ${propTitle}: ${description}` : `- ${propTitle}`;
151183})
152184.filter((line): line is string => Boolean(line));
@@ -157,26 +189,105 @@ function readDisplayParamLines(meta: JsonObject): string[] {
157189if (!Array.isArray(displayParams)) {
158190return [];
159191}
160-return displayParams
192+const lines = displayParams
193+.slice(0, MAX_DISPLAY_PARAM_ENTRIES)
161194.map((entry) => {
162195const param = isJsonObject(entry) ? entry : undefined;
163196if (!param) {
164197return undefined;
165198}
166-const name = readString(param, "display_name") ?? readString(param, "name");
199+const name =
200+sanitizeOptionalDisplayText(readString(param, "display_name")) ??
201+sanitizeOptionalDisplayText(readString(param, "name"));
167202if (!name) {
168203return undefined;
169204}
170205return `- ${name}: ${formatDisplayParamValue(param.value)}`;
171206})
172207.filter((line): line is string => Boolean(line));
208+const remaining = displayParams.length - MAX_DISPLAY_PARAM_ENTRIES;
209+return remaining > 0 ? [...lines, `- Additional parameters: ${remaining} more`] : lines;
173210}
174211175212function formatDisplayParamValue(value: JsonValue | undefined): string {
176-const formatted = typeof value === "string" ? value : JSON.stringify(value ?? null);
177-return formatted.length <= MAX_DISPLAY_PARAM_VALUE_LENGTH
178- ? formatted
179- : `${formatted.slice(0, MAX_DISPLAY_PARAM_VALUE_LENGTH - 3)}...`;
213+const formatted = typeof value === "string" ? value : formatDisplayJsonValue(value ?? null);
214+return truncateDisplayText(sanitizeDisplayText(formatted), MAX_DISPLAY_PARAM_VALUE_LENGTH);
215+}
216+217+function formatDisplayJsonValue(value: JsonValue, depth = MAX_DISPLAY_VALUE_DEPTH): string {
218+if (value === null) {
219+return "null";
220+}
221+if (typeof value === "string") {
222+return JSON.stringify(truncateDisplayText(sanitizeDisplayText(value), 80));
223+}
224+if (typeof value === "number" || typeof value === "boolean") {
225+return String(value);
226+}
227+if (Array.isArray(value)) {
228+if (depth <= 0) {
229+return "[truncated]";
230+}
231+const parts: string[] = [];
232+const limit = Math.min(value.length, MAX_DISPLAY_VALUE_ARRAY_ITEMS);
233+for (let i = 0; i < limit; i += 1) {
234+parts.push(formatDisplayJsonValue(value[i] ?? null, depth - 1));
235+}
236+if (value.length > MAX_DISPLAY_VALUE_ARRAY_ITEMS) {
237+parts.push("...");
238+}
239+return `[${parts.join(",")}]`;
240+}
241+if (typeof value === "object") {
242+if (depth <= 0) {
243+return "{truncated}";
244+}
245+const parts: string[] = [];
246+let count = 0;
247+let truncated = false;
248+for (const key in value) {
249+if (!Object.prototype.hasOwnProperty.call(value, key)) {
250+continue;
251+}
252+if (count >= MAX_DISPLAY_VALUE_OBJECT_KEYS) {
253+truncated = true;
254+break;
255+}
256+const safeKey = truncateDisplayText(sanitizeDisplayText(key), 80);
257+parts.push(
258+`${JSON.stringify(safeKey)}:${formatDisplayJsonValue(value[key] ?? null, depth - 1)}`,
259+);
260+count += 1;
261+}
262+if (truncated) {
263+parts.push("...");
264+}
265+return `{${parts.join(",")}}`;
266+}
267+return "null";
268+}
269+270+function sanitizeOptionalDisplayText(value: string | undefined): string | undefined {
271+const sanitized = value === undefined ? "" : sanitizeDisplayText(value);
272+return sanitized || undefined;
273+}
274+275+function sanitizeDisplayText(value: string): string {
276+const scanned = value.slice(0, DISPLAY_TEXT_SCAN_MAX_LENGTH);
277+const clipped = value.length > DISPLAY_TEXT_SCAN_MAX_LENGTH;
278+const sanitized = scanned
279+.replace(ANSI_OSC_SEQUENCE_RE, "")
280+.replace(ANSI_CONTROL_SEQUENCE_RE, "")
281+.replace(DANGLING_TERMINAL_SEQUENCE_SUFFIX_RE, "")
282+.replace(INVISIBLE_FORMATTING_CONTROL_RE, " ")
283+.replace(CONTROL_CHARACTER_RE, " ")
284+.replace(/\s+/g, " ")
285+.trim();
286+return clipped ? `${sanitized}...` : sanitized;
287+}
288+289+function truncateDisplayText(value: string, maxLength: number): string {
290+return value.length <= maxLength ? value : `${value.slice(0, Math.max(0, maxLength - 3))}...`;
180291}
181292182293async function requestPluginApprovalOutcome(params: {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。