#
tokens: 10171/50000 13/13 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── bin.ts
├── common
│   ├── errors.ts
│   ├── server.ts
│   ├── types.ts
│   ├── utils.ts
│   └── version.ts
├── Dockerfile
├── index.ts
├── LICENSE
├── operations
│   ├── branches.ts
│   ├── files.ts
│   ├── issues.ts
│   ├── pulls.ts
│   ├── repos.ts
│   └── users.ts
├── package-lock.json
├── package.json
├── README.md
├── README.zh-cn.md
├── smithery.yaml
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # node_modules
 2 | node_modules
 3 | 
 4 | # dist
 5 | dist
 6 | 
 7 | # logs
 8 | *.log
 9 | 
10 | # dotenv
11 | .env
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Gitee MCP Server
  2 | 
  3 | Let AI operate Gitee repositories/Issues/Pull Requests for you through MCP
  4 | 
  5 | [![Node Version](https://img.shields.io/badge/node-%3E%3D22.12.0-brightgreen.svg)](./package.json)
  6 | ![NPM Version](https://img.shields.io/npm/v/gitee-mcp-server)
  7 | ![Docker Pulls](https://img.shields.io/docker/pulls/normalcoder/gitee-mcp-server)
  8 | ![Docker Image Version](https://img.shields.io/docker/v/normalcoder/gitee-mcp-server)
  9 | [![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
 10 | [![smithery badge](https://smithery.ai/badge/@normal-coder/gitee-mcp-server)](https://smithery.ai/server/@normal-coder/gitee-mcp-server)
 11 | 
 12 | [<img width="380" height="200" src="https://glama.ai/mcp/servers/cck9xigm1d/badge" />](https://glama.ai/mcp/servers/Cck9XigM1d)
 13 | 
 14 | ---
 15 | 
 16 | ## Supported AI Operations
 17 | 
 18 | | Category | MCP Tool | Description |
 19 | |:----:|:----|:----|
 20 | | Repository Operations | `create_repository` | Create a Gitee repository | 
 21 | | | `fork_repository` | Fork a Gitee repository | 
 22 | | Branch Operations | `create_branch` | Create a new branch in a Gitee repository | 
 23 | | | `list_branches` | List branches in a Gitee repository | 
 24 | | | `get_branch` | Get details of a specific branch in a Gitee repository | 
 25 | | File Operations | `get_file_contents` | Get contents of a file or directory in a Gitee repository | 
 26 | | | `create_or_update_file` | Create or update a file in a Gitee repository | 
 27 | | | `push_files` | Push multiple files to a Gitee repository | 
 28 | | Issue Operations | `create_issue` | Create an Issue in a Gitee repository | 
 29 | | | `list_issues` | List Issues in a Gitee repository | 
 30 | | | `get_issue` | Get details of a specific Issue in a Gitee repository | 
 31 | | | `update_issue` | Update an Issue in a Gitee repository | 
 32 | | | `add_issue_comment` | Add a comment to an Issue in a Gitee repository | 
 33 | | Pull Request Operations | `create_pull_request` | Create a Pull Request in a Gitee repository | 
 34 | | | `list_pull_requests` | List Pull Requests in a Gitee repository | 
 35 | | | `get_pull_request` | Get details of a specific Pull Request in a Gitee repository | 
 36 | | | `update_pull_request` | Update a Pull Request in a Gitee repository | 
 37 | | | `merge_pull_request` | Merge a Pull Request in a Gitee repository | 
 38 | | User Operations | `get_user` | Get Gitee user information | 
 39 | | | `get_current_user` | Get authenticated Gitee user information | 
 40 | 
 41 | ## Usage
 42 | 
 43 | ### Installing via Smithery
 44 | 
 45 | To install Gitee MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@normal-coder/gitee-mcp-server):
 46 | 
 47 | ```bash
 48 | npx -y @smithery/cli install @normal-coder/gitee-mcp-server --client claude
 49 | ```
 50 | 
 51 | ### Configuration
 52 | 
 53 | - `GITEE_API_BASE_URL`: Optional, Gitee OpenAPI Endpoint, default is `https://gitee.com/api/v5`
 54 | - `GITEE_PERSONAL_ACCESS_TOKEN`: Required, Gitee account personal access token (PAT), can be obtained from Gitee account settings [Personal Access Tokens](https://gitee.com/profile/personal_access_tokens)
 55 | - `DEBUG`: Optional, set to `true` to enable debug logging, default is disabled
 56 | 
 57 | ### Run MCP Server via NPX
 58 | 
 59 | ```json
 60 | {
 61 |   "mcpServers": {
 62 |     "Gitee": {
 63 |       "command": "npx",
 64 |       "args": [
 65 |         "-y",
 66 |         "gitee-mcp-server"
 67 |       ],
 68 |       "env": {
 69 |         "GITEE_PERSONAL_ACCESS_TOKEN": "<YOUR_TOKEN>"
 70 |       }
 71 |     }
 72 |   }
 73 | }
 74 | ```
 75 | 
 76 | ### Run MCP Server via Docker Container
 77 | 
 78 | 1. Get Docker Image
 79 | 
 80 | ```bash
 81 | # Get from DockerHub
 82 | docker pull normalcoder/gitee-mcp-server
 83 | 
 84 | # Build locally
 85 | docker build -t normalcoder/gitee-mcp-server .
 86 | ```
 87 | 
 88 | 2. Configure MCP Server
 89 | 
 90 | ```json
 91 | {
 92 |   "mcpServers": {
 93 |     "Gitee": {
 94 |       "command": "docker",
 95 |       "args": [
 96 |         "run",
 97 |         "-i",
 98 |         "--rm",
 99 |         "-e",
100 |         "GITEE_PERSONAL_ACCESS_TOKEN",
101 |         "normalcoder/gitee-mcp-server"
102 |       ],
103 |       "env": {
104 |         "GITEE_PERSONAL_ACCESS_TOKEN": "<YOUR_TOKEN>"
105 |       }
106 |     }
107 |   }
108 | }
109 | ```
110 | 
111 | ## Development Guide
112 | 
113 | ### Install Dependencies
114 | 
115 | ```bash
116 | npm install
117 | ```
118 | 
119 | ### Build
120 | 
121 | ```bash
122 | npm run build
123 | ```
124 | 
125 | After successful build, `/dist` will contain the runnable MCP server.
126 | 
127 | ### Run Server
128 | 
129 | ```bash
130 | npm start
131 | ```
132 | 
133 | The MCP server will run on stdio, allowing it to be used as a subprocess by MCP clients.
134 | 
135 | ### Build Docker Image
136 | 
137 | You can also run the server using Docker:
138 | 
139 | ```bash
140 | docker build -t normalcoder/gitee-mcp-server .
141 | ```
142 | 
143 | Run MCP Server with Docker:
144 | 
145 | ```bash
146 | docker run -e GITEE_PERSONAL_ACCESS_TOKEN=<YOUR_TOKEN> normalcoder/gitee-mcp-server
147 | ```
148 | 
149 | ### Debug MCP Server
150 | 
151 | You can use `@modelcontextprotocol/inspector` for debugging:
152 | 
153 | Create a `.env` file in the root directory for environment variables:
154 | 
155 | ```.env
156 | GITEE_API_BASE_URL=https://gitee.com/api/v5
157 | GITEE_PERSONAL_ACCESS_TOKEN=<YOUR_TOKEN>
158 | ```
159 | 
160 | Run the debug tool to start the service and web debug interface:
161 | 
162 | ```bash
163 | npx @modelcontextprotocol/inspector npm run start --env-file=.env
164 | ```
165 | 
166 | The project includes a `debug()` function for printing debug information, usage:
167 | 
168 | ```typescript
169 | import { debug } from './common/utils.js';
170 | 
171 | debug('Message to log');
172 | debug('Message with data:', { key: 'value' });
173 | ```
174 | 
175 | Debug logs are only printed when the `DEBUG` environment variable is set to `true`.
176 | 
177 | ## Dependencies
178 | 
179 | - `@modelcontextprotocol/sdk`: MCP SDK for server implementation
180 | - `universal-user-agent`: For generating user agent strings
181 | - `zod`: For schema validation
182 | - `zod-to-json-schema`: For converting Zod schemas to JSON schemas
183 | 
184 | ## License
185 | 
186 | Licensed under MIT License. You are free to use, modify and distribute the software, subject to the terms and conditions of the MIT License. For more details, see the [LICENSE](./LICENSE) file in the project repository.
187 | 
188 | ## Related Links
189 | 
190 | - [Model Context Protocol](https://modelcontextprotocol.io)
191 | - [Gitee](https://gitee.com)
192 | 
```

--------------------------------------------------------------------------------
/common/version.ts:
--------------------------------------------------------------------------------

```typescript
1 | export const VERSION = "0.1.0";
2 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "Node16",
 5 |     "moduleResolution": "Node16",
 6 |     "strict": true,
 7 |     "esModuleInterop": true,
 8 |     "skipLibCheck": true,
 9 |     "forceConsistentCasingInFileNames": true,
10 |     "resolveJsonModule": true,
11 |     "outDir": "dist",
12 |     "rootDir": "."
13 |   },
14 |   "include": [
15 |     "./**/*.ts"
16 |   ],
17 |   "exclude": [
18 |     "node_modules"
19 |   ]
20 | }
```

--------------------------------------------------------------------------------
/bin.ts:
--------------------------------------------------------------------------------

```typescript
 1 | #!/usr/bin/env node
 2 | import { config } from "dotenv";
 3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
 4 | import createGiteeMCPServer from "./index.js";
 5 | 
 6 | // 加载 .env 文件
 7 | config();
 8 | 
 9 | async function runServer() {
10 |   const server = createGiteeMCPServer();
11 |   const transport = new StdioServerTransport();
12 |   await server.connect(transport);
13 |   console.error("Gitee MCP Server running on stdio");
14 | }
15 | 
16 | runServer().catch((error) => {
17 |   console.error("Fatal error in main():", error);
18 |   process.exit(1);
19 | });
20 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "gitee-mcp-server",
 3 |   "version": "0.1.1",
 4 |   "description": "MCP Server for using the Gitee API",
 5 |   "license": "MIT",
 6 |   "author": {
 7 |     "name": "诺墨",
 8 |     "email": "[email protected]",
 9 |     "url": "https://gitee.com/normalcoder/gitee-mcp-server"
10 |   },
11 |   "homepage": "https://gitee.com/normalcoder/gitee-mcp-server",
12 |   "type": "module",
13 |   "bin": {
14 |     "mcp-server-gitee": "dist/bin.js"
15 |   },
16 |   "files": [
17 |     "dist"
18 |   ],
19 |   "scripts": {
20 |     "build": "tsc && shx chmod +x dist/*.js",
21 |     "watch": "tsc --watch",
22 |     "start": "node dist/bin.js"
23 |   },
24 |   "dependencies": {
25 |     "@modelcontextprotocol/sdk": "^1.0.1",
26 |     "dotenv": "^16.4.7",
27 |     "universal-user-agent": "^7.0.0",
28 |     "zod": "^3.22.4",
29 |     "zod-to-json-schema": "^3.22.3"
30 |   },
31 |   "devDependencies": {
32 |     "@types/node": "^20.10.5",
33 |     "shx": "^0.3.4",
34 |     "typescript": "^5.8.2"
35 |   }
36 | }
```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
 2 | 
 3 | startCommand:
 4 |   type: stdio
 5 |   configSchema:
 6 |     # JSON Schema defining the configuration options for the MCP.
 7 |     type: object
 8 |     required:
 9 |       - giteePersonalAccessToken
10 |     properties:
11 |       giteePersonalAccessToken:
12 |         type: string
13 |         description: Gitee personal access token, required for authentication.
14 |       giteeApiBaseUrl:
15 |         type: string
16 |         default: https://gitee.com/api/v5
17 |         description: Optional Gitee API base URL
18 |       debug:
19 |         type: boolean
20 |         description: Enable debug mode
21 |   commandFunction:
22 |     # A JS function that produces the CLI command based on the given config to start the MCP on stdio.
23 |     |-
24 |     (config) => ({
25 |       command: 'node',
26 |       args: ['dist/bin.js'],
27 |       env: {
28 |         GITEE_PERSONAL_ACCESS_TOKEN: config.giteePersonalAccessToken,
29 |         GITEE_API_BASE_URL: config.giteeApiBaseUrl || 'https://gitee.com/api/v5',
30 |         DEBUG: config.debug === true ? 'true' : undefined
31 |       }
32 |     })
33 |   exampleConfig:
34 |     giteePersonalAccessToken: <YOUR_GITEE_PERSONAL_ACCESS_TOKEN>
35 |     giteeApiBaseUrl: https://gitee.com/api/v5
36 |     debug: false
37 | 
```

--------------------------------------------------------------------------------
/operations/users.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from "zod";
 2 | import { giteeRequest, validateOwnerName, getGiteeApiBaseUrl } from "../common/utils.js";
 3 | import { GiteeUserSchema } from "../common/types.js";
 4 | 
 5 | // Schema definitions
 6 | export const GetUserSchema = z.object({
 7 |   // 用户名
 8 |   username: z.string().describe("Username"),
 9 | });
10 | 
11 | export const SearchUsersSchema = z.object({
12 |   // 搜索关键词
13 |   q: z.string().describe("Search keyword"),
14 |   // 当前的页码
15 |   page: z.number().int().min(1).default(1).optional().describe("Page number"),
16 |   // 每页的数量,最大为 100
17 |   per_page: z.number().int().min(1).max(100).default(30).optional().describe("Number of items per page, maximum 100"),
18 |   // 排序字段
19 |   sort: z.enum(["followers", "repositories", "joined"]).default("followers").optional().describe("Sort field"),
20 |   // 排序方式
21 |   order: z.enum(["desc", "asc"]).default("desc").optional().describe("Sort direction"),
22 | });
23 | 
24 | // Type exports
25 | export type GetUserOptions = z.infer<typeof GetUserSchema>;
26 | export type SearchUsersOptions = z.infer<typeof SearchUsersSchema>;
27 | 
28 | // Function implementations
29 | export async function getUser(username: string) {
30 |   username = validateOwnerName(username);
31 | 
32 |   const url = `/users/${username}`;
33 |   const response = await giteeRequest(url, "GET");
34 | 
35 |   return GiteeUserSchema.parse(response);
36 | }
37 | 
38 | export async function getCurrentUser() {
39 |   const url = "/user";
40 |   const response = await giteeRequest(url, "GET");
41 | 
42 |   return GiteeUserSchema.parse(response);
43 | }
44 | 
45 | export async function searchUsers(options: SearchUsersOptions) {
46 |   const { q, page, per_page, sort, order } = options;
47 | 
48 |   const url = new URL(`${getGiteeApiBaseUrl()}/search/users`);
49 |   url.searchParams.append("q", q);
50 |   if (page !== undefined) {
51 |     url.searchParams.append("page", page.toString());
52 |   }
53 |   if (per_page !== undefined) {
54 |     url.searchParams.append("per_page", per_page.toString());
55 |   }
56 |   if (sort) {
57 |     url.searchParams.append("sort", sort);
58 |   }
59 |   if (order) {
60 |     url.searchParams.append("order", order);
61 |   }
62 | 
63 |   const response = await giteeRequest(url.toString(), "GET");
64 | 
65 |   return {
66 |     total_count: (response as any).total_count || 0,
67 |     items: z.array(GiteeUserSchema).parse((response as any).items || []),
68 |   };
69 | }
70 | 
```

--------------------------------------------------------------------------------
/common/server.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
 2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
 3 | import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
 4 | import { z } from 'zod';
 5 | import { zodToJsonSchema } from 'zod-to-json-schema';
 6 | import { isGiteeError } from "./errors.js";
 7 | 
 8 | type MCPServerOptions = {
 9 |   name: string;
10 |   version: string;
11 | };
12 | 
13 | type ToolDefinition = {
14 |   name: string;
15 |   description: string;
16 |   schema: z.ZodType<any, any, any>;
17 |   handler: (params: any) => Promise<any>;
18 | };
19 | 
20 | export class MCPServer {
21 |   private server: Server;
22 |   private tools: Map<string, ToolDefinition> = new Map();
23 | 
24 |   constructor(options: MCPServerOptions) {
25 |     this.server = new Server(
26 |       {
27 |         name: options.name,
28 |         version: options.version,
29 |       },
30 |       {
31 |         capabilities: {
32 |           tools: {},
33 |         },
34 |       }
35 |     );
36 | 
37 |     this.setupRequestHandlers();
38 |   }
39 | 
40 |   private setupRequestHandlers() {
41 |     this.server.setRequestHandler(ListToolsRequestSchema, async () => {
42 |       const toolsList = Array.from(this.tools.values()).map((tool) => ({
43 |         name: tool.name,
44 |         description: tool.description,
45 |         inputSchema: zodToJsonSchema(tool.schema),
46 |       }));
47 | 
48 |       return {
49 |         tools: toolsList,
50 |       };
51 |     });
52 | 
53 |     this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
54 |       try {
55 |         if (!request.params.arguments) {
56 |           throw new Error("Parameters are required.");
57 |         }
58 | 
59 |         const tool = this.tools.get(request.params.name);
60 |         if (!tool) {
61 |           throw new Error(`Unknown tool: ${request.params.name}`);
62 |         }
63 | 
64 |         const args = tool.schema.parse(request.params.arguments);
65 |         const result = await tool.handler(args);
66 | 
67 |         return {
68 |           content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
69 |         };
70 |       } catch (error) {
71 |         if (error instanceof z.ZodError) {
72 |           throw new Error(`Invalid input: ${JSON.stringify(error.errors)}`);
73 |         }
74 |         if (isGiteeError(error)) {
75 |           throw error;
76 |         }
77 |         throw error;
78 |       }
79 |     });
80 |   }
81 | 
82 |   public registerTool(tool: ToolDefinition) {
83 |     this.tools.set(tool.name, tool);
84 |   }
85 | 
86 |   public async connect(transport: StdioServerTransport) {
87 |     await this.server.connect(transport);
88 |   }
89 | }
90 | 
```

--------------------------------------------------------------------------------
/common/errors.ts:
--------------------------------------------------------------------------------

```typescript
 1 | export class GiteeError extends Error {
 2 |   constructor(message: string) {
 3 |     super(message);
 4 |     this.name = "GiteeError";
 5 |   }
 6 | }
 7 | 
 8 | export class GiteeValidationError extends GiteeError {
 9 |   response?: unknown;
10 | 
11 |   constructor(message: string, response?: unknown) {
12 |     super(message);
13 |     this.name = "GiteeValidationError";
14 |     this.response = response;
15 |   }
16 | }
17 | 
18 | export class GiteeResourceNotFoundError extends GiteeError {
19 |   constructor(message: string) {
20 |     super(message);
21 |     this.name = "GiteeResourceNotFoundError";
22 |   }
23 | }
24 | 
25 | export class GiteeAuthenticationError extends GiteeError {
26 |   constructor(message: string) {
27 |     super(message);
28 |     this.name = "GiteeAuthenticationError";
29 |   }
30 | }
31 | 
32 | export class GiteePermissionError extends GiteeError {
33 |   constructor(message: string) {
34 |     super(message);
35 |     this.name = "GiteePermissionError";
36 |   }
37 | }
38 | 
39 | export class GiteeRateLimitError extends GiteeError {
40 |   resetAt: Date;
41 | 
42 |   constructor(message: string, resetAt: Date) {
43 |     super(message);
44 |     this.name = "GiteeRateLimitError";
45 |     this.resetAt = resetAt;
46 |   }
47 | }
48 | 
49 | export class GiteeConflictError extends GiteeError {
50 |   constructor(message: string) {
51 |     super(message);
52 |     this.name = "GiteeConflictError";
53 |   }
54 | }
55 | 
56 | export function isGiteeError(error: unknown): error is GiteeError {
57 |   return error instanceof GiteeError;
58 | }
59 | 
60 | export function createGiteeError(status: number, responseBody: unknown): GiteeError {
61 |   let message = "Gitee API request failed";
62 |   let resetAt: Date | undefined;
63 | 
64 |   if (typeof responseBody === "object" && responseBody !== null) {
65 |     const body = responseBody as Record<string, unknown>;
66 | 
67 |     if (body.message && typeof body.message === "string") {
68 |       message = body.message;
69 |     }
70 | 
71 |     if (body.documentation_url && typeof body.documentation_url === "string") {
72 |       message += ` - Documentation: ${body.documentation_url}`;
73 |     }
74 |   }
75 | 
76 |   switch (status) {
77 |     case 400:
78 |       return new GiteeValidationError(message, responseBody);
79 |     case 401:
80 |       return new GiteeAuthenticationError(message);
81 |     case 403:
82 |       return new GiteePermissionError(message);
83 |     case 404:
84 |       return new GiteeResourceNotFoundError(message);
85 |     case 409:
86 |       return new GiteeConflictError(message);
87 |     case 429:
88 |       return new GiteeRateLimitError(
89 |         message,
90 |         resetAt || new Date(Date.now() + 60 * 1000) // Default: reset after 1 minute
91 |       );
92 |     default:
93 |       return new GiteeError(message);
94 |   }
95 | }
96 | 
```

--------------------------------------------------------------------------------
/operations/repos.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from "zod";
 2 | import { giteeRequest, validateOwnerName, validateRepositoryName, getGiteeApiBaseUrl } from "../common/utils.js";
 3 | import { GiteeRepositorySchema } from "../common/types.js";
 4 | 
 5 | // Schema definitions
 6 | export const CreateRepositorySchema = z.object({
 7 |   // 仓库名称
 8 |   name: z.string().describe("Repository name"),
 9 |   // 仓库描述
10 |   description: z.string().optional().describe("Repository description"),
11 |   // 主页地址
12 |   homepage: z.string().optional().describe("Homepage URL"),
13 |   // 是否私有
14 |   private: z.boolean().default(false).optional().describe("Whether the repository is private"),
15 |   // 是否开启 Issue 功能
16 |   has_issues: z.boolean().default(true).optional().describe("Whether to enable Issue functionality"),
17 |   // 是否开启 Wiki 功能
18 |   has_wiki: z.boolean().default(true).optional().describe("Whether to enable Wiki functionality"),
19 |   // 是否自动初始化仓库
20 |   auto_init: z.boolean().default(false).optional().describe("Whether to automatically initialize the repository"),
21 |   // Git Ignore 模板
22 |   gitignore_template: z.string().optional().describe("Git Ignore template"),
23 |   // License 模板
24 |   license_template: z.string().optional().describe("License template"),
25 |   // 仓库路径
26 |   path: z.string().optional().describe("Repository path"),
27 | });
28 | 
29 | export const ForkRepositorySchema = z.object({
30 |   // 仓库所属空间地址 (企业、组织或个人的地址 path)
31 |   owner: z.string().describe("Repository owner path (enterprise, organization, or personal path)"),
32 |   // 仓库路径 (path)
33 |   repo: z.string().describe("Repository path"),
34 |   // 组织空间地址,不传默认为个人
35 |   organization: z.string().optional().describe("Organization path, defaults to personal account if not provided"),
36 | });
37 | 
38 | // Type exports
39 | export type CreateRepositoryOptions = z.infer<typeof CreateRepositorySchema>;
40 | export type ForkRepositoryOptions = z.infer<typeof ForkRepositorySchema>;
41 | 
42 | // Function implementations
43 | export async function createRepository(options: CreateRepositoryOptions) {
44 |   try {
45 |     console.log('Creating repository parameters:', JSON.stringify(options));
46 |     const url = "/user/repos";
47 |     const response = await giteeRequest(url, "POST", options);
48 |     console.log('Create repository response:', JSON.stringify(response));
49 | 
50 |     // Try to parse the response
51 |     try {
52 |       return GiteeRepositorySchema.parse(response);
53 |     } catch (parseError) {
54 |       console.error('Failed to parse repository response:', parseError);
55 |       // Return the original response to avoid parsing errors
56 |       return response;
57 |     }
58 |   } catch (error) {
59 |     console.error('Failed to create repository request:', error);
60 |     throw error;
61 |   }
62 | }
63 | 
64 | export async function forkRepository(
65 |   owner: string,
66 |   repo: string,
67 |   organization?: string
68 | ) {
69 |   owner = validateOwnerName(owner);
70 |   repo = validateRepositoryName(repo);
71 | 
72 |   const url = `/repos/${owner}/${repo}/forks`;
73 |   const body: Record<string, string> = {};
74 | 
75 |   if (organization) {
76 |     body.organization = validateOwnerName(organization);
77 |   }
78 | 
79 |   const response = await giteeRequest(url, "POST", body);
80 | 
81 |   return GiteeRepositorySchema.parse(response);
82 | }
```

--------------------------------------------------------------------------------
/operations/branches.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { z } from "zod";
  2 | import { giteeRequest, validateBranchName, validateOwnerName, validateRepositoryName, getGiteeApiBaseUrl } from "../common/utils.js";
  3 | import { GiteeCompleteBranchSchema, GiteeBranchSchema } from "../common/types.js";
  4 | 
  5 | // Schema definitions
  6 | export const CreateBranchSchema = z.object({
  7 |   // 仓库所属空间地址 (企业、组织或个人的地址 path)
  8 |   owner: z.string().describe("Repository owner path (enterprise, organization, or personal path)"),
  9 |   // 仓库路径 (path)
 10 |   repo: z.string().describe("Repository path"),
 11 |   // 新创建的分支名称
 12 |   branch_name: z.string().describe("Name for the new branch"),
 13 |   // 起点名称,默认:master
 14 |   refs: z.string().default("master").describe("Source reference for the branch, default: master"),
 15 | });
 16 | 
 17 | export const ListBranchesSchema = z.object({
 18 |   // 仓库所属空间地址 (企业、组织或个人的地址 path)
 19 |   owner: z.string().describe("Repository owner path (enterprise, organization, or personal path)"),
 20 |   // 仓库路径 (path)
 21 |   repo: z.string().describe("Repository path"),
 22 |   // 排序字段
 23 |   sort: z.enum(["name", "updated"]).default("name").optional().describe("Sort field"),
 24 |   // 排序方向
 25 |   direction: z.enum(["asc", "desc"]).default("asc").optional().describe("Sort direction"),
 26 |   // 当前的页码
 27 |   page: z.number().int().default(1).optional().describe("Page number"),
 28 |   // 每页的数量,最大为 100
 29 |   per_page: z.number().int().min(1).max(100).optional().describe("Number of items per page, maximum 100"),
 30 | });
 31 | 
 32 | export const GetBranchSchema = z.object({
 33 |   // 仓库所属空间地址 (企业、组织或个人的地址 path)
 34 |   owner: z.string().describe("Repository owner path (enterprise, organization, or personal path)"),
 35 |   // 仓库路径 (path)
 36 |   repo: z.string().describe("Repository path"),
 37 |   // 分支名称
 38 |   branch: z.string().describe("Branch name"),
 39 | });
 40 | 
 41 | // Type exports
 42 | export type CreateBranchOptions = z.infer<typeof CreateBranchSchema>;
 43 | export type ListBranchesOptions = z.infer<typeof ListBranchesSchema>;
 44 | export type GetBranchOptions = z.infer<typeof GetBranchSchema>;
 45 | 
 46 | // Function implementations
 47 | export async function createBranchFromRef(
 48 |   owner: string,
 49 |   repo: string,
 50 |   branchName: string,
 51 |   refs: string = "master"
 52 | ) {
 53 |   owner = validateOwnerName(owner);
 54 |   repo = validateRepositoryName(repo);
 55 |   branchName = validateBranchName(branchName);
 56 | 
 57 |   const url = `/repos/${owner}/${repo}/branches`;
 58 |   const body = {
 59 |     branch_name: branchName,
 60 |     refs: refs,
 61 |   };
 62 | 
 63 |   const response = await giteeRequest(url, "POST", body);
 64 |   return GiteeBranchSchema.parse(response);
 65 | }
 66 | 
 67 | export async function listBranches(
 68 |   owner: string,
 69 |   repo: string,
 70 |   sort?: string,
 71 |   direction?: string,
 72 |   page?: number,
 73 |   per_page?: number
 74 | ) {
 75 |   owner = validateOwnerName(owner);
 76 |   repo = validateRepositoryName(repo);
 77 | 
 78 |   const url = new URL(`${getGiteeApiBaseUrl()}/repos/${owner}/${repo}/branches`);
 79 | 
 80 |   if (sort) {
 81 |     url.searchParams.append("sort", sort);
 82 |   }
 83 |   if (direction) {
 84 |     url.searchParams.append("direction", direction);
 85 |   }
 86 |   if (page !== undefined) {
 87 |     url.searchParams.append("page", page.toString());
 88 |   }
 89 |   if (per_page !== undefined) {
 90 |     url.searchParams.append("per_page", per_page.toString());
 91 |   }
 92 | 
 93 |   const response = await giteeRequest(url.toString());
 94 |   return z.array(GiteeBranchSchema).parse(response);
 95 | }
 96 | 
 97 | export async function getBranch(owner: string, repo: string, branch: string) {
 98 |   owner = validateOwnerName(owner);
 99 |   repo = validateRepositoryName(repo);
100 |   branch = validateBranchName(branch);
101 | 
102 |   const url = `/repos/${owner}/${repo}/branches/${branch}`;
103 |   const response = await giteeRequest(url);
104 | 
105 |   return GiteeCompleteBranchSchema.parse(response);
106 | }
107 | 
```

--------------------------------------------------------------------------------
/common/utils.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { getUserAgent } from "universal-user-agent";
  2 | import { createGiteeError } from "./errors.js";
  3 | import { VERSION } from "./version.js";
  4 | 
  5 | // Default Gitee API base URL
  6 | const DEFAULT_GITEE_API_BASE_URL = "https://gitee.com/api/v5";
  7 | 
  8 | /**
  9 |  * Get the Gitee API base URL from environment variables or use the default
 10 |  * @returns The Gitee API base URL
 11 |  */
 12 | export function getGiteeApiBaseUrl(): string {
 13 |   return process.env.GITEE_API_BASE_URL || DEFAULT_GITEE_API_BASE_URL;
 14 | }
 15 | 
 16 | type RequestOptions = {
 17 |   method?: string;
 18 |   body?: unknown;
 19 |   headers?: Record<string, string>;
 20 | }
 21 | 
 22 | async function parseResponseBody(response: Response): Promise<unknown> {
 23 |   const contentType = response.headers.get("content-type");
 24 |   if (contentType?.includes("application/json")) {
 25 |     return response.json();
 26 |   }
 27 |   return response.text();
 28 | }
 29 | 
 30 | export function buildUrl(baseUrl: string, params: Record<string, string | number | undefined>): string {
 31 |   const url = new URL(baseUrl);
 32 |   Object.entries(params).forEach(([key, value]) => {
 33 |     if (value !== undefined) {
 34 |       url.searchParams.append(key, value.toString());
 35 |     }
 36 |   });
 37 |   return url.toString();
 38 | }
 39 | 
 40 | const USER_AGENT = `modelcontextprotocol/servers/gitee/v${VERSION} ${getUserAgent()}`;
 41 | 
 42 | // Generate the equivalent curl command for debugging.
 43 | function generateCurlCommand(url: string, method: string, headers: Record<string, string>, body?: unknown): string {
 44 |   let curl = `curl -X ${method} "${url}"`;
 45 | 
 46 |   // Add request headers
 47 |   Object.entries(headers).forEach(([key, value]) => {
 48 |     curl += ` -H "${key}: ${value}"`;
 49 |   });
 50 | 
 51 |   // Add request body
 52 |   if (body) {
 53 |     curl += ` -d '${JSON.stringify(body)}'`;
 54 |   }
 55 | 
 56 |   return curl;
 57 | }
 58 | 
 59 | // debug utility function
 60 | export function debug(message: string, data?: unknown): void {
 61 |   // Only output debug logs if DEBUG environment variable is set
 62 |   if (process.env.DEBUG !== "true") {
 63 |     return;
 64 |   }
 65 |   
 66 |   if (data !== undefined) {
 67 |     console.error(`[DEBUG] ${message}`, typeof data === 'object' ? JSON.stringify(data, null, 2) : data);
 68 |   } else {
 69 |     console.error(`[DEBUG] ${message}`);
 70 |   }
 71 | }
 72 | 
 73 | export async function giteeRequest(
 74 |   urlPath: string,
 75 |   method: string = "GET",
 76 |   body?: unknown,
 77 |   headers?: Record<string, string>
 78 | ): Promise<unknown> {
 79 |   // Check if the URL is already a full URL or a path
 80 |   let url = urlPath.startsWith("http") ? urlPath : `${getGiteeApiBaseUrl()}${urlPath.startsWith("/") ? urlPath : `/${urlPath}`}`;
 81 |   const requestHeaders: Record<string, string> = {
 82 |     "Accept": "application/json",
 83 |     "Content-Type": "application/json",
 84 |     "User-Agent": USER_AGENT,
 85 |     ...headers,
 86 |   };
 87 | 
 88 |   if (process.env.GITEE_PERSONAL_ACCESS_TOKEN) {
 89 |     // The Gitee API uses `access_token` as a query parameter or in the `Authorization` header.
 90 |     // Method 1: Add to URL Query Parameters
 91 |     let urlObj = new URL(url);
 92 |     urlObj.searchParams.append('access_token', process.env.GITEE_PERSONAL_ACCESS_TOKEN);
 93 |     url = urlObj.toString();
 94 | 
 95 |     // Method 2: Add to Request Headers (Two methods are tried to increase success rate)
 96 |     requestHeaders["Authorization"] = `token ${process.env.GITEE_PERSONAL_ACCESS_TOKEN}`;
 97 | 
 98 |     debug(`Using access token: ${process.env.GITEE_PERSONAL_ACCESS_TOKEN.substring(0, 4)}...`);
 99 |   } else {
100 |     debug(`No access token found in environment variables`);
101 |   }
102 | 
103 |   // Print the request
104 |   debug(`Request: ${method} ${url}`);
105 |   debug(`Headers:`, requestHeaders);
106 |   if (body) {
107 |     debug(`Body:`, body);
108 |   }
109 | 
110 |   // Print the equivalent curl command
111 |   const curlCommand = generateCurlCommand(url, method, requestHeaders, body);
112 |   debug(`cURL: ${curlCommand}\n`);
113 | 
114 |   const response = await fetch(url, {
115 |     method,
116 |     headers: requestHeaders,
117 |     body: body ? JSON.stringify(body) : undefined,
118 |   });
119 | 
120 |   const responseBody = await parseResponseBody(response);
121 | 
122 |   // Print the response
123 |   debug(`Response Status: ${response.status} ${response.statusText}`);
124 |   debug(`Response Body:`, responseBody);
125 | 
126 |   if (!response.ok) {
127 |     throw createGiteeError(response.status, responseBody);
128 |   }
129 | 
130 |   return responseBody;
131 | }
132 | 
133 | export function validateBranchName(branch: string): string {
134 |   const sanitized = branch.trim();
135 |   if (!sanitized) {
136 |     throw new Error("分支名不能为空");
137 |   }
138 |   if (sanitized.includes("..")) {
139 |     throw new Error("分支名不能包含 '..'");
140 |   }
141 |   if (/[\s~^:?*[\\\]]/.test(sanitized)) {
142 |     throw new Error("分支名包含无效字符");
143 |   }
144 |   if (sanitized.startsWith("/") || sanitized.endsWith("/")) {
145 |     throw new Error("分支名不能以 '/' 开头或结尾");
146 |   }
147 |   if (sanitized.endsWith(".lock")) {
148 |     throw new Error("分支名不能以 '.lock' 结尾");
149 |   }
150 |   return sanitized;
151 | }
152 | 
153 | export function validateRepositoryName(name: string): string {
154 |   const sanitized = name.trim();
155 |   if (!sanitized) {
156 |     throw new Error("仓库名不能为空");
157 |   }
158 |   if (!/^[a-zA-Z0-9_.-]+$/.test(sanitized)) {
159 |     throw new Error(
160 |       "仓库名只能包含字母、数字、连字符、句点和下划线"
161 |     );
162 |   }
163 |   if (sanitized.startsWith(".") || sanitized.endsWith(".")) {
164 |     throw new Error("仓库名不能以句点开头或结尾");
165 |   }
166 |   return sanitized;
167 | }
168 | 
169 | export function validateOwnerName(owner: string): string {
170 |   const sanitized = owner.trim();
171 |   if (!sanitized) {
172 |     throw new Error("所有者名称不能为空");
173 |   }
174 |   if (!/^[a-zA-Z0-9][a-zA-Z0-9-]*$/.test(sanitized)) {
175 |     throw new Error(
176 |       "所有者名称只能包含字母、数字和连字符,且必须以字母或数字开头"
177 |     );
178 |   }
179 |   return sanitized;
180 | }
181 | 
182 | export async function checkBranchExists(
183 |   owner: string,
184 |   repo: string,
185 |   branch: string
186 | ): Promise<boolean> {
187 |   try {
188 |     await giteeRequest(`/repos/${owner}/${repo}/branches/${branch}`, "GET");
189 |     return true;
190 |   } catch (error) {
191 |     if (error && typeof error === "object" && "name" in error && error.name === "GiteeResourceNotFoundError") {
192 |       return false;
193 |     }
194 |     throw error;
195 |   }
196 | }
197 | 
198 | export async function checkUserExists(username: string): Promise<boolean> {
199 |   try {
200 |     await giteeRequest(`/users/${username}`, "GET");
201 |     return true;
202 |   } catch (error) {
203 |     if (error && typeof error === "object" && "name" in error && error.name === "GiteeResourceNotFoundError") {
204 |       return false;
205 |     }
206 |     throw error;
207 |   }
208 | }
209 | 
```