devops-mcp-server-extension 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/LICENSE +218 -0
  2. package/README.md +527 -0
  3. package/README.zh-cn.md +503 -0
  4. package/dist/common/errors.js +77 -0
  5. package/dist/common/modularTemplates.js +483 -0
  6. package/dist/common/pipelineTemplates.js +19 -0
  7. package/dist/common/toolsetManager.js +123 -0
  8. package/dist/common/toolsets.js +23 -0
  9. package/dist/common/types.js +60 -0
  10. package/dist/common/utils.js +381 -0
  11. package/dist/common/version.js +1 -0
  12. package/dist/index.js +225 -0
  13. package/dist/operations/appstack/appOrchestrations.js +260 -0
  14. package/dist/operations/appstack/appTags.js +168 -0
  15. package/dist/operations/appstack/appTemplates.js +72 -0
  16. package/dist/operations/appstack/applications.js +171 -0
  17. package/dist/operations/appstack/changeOrders.js +320 -0
  18. package/dist/operations/appstack/changeRequests.js +288 -0
  19. package/dist/operations/appstack/deploymentResources.js +286 -0
  20. package/dist/operations/appstack/globalVars.js +221 -0
  21. package/dist/operations/appstack/releaseWorkflows.js +695 -0
  22. package/dist/operations/appstack/variableGroups.js +245 -0
  23. package/dist/operations/codeup/branches.js +157 -0
  24. package/dist/operations/codeup/changeRequestComments.js +140 -0
  25. package/dist/operations/codeup/changeRequests.js +230 -0
  26. package/dist/operations/codeup/commits.js +121 -0
  27. package/dist/operations/codeup/compare.js +30 -0
  28. package/dist/operations/codeup/files.js +249 -0
  29. package/dist/operations/codeup/repositories.js +71 -0
  30. package/dist/operations/codeup/types.js +414 -0
  31. package/dist/operations/flow/hostGroup.js +52 -0
  32. package/dist/operations/flow/pipeline.js +609 -0
  33. package/dist/operations/flow/pipelineJob.js +126 -0
  34. package/dist/operations/flow/resourceMember.js +137 -0
  35. package/dist/operations/flow/serviceConnection.js +27 -0
  36. package/dist/operations/flow/tag.js +191 -0
  37. package/dist/operations/flow/types.js +523 -0
  38. package/dist/operations/flow/vmDeployOrder.js +171 -0
  39. package/dist/operations/organization/members.js +106 -0
  40. package/dist/operations/organization/organization.js +110 -0
  41. package/dist/operations/organization/types.js +111 -0
  42. package/dist/operations/packages/artifacts.js +71 -0
  43. package/dist/operations/packages/repositories.js +39 -0
  44. package/dist/operations/packages/types.js +56 -0
  45. package/dist/operations/projex/effort.js +122 -0
  46. package/dist/operations/projex/project.js +243 -0
  47. package/dist/operations/projex/sprint.js +103 -0
  48. package/dist/operations/projex/types.js +618 -0
  49. package/dist/operations/projex/workitem.js +826 -0
  50. package/dist/operations/testhub/testcases.js +240 -0
  51. package/dist/operations/testhub/testplans.js +128 -0
  52. package/dist/tool-handlers/appstack-app-release-workflows.js +103 -0
  53. package/dist/tool-handlers/appstack-change-orders.js +55 -0
  54. package/dist/tool-handlers/appstack-change-requests.js +49 -0
  55. package/dist/tool-handlers/appstack-deployment-resources.js +31 -0
  56. package/dist/tool-handlers/appstack-global-vars.js +37 -0
  57. package/dist/tool-handlers/appstack-orchestrations.js +49 -0
  58. package/dist/tool-handlers/appstack-release-workflows.js +37 -0
  59. package/dist/tool-handlers/appstack-tags.js +37 -0
  60. package/dist/tool-handlers/appstack-templates.js +19 -0
  61. package/dist/tool-handlers/appstack-variable-groups.js +55 -0
  62. package/dist/tool-handlers/appstack.js +37 -0
  63. package/dist/tool-handlers/base.js +25 -0
  64. package/dist/tool-handlers/code-management.js +150 -0
  65. package/dist/tool-handlers/commit.js +31 -0
  66. package/dist/tool-handlers/effort.js +103 -0
  67. package/dist/tool-handlers/index.js +119 -0
  68. package/dist/tool-handlers/organization.js +72 -0
  69. package/dist/tool-handlers/packages.js +32 -0
  70. package/dist/tool-handlers/pipeline.js +289 -0
  71. package/dist/tool-handlers/project-management.js +201 -0
  72. package/dist/tool-handlers/resourceMember.js +43 -0
  73. package/dist/tool-handlers/service-connections.js +16 -0
  74. package/dist/tool-handlers/tag.js +64 -0
  75. package/dist/tool-handlers/test-management.js +74 -0
  76. package/dist/tool-handlers/vmDeployOrder.js +50 -0
  77. package/dist/tool-registry/appstack-app-release-workflows.js +80 -0
  78. package/dist/tool-registry/appstack-change-orders.js +40 -0
  79. package/dist/tool-registry/appstack-change-requests.js +35 -0
  80. package/dist/tool-registry/appstack-deployment-resources.js +20 -0
  81. package/dist/tool-registry/appstack-global-vars.js +25 -0
  82. package/dist/tool-registry/appstack-orchestrations.js +35 -0
  83. package/dist/tool-registry/appstack-release-workflows.js +25 -0
  84. package/dist/tool-registry/appstack-tags.js +25 -0
  85. package/dist/tool-registry/appstack-templates.js +10 -0
  86. package/dist/tool-registry/appstack-variable-groups.js +40 -0
  87. package/dist/tool-registry/appstack.js +25 -0
  88. package/dist/tool-registry/base.js +19 -0
  89. package/dist/tool-registry/code-management.js +109 -0
  90. package/dist/tool-registry/commit.js +20 -0
  91. package/dist/tool-registry/effort.js +39 -0
  92. package/dist/tool-registry/index.js +7 -0
  93. package/dist/tool-registry/organization.js +65 -0
  94. package/dist/tool-registry/packages.js +21 -0
  95. package/dist/tool-registry/pipeline.js +190 -0
  96. package/dist/tool-registry/project-management.js +143 -0
  97. package/dist/tool-registry/resourceMember.js +29 -0
  98. package/dist/tool-registry/service-connections.js +10 -0
  99. package/dist/tool-registry/tag.js +44 -0
  100. package/dist/tool-registry/test-management.js +59 -0
  101. package/dist/tool-registry/vmDeployOrder.js +34 -0
  102. package/package.json +52 -0
