From bf754a3ef80af39960ed7bb490ceec35e9996827 Mon Sep 17 00:00:00 2001 From: Alfonso de la Guarda Reyes Date: Sat, 25 Apr 2026 22:30:48 -0500 Subject: [PATCH] fix: add defensive guards for project_id coercion, labels query format, and null MR response - Guard getEffectiveProjectId against z.coerce.string() converting undefined/null to literal strings "undefined"/"null" - Fix labels query parameter to use comma-separated format per GitLab API docs instead of array notation (labels[]) - Add null coalescing for listMergeRequests response to prevent JSON.stringify(null) errors --- index.ts | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/index.ts b/index.ts index 615c3f7b..594d051a 100644 --- a/index.ts +++ b/index.ts @@ -1281,6 +1281,22 @@ async function handleGitLabError(response: import("node-fetch").Response): Promi * @throws {Error} If GITLAB_ALLOWED_PROJECT_IDS is set and the requested project is not in the whitelist */ function getEffectiveProjectId(projectId: string): string { + // Guard against z.coerce.string() converting undefined/null to literal strings + if (!projectId || projectId === "undefined" || projectId === "null") { + if (GITLAB_ALLOWED_PROJECT_IDS.length === 1) { + return GITLAB_ALLOWED_PROJECT_IDS[0]; + } + if (GITLAB_ALLOWED_PROJECT_IDS.length > 1) { + throw new Error( + `Multiple projects allowed (${GITLAB_ALLOWED_PROJECT_IDS.join(", ")}). Please specify a project ID.` + ); + } + if (GITLAB_PROJECT_ID) { + return GITLAB_PROJECT_ID; + } + throw new Error("No project ID provided and GITLAB_PROJECT_ID is not set"); + } + if (GITLAB_ALLOWED_PROJECT_IDS.length > 0) { // If there's only one allowed project, use it as default if (GITLAB_ALLOWED_PROJECT_IDS.length === 1 && !projectId) { @@ -1509,16 +1525,10 @@ async function listIssues( // Add all query parameters Object.entries(options).forEach(([key, value]) => { if (value !== undefined) { - const keys = ["labels", "assignee_username"]; - if (keys.includes(key)) { - if (Array.isArray(value)) { - // Handle array of labels - value.forEach(label => { - url.searchParams.append(`${key}[]`, label.toString()); - }); - } else if (value) { - url.searchParams.append(`${key}[]`, value.toString()); - } + if (key === "labels" && Array.isArray(value)) { + url.searchParams.append(key, value.join(",")); + } else if (key === "assignee_username" && Array.isArray(value)) { + value.forEach(v => url.searchParams.append(`${key}[]`, v.toString())); } else { url.searchParams.append(key, String(value)); } @@ -9390,7 +9400,7 @@ async function handleToolCall(params: any) { const mergeRequests = await listMergeRequests(project_id, cleanedOptions); return { - content: [{ type: "text", text: JSON.stringify(mergeRequests, null, 2) }], + content: [{ type: "text", text: JSON.stringify(mergeRequests ?? [], null, 2) }], }; }