tdecollab 0.2.1 → 0.2.3
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.
- package/README.md +91 -9
- package/dist/{chunk-T73I3OT6.js → chunk-2HLUO2TQ.js} +54 -21
- package/dist/chunk-2HLUO2TQ.js.map +1 -0
- package/dist/{chunk-SJ7KPK6Q.js → chunk-IFYMZLQI.js} +21 -6
- package/dist/chunk-IFYMZLQI.js.map +1 -0
- package/dist/{chunk-2IQ4QMK3.js → chunk-UH2YGKTB.js} +280 -125
- package/dist/chunk-UH2YGKTB.js.map +1 -0
- package/dist/cli.js +126 -12
- package/dist/cli.js.map +1 -1
- package/dist/{image-downloader-D57KFAIQ.js → image-downloader-IY2A3R5N.js} +7 -6
- package/dist/image-downloader-IY2A3R5N.js.map +1 -0
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/server-GLKCDDKS.js +9 -0
- package/package.json +13 -7
- package/dist/chunk-2IQ4QMK3.js.map +0 -1
- package/dist/chunk-SJ7KPK6Q.js.map +0 -1
- package/dist/chunk-T73I3OT6.js.map +0 -1
- package/dist/image-downloader-D57KFAIQ.js.map +0 -1
- package/dist/server-HS774DWY.js +0 -9
- /package/dist/{server-HS774DWY.js.map → server-GLKCDDKS.js.map} +0 -0
package/README.md
CHANGED
|
@@ -169,9 +169,51 @@ tdecollab gitlab file get <projectId> <filePath> [--ref <ref>]
|
|
|
169
169
|
tdecollab gitlab file tree <projectId> [--path <dir>] [--ref <ref>] [--recursive]
|
|
170
170
|
```
|
|
171
171
|
|
|
172
|
+
### MCP 도구 목록
|
|
173
|
+
|
|
174
|
+
MCP 서버를 통해 AI 에이전트(Claude 등)에서 사용할 수 있는 도구입니다.
|
|
175
|
+
|
|
176
|
+
#### Confluence (9개)
|
|
177
|
+
|
|
178
|
+
| 도구 | 설명 |
|
|
179
|
+
|------|------|
|
|
180
|
+
| `confluence_get_page` | 페이지 상세 조회 (Markdown 변환) |
|
|
181
|
+
| `confluence_create_page` | 새 페이지 생성 (Markdown → Storage Format 자동 변환) |
|
|
182
|
+
| `confluence_update_page` | 페이지 수정 |
|
|
183
|
+
| `confluence_delete_page` | 페이지 삭제 |
|
|
184
|
+
| `confluence_search_pages` | CQL로 페이지 검색 |
|
|
185
|
+
| `confluence_get_spaces` | 스페이스 목록 조회 |
|
|
186
|
+
| `confluence_get_page_tree` | 하위 페이지(자식 페이지) 목록 조회 |
|
|
187
|
+
| `confluence_manage_labels` | 페이지 라벨 조회/추가/삭제 |
|
|
188
|
+
| `confluence_convert_content` | Markdown ↔ Storage Format 양방향 변환 |
|
|
189
|
+
|
|
190
|
+
#### JIRA (7개)
|
|
191
|
+
|
|
192
|
+
| 도구 | 설명 |
|
|
193
|
+
|------|------|
|
|
194
|
+
| `jira_get_issue` | 이슈 상세 조회 |
|
|
195
|
+
| `jira_create_issue` | 새 이슈 생성 |
|
|
196
|
+
| `jira_update_issue` | 이슈 수정 |
|
|
197
|
+
| `jira_search_issues` | JQL로 이슈 검색 |
|
|
198
|
+
| `jira_transition_issue` | 이슈 상태 변경 (트랜지션 조회/실행) |
|
|
199
|
+
| `jira_manage_comments` | 코멘트 조회/추가/수정/삭제 |
|
|
200
|
+
| `jira_get_projects` | 프로젝트/보드/스프린트 조회 |
|
|
201
|
+
|
|
202
|
+
#### GitLab (7개)
|
|
203
|
+
|
|
204
|
+
| 도구 | 설명 |
|
|
205
|
+
|------|------|
|
|
206
|
+
| `gitlab_get_project` | 프로젝트 목록 또는 상세 조회 |
|
|
207
|
+
| `gitlab_get_merge_request` | MR 목록 또는 상세 조회 (변경 파일 포함 가능) |
|
|
208
|
+
| `gitlab_create_merge_request` | 새 Merge Request 생성 |
|
|
209
|
+
| `gitlab_manage_merge_request` | MR 머지/닫기/재열기/코멘트 추가 |
|
|
210
|
+
| `gitlab_get_pipelines` | 파이프라인 목록 또는 상세 조회 (Job 포함 가능) |
|
|
211
|
+
| `gitlab_manage_branches` | 브랜치 목록/상세/생성/삭제 |
|
|
212
|
+
| `gitlab_get_file` | 파일 내용 또는 디렉토리 트리 조회 |
|
|
213
|
+
|
|
172
214
|
### MCP 서버 (Claude Desktop 연동)
|
|
173
215
|
|
|
174
|
-
Claude Desktop에서 Confluence 도구를 사용할 수 있습니다.
|
|
216
|
+
Claude Desktop에서 Confluence, JIRA, GitLab 도구를 사용할 수 있습니다.
|
|
175
217
|
|
|
176
218
|
#### 설정 파일 위치
|
|
177
219
|
|
|
@@ -274,7 +316,7 @@ pnpm format
|
|
|
274
316
|
## 프로젝트 구조
|
|
275
317
|
|
|
276
318
|
```
|
|
277
|
-
src/
|
|
319
|
+
tools/ # (기존 src/) 사내 시스템 연계용 Tool & MCP
|
|
278
320
|
├── index.ts # MCP 서버 엔트리포인트
|
|
279
321
|
├── cli.ts # CLI 엔트리포인트
|
|
280
322
|
├── common/ # 공통 모듈 (인증, HTTP, 설정, 에러)
|
|
@@ -282,15 +324,55 @@ src/
|
|
|
282
324
|
├── jira/ # JIRA 모듈 (api/tools/commands)
|
|
283
325
|
├── gitlab/ # GitLab 모듈 (api/tools/commands)
|
|
284
326
|
└── mcp/ # MCP 서버 코어
|
|
327
|
+
|
|
328
|
+
backend/ # Agentic PRD Harness 백엔드 (Python/FastAPI)
|
|
329
|
+
├── app/
|
|
330
|
+
│ ├── api/ # 엔드포인트 및 라우터
|
|
331
|
+
│ ├── core/ # 비즈니스 로직, 데이터베이스, AI 연동
|
|
332
|
+
│ ├── models/ # 데이터 모델 (SQLAlchemy)
|
|
333
|
+
│ └── webhooks/ # GitLab Webhook 연동
|
|
334
|
+
└── tests/
|
|
335
|
+
|
|
336
|
+
frontend/ # Agentic PRD Harness 프론트엔드 (React/Next.js)
|
|
337
|
+
├── src/
|
|
338
|
+
│ ├── components/ # UI 컴포넌트 (shadcn/ui)
|
|
339
|
+
│ ├── pages/ # 웹 페이지
|
|
340
|
+
│ └── services/ # 백엔드 API 호출
|
|
341
|
+
└── tests/
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## 실행 방법 (Agentic PRD Harness)
|
|
345
|
+
|
|
346
|
+
새롭게 추가된 기획 문서 관리 및 개발 연동 웹 UI를 실행하는 방법입니다.
|
|
347
|
+
|
|
348
|
+
### 1. 필수 요구사항
|
|
349
|
+
- Node.js 20+
|
|
350
|
+
- Python 3.11+
|
|
351
|
+
- pnpm
|
|
352
|
+
|
|
353
|
+
### 2. 초기 셋업
|
|
354
|
+
가상환경(venv)을 생성하고 프론트엔드/백엔드 패키지를 모두 설치합니다.
|
|
355
|
+
```bash
|
|
356
|
+
make setup
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### 3. 서버 실행
|
|
360
|
+
MCP, 백엔드(FastAPI), 프론트엔드(Next.js)를 동시에 실행합니다.
|
|
361
|
+
```bash
|
|
362
|
+
make dev
|
|
285
363
|
```
|
|
364
|
+
- **Frontend URL**: [http://localhost:3000](http://localhost:3000)
|
|
365
|
+
- **Backend API Docs**: [http://localhost:8000/docs](http://localhost:8000/docs)
|
|
366
|
+
|
|
286
367
|
|
|
287
368
|
## 문서
|
|
288
369
|
|
|
289
|
-
상세한 설계 문서는 `docs/` 디렉토리를 참조하세요.
|
|
370
|
+
상세한 설계 문서는 `tdecollab-docs/` 디렉토리를 참조하세요.
|
|
290
371
|
|
|
291
|
-
- [아키텍처](docs/architecture.md)
|
|
292
|
-
- [인증 및 설정](docs/auth-and-config.md)
|
|
293
|
-
- [MCP 서버 설계](docs/mcp-server-design.md)
|
|
294
|
-
-
|
|
295
|
-
-
|
|
296
|
-
-
|
|
372
|
+
- [아키텍처](tdecollab-docs/architecture.md)
|
|
373
|
+
- [인증 및 설정](tdecollab-docs/auth-and-config.md)
|
|
374
|
+
- [MCP 서버 설계](tdecollab-docs/mcp-server-design.md)
|
|
375
|
+
- [npm 패키지 등록(Publish) 가이드](tdecollab-docs/npm-publish-guide.md)
|
|
376
|
+
- Confluence: [API 스펙](tdecollab-docs/confluence/api-spec.md) | [기능 정의](tdecollab-docs/confluence/features.md) | [MCP 도구](tdecollab-docs/confluence/mcp-tools.md)
|
|
377
|
+
- JIRA: [API 스펙](tdecollab-docs/jira/api-spec.md) | [기능 정의](tdecollab-docs/jira/features.md) | [MCP 도구](tdecollab-docs/jira/mcp-tools.md)
|
|
378
|
+
- GitLab: [API 스펙](tdecollab-docs/gitlab/api-spec.md) | [기능 정의](tdecollab-docs/gitlab/features.md) | [MCP 도구](tdecollab-docs/gitlab/mcp-tools.md)
|
|
@@ -20,19 +20,19 @@ import {
|
|
|
20
20
|
loadConfluenceConfig,
|
|
21
21
|
loadGitlabConfig,
|
|
22
22
|
loadJiraConfig
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-UH2YGKTB.js";
|
|
24
24
|
import {
|
|
25
25
|
logger
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-IFYMZLQI.js";
|
|
27
27
|
|
|
28
|
-
//
|
|
28
|
+
// tools/mcp/server.ts
|
|
29
29
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
30
30
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
31
31
|
|
|
32
|
-
//
|
|
32
|
+
// tools/confluence/tools/index.ts
|
|
33
33
|
import { z } from "zod";
|
|
34
34
|
|
|
35
|
-
//
|
|
35
|
+
// tools/confluence/api/label.ts
|
|
36
36
|
var ConfluenceLabelApi = class {
|
|
37
37
|
constructor(client) {
|
|
38
38
|
this.client = client;
|
|
@@ -52,7 +52,7 @@ var ConfluenceLabelApi = class {
|
|
|
52
52
|
}
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
-
//
|
|
55
|
+
// tools/confluence/tools/index.ts
|
|
56
56
|
function registerConfluenceTools(server) {
|
|
57
57
|
try {
|
|
58
58
|
const config = loadConfluenceConfig();
|
|
@@ -63,22 +63,24 @@ function registerConfluenceTools(server) {
|
|
|
63
63
|
const labelApi = new ConfluenceLabelApi(client);
|
|
64
64
|
const mdToStorage = new MarkdownToStorageConverter();
|
|
65
65
|
const storageToMd = new StorageToMarkdownConverter();
|
|
66
|
+
const aiService = new AIConversionService();
|
|
66
67
|
server.tool(
|
|
67
68
|
"confluence_get_page",
|
|
68
69
|
"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
|
{
|
|
70
71
|
pageId: z.string().describe("\uD398\uC774\uC9C0 ID"),
|
|
71
72
|
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
|
+
imageDir: z.string().optional().describe("\uC774\uBBF8\uC9C0 \uC800\uC7A5 \uB514\uB809\uD1A0\uB9AC (\uAE30\uBCF8\uAC12: ./images)"),
|
|
74
|
+
useAiFallback: z.boolean().optional().describe("\uBCC0\uD658 \uACB0\uACFC \uBCF4\uC815\uC744 \uC704\uD574 AI\uB97C \uC0AC\uC6A9\uD560\uC9C0 \uC5EC\uBD80")
|
|
73
75
|
},
|
|
74
|
-
async ({ pageId, downloadImages, imageDir }) => {
|
|
76
|
+
async ({ pageId, downloadImages, imageDir, useAiFallback }) => {
|
|
75
77
|
const page = await contentApi.getPage(pageId);
|
|
76
78
|
let md = "";
|
|
77
79
|
let imageInfo = "";
|
|
78
80
|
if (page.body?.storage?.value) {
|
|
79
81
|
let imageUrlMap;
|
|
80
82
|
if (downloadImages) {
|
|
81
|
-
const { ImageDownloader } = await import("./image-downloader-
|
|
83
|
+
const { ImageDownloader } = await import("./image-downloader-IY2A3R5N.js");
|
|
82
84
|
const downloader = new ImageDownloader(contentApi, {
|
|
83
85
|
outputDir: imageDir || "./images",
|
|
84
86
|
pageId: page.id,
|
|
@@ -90,6 +92,15 @@ function registerConfluenceTools(server) {
|
|
|
90
92
|
\uB2E4\uC6B4\uB85C\uB4DC\uB41C \uC774\uBBF8\uC9C0: ${imageUrlMap.size}\uAC1C`;
|
|
91
93
|
}
|
|
92
94
|
md = storageToMd.convert(page.body.storage.value, imageUrlMap);
|
|
95
|
+
if (useAiFallback) {
|
|
96
|
+
const aiResult = await aiService.refine({
|
|
97
|
+
sourceContent: md,
|
|
98
|
+
sourceType: "markdown",
|
|
99
|
+
targetType: "markdown",
|
|
100
|
+
context: "\uC774\uC804 \uBCC0\uD658 \uACB0\uACFC\uAC00 \uAE68\uC84C\uAC70\uB098 \uD45C \uD615\uC2DD\uC774 \uBD80\uC801\uC808\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uAE68\uB057\uD55C GFM \uD615\uC2DD\uC73C\uB85C \uBCF4\uC815\uD574\uC8FC\uC138\uC694."
|
|
101
|
+
});
|
|
102
|
+
md = aiResult.convertedContent;
|
|
103
|
+
}
|
|
93
104
|
}
|
|
94
105
|
return {
|
|
95
106
|
content: [
|
|
@@ -114,10 +125,20 @@ ${md}`
|
|
|
114
125
|
title: z.string().describe("\uD398\uC774\uC9C0 \uC81C\uBAA9"),
|
|
115
126
|
content: z.string().describe("\uD398\uC774\uC9C0 \uB0B4\uC6A9 (Markdown)"),
|
|
116
127
|
parentId: z.string().optional().describe("\uBD80\uBAA8 \uD398\uC774\uC9C0 ID"),
|
|
117
|
-
labels: z.array(z.string()).optional().describe("\uB77C\uBCA8 \uBAA9\uB85D")
|
|
128
|
+
labels: z.array(z.string()).optional().describe("\uB77C\uBCA8 \uBAA9\uB85D"),
|
|
129
|
+
useAiFallback: z.boolean().optional().describe("\uBCF5\uC7A1\uD55C \uBCC0\uD658\uC744 \uC704\uD574 AI\uB97C \uC0AC\uC6A9\uD560\uC9C0 \uC5EC\uBD80")
|
|
118
130
|
},
|
|
119
|
-
async ({ spaceKey, title, content, parentId, labels }) => {
|
|
120
|
-
|
|
131
|
+
async ({ spaceKey, title, content, parentId, labels, useAiFallback }) => {
|
|
132
|
+
let storageBody = mdToStorage.convert(content);
|
|
133
|
+
if (useAiFallback) {
|
|
134
|
+
const aiResult = await aiService.refine({
|
|
135
|
+
sourceContent: content,
|
|
136
|
+
sourceType: "markdown",
|
|
137
|
+
targetType: "storage-xml",
|
|
138
|
+
context: "Confluence Storage XML \uD615\uC2DD\uC73C\uB85C \uBCC0\uD658\uD574\uC8FC\uC138\uC694. Mermaid\uB294 mermaiddiagram \uB9E4\uD06C\uB85C\uB97C \uC0AC\uC6A9\uD558\uC138\uC694."
|
|
139
|
+
});
|
|
140
|
+
storageBody = aiResult.convertedContent;
|
|
141
|
+
}
|
|
121
142
|
const page = await contentApi.createPage({
|
|
122
143
|
spaceKey,
|
|
123
144
|
title,
|
|
@@ -279,14 +300,26 @@ ${summary}`
|
|
|
279
300
|
"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
301
|
{
|
|
281
302
|
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")
|
|
303
|
+
format: z.enum(["storage_to_markdown", "markdown_to_storage"]).describe("\uBCC0\uD658 \uBC29\uD5A5"),
|
|
304
|
+
useAi: z.boolean().optional().describe("\uC9C0\uB2A5\uD615 \uBCC0\uD658\uC744 \uC704\uD574 AI\uB97C \uC0AC\uC6A9\uD560\uC9C0 \uC5EC\uBD80")
|
|
283
305
|
},
|
|
284
|
-
async ({ content, format }) => {
|
|
306
|
+
async ({ content, format, useAi }) => {
|
|
285
307
|
let result = "";
|
|
286
|
-
if (
|
|
287
|
-
|
|
308
|
+
if (useAi) {
|
|
309
|
+
const sourceType = format === "storage_to_markdown" ? "storage-xml" : "markdown";
|
|
310
|
+
const targetType = format === "storage_to_markdown" ? "markdown" : "storage-xml";
|
|
311
|
+
const aiResult = await aiService.refine({
|
|
312
|
+
sourceContent: content,
|
|
313
|
+
sourceType,
|
|
314
|
+
targetType
|
|
315
|
+
});
|
|
316
|
+
result = aiResult.convertedContent;
|
|
288
317
|
} else {
|
|
289
|
-
|
|
318
|
+
if (format === "storage_to_markdown") {
|
|
319
|
+
result = storageToMd.convert(content);
|
|
320
|
+
} else {
|
|
321
|
+
result = mdToStorage.convert(content);
|
|
322
|
+
}
|
|
290
323
|
}
|
|
291
324
|
return {
|
|
292
325
|
content: [{ type: "text", text: result }]
|
|
@@ -298,7 +331,7 @@ ${summary}`
|
|
|
298
331
|
}
|
|
299
332
|
}
|
|
300
333
|
|
|
301
|
-
//
|
|
334
|
+
// tools/jira/tools/index.ts
|
|
302
335
|
import { z as z2 } from "zod";
|
|
303
336
|
function registerJiraTools(server) {
|
|
304
337
|
try {
|
|
@@ -576,7 +609,7 @@ ${summary}` }]
|
|
|
576
609
|
}
|
|
577
610
|
}
|
|
578
611
|
|
|
579
|
-
//
|
|
612
|
+
// tools/gitlab/tools/index.ts
|
|
580
613
|
import { z as z3 } from "zod";
|
|
581
614
|
function registerGitlabTools(server) {
|
|
582
615
|
try {
|
|
@@ -938,7 +971,7 @@ ${summary}`
|
|
|
938
971
|
}
|
|
939
972
|
}
|
|
940
973
|
|
|
941
|
-
//
|
|
974
|
+
// tools/mcp/server.ts
|
|
942
975
|
async function runServer() {
|
|
943
976
|
try {
|
|
944
977
|
const server = new McpServer({
|
|
@@ -961,4 +994,4 @@ async function runServer() {
|
|
|
961
994
|
export {
|
|
962
995
|
runServer
|
|
963
996
|
};
|
|
964
|
-
//# sourceMappingURL=chunk-
|
|
997
|
+
//# sourceMappingURL=chunk-2HLUO2TQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../tools/mcp/server.ts","../tools/confluence/tools/index.ts","../tools/confluence/api/label.ts","../tools/jira/tools/index.ts","../tools/gitlab/tools/index.ts"],"sourcesContent":["\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { registerConfluenceTools } from '../confluence/tools/index.js';\nimport { registerJiraTools } from '../jira/tools/index.js';\nimport { registerGitlabTools } from '../gitlab/tools/index.js';\nimport { logger } from '../common/logger.js';\n\nexport async function runServer() {\n try {\n const server = new McpServer({\n name: 'TDE Collab',\n version: '1.0.0',\n description: 'TDE 포털(Confluence, JIRA, GitLab) 통합 도구. Confluence 페이지 관리, JIRA 이슈 관리, 검색 기능을 제공합니다.',\n });\n\n // Confluence 도구 등록\n registerConfluenceTools(server);\n\n // JIRA 도구 등록\n registerJiraTools(server);\n\n // GitLab 도구 등록\n registerGitlabTools(server);\n\n // Stdio 전송 계층 연결\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n logger.info('TDE Collab MCP Server running on stdio');\n } catch (error) {\n logger.error(`Fatal error in MCP Server: ${error}`);\n process.exit(1);\n }\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { ConfluenceContentApi } from '../api/content.js';\nimport { ConfluenceSpaceApi } from '../api/space.js';\nimport { ConfluenceSearchApi } from '../api/search.js';\nimport { ConfluenceLabelApi } from '../api/label.js';\nimport { createConfluenceClient } from '../api/client.js';\nimport { MarkdownToStorageConverter } from '../converters/md-to-storage.js';\nimport { StorageToMarkdownConverter } from '../converters/storage-to-md.js';\nimport { loadConfluenceConfig } from '../../common/config.js';\nimport { logger } from '../../common/logger.js';\n\nexport function registerConfluenceTools(server: McpServer) {\n try {\n const config = loadConfluenceConfig();\n const client = createConfluenceClient(config);\n\n const contentApi = new ConfluenceContentApi(client);\n const spaceApi = new ConfluenceSpaceApi(client);\n const searchApi = new ConfluenceSearchApi(client);\n const labelApi = new ConfluenceLabelApi(client);\n\n const mdToStorage = new MarkdownToStorageConverter();\n const storageToMd = new StorageToMarkdownConverter();\n const aiService = new AIConversionService();\n\n // Tools\n\n server.tool(\n 'confluence_get_page',\n 'TDE Confluence 페이지 상세 조회. 페이지 내용을 Markdown으로 변환하여 반환합니다.',\n {\n pageId: z.string().describe('페이지 ID'),\n downloadImages: z.boolean().optional().describe('이미지 다운로드 여부'),\n imageDir: z.string().optional().describe('이미지 저장 디렉토리 (기본값: ./images)'),\n useAiFallback: z.boolean().optional().describe('변환 결과 보정을 위해 AI를 사용할지 여부'),\n },\n async ({ pageId, downloadImages, imageDir, useAiFallback }) => {\n const page = await contentApi.getPage(pageId);\n\n let md = '';\n let imageInfo = '';\n\n if (page.body?.storage?.value) {\n let imageUrlMap: Map<string, string> | undefined;\n\n // 이미지 다운로드 옵션이 활성화된 경우\n if (downloadImages) {\n const { ImageDownloader } = await import('../utils/image-downloader.js');\n const downloader = new ImageDownloader(contentApi, {\n outputDir: imageDir || './images',\n pageId: page.id,\n baseUrl: config.baseUrl\n });\n\n imageUrlMap = await downloader.downloadAllImages(page.body.storage.value);\n imageInfo = `\\n\\n다운로드된 이미지: ${imageUrlMap.size}개`;\n }\n\n md = storageToMd.convert(page.body.storage.value, imageUrlMap);\n \n if (useAiFallback) {\n const aiResult = await aiService.refine({\n sourceContent: md,\n sourceType: 'markdown',\n targetType: 'markdown',\n context: '이전 변환 결과가 깨졌거나 표 형식이 부적절할 수 있습니다. 깨끗한 GFM 형식으로 보정해주세요.'\n });\n md = aiResult.convertedContent;\n }\n }\n\n return {\n content: [\n {\n type: 'text',\n text: `Title: ${page.title}\\nID: ${page.id}\\nSpace: ${page.space?.name} (${page.space?.key})\\nURL: ${page._links?.base}${page._links?.webui}${imageInfo}\\n\\n${md}`,\n },\n ],\n };\n }\n );\n\n server.tool(\n 'confluence_create_page',\n 'TDE Confluence에 새 페이지를 생성합니다. Markdown 형식의 내용을 Confluence Storage Format으로 자동 변환합니다.',\n {\n spaceKey: z.string().describe('스페이스 키'),\n title: z.string().describe('페이지 제목'),\n content: z.string().describe('페이지 내용 (Markdown)'),\n parentId: z.string().optional().describe('부모 페이지 ID'),\n labels: z.array(z.string()).optional().describe('라벨 목록'),\n useAiFallback: z.boolean().optional().describe('복잡한 변환을 위해 AI를 사용할지 여부'),\n },\n async ({ spaceKey, title, content, parentId, labels, useAiFallback }) => {\n let storageBody = mdToStorage.convert(content);\n \n if (useAiFallback) {\n const aiResult = await aiService.refine({\n sourceContent: content,\n sourceType: 'markdown',\n targetType: 'storage-xml',\n context: 'Confluence Storage XML 형식으로 변환해주세요. Mermaid는 mermaiddiagram 매크로를 사용하세요.'\n });\n storageBody = aiResult.convertedContent;\n }\n\n const page = await contentApi.createPage({\n spaceKey,\n title,\n body: storageBody,\n parentId,\n labels\n });\n\n return {\n content: [\n {\n type: 'text',\n text: `페이지 생성 성공: ${page.title} (ID: ${page.id})\\nURL: ${page._links?.base}${page._links?.webui}`,\n },\n ],\n };\n }\n );\n\n server.tool(\n 'confluence_update_page',\n 'TDE Confluence 페이지를 수정합니다. Markdown 형식의 내용으로 업데이트할 수 있습니다.',\n {\n pageId: z.string().describe('페이지 ID'),\n title: z.string().describe('페이지 제목'),\n content: z.string().describe('페이지 내용 (Markdown)'),\n version: z.number().describe('현재 페이지 버전 (충돌 방지용)'),\n },\n async ({ pageId, title, content, version }) => {\n const storageBody = mdToStorage.convert(content);\n const page = await contentApi.updatePage({\n id: pageId,\n title,\n body: storageBody,\n version\n });\n\n return {\n content: [\n {\n type: 'text',\n text: `페이지 수정 성공: ${page.title} (Version: ${page.version?.number})`,\n },\n ],\n };\n }\n );\n\n server.tool(\n 'confluence_search_pages',\n 'TDE Confluence에서 페이지를 검색합니다. CQL(Confluence Query Language)을 사용하여 고급 검색이 가능합니다.',\n {\n cql: z.string().describe('Confluence Query Language (예: title ~ \"guide\")'),\n limit: z.number().default(10).describe('결과 개수 제한'),\n },\n async ({ cql, limit }) => {\n const result = await searchApi.searchByCql(cql, 0, limit);\n const summary = result.results.map(p => `- [${p.id}] ${p.title} (Space: ${p.space?.key})`).join('\\n');\n\n return {\n content: [\n {\n type: 'text',\n text: `검색 결과 (${result.size}/${result.totalSize}):\\n${summary}`,\n },\n ],\n };\n }\n );\n\n server.tool(\n 'confluence_get_spaces',\n 'TDE Confluence의 스페이스 목록을 조회합니다.',\n {\n limit: z.number().default(20).describe('결과 개수 제한')\n },\n async ({ limit }) => {\n const spaces = await spaceApi.getSpaces('global', 0, limit);\n const summary = spaces.map(s => `- [${s.key}] ${s.name}`).join('\\n');\n return {\n content: [{ type: 'text', text: `스페이스 목록:\\n${summary}` }]\n };\n }\n );\n\n server.tool(\n 'confluence_delete_page',\n 'TDE Confluence 페이지를 삭제합니다.',\n {\n pageId: z.string().describe('페이지 ID'),\n },\n async ({ pageId }) => {\n await contentApi.deletePage(pageId);\n return {\n content: [\n {\n type: 'text',\n text: `페이지 삭제 성공 (ID: ${pageId})`,\n },\n ],\n };\n }\n );\n\n server.tool(\n 'confluence_get_page_tree',\n 'TDE Confluence 페이지의 하위 페이지(자식 페이지) 목록을 조회합니다.',\n {\n pageId: z.string().describe('페이지 ID'),\n limit: z.number().default(20).describe('결과 개수 제한'),\n },\n async ({ pageId, limit }) => {\n const children = await contentApi.getChildPages(pageId, 0, limit);\n const summary = children.map(p => `- [${p.id}] ${p.title}`).join('\\n');\n return {\n content: [\n {\n type: 'text',\n text: `자식 페이지 목록 (${children.length}):\\n${summary}`,\n },\n ],\n };\n }\n );\n\n server.tool(\n 'confluence_manage_labels',\n 'TDE Confluence 페이지의 라벨을 관리합니다. 라벨 조회, 추가, 삭제 기능을 제공합니다.',\n {\n pageId: z.string().describe('페이지 ID'),\n action: z.enum(['list', 'add', 'remove']).describe('작업 유형'),\n labels: z.array(z.string()).optional().describe('추가할 라벨 목록 (add 작업 시 필수)'),\n label: z.string().optional().describe('삭제할 라벨 (remove 작업 시 필수)'),\n },\n async ({ pageId, action, labels, label }) => {\n if (action === 'list') {\n const result = await labelApi.getLabels(pageId);\n const summary = result.map(l => l.name).join(', ');\n return {\n content: [{ type: 'text', text: `라벨 목록: ${summary}` }],\n };\n } else if (action === 'add') {\n if (!labels || labels.length === 0) {\n throw new Error('라벨 목록을 입력해주세요.');\n }\n await labelApi.addLabels(pageId, labels);\n return {\n content: [{ type: 'text', text: `라벨 추가 성공: ${labels.join(', ')}` }],\n };\n } else if (action === 'remove') {\n if (!label) {\n throw new Error('삭제할 라벨을 입력해주세요.');\n }\n await labelApi.removeLabel(pageId, label);\n return {\n content: [{ type: 'text', text: `라벨 삭제 성공: ${label}` }],\n };\n }\n return { content: [] };\n }\n );\n\n server.tool(\n 'confluence_convert_content',\n 'TDE Confluence 컨텐츠 포맷을 변환합니다. Markdown과 Confluence Storage Format 간 양방향 변환을 지원합니다.',\n {\n content: z.string().describe('변환할 컨텐츠'),\n format: z.enum(['storage_to_markdown', 'markdown_to_storage']).describe('변환 방향'),\n useAi: z.boolean().optional().describe('지능형 변환을 위해 AI를 사용할지 여부'),\n },\n async ({ content, format, useAi }) => {\n let result = '';\n if (useAi) {\n const sourceType = format === 'storage_to_markdown' ? 'storage-xml' : 'markdown';\n const targetType = format === 'storage_to_markdown' ? 'markdown' : 'storage-xml';\n const aiResult = await aiService.refine({\n sourceContent: content,\n sourceType,\n targetType\n });\n result = aiResult.convertedContent;\n } else {\n if (format === 'storage_to_markdown') {\n result = storageToMd.convert(content);\n } else {\n result = mdToStorage.convert(content);\n }\n }\n return {\n content: [{ type: 'text', text: result }],\n };\n }\n );\n\n } catch (error) {\n logger.warn(`Confluence 설정 로드 실패 또는 도구 등록 중 오류 발생: ${(error as Error).message}`);\n // Do not throw, just skip registration if config is missing (optional module pattern)\n }\n}\n","import { AxiosInstance } from 'axios';\nimport { ConfluenceLabel } from '../types.js';\n\nexport class ConfluenceLabelApi {\n constructor(private client: AxiosInstance) { }\n\n async getLabels(pageId: string): Promise<ConfluenceLabel[]> {\n const response = await this.client.get(`/rest/api/content/${pageId}/label`);\n return response.data.results;\n }\n\n async addLabels(pageId: string, labels: string[]): Promise<void> {\n const data = labels.map(name => ({ prefix: 'global', name }));\n await this.client.post(`/rest/api/content/${pageId}/label`, data);\n }\n\n async removeLabel(pageId: string, labelName: string): Promise<void> {\n // Label deletion usually requires query param for name\n // /rest/api/content/{id}/label?name={name}\n await this.client.delete(`/rest/api/content/${pageId}/label`, {\n params: { name: labelName }\n });\n }\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { JiraIssueApi } from '../api/issue.js';\nimport { JiraSearchApi } from '../api/search.js';\nimport { JiraTransitionApi } from '../api/transition.js';\nimport { JiraCommentApi } from '../api/comment.js';\nimport { JiraProjectApi } from '../api/project.js';\nimport { createJiraClient } from '../api/client.js';\nimport { loadJiraConfig } from '../../common/config.js';\nimport { logger } from '../../common/logger.js';\n\nexport function registerJiraTools(server: McpServer) {\n try {\n const config = loadJiraConfig();\n const client = createJiraClient(config);\n\n const issueApi = new JiraIssueApi(client);\n const searchApi = new JiraSearchApi(client);\n const transitionApi = new JiraTransitionApi(client);\n const commentApi = new JiraCommentApi(client);\n const projectApi = new JiraProjectApi(client);\n\n // 이슈 상세 조회\n server.tool(\n 'jira_get_issue',\n 'TDE JIRA 이슈를 상세 조회합니다. 이슈 키(예: PROJ-123)로 조회하며, 필드 목록을 지정할 수 있습니다.',\n {\n issueKey: z.string().describe('이슈 키 (예: PROJ-123)'),\n fields: z\n .array(z.string())\n .optional()\n .describe('조회할 필드 목록 (미지정 시 전체)'),\n },\n async ({ issueKey, fields }) => {\n const issue = await issueApi.getIssue(issueKey, fields);\n const f = issue.fields;\n\n const lines = [\n `Key: ${issue.key}`,\n `Summary: ${f.summary}`,\n `Status: ${f.status?.name || 'N/A'}`,\n `Type: ${f.issuetype?.name || 'N/A'}`,\n `Priority: ${f.priority?.name || 'N/A'}`,\n `Assignee: ${f.assignee?.displayName || '미배정'}`,\n `Reporter: ${f.reporter?.displayName || 'N/A'}`,\n `Labels: ${f.labels?.join(', ') || '없음'}`,\n `Created: ${f.created || 'N/A'}`,\n `Updated: ${f.updated || 'N/A'}`,\n ];\n\n if (f.parent) {\n lines.push(`Parent: ${f.parent.key} - ${f.parent.fields?.summary || ''}`);\n }\n\n if (f.description) {\n lines.push('', '--- Description ---', f.description);\n }\n\n if (f.subtasks && f.subtasks.length > 0) {\n lines.push(\n '',\n '--- Subtasks ---',\n ...f.subtasks.map(\n (st) => `- [${st.key}] ${st.fields.summary} (${st.fields.status.name})`,\n ),\n );\n }\n\n return {\n content: [{ type: 'text', text: lines.join('\\n') }],\n };\n },\n );\n\n // 이슈 생성\n server.tool(\n 'jira_create_issue',\n 'TDE JIRA에 새 이슈를 생성합니다.',\n {\n projectKey: z.string().describe('프로젝트 키 (예: PROJ)'),\n summary: z.string().describe('이슈 제목'),\n issueType: z.string().describe('이슈 유형 (예: Task, Bug, Story, Sub-task)'),\n description: z.string().optional().describe('이슈 설명'),\n assignee: z.string().optional().describe('담당자 사용자 이름'),\n priority: z.string().optional().describe('우선순위 (예: High, Medium, Low)'),\n labels: z.array(z.string()).optional().describe('라벨 목록'),\n components: z.array(z.string()).optional().describe('컴포넌트 이름 목록'),\n parentKey: z\n .string()\n .optional()\n .describe('상위 이슈 키 (Sub-task 생성 시)'),\n },\n async ({ projectKey, summary, issueType, description, assignee, priority, labels, components, parentKey }) => {\n const issue = await issueApi.createIssue({\n projectKey,\n summary,\n issueType,\n description,\n assignee,\n priority,\n labels,\n components,\n parentKey,\n });\n\n return {\n content: [\n {\n type: 'text',\n text: `이슈 생성 성공: ${issue.key}\\nURL: ${config.baseUrl}/browse/${issue.key}`,\n },\n ],\n };\n },\n );\n\n // 이슈 수정\n server.tool(\n 'jira_update_issue',\n 'TDE JIRA 이슈를 수정합니다.',\n {\n issueKey: z.string().describe('이슈 키'),\n summary: z.string().optional().describe('이슈 제목'),\n description: z.string().optional().describe('이슈 설명'),\n assignee: z.string().optional().describe('담당자 (빈 문자열로 배정 해제)'),\n priority: z.string().optional().describe('우선순위'),\n labels: z.array(z.string()).optional().describe('라벨 목록 (전체 교체)'),\n components: z.array(z.string()).optional().describe('컴포넌트 목록 (전체 교체)'),\n },\n async ({ issueKey, summary, description, assignee, priority, labels, components }) => {\n await issueApi.updateIssue(issueKey, {\n summary,\n description,\n assignee,\n priority,\n labels,\n components,\n });\n\n return {\n content: [{ type: 'text', text: `이슈 수정 성공: ${issueKey}` }],\n };\n },\n );\n\n // JQL 검색\n server.tool(\n 'jira_search_issues',\n 'TDE JIRA에서 JQL(JIRA Query Language)로 이슈를 검색합니다.',\n {\n jql: z.string().describe('JQL 쿼리 (예: project = PROJ AND status = Open)'),\n maxResults: z.number().default(20).describe('최대 결과 수'),\n fields: z\n .array(z.string())\n .optional()\n .describe('조회할 필드 목록'),\n },\n async ({ jql, maxResults, fields }) => {\n const result = await searchApi.searchByJql(jql, 0, maxResults, fields);\n const summary = result.issues\n .map(\n (i) =>\n `- [${i.key}] ${i.fields.summary} (${i.fields.status?.name || 'N/A'}, ${i.fields.assignee?.displayName || '미배정'})`,\n )\n .join('\\n');\n\n return {\n content: [\n {\n type: 'text',\n text: `검색 결과 (${result.issues.length}/${result.total}):\\n${summary}`,\n },\n ],\n };\n },\n );\n\n // 상태 변경 (트랜지션)\n server.tool(\n 'jira_transition_issue',\n 'TDE JIRA 이슈의 상태를 변경합니다. 가능한 트랜지션 목록 조회 또는 트랜지션 실행을 할 수 있습니다.',\n {\n issueKey: z.string().describe('이슈 키'),\n action: z\n .enum(['list', 'do'])\n .describe('작업 유형: list(가능한 트랜지션 조회), do(트랜지션 실행)'),\n transitionId: z\n .string()\n .optional()\n .describe('트랜지션 ID (do 작업 시 필수)'),\n },\n async ({ issueKey, action, transitionId }) => {\n if (action === 'list') {\n const transitions = await transitionApi.getTransitions(issueKey);\n const summary = transitions\n .map((t) => `- [${t.id}] ${t.name} → ${t.to.name}`)\n .join('\\n');\n return {\n content: [\n { type: 'text', text: `가능한 트랜지션:\\n${summary}` },\n ],\n };\n } else {\n if (!transitionId) {\n throw new Error('트랜지션 ID를 입력해주세요.');\n }\n await transitionApi.doTransition(issueKey, transitionId);\n return {\n content: [\n { type: 'text', text: `트랜지션 완료: ${issueKey}` },\n ],\n };\n }\n },\n );\n\n // 코멘트 관리\n server.tool(\n 'jira_manage_comments',\n 'TDE JIRA 이슈의 코멘트를 관리합니다. 조회, 추가, 수정, 삭제 기능을 제공합니다.',\n {\n issueKey: z.string().describe('이슈 키'),\n action: z\n .enum(['list', 'add', 'update', 'delete'])\n .describe('작업 유형'),\n body: z\n .string()\n .optional()\n .describe('코멘트 내용 (add, update 시 필수)'),\n commentId: z\n .string()\n .optional()\n .describe('코멘트 ID (update, delete 시 필수)'),\n },\n async ({ issueKey, action, body, commentId }) => {\n if (action === 'list') {\n const result = await commentApi.getComments(issueKey);\n const summary = result.comments\n .map(\n (c) =>\n `- [${c.id}] ${c.author.displayName} (${c.created}):\\n ${c.body.substring(0, 200)}`,\n )\n .join('\\n');\n return {\n content: [\n {\n type: 'text',\n text: `코멘트 목록 (${result.comments.length}/${result.total}):\\n${summary}`,\n },\n ],\n };\n } else if (action === 'add') {\n if (!body) throw new Error('코멘트 내용을 입력해주세요.');\n const comment = await commentApi.addComment(issueKey, body);\n return {\n content: [\n { type: 'text', text: `코멘트 추가 성공 (ID: ${comment.id})` },\n ],\n };\n } else if (action === 'update') {\n if (!commentId) throw new Error('코멘트 ID를 입력해주세요.');\n if (!body) throw new Error('코멘트 내용을 입력해주세요.');\n await commentApi.updateComment(issueKey, commentId, body);\n return {\n content: [{ type: 'text', text: `코멘트 수정 성공 (ID: ${commentId})` }],\n };\n } else {\n if (!commentId) throw new Error('코멘트 ID를 입력해주세요.');\n await commentApi.deleteComment(issueKey, commentId);\n return {\n content: [{ type: 'text', text: `코멘트 삭제 성공 (ID: ${commentId})` }],\n };\n }\n },\n );\n\n // 프로젝트/보드 조회\n server.tool(\n 'jira_get_projects',\n 'TDE JIRA 프로젝트 목록 또는 Agile 보드/스프린트를 조회합니다.',\n {\n action: z\n .enum(['projects', 'project', 'boards', 'sprints'])\n .describe('조회 유형'),\n projectKey: z\n .string()\n .optional()\n .describe('프로젝트 키 (project, boards 조회 시)'),\n boardId: z\n .number()\n .optional()\n .describe('보드 ID (sprints 조회 시 필수)'),\n sprintState: z\n .string()\n .optional()\n .describe('스프린트 상태 필터 (active, closed, future)'),\n },\n async ({ action, projectKey, boardId, sprintState }) => {\n if (action === 'projects') {\n const projects = await projectApi.getProjects();\n const summary = projects\n .map((p) => `- [${p.key}] ${p.name} (Lead: ${p.lead?.displayName || 'N/A'})`)\n .join('\\n');\n return {\n content: [{ type: 'text', text: `프로젝트 목록:\\n${summary}` }],\n };\n } else if (action === 'project') {\n if (!projectKey) throw new Error('프로젝트 키를 입력해주세요.');\n const project = await projectApi.getProject(projectKey);\n const issueTypes = project.issueTypes\n ?.map((t) => t.name)\n .join(', ');\n return {\n content: [\n {\n type: 'text',\n text: `프로젝트: ${project.name} (${project.key})\\nLead: ${project.lead?.displayName || 'N/A'}\\nIssue Types: ${issueTypes || 'N/A'}`,\n },\n ],\n };\n } else if (action === 'boards') {\n const result = await projectApi.getBoards(projectKey);\n const summary = result.values\n .map((b) => `- [${b.id}] ${b.name} (${b.type})`)\n .join('\\n');\n return {\n content: [{ type: 'text', text: `보드 목록:\\n${summary}` }],\n };\n } else {\n if (!boardId) throw new Error('보드 ID를 입력해주세요.');\n const result = await projectApi.getSprints(boardId, sprintState);\n const summary = result.values\n .map(\n (s) =>\n `- [${s.id}] ${s.name} (${s.state})${s.goal ? ' - ' + s.goal : ''}`,\n )\n .join('\\n');\n return {\n content: [{ type: 'text', text: `스프린트 목록:\\n${summary}` }],\n };\n }\n },\n );\n } catch (error) {\n logger.warn(\n `JIRA 설정 로드 실패 또는 도구 등록 중 오류 발생: ${(error as Error).message}`,\n );\n }\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { GitlabProjectApi } from '../api/project.js';\nimport { GitlabMergeRequestApi } from '../api/merge-request.js';\nimport { GitlabPipelineApi } from '../api/pipeline.js';\nimport { GitlabBranchApi } from '../api/branch.js';\nimport { GitlabRepositoryApi } from '../api/repository.js';\nimport { createGitlabClient } from '../api/client.js';\nimport { loadGitlabConfig } from '../../common/config.js';\nimport { logger } from '../../common/logger.js';\n\nexport function registerGitlabTools(server: McpServer) {\n try {\n const config = loadGitlabConfig();\n const client = createGitlabClient(config);\n\n const projectApi = new GitlabProjectApi(client);\n const mrApi = new GitlabMergeRequestApi(client);\n const pipelineApi = new GitlabPipelineApi(client);\n const branchApi = new GitlabBranchApi(client);\n const repoApi = new GitlabRepositoryApi(client);\n\n // 1. 프로젝트 조회\n server.tool(\n 'gitlab_get_project',\n 'TDE GitLab 프로젝트를 조회합니다. projectId 지정 시 상세 조회, 미지정 시 목록 조회합니다.',\n {\n projectId: z\n .number()\n .optional()\n .describe('프로젝트 ID (지정 시 상세 조회)'),\n search: z.string().optional().describe('프로젝트명 검색 (목록 조회 시)'),\n owned: z.boolean().optional().describe('소유 프로젝트만 필터'),\n membership: z.boolean().optional().describe('멤버십 프로젝트만 필터'),\n },\n async ({ projectId, search, owned, membership }) => {\n if (projectId) {\n const project = await projectApi.getProject(projectId);\n const lines = [\n `ID: ${project.id}`,\n `Name: ${project.name_with_namespace}`,\n `Path: ${project.path_with_namespace}`,\n `Default Branch: ${project.default_branch}`,\n `Visibility: ${project.visibility}`,\n `Web URL: ${project.web_url}`,\n `SSH URL: ${project.ssh_url_to_repo}`,\n `HTTP URL: ${project.http_url_to_repo}`,\n `Last Activity: ${project.last_activity_at}`,\n ];\n if (project.description) {\n lines.push(`Description: ${project.description}`);\n }\n return { content: [{ type: 'text', text: lines.join('\\n') }] };\n } else {\n const projects = await projectApi.getProjects({\n search,\n owned,\n membership,\n });\n const summary = projects\n .map(\n (p) =>\n `- [${p.id}] ${p.name_with_namespace} (${p.visibility}) - ${p.web_url}`,\n )\n .join('\\n');\n return {\n content: [\n {\n type: 'text',\n text: `프로젝트 목록 (${projects.length}건):\\n${summary}`,\n },\n ],\n };\n }\n },\n );\n\n // 2. MR 조회\n server.tool(\n 'gitlab_get_merge_request',\n 'TDE GitLab Merge Request를 조회합니다. mrIid 지정 시 상세 조회, 미지정 시 목록 조회합니다.',\n {\n projectId: z.number().describe('프로젝트 ID'),\n mrIid: z.number().optional().describe('MR IID (지정 시 상세 조회)'),\n state: z\n .enum(['opened', 'closed', 'merged', 'all'])\n .optional()\n .describe('상태 필터 (목록 조회 시)'),\n includeChanges: z\n .boolean()\n .optional()\n .describe('변경 파일 포함 여부 (상세 조회 시)'),\n },\n async ({ projectId, mrIid, state, includeChanges }) => {\n if (mrIid) {\n const mr = includeChanges\n ? await mrApi.getMergeRequestChanges(projectId, mrIid)\n : await mrApi.getMergeRequest(projectId, mrIid);\n const lines = [\n `IID: !${mr.iid}`,\n `Title: ${mr.title}`,\n `State: ${mr.state}`,\n `Source: ${mr.source_branch} → Target: ${mr.target_branch}`,\n `Author: ${mr.author?.name || 'N/A'}`,\n `Assignee: ${mr.assignee?.name || '미배정'}`,\n `Merge Status: ${mr.merge_status}`,\n `Has Conflicts: ${mr.has_conflicts}`,\n `Pipeline: ${mr.pipeline?.status || 'N/A'}`,\n `Web URL: ${mr.web_url}`,\n `Created: ${mr.created_at}`,\n `Updated: ${mr.updated_at}`,\n ];\n if (mr.description) {\n lines.push('', '--- Description ---', mr.description);\n }\n if (mr.changes && mr.changes.length > 0) {\n lines.push(\n '',\n `--- Changes (${mr.changes.length}개 파일) ---`,\n ...mr.changes.map(\n (c) =>\n `- ${c.new_file ? '[NEW] ' : c.deleted_file ? '[DEL] ' : c.renamed_file ? '[REN] ' : ''}${c.new_path}`,\n ),\n );\n }\n return { content: [{ type: 'text', text: lines.join('\\n') }] };\n } else {\n const mrs = await mrApi.getMergeRequests(projectId, { state });\n const summary = mrs\n .map(\n (m) =>\n `- [!${m.iid}] ${m.title} (${m.state}, ${m.source_branch} → ${m.target_branch}) by ${m.author?.name || 'N/A'}`,\n )\n .join('\\n');\n return {\n content: [\n {\n type: 'text',\n text: `MR 목록 (${mrs.length}건):\\n${summary}`,\n },\n ],\n };\n }\n },\n );\n\n // 3. MR 생성\n server.tool(\n 'gitlab_create_merge_request',\n 'TDE GitLab에 새 Merge Request를 생성합니다.',\n {\n projectId: z.number().describe('프로젝트 ID'),\n sourceBranch: z.string().describe('소스 브랜치'),\n targetBranch: z.string().describe('타겟 브랜치'),\n title: z.string().describe('MR 제목'),\n description: z.string().optional().describe('MR 설명'),\n },\n async ({ projectId, sourceBranch, targetBranch, title, description }) => {\n const mr = await mrApi.createMergeRequest(projectId, {\n source_branch: sourceBranch,\n target_branch: targetBranch,\n title,\n description,\n });\n return {\n content: [\n {\n type: 'text',\n text: `MR 생성 성공: !${mr.iid}\\nTitle: ${mr.title}\\nURL: ${mr.web_url}`,\n },\n ],\n };\n },\n );\n\n // 4. MR 관리 (머지/닫기/재열기/코멘트)\n server.tool(\n 'gitlab_manage_merge_request',\n 'TDE GitLab MR를 관리합니다. 머지, 닫기, 재열기, 코멘트 추가 작업을 수행합니다.',\n {\n projectId: z.number().describe('프로젝트 ID'),\n mrIid: z.number().describe('MR IID'),\n action: z\n .enum(['merge', 'close', 'reopen', 'comment'])\n .describe('작업 유형'),\n comment: z.string().optional().describe('코멘트 내용 (action=comment 시 필수)'),\n squash: z.boolean().optional().describe('스쿼시 머지 (action=merge 시)'),\n removeSourceBranch: z\n .boolean()\n .optional()\n .describe('소스 브랜치 삭제 (action=merge 시)'),\n },\n async ({ projectId, mrIid, action, comment, squash, removeSourceBranch }) => {\n if (action === 'merge') {\n const mr = await mrApi.mergeMergeRequest(projectId, mrIid, {\n squash,\n should_remove_source_branch: removeSourceBranch,\n });\n return {\n content: [\n {\n type: 'text',\n text: `MR !${mrIid} 머지 성공\\nState: ${mr.state}`,\n },\n ],\n };\n } else if (action === 'close' || action === 'reopen') {\n const mr = await mrApi.updateMergeRequest(projectId, mrIid, {\n state_event: action,\n });\n const msg = action === 'close' ? '닫기' : '재열기';\n return {\n content: [\n {\n type: 'text',\n text: `MR !${mrIid} ${msg} 성공\\nState: ${mr.state}`,\n },\n ],\n };\n } else {\n if (!comment) throw new Error('코멘트 내용을 입력해주세요.');\n const note = await mrApi.addMergeRequestNote(projectId, mrIid, comment);\n return {\n content: [\n {\n type: 'text',\n text: `MR !${mrIid}에 코멘트 추가 성공 (ID: ${note.id})`,\n },\n ],\n };\n }\n },\n );\n\n // 5. 파이프라인 조회\n server.tool(\n 'gitlab_get_pipelines',\n 'TDE GitLab CI/CD 파이프라인을 조회합니다. pipelineId 지정 시 상세 조회, 미지정 시 목록 조회합니다.',\n {\n projectId: z.number().describe('프로젝트 ID'),\n pipelineId: z.number().optional().describe('파이프라인 ID (지정 시 상세 조회)'),\n status: z.string().optional().describe('상태 필터 (running, success, failed 등)'),\n ref: z.string().optional().describe('브랜치/태그 필터'),\n includeJobs: z.boolean().optional().describe('작업(Job) 목록 포함 여부'),\n },\n async ({ projectId, pipelineId, status, ref, includeJobs }) => {\n if (pipelineId) {\n const pipeline = await pipelineApi.getPipeline(projectId, pipelineId);\n const lines = [\n `ID: ${pipeline.id}`,\n `Status: ${pipeline.status}`,\n `Ref: ${pipeline.ref}`,\n `SHA: ${pipeline.sha}`,\n `Created: ${pipeline.created_at}`,\n `Duration: ${pipeline.duration ? pipeline.duration + 's' : 'N/A'}`,\n `Web URL: ${pipeline.web_url}`,\n ];\n if (includeJobs) {\n const jobs = await pipelineApi.getPipelineJobs(projectId, pipelineId);\n lines.push(\n '',\n `--- Jobs (${jobs.length}개) ---`,\n ...jobs.map(\n (j) =>\n `- [${j.stage}] ${j.name}: ${j.status} (${j.duration ? j.duration + 's' : 'N/A'})`,\n ),\n );\n }\n return { content: [{ type: 'text', text: lines.join('\\n') }] };\n } else {\n const pipelines = await pipelineApi.getPipelines(projectId, {\n status,\n ref,\n });\n const summary = pipelines\n .map(\n (p) =>\n `- [${p.id}] ${p.status} (ref: ${p.ref}, sha: ${p.sha.substring(0, 8)}) ${p.web_url}`,\n )\n .join('\\n');\n return {\n content: [\n {\n type: 'text',\n text: `파이프라인 목록 (${pipelines.length}건):\\n${summary}`,\n },\n ],\n };\n }\n },\n );\n\n // 6. 브랜치 관리\n server.tool(\n 'gitlab_manage_branches',\n 'TDE GitLab 브랜치를 관리합니다. 목록 조회, 상세 조회, 생성, 삭제 작업을 수행합니다.',\n {\n projectId: z.number().describe('프로젝트 ID'),\n action: z\n .enum(['list', 'get', 'create', 'delete'])\n .describe('작업 유형'),\n branchName: z\n .string()\n .optional()\n .describe('브랜치 이름 (get/create/delete 시 필수)'),\n ref: z.string().optional().describe('기준 ref (create 시 필수)'),\n search: z.string().optional().describe('검색 키워드 (list 시)'),\n },\n async ({ projectId, action, branchName, ref, search }) => {\n if (action === 'list') {\n const branches = await branchApi.getBranches(projectId, { search });\n const summary = branches\n .map(\n (b) =>\n `- ${b.name}${b.default ? ' [default]' : ''}${b.protected ? ' [protected]' : ''} (${b.commit.short_id}: ${b.commit.message.split('\\n')[0]})`,\n )\n .join('\\n');\n return {\n content: [\n {\n type: 'text',\n text: `브랜치 목록 (${branches.length}건):\\n${summary}`,\n },\n ],\n };\n } else if (action === 'get') {\n if (!branchName) throw new Error('브랜치 이름을 입력해주세요.');\n const branch = await branchApi.getBranch(projectId, branchName);\n const lines = [\n `Name: ${branch.name}`,\n `Default: ${branch.default}`,\n `Protected: ${branch.protected}`,\n `Merged: ${branch.merged}`,\n `Commit: ${branch.commit.id}`,\n `Message: ${branch.commit.message}`,\n `Author: ${branch.commit.author_name}`,\n `Date: ${branch.commit.authored_date}`,\n ];\n return { content: [{ type: 'text', text: lines.join('\\n') }] };\n } else if (action === 'create') {\n if (!branchName) throw new Error('브랜치 이름을 입력해주세요.');\n if (!ref) throw new Error('기준 ref를 입력해주세요.');\n const branch = await branchApi.createBranch(projectId, branchName, ref);\n return {\n content: [\n {\n type: 'text',\n text: `브랜치 생성 성공: ${branch.name} (from ${ref})`,\n },\n ],\n };\n } else {\n if (!branchName) throw new Error('브랜치 이름을 입력해주세요.');\n await branchApi.deleteBranch(projectId, branchName);\n return {\n content: [\n { type: 'text', text: `브랜치 삭제 성공: ${branchName}` },\n ],\n };\n }\n },\n );\n\n // 7. 파일/트리 조회\n server.tool(\n 'gitlab_get_file',\n 'TDE GitLab 저장소의 파일 내용 또는 디렉토리 트리를 조회합니다.',\n {\n projectId: z.number().describe('프로젝트 ID'),\n path: z.string().describe('파일 경로 또는 디렉토리 경로'),\n ref: z.string().optional().describe('브랜치/태그/커밋 (기본: 기본 브랜치)'),\n type: z\n .enum(['file', 'tree'])\n .optional()\n .describe('\"file\" (기본) 또는 \"tree\"'),\n recursive: z.boolean().optional().describe('재귀 조회 (type=tree 시)'),\n },\n async ({ projectId, path, ref, type, recursive }) => {\n if (type === 'tree') {\n const entries = await repoApi.getTree(projectId, {\n path,\n ref,\n recursive,\n });\n const summary = entries\n .map((e) => `${e.type === 'tree' ? '📁' : '📄'} ${e.path}`)\n .join('\\n');\n return {\n content: [\n {\n type: 'text',\n text: `디렉토리 트리 (${entries.length}개 항목):\\n${summary}`,\n },\n ],\n };\n } else {\n const file = await repoApi.getFile(projectId, path, ref);\n const lines = [\n `File: ${file.file_path}`,\n `Size: ${file.size} bytes`,\n `Ref: ${file.ref}`,\n `Last Commit: ${file.last_commit_id}`,\n '',\n '--- Content ---',\n file.content,\n ];\n return { content: [{ type: 'text', text: lines.join('\\n') }] };\n }\n },\n );\n } catch (error) {\n logger.warn(\n `GitLab 설정 로드 실패 또는 도구 등록 중 오류 발생: ${(error as Error).message}`,\n );\n }\n}\n"],"mappings":"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","names":["z","z","z","z"]}
|
|
@@ -1,24 +1,39 @@
|
|
|
1
|
-
//
|
|
1
|
+
// tools/common/logger.ts
|
|
2
2
|
var Logger = class {
|
|
3
3
|
level = "info";
|
|
4
4
|
constructor() {
|
|
5
5
|
this.level = process.env.LOG_LEVEL || "info";
|
|
6
6
|
}
|
|
7
|
+
// 로그 레벨 설정
|
|
8
|
+
setLevel(level) {
|
|
9
|
+
this.level = level;
|
|
10
|
+
}
|
|
7
11
|
// 로그 메시지 포맷팅 (타임스탬프, 레벨 포함)
|
|
8
12
|
formatMessage(level, message, ...args) {
|
|
9
13
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
10
14
|
let formattedMessage = `[${timestamp}] [${level.toUpperCase()}] ${message}`;
|
|
11
15
|
if (args.length > 0) {
|
|
12
|
-
formattedMessage += " " + args.map(
|
|
13
|
-
(arg
|
|
14
|
-
|
|
16
|
+
formattedMessage += " " + args.map((arg) => {
|
|
17
|
+
if (arg instanceof Error) {
|
|
18
|
+
return arg.stack || arg.message;
|
|
19
|
+
}
|
|
20
|
+
return typeof arg === "object" ? JSON.stringify(arg, null, 2) : String(arg);
|
|
21
|
+
}).join(" ");
|
|
15
22
|
}
|
|
16
23
|
formattedMessage = this.maskSensitiveData(formattedMessage);
|
|
17
24
|
return formattedMessage;
|
|
18
25
|
}
|
|
19
26
|
// 민감한 데이터(키, 패스워드 등) 마스킹 처리
|
|
20
27
|
maskSensitiveData(text) {
|
|
21
|
-
|
|
28
|
+
let masked = text.replace(/(Authorization["']?\s*[:=]\s*["']?)(Basic|Bearer)\s+([^"'\s]+)(["']?)/gi, (match, p1, p2, p3, p4) => {
|
|
29
|
+
const maskedToken = p3.length > 8 ? p3.substring(0, 4) + "..." + p3.substring(p3.length - 4) : "******";
|
|
30
|
+
return `${p1}${p2} ${maskedToken}${p4}`;
|
|
31
|
+
});
|
|
32
|
+
masked = masked.replace(/((?:token|password|secret|key|api_token|private_token)["']?\s*[:=]\s*["']?)([^"'\s]+)(["']?)/gi, (match, p1, p2, p3) => {
|
|
33
|
+
const maskedValue = p2.length > 8 ? p2.substring(0, 4) + "..." + p2.substring(p2.length - 4) : "******";
|
|
34
|
+
return `${p1}${maskedValue}${p3}`;
|
|
35
|
+
});
|
|
36
|
+
return masked;
|
|
22
37
|
}
|
|
23
38
|
// Debug 레벨 로그 출력
|
|
24
39
|
debug(message, ...args) {
|
|
@@ -55,4 +70,4 @@ var logger = new Logger();
|
|
|
55
70
|
export {
|
|
56
71
|
logger
|
|
57
72
|
};
|
|
58
|
-
//# sourceMappingURL=chunk-
|
|
73
|
+
//# sourceMappingURL=chunk-IFYMZLQI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../tools/common/logger.ts"],"sourcesContent":["export type LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\nclass Logger {\n private level: LogLevel = 'info';\n\n constructor() {\n this.level = (process.env.LOG_LEVEL as LogLevel) || 'info';\n }\n\n // 로그 레벨 설정\n setLevel(level: LogLevel) {\n this.level = level;\n }\n\n // 로그 메시지 포맷팅 (타임스탬프, 레벨 포함)\n private formatMessage(level: LogLevel, message: string, ...args: any[]): string {\n const timestamp = new Date().toISOString();\n let formattedMessage = `[${timestamp}] [${level.toUpperCase()}] ${message}`;\n\n if (args.length > 0) {\n formattedMessage += ' ' + args.map(arg => {\n if (arg instanceof Error) {\n return arg.stack || arg.message;\n }\n return typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg);\n }).join(' ');\n }\n\n // 민감 정보 마스킹 (토큰, 비밀번호 등)\n formattedMessage = this.maskSensitiveData(formattedMessage);\n\n return formattedMessage;\n }\n\n // 민감한 데이터(키, 패스워드 등) 마스킹 처리\n private maskSensitiveData(text: string): string {\n // 1. Authorization 헤더 마스킹 (Basic ***, Bearer ***)\n let masked = text.replace(/(Authorization[\"']?\\s*[:=]\\s*[\"']?)(Basic|Bearer)\\s+([^\"'\\s]+)([\"']?)/gi, (match, p1, p2, p3, p4) => {\n const maskedToken = p3.length > 8 ? p3.substring(0, 4) + '...' + p3.substring(p3.length - 4) : '******';\n return `${p1}${p2} ${maskedToken}${p4}`;\n });\n\n // 2. 키워드 기반 마스킹 (token, password, secret, key 등)\n // 헤더 이름(PRIVATE-TOKEN 등)과 변수명 모두 대응 가능하도록 개선\n masked = masked.replace(/((?:token|password|secret|key|api_token|private_token)[\"']?\\s*[:=]\\s*[\"']?)([^\"'\\s]+)([\"']?)/gi, (match, p1, p2, p3) => {\n const maskedValue = p2.length > 8 ? p2.substring(0, 4) + '...' + p2.substring(p2.length - 4) : '******';\n return `${p1}${maskedValue}${p3}`;\n });\n\n return masked;\n }\n\n // Debug 레벨 로그 출력\n debug(message: string, ...args: any[]) {\n if (this.shouldLog('debug')) {\n console.error(this.formatMessage('debug', message, ...args));\n }\n }\n\n // Info 레벨 로그 출력\n info(message: string, ...args: any[]) {\n if (this.shouldLog('info')) {\n console.error(this.formatMessage('info', message, ...args));\n }\n }\n\n // Warn 레벨 로그 출력\n warn(message: string, ...args: any[]) {\n if (this.shouldLog('warn')) {\n console.error(this.formatMessage('warn', message, ...args));\n }\n }\n\n // Error 레벨 로그 출력\n error(message: string, ...args: any[]) {\n if (this.shouldLog('error')) {\n console.error(this.formatMessage('error', message, ...args));\n }\n }\n\n // 현재 설정된 로그 레벨에 따라 출력 여부 결정\n private shouldLog(level: LogLevel): boolean {\n const levels: LogLevel[] = ['debug', 'info', 'warn', 'error'];\n return levels.indexOf(level) >= levels.indexOf(this.level);\n }\n}\n\nexport const logger = new Logger();\n"],"mappings":";AAEA,IAAM,SAAN,MAAa;AAAA,EACD,QAAkB;AAAA,EAE1B,cAAc;AACV,SAAK,QAAS,QAAQ,IAAI,aAA0B;AAAA,EACxD;AAAA;AAAA,EAGA,SAAS,OAAiB;AACtB,SAAK,QAAQ;AAAA,EACjB;AAAA;AAAA,EAGQ,cAAc,OAAiB,YAAoB,MAAqB;AAC5E,UAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AACzC,QAAI,mBAAmB,IAAI,SAAS,MAAM,MAAM,YAAY,CAAC,KAAK,OAAO;AAEzE,QAAI,KAAK,SAAS,GAAG;AACjB,0BAAoB,MAAM,KAAK,IAAI,SAAO;AACtC,YAAI,eAAe,OAAO;AACtB,iBAAO,IAAI,SAAS,IAAI;AAAA,QAC5B;AACA,eAAO,OAAO,QAAQ,WAAW,KAAK,UAAU,KAAK,MAAM,CAAC,IAAI,OAAO,GAAG;AAAA,MAC9E,CAAC,EAAE,KAAK,GAAG;AAAA,IACf;AAGA,uBAAmB,KAAK,kBAAkB,gBAAgB;AAE1D,WAAO;AAAA,EACX;AAAA;AAAA,EAGQ,kBAAkB,MAAsB;AAE5C,QAAI,SAAS,KAAK,QAAQ,2EAA2E,CAAC,OAAO,IAAI,IAAI,IAAI,OAAO;AAC5H,YAAM,cAAc,GAAG,SAAS,IAAI,GAAG,UAAU,GAAG,CAAC,IAAI,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC,IAAI;AAC/F,aAAO,GAAG,EAAE,GAAG,EAAE,IAAI,WAAW,GAAG,EAAE;AAAA,IACzC,CAAC;AAID,aAAS,OAAO,QAAQ,kGAAkG,CAAC,OAAO,IAAI,IAAI,OAAO;AAC7I,YAAM,cAAc,GAAG,SAAS,IAAI,GAAG,UAAU,GAAG,CAAC,IAAI,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC,IAAI;AAC/F,aAAO,GAAG,EAAE,GAAG,WAAW,GAAG,EAAE;AAAA,IACnC,CAAC;AAED,WAAO;AAAA,EACX;AAAA;AAAA,EAGA,MAAM,YAAoB,MAAa;AACnC,QAAI,KAAK,UAAU,OAAO,GAAG;AACzB,cAAQ,MAAM,KAAK,cAAc,SAAS,SAAS,GAAG,IAAI,CAAC;AAAA,IAC/D;AAAA,EACJ;AAAA;AAAA,EAGA,KAAK,YAAoB,MAAa;AAClC,QAAI,KAAK,UAAU,MAAM,GAAG;AACxB,cAAQ,MAAM,KAAK,cAAc,QAAQ,SAAS,GAAG,IAAI,CAAC;AAAA,IAC9D;AAAA,EACJ;AAAA;AAAA,EAGA,KAAK,YAAoB,MAAa;AAClC,QAAI,KAAK,UAAU,MAAM,GAAG;AACxB,cAAQ,MAAM,KAAK,cAAc,QAAQ,SAAS,GAAG,IAAI,CAAC;AAAA,IAC9D;AAAA,EACJ;AAAA;AAAA,EAGA,MAAM,YAAoB,MAAa;AACnC,QAAI,KAAK,UAAU,OAAO,GAAG;AACzB,cAAQ,MAAM,KAAK,cAAc,SAAS,SAAS,GAAG,IAAI,CAAC;AAAA,IAC/D;AAAA,EACJ;AAAA;AAAA,EAGQ,UAAU,OAA0B;AACxC,UAAM,SAAqB,CAAC,SAAS,QAAQ,QAAQ,OAAO;AAC5D,WAAO,OAAO,QAAQ,KAAK,KAAK,OAAO,QAAQ,KAAK,KAAK;AAAA,EAC7D;AACJ;AAEO,IAAM,SAAS,IAAI,OAAO;","names":[]}
|