Browse Source

feat: add Organizations & Teams support #6

This commit implements comprehensive support for Gogs Organizations and Teams API endpoints.

## Changes:

### Type Definitions (types.ts)
- Added GogsOrganization interface with fields: id, username, full_name, avatar_url, description, website, location
- Added GogsTeam interface with fields: id, name, description, permission

### API Client (gogs-client.ts)
- listUserOrganizations() - GET /user/orgs
- listPublicOrganizations(username) - GET /users/:username/orgs
- getOrganization(orgname) - GET /orgs/:orgname
- updateOrganization(orgname, data) - PATCH /orgs/:org
- addOrganizationMember(orgname, username, role) - PUT /orgs/:orgname/memberships/:username
- listOrganizationTeams(orgname) - GET /orgs/:orgname/teams

### MCP Server (server.ts)
- Added Zod schemas for all new tools
- Registered 6 new MCP tools:
  - list_user_organizations
  - list_public_organizations
  - get_organization
  - update_organization
  - add_organization_member
  - list_organization_teams
- Implemented handlers for all new tools in CallToolRequestSchema

## API Coverage:
This implementation provides full coverage of the Gogs Organizations and Teams API v1 endpoints as documented in the official Gogs API documentation.

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

Co-Authored-By: Claude <noreply@anthropic.com>
claude 9 tháng trước cách đây
mục cha
commit
f14fa46aa7
3 tập tin đã thay đổi với 289 bổ sung0 xóa
  1. 61 0
      src/gogs-client.ts
  2. 211 0
      src/server.ts
  3. 17 0
      src/types.ts

+ 61 - 0
src/gogs-client.ts

@@ -15,6 +15,8 @@ import type {
   GogsIssue,
   GogsIssue,
   GogsIssueComment,
   GogsIssueComment,
   GogsLabel,
   GogsLabel,
+  GogsOrganization,
+  GogsTeam,
 } from './types.js';
 } from './types.js';
 
 
 export class GogsClient {
 export class GogsClient {
@@ -426,4 +428,63 @@ export class GogsClient {
   ): Promise<void> {
   ): Promise<void> {
     await this.client.delete(`/repos/${owner}/${repo}/issues/${issueNumber}/labels`);
     await this.client.delete(`/repos/${owner}/${repo}/issues/${issueNumber}/labels`);
   }
   }
+
+  /**
+   * List organizations for the authenticated user
+   */
+  async listUserOrganizations(): Promise<GogsOrganization[]> {
+    const response = await this.client.get<GogsOrganization[]>('/user/orgs');
+    return response.data;
+  }
+
+  /**
+   * List public organizations for a specific user
+   */
+  async listPublicOrganizations(username: string): Promise<GogsOrganization[]> {
+    const response = await this.client.get<GogsOrganization[]>(`/users/${username}/orgs`);
+    return response.data;
+  }
+
+  /**
+   * Get information about a specific organization
+   */
+  async getOrganization(orgname: string): Promise<GogsOrganization> {
+    const response = await this.client.get<GogsOrganization>(`/orgs/${orgname}`);
+    return response.data;
+  }
+
+  /**
+   * Update an organization
+   */
+  async updateOrganization(
+    orgname: string,
+    data: {
+      full_name?: string;
+      description?: string;
+      website?: string;
+      location?: string;
+    }
+  ): Promise<GogsOrganization> {
+    const response = await this.client.patch<GogsOrganization>(`/orgs/${orgname}`, data);
+    return response.data;
+  }
+
+  /**
+   * Add or update organization membership
+   */
+  async addOrganizationMember(
+    orgname: string,
+    username: string,
+    role: 'admin' | 'member'
+  ): Promise<void> {
+    await this.client.put(`/orgs/${orgname}/memberships/${username}`, { role });
+  }
+
+  /**
+   * List teams in an organization
+   */
+  async listOrganizationTeams(orgname: string): Promise<GogsTeam[]> {
+    const response = await this.client.get<GogsTeam[]>(`/orgs/${orgname}/teams`);
+    return response.data;
+  }
 }
 }

+ 211 - 0
src/server.ts

@@ -202,6 +202,32 @@ const RemoveAllIssueLabelsSchema = z.object({
   number: z.number().describe('Issue number'),
   number: z.number().describe('Issue number'),
 });
 });
 
 
+const ListPublicOrganizationsSchema = z.object({
+  username: z.string().describe('Username to get public organizations for'),
+});
+
+const GetOrganizationSchema = z.object({
+  orgname: z.string().describe('Organization name'),
+});
+
+const UpdateOrganizationSchema = z.object({
+  orgname: z.string().describe('Organization name'),
+  full_name: z.string().optional().describe('Organization full name'),
+  description: z.string().optional().describe('Organization description'),
+  website: z.string().optional().describe('Organization website'),
+  location: z.string().optional().describe('Organization location'),
+});
+
+const AddOrganizationMemberSchema = z.object({
+  orgname: z.string().describe('Organization name'),
+  username: z.string().describe('Username to add as member'),
+  role: z.enum(['admin', 'member']).describe('Member role (admin or member)'),
+});
+
+const ListOrganizationTeamsSchema = z.object({
+  orgname: z.string().describe('Organization name'),
+});
+
 /**
 /**
  * Create and configure an MCP server with all tools and handlers
  * Create and configure an MCP server with all tools and handlers
  */
  */
