diff --git a/index.ts b/index.ts index 30766a2..4f528ba 100644 --- a/index.ts +++ b/index.ts @@ -4,10 +4,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; -import { - CallToolRequestSchema, - ListToolsRequestSchema, -} from "@modelcontextprotocol/sdk/types.js"; +import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js"; import express, { Request, Response } from "express"; import fetchCookie from "fetch-cookie"; import fs from "fs"; @@ -169,6 +166,7 @@ import { ListWikiPagesOptions, ListWikiPagesSchema, MarkdownUploadSchema, + MergeMergeRequestSchema, type MergeRequestThreadPosition, type MergeRequestThreadPositionCreate, type MyIssuesOptions, @@ -240,7 +238,7 @@ const server = new Server( capabilities: { tools: {}, }, - }, + } ); const GITLAB_PERSONAL_ACCESS_TOKEN = process.env.GITLAB_PERSONAL_ACCESS_TOKEN; @@ -301,7 +299,7 @@ const createCookieJar = (): CookieJar | null => { const jar = new CookieJar(); const cookieContent = fs.readFileSync(cookiePath, "utf8"); - cookieContent.split("\n").forEach((line) => { + cookieContent.split("\n").forEach(line => { // Handle #HttpOnly_ prefix if (line.startsWith("#HttpOnly_")) { line = line.slice(10); @@ -350,8 +348,7 @@ async function ensureSessionForRequest(): Promise { // Check if we already have GitLab session cookies const gitlabCookies = cookieJar.getCookiesSync(baseUrl); const hasSessionCookie = gitlabCookies.some( - (cookie) => - cookie.key === "_gitlab_session" || cookie.key === "remember_user_token", + cookie => cookie.key === "_gitlab_session" || cookie.key === "remember_user_token" ); if (!hasSessionCookie) { @@ -365,7 +362,7 @@ async function ensureSessionForRequest(): Promise { }); // Small delay to ensure cookies are fully processed - await new Promise((resolve) => setTimeout(resolve, 100)); + await new Promise(resolve => setTimeout(resolve, 100)); } catch (error) { // Ignore session establishment errors } @@ -396,6 +393,11 @@ const DEFAULT_FETCH_CONFIG = { // Define all available tools const allTools = [ + { + name: "merge_merge_request", + description: "Merge a merge request in a GitLab project", + inputSchema: zodToJsonSchema(MergeMergeRequestSchema), + }, { name: "create_or_update_file", description: "Create or update a single file in a GitLab project", @@ -413,8 +415,7 @@ const allTools = [ }, { name: "get_file_contents", - description: - "Get the contents of a file or directory from a GitLab project", + description: "Get the contents of a file or directory from a GitLab project", inputSchema: zodToJsonSchema(GetFileContentsSchema), }, { @@ -462,14 +463,12 @@ const allTools = [ }, { name: "get_branch_diffs", - description: - "Get the changes/diffs between two branches or commits in a GitLab project", + description: "Get the changes/diffs between two branches or commits in a GitLab project", inputSchema: zodToJsonSchema(GetBranchDiffsSchema), }, { name: "update_merge_request", - description: - "Update a merge request (Either mergeRequestIid or branchName must be provided)", + description: "Update a merge request (Either mergeRequestIid or branchName must be provided)", inputSchema: zodToJsonSchema(UpdateMergeRequestSchema), }, { @@ -675,8 +674,7 @@ const allTools = [ }, { name: "get_repository_tree", - description: - "Get the repository tree for a GitLab project (list files and directories)", + description: "Get the repository tree for a GitLab project (list files and directories)", inputSchema: zodToJsonSchema(GetRepositoryTreeSchema), }, { @@ -728,8 +726,7 @@ const allTools = [ }, { name: "list_merge_requests", - description: - "List merge requests in a GitLab project with filtering options", + description: "List merge requests in a GitLab project with filtering options", inputSchema: zodToJsonSchema(ListMergeRequestsSchema), }, { @@ -804,8 +801,7 @@ const allTools = [ }, { name: "upload_markdown", - description: - "Upload a file to a GitLab project for use in markdown content", + description: "Upload a file to a GitLab project for use in markdown content", inputSchema: zodToJsonSchema(MarkdownUploadSchema), }, ]; @@ -907,10 +903,7 @@ function normalizeGitLabApiUrl(url?: string): string { let normalizedUrl = url.endsWith("/") ? url.slice(0, -1) : url; // Check if URL already has /api/v4 - if ( - !normalizedUrl.endsWith("/api/v4") && - !normalizedUrl.endsWith("/api/v4/") - ) { + if (!normalizedUrl.endsWith("/api/v4") && !normalizedUrl.endsWith("/api/v4/")) { // Append /api/v4 if not already present normalizedUrl = `${normalizedUrl}/api/v4`; } @@ -935,24 +928,17 @@ if (!GITLAB_PERSONAL_ACCESS_TOKEN) { * @param {import("node-fetch").Response} response - The response from GitLab API * @throws {Error} Throws an error with response details if the request failed */ -async function handleGitLabError( - response: import("node-fetch").Response, -): Promise { +async function handleGitLabError(response: import("node-fetch").Response): Promise { if (!response.ok) { const errorBody = await response.text(); // Check specifically for Rate Limit error - if ( - response.status === 403 && - errorBody.includes("User API Key Rate limit exceeded") - ) { + if (response.status === 403 && errorBody.includes("User API Key Rate limit exceeded")) { logger.error("GitLab API Rate Limit Exceeded:", errorBody); logger.error("User API Key Rate limit exceeded. Please try again later."); throw new Error(`GitLab API Rate Limit Exceeded: ${errorBody}`); } else { // Handle other API errors - throw new Error( - `GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`, - ); + throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`); } } } @@ -992,15 +978,10 @@ function getEffectiveProjectId(projectId: string): string { * @param {string} [namespace] - The namespace to fork the project to * @returns {Promise} The created fork */ -async function forkProject( - projectId: string, - namespace?: string, -): Promise { +async function forkProject(projectId: string, namespace?: string): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const effectiveProjectId = getEffectiveProjectId(projectId); - const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/fork`, - ); + const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/fork`); if (namespace) { url.searchParams.append("namespace", namespace); @@ -1031,12 +1012,12 @@ async function forkProject( */ async function createBranch( projectId: string, - options: z.infer, + options: z.infer ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const effectiveProjectId = getEffectiveProjectId(projectId); const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/repository/branches`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/repository/branches` ); const response = await fetch(url.toString(), { @@ -1062,9 +1043,7 @@ async function createBranch( async function getDefaultBranchRef(projectId: string): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const effectiveProjectId = getEffectiveProjectId(projectId); - const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}`, - ); + const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}`); const response = await fetch(url.toString(), { ...DEFAULT_FETCH_CONFIG, @@ -1087,7 +1066,7 @@ async function getDefaultBranchRef(projectId: string): Promise { async function getFileContents( projectId: string, filePath: string, - ref?: string, + ref?: string ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const effectiveProjectId = getEffectiveProjectId(projectId); @@ -1099,7 +1078,7 @@ async function getFileContents( } const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/repository/files/${encodedPath}`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/repository/files/${encodedPath}` ); url.searchParams.append("ref", ref); @@ -1119,9 +1098,7 @@ async function getFileContents( // Base64로 인코딩된 파일 내용을 UTF-8로 디코딩 if (!Array.isArray(parsedData) && parsedData.content) { - parsedData.content = Buffer.from(parsedData.content, "base64").toString( - "utf8", - ); + parsedData.content = Buffer.from(parsedData.content, "base64").toString("utf8"); parsedData.encoding = "utf8"; } @@ -1138,12 +1115,12 @@ async function getFileContents( */ async function createIssue( projectId: string, - options: z.infer, + options: z.infer ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const effectiveProjectId = getEffectiveProjectId(projectId); const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/issues`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/issues` ); const response = await fetch(url.toString(), { @@ -1179,15 +1156,13 @@ async function createIssue( */ async function listIssues( projectId?: string, - options: Omit, "project_id"> = {}, + options: Omit, "project_id"> = {} ): Promise { let url: URL; if (projectId) { projectId = decodeURIComponent(projectId); // Decode project ID const effectiveProjectId = getEffectiveProjectId(projectId); - url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/issues`, - ); + url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/issues`); } else { url = new URL(`${GITLAB_API_URL}/issues`); } @@ -1199,7 +1174,7 @@ async function listIssues( if (keys.includes(key)) { if (Array.isArray(value)) { // Handle array of labels - value.forEach((label) => { + value.forEach(label => { url.searchParams.append(`${key}[]`, label.toString()); }); } else if (value) { @@ -1229,11 +1204,11 @@ async function listIssues( */ async function listMergeRequests( projectId: string, - options: Omit, "project_id"> = {}, + options: Omit, "project_id"> = {} ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests` ); // Add all query parameters @@ -1265,13 +1240,10 @@ async function listMergeRequests( * @param {number} issueIid - The internal ID of the project issue * @returns {Promise} The issue */ -async function getIssue( - projectId: string, - issueIid: number | string, -): Promise { +async function getIssue(projectId: string, issueIid: number | string): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${issueIid}`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${issueIid}` ); const response = await fetch(url.toString(), { @@ -1295,11 +1267,11 @@ async function getIssue( async function updateIssue( projectId: string, issueIid: number | string, - options: Omit, "project_id" | "issue_iid">, + options: Omit, "project_id" | "issue_iid"> ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${issueIid}`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${issueIid}` ); // Convert labels array to comma-separated string if present @@ -1327,13 +1299,10 @@ async function updateIssue( * @param {number} issueIid - The internal ID of the project issue * @returns {Promise} */ -async function deleteIssue( - projectId: string, - issueIid: number | string, -): Promise { +async function deleteIssue(projectId: string, issueIid: number | string): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${issueIid}`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${issueIid}` ); const response = await fetch(url.toString(), { @@ -1354,11 +1323,11 @@ async function deleteIssue( */ async function listIssueLinks( projectId: string, - issueIid: number | string, + issueIid: number | string ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${issueIid}/links`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${issueIid}/links` ); const response = await fetch(url.toString(), { @@ -1382,13 +1351,13 @@ async function listIssueLinks( async function getIssueLink( projectId: string, issueIid: number | string, - issueLinkId: number | string, + issueLinkId: number | string ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( `${GITLAB_API_URL}/projects/${encodeURIComponent( - getEffectiveProjectId(projectId), - )}/issues/${issueIid}/links/${issueLinkId}`, + getEffectiveProjectId(projectId) + )}/issues/${issueIid}/links/${issueLinkId}` ); const response = await fetch(url.toString(), { @@ -1416,12 +1385,12 @@ async function createIssueLink( issueIid: number | string, targetProjectId: string, targetIssueIid: number | string, - linkType: "relates_to" | "blocks" | "is_blocked_by" = "relates_to", + linkType: "relates_to" | "blocks" | "is_blocked_by" = "relates_to" ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID targetProjectId = decodeURIComponent(targetProjectId); // Decode target project ID as well const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${issueIid}/links`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/issues/${issueIid}/links` ); const response = await fetch(url.toString(), { @@ -1451,13 +1420,13 @@ async function createIssueLink( async function deleteIssueLink( projectId: string, issueIid: number | string, - issueLinkId: number | string, + issueLinkId: number | string ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( `${GITLAB_API_URL}/projects/${encodeURIComponent( - getEffectiveProjectId(projectId), - )}/issues/${issueIid}/links/${issueLinkId}`, + getEffectiveProjectId(projectId) + )}/issues/${issueIid}/links/${issueLinkId}` ); const response = await fetch(url.toString(), { @@ -1478,11 +1447,11 @@ async function deleteIssueLink( */ async function createMergeRequest( projectId: string, - options: z.infer, + options: z.infer ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests` ); const response = await fetch(url.toString(), { @@ -1511,9 +1480,7 @@ async function createMergeRequest( if (!response.ok) { const errorBody = await response.text(); - throw new Error( - `GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`, - ); + throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`); } const data = await response.json(); @@ -1534,13 +1501,13 @@ async function listDiscussions( projectId: string, resourceType: "issues" | "merge_requests", resourceIid: number | string, - options: PaginationOptions = {}, + options: PaginationOptions = {} ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( `${GITLAB_API_URL}/projects/${encodeURIComponent( - getEffectiveProjectId(projectId), - )}/${resourceType}/${resourceIid}/discussions`, + getEffectiveProjectId(projectId) + )}/${resourceType}/${resourceIid}/discussions` ); // Add query parameters for pagination and sorting @@ -1563,18 +1530,14 @@ async function listDiscussions( x_next_page: response.headers.get("x-next-page") ? parseInt(response.headers.get("x-next-page")!) : null, - x_page: response.headers.get("x-page") - ? parseInt(response.headers.get("x-page")!) - : undefined, + x_page: response.headers.get("x-page") ? parseInt(response.headers.get("x-page")!) : undefined, x_per_page: response.headers.get("x-per-page") ? parseInt(response.headers.get("x-per-page")!) : undefined, x_prev_page: response.headers.get("x-prev-page") ? parseInt(response.headers.get("x-prev-page")!) : null, - x_total: response.headers.get("x-total") - ? parseInt(response.headers.get("x-total")!) - : null, + x_total: response.headers.get("x-total") ? parseInt(response.headers.get("x-total")!) : null, x_total_pages: response.headers.get("x-total-pages") ? parseInt(response.headers.get("x-total-pages")!) : null, @@ -1598,7 +1561,7 @@ async function listDiscussions( async function listMergeRequestDiscussions( projectId: string, mergeRequestIid: number | string, - options: PaginationOptions = {}, + options: PaginationOptions = {} ): Promise { return listDiscussions(projectId, "merge_requests", mergeRequestIid, options); } @@ -1614,7 +1577,7 @@ async function listMergeRequestDiscussions( async function listIssueDiscussions( projectId: string, issueIid: number | string, - options: PaginationOptions = {}, + options: PaginationOptions = {} ): Promise { return listDiscussions(projectId, "issues", issueIid, options); } @@ -1637,13 +1600,13 @@ async function updateMergeRequestNote( discussionId: string, noteId: number | string, body?: string, - resolved?: boolean, + resolved?: boolean ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( `${GITLAB_API_URL}/projects/${encodeURIComponent( - getEffectiveProjectId(projectId), - )}/merge_requests/${mergeRequestIid}/discussions/${discussionId}/notes/${noteId}`, + getEffectiveProjectId(projectId) + )}/merge_requests/${mergeRequestIid}/discussions/${discussionId}/notes/${noteId}` ); // Only one of body or resolved can be sent according to GitLab API @@ -1679,13 +1642,13 @@ async function updateIssueNote( issueIid: number | string, discussionId: string, noteId: number | string, - body: string, + body: string ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( `${GITLAB_API_URL}/projects/${encodeURIComponent( - getEffectiveProjectId(projectId), - )}/issues/${issueIid}/discussions/${discussionId}/notes/${noteId}`, + getEffectiveProjectId(projectId) + )}/issues/${issueIid}/discussions/${discussionId}/notes/${noteId}` ); const payload = { body }; @@ -1715,13 +1678,13 @@ async function createIssueNote( issueIid: number | string, discussionId: string, body: string, - createdAt?: string, + createdAt?: string ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( `${GITLAB_API_URL}/projects/${encodeURIComponent( - getEffectiveProjectId(projectId), - )}/issues/${issueIid}/discussions/${discussionId}/notes`, + getEffectiveProjectId(projectId) + )}/issues/${issueIid}/discussions/${discussionId}/notes` ); const payload: { body: string; created_at?: string } = { body }; @@ -1756,13 +1719,13 @@ async function createMergeRequestNote( mergeRequestIid: number | string, discussionId: string, body: string, - createdAt?: string, + createdAt?: string ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( `${GITLAB_API_URL}/projects/${encodeURIComponent( - getEffectiveProjectId(projectId), - )}/merge_requests/${mergeRequestIid}/discussions/${discussionId}/notes`, + getEffectiveProjectId(projectId) + )}/merge_requests/${mergeRequestIid}/discussions/${discussionId}/notes` ); const payload: { body: string; created_at?: string } = { body }; @@ -1801,12 +1764,12 @@ async function createOrUpdateFile( branch: string, previousPath?: string, last_commit_id?: string, - commit_id?: string, + commit_id?: string ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const encodedPath = encodeURIComponent(filePath); const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/files/${encodedPath}`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/files/${encodedPath}` ); const body: Record = { @@ -1861,9 +1824,7 @@ async function createOrUpdateFile( if (!response.ok) { const errorBody = await response.text(); - throw new Error( - `GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`, - ); + throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`); } const data = await response.json(); @@ -1882,11 +1843,11 @@ async function createOrUpdateFile( async function createTree( projectId: string, files: FileOperation[], - ref?: string, + ref?: string ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/tree`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/tree` ); if (ref) { @@ -1897,7 +1858,7 @@ async function createTree( ...DEFAULT_FETCH_CONFIG, method: "POST", body: JSON.stringify({ - files: files.map((file) => ({ + files: files.map(file => ({ file_path: file.path, content: file.content, encoding: "text", @@ -1912,9 +1873,7 @@ async function createTree( if (!response.ok) { const errorBody = await response.text(); - throw new Error( - `GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`, - ); + throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`); } const data = await response.json(); @@ -1935,11 +1894,11 @@ async function createCommit( projectId: string, message: string, branch: string, - actions: FileOperation[], + actions: FileOperation[] ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/commits`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/commits` ); const response = await fetch(url.toString(), { @@ -1948,7 +1907,7 @@ async function createCommit( body: JSON.stringify({ branch, commit_message: message, - actions: actions.map((action) => ({ + actions: actions.map(action => ({ action: "create", file_path: action.path, content: action.content, @@ -1964,9 +1923,7 @@ async function createCommit( if (!response.ok) { const errorBody = await response.text(); - throw new Error( - `GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`, - ); + throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`); } const data = await response.json(); @@ -1985,7 +1942,7 @@ async function createCommit( async function searchProjects( query: string, page: number = 1, - perPage: number = 20, + perPage: number = 20 ): Promise { const url = new URL(`${GITLAB_API_URL}/projects`); url.searchParams.append("search", query); @@ -2000,9 +1957,7 @@ async function searchProjects( if (!response.ok) { const errorBody = await response.text(); - throw new Error( - `GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`, - ); + throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`); } const projects = (await response.json()) as GitLabRepository[]; @@ -2028,7 +1983,7 @@ async function searchProjects( * @returns {Promise} The created repository */ async function createRepository( - options: z.infer, + options: z.infer ): Promise { const response = await fetch(`${GITLAB_API_URL}/projects`, { ...DEFAULT_FETCH_CONFIG, @@ -2045,9 +2000,7 @@ async function createRepository( if (!response.ok) { const errorBody = await response.text(); - throw new Error( - `GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`, - ); + throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`); } const data = await response.json(); @@ -2066,7 +2019,7 @@ async function createRepository( async function getMergeRequest( projectId: string, mergeRequestIid?: number | string, - branchName?: string, + branchName?: string ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID let url: URL; @@ -2074,14 +2027,14 @@ async function getMergeRequest( if (mergeRequestIid) { url = new URL( `${GITLAB_API_URL}/projects/${encodeURIComponent( - getEffectiveProjectId(projectId), - )}/merge_requests/${mergeRequestIid}`, + getEffectiveProjectId(projectId) + )}/merge_requests/${mergeRequestIid}` ); } else if (branchName) { url = new URL( `${GITLAB_API_URL}/projects/${encodeURIComponent( - getEffectiveProjectId(projectId), - )}/merge_requests?source_branch=${encodeURIComponent(branchName)}`, + getEffectiveProjectId(projectId) + )}/merge_requests?source_branch=${encodeURIComponent(branchName)}` ); } else { throw new Error("Either mergeRequestIid or branchName must be provided"); @@ -2117,7 +2070,7 @@ async function getMergeRequestDiffs( projectId: string, mergeRequestIid?: number | string, branchName?: string, - view?: "inline" | "parallel", + view?: "inline" | "parallel" ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID if (!mergeRequestIid && !branchName) { @@ -2125,18 +2078,14 @@ async function getMergeRequestDiffs( } if (branchName && !mergeRequestIid) { - const mergeRequest = await getMergeRequest( - projectId, - undefined, - branchName, - ); + const mergeRequest = await getMergeRequest(projectId, undefined, branchName); mergeRequestIid = mergeRequest.iid; } const url = new URL( `${GITLAB_API_URL}/projects/${encodeURIComponent( - getEffectiveProjectId(projectId), - )}/merge_requests/${mergeRequestIid}/changes`, + getEffectiveProjectId(projectId) + )}/merge_requests/${mergeRequestIid}/changes` ); if (view) { @@ -2168,7 +2117,7 @@ async function listMergeRequestDiffs( branchName?: string, page?: number, perPage?: number, - unidiff?: boolean, + unidiff?: boolean ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID if (!mergeRequestIid && !branchName) { @@ -2176,18 +2125,14 @@ async function listMergeRequestDiffs( } if (branchName && !mergeRequestIid) { - const mergeRequest = await getMergeRequest( - projectId, - undefined, - branchName, - ); + const mergeRequest = await getMergeRequest(projectId, undefined, branchName); mergeRequestIid = mergeRequest.iid; } const url = new URL( `${GITLAB_API_URL}/projects/${encodeURIComponent( - getEffectiveProjectId(projectId), - )}/merge_requests/${mergeRequestIid}/diffs`, + getEffectiveProjectId(projectId) + )}/merge_requests/${mergeRequestIid}/diffs` ); if (page) { @@ -2223,12 +2168,12 @@ async function getBranchDiffs( projectId: string, from: string, to: string, - straight?: boolean, + straight?: boolean ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/compare`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/compare` ); url.searchParams.append("from", from); @@ -2244,9 +2189,7 @@ async function getBranchDiffs( if (!response.ok) { const errorBody = await response.text(); - throw new Error( - `GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`, - ); + throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`); } const data = await response.json(); @@ -2270,7 +2213,7 @@ async function updateMergeRequest( "project_id" | "merge_request_iid" | "source_branch" >, mergeRequestIid?: number | string, - branchName?: string, + branchName?: string ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID if (!mergeRequestIid && !branchName) { @@ -2278,16 +2221,41 @@ async function updateMergeRequest( } if (branchName && !mergeRequestIid) { - const mergeRequest = await getMergeRequest( - projectId, - undefined, - branchName, - ); + const mergeRequest = await getMergeRequest(projectId, undefined, branchName); mergeRequestIid = mergeRequest.iid; } const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${mergeRequestIid}`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${mergeRequestIid}` + ); + + const response = await fetch(url.toString(), { + ...DEFAULT_FETCH_CONFIG, + method: "PUT", + body: JSON.stringify(options), + }); + + await handleGitLabError(response); + return GitLabMergeRequestSchema.parse(await response.json()); +} + +/** + * Merge a merge request + * マージリクエストをマージする + * + * @param {string} projectId - The ID or URL-encoded path of the project + * @param {number} mergeRequestIid - The internal ID of the merge request + * @param {Object} options - Options for merging the merge request + * @returns {Promise} The merged merge request + */ +async function mergeMergeRequest( + projectId: string, + options: Omit, "project_id" | "merge_request_iid">, + mergeRequestIid?: number | string +): Promise { + projectId = decodeURIComponent(projectId); // Decode project ID + const url = new URL( + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${mergeRequestIid}/merge` ); const response = await fetch(url.toString(), { @@ -2315,14 +2283,14 @@ async function createNote( projectId: string, noteableType: "issue" | "merge_request", // 'issue' 또는 'merge_request' 타입 명시 noteableIid: number | string, - body: string, + body: string ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID // ⚙️ 응답 타입은 GitLab API 문서에 따라 조정 가능 const url = new URL( `${GITLAB_API_URL}/projects/${encodeURIComponent( - getEffectiveProjectId(projectId), - )}/${noteableType}s/${noteableIid}/notes`, // Using plural form (issues/merge_requests) as per GitLab API documentation + getEffectiveProjectId(projectId) + )}/${noteableType}s/${noteableIid}/notes` // Using plural form (issues/merge_requests) as per GitLab API documentation ); const response = await fetch(url.toString(), { @@ -2333,9 +2301,7 @@ async function createNote( if (!response.ok) { const errorText = await response.text(); - throw new Error( - `GitLab API error: ${response.status} ${response.statusText}\n${errorText}`, - ); + throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorText}`); } return await response.json(); @@ -2632,13 +2598,13 @@ async function createMergeRequestThread( mergeRequestIid: number | string, body: string, position?: MergeRequestThreadPosition, - createdAt?: string, + createdAt?: string ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( `${GITLAB_API_URL}/projects/${encodeURIComponent( - getEffectiveProjectId(projectId), - )}/merge_requests/${mergeRequestIid}/discussions`, + getEffectiveProjectId(projectId) + )}/merge_requests/${mergeRequestIid}/discussions` ); const payload: Record = { body }; @@ -2730,11 +2696,9 @@ async function getNamespace(id: string): Promise { */ async function verifyNamespaceExistence( namespacePath: string, - parentId?: number, + parentId?: number ): Promise { - const url = new URL( - `${GITLAB_API_URL}/namespaces/${encodeURIComponent(namespacePath)}/exists`, - ); + const url = new URL(`${GITLAB_API_URL}/namespaces/${encodeURIComponent(namespacePath)}/exists`); if (parentId) { url.searchParams.append("parent_id", parentId.toString()); @@ -2766,11 +2730,11 @@ async function getProject( license?: boolean; statistics?: boolean; with_custom_attributes?: boolean; - } = {}, + } = {} ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}` ); if (options.license) { @@ -2802,7 +2766,7 @@ async function getProject( * @returns {Promise} List of projects */ async function listProjects( - options: z.infer = {}, + options: z.infer = {} ): Promise { // Construct the query parameters const params = new URLSearchParams(); @@ -2817,12 +2781,9 @@ async function listProjects( } // Make the API request - const response = await fetch( - `${GITLAB_API_URL}/projects?${params.toString()}`, - { - ...DEFAULT_FETCH_CONFIG, - }, - ); + const response = await fetch(`${GITLAB_API_URL}/projects?${params.toString()}`, { + ...DEFAULT_FETCH_CONFIG, + }); // Handle errors await handleGitLabError(response); @@ -2841,12 +2802,12 @@ async function listProjects( */ async function listLabels( projectId: string, - options: Omit, "project_id"> = {}, + options: Omit, "project_id"> = {} ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID // Construct the URL with project path const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/labels`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/labels` ); // Add query parameters @@ -2884,21 +2845,18 @@ async function listLabels( async function getLabel( projectId: string, labelId: number | string, - includeAncestorGroups?: boolean, + includeAncestorGroups?: boolean ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( `${GITLAB_API_URL}/projects/${encodeURIComponent( - getEffectiveProjectId(projectId), - )}/labels/${encodeURIComponent(String(labelId))}`, + getEffectiveProjectId(projectId) + )}/labels/${encodeURIComponent(String(labelId))}` ); // Add query parameters if (includeAncestorGroups !== undefined) { - url.searchParams.append( - "include_ancestor_groups", - includeAncestorGroups ? "true" : "false", - ); + url.searchParams.append("include_ancestor_groups", includeAncestorGroups ? "true" : "false"); } // Make the API request @@ -2923,7 +2881,7 @@ async function getLabel( */ async function createLabel( projectId: string, - options: Omit, "project_id">, + options: Omit, "project_id"> ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID // Make the API request @@ -2933,7 +2891,7 @@ async function createLabel( ...DEFAULT_FETCH_CONFIG, method: "POST", body: JSON.stringify(options), - }, + } ); // Handle errors @@ -2955,19 +2913,19 @@ async function createLabel( async function updateLabel( projectId: string, labelId: number | string, - options: Omit, "project_id" | "label_id">, + options: Omit, "project_id" | "label_id"> ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID // Make the API request const response = await fetch( `${GITLAB_API_URL}/projects/${encodeURIComponent( - getEffectiveProjectId(projectId), + getEffectiveProjectId(projectId) )}/labels/${encodeURIComponent(String(labelId))}`, { ...DEFAULT_FETCH_CONFIG, method: "PUT", body: JSON.stringify(options), - }, + } ); // Handle errors @@ -2984,20 +2942,17 @@ async function updateLabel( * @param projectId The ID or URL-encoded path of the project * @param labelId The ID or name of the label to delete */ -async function deleteLabel( - projectId: string, - labelId: number | string, -): Promise { +async function deleteLabel(projectId: string, labelId: number | string): Promise { projectId = decodeURIComponent(projectId); // Decode project ID // Make the API request const response = await fetch( `${GITLAB_API_URL}/projects/${encodeURIComponent( - getEffectiveProjectId(projectId), + getEffectiveProjectId(projectId) )}/labels/${encodeURIComponent(String(labelId))}`, { ...DEFAULT_FETCH_CONFIG, method: "DELETE", - }, + } ); // Handle errors @@ -3011,59 +2966,38 @@ async function deleteLabel( * @returns {Promise} Array of projects in the group */ async function listGroupProjects( - options: z.infer, + options: z.infer ): Promise { - const url = new URL( - `${GITLAB_API_URL}/groups/${encodeURIComponent(options.group_id)}/projects`, - ); + const url = new URL(`${GITLAB_API_URL}/groups/${encodeURIComponent(options.group_id)}/projects`); // Add optional parameters to URL - if (options.include_subgroups) - url.searchParams.append("include_subgroups", "true"); + if (options.include_subgroups) url.searchParams.append("include_subgroups", "true"); if (options.search) url.searchParams.append("search", options.search); if (options.order_by) url.searchParams.append("order_by", options.order_by); if (options.sort) url.searchParams.append("sort", options.sort); if (options.page) url.searchParams.append("page", options.page.toString()); - if (options.per_page) - url.searchParams.append("per_page", options.per_page.toString()); + if (options.per_page) url.searchParams.append("per_page", options.per_page.toString()); if (options.archived !== undefined) url.searchParams.append("archived", options.archived.toString()); - if (options.visibility) - url.searchParams.append("visibility", options.visibility); + if (options.visibility) url.searchParams.append("visibility", options.visibility); if (options.with_issues_enabled !== undefined) - url.searchParams.append( - "with_issues_enabled", - options.with_issues_enabled.toString(), - ); + url.searchParams.append("with_issues_enabled", options.with_issues_enabled.toString()); if (options.with_merge_requests_enabled !== undefined) url.searchParams.append( "with_merge_requests_enabled", - options.with_merge_requests_enabled.toString(), + options.with_merge_requests_enabled.toString() ); if (options.min_access_level !== undefined) - url.searchParams.append( - "min_access_level", - options.min_access_level.toString(), - ); + url.searchParams.append("min_access_level", options.min_access_level.toString()); if (options.with_programming_language) - url.searchParams.append( - "with_programming_language", - options.with_programming_language, - ); - if (options.starred !== undefined) - url.searchParams.append("starred", options.starred.toString()); + url.searchParams.append("with_programming_language", options.with_programming_language); + if (options.starred !== undefined) url.searchParams.append("starred", options.starred.toString()); if (options.statistics !== undefined) url.searchParams.append("statistics", options.statistics.toString()); if (options.with_custom_attributes !== undefined) - url.searchParams.append( - "with_custom_attributes", - options.with_custom_attributes.toString(), - ); + url.searchParams.append("with_custom_attributes", options.with_custom_attributes.toString()); if (options.with_security_reports !== undefined) - url.searchParams.append( - "with_security_reports", - options.with_security_reports.toString(), - ); + url.searchParams.append("with_security_reports", options.with_security_reports.toString()); const response = await fetch(url.toString(), { ...DEFAULT_FETCH_CONFIG, @@ -3080,15 +3014,14 @@ async function listGroupProjects( */ async function listWikiPages( projectId: string, - options: Omit = {}, + options: Omit = {} ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/wikis`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/wikis` ); if (options.page) url.searchParams.append("page", options.page.toString()); - if (options.per_page) - url.searchParams.append("per_page", options.per_page.toString()); + if (options.per_page) url.searchParams.append("per_page", options.per_page.toString()); if (options.with_content) url.searchParams.append("with_content", options.with_content.toString()); const response = await fetch(url.toString(), { @@ -3102,14 +3035,11 @@ async function listWikiPages( /** * Get a specific wiki page */ -async function getWikiPage( - projectId: string, - slug: string, -): Promise { +async function getWikiPage(projectId: string, slug: string): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const response = await fetch( `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/wikis/${encodeURIComponent(slug)}`, - { ...DEFAULT_FETCH_CONFIG }, + { ...DEFAULT_FETCH_CONFIG } ); await handleGitLabError(response); const data = await response.json(); @@ -3123,7 +3053,7 @@ async function createWikiPage( projectId: string, title: string, content: string, - format?: string, + format?: string ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const body: Record = { title, content }; @@ -3134,7 +3064,7 @@ async function createWikiPage( ...DEFAULT_FETCH_CONFIG, method: "POST", body: JSON.stringify(body), - }, + } ); await handleGitLabError(response); const data = await response.json(); @@ -3149,7 +3079,7 @@ async function updateWikiPage( slug: string, title?: string, content?: string, - format?: string, + format?: string ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const body: Record = {}; @@ -3162,7 +3092,7 @@ async function updateWikiPage( ...DEFAULT_FETCH_CONFIG, method: "PUT", body: JSON.stringify(body), - }, + } ); await handleGitLabError(response); const data = await response.json(); @@ -3179,7 +3109,7 @@ async function deleteWikiPage(projectId: string, slug: string): Promise { { ...DEFAULT_FETCH_CONFIG, method: "DELETE", - }, + } ); await handleGitLabError(response); } @@ -3193,11 +3123,11 @@ async function deleteWikiPage(projectId: string, slug: string): Promise { */ async function listPipelines( projectId: string, - options: Omit = {}, + options: Omit = {} ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines` ); // Add all query parameters @@ -3225,11 +3155,11 @@ async function listPipelines( */ async function getPipeline( projectId: string, - pipelineId: number | string, + pipelineId: number | string ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${pipelineId}`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${pipelineId}` ); const response = await fetch(url.toString(), { @@ -3256,11 +3186,11 @@ async function getPipeline( async function listPipelineJobs( projectId: string, pipelineId: number | string, - options: Omit = {}, + options: Omit = {} ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${pipelineId}/jobs`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${pipelineId}/jobs` ); // Add all query parameters @@ -3298,14 +3228,11 @@ async function listPipelineJobs( async function listPipelineTriggerJobs( projectId: string, pipelineId: number | string, - options: Omit< - ListPipelineTriggerJobsOptions, - "project_id" | "pipeline_id" - > = {}, + options: Omit = {} ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${pipelineId}/bridges`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${pipelineId}/bridges` ); // Add all query parameters @@ -3334,11 +3261,11 @@ async function listPipelineTriggerJobs( async function getPipelineJob( projectId: string, - jobId: number | string, + jobId: number | string ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/jobs/${jobId}`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/jobs/${jobId}` ); const response = await fetch(url.toString(), { @@ -3367,11 +3294,11 @@ async function getPipelineJobOutput( projectId: string, jobId: number | string, limit?: number, - offset?: number, + offset?: number ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/jobs/${jobId}/trace`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/jobs/${jobId}/trace` ); const response = await fetch(url.toString(), { @@ -3429,11 +3356,11 @@ async function getPipelineJobOutput( async function createPipeline( projectId: string, ref: string, - variables?: Array<{ key: string; value: string }>, + variables?: Array<{ key: string; value: string }> ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipeline`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipeline` ); const body: any = { ref }; @@ -3461,11 +3388,11 @@ async function createPipeline( */ async function retryPipeline( projectId: string, - pipelineId: number | string, + pipelineId: number | string ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${pipelineId}/retry`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${pipelineId}/retry` ); const response = await fetch(url.toString(), { @@ -3487,11 +3414,11 @@ async function retryPipeline( */ async function cancelPipeline( projectId: string, - pipelineId: number | string, + pipelineId: number | string ): Promise { projectId = decodeURIComponent(projectId); // Decode project ID const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${pipelineId}/cancel`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/pipelines/${pipelineId}/cancel` ); const response = await fetch(url.toString(), { @@ -3510,16 +3437,13 @@ async function cancelPipeline( * @param {GetRepositoryTreeOptions} options - Options for the tree * @returns {Promise} */ -async function getRepositoryTree( - options: GetRepositoryTreeOptions, -): Promise { +async function getRepositoryTree(options: GetRepositoryTreeOptions): Promise { options.project_id = decodeURIComponent(options.project_id); // Decode project_id within options const queryParams = new URLSearchParams(); if (options.path) queryParams.append("path", options.path); if (options.ref) queryParams.append("ref", options.ref); if (options.recursive) queryParams.append("recursive", "true"); - if (options.per_page) - queryParams.append("per_page", options.per_page.toString()); + if (options.per_page) queryParams.append("per_page", options.per_page.toString()); if (options.page_token) queryParams.append("page_token", options.page_token); if (options.pagination) queryParams.append("pagination", options.pagination); @@ -3533,11 +3457,11 @@ async function getRepositoryTree( } const response = await fetch( `${GITLAB_API_URL}/projects/${encodeURIComponent( - getEffectiveProjectId(options.project_id), + getEffectiveProjectId(options.project_id) )}/repository/tree?${queryParams.toString()}`, { headers, - }, + } ); if (response.status === 404) { @@ -3560,17 +3484,17 @@ async function getRepositoryTree( */ async function listProjectMilestones( projectId: string, - options: Omit, "project_id">, + options: Omit, "project_id"> ): Promise { projectId = decodeURIComponent(projectId); const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones` ); Object.entries(options).forEach(([key, value]) => { if (value !== undefined) { if (key === "iids" && Array.isArray(value) && value.length > 0) { - value.forEach((iid) => { + value.forEach(iid => { url.searchParams.append("iids[]", iid.toString()); }); } else if (value !== undefined) { @@ -3595,11 +3519,11 @@ async function listProjectMilestones( */ async function getProjectMilestone( projectId: string, - milestoneId: number | string, + milestoneId: number | string ): Promise { projectId = decodeURIComponent(projectId); const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${milestoneId}`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${milestoneId}` ); const response = await fetch(url.toString(), { @@ -3618,11 +3542,11 @@ async function getProjectMilestone( */ async function createProjectMilestone( projectId: string, - options: Omit, "project_id">, + options: Omit, "project_id"> ): Promise { projectId = decodeURIComponent(projectId); const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones` ); const response = await fetch(url.toString(), { @@ -3645,14 +3569,11 @@ async function createProjectMilestone( async function editProjectMilestone( projectId: string, milestoneId: number | string, - options: Omit< - z.infer, - "project_id" | "milestone_id" - >, + options: Omit, "project_id" | "milestone_id"> ): Promise { projectId = decodeURIComponent(projectId); const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${milestoneId}`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${milestoneId}` ); const response = await fetch(url.toString(), { @@ -3673,11 +3594,11 @@ async function editProjectMilestone( */ async function deleteProjectMilestone( projectId: string, - milestoneId: number | string, + milestoneId: number | string ): Promise { projectId = decodeURIComponent(projectId); const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${milestoneId}`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${milestoneId}` ); const response = await fetch(url.toString(), { @@ -3695,11 +3616,11 @@ async function deleteProjectMilestone( */ async function getMilestoneIssues( projectId: string, - milestoneId: number | string, + milestoneId: number | string ): Promise { projectId = decodeURIComponent(projectId); const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${milestoneId}/issues`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${milestoneId}/issues` ); const response = await fetch(url.toString(), { @@ -3718,13 +3639,13 @@ async function getMilestoneIssues( */ async function getMilestoneMergeRequests( projectId: string, - milestoneId: number | string, + milestoneId: number | string ): Promise { projectId = decodeURIComponent(projectId); const url = new URL( `${GITLAB_API_URL}/projects/${encodeURIComponent( - getEffectiveProjectId(projectId), - )}/milestones/${milestoneId}/merge_requests`, + getEffectiveProjectId(projectId) + )}/milestones/${milestoneId}/merge_requests` ); const response = await fetch(url.toString(), { @@ -3743,11 +3664,11 @@ async function getMilestoneMergeRequests( */ async function promoteProjectMilestone( projectId: string, - milestoneId: number | string, + milestoneId: number | string ): Promise { projectId = decodeURIComponent(projectId); const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${milestoneId}/promote`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/milestones/${milestoneId}/promote` ); const response = await fetch(url.toString(), { @@ -3767,13 +3688,13 @@ async function promoteProjectMilestone( */ async function getMilestoneBurndownEvents( projectId: string, - milestoneId: number | string, + milestoneId: number | string ): Promise { projectId = decodeURIComponent(projectId); const url = new URL( `${GITLAB_API_URL}/projects/${encodeURIComponent( - getEffectiveProjectId(projectId), - )}/milestones/${milestoneId}/burndown_events`, + getEffectiveProjectId(projectId) + )}/milestones/${milestoneId}/burndown_events` ); const response = await fetch(url.toString(), { @@ -3806,7 +3727,7 @@ async function getUser(username: string): Promise { // GitLab returns an array of users that match the username if (Array.isArray(users) && users.length > 0) { // Find exact match for username (case-sensitive) - const exactMatch = users.find((user) => user.username === username); + const exactMatch = users.find(user => user.username === username); if (exactMatch) { return GitLabUserSchema.parse(exactMatch); } @@ -3853,11 +3774,11 @@ async function getUsers(usernames: string[]): Promise { */ async function listCommits( projectId: string, - options: Omit = {}, + options: Omit = {} ): Promise { projectId = decodeURIComponent(projectId); const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/commits`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/commits` ); // Add query parameters @@ -3867,16 +3788,13 @@ async function listCommits( if (options.path) url.searchParams.append("path", options.path); if (options.author) url.searchParams.append("author", options.author); if (options.all) url.searchParams.append("all", options.all.toString()); - if (options.with_stats) - url.searchParams.append("with_stats", options.with_stats.toString()); + if (options.with_stats) url.searchParams.append("with_stats", options.with_stats.toString()); if (options.first_parent) url.searchParams.append("first_parent", options.first_parent.toString()); if (options.order) url.searchParams.append("order", options.order); - if (options.trailers) - url.searchParams.append("trailers", options.trailers.toString()); + if (options.trailers) url.searchParams.append("trailers", options.trailers.toString()); if (options.page) url.searchParams.append("page", options.page.toString()); - if (options.per_page) - url.searchParams.append("per_page", options.per_page.toString()); + if (options.per_page) url.searchParams.append("per_page", options.per_page.toString()); const response = await fetch(url.toString(), { ...DEFAULT_FETCH_CONFIG, @@ -3897,14 +3815,10 @@ async function listCommits( * @param {boolean} [stats] - Include commit stats * @returns {Promise} The commit details */ -async function getCommit( - projectId: string, - sha: string, - stats?: boolean, -): Promise { +async function getCommit(projectId: string, sha: string, stats?: boolean): Promise { projectId = decodeURIComponent(projectId); const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/commits/${encodeURIComponent(sha)}`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/commits/${encodeURIComponent(sha)}` ); if (stats) { @@ -3929,13 +3843,10 @@ async function getCommit( * @param {string} sha - The commit hash or name of a repository branch or tag * @returns {Promise} The commit diffs */ -async function getCommitDiff( - projectId: string, - sha: string, -): Promise { +async function getCommitDiff(projectId: string, sha: string): Promise { projectId = decodeURIComponent(projectId); const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/commits/${encodeURIComponent(sha)}/diff`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/commits/${encodeURIComponent(sha)}/diff` ); const response = await fetch(url.toString(), { @@ -4041,34 +3952,23 @@ async function listProjectMembers( */ async function listGroupIterations( groupId: string, - options: Omit, "group_id"> = {}, + options: Omit, "group_id"> = {} ): Promise { groupId = decodeURIComponent(groupId); - const url = new URL( - `${GITLAB_API_URL}/groups/${encodeURIComponent(groupId)}/iterations`, - ); + const url = new URL(`${GITLAB_API_URL}/groups/${encodeURIComponent(groupId)}/iterations`); // クエリパラメータの追加 if (options.state) url.searchParams.append("state", options.state); if (options.search) url.searchParams.append("search", options.search); if (options.in) url.searchParams.append("in", options.in.join(",")); if (options.include_ancestors !== undefined) - url.searchParams.append( - "include_ancestors", - options.include_ancestors.toString(), - ); + url.searchParams.append("include_ancestors", options.include_ancestors.toString()); if (options.include_descendants !== undefined) - url.searchParams.append( - "include_descendants", - options.include_descendants.toString(), - ); - if (options.updated_before) - url.searchParams.append("updated_before", options.updated_before); - if (options.updated_after) - url.searchParams.append("updated_after", options.updated_after); + url.searchParams.append("include_descendants", options.include_descendants.toString()); + if (options.updated_before) url.searchParams.append("updated_before", options.updated_before); + if (options.updated_after) url.searchParams.append("updated_after", options.updated_after); if (options.page) url.searchParams.append("page", options.page.toString()); - if (options.per_page) - url.searchParams.append("per_page", options.per_page.toString()); + if (options.per_page) url.searchParams.append("per_page", options.per_page.toString()); const response = await fetch(url.toString(), DEFAULT_FETCH_CONFIG); @@ -4087,10 +3987,7 @@ async function listGroupIterations( * @param {string} filePath - Path to the local file to upload * @returns {Promise} The upload response */ -async function markdownUpload( - projectId: string, - filePath: string, -): Promise { +async function markdownUpload(projectId: string, filePath: string): Promise { projectId = decodeURIComponent(projectId); const effectiveProjectId = getEffectiveProjectId(projectId); @@ -4112,7 +4009,7 @@ async function markdownUpload( }); const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/uploads`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(effectiveProjectId)}/uploads` ); const response = await fetch(url.toString(), { @@ -4136,29 +4033,23 @@ async function markdownUpload( server.setRequestHandler(ListToolsRequestSchema, async () => { // Apply read-only filter first const tools0 = GITLAB_READ_ONLY_MODE - ? allTools.filter((tool) => readOnlyTools.includes(tool.name)) + ? allTools.filter(tool => readOnlyTools.includes(tool.name)) : allTools; // Toggle wiki tools by USE_GITLAB_WIKI flag const tools1 = USE_GITLAB_WIKI ? tools0 - : tools0.filter((tool) => !wikiToolNames.includes(tool.name)); + : tools0.filter(tool => !wikiToolNames.includes(tool.name)); // Toggle milestone tools by USE_MILESTONE flag const tools2 = USE_MILESTONE ? tools1 - : tools1.filter((tool) => !milestoneToolNames.includes(tool.name)); + : tools1.filter(tool => !milestoneToolNames.includes(tool.name)); // Toggle pipeline tools by USE_PIPELINE flag - let tools = USE_PIPELINE - ? tools2 - : tools2.filter((tool) => !pipelineToolNames.includes(tool.name)); + let tools = USE_PIPELINE ? tools2 : tools2.filter(tool => !pipelineToolNames.includes(tool.name)); // <<< START: Gemini 호환성을 위해 $schema 제거 >>> - tools = tools.map((tool) => { + tools = tools.map(tool => { // inputSchema가 존재하고 객체인지 확인 - if ( - tool.inputSchema && - typeof tool.inputSchema === "object" && - tool.inputSchema !== null - ) { + if (tool.inputSchema && typeof tool.inputSchema === "object" && tool.inputSchema !== null) { // $schema 키가 존재하면 삭제 if ("$schema" in tool.inputSchema) { // 불변성을 위해 새로운 객체 생성 (선택적이지만 권장) @@ -4177,7 +4068,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { }; }); -server.setRequestHandler(CallToolRequestSchema, async (request) => { +server.setRequestHandler(CallToolRequestSchema, async request => { try { if (!request.params.arguments) { throw new Error("Arguments are required"); @@ -4191,20 +4082,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { switch (request.params.name) { case "fork_repository": { if (GITLAB_PROJECT_ID) { - throw new Error( - "Direct project ID is set. So fork_repository is not allowed", - ); + throw new Error("Direct project ID is set. So fork_repository is not allowed"); } const forkArgs = ForkRepositorySchema.parse(request.params.arguments); try { - const forkedProject = await forkProject( - forkArgs.project_id, - forkArgs.namespace, - ); + const forkedProject = await forkProject(forkArgs.project_id, forkArgs.namespace); return { - content: [ - { type: "text", text: JSON.stringify(forkedProject, null, 2) }, - ], + content: [{ type: "text", text: JSON.stringify(forkedProject, null, 2) }], }; } catch (forkError) { logger.error("Error forking repository:", forkError); @@ -4242,28 +4126,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { case "get_branch_diffs": { const args = GetBranchDiffsSchema.parse(request.params.arguments); - const diffResp = await getBranchDiffs( - args.project_id, - args.from, - args.to, - args.straight, - ); + const diffResp = await getBranchDiffs(args.project_id, args.from, args.to, args.straight); if (args.excluded_file_patterns?.length) { - const regexPatterns = args.excluded_file_patterns.map( - (pattern) => new RegExp(pattern), - ); + const regexPatterns = args.excluded_file_patterns.map(pattern => new RegExp(pattern)); // Helper function to check if a path matches any regex pattern const matchesAnyPattern = (path: string): boolean => { if (!path) return false; - return regexPatterns.some((regex) => regex.test(path)); + return regexPatterns.some(regex => regex.test(path)); }; // Filter out files that match any of the regex patterns on new files - diffResp.diffs = diffResp.diffs.filter( - (diff) => !matchesAnyPattern(diff.new_path), - ); + diffResp.diffs = diffResp.diffs.filter(diff => !matchesAnyPattern(diff.new_path)); } return { content: [{ type: "text", text: JSON.stringify(diffResp, null, 2) }], @@ -4272,11 +4147,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { case "search_repositories": { const args = SearchRepositoriesSchema.parse(request.params.arguments); - const results = await searchProjects( - args.search, - args.page, - args.per_page, - ); + const results = await searchProjects(args.search, args.page, args.per_page); return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }], }; @@ -4284,26 +4155,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { case "create_repository": { if (GITLAB_PROJECT_ID) { - throw new Error( - "Direct project ID is set. So fork_repository is not allowed", - ); + throw new Error("Direct project ID is set. So fork_repository is not allowed"); } const args = CreateRepositorySchema.parse(request.params.arguments); const repository = await createRepository(args); return { - content: [ - { type: "text", text: JSON.stringify(repository, null, 2) }, - ], + content: [{ type: "text", text: JSON.stringify(repository, null, 2) }], }; } case "get_file_contents": { const args = GetFileContentsSchema.parse(request.params.arguments); - const contents = await getFileContents( - args.project_id, - args.file_path, - args.ref, - ); + const contents = await getFileContents(args.project_id, args.file_path, args.ref); return { content: [{ type: "text", text: JSON.stringify(contents, null, 2) }], }; @@ -4319,7 +4182,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { args.branch, args.previous_path, args.last_commit_id, - args.commit_id, + args.commit_id ); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], @@ -4332,7 +4195,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { args.project_id, args.commit_message, args.branch, - args.files.map((f) => ({ path: f.file_path, content: f.content })), + args.files.map(f => ({ path: f.file_path, content: f.content })) ); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], @@ -4353,23 +4216,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { const { project_id, ...options } = args; const mergeRequest = await createMergeRequest(project_id, options); return { - content: [ - { type: "text", text: JSON.stringify(mergeRequest, null, 2) }, - ], + content: [{ type: "text", text: JSON.stringify(mergeRequest, null, 2) }], }; } case "update_merge_request_note": { - const args = UpdateMergeRequestNoteSchema.parse( - request.params.arguments, - ); + const args = UpdateMergeRequestNoteSchema.parse(request.params.arguments); const note = await updateMergeRequestNote( args.project_id, args.merge_request_iid, args.discussion_id, args.note_id, args.body, // Now optional - args.resolved, // Now one of body or resolved must be provided, not both + args.resolved // Now one of body or resolved must be provided, not both ); return { content: [{ type: "text", text: JSON.stringify(note, null, 2) }], @@ -4377,15 +4236,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } case "create_merge_request_note": { - const args = CreateMergeRequestNoteSchema.parse( - request.params.arguments, - ); + const args = CreateMergeRequestNoteSchema.parse(request.params.arguments); const note = await createMergeRequestNote( args.project_id, args.merge_request_iid, args.discussion_id, args.body, - args.created_at, + args.created_at ); return { content: [{ type: "text", text: JSON.stringify(note, null, 2) }], @@ -4399,7 +4256,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { args.issue_iid, args.discussion_id, args.note_id, - args.body, + args.body ); return { content: [{ type: "text", text: JSON.stringify(note, null, 2) }], @@ -4413,7 +4270,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { args.issue_iid, args.discussion_id, args.body, - args.created_at, + args.created_at ); return { content: [{ type: "text", text: JSON.stringify(note, null, 2) }], @@ -4425,12 +4282,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { const mergeRequest = await getMergeRequest( args.project_id, args.merge_request_iid, - args.source_branch, + args.source_branch ); return { - content: [ - { type: "text", text: JSON.stringify(mergeRequest, null, 2) }, - ], + content: [{ type: "text", text: JSON.stringify(mergeRequest, null, 2) }], }; } @@ -4440,7 +4295,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { args.project_id, args.merge_request_iid, args.source_branch, - args.view, + args.view ); return { content: [{ type: "text", text: JSON.stringify(diffs, null, 2) }], @@ -4448,16 +4303,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } case "list_merge_request_diffs": { - const args = ListMergeRequestDiffsSchema.parse( - request.params.arguments, - ); + const args = ListMergeRequestDiffsSchema.parse(request.params.arguments); const changes = await listMergeRequestDiffs( args.project_id, args.merge_request_iid, args.source_branch, args.page, args.per_page, - args.unidiff, + args.unidiff ); return { content: [{ type: "text", text: JSON.stringify(changes, null, 2) }], @@ -4466,35 +4319,37 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { case "update_merge_request": { const args = UpdateMergeRequestSchema.parse(request.params.arguments); - const { project_id, merge_request_iid, source_branch, ...options } = - args; + const { project_id, merge_request_iid, source_branch, ...options } = args; const mergeRequest = await updateMergeRequest( project_id, options, merge_request_iid, - source_branch, + source_branch ); return { - content: [ - { type: "text", text: JSON.stringify(mergeRequest, null, 2) }, - ], + content: [{ type: "text", text: JSON.stringify(mergeRequest, null, 2) }], + }; + } + + case "merge_merge_request": { + const args = MergeMergeRequestSchema.parse(request.params.arguments); + const { project_id, merge_request_iid, ...options } = args; + const mergeRequest = await mergeMergeRequest(project_id, options, merge_request_iid); + return { + content: [{ type: "text", text: JSON.stringify(mergeRequest, null, 2) }], }; } case "mr_discussions": { - const args = ListMergeRequestDiscussionsSchema.parse( - request.params.arguments, - ); + const args = ListMergeRequestDiscussionsSchema.parse(request.params.arguments); const { project_id, merge_request_iid, ...options } = args; const discussions = await listMergeRequestDiscussions( project_id, merge_request_iid, - options, + options ); return { - content: [ - { type: "text", text: JSON.stringify(discussions, null, 2) }, - ], + content: [{ type: "text", text: JSON.stringify(discussions, null, 2) }], }; } @@ -4524,16 +4379,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { const namespaces = z.array(GitLabNamespaceSchema).parse(data); return { - content: [ - { type: "text", text: JSON.stringify(namespaces, null, 2) }, - ], + content: [{ type: "text", text: JSON.stringify(namespaces, null, 2) }], }; } case "get_namespace": { const args = GetNamespaceSchema.parse(request.params.arguments); const url = new URL( - `${GITLAB_API_URL}/namespaces/${encodeURIComponent(args.namespace_id)}`, + `${GITLAB_API_URL}/namespaces/${encodeURIComponent(args.namespace_id)}` ); const response = await fetch(url.toString(), { @@ -4551,9 +4404,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { case "verify_namespace": { const args = VerifyNamespaceSchema.parse(request.params.arguments); - const url = new URL( - `${GITLAB_API_URL}/namespaces/${encodeURIComponent(args.path)}/exists`, - ); + const url = new URL(`${GITLAB_API_URL}/namespaces/${encodeURIComponent(args.path)}/exists`); const response = await fetch(url.toString(), { ...DEFAULT_FETCH_CONFIG, @@ -4564,16 +4415,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { const namespaceExists = GitLabNamespaceExistsResponseSchema.parse(data); return { - content: [ - { type: "text", text: JSON.stringify(namespaceExists, null, 2) }, - ], + content: [{ type: "text", text: JSON.stringify(namespaceExists, null, 2) }], }; } case "get_project": { const args = GetProjectSchema.parse(request.params.arguments); const url = new URL( - `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(args.project_id))}`, + `${GITLAB_API_URL}/projects/${encodeURIComponent(getEffectiveProjectId(args.project_id))}` ); const response = await fetch(url.toString(), { @@ -4620,12 +4469,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { const args = CreateNoteSchema.parse(request.params.arguments); const { project_id, noteable_type, noteable_iid, body } = args; - const note = await createNote( - project_id, - noteable_type, - noteable_iid, - body, - ); + const note = await createNote(project_id, noteable_type, noteable_iid, body); return { content: [{ type: "text", text: JSON.stringify(note, null, 2) }], }; @@ -4692,18 +4536,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } case "create_merge_request_thread": { - const args = CreateMergeRequestThreadSchema.parse( - request.params.arguments, - ); - const { project_id, merge_request_iid, body, position, created_at } = - args; + const args = CreateMergeRequestThreadSchema.parse(request.params.arguments); + const { project_id, merge_request_iid, body, position, created_at } = args; const thread = await createMergeRequestThread( project_id, merge_request_iid, body, position, - created_at, + created_at ); return { content: [{ type: "text", text: JSON.stringify(thread, null, 2) }], @@ -4754,7 +4595,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { text: JSON.stringify( { status: "success", message: "Issue deleted successfully" }, null, - 2, + 2 ), }, ], @@ -4773,25 +4614,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { const args = ListIssueDiscussionsSchema.parse(request.params.arguments); const { project_id, issue_iid, ...options } = args; - const discussions = await listIssueDiscussions( - project_id, - issue_iid, - options, - ); + const discussions = await listIssueDiscussions(project_id, issue_iid, options); return { - content: [ - { type: "text", text: JSON.stringify(discussions, null, 2) }, - ], + content: [{ type: "text", text: JSON.stringify(discussions, null, 2) }], }; } case "get_issue_link": { const args = GetIssueLinkSchema.parse(request.params.arguments); - const link = await getIssueLink( - args.project_id, - args.issue_iid, - args.issue_link_id, - ); + const link = await getIssueLink(args.project_id, args.issue_iid, args.issue_link_id); return { content: [{ type: "text", text: JSON.stringify(link, null, 2) }], }; @@ -4804,7 +4635,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { args.issue_iid, args.target_project_id, args.target_issue_iid, - args.link_type, + args.link_type ); return { content: [{ type: "text", text: JSON.stringify(link, null, 2) }], @@ -4813,11 +4644,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { case "delete_issue_link": { const args = DeleteIssueLinkSchema.parse(request.params.arguments); - await deleteIssueLink( - args.project_id, - args.issue_iid, - args.issue_link_id, - ); + await deleteIssueLink(args.project_id, args.issue_iid, args.issue_link_id); return { content: [ { @@ -4828,7 +4655,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { message: "Issue link deleted successfully", }, null, - 2, + 2 ), }, ], @@ -4845,11 +4672,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { case "get_label": { const args = GetLabelSchema.parse(request.params.arguments); - const label = await getLabel( - args.project_id, - args.label_id, - args.include_ancestor_groups, - ); + const label = await getLabel(args.project_id, args.label_id, args.include_ancestor_groups); return { content: [{ type: "text", text: JSON.stringify(label, null, 2) }], }; @@ -4882,7 +4705,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { text: JSON.stringify( { status: "success", message: "Label deleted successfully" }, null, - 2, + 2 ), }, ], @@ -4898,8 +4721,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } case "list_wiki_pages": { - const { project_id, page, per_page, with_content } = - ListWikiPagesSchema.parse(request.params.arguments); + const { project_id, page, per_page, with_content } = ListWikiPagesSchema.parse( + request.params.arguments + ); const wikiPages = await listWikiPages(project_id, { page, per_page, @@ -4911,9 +4735,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } case "get_wiki_page": { - const { project_id, slug } = GetWikiPageSchema.parse( - request.params.arguments, - ); + const { project_id, slug } = GetWikiPageSchema.parse(request.params.arguments); const wikiPage = await getWikiPage(project_id, slug); return { content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }], @@ -4921,38 +4743,27 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } case "create_wiki_page": { - const { project_id, title, content, format } = - CreateWikiPageSchema.parse(request.params.arguments); - const wikiPage = await createWikiPage( - project_id, - title, - content, - format, + const { project_id, title, content, format } = CreateWikiPageSchema.parse( + request.params.arguments ); + const wikiPage = await createWikiPage(project_id, title, content, format); return { content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }], }; } case "update_wiki_page": { - const { project_id, slug, title, content, format } = - UpdateWikiPageSchema.parse(request.params.arguments); - const wikiPage = await updateWikiPage( - project_id, - slug, - title, - content, - format, + const { project_id, slug, title, content, format } = UpdateWikiPageSchema.parse( + request.params.arguments ); + const wikiPage = await updateWikiPage(project_id, slug, title, content, format); return { content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }], }; } case "delete_wiki_page": { - const { project_id, slug } = DeleteWikiPageSchema.parse( - request.params.arguments, - ); + const { project_id, slug } = DeleteWikiPageSchema.parse(request.params.arguments); await deleteWikiPage(project_id, slug); return { content: [ @@ -4964,7 +4775,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { message: "Wiki page deleted successfully", }, null, - 2, + 2 ), }, ], @@ -4989,9 +4800,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } case "get_pipeline": { - const { project_id, pipeline_id } = GetPipelineSchema.parse( - request.params.arguments, - ); + const { project_id, pipeline_id } = GetPipelineSchema.parse(request.params.arguments); const pipeline = await getPipeline(project_id, pipeline_id); return { content: [ @@ -5004,8 +4813,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } case "list_pipeline_jobs": { - const { project_id, pipeline_id, ...options } = - ListPipelineJobsSchema.parse(request.params.arguments); + const { project_id, pipeline_id, ...options } = ListPipelineJobsSchema.parse( + request.params.arguments + ); const jobs = await listPipelineJobs(project_id, pipeline_id, options); return { content: [ @@ -5018,13 +4828,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } case "list_pipeline_trigger_jobs": { - const { project_id, pipeline_id, ...options } = - ListPipelineTriggerJobsSchema.parse(request.params.arguments); - const triggerJobs = await listPipelineTriggerJobs( - project_id, - pipeline_id, - options, + const { project_id, pipeline_id, ...options } = ListPipelineTriggerJobsSchema.parse( + request.params.arguments ); + const triggerJobs = await listPipelineTriggerJobs(project_id, pipeline_id, options); return { content: [ { @@ -5036,9 +4843,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } case "get_pipeline_job": { - const { project_id, job_id } = GetPipelineJobOutputSchema.parse( - request.params.arguments, - ); + const { project_id, job_id } = GetPipelineJobOutputSchema.parse(request.params.arguments); const jobDetails = await getPipelineJob(project_id, job_id); return { content: [ @@ -5051,14 +4856,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } case "get_pipeline_job_output": { - const { project_id, job_id, limit, offset } = - GetPipelineJobOutputSchema.parse(request.params.arguments); - const jobOutput = await getPipelineJobOutput( - project_id, - job_id, - limit, - offset, + const { project_id, job_id, limit, offset } = GetPipelineJobOutputSchema.parse( + request.params.arguments ); + const jobOutput = await getPipelineJobOutput(project_id, job_id, limit, offset); return { content: [ { @@ -5070,9 +4871,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } case "create_pipeline": { - const { project_id, ref, variables } = CreatePipelineSchema.parse( - request.params.arguments, - ); + const { project_id, ref, variables } = CreatePipelineSchema.parse(request.params.arguments); const pipeline = await createPipeline(project_id, ref, variables); return { content: [ @@ -5085,9 +4884,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } case "retry_pipeline": { - const { project_id, pipeline_id } = RetryPipelineSchema.parse( - request.params.arguments, - ); + const { project_id, pipeline_id } = RetryPipelineSchema.parse(request.params.arguments); const pipeline = await retryPipeline(project_id, pipeline_id); return { content: [ @@ -5100,9 +4897,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } case "cancel_pipeline": { - const { project_id, pipeline_id } = CancelPipelineSchema.parse( - request.params.arguments, - ); + const { project_id, pipeline_id } = CancelPipelineSchema.parse(request.params.arguments); const pipeline = await cancelPipeline(project_id, pipeline_id); return { content: [ @@ -5118,15 +4913,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { const args = ListMergeRequestsSchema.parse(request.params.arguments); const mergeRequests = await listMergeRequests(args.project_id, args); return { - content: [ - { type: "text", text: JSON.stringify(mergeRequests, null, 2) }, - ], + content: [{ type: "text", text: JSON.stringify(mergeRequests, null, 2) }], }; } case "list_milestones": { const { project_id, ...options } = ListProjectMilestonesSchema.parse( - request.params.arguments, + request.params.arguments ); const milestones = await listProjectMilestones(project_id, options); return { @@ -5141,7 +4934,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { case "get_milestone": { const { project_id, milestone_id } = GetProjectMilestoneSchema.parse( - request.params.arguments, + request.params.arguments ); const milestone = await getProjectMilestone(project_id, milestone_id); return { @@ -5156,7 +4949,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { case "create_milestone": { const { project_id, ...options } = CreateProjectMilestoneSchema.parse( - request.params.arguments, + request.params.arguments ); const milestone = await createProjectMilestone(project_id, options); return { @@ -5170,13 +4963,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } case "edit_milestone": { - const { project_id, milestone_id, ...options } = - EditProjectMilestoneSchema.parse(request.params.arguments); - const milestone = await editProjectMilestone( - project_id, - milestone_id, - options, + const { project_id, milestone_id, ...options } = EditProjectMilestoneSchema.parse( + request.params.arguments ); + const milestone = await editProjectMilestone(project_id, milestone_id, options); return { content: [ { @@ -5189,7 +4979,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { case "delete_milestone": { const { project_id, milestone_id } = DeleteProjectMilestoneSchema.parse( - request.params.arguments, + request.params.arguments ); await deleteProjectMilestone(project_id, milestone_id); return { @@ -5202,7 +4992,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { message: "Milestone deleted successfully", }, null, - 2, + 2 ), }, ], @@ -5211,7 +5001,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { case "get_milestone_issue": { const { project_id, milestone_id } = GetMilestoneIssuesSchema.parse( - request.params.arguments, + request.params.arguments ); const issues = await getMilestoneIssues(project_id, milestone_id); return { @@ -5225,12 +5015,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } case "get_milestone_merge_requests": { - const { project_id, milestone_id } = - GetMilestoneMergeRequestsSchema.parse(request.params.arguments); - const mergeRequests = await getMilestoneMergeRequests( - project_id, - milestone_id, + const { project_id, milestone_id } = GetMilestoneMergeRequestsSchema.parse( + request.params.arguments ); + const mergeRequests = await getMilestoneMergeRequests(project_id, milestone_id); return { content: [ { @@ -5242,12 +5030,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } case "promote_milestone": { - const { project_id, milestone_id } = - PromoteProjectMilestoneSchema.parse(request.params.arguments); - const milestone = await promoteProjectMilestone( - project_id, - milestone_id, + const { project_id, milestone_id } = PromoteProjectMilestoneSchema.parse( + request.params.arguments ); + const milestone = await promoteProjectMilestone(project_id, milestone_id); return { content: [ { @@ -5259,12 +5045,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } case "get_milestone_burndown_events": { - const { project_id, milestone_id } = - GetMilestoneBurndownEventsSchema.parse(request.params.arguments); - const events = await getMilestoneBurndownEvents( - project_id, - milestone_id, + const { project_id, milestone_id } = GetMilestoneBurndownEventsSchema.parse( + request.params.arguments ); + const events = await getMilestoneBurndownEvents(project_id, milestone_id); return { content: [ { @@ -5303,9 +5087,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { const args = ListGroupIterationsSchema.parse(request.params.arguments); const iterations = await listGroupIterations(args.group_id, args); return { - content: [ - { type: "text", text: JSON.stringify(iterations, null, 2) }, - ], + content: [{ type: "text", text: JSON.stringify(iterations, null, 2) }], }; } @@ -5325,8 +5107,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { if (error instanceof z.ZodError) { throw new Error( `Invalid arguments: ${error.errors - .map((e) => `${e.path.join(".")}: ${e.message}`) - .join(", ")}`, + .map(e => `${e.path.join(".")}: ${e.message}`) + .join(", ")}` ); } throw error; @@ -5408,9 +5190,7 @@ async function startSSEServer(): Promise { logger.info(`GitLab MCP Server running with SSE transport`); const colorGreen = "\x1b[32m"; const colorReset = "\x1b[0m"; - logger.info( - `${colorGreen}Endpoint: http://${HOST}:${PORT}/sse${colorReset}`, - ); + logger.info(`${colorGreen}Endpoint: http://${HOST}:${PORT}/sse${colorReset}`); }); } @@ -5451,9 +5231,7 @@ async function startStreamableHTTPServer(): Promise { transport.onclose = () => { const sid = transport.sessionId; if (sid && streamableTransports[sid]) { - logger.warn( - `Streamable HTTP transport closed for session ${sid}, cleaning up`, - ); + logger.warn(`Streamable HTTP transport closed for session ${sid}, cleaning up`); delete streamableTransports[sid]; } }; @@ -5484,9 +5262,7 @@ async function startStreamableHTTPServer(): Promise { // Start server app.listen(Number(PORT), HOST, () => { logger.info(`GitLab MCP Server running with Streamable HTTP transport`); - logger.info( - `${colorGreen}Endpoint: http://${HOST}:${PORT}/mcp${colorReset}`, - ); + logger.info(`${colorGreen}Endpoint: http://${HOST}:${PORT}/mcp${colorReset}`); }); } @@ -5494,9 +5270,7 @@ async function startStreamableHTTPServer(): Promise { * Initialize server with specific transport mode * Handle transport-specific initialization logic */ -async function initializeServerByTransportMode( - mode: TransportMode, -): Promise { +async function initializeServerByTransportMode(mode: TransportMode): Promise { logger.info("Initializing server with transport mode:", mode); switch (mode) { case TransportMode.STDIO: @@ -5536,7 +5310,7 @@ async function runServer() { } // 下記の2行を追記 -runServer().catch((error) => { +runServer().catch(error => { logger.error("Fatal error in main():", error); process.exit(1); }); diff --git a/schemas.ts b/schemas.ts index bd77820..9c26b36 100644 --- a/schemas.ts +++ b/schemas.ts @@ -1090,6 +1090,16 @@ export const UpdateMergeRequestSchema = GetMergeRequestSchema.extend({ draft: flexibleBoolean.optional().describe("Work in progress merge request"), }); +export const MergeMergeRequestSchema = ProjectParamsSchema.extend({ + merge_request_iid: z.coerce.string().optional().describe("The IID of a merge request"), + auto_merge: flexibleBoolean.optional().default(false).describe("If true, the merge request merges when the pipeline succeeds."), + merge_commit_message: z.string().optional().describe("Custom merge commit message"), + merge_when_pipeline_succeeds: flexibleBoolean.optional().default(false).describe("If true, the merge request merges when the pipeline succeeds.in GitLab 17.11. Use"), + should_remove_source_branch: flexibleBoolean.optional().default(false).describe("Remove source branch after merge"), + squash_commit_message: z.string().optional().describe("Custom squash commit message"), + squash: flexibleBoolean.optional().default(false).describe("Squash commits into a single commit when merging"), +}); + export const GetMergeRequestDiffsSchema = GetMergeRequestSchema.extend({ view: z.enum(["inline", "parallel"]).optional().describe("Diff view type"), });