docuking-mcp 2.11.0 → 2.12.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.
- package/handlers/sync.js +109 -11
- package/lib/index-cache.js +242 -0
- package/lib/version-check.js +15 -1
- package/package.json +1 -1
package/handlers/sync.js
CHANGED
|
@@ -37,6 +37,17 @@ import {
|
|
|
37
37
|
|
|
38
38
|
import { executeKingcast } from './kingcast.js';
|
|
39
39
|
|
|
40
|
+
import {
|
|
41
|
+
loadIndex,
|
|
42
|
+
saveIndex,
|
|
43
|
+
detectChanges,
|
|
44
|
+
updateIndexEntry,
|
|
45
|
+
removeIndexEntry,
|
|
46
|
+
updateIndexAfterSync,
|
|
47
|
+
computeFileHash,
|
|
48
|
+
getIndexStats,
|
|
49
|
+
} from '../lib/index-cache.js';
|
|
50
|
+
|
|
40
51
|
// 레포지토리 매핑 (메모리에 캐시)
|
|
41
52
|
const repoMapping = {};
|
|
42
53
|
|
|
@@ -522,6 +533,16 @@ docuking_init을 먼저 실행하세요.`,
|
|
|
522
533
|
let current = 0;
|
|
523
534
|
let skipped = 0;
|
|
524
535
|
|
|
536
|
+
// ========================================
|
|
537
|
+
// 로컬 인덱스 로드 (mtime 기반 변경 감지용)
|
|
538
|
+
// ========================================
|
|
539
|
+
const localIndex = loadIndex(localPath);
|
|
540
|
+
const indexStats = getIndexStats(localIndex);
|
|
541
|
+
console.error(`[DocuKing] 로컬 인덱스: ${indexStats.fileCount}개 파일 (${indexStats.totalSizeMB}MB)`);
|
|
542
|
+
|
|
543
|
+
// 동기화 후 인덱스 업데이트용 배열
|
|
544
|
+
const syncedFiles = [];
|
|
545
|
+
|
|
525
546
|
// 시작 안내 메시지 출력 (AI가 사용자에게 전달할 수 있도록)
|
|
526
547
|
console.error(`[DocuKing] Push 시작: ${total}개 파일 (총 ${totalSizeMB.toFixed(1)}MB)`);
|
|
527
548
|
console.error(`[DocuKing] 💡 실시간 진행상황은 DocuKing 웹(https://docuking.ai)에서 확인하세요`);
|
|
@@ -582,32 +603,75 @@ docuking_init을 먼저 실행하세요.`,
|
|
|
582
603
|
}
|
|
583
604
|
|
|
584
605
|
try {
|
|
585
|
-
//
|
|
606
|
+
// ========================================
|
|
607
|
+
// 로컬 인덱스 기반 mtime 체크 (해시 계산 최소화)
|
|
608
|
+
// ========================================
|
|
609
|
+
const cached = localIndex.files[file.serverPath];
|
|
586
610
|
let fileHash;
|
|
587
611
|
let content;
|
|
588
612
|
let encoding = 'utf-8';
|
|
613
|
+
let usedCache = false;
|
|
589
614
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
615
|
+
// mtime 비교: 변경되지 않았으면 캐시된 해시 사용
|
|
616
|
+
if (cached) {
|
|
617
|
+
try {
|
|
618
|
+
const stat = fs.statSync(file.fullPath);
|
|
619
|
+
const currentMtime = stat.mtimeMs;
|
|
620
|
+
|
|
621
|
+
// mtime 동일 (1초 오차 허용) + 서버 해시와도 동일 → 완전 스킵
|
|
622
|
+
if (Math.abs(currentMtime - cached.mtime) < 1000) {
|
|
623
|
+
if (serverPathToHash[file.serverPath] === cached.hash) {
|
|
624
|
+
const resultText = `${progress} ⊘ ${file.serverPath} (변경 없음)`;
|
|
625
|
+
results.push(resultText);
|
|
626
|
+
skipped++;
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
// mtime 동일하지만 서버 해시 다름 → 캐시 해시 사용, 업로드 필요
|
|
630
|
+
fileHash = cached.hash;
|
|
631
|
+
usedCache = true;
|
|
632
|
+
}
|
|
633
|
+
} catch (e) {
|
|
634
|
+
// stat 실패 시 해시 계산으로 폴백
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// 캐시 사용 못하면 파일 읽어서 해시 계산
|
|
639
|
+
if (!usedCache) {
|
|
640
|
+
if (file.fileType === 'binary') {
|
|
641
|
+
// 바이너리 파일은 Base64로 인코딩
|
|
642
|
+
const buffer = fs.readFileSync(file.fullPath);
|
|
643
|
+
fileHash = crypto.createHash('sha256').update(buffer).digest('hex');
|
|
644
|
+
content = buffer.toString('base64');
|
|
645
|
+
encoding = 'base64';
|
|
646
|
+
} else {
|
|
647
|
+
// 텍스트 파일은 UTF-8
|
|
648
|
+
content = fs.readFileSync(file.fullPath, 'utf-8');
|
|
649
|
+
fileHash = crypto.createHash('sha256').update(content, 'utf-8').digest('hex');
|
|
650
|
+
}
|
|
600
651
|
}
|
|
601
652
|
|
|
602
653
|
// Git 스타일 동기화:
|
|
603
654
|
// 1. 같은 경로 + 같은 해시 → 스킵 (변경 없음)
|
|
604
655
|
if (serverPathToHash[file.serverPath] === fileHash) {
|
|
656
|
+
// 인덱스 업데이트 (다음 Push 때 스킵 가능하도록)
|
|
657
|
+
syncedFiles.push({ serverPath: file.serverPath, fullPath: file.fullPath, hash: fileHash });
|
|
605
658
|
const resultText = `${progress} ⊘ ${file.serverPath} (변경 없음)`;
|
|
606
659
|
results.push(resultText);
|
|
607
660
|
skipped++;
|
|
608
661
|
continue;
|
|
609
662
|
}
|
|
610
663
|
|
|
664
|
+
// 업로드 필요 → content가 없으면 파일 읽기
|
|
665
|
+
if (!content) {
|
|
666
|
+
if (file.fileType === 'binary') {
|
|
667
|
+
const buffer = fs.readFileSync(file.fullPath);
|
|
668
|
+
content = buffer.toString('base64');
|
|
669
|
+
encoding = 'base64';
|
|
670
|
+
} else {
|
|
671
|
+
content = fs.readFileSync(file.fullPath, 'utf-8');
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
611
675
|
// 2. 같은 해시가 다른 경로에 있음 → Move (경로 변경)
|
|
612
676
|
const existingPath = serverHashToPath[fileHash];
|
|
613
677
|
if (existingPath && existingPath !== file.serverPath) {
|
|
@@ -632,6 +696,9 @@ docuking_init을 먼저 실행하세요.`,
|
|
|
632
696
|
// 이동된 파일의 해시 맵 업데이트
|
|
633
697
|
delete serverHashToPath[fileHash];
|
|
634
698
|
serverHashToPath[fileHash] = file.serverPath;
|
|
699
|
+
// 인덱스에 이동된 파일 기록 (기존 경로 삭제, 새 경로 추가)
|
|
700
|
+
removeIndexEntry(localIndex, existingPath);
|
|
701
|
+
syncedFiles.push({ serverPath: file.serverPath, fullPath: file.fullPath, hash: fileHash });
|
|
635
702
|
continue;
|
|
636
703
|
}
|
|
637
704
|
} catch (e) {
|
|
@@ -684,6 +751,8 @@ docuking_init을 먼저 실행하세요.`,
|
|
|
684
751
|
: `${progress} ✓ ${file.path}`;
|
|
685
752
|
results.push(resultText);
|
|
686
753
|
success = true;
|
|
754
|
+
// 인덱스에 업로드된 파일 기록
|
|
755
|
+
syncedFiles.push({ serverPath: file.serverPath, fullPath: file.fullPath, hash: fileHash });
|
|
687
756
|
break; // 성공하면 재시도 중단
|
|
688
757
|
} else {
|
|
689
758
|
const error = await response.text();
|
|
@@ -843,6 +912,16 @@ docuking_init을 먼저 실행하세요.`,
|
|
|
843
912
|
}
|
|
844
913
|
}
|
|
845
914
|
|
|
915
|
+
// ========================================
|
|
916
|
+
// 로컬 인덱스 업데이트 및 저장
|
|
917
|
+
// ========================================
|
|
918
|
+
if (syncedFiles.length > 0) {
|
|
919
|
+
updateIndexAfterSync(localIndex, syncedFiles, deletedFilePaths);
|
|
920
|
+
if (saveIndex(localPath, localIndex)) {
|
|
921
|
+
console.error(`[DocuKing] 로컬 인덱스 업데이트: ${syncedFiles.length}개 파일`);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
846
925
|
// Sync 완료 알림
|
|
847
926
|
try {
|
|
848
927
|
await fetch(`${API_ENDPOINT}/projects/${projectId}/sync/complete`, {
|
|
@@ -1142,6 +1221,12 @@ export async function handlePullInternal(args) {
|
|
|
1142
1221
|
// Pull 후 mark-as-pulled할 파일 목록 (source: web 파일들)
|
|
1143
1222
|
const filesToMarkAsPulled = [];
|
|
1144
1223
|
|
|
1224
|
+
// ========================================
|
|
1225
|
+
// 로컬 인덱스 로드 (Pull 후 업데이트용)
|
|
1226
|
+
// ========================================
|
|
1227
|
+
const localIndex = loadIndex(localPath);
|
|
1228
|
+
const syncedFiles = []; // 다운로드/업데이트된 파일 목록
|
|
1229
|
+
|
|
1145
1230
|
for (const file of sortedFiles) {
|
|
1146
1231
|
current++;
|
|
1147
1232
|
const progress = `${current}/${total}`;
|
|
@@ -1264,6 +1349,9 @@ export async function handlePullInternal(args) {
|
|
|
1264
1349
|
downloaded++;
|
|
1265
1350
|
}
|
|
1266
1351
|
|
|
1352
|
+
// 인덱스에 다운로드된 파일 기록
|
|
1353
|
+
syncedFiles.push({ serverPath: file.path, fullPath, hash: serverHash });
|
|
1354
|
+
|
|
1267
1355
|
// source: 'web' 파일이면 mark-as-pulled 대상
|
|
1268
1356
|
if (serverMeta && serverMeta.source === 'web') {
|
|
1269
1357
|
filesToMarkAsPulled.push(file.path);
|
|
@@ -1300,6 +1388,16 @@ export async function handlePullInternal(args) {
|
|
|
1300
1388
|
}
|
|
1301
1389
|
}
|
|
1302
1390
|
|
|
1391
|
+
// ========================================
|
|
1392
|
+
// 로컬 인덱스 업데이트 및 저장
|
|
1393
|
+
// ========================================
|
|
1394
|
+
if (syncedFiles.length > 0) {
|
|
1395
|
+
updateIndexAfterSync(localIndex, syncedFiles, []);
|
|
1396
|
+
if (saveIndex(localPath, localIndex)) {
|
|
1397
|
+
console.error(`[DocuKing] 로컬 인덱스 업데이트: ${syncedFiles.length}개 파일`);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1303
1401
|
// 상세 결과 구성 (Push 스타일)
|
|
1304
1402
|
let resultText = `✓ Pull 완료!
|
|
1305
1403
|
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocuKing MCP - 로컬 인덱스 캐시 모듈
|
|
3
|
+
*
|
|
4
|
+
* Git의 .git/index처럼 마지막 동기화 상태를 저장하여
|
|
5
|
+
* mtime 기반으로 변경된 파일만 빠르게 탐지합니다.
|
|
6
|
+
*
|
|
7
|
+
* 효과:
|
|
8
|
+
* - 변경되지 않은 파일은 해시 계산 스킵
|
|
9
|
+
* - Pull/Push 시 변경된 파일만 처리
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from 'fs';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import crypto from 'crypto';
|
|
15
|
+
|
|
16
|
+
const INDEX_VERSION = 1;
|
|
17
|
+
const INDEX_FILENAME = 'index.json';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 인덱스 파일 경로 반환
|
|
21
|
+
*/
|
|
22
|
+
function getIndexPath(localPath) {
|
|
23
|
+
return path.join(localPath, '.docuking', INDEX_FILENAME);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 로컬 인덱스 로드
|
|
28
|
+
* @returns {{ version: number, lastSync: string, files: Object }} 인덱스 객체
|
|
29
|
+
*/
|
|
30
|
+
export function loadIndex(localPath) {
|
|
31
|
+
const indexPath = getIndexPath(localPath);
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
if (fs.existsSync(indexPath)) {
|
|
35
|
+
const data = JSON.parse(fs.readFileSync(indexPath, 'utf-8'));
|
|
36
|
+
|
|
37
|
+
// 버전 체크 (호환되지 않으면 초기화)
|
|
38
|
+
if (data.version !== INDEX_VERSION) {
|
|
39
|
+
console.error('[DocuKing] 인덱스 버전 불일치, 재생성합니다.');
|
|
40
|
+
return createEmptyIndex();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return data;
|
|
44
|
+
}
|
|
45
|
+
} catch (e) {
|
|
46
|
+
console.error('[DocuKing] 인덱스 로드 실패:', e.message);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return createEmptyIndex();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 빈 인덱스 생성
|
|
54
|
+
*/
|
|
55
|
+
function createEmptyIndex() {
|
|
56
|
+
return {
|
|
57
|
+
version: INDEX_VERSION,
|
|
58
|
+
lastSync: null,
|
|
59
|
+
files: {},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 인덱스 저장
|
|
65
|
+
*/
|
|
66
|
+
export function saveIndex(localPath, index) {
|
|
67
|
+
const indexPath = getIndexPath(localPath);
|
|
68
|
+
const indexDir = path.dirname(indexPath);
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
if (!fs.existsSync(indexDir)) {
|
|
72
|
+
fs.mkdirSync(indexDir, { recursive: true });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
index.lastSync = new Date().toISOString();
|
|
76
|
+
fs.writeFileSync(indexPath, JSON.stringify(index, null, 2), 'utf-8');
|
|
77
|
+
|
|
78
|
+
return true;
|
|
79
|
+
} catch (e) {
|
|
80
|
+
console.error('[DocuKing] 인덱스 저장 실패:', e.message);
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 파일 해시 계산
|
|
87
|
+
*/
|
|
88
|
+
export function computeFileHash(filePath, isBinary = false) {
|
|
89
|
+
try {
|
|
90
|
+
const buffer = fs.readFileSync(filePath);
|
|
91
|
+
return crypto.createHash('sha256').update(buffer).digest('hex');
|
|
92
|
+
} catch (e) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* mtime 기반으로 변경된 파일 탐지
|
|
99
|
+
*
|
|
100
|
+
* @param {string} localPath - 프로젝트 루트 경로
|
|
101
|
+
* @param {Object} index - 로컬 인덱스
|
|
102
|
+
* @param {string[]} filePaths - 검사할 파일 경로 목록 (serverPath 형식)
|
|
103
|
+
* @param {Function} getFullPath - serverPath → fullPath 변환 함수
|
|
104
|
+
* @returns {{ changed: Array, unchanged: Array, newFiles: Array, deleted: Array }}
|
|
105
|
+
*/
|
|
106
|
+
export function detectChanges(localPath, index, filePaths, getFullPath) {
|
|
107
|
+
const result = {
|
|
108
|
+
changed: [], // mtime 변경됨 (해시 계산 필요)
|
|
109
|
+
unchanged: [], // mtime 동일 (해시 계산 스킵)
|
|
110
|
+
newFiles: [], // 인덱스에 없음 (새 파일)
|
|
111
|
+
deleted: [], // 로컬에 없음 (삭제됨)
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
for (const serverPath of filePaths) {
|
|
115
|
+
const fullPath = getFullPath(serverPath);
|
|
116
|
+
|
|
117
|
+
// 로컬 파일 존재 여부 확인
|
|
118
|
+
if (!fs.existsSync(fullPath)) {
|
|
119
|
+
result.deleted.push(serverPath);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 인덱스에 있는지 확인
|
|
124
|
+
const cached = index.files[serverPath];
|
|
125
|
+
if (!cached) {
|
|
126
|
+
result.newFiles.push(serverPath);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// mtime 비교
|
|
131
|
+
try {
|
|
132
|
+
const stat = fs.statSync(fullPath);
|
|
133
|
+
const currentMtime = stat.mtimeMs;
|
|
134
|
+
|
|
135
|
+
if (Math.abs(currentMtime - cached.mtime) < 1000) {
|
|
136
|
+
// mtime 동일 (1초 오차 허용) → 변경 없음
|
|
137
|
+
result.unchanged.push(serverPath);
|
|
138
|
+
} else {
|
|
139
|
+
// mtime 다름 → 변경됨
|
|
140
|
+
result.changed.push(serverPath);
|
|
141
|
+
}
|
|
142
|
+
} catch (e) {
|
|
143
|
+
// stat 실패 → 변경됨으로 처리
|
|
144
|
+
result.changed.push(serverPath);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* 인덱스에 파일 정보 업데이트
|
|
153
|
+
*/
|
|
154
|
+
export function updateIndexEntry(index, serverPath, fullPath, hash) {
|
|
155
|
+
try {
|
|
156
|
+
const stat = fs.statSync(fullPath);
|
|
157
|
+
|
|
158
|
+
index.files[serverPath] = {
|
|
159
|
+
hash,
|
|
160
|
+
mtime: stat.mtimeMs,
|
|
161
|
+
size: stat.size,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
return true;
|
|
165
|
+
} catch (e) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* 인덱스에서 파일 정보 삭제
|
|
172
|
+
*/
|
|
173
|
+
export function removeIndexEntry(index, serverPath) {
|
|
174
|
+
delete index.files[serverPath];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* 인덱스의 해시와 비교하여 실제 변경 여부 확인
|
|
179
|
+
* (mtime 변경됐지만 내용은 같을 수 있음)
|
|
180
|
+
*
|
|
181
|
+
* @returns {{ actuallyChanged: Array, sameContent: Array }}
|
|
182
|
+
*/
|
|
183
|
+
export function verifyChanges(index, changedPaths, getFullPath) {
|
|
184
|
+
const result = {
|
|
185
|
+
actuallyChanged: [], // 해시도 다름 (실제 변경)
|
|
186
|
+
sameContent: [], // 해시 동일 (mtime만 변경)
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
for (const serverPath of changedPaths) {
|
|
190
|
+
const fullPath = getFullPath(serverPath);
|
|
191
|
+
const cached = index.files[serverPath];
|
|
192
|
+
|
|
193
|
+
if (!cached) {
|
|
194
|
+
result.actuallyChanged.push(serverPath);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const currentHash = computeFileHash(fullPath);
|
|
199
|
+
|
|
200
|
+
if (currentHash === cached.hash) {
|
|
201
|
+
result.sameContent.push(serverPath);
|
|
202
|
+
} else {
|
|
203
|
+
result.actuallyChanged.push(serverPath);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* 동기화 후 인덱스 일괄 업데이트
|
|
212
|
+
*
|
|
213
|
+
* @param {Object} index - 인덱스 객체
|
|
214
|
+
* @param {Array} syncedFiles - 동기화된 파일 목록 [{ serverPath, fullPath, hash }]
|
|
215
|
+
* @param {Array} deletedPaths - 삭제된 파일 경로 목록
|
|
216
|
+
*/
|
|
217
|
+
export function updateIndexAfterSync(index, syncedFiles, deletedPaths = []) {
|
|
218
|
+
// 동기화된 파일 업데이트
|
|
219
|
+
for (const file of syncedFiles) {
|
|
220
|
+
updateIndexEntry(index, file.serverPath, file.fullPath, file.hash);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// 삭제된 파일 제거
|
|
224
|
+
for (const serverPath of deletedPaths) {
|
|
225
|
+
removeIndexEntry(index, serverPath);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* 인덱스 통계 반환
|
|
231
|
+
*/
|
|
232
|
+
export function getIndexStats(index) {
|
|
233
|
+
const files = Object.keys(index.files);
|
|
234
|
+
const totalSize = Object.values(index.files).reduce((sum, f) => sum + (f.size || 0), 0);
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
fileCount: files.length,
|
|
238
|
+
totalSize,
|
|
239
|
+
totalSizeMB: (totalSize / 1024 / 1024).toFixed(2),
|
|
240
|
+
lastSync: index.lastSync,
|
|
241
|
+
};
|
|
242
|
+
}
|
package/lib/version-check.js
CHANGED
|
@@ -110,9 +110,23 @@ export function getVersionWarningMessage(updateInfo) {
|
|
|
110
110
|
현재 버전: ${updateInfo.current}
|
|
111
111
|
최신 버전: ${updateInfo.latest}
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
【글로벌 설치 사용자】
|
|
114
114
|
npm update -g docuking-mcp
|
|
115
115
|
|
|
116
|
+
【npx 방식 사용자 (전환 필요)】
|
|
117
|
+
npx 캐시 문제로 글로벌 설치로 전환해야 합니다:
|
|
118
|
+
|
|
119
|
+
1. 글로벌 설치:
|
|
120
|
+
npm install -g docuking-mcp
|
|
121
|
+
|
|
122
|
+
2. MCP 설정 변경:
|
|
123
|
+
- Claude Code: claude mcp remove docuking && claude mcp add docuking -s user -- docuking-mcp
|
|
124
|
+
- Cursor/기타: ~/.cursor/mcp.json 등에서 아래와 같이 변경
|
|
125
|
+
Before: { "command": "npx", "args": ["-y", "docuking-mcp@latest"] }
|
|
126
|
+
After: { "command": "docuking-mcp", "args": [] }
|
|
127
|
+
|
|
128
|
+
3. AI 도구 재시작
|
|
129
|
+
|
|
116
130
|
새 버전에서 버그 수정 및 기능 개선이 있습니다.
|
|
117
131
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
118
132
|
`;
|