fix(claude-code): harden MCP elicitation schema handling

This commit is contained in:
Jeremy 2026-04-11 13:26:24 -05:00
parent 1495e711e1
commit bf4bcfadde
3 changed files with 46 additions and 9 deletions

View file

@ -547,9 +547,6 @@ export async function createMcpServer(sessionManager: SessionManager): Promise<{
type: 'string',
title: item.key,
description: descParts.join('\n'),
format: 'password',
writeOnly: true,
'x-sensitive': true,
};
// Don't mark as required — empty string = skip
}

View file

@ -313,8 +313,15 @@ export function parseTextInputElicitation(
request: Pick<SdkElicitationRequest, "message" | "mode" | "requestedSchema">,
): ParsedTextInputField[] | null {
if (request.mode && request.mode !== "form") return null;
const properties = request.requestedSchema?.properties;
if (!properties || typeof properties !== "object") return null;
const schema = request.requestedSchema as
| ({ properties?: Record<string, SdkElicitationFieldSchema>; keys?: Record<string, SdkElicitationFieldSchema> } & Record<string, unknown>)
| undefined;
const fieldsSource = schema?.properties && typeof schema.properties === "object"
? schema.properties
: schema?.keys && typeof schema.keys === "object"
? schema.keys
: undefined;
if (!fieldsSource) return null;
const requiredSet = new Set(
Array.isArray(request.requestedSchema?.required)
@ -323,10 +330,10 @@ export function parseTextInputElicitation(
);
const fields: ParsedTextInputField[] = [];
for (const [fieldId, field] of Object.entries(properties)) {
if (!field || typeof field !== "object") return null;
if (field.type !== "string") return null;
if (Array.isArray(field.oneOf) && field.oneOf.length > 0) return null;
for (const [fieldId, field] of Object.entries(fieldsSource)) {
if (!field || typeof field !== "object") continue;
if (field.type !== "string") continue;
if (Array.isArray(field.oneOf) && field.oneOf.length > 0) continue;
fields.push({
id: fieldId,

View file

@ -557,6 +557,39 @@ describe("stream-adapter — MCP elicitation bridge", () => {
]);
});
test("parseTextInputElicitation accepts legacy keys schema and skips unsupported fields", () => {
const request = {
serverName: "gsd-workflow",
message: "Enter secure values",
mode: "form" as const,
requestedSchema: {
type: "object" as const,
keys: {
API_TOKEN: {
type: "string",
title: "API_TOKEN",
description: "Leave empty to skip.",
},
META: {
type: "object",
title: "metadata",
},
},
},
};
const parsed = parseTextInputElicitation(request as any);
assert.deepEqual(parsed, [
{
id: "API_TOKEN",
title: "API_TOKEN",
description: "Leave empty to skip.",
required: false,
secure: true,
},
]);
});
test("createClaudeCodeElicitationHandler collects secure_env_collect fields through input dialogs", async () => {
const secureRequest = {
serverName: "gsd-workflow",