@@ -0,0 +1,60 @@
1
+ // Organization types
2
+ export { UserInfoSchema, CurrentUserSchema, GetOrganizationMembersSchema, OrganizationRoleSchema, OrganizationRole, ListOrganizationRolesSchema, GetOrganizationRoleSchema, GetOrganizationDepartmentAncestorsSchema, GetOrganizationDepartmentInfoSchema, GetOrganizationDepartmentsSchema, CurrentOrganizationInfoSchema, OrganizationInfoSchema, UserOrganizationsInfoSchema, DepartmentInfoSchema, OrganizationDepartmentsSchema, MemberInfoSchema, OrganizationMembersSchema, GetOrganizationMemberInfoSchema, GetOrganizationMemberByUserIdInfoSchema, SearchOrganizationMembersSchema, SearchOrganizationMembersResultSchema } from "../operations/organization/types.js";
3
+ // Codeup types
4
+ export {
5
+ // Branch schemas
6
+ CreateBranchSchema, GetBranchSchema, DeleteBranchSchema, ListBranchesSchema,
7
+ // File schemas
8
+ GetFileBlobsSchema, CreateFileSchema, UpdateFileSchema, DeleteFileSchema, ListFilesSchema,
9
+ // Repository schemas
10
+ GetRepositorySchema, ListRepositoriesSchema,
11
+ // Compare schemas
12
+ GetCompareSchema,
13
+ // Change request schemas
14
+ GetChangeRequestSchema, ListChangeRequestsSchema, CreateChangeRequestSchema, ListChangeRequestPatchSetsSchema,
15
+ // Change request comment schemas
16
+ CreateChangeRequestCommentSchema, ListChangeRequestCommentsSchema, UpdateChangeRequestCommentSchema,
17
+ // Commit schemas
18
+ ListCommitsRequestSchema, GetCommitRequestSchema, CreateCommitCommentRequestSchema, DevopsCommitVOSchema, DevopsCommitStatVOSchema, CreateCommitCommentVOSchema } from "../operations/codeup/types.js";
19
+ // Projex types
20
+ export {
21
+ // Project schemas
22
+ GetProjectSchema, SearchProjectsSchema, ListProjectMembersSchema,
23
+ // Sprint schemas
24
+ GetSprintSchema, ListSprintsSchema, CreateSprintSchema, UpdateSprintSchema,
25
+ // Work item schemas
26
+ GetWorkItemSchema, CreateWorkItemSchema, SearchWorkitemsSchema, UpdateWorkItemSchema,
27
+ // Work item type schemas
28
+ ListAllWorkItemTypesSchema, ListWorkItemTypesSchema, GetWorkItemTypeSchema, ListWorkItemRelationWorkItemTypesSchema, GetWorkItemTypeFieldConfigSchema, GetWorkItemWorkflowSchema,
29
+ // Work item comment schemas
30
+ ListWorkItemCommentsSchema, CreateWorkItemCommentSchema,
31
+ // Work item activity schemas
32
+ ListWorkitemActivitiesSchema,
33
+ // Work item attachment schemas
34
+ ListWorkitemAttachmentsSchema,
35
+ // Work item file schemas
36
+ GetWorkitemFileSchema,
37
+ // Work item relation record schemas
38
+ ListWorkitemRelationRecordsSchema, CreateWorkitemRelationRecordSchema, DeleteWorkitemRelationRecordSchema,
39
+ // Effort schemas
40
+ ListCurrentUserEffortRecordsSchema, ListEffortRecordsSchema, CreateEffortRecordSchema, ListEstimatedEffortsSchema, CreateEstimatedEffortSchema, UpdateEffortRecordSchema, UpdateEstimatedEffortSchema } from "../operations/projex/types.js";
41
+ // Flow types
42
+ export {
43
+ // Pipeline schemas
44
+ GetPipelineSchema, ListPipelinesSchema, CreatePipelineFromDescriptionSchema, CreatePipelineRunSchema, GetLatestPipelineRunSchema, GetPipelineRunSchema, ListPipelineRunsSchema, UpdatePipelineSchema,
45
+ // Pipeline job schemas
46
+ ListPipelineJobsByCategorySchema, ListPipelineJobHistorysSchema, ExecutePipelineJobRunSchema, GetPipelineJobRunLogSchema,
47
+ // Service connection schemas
48
+ ListServiceConnectionsSchema,
49
+ // Resource member schemas
50
+ DeleteResourceMemberSchema, UpdateResourceMemberSchema, CreateResourceMemberSchema, UpdateResourceOwnerSchema, ResourceMemberSchema, ResourceMemberBaseSchema,
51
+ // Tag schemas
52
+ CreateTagSchema, CreateTagGroupSchema, DeleteTagGroupSchema, UpdateTagGroupSchema, DeleteTagSchema, UpdateTagSchema, GetTagGroupSchema, TagGroupSchema, TagSchema, TagGroupWithTagsSchema, BaseTagSchema,
53
+ // VM Deploy Order schemas
54
+ StopVMDeployOrderSchema, SkipVMDeployMachineSchema, RetryVMDeployMachineSchema, ResumeVMDeployOrderSchema, GetVMDeployOrderSchema, GetVMDeployMachineLogSchema, DeployOrderSchema, DeployOrderLogSchema } from "../operations/flow/types.js";
55
+ // Packages types
56
+ export {
57
+ // Package repository schemas
58
+ ListPackageRepositoriesSchema,
59
+ // Artifact schemas
60
+ ListArtifactsSchema, GetArtifactSchema } from "../operations/packages/types.js";
@@ -0,0 +1,381 @@
1
+ import { getUserAgent } from "universal-user-agent";
2
+ import { createYunxiaoError } from "./errors.js";
3
+ import { VERSION } from "./version.js";
4
+ const DEFAULT_YUNXIAO_API_BASE_URL = "https://openapi-rdc.aliyuncs.com";
5
+ /**
6
+ * Get the Yunxiao API base URL from environment variables or use the default
7
+ * @returns The Yunxiao API base URL
8
+ */
9
+ export function getYunxiaoApiBaseUrl() {
10
+ return process.env.YUNXIAO_API_BASE_URL || DEFAULT_YUNXIAO_API_BASE_URL;
11
+ }
12
+ /**
13
+ * 根据 YUNXIAO_API_BASE_URL 自动判断是否为 region 站:
14
+ * - 域名包含 openapi-rdc.aliyuncs.com -> 中心站
15
+ * - 其他域名 -> 认为是 region 站
16
+ */
17
+ export function isRegionEdition() {
18
+ const baseUrl = getYunxiaoApiBaseUrl();
19
+ return !baseUrl.includes("openapi-rdc.aliyuncs.com");
20
+ }
21
+ /**
22
+ * region 站下使用的"默认 organizationId"(占位值)
23
+ * - 默认使用 "default"
24
+ * - 如有需要,可以通过环境变量覆盖
25
+ */
26
+ export function getRegionDefaultOrganizationId() {
27
+ return process.env.YUNXIAO_REGION_DEFAULT_ORG_ID || "default";
28
+ }
29
+ export function debug(message, data) {
30
+ if (data !== undefined) {
31
+ console.error(`[DEBUG] ${message}`, typeof data === 'object' ? JSON.stringify(data, null, 2) : data);
32
+ }
33
+ else {
34
+ console.error(`[DEBUG] ${message}`);
35
+ }
36
+ }
37
+ async function parseResponseBody(response) {
38
+ const contentType = response.headers.get("content-type");
39
+ if (contentType?.includes("application/json")) {
40
+ return response.json();
41
+ }
42
+ return response.text();
43
+ }
44
+ export function buildUrl(baseUrl, params) {
45
+ // Handle baseUrl that doesn't have protocol
46
+ const isAbsolute = baseUrl.startsWith("http://") || baseUrl.startsWith("https://");
47
+ const fullBaseUrl = isAbsolute ? baseUrl : `${getYunxiaoApiBaseUrl()}${baseUrl.startsWith('/') ? baseUrl : `/${baseUrl}`}`;
48
+ try {
49
+ const url = new URL(fullBaseUrl);
50
+ Object.entries(params).forEach(([key, value]) => {
51
+ if (value !== undefined) {
52
+ url.searchParams.append(key, value.toString());
53
+ }
54
+ });
55
+ const result = url.toString();
56
+ console.error(`[DEBUG] Final URL: ${result}`);
57
+ // If we started with a relative URL, return just the path portion
58
+ if (!baseUrl.startsWith('http')) {
59
+ // Extract the path and query string from the full URL
60
+ const urlObj = new URL(result);
61
+ return urlObj.pathname + urlObj.search;
62
+ }
63
+ return result;
64
+ }
65
+ catch (error) {
66
+ console.error(`[ERROR] Failed to build URL: ${error}`);
67
+ // Fallback: manually append query parameters
68
+ let urlWithParams = baseUrl;
69
+ const queryParts = [];
70
+ Object.entries(params).forEach(([key, value]) => {
71
+ if (value !== undefined) {
72
+ queryParts.push(`${encodeURIComponent(key)}=${encodeURIComponent(value.toString())}`);
73
+ }
74
+ });
75
+ if (queryParts.length > 0) {
76
+ urlWithParams += (urlWithParams.includes('?') ? '&' : '?') + queryParts.join('&');
77
+ }
78
+ console.error(`[DEBUG] Fallback URL: ${urlWithParams}`);
79
+ return urlWithParams;
80
+ }
81
+ }
82
+ const USER_AGENT = `modelcontextprotocol/servers/alibabacloud-devops-mcp-server/v${VERSION} ${getUserAgent()}`;
83
+ let currentSessionToken = undefined;
84
+ /**
85
+ * Set the token for the current session (used in SSE mode)
86
+ * @param yunxiao_access_token The token to use for the current session
87
+ */
88
+ export function setCurrentSessionToken(yunxiao_access_token) {
89
+ currentSessionToken = yunxiao_access_token;
90
+ }
91
+ /**
92
+ * Get the token for the current session
93
+ * @returns The token for the current session, or the default token from environment
94
+ */
95
+ export function getCurrentSessionToken() {
96
+ return currentSessionToken || process.env.YUNXIAO_ACCESS_TOKEN;
97
+ }
98
+ export async function yunxiaoRequest(urlPath, options = {}) {
99
+ // Check if the URL is already a full URL or a path
100
+ const isAbsolute = urlPath.startsWith("http://") || urlPath.startsWith("https://");
101
+ let url = isAbsolute ? urlPath : `${getYunxiaoApiBaseUrl()}${urlPath.startsWith("/") ? urlPath : `/${urlPath}`}`;
102
+ const requestHeaders = {
103
+ "Accept": "application/json",
104
+ "Content-Type": "application/json",
105
+ "User-Agent": USER_AGENT,
106
+ ...options.headers,
107
+ };
108
+ // Use session-specific token if available, otherwise use default token
109
+ const token = getCurrentSessionToken();
110
+ if (token) {
111
+ requestHeaders["x-yunxiao-token"] = token;
112
+ }
113
+ debug(`Request: ${options.method} ${url}`);
114
+ debug(`Headers:`, requestHeaders);
115
+ debug(`Body:`, options.body);
116
+ const response = await fetch(url, {
117
+ method: options.method || "GET",
118
+ headers: requestHeaders,
119
+ body: options.body ? JSON.stringify(options.body) : undefined,
120
+ });
121
+ const responseBody = await parseResponseBody(response);
122
+ debug(`Response Body:`, responseBody);
123
+ debug(`Response status:`, response.ok);
124
+ if (!response.ok) {
125
+ throw createYunxiaoError(response.status, responseBody, url, options.method || "GET", requestHeaders, options.body);
126
+ }
127
+ return responseBody;
128
+ }
129
+ export function pathEscape(filePath) {
130
+ // 先使用encodeURIComponent进行编码
131
+ let encoded = encodeURIComponent(filePath);
132
+ // 将编码后的%2F(/的编码)替换回/
133
+ encoded = encoded.replace(/%2F/gi, "/");
134
+ return encoded;
135
+ }
136
+ /**
137
+ * Handle repository ID encoding
138
+ * @param repositoryId Repository ID which may contain unencoded slash
139
+ * @returns Properly encoded repository ID
140
+ */
141
+ export function handleRepositoryIdEncoding(repositoryId) {
142
+ let encodedRepoId = repositoryId;
143
+ // Automatically handle unencoded slashes in repositoryId
144
+ if (repositoryId.includes("/")) {
145
+ // Found unencoded slash, automatically URL encode it
146
+ const parts = repositoryId.split("/", 2);
147
+ if (parts.length === 2) {
148
+ const encodedRepoName = encodeURIComponent(parts[1]);
149
+ // Remove + signs from encoding (spaces are encoded as +, but we need %20)
150
+ const formattedEncodedName = encodedRepoName.replace(/\+/g, "%20");
151
+ encodedRepoId = `${parts[0]}%2F${formattedEncodedName}`;
152
+ }
153
+ }
154
+ return encodedRepoId;
155
+ }
156
+ /**
157
+ * Converts a floating point number to an integer string (removes decimal point and decimal part)
158
+ * Used primarily for handling numeric IDs that might come as floats from JSON parsing
159
+ * @param value Value to convert
160
+ * @returns Integer string representation
161
+ */
162
+ export function floatToIntString(value) {
163
+ // 如果传入的是字符串,先尝试转为浮点数
164
+ if (typeof value === 'string') {
165
+ const floatValue = parseFloat(value);
166
+ if (!isNaN(floatValue)) {
167
+ value = floatValue;
168
+ }
169
+ else {
170
+ return value; // 如果转换失败,返回原字符串
171
+ }
172
+ }
173
+ // 处理浮点数
174
+ if (typeof value === 'number') {
175
+ const intValue = Math.floor(value + 0.5); // 四舍五入转整数
176
+ return intValue.toString();
177
+ }
178
+ // 处理其他情况,直接转字符串
179
+ return String(value);
180
+ }
181
+ /**
182
+ * 将各种时间格式转换为毫秒时间戳
183
+ * 支持:
184
+ * - 已有时间戳(number)直接返回
185
+ * - Date对象转换为时间戳
186
+ * - ISO格式日期字符串 (如: '2023-01-01T00:00:00Z')
187
+ * - 日期字符串 (如: '2023-01-01')
188
+ *
189
+ * @param time 时间输入
190
+ * @returns 毫秒时间戳
191
+ */
192
+ export function convertToTimestamp(time) {
193
+ if (typeof time === 'number') {
194
+ // 如果已经是数字,假设已是时间戳
195
+ return time;
196
+ }
197
+ else if (time instanceof Date) {
198
+ // 如果是Date对象,转换为时间戳
199
+ return time.getTime();
200
+ }
201
+ else if (typeof time === 'string') {
202
+ // 尝试解析日期字符串
203
+ const date = new Date(time);
204
+ if (!isNaN(date.getTime())) {
205
+ return date.getTime();
206
+ }
207
+ }
208
+ // 无法转换时返回原值(如果是数字)或当前时间戳
209
+ return typeof time === 'number' ? time : Date.now();
210
+ }
211
+ /**
212
+ * Get start of today timestamp
213
+ * @returns Timestamp for start of the current day (00:00:00)
214
+ */
215
+ export function getStartOfTodayTimestamp() {
216
+ const now = new Date();
217
+ // Reset time to start of day (00:00:00.000)
218
+ now.setHours(0, 0, 0, 0);
219
+ return now.getTime();
220
+ }
221
+ /**
222
+ * Get end of today timestamp
223
+ * @returns Timestamp for end of the current day (23:59:59.999)
224
+ */
225
+ export function getEndOfTodayTimestamp() {
226
+ const now = new Date();
227
+ // Set time to end of day (23:59:59.999)
228
+ now.setHours(23, 59, 59, 999);
229
+ return now.getTime();
230
+ }
231
+ /**
232
+ * Get timestamp for start of a specific day
233
+ * @param date Date object or date string
234
+ * @returns Timestamp for start of the specified day
235
+ */
236
+ export function getStartOfDayTimestamp(date) {
237
+ const targetDate = typeof date === 'string' ? new Date(date) : new Date(date);
238
+ targetDate.setHours(0, 0, 0, 0);
239
+ return targetDate.getTime();
240
+ }
241
+ /**
242
+ * Get timestamp for end of a specific day
243
+ * @param date Date object or date string
244
+ * @returns Timestamp for end of the specified day
245
+ */
246
+ export function getEndOfDayTimestamp(date) {
247
+ const targetDate = typeof date === 'string' ? new Date(date) : new Date(date);
248
+ targetDate.setHours(23, 59, 59, 999);
249
+ return targetDate.getTime();
250
+ }
251
+ /**
252
+ * Get timestamp for start of current week
253
+ * @param startOnMonday Whether week should start on Monday (true) or Sunday (false)
254
+ * @returns Timestamp for start of the current week
255
+ */
256
+ export function getStartOfWeekTimestamp(startOnMonday = true) {
257
+ const now = new Date();
258
+ const dayOfWeek = now.getDay(); // 0 is Sunday, 1 is Monday, etc.
259
+ const diff = startOnMonday ?
260
+ (dayOfWeek === 0 ? 6 : dayOfWeek - 1) : // If startOnMonday, set Sunday as day 7
261
+ dayOfWeek;
262
+ // Set to beginning of the week
263
+ now.setDate(now.getDate() - diff);
264
+ now.setHours(0, 0, 0, 0);
265
+ return now.getTime();
266
+ }
267
+ /**
268
+ * Get timestamp for end of current week
269
+ * @param startOnMonday Whether week should start on Monday (true) or Sunday (false)
270
+ * @returns Timestamp for end of the current week
271
+ */
272
+ export function getEndOfWeekTimestamp(startOnMonday = true) {
273
+ const now = new Date();
274
+ const dayOfWeek = now.getDay(); // 0 is Sunday, 1 is Monday, etc.
275
+ const diff = startOnMonday ?
276
+ (dayOfWeek === 0 ? 0 : 7 - dayOfWeek) : // If startOnMonday, set Sunday as day 7
277
+ (6 - dayOfWeek);
278
+ // Set to end of the week
279
+ now.setDate(now.getDate() + diff);
280
+ now.setHours(23, 59, 59, 999);
281
+ return now.getTime();
282
+ }
283
+ /**
284
+ * Get timestamp for start of current month
285
+ * @returns Timestamp for start of the current month
286
+ */
287
+ export function getStartOfMonthTimestamp() {
288
+ const now = new Date();
289
+ now.setDate(1); // First day of current month
290
+ now.setHours(0, 0, 0, 0);
291
+ return now.getTime();
292
+ }
293
+ /**
294
+ * Get timestamp for end of current month
295
+ * @returns Timestamp for end of the current month
296
+ */
297
+ export function getEndOfMonthTimestamp() {
298
+ const now = new Date();
299
+ now.setMonth(now.getMonth() + 1); // Move to next month
300
+ now.setDate(0); // Last day of previous month (i.e., current month)
301
+ now.setHours(23, 59, 59, 999);
302
+ return now.getTime();
303
+ }
304
+ /**
305
+ * Analyzes natural language date reference and returns corresponding timestamp range
306
+ * @param dateReference Natural language date reference (e.g., "today", "this week", "last month")
307
+ * @returns Object containing start and end timestamps
308
+ */
309
+ export function parseDateReference(dateReference) {
310
+ if (!dateReference) {
311
+ // Default to all time
312
+ return {
313
+ startTime: 0,
314
+ endTime: Date.now()
315
+ };
316
+ }
317
+ const normalizedRef = dateReference.trim().toLowerCase();
318
+ // Today/yesterday
319
+ if (normalizedRef === 'today' || normalizedRef === '今天') {
320
+ return {
321
+ startTime: getStartOfTodayTimestamp(),
322
+ endTime: getEndOfTodayTimestamp()
323
+ };
324
+ }
325
+ if (normalizedRef === 'yesterday' || normalizedRef === '昨天') {
326
+ const yesterday = new Date();
327
+ yesterday.setDate(yesterday.getDate() - 1);
328
+ return {
329
+ startTime: getStartOfDayTimestamp(yesterday),
330
+ endTime: getEndOfDayTimestamp(yesterday)
331
+ };
332
+ }
333
+ // This week/last week
334
+ if (normalizedRef === 'this week' || normalizedRef === '本周' ||
335
+ normalizedRef === 'current week' || normalizedRef === '这周' ||
336
+ normalizedRef === '这个星期') {
337
+ return {
338
+ startTime: getStartOfWeekTimestamp(),
339
+ endTime: getEndOfWeekTimestamp()
340
+ };
341
+ }
342
+ if (normalizedRef === 'last week' || normalizedRef === '上周' ||
343
+ normalizedRef === '上個星期' || normalizedRef === '上个星期') {
344
+ const lastWeekStart = new Date(getStartOfWeekTimestamp());
345
+ lastWeekStart.setDate(lastWeekStart.getDate() - 7);
346
+ const lastWeekEnd = new Date(getEndOfWeekTimestamp());
347
+ lastWeekEnd.setDate(lastWeekEnd.getDate() - 7);
348
+ return {
349
+ startTime: lastWeekStart.getTime(),
350
+ endTime: lastWeekEnd.getTime()
351
+ };
352
+ }
353
+ // This month/last month
354
+ if (normalizedRef === 'this month' || normalizedRef === '本月' ||
355
+ normalizedRef === 'current month' || normalizedRef === '这个月') {
356
+ return {
357
+ startTime: getStartOfMonthTimestamp(),
358
+ endTime: getEndOfMonthTimestamp()
359
+ };
360
+ }
361
+ if (normalizedRef === 'last month' || normalizedRef === '上月' ||
362
+ normalizedRef === '上个月') {
363
+ const now = new Date();
364
+ const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1);
365
+ // Start of last month
366
+ const startOfLastMonth = new Date(lastMonth.getFullYear(), lastMonth.getMonth(), 1);
367
+ startOfLastMonth.setHours(0, 0, 0, 0);
368
+ // End of last month
369
+ const endOfLastMonth = new Date(now.getFullYear(), now.getMonth(), 0);
370
+ endOfLastMonth.setHours(23, 59, 59, 999);
371
+ return {
372
+ startTime: startOfLastMonth.getTime(),
373
+ endTime: endOfLastMonth.getTime()
374
+ };
375
+ }
376
+ // Default to all time
377
+ return {
378
+ startTime: 0,
379
+ endTime: Date.now()
380
+ };
381
+ }
@@ -0,0 +1 @@
1
+ export const VERSION = "0.1.17";
package/dist/index.js ADDED
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
5
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
6
+ import { z } from 'zod';
7
+ import { isYunxiaoError, YunxiaoValidationError } from "./common/errors.js";
8
+ import { VERSION } from "./common/version.js";
9
+ import { config } from "dotenv";
10
+ import { getAllTools, getEnabledTools } from "./tool-registry/index.js";
11
+ import { handleToolRequest, handleEnabledToolRequest } from "./tool-handlers/index.js";
12
+ import { Toolset } from "./common/toolsets.js";
13
+ const server = new Server({
14
+ name: "alibabacloud-devops-mcp-server",
15
+ version: VERSION,
16
+ }, {
17
+ capabilities: {
18
+ tools: {},
19
+ },
20
+ });
21
+ function formatYunxiaoError(error) {
22
+ let message = `Yunxiao API Error: ${error.message}`;
23
+ // 添加请求上下文信息
24
+ if (error.method || error.url) {
25
+ message += `\n Request: ${error.method || 'GET'} ${error.url || 'unknown'}`;
26
+ }
27
+ if (error.requestHeaders) {
28
+ message += `\n Request Headers: ${JSON.stringify(error.requestHeaders, null, 2)}`;
29
+ }
30
+ if (error.requestBody) {
31
+ message += `\n Request Body: ${JSON.stringify(error.requestBody, null, 2)}`;
32
+ }
33
+ if (error instanceof YunxiaoValidationError) {
34
+ message = `Parameter validation failed: ${error.message}`;
35
+ if (error.response) {
36
+ message += `\n Response: ${JSON.stringify(error.response, null, 2)}`;
37
+ }
38
+ // 添加常见参数错误的提示
39
+ if (error.message.includes('name')) {
40
+ message += `\n Suggestion: Please check whether the pipeline name meets the requirements.`;
41
+ }
42
+ if (error.message.includes('content') || error.message.includes('yaml')) {
43
+ message += `\n Suggestion: Please check whether the generated YAML format is correct.`;
44
+ }
45
+ }
46
+ else {
47
+ // 处理通用的Yunxiao错误
48
+ message = `Yunxiao API error (${error.status}): ${error.message}`;
49
+ if (error.response) {
50
+ const response = error.response;
51
+ if (response.errorCode) {
52
+ message += `\n errorCode: ${response.errorCode}`;
53
+ }
54
+ if (response.errorMessage && response.errorMessage !== error.message) {
55
+ message += `\n errorMessage: ${response.errorMessage}`;
56
+ }
57
+ if (response.data && typeof response.data === 'object') {
58
+ message += `\n data: ${JSON.stringify(response.data, null, 2)}`;
59
+ }
60
+ // 如果响应体中有更多详细信息,也一并显示
61
+ if (Object.keys(response).length > 0) {
62
+ message += `\n Full Response: ${JSON.stringify(response, null, 2)}`;
63
+ }
64
+ }
65
+ // 根据状态码提供通用建议
66
+ switch (error.status) {
67
+ case 400:
68
+ message += `\n Suggestion: Please check whether the request parameters are correct, especially whether all required parameters have been provided.`;
69
+ break;
70
+ case 500:
71
+ message += `\n Suggestion: Internal server error. Please try again later or contact technical support.`;
72
+ break;
73
+ case 502:
74
+ case 503:
75
+ case 504:
76
+ message += `\n Suggestion: The service is temporarily unavailable. Please try again later.`;
77
+ break;
78
+ }
79
+ }
80
+ return message;
81
+ }
82
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
83
+ let tools;
84
+ if (enabledToolsets.length > 0) {
85
+ // 获取基础工具(总是加载)
86
+ const baseTools = getEnabledTools([Toolset.BASE]);
87
+ // 获取启用的工具集工具
88
+ const enabledTools = getEnabledTools(enabledToolsets);
89
+ // 合并基础工具和启用的工具集工具
90
+ tools = [...baseTools, ...enabledTools];
91
+ }
92
+ else {
93
+ // 如果没有指定启用的工具集,则获取所有工具(已包含基础工具)
94
+ tools = getAllTools();
95
+ }
96
+ return {
97
+ tools,
98
+ };
99
+ });
100
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
101
+ try {
102
+ if (!request.params.arguments) {
103
+ throw new Error("Arguments are required");
104
+ }
105
+ // Delegate to our modular tool handler with toolset support
106
+ const result = enabledToolsets.length > 0
107
+ ? await handleEnabledToolRequest(request, enabledToolsets)
108
+ : await handleToolRequest(request);
109
+ return result;
110
+ }
111
+ catch (error) {
112
+ if (error instanceof z.ZodError) {
113
+ throw new Error(`Invalid input: ${JSON.stringify(error.errors)}`);
114
+ }
115
+ if (isYunxiaoError(error)) {
116
+ throw new Error(formatYunxiaoError(error));
117
+ }
118
+ throw error;
119
+ }
120
+ });
121
+ config();
122
+ // 解析启用的工具集
123
+ const parseEnabledToolsets = (input) => {
124
+ if (!input)
125
+ return [];
126
+ return input.split(',').map(toolset => {
127
+ const trimmed = toolset.trim();
128
+ // 验证工具集名称是否有效
129
+ if (!Object.values(Toolset).includes(trimmed)) {
130
+ throw new Error(`Unknown toolset: ${trimmed}`);
131
+ }
132
+ return trimmed;
133
+ });
134
+ };
135
+ // 获取启用的工具集(从命令行参数或环境变量)
136
+ const enabledToolsets = parseEnabledToolsets(process.argv.find(arg => arg.startsWith('--toolsets='))?.split('=')[1] ||
137
+ process.env.DEVOPS_TOOLSETS);
138
+ // Check if we should run in SSE mode
139
+ const useSSE = process.argv.includes('--sse') || process.env.MCP_TRANSPORT === 'sse';
140
+ async function runServer() {
141
+ if (useSSE) {
142
+ // Import express only when needed for SSE mode
143
+ const { default: express } = await import('express');
144
+ const app = express();
145
+ const port = process.env.PORT || 3000;
146
+ // Store sessions with their tokens
147
+ const sessions = {};
148
+ // SSE endpoint - handles initial connection
149
+ app.get('/sse', async (req, res) => {
150
+ // In SSE mode, we can use console.log for debugging since it doesn't interfere with the protocol
151
+ console.log(`New SSE connection from ${req.ip}`);
152
+ // Get token from query parameters or headers
153
+ const yunxiao_access_token = req.query.yunxiao_access_token || req.headers['x-yunxiao-token'] || process.env.YUNXIAO_ACCESS_TOKEN;
154
+ // Create transport with endpoint for POST messages
155
+ const sseTransport = new SSEServerTransport('/messages', res);
156
+ const sessionId = sseTransport.sessionId;
157
+ if (sessionId) {
158
+ sessions[sessionId] = { transport: sseTransport, server, yunxiao_access_token };
159
+ }
160
+ try {
161
+ await server.connect(sseTransport);
162
+ // In SSE mode, console.error is acceptable for status messages
163
+ console.info(`Yunxiao MCP Server connected via SSE with session ${sessionId}`);
164
+ if (yunxiao_access_token) {
165
+ console.error(`Session ${sessionId} using custom token`);
166
+ }
167
+ else {
168
+ console.error(`Session ${sessionId} using default token from environment`);
169
+ }
170
+ }
171
+ catch (error) {
172
+ console.error("Failed to start SSE server:", error);
173
+ res.status(500).send("Server error");
174
+ }
175
+ });
176
+ // POST endpoint - handles incoming messages
177
+ app.use(express.json({ limit: '10mb' })); // Add JSON body parser
178
+ app.post('/messages', async (req, res) => {
179
+ const sessionId = req.query.sessionId;
180
+ const session = sessions[sessionId];
181
+ if (!session) {
182
+ res.status(404).send("Session not found");
183
+ return;
184
+ }
185
+ try {
186
+ // Set the session token before handling the message
187
+ const utils = await import('./common/utils.js');
188
+ utils.setCurrentSessionToken(session.yunxiao_access_token);
189
+ await session.transport.handlePostMessage(req, res, req.body);
190
+ }
191
+ catch (error) {
192
+ console.error("Error handling POST message:", error);
193
+ res.status(500).send("Server error");
194
+ }
195
+ });
196
+ // Start server
197
+ const serverInstance = app.listen(port, () => {
198
+ console.log(`Yunxiao MCP Server running in SSE mode on port ${port}`);
199
+ console.log(`Connect via SSE at http://localhost:${port}/sse`);
200
+ console.log(`Send messages to http://localhost:${port}/messages?sessionId=<session-id>`);
201
+ });
202
+ // Handle graceful shutdown
203
+ process.on('SIGINT', () => {
204
+ console.log('Shutting down SSE server...');
205
+ serverInstance.close(() => {
206
+ console.log('Server closed.');
207
+ process.exit(0);
208
+ });
209
+ });
210
+ }
211
+ else {
212
+ // Stdio mode (default)
213
+ // In stdio mode, we must avoid console.log/console.error as they interfere with the JSON-RPC protocol
214
+ const transport = new StdioServerTransport();
215
+ await server.connect(transport);
216
+ // Don't output anything to stdout/stderr in stdio mode - only JSON-RPC messages should go through the transport
217
+ }
218
+ }
219
+ runServer().catch((error) => {
220
+ // Only output error to stderr in SSE mode, not in stdio mode
221
+ if (useSSE) {
222
+ console.error("Fatal error in main():", error);
223
+ }
224
+ process.exit(1);
225
+ });