Add GitLab Issue Links API support

This commit is contained in:
Admin
2025-03-18 01:07:12 -07:00
parent 1927a23684
commit 48d33ae990
2 changed files with 210 additions and 1 deletions

175
index.ts
View File

@ -45,6 +45,12 @@ import {
GetIssueSchema, GetIssueSchema,
UpdateIssueSchema, UpdateIssueSchema,
DeleteIssueSchema, DeleteIssueSchema,
GitLabIssueLinkSchema,
ListIssueLinksSchema,
GetIssueLinkSchema,
CreateIssueLinkSchema,
DeleteIssueLinkSchema,
CreateNoteSchema,
type GitLabFork, type GitLabFork,
type GitLabReference, type GitLabReference,
type GitLabRepository, type GitLabRepository,
@ -57,7 +63,7 @@ import {
type GitLabCommit, type GitLabCommit,
type FileOperation, type FileOperation,
type GitLabMergeRequestDiff, type GitLabMergeRequestDiff,
CreateNoteSchema, type GitLabIssueLink,
} from "./schemas.js"; } from "./schemas.js";
/** /**
@ -457,6 +463,121 @@ async function deleteIssue(
await handleGitLabError(response); await handleGitLabError(response);
} }
/**
* List all issue links for a specific issue
* 이슈 관계 목록 조회
*
* @param {string} projectId - The ID or URL-encoded path of the project
* @param {number} issueIid - The internal ID of the project issue
* @returns {Promise<GitLabIssueLink[]>} List of issue links
*/
async function listIssueLinks(
projectId: string,
issueIid: number
): Promise<GitLabIssueLink[]> {
const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}/links`
);
const response = await fetch(url.toString(), {
headers: DEFAULT_HEADERS,
});
await handleGitLabError(response);
const data = await response.json();
return z.array(GitLabIssueLinkSchema).parse(data);
}
/**
* Get a specific issue link
* 특정 이슈 관계 조회
*
* @param {string} projectId - The ID or URL-encoded path of the project
* @param {number} issueIid - The internal ID of the project issue
* @param {number} issueLinkId - The ID of the issue link
* @returns {Promise<GitLabIssueLink>} The issue link
*/
async function getIssueLink(
projectId: string,
issueIid: number,
issueLinkId: number
): Promise<GitLabIssueLink> {
const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}/links/${issueLinkId}`
);
const response = await fetch(url.toString(), {
headers: DEFAULT_HEADERS,
});
await handleGitLabError(response);
const data = await response.json();
return GitLabIssueLinkSchema.parse(data);
}
/**
* Create an issue link between two issues
* 이슈 관계 생성
*
* @param {string} projectId - The ID or URL-encoded path of the project
* @param {number} issueIid - The internal ID of the project issue
* @param {string} targetProjectId - The ID or URL-encoded path of the target project
* @param {number} targetIssueIid - The internal ID of the target project issue
* @param {string} linkType - The type of the relation (relates_to, blocks, is_blocked_by)
* @returns {Promise<GitLabIssueLink>} The created issue link
*/
async function createIssueLink(
projectId: string,
issueIid: number,
targetProjectId: string,
targetIssueIid: number,
linkType: 'relates_to' | 'blocks' | 'is_blocked_by' = 'relates_to'
): Promise<GitLabIssueLink> {
const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}/links`
);
const response = await fetch(url.toString(), {
method: "POST",
headers: DEFAULT_HEADERS,
body: JSON.stringify({
target_project_id: targetProjectId,
target_issue_iid: targetIssueIid,
link_type: linkType
}),
});
await handleGitLabError(response);
const data = await response.json();
return GitLabIssueLinkSchema.parse(data);
}
/**
* Delete an issue link
* 이슈 관계 삭제
*
* @param {string} projectId - The ID or URL-encoded path of the project
* @param {number} issueIid - The internal ID of the project issue
* @param {number} issueLinkId - The ID of the issue link
* @returns {Promise<void>}
*/
async function deleteIssueLink(
projectId: string,
issueIid: number,
issueLinkId: number
): Promise<void> {
const url = new URL(
`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues/${issueIid}/links/${issueLinkId}`
);
const response = await fetch(url.toString(), {
method: "DELETE",
headers: DEFAULT_HEADERS,
});
await handleGitLabError(response);
}
/** /**
* Create a new merge request in a GitLab project * Create a new merge request in a GitLab project
* 병합 요청 생성 * 병합 요청 생성
@ -1027,6 +1148,26 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
description: "Delete an issue from a GitLab project", description: "Delete an issue from a GitLab project",
inputSchema: zodToJsonSchema(DeleteIssueSchema), inputSchema: zodToJsonSchema(DeleteIssueSchema),
}, },
{
name: "list_issue_links",
description: "List all issue links for a specific issue",
inputSchema: zodToJsonSchema(ListIssueLinksSchema),
},
{
name: "get_issue_link",
description: "Get a specific issue link",
inputSchema: zodToJsonSchema(GetIssueLinkSchema),
},
{
name: "create_issue_link",
description: "Create an issue link between two issues",
inputSchema: zodToJsonSchema(CreateIssueLinkSchema),
},
{
name: "delete_issue_link",
description: "Delete an issue link",
inputSchema: zodToJsonSchema(DeleteIssueLinkSchema),
},
], ],
}; };
}); });
@ -1247,6 +1388,38 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
}; };
} }
case "list_issue_links": {
const args = ListIssueLinksSchema.parse(request.params.arguments);
const links = await listIssueLinks(args.project_id, args.issue_iid);
return {
content: [{ type: "text", text: JSON.stringify(links, 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);
return {
content: [{ type: "text", text: JSON.stringify(link, null, 2) }],
};
}
case "create_issue_link": {
const args = CreateIssueLinkSchema.parse(request.params.arguments);
const link = await createIssueLink(args.project_id, args.issue_iid, args.target_project_id, args.target_issue_iid, args.link_type);
return {
content: [{ type: "text", text: JSON.stringify(link, null, 2) }],
};
}
case "delete_issue_link": {
const args = DeleteIssueLinkSchema.parse(request.params.arguments);
await deleteIssueLink(args.project_id, args.issue_iid, args.issue_link_id);
return {
content: [{ type: "text", text: JSON.stringify({ status: "success", message: "Issue link deleted successfully" }, null, 2) }],
};
}
default: default:
throw new Error(`Unknown tool: ${request.params.name}`); throw new Error(`Unknown tool: ${request.params.name}`);
} }

View File

@ -470,6 +470,41 @@ export const CreateNoteSchema = z.object({
body: z.string().describe("Note content"), body: z.string().describe("Note content"),
}); });
// Issue links related schemas
export const GitLabIssueLinkSchema = z.object({
id: z.number(),
link_type: z.enum(['relates_to', 'blocks', 'is_blocked_by']),
source_issue: GitLabIssueSchema,
target_issue: GitLabIssueSchema,
link_created_at: z.string().optional(),
link_updated_at: z.string().optional(),
});
export const ListIssueLinksSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
issue_iid: z.number().describe("The internal ID of a project's issue"),
});
export const GetIssueLinkSchema = z.object({
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_link_id: z.number().describe("ID of an issue relationship"),
});
export const CreateIssueLinkSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
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_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({
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_link_id: z.number().describe("The ID of an issue relationship"),
});
// Export types // Export types
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>;
@ -501,3 +536,4 @@ export type GitLabMergeRequestDiff = z.infer<
typeof GitLabMergeRequestDiffSchema typeof GitLabMergeRequestDiffSchema
>; >;
export type CreateNoteOptions = z.infer<typeof CreateNoteSchema>; export type CreateNoteOptions = z.infer<typeof CreateNoteSchema>;
export type GitLabIssueLink = z.infer<typeof GitLabIssueLinkSchema>;