Merge pull request #26 from zereight/feature/fix-note
Add schemas for GitLab discussion notes and merge request discussions
This commit is contained in:
@ -9,7 +9,11 @@ import { fileURLToPath } from "url";
|
||||
import { dirname } from "path";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { GitLabForkSchema, GitLabReferenceSchema, GitLabRepositorySchema, GitLabIssueSchema, GitLabMergeRequestSchema, GitLabContentSchema, GitLabCreateUpdateFileResponseSchema, GitLabSearchResponseSchema, GitLabTreeSchema, GitLabCommitSchema, GitLabNamespaceSchema, GitLabNamespaceExistsResponseSchema, GitLabProjectSchema, CreateOrUpdateFileSchema, SearchRepositoriesSchema, CreateRepositorySchema, GetFileContentsSchema, PushFilesSchema, CreateIssueSchema, CreateMergeRequestSchema, ForkRepositorySchema, CreateBranchSchema, GitLabMergeRequestDiffSchema, GetMergeRequestSchema, GetMergeRequestDiffsSchema, UpdateMergeRequestSchema, ListIssuesSchema, GetIssueSchema, UpdateIssueSchema, DeleteIssueSchema, GitLabIssueLinkSchema, GitLabIssueWithLinkDetailsSchema, ListIssueLinksSchema, GetIssueLinkSchema, CreateIssueLinkSchema, DeleteIssueLinkSchema, ListNamespacesSchema, GetNamespaceSchema, VerifyNamespaceSchema, GetProjectSchema, ListProjectsSchema, ListLabelsSchema, GetLabelSchema, CreateLabelSchema, UpdateLabelSchema, DeleteLabelSchema, CreateNoteSchema, ListGroupProjectsSchema, } from "./schemas.js";
|
||||
import { GitLabForkSchema, GitLabReferenceSchema, GitLabRepositorySchema, GitLabIssueSchema, GitLabMergeRequestSchema, GitLabContentSchema, GitLabCreateUpdateFileResponseSchema, GitLabSearchResponseSchema, GitLabTreeSchema, GitLabCommitSchema, GitLabNamespaceSchema, GitLabNamespaceExistsResponseSchema, GitLabProjectSchema, CreateOrUpdateFileSchema, SearchRepositoriesSchema, CreateRepositorySchema, GetFileContentsSchema, PushFilesSchema, CreateIssueSchema, CreateMergeRequestSchema, ForkRepositorySchema, CreateBranchSchema, GitLabMergeRequestDiffSchema, GetMergeRequestSchema, GetMergeRequestDiffsSchema, UpdateMergeRequestSchema, ListIssuesSchema, GetIssueSchema, UpdateIssueSchema, DeleteIssueSchema, GitLabIssueLinkSchema, GitLabIssueWithLinkDetailsSchema, ListIssueLinksSchema, GetIssueLinkSchema, CreateIssueLinkSchema, DeleteIssueLinkSchema, ListNamespacesSchema, GetNamespaceSchema, VerifyNamespaceSchema, GetProjectSchema, ListProjectsSchema, ListLabelsSchema, GetLabelSchema, CreateLabelSchema, UpdateLabelSchema, DeleteLabelSchema, CreateNoteSchema, ListGroupProjectsSchema,
|
||||
// Discussion Schemas
|
||||
GitLabDiscussionNoteSchema, // Added
|
||||
GitLabDiscussionSchema, UpdateMergeRequestNoteSchema, // Added
|
||||
ListMergeRequestDiscussionsSchema, } from "./schemas.js";
|
||||
/**
|
||||
* Read version from package.json
|
||||
*/
|
||||
@ -417,6 +421,51 @@ async function createMergeRequest(projectId, options) {
|
||||
const data = await response.json();
|
||||
return GitLabMergeRequestSchema.parse(data);
|
||||
}
|
||||
/**
|
||||
* List merge request discussion items
|
||||
* 병합 요청 토론 목록 조회
|
||||
*
|
||||
* @param {string} projectId - The ID or URL-encoded path of the project
|
||||
* @param {number} mergeRequestIid - The IID of a merge request
|
||||
* @returns {Promise<GitLabDiscussion[]>} List of discussions
|
||||
*/
|
||||
async function listMergeRequestDiscussions(projectId, mergeRequestIid) {
|
||||
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}/discussions`);
|
||||
const response = await fetch(url.toString(), {
|
||||
headers: DEFAULT_HEADERS,
|
||||
});
|
||||
await handleGitLabError(response);
|
||||
const data = await response.json();
|
||||
// Ensure the response is parsed as an array of discussions
|
||||
return z.array(GitLabDiscussionSchema).parse(data);
|
||||
}
|
||||
/**
|
||||
* Modify an existing merge request thread note
|
||||
* 병합 요청 토론 노트 수정
|
||||
*
|
||||
* @param {string} projectId - The ID or URL-encoded path of the project
|
||||
* @param {number} mergeRequestIid - The IID of a merge request
|
||||
* @param {string} discussionId - The ID of a thread
|
||||
* @param {number} noteId - The ID of a thread note
|
||||
* @param {string} body - The new content of the note
|
||||
* @param {boolean} [resolved] - Resolve/unresolve state
|
||||
* @returns {Promise<GitLabDiscussionNote>} The updated note
|
||||
*/
|
||||
async function updateMergeRequestNote(projectId, mergeRequestIid, discussionId, noteId, body, resolved) {
|
||||
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}/discussions/${discussionId}/notes/${noteId}`);
|
||||
const payload = { body };
|
||||
if (resolved !== undefined) {
|
||||
payload.resolved = resolved;
|
||||
}
|
||||
const response = await fetch(url.toString(), {
|
||||
method: "PUT",
|
||||
headers: DEFAULT_HEADERS,
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
await handleGitLabError(response);
|
||||
const data = await response.json();
|
||||
return GitLabDiscussionNoteSchema.parse(data);
|
||||
}
|
||||
/**
|
||||
* Create or update a file in a GitLab project
|
||||
* 파일 생성 또는 업데이트
|
||||
@ -1077,6 +1126,16 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
description: "Create a new note (comment) to an issue or merge request",
|
||||
inputSchema: zodToJsonSchema(CreateNoteSchema),
|
||||
},
|
||||
{
|
||||
name: "list_merge_request_discussions",
|
||||
description: "List discussion items for a merge request",
|
||||
inputSchema: zodToJsonSchema(ListMergeRequestDiscussionsSchema),
|
||||
},
|
||||
{
|
||||
name: "update_merge_request_note",
|
||||
description: "Modify an existing merge request thread note",
|
||||
inputSchema: zodToJsonSchema(UpdateMergeRequestNoteSchema),
|
||||
},
|
||||
{
|
||||
name: "list_issues",
|
||||
description: "List issues in a GitLab project with filtering options",
|
||||
@ -1269,6 +1328,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
],
|
||||
};
|
||||
}
|
||||
case "update_merge_request_note": {
|
||||
const args = UpdateMergeRequestNoteSchema.parse(request.params.arguments);
|
||||
const note = await updateMergeRequestNote(args.project_id, args.merge_request_iid, args.discussion_id, args.note_id, args.body, args.resolved // Pass resolved if provided
|
||||
);
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(note, null, 2) }],
|
||||
};
|
||||
}
|
||||
case "get_merge_request": {
|
||||
const args = GetMergeRequestSchema.parse(request.params.arguments);
|
||||
const mergeRequest = await getMergeRequest(args.project_id, args.merge_request_iid);
|
||||
@ -1295,6 +1362,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
],
|
||||
};
|
||||
}
|
||||
case "list_merge_request_discussions": {
|
||||
const args = ListMergeRequestDiscussionsSchema.parse(request.params.arguments);
|
||||
const discussions = await listMergeRequestDiscussions(args.project_id, args.merge_request_iid);
|
||||
return {
|
||||
content: [
|
||||
{ type: "text", text: JSON.stringify(discussions, null, 2) },
|
||||
],
|
||||
};
|
||||
}
|
||||
case "list_namespaces": {
|
||||
const args = ListNamespacesSchema.parse(request.params.arguments);
|
||||
const url = new URL(`${GITLAB_API_URL}/namespaces`);
|
||||
|
@ -6,6 +6,10 @@ export const GitLabAuthorSchema = z.object({
|
||||
date: z.string(),
|
||||
});
|
||||
// Namespace related schemas
|
||||
// Base schema for project-related operations
|
||||
const ProjectParamsSchema = z.object({
|
||||
project_id: z.string().describe("Project ID or URL-encoded path"), // Changed from owner/repo to match GitLab API
|
||||
});
|
||||
export const GitLabNamespaceSchema = z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
@ -324,10 +328,71 @@ export const GitLabMergeRequestSchema = z.object({
|
||||
squash: z.boolean().optional(),
|
||||
labels: z.array(z.string()).optional(),
|
||||
});
|
||||
// API Operation Parameter Schemas
|
||||
const ProjectParamsSchema = z.object({
|
||||
project_id: z.string().describe("Project ID or URL-encoded path"), // Changed from owner/repo to match GitLab API
|
||||
// Discussion related schemas
|
||||
export const GitLabDiscussionNoteSchema = z.object({
|
||||
id: z.number(),
|
||||
type: z.enum(["DiscussionNote", "DiffNote", "Note"]).nullable(), // Allow null type for regular notes
|
||||
body: z.string(),
|
||||
attachment: z.any().nullable(), // Can be string or object, handle appropriately
|
||||
author: GitLabUserSchema,
|
||||
created_at: z.string(),
|
||||
updated_at: z.string(),
|
||||
system: z.boolean(),
|
||||
noteable_id: z.number(),
|
||||
noteable_type: z.enum(["Issue", "MergeRequest", "Snippet", "Commit", "Epic"]),
|
||||
project_id: z.number().optional(), // Optional for group-level discussions like Epics
|
||||
noteable_iid: z.number().nullable(),
|
||||
resolvable: z.boolean().optional(),
|
||||
resolved: z.boolean().optional(),
|
||||
resolved_by: GitLabUserSchema.nullable().optional(),
|
||||
resolved_at: z.string().nullable().optional(),
|
||||
position: z.object({
|
||||
base_sha: z.string(),
|
||||
start_sha: z.string(),
|
||||
head_sha: z.string(),
|
||||
old_path: z.string(),
|
||||
new_path: z.string(),
|
||||
position_type: z.enum(["text", "image", "file"]),
|
||||
old_line: z.number().nullable(),
|
||||
new_line: z.number().nullable(),
|
||||
line_range: z.object({
|
||||
start: z.object({
|
||||
line_code: z.string(),
|
||||
type: z.enum(["new", "old"]),
|
||||
old_line: z.number().nullable(),
|
||||
new_line: z.number().nullable(),
|
||||
}),
|
||||
end: z.object({
|
||||
line_code: z.string(),
|
||||
type: z.enum(["new", "old"]),
|
||||
old_line: z.number().nullable(),
|
||||
new_line: z.number().nullable(),
|
||||
}),
|
||||
}).nullable().optional(), // For multi-line diff notes
|
||||
width: z.number().optional(), // For image diff notes
|
||||
height: z.number().optional(), // For image diff notes
|
||||
x: z.number().optional(), // For image diff notes
|
||||
y: z.number().optional(), // For image diff notes
|
||||
}).optional(),
|
||||
});
|
||||
export const GitLabDiscussionSchema = z.object({
|
||||
id: z.string(),
|
||||
individual_note: z.boolean(),
|
||||
notes: z.array(GitLabDiscussionNoteSchema),
|
||||
});
|
||||
// Input schema for listing merge request discussions
|
||||
export const ListMergeRequestDiscussionsSchema = ProjectParamsSchema.extend({
|
||||
merge_request_iid: z.number().describe("The IID of a merge request"),
|
||||
});
|
||||
// Input schema for updating a merge request discussion note
|
||||
export const UpdateMergeRequestNoteSchema = ProjectParamsSchema.extend({
|
||||
merge_request_iid: z.number().describe("The IID of a merge request"),
|
||||
discussion_id: z.string().describe("The ID of a thread"),
|
||||
note_id: z.number().describe("The ID of a thread note"),
|
||||
body: z.string().describe("The content of the note or reply"),
|
||||
resolved: z.boolean().optional().describe("Resolve or unresolve the note"), // Optional based on API docs
|
||||
});
|
||||
// API Operation Parameter Schemas
|
||||
export const CreateOrUpdateFileSchema = ProjectParamsSchema.extend({
|
||||
file_path: z.string().describe("Path where to create/update the file"),
|
||||
content: z.string().describe("Content of the file"),
|
||||
|
116
index.ts
116
index.ts
@ -67,6 +67,11 @@ import {
|
||||
DeleteLabelSchema,
|
||||
CreateNoteSchema,
|
||||
ListGroupProjectsSchema,
|
||||
// Discussion Schemas
|
||||
GitLabDiscussionNoteSchema, // Added
|
||||
GitLabDiscussionSchema,
|
||||
UpdateMergeRequestNoteSchema, // Added
|
||||
ListMergeRequestDiscussionsSchema,
|
||||
type GitLabFork,
|
||||
type GitLabReference,
|
||||
type GitLabRepository,
|
||||
@ -85,6 +90,9 @@ import {
|
||||
type GitLabNamespaceExistsResponse,
|
||||
type GitLabProject,
|
||||
type GitLabLabel,
|
||||
// Discussion Types
|
||||
type GitLabDiscussionNote, // Added
|
||||
type GitLabDiscussion,
|
||||
} from "./schemas.js";
|
||||
|
||||
/**
|
||||
@ -650,6 +658,76 @@ async function createMergeRequest(
|
||||
return GitLabMergeRequestSchema.parse(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* List merge request discussion items
|
||||
* 병합 요청 토론 목록 조회
|
||||
*
|
||||
* @param {string} projectId - The ID or URL-encoded path of the project
|
||||
* @param {number} mergeRequestIid - The IID of a merge request
|
||||
* @returns {Promise<GitLabDiscussion[]>} List of discussions
|
||||
*/
|
||||
async function listMergeRequestDiscussions(
|
||||
projectId: string,
|
||||
mergeRequestIid: number
|
||||
): Promise<GitLabDiscussion[]> {
|
||||
const url = new URL(
|
||||
`${GITLAB_API_URL}/projects/${encodeURIComponent(
|
||||
projectId
|
||||
)}/merge_requests/${mergeRequestIid}/discussions`
|
||||
);
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
headers: DEFAULT_HEADERS,
|
||||
});
|
||||
|
||||
await handleGitLabError(response);
|
||||
const data = await response.json();
|
||||
// Ensure the response is parsed as an array of discussions
|
||||
return z.array(GitLabDiscussionSchema).parse(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify an existing merge request thread note
|
||||
* 병합 요청 토론 노트 수정
|
||||
*
|
||||
* @param {string} projectId - The ID or URL-encoded path of the project
|
||||
* @param {number} mergeRequestIid - The IID of a merge request
|
||||
* @param {string} discussionId - The ID of a thread
|
||||
* @param {number} noteId - The ID of a thread note
|
||||
* @param {string} body - The new content of the note
|
||||
* @param {boolean} [resolved] - Resolve/unresolve state
|
||||
* @returns {Promise<GitLabDiscussionNote>} The updated note
|
||||
*/
|
||||
async function updateMergeRequestNote(
|
||||
projectId: string,
|
||||
mergeRequestIid: number,
|
||||
discussionId: string,
|
||||
noteId: number,
|
||||
body: string,
|
||||
resolved?: boolean
|
||||
): Promise<GitLabDiscussionNote> {
|
||||
const url = new URL(
|
||||
`${GITLAB_API_URL}/projects/${encodeURIComponent(
|
||||
projectId
|
||||
)}/merge_requests/${mergeRequestIid}/discussions/${discussionId}/notes/${noteId}`
|
||||
);
|
||||
|
||||
const payload: { body: string; resolved?: boolean } = { body };
|
||||
if (resolved !== undefined) {
|
||||
payload.resolved = resolved;
|
||||
}
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
method: "PUT",
|
||||
headers: DEFAULT_HEADERS,
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
await handleGitLabError(response);
|
||||
const data = await response.json();
|
||||
return GitLabDiscussionNoteSchema.parse(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update a file in a GitLab project
|
||||
* 파일 생성 또는 업데이트
|
||||
@ -1507,6 +1585,16 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
description: "Create a new note (comment) to an issue or merge request",
|
||||
inputSchema: zodToJsonSchema(CreateNoteSchema),
|
||||
},
|
||||
{
|
||||
name: "list_merge_request_discussions",
|
||||
description: "List discussion items for a merge request",
|
||||
inputSchema: zodToJsonSchema(ListMergeRequestDiscussionsSchema),
|
||||
},
|
||||
{
|
||||
name: "update_merge_request_note",
|
||||
description: "Modify an existing merge request thread note",
|
||||
inputSchema: zodToJsonSchema(UpdateMergeRequestNoteSchema),
|
||||
},
|
||||
{
|
||||
name: "list_issues",
|
||||
description: "List issues in a GitLab project with filtering options",
|
||||
@ -1733,6 +1821,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
};
|
||||
}
|
||||
|
||||
case "update_merge_request_note": {
|
||||
const args = UpdateMergeRequestNoteSchema.parse(request.params.arguments);
|
||||
const note = await updateMergeRequestNote(
|
||||
args.project_id,
|
||||
args.merge_request_iid,
|
||||
args.discussion_id,
|
||||
args.note_id,
|
||||
args.body,
|
||||
args.resolved // Pass resolved if provided
|
||||
);
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(note, null, 2) }],
|
||||
};
|
||||
}
|
||||
|
||||
case "get_merge_request": {
|
||||
const args = GetMergeRequestSchema.parse(request.params.arguments);
|
||||
const mergeRequest = await getMergeRequest(
|
||||
@ -1773,6 +1876,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
};
|
||||
}
|
||||
|
||||
case "list_merge_request_discussions": {
|
||||
const args = ListMergeRequestDiscussionsSchema.parse(request.params.arguments);
|
||||
const discussions = await listMergeRequestDiscussions(
|
||||
args.project_id,
|
||||
args.merge_request_iid
|
||||
);
|
||||
return {
|
||||
content: [
|
||||
{ type: "text", text: JSON.stringify(discussions, null, 2) },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
case "list_namespaces": {
|
||||
const args = ListNamespacesSchema.parse(request.params.arguments);
|
||||
const url = new URL(`${GITLAB_API_URL}/namespaces`);
|
||||
|
78
schemas.ts
78
schemas.ts
@ -8,6 +8,11 @@ export const GitLabAuthorSchema = z.object({
|
||||
});
|
||||
|
||||
// Namespace related schemas
|
||||
|
||||
// Base schema for project-related operations
|
||||
const ProjectParamsSchema = z.object({
|
||||
project_id: z.string().describe("Project ID or URL-encoded path"), // Changed from owner/repo to match GitLab API
|
||||
});
|
||||
export const GitLabNamespaceSchema = z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
@ -354,10 +359,77 @@ export const GitLabMergeRequestSchema = z.object({
|
||||
labels: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
// API Operation Parameter Schemas
|
||||
const ProjectParamsSchema = z.object({
|
||||
project_id: z.string().describe("Project ID or URL-encoded path"), // Changed from owner/repo to match GitLab API
|
||||
// Discussion related schemas
|
||||
export const GitLabDiscussionNoteSchema = z.object({
|
||||
id: z.number(),
|
||||
type: z.enum(["DiscussionNote", "DiffNote", "Note"]).nullable(), // Allow null type for regular notes
|
||||
body: z.string(),
|
||||
attachment: z.any().nullable(), // Can be string or object, handle appropriately
|
||||
author: GitLabUserSchema,
|
||||
created_at: z.string(),
|
||||
updated_at: z.string(),
|
||||
system: z.boolean(),
|
||||
noteable_id: z.number(),
|
||||
noteable_type: z.enum(["Issue", "MergeRequest", "Snippet", "Commit", "Epic"]),
|
||||
project_id: z.number().optional(), // Optional for group-level discussions like Epics
|
||||
noteable_iid: z.number().nullable(),
|
||||
resolvable: z.boolean().optional(),
|
||||
resolved: z.boolean().optional(),
|
||||
resolved_by: GitLabUserSchema.nullable().optional(),
|
||||
resolved_at: z.string().nullable().optional(),
|
||||
position: z.object({ // Only present for DiffNote
|
||||
base_sha: z.string(),
|
||||
start_sha: z.string(),
|
||||
head_sha: z.string(),
|
||||
old_path: z.string(),
|
||||
new_path: z.string(),
|
||||
position_type: z.enum(["text", "image", "file"]),
|
||||
old_line: z.number().nullable(),
|
||||
new_line: z.number().nullable(),
|
||||
line_range: z.object({
|
||||
start: z.object({
|
||||
line_code: z.string(),
|
||||
type: z.enum(["new", "old"]),
|
||||
old_line: z.number().nullable(),
|
||||
new_line: z.number().nullable(),
|
||||
}),
|
||||
end: z.object({
|
||||
line_code: z.string(),
|
||||
type: z.enum(["new", "old"]),
|
||||
old_line: z.number().nullable(),
|
||||
new_line: z.number().nullable(),
|
||||
}),
|
||||
}).nullable().optional(), // For multi-line diff notes
|
||||
width: z.number().optional(), // For image diff notes
|
||||
height: z.number().optional(), // For image diff notes
|
||||
x: z.number().optional(), // For image diff notes
|
||||
y: z.number().optional(), // For image diff notes
|
||||
}).optional(),
|
||||
});
|
||||
export type GitLabDiscussionNote = z.infer<typeof GitLabDiscussionNoteSchema>;
|
||||
|
||||
export const GitLabDiscussionSchema = z.object({
|
||||
id: z.string(),
|
||||
individual_note: z.boolean(),
|
||||
notes: z.array(GitLabDiscussionNoteSchema),
|
||||
});
|
||||
export type GitLabDiscussion = z.infer<typeof GitLabDiscussionSchema>;
|
||||
|
||||
// Input schema for listing merge request discussions
|
||||
export const ListMergeRequestDiscussionsSchema = ProjectParamsSchema.extend({
|
||||
merge_request_iid: z.number().describe("The IID of a merge request"),
|
||||
});
|
||||
|
||||
// Input schema for updating a merge request discussion note
|
||||
export const UpdateMergeRequestNoteSchema = ProjectParamsSchema.extend({
|
||||
merge_request_iid: z.number().describe("The IID of a merge request"),
|
||||
discussion_id: z.string().describe("The ID of a thread"),
|
||||
note_id: z.number().describe("The ID of a thread note"),
|
||||
body: z.string().describe("The content of the note or reply"),
|
||||
resolved: z.boolean().optional().describe("Resolve or unresolve the note"), // Optional based on API docs
|
||||
});
|
||||
|
||||
// API Operation Parameter Schemas
|
||||
|
||||
export const CreateOrUpdateFileSchema = ProjectParamsSchema.extend({
|
||||
file_path: z.string().describe("Path where to create/update the file"),
|
||||
|
Reference in New Issue
Block a user