Add GitLab Issue Links API support
This commit is contained in:
175
index.ts
175
index.ts
@ -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}`);
|
||||||
}
|
}
|
||||||
|
36
schemas.ts
36
schemas.ts
@ -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>;
|
||||||
|
Reference in New Issue
Block a user