From 6f796f109457fd253714460fd3330c31252011d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=87=B4=E4=BF=A1?= Date: Thu, 8 May 2025 17:37:16 +0800 Subject: [PATCH] init project --- .DS_Store | Bin 0 -> 6148 bytes Dockerfile | 29 + LICENSE.txt | 45 + README.md | 103 ++ README.zh-ch.md | 102 ++ common/errors.ts | 89 ++ common/types.ts | 520 +++++++++ common/utils.ts | 131 +++ common/version.ts | 1 + index.ts | 634 +++++++++++ operations/.DS_Store | Bin 0 -> 6148 bytes operations/codeup/branches.ts | 201 ++++ operations/codeup/changeRequestComments.ts | 132 +++ operations/codeup/changeRequests.ts | 285 +++++ operations/codeup/compare.ts | 78 ++ operations/codeup/files.ts | 323 ++++++ operations/codeup/repositories.ts | 112 ++ operations/codeup/utils.ts | 74 ++ operations/organization/organization.ts | 31 + operations/projex/project.ts | 236 ++++ operations/projex/sprint.ts | 78 ++ operations/projex/workitem.ts | 201 ++++ package-lock.json | 1158 ++++++++++++++++++++ package.json | 51 + tsconfig.json | 20 + 25 files changed, 4634 insertions(+) create mode 100644 .DS_Store create mode 100644 Dockerfile create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 README.zh-ch.md create mode 100644 common/errors.ts create mode 100644 common/types.ts create mode 100644 common/utils.ts create mode 100644 common/version.ts create mode 100644 index.ts create mode 100644 operations/.DS_Store create mode 100644 operations/codeup/branches.ts create mode 100644 operations/codeup/changeRequestComments.ts create mode 100644 operations/codeup/changeRequests.ts create mode 100644 operations/codeup/compare.ts create mode 100644 operations/codeup/files.ts create mode 100644 operations/codeup/repositories.ts create mode 100644 operations/codeup/utils.ts create mode 100644 operations/organization/organization.ts create mode 100644 operations/projex/project.ts create mode 100644 operations/projex/sprint.ts create mode 100644 operations/projex/workitem.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 tsconfig.json diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..179e8164784eb12ab5147dd195f00ac688e0007e GIT binary patch literal 6148 zcmeHKOHRWu5S=L%3ZF$+h^4R48-x;0&=o77Eh|#T(D4 zG$g1y1ZXB2KW98+JI|J#AtD~#&4xq+BFdnGy)6_SBJH9r>6t|uIrS0gYB3*@!QKm+h?}cr@C0(C(r1DwlRK1 z=X6bHbW4{q8@g%iC*H$8>-Jln*U9}uVe{(jvy7kfTj$%DC;PURg>rEQoPj^jfSjd} zY)8>cXTTY729^xS`4FIjxnZrCjt&$l1pxM8PJ+7R5)u;(bHiE@EfCgFpoX%g7_8y2 z2lLAfYefwww&a6tWVQ^2)9C0wBzNLm(MxB*8E7)Fp|1nE|Bt@U|C>Sn<_tIk|B3K372IwhNMEq*SWe6(iDu%CI#rx1Cum`CCbHiE@7Kr}{BpSSM27Z)* E52o-{AOHXW literal 0 HcmV?d00001 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c3533cf --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +FROM node:22.12-alpine AS builder + +# 复制项目文件 +COPY . /app +COPY tsconfig.json /tsconfig.json + +WORKDIR /app + +# 安装依赖并构建 +RUN --mount=type=cache,target=/root/.npm npm install + +# 添加构建步骤以生成 dist 目录 +RUN npm run build + +FROM node:22.12-alpine AS release + +# 从构建阶段复制必要文件 +COPY --from=builder /app/dist /app/dist +COPY --from=builder /app/package.json /app/package.json +COPY --from=builder /app/package-lock.json /app/package-lock.json + +ENV NODE_ENV=production + +WORKDIR /app + +# 安装生产依赖 +RUN npm ci --ignore-scripts --omit=dev + +ENTRYPOINT ["node", "dist/index.js"] diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..ba9aac4 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,45 @@ + + + + + Rate limit · GitHub + + + + +
+

Access has been restricted

+

You have triggered a rate limit.

+ Please wait a few minutes before you try again;
+ in some cases this may take up to an hour. +

