Переглянути джерело

feat: add User Followers support #9

Implemented complete User Followers functionality for Gogs MCP server:

- Added GogsFollower type in types.ts extending GogsUser
- Implemented 5 new API methods in gogs-client.ts:
  * listFollowers: List followers of a user
  * listFollowing: List users that a user is following
  * checkFollowing: Check if authenticated user follows a target
  * followUser: Follow a user
  * unfollowUser: Unfollow a user
- Added Zod schemas for all follower tools in server.ts
- Registered 5 new MCP tools: list_followers, list_following,
  check_following, follow_user, unfollow_user
- Implemented tool handlers with proper error handling
- Updated README.md with comprehensive follower tools documentation
  including usage examples and response formats
- Updated CLAUDE.md with User Followers tool category
- Built and tested compilation successfully

Closes requirements for issue #9.

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

Co-Authored-By: Claude <noreply@anthropic.com>
claude 9 місяців тому
батько
коміт
743bcdcbcf
5 змінених файлів з 302 додано та 0 видалено
  1. 1 0
      CLAUDE.md
  2. 82 0
      README.md
  3. 46 0
      src/gogs-client.ts
  4. 169 0
      src/server.ts
  5. 4 0
      src/types.ts

+ 1 - 0
CLAUDE.md

@@ -40,6 +40,7 @@ The server exposes tools in these categories:
 - **Collaborator Management**: list_collaborators, check_collaborator, add_collaborator, remove_collaborator
 - **Public Key Management**: list_user_keys, list_my_keys, get_public_key, create_public_key, delete_public_key
 - **Webhook Management**: list_hooks, create_hook, update_hook, delete_hook
+- **User Followers**: list_followers, list_following, check_following, follow_user, unfollow_user
 
 ## Development Commands
 

+ 82 - 0
README.md

@@ -78,6 +78,13 @@ This MCP server provides tools to:
   - Create new public SSH key
   - Delete public SSH key
 
+- **User Followers**
+  - List followers of a user
+  - List users that a user is following
+  - Check if authenticated user is following a target user
+  - Follow a user
+  - Unfollow a user
+
 ## Installation
 
 ### Prerequisites
@@ -775,6 +782,81 @@ Delete a webhook from a repository.
 - `repo` (string, required): Repository name
 - `hookId` (number, required): Webhook ID to delete
 
+### User Followers Tools
+
+#### `list_followers`
+List all followers of a specific user. Returns an array of user objects representing followers.
+- `username` (string, required): Username to list followers for
+
+**Example response:**
+```json
+[
+  {
+    "id": 1,
+    "username": "john",
+    "full_name": "John Doe",
+    "email": "john@example.com",
+    "avatar_url": "https://example.com/avatars/john"
+  }
+]
+```
+
+#### `list_following`
+List all users that a specific user is following. Returns an array of user objects.
+- `username` (string, required): Username to list following for
+
+**Example response:**
+```json
+[
+  {
+    "id": 2,
+    "username": "jane",
+    "full_name": "Jane Smith",
+    "email": "jane@example.com",
+    "avatar_url": "https://example.com/avatars/jane"
+  }
+]
+```
+
+#### `check_following`
+Check if the authenticated user is following a target user. Returns a boolean result.
+- `username` (string, required): Username to check if authenticated user is following
+
+**Example response:**
+```json
+{
+  "isFollowing": true,
+  "username": "john",
+  "message": "You are following john"
+}
+```
+
+#### `follow_user`
+Follow a user. The authenticated user will follow the target user.
+- `username` (string, required): Username to follow
+
+**Example response:**
+```json
+{
+  "success": true,
+  "message": "Successfully followed user john",
+  "username": "john"
+}
+```
+
+#### `unfollow_user`
+Unfollow a user. The authenticated user will unfollow the target user.
+- `username` (string, required): Username to unfollow
+
+**Example response:**
+```json
+{
+  "success": true,
+  "message": "Successfully unfollowed user john",
+  "username": "john"
+}
+```
+
 ## Documented but Not Yet Implemented API Endpoints
 
 The following Gogs API v1 endpoints are officially documented but not yet implemented in this MCP server. These represent potential features for future development:

+ 46 - 0
src/gogs-client.ts

