@catchmexz/fedin-vibe-mcp-server 0.1.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 (83) hide show
  1. package/LICENSE +202 -0
  2. package/dist/common/errors.js +69 -0
  3. package/dist/common/modularTemplates.js +483 -0
  4. package/dist/common/pipelineTemplates.js +19 -0
  5. package/dist/common/types.js +42 -0
  6. package/dist/common/utils.js +347 -0
  7. package/dist/common/version.js +1 -0
  8. package/dist/index.js +217 -0
  9. package/dist/operations/appstack/appOrchestrations.js +235 -0
  10. package/dist/operations/appstack/appTags.js +147 -0
  11. package/dist/operations/appstack/appTemplates.js +67 -0
  12. package/dist/operations/appstack/applications.js +154 -0
  13. package/dist/operations/appstack/changeOrders.js +293 -0
  14. package/dist/operations/appstack/changeRequests.js +263 -0
  15. package/dist/operations/appstack/deploymentResources.js +265 -0
  16. package/dist/operations/appstack/globalVars.js +200 -0
  17. package/dist/operations/appstack/releaseWorkflows.js +178 -0
  18. package/dist/operations/appstack/variableGroups.js +216 -0
  19. package/dist/operations/codeup/branches.js +144 -0
  20. package/dist/operations/codeup/changeRequestComments.js +89 -0
  21. package/dist/operations/codeup/changeRequests.js +203 -0
  22. package/dist/operations/codeup/compare.js +26 -0
  23. package/dist/operations/codeup/files.js +483 -0
  24. package/dist/operations/codeup/repositories.js +83 -0
  25. package/dist/operations/codeup/types.js +372 -0
  26. package/dist/operations/flow/hostGroup.js +48 -0
  27. package/dist/operations/flow/pipeline.js +530 -0
  28. package/dist/operations/flow/pipelineJob.js +113 -0
  29. package/dist/operations/flow/serviceConnection.js +23 -0
  30. package/dist/operations/flow/types.js +377 -0
  31. package/dist/operations/git/git-repository.js +334 -0
  32. package/dist/operations/git/index.js +210 -0
  33. package/dist/operations/organization/members.js +94 -0
  34. package/dist/operations/organization/organization.js +73 -0
  35. package/dist/operations/organization/types.js +111 -0
  36. package/dist/operations/packages/artifacts.js +64 -0
  37. package/dist/operations/packages/repositories.js +35 -0
  38. package/dist/operations/packages/types.js +56 -0
  39. package/dist/operations/projex/project.js +206 -0
  40. package/dist/operations/projex/sprint.js +90 -0
  41. package/dist/operations/projex/types.js +390 -0
  42. package/dist/operations/projex/workitem.js +452 -0
  43. package/dist/tool-handlers/appstack-change-orders.js +55 -0
  44. package/dist/tool-handlers/appstack-change-requests.js +49 -0
  45. package/dist/tool-handlers/appstack-deployment-resources.js +43 -0
  46. package/dist/tool-handlers/appstack-global-vars.js +43 -0
  47. package/dist/tool-handlers/appstack-orchestrations.js +49 -0
  48. package/dist/tool-handlers/appstack-tags.js +43 -0
  49. package/dist/tool-handlers/appstack-templates.js +19 -0
  50. package/dist/tool-handlers/appstack-variable-groups.js +55 -0
  51. package/dist/tool-handlers/appstack.js +37 -0
  52. package/dist/tool-handlers/code-management.js +174 -0
  53. package/dist/tool-handlers/git/branch-operations.js +1 -0
  54. package/dist/tool-handlers/git/clone-repository.js +36 -0
  55. package/dist/tool-handlers/git/create-branch.js +26 -0
  56. package/dist/tool-handlers/git/get-repository-status.js +33 -0
  57. package/dist/tool-handlers/git/pull-repository.js +27 -0
  58. package/dist/tool-handlers/git/push-repository.js +37 -0
  59. package/dist/tool-handlers/git/switch-branch.js +25 -0
  60. package/dist/tool-handlers/index.js +43 -0
  61. package/dist/tool-handlers/organization.js +90 -0
  62. package/dist/tool-handlers/packages.js +32 -0
  63. package/dist/tool-handlers/pipeline.js +272 -0
  64. package/dist/tool-handlers/project-management.js +152 -0
  65. package/dist/tool-handlers/service-connections.js +16 -0
  66. package/dist/tool-registry/appstack-change-orders.js +40 -0
  67. package/dist/tool-registry/appstack-change-requests.js +35 -0
  68. package/dist/tool-registry/appstack-deployment-resources.js +30 -0
  69. package/dist/tool-registry/appstack-global-vars.js +30 -0
  70. package/dist/tool-registry/appstack-orchestrations.js +35 -0
  71. package/dist/tool-registry/appstack-tags.js +30 -0
  72. package/dist/tool-registry/appstack-templates.js +10 -0
  73. package/dist/tool-registry/appstack-variable-groups.js +40 -0
  74. package/dist/tool-registry/appstack.js +25 -0
  75. package/dist/tool-registry/code-management.js +89 -0
  76. package/dist/tool-registry/git-repository.js +41 -0
  77. package/dist/tool-registry/index.js +6 -0
  78. package/dist/tool-registry/organization.js +65 -0
  79. package/dist/tool-registry/packages.js +21 -0
  80. package/dist/tool-registry/pipeline.js +157 -0
  81. package/dist/tool-registry/project-management.js +108 -0
  82. package/dist/tool-registry/service-connections.js +10 -0
  83. package/package.json +39 -0