+ +
+ + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..eca37b2 --- /dev/null +++ b/README.md @@ -0,0 +1,103 @@ +# alibabacloud-devops-mcp-server +Yunxiao MCP Server provides AI assistants with the ability to interact with the Yunxiao platform, enabling them to read work item contents in projects, automatically write code after understanding requirements, and submit code merge requests. Enterprise development teams can use it to assist with code reviews, optimize task management, reduce repetitive operations, and thus focus on more important innovation and product delivery. + +## Features + +alibabacloud-devops-mcp-server provides the following capabilities for AI assistants: + +* **Code Repository Management**: Query code repositories and their branches, create branches +* **File Operations**: Create, update, delete, and retrieve code file content +* **Code Review**: Create and manage code review processes +* **Project Management**: Search projects, get project details + +## Tools + +alibabacloud-devops-mcp-server integrates various tools, including: + +### Organization +- `get_current_organization_Info`: Get current user's organization information +- `get_user_organizations`: Get the list of organizations the current user has joined + +### Code Management Tools + +- `create_branch`: Create a branch +- `delete_branch`: Delete a branch +- `get_branch`: Get branch information +- `list_branches`: Get branch list +- `create_file`: Create a file +- `delete_file`: Delete a file +- `get_file_blobs`: Get file content +- `list_files`: Query file tree +- `update_file`: Update file content +- `create_change_request`: Create a merge request +- `create_change_request_comment`: Create a comment on a merge request +- `get_change_request`: Query merge request +- `list_change_request_patch_sets`: Query merge request version list +- `list_change_request`: Query merge request list +- `list_change_request_comments`: Query merge request comment list +- `get_compare`: Compare code +- `get_repository`: Get repository details +- `list_repositories`: Get repository list + +### Project Management Tools + +- `get_project`: Get project details +- `search_projects`: Search projects +- `get_work_item`: Get work item details +- `search_workitems`: Search work items + +## Usage + +### Prerequisites +* node version >= 16.0.0 +* Alibaba Cloud Yunxiao Personal Access Token, [click here to obtain](https://help.aliyun.com/zh/yunxiao/developer-reference/obtain-personal-access-token?spm=a2c4g.11186623.help-menu-150040.d_5_0_1.5dc72af2GnT64i). Grant read and write permissions to all APIs under organization management, project collaboration, and code management. + + ![The personal token authorization page](https://agent-install-beijing.oss-cn-beijing.aliyuncs.com/alibabacloud-devops-mcp-server/img_8.png) + +### Run MCP Server via NPX +```json +{ + "mcpServers": { + "yunxiao": { + "command": "npx", + "args": [ + "-y", + "alibabacloud-devops-mcp-server" + ], + "env": { + "YUNXIAO_ACCESS_TOKEN": "" + } + } + } +} +``` + +### Run MCP Server via Docker Container +1. Docker build +```shell +docker build -t alibabacloud/alibabacloud-devops-mcp-server . +``` +2. Configure MCP Server +```json +{ + "mcpServers": { + "yunxiao": { + "command": "docker", + "args": [ + "run", + "-i", + "--rm", + "-e", + "YUNXIAO_ACCESS_TOKEN", + "alibabacloud/alibabacloud-devops-mcp-server" + ], + "env": { + "YUNXIAO_ACCESS_TOKEN": "" + } + } + } +} +``` + +## Related Links +- [Alibaba Cloud Yunxiao](https://devops.aliyun.com) diff --git a/README.zh-ch.md b/README.zh-ch.md new file mode 100644 index 0000000..249b80b --- /dev/null +++ b/README.zh-ch.md @@ -0,0 +1,102 @@ +# alibabacloud-devops-mcp-server +云效mcp-server工具为 AI 助手提供了与云效平台交互的能力,能够让 AI 助手可以读取项目中工作项的内容,在理解需求后自动编写代码,并提交代码合并请求。企业研发团队可以使用它协助代码审查、优化任务管理、减少重复性操作,从而专注于更重要的创新和产品交付。 + +## 功能特性 + +alibabacloud-devops-mcp-server提供了以下功能,让AI助手能够: + +* **代码仓库管理**:查询代码仓库及其分支、创建分支 +* **文件操作**:创建、更新、删除和获取代码文件内容 +* **代码评审**:创建和管理代码评审流程 +* **项目管理**:搜索项目、获取项目详情 + +## 工具列表 + +alibabacloud-devops-mcp-server集成了多种工具,包括: + +### 组织 +- `get_current_organization_Info`: 获取当前用户所在组织信息 +- `get_user_organizations`: 获取当前用户加入的组织列表 + +### 代码管理工具 + +- `create_branch`: 创建分支 +- `delete_branch`: 删除分支 +- `get_branch`: 获取分支信息 +- `list_branches`: 获取分支列表 +- `create_file`: 创建文件 +- `delete_file`: 删除文件 +- `get_file_blobs`: 获取文件内容 +- `list_files`: 查询文件树 +- `update_file`: 更新文件内容 +- `create_change_request`: 创建合并请求 +- `create_change_request_comment`: 创建合并请求评论 +- `get_change_request`: 查询合并请求 +- `list_change_request_patch_sets`: 查询合并请求版本列表 +- `list_change_request`: 查询合并请求列表 +- `list_change_request_comments`: 查询合并请求评论列表 +- `get_compare`: 代码比较 +- `get_repository`: 获取仓库详情 +- `list_repositories`: 获取仓库列表 + +### 项目管理工具 + +- `get_project`: 获取项目详情 +- `search_projects`: 搜索项目 +- `get_work_item`: 获取工作项详情 +- `search_workitems`: 搜索工作项 + +## 用法 + +### 先决条件 +* node 版本 >= 16.0.0 +* 阿里云云效个人访问令牌,[点击前往](https://help.aliyun.com/zh/yunxiao/developer-reference/obtain-personal-access-token?spm=a2c4g.11186623.help-menu-150040.d_5_0_1.5dc72af2GnT64i),授予组织管理、项目协作、代码管理下所有api的读写权限。 + + ![个人令牌授权页面](https://agent-install-beijing.oss-cn-beijing.aliyuncs.com/alibabacloud-devops-mcp-server/img_8.png) + +### 通过 NPX 运行 MCP 服务器 +```json +{ + "mcpServers": { + "yunxiao": { + "command": "npx", + "args": [ + "-y", + "alibabacloud-devops-mcp-server" + ], + "env": { + "YUNXIAO_ACCESS_TOKEN": "" + } + } + } +} +``` +### 通过 Docker 容器运行 MCP 服务器 +1.Docker build +```shell +docker build -t alibabacloud/alibabacloud-devops-mcp-server . +``` +2.配置 MCP 服务器 +```json +{ + "mcpServers": { + "yunxiao": { + "command": "docker", + "args": [ + "run", + "-i", + "--rm", + "-e", + "YUNXIAO_ACCESS_TOKEN", + "alibabacloud/alibabacloud-devops-mcp-server" + ], + "env": { + "YUNXIAO_ACCESS_TOKEN": "" + } + } + } +} +``` + +## 相关链接 +- [阿里云云效](https://devops.aliyun.com) diff --git a/common/errors.ts b/common/errors.ts new file mode 100644 index 0000000..6a0ae4b --- /dev/null +++ b/common/errors.ts @@ -0,0 +1,89 @@ +export class YunxiaoError extends Error { + constructor( + message: string, + public readonly status: number, + public readonly response: unknown + ) { + super(message); + this.name = "YunxiaoError"; + } +} + +export class YunxiaoValidationError extends YunxiaoError { + constructor(message: string, status: number, response: unknown) { + super(message, status, response); + this.name = "YunxiaoValidationError"; + } +} + +export class YunxiaoResourceNotFoundError extends YunxiaoError { + constructor(resource: string) { + super(`Resource not found: ${resource}`, 404, { message: `${resource} not found` }); + this.name = "YunxiaoResourceNotFoundError"; + } +} + +export class YunxiaoAuthenticationError extends YunxiaoError { + constructor(message = "Authentication failed") { + super(message, 401, { message }); + this.name = "YunxiaoAuthenticationError"; + } +} + +export class YunxiaoPermissionError extends YunxiaoError { + constructor(message = "Insufficient permissions") { + super(message, 403, { message }); + this.name = "YunxiaoPermissionError"; + } +} + +export class YunxiaoRateLimitError extends YunxiaoError { + constructor( + message = "Rate limit exceeded", + public readonly resetAt: Date + ) { + super(message, 429, { message, reset_at: resetAt.toISOString() }); + this.name = "YunxiaoRateLimitError"; + } +} + +export class YunxiaoConflictError extends YunxiaoError { + constructor(message: string) { + super(message, 409, { message }); + this.name = "YunxiaoConflictError"; + } +} + +export function isYunxiaoError(error: unknown): error is YunxiaoError { + return error instanceof YunxiaoError; +} + +export function createYunxiaoError(status: number, response: any): YunxiaoError { + switch (status) { + case 401: + return new YunxiaoAuthenticationError(response?.message); + case 403: + return new YunxiaoPermissionError(response?.message); + case 404: + return new YunxiaoResourceNotFoundError(response?.message || "Resource"); + case 409: + return new YunxiaoConflictError(response?.message || "Conflict occurred"); + case 422: + return new YunxiaoValidationError( + response?.message || "Validation failed", + status, + response + ); + case 429: + return new YunxiaoRateLimitError( + response?.message, + new Date(response?.reset_at || Date.now() + 60000) + ); + default: + return new YunxiaoError( + response?.message || "Yunxiao API error", + status, + response + ); + } +} \ No newline at end of file diff --git a/common/types.ts b/common/types.ts new file mode 100644 index 0000000..ff883fc --- /dev/null +++ b/common/types.ts @@ -0,0 +1,520 @@ +import { z } from "zod"; + +// Organization related types +export const CurrentOrganizationInfoSchema = z.object({ + lastOrganization: z.string().optional().describe("Last logged-in organization"), +}); + +export const OrganizationInfoSchema = z.object({ + id: z.string().optional().describe("Organization ID"), + name: z.string().optional().describe("Organization name"), + description: z.string().optional().describe("Organization description"), +}); + +export const UserOrganizationsInfoSchema = z.array(OrganizationInfoSchema); + +// User related types +export const UserInfoSchema = z.object({ + id: z.string().nullable().optional().describe("User ID"), + name: z.string().nullable().optional().describe("User name"), +}); + +// Custom field related types +export const FieldItemSchema = z.object({ + displayValue: z.string().nullable().optional().describe("Display value"), + identifier: z.string().nullable().optional().describe("Identifier"), +}); + +export const CustomFieldValuesSchema = z.object({ + fieldId: z.string().nullable().optional().describe("Custom field ID"), + fieldName: z.string().nullable().optional().describe("Custom field name"), + values: z.array(FieldItemSchema).nullable().optional().describe("Values"), +}); + +export const ValueSchema = z.object({ + displayValue: z.string().nullable().optional().describe("Display value"), + identifier: z.string().nullable().optional().describe("Identifier"), +}); + +export const CustomFieldValueSchema = z.object({ + fieldFormat: z.string().nullable().optional().describe("Field format"), + fieldId: z.string().nullable().optional().describe("Field ID"), + fieldName: z.string().nullable().optional().describe("Field name"), + values: z.array(ValueSchema).nullable().optional().describe("Values"), +}); + +// Project related types +export const ProjectStatusInfoSchema = z.object({ + id: z.string().nullable().optional().describe("Status ID"), + name: z.string().nullable().optional().describe("Status name"), +}); + +export const ProjectInfoSchema = z.object({ + id: z.string().nullable().optional().describe("Project ID, unique identifier for the project"), + name: z.string().nullable().optional().describe("Project name"), + description: z.string().nullable().optional().describe("Project description"), + icon: z.string().nullable().optional().describe("Icon URL"), + customCode: z.string().nullable().optional().describe("Code number"), + gmtCreate: z.number().int().nullable().optional().describe("Creation timestamp"), + gmtModified: z.number().int().nullable().optional().describe("Last update timestamp"), + logicalStatus: z.string().nullable().optional().describe("Logical status, e.g., normal"), + scope: z.string().nullable().optional().describe("Public type, enum values: public, private"), + creator: UserInfoSchema.nullable().optional().describe("Creator"), + modifier: UserInfoSchema.nullable().optional().describe("Modifier"), + status: ProjectStatusInfoSchema.nullable().optional().describe("Status"), + customFieldValues: z.array(CustomFieldValuesSchema).nullable().optional().describe("Custom field values"), + logoUrl: z.string().nullable().optional().describe("Project logo URL"), + isArchived: z.boolean().nullable().optional().describe("Whether archived"), + isPublic: z.boolean().nullable().optional().describe("Whether public"), + accessLevel: z.string().nullable().optional().describe("Access level"), + organizationIdentifier: z.string().nullable().optional().describe("Organization identifier"), + includeSubOrgs: z.boolean().nullable().optional().describe("Whether to include sub-organizations"), + isTemplate: z.boolean().nullable().optional().describe("Whether it's a template"), + parentIdentifier: z.string().nullable().optional().describe("Parent identifier"), + associatedCodes: z.array(z.string()).nullable().optional().describe("Associated code repositories"), + associatedCodeSources: z.array(z.string()).nullable().optional().describe("Associated code sources"), + customField: z.record(z.string(), z.any()).nullable().optional().describe("Custom fields"), + statusInfo: z.object({ + identifier: z.string().nullable().optional().describe("Status identifier"), + name: z.string().nullable().optional().describe("Status name"), + color: z.string().nullable().optional().describe("Status color"), + type: z.string().nullable().optional().describe("Status type"), + desc: z.string().nullable().optional().describe("Status description"), + }).nullable().optional().describe("Status information"), +}); + +export const ProjectListSchema = z.object({ + totalCount: z.number().optional().describe("Total count of projects"), + result: z.array(ProjectInfoSchema).optional().describe("List of projects"), +}); + +export const SearchProjectsParamsSchema = z.object({ + organizationId: z.string().describe("Organization ID"), + name: z.string().optional().describe("Text contained in project name"), + creator: z.string().optional().describe("Creator"), + admin: z.string().optional().describe("Administrator"), + status: z.string().optional().describe("Project status ID, multiple separated by commas"), + createdBefore: z.string().optional().describe("Created not later than, format: YYYY-MM-DD"), + createdAfter: z.string().optional().describe("Created not earlier than, format: YYYY-MM-DD"), + logicalStatus: z.string().optional().describe("Logical status, e.g., NORMAL"), + extraConditions: z.string().optional().describe("Additional filter conditions"), + advancedConditions: z.string().optional().describe("Advanced filter conditions, JSON format"), + page: z.number().optional().describe("Page number"), + perPage: z.number().optional().describe("Page size, 0-200, default value is 20"), + orderBy: z.string().optional().describe("Sort field, default is gmtCreate"), + sort: z.string().optional().describe("Sort order, default is desc"), +}); + +// Sprint related types +export const SprintInfoSchema = z.object({ + identifier: z.string().optional().describe("Sprint identifier"), + name: z.string().optional().describe("Sprint name"), + goal: z.string().optional().describe("Sprint goal"), + startDate: z.string().optional().describe("Start date"), + endDate: z.string().optional().describe("End date"), + status: z.string().optional().describe("Status"), + spaceIdentifier: z.string().optional().describe("Project identifier"), + organizationIdentifier: z.string().optional().describe("Organization identifier"), + gmtCreate: z.string().optional().describe("Creation time"), + gmtModified: z.string().optional().describe("Last modified time"), + capacityHours: z.number().optional().describe("Capacity hours"), + creator: UserInfoSchema.nullable().optional().describe("Creator"), + description: z.string().optional().describe("Description"), + locked: z.boolean().optional().describe("Whether locked"), + modifier: UserInfoSchema.nullable().optional().describe("Modifier"), + owners: z.array(UserInfoSchema).nullable().optional().describe("Owners"), +}); + +export const SprintSchema = z.object({ + id: z.string().optional().describe("Sprint ID"), + name: z.string().optional().describe("Sprint name"), +}); + +// Work item related types +export const WorkItemTypeSchema = z.object({ + id: z.string().nullable().optional().describe("Work item type ID"), + name: z.string().nullable().optional().describe("Work item type name"), +}); + +export const StatusSchema = z.object({ + displayName: z.string().nullable().optional().describe("Display name"), + id: z.string().nullable().optional().describe("Status ID"), + name: z.string().nullable().optional().describe("Status name"), + nameEn: z.string().nullable().optional().describe("English name"), +}); + +export const SpaceSchema = z.object({ + id: z.string().optional().describe("Space ID"), + name: z.string().optional().describe("Space name"), +}); + +export const LabelSchema = z.object({ + id: z.string().nullable().optional().describe("Label ID"), + name: z.string().nullable().optional().describe("Label name"), + color: z.string().nullable().optional().describe("Label color"), +}); + +export const VersionSchema = z.object({ + id: z.string().nullable().optional().describe("Version ID"), + name: z.string().nullable().optional().describe("Version name"), +}); + +export const WorkItemSchema = z.object({ + id: z.string().nullable().optional().describe("Work item ID"), + subject: z.string().nullable().optional().describe("Title"), + description: z.string().nullable().optional().describe("Description"), + gmtCreate: z.number().int().nullable().optional().describe("Creation time"), + gmtModified: z.number().int().nullable().optional().describe("Last modification time"), + workitemType: WorkItemTypeSchema.nullable().optional().describe("Work item type"), + status: StatusSchema.nullable().optional().describe("Status"), + formatType: z.string().nullable().optional().describe("Format type"), + categoryId: z.string().nullable().optional().describe("Category ID"), + logicalStatus: z.string().nullable().optional().describe("Logical status"), + parentId: z.string().nullable().optional().describe("Parent ID"), + serialNumber: z.string().nullable().optional().describe("Serial number"), + statusStageId: z.string().nullable().optional().describe("Status stage ID"), + updateStatusAt: z.string().nullable().optional().describe("Status update time"), + idPath: z.string().nullable().optional().describe("ID path"), + + assignedTo: UserInfoSchema.nullable().optional().describe("Assignee"), + creator: UserInfoSchema.nullable().optional().describe("Creator"), + modifier: UserInfoSchema.nullable().optional().describe("Modifier"), + verifier: UserInfoSchema.nullable().optional().describe("Verifier"), + space: SpaceSchema.nullable().optional().describe("Space"), + sprint: SprintSchema.nullable().optional().describe("Sprint"), + + labels: z.array(LabelSchema).nullable().optional().describe("Labels"), + participants: z.array(UserInfoSchema).nullable().optional().describe("Participants"), + trackers: z.array(UserInfoSchema).nullable().optional().describe("Trackers"), + versions: z.array(VersionSchema).nullable().optional().describe("Versions"), + customFieldValues: z.array(CustomFieldValueSchema).nullable().optional().describe("Custom field values"), + + identifier: z.string().nullable().optional().describe("Work item identifier"), + priority: z.string().nullable().optional().describe("Priority"), + spaceIdentifier: z.string().nullable().optional().describe("Project identifier"), + organizationIdentifier: z.string().nullable().optional().describe("Organization identifier"), + parentIdentifier: z.string().nullable().optional().describe("Parent work item identifier"), + customFields: z.record(z.string(), z.any()).nullable().optional().describe("Custom fields"), + type: z.string().nullable().optional().describe("Type"), +}); + +// Search condition related types +export const FilterConditionSchema = z.object({ + className: z.string().optional().describe("Class name"), + fieldIdentifier: z.string().optional().describe("Field identifier"), + format: z.string().optional().describe("Format"), + operator: z.string().optional().describe("Operator"), + toValue: z.string().nullable().optional().describe("To value"), + value: z.array(z.string()).nullable().optional().describe("Values"), +}); + +export const ConditionsSchema = z.object({ + conditionGroups: z.array(z.array(z.any())).nullable().optional().describe("Condition groups"), +}); + +export const CodeupBranchSchema = z.object({ + name: z.string().optional().describe("Branch name"), + defaultBranch: z.boolean().optional().describe("Whether it is the default branch"), + commit: z.object({ + authorEmail: z.string().optional().describe("Author email"), + authorName: z.string().optional().describe("Author name"), + committedDate: z.string().optional().describe("Commit date"), + committerEmail: z.string().optional().describe("Committer email"), + committerName: z.string().optional().describe("Committer name"), + id: z.string().optional().describe("Commit ID"), + message: z.string().optional().describe("Commit message"), + parentIds: z.array(z.string()).optional().describe("Parent commit IDs"), + shortId: z.string().optional().describe("Code group path"), + stats: z.object({ + additions: z.number().optional().describe("Added lines"), + deletions: z.number().optional().describe("Deleted lines"), + total: z.number().optional().describe("Total lines"), + }).nullable().optional().describe("Commit statistics"), + title: z.string().optional().describe("Title, first line of the commit message"), + webUrl: z.string().url().optional().describe("Web access URL"), + }).nullable().optional().describe("Commit information"), + protected: z.boolean().optional().describe("Whether it is a protected branch"), + webUrl: z.string().url().optional().describe("Web access URL"), +}); + +export const FileContentSchema = z.object({ + fileName: z.string().optional().describe("File name"), + filePath: z.string().optional().describe("File path"), + size: z.string().optional().describe("File size"), + content: z.string().optional().describe("File content"), + encoding: z.string().optional().describe("Content encoding (base64 or text)"), + ref: z.string().optional().describe("Reference (branch, tag, or commit)"), + blobId: z.string().optional().describe("Blob ID"), + commitId: z.string().optional().describe("Commit ID"), +}); + +export const FileInfoSchema = z.object({ + id: z.string().optional().describe("File/directory ID"), + name: z.string().optional().describe("File/directory name"), + path: z.string().optional().describe("File/directory path"), + type: z.string().optional().describe("Type of entry: tree (directory) or blob (file)"), + mode: z.string().optional().describe("File mode"), + size: z.number().int().optional().describe("File size (not present for directories)"), +}); + +export const RepositorySchema = z.object({ + id: z.number().int().optional().describe("Repository ID"), + name: z.string().optional().describe("Repository name"), + webUrl: z.string().optional().describe("Web URL for accessing the repository"), + description: z.string().optional().describe("Repository description"), + path: z.string().optional().describe("Repository path"), +}); + +export const CodeupUserSchema = z.object({ + userId: z.string().optional().describe("User ID"), + username: z.string().optional().describe("Username"), + name: z.string().optional().describe("Full name"), + email: z.string().optional().describe("Email address"), + avatar: z.string().optional().describe("Avatar URL"), + state: z.string().optional().describe("User state"), +}); + +export const PatchSetSchema = z.object({ + commitId: z.string().nullable().optional(), + createTime: z.string().nullable().optional(), + patchSetBizId: z.string().nullable().optional(), + patchSetName: z.string().optional(), + ref: z.string().optional(), + relatedMergeItemType: z.string().optional(), + shortId: z.string().optional(), + versionNo: z.number().int().optional(), +}); + +export const CommentLocationSchema = z.object({ + canLocated: z.boolean().optional().describe("Can be located"), + locatedFilePath: z.string().optional().describe("Located file path"), + locatedLineNumber: z.number().optional().describe("Located line number"), + locatedPatchSetBizId: z.string().optional().describe("Located patch set business ID"), +}); + +export const ChangeRequestCommentSchema = z.object({ + author: z.object({ + avatar: z.string().nullable().optional().describe("用户头像地址"), + email: z.string().nullable().optional().describe("用户邮箱"), + name: z.string().nullable().optional().describe("用户名称"), + state: z.string().nullable().optional().describe("用户状态:active - 激活可用;blocked - 阻塞暂不可用"), + userId: z.string().nullable().optional().describe("云效用户ID"), + username: z.string().nullable().optional().describe("用户登录名") + }).nullable().optional().describe("评论作者信息"), + child_comments_list: z.array(z.any()).nullable().optional().describe("子评论列表"), + comment_biz_id: z.string().nullable().optional().describe("评论业务ID"), + comment_time: z.string().nullable().optional().describe("评论时间"), + comment_type: z.string().nullable().optional().describe("评论类型:GLOBAL_COMMENT - 全局评论;INLINE_COMMENT - 行内评论"), + content: z.string().nullable().optional().describe("评论内容"), + expression_reply_list: z.array(z.any()).nullable().optional().describe("表情回复列表"), + filePath: z.string().nullable().optional().describe("文件路径,仅行内评论有"), + from_patchset_biz_id: z.string().nullable().optional().describe("比较的起始版本ID"), + is_deleted: z.boolean().nullable().optional().describe("是否已删除"), + last_edit_time: z.string().nullable().optional().describe("最后编辑时间"), + last_edit_user: z.object({ + avatar: z.string().nullable().optional().describe("用户头像地址"), + email: z.string().nullable().optional().describe("用户邮箱"), + name: z.string().nullable().optional().describe("用户名称"), + state: z.string().nullable().optional().describe("用户状态"), + userId: z.string().nullable().optional().describe("云效用户ID"), + username: z.string().nullable().optional().describe("用户登录名") + }).nullable().optional().describe("最后编辑用户"), + last_resolved_status_change_time: z.string().nullable().optional().describe("最后解决状态变更时间"), + last_resolved_status_change_user: z.object({ + avatar: z.string().nullable().optional().describe("用户头像地址"), + email: z.string().nullable().optional().describe("用户邮箱"), + name: z.string().nullable().optional().describe("用户名称"), + state: z.string().nullable().optional().describe("用户状态"), + userId: z.string().nullable().optional().describe("云效用户ID"), + username: z.string().nullable().optional().describe("用户登录名") + }).nullable().optional().describe("最后解决状态变更用户"), + line_number: z.number().int().nullable().optional().describe("所在行号"), + location: z.object({ + can_located: z.boolean().nullable().optional().describe("是否可以定位"), + located_file_path: z.string().nullable().optional().describe("定位的文件路径"), + located_line_number: z.number().int().nullable().optional().describe("定位的行号"), + located_patch_set_biz_id: z.string().nullable().optional().describe("定位的补丁集业务ID") + }).nullable().optional().describe("位置信息"), + mr_biz_id: z.string().nullable().optional().describe("所属合并请求的业务ID"), + out_dated: z.boolean().nullable().optional().describe("是否过期评论"), + parent_comment_biz_id: z.string().nullable().optional().describe("父评论业务ID"), + project_id: z.number().int().nullable().optional().describe("代码库ID"), + related_patchset: z.object({ + commitId: z.string().nullable().optional().describe("版本对应的提交ID"), + createTime: z.string().nullable().optional().describe("版本创建时间"), + patchSetBizId: z.string().nullable().optional().describe("版本ID,具有唯一性"), + patchSetName: z.string().nullable().optional().describe("版本名称"), + ref: z.string().nullable().optional().describe("版本对应的ref信息"), + relatedMergeItemType: z.string().nullable().optional().describe("关联的类型:MERGE_SOURCE - 合并源;MERGE_TARGET - 合并目标"), + shortId: z.string().nullable().optional().describe("提交ID对应的短ID,通常为8位"), + versionNo: z.number().int().nullable().optional().describe("版本号") + }).nullable().optional().describe("关联的补丁集信息"), + resolved: z.boolean().nullable().optional().describe("是否已解决"), + root_comment_biz_id: z.string().nullable().optional().describe("根评论业务ID"), + state: z.string().nullable().optional().describe("评论状态:OPENED - 已公开;DRAFT - 草稿"), + to_patchset_biz_id: z.string().nullable().optional().describe("比较的目标版本ID"), +}); + +export const ChangeRequestSchema = z.object({ + ahead: z.number().int().nullable().optional().describe("源分支领先目标分支的commit数量"), + allRequirementsPass: z.boolean().nullable().optional().describe("是否所有卡点项通过"), + author: z.object({ + avatar: z.string().nullable().optional().describe("用户头像地址"), + email: z.string().nullable().optional().describe("用户邮箱"), + name: z.string().nullable().optional().describe("用户名称"), + state: z.string().nullable().optional().describe("用户状态:active - 激活可用;blocked - 阻塞暂不可用"), + userId: z.string().nullable().optional().describe("云效用户ID"), + username: z.string().nullable().optional().describe("用户登录名") + }).nullable().optional().describe("创建者信息"), + behind: z.number().int().nullable().optional().describe("目标分支领先源分支的commit数量"), + canRevertOrCherryPick: z.boolean().nullable().optional().describe("是否能Revert或者CherryPick"), + conflictCheckStatus: z.string().nullable().optional().describe("冲突检测状态:CHECKING - 检测中;HAS_CONFLICT - 有冲突;NO_CONFLICT - 无冲突;FAILED - 检测失败"), + createFrom: z.string().nullable().optional().describe("创建来源:WEB - 页面创建;COMMAND_LINE - 命令行创建"), + createTime: z.string().nullable().optional().describe("创建时间"), + description: z.string().nullable().optional().describe("描述"), + detailUrl: z.string().nullable().optional().describe("合并请求详情地址"), + hasReverted: z.boolean().nullable().optional().describe("是否Revert过"), + localId: z.union([z.string(), z.number().int()]).nullable().optional().describe("局部ID,表示代码库中第几个合并请求"), + mergedRevision: z.string().nullable().optional().describe("合并版本(提交ID),仅已合并状态才有值"), + mrType: z.string().nullable().optional().describe("合并请求类型"), + projectId: z.number().int().nullable().optional().describe("项目ID"), + reviewers: z.array(z.object({ + avatar: z.string().nullable().optional().describe("用户头像地址"), + email: z.string().nullable().optional().describe("用户邮箱"), + hasCommented: z.boolean().nullable().optional().describe("是否已评论"), + hasReviewed: z.boolean().nullable().optional().describe("是否已审阅"), + name: z.string().nullable().optional().describe("用户名称"), + reviewOpinionStatus: z.string().nullable().optional().describe("审阅意见状态"), + reviewTime: z.string().nullable().optional().describe("审阅时间"), + state: z.string().nullable().optional().describe("用户状态"), + userId: z.string().nullable().optional().describe("云效用户ID"), + username: z.string().nullable().optional().describe("用户登录名") + })).nullable().optional().describe("评审人列表"), + sourceBranch: z.string().nullable().optional().describe("源分支"), + sourceCommitId: z.string().nullable().optional().describe("源提交ID,当createFrom=COMMAND_LINE时有值"), + sourceProjectId: z.union([z.string(), z.number().int()]).nullable().optional().describe("源库ID"), + sourceRef: z.string().nullable().optional().describe("源提交引用,当createFrom=COMMAND_LINE时有值"), + status: z.string().nullable().optional().describe("合并请求状态:UNDER_DEV - 开发中;UNDER_REVIEW - 评审中;TO_BE_MERGED - 待合并;CLOSED - 已关闭;MERGED - 已合并"), + subscribers: z.array(z.object({ + avatar: z.string().nullable().optional().describe("用户头像地址"), + email: z.string().nullable().optional().describe("用户邮箱"), + name: z.string().nullable().optional().describe("用户名称"), + state: z.string().nullable().optional().describe("用户状态"), + userId: z.string().nullable().optional().describe("云效用户ID"), + username: z.string().nullable().optional().describe("用户登录名") + })).nullable().optional().describe("订阅人列表"), + supportMergeFastForwardOnly: z.boolean().nullable().optional().describe("是否支持fast-forward-only"), + targetBranch: z.string().nullable().optional().describe("目标分支"), + targetProjectId: z.union([z.string(), z.number().int()]).nullable().optional().describe("目标库ID"), + targetProjectNameWithNamespace: z.string().nullable().optional().describe("目标库名称(含完整父路径)"), + targetProjectPathWithNamespace: z.string().nullable().optional().describe("目标库路径(含完整父路径)"), + title: z.string().nullable().optional().describe("标题"), + totalCommentCount: z.number().int().nullable().optional().describe("总评论数"), + unResolvedCommentCount: z.number().int().nullable().optional().describe("未解决评论数"), + updateTime: z.string().nullable().optional().describe("更新时间"), + webUrl: z.string().nullable().optional().describe("页面地址") +}); + +export const StatsSchema = z.object({ + additions: z.number().optional().describe("Added lines"), + deletions: z.number().optional().describe("Deleted lines"), + total: z.number().optional().describe("Total lines"), +}); + +export const CommitSchema = z.object({ + authorEmail: z.string().optional().describe("Author email"), + authorName: z.string().optional().describe("Author name"), + authoredDate: z.string().optional().describe("Authored date"), + committedDate: z.string().optional().describe("Committed date"), + committerEmail: z.string().optional().describe("Committer email"), + committerName: z.string().optional().describe("Committer name"), + id: z.string().optional().describe("Commit ID"), + message: z.string().optional().describe("Commit message"), + parentIds: z.array(z.string()).nullable().optional().describe("Parent commit IDs"), + shortId: z.string().optional().describe("Short ID"), + stats: StatsSchema.nullable().optional().describe("Commit statistics"), + title: z.string().optional().describe("Title"), + webUrl: z.string().optional().describe("Web URL"), +}); + +export const DiffSchema = z.object({ + aMode: z.string().optional().describe("A mode"), + bMode: z.string().optional().describe("B mode"), + deletedFile: z.boolean().optional().describe("Whether the file was deleted"), + diff: z.string().optional().describe("Diff content"), + isBinary: z.boolean().optional().describe("Whether the file is binary"), + newFile: z.boolean().optional().describe("Whether the file is new"), + newId: z.string().optional().describe("New ID"), + newPath: z.string().optional().describe("New path"), + oldId: z.string().optional().describe("Old ID"), + oldPath: z.string().optional().describe("Old path"), + renamedFile: z.boolean().optional().describe("Whether the file was renamed"), +}); + +export const CompareSchema = z.object({ + base_commit_sha: z.string().optional(), + commits: z.array(z.unknown()).optional(), + commits_count: z.number().optional(), + diffs: z.array(z.unknown()).optional(), + files_count: z.number().optional(), + from: z.string().optional(), + to: z.string().optional(), +}); + +export const CreateFileResponseSchema = z.object({ + filePath: z.string().optional().describe("File path"), + branch: z.string().optional().describe("Branch name"), + newOid: z.string().optional().describe("Git Object ID"), +}); + +export const DeleteFileResponseSchema = z.object({ + filePath: z.string().optional().describe("File path"), + branch: z.string().optional().describe("Branch name"), + commitId: z.string().optional().describe("Commit ID"), + commitMessage: z.string().optional().describe("Commit message"), +}); + +// Type Exports +export type CurrentOrganizationInfo = z.infer; +export type OrganizationInfo = z.infer; +export type UserOrganizationsInfo = z.infer; + +export type UserInfo = z.infer; +export type FieldItem = z.infer; +export type CustomFieldValues = z.infer; +export type Value = z.infer; +export type CustomFieldValue = z.infer; + +export type ProjectStatusInfo = z.infer; +export type ProjectInfo = z.infer; +export type ProjectList = z.infer; +export type SearchProjectsParams = z.infer; + +export type SprintInfo = z.infer; +export type Sprint = z.infer; + +export type WorkItemType = z.infer; +export type Status = z.infer; +export type Space = z.infer; +export type Label = z.infer; +export type Version = z.infer; +export type WorkItem = z.infer; + +export type FilterCondition = z.infer; +export type Conditions = z.infer; + +export type CodeupBranch = z.infer; +export type FileContent = z.infer; +export type FileInfo = z.infer; +export type Repository = z.infer; +export type CodeupUser = z.infer; +export type PatchSet = z.infer; +export type CommentLocation = z.infer; +export type ChangeRequestComment = z.infer; +export type ChangeRequest = z.infer; +export type Stats = z.infer; +export type Commit = z.infer; +export type Diff = z.infer; +export type Compare = z.infer; +export type CreateFileResponse = z.infer; +export type DeleteFileResponse = z.infer; \ No newline at end of file diff --git a/common/utils.ts b/common/utils.ts new file mode 100644 index 0000000..92ac061 --- /dev/null +++ b/common/utils.ts @@ -0,0 +1,131 @@ +import { getUserAgent } from "universal-user-agent"; +import { createYunxiaoError } from "./errors.js"; +import { VERSION } from "./version.js"; + +const DEFAULT_YUNXIAO_API_BASE_URL = "https://openapi-rdc.aliyuncs.com"; + +/** + * Get the Yunxiao API base URL from environment variables or use the default + * @returns The Yunxiao API base URL + */ +export function getYunxiaoApiBaseUrl(): string { + return process.env.YUNXIAO_API_BASE_URL || DEFAULT_YUNXIAO_API_BASE_URL; +} + +type RequestOptions = { + method?: string; + body?: unknown; + headers?: Record; +} + +export function debug(message: string, data?: unknown): void { + if (data !== undefined) { + console.error(`[DEBUG] ${message}`, typeof data === 'object' ? JSON.stringify(data, null, 2) : data); + } else { + console.error(`[DEBUG] ${message}`); + } +} + +async function parseResponseBody(response: Response): Promise { + const contentType = response.headers.get("content-type"); + if (contentType?.includes("application/json")) { + return response.json(); + } + return response.text(); +} + +export function buildUrl(baseUrl: string, params: Record): string { + // Handle baseUrl that doesn't have protocol + const fullBaseUrl = baseUrl.startsWith('http') + ? baseUrl + : `${getYunxiaoApiBaseUrl()}${baseUrl.startsWith('/') ? baseUrl : `/${baseUrl}`}`; + + try { + const url = new URL(fullBaseUrl); + + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined) { + url.searchParams.append(key, value.toString()); + } + }); + + const result = url.toString(); + console.error(`[DEBUG] Final URL: ${result}`); + + // If we started with a relative URL, return just the path portion + if (!baseUrl.startsWith('http')) { + // Extract the path and query string from the full URL + const urlObj = new URL(result); + return urlObj.pathname + urlObj.search; + } + + return result; + } catch (error) { + console.error(`[ERROR] Failed to build URL: ${error}`); + + // Fallback: manually append query parameters + let urlWithParams = baseUrl; + const queryParts: string[] = []; + + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined) { + queryParts.push(`${encodeURIComponent(key)}=${encodeURIComponent(value.toString())}`); + } + }); + + if (queryParts.length > 0) { + urlWithParams += (urlWithParams.includes('?') ? '&' : '?') + queryParts.join('&'); + } + + console.error(`[DEBUG] Fallback URL: ${urlWithParams}`); + return urlWithParams; + } +} + +const USER_AGENT = `modelcontextprotocol/servers/alibabacloud-devops-mcp-server/v${VERSION} ${getUserAgent()}`; + +export async function yunxiaoRequest( + urlPath: string, + options: RequestOptions = {}, +): Promise { + // Check if the URL is already a full URL or a path + let url = urlPath.startsWith("http") ? urlPath : `${getYunxiaoApiBaseUrl()}${urlPath.startsWith("/") ? urlPath : `/${urlPath}`}`; + const requestHeaders: Record = { + "Accept": "application/json", + "Content-Type": "application/json", + "User-Agent": USER_AGENT, + ...options.headers, + }; + + if (process.env.YUNXIAO_ACCESS_TOKEN) { + requestHeaders["x-yunxiao-token"] = process.env.YUNXIAO_ACCESS_TOKEN; + } + + debug(`Request: ${options.method} ${url}`); + debug(`Headers:`, requestHeaders); + + const response = await fetch(url, { + method : options.method || "GET", + headers: requestHeaders, + body: options.body ? JSON.stringify(options.body) : undefined, + } as RequestInit); + + const responseBody = await parseResponseBody(response); + debug(`Response Body:`, responseBody) + + if (!response.ok) { + throw createYunxiaoError(response.status, responseBody); + } + + return responseBody; +} + +export function pathEscape(filePath: string): string { + // 先使用encodeURIComponent进行编码 + let encoded = encodeURIComponent(filePath); + + // 将编码后的%2F(/的编码)替换回/ + encoded = encoded.replace(/%2F/gi, "/"); + + return encoded; +} diff --git a/common/version.ts b/common/version.ts new file mode 100644 index 0000000..52c905c --- /dev/null +++ b/common/version.ts @@ -0,0 +1 @@ +export const VERSION = "0.1.0"; diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..f717348 --- /dev/null +++ b/index.ts @@ -0,0 +1,634 @@ +#!/usr/bin/env node +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; +import { z } from 'zod'; +import { zodToJsonSchema } from 'zod-to-json-schema'; + +import * as branches from './operations/codeup/branches.js'; +import * as files from './operations/codeup/files.js'; +import * as repositories from './operations/codeup/repositories.js'; +import * as changeRequests from './operations/codeup/changeRequests.js'; +import * as changeRequestComments from './operations/codeup/changeRequestComments.js'; +import * as organization from './operations/organization/organization.js'; +import * as project from './operations/projex/project.js'; +import * as sprint from './operations/projex/sprint.js'; +import * as workitem from './operations/projex/workitem.js'; +import * as compare from './operations/codeup/compare.js' +import { + isYunxiaoError, + YunxiaoAuthenticationError, YunxiaoConflictError, + YunxiaoError, YunxiaoPermissionError, YunxiaoRateLimitError, + YunxiaoResourceNotFoundError, + YunxiaoValidationError +} from "./common/errors.js"; +import { VERSION } from "./common/version.js"; +import {config} from "dotenv"; +import {GetCompareSchema} from "./operations/codeup/compare.js"; + + +const server = new Server( + { + name: "alibabacloud-devops-mcp-server", + version: VERSION, + }, + { + capabilities: { + tools: {}, + }, + } +); + +function formatYunxiaoError(error: YunxiaoError): string { + let message = `Yunxiao API Error: ${error.message}`; + + if (error instanceof YunxiaoValidationError) { + message = `Validation Error: ${error.message}`; + if (error.response) { + message += `\nDetails: ${JSON.stringify(error.response)}`; + } + } else if (error instanceof YunxiaoResourceNotFoundError) { + message = `Not Found: ${error.message}`; + } else if (error instanceof YunxiaoAuthenticationError) { + message = `Authentication Failed: ${error.message}`; + } else if (error instanceof YunxiaoPermissionError) { + message = `Permission Denied: ${error.message}`; + } else if (error instanceof YunxiaoRateLimitError) { + message = `Rate Limit Exceeded: ${error.message}\nResets at: ${error.resetAt.toISOString()}`; + } else if (error instanceof YunxiaoConflictError) { + message = `Conflict: ${error.message}`; + } + + return message; +} + +server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: [ + // Branch Operations + { + name: "create_branch", + description: "Create a new branch in a Codeup repository", + inputSchema: zodToJsonSchema(branches.CreateBranchSchema), + }, + { + name: "get_branch", + description: "Get information about a branch in a Codeup repository", + inputSchema: zodToJsonSchema(branches.GetBranchSchema), + }, + { + name: "delete_branch", + description: "Delete a branch from a Codeup repository", + inputSchema: zodToJsonSchema(branches.DeleteBranchSchema), + }, + { + name: "list_branches", + description: "List branches in a Codeup repository", + inputSchema: zodToJsonSchema(branches.ListBranchesSchema), + }, + + // File Operations + { + name: "get_file_blobs", + description: "Get file content from a Codeup repository", + inputSchema: zodToJsonSchema(files.GetFileBlobsSchema), + }, + { + name: "create_file", + description: "Create a new file in a Codeup repository", + inputSchema: zodToJsonSchema(files.CreateFileSchema), + }, + { + name: "update_file", + description: "Update an existing file in a Codeup repository", + inputSchema: zodToJsonSchema(files.UpdateFileSchema), + }, + { + name: "delete_file", + description: "Delete a file from a Codeup repository", + inputSchema: zodToJsonSchema(files.DeleteFileSchema), + }, + { + name: "compare", + description: "Query code to compare content", + inputSchema: zodToJsonSchema(compare.GetCompareSchema), + }, + + // Repository Operations + { + name: "get_repository", + description: "Get information about a Codeup repository", + inputSchema: zodToJsonSchema(repositories.GetRepositorySchema), + }, + { + name: "list_repositories", + description: "List repositories in an organization", + inputSchema: zodToJsonSchema(repositories.ListRepositoriesSchema), + }, + + // Change Request Operations + { + name: "get_change_request", + description: "Get information about a change request", + inputSchema: zodToJsonSchema(changeRequests.GetChangeRequestSchema), + }, + { + name: "list_change_requests", + description: "List change requests", + inputSchema: zodToJsonSchema(changeRequests.ListChangeRequestsSchema), + }, + { + name: "create_change_request", + description: "Create a new change request", + inputSchema: zodToJsonSchema(changeRequests.CreateChangeRequestSchema), + }, + { + name: "create_change_request_comment", + description: "Create a comment on a change request", + inputSchema: zodToJsonSchema(changeRequestComments.CreateChangeRequestCommentSchema), + }, + { + name: "list_change_request_comments", + description: "List comments on a change request", + inputSchema: zodToJsonSchema(changeRequestComments.ListChangeRequestCommentsSchema), + }, + { + name: "list_change_request_patch_sets", + description: "List patch sets for a change request", + inputSchema: zodToJsonSchema(changeRequests.ListChangeRequestPatchSetsSchema), + }, + + // Organization Operations + { + name: "get_current_organization_info", + description: "Get information about the current user and organization based on the token", + inputSchema: zodToJsonSchema(z.object({})), + }, + { + name: "get_user_organizations", + description: "Get the list of organizations the current user belongs to", + inputSchema: zodToJsonSchema(z.object({})), + }, + + // Project Operations + { + name: "get_project", + description: "Get information about a project", + inputSchema: zodToJsonSchema(project.GetProjectSchema), + }, + { + name: "search_projects", + description: "Search projects with various filter conditions", + inputSchema: zodToJsonSchema(project.SearchProjectsSchema), + }, + + // Sprint Operations + // { + // name: "get_sprint", + // description: "Get information about a sprint", + // inputSchema: zodToJsonSchema(sprint.GetSprintSchema), + // }, + // { + // name: "list_sprints", + // description: "List sprints in a project", + // inputSchema: zodToJsonSchema(sprint.ListSprintsSchema), + // }, + + // Work Item Operations + { + name: "get_work_item", + description: "Get information about a work item", + inputSchema: zodToJsonSchema(workitem.GetWorkItemSchema), + }, + { + name: "search_workitems", + description: "Search work items with various filter conditions", + inputSchema: zodToJsonSchema(workitem.SearchWorkitemsSchema), + } + ], + }; +}); + +server.setRequestHandler(CallToolRequestSchema, async (request) => { + try { + if (!request.params.arguments) { + throw new Error("Arguments are required"); + } + + switch (request.params.name) { + // Branch Operations + case "create_branch": { + const args = branches.CreateBranchSchema.parse(request.params.arguments); + const branch = await branches.createBranchFunc( + args.organizationId, + args.repositoryId, + args.branch, + args.ref + ); + return { + content: [{ type: "text", text: JSON.stringify(branch, null, 2) }], + }; + } + + case "get_branch": { + const args = branches.GetBranchSchema.parse(request.params.arguments); + const branch = await branches.getBranchFunc( + args.organizationId, + args.repositoryId, + args.branchName + ); + return { + content: [{ type: "text", text: JSON.stringify(branch, null, 2) }], + }; + } + + case "delete_branch": { + const args = branches.DeleteBranchSchema.parse(request.params.arguments); + const result = await branches.deleteBranchFunc( + args.organizationId, + args.repositoryId, + args.branchName + ); + return { + content: [{ type: "text", text: JSON.stringify(result, null, 2) }], + }; + } + + case "list_branches": { + const args = branches.ListBranchesSchema.parse(request.params.arguments); + const branchList = await branches.listBranchesFunc( + args.organizationId, + args.repositoryId, + args.page, + args.perPage, + args.sort, + args.search ?? undefined + ); + return { + content: [{ type: "text", text: JSON.stringify(branchList, null, 2) }], + }; + } + + // File Operations + case "get_file_blobs": { + const args = files.GetFileBlobsSchema.parse(request.params.arguments); + const fileContent = await files.getFileBlobsFunc( + args.organizationId, + args.repositoryId, + args.filePath, + args.ref + ); + return { + content: [{ type: "text", text: JSON.stringify(fileContent, null, 2) }], + }; + } + + case "create_file": { + const args = files.CreateFileSchema.parse(request.params.arguments); + const result = await files.createFileFunc( + args.organizationId, + args.repositoryId, + args.filePath, + args.content, + args.commitMessage, + args.branch, + args.encoding + ); + return { + content: [{ type: "text", text: JSON.stringify(result, null, 2) }], + }; + } + + case "update_file": { + const args = files.UpdateFileSchema.parse(request.params.arguments); + const result = await files.updateFileFunc( + args.organizationId, + args.repositoryId, + args.filePath, + args.content, + args.commitMessage, + args.branch, + args.encoding + ); + return { + content: [{ type: "text", text: JSON.stringify(result, null, 2) }], + }; + } + + case "delete_file": { + const args = files.DeleteFileSchema.parse(request.params.arguments); + const result = await files.deleteFileFunc( + args.organizationId, + args.repositoryId, + args.filePath, + args.commitMessage, + args.branch + ); + return { + content: [{ type: "text", text: JSON.stringify(result, null, 2) }], + }; + } + + case "list_files": { + const args = files.ListFilesSchema.parse(request.params.arguments); + const fileList = await files.listFilesFunc( + args.organizationId, + args.repositoryId, + args.path, + args.ref, + args.type + ); + return { + content: [{ type: "text", text: JSON.stringify(fileList, null, 2) }], + }; + } + + case "compare": { + const args = compare.GetCompareSchema.parse(request.params.arguments); + const compareResult = await compare.getCompareFunc( + args.organizationId, + args.repositoryId, + args.from, + args.to, + args.sourceType ?? undefined, + args.targetType ?? undefined, + args.straight ?? undefined + ); + + return { + content: [{ type: "text", text: JSON.stringify(compareResult, null, 2) }], + }; + } + + // Repository Operations + case "get_repository": { + const args = repositories.GetRepositorySchema.parse(request.params.arguments); + const repository = await repositories.getRepositoryFunc( + args.organizationId, + args.repositoryId + ); + return { + content: [{ type: "text", text: JSON.stringify(repository, null, 2) }], + }; + } + + case "list_repositories": { + const args = repositories.ListRepositoriesSchema.parse(request.params.arguments); + const repositoryList = await repositories.listRepositoriesFunc( + args.organizationId, + args.page, + args.perPage, + args.orderBy, + args.sort, + args.search ?? undefined, + args.archived + ); + return { + content: [{ type: "text", text: JSON.stringify(repositoryList, null, 2) }], + }; + } + + // Change Request Operations + case "get_change_request": { + const args = changeRequests.GetChangeRequestSchema.parse(request.params.arguments); + const changeRequest = await changeRequests.getChangeRequestFunc( + args.organizationId, + args.repositoryId, + args.localId + ); + return { + content: [{ type: "text", text: JSON.stringify(changeRequest, null, 2) }], + }; + } + + case "list_change_requests": { + const args = changeRequests.ListChangeRequestsSchema.parse(request.params.arguments); + const changeRequestList = await changeRequests.listChangeRequestsFunc( + args.organizationId, + args.page, + args.perPage, + args.projectIds ?? undefined, + args.authorIds ?? undefined, + args.reviewerIds ?? undefined, + args.state ?? undefined, + args.search ?? undefined, + args.orderBy, + args.sort, + args.createdBefore ?? undefined, + args.createdAfter ?? undefined + ); + return { + content: [{ type: "text", text: JSON.stringify(changeRequestList, null, 2) }], + }; + } + + case "create_change_request": { + const args = changeRequests.CreateChangeRequestSchema.parse(request.params.arguments); + const changeRequest = await changeRequests.createChangeRequestFunc( + args.organizationId, + args.repositoryId, + args.title, + args.sourceBranch, + args.targetBranch, + args.description ?? undefined, + args.sourceProjectId, + args.targetProjectId, + args.reviewerUserIds ?? undefined, + args.workItemIds ?? undefined, + args.createFrom + ); + return { + content: [{ type: "text", text: JSON.stringify(changeRequest, null, 2) }], + }; + } + + case "create_change_request_comment": { + const args = changeRequestComments.CreateChangeRequestCommentSchema.parse(request.params.arguments); + const comment = await changeRequestComments.createChangeRequestCommentFunc( + args.organizationId, + args.repositoryId, + args.localId, + args.comment_type, + args.content, + args.draft, + args.resolved, + args.patchset_biz_id, + args.file_path ?? undefined, + args.line_number ?? undefined, + args.from_patchset_biz_id ?? undefined, + args.to_patchset_biz_id ?? undefined, + args.parent_comment_biz_id ?? undefined + ); + return { + content: [{ type: "text", text: JSON.stringify(comment, null, 2) }], + }; + } + + case "list_change_request_comments": { + const args = changeRequestComments.ListChangeRequestCommentsSchema.parse(request.params.arguments); + const comments = await changeRequestComments.listChangeRequestCommentsFunc( + args.organizationId, + args.repositoryId, + args.localId, + args.patchSetBizIds ?? undefined, + args.commentType, + args.state, + args.resolved, + args.filePath ?? undefined + ); + return { + content: [{ type: "text", text: JSON.stringify(comments, null, 2) }], + }; + } + + case "list_change_request_patch_sets": { + const args = changeRequests.ListChangeRequestPatchSetsSchema.parse(request.params.arguments); + const patchSets = await changeRequests.listChangeRequestPatchSetsFunc( + args.organizationId, + args.repositoryId, + args.localId + ); + + return { + content: [{ type: "text", text: JSON.stringify(patchSets, null, 2) }], + }; + } + + // Organization Operations + case "get_current_organization_info": { + const currentOrgInfo = await organization.getCurrentOrganizationInfoFunc(); + return { + content: [{ type: "text", text: JSON.stringify(currentOrgInfo, null, 2) }], + }; + } + + case "get_user_organizations": { + const userOrgs = await organization.getUserOrganizationsFunc(); + return { + content: [{ type: "text", text: JSON.stringify(userOrgs, null, 2) }], + }; + } + + // Project Operations + case "get_project": { + const args = project.GetProjectSchema.parse(request.params.arguments); + const projectInfo = await project.getProjectFunc( + args.organizationId, + args.id + ); + return { + content: [{ type: "text", text: JSON.stringify(projectInfo, null, 2) }], + }; + } + + case "search_projects": { + const args = project.SearchProjectsSchema.parse(request.params.arguments); + const projects = await project.searchProjectsFunc( + args.organizationId, + args.name ?? undefined, + args.status ?? undefined, + args.createdAfter ?? undefined, + args.createdBefore ?? undefined, + args.creator ?? undefined, + args.admin ?? undefined, + args.logicalStatus ?? undefined, + args.advancedConditions ?? undefined, + args.extraConditions ?? undefined, + args.orderBy, + args.page, + args.perPage, + args.sort + ); + return { + content: [{ type: "text", text: JSON.stringify(projects, null, 2) }], + }; + } + + // Sprint Operations + // case "get_sprint": { + // const args = sprint.GetSprintSchema.parse(request.params.arguments); + // const sprintInfo = await sprint.getSprintFunc( + // args.organizationId, + // args.projectId, + // args.id + // ); + // return { + // content: [{ type: "text", text: JSON.stringify(sprintInfo, null, 2) }], + // }; + // } + + // case "list_sprints": { + // const args = sprint.ListSprintsSchema.parse(request.params.arguments); + // const sprints = await sprint.listSprintsFunc( + // args.organizationId, + // args.id, + // args.status, + // args.page, + // args.perPage + // ); + // return { + // content: [{ type: "text", text: JSON.stringify(sprints, null, 2) }], + // }; + // } + + // Work Item Operations + case "get_work_item": { + const args = workitem.GetWorkItemSchema.parse(request.params.arguments); + const workItemInfo = await workitem.getWorkItemFunc( + args.organizationId, + args.workItemId + ); + return { + content: [{ type: "text", text: JSON.stringify(workItemInfo, null, 2) }], + }; + } + + case "search_workitems": { + const args = workitem.SearchWorkitemsSchema.parse(request.params.arguments); + const workItems = await workitem.searchWorkitemsFunc( + args.organizationId, + args.category, + args.spaceId, + args.subject ?? undefined, + args.status ?? undefined, + args.createdAfter ?? undefined, + args.createdBefore ?? undefined, + args.creator ?? undefined, + args.assignedTo ?? undefined, + args.advancedConditions ?? undefined, + args.orderBy + ); + return { + content: [{ type: "text", text: JSON.stringify(workItems, null, 2) }], + }; + } + + default: + throw new Error(`Unknown tool: ${request.params.name}`); + } + } catch (error) { + if (error instanceof z.ZodError) { + throw new Error(`Invalid input: ${JSON.stringify(error.errors)}`); + } + if (isYunxiaoError(error)) { + throw new Error(formatYunxiaoError(error)); + } + throw error; + } +}); + +// config(); + +async function runServer() { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error("Yunxiao MCP Server running on stdio"); +} + +runServer().catch((error) => { + console.error("Fatal error in main():", error); + process.exit(1); +}); \ No newline at end of file diff --git a/operations/.DS_Store b/operations/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..016cbb4d15c36004c4c73c9dfb12ca3608e6b333 GIT binary patch literal 6148 zcmeHKJ5Iwu5S>jT6hw-Y6coMyMT&G3COAD(Dmq9U1yG!|5Rnj~@P)Vn72*t>fSM!l zW@lx*F@{tKp&4oRt!HP}&(ELr5|J6L(*aSRh`Mmb)-IYajK|q)*7A`a(6~9)lrI;9 z$;@VXEn5SBQ31YpJ2WFpDb1;N|DFyu$1|HG)1{pv-hDlMJUM%NIj-lVKj!3@)#v0L zQ&0~qZfHRlw4y7z2i^iLt-@!jJ^wDZ%I|qt@@t$sv!7hI$q#w+djtJ)@V0af1@A(x zM-Q9hRer;9$uF3r%Bd+Qzhr~;t^Z8_}o`af>o|A$3-qzb44|4ISVNhZk%m*m&h+Qso&8^O=v pY@AmJmL=%8r5L%q6z{{GAs%rD7<()P5rOHCfRjNRRp3_@_yA6&cf9}r literal 0 HcmV?d00001 diff --git a/operations/codeup/branches.ts b/operations/codeup/branches.ts new file mode 100644 index 0000000..e1293f6 --- /dev/null +++ b/operations/codeup/branches.ts @@ -0,0 +1,201 @@ +import { z } from "zod"; +import {CodeupBranchSchema} from "../../common/types.js"; +import {buildUrl, yunxiaoRequest} from "../../common/utils.js"; + +// Schema definitions +export const CreateBranchSchema = z.object({ + organizationId: z.string().describe("Organization ID, can be found in the basic information page of the organization admin console"), + repositoryId: z.string().describe("Repository ID or a combination of organization ID and repository name, for example: 2835387 or organizationId%2Frepo-name (Note: slashes need to be URL encoded as %2F)"), + branch: z.string().describe("Name of the branch to be created"), + ref: z.string().default("master").describe("Source branch name, the new branch will be created based on this branch, default value is master"), +}); + +export const GetBranchSchema = z.object({ + organizationId: z.string().describe("Organization ID, can be found in the basic information page of the organization admin console"), + repositoryId: z.string().describe("Repository ID or a combination of organization ID and repository name, for example: 2835387 or organizationId%2Frepo-name (Note: slashes need to be URL encoded as %2F)"), + branchName: z.string().describe("Branch name (if it contains special characters, use URL encoding), example: master or feature%2Fdev"), +}); + +export const DeleteBranchSchema = z.object({ + organizationId: z.string().describe("Organization ID, can be found in the basic information page of the organization admin console"), + repositoryId: z.string().describe("Repository ID or a combination of organization ID and repository name, for example: 2835387 or organizationId%2Frepo-name (Note: slashes need to be URL encoded as %2F)"), + branchName: z.string().describe("Branch name (use URL-Encoder for encoding, example: feature%2Fdev)"), +}); + +export const ListBranchesSchema = z.object({ + organizationId: z.string().describe("Organization ID, can be found in the basic information page of the organization admin console"), + repositoryId: z.string().describe("Repository ID or a combination of organization ID and repository name, for example: 2835387 or organizationId%2Frepo-name (Note: slashes need to be URL encoded as %2F)"), + page: z.number().int().default(1).optional().describe("Page number"), + perPage: z.number().int().default(20).optional().describe("Items per page"), + sort: z.enum(["name_asc", "name_desc", "updated_asc", "updated_desc"]).default("name_asc").optional().describe("Sort order: name_asc - name ascending, name_desc - name descending, updated_asc - update time ascending, updated_desc - update time descending"), + search: z.string().nullable().optional().describe("Search query"), +}); + +// Type exports +export type CreateBranchOptions = z.infer; +export type GetBranchOptions = z.infer; +export type DeleteBranchOptions = z.infer; +export type ListBranchesOptions = z.infer; + +// Response type for delete branch operation +interface DeleteBranchResponse { + branchName: string; +} + +// Function implementations +export async function createBranchFunc( + organizationId: string, + repositoryId: string, + branch: string, + ref: string = "master" +): Promise>{ + // Automatically handle unencoded slashes in repositoryId + if (repositoryId.includes("/")) { + // Found unencoded slash, automatically URL encode it + const parts = repositoryId.split("/", 2); + if (parts.length === 2) { + const encodedRepoName = encodeURIComponent(parts[1]); + // Remove + signs from encoding (spaces are encoded as +, but we need %20) + const formattedEncodedName = encodedRepoName.replace(/\+/g, "%20"); + repositoryId = `${parts[0]}%2F${formattedEncodedName}`; + } + } + + const baseUrl = `/oapi/v1/codeup/organizations/${organizationId}/repositories/${repositoryId}/branches`; + + // Build query parameters + const queryParams: Record = { + branch: branch, + ref: ref + }; + + const url = buildUrl(baseUrl, queryParams); + console.error("createBranchFunc url:" + url); + + const response = await yunxiaoRequest(url, { + method: "POST", + }); + return CodeupBranchSchema.parse(response); +} + +export async function getBranchFunc( + organizationId: string, + repositoryId: string, + branchName: string +): Promise>{ + // Automatically handle unencoded slashes in repositoryId + if (repositoryId.includes("/")) { + // Found unencoded slash, automatically URL encode it + const parts = repositoryId.split("/", 2); + if (parts.length === 2) { + const encodedRepoName = encodeURIComponent(parts[1]); + // Remove + signs from encoding (spaces are encoded as +, but we need %20) + const formattedEncodedName = encodedRepoName.replace(/\+/g, "%20"); + repositoryId = `${parts[0]}%2F${formattedEncodedName}`; + } + } + + // Automatically handle unencoded slashes in branchName + if (branchName.includes("/")) { + branchName = encodeURIComponent(branchName); + } + + const url = `/oapi/v1/codeup/organizations/${organizationId}/repositories/${repositoryId}/branches/${branchName}`; + + const response = await yunxiaoRequest(url, { + method: "GET", + }); + return CodeupBranchSchema.parse(response); +} + +export async function deleteBranchFunc( + organizationId: string, + repositoryId: string, + branchName: string +): Promise { + // Automatically handle unencoded slashes in repositoryId + if (repositoryId.includes("/")) { + // Found unencoded slash, automatically URL encode it + const parts = repositoryId.split("/", 2); + if (parts.length === 2) { + const encodedRepoName = encodeURIComponent(parts[1]); + // Remove + signs from encoding (spaces are encoded as +, but we need %20) + const formattedEncodedName = encodedRepoName.replace(/\+/g, "%20"); + repositoryId = `${parts[0]}%2F${formattedEncodedName}`; + } + } + + // Automatically handle unencoded slashes in branchName + if (branchName.includes("/")) { + branchName = encodeURIComponent(branchName); + } + + const url = `/oapi/v1/codeup/organizations/${organizationId}/repositories/${repositoryId}/branches/${branchName}`; + + const response = await yunxiaoRequest(url, { + method: "DELETE", + }); + + return { + branchName: branchName + }; +} + +export async function listBranchesFunc( + organizationId: string, + repositoryId: string, + page?: number, + perPage?: number, + sort?: string, // Possible values: name_asc, name_desc, updated_asc, updated_desc + search?: string +): Promise[]> { + console.error("listBranchesFunc page:" + page + " perPage:" + perPage + " sort:" + sort + " search:" + search); + // Automatically handle unencoded slashes in repositoryId + if (repositoryId.includes("/")) { + // Found unencoded slash, automatically URL encode it + const parts = repositoryId.split("/", 2); + if (parts.length === 2) { + const encodedRepoName = encodeURIComponent(parts[1]); + // Remove + signs from encoding (spaces are encoded as +, but we need %20) + const formattedEncodedName = encodedRepoName.replace(/\+/g, "%20"); + repositoryId = `${parts[0]}%2F${formattedEncodedName}`; + } + } + + const baseUrl = `/oapi/v1/codeup/organizations/${organizationId}/repositories/${repositoryId}/branches`; + + // Build query parameters - use lowercase parameter names as expected by the API + const queryParams: Record = {}; + if (page !== undefined && page !== null) { + queryParams.page = page; + } + if (perPage !== undefined && perPage !== null) { + queryParams.perPage = perPage; + } + if (sort !== undefined && sort !== null) { + queryParams.sort = sort; + } + if (search !== undefined && search !== null) { + queryParams.search = search; + } + + // Use buildUrl function to construct URL with query parameters + const url = buildUrl(baseUrl, queryParams); + + const response = await yunxiaoRequest(url, { + method: "GET", + }); + + // Ensure the response is an array + if (!Array.isArray(response)) { + return []; + } + + // Map each branch object and handle null values + return response.map(branchData => { + // Filter out null values that would cause parsing errors + // This is a defensive approach until we update all schemas properly + return CodeupBranchSchema.parse(branchData); + }); +} + diff --git a/operations/codeup/changeRequestComments.ts b/operations/codeup/changeRequestComments.ts new file mode 100644 index 0000000..9c511a5 --- /dev/null +++ b/operations/codeup/changeRequestComments.ts @@ -0,0 +1,132 @@ +import { z } from "zod"; +import { yunxiaoRequest, buildUrl } from "../../common/utils.js"; +import { ChangeRequestCommentSchema } from "../../common/types.js"; +import { handleRepositoryIdEncoding } from "./utils.js"; + +// Schema definitions +export const CreateChangeRequestCommentSchema = z.object({ + organizationId: z.string().describe("Organization ID, can be found in the basic information page of the organization admin console"), + repositoryId: z.string().describe("Repository ID or a combination of organization ID and repository name, for example: 2835387 or organizationId%2Frepo-name (Note: slashes need to be URL encoded as %2F)"), + localId: z.string().describe("Local ID, represents the nth merge request in the repository"), + comment_type: z.string().default("GLOBAL_COMMENT").describe("Comment type. Possible values: GLOBAL_COMMENT, INLINE_COMMENT"), + content: z.string().describe("Comment content, length must be between 1 and 65535"), + draft: z.boolean().default(false).describe("Whether it is a draft comment"), + resolved: z.boolean().default(false).describe("Whether to mark as resolved"), + patchset_biz_id: z.string().describe("Associated version ID, if it's INLINE_COMMENT, choose one from from_patchset_biz_id or to_patchset_biz_id"), + file_path: z.string().nullable().optional().describe("File name, only for inline comments"), + line_number: z.number().int().nullable().optional().describe("Line number, only for inline comments"), + from_patchset_biz_id: z.string().nullable().optional().describe("Start version ID for comparison, required for INLINE_COMMENT type"), + to_patchset_biz_id: z.string().nullable().optional().describe("Target version ID for comparison, required for INLINE_COMMENT type"), + parent_comment_biz_id: z.string().nullable().optional().describe("Parent comment ID"), +}); + +export const ListChangeRequestCommentsSchema = z.object({ + organizationId: z.string().describe("Organization ID, can be found in the basic information page of the organization admin console"), + repositoryId: z.string().describe("Repository ID or a combination of organization ID and repository name, for example: 2835387 or organizationId%2Frepo-name (Note: slashes need to be URL encoded as %2F)"), + localId: z.string().describe("Local ID, represents the nth merge request in the repository"), + patchSetBizIds: z.array(z.string()).nullable().optional().describe("Associated version ID list, each comment is associated with a version, indicating which version the comment was posted on, for global comments, it's associated with the latest merge source version"), + commentType: z.string().optional().default("GLOBAL_COMMENT").describe("Comment type. Possible values: GLOBAL_COMMENT, INLINE_COMMENT"), + state: z.string().optional().default("OPENED").describe("Comment state. Possible values: OPENED, DRAFT"), + resolved: z.boolean().optional().default(false).describe("Whether marked as resolved"), + filePath: z.string().nullable().optional().describe("File name, only for inline comments"), +}); + +// Type exports +export type CreateChangeRequestCommentOptions = z.infer; +export type ListChangeRequestCommentsOptions = z.infer; + +// Function implementations +export async function createChangeRequestCommentFunc( + organizationId: string, + repositoryId: string, + localId: string, + comment_type: string, // Possible values: GLOBAL_COMMENT, INLINE_COMMENT + content: string, + draft: boolean, + resolved: boolean, + patchset_biz_id: string, + file_path?: string, + line_number?: number, + from_patchset_biz_id?: string, + to_patchset_biz_id?: string, + parent_comment_biz_id?: string +): Promise> { + const encodedRepoId = handleRepositoryIdEncoding(repositoryId); + + const url = `/oapi/v1/codeup/organizations/${organizationId}/repositories/${encodedRepoId}/changeRequests/${localId}/comments`; + + // 准备payload + const payload: Record = { + comment_type: comment_type, + content: content, + draft: draft, + resolved: resolved, + patchset_biz_id: patchset_biz_id, + }; + + // 根据评论类型添加必要参数 + if (comment_type === "INLINE_COMMENT") { + // 检查INLINE_COMMENT必需的参数 + if (!file_path || line_number === undefined || !from_patchset_biz_id || !to_patchset_biz_id) { + throw new Error("For INLINE_COMMENT, file_path, line_number, from_patchset_biz_id, and to_patchset_biz_id are required"); + } + + payload.file_path = file_path; + payload.line_number = line_number; + payload.from_patchset_biz_id = from_patchset_biz_id; + payload.to_patchset_biz_id = to_patchset_biz_id; + } + + // 添加可选参数 + if (parent_comment_biz_id) { + payload.parent_comment_biz_id = parent_comment_biz_id; + } + + const response = await yunxiaoRequest(url, { + method: "POST", + body: payload, + }); + + return ChangeRequestCommentSchema.parse(response); +} + +export async function listChangeRequestCommentsFunc( + organizationId: string, + repositoryId: string, + localId: string, + patchSetBizIds?: string[], + commentType: string = "GLOBAL_COMMENT", // Possible values: GLOBAL_COMMENT, INLINE_COMMENT + state: string = "OPENED", // Possible values: OPENED, DRAFT + resolved: boolean = false, + filePath?: string +): Promise[]> { + const encodedRepoId = handleRepositoryIdEncoding(repositoryId); + + const url = `/oapi/v1/codeup/organizations/${organizationId}/repositories/${encodedRepoId}/changeRequests/${localId}/comments/list`; + + // 准备payload + const payload: Record = { + patchSetBizIds: patchSetBizIds || [], + commentType: commentType, + state: state, + resolved: resolved, + }; + + // 添加可选参数 + if (filePath) { + payload.filePath = filePath; + } + + const response = await yunxiaoRequest(url, { + method: "POST", + body: payload, + }); + + // 确保响应是数组 + if (!Array.isArray(response)) { + return []; + } + + // 解析每个评论对象 + return response.map(comment => ChangeRequestCommentSchema.parse(comment)); +} \ No newline at end of file diff --git a/operations/codeup/changeRequests.ts b/operations/codeup/changeRequests.ts new file mode 100644 index 0000000..cf3bf74 --- /dev/null +++ b/operations/codeup/changeRequests.ts @@ -0,0 +1,285 @@ +import { z } from "zod"; +import { yunxiaoRequest, buildUrl } from "../../common/utils.js"; +import { ChangeRequestSchema, PatchSetSchema } from "../../common/types.js"; +import { handleRepositoryIdEncoding, floatToIntString } from "./utils.js"; + +// Schema definitions +export const GetChangeRequestSchema = z.object({ + organizationId: z.string().describe("Organization ID, can be found in the basic information page of the organization admin console"), + repositoryId: z.string().describe("Repository ID or a combination of organization ID and repository name, for example: 2835387 or organizationId%2Frepo-name (Note: slashes need to be URL encoded as %2F)"), + localId: z.string().describe("Local ID, represents the nth merge request in the repository"), +}); + +export const ListChangeRequestsSchema = z.object({ + organizationId: z.string().describe("Organization ID, can be found in the basic information page of the organization admin console"), + page: z.number().int().default(1).optional().describe("Page number"), + perPage: z.number().int().default(20).optional().describe("Items per page"), + projectIds: z.string().nullable().optional().describe("Repository ID or a combination of organization ID and repository name list, for example: 2835387 or organizationId%2Frepo-name (Note: slashes need to be URL encoded as %2F), multiple separated by commas"), + authorIds: z.string().nullable().optional().describe("Creator user ID list, multiple separated by commas"), + reviewerIds: z.string().nullable().optional().describe("Reviewer user ID list, multiple separated by commas"), + state: z.string().nullable().optional().describe("Merge request filter status. Possible values: opened, merged, closed. Default is null, which queries all statuses"), + search: z.string().nullable().optional().describe("Title keyword search"), + orderBy: z.string().default("updated_at").optional().describe("Sort field. Possible values: created_at (creation time), updated_at (update time, default)"), + sort: z.string().default("desc").optional().describe("Sort order. Possible values: asc (ascending), desc (descending, default)"), + createdBefore: z.string().nullable().optional().describe("Start creation time, time format is ISO 8601, for example: 2019-03-15T08:00:00Z"), + createdAfter: z.string().nullable().optional().describe("End creation time, time format is ISO 8601, for example: 2019-03-15T08:00:00Z"), +}); + +export const ListChangeRequestPatchSetsSchema = z.object({ + organizationId: z.string().describe("Organization ID, can be found in the basic information page of the organization admin console"), + repositoryId: z.string().describe("Repository ID or a combination of organization ID and repository name, for example: 2835387 or organizationId%2Frepo-name (Note: slashes need to be URL encoded as %2F)"), + localId: z.string().describe("Local ID, represents the nth merge request in the repository"), +}); + +export const CreateChangeRequestSchema = z.object({ + organizationId: z.string().describe("Organization ID, can be found in the basic information page of the organization admin console"), + repositoryId: z.string().describe("Repository ID or a combination of organization ID and repository name, for example: 2835387 or organizationId%2Frepo-name (Note: slashes need to be URL encoded as %2F)"), + title: z.string().describe("Title, no more than 256 characters"), + description: z.string().nullable().optional().describe("Description, no more than 10000 characters"), + sourceBranch: z.string().describe("Source branch name"), + sourceProjectId: z.number().optional().describe("Source repository ID (if not provided, will try to get automatically)"), + targetBranch: z.string().describe("Target branch name"), + targetProjectId: z.number().optional().describe("Target repository ID (if not provided, will try to get automatically)"), + reviewerUserIds: z.array(z.string()).nullable().optional().describe("Reviewer user ID list"), + workItemIds: z.array(z.string()).nullable().optional().describe("Associated work item ID list"), + createFrom: z.string().optional().default("WEB").describe("Creation source. Possible values: WEB (created from web page), COMMAND_LINE (created from command line). Default is WEB"), +}); + +// Type exports +export type GetChangeRequestOptions = z.infer; +export type ListChangeRequestsOptions = z.infer; +export type ListChangeRequestPatchSetsOptions = z.infer; +export type CreateChangeRequestOptions = z.infer; + +// 通过API获取仓库的数字ID +async function getRepositoryNumericId(organizationId: string, repositoryId: string): Promise { + const url = `/oapi/v1/codeup/organizations/${organizationId}/repositories/${repositoryId}`; + + const response = await yunxiaoRequest(url, { + method: "GET", + }); + + if (!response || typeof response !== 'object' || !('id' in response)) { + throw new Error("Failed to get repository ID"); + } + + const repoId = response.id; + if (!repoId) { + throw new Error("Could not get repository ID"); + } + + return repoId.toString(); +} + +// Function implementations +export async function getChangeRequestFunc( + organizationId: string, + repositoryId: string, + localId: string +): Promise> { + const encodedRepoId = handleRepositoryIdEncoding(repositoryId); + + const url = `/oapi/v1/codeup/organizations/${organizationId}/repositories/${encodedRepoId}/changeRequests/${localId}`; + + const response = await yunxiaoRequest(url, { + method: "GET", + }); + + return ChangeRequestSchema.parse(response); +} + +export async function listChangeRequestsFunc( + organizationId: string, + page?: number, + perPage?: number, + projectIds?: string, + authorIds?: string, + reviewerIds?: string, + state?: string, // Possible values: opened, merged, closed + search?: string, + orderBy?: string, // Possible values: created_at, updated_at + sort?: string, // Possible values: asc, desc + createdBefore?: string, + createdAfter?: string +): Promise[]> { + const baseUrl = `/oapi/v1/codeup/organizations/${organizationId}/changeRequests`; + + // 构建查询参数 + const queryParams: Record = {}; + + if (page !== undefined) { + queryParams.page = page; + } + + if (perPage !== undefined) { + queryParams.perPage = perPage; + } + + if (projectIds !== undefined) { + queryParams.projectIds = projectIds; + } + + if (authorIds !== undefined) { + queryParams.authorIds = authorIds; + } + + if (reviewerIds !== undefined) { + queryParams.reviewerIds = reviewerIds; + } + + if (state !== undefined) { + queryParams.state = state; + } + + if (search !== undefined) { + queryParams.search = search; + } + + if (orderBy !== undefined) { + queryParams.orderBy = orderBy; + } + + if (sort !== undefined) { + queryParams.sort = sort; + } + + if (createdBefore !== undefined) { + queryParams.createdBefore = createdBefore; + } + + if (createdAfter !== undefined) { + queryParams.createdAfter = createdAfter; + } + + // 使用buildUrl函数构建包含查询参数的URL + const url = buildUrl(baseUrl, queryParams); + + const response = await yunxiaoRequest(url, { + method: "GET", + }); + + // 确保响应是数组 + if (!Array.isArray(response)) { + return []; + } + + // 解析每个变更请求对象 + return response.map(changeRequest => ChangeRequestSchema.parse(changeRequest)); +} + +export async function listChangeRequestPatchSetsFunc( + organizationId: string, + repositoryId: string, + localId: string +): Promise[]> { + const encodedRepoId = handleRepositoryIdEncoding(repositoryId); + + const url = `/oapi/v1/codeup/organizations/${organizationId}/repositories/${encodedRepoId}/changeRequests/${localId}/diffs/patches`; + + const response = await yunxiaoRequest(url, { + method: "GET", + }); + + // 确保响应是数组 + if (!Array.isArray(response)) { + return []; + } + + // 解析每个版本对象 + return response.map(patchSet => PatchSetSchema.parse(patchSet)); +} + +export async function createChangeRequestFunc( + organizationId: string, + repositoryId: string, + title: string, + sourceBranch: string, + targetBranch: string, + description?: string, + sourceProjectId?: number, + targetProjectId?: number, + reviewerUserIds?: string[], + workItemIds?: string[], + createFrom: string = "WEB" // Possible values: WEB, COMMAND_LINE +): Promise> { + const encodedRepoId = handleRepositoryIdEncoding(repositoryId); + + // 检查和获取sourceProjectId和targetProjectId + let sourceIdString: string | undefined; + let targetIdString: string | undefined; + + if (sourceProjectId !== undefined) { + sourceIdString = floatToIntString(sourceProjectId); + } + + if (targetProjectId !== undefined) { + targetIdString = floatToIntString(targetProjectId); + } + + // 如果repositoryId是纯数字,且sourceProjectId或targetProjectId未提供,直接使用repositoryId的值 + if (!isNaN(Number(repositoryId))) { + // 是数字ID,可以直接使用 + if (sourceIdString === undefined) { + sourceIdString = repositoryId; + } + if (targetIdString === undefined) { + targetIdString = repositoryId; + } + } else if (repositoryId.includes("%2F") || repositoryId.includes("/")) { + // 如果是组织ID与仓库名称的组合,调用API获取数字ID + if (sourceIdString === undefined || targetIdString === undefined) { + try { + const numericId = await getRepositoryNumericId(organizationId, encodedRepoId); + + if (sourceIdString === undefined) { + sourceIdString = numericId; + } + if (targetIdString === undefined) { + targetIdString = numericId; + } + } catch (error) { + throw new Error(`When using 'organizationId%2Frepo-name' format, you must first get the numeric ID of the repository and use it for sourceProjectId and targetProjectId parameters. Please use get_repository tool to get the numeric ID of '${repositoryId}' and then use that ID as the value for sourceProjectId and targetProjectId.`); + } + } + } + + // 确保sourceProjectId和targetProjectId已设置 + if (sourceIdString === undefined) { + throw new Error("Could not get sourceProjectId, please provide this parameter manually"); + } + if (targetIdString === undefined) { + throw new Error("Could not get targetProjectId, please provide this parameter manually"); + } + + const url = `/oapi/v1/codeup/organizations/${organizationId}/repositories/${encodedRepoId}/changeRequests`; + + // 准备payload + const payload: Record = { + title: title, + sourceBranch: sourceBranch, + targetBranch: targetBranch, + sourceProjectId: sourceIdString, + targetProjectId: targetIdString, + createFrom: createFrom, + }; + + // 添加可选参数 + if (description !== undefined) { + payload.description = description; + } + + if (reviewerUserIds !== undefined) { + payload.reviewerUserIds = reviewerUserIds; + } + + if (workItemIds !== undefined) { + payload.workItemIds = workItemIds; + } + + const response = await yunxiaoRequest(url, { + method: "POST", + body: payload, + }); + + return ChangeRequestSchema.parse(response); +} \ No newline at end of file diff --git a/operations/codeup/compare.ts b/operations/codeup/compare.ts new file mode 100644 index 0000000..9ddb0da --- /dev/null +++ b/operations/codeup/compare.ts @@ -0,0 +1,78 @@ +import { z } from "zod"; +import { yunxiaoRequest, buildUrl } from "../../common/utils.js"; +import { CompareSchema } from "../../common/types.js"; + +// Schema definitions +export const GetCompareSchema = z.object({ + organizationId: z.string().describe("Organization ID, can be found in the basic information page of the organization admin console"), + repositoryId: z.string().describe("Repository ID or a combination of organization ID and repository name, for example: 2835387 or organizationId%2Frepo-name (Note: slashes need to be URL encoded as %2F)"), + from: z.string().describe("Can be CommitSHA, branch name or tag name"), + to: z.string().describe("Can be CommitSHA, branch name or tag name"), + sourceType: z.string().nullable().optional().describe("Options: branch, tag; if it's a commit comparison, you can omit this; if it's a branch comparison, you need to provide: branch, or you can omit it but ensure there are no branch or tag name conflicts; if it's a tag comparison, you need to provide: tag; if there are branches and tags with the same name, you need to strictly provide branch or tag"), + targetType: z.string().nullable().optional().describe("Options: branch, tag; if it's a commit comparison, you can omit this; if it's a branch comparison, you need to provide: branch, or you can omit it but ensure there are no branch or tag name conflicts; if it's a tag comparison, you need to provide: tag; if there are branches and tags with the same name, you need to strictly provide branch or tag"), + straight: z.string().default("false").nullable().optional().describe("Whether to use Merge-Base: straight=false means using Merge-Base; straight=true means not using Merge-Base; default is false, meaning using Merge-Base"), +}); + +// Type exports +export type GetCompareOptions = z.infer; + +// Helper function to handle repositoryId encoding +function handleRepositoryIdEncoding(repositoryId: string): string { + let encodedRepoId = repositoryId; + + // Automatically handle unencoded slashes in repositoryId + if (repositoryId.includes("/")) { + // Found unencoded slash, automatically URL encode it + const parts = repositoryId.split("/", 2); + if (parts.length === 2) { + const encodedRepoName = encodeURIComponent(parts[1]); + // Remove + signs from encoding (spaces are encoded as +, but we need %20) + const formattedEncodedName = encodedRepoName.replace(/\+/g, "%20"); + encodedRepoId = `${parts[0]}%2F${formattedEncodedName}`; + } + } + + return encodedRepoId; +} + +// Function implementations +export async function getCompareFunc( + organizationId: string, + repositoryId: string, + from: string, + to: string, + sourceType?: string, // Possible values: branch, tag + targetType?: string, // Possible values: branch, tag + straight?: string +): Promise> { + const encodedRepoId = handleRepositoryIdEncoding(repositoryId); + + const baseUrl = `/oapi/v1/codeup/organizations/${organizationId}/repositories/${encodedRepoId}/compares`; + + // Build query parameters + const queryParams: Record = { + from, + to + }; + + if (sourceType !== undefined) { + queryParams.sourceType = sourceType; + } + + if (targetType !== undefined) { + queryParams.targetType = targetType; + } + + if (straight !== undefined) { + queryParams.straight = straight; + } + + // Use buildUrl function to construct URL with query parameters + const url = buildUrl(baseUrl, queryParams); + + const response = await yunxiaoRequest(url, { + method: "GET", + }); + + return CompareSchema.parse(response); +} \ No newline at end of file diff --git a/operations/codeup/files.ts b/operations/codeup/files.ts new file mode 100644 index 0000000..7b3f9f1 --- /dev/null +++ b/operations/codeup/files.ts @@ -0,0 +1,323 @@ +import { z } from "zod"; +import {yunxiaoRequest, buildUrl, pathEscape} from "../../common/utils.js"; +import { + FileContentSchema, + CreateFileResponseSchema, + DeleteFileResponseSchema, + FileInfoSchema +} from "../../common/types.js"; + +// Schema definitions +export const GetFileBlobsSchema = z.object({ + organizationId: z.string().describe("Organization ID, can be found in the basic information page of the organization admin console"), + repositoryId: z.string().describe("Repository ID or a combination of organization ID and repository name, for example: 2835387 or organizationId%2Frepo-name (Note: slashes need to be URL encoded as %2F)"), + filePath: z.string().describe("File path, needs to be URL encoded, for example: /src/main/java/com/aliyun/test.java"), + ref: z.string().describe("Reference name, usually branch name, can be branch name, tag name or commit SHA. If not provided, the default branch of the repository will be used, such as master"), +}); + +export const CreateFileSchema = z.object({ + organizationId: z.string().describe("Organization ID, can be found in the basic information page of the organization admin console"), + repositoryId: z.string().describe("Repository ID or a combination of organization ID and repository name, for example: 2835387 or organizationId%2Frepo-name (Note: slashes need to be URL encoded as %2F)"), + filePath: z.string().describe("File path, needs to be URL encoded, for example: /src/main/java/com/aliyun/test.java"), + content: z.string().describe("File content"), + commitMessage: z.string().describe("Commit message, not empty, no more than 102400 characters"), + branch: z.string().describe("Branch name"), + encoding: z.string().optional().describe("Encoding rule, options {text, base64}, default is text"), +}); + +export const UpdateFileSchema = z.object({ + organizationId: z.string().describe("Organization ID, can be found in the basic information page of the organization admin console"), + repositoryId: z.string().describe("Repository ID or a combination of organization ID and repository name, for example: 2835387 or organizationId%2Frepo-name (Note: slashes need to be URL encoded as %2F)"), + filePath: z.string().describe("File path, needs to be URL encoded, for example: /src/main/java/com/aliyun/test.java"), + content: z.string().describe("File content"), + commitMessage: z.string().describe("Commit message, not empty, no more than 102400 characters"), + branch: z.string().describe("Branch name"), + encoding: z.string().default("text").optional().describe("Encoding rule, options {text, base64}, default is text"), +}); + +export const DeleteFileSchema = z.object({ + organizationId: z.string().describe("Organization ID, can be found in the basic information page of the organization admin console"), + repositoryId: z.string().describe("Repository ID or a combination of organization ID and repository name, for example: 2835387 or organizationId%2Frepo-name (Note: slashes need to be URL encoded as %2F)"), + filePath: z.string().describe("File path, needs to be URL encoded, for example: /src/main/java/com/aliyun/test.java"), + commitMessage: z.string().describe("Commit message"), + branch: z.string().describe("Branch name"), +}); + +export const ListFilesSchema = z.object({ + organizationId: z.string().describe("Organization ID, can be found in the basic information page of the organization admin console"), + repositoryId: z.string().describe("Repository ID or a combination of organization ID and repository name, for example: 2835387 or organizationId%2Frepo-name (Note: slashes need to be URL encoded as %2F)"), + path: z.string().optional().describe("Specific path to query, for example to query files in the src/main directory"), + ref: z.string().optional().describe("Reference name, usually branch name, can be branch name, tag name or commit SHA. If not provided, the default branch of the repository will be used, such as master"), + type: z.string().default("RECURSIVE").optional().describe("File tree retrieval method: DIRECT - only get the current directory, default method; RECURSIVE - recursively find all files under the current path; FLATTEN - flat display (if it is a directory, recursively find until the subdirectory contains files or multiple directories)"), +}); + +// Type exports +export type GetFileBlobsOptions = z.infer; +export type CreateFileOptions = z.infer; +export type UpdateFileOptions = z.infer; +export type DeleteFileOptions = z.infer; +export type ListFilesOptions = z.infer; + +// Common helper function to handle repositoryId and filePath encoding +function handlePathEncoding(repositoryId: string, filePath: string): { encodedRepoId: string; encodedFilePath: string } { + let encodedRepoId = repositoryId; + let encodedFilePath = filePath; + + // 自动处理repositoryId中未编码的斜杠 + if (repositoryId.includes("/")) { + // 发现未编码的斜杠,自动进行URL编码 + const parts = repositoryId.split("/", 2); + if (parts.length === 2) { + const encodedRepoName = encodeURIComponent(parts[1]); + // 移除编码中的+号(空格被编码为+,但我们需要%20) + const formattedEncodedName = encodedRepoName.replace(/\+/g, "%20"); + encodedRepoId = `${parts[0]}%2F${formattedEncodedName}`; + } + } + + // 确保filePath已被URL编码 + if (filePath.includes("/")) { + const startsWithSlash = filePath.startsWith("/"); + if (startsWithSlash) { + encodedFilePath = encodedFilePath.substring(1); + } + encodedFilePath = encodeURIComponent(filePath); + } + + return { encodedRepoId, encodedFilePath }; +} + +// Function implementations +export async function getFileBlobsFunc( + organizationId: string, + repositoryId: string, + filePath: string, + ref: string +): Promise> { + // const { encodedRepoId, encodedFilePath } = handlePathEncoding(repositoryId, filePath); + let encodedRepoId = repositoryId; + let encodedFilePath = filePath; + // 自动处理repositoryId中未编码的斜杠 + if (repositoryId.includes("/")) { + // 发现未编码的斜杠,自动进行URL编码 + const parts = repositoryId.split("/", 2); + if (parts.length === 2) { + const encodedRepoName = encodeURIComponent(parts[1]); + // 移除编码中的+号(空格被编码为+,但我们需要%20) + const formattedEncodedName = encodedRepoName.replace(/\+/g, "%20"); + encodedRepoId = `${parts[0]}%2F${formattedEncodedName}`; + } + } + + // 确保filePath已被URL编码 + if (filePath.includes("/")) { + encodedFilePath = encodeURIComponent(filePath); + } + + const baseUrl = `/oapi/v1/codeup/organizations/${organizationId}/repositories/${encodedRepoId}/files/${encodedFilePath}`; + + // 构建查询参数 + const queryParams: Record = { + ref: ref + }; + + // 使用buildUrl函数构建包含查询参数的URL + const url = buildUrl(baseUrl, queryParams); + + const response = await yunxiaoRequest(url, { + method: "GET", + }); + + return FileContentSchema.parse(response); +} + +export async function createFileFunc( + organizationId: string, + repositoryId: string, + filePath: string, + content: string, + commitMessage: string, + branch: string, + encoding?: string +): Promise> { + let encodedRepoId = repositoryId; + let encodedFilePath = filePath; + + if (repositoryId.includes("/")) { + // 发现未编码的斜杠,自动进行URL编码 + const parts = repositoryId.split("/", 2); + if (parts.length === 2) { + const encodedRepoName = encodeURIComponent(parts[1]); + // 移除编码中的+号(空格被编码为+,但我们需要%20) + const formattedEncodedName = encodedRepoName.replace(/\+/g, "%20"); + encodedRepoId = `${parts[0]}%2F${formattedEncodedName}`; + } + } + + // 确保filePath已被URL编码 + if (filePath.includes("/")) { + encodedFilePath = pathEscape(filePath); + } + + const url = `/oapi/v1/codeup/organizations/${organizationId}/repositories/${encodedRepoId}/files`; + + const body = { + branch: branch, + filePath: encodedFilePath, + content: content, + commitMessage: commitMessage, + encoding: encoding || "text" // 默认使用text编码 + }; + + const response = await yunxiaoRequest(url, { + method: "POST", + body: body + }); + + return CreateFileResponseSchema.parse(response); +} + +export async function updateFileFunc( + organizationId: string, + repositoryId: string, + filePath: string, + content: string, + commitMessage: string, + branch: string, + encoding?: string +): Promise> { + //const { encodedRepoId, encodedFilePath } = handlePathEncoding(repositoryId, filePath); + let encodedRepoId = repositoryId; + let encodedFilePath = filePath; + + // 自动处理repositoryId中未编码的斜杠 + if (repositoryId.includes("/")) { + // 发现未编码的斜杠,自动进行URL编码 + const parts = repositoryId.split("/", 2); + if (parts.length === 2) { + const encodedRepoName = encodeURIComponent(parts[1]); + // 移除编码中的+号(空格被编码为+,但我们需要%20) + const formattedEncodedName = encodedRepoName.replace(/\+/g, "%20"); + encodedRepoId = `${parts[0]}%2F${formattedEncodedName}`; + } + } + + // 确保filePath已被URL编码 + if (filePath.includes("/")) { + const pathToEncode = filePath.startsWith("/") ? filePath.substring(1) : filePath; + encodedFilePath = encodeURIComponent(pathToEncode); + } + + const url = `/oapi/v1/codeup/organizations/${organizationId}/repositories/${encodedRepoId}/files/${encodedFilePath}`; + + const body = { + branch: branch, + commitMessage: commitMessage, + content: content, + encoding: encoding || "text" // 默认使用text编码 + }; + + const response = await yunxiaoRequest(url, { + method: "PUT", + body: body + }); + + return CreateFileResponseSchema.parse(response); +} + +export async function deleteFileFunc( + organizationId: string, + repositoryId: string, + filePath: string, + commitMessage: string, + branch: string +): Promise> { + let encodedRepoId = repositoryId; + let encodedFilePath = filePath; + + if (repositoryId.includes("/")) { + // 发现未编码的斜杠,自动进行URL编码 + const parts = repositoryId.split("/", 2); + if (parts.length === 2) { + const encodedRepoName = encodeURIComponent(parts[1]); + // 移除编码中的+号(空格被编码为+,但我们需要%20) + const formattedEncodedName = encodedRepoName.replace(/\+/g, "%20"); + encodedRepoId = `${parts[0]}%2F${formattedEncodedName}`; + } + } + + // 确保filePath已被URL编码 + if (filePath.includes("/")) { + encodedFilePath = encodeURIComponent(filePath); + } + + const baseUrl = `/oapi/v1/codeup/organizations/${organizationId}/repositories/${encodedRepoId}/files/${encodedFilePath}`; + + // 构建查询参数 + const queryParams: Record = { + branch: branch, + commitMessage: commitMessage + }; + + // 使用buildUrl函数构建包含查询参数的URL + const url = buildUrl(baseUrl, queryParams); + + const response = await yunxiaoRequest(url, { + method: "DELETE", + }); + + return DeleteFileResponseSchema.parse(response); +} + +export async function listFilesFunc( + organizationId: string, + repositoryId: string, + path?: string, + ref?: string, + type?: string // Possible values: DIRECT, RECURSIVE, FLATTEN +): Promise[]> { + // 自动处理repositoryId中未编码的斜杠 + let encodedRepoId = repositoryId; + if (repositoryId.includes("/")) { + // 发现未编码的斜杠,自动进行URL编码 + const parts = repositoryId.split("/", 2); + if (parts.length === 2) { + const encodedRepoName = encodeURIComponent(parts[1]); + // 移除编码中的+号(空格被编码为+,但我们需要%20) + const formattedEncodedName = encodedRepoName.replace(/\+/g, "%20"); + encodedRepoId = `${parts[0]}%2F${formattedEncodedName}`; + } + } + + const baseUrl = `/oapi/v1/codeup/organizations/${organizationId}/repositories/${encodedRepoId}/files/tree`; + + // 构建查询参数 + const queryParams: Record = {}; + + if (path) { + queryParams.path = path; + } + + if (ref) { + queryParams.ref = ref; + } + + if (type) { + queryParams.type = type; + } + + // 使用buildUrl函数构建包含查询参数的URL + const url = buildUrl(baseUrl, queryParams); + + const response = await yunxiaoRequest(url, { + method: "GET", + }); + + // 确保响应是数组 + if (!Array.isArray(response)) { + return []; + } + + // 解析每个文件信息对象 + return response.map(fileInfo => FileInfoSchema.parse(fileInfo)); +} \ No newline at end of file diff --git a/operations/codeup/repositories.ts b/operations/codeup/repositories.ts new file mode 100644 index 0000000..d8f418c --- /dev/null +++ b/operations/codeup/repositories.ts @@ -0,0 +1,112 @@ +import { z } from "zod"; +import { yunxiaoRequest, buildUrl } from "../../common/utils.js"; +import { RepositorySchema } from "../../common/types.js"; + +// Schema definitions +export const GetRepositorySchema = z.object({ + organizationId: z.string().describe("Organization ID, can be found in the basic information page of the organization admin console"), + repositoryId: z.string().describe("Repository ID or a combination of organization ID and repository name, for example: 2835387 or organizationId%2Frepo-name (Note: slashes need to be URL encoded as %2F)"), +}); + +export const ListRepositoriesSchema = z.object({ + organizationId: z.string().describe("Organization ID, can be found in the basic information page of the organization admin console"), + page: z.number().int().default(1).optional().describe("Page number, default starts from 1, generally should not exceed 150 pages"), + perPage: z.number().int().default(20).optional().describe("Items per page, default 20, value range [1, 100]"), + orderBy: z.string().default("created_at").optional().describe("Sort field, options include {created_at, name, path, last_activity_at}, default is created_at"), + sort: z.string().default("desc").optional().describe("Sort order, options include {asc, desc}, default is desc"), + search: z.string().nullable().optional().describe("Search keyword, used to fuzzy match repository paths"), + archived: z.boolean().default(false).optional().describe("Whether archived"), +}); + +// Type exports +export type GetRepositoryOptions = z.infer; +export type ListRepositoriesOptions = z.infer; + +// Common helper function to handle repositoryId encoding +function handleRepositoryIdEncoding(repositoryId: string): string { + let encodedRepoId = repositoryId; + + // Automatically handle unencoded slashes in repositoryId + if (repositoryId.includes("/")) { + // Found unencoded slash, automatically URL encode it + const parts = repositoryId.split("/", 2); + if (parts.length === 2) { + const encodedRepoName = encodeURIComponent(parts[1]); + // Remove + signs from encoding (spaces are encoded as +, but we need %20) + const formattedEncodedName = encodedRepoName.replace(/\+/g, "%20"); + encodedRepoId = `${parts[0]}%2F${formattedEncodedName}`; + } + } + + return encodedRepoId; +} + +// Function implementations +export async function getRepositoryFunc( + organizationId: string, + repositoryId: string +): Promise> { + const encodedRepoId = handleRepositoryIdEncoding(repositoryId); + + const url = `/oapi/v1/codeup/organizations/${organizationId}/repositories/${encodedRepoId}`; + + const response = await yunxiaoRequest(url, { + method: "GET", + }); + + return RepositorySchema.parse(response); +} + +export async function listRepositoriesFunc( + organizationId: string, + page?: number, + perPage?: number, + orderBy?: string, + sort?: string, + search?: string, + archived?: boolean +): Promise[]> { + const baseUrl = `/oapi/v1/codeup/organizations/${organizationId}/repositories`; + + // Build query parameters + const queryParams: Record = {}; + + if (page !== undefined) { + queryParams.page = page; + } + + if (perPage !== undefined) { + queryParams.perPage = perPage; + } + + if (orderBy !== undefined) { + queryParams.orderBy = orderBy; + } + + if (sort !== undefined) { + queryParams.sort = sort; + } + + if (search !== undefined) { + queryParams.search = search; + } + + if (archived !== undefined) { + queryParams.archived = String(archived); // Convert boolean to string + } + + // Use buildUrl function to construct URL with query parameters + const url = buildUrl(baseUrl, queryParams); + + const response = await yunxiaoRequest(url, { + method: "GET", + }); + + // Ensure the response is an array + if (!Array.isArray(response)) { + return []; + } + + // Parse each repository object + return response.map(repo => RepositorySchema.parse(repo)); +} \ No newline at end of file diff --git a/operations/codeup/utils.ts b/operations/codeup/utils.ts new file mode 100644 index 0000000..83ad8a5 --- /dev/null +++ b/operations/codeup/utils.ts @@ -0,0 +1,74 @@ +/** + * Common utility functions for Codeup operations + */ + +import { z } from "zod"; + +/** + * Handle repository ID encoding + * @param repositoryId Repository ID which may contain unencoded slash + * @returns Properly encoded repository ID + */ +export function handleRepositoryIdEncoding(repositoryId: string): string { + let encodedRepoId = repositoryId; + + // Automatically handle unencoded slashes in repositoryId + if (repositoryId.includes("/")) { + // Found unencoded slash, automatically URL encode it + const parts = repositoryId.split("/", 2); + if (parts.length === 2) { + const encodedRepoName = encodeURIComponent(parts[1]); + // Remove + signs from encoding (spaces are encoded as +, but we need %20) + const formattedEncodedName = encodedRepoName.replace(/\+/g, "%20"); + encodedRepoId = `${parts[0]}%2F${formattedEncodedName}`; + } + } + + return encodedRepoId; +} + +/** + * Converts a floating point number to an integer string (removes decimal point and decimal part) + * Used primarily for handling numeric IDs that might come as floats from JSON parsing + * @param value Value to convert + * @returns Integer string representation + */ +export function floatToIntString(value: any): string { + // 如果传入的是字符串,先尝试转为浮点数 + if (typeof value === 'string') { + const floatValue = parseFloat(value); + if (!isNaN(floatValue)) { + value = floatValue; + } else { + return value; // 如果转换失败,返回原字符串 + } + } + + // 处理浮点数 + if (typeof value === 'number') { + const intValue = Math.floor(value + 0.5); // 四舍五入转整数 + return intValue.toString(); + } + + // 处理其他情况,直接转字符串 + return String(value); +} + +export function safeParseNumber(input: string | number | undefined): number | undefined { + if (input === undefined) { + return undefined; + } + + // If the input is a string, try to convert it to a float first + let numValue: number; + if (typeof input === 'string') { + numValue = parseFloat(input); + if (isNaN(numValue)) { + return undefined; + } + } else { + numValue = input; + } + + return numValue; +} \ No newline at end of file diff --git a/operations/organization/organization.ts b/operations/organization/organization.ts new file mode 100644 index 0000000..7d02034 --- /dev/null +++ b/operations/organization/organization.ts @@ -0,0 +1,31 @@ +import { z } from "zod"; +import { yunxiaoRequest } from "../../common/utils.js"; +import { CurrentOrganizationInfoSchema, UserOrganizationsInfoSchema } from "../../common/types.js"; + +// Function implementations +export async function getCurrentOrganizationInfoFunc( +): Promise> { + const url = "/oapi/v1/platform/user"; + + const response = await yunxiaoRequest(url, { + method: "GET", + }); + + return CurrentOrganizationInfoSchema.parse(response); +} + +export async function getUserOrganizationsFunc( +): Promise> { + const url = "/oapi/v1/platform/organizations"; + + const response = await yunxiaoRequest(url, { + method: "GET", + }); + + // Ensure response is an array + if (!Array.isArray(response)) { + return []; + } + + return UserOrganizationsInfoSchema.parse(response); +} \ No newline at end of file diff --git a/operations/projex/project.ts b/operations/projex/project.ts new file mode 100644 index 0000000..a1ee254 --- /dev/null +++ b/operations/projex/project.ts @@ -0,0 +1,236 @@ +import { z } from "zod"; +import { yunxiaoRequest } from "../../common/utils.js"; +import { ProjectInfoSchema, FilterConditionSchema, ConditionsSchema } from "../../common/types.js"; + +// Schema definitions +export const GetProjectSchema = z.object({ + organizationId: z.string().describe("Organization ID"), + id: z.string().describe("Project unique identifier"), +}); + +export const SearchProjectsSchema = z.object({ + organizationId: z.string().describe("Organization ID"), + + // Simplified search parameters + name: z.string().nullable().optional().describe("Text contained in project name"), + status: z.string().nullish().optional().describe("Project status ID, multiple separated by commas"), + createdAfter: z.string().nullable().optional().describe("Created not earlier than, format: YYYY-MM-DD"), + createdBefore: z.string().nullable().optional().describe("Created not later than, format: YYYY-MM-DD"), + creator: z.string().nullable().optional().describe("Creator"), + admin: z.string().nullable().optional().describe("Administrator"), + logicalStatus: z.string().nullable().optional().describe("Logical status, e.g., NORMAL"), + + // Advanced parameters + advancedConditions: z.string().nullable().optional().describe("Advanced filter conditions, JSON format"), + extraConditions: z.string().nullable().optional().describe("Additional filter conditions, e.g., projects I manage, projects I participate in, projects I favorited, etc."), + orderBy: z.string().optional().default("gmtCreate").describe("Sort field, default is gmtCreate, supports: gmtCreate (creation time), name (name)"), + page: z.number().int().default(1).optional().describe("Pagination parameter, page number"), + perPage: z.number().int().default(20).optional().describe("Pagination parameter, page size, 0-200, default value is 20"), + sort: z.string().optional().default("desc").describe("Sort order, default is desc, options: desc (descending), asc (ascending)"), +}); + +// Type exports +export type GetProjectOptions = z.infer; +export type SearchProjectsOptions = z.infer; + +// Function implementations +export async function getProjectFunc( + organizationId: string, + id: string +): Promise> { + const url = `/oapi/v1/projex/organizations/${organizationId}/projects/${id}`; + + const response = await yunxiaoRequest(url, { + method: "GET", + }); + + return ProjectInfoSchema.parse(response); +} + +export async function searchProjectsFunc( + organizationId: string, + name?: string, + status?: string, + createdAfter?: string, + createdBefore?: string, + creator?: string, + admin?: string, + logicalStatus?: string, + advancedConditions?: string, + extraConditions?: string, + orderBy?: string, // Possible values: "gmtCreate", "name" + page?: number, + perPage?: number, + sort?: string // Possible values: "desc", "asc" +): Promise[]> { + const url = `/oapi/v1/projex/organizations/${organizationId}/projects:search`; + + // Prepare payload + const payload: Record = {}; + + // Process condition parameters + const conditions = buildProjectConditions({ + name, + status, + createdAfter, + createdBefore, + creator, + admin, + logicalStatus, + advancedConditions + }); + + if (conditions) { + payload.conditions = conditions; + } + + // Add other optional parameters + if (extraConditions) { + payload.extraConditions = extraConditions; + } + + if (orderBy) { + payload.orderBy = orderBy; + } + + if (page !== undefined) { + payload.page = page; + } + + if (perPage !== undefined) { + payload.perPage = perPage; + } + + if (sort) { + payload.sort = sort; + } + + const response = await yunxiaoRequest(url, { + method: "POST", + body: payload, + }); + + // Ensure response is an array + if (!Array.isArray(response)) { + return []; + } + + // Parse each project object + return response.map(project => ProjectInfoSchema.parse(project)); +} + +// Build project search conditions +function buildProjectConditions(args: { + name?: string; + status?: string; + createdAfter?: string; + createdBefore?: string; + creator?: string; + admin?: string; + logicalStatus?: string; + advancedConditions?: string; +}): string | undefined { + // If advanced conditions are provided directly, use them preferentially + if (args.advancedConditions) { + return args.advancedConditions; + } + + // Build condition group + const filterConditions: z.infer[] = []; + + // Process name + if (args.name) { + filterConditions.push({ + className: "string", + fieldIdentifier: "name", + format: "input", + operator: "CONTAINS", + toValue: null, + value: [args.name], + }); + } + + // Process status + if (args.status) { + const statusValues = args.status.split(","); + const values = statusValues.map(v => v.trim()); + + filterConditions.push({ + className: "status", + fieldIdentifier: "status", + format: "list", + operator: "CONTAINS", + toValue: null, + value: values, + }); + } + + // Process creation time range + if (args.createdAfter) { + const createdBefore = args.createdBefore ? `${args.createdBefore} 23:59:59` : null; + + filterConditions.push({ + className: "date", + fieldIdentifier: "gmtCreate", + format: "input", + operator: "BETWEEN", + toValue: createdBefore, + value: [`${args.createdAfter} 00:00:00`], + }); + } + + // Process creator + if (args.creator) { + const creatorValues = args.creator.split(","); + const values = creatorValues.map(v => v.trim()); + + filterConditions.push({ + className: "user", + fieldIdentifier: "creator", + format: "list", + operator: "CONTAINS", + toValue: null, + value: values, + }); + } + + // Process administrator + if (args.admin) { + const adminValues = args.admin.split(","); + const values = adminValues.map(v => v.trim()); + + filterConditions.push({ + className: "user", + fieldIdentifier: "project.admin", + format: "multiList", + operator: "CONTAINS", + toValue: null, + value: values, + }); + } + + // Process logical status + if (args.logicalStatus) { + filterConditions.push({ + className: "string", + fieldIdentifier: "logicalStatus", + format: "list", + operator: "CONTAINS", + toValue: null, + value: [args.logicalStatus], + }); + } + + // If there are no conditions, return undefined + if (filterConditions.length === 0) { + return undefined; + } + + // Build complete condition object + const conditions: z.infer = { + conditionGroups: [filterConditions], + }; + + // Serialize to JSON + return JSON.stringify(conditions); +} \ No newline at end of file diff --git a/operations/projex/sprint.ts b/operations/projex/sprint.ts new file mode 100644 index 0000000..b606316 --- /dev/null +++ b/operations/projex/sprint.ts @@ -0,0 +1,78 @@ +import { z } from "zod"; +import { yunxiaoRequest, buildUrl } from "../../common/utils.js"; +import { SprintInfoSchema } from "../../common/types.js"; + +// Schema definitions +export const GetSprintSchema = z.object({ + organizationId: z.string().describe("Organization ID"), + projectId: z.string().describe("Project unique identifier"), + id: z.string().describe("Sprint unique identifier"), +}); + +export const ListSprintsSchema = z.object({ + organizationId: z.string().describe("Organization ID"), + id: z.string().describe("Project unique identifier"), + status: z.string().optional().describe("Filter by status: TODO, DOING, ARCHIVED, corresponding to not started, in progress, and completed; multiple statuses separated by commas"), + page: z.number().optional().describe("Pagination parameter, page number"), + perPage: z.number().optional().describe("Pagination parameter, page size"), +}); + +// Type exports +export type GetSprintOptions = z.infer; +export type ListSprintsOptions = z.infer; + +// Function implementations +export async function getSprintFunc( + organizationId: string, + projectId: string, + id: string +): Promise> { + const url = `/oapi/v1/projex/organizations/${organizationId}/projects/${projectId}/sprints/${id}`; + + const response = await yunxiaoRequest(url, { + method: "GET", + }); + + return SprintInfoSchema.parse(response); +} + +export async function listSprintsFunc( + organizationId: string, + id: string, + status?: string, + page?: number, + perPage?: number +): Promise[]> { + const baseUrl = `/oapi/v1/projex/organizations/${organizationId}/projects/${id}/sprints`; + + // Build query parameters + const queryParams: Record = {}; + + // Add optional parameters + if (status !== undefined) { + queryParams.status = status; + } + + if (page !== undefined) { + queryParams.page = page; + } + + if (perPage !== undefined) { + queryParams.perPage = perPage; + } + + // Use buildUrl function to construct URL with query parameters + const url = buildUrl(baseUrl, queryParams); + + const response = await yunxiaoRequest(url, { + method: "GET", + }); + + // Ensure response is an array + if (!Array.isArray(response)) { + return []; + } + + // Parse each sprint object + return response.map(sprint => SprintInfoSchema.parse(sprint)); +} \ No newline at end of file diff --git a/operations/projex/workitem.ts b/operations/projex/workitem.ts new file mode 100644 index 0000000..bbbc3c6 --- /dev/null +++ b/operations/projex/workitem.ts @@ -0,0 +1,201 @@ +import { z } from "zod"; +import { yunxiaoRequest } from "../../common/utils.js"; +import { WorkItemSchema, FilterConditionSchema, ConditionsSchema } from "../../common/types.js"; + +// Schema definitions +export const GetWorkItemSchema = z.object({ + organizationId: z.string().describe("Organization ID, can be found in the basic information page of the organization admin console"), + workItemId: z.string().describe("Work item unique identifier, required parameter"), +}); + +export const SearchWorkitemsSchema = z.object({ + organizationId: z.string().describe("Organization ID"), + category: z.string().describe("Search for work item types, such as Req (requirement), Task (task), Bug (defect), etc., multiple values separated by commas"), + spaceId: z.string().describe("Project ID, project unique identifier"), + + // Simplified search parameters + subject: z.string().nullable().optional().describe("Text contained in the title"), + status: z.string().nullable().optional().describe("Status ID, multiple separated by commas. Status names and their IDs: Pending Confirmation (28), Pending Processing (100005), Reopened (30), Deferred Fix (34), Confirmed (32), Selected (625489), In Analysis (154395), Analysis Complete (165115), In Progress (100010), In Design (156603), Design Complete (307012), In Development (142838), Development Complete (100011), In Testing (100012)"), + createdAfter: z.string().nullable().optional().describe("Created not earlier than, format: YYYY-MM-DD"), + createdBefore: z.string().nullable().optional().describe("Created not later than, format: YYYY-MM-DD"), + creator: z.string().nullable().optional().describe("Creator ID, multiple separated by commas"), + assignedTo: z.string().nullable().optional().describe("Assignee ID, multiple separated by commas"), + + // Advanced parameters + advancedConditions: z.string().nullable().optional().describe("Advanced filter conditions, JSON format"), + orderBy: z.string().optional().default("gmtCreate").describe("Sort field, default is gmtCreate. Possible values: gmtCreate, subject, status, priority, assignedTo"), +}); + +// Type exports +export type GetWorkItemOptions = z.infer; +export type SearchWorkitemsOptions = z.infer; + +// Function implementations +export async function getWorkItemFunc( + organizationId: string, + workItemId: string +): Promise> { + const url = `/oapi/v1/projex/organizations/${organizationId}/workitems/${workItemId}`; + + const response = await yunxiaoRequest(url, { + method: "GET", + }); + + return WorkItemSchema.parse(response); +} + +export async function searchWorkitemsFunc( + organizationId: string, + category: string, + spaceId: string, + subject?: string, + status?: string, + createdAfter?: string, + createdBefore?: string, + creator?: string, + assignedTo?: string, + advancedConditions?: string, + orderBy: string = "gmtCreate" // Possible values: gmtCreate, subject, status, priority, assignedTo +): Promise[]> { + const url = `/oapi/v1/projex/organizations/${organizationId}/workitems:search`; + + // Prepare payload + const payload: Record = { + category: category, + spaceId: spaceId, + }; + + // Process condition parameters + const conditions = buildWorkitemConditions({ + subject, + status, + createdAfter, + createdBefore, + creator, + assignedTo, + advancedConditions + }); + + if (conditions) { + payload.conditions = conditions; + } + + // Add orderBy parameter + payload.orderBy = orderBy; + + const response = await yunxiaoRequest(url, { + method: "POST", + body: payload, + }); + + // Ensure response is an array + if (!Array.isArray(response)) { + return []; + } + + // Parse each work item object + return response.map(workitem => WorkItemSchema.parse(workitem)); +} + +// Build work item search conditions +function buildWorkitemConditions(args: { + subject?: string; + status?: string; + createdAfter?: string; + createdBefore?: string; + creator?: string; + assignedTo?: string; + advancedConditions?: string; +}): string | undefined { + // If advanced conditions are provided directly, use them preferentially + if (args.advancedConditions) { + return args.advancedConditions; + } + + // Build condition group + const filterConditions: z.infer[] = []; + + // Process title + if (args.subject) { + filterConditions.push({ + className: "string", + fieldIdentifier: "subject", + format: "input", + operator: "CONTAINS", + toValue: null, + value: [args.subject], + }); + } + + // Process status + if (args.status) { + const statusValues = args.status.split(","); + const values = statusValues.map(v => v.trim()); + + filterConditions.push({ + className: "status", + fieldIdentifier: "status", + format: "list", + operator: "CONTAINS", + toValue: null, + value: values, + }); + } + + // Process creation time range + if (args.createdAfter) { + const createdBefore = args.createdBefore ? `${args.createdBefore} 23:59:59` : null; + + filterConditions.push({ + className: "date", + fieldIdentifier: "gmtCreate", + format: "input", + operator: "BETWEEN", + toValue: createdBefore, + value: [`${args.createdAfter} 00:00:00`], + }); + } + + // Process creator + if (args.creator) { + const creatorValues = args.creator.split(","); + const values = creatorValues.map(v => v.trim()); + + filterConditions.push({ + className: "user", + fieldIdentifier: "creator", + format: "list", + operator: "CONTAINS", + toValue: null, + value: values, + }); + } + + // Process assignee + if (args.assignedTo) { + const assignedToValues = args.assignedTo.split(","); + const values = assignedToValues.map(v => v.trim()); + + filterConditions.push({ + className: "user", + fieldIdentifier: "assignedTo", + format: "list", + operator: "CONTAINS", + toValue: null, + value: values, + }); + } + + // If there are no conditions, return undefined + if (filterConditions.length === 0) { + return undefined; + } + + // Build complete condition object + const conditions: z.infer = { + conditionGroups: [filterConditions], + }; + + // Serialize to JSON + return JSON.stringify(conditions); +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f7a2e5e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1158 @@ +{ + "name": "alibabacloud-devops-mcp-server", + "version": "0.1.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "alibabacloud-devops-mcp-server", + "version": "0.1.1", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.0.1", + "dotenv": "^16.4.7", + "universal-user-agent": "^7.0.0", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "bin": { + "alibabacloud-devops-mcp-server": "dist/index.js" + }, + "devDependencies": { + "@types/node": "^20.10.5", + "shx": "^0.3.4", + "typescript": "^5.8.2" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.10.2", + "resolved": "https://registry.npmmirror.com/@modelcontextprotocol/sdk/-/sdk-1.10.2.tgz", + "integrity": "sha512-rb6AMp2DR4SN+kc6L1ta2NCpApyA9WYNx3CrTSZvGxq9wH71bRur+zRqPfg0vQ9mjywR7qZdX2RGHOPq3ss+tA==", + "dependencies": { + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.3", + "eventsource": "^3.0.2", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/node": { + "version": "20.17.31", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.17.31.tgz", + "integrity": "sha512-quODOCNXQAbNf1Q7V+fI8WyErOCh0D5Yd31vHnKu4GkSztGQ7rlltAaqXhHhLl33tlVyUXs2386MkANSwgDn6A==", + "dev": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmmirror.com/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/eventsource/-/eventsource-3.0.6.tgz", + "integrity": "sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA==", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/eventsource-parser/-/eventsource-parser-3.0.1.tgz", + "integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.0", + "resolved": "https://registry.npmmirror.com/express-rate-limit/-/express-rate-limit-7.5.0.tgz", + "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": "^4.11 || 5 || ^5.0.0-beta.1" + } + }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "engines": { + "node": ">=16" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmmirror.com/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmmirror.com/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/shx": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/shx/-/shx-0.3.4.tgz", + "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", + "dev": true, + "dependencies": { + "minimist": "^1.2.3", + "shelljs": "^0.8.5" + }, + "bin": { + "shx": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true + }, + "node_modules/universal-user-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmmirror.com/universal-user-agent/-/universal-user-agent-7.0.2.tgz", + "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/zod": { + "version": "3.24.3", + "resolved": "https://registry.npmmirror.com/zod/-/zod-3.24.3.tgz", + "integrity": "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.5", + "resolved": "https://registry.npmmirror.com/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "peerDependencies": { + "zod": "^3.24.1" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..a8753f9 --- /dev/null +++ b/package.json @@ -0,0 +1,51 @@ +{ + "name": "alibabacloud-devops-mcp-server", + "version": "0.1.5", + "description": "MCP Server for using the alibabacloud-devops API: allows AI assistants to directly participate in development collaboration, helping teams optimize development processes and improve efficiency.", + "type": "module", + "license": "Apache-2.0", + "author": { + "name": "Yunxiao", + "email": "liyebin.lyb@alibaba-inc.com", + "url": "https://github.com/aliyun/alibabacloud-devops-mcp-server.git" + }, + "homepage": "https://github.com/aliyun/alibabacloud-devops-mcp-server.git", + "bin": { + "alibabacloud-devops-mcp-server": "dist/index.js" + }, + "files": [ + "dist" + ], + "repository": { + "type": "git", + "url": "git@github.com:aliyun/alibabacloud-devops-mcp-server.git" + }, + "keywords": [ + "alibaba", + "aliyun", + "cloud", + "devops", + "mcp", + "mcp", + "ai", + "open api", + "devops-mcp" + ], + "scripts": { + "build": "tsc && shx chmod +x dist/*.js", + "watch": "tsc --watch", + "start": "node dist/index.js" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.0.1", + "dotenv": "^16.4.7", + "universal-user-agent": "^7.0.0", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "devDependencies": { + "@types/node": "^20.10.5", + "shx": "^0.3.4", + "typescript": "^5.8.2" + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..62e6adf --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "Node16", + "moduleResolution": "Node16", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "outDir": "dist", + "rootDir": "." + }, + "include": [ + "./**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file