Compare commits

...

11 Commits

Author SHA1 Message Date
08ab1357a0 feat: Decode project_id for GitLab API calls
- Decode project_id using decodeURIComponent() in relevant helper functions.
- This resolves API call issues related to project ID encoding differences between models.
- Updated CHANGELOG for 1.0.36 and added thanks to Aubermean.
2025-05-13 02:20:59 +09:00
651072dfd7 [main] chore: update version to 1.0.35 🚀
📝 Details:
- Incremented package version from 1.0.34 to 1.0.35
2025-05-13 01:53:10 +09:00
bf250b0d88 [main] refactor: update label_id schema to use string type
🚀 Breaking Changes:
- label_id now requires a string type instead of a union of number and string.

📝 Details:
- Improved consistency in schema definitions for label handling.
2025-05-13 01:53:00 +09:00
3a25e7c5e8 [main] docs: update README with detailed descriptions for merge request functions
📝 Details:
- Added clarifications for `get_merge_request`, `get_merge_request_diffs`, and `update_merge_request` functions.
- Introduced `get_repository_tree` function to the documentation.
2025-05-07 21:46:35 +09:00
23a9bbc728 [main] chore: update version to 1.0.34
🚀 Breaking Changes:
- Updated package version from 1.0.33 to 1.0.34
2025-05-07 21:45:59 +09:00
5398526f94 Merge pull request #35 from BobMerkus/feat/list-repository
feat: Gitlab list repository tree tool
2025-05-07 21:45:35 +09:00
Bob
bccd5f29c3 feat: Gitlab list repository tree tool 2025-05-07 14:11:37 +02:00
8071fef6c4 [main] chore: package.json 버전 1.0.33으로 업데이트
🚀 Breaking Changes:
- 없음
2025-05-01 08:33:56 +09:00
f0eac83788 Merge pull request #34 from nkaewam/main
feat: support search by branch for get_merge_request
2025-05-01 08:33:29 +09:00
7b8cbc0806 fix: rename to source branch 2025-04-30 15:44:21 +07:00
20f62756c1 feat: support search by branch for get_merge_request 2025-04-30 15:41:51 +07:00
6 changed files with 668 additions and 219 deletions

5
CHANGELOG.md Normal file
View File

@ -0,0 +1,5 @@
## [Released] - 2025-05-13
### Fixed
- **GitLab MCP Server:** Modified GitLab API helper functions to decode the `project_id` using `decodeURIComponent()` before processing. This resolves API call failures caused by differences in project ID encoding between Gemini and other AI models. API requests are now handled consistently regardless of the model.

View File

@ -51,9 +51,9 @@ When using with the Claude App, you need to set up your API key and URLs directl
7. `create_merge_request` - Create a new merge request in a GitLab project 7. `create_merge_request` - Create a new merge request in a GitLab project
8. `fork_repository` - Fork a GitLab project to your account or specified namespace 8. `fork_repository` - Fork a GitLab project to your account or specified namespace
9. `create_branch` - Create a new branch in a GitLab project 9. `create_branch` - Create a new branch in a GitLab project
10. `get_merge_request` - Get details of a merge request 10. `get_merge_request` - Get details of a merge request (Either mergeRequestIid or branchName must be provided)
11. `get_merge_request_diffs` - Get the changes/diffs of a merge request 11. `get_merge_request_diffs` - Get the changes/diffs of a merge request (Either mergeRequestIid or branchName must be provided)
12. `update_merge_request` - Update a merge request 12. `update_merge_request` - Update a merge request (Either mergeRequestIid or branchName must be provided)
13. `create_note` - Create a new note (comment) to an issue or merge request 13. `create_note` - Create a new note (comment) to an issue or merge request
14. `mr_discussions` - List discussion items for a merge request 14. `mr_discussions` - List discussion items for a merge request
15. `update_merge_request_note` - Modify an existing merge request thread note 15. `update_merge_request_note` - Modify an existing merge request thread note
@ -81,4 +81,5 @@ When using with the Claude App, you need to set up your API key and URLs directl
37. `create_wiki_page` - Create a new wiki page in a GitLab project 37. `create_wiki_page` - Create a new wiki page in a GitLab project
38. `update_wiki_page` - Update an existing wiki page in a GitLab project 38. `update_wiki_page` - Update an existing wiki page in a GitLab project
39. `delete_wiki_page` - Delete a wiki page from a GitLab project 39. `delete_wiki_page` - Delete a wiki page from a GitLab project
40. `get_repository_tree` - Get the repository tree for a GitLab project (list files and directories)
<!-- TOOLS-END --> <!-- TOOLS-END -->

342
index.ts
View File

