瀏覽代碼

feat: add milestone management support #5

Implemented complete milestone management functionality for the Gogs MCP server:

- Added milestone CRUD methods to GogsClient (listMilestones, getMilestone, createMilestone, updateMilestone, deleteMilestone)
- Added MCP tool schemas and handlers for all milestone operations
- Added 5 new MCP tools: list_milestones, get_milestone, create_milestone, update_milestone, delete_milestone
- Updated documentation in README.md and CLAUDE.md with milestone tools
- GogsMilestone type was already defined in types.ts

This enables full milestone management through the MCP interface, allowing grouping of issues and tracking progress towards specific goals.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
claude 9 月之前
父節點
當前提交
216ee49726
共有 4 個文件被更改,包括 349 次插入0 次删除
  1. 1 0
      CLAUDE.md
  2. 44 0
      README.md
  3. 64 0
      src/gogs-client.ts
  4. 240 0
      src/server.ts

+ 1 - 0
CLAUDE.md

@@ -33,6 +33,7 @@ The server exposes tools in these categories:
 - **Content Access**: get_contents, get_raw_content, list_branches, get_commits, get_tree
 - **Issue Tracking**: list_issues, get_issue, create_issue, update_issue, list_issue_comments, create_issue_comment, update_issue_comment
 - **Label Management**: list_labels, get_label, create_label, update_label, delete_label, list_issue_labels, add_issue_labels, remove_issue_label, replace_issue_labels, remove_all_issue_labels
+- **Milestone Management**: list_milestones, get_milestone, create_milestone, update_milestone, delete_milestone
 - **Organization Management**: list_user_organizations, create_organization, list_public_organizations, get_organization, update_organization, add_organization_member
 - **Team Management**: list_organization_teams, create_team, get_team, add_team_member, remove_team_member
 - **Release Management**: list_releases

+ 44 - 0
README.md

@@ -46,6 +46,13 @@ This MCP server provides tools to:
   - Replace all labels on an issue
   - Remove all labels from an issue
 
+- **Milestone Management**
+  - List all milestones in a repository
+  - Get specific milestone details
+  - Create new milestones
+  - Update existing milestones
+  - Delete milestones
+
 - **Organization Management**
   - List organizations for authenticated user
   - Create new organization
@@ -500,6 +507,43 @@ Remove all labels from an issue.
 - `repo` (string, required): Repository name
 - `number` (number, required): Issue number
 
+### Milestone Management Tools
+
+#### `list_milestones`
+List all milestones in a repository.
+- `owner` (string, required): Repository owner username
+- `repo` (string, required): Repository name
+
+#### `get_milestone`
+Get a specific milestone by ID.
+- `owner` (string, required): Repository owner username
+- `repo` (string, required): Repository name
+- `milestoneId` (number, required): Milestone ID
+
+#### `create_milestone`
+Create a new milestone in a repository.
+- `owner` (string, required): Repository owner username
+- `repo` (string, required): Repository name
+- `title` (string, required): Milestone title
+- `description` (string, optional): Milestone description
+- `due_on` (string, optional): Due date in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)
+
+#### `update_milestone`
+Update an existing milestone.
+- `owner` (string, required): Repository owner username
+- `repo` (string, required): Repository name
+- `milestoneId` (number, required): Milestone ID
+- `title` (string, optional): Milestone title
+- `description` (string, optional): Milestone description
+- `due_on` (string, optional): Due date in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)
+- `state` (string, optional): Milestone state - 'open' or 'closed'
+
+#### `delete_milestone`
+Delete a milestone from a repository.
+- `owner` (string, required): Repository owner username
+- `repo` (string, required): Repository name
+- `milestoneId` (number, required): Milestone ID
+
 ### Organization Management Tools
 
 #### `list_user_organizations`

+ 64 - 0
src/gogs-client.ts

