tdecollab 0.1.2 → 0.2.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.
@@ -0,0 +1,964 @@
1
+ import {
2
+ ConfluenceContentApi,
3
+ ConfluenceSearchApi,
4
+ ConfluenceSpaceApi,
5
+ GitlabBranchApi,
6
+ GitlabMergeRequestApi,
7
+ GitlabPipelineApi,
8
+ GitlabProjectApi,
9
+ GitlabRepositoryApi,
10
+ JiraCommentApi,
11
+ JiraIssueApi,
12
+ JiraProjectApi,
13
+ JiraSearchApi,
14
+ JiraTransitionApi,
15
+ MarkdownToStorageConverter,
16
+ StorageToMarkdownConverter,
17
+ createConfluenceClient,
18
+ createGitlabClient,
19
+ createJiraClient,
20
+ loadConfluenceConfig,
21
+ loadGitlabConfig,
22
+ loadJiraConfig
23
+ } from "./chunk-2IQ4QMK3.js";
24
+ import {
25
+ logger
26
+ } from "./chunk-SJ7KPK6Q.js";
27
+
28
+ // src/mcp/server.ts
29
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
30
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
31
+
32
+ // src/confluence/tools/index.ts
33
+ import { z } from "zod";
34
+
35
+ // src/confluence/api/label.ts
36
+ var ConfluenceLabelApi = class {
37
+ constructor(client) {
38
+ this.client = client;
39
+ }
40
+ async getLabels(pageId) {
41
+ const response = await this.client.get(`/rest/api/content/${pageId}/label`);
42
+ return response.data.results;
43
+ }
44
+ async addLabels(pageId, labels) {
45
+ const data = labels.map((name) => ({ prefix: "global", name }));
46
+ await this.client.post(`/rest/api/content/${pageId}/label`, data);
47
+ }
48
+ async removeLabel(pageId, labelName) {
49
+ await this.client.delete(`/rest/api/content/${pageId}/label`, {
50
+ params: { name: labelName }
51
+ });
52
+ }
53
+ };
54
+
55
+ // src/confluence/tools/index.ts
56
+ function registerConfluenceTools(server) {
57
+ try {
58
+ const config = loadConfluenceConfig();
59
+ const client = createConfluenceClient(config);
60
+ const contentApi = new ConfluenceContentApi(client);
61
+ const spaceApi = new ConfluenceSpaceApi(client);
62
+ const searchApi = new ConfluenceSearchApi(client);
63
+ const labelApi = new ConfluenceLabelApi(client);
64
+ const mdToStorage = new MarkdownToStorageConverter();
65
+ const storageToMd = new StorageToMarkdownConverter();
66
+ server.tool(
67
+ "confluence_get_page",
68
+ "TDE Confluence \uD398\uC774\uC9C0 \uC0C1\uC138 \uC870\uD68C. \uD398\uC774\uC9C0 \uB0B4\uC6A9\uC744 Markdown\uC73C\uB85C \uBCC0\uD658\uD558\uC5EC \uBC18\uD658\uD569\uB2C8\uB2E4.",
69
+ {
70
+ pageId: z.string().describe("\uD398\uC774\uC9C0 ID"),
71
+ downloadImages: z.boolean().optional().describe("\uC774\uBBF8\uC9C0 \uB2E4\uC6B4\uB85C\uB4DC \uC5EC\uBD80"),
72
+ imageDir: z.string().optional().describe("\uC774\uBBF8\uC9C0 \uC800\uC7A5 \uB514\uB809\uD1A0\uB9AC (\uAE30\uBCF8\uAC12: ./images)")
73
+ },
74
+ async ({ pageId, downloadImages, imageDir }) => {
75
+ const page = await contentApi.getPage(pageId);
76
+ let md = "";
77
+ let imageInfo = "";
78
+ if (page.body?.storage?.value) {
79
+ let imageUrlMap;
80
+ if (downloadImages) {
81
+ const { ImageDownloader } = await import("./image-downloader-D57KFAIQ.js");
82
+ const downloader = new ImageDownloader(contentApi, {
83
+ outputDir: imageDir || "./images",
84
+ pageId: page.id,
85
+ baseUrl: config.baseUrl
86
+ });
87
+ imageUrlMap = await downloader.downloadAllImages(page.body.storage.value);
88
+ imageInfo = `
89
+
90
+ \uB2E4\uC6B4\uB85C\uB4DC\uB41C \uC774\uBBF8\uC9C0: ${imageUrlMap.size}\uAC1C`;
91
+ }
92
+ md = storageToMd.convert(page.body.storage.value, imageUrlMap);
93
+ }
94
+ return {
95
+ content: [
96
+ {
97
+ type: "text",
98
+ text: `Title: ${page.title}
99
+ ID: ${page.id}
100
+ Space: ${page.space?.name} (${page.space?.key})
101
+ URL: ${page._links?.base}${page._links?.webui}${imageInfo}
102
+
103
+ ${md}`
104
+ }
105
+ ]
106
+ };
107
+ }
108
+ );
109
+ server.tool(
110
+ "confluence_create_page",
111
+ "TDE Confluence\uC5D0 \uC0C8 \uD398\uC774\uC9C0\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4. Markdown \uD615\uC2DD\uC758 \uB0B4\uC6A9\uC744 Confluence Storage Format\uC73C\uB85C \uC790\uB3D9 \uBCC0\uD658\uD569\uB2C8\uB2E4.",
112
+ {
113
+ spaceKey: z.string().describe("\uC2A4\uD398\uC774\uC2A4 \uD0A4"),
114
+ title: z.string().describe("\uD398\uC774\uC9C0 \uC81C\uBAA9"),
115
+ content: z.string().describe("\uD398\uC774\uC9C0 \uB0B4\uC6A9 (Markdown)"),
116
+ parentId: z.string().optional().describe("\uBD80\uBAA8 \uD398\uC774\uC9C0 ID"),
117
+ labels: z.array(z.string()).optional().describe("\uB77C\uBCA8 \uBAA9\uB85D")
118
+ },
119
+ async ({ spaceKey, title, content, parentId, labels }) => {
120
+ const storageBody = mdToStorage.convert(content);
121
+ const page = await contentApi.createPage({
122
+ spaceKey,
123
+ title,
124
+ body: storageBody,
125
+ parentId,
126
+ labels
127
+ });
128
+ return {
129
+ content: [
130
+ {
131
+ type: "text",
132
+ text: `\uD398\uC774\uC9C0 \uC0DD\uC131 \uC131\uACF5: ${page.title} (ID: ${page.id})
133
+ URL: ${page._links?.base}${page._links?.webui}`
134
+ }
135
+ ]
136
+ };
137
+ }
138
+ );
139
+ server.tool(
140
+ "confluence_update_page",
141
+ "TDE Confluence \uD398\uC774\uC9C0\uB97C \uC218\uC815\uD569\uB2C8\uB2E4. Markdown \uD615\uC2DD\uC758 \uB0B4\uC6A9\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
142
+ {
143
+ pageId: z.string().describe("\uD398\uC774\uC9C0 ID"),
144
+ title: z.string().describe("\uD398\uC774\uC9C0 \uC81C\uBAA9"),
145
+ content: z.string().describe("\uD398\uC774\uC9C0 \uB0B4\uC6A9 (Markdown)"),
146
+ version: z.number().describe("\uD604\uC7AC \uD398\uC774\uC9C0 \uBC84\uC804 (\uCDA9\uB3CC \uBC29\uC9C0\uC6A9)")
147
+ },
148
+ async ({ pageId, title, content, version }) => {
149
+ const storageBody = mdToStorage.convert(content);
150
+ const page = await contentApi.updatePage({
151
+ id: pageId,
152
+ title,
153
+ body: storageBody,
154
+ version
155
+ });
156
+ return {
157
+ content: [
158
+ {
159
+ type: "text",
160
+ text: `\uD398\uC774\uC9C0 \uC218\uC815 \uC131\uACF5: ${page.title} (Version: ${page.version?.number})`
161
+ }
162
+ ]
163
+ };
164
+ }
165
+ );
166
+ server.tool(
167
+ "confluence_search_pages",
168
+ "TDE Confluence\uC5D0\uC11C \uD398\uC774\uC9C0\uB97C \uAC80\uC0C9\uD569\uB2C8\uB2E4. CQL(Confluence Query Language)\uC744 \uC0AC\uC6A9\uD558\uC5EC \uACE0\uAE09 \uAC80\uC0C9\uC774 \uAC00\uB2A5\uD569\uB2C8\uB2E4.",
169
+ {
170
+ cql: z.string().describe('Confluence Query Language (\uC608: title ~ "guide")'),
171
+ limit: z.number().default(10).describe("\uACB0\uACFC \uAC1C\uC218 \uC81C\uD55C")
172
+ },
173
+ async ({ cql, limit }) => {
174
+ const result = await searchApi.searchByCql(cql, 0, limit);
175
+ const summary = result.results.map((p) => `- [${p.id}] ${p.title} (Space: ${p.space?.key})`).join("\n");
176
+ return {
177
+ content: [
178
+ {
179
+ type: "text",
180
+ text: `\uAC80\uC0C9 \uACB0\uACFC (${result.size}/${result.totalSize}):
181
+ ${summary}`
182
+ }
183
+ ]
184
+ };
185
+ }
186
+ );
187
+ server.tool(
188
+ "confluence_get_spaces",
189
+ "TDE Confluence\uC758 \uC2A4\uD398\uC774\uC2A4 \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4.",
190
+ {
191
+ limit: z.number().default(20).describe("\uACB0\uACFC \uAC1C\uC218 \uC81C\uD55C")
192
+ },
193
+ async ({ limit }) => {
194
+ const spaces = await spaceApi.getSpaces("global", 0, limit);
195
+ const summary = spaces.map((s) => `- [${s.key}] ${s.name}`).join("\n");
196
+ return {
197
+ content: [{ type: "text", text: `\uC2A4\uD398\uC774\uC2A4 \uBAA9\uB85D:
198
+ ${summary}` }]
199
+ };
200
+ }
201
+ );
202
+ server.tool(
203
+ "confluence_delete_page",
204
+ "TDE Confluence \uD398\uC774\uC9C0\uB97C \uC0AD\uC81C\uD569\uB2C8\uB2E4.",
205
+ {
206
+ pageId: z.string().describe("\uD398\uC774\uC9C0 ID")
207
+ },
208
+ async ({ pageId }) => {
209
+ await contentApi.deletePage(pageId);
210
+ return {
211
+ content: [
212
+ {
213
+ type: "text",
214
+ text: `\uD398\uC774\uC9C0 \uC0AD\uC81C \uC131\uACF5 (ID: ${pageId})`
215
+ }
216
+ ]
217
+ };
218
+ }
219
+ );
220
+ server.tool(
221
+ "confluence_get_page_tree",
222
+ "TDE Confluence \uD398\uC774\uC9C0\uC758 \uD558\uC704 \uD398\uC774\uC9C0(\uC790\uC2DD \uD398\uC774\uC9C0) \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4.",
223
+ {
224
+ pageId: z.string().describe("\uD398\uC774\uC9C0 ID"),
225
+ limit: z.number().default(20).describe("\uACB0\uACFC \uAC1C\uC218 \uC81C\uD55C")
226
+ },
227
+ async ({ pageId, limit }) => {
228
+ const children = await contentApi.getChildPages(pageId, 0, limit);
229
+ const summary = children.map((p) => `- [${p.id}] ${p.title}`).join("\n");
230
+ return {
231
+ content: [
232
+ {
233
+ type: "text",
234
+ text: `\uC790\uC2DD \uD398\uC774\uC9C0 \uBAA9\uB85D (${children.length}):
235
+ ${summary}`
236
+ }
237
+ ]
238
+ };
239
+ }
240
+ );
241
+ server.tool(
242
+ "confluence_manage_labels",
243
+ "TDE Confluence \uD398\uC774\uC9C0\uC758 \uB77C\uBCA8\uC744 \uAD00\uB9AC\uD569\uB2C8\uB2E4. \uB77C\uBCA8 \uC870\uD68C, \uCD94\uAC00, \uC0AD\uC81C \uAE30\uB2A5\uC744 \uC81C\uACF5\uD569\uB2C8\uB2E4.",
244
+ {
245
+ pageId: z.string().describe("\uD398\uC774\uC9C0 ID"),
246
+ action: z.enum(["list", "add", "remove"]).describe("\uC791\uC5C5 \uC720\uD615"),
247
+ labels: z.array(z.string()).optional().describe("\uCD94\uAC00\uD560 \uB77C\uBCA8 \uBAA9\uB85D (add \uC791\uC5C5 \uC2DC \uD544\uC218)"),
248
+ label: z.string().optional().describe("\uC0AD\uC81C\uD560 \uB77C\uBCA8 (remove \uC791\uC5C5 \uC2DC \uD544\uC218)")
249
+ },
250
+ async ({ pageId, action, labels, label }) => {
251
+ if (action === "list") {
252
+ const result = await labelApi.getLabels(pageId);
253
+ const summary = result.map((l) => l.name).join(", ");
254
+ return {
255
+ content: [{ type: "text", text: `\uB77C\uBCA8 \uBAA9\uB85D: ${summary}` }]
256
+ };
257
+ } else if (action === "add") {
258
+ if (!labels || labels.length === 0) {
259
+ throw new Error("\uB77C\uBCA8 \uBAA9\uB85D\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694.");
260
+ }
261
+ await labelApi.addLabels(pageId, labels);
262
+ return {
263
+ content: [{ type: "text", text: `\uB77C\uBCA8 \uCD94\uAC00 \uC131\uACF5: ${labels.join(", ")}` }]
264
+ };
265
+ } else if (action === "remove") {
266
+ if (!label) {
267
+ throw new Error("\uC0AD\uC81C\uD560 \uB77C\uBCA8\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694.");
268
+ }
269
+ await labelApi.removeLabel(pageId, label);
270
+ return {
271
+ content: [{ type: "text", text: `\uB77C\uBCA8 \uC0AD\uC81C \uC131\uACF5: ${label}` }]
272
+ };
273
+ }
274
+ return { content: [] };
275
+ }
276
+ );
277
+ server.tool(
278
+ "confluence_convert_content",
279
+ "TDE Confluence \uCEE8\uD150\uCE20 \uD3EC\uB9F7\uC744 \uBCC0\uD658\uD569\uB2C8\uB2E4. Markdown\uACFC Confluence Storage Format \uAC04 \uC591\uBC29\uD5A5 \uBCC0\uD658\uC744 \uC9C0\uC6D0\uD569\uB2C8\uB2E4.",
280
+ {
281
+ content: z.string().describe("\uBCC0\uD658\uD560 \uCEE8\uD150\uCE20"),
282
+ format: z.enum(["storage_to_markdown", "markdown_to_storage"]).describe("\uBCC0\uD658 \uBC29\uD5A5")
283
+ },
284
+ async ({ content, format }) => {
285
+ let result = "";
286
+ if (format === "storage_to_markdown") {
287
+ result = storageToMd.convert(content);
288
+ } else {
289
+ result = mdToStorage.convert(content);
290
+ }
291
+ return {
292
+ content: [{ type: "text", text: result }]
293
+ };
294
+ }
295
+ );
296
+ } catch (error) {
297
+ logger.warn(`Confluence \uC124\uC815 \uB85C\uB4DC \uC2E4\uD328 \uB610\uB294 \uB3C4\uAD6C \uB4F1\uB85D \uC911 \uC624\uB958 \uBC1C\uC0DD: ${error.message}`);
298
+ }
299
+ }
300
+
301
+ // src/jira/tools/index.ts
302
+ import { z as z2 } from "zod";
303
+ function registerJiraTools(server) {
304
+ try {
305
+ const config = loadJiraConfig();
306
+ const client = createJiraClient(config);
307
+ const issueApi = new JiraIssueApi(client);
308
+ const searchApi = new JiraSearchApi(client);
309
+ const transitionApi = new JiraTransitionApi(client);
310
+ const commentApi = new JiraCommentApi(client);
311
+ const projectApi = new JiraProjectApi(client);
312
+ server.tool(
313
+ "jira_get_issue",
314
+ "TDE JIRA \uC774\uC288\uB97C \uC0C1\uC138 \uC870\uD68C\uD569\uB2C8\uB2E4. \uC774\uC288 \uD0A4(\uC608: PROJ-123)\uB85C \uC870\uD68C\uD558\uBA70, \uD544\uB4DC \uBAA9\uB85D\uC744 \uC9C0\uC815\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
315
+ {
316
+ issueKey: z2.string().describe("\uC774\uC288 \uD0A4 (\uC608: PROJ-123)"),
317
+ fields: z2.array(z2.string()).optional().describe("\uC870\uD68C\uD560 \uD544\uB4DC \uBAA9\uB85D (\uBBF8\uC9C0\uC815 \uC2DC \uC804\uCCB4)")
318
+ },
319
+ async ({ issueKey, fields }) => {
320
+ const issue = await issueApi.getIssue(issueKey, fields);
321
+ const f = issue.fields;
322
+ const lines = [
323
+ `Key: ${issue.key}`,
324
+ `Summary: ${f.summary}`,
325
+ `Status: ${f.status?.name || "N/A"}`,
326
+ `Type: ${f.issuetype?.name || "N/A"}`,
327
+ `Priority: ${f.priority?.name || "N/A"}`,
328
+ `Assignee: ${f.assignee?.displayName || "\uBBF8\uBC30\uC815"}`,
329
+ `Reporter: ${f.reporter?.displayName || "N/A"}`,
330
+ `Labels: ${f.labels?.join(", ") || "\uC5C6\uC74C"}`,
331
+ `Created: ${f.created || "N/A"}`,
332
+ `Updated: ${f.updated || "N/A"}`
333
+ ];
334
+ if (f.parent) {
335
+ lines.push(`Parent: ${f.parent.key} - ${f.parent.fields?.summary || ""}`);
336
+ }
337
+ if (f.description) {
338
+ lines.push("", "--- Description ---", f.description);
339
+ }
340
+ if (f.subtasks && f.subtasks.length > 0) {
341
+ lines.push(
342
+ "",
343
+ "--- Subtasks ---",
344
+ ...f.subtasks.map(
345
+ (st) => `- [${st.key}] ${st.fields.summary} (${st.fields.status.name})`
346
+ )
347
+ );
348
+ }
349
+ return {
350
+ content: [{ type: "text", text: lines.join("\n") }]
351
+ };
352
+ }
353
+ );
354
+ server.tool(
355
+ "jira_create_issue",
356
+ "TDE JIRA\uC5D0 \uC0C8 \uC774\uC288\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4.",
357
+ {
358
+ projectKey: z2.string().describe("\uD504\uB85C\uC81D\uD2B8 \uD0A4 (\uC608: PROJ)"),
359
+ summary: z2.string().describe("\uC774\uC288 \uC81C\uBAA9"),
360
+ issueType: z2.string().describe("\uC774\uC288 \uC720\uD615 (\uC608: Task, Bug, Story, Sub-task)"),
361
+ description: z2.string().optional().describe("\uC774\uC288 \uC124\uBA85"),
362
+ assignee: z2.string().optional().describe("\uB2F4\uB2F9\uC790 \uC0AC\uC6A9\uC790 \uC774\uB984"),
363
+ priority: z2.string().optional().describe("\uC6B0\uC120\uC21C\uC704 (\uC608: High, Medium, Low)"),
364
+ labels: z2.array(z2.string()).optional().describe("\uB77C\uBCA8 \uBAA9\uB85D"),
365
+ components: z2.array(z2.string()).optional().describe("\uCEF4\uD3EC\uB10C\uD2B8 \uC774\uB984 \uBAA9\uB85D"),
366
+ parentKey: z2.string().optional().describe("\uC0C1\uC704 \uC774\uC288 \uD0A4 (Sub-task \uC0DD\uC131 \uC2DC)")
367
+ },
368
+ async ({ projectKey, summary, issueType, description, assignee, priority, labels, components, parentKey }) => {
369
+ const issue = await issueApi.createIssue({
370
+ projectKey,
371
+ summary,
372
+ issueType,
373
+ description,
374
+ assignee,
375
+ priority,
376
+ labels,
377
+ components,
378
+ parentKey
379
+ });
380
+ return {
381
+ content: [
382
+ {
383
+ type: "text",
384
+ text: `\uC774\uC288 \uC0DD\uC131 \uC131\uACF5: ${issue.key}
385
+ URL: ${config.baseUrl}/browse/${issue.key}`
386
+ }
387
+ ]
388
+ };
389
+ }
390
+ );
391
+ server.tool(
392
+ "jira_update_issue",
393
+ "TDE JIRA \uC774\uC288\uB97C \uC218\uC815\uD569\uB2C8\uB2E4.",
394
+ {
395
+ issueKey: z2.string().describe("\uC774\uC288 \uD0A4"),
396
+ summary: z2.string().optional().describe("\uC774\uC288 \uC81C\uBAA9"),
397
+ description: z2.string().optional().describe("\uC774\uC288 \uC124\uBA85"),
398
+ assignee: z2.string().optional().describe("\uB2F4\uB2F9\uC790 (\uBE48 \uBB38\uC790\uC5F4\uB85C \uBC30\uC815 \uD574\uC81C)"),
399
+ priority: z2.string().optional().describe("\uC6B0\uC120\uC21C\uC704"),
400
+ labels: z2.array(z2.string()).optional().describe("\uB77C\uBCA8 \uBAA9\uB85D (\uC804\uCCB4 \uAD50\uCCB4)"),
401
+ components: z2.array(z2.string()).optional().describe("\uCEF4\uD3EC\uB10C\uD2B8 \uBAA9\uB85D (\uC804\uCCB4 \uAD50\uCCB4)")
402
+ },
403
+ async ({ issueKey, summary, description, assignee, priority, labels, components }) => {
404
+ await issueApi.updateIssue(issueKey, {
405
+ summary,
406
+ description,
407
+ assignee,
408
+ priority,
409
+ labels,
410
+ components
411
+ });
412
+ return {
413
+ content: [{ type: "text", text: `\uC774\uC288 \uC218\uC815 \uC131\uACF5: ${issueKey}` }]
414
+ };
415
+ }
416
+ );
417
+ server.tool(
418
+ "jira_search_issues",
419
+ "TDE JIRA\uC5D0\uC11C JQL(JIRA Query Language)\uB85C \uC774\uC288\uB97C \uAC80\uC0C9\uD569\uB2C8\uB2E4.",
420
+ {
421
+ jql: z2.string().describe("JQL \uCFFC\uB9AC (\uC608: project = PROJ AND status = Open)"),
422
+ maxResults: z2.number().default(20).describe("\uCD5C\uB300 \uACB0\uACFC \uC218"),
423
+ fields: z2.array(z2.string()).optional().describe("\uC870\uD68C\uD560 \uD544\uB4DC \uBAA9\uB85D")
424
+ },
425
+ async ({ jql, maxResults, fields }) => {
426
+ const result = await searchApi.searchByJql(jql, 0, maxResults, fields);
427
+ const summary = result.issues.map(
428
+ (i) => `- [${i.key}] ${i.fields.summary} (${i.fields.status?.name || "N/A"}, ${i.fields.assignee?.displayName || "\uBBF8\uBC30\uC815"})`
429
+ ).join("\n");
430
+ return {
431
+ content: [
432
+ {
433
+ type: "text",
434
+ text: `\uAC80\uC0C9 \uACB0\uACFC (${result.issues.length}/${result.total}):
435
+ ${summary}`
436
+ }
437
+ ]
438
+ };
439
+ }
440
+ );
441
+ server.tool(
442
+ "jira_transition_issue",
443
+ "TDE JIRA \uC774\uC288\uC758 \uC0C1\uD0DC\uB97C \uBCC0\uACBD\uD569\uB2C8\uB2E4. \uAC00\uB2A5\uD55C \uD2B8\uB79C\uC9C0\uC158 \uBAA9\uB85D \uC870\uD68C \uB610\uB294 \uD2B8\uB79C\uC9C0\uC158 \uC2E4\uD589\uC744 \uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
444
+ {
445
+ issueKey: z2.string().describe("\uC774\uC288 \uD0A4"),
446
+ action: z2.enum(["list", "do"]).describe("\uC791\uC5C5 \uC720\uD615: list(\uAC00\uB2A5\uD55C \uD2B8\uB79C\uC9C0\uC158 \uC870\uD68C), do(\uD2B8\uB79C\uC9C0\uC158 \uC2E4\uD589)"),
447
+ transitionId: z2.string().optional().describe("\uD2B8\uB79C\uC9C0\uC158 ID (do \uC791\uC5C5 \uC2DC \uD544\uC218)")
448
+ },
449
+ async ({ issueKey, action, transitionId }) => {
450
+ if (action === "list") {
451
+ const transitions = await transitionApi.getTransitions(issueKey);
452
+ const summary = transitions.map((t) => `- [${t.id}] ${t.name} \u2192 ${t.to.name}`).join("\n");
453
+ return {
454
+ content: [
455
+ { type: "text", text: `\uAC00\uB2A5\uD55C \uD2B8\uB79C\uC9C0\uC158:
456
+ ${summary}` }
457
+ ]
458
+ };
459
+ } else {
460
+ if (!transitionId) {
461
+ throw new Error("\uD2B8\uB79C\uC9C0\uC158 ID\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694.");
462
+ }
463
+ await transitionApi.doTransition(issueKey, transitionId);
464
+ return {
465
+ content: [
466
+ { type: "text", text: `\uD2B8\uB79C\uC9C0\uC158 \uC644\uB8CC: ${issueKey}` }
467
+ ]
468
+ };
469
+ }
470
+ }
471
+ );
472
+ server.tool(
473
+ "jira_manage_comments",
474
+ "TDE JIRA \uC774\uC288\uC758 \uCF54\uBA58\uD2B8\uB97C \uAD00\uB9AC\uD569\uB2C8\uB2E4. \uC870\uD68C, \uCD94\uAC00, \uC218\uC815, \uC0AD\uC81C \uAE30\uB2A5\uC744 \uC81C\uACF5\uD569\uB2C8\uB2E4.",
475
+ {
476
+ issueKey: z2.string().describe("\uC774\uC288 \uD0A4"),
477
+ action: z2.enum(["list", "add", "update", "delete"]).describe("\uC791\uC5C5 \uC720\uD615"),
478
+ body: z2.string().optional().describe("\uCF54\uBA58\uD2B8 \uB0B4\uC6A9 (add, update \uC2DC \uD544\uC218)"),
479
+ commentId: z2.string().optional().describe("\uCF54\uBA58\uD2B8 ID (update, delete \uC2DC \uD544\uC218)")
480
+ },
481
+ async ({ issueKey, action, body, commentId }) => {
482
+ if (action === "list") {
483
+ const result = await commentApi.getComments(issueKey);
484
+ const summary = result.comments.map(
485
+ (c) => `- [${c.id}] ${c.author.displayName} (${c.created}):
486
+ ${c.body.substring(0, 200)}`
487
+ ).join("\n");
488
+ return {
489
+ content: [
490
+ {
491
+ type: "text",
492
+ text: `\uCF54\uBA58\uD2B8 \uBAA9\uB85D (${result.comments.length}/${result.total}):
493
+ ${summary}`
494
+ }
495
+ ]
496
+ };
497
+ } else if (action === "add") {
498
+ if (!body) throw new Error("\uCF54\uBA58\uD2B8 \uB0B4\uC6A9\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694.");
499
+ const comment = await commentApi.addComment(issueKey, body);
500
+ return {
501
+ content: [
502
+ { type: "text", text: `\uCF54\uBA58\uD2B8 \uCD94\uAC00 \uC131\uACF5 (ID: ${comment.id})` }
503
+ ]
504
+ };
505
+ } else if (action === "update") {
506
+ if (!commentId) throw new Error("\uCF54\uBA58\uD2B8 ID\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694.");
507
+ if (!body) throw new Error("\uCF54\uBA58\uD2B8 \uB0B4\uC6A9\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694.");
508
+ await commentApi.updateComment(issueKey, commentId, body);
509
+ return {
510
+ content: [{ type: "text", text: `\uCF54\uBA58\uD2B8 \uC218\uC815 \uC131\uACF5 (ID: ${commentId})` }]
511
+ };
512
+ } else {
513
+ if (!commentId) throw new Error("\uCF54\uBA58\uD2B8 ID\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694.");
514
+ await commentApi.deleteComment(issueKey, commentId);
515
+ return {
516
+ content: [{ type: "text", text: `\uCF54\uBA58\uD2B8 \uC0AD\uC81C \uC131\uACF5 (ID: ${commentId})` }]
517
+ };
518
+ }
519
+ }
520
+ );
521
+ server.tool(
522
+ "jira_get_projects",
523
+ "TDE JIRA \uD504\uB85C\uC81D\uD2B8 \uBAA9\uB85D \uB610\uB294 Agile \uBCF4\uB4DC/\uC2A4\uD504\uB9B0\uD2B8\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4.",
524
+ {
525
+ action: z2.enum(["projects", "project", "boards", "sprints"]).describe("\uC870\uD68C \uC720\uD615"),
526
+ projectKey: z2.string().optional().describe("\uD504\uB85C\uC81D\uD2B8 \uD0A4 (project, boards \uC870\uD68C \uC2DC)"),
527
+ boardId: z2.number().optional().describe("\uBCF4\uB4DC ID (sprints \uC870\uD68C \uC2DC \uD544\uC218)"),
528
+ sprintState: z2.string().optional().describe("\uC2A4\uD504\uB9B0\uD2B8 \uC0C1\uD0DC \uD544\uD130 (active, closed, future)")
529
+ },
530
+ async ({ action, projectKey, boardId, sprintState }) => {
531
+ if (action === "projects") {
532
+ const projects = await projectApi.getProjects();
533
+ const summary = projects.map((p) => `- [${p.key}] ${p.name} (Lead: ${p.lead?.displayName || "N/A"})`).join("\n");
534
+ return {
535
+ content: [{ type: "text", text: `\uD504\uB85C\uC81D\uD2B8 \uBAA9\uB85D:
536
+ ${summary}` }]
537
+ };
538
+ } else if (action === "project") {
539
+ if (!projectKey) throw new Error("\uD504\uB85C\uC81D\uD2B8 \uD0A4\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694.");
540
+ const project = await projectApi.getProject(projectKey);
541
+ const issueTypes = project.issueTypes?.map((t) => t.name).join(", ");
542
+ return {
543
+ content: [
544
+ {
545
+ type: "text",
546
+ text: `\uD504\uB85C\uC81D\uD2B8: ${project.name} (${project.key})
547
+ Lead: ${project.lead?.displayName || "N/A"}
548
+ Issue Types: ${issueTypes || "N/A"}`
549
+ }
550
+ ]
551
+ };
552
+ } else if (action === "boards") {
553
+ const result = await projectApi.getBoards(projectKey);
554
+ const summary = result.values.map((b) => `- [${b.id}] ${b.name} (${b.type})`).join("\n");
555
+ return {
556
+ content: [{ type: "text", text: `\uBCF4\uB4DC \uBAA9\uB85D:
557
+ ${summary}` }]
558
+ };
559
+ } else {
560
+ if (!boardId) throw new Error("\uBCF4\uB4DC ID\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694.");
561
+ const result = await projectApi.getSprints(boardId, sprintState);
562
+ const summary = result.values.map(
563
+ (s) => `- [${s.id}] ${s.name} (${s.state})${s.goal ? " - " + s.goal : ""}`
564
+ ).join("\n");
565
+ return {
566
+ content: [{ type: "text", text: `\uC2A4\uD504\uB9B0\uD2B8 \uBAA9\uB85D:
567
+ ${summary}` }]
568
+ };
569
+ }
570
+ }
571
+ );
572
+ } catch (error) {
573
+ logger.warn(
574
+ `JIRA \uC124\uC815 \uB85C\uB4DC \uC2E4\uD328 \uB610\uB294 \uB3C4\uAD6C \uB4F1\uB85D \uC911 \uC624\uB958 \uBC1C\uC0DD: ${error.message}`
575
+ );
576
+ }
577
+ }
578
+
579
+ // src/gitlab/tools/index.ts
580
+ import { z as z3 } from "zod";
581
+ function registerGitlabTools(server) {
582
+ try {
583
+ const config = loadGitlabConfig();
584
+ const client = createGitlabClient(config);
585
+ const projectApi = new GitlabProjectApi(client);
586
+ const mrApi = new GitlabMergeRequestApi(client);
587
+ const pipelineApi = new GitlabPipelineApi(client);
588
+ const branchApi = new GitlabBranchApi(client);
589
+ const repoApi = new GitlabRepositoryApi(client);
590
+ server.tool(
591
+ "gitlab_get_project",
592
+ "TDE GitLab \uD504\uB85C\uC81D\uD2B8\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. projectId \uC9C0\uC815 \uC2DC \uC0C1\uC138 \uC870\uD68C, \uBBF8\uC9C0\uC815 \uC2DC \uBAA9\uB85D \uC870\uD68C\uD569\uB2C8\uB2E4.",
593
+ {
594
+ projectId: z3.number().optional().describe("\uD504\uB85C\uC81D\uD2B8 ID (\uC9C0\uC815 \uC2DC \uC0C1\uC138 \uC870\uD68C)"),
595
+ search: z3.string().optional().describe("\uD504\uB85C\uC81D\uD2B8\uBA85 \uAC80\uC0C9 (\uBAA9\uB85D \uC870\uD68C \uC2DC)"),
596
+ owned: z3.boolean().optional().describe("\uC18C\uC720 \uD504\uB85C\uC81D\uD2B8\uB9CC \uD544\uD130"),
597
+ membership: z3.boolean().optional().describe("\uBA64\uBC84\uC2ED \uD504\uB85C\uC81D\uD2B8\uB9CC \uD544\uD130")
598
+ },
599
+ async ({ projectId, search, owned, membership }) => {
600
+ if (projectId) {
601
+ const project = await projectApi.getProject(projectId);
602
+ const lines = [
603
+ `ID: ${project.id}`,
604
+ `Name: ${project.name_with_namespace}`,
605
+ `Path: ${project.path_with_namespace}`,
606
+ `Default Branch: ${project.default_branch}`,
607
+ `Visibility: ${project.visibility}`,
608
+ `Web URL: ${project.web_url}`,
609
+ `SSH URL: ${project.ssh_url_to_repo}`,
610
+ `HTTP URL: ${project.http_url_to_repo}`,
611
+ `Last Activity: ${project.last_activity_at}`
612
+ ];
613
+ if (project.description) {
614
+ lines.push(`Description: ${project.description}`);
615
+ }
616
+ return { content: [{ type: "text", text: lines.join("\n") }] };
617
+ } else {
618
+ const projects = await projectApi.getProjects({
619
+ search,
620
+ owned,
621
+ membership
622
+ });
623
+ const summary = projects.map(
624
+ (p) => `- [${p.id}] ${p.name_with_namespace} (${p.visibility}) - ${p.web_url}`
625
+ ).join("\n");
626
+ return {
627
+ content: [
628
+ {
629
+ type: "text",
630
+ text: `\uD504\uB85C\uC81D\uD2B8 \uBAA9\uB85D (${projects.length}\uAC74):
631
+ ${summary}`
632
+ }
633
+ ]
634
+ };
635
+ }
636
+ }
637
+ );
638
+ server.tool(
639
+ "gitlab_get_merge_request",
640
+ "TDE GitLab Merge Request\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. mrIid \uC9C0\uC815 \uC2DC \uC0C1\uC138 \uC870\uD68C, \uBBF8\uC9C0\uC815 \uC2DC \uBAA9\uB85D \uC870\uD68C\uD569\uB2C8\uB2E4.",
641
+ {
642
+ projectId: z3.number().describe("\uD504\uB85C\uC81D\uD2B8 ID"),
643
+ mrIid: z3.number().optional().describe("MR IID (\uC9C0\uC815 \uC2DC \uC0C1\uC138 \uC870\uD68C)"),
644
+ state: z3.enum(["opened", "closed", "merged", "all"]).optional().describe("\uC0C1\uD0DC \uD544\uD130 (\uBAA9\uB85D \uC870\uD68C \uC2DC)"),
645
+ includeChanges: z3.boolean().optional().describe("\uBCC0\uACBD \uD30C\uC77C \uD3EC\uD568 \uC5EC\uBD80 (\uC0C1\uC138 \uC870\uD68C \uC2DC)")
646
+ },
647
+ async ({ projectId, mrIid, state, includeChanges }) => {
648
+ if (mrIid) {
649
+ const mr = includeChanges ? await mrApi.getMergeRequestChanges(projectId, mrIid) : await mrApi.getMergeRequest(projectId, mrIid);
650
+ const lines = [
651
+ `IID: !${mr.iid}`,
652
+ `Title: ${mr.title}`,
653
+ `State: ${mr.state}`,
654
+ `Source: ${mr.source_branch} \u2192 Target: ${mr.target_branch}`,
655
+ `Author: ${mr.author?.name || "N/A"}`,
656
+ `Assignee: ${mr.assignee?.name || "\uBBF8\uBC30\uC815"}`,
657
+ `Merge Status: ${mr.merge_status}`,
658
+ `Has Conflicts: ${mr.has_conflicts}`,
659
+ `Pipeline: ${mr.pipeline?.status || "N/A"}`,
660
+ `Web URL: ${mr.web_url}`,
661
+ `Created: ${mr.created_at}`,
662
+ `Updated: ${mr.updated_at}`
663
+ ];
664
+ if (mr.description) {
665
+ lines.push("", "--- Description ---", mr.description);
666
+ }
667
+ if (mr.changes && mr.changes.length > 0) {
668
+ lines.push(
669
+ "",
670
+ `--- Changes (${mr.changes.length}\uAC1C \uD30C\uC77C) ---`,
671
+ ...mr.changes.map(
672
+ (c) => `- ${c.new_file ? "[NEW] " : c.deleted_file ? "[DEL] " : c.renamed_file ? "[REN] " : ""}${c.new_path}`
673
+ )
674
+ );
675
+ }
676
+ return { content: [{ type: "text", text: lines.join("\n") }] };
677
+ } else {
678
+ const mrs = await mrApi.getMergeRequests(projectId, { state });
679
+ const summary = mrs.map(
680
+ (m) => `- [!${m.iid}] ${m.title} (${m.state}, ${m.source_branch} \u2192 ${m.target_branch}) by ${m.author?.name || "N/A"}`
681
+ ).join("\n");
682
+ return {
683
+ content: [
684
+ {
685
+ type: "text",
686
+ text: `MR \uBAA9\uB85D (${mrs.length}\uAC74):
687
+ ${summary}`
688
+ }
689
+ ]
690
+ };
691
+ }
692
+ }
693
+ );
694
+ server.tool(
695
+ "gitlab_create_merge_request",
696
+ "TDE GitLab\uC5D0 \uC0C8 Merge Request\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4.",
697
+ {
698
+ projectId: z3.number().describe("\uD504\uB85C\uC81D\uD2B8 ID"),
699
+ sourceBranch: z3.string().describe("\uC18C\uC2A4 \uBE0C\uB79C\uCE58"),
700
+ targetBranch: z3.string().describe("\uD0C0\uAC9F \uBE0C\uB79C\uCE58"),
701
+ title: z3.string().describe("MR \uC81C\uBAA9"),
702
+ description: z3.string().optional().describe("MR \uC124\uBA85")
703
+ },
704
+ async ({ projectId, sourceBranch, targetBranch, title, description }) => {
705
+ const mr = await mrApi.createMergeRequest(projectId, {
706
+ source_branch: sourceBranch,
707
+ target_branch: targetBranch,
708
+ title,
709
+ description
710
+ });
711
+ return {
712
+ content: [
713
+ {
714
+ type: "text",
715
+ text: `MR \uC0DD\uC131 \uC131\uACF5: !${mr.iid}
716
+ Title: ${mr.title}
717
+ URL: ${mr.web_url}`
718
+ }
719
+ ]
720
+ };
721
+ }
722
+ );
723
+ server.tool(
724
+ "gitlab_manage_merge_request",
725
+ "TDE GitLab MR\uB97C \uAD00\uB9AC\uD569\uB2C8\uB2E4. \uBA38\uC9C0, \uB2EB\uAE30, \uC7AC\uC5F4\uAE30, \uCF54\uBA58\uD2B8 \uCD94\uAC00 \uC791\uC5C5\uC744 \uC218\uD589\uD569\uB2C8\uB2E4.",
726
+ {
727
+ projectId: z3.number().describe("\uD504\uB85C\uC81D\uD2B8 ID"),
728
+ mrIid: z3.number().describe("MR IID"),
729
+ action: z3.enum(["merge", "close", "reopen", "comment"]).describe("\uC791\uC5C5 \uC720\uD615"),
730
+ comment: z3.string().optional().describe("\uCF54\uBA58\uD2B8 \uB0B4\uC6A9 (action=comment \uC2DC \uD544\uC218)"),
731
+ squash: z3.boolean().optional().describe("\uC2A4\uCFFC\uC2DC \uBA38\uC9C0 (action=merge \uC2DC)"),
732
+ removeSourceBranch: z3.boolean().optional().describe("\uC18C\uC2A4 \uBE0C\uB79C\uCE58 \uC0AD\uC81C (action=merge \uC2DC)")
733
+ },
734
+ async ({ projectId, mrIid, action, comment, squash, removeSourceBranch }) => {
735
+ if (action === "merge") {
736
+ const mr = await mrApi.mergeMergeRequest(projectId, mrIid, {
737
+ squash,
738
+ should_remove_source_branch: removeSourceBranch
739
+ });
740
+ return {
741
+ content: [
742
+ {
743
+ type: "text",
744
+ text: `MR !${mrIid} \uBA38\uC9C0 \uC131\uACF5
745
+ State: ${mr.state}`
746
+ }
747
+ ]
748
+ };
749
+ } else if (action === "close" || action === "reopen") {
750
+ const mr = await mrApi.updateMergeRequest(projectId, mrIid, {
751
+ state_event: action
752
+ });
753
+ const msg = action === "close" ? "\uB2EB\uAE30" : "\uC7AC\uC5F4\uAE30";
754
+ return {
755
+ content: [
756
+ {
757
+ type: "text",
758
+ text: `MR !${mrIid} ${msg} \uC131\uACF5
759
+ State: ${mr.state}`
760
+ }
761
+ ]
762
+ };
763
+ } else {
764
+ if (!comment) throw new Error("\uCF54\uBA58\uD2B8 \uB0B4\uC6A9\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694.");
765
+ const note = await mrApi.addMergeRequestNote(projectId, mrIid, comment);
766
+ return {
767
+ content: [
768
+ {
769
+ type: "text",
770
+ text: `MR !${mrIid}\uC5D0 \uCF54\uBA58\uD2B8 \uCD94\uAC00 \uC131\uACF5 (ID: ${note.id})`
771
+ }
772
+ ]
773
+ };
774
+ }
775
+ }
776
+ );
777
+ server.tool(
778
+ "gitlab_get_pipelines",
779
+ "TDE GitLab CI/CD \uD30C\uC774\uD504\uB77C\uC778\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. pipelineId \uC9C0\uC815 \uC2DC \uC0C1\uC138 \uC870\uD68C, \uBBF8\uC9C0\uC815 \uC2DC \uBAA9\uB85D \uC870\uD68C\uD569\uB2C8\uB2E4.",
780
+ {
781
+ projectId: z3.number().describe("\uD504\uB85C\uC81D\uD2B8 ID"),
782
+ pipelineId: z3.number().optional().describe("\uD30C\uC774\uD504\uB77C\uC778 ID (\uC9C0\uC815 \uC2DC \uC0C1\uC138 \uC870\uD68C)"),
783
+ status: z3.string().optional().describe("\uC0C1\uD0DC \uD544\uD130 (running, success, failed \uB4F1)"),
784
+ ref: z3.string().optional().describe("\uBE0C\uB79C\uCE58/\uD0DC\uADF8 \uD544\uD130"),
785
+ includeJobs: z3.boolean().optional().describe("\uC791\uC5C5(Job) \uBAA9\uB85D \uD3EC\uD568 \uC5EC\uBD80")
786
+ },
787
+ async ({ projectId, pipelineId, status, ref, includeJobs }) => {
788
+ if (pipelineId) {
789
+ const pipeline = await pipelineApi.getPipeline(projectId, pipelineId);
790
+ const lines = [
791
+ `ID: ${pipeline.id}`,
792
+ `Status: ${pipeline.status}`,
793
+ `Ref: ${pipeline.ref}`,
794
+ `SHA: ${pipeline.sha}`,
795
+ `Created: ${pipeline.created_at}`,
796
+ `Duration: ${pipeline.duration ? pipeline.duration + "s" : "N/A"}`,
797
+ `Web URL: ${pipeline.web_url}`
798
+ ];
799
+ if (includeJobs) {
800
+ const jobs = await pipelineApi.getPipelineJobs(projectId, pipelineId);
801
+ lines.push(
802
+ "",
803
+ `--- Jobs (${jobs.length}\uAC1C) ---`,
804
+ ...jobs.map(
805
+ (j) => `- [${j.stage}] ${j.name}: ${j.status} (${j.duration ? j.duration + "s" : "N/A"})`
806
+ )
807
+ );
808
+ }
809
+ return { content: [{ type: "text", text: lines.join("\n") }] };
810
+ } else {
811
+ const pipelines = await pipelineApi.getPipelines(projectId, {
812
+ status,
813
+ ref
814
+ });
815
+ const summary = pipelines.map(
816
+ (p) => `- [${p.id}] ${p.status} (ref: ${p.ref}, sha: ${p.sha.substring(0, 8)}) ${p.web_url}`
817
+ ).join("\n");
818
+ return {
819
+ content: [
820
+ {
821
+ type: "text",
822
+ text: `\uD30C\uC774\uD504\uB77C\uC778 \uBAA9\uB85D (${pipelines.length}\uAC74):
823
+ ${summary}`
824
+ }
825
+ ]
826
+ };
827
+ }
828
+ }
829
+ );
830
+ server.tool(
831
+ "gitlab_manage_branches",
832
+ "TDE GitLab \uBE0C\uB79C\uCE58\uB97C \uAD00\uB9AC\uD569\uB2C8\uB2E4. \uBAA9\uB85D \uC870\uD68C, \uC0C1\uC138 \uC870\uD68C, \uC0DD\uC131, \uC0AD\uC81C \uC791\uC5C5\uC744 \uC218\uD589\uD569\uB2C8\uB2E4.",
833
+ {
834
+ projectId: z3.number().describe("\uD504\uB85C\uC81D\uD2B8 ID"),
835
+ action: z3.enum(["list", "get", "create", "delete"]).describe("\uC791\uC5C5 \uC720\uD615"),
836
+ branchName: z3.string().optional().describe("\uBE0C\uB79C\uCE58 \uC774\uB984 (get/create/delete \uC2DC \uD544\uC218)"),
837
+ ref: z3.string().optional().describe("\uAE30\uC900 ref (create \uC2DC \uD544\uC218)"),
838
+ search: z3.string().optional().describe("\uAC80\uC0C9 \uD0A4\uC6CC\uB4DC (list \uC2DC)")
839
+ },
840
+ async ({ projectId, action, branchName, ref, search }) => {
841
+ if (action === "list") {
842
+ const branches = await branchApi.getBranches(projectId, { search });
843
+ const summary = branches.map(
844
+ (b) => `- ${b.name}${b.default ? " [default]" : ""}${b.protected ? " [protected]" : ""} (${b.commit.short_id}: ${b.commit.message.split("\n")[0]})`
845
+ ).join("\n");
846
+ return {
847
+ content: [
848
+ {
849
+ type: "text",
850
+ text: `\uBE0C\uB79C\uCE58 \uBAA9\uB85D (${branches.length}\uAC74):
851
+ ${summary}`
852
+ }
853
+ ]
854
+ };
855
+ } else if (action === "get") {
856
+ if (!branchName) throw new Error("\uBE0C\uB79C\uCE58 \uC774\uB984\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694.");
857
+ const branch = await branchApi.getBranch(projectId, branchName);
858
+ const lines = [
859
+ `Name: ${branch.name}`,
860
+ `Default: ${branch.default}`,
861
+ `Protected: ${branch.protected}`,
862
+ `Merged: ${branch.merged}`,
863
+ `Commit: ${branch.commit.id}`,
864
+ `Message: ${branch.commit.message}`,
865
+ `Author: ${branch.commit.author_name}`,
866
+ `Date: ${branch.commit.authored_date}`
867
+ ];
868
+ return { content: [{ type: "text", text: lines.join("\n") }] };
869
+ } else if (action === "create") {
870
+ if (!branchName) throw new Error("\uBE0C\uB79C\uCE58 \uC774\uB984\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694.");
871
+ if (!ref) throw new Error("\uAE30\uC900 ref\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694.");
872
+ const branch = await branchApi.createBranch(projectId, branchName, ref);
873
+ return {
874
+ content: [
875
+ {
876
+ type: "text",
877
+ text: `\uBE0C\uB79C\uCE58 \uC0DD\uC131 \uC131\uACF5: ${branch.name} (from ${ref})`
878
+ }
879
+ ]
880
+ };
881
+ } else {
882
+ if (!branchName) throw new Error("\uBE0C\uB79C\uCE58 \uC774\uB984\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694.");
883
+ await branchApi.deleteBranch(projectId, branchName);
884
+ return {
885
+ content: [
886
+ { type: "text", text: `\uBE0C\uB79C\uCE58 \uC0AD\uC81C \uC131\uACF5: ${branchName}` }
887
+ ]
888
+ };
889
+ }
890
+ }
891
+ );
892
+ server.tool(
893
+ "gitlab_get_file",
894
+ "TDE GitLab \uC800\uC7A5\uC18C\uC758 \uD30C\uC77C \uB0B4\uC6A9 \uB610\uB294 \uB514\uB809\uD1A0\uB9AC \uD2B8\uB9AC\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4.",
895
+ {
896
+ projectId: z3.number().describe("\uD504\uB85C\uC81D\uD2B8 ID"),
897
+ path: z3.string().describe("\uD30C\uC77C \uACBD\uB85C \uB610\uB294 \uB514\uB809\uD1A0\uB9AC \uACBD\uB85C"),
898
+ ref: z3.string().optional().describe("\uBE0C\uB79C\uCE58/\uD0DC\uADF8/\uCEE4\uBC0B (\uAE30\uBCF8: \uAE30\uBCF8 \uBE0C\uB79C\uCE58)"),
899
+ type: z3.enum(["file", "tree"]).optional().describe('"file" (\uAE30\uBCF8) \uB610\uB294 "tree"'),
900
+ recursive: z3.boolean().optional().describe("\uC7AC\uADC0 \uC870\uD68C (type=tree \uC2DC)")
901
+ },
902
+ async ({ projectId, path, ref, type, recursive }) => {
903
+ if (type === "tree") {
904
+ const entries = await repoApi.getTree(projectId, {
905
+ path,
906
+ ref,
907
+ recursive
908
+ });
909
+ const summary = entries.map((e) => `${e.type === "tree" ? "\u{1F4C1}" : "\u{1F4C4}"} ${e.path}`).join("\n");
910
+ return {
911
+ content: [
912
+ {
913
+ type: "text",
914
+ text: `\uB514\uB809\uD1A0\uB9AC \uD2B8\uB9AC (${entries.length}\uAC1C \uD56D\uBAA9):
915
+ ${summary}`
916
+ }
917
+ ]
918
+ };
919
+ } else {
920
+ const file = await repoApi.getFile(projectId, path, ref);
921
+ const lines = [
922
+ `File: ${file.file_path}`,
923
+ `Size: ${file.size} bytes`,
924
+ `Ref: ${file.ref}`,
925
+ `Last Commit: ${file.last_commit_id}`,
926
+ "",
927
+ "--- Content ---",
928
+ file.content
929
+ ];
930
+ return { content: [{ type: "text", text: lines.join("\n") }] };
931
+ }
932
+ }
933
+ );
934
+ } catch (error) {
935
+ logger.warn(
936
+ `GitLab \uC124\uC815 \uB85C\uB4DC \uC2E4\uD328 \uB610\uB294 \uB3C4\uAD6C \uB4F1\uB85D \uC911 \uC624\uB958 \uBC1C\uC0DD: ${error.message}`
937
+ );
938
+ }
939
+ }
940
+
941
+ // src/mcp/server.ts
942
+ async function runServer() {
943
+ try {
944
+ const server = new McpServer({
945
+ name: "TDE Collab",
946
+ version: "1.0.0",
947
+ description: "TDE \uD3EC\uD138(Confluence, JIRA, GitLab) \uD1B5\uD569 \uB3C4\uAD6C. Confluence \uD398\uC774\uC9C0 \uAD00\uB9AC, JIRA \uC774\uC288 \uAD00\uB9AC, \uAC80\uC0C9 \uAE30\uB2A5\uC744 \uC81C\uACF5\uD569\uB2C8\uB2E4."
948
+ });
949
+ registerConfluenceTools(server);
950
+ registerJiraTools(server);
951
+ registerGitlabTools(server);
952
+ const transport = new StdioServerTransport();
953
+ await server.connect(transport);
954
+ logger.info("TDE Collab MCP Server running on stdio");
955
+ } catch (error) {
956
+ logger.error(`Fatal error in MCP Server: ${error}`);
957
+ process.exit(1);
958
+ }
959
+ }
960
+
961
+ export {
962
+ runServer
963
+ };
964
+ //# sourceMappingURL=chunk-T73I3OT6.js.map