@@ -27,6 +27,7 @@ import type {
   GogsWebhookEvent,
   GogsWebhookType,
   GogsWebhookConfig,
+  GogsFollower,
 } from './types.js';
 
 export class GogsClient {
@@ -796,4 +797,49 @@ export class GogsClient {
   async deleteHook(owner: string, repo: string, hookId: number): Promise<void> {
     await this.client.delete(`/repos/${owner}/${repo}/hooks/${hookId}`);
   }
+
+  /**
+   * List followers of a user
+   */
+  async listFollowers(username: string): Promise<GogsFollower[]> {
+    const response = await this.client.get<GogsFollower[]>(`/users/${username}/followers`);
+    return response.data;
+  }
+
+  /**
+   * List users that a user is following
+   */
+  async listFollowing(username: string): Promise<GogsFollower[]> {
+    const response = await this.client.get<GogsFollower[]>(`/users/${username}/following`);
+    return response.data;
+  }
+
+  /**
+   * Check if the authenticated user is following a target user
+   */
+  async checkFollowing(username: string): Promise<boolean> {
+    try {
+      await this.client.get(`/user/following/${username}`);
+      return true;
+    } catch (error) {
+      if (axios.isAxiosError(error) && error.response?.status === 404) {
+        return false;
+      }
+      throw error;
+    }
+  }
+
+  /**
+   * Follow a user (authenticated user follows the target user)
+   */
+  async followUser(username: string): Promise<void> {
+    await this.client.put(`/user/following/${username}`);
+  }
+
+  /**
+   * Unfollow a user (authenticated user unfollows the target user)
+   */
+  async unfollowUser(username: string): Promise<void> {
+    await this.client.delete(`/user/following/${username}`);
+  }
 }

+ 169 - 0
src/server.ts

@@ -401,6 +401,26 @@ const DeleteHookSchema = z.object({
   hookId: z.number().describe('Webhook ID to delete'),
 });
 
+const ListFollowersSchema = z.object({
+  username: z.string().describe('Username to list followers for'),
+});
+
+const ListFollowingSchema = z.object({
+  username: z.string().describe('Username to list following for'),
+});
+
+const CheckFollowingSchema = z.object({
+  username: z.string().describe('Username to check if authenticated user is following'),
+});
+
+const FollowUserSchema = z.object({
+  username: z.string().describe('Username to follow'),
+});
+
+const UnfollowUserSchema = z.object({
+  username: z.string().describe('Username to unfollow'),
+});
+
 /**
  * Create and configure an MCP server with all tools and handlers
  */
@@ -1900,6 +1920,76 @@ export function createMcpServer(gogsClient: GogsClient): Server {
             required: ['owner', 'repo', 'hookId'],
           },
         },
+        {
+          name: 'list_followers',
+          description: 'List followers of a specific user',
+          inputSchema: {
+            type: 'object',
+            properties: {
+              username: {
+                type: 'string',
+                description: 'Username to list followers for',
+              },
+            },
+            required: ['username'],
+          },
+        },
+        {
+          name: 'list_following',
+          description: 'List users that a specific user is following',
+          inputSchema: {
+            type: 'object',
+            properties: {
+              username: {
+                type: 'string',
+                description: 'Username to list following for',
+              },
+            },
+            required: ['username'],
+          },
+        },
+        {
+          name: 'check_following',
+          description: 'Check if the authenticated user is following a target user',
+          inputSchema: {
+            type: 'object',
+            properties: {
+              username: {
+                type: 'string',
+                description: 'Username to check if authenticated user is following',
+              },
+            },
+            required: ['username'],
+          },
+        },
+        {
+          name: 'follow_user',
+          description: 'Follow a user (authenticated user follows the target user)',
+          inputSchema: {
+            type: 'object',
+            properties: {
+              username: {
+                type: 'string',
+                description: 'Username to follow',
+              },
+            },
+            required: ['username'],
+          },
+        },
+        {
+          name: 'unfollow_user',
+          description: 'Unfollow a user (authenticated user unfollows the target user)',
+          inputSchema: {
+            type: 'object',
+            properties: {
+              username: {
+                type: 'string',
+                description: 'Username to unfollow',
+              },
+            },
+            required: ['username'],
+          },
+        },
       ],
     };
   });
@@ -2807,6 +2897,85 @@ export function createMcpServer(gogsClient: GogsClient): Server {
           };
         }
 
+        case 'list_followers': {
+          const { username } = ListFollowersSchema.parse(args);
+          const followers = await gogsClient.listFollowers(username);
+          return {
+            content: [
+              {
+                type: 'text',
+                text: JSON.stringify(followers, null, 2),
+              },
+            ],
+          };
+        }
+
+        case 'list_following': {
+          const { username } = ListFollowingSchema.parse(args);
+          const following = await gogsClient.listFollowing(username);
+          return {
+            content: [
+              {
+                type: 'text',
+                text: JSON.stringify(following, null, 2),
+              },
+            ],
+          };
+        }
+
+        case 'check_following': {
+          const { username } = CheckFollowingSchema.parse(args);
+          const isFollowing = await gogsClient.checkFollowing(username);
+          return {
+            content: [
+              {
+                type: 'text',
+                text: JSON.stringify({
+                  isFollowing,
+                  username,
+                  message: isFollowing
+                    ? `You are following ${username}`
+                    : `You are not following ${username}`
+                }, null, 2),
+              },
+            ],
+          };
+        }
+
+        case 'follow_user': {
+          const { username } = FollowUserSchema.parse(args);
+          await gogsClient.followUser(username);
+          return {
+            content: [
+              {
+                type: 'text',
+                text: JSON.stringify({
+                  success: true,
+                  message: `Successfully followed user ${username}`,
+                  username
+                }, null, 2),
+              },
+            ],
+          };
+        }
+
+        case 'unfollow_user': {
+          const { username } = UnfollowUserSchema.parse(args);
+          await gogsClient.unfollowUser(username);
+          return {
+            content: [
+              {
+                type: 'text',
+                text: JSON.stringify({
+                  success: true,
+                  message: `Successfully unfollowed user ${username}`,
+                  username
+                }, null, 2),
+              },
+            ],
+          };
+        }
+
         default:
           throw new Error(`Unknown tool: ${name}`);
       }

+ 4 - 0
src/types.ts

@@ -245,3 +245,7 @@ export interface GogsWebhook {
   updated_at: string;
   created_at: string;
 }
+
+export interface GogsFollower extends GogsUser {
+  // Follower is a user, inherits all GogsUser properties
+}