@@ -15,6 +15,7 @@ import type {
   GogsIssue,
   GogsIssueComment,
   GogsLabel,
+  GogsMilestone,
   GogsOrganization,
   GogsTeam,
   GogsTree,
@@ -440,6 +441,69 @@ export class GogsClient {
     await this.client.delete(`/repos/${owner}/${repo}/issues/${issueNumber}/labels`);
   }
 
+  /**
+   * List all milestones in a repository
+   */
+  async listMilestones(owner: string, repo: string): Promise<GogsMilestone[]> {
+    const response = await this.client.get<GogsMilestone[]>(`/repos/${owner}/${repo}/milestones`);
+    return response.data;
+  }
+
+  /**
+   * Get a specific milestone by ID
+   */
+  async getMilestone(owner: string, repo: string, milestoneId: number): Promise<GogsMilestone> {
+    const response = await this.client.get<GogsMilestone>(`/repos/${owner}/${repo}/milestones/${milestoneId}`);
+    return response.data;
+  }
+
+  /**
+   * Create a new milestone
+   */
+  async createMilestone(
+    owner: string,
+    repo: string,
+    data: {
+      title: string;
+      description?: string;
+      due_on?: string;
+    }
+  ): Promise<GogsMilestone> {
+    const response = await this.client.post<GogsMilestone>(
+      `/repos/${owner}/${repo}/milestones`,
+      data
+    );
+    return response.data;
+  }
+
+  /**
+   * Update an existing milestone
+   */
+  async updateMilestone(
+    owner: string,
+    repo: string,
+    milestoneId: number,
+    data: {
+      title?: string;
+      description?: string;
+      due_on?: string;
+      state?: 'open' | 'closed';
+    }
+  ): Promise<GogsMilestone> {
+    const response = await this.client.patch<GogsMilestone>(
+      `/repos/${owner}/${repo}/milestones/${milestoneId}`,
+      data
+    );
+    return response.data;
+  }
+
+  /**
+   * Delete a milestone
+   */
+  async deleteMilestone(owner: string, repo: string, milestoneId: number): Promise<void> {
+    await this.client.delete(`/repos/${owner}/${repo}/milestones/${milestoneId}`);
+  }
+
   /**
    * List organizations for the authenticated user
    */

+ 240 - 0
src/server.ts

@@ -208,6 +208,41 @@ const RemoveAllIssueLabelsSchema = z.object({
   number: z.number().describe('Issue number'),
 });
 
+const ListMilestonesSchema = z.object({
+  owner: z.string().describe('Repository owner username'),
+  repo: z.string().describe('Repository name'),
+});
+
+const GetMilestoneSchema = z.object({
+  owner: z.string().describe('Repository owner username'),
+  repo: z.string().describe('Repository name'),
+  milestoneId: z.number().describe('Milestone ID'),
+});
+
+const CreateMilestoneSchema = z.object({
+  owner: z.string().describe('Repository owner username'),
+  repo: z.string().describe('Repository name'),
+  title: z.string().describe('Milestone title'),
+  description: z.string().optional().describe('Milestone description'),
+  due_on: z.string().optional().describe('Due date in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)'),
+});
+
+const UpdateMilestoneSchema = z.object({
+  owner: z.string().describe('Repository owner username'),
+  repo: z.string().describe('Repository name'),
+  milestoneId: z.number().describe('Milestone ID'),
+  title: z.string().optional().describe('Milestone title'),
+  description: z.string().optional().describe('Milestone description'),
+  due_on: z.string().optional().describe('Due date in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)'),
+  state: z.enum(['open', 'closed']).optional().describe('Milestone state'),
+});
+
+const DeleteMilestoneSchema = z.object({
+  owner: z.string().describe('Repository owner username'),
+  repo: z.string().describe('Repository name'),
+  milestoneId: z.number().describe('Milestone ID'),
+});
+
 const CreateOrganizationSchema = z.object({
   username: z.string().describe('Organization username (alphanumeric, dashes, underscores)'),
   full_name: z.string().optional().describe('Organization full name'),
@@ -1052,6 +1087,137 @@ export function createMcpServer(gogsClient: GogsClient): Server {
             required: ['owner', 'repo', 'number'],
           },
         },
+        {
+          name: 'list_milestones',
+          description: 'List all milestones in a repository',
+          inputSchema: {
+            type: 'object',
+            properties: {
+              owner: {
+                type: 'string',
+                description: 'Repository owner username',
+              },
+              repo: {
+                type: 'string',
+                description: 'Repository name',
+              },
+            },
+            required: ['owner', 'repo'],
+          },
+        },
+        {
+          name: 'get_milestone',
+          description: 'Get a specific milestone by ID',
+          inputSchema: {
+            type: 'object',
+            properties: {
+              owner: {
+                type: 'string',
+                description: 'Repository owner username',
+              },
+              repo: {
+                type: 'string',
+                description: 'Repository name',
+              },
+              milestoneId: {
+                type: 'number',
+                description: 'Milestone ID',
+              },
+            },
+            required: ['owner', 'repo', 'milestoneId'],
+          },
+        },
+        {
+          name: 'create_milestone',
+          description: 'Create a new milestone in a repository',
+          inputSchema: {
+            type: 'object',
+            properties: {
+              owner: {
+                type: 'string',
+                description: 'Repository owner username',
+              },
+              repo: {
+                type: 'string',
+                description: 'Repository name',
+              },
+              title: {
+                type: 'string',
+                description: 'Milestone title',
+              },
+              description: {
+                type: 'string',
+                description: 'Milestone description',
+              },
+              due_on: {
+                type: 'string',
+                description: 'Due date in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)',
+              },
+            },
+            required: ['owner', 'repo', 'title'],
+          },
+        },
+        {
+          name: 'update_milestone',
+          description: 'Update an existing milestone',
+          inputSchema: {
+            type: 'object',
+            properties: {
+              owner: {
+                type: 'string',
+                description: 'Repository owner username',
+              },
+              repo: {
+                type: 'string',
+                description: 'Repository name',
+              },
+              milestoneId: {
+                type: 'number',
+                description: 'Milestone ID',
+              },
+              title: {
+                type: 'string',
+                description: 'Milestone title',
+              },
+              description: {
+                type: 'string',
+                description: 'Milestone description',
+              },
+              due_on: {
+                type: 'string',
+                description: 'Due date in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)',
+              },
+              state: {
+                type: 'string',
+                enum: ['open', 'closed'],
+                description: 'Milestone state',
+              },
+            },
+            required: ['owner', 'repo', 'milestoneId'],
+          },
+        },
+        {
+          name: 'delete_milestone',
+          description: 'Delete a milestone from a repository',
+          inputSchema: {
+            type: 'object',
+            properties: {
+              owner: {
+                type: 'string',
+                description: 'Repository owner username',
+              },
+              repo: {
+                type: 'string',
+                description: 'Repository name',
+              },
+              milestoneId: {
+                type: 'number',
+                description: 'Milestone ID',
+              },
+            },
+            required: ['owner', 'repo', 'milestoneId'],
+          },
+        },
         {
           name: 'list_user_organizations',
           description: 'List organizations for the authenticated user',
@@ -1797,6 +1963,80 @@ export function createMcpServer(gogsClient: GogsClient): Server {
           };
         }
 
+        case 'list_milestones': {
+          const { owner, repo } = ListMilestonesSchema.parse(args);
+          const milestones = await gogsClient.listMilestones(owner, repo);
+          return {
+            content: [
+              {
+                type: 'text',
+                text: JSON.stringify(milestones, null, 2),
+              },
+            ],
+          };
+        }
+
+        case 'get_milestone': {
+          const { owner, repo, milestoneId } = GetMilestoneSchema.parse(args);
+          const milestone = await gogsClient.getMilestone(owner, repo, milestoneId);
+          return {
+            content: [
+              {
+                type: 'text',
+                text: JSON.stringify(milestone, null, 2),
+              },
+            ],
+          };
+        }
+
+        case 'create_milestone': {
+          const { owner, repo, title, description, due_on } = CreateMilestoneSchema.parse(args);
+          const milestone = await gogsClient.createMilestone(owner, repo, {
+            title,
+            description,
+            due_on,
+          });
+          return {
+            content: [
+              {
+                type: 'text',
+                text: JSON.stringify(milestone, null, 2),
+              },
+            ],
+          };
+        }
+
+        case 'update_milestone': {
+          const { owner, repo, milestoneId, title, description, due_on, state } = UpdateMilestoneSchema.parse(args);
+          const milestone = await gogsClient.updateMilestone(owner, repo, milestoneId, {
+            title,
+            description,
+            due_on,
+            state,
+          });
+          return {
+            content: [
+              {
+                type: 'text',
+                text: JSON.stringify(milestone, null, 2),
+              },
+            ],
+          };
+        }
+
+        case 'delete_milestone': {
+          const { owner, repo, milestoneId } = DeleteMilestoneSchema.parse(args);
+          await gogsClient.deleteMilestone(owner, repo, milestoneId);
+          return {
+            content: [
+              {
+                type: 'text',
+                text: `Milestone ${milestoneId} deleted successfully from ${owner}/${repo}`,
+              },
+            ],
+          };
+        }
+
         case 'list_user_organizations': {
           const organizations = await gogsClient.listUserOrganizations();
           return {