@@ -940,6 +966,109 @@ export function createMcpServer(gogsClient: GogsClient): Server {
             required: ['owner', 'repo', 'number'],
             required: ['owner', 'repo', 'number'],
           },
           },
         },
         },
+        {
+          name: 'list_user_organizations',
+          description: 'List organizations for the authenticated user',
+          inputSchema: {
+            type: 'object',
+            properties: {},
+          },
+        },
+        {
+          name: 'list_public_organizations',
+          description: 'List public organizations for a specific user',
+          inputSchema: {
+            type: 'object',
+            properties: {
+              username: {
+                type: 'string',
+                description: 'Username to get public organizations for',
+              },
+            },
+            required: ['username'],
+          },
+        },
+        {
+          name: 'get_organization',
+          description: 'Get information about a specific organization',
+          inputSchema: {
+            type: 'object',
+            properties: {
+              orgname: {
+                type: 'string',
+                description: 'Organization name',
+              },
+            },
+            required: ['orgname'],
+          },
+        },
+        {
+          name: 'update_organization',
+          description: 'Update an organization (requires owner permissions)',
+          inputSchema: {
+            type: 'object',
+            properties: {
+              orgname: {
+                type: 'string',
+                description: 'Organization name',
+              },
+              full_name: {
+                type: 'string',
+                description: 'Organization full name',
+              },
+              description: {
+                type: 'string',
+                description: 'Organization description',
+              },
+              website: {
+                type: 'string',
+                description: 'Organization website',
+              },
+              location: {
+                type: 'string',
+                description: 'Organization location',
+              },
+            },
+            required: ['orgname'],
+          },
+        },
+        {
+          name: 'add_organization_member',
+          description: 'Add or update organization membership (requires owner permissions)',
+          inputSchema: {
+            type: 'object',
+            properties: {
+              orgname: {
+                type: 'string',
+                description: 'Organization name',
+              },
+              username: {
+                type: 'string',
+                description: 'Username to add as member',
+              },
+              role: {
+                type: 'string',
+                enum: ['admin', 'member'],
+                description: 'Member role (admin or member)',
+              },
+            },
+            required: ['orgname', 'username', 'role'],
+          },
+        },
+        {
+          name: 'list_organization_teams',
+          description: 'List teams in an organization',
+          inputSchema: {
+            type: 'object',
+            properties: {
+              orgname: {
+                type: 'string',
+                description: 'Organization name',
+              },
+            },
+            required: ['orgname'],
+          },
+        },
       ],
       ],
     };
     };
   });
   });
@@ -1355,6 +1484,88 @@ export function createMcpServer(gogsClient: GogsClient): Server {
           };
           };
         }
         }
 
 
+        case 'list_user_organizations': {
+          const organizations = await gogsClient.listUserOrganizations();
+          return {
+            content: [
+              {
+                type: 'text',
+                text: JSON.stringify(organizations, null, 2),
+              },
+            ],
+          };
+        }
+
+        case 'list_public_organizations': {
+          const { username } = ListPublicOrganizationsSchema.parse(args);
+          const organizations = await gogsClient.listPublicOrganizations(username);
+          return {
+            content: [
+              {
+                type: 'text',
+                text: JSON.stringify(organizations, null, 2),
+              },
+            ],
+          };
+        }
+
+        case 'get_organization': {
+          const { orgname } = GetOrganizationSchema.parse(args);
+          const organization = await gogsClient.getOrganization(orgname);
+          return {
+            content: [
+              {
+                type: 'text',
+                text: JSON.stringify(organization, null, 2),
+              },
+            ],
+          };
+        }
+
+        case 'update_organization': {
+          const { orgname, full_name, description, website, location } = UpdateOrganizationSchema.parse(args);
+          const organization = await gogsClient.updateOrganization(orgname, {
+            full_name,
+            description,
+            website,
+            location,
+          });
+          return {
+            content: [
+              {
+                type: 'text',
+                text: JSON.stringify(organization, null, 2),
+              },
+            ],
+          };
+        }
+
+        case 'add_organization_member': {
+          const { orgname, username, role } = AddOrganizationMemberSchema.parse(args);
+          await gogsClient.addOrganizationMember(orgname, username, role);
+          return {
+            content: [
+              {
+                type: 'text',
+                text: `User ${username} added to organization ${orgname} with role ${role}`,
+              },
+            ],
+          };
+        }
+
+        case 'list_organization_teams': {
+          const { orgname } = ListOrganizationTeamsSchema.parse(args);
+          const teams = await gogsClient.listOrganizationTeams(orgname);
+          return {
+            content: [
+              {
+                type: 'text',
+                text: JSON.stringify(teams, null, 2),
+              },
+            ],
+          };
+        }
+
         default:
         default:
           throw new Error(`Unknown tool: ${name}`);
           throw new Error(`Unknown tool: ${name}`);
       }
       }

+ 17 - 0
src/types.ts

@@ -126,3 +126,20 @@ export interface GogsIssueComment {
   created_at: string;
   created_at: string;
   updated_at: string;
   updated_at: string;
 }
 }
+
+export interface GogsOrganization {
+  id: number;
+  username: string;
+  full_name: string;
+  avatar_url: string;
+  description: string;
+  website: string;
+  location: string;
+}
+
+export interface GogsTeam {
+  id: number;
+  name: string;
+  description: string;
+  permission: 'owner' | 'read' | 'write';
+}