nworks-plus 1.3.0 → 1.4.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/README.ja.md +8 -8
- package/README.ko.md +24 -10
- package/README.md +23 -9
- package/dist/index.js +220 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +148 -2
- package/dist/mcp.js.map +1 -1
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# nworks
|
|
1
|
+
# nworks-plus
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/nworks)
|
|
4
|
-
[](LICENSE)
|
|
5
|
-
[](https://www.npmjs.com/package/nworks)
|
|
3
|
+
[](https://www.npmjs.com/package/nworks-plus)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://www.npmjs.com/package/nworks-plus)
|
|
6
6
|
[](https://glama.ai/mcp/servers/yjcho9317/nworks)
|
|
7
7
|
|
|
8
8
|
Featured in [awesome-mcp-servers](https://github.com/punkpeye/awesome-mcp-servers)
|
|
@@ -19,7 +19,7 @@ LINE WORKS (NAVER WORKS) 初のMCPサーバー。
|
|
|
19
19
|
## クイックスタート
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
npm install -g nworks
|
|
22
|
+
npm install -g nworks-plus
|
|
23
23
|
nworks login --user
|
|
24
24
|
nworks calendar list
|
|
25
25
|
```
|
|
@@ -49,8 +49,8 @@ Claude → nworks_mail_read (各)
|
|
|
49
49
|
## インストール
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
|
-
npx nworks # 直接実行
|
|
53
|
-
npm install -g nworks # グローバルインストール
|
|
52
|
+
npx nworks-plus # 直接実行
|
|
53
|
+
npm install -g nworks-plus # グローバルインストール
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
## ログイン
|
|
@@ -376,7 +376,7 @@ NWORKS_VERBOSE=1 # デバッグログ
|
|
|
376
376
|
"mcpServers": {
|
|
377
377
|
"nworks": {
|
|
378
378
|
"command": "npx",
|
|
379
|
-
"args": ["-y", "nworks", "mcp"],
|
|
379
|
+
"args": ["-y", "nworks-plus", "mcp"],
|
|
380
380
|
"env": {
|
|
381
381
|
"NWORKS_CLIENT_SECRET": "<Client Secret>",
|
|
382
382
|
"NWORKS_PRIVATE_KEY_PATH": "<Private Keyファイルの絶対パス(Service Account使用時)>"
|
package/README.ko.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# nworks
|
|
1
|
+
# nworks-plus
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/nworks)
|
|
4
|
-
[](LICENSE)
|
|
5
|
-
[](https://www.npmjs.com/package/nworks)
|
|
3
|
+
[](https://www.npmjs.com/package/nworks-plus)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://www.npmjs.com/package/nworks-plus)
|
|
6
6
|
[](https://glama.ai/mcp/servers/yjcho9317/nworks)
|
|
7
7
|
|
|
8
8
|
Featured in [awesome-mcp-servers](https://github.com/punkpeye/awesome-mcp-servers)
|
|
@@ -14,12 +14,12 @@ Featured in [awesome-mcp-servers](https://github.com/punkpeye/awesome-mcp-server
|
|
|
14
14
|
</p>
|
|
15
15
|
|
|
16
16
|
NAVER WORKS (LINE WORKS) 최초의 MCP 서버.
|
|
17
|
-
메시지, 캘린더, 드라이브, 메일, 할 일, 게시판 —
|
|
17
|
+
메시지, 캘린더, 드라이브, 메일, 할 일, 게시판 — 31개 도구를 CLI와 AI 에이전트에서 쓸 수 있습니다.
|
|
18
18
|
|
|
19
19
|
## Quickstart
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
npm install -g nworks
|
|
22
|
+
npm install -g nworks-plus
|
|
23
23
|
nworks login --user
|
|
24
24
|
nworks calendar list
|
|
25
25
|
```
|
|
@@ -49,8 +49,8 @@ Claude → nworks_mail_read (각각)
|
|
|
49
49
|
## Install
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
|
-
npx nworks # 바로 실행
|
|
53
|
-
npm install -g nworks # 글로벌 설치
|
|
52
|
+
npx nworks-plus # 바로 실행
|
|
53
|
+
npm install -g nworks-plus # 글로벌 설치
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
## 로그인
|
|
@@ -104,7 +104,7 @@ nworks login --user --scope "calendar calendar.read file file.read mail mail.rea
|
|
|
104
104
|
|
|
105
105
|
> **MCP에서 AI 에이전트가 직접 설정하기**: CLI 로그인 없이도 AI 에이전트가 `nworks_setup` → `nworks_login_user` 순서로 호출하면 브라우저 로그인만으로 전체 기능을 사용할 수 있습니다. Client Secret과 Private Key 경로는 MCP 설정의 `env` 필드 또는 시스템 환경변수로 미리 설정해야 합니다.
|
|
106
106
|
|
|
107
|
-
### MCP 도구 목록 (
|
|
107
|
+
### MCP 도구 목록 (31개)
|
|
108
108
|
|
|
109
109
|
| 도구 | 설명 | 필요 인증 |
|
|
110
110
|
|------|------|----------|
|
|
@@ -128,6 +128,9 @@ nworks login --user --scope "calendar calendar.read file file.read mail mail.rea
|
|
|
128
128
|
| `nworks_drive_list` | 드라이브 파일/폴더 목록 | User OAuth (file.read) |
|
|
129
129
|
| `nworks_drive_upload` | 드라이브 파일 업로드 | User OAuth (file) |
|
|
130
130
|
| `nworks_drive_download` | 드라이브 파일 다운로드 (5MB 초과 시 로컬 저장) | User OAuth (file.read) |
|
|
131
|
+
| `nworks_drive_sharedrive_list` | 팀 공유 드라이브 목록 | User OAuth (file.read) |
|
|
132
|
+
| `nworks_drive_sharedrive_files` | 공유 드라이브 내 파일 목록 | User OAuth (file.read) |
|
|
133
|
+
| `nworks_drive_sharedrive_download` | 공유 드라이브 파일 다운로드 | User OAuth (file.read) |
|
|
131
134
|
| **메일** | | |
|
|
132
135
|
| `nworks_mail_send` | 메일 전송 | User OAuth (mail) |
|
|
133
136
|
| `nworks_mail_list` | 메일 목록 조회 | User OAuth (mail.read) |
|
|
@@ -248,6 +251,17 @@ nworks drive download --file-id <fileId>
|
|
|
248
251
|
|
|
249
252
|
# 다운로드 경로/파일명 지정
|
|
250
253
|
nworks drive download --file-id <fileId> --out ./downloads --name report.pdf
|
|
254
|
+
|
|
255
|
+
# ── 공유 드라이브 (개인 "내 드라이브"와 별개인 팀 공유 저장소) ──
|
|
256
|
+
# 공유 드라이브 목록 (sharedriveId 확인)
|
|
257
|
+
nworks drive sharedrive-list
|
|
258
|
+
|
|
259
|
+
# 공유 드라이브 파일 목록 (루트 → 폴더 진입)
|
|
260
|
+
nworks drive sharedrive-files --drive <sharedriveId>
|
|
261
|
+
nworks drive sharedrive-files --drive <sharedriveId> --folder <fileId>
|
|
262
|
+
|
|
263
|
+
# 공유 드라이브 파일 다운로드
|
|
264
|
+
nworks drive sharedrive-download --drive <sharedriveId> --file-id <fileId> --out ./downloads
|
|
251
265
|
```
|
|
252
266
|
|
|
253
267
|
### 메일 (User OAuth)
|
|
@@ -399,7 +413,7 @@ NWORKS_VERBOSE=1 # 디버그 로깅
|
|
|
399
413
|
"mcpServers": {
|
|
400
414
|
"nworks": {
|
|
401
415
|
"command": "npx",
|
|
402
|
-
"args": ["-y", "nworks", "mcp"],
|
|
416
|
+
"args": ["-y", "nworks-plus", "mcp"],
|
|
403
417
|
"env": {
|
|
404
418
|
"NWORKS_CLIENT_SECRET": "<Client Secret>",
|
|
405
419
|
"NWORKS_PRIVATE_KEY_PATH": "<Private Key 파일 절대 경로 (Service Account 사용 시)>"
|
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# nworks
|
|
1
|
+
# nworks-plus
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/nworks)
|
|
4
|
-
[](LICENSE)
|
|
5
|
-
[](https://www.npmjs.com/package/nworks)
|
|
3
|
+
[](https://www.npmjs.com/package/nworks-plus)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://www.npmjs.com/package/nworks-plus)
|
|
6
6
|
[](https://glama.ai/mcp/servers/yjcho9317/nworks)
|
|
7
7
|
|
|
8
8
|
Featured in [awesome-mcp-servers](https://github.com/punkpeye/awesome-mcp-servers)
|
|
@@ -19,7 +19,7 @@ CLI + MCP server — 28 tools covering messages, calendar, drive, mail, tasks, a
|
|
|
19
19
|
## Quickstart
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
npm install -g nworks
|
|
22
|
+
npm install -g nworks-plus
|
|
23
23
|
nworks login --user
|
|
24
24
|
nworks calendar list
|
|
25
25
|
```
|
|
@@ -49,8 +49,8 @@ Claude → nworks_mail_read (each)
|
|
|
49
49
|
## Install
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
|
-
npx nworks # Run directly
|
|
53
|
-
npm install -g nworks # Global install
|
|
52
|
+
npx nworks-plus # Run directly
|
|
53
|
+
npm install -g nworks-plus # Global install
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
## Login
|
|
@@ -104,7 +104,7 @@ One login enables all 28 tools. No extra env setup needed.
|
|
|
104
104
|
|
|
105
105
|
> Without CLI login, an AI agent can call `nworks_setup` → `nworks_login_user` to authenticate via browser directly. Client Secret and Private Key path must be pre-configured via MCP config `env` field or system environment variables.
|
|
106
106
|
|
|
107
|
-
### MCP Tools (
|
|
107
|
+
### MCP Tools (31)
|
|
108
108
|
|
|
109
109
|
| Tool | Description | Auth |
|
|
110
110
|
|------|-------------|------|
|
|
@@ -128,6 +128,9 @@ One login enables all 28 tools. No extra env setup needed.
|
|
|
128
128
|
| `nworks_drive_list` | List drive files/folders | User OAuth (file.read) |
|
|
129
129
|
| `nworks_drive_upload` | Upload file to drive | User OAuth (file) |
|
|
130
130
|
| `nworks_drive_download` | Download file (saves locally if >5MB) | User OAuth (file.read) |
|
|
131
|
+
| `nworks_drive_sharedrive_list` | List team shared drives | User OAuth (file.read) |
|
|
132
|
+
| `nworks_drive_sharedrive_files` | List files in a shared drive | User OAuth (file.read) |
|
|
133
|
+
| `nworks_drive_sharedrive_download` | Download a file from a shared drive | User OAuth (file.read) |
|
|
131
134
|
| **Mail** | | |
|
|
132
135
|
| `nworks_mail_send` | Send mail | User OAuth (mail) |
|
|
133
136
|
| `nworks_mail_list` | List mailbox | User OAuth (mail.read) |
|
|
@@ -248,6 +251,17 @@ nworks drive download --file-id <fileId>
|
|
|
248
251
|
|
|
249
252
|
# Specify output path/name
|
|
250
253
|
nworks drive download --file-id <fileId> --out ./downloads --name report.pdf
|
|
254
|
+
|
|
255
|
+
# ── Shared drives (team storage, separate from personal "My Drive") ──
|
|
256
|
+
# List shared drives (get sharedriveId)
|
|
257
|
+
nworks drive sharedrive-list
|
|
258
|
+
|
|
259
|
+
# List files in a shared drive (root, then drill into a folder)
|
|
260
|
+
nworks drive sharedrive-files --drive <sharedriveId>
|
|
261
|
+
nworks drive sharedrive-files --drive <sharedriveId> --folder <fileId>
|
|
262
|
+
|
|
263
|
+
# Download a file from a shared drive
|
|
264
|
+
nworks drive sharedrive-download --drive <sharedriveId> --file-id <fileId> --out ./downloads
|
|
251
265
|
```
|
|
252
266
|
|
|
253
267
|
### Mail (User OAuth)
|
|
@@ -399,7 +413,7 @@ Sensitive values (Client Secret, Private Key path) must be set via MCP config `e
|
|
|
399
413
|
"mcpServers": {
|
|
400
414
|
"nworks": {
|
|
401
415
|
"command": "npx",
|
|
402
|
-
"args": ["-y", "nworks", "mcp"],
|
|
416
|
+
"args": ["-y", "nworks-plus", "mcp"],
|
|
403
417
|
"env": {
|
|
404
418
|
"NWORKS_CLIENT_SECRET": "<Client Secret>",
|
|
405
419
|
"NWORKS_PRIVATE_KEY_PATH": "<Private Key file absolute path (for Service Account)>"
|
package/dist/index.js
CHANGED
|
@@ -1576,8 +1576,7 @@ Content-Type: application/octet-stream\r
|
|
|
1576
1576
|
if (!uploadRes.ok) return handleError2(uploadRes);
|
|
1577
1577
|
return await uploadRes.json();
|
|
1578
1578
|
}
|
|
1579
|
-
async function
|
|
1580
|
-
const url = `${BASE_URL3}/users/${sanitizePathSegment(userId)}/drive/files/${sanitizePathSegment(fileId)}/download`;
|
|
1579
|
+
async function downloadViaRedirect(url, profile) {
|
|
1581
1580
|
if (process.env["NWORKS_VERBOSE"] === "1") {
|
|
1582
1581
|
console.error(`[nworks] GET ${url} (get download URL)`);
|
|
1583
1582
|
}
|
|
@@ -1612,6 +1611,48 @@ async function downloadFile(fileId, userId = "me", profile = "default") {
|
|
|
1612
1611
|
}
|
|
1613
1612
|
return { buffer, fileName };
|
|
1614
1613
|
}
|
|
1614
|
+
async function downloadFile(fileId, userId = "me", profile = "default") {
|
|
1615
|
+
const url = `${BASE_URL3}/users/${sanitizePathSegment(userId)}/drive/files/${sanitizePathSegment(fileId)}/download`;
|
|
1616
|
+
return downloadViaRedirect(url, profile);
|
|
1617
|
+
}
|
|
1618
|
+
async function listSharedDrives(count = 100, cursor, profile = "default") {
|
|
1619
|
+
const params = new URLSearchParams();
|
|
1620
|
+
params.set("count", String(count));
|
|
1621
|
+
if (cursor) params.set("cursor", cursor);
|
|
1622
|
+
const url = `${BASE_URL3}/sharedrives?${params.toString()}`;
|
|
1623
|
+
if (process.env["NWORKS_VERBOSE"] === "1") {
|
|
1624
|
+
console.error(`[nworks] GET ${url}`);
|
|
1625
|
+
}
|
|
1626
|
+
const res = await authedFetch2(url, { method: "GET" }, profile);
|
|
1627
|
+
if (!res.ok) return handleError2(res);
|
|
1628
|
+
const data = await res.json();
|
|
1629
|
+
const raw = data.sharedDrives ?? data.sharedrives ?? [];
|
|
1630
|
+
const sharedDrives = raw.map((d) => ({
|
|
1631
|
+
sharedriveId: d.sharedriveId,
|
|
1632
|
+
name: d.name ?? d.sharedriveName ?? "",
|
|
1633
|
+
permissionType: d.permissionType
|
|
1634
|
+
}));
|
|
1635
|
+
return { sharedDrives, responseMetaData: data.responseMetaData };
|
|
1636
|
+
}
|
|
1637
|
+
async function listSharedDriveFiles(sharedriveId, folderId, count = 20, cursor, profile = "default") {
|
|
1638
|
+
const base = `${BASE_URL3}/sharedrives/${sanitizePathSegment(sharedriveId)}/files`;
|
|
1639
|
+
const path = folderId ? `${base}/${sanitizePathSegment(folderId)}/children` : base;
|
|
1640
|
+
const params = new URLSearchParams();
|
|
1641
|
+
params.set("count", String(count));
|
|
1642
|
+
if (cursor) params.set("cursor", cursor);
|
|
1643
|
+
const url = `${path}?${params.toString()}`;
|
|
1644
|
+
if (process.env["NWORKS_VERBOSE"] === "1") {
|
|
1645
|
+
console.error(`[nworks] GET ${url}`);
|
|
1646
|
+
}
|
|
1647
|
+
const res = await authedFetch2(url, { method: "GET" }, profile);
|
|
1648
|
+
if (!res.ok) return handleError2(res);
|
|
1649
|
+
const data = await res.json();
|
|
1650
|
+
return { files: data.files ?? [], responseMetaData: data.responseMetaData };
|
|
1651
|
+
}
|
|
1652
|
+
async function downloadSharedDriveFile(sharedriveId, fileId, profile = "default") {
|
|
1653
|
+
const url = `${BASE_URL3}/sharedrives/${sanitizePathSegment(sharedriveId)}/files/${sanitizePathSegment(fileId)}/download`;
|
|
1654
|
+
return downloadViaRedirect(url, profile);
|
|
1655
|
+
}
|
|
1615
1656
|
|
|
1616
1657
|
// src/commands/drive.ts
|
|
1617
1658
|
var listCommand2 = new Command7("list").description("List files in Drive (requires User OAuth with file or file.read scope)").option("--user <userId>", "Target user ID (default: me)").option("--folder <folderId>", "Folder ID to list (default: root)").option("--count <n>", "Items per page (default: 20)", "20").option("--cursor <cursor>", "Pagination cursor").option("--profile <name>", "Profile name", "default").option("--json", "JSON output").action(async (opts) => {
|
|
@@ -1705,7 +1746,78 @@ var downloadCommand = new Command7("download").description("Download a file from
|
|
|
1705
1746
|
cliError(err, opts, "drive");
|
|
1706
1747
|
}
|
|
1707
1748
|
});
|
|
1708
|
-
var
|
|
1749
|
+
var sharedriveListCommand = new Command7("sharedrive-list").description("List shared drives (requires User OAuth with file or file.read scope)").option("--count <n>", "Items per page (default: 100)", "100").option("--cursor <cursor>", "Pagination cursor").option("--profile <name>", "Profile name", "default").option("--json", "JSON output").action(async (opts) => {
|
|
1750
|
+
try {
|
|
1751
|
+
const result = await listSharedDrives(
|
|
1752
|
+
parseInt(opts.count, 10),
|
|
1753
|
+
opts.cursor,
|
|
1754
|
+
opts.profile
|
|
1755
|
+
);
|
|
1756
|
+
output(
|
|
1757
|
+
{
|
|
1758
|
+
sharedDrives: result.sharedDrives,
|
|
1759
|
+
count: result.sharedDrives.length,
|
|
1760
|
+
nextCursor: result.responseMetaData?.nextCursor ?? null
|
|
1761
|
+
},
|
|
1762
|
+
opts
|
|
1763
|
+
);
|
|
1764
|
+
} catch (err) {
|
|
1765
|
+
cliError(err, opts, "drive");
|
|
1766
|
+
}
|
|
1767
|
+
});
|
|
1768
|
+
var sharedriveFilesCommand = new Command7("sharedrive-files").description("List files in a shared drive (requires User OAuth with file or file.read scope)").requiredOption("--drive <sharedriveId>", "Shared drive ID (from `drive sharedrive-list`)").option("--folder <fileId>", "Folder ID to list (default: shared drive root)").option("--count <n>", "Items per page (default: 20)", "20").option("--cursor <cursor>", "Pagination cursor").option("--profile <name>", "Profile name", "default").option("--json", "JSON output").action(async (opts) => {
|
|
1769
|
+
try {
|
|
1770
|
+
const result = await listSharedDriveFiles(
|
|
1771
|
+
opts.drive,
|
|
1772
|
+
opts.folder,
|
|
1773
|
+
parseInt(opts.count, 10),
|
|
1774
|
+
opts.cursor,
|
|
1775
|
+
opts.profile
|
|
1776
|
+
);
|
|
1777
|
+
const files = result.files.map((f) => ({
|
|
1778
|
+
name: f.fileName,
|
|
1779
|
+
type: f.fileType,
|
|
1780
|
+
size: f.fileSize,
|
|
1781
|
+
modified: f.modifiedTime,
|
|
1782
|
+
fileId: f.fileId
|
|
1783
|
+
}));
|
|
1784
|
+
output(
|
|
1785
|
+
{
|
|
1786
|
+
files,
|
|
1787
|
+
count: files.length,
|
|
1788
|
+
nextCursor: result.responseMetaData?.nextCursor ?? null
|
|
1789
|
+
},
|
|
1790
|
+
opts
|
|
1791
|
+
);
|
|
1792
|
+
} catch (err) {
|
|
1793
|
+
cliError(err, opts, "drive");
|
|
1794
|
+
}
|
|
1795
|
+
});
|
|
1796
|
+
var sharedriveDownloadCommand = new Command7("sharedrive-download").description("Download a file from a shared drive (requires User OAuth with file or file.read scope)").requiredOption("--drive <sharedriveId>", "Shared drive ID (from `drive sharedrive-list`)").requiredOption("--file-id <fileId>", "File ID to download (from `drive sharedrive-files`)").option("--out <path>", "Output directory (default: current directory)").option("--name <filename>", "Output filename (default: original name)").option("--profile <name>", "Profile name", "default").option("--json", "JSON output").action(async (opts) => {
|
|
1797
|
+
try {
|
|
1798
|
+
const result = await downloadSharedDriveFile(
|
|
1799
|
+
opts.drive,
|
|
1800
|
+
opts.fileId,
|
|
1801
|
+
opts.profile
|
|
1802
|
+
);
|
|
1803
|
+
const fileName = opts.name ?? result.fileName ?? opts.fileId;
|
|
1804
|
+
const outDir = opts.out ?? process.cwd();
|
|
1805
|
+
const outPath = join2(outDir, fileName);
|
|
1806
|
+
await writeFile2(outPath, result.buffer);
|
|
1807
|
+
output(
|
|
1808
|
+
{
|
|
1809
|
+
success: true,
|
|
1810
|
+
fileName,
|
|
1811
|
+
path: outPath,
|
|
1812
|
+
size: result.buffer.length
|
|
1813
|
+
},
|
|
1814
|
+
opts
|
|
1815
|
+
);
|
|
1816
|
+
} catch (err) {
|
|
1817
|
+
cliError(err, opts, "drive");
|
|
1818
|
+
}
|
|
1819
|
+
});
|
|
1820
|
+
var driveCommand = new Command7("drive").description("Drive operations (requires User OAuth with file scope)").addCommand(listCommand2).addCommand(uploadCommand).addCommand(downloadCommand).addCommand(sharedriveListCommand).addCommand(sharedriveFilesCommand).addCommand(sharedriveDownloadCommand);
|
|
1709
1821
|
|
|
1710
1822
|
// src/commands/mail.ts
|
|
1711
1823
|
import { writeFile as writeFile3 } from "fs/promises";
|
|
@@ -3135,6 +3247,111 @@ OAuth Redirect URI: http://localhost:9876/callback`,
|
|
|
3135
3247
|
}
|
|
3136
3248
|
}
|
|
3137
3249
|
);
|
|
3250
|
+
server.tool(
|
|
3251
|
+
"nworks_drive_sharedrive_list",
|
|
3252
|
+
"\uD300 \uACF5\uC720 \uB4DC\uB77C\uC774\uBE0C(Shared Drive) \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. \uAC1C\uC778 '\uB0B4 \uB4DC\uB77C\uC774\uBE0C'\uAC00 \uC544\uB2CC \uD300 \uACF5\uC720 \uC800\uC7A5\uC18C\uB97C \uCC3E\uC744 \uB54C \uC0AC\uC6A9. '\uACF5\uC720 \uB4DC\uB77C\uC774\uBE0C \uBCF4\uC5EC\uC918', '\uD300 \uB4DC\uB77C\uC774\uBE0C \uBAA9\uB85D' \uB4F1\uC758 \uC694\uCCAD\uC5D0 \uC0AC\uC6A9. \uBC18\uD658\uB41C sharedriveId\uB85C nworks_drive_sharedrive_files\uB97C \uD638\uCD9C\uD574 \uB0B4\uBD80 \uD30C\uC77C\uC744 \uC870\uD68C\uD558\uC138\uC694. User OAuth \uC778\uC99D \uD544\uC694 (file.read scope)",
|
|
3253
|
+
{
|
|
3254
|
+
count: z.number().optional().describe("\uD398\uC774\uC9C0\uB2F9 \uD56D\uBAA9 \uC218 (\uAE30\uBCF8: 100)"),
|
|
3255
|
+
cursor: z.string().optional().describe("\uD398\uC774\uC9C0\uB124\uC774\uC158 \uCEE4\uC11C")
|
|
3256
|
+
},
|
|
3257
|
+
async ({ count, cursor }) => {
|
|
3258
|
+
try {
|
|
3259
|
+
const result = await listSharedDrives(count ?? 100, cursor);
|
|
3260
|
+
return {
|
|
3261
|
+
content: [{ type: "text", text: JSON.stringify({ sharedDrives: result.sharedDrives, count: result.sharedDrives.length, hasMore: !!result.responseMetaData?.nextCursor, nextCursor: result.responseMetaData?.nextCursor ?? null }) }]
|
|
3262
|
+
};
|
|
3263
|
+
} catch (err) {
|
|
3264
|
+
return {
|
|
3265
|
+
content: [{ type: "text", text: mcpErrorHint(err, "drive.list") }],
|
|
3266
|
+
isError: true
|
|
3267
|
+
};
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
);
|
|
3271
|
+
server.tool(
|
|
3272
|
+
"nworks_drive_sharedrive_files",
|
|
3273
|
+
"\uD2B9\uC815 \uACF5\uC720 \uB4DC\uB77C\uC774\uBE0C(Shared Drive) \uC548\uC758 \uD30C\uC77C/\uD3F4\uB354 \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. sharedriveId\uB294 nworks_drive_sharedrive_list\uB85C \uC870\uD68C \uAC00\uB2A5. folderId\uB97C \uC9C0\uC815\uD558\uBA74 \uD574\uB2F9 \uD3F4\uB354 \uB0B4\uBD80\uB97C, \uBBF8\uC9C0\uC815 \uC2DC \uACF5\uC720 \uB4DC\uB77C\uC774\uBE0C \uB8E8\uD2B8\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. User OAuth \uC778\uC99D \uD544\uC694 (file.read scope)",
|
|
3274
|
+
{
|
|
3275
|
+
sharedriveId: z.string().describe("\uACF5\uC720 \uB4DC\uB77C\uC774\uBE0C ID (nworks_drive_sharedrive_list\uB85C \uC870\uD68C \uAC00\uB2A5)"),
|
|
3276
|
+
folderId: z.string().optional().describe("\uD3F4\uB354 ID (\uBBF8\uC9C0\uC815 \uC2DC \uACF5\uC720 \uB4DC\uB77C\uC774\uBE0C \uB8E8\uD2B8)"),
|
|
3277
|
+
count: z.number().optional().describe("\uD398\uC774\uC9C0\uB2F9 \uD56D\uBAA9 \uC218 (\uAE30\uBCF8: 20, \uCD5C\uB300: 200)"),
|
|
3278
|
+
cursor: z.string().optional().describe("\uD398\uC774\uC9C0\uB124\uC774\uC158 \uCEE4\uC11C")
|
|
3279
|
+
},
|
|
3280
|
+
async ({ sharedriveId, folderId, count, cursor }) => {
|
|
3281
|
+
try {
|
|
3282
|
+
const result = await listSharedDriveFiles(sharedriveId, folderId, count ?? 20, cursor);
|
|
3283
|
+
const files = result.files.map((f) => ({
|
|
3284
|
+
fileId: f.fileId,
|
|
3285
|
+
name: f.fileName,
|
|
3286
|
+
type: f.fileType,
|
|
3287
|
+
size: f.fileSize,
|
|
3288
|
+
modified: f.modifiedTime,
|
|
3289
|
+
path: f.filePath
|
|
3290
|
+
}));
|
|
3291
|
+
return {
|
|
3292
|
+
content: [{ type: "text", text: JSON.stringify({ files, count: files.length, hasMore: !!result.responseMetaData?.nextCursor, nextCursor: result.responseMetaData?.nextCursor ?? null }) }]
|
|
3293
|
+
};
|
|
3294
|
+
} catch (err) {
|
|
3295
|
+
return {
|
|
3296
|
+
content: [{ type: "text", text: mcpErrorHint(err, "drive.list") }],
|
|
3297
|
+
isError: true
|
|
3298
|
+
};
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
);
|
|
3302
|
+
server.tool(
|
|
3303
|
+
"nworks_drive_sharedrive_download",
|
|
3304
|
+
"\uACF5\uC720 \uB4DC\uB77C\uC774\uBE0C(Shared Drive)\uC758 \uD30C\uC77C\uC744 \uB2E4\uC6B4\uB85C\uB4DC\uD569\uB2C8\uB2E4. sharedriveId\xB7fileId\uB294 nworks_drive_sharedrive_files\uB85C \uC870\uD68C \uAC00\uB2A5. outputDir\uC744 \uC9C0\uC815\uD558\uBA74 \uB85C\uCEEC\uC5D0 \uD30C\uC77C\uB85C \uC800\uC7A5\uD558\uACE0, \uBBF8\uC9C0\uC815 \uC2DC \uD30C\uC77C \uB0B4\uC6A9\uC744 \uC9C1\uC811 \uBC18\uD658\uD569\uB2C8\uB2E4 (\uD14D\uC2A4\uD2B8\uB294 text, \uBC14\uC774\uB108\uB9AC\uB294 base64). 5MB \uCD08\uACFC \uD30C\uC77C\uC740 \uBC18\uB4DC\uC2DC outputDir\uB97C \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4. User OAuth \uC778\uC99D \uD544\uC694 (file.read scope)",
|
|
3305
|
+
{
|
|
3306
|
+
sharedriveId: z.string().describe("\uACF5\uC720 \uB4DC\uB77C\uC774\uBE0C ID (nworks_drive_sharedrive_list\uB85C \uC870\uD68C \uAC00\uB2A5)"),
|
|
3307
|
+
fileId: z.string().describe("\uB2E4\uC6B4\uB85C\uB4DC\uD560 \uD30C\uC77C ID (nworks_drive_sharedrive_files\uB85C \uC870\uD68C \uAC00\uB2A5)"),
|
|
3308
|
+
outputDir: z.string().optional().describe("\uC800\uC7A5 \uB514\uB809\uD1A0\uB9AC (\uC9C0\uC815 \uC2DC \uD30C\uC77C\uB85C \uC800\uC7A5, \uBBF8\uC9C0\uC815 \uC2DC \uB0B4\uC6A9\uC744 \uC9C1\uC811 \uBC18\uD658)"),
|
|
3309
|
+
outputName: z.string().optional().describe("\uC800\uC7A5 \uD30C\uC77C\uBA85 (\uBBF8\uC9C0\uC815 \uC2DC \uC6D0\uBCF8 \uD30C\uC77C\uBA85)")
|
|
3310
|
+
},
|
|
3311
|
+
async ({ sharedriveId, fileId, outputDir, outputName }) => {
|
|
3312
|
+
try {
|
|
3313
|
+
const result = await downloadSharedDriveFile(sharedriveId, fileId);
|
|
3314
|
+
const fileName = outputName ?? result.fileName ?? fileId;
|
|
3315
|
+
if (outputDir) {
|
|
3316
|
+
const { writeFile: writeFile4 } = await import("fs/promises");
|
|
3317
|
+
const { join: join4 } = await import("path");
|
|
3318
|
+
const safeDir = validateLocalPath(outputDir);
|
|
3319
|
+
const safeName = sanitizeFileName(fileName);
|
|
3320
|
+
const outPath = join4(safeDir, safeName);
|
|
3321
|
+
validateLocalPath(outPath, safeDir);
|
|
3322
|
+
await writeFile4(outPath, result.buffer);
|
|
3323
|
+
return {
|
|
3324
|
+
content: [{ type: "text", text: JSON.stringify({ success: true, fileName, path: outPath, size: result.buffer.length }) }]
|
|
3325
|
+
};
|
|
3326
|
+
}
|
|
3327
|
+
const MAX_INLINE_SIZE = 5 * 1024 * 1024;
|
|
3328
|
+
if (result.buffer.length > MAX_INLINE_SIZE) {
|
|
3329
|
+
const sizeMB = (result.buffer.length / (1024 * 1024)).toFixed(1);
|
|
3330
|
+
return {
|
|
3331
|
+
content: [{ type: "text", text: JSON.stringify({ error: true, message: `\uD30C\uC77C\uC774 \uB108\uBB34 \uD07D\uB2C8\uB2E4 (${sizeMB}MB). outputDir\uB97C \uC9C0\uC815\uD574\uC11C \uB85C\uCEEC\uC5D0 \uC800\uC7A5\uD558\uC138\uC694.`, fileName, size: result.buffer.length }) }],
|
|
3332
|
+
isError: true
|
|
3333
|
+
};
|
|
3334
|
+
}
|
|
3335
|
+
const textExtensions = /\.(txt|md|csv|json|xml|html|htm|css|js|ts|jsx|tsx|yaml|yml|toml|ini|cfg|conf|log|sh|bash|zsh|py|rb|java|go|rs|c|cpp|h|hpp|sql|graphql|env|gitignore|dockerignore|editorconfig)$/i;
|
|
3336
|
+
const isText = textExtensions.test(fileName);
|
|
3337
|
+
if (isText) {
|
|
3338
|
+
const text = result.buffer.toString("utf-8");
|
|
3339
|
+
return {
|
|
3340
|
+
content: [{ type: "text", text: JSON.stringify({ success: true, fileName, size: result.buffer.length, encoding: "text", content: text }) }]
|
|
3341
|
+
};
|
|
3342
|
+
}
|
|
3343
|
+
const base64 = result.buffer.toString("base64");
|
|
3344
|
+
return {
|
|
3345
|
+
content: [{ type: "text", text: JSON.stringify({ success: true, fileName, size: result.buffer.length, encoding: "base64", content: base64 }) }]
|
|
3346
|
+
};
|
|
3347
|
+
} catch (err) {
|
|
3348
|
+
return {
|
|
3349
|
+
content: [{ type: "text", text: mcpErrorHint(err, "drive.download") }],
|
|
3350
|
+
isError: true
|
|
3351
|
+
};
|
|
3352
|
+
}
|
|
3353
|
+
}
|
|
3354
|
+
);
|
|
3138
3355
|
server.tool(
|
|
3139
3356
|
"nworks_mail_send",
|
|
3140
3357
|
"NAVER WORKS \uBA54\uC77C\uC744 \uC804\uC1A1\uD569\uB2C8\uB2E4. '\uBA54\uC77C \uBCF4\uB0B4\uC918', '\uC774\uBA54\uC77C \uC791\uC131\uD574\uC918' \uB4F1\uC758 \uC694\uCCAD\uC5D0 \uC0AC\uC6A9. \uBE44\uB3D9\uAE30 \uC804\uC1A1(\uC131\uACF5 \uC2DC 202). User OAuth \uC778\uC99D \uD544\uC694 (mail scope)",
|