@ -8,9 +8,9 @@ import {
} from "@modelcontextprotocol/sdk/types.js"; } from "@modelcontextprotocol/sdk/types.js";
import FormData from "form-data"; import FormData from "form-data";
import fetch from "node-fetch"; import fetch from "node-fetch";
import { SocksProxyAgent } from 'socks-proxy-agent'; import { SocksProxyAgent } from "socks-proxy-agent";
import { HttpsProxyAgent } from 'https-proxy-agent'; import { HttpsProxyAgent } from "https-proxy-agent";
import { HttpProxyAgent } from 'http-proxy-agent'; import { HttpProxyAgent } from "http-proxy-agent";
import { z } from "zod"; import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema"; import { zodToJsonSchema } from "zod-to-json-schema";
import { fileURLToPath } from "url"; import { fileURLToPath } from "url";
@ -19,8 +19,8 @@ import fs from "fs";
import path from "path"; import path from "path";
// Add type imports for proxy agents // Add type imports for proxy agents
import { Agent } from 'http'; import { Agent } from "http";
import { URL } from 'url'; import { URL } from "url";
import { import {
GitLabForkSchema, GitLabForkSchema,
@ -113,6 +113,10 @@ import {
type UpdateWikiPageOptions, type UpdateWikiPageOptions,
type DeleteWikiPageOptions, type DeleteWikiPageOptions,
type GitLabWikiPage, type GitLabWikiPage,
GitLabTreeItemSchema,
GetRepositoryTreeSchema,
type GitLabTreeItem,
type GetRepositoryTreeOptions,
} from "./schemas.js"; } from "./schemas.js";
/** /**
@ -156,14 +160,14 @@ let httpAgent: Agent | undefined = undefined;
let httpsAgent: Agent | undefined = undefined; let httpsAgent: Agent | undefined = undefined;
if (HTTP_PROXY) { if (HTTP_PROXY) {
if (HTTP_PROXY.startsWith('socks')) { if (HTTP_PROXY.startsWith("socks")) {
httpAgent = new SocksProxyAgent(HTTP_PROXY); httpAgent = new SocksProxyAgent(HTTP_PROXY);
} else { } else {
httpAgent = new HttpProxyAgent(HTTP_PROXY); httpAgent = new HttpProxyAgent(HTTP_PROXY);
} }
} }
if (HTTPS_PROXY) { if (HTTPS_PROXY) {
if (HTTPS_PROXY.startsWith('socks')) { if (HTTPS_PROXY.startsWith("socks")) {
httpsAgent = new SocksProxyAgent(HTTPS_PROXY); httpsAgent = new SocksProxyAgent(HTTPS_PROXY);
} else { } else {
httpsAgent = new HttpsProxyAgent(HTTPS_PROXY); httpsAgent = new HttpsProxyAgent(HTTPS_PROXY);
@ -181,11 +185,11 @@ const DEFAULT_HEADERS = {
const DEFAULT_FETCH_CONFIG = { const DEFAULT_FETCH_CONFIG = {
headers: DEFAULT_HEADERS, headers: DEFAULT_HEADERS,
agent: (parsedUrl: URL) => { agent: (parsedUrl: URL) => {
if (parsedUrl.protocol === 'https:') { if (parsedUrl.protocol === "https:") {
return httpsAgent; return httpsAgent;
} }
return httpAgent; return httpAgent;
} },
}; };
// Define all available tools // Define all available tools
@ -238,17 +242,20 @@ const allTools = [
}, },
{ {
name: "get_merge_request", name: "get_merge_request",
description: "Get details of a merge request", description:
"Get details of a merge request (Either mergeRequestIid or branchName must be provided)",
inputSchema: zodToJsonSchema(GetMergeRequestSchema), inputSchema: zodToJsonSchema(GetMergeRequestSchema),
}, },
{ {
name: "get_merge_request_diffs", name: "get_merge_request_diffs",
description: "Get the changes/diffs of a merge request", description:
"Get the changes/diffs of a merge request (Either mergeRequestIid or branchName must be provided)",
inputSchema: zodToJsonSchema(GetMergeRequestDiffsSchema), inputSchema: zodToJsonSchema(GetMergeRequestDiffsSchema),
}, },
{ {
name: "update_merge_request", name: "update_merge_request",
description: "Update a merge request", description:
"Update a merge request (Either mergeRequestIid or branchName must be provided)",
inputSchema: zodToJsonSchema(UpdateMergeRequestSchema), inputSchema: zodToJsonSchema(UpdateMergeRequestSchema),
}, },
{ {
@ -385,7 +392,13 @@ const allTools = [
name: "delete_wiki_page", name: "delete_wiki_page",
description: "Delete a wiki page from a GitLab project", description: "Delete a wiki page from a GitLab project",
inputSchema: zodToJsonSchema(DeleteWikiPageSchema), inputSchema: zodToJsonSchema(DeleteWikiPageSchema),
} },
{
name: "get_repository_tree",
description:
"Get the repository tree for a GitLab project (list files and directories)",
inputSchema: zodToJsonSchema(GetRepositoryTreeSchema),
},
]; ];
// Define which tools are read-only // Define which tools are read-only
@ -466,12 +479,13 @@ async function handleGitLabError(
if (!response.ok) { if (!response.ok) {
const errorBody = await response.text(); const errorBody = await response.text();
// Check specifically for Rate Limit error // 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")
) {
console.error("GitLab API Rate Limit Exceeded:", errorBody); console.error("GitLab API Rate Limit Exceeded:", errorBody);
console.log("User API Key Rate limit exceeded. Please try again later."); console.log("User API Key Rate limit exceeded. Please try again later.");
throw new Error( throw new Error(`GitLab API Rate Limit Exceeded: ${errorBody}`);
`GitLab API Rate Limit Exceeded: ${errorBody}`
);
} else { } else {
// Handle other API errors // Handle other API errors
throw new Error( throw new Error(
@ -493,6 +507,7 @@ async function forkProject(
projectId: string, projectId: string,
namespace?: string namespace?: string
): Promise<GitLabFork> { ): Promise<GitLabFork> {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL( const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/fork` `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/fork`
); );
@ -528,6 +543,7 @@ async function createBranch(
projectId: string, projectId: string,
options: z.infer<typeof CreateBranchOptionsSchema> options: z.infer<typeof CreateBranchOptionsSchema>
): Promise<GitLabReference> { ): Promise<GitLabReference> {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL( const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent( `${GITLAB_API_URL}/projects/${encodeURIComponent(
projectId projectId
@ -555,6 +571,7 @@ async function createBranch(
* @returns {Promise<string>} The name of the default branch * @returns {Promise<string>} The name of the default branch
*/ */
async function getDefaultBranchRef(projectId: string): Promise<string> { async function getDefaultBranchRef(projectId: string): Promise<string> {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL( const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}` `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}`
); );
@ -582,6 +599,7 @@ async function getFileContents(
filePath: string, filePath: string,
ref?: string ref?: string
): Promise<GitLabContent> { ): Promise<GitLabContent> {
projectId = decodeURIComponent(projectId); // Decode project ID
const encodedPath = encodeURIComponent(filePath); const encodedPath = encodeURIComponent(filePath);
// ref가 없는 경우 default branch를 가져옴 // ref가 없는 경우 default branch를 가져옴
@ -633,6 +651,7 @@ async function createIssue(
projectId: string, projectId: string,
options: z.infer<typeof CreateIssueOptionsSchema> options: z.infer<typeof CreateIssueOptionsSchema>
): Promise<GitLabIssue> { ): Promise<GitLabIssue> {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL( const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues` `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues`
); );
@ -672,6 +691,7 @@ async function listIssues(
projectId: string, projectId: string,
options: Omit<z.infer<typeof ListIssuesSchema>, "project_id"> = {} options: Omit<z.infer<typeof ListIssuesSchema>, "project_id"> = {}
): Promise<GitLabIssue[]> { ): Promise<GitLabIssue[]> {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL( const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues` `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues`
); );
@ -709,6 +729,7 @@ async function getIssue(
projectId: string, projectId: string,
issueIid: number issueIid: number
): Promise<GitLabIssue> { ): Promise<GitLabIssue> {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL( const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent( `${GITLAB_API_URL}/projects/${encodeURIComponent(
projectId projectId
@ -738,6 +759,7 @@ async function updateIssue(
issueIid: number, issueIid: number,
options: Omit<z.infer<typeof UpdateIssueSchema>, "project_id" | "issue_iid"> options: Omit<z.infer<typeof UpdateIssueSchema>, "project_id" | "issue_iid">
): Promise<GitLabIssue> { ): Promise<GitLabIssue> {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL( const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent( `${GITLAB_API_URL}/projects/${encodeURIComponent(
projectId projectId
@ -770,6 +792,7 @@ async function updateIssue(
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async function deleteIssue(projectId: string, issueIid: number): Promise<void> { async function deleteIssue(projectId: string, issueIid: number): Promise<void> {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL( const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent( `${GITLAB_API_URL}/projects/${encodeURIComponent(
projectId projectId
@ -796,6 +819,7 @@ async function listIssueLinks(
projectId: string, projectId: string,
issueIid: number issueIid: number
): Promise<GitLabIssueWithLinkDetails[]> { ): Promise<GitLabIssueWithLinkDetails[]> {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL( const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent( `${GITLAB_API_URL}/projects/${encodeURIComponent(
projectId projectId
@ -825,6 +849,7 @@ async function getIssueLink(
issueIid: number, issueIid: number,
issueLinkId: number issueLinkId: number
): Promise<GitLabIssueLink> { ): Promise<GitLabIssueLink> {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL( const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent( `${GITLAB_API_URL}/projects/${encodeURIComponent(
projectId projectId
@ -858,6 +883,8 @@ async function createIssueLink(
targetIssueIid: number, targetIssueIid: number,
linkType: "relates_to" | "blocks" | "is_blocked_by" = "relates_to" linkType: "relates_to" | "blocks" | "is_blocked_by" = "relates_to"
): Promise<GitLabIssueLink> { ): Promise<GitLabIssueLink> {
projectId = decodeURIComponent(projectId); // Decode project ID
targetProjectId = decodeURIComponent(targetProjectId); // Decode target project ID as well
const url = new URL( const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent( `${GITLAB_API_URL}/projects/${encodeURIComponent(
projectId projectId
@ -893,6 +920,7 @@ async function deleteIssueLink(
issueIid: number, issueIid: number,
issueLinkId: number issueLinkId: number
): Promise<void> { ): Promise<void> {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL( const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent( `${GITLAB_API_URL}/projects/${encodeURIComponent(
projectId projectId
@ -919,6 +947,7 @@ async function createMergeRequest(
projectId: string, projectId: string,
options: z.infer<typeof CreateMergeRequestOptionsSchema> options: z.infer<typeof CreateMergeRequestOptionsSchema>
): Promise<GitLabMergeRequest> { ): Promise<GitLabMergeRequest> {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL( const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests` `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests`
); );
@ -964,6 +993,7 @@ async function listMergeRequestDiscussions(
projectId: string, projectId: string,
mergeRequestIid: number mergeRequestIid: number
): Promise<GitLabDiscussion[]> { ): Promise<GitLabDiscussion[]> {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL( const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent( `${GITLAB_API_URL}/projects/${encodeURIComponent(
projectId projectId
@ -1000,6 +1030,7 @@ async function updateMergeRequestNote(
body: string, body: string,
resolved?: boolean resolved?: boolean
): Promise<GitLabDiscussionNote> { ): Promise<GitLabDiscussionNote> {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL( const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent( `${GITLAB_API_URL}/projects/${encodeURIComponent(
projectId projectId
@ -1044,6 +1075,7 @@ async function createOrUpdateFile(
last_commit_id?: string, last_commit_id?: string,
commit_id?: string commit_id?: string
): Promise<GitLabCreateUpdateFileResponse> { ): Promise<GitLabCreateUpdateFileResponse> {
projectId = decodeURIComponent(projectId); // Decode project ID
const encodedPath = encodeURIComponent(filePath); const encodedPath = encodeURIComponent(filePath);
const url = new URL( const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent( `${GITLAB_API_URL}/projects/${encodeURIComponent(
@ -1126,6 +1158,7 @@ async function createTree(
files: FileOperation[], files: FileOperation[],
ref?: string ref?: string
): Promise<GitLabTree> { ): Promise<GitLabTree> {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL( const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent( `${GITLAB_API_URL}/projects/${encodeURIComponent(
projectId projectId
@ -1180,6 +1213,7 @@ async function createCommit(
branch: string, branch: string,
actions: FileOperation[] actions: FileOperation[]
): Promise<GitLabCommit> { ): Promise<GitLabCommit> {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL( const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent( `${GITLAB_API_URL}/projects/${encodeURIComponent(
projectId projectId
@ -1303,25 +1337,48 @@ async function createRepository(
* MR 조회 함수 (Function to retrieve merge request) * MR 조회 함수 (Function to retrieve merge request)
* *
* @param {string} projectId - The ID or URL-encoded path of the project * @param {string} projectId - The ID or URL-encoded path of the project
* @param {number} mergeRequestIid - The internal ID of the merge request * @param {number} mergeRequestIid - The internal ID of the merge request (Optional)
* @param {string} [branchName] - The name of the branch to search for merge request by branch name (Optional)
* @returns {Promise<GitLabMergeRequest>} The merge request details * @returns {Promise<GitLabMergeRequest>} The merge request details
*/ */
async function getMergeRequest( async function getMergeRequest(
projectId: string, projectId: string,
mergeRequestIid: number mergeRequestIid?: number,
branchName?: string
): Promise<GitLabMergeRequest> { ): Promise<GitLabMergeRequest> {
const url = new URL( projectId = decodeURIComponent(projectId); // Decode project ID
let url: URL;
if (mergeRequestIid) {
url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent( `${GITLAB_API_URL}/projects/${encodeURIComponent(
projectId projectId
)}/merge_requests/${mergeRequestIid}` )}/merge_requests/${mergeRequestIid}`
); );
} else if (branchName) {
url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent(
projectId
)}/merge_requests?source_branch=${encodeURIComponent(branchName)}`
);
} else {
throw new Error("Either mergeRequestIid or branchName must be provided");
}
const response = await fetch(url.toString(), { const response = await fetch(url.toString(), {
...DEFAULT_FETCH_CONFIG, ...DEFAULT_FETCH_CONFIG,
}); });
await handleGitLabError(response); await handleGitLabError(response);
return GitLabMergeRequestSchema.parse(await response.json());
const data = await response.json();
// If response is an array (Comes from branchName search), return the first item if exist
if (Array.isArray(data) && data.length > 0) {
return GitLabMergeRequestSchema.parse(data[0]);
}
return GitLabMergeRequestSchema.parse(data);
} }
/** /**
@ -1329,15 +1386,31 @@ async function getMergeRequest(
* MR 변경사항 조회 함수 (Function to retrieve merge request changes) * MR 변경사항 조회 함수 (Function to retrieve merge request changes)
* *
* @param {string} projectId - The ID or URL-encoded path of the project * @param {string} projectId - The ID or URL-encoded path of the project
* @param {number} mergeRequestIid - The internal ID of the merge request * @param {number} mergeRequestIid - The internal ID of the merge request (Either mergeRequestIid or branchName must be provided)
* @param {string} [branchName] - The name of the branch to search for merge request by branch name (Either mergeRequestIid or branchName must be provided)
* @param {string} [view] - The view type for the diff (inline or parallel) * @param {string} [view] - The view type for the diff (inline or parallel)
* @returns {Promise<GitLabMergeRequestDiff[]>} The merge request diffs * @returns {Promise<GitLabMergeRequestDiff[]>} The merge request diffs
*/ */
async function getMergeRequestDiffs( async function getMergeRequestDiffs(
projectId: string, projectId: string,
mergeRequestIid: number, mergeRequestIid?: number,
branchName?: string,
view?: "inline" | "parallel" view?: "inline" | "parallel"
): Promise<GitLabMergeRequestDiff[]> { ): Promise<GitLabMergeRequestDiff[]> {
projectId = decodeURIComponent(projectId); // Decode project ID
if (!mergeRequestIid && !branchName) {
throw new Error("Either mergeRequestIid or branchName must be provided");
}
if (branchName && !mergeRequestIid) {
const mergeRequest = await getMergeRequest(
projectId,
undefined,
branchName
);
mergeRequestIid = mergeRequest.iid;
}
const url = new URL( const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent( `${GITLAB_API_URL}/projects/${encodeURIComponent(
projectId projectId
@ -1362,18 +1435,34 @@ async function getMergeRequestDiffs(
* MR 업데이트 함수 (Function to update merge request) * MR 업데이트 함수 (Function to update merge request)
* *
* @param {string} projectId - The ID or URL-encoded path of the project * @param {string} projectId - The ID or URL-encoded path of the project
* @param {number} mergeRequestIid - The internal ID of the merge request * @param {number} mergeRequestIid - The internal ID of the merge request (Optional)
* @param {string} branchName - The name of the branch to search for merge request by branch name (Optional)
* @param {Object} options - The update options * @param {Object} options - The update options
* @returns {Promise<GitLabMergeRequest>} The updated merge request * @returns {Promise<GitLabMergeRequest>} The updated merge request
*/ */
async function updateMergeRequest( async function updateMergeRequest(
projectId: string, projectId: string,
mergeRequestIid: number,
options: Omit< options: Omit<
z.infer<typeof UpdateMergeRequestSchema>, z.infer<typeof UpdateMergeRequestSchema>,
"project_id" | "merge_request_iid" "project_id" | "merge_request_iid" | "source_branch"
> >,
mergeRequestIid?: number,
branchName?: string
): Promise<GitLabMergeRequest> { ): Promise<GitLabMergeRequest> {
projectId = decodeURIComponent(projectId); // Decode project ID
if (!mergeRequestIid && !branchName) {
throw new Error("Either mergeRequestIid or branchName must be provided");
}
if (branchName && !mergeRequestIid) {
const mergeRequest = await getMergeRequest(
projectId,
undefined,
branchName
);
mergeRequestIid = mergeRequest.iid;
}
const url = new URL( const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent( `${GITLAB_API_URL}/projects/${encodeURIComponent(
projectId projectId
@ -1407,6 +1496,7 @@ async function createNote(
noteableIid: number, noteableIid: number,
body: string body: string
): Promise<any> { ): Promise<any> {
projectId = decodeURIComponent(projectId); // Decode project ID
// ⚙️ 응답 타입은 GitLab API 문서에 따라 조정 가능 // ⚙️ 응답 타입은 GitLab API 문서에 따라 조정 가능
const url = new URL( const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent( `${GITLAB_API_URL}/projects/${encodeURIComponent(
@ -1535,6 +1625,7 @@ async function getProject(
with_custom_attributes?: boolean; with_custom_attributes?: boolean;
} = {} } = {}
): Promise<GitLabProject> { ): Promise<GitLabProject> {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL( const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}` `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}`
); );
@ -1609,6 +1700,7 @@ async function listLabels(
projectId: string, projectId: string,
options: Omit<z.infer<typeof ListLabelsSchema>, "project_id"> = {} options: Omit<z.infer<typeof ListLabelsSchema>, "project_id"> = {}
): Promise<GitLabLabel[]> { ): Promise<GitLabLabel[]> {
projectId = decodeURIComponent(projectId); // Decode project ID
// Construct the URL with project path // Construct the URL with project path
const url = new URL( const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/labels` `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/labels`
@ -1651,6 +1743,7 @@ async function getLabel(
labelId: number | string, labelId: number | string,
includeAncestorGroups?: boolean includeAncestorGroups?: boolean
): Promise<GitLabLabel> { ): Promise<GitLabLabel> {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL( const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent( `${GITLAB_API_URL}/projects/${encodeURIComponent(
projectId projectId
@ -1689,6 +1782,7 @@ async function createLabel(
projectId: string, projectId: string,
options: Omit<z.infer<typeof CreateLabelSchema>, "project_id"> options: Omit<z.infer<typeof CreateLabelSchema>, "project_id">
): Promise<GitLabLabel> { ): Promise<GitLabLabel> {
projectId = decodeURIComponent(projectId); // Decode project ID
// Make the API request // Make the API request
const response = await fetch( const response = await fetch(
`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/labels`, `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/labels`,
@ -1720,6 +1814,7 @@ async function updateLabel(
labelId: number | string, labelId: number | string,
options: Omit<z.infer<typeof UpdateLabelSchema>, "project_id" | "label_id"> options: Omit<z.infer<typeof UpdateLabelSchema>, "project_id" | "label_id">
): Promise<GitLabLabel> { ): Promise<GitLabLabel> {
projectId = decodeURIComponent(projectId); // Decode project ID
// Make the API request // Make the API request
const response = await fetch( const response = await fetch(
`${GITLAB_API_URL}/projects/${encodeURIComponent( `${GITLAB_API_URL}/projects/${encodeURIComponent(
@ -1750,6 +1845,7 @@ async function deleteLabel(
projectId: string, projectId: string,
labelId: number | string labelId: number | string
): Promise<void> { ): Promise<void> {
projectId = decodeURIComponent(projectId); // Decode project ID
// Make the API request // Make the API request
const response = await fetch( const response = await fetch(
`${GITLAB_API_URL}/projects/${encodeURIComponent( `${GITLAB_API_URL}/projects/${encodeURIComponent(
@ -1841,13 +1937,15 @@ async function listGroupProjects(
*/ */
async function listWikiPages( async function listWikiPages(
projectId: string, projectId: string,
options: Omit<z.infer<typeof ListWikiPagesSchema>, 'project_id'> = {} options: Omit<z.infer<typeof ListWikiPagesSchema>, "project_id"> = {}
): Promise<GitLabWikiPage[]> { ): Promise<GitLabWikiPage[]> {
projectId = decodeURIComponent(projectId); // Decode project ID
const url = new URL( const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis` `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis`
); );
if (options.page) url.searchParams.append('page', options.page.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(), { const response = await fetch(url.toString(), {
...DEFAULT_FETCH_CONFIG, ...DEFAULT_FETCH_CONFIG,
}); });
@ -1863,10 +1961,11 @@ async function getWikiPage(
projectId: string, projectId: string,
slug: string slug: string
): Promise<GitLabWikiPage> { ): Promise<GitLabWikiPage> {
projectId = decodeURIComponent(projectId); // Decode project ID
const response = await fetch( const response = await fetch(
`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis/${encodeURIComponent( `${GITLAB_API_URL}/projects/${encodeURIComponent(
slug projectId
)}`, )}/wikis/${encodeURIComponent(slug)}`,
{ ...DEFAULT_FETCH_CONFIG } { ...DEFAULT_FETCH_CONFIG }
); );
await handleGitLabError(response); await handleGitLabError(response);
@ -1883,13 +1982,14 @@ async function createWikiPage(
content: string, content: string,
format?: string format?: string
): Promise<GitLabWikiPage> { ): Promise<GitLabWikiPage> {
projectId = decodeURIComponent(projectId); // Decode project ID
const body: Record<string, any> = { title, content }; const body: Record<string, any> = { title, content };
if (format) body.format = format; if (format) body.format = format;
const response = await fetch( const response = await fetch(
`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis`, `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis`,
{ {
...DEFAULT_FETCH_CONFIG, ...DEFAULT_FETCH_CONFIG,
method: 'POST', method: "POST",
body: JSON.stringify(body), body: JSON.stringify(body),
} }
); );
@ -1908,17 +2008,18 @@ async function updateWikiPage(
content?: string, content?: string,
format?: string format?: string
): Promise<GitLabWikiPage> { ): Promise<GitLabWikiPage> {
projectId = decodeURIComponent(projectId); // Decode project ID
const body: Record<string, any> = {}; const body: Record<string, any> = {};
if (title) body.title = title; if (title) body.title = title;
if (content) body.content = content; if (content) body.content = content;
if (format) body.format = format; if (format) body.format = format;
const response = await fetch( const response = await fetch(
`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis/${encodeURIComponent( `${GITLAB_API_URL}/projects/${encodeURIComponent(
slug projectId
)}`, )}/wikis/${encodeURIComponent(slug)}`,
{ {
...DEFAULT_FETCH_CONFIG, ...DEFAULT_FETCH_CONFIG,
method: 'PUT', method: "PUT",
body: JSON.stringify(body), body: JSON.stringify(body),
} }
); );
@ -1930,34 +2031,96 @@ async function updateWikiPage(
/** /**
* Delete a wiki page * Delete a wiki page
*/ */
async function deleteWikiPage( async function deleteWikiPage(projectId: string, slug: string): Promise<void> {
projectId: string, projectId = decodeURIComponent(projectId); // Decode project ID
slug: string
): Promise<void> {
const response = await fetch( const response = await fetch(
`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/wikis/${encodeURIComponent( `${GITLAB_API_URL}/projects/${encodeURIComponent(
slug projectId
)}`, )}/wikis/${encodeURIComponent(slug)}`,
{ {
...DEFAULT_FETCH_CONFIG, ...DEFAULT_FETCH_CONFIG,
method: 'DELETE', method: "DELETE",
} }
); );
await handleGitLabError(response); await handleGitLabError(response);
} }
/**
* Get the repository tree for a project
* @param {string} projectId - The ID or URL-encoded path of the project
* @param {GetRepositoryTreeOptions} options - Options for the tree
* @returns {Promise<GitLabTreeItem[]>}
*/
async function getRepositoryTree(
options: GetRepositoryTreeOptions
): Promise<GitLabTreeItem[]> {
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.page_token) queryParams.append("page_token", options.page_token);
if (options.pagination) queryParams.append("pagination", options.pagination);
const response = await fetch(
`${GITLAB_API_URL}/projects/${encodeURIComponent(
options.project_id
)}/repository/tree?${queryParams.toString()}`,
{
headers: {
Authorization: `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
}
);
if (response.status === 404) {
throw new Error("Repository or path not found");
}
if (!response.ok) {
throw new Error(`Failed to get repository tree: ${response.statusText}`);
}
const data = await response.json();
return z.array(GitLabTreeItemSchema).parse(data);
}
server.setRequestHandler(ListToolsRequestSchema, async () => { server.setRequestHandler(ListToolsRequestSchema, async () => {
// Apply read-only filter first // Apply read-only filter first
const tools0 = GITLAB_READ_ONLY_MODE const tools0 = GITLAB_READ_ONLY_MODE
? allTools.filter((tool) => readOnlyTools.includes(tool.name)) ? allTools.filter((tool) => readOnlyTools.includes(tool.name))
: allTools; : allTools;
// Toggle wiki tools by USE_GITLAB_WIKI flag // Toggle wiki tools by USE_GITLAB_WIKI flag
const tools = USE_GITLAB_WIKI let tools = USE_GITLAB_WIKI
? tools0 ? tools0
: tools0.filter((tool) => !wikiToolNames.includes(tool.name)); : tools0.filter((tool) => !wikiToolNames.includes(tool.name));
// <<< START: Gemini 호환성을 위해 $schema 제거 >>>
tools = tools.map((tool) => {
// inputSchema가 존재하고 객체인지 확인
if (
tool.inputSchema &&
typeof tool.inputSchema === "object" &&
tool.inputSchema !== null
) {
// $schema 키가 존재하면 삭제
if ("$schema" in tool.inputSchema) {
// 불변성을 위해 새로운 객체 생성 (선택적이지만 권장)
const modifiedSchema = { ...tool.inputSchema };
delete modifiedSchema.$schema;
return { ...tool, inputSchema: modifiedSchema };
}
}
// 변경이 필요 없으면 그대로 반환
return tool;
});
// <<< END: Gemini 호환성을 위해 $schema 제거 >>>
return { return {
tools, tools, // $schema가 제거된 도구 목록 반환
}; };
}); });
@ -2119,7 +2282,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
const args = GetMergeRequestSchema.parse(request.params.arguments); const args = GetMergeRequestSchema.parse(request.params.arguments);
const mergeRequest = await getMergeRequest( const mergeRequest = await getMergeRequest(
args.project_id, args.project_id,
args.merge_request_iid args.merge_request_iid,
args.source_branch
); );
return { return {
content: [ content: [
@ -2133,6 +2297,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
const diffs = await getMergeRequestDiffs( const diffs = await getMergeRequestDiffs(
args.project_id, args.project_id,
args.merge_request_iid, args.merge_request_iid,
args.source_branch,
args.view args.view
); );
return { return {
@ -2142,11 +2307,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
case "update_merge_request": { case "update_merge_request": {
const args = UpdateMergeRequestSchema.parse(request.params.arguments); const args = UpdateMergeRequestSchema.parse(request.params.arguments);
const { project_id, merge_request_iid, ...options } = args; const { project_id, merge_request_iid, source_branch, ...options } =
args;
const mergeRequest = await updateMergeRequest( const mergeRequest = await updateMergeRequest(
project_id, project_id,
options,
merge_request_iid, merge_request_iid,
options source_branch
); );
return { return {
content: [ content: [
@ -2451,33 +2618,82 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
} }
case "list_wiki_pages": { case "list_wiki_pages": {
const { project_id, page, per_page } = ListWikiPagesSchema.parse(request.params.arguments); const { project_id, page, per_page } = ListWikiPagesSchema.parse(
request.params.arguments
);
const wikiPages = await listWikiPages(project_id, { page, per_page }); const wikiPages = await listWikiPages(project_id, { page, per_page });
return { content: [{ type: "text", text: JSON.stringify(wikiPages, null, 2) }] }; return {
content: [{ type: "text", text: JSON.stringify(wikiPages, null, 2) }],
};
} }
case "get_wiki_page": { 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); const wikiPage = await getWikiPage(project_id, slug);
return { content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }] }; return {
content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }],
};
} }
case "create_wiki_page": { case "create_wiki_page": {
const { project_id, title, content, format } = CreateWikiPageSchema.parse(request.params.arguments); const { project_id, title, content, format } =
const wikiPage = await createWikiPage(project_id, title, content, format); CreateWikiPageSchema.parse(request.params.arguments);
return { content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }] }; const wikiPage = await createWikiPage(
project_id,
title,
content,
format
);
return {
content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }],
};
} }
case "update_wiki_page": { case "update_wiki_page": {
const { project_id, slug, title, content, format } = UpdateWikiPageSchema.parse(request.params.arguments); const { project_id, slug, title, content, format } =
const wikiPage = await updateWikiPage(project_id, slug, title, content, format); UpdateWikiPageSchema.parse(request.params.arguments);
return { content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }] }; const wikiPage = await updateWikiPage(
project_id,
slug,
title,
content,
format
);
return {
content: [{ type: "text", text: JSON.stringify(wikiPage, null, 2) }],
};
} }
case "delete_wiki_page": { 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); await deleteWikiPage(project_id, slug);
return { content: [{ type: "text", text: JSON.stringify({ status: "success", message: "Wiki page deleted successfully" }, null, 2) }] }; return {
content: [
{
type: "text",
text: JSON.stringify(
{
status: "success",
message: "Wiki page deleted successfully",
},
null,
2
),
},
],
};
}
case "get_repository_tree": {
const args = GetRepositoryTreeSchema.parse(request.params.arguments);
const tree = await getRepositoryTree(args);
return {
content: [{ type: "text", text: JSON.stringify(tree, null, 2) }],
};
} }
default: default:

5
package-lock.json generated
View File

@ -1,16 +1,17 @@
{ {
"name": "@zereight/mcp-gitlab", "name": "@zereight/mcp-gitlab",
"version": "1.0.30", "version": "1.0.33",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@zereight/mcp-gitlab", "name": "@zereight/mcp-gitlab",
"version": "1.0.30", "version": "1.0.33",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "1.8.0", "@modelcontextprotocol/sdk": "1.8.0",
"@types/node-fetch": "^2.6.12", "@types/node-fetch": "^2.6.12",
"form-data": "^4.0.0",
"http-proxy-agent": "^7.0.2", "http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.6", "https-proxy-agent": "^7.0.6",
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",

View File

@ -1,6 +1,6 @@
{ {
"name": "@zereight/mcp-gitlab", "name": "@zereight/mcp-gitlab",
"version": "1.0.32", "version": "1.0.36",
"description": "MCP server for using the GitLab API", "description": "MCP server for using the GitLab API",
"license": "MIT", "license": "MIT",
"author": "zereight", "author": "zereight",

View File

@ -63,7 +63,8 @@ export const GitLabRepositorySchema = z.object({
created_at: z.string().optional(), created_at: z.string().optional(),
last_activity_at: z.string().optional(), last_activity_at: z.string().optional(),
default_branch: z.string().optional(), default_branch: z.string().optional(),
namespace: z.object({ namespace: z
.object({
id: z.number(), id: z.number(),
name: z.string(), name: z.string(),
path: z.string(), path: z.string(),
@ -71,7 +72,8 @@ export const GitLabRepositorySchema = z.object({
full_path: z.string(), full_path: z.string(),
avatar_url: z.string().nullable().optional(), avatar_url: z.string().nullable().optional(),
web_url: z.string().optional(), web_url: z.string().optional(),
}).optional(), })
.optional(),
readme_url: z.string().optional().nullable(), readme_url: z.string().optional().nullable(),
topics: z.array(z.string()).optional(), topics: z.array(z.string()).optional(),
tag_list: z.array(z.string()).optional(), // deprecated but still present tag_list: z.array(z.string()).optional(), // deprecated but still present
@ -79,16 +81,24 @@ export const GitLabRepositorySchema = z.object({
archived: z.boolean().optional(), archived: z.boolean().optional(),
forks_count: z.number().optional(), forks_count: z.number().optional(),
star_count: z.number().optional(), star_count: z.number().optional(),
permissions: z.object({ permissions: z
project_access: z.object({ .object({
project_access: z
.object({
access_level: z.number(), access_level: z.number(),
notification_level: z.number().optional(), notification_level: z.number().optional(),
}).optional().nullable(), })
group_access: z.object({ .optional()
.nullable(),
group_access: z
.object({
access_level: z.number(), access_level: z.number(),
notification_level: z.number().optional(), notification_level: z.number().optional(),
}).optional().nullable(), })
}).optional(), .optional()
.nullable(),
})
.optional(),
container_registry_enabled: z.boolean().optional(), container_registry_enabled: z.boolean().optional(),
container_registry_access_level: z.string().optional(), container_registry_access_level: z.string().optional(),
issues_enabled: z.boolean().optional(), issues_enabled: z.boolean().optional(),
@ -99,12 +109,16 @@ export const GitLabRepositorySchema = z.object({
can_create_merge_request_in: z.boolean().optional(), can_create_merge_request_in: z.boolean().optional(),
resolve_outdated_diff_discussions: z.boolean().optional(), resolve_outdated_diff_discussions: z.boolean().optional(),
shared_runners_enabled: z.boolean().optional(), shared_runners_enabled: z.boolean().optional(),
shared_with_groups: z.array(z.object({ shared_with_groups: z
.array(
z.object({
group_id: z.number(), group_id: z.number(),
group_name: z.string(), group_name: z.string(),
group_full_path: z.string(), group_full_path: z.string(),
group_access_level: z.number(), group_access_level: z.number(),
})).optional(), })
)
.optional(),
}); });
// Project schema (extended from repository schema) // Project schema (extended from repository schema)
@ -146,17 +160,41 @@ export const FileOperationSchema = z.object({
}); });
// Tree and commit schemas // Tree and commit schemas
export const GitLabTreeEntrySchema = z.object({ export const GitLabTreeItemSchema = z.object({
id: z.string(), // Changed from sha to match GitLab API id: z.string(),
name: z.string(), name: z.string(),
type: z.enum(["blob", "tree"]), type: z.enum(["tree", "blob"]),
path: z.string(), path: z.string(),
mode: z.string(), mode: z.string(),
}); });
export const GetRepositoryTreeSchema = z.object({
project_id: z.string().describe("The ID or URL-encoded path of the project"),
path: z.string().optional().describe("The path inside the repository"),
ref: z
.string()
.optional()
.describe(
"The name of a repository branch or tag. Defaults to the default branch."
),
recursive: z
.boolean()
.optional()
.describe("Boolean value to get a recursive tree"),
per_page: z
.number()
.optional()
.describe("Number of results to show per page"),
page_token: z
.string()
.optional()
.describe("The tree record ID for pagination"),
pagination: z.string().optional().describe("Pagination method (keyset)"),
});
export const GitLabTreeSchema = z.object({ export const GitLabTreeSchema = z.object({
id: z.string(), // Changed from sha to match GitLab API id: z.string(), // Changed from sha to match GitLab API
tree: z.array(GitLabTreeEntrySchema), tree: z.array(GitLabTreeItemSchema),
}); });
export const GitLabCommitSchema = z.object({ export const GitLabCommitSchema = z.object({
@ -276,17 +314,21 @@ export const GitLabIssueSchema = z.object({
updated_at: z.string(), updated_at: z.string(),
closed_at: z.string().nullable(), closed_at: z.string().nullable(),
web_url: z.string(), // Changed from html_url to match GitLab API web_url: z.string(), // Changed from html_url to match GitLab API
references: z.object({ references: z
.object({
short: z.string(), short: z.string(),
relative: z.string(), relative: z.string(),
full: z.string(), full: z.string(),
}).optional(), })
time_stats: z.object({ .optional(),
time_stats: z
.object({
time_estimate: z.number(), time_estimate: z.number(),
total_time_spent: z.number(), total_time_spent: z.number(),
human_time_estimate: z.string().nullable(), human_time_estimate: z.string().nullable(),
human_total_time_spent: z.string().nullable(), human_total_time_spent: z.string().nullable(),
}).optional(), })
.optional(),
confidential: z.boolean().optional(), confidential: z.boolean().optional(),
due_date: z.string().nullable().optional(), due_date: z.string().nullable().optional(),
discussion_locked: z.boolean().nullable().optional(), discussion_locked: z.boolean().nullable().optional(),
@ -296,7 +338,7 @@ export const GitLabIssueSchema = z.object({
// NEW SCHEMA: For issue with link details (used in listing issue links) // NEW SCHEMA: For issue with link details (used in listing issue links)
export const GitLabIssueWithLinkDetailsSchema = GitLabIssueSchema.extend({ export const GitLabIssueWithLinkDetailsSchema = GitLabIssueSchema.extend({
issue_link_id: z.number(), issue_link_id: z.number(),
link_type: z.enum(['relates_to', 'blocks', 'is_blocked_by']), link_type: z.enum(["relates_to", "blocks", "is_blocked_by"]),
link_created_at: z.string(), link_created_at: z.string(),
link_updated_at: z.string(), link_updated_at: z.string(),
}); });
@ -305,11 +347,13 @@ export const GitLabIssueWithLinkDetailsSchema = GitLabIssueSchema.extend({
export const GitLabForkParentSchema = z.object({ export const GitLabForkParentSchema = z.object({
name: z.string(), name: z.string(),
path_with_namespace: z.string(), // Changed from full_name to match GitLab API path_with_namespace: z.string(), // Changed from full_name to match GitLab API
owner: z.object({ owner: z
.object({
username: z.string(), // Changed from login to match GitLab API username: z.string(), // Changed from login to match GitLab API
id: z.number(), id: z.number(),
avatar_url: z.string(), avatar_url: z.string(),
}).optional(), // Made optional to handle cases where GitLab API doesn't include it })
.optional(), // Made optional to handle cases where GitLab API doesn't include it
web_url: z.string(), // Changed from html_url to match GitLab API web_url: z.string(), // Changed from html_url to match GitLab API
}); });
@ -377,7 +421,9 @@ export const GitLabDiscussionNoteSchema = z.object({
resolved: z.boolean().optional(), resolved: z.boolean().optional(),
resolved_by: GitLabUserSchema.nullable().optional(), resolved_by: GitLabUserSchema.nullable().optional(),
resolved_at: z.string().nullable().optional(), resolved_at: z.string().nullable().optional(),
position: z.object({ // Only present for DiffNote position: z
.object({
// Only present for DiffNote
base_sha: z.string(), base_sha: z.string(),
start_sha: z.string(), start_sha: z.string(),
head_sha: z.string(), head_sha: z.string(),
@ -386,7 +432,8 @@ export const GitLabDiscussionNoteSchema = z.object({
position_type: z.enum(["text", "image", "file"]), position_type: z.enum(["text", "image", "file"]),
old_line: z.number().nullable(), old_line: z.number().nullable(),
new_line: z.number().nullable(), new_line: z.number().nullable(),
line_range: z.object({ line_range: z
.object({
start: z.object({ start: z.object({
line_code: z.string(), line_code: z.string(),
type: z.enum(["new", "old"]), type: z.enum(["new", "old"]),
@ -399,12 +446,15 @@ export const GitLabDiscussionNoteSchema = z.object({
old_line: z.number().nullable(), old_line: z.number().nullable(),
new_line: z.number().nullable(), new_line: z.number().nullable(),
}), }),
}).nullable().optional(), // For multi-line diff notes })
.nullable()
.optional(), // For multi-line diff notes
width: z.number().optional(), // For image diff notes width: z.number().optional(), // For image diff notes
height: z.number().optional(), // For image diff notes height: z.number().optional(), // For image diff notes
x: z.number().optional(), // For image diff notes x: z.number().optional(), // For image diff notes
y: z.number().optional(), // For image diff notes y: z.number().optional(), // For image diff notes
}).optional(), })
.optional(),
}); });
export type GitLabDiscussionNote = z.infer<typeof GitLabDiscussionNoteSchema>; export type GitLabDiscussionNote = z.infer<typeof GitLabDiscussionNoteSchema>;
@ -440,10 +490,7 @@ export const CreateOrUpdateFileSchema = ProjectParamsSchema.extend({
.string() .string()
.optional() .optional()
.describe("Path of the file to move/rename"), .describe("Path of the file to move/rename"),
last_commit_id: z last_commit_id: z.string().optional().describe("Last known file commit ID"),
.string()
.optional()
.describe("Last known file commit ID"),
commit_id: z commit_id: z
.string() .string()
.optional() .optional()
@ -539,7 +586,9 @@ export const GitLabMergeRequestDiffSchema = z.object({
export const GetMergeRequestSchema = ProjectParamsSchema.extend({ export const GetMergeRequestSchema = ProjectParamsSchema.extend({
merge_request_iid: z merge_request_iid: z
.number() .number()
.describe("The internal ID of the merge request"), .optional()
.describe("The IID of a merge request"),
source_branch: z.string().optional().describe("Source branch name"),
}); });
export const UpdateMergeRequestSchema = GetMergeRequestSchema.extend({ export const UpdateMergeRequestSchema = GetMergeRequestSchema.extend({
@ -585,22 +634,61 @@ export const CreateNoteSchema = z.object({
// Issues API operation schemas // Issues API operation schemas
export const ListIssuesSchema = z.object({ export const ListIssuesSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"), project_id: z.string().describe("Project ID or URL-encoded path"),
assignee_id: z.number().optional().describe("Return issues assigned to the given user ID"), assignee_id: z
assignee_username: z.string().optional().describe("Return issues assigned to the given username"), .number()
author_id: z.number().optional().describe("Return issues created by the given user ID"), .optional()
author_username: z.string().optional().describe("Return issues created by the given username"), .describe("Return issues assigned to the given user ID"),
confidential: z.boolean().optional().describe("Filter confidential or public issues"), assignee_username: z
created_after: z.string().optional().describe("Return issues created after the given time"), .string()
created_before: z.string().optional().describe("Return issues created before the given time"), .optional()
due_date: z.string().optional().describe("Return issues that have the due date"), .describe("Return issues assigned to the given username"),
author_id: z
.number()
.optional()
.describe("Return issues created by the given user ID"),
author_username: z
.string()
.optional()
.describe("Return issues created by the given username"),
confidential: z
.boolean()
.optional()
.describe("Filter confidential or public issues"),
created_after: z
.string()
.optional()
.describe("Return issues created after the given time"),
created_before: z
.string()
.optional()
.describe("Return issues created before the given time"),
due_date: z
.string()
.optional()
.describe("Return issues that have the due date"),
label_name: z.array(z.string()).optional().describe("Array of label names"), label_name: z.array(z.string()).optional().describe("Array of label names"),
milestone: z.string().optional().describe("Milestone title"), milestone: z.string().optional().describe("Milestone title"),
scope: z.enum(['created-by-me', 'assigned-to-me', 'all']).optional().describe("Return issues from a specific scope"), scope: z
.enum(["created-by-me", "assigned-to-me", "all"])
.optional()
.describe("Return issues from a specific scope"),
search: z.string().optional().describe("Search for specific terms"), search: z.string().optional().describe("Search for specific terms"),
state: z.enum(['opened', 'closed', 'all']).optional().describe("Return issues with a specific state"), state: z
updated_after: z.string().optional().describe("Return issues updated after the given time"), .enum(["opened", "closed", "all"])
updated_before: z.string().optional().describe("Return issues updated before the given time"), .optional()
with_labels_details: z.boolean().optional().describe("Return more details for each label"), .describe("Return issues with a specific state"),
updated_after: z
.string()
.optional()
.describe("Return issues updated after the given time"),
updated_before: z
.string()
.optional()
.describe("Return issues updated before the given time"),
with_labels_details: z
.boolean()
.optional()
.describe("Return more details for each label"),
page: z.number().optional().describe("Page number for pagination"), page: z.number().optional().describe("Page number for pagination"),
per_page: z.number().optional().describe("Number of items per page"), per_page: z.number().optional().describe("Number of items per page"),
}); });
@ -615,13 +703,28 @@ export const UpdateIssueSchema = z.object({
issue_iid: z.number().describe("The internal ID of the project issue"), issue_iid: z.number().describe("The internal ID of the project issue"),
title: z.string().optional().describe("The title of the issue"), title: z.string().optional().describe("The title of the issue"),
description: z.string().optional().describe("The description of the issue"), description: z.string().optional().describe("The description of the issue"),
assignee_ids: z.array(z.number()).optional().describe("Array of user IDs to assign issue to"), assignee_ids: z
confidential: z.boolean().optional().describe("Set the issue to be confidential"), .array(z.number())
discussion_locked: z.boolean().optional().describe("Flag to lock discussions"), .optional()
due_date: z.string().optional().describe("Date the issue is due (YYYY-MM-DD)"), .describe("Array of user IDs to assign issue to"),
confidential: z
.boolean()
.optional()
.describe("Set the issue to be confidential"),
discussion_locked: z
.boolean()
.optional()
.describe("Flag to lock discussions"),
due_date: z
.string()
.optional()
.describe("Date the issue is due (YYYY-MM-DD)"),
labels: z.array(z.string()).optional().describe("Array of label names"), labels: z.array(z.string()).optional().describe("Array of label names"),
milestone_id: z.number().optional().describe("Milestone ID to assign"), milestone_id: z.number().optional().describe("Milestone ID to assign"),
state_event: z.enum(['close', 'reopen']).optional().describe("Update issue state (close/reopen)"), state_event: z
.enum(["close", "reopen"])
.optional()
.describe("Update issue state (close/reopen)"),
weight: z.number().optional().describe("Weight of the issue (0-9)"), weight: z.number().optional().describe("Weight of the issue (0-9)"),
}); });
@ -634,7 +737,7 @@ export const DeleteIssueSchema = z.object({
export const GitLabIssueLinkSchema = z.object({ export const GitLabIssueLinkSchema = z.object({
source_issue: GitLabIssueSchema, source_issue: GitLabIssueSchema,
target_issue: GitLabIssueSchema, target_issue: GitLabIssueSchema,
link_type: z.enum(['relates_to', 'blocks', 'is_blocked_by']), link_type: z.enum(["relates_to", "blocks", "is_blocked_by"]),
}); });
export const ListIssueLinksSchema = z.object({ export const ListIssueLinksSchema = z.object({
@ -651,9 +754,16 @@ export const GetIssueLinkSchema = z.object({
export const CreateIssueLinkSchema = z.object({ export const CreateIssueLinkSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"), project_id: z.string().describe("Project ID or URL-encoded path"),
issue_iid: z.number().describe("The internal ID of a project's issue"), issue_iid: z.number().describe("The internal ID of a project's issue"),
target_project_id: z.string().describe("The ID or URL-encoded path of a target project"), target_project_id: z
target_issue_iid: z.number().describe("The internal ID of a target project's issue"), .string()
link_type: z.enum(['relates_to', 'blocks', 'is_blocked_by']).optional().describe("The type of the relation, defaults to relates_to"), .describe("The ID or URL-encoded path of a target project"),
target_issue_iid: z
.number()
.describe("The internal ID of a target project's issue"),
link_type: z
.enum(["relates_to", "blocks", "is_blocked_by"])
.optional()
.describe("The type of the relation, defaults to relates_to"),
}); });
export const DeleteIssueLinkSchema = z.object({ export const DeleteIssueLinkSchema = z.object({
@ -667,7 +777,10 @@ export const ListNamespacesSchema = z.object({
search: z.string().optional().describe("Search term for namespaces"), search: z.string().optional().describe("Search term for namespaces"),
page: z.number().optional().describe("Page number for pagination"), page: z.number().optional().describe("Page number for pagination"),
per_page: z.number().optional().describe("Number of items per page"), per_page: z.number().optional().describe("Number of items per page"),
owned: z.boolean().optional().describe("Filter for namespaces owned by current user"), owned: z
.boolean()
.optional()
.describe("Filter for namespaces owned by current user"),
}); });
export const GetNamespaceSchema = z.object({ export const GetNamespaceSchema = z.object({
@ -687,73 +800,164 @@ export const ListProjectsSchema = z.object({
search: z.string().optional().describe("Search term for projects"), search: z.string().optional().describe("Search term for projects"),
page: z.number().optional().describe("Page number for pagination"), page: z.number().optional().describe("Page number for pagination"),
per_page: z.number().optional().describe("Number of items per page"), per_page: z.number().optional().describe("Number of items per page"),
owned: z.boolean().optional().describe("Filter for projects owned by current user"), search_namespaces: z
membership: z.boolean().optional().describe("Filter for projects where current user is a member"), .boolean()
.optional()
.describe("Needs to be true if search is full path"),
owned: z
.boolean()
.optional()
.describe("Filter for projects owned by current user"),
membership: z
.boolean()
.optional()
.describe("Filter for projects where current user is a member"),
simple: z.boolean().optional().describe("Return only limited fields"), simple: z.boolean().optional().describe("Return only limited fields"),
archived: z.boolean().optional().describe("Filter for archived projects"), archived: z.boolean().optional().describe("Filter for archived projects"),
visibility: z.enum(["public", "internal", "private"]).optional().describe("Filter by project visibility"), visibility: z
order_by: z.enum(["id", "name", "path", "created_at", "updated_at", "last_activity_at"]).optional().describe("Return projects ordered by field"), .enum(["public", "internal", "private"])
sort: z.enum(["asc", "desc"]).optional().describe("Return projects sorted in ascending or descending order"), .optional()
with_issues_enabled: z.boolean().optional().describe("Filter projects with issues feature enabled"), .describe("Filter by project visibility"),
with_merge_requests_enabled: z.boolean().optional().describe("Filter projects with merge requests feature enabled"), order_by: z
min_access_level: z.number().optional().describe("Filter by minimum access level"), .enum([
"id",
"name",
"path",
"created_at",
"updated_at",
"last_activity_at",
])
.optional()
.describe("Return projects ordered by field"),
sort: z
.enum(["asc", "desc"])
.optional()
.describe("Return projects sorted in ascending or descending order"),
with_issues_enabled: z
.boolean()
.optional()
.describe("Filter projects with issues feature enabled"),
with_merge_requests_enabled: z
.boolean()
.optional()
.describe("Filter projects with merge requests feature enabled"),
min_access_level: z
.number()
.optional()
.describe("Filter by minimum access level"),
}); });
// Label operation schemas // Label operation schemas
export const ListLabelsSchema = z.object({ export const ListLabelsSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"), project_id: z.string().describe("Project ID or URL-encoded path"),
with_counts: z.boolean().optional().describe("Whether or not to include issue and merge request counts"), with_counts: z
include_ancestor_groups: z.boolean().optional().describe("Include ancestor groups"), .boolean()
.optional()
.describe("Whether or not to include issue and merge request counts"),
include_ancestor_groups: z
.boolean()
.optional()
.describe("Include ancestor groups"),
search: z.string().optional().describe("Keyword to filter labels by"), search: z.string().optional().describe("Keyword to filter labels by"),
}); });
export const GetLabelSchema = z.object({ export const GetLabelSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"), project_id: z.string().describe("Project ID or URL-encoded path"),
label_id: z.union([z.number(), z.string()]).describe("The ID or title of a project's label"), label_id: z.string().describe("The ID or title of a project's label"),
include_ancestor_groups: z.boolean().optional().describe("Include ancestor groups"), include_ancestor_groups: z
.boolean()
.optional()
.describe("Include ancestor groups"),
}); });
export const CreateLabelSchema = z.object({ export const CreateLabelSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"), project_id: z.string().describe("Project ID or URL-encoded path"),
name: z.string().describe("The name of the label"), name: z.string().describe("The name of the label"),
color: z.string().describe("The color of the label given in 6-digit hex notation with leading '#' sign"), color: z
.string()
.describe(
"The color of the label given in 6-digit hex notation with leading '#' sign"
),
description: z.string().optional().describe("The description of the label"), description: z.string().optional().describe("The description of the label"),
priority: z.number().nullable().optional().describe("The priority of the label"), priority: z
.number()
.nullable()
.optional()
.describe("The priority of the label"),
}); });
export const UpdateLabelSchema = z.object({ export const UpdateLabelSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"), project_id: z.string().describe("Project ID or URL-encoded path"),
label_id: z.union([z.number(), z.string()]).describe("The ID or title of a project's label"), label_id: z.string().describe("The ID or title of a project's label"),
new_name: z.string().optional().describe("The new name of the label"), new_name: z.string().optional().describe("The new name of the label"),
color: z.string().optional().describe("The color of the label given in 6-digit hex notation with leading '#' sign"), color: z
description: z.string().optional().describe("The new description of the label"), .string()
priority: z.number().nullable().optional().describe("The new priority of the label"), .optional()
.describe(
"The color of the label given in 6-digit hex notation with leading '#' sign"
),
description: z
.string()
.optional()
.describe("The new description of the label"),
priority: z
.number()
.nullable()
.optional()
.describe("The new priority of the label"),
}); });
export const DeleteLabelSchema = z.object({ export const DeleteLabelSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"), project_id: z.string().describe("Project ID or URL-encoded path"),
label_id: z.union([z.number(), z.string()]).describe("The ID or title of a project's label"), label_id: z.string().describe("The ID or title of a project's label"),
}); });
// Group projects schema // Group projects schema
export const ListGroupProjectsSchema = z.object({ export const ListGroupProjectsSchema = z.object({
group_id: z.string().describe("Group ID or path"), group_id: z.string().describe("Group ID or path"),
include_subgroups: z.boolean().optional().describe("Include projects from subgroups"), include_subgroups: z
.boolean()
.optional()
.describe("Include projects from subgroups"),
search: z.string().optional().describe("Search term to filter projects"), search: z.string().optional().describe("Search term to filter projects"),
order_by: z.enum(['name', 'path', 'created_at', 'updated_at', 'last_activity_at']).optional().describe("Field to sort by"), order_by: z
sort: z.enum(['asc', 'desc']).optional().describe("Sort direction"), .enum(["name", "path", "created_at", "updated_at", "last_activity_at"])
.optional()
.describe("Field to sort by"),
sort: z.enum(["asc", "desc"]).optional().describe("Sort direction"),
page: z.number().optional().describe("Page number"), page: z.number().optional().describe("Page number"),
per_page: z.number().optional().describe("Number of results per page"), per_page: z.number().optional().describe("Number of results per page"),
archived: z.boolean().optional().describe("Filter for archived projects"), archived: z.boolean().optional().describe("Filter for archived projects"),
visibility: z.enum(["public", "internal", "private"]).optional().describe("Filter by project visibility"), visibility: z
with_issues_enabled: z.boolean().optional().describe("Filter projects with issues feature enabled"), .enum(["public", "internal", "private"])
with_merge_requests_enabled: z.boolean().optional().describe("Filter projects with merge requests feature enabled"), .optional()
min_access_level: z.number().optional().describe("Filter by minimum access level"), .describe("Filter by project visibility"),
with_programming_language: z.string().optional().describe("Filter by programming language"), with_issues_enabled: z
.boolean()
.optional()
.describe("Filter projects with issues feature enabled"),
with_merge_requests_enabled: z
.boolean()
.optional()
.describe("Filter projects with merge requests feature enabled"),
min_access_level: z
.number()
.optional()
.describe("Filter by minimum access level"),
with_programming_language: z
.string()
.optional()
.describe("Filter by programming language"),
starred: z.boolean().optional().describe("Filter by starred projects"), starred: z.boolean().optional().describe("Filter by starred projects"),
statistics: z.boolean().optional().describe("Include project statistics"), statistics: z.boolean().optional().describe("Include project statistics"),
with_custom_attributes: z.boolean().optional().describe("Include custom attributes"), with_custom_attributes: z
with_security_reports: z.boolean().optional().describe("Include security reports") .boolean()
.optional()
.describe("Include custom attributes"),
with_security_reports: z
.boolean()
.optional()
.describe("Include security reports"),
}); });
// Add wiki operation schemas // Add wiki operation schemas
@ -770,14 +974,20 @@ export const CreateWikiPageSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"), project_id: z.string().describe("Project ID or URL-encoded path"),
title: z.string().describe("Title of the wiki page"), title: z.string().describe("Title of the wiki page"),
content: z.string().describe("Content of the wiki page"), content: z.string().describe("Content of the wiki page"),
format: z.string().optional().describe("Content format, e.g., markdown, rdoc"), format: z
.string()
.optional()
.describe("Content format, e.g., markdown, rdoc"),
}); });
export const UpdateWikiPageSchema = z.object({ export const UpdateWikiPageSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"), project_id: z.string().describe("Project ID or URL-encoded path"),
slug: z.string().describe("URL-encoded slug of the wiki page"), slug: z.string().describe("URL-encoded slug of the wiki page"),
title: z.string().optional().describe("New title of the wiki page"), title: z.string().optional().describe("New title of the wiki page"),
content: z.string().optional().describe("New content of the wiki page"), content: z.string().optional().describe("New content of the wiki page"),
format: z.string().optional().describe("Content format, e.g., markdown, rdoc"), format: z
.string()
.optional()
.describe("Content format, e.g., markdown, rdoc"),
}); });
export const DeleteWikiPageSchema = z.object({ export const DeleteWikiPageSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"), project_id: z.string().describe("Project ID or URL-encoded path"),
@ -798,27 +1008,41 @@ export const GitLabWikiPageSchema = z.object({
export type GitLabAuthor = z.infer<typeof GitLabAuthorSchema>; export type GitLabAuthor = z.infer<typeof GitLabAuthorSchema>;
export type GitLabFork = z.infer<typeof GitLabForkSchema>; export type GitLabFork = z.infer<typeof GitLabForkSchema>;
export type GitLabIssue = z.infer<typeof GitLabIssueSchema>; export type GitLabIssue = z.infer<typeof GitLabIssueSchema>;
export type GitLabIssueWithLinkDetails = z.infer<typeof GitLabIssueWithLinkDetailsSchema>; export type GitLabIssueWithLinkDetails = z.infer<
typeof GitLabIssueWithLinkDetailsSchema
>;
export type GitLabMergeRequest = z.infer<typeof GitLabMergeRequestSchema>; export type GitLabMergeRequest = z.infer<typeof GitLabMergeRequestSchema>;
export type GitLabRepository = z.infer<typeof GitLabRepositorySchema>; export type GitLabRepository = z.infer<typeof GitLabRepositorySchema>;
export type GitLabFileContent = z.infer<typeof GitLabFileContentSchema>; export type GitLabFileContent = z.infer<typeof GitLabFileContentSchema>;
export type GitLabDirectoryContent = z.infer<typeof GitLabDirectoryContentSchema>; export type GitLabDirectoryContent = z.infer<
typeof GitLabDirectoryContentSchema
>;
export type GitLabContent = z.infer<typeof GitLabContentSchema>; export type GitLabContent = z.infer<typeof GitLabContentSchema>;
export type FileOperation = z.infer<typeof FileOperationSchema>; export type FileOperation = z.infer<typeof FileOperationSchema>;
export type GitLabTree = z.infer<typeof GitLabTreeSchema>; export type GitLabTree = z.infer<typeof GitLabTreeSchema>;
export type GitLabCommit = z.infer<typeof GitLabCommitSchema>; export type GitLabCommit = z.infer<typeof GitLabCommitSchema>;
export type GitLabReference = z.infer<typeof GitLabReferenceSchema>; export type GitLabReference = z.infer<typeof GitLabReferenceSchema>;
export type CreateRepositoryOptions = z.infer<typeof CreateRepositoryOptionsSchema>; export type CreateRepositoryOptions = z.infer<
typeof CreateRepositoryOptionsSchema
>;
export type CreateIssueOptions = z.infer<typeof CreateIssueOptionsSchema>; export type CreateIssueOptions = z.infer<typeof CreateIssueOptionsSchema>;
export type CreateMergeRequestOptions = z.infer<typeof CreateMergeRequestOptionsSchema>; export type CreateMergeRequestOptions = z.infer<
typeof CreateMergeRequestOptionsSchema
>;
export type CreateBranchOptions = z.infer<typeof CreateBranchOptionsSchema>; export type CreateBranchOptions = z.infer<typeof CreateBranchOptionsSchema>;
export type GitLabCreateUpdateFileResponse = z.infer<typeof GitLabCreateUpdateFileResponseSchema>; export type GitLabCreateUpdateFileResponse = z.infer<
typeof GitLabCreateUpdateFileResponseSchema
>;
export type GitLabSearchResponse = z.infer<typeof GitLabSearchResponseSchema>; export type GitLabSearchResponse = z.infer<typeof GitLabSearchResponseSchema>;
export type GitLabMergeRequestDiff = z.infer<typeof GitLabMergeRequestDiffSchema>; export type GitLabMergeRequestDiff = z.infer<
typeof GitLabMergeRequestDiffSchema
>;
export type CreateNoteOptions = z.infer<typeof CreateNoteSchema>; export type CreateNoteOptions = z.infer<typeof CreateNoteSchema>;
export type GitLabIssueLink = z.infer<typeof GitLabIssueLinkSchema>; export type GitLabIssueLink = z.infer<typeof GitLabIssueLinkSchema>;
export type GitLabNamespace = z.infer<typeof GitLabNamespaceSchema>; export type GitLabNamespace = z.infer<typeof GitLabNamespaceSchema>;
export type GitLabNamespaceExistsResponse = z.infer<typeof GitLabNamespaceExistsResponseSchema>; export type GitLabNamespaceExistsResponse = z.infer<
typeof GitLabNamespaceExistsResponseSchema
>;
export type GitLabProject = z.infer<typeof GitLabProjectSchema>; export type GitLabProject = z.infer<typeof GitLabProjectSchema>;
export type GitLabLabel = z.infer<typeof GitLabLabelSchema>; export type GitLabLabel = z.infer<typeof GitLabLabelSchema>;
export type ListWikiPagesOptions = z.infer<typeof ListWikiPagesSchema>; export type ListWikiPagesOptions = z.infer<typeof ListWikiPagesSchema>;
@ -827,3 +1051,5 @@ export type CreateWikiPageOptions = z.infer<typeof CreateWikiPageSchema>;
export type UpdateWikiPageOptions = z.infer<typeof UpdateWikiPageSchema>; export type UpdateWikiPageOptions = z.infer<typeof UpdateWikiPageSchema>;
export type DeleteWikiPageOptions = z.infer<typeof DeleteWikiPageSchema>; export type DeleteWikiPageOptions = z.infer<typeof DeleteWikiPageSchema>;
export type GitLabWikiPage = z.infer<typeof GitLabWikiPageSchema>; export type GitLabWikiPage = z.infer<typeof GitLabWikiPageSchema>;
export type GitLabTreeItem = z.infer<typeof GitLabTreeItemSchema>;
export type GetRepositoryTreeOptions = z.infer<typeof GetRepositoryTreeSchema>;