@@ -0,0 +1,347 @@
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
+ export function debug(message, data) {
13
+ if (data !== undefined) {
14
+ console.error(`[DEBUG] ${message}`, typeof data === 'object' ? JSON.stringify(data, null, 2) : data);
15
+ }
16
+ else {
17
+ console.error(`[DEBUG] ${message}`);
18
+ }
19
+ }
20
+ async function parseResponseBody(response) {
21
+ const contentType = response.headers.get("content-type");
22
+ if (contentType?.includes("application/json")) {
23
+ return response.json();
24
+ }
25
+ return response.text();
26
+ }
27
+ export function buildUrl(baseUrl, params) {
28
+ // Handle baseUrl that doesn't have protocol
29
+ const isAbsolute = baseUrl.startsWith("http://") || baseUrl.startsWith("https://");
30
+ const fullBaseUrl = isAbsolute ? baseUrl : `${getYunxiaoApiBaseUrl()}${baseUrl.startsWith('/') ? baseUrl : `/${baseUrl}`}`;
31
+ try {
32
+ const url = new URL(fullBaseUrl);
33
+ Object.entries(params).forEach(([key, value]) => {
34
+ if (value !== undefined) {
35
+ url.searchParams.append(key, value.toString());
36
+ }
37
+ });
38
+ const result = url.toString();
39
+ console.error(`[DEBUG] Final URL: ${result}`);
40
+ // If we started with a relative URL, return just the path portion
41
+ if (!baseUrl.startsWith('http')) {
42
+ // Extract the path and query string from the full URL
43
+ const urlObj = new URL(result);
44
+ return urlObj.pathname + urlObj.search;
45
+ }
46
+ return result;
47
+ }
48
+ catch (error) {
49
+ console.error(`[ERROR] Failed to build URL: ${error}`);
50
+ // Fallback: manually append query parameters
51
+ let urlWithParams = baseUrl;
52
+ const queryParts = [];
53
+ Object.entries(params).forEach(([key, value]) => {
54
+ if (value !== undefined) {
55
+ queryParts.push(`${encodeURIComponent(key)}=${encodeURIComponent(value.toString())}`);
56
+ }
57
+ });
58
+ if (queryParts.length > 0) {
59
+ urlWithParams += (urlWithParams.includes('?') ? '&' : '?') + queryParts.join('&');
60
+ }
61
+ console.error(`[DEBUG] Fallback URL: ${urlWithParams}`);
62
+ return urlWithParams;
63
+ }
64
+ }
65
+ const USER_AGENT = `modelcontextprotocol/servers/alibabacloud-devops-mcp-server/v${VERSION} ${getUserAgent()}`;
66
+ export async function yunxiaoRequest(urlPath, options = {}) {
67
+ // Check if the URL is already a full URL or a path
68
+ const isAbsolute = urlPath.startsWith("http://") || urlPath.startsWith("https://");
69
+ let url = isAbsolute ? urlPath : `${getYunxiaoApiBaseUrl()}${urlPath.startsWith("/") ? urlPath : `/${urlPath}`}`;
70
+ const requestHeaders = {
71
+ "Accept": "application/json",
72
+ "Content-Type": "application/json",
73
+ "User-Agent": USER_AGENT,
74
+ ...options.headers,
75
+ };
76
+ if (process.env.YUNXIAO_ACCESS_TOKEN) {
77
+ requestHeaders["x-yunxiao-token"] = process.env.YUNXIAO_ACCESS_TOKEN;
78
+ }
79
+ debug(`Request: ${options.method} ${url}`);
80
+ debug(`Headers:`, requestHeaders);
81
+ debug(`Body:`, options.body);
82
+ const response = await fetch(url, {
83
+ method: options.method || "GET",
84
+ headers: requestHeaders,
85
+ body: options.body ? JSON.stringify(options.body) : undefined,
86
+ });
87
+ const responseBody = await parseResponseBody(response);
88
+ debug(`Response Body:`, responseBody);
89
+ debug(`Response status:`, response.ok);
90
+ if (!response.ok) {
91
+ throw createYunxiaoError(response.status, responseBody);
92
+ }
93
+ return responseBody;
94
+ }
95
+ export function pathEscape(filePath) {
96
+ // 先使用encodeURIComponent进行编码
97
+ let encoded = encodeURIComponent(filePath);
98
+ // 将编码后的%2F(/的编码)替换回/
99
+ encoded = encoded.replace(/%2F/gi, "/");
100
+ return encoded;
101
+ }
102
+ /**
103
+ * Handle repository ID encoding
104
+ * @param repositoryId Repository ID which may contain unencoded slash
105
+ * @returns Properly encoded repository ID
106
+ */
107
+ export function handleRepositoryIdEncoding(repositoryId) {
108
+ let encodedRepoId = repositoryId;
109
+ // Automatically handle unencoded slashes in repositoryId
110
+ if (repositoryId.includes("/")) {
111
+ // Found unencoded slash, automatically URL encode it
112
+ const parts = repositoryId.split("/", 2);
113
+ if (parts.length === 2) {
114
+ const encodedRepoName = encodeURIComponent(parts[1]);
115
+ // Remove + signs from encoding (spaces are encoded as +, but we need %20)
116
+ const formattedEncodedName = encodedRepoName.replace(/\+/g, "%20");
117
+ encodedRepoId = `${parts[0]}%2F${formattedEncodedName}`;
118
+ }
119
+ }
120
+ return encodedRepoId;
121
+ }
122
+ /**
123
+ * Converts a floating point number to an integer string (removes decimal point and decimal part)
124
+ * Used primarily for handling numeric IDs that might come as floats from JSON parsing
125
+ * @param value Value to convert
126
+ * @returns Integer string representation
127
+ */
128
+ export function floatToIntString(value) {
129
+ // 如果传入的是字符串,先尝试转为浮点数
130
+ if (typeof value === 'string') {
131
+ const floatValue = parseFloat(value);
132
+ if (!isNaN(floatValue)) {
133
+ value = floatValue;
134
+ }
135
+ else {
136
+ return value; // 如果转换失败,返回原字符串
137
+ }
138
+ }
139
+ // 处理浮点数
140
+ if (typeof value === 'number') {
141
+ const intValue = Math.floor(value + 0.5); // 四舍五入转整数
142
+ return intValue.toString();
143
+ }
144
+ // 处理其他情况,直接转字符串
145
+ return String(value);
146
+ }
147
+ /**
148
+ * 将各种时间格式转换为毫秒时间戳
149
+ * 支持:
150
+ * - 已有时间戳(number)直接返回
151
+ * - Date对象转换为时间戳
152
+ * - ISO格式日期字符串 (如: '2023-01-01T00:00:00Z')
153
+ * - 日期字符串 (如: '2023-01-01')
154
+ *
155
+ * @param time 时间输入
156
+ * @returns 毫秒时间戳
157
+ */
158
+ export function convertToTimestamp(time) {
159
+ if (typeof time === 'number') {
160
+ // 如果已经是数字,假设已是时间戳
161
+ return time;
162
+ }
163
+ else if (time instanceof Date) {
164
+ // 如果是Date对象,转换为时间戳
165
+ return time.getTime();
166
+ }
167
+ else if (typeof time === 'string') {
168
+ // 尝试解析日期字符串
169
+ const date = new Date(time);
170
+ if (!isNaN(date.getTime())) {
171
+ return date.getTime();
172
+ }
173
+ }
174
+ // 无法转换时返回原值(如果是数字)或当前时间戳
175
+ return typeof time === 'number' ? time : Date.now();
176
+ }
177
+ /**
178
+ * Get start of today timestamp
179
+ * @returns Timestamp for start of the current day (00:00:00)
180
+ */
181
+ export function getStartOfTodayTimestamp() {
182
+ const now = new Date();
183
+ // Reset time to start of day (00:00:00.000)
184
+ now.setHours(0, 0, 0, 0);
185
+ return now.getTime();
186
+ }
187
+ /**
188
+ * Get end of today timestamp
189
+ * @returns Timestamp for end of the current day (23:59:59.999)
190
+ */
191
+ export function getEndOfTodayTimestamp() {
192
+ const now = new Date();
193
+ // Set time to end of day (23:59:59.999)
194
+ now.setHours(23, 59, 59, 999);
195
+ return now.getTime();
196
+ }
197
+ /**
198
+ * Get timestamp for start of a specific day
199
+ * @param date Date object or date string
200
+ * @returns Timestamp for start of the specified day
201
+ */
202
+ export function getStartOfDayTimestamp(date) {
203
+ const targetDate = typeof date === 'string' ? new Date(date) : new Date(date);
204
+ targetDate.setHours(0, 0, 0, 0);
205
+ return targetDate.getTime();
206
+ }
207
+ /**
208
+ * Get timestamp for end of a specific day
209
+ * @param date Date object or date string
210
+ * @returns Timestamp for end of the specified day
211
+ */
212
+ export function getEndOfDayTimestamp(date) {
213
+ const targetDate = typeof date === 'string' ? new Date(date) : new Date(date);
214
+ targetDate.setHours(23, 59, 59, 999);
215
+ return targetDate.getTime();
216
+ }
217
+ /**
218
+ * Get timestamp for start of current week
219
+ * @param startOnMonday Whether week should start on Monday (true) or Sunday (false)
220
+ * @returns Timestamp for start of the current week
221
+ */
222
+ export function getStartOfWeekTimestamp(startOnMonday = true) {
223
+ const now = new Date();
224
+ const dayOfWeek = now.getDay(); // 0 is Sunday, 1 is Monday, etc.
225
+ const diff = startOnMonday ?
226
+ (dayOfWeek === 0 ? 6 : dayOfWeek - 1) : // If startOnMonday, set Sunday as day 7
227
+ dayOfWeek;
228
+ // Set to beginning of the week
229
+ now.setDate(now.getDate() - diff);
230
+ now.setHours(0, 0, 0, 0);
231
+ return now.getTime();
232
+ }
233
+ /**
234
+ * Get timestamp for end of current week
235
+ * @param startOnMonday Whether week should start on Monday (true) or Sunday (false)
236
+ * @returns Timestamp for end of the current week
237
+ */
238
+ export function getEndOfWeekTimestamp(startOnMonday = true) {
239
+ const now = new Date();
240
+ const dayOfWeek = now.getDay(); // 0 is Sunday, 1 is Monday, etc.
241
+ const diff = startOnMonday ?
242
+ (dayOfWeek === 0 ? 0 : 7 - dayOfWeek) : // If startOnMonday, set Sunday as day 7
243
+ (6 - dayOfWeek);
244
+ // Set to end of the week
245
+ now.setDate(now.getDate() + diff);
246
+ now.setHours(23, 59, 59, 999);
247
+ return now.getTime();
248
+ }
249
+ /**
250
+ * Get timestamp for start of current month
251
+ * @returns Timestamp for start of the current month
252
+ */
253
+ export function getStartOfMonthTimestamp() {
254
+ const now = new Date();
255
+ now.setDate(1); // First day of current month
256
+ now.setHours(0, 0, 0, 0);
257
+ return now.getTime();
258
+ }
259
+ /**
260
+ * Get timestamp for end of current month
261
+ * @returns Timestamp for end of the current month
262
+ */
263
+ export function getEndOfMonthTimestamp() {
264
+ const now = new Date();
265
+ now.setMonth(now.getMonth() + 1); // Move to next month
266
+ now.setDate(0); // Last day of previous month (i.e., current month)
267
+ now.setHours(23, 59, 59, 999);
268
+ return now.getTime();
269
+ }
270
+ /**
271
+ * Analyzes natural language date reference and returns corresponding timestamp range
272
+ * @param dateReference Natural language date reference (e.g., "today", "this week", "last month")
273
+ * @returns Object containing start and end timestamps
274
+ */
275
+ export function parseDateReference(dateReference) {
276
+ if (!dateReference) {
277
+ // Default to all time
278
+ return {
279
+ startTime: 0,
280
+ endTime: Date.now()
281
+ };
282
+ }
283
+ const normalizedRef = dateReference.trim().toLowerCase();
284
+ // Today/yesterday
285
+ if (normalizedRef === 'today' || normalizedRef === '今天') {
286
+ return {
287
+ startTime: getStartOfTodayTimestamp(),
288
+ endTime: getEndOfTodayTimestamp()
289
+ };
290
+ }
291
+ if (normalizedRef === 'yesterday' || normalizedRef === '昨天') {
292
+ const yesterday = new Date();
293
+ yesterday.setDate(yesterday.getDate() - 1);
294
+ return {
295
+ startTime: getStartOfDayTimestamp(yesterday),
296
+ endTime: getEndOfDayTimestamp(yesterday)
297
+ };
298
+ }
299
+ // This week/last week
300
+ if (normalizedRef === 'this week' || normalizedRef === '本周' ||
301
+ normalizedRef === 'current week' || normalizedRef === '这周' ||
302
+ normalizedRef === '这个星期') {
303
+ return {
304
+ startTime: getStartOfWeekTimestamp(),
305
+ endTime: getEndOfWeekTimestamp()
306
+ };
307
+ }
308
+ if (normalizedRef === 'last week' || normalizedRef === '上周' ||
309
+ normalizedRef === '上個星期' || normalizedRef === '上个星期') {
310
+ const lastWeekStart = new Date(getStartOfWeekTimestamp());
311
+ lastWeekStart.setDate(lastWeekStart.getDate() - 7);
312
+ const lastWeekEnd = new Date(getEndOfWeekTimestamp());
313
+ lastWeekEnd.setDate(lastWeekEnd.getDate() - 7);
314
+ return {
315
+ startTime: lastWeekStart.getTime(),
316
+ endTime: lastWeekEnd.getTime()
317
+ };
318
+ }
319
+ // This month/last month
320
+ if (normalizedRef === 'this month' || normalizedRef === '本月' ||
321
+ normalizedRef === 'current month' || normalizedRef === '这个月') {
322
+ return {
323
+ startTime: getStartOfMonthTimestamp(),
324
+ endTime: getEndOfMonthTimestamp()
325
+ };
326
+ }
327
+ if (normalizedRef === 'last month' || normalizedRef === '上月' ||
328
+ normalizedRef === '上个月') {
329
+ const now = new Date();
330
+ const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1);
331
+ // Start of last month
332
+ const startOfLastMonth = new Date(lastMonth.getFullYear(), lastMonth.getMonth(), 1);
333
+ startOfLastMonth.setHours(0, 0, 0, 0);
334
+ // End of last month
335
+ const endOfLastMonth = new Date(now.getFullYear(), now.getMonth(), 0);
336
+ endOfLastMonth.setHours(23, 59, 59, 999);
337
+ return {
338
+ startTime: startOfLastMonth.getTime(),
339
+ endTime: endOfLastMonth.getTime()
340
+ };
341
+ }
342
+ // Default to all time
343
+ return {
344
+ startTime: 0,
345
+ endTime: Date.now()
346
+ };
347
+ }
@@ -0,0 +1 @@
1
+ export const VERSION = "0.1.17";
package/dist/index.js ADDED
@@ -0,0 +1,217 @@
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 } from "./tool-registry/index.js";
11
+ import { handleToolRequest } from "./tool-handlers/index.js";
12
+ const server = new Server({
13
+ name: "alibabacloud-devops-mcp-server",
14
+ version: VERSION,
15
+ }, {
16
+ capabilities: {
17
+ tools: {},
18
+ },
19
+ });
20
+ function formatYunxiaoError(error) {
21
+ let message = `Yunxiao API Error: ${error.message}`;
22
+ if (error instanceof YunxiaoValidationError) {
23
+ message = `Parameter validation failed: ${error.message}`;
24
+ if (error.response) {
25
+ message += `\n errorMessage: ${JSON.stringify(error.response, null, 2)}`;
26
+ }
27
+ // 添加常见参数错误的提示
28
+ if (error.message.includes('name')) {
29
+ message += `\n Suggestion: Please check whether the pipeline name meets the requirements.`;
30
+ }
31
+ if (error.message.includes('content') || error.message.includes('yaml')) {
32
+ message += `\n Suggestion: Please check whether the generated YAML format is correct.`;
33
+ }
34
+ }
35
+ else {
36
+ // 处理通用的Yunxiao错误
37
+ message = `Yunxiao API error (${error.status}): ${error.message}`;
38
+ if (error.response) {
39
+ const response = error.response;
40
+ if (response.errorCode) {
41
+ message += `\n errorCode: ${response.errorCode}`;
42
+ }
43
+ if (response.errorMessage && response.errorMessage !== error.message) {
44
+ message += `\n errorMessage: ${response.errorMessage}`;
45
+ }
46
+ if (response.data && typeof response.data === 'object') {
47
+ message += `\n data: ${JSON.stringify(response.data, null, 2)}`;
48
+ }
49
+ }
50
+ // 根据状态码提供通用建议
51
+ switch (error.status) {
52
+ case 400:
53
+ message += `\n Suggestion: Please check whether the request parameters are correct, especially whether all required parameters have been provided.`;
54
+ break;
55
+ case 500:
56
+ message += `\n Suggestion: Internal server error. Please try again later or contact technical support.`;
57
+ break;
58
+ case 502:
59
+ case 503:
60
+ case 504:
61
+ message += `\n Suggestion: The service is temporarily unavailable. Please try again later.`;
62
+ break;
63
+ }
64
+ }
65
+ return message;
66
+ }
67
+ async function getRepositoryIdWithPat(pat) {
68
+ const query = `
69
+ SELECT
70
+ c.*,
71
+ ch.pat
72
+ FROM
73
+ schema_dev.codeup AS c
74
+ INNER JOIN
75
+ schema_dev.chat_history AS ch
76
+ ON
77
+ c.chat_id = ch.id
78
+ WHERE
79
+ ch.pat = '${pat}'
80
+ `;
81
+ try {
82
+ const response = await fetch('https://dev.fedin.cn/api/supabase/query', {
83
+ method: "post",
84
+ body: JSON.stringify({
85
+ query
86
+ }),
87
+ headers: {
88
+ 'Content-Type': 'application/json',
89
+ },
90
+ });
91
+ const data = await response.json();
92
+ if (data.length > 0) {
93
+ return data[0].repositoryId;
94
+ }
95
+ else {
96
+ throw new Error("pat密钥无效:未找到对应的repositoryId");
97
+ }
98
+ }
99
+ catch (error) {
100
+ throw new Error("获取repositoryId失败");
101
+ }
102
+ }
103
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
104
+ return {
105
+ tools: getAllTools(),
106
+ };
107
+ });
108
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
109
+ try {
110
+ if (!request.params.arguments) {
111
+ throw new Error("Arguments are required");
112
+ }
113
+ let pat = "";
114
+ process.argv.forEach(arg => {
115
+ if (arg.includes("--project-pat")) {
116
+ pat = arg.split("=")?.[1];
117
+ }
118
+ });
119
+ if (!pat) {
120
+ throw new Error("缺少项目访问令牌project-pat");
121
+ }
122
+ const repositoryId = await getRepositoryIdWithPat(pat);
123
+ if (!repositoryId) {
124
+ throw new Error("获取repositoryId失败");
125
+ }
126
+ request.params.arguments.repositoryId = repositoryId;
127
+ // Delegate to our modular tool handler
128
+ return await handleToolRequest(request);
129
+ }
130
+ catch (error) {
131
+ if (error instanceof z.ZodError) {
132
+ throw new Error(`Invalid input: ${JSON.stringify(error.errors)}`);
133
+ }
134
+ if (isYunxiaoError(error)) {
135
+ throw new Error(formatYunxiaoError(error));
136
+ }
137
+ throw error;
138
+ }
139
+ });
140
+ config();
141
+ // Check if we should run in SSE mode
142
+ const useSSE = process.argv.includes('--sse') || process.env.MCP_TRANSPORT === 'sse';
143
+ async function runServer() {
144
+ if (useSSE) {
145
+ // Import express only when needed for SSE mode
146
+ const { default: express } = await import('express');
147
+ const app = express();
148
+ const port = process.env.PORT || 3000;
149
+ // Store sessions
150
+ const sessions = {};
151
+ // SSE endpoint - handles initial connection
152
+ app.get('/sse', async (req, res) => {
153
+ // In SSE mode, we can use console.log for debugging since it doesn't interfere with the protocol
154
+ console.log(`New SSE connection from ${req.ip}`);
155
+ // Create transport with endpoint for POST messages
156
+ const sseTransport = new SSEServerTransport('/messages', res);
157
+ const sessionId = sseTransport.sessionId;
158
+ if (sessionId) {
159
+ sessions[sessionId] = { transport: sseTransport, server };
160
+ }
161
+ try {
162
+ await server.connect(sseTransport);
163
+ // In SSE mode, console.error is acceptable for status messages
164
+ console.error(`Yunxiao MCP Server connected via SSE with session ${sessionId}`);
165
+ }
166
+ catch (error) {
167
+ console.error("Failed to start SSE server:", error);
168
+ res.status(500).send("Server error");
169
+ }
170
+ });
171
+ // POST endpoint - handles incoming messages
172
+ app.use(express.json({ limit: '10mb' })); // Add JSON body parser
173
+ app.post('/messages', async (req, res) => {
174
+ const sessionId = req.query.sessionId;
175
+ const session = sessions[sessionId];
176
+ if (!session) {
177
+ res.status(404).send("Session not found");
178
+ return;
179
+ }
180
+ try {
181
+ await session.transport.handlePostMessage(req, res, req.body);
182
+ }
183
+ catch (error) {
184
+ console.error("Error handling POST message:", error);
185
+ res.status(500).send("Server error");
186
+ }
187
+ });
188
+ // Start server
189
+ const serverInstance = app.listen(port, () => {
190
+ console.log(`Yunxiao MCP Server running in SSE mode on port ${port}`);
191
+ console.log(`Connect via SSE at http://localhost:${port}/sse`);
192
+ console.log(`Send messages to http://localhost:${port}/messages?sessionId=<session-id>`);
193
+ });
194
+ // Handle graceful shutdown
195
+ process.on('SIGINT', () => {
196
+ console.log('Shutting down SSE server...');
197
+ serverInstance.close(() => {
198
+ console.log('Server closed.');
199
+ process.exit(0);
200
+ });
201
+ });
202
+ }
203
+ else {
204
+ // Stdio mode (default)
205
+ // In stdio mode, we must avoid console.log/console.error as they interfere with the JSON-RPC protocol
206
+ const transport = new StdioServerTransport();
207
+ await server.connect(transport);
208
+ // Don't output anything to stdout/stderr in stdio mode - only JSON-RPC messages should go through the transport
209
+ }
210
+ }
211
+ runServer().catch((error) => {
212
+ // Only output error to stderr in SSE mode, not in stdio mode
213
+ if (useSSE) {
214
+ console.error("Fatal error in main():", error);
215
+ }
216
+ process.exit(1);
217
+ });