ncloud-mcp-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +159 -0
  3. package/README_EN.md +159 -0
  4. package/dist/auth/signature.d.ts +16 -0
  5. package/dist/auth/signature.js +18 -0
  6. package/dist/auth/signature.js.map +1 -0
  7. package/dist/auth/signature.test.d.ts +1 -0
  8. package/dist/auth/signature.test.js +90 -0
  9. package/dist/auth/signature.test.js.map +1 -0
  10. package/dist/client/ncloud-client.d.ts +27 -0
  11. package/dist/client/ncloud-client.js +270 -0
  12. package/dist/client/ncloud-client.js.map +1 -0
  13. package/dist/client/ncloud-client.test.d.ts +1 -0
  14. package/dist/client/ncloud-client.test.js +477 -0
  15. package/dist/client/ncloud-client.test.js.map +1 -0
  16. package/dist/client/s3-compatible-client.d.ts +49 -0
  17. package/dist/client/s3-compatible-client.js +210 -0
  18. package/dist/client/s3-compatible-client.js.map +1 -0
  19. package/dist/client/swift-compatible-client.d.ts +68 -0
  20. package/dist/client/swift-compatible-client.js +173 -0
  21. package/dist/client/swift-compatible-client.js.map +1 -0
  22. package/dist/index.d.ts +2 -0
  23. package/dist/index.js +362 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/tools/acg.d.ts +3 -0
  26. package/dist/tools/acg.js +220 -0
  27. package/dist/tools/acg.js.map +1 -0
  28. package/dist/tools/activity-tracer.d.ts +3 -0
  29. package/dist/tools/activity-tracer.js +77 -0
  30. package/dist/tools/activity-tracer.js.map +1 -0
  31. package/dist/tools/analytics-cdss.d.ts +3 -0
  32. package/dist/tools/analytics-cdss.js +618 -0
  33. package/dist/tools/analytics-cdss.js.map +1 -0
  34. package/dist/tools/analytics-datacatalog.d.ts +10 -0
  35. package/dist/tools/analytics-datacatalog.js +409 -0
  36. package/dist/tools/analytics-datacatalog.js.map +1 -0
  37. package/dist/tools/analytics-dataflow.d.ts +16 -0
  38. package/dist/tools/analytics-dataflow.js +478 -0
  39. package/dist/tools/analytics-dataflow.js.map +1 -0
  40. package/dist/tools/analytics-dataforest.d.ts +10 -0
  41. package/dist/tools/analytics-dataforest.js +379 -0
  42. package/dist/tools/analytics-dataforest.js.map +1 -0
  43. package/dist/tools/analytics-dataquery.d.ts +16 -0
  44. package/dist/tools/analytics-dataquery.js +143 -0
  45. package/dist/tools/analytics-dataquery.js.map +1 -0
  46. package/dist/tools/analytics-datastream.d.ts +13 -0
  47. package/dist/tools/analytics-datastream.js +359 -0
  48. package/dist/tools/analytics-datastream.js.map +1 -0
  49. package/dist/tools/analytics-hadoop.d.ts +12 -0
  50. package/dist/tools/analytics-hadoop.js +450 -0
  51. package/dist/tools/analytics-hadoop.js.map +1 -0
  52. package/dist/tools/analytics-ses.d.ts +3 -0
  53. package/dist/tools/analytics-ses.js +653 -0
  54. package/dist/tools/analytics-ses.js.map +1 -0
  55. package/dist/tools/api-gateway.d.ts +3 -0
  56. package/dist/tools/api-gateway.js +202 -0
  57. package/dist/tools/api-gateway.js.map +1 -0
  58. package/dist/tools/autoscaling.d.ts +3 -0
  59. package/dist/tools/autoscaling.js +430 -0
  60. package/dist/tools/autoscaling.js.map +1 -0
  61. package/dist/tools/billing.d.ts +14 -0
  62. package/dist/tools/billing.js +545 -0
  63. package/dist/tools/billing.js.map +1 -0
  64. package/dist/tools/certificate-manager.d.ts +18 -0
  65. package/dist/tools/certificate-manager.js +84 -0
  66. package/dist/tools/certificate-manager.js.map +1 -0
  67. package/dist/tools/cloud-advisor.d.ts +3 -0
  68. package/dist/tools/cloud-advisor.js +202 -0
  69. package/dist/tools/cloud-advisor.js.map +1 -0
  70. package/dist/tools/cloud-functions.d.ts +3 -0
  71. package/dist/tools/cloud-functions.js +497 -0
  72. package/dist/tools/cloud-functions.js.map +1 -0
  73. package/dist/tools/cloud-insight-integration.d.ts +3 -0
  74. package/dist/tools/cloud-insight-integration.js +90 -0
  75. package/dist/tools/cloud-insight-integration.js.map +1 -0
  76. package/dist/tools/cloud-insight-plugin.d.ts +3 -0
  77. package/dist/tools/cloud-insight-plugin.js +600 -0
  78. package/dist/tools/cloud-insight-plugin.js.map +1 -0
  79. package/dist/tools/cloud-insight-rule.d.ts +3 -0
  80. package/dist/tools/cloud-insight-rule.js +570 -0
  81. package/dist/tools/cloud-insight-rule.js.map +1 -0
  82. package/dist/tools/cloud-insight.d.ts +3 -0
  83. package/dist/tools/cloud-insight.js +246 -0
  84. package/dist/tools/cloud-insight.js.map +1 -0
  85. package/dist/tools/common.d.ts +3 -0
  86. package/dist/tools/common.js +120 -0
  87. package/dist/tools/common.js.map +1 -0
  88. package/dist/tools/compute-initscript.d.ts +3 -0
  89. package/dist/tools/compute-initscript.js +62 -0
  90. package/dist/tools/compute-initscript.js.map +1 -0
  91. package/dist/tools/compute-loginkey.d.ts +3 -0
  92. package/dist/tools/compute-loginkey.js +59 -0
  93. package/dist/tools/compute-loginkey.js.map +1 -0
  94. package/dist/tools/compute-placement.d.ts +3 -0
  95. package/dist/tools/compute-placement.js +196 -0
  96. package/dist/tools/compute-placement.js.map +1 -0
  97. package/dist/tools/compute-publicip.d.ts +3 -0
  98. package/dist/tools/compute-publicip.js +97 -0
  99. package/dist/tools/compute-publicip.js.map +1 -0
  100. package/dist/tools/compute-server.d.ts +3 -0
  101. package/dist/tools/compute-server.js +445 -0
  102. package/dist/tools/compute-server.js.map +1 -0
  103. package/dist/tools/compute-server.test.d.ts +1 -0
  104. package/dist/tools/compute-server.test.js +224 -0
  105. package/dist/tools/compute-server.test.js.map +1 -0
  106. package/dist/tools/compute-storage.d.ts +3 -0
  107. package/dist/tools/compute-storage.js +240 -0
  108. package/dist/tools/compute-storage.js.map +1 -0
  109. package/dist/tools/containers-nks.d.ts +13 -0
  110. package/dist/tools/containers-nks.js +576 -0
  111. package/dist/tools/containers-nks.js.map +1 -0
  112. package/dist/tools/containers-registry.d.ts +3 -0
  113. package/dist/tools/containers-registry.js +181 -0
  114. package/dist/tools/containers-registry.js.map +1 -0
  115. package/dist/tools/database-cache.d.ts +3 -0
  116. package/dist/tools/database-cache.js +388 -0
  117. package/dist/tools/database-cache.js.map +1 -0
  118. package/dist/tools/database-mongodb.d.ts +3 -0
  119. package/dist/tools/database-mongodb.js +460 -0
  120. package/dist/tools/database-mongodb.js.map +1 -0
  121. package/dist/tools/database-mssql.d.ts +3 -0
  122. package/dist/tools/database-mssql.js +345 -0
  123. package/dist/tools/database-mssql.js.map +1 -0
  124. package/dist/tools/database-mysql.d.ts +3 -0
  125. package/dist/tools/database-mysql.js +500 -0
  126. package/dist/tools/database-mysql.js.map +1 -0
  127. package/dist/tools/database-postgresql.d.ts +3 -0
  128. package/dist/tools/database-postgresql.js +432 -0
  129. package/dist/tools/database-postgresql.js.map +1 -0
  130. package/dist/tools/global-dns.d.ts +3 -0
  131. package/dist/tools/global-dns.js +207 -0
  132. package/dist/tools/global-dns.js.map +1 -0
  133. package/dist/tools/global-edge.d.ts +3 -0
  134. package/dist/tools/global-edge.js +386 -0
  135. package/dist/tools/global-edge.js.map +1 -0
  136. package/dist/tools/global-traffic-manager.d.ts +3 -0
  137. package/dist/tools/global-traffic-manager.js +497 -0
  138. package/dist/tools/global-traffic-manager.js.map +1 -0
  139. package/dist/tools/index.d.ts +63 -0
  140. package/dist/tools/index.js +64 -0
  141. package/dist/tools/index.js.map +1 -0
  142. package/dist/tools/kms.d.ts +8 -0
  143. package/dist/tools/kms.js +529 -0
  144. package/dist/tools/kms.js.map +1 -0
  145. package/dist/tools/loadbalancer.d.ts +3 -0
  146. package/dist/tools/loadbalancer.js +341 -0
  147. package/dist/tools/loadbalancer.js.map +1 -0
  148. package/dist/tools/log-analytics.d.ts +3 -0
  149. package/dist/tools/log-analytics.js +183 -0
  150. package/dist/tools/log-analytics.js.map +1 -0
  151. package/dist/tools/media-imageoptimizer.d.ts +3 -0
  152. package/dist/tools/media-imageoptimizer.js +187 -0
  153. package/dist/tools/media-imageoptimizer.js.map +1 -0
  154. package/dist/tools/media-livestation.d.ts +3 -0
  155. package/dist/tools/media-livestation.js +252 -0
  156. package/dist/tools/media-livestation.js.map +1 -0
  157. package/dist/tools/media-vodstation.d.ts +3 -0
  158. package/dist/tools/media-vodstation.js +197 -0
  159. package/dist/tools/media-vodstation.js.map +1 -0
  160. package/dist/tools/nat-gateway.d.ts +3 -0
  161. package/dist/tools/nat-gateway.js +91 -0
  162. package/dist/tools/nat-gateway.js.map +1 -0
  163. package/dist/tools/network-acl.d.ts +3 -0
  164. package/dist/tools/network-acl.js +322 -0
  165. package/dist/tools/network-acl.js.map +1 -0
  166. package/dist/tools/network-interface.d.ts +3 -0
  167. package/dist/tools/network-interface.js +193 -0
  168. package/dist/tools/network-interface.js.map +1 -0
  169. package/dist/tools/private-ca.d.ts +8 -0
  170. package/dist/tools/private-ca.js +368 -0
  171. package/dist/tools/private-ca.js.map +1 -0
  172. package/dist/tools/resource-manager.d.ts +3 -0
  173. package/dist/tools/resource-manager.js +151 -0
  174. package/dist/tools/resource-manager.js.map +1 -0
  175. package/dist/tools/route-table.d.ts +3 -0
  176. package/dist/tools/route-table.js +205 -0
  177. package/dist/tools/route-table.js.map +1 -0
  178. package/dist/tools/security-monitoring.d.ts +3 -0
  179. package/dist/tools/security-monitoring.js +182 -0
  180. package/dist/tools/security-monitoring.js.map +1 -0
  181. package/dist/tools/sens.d.ts +3 -0
  182. package/dist/tools/sens.js +240 -0
  183. package/dist/tools/sens.js.map +1 -0
  184. package/dist/tools/sourcebuild.d.ts +3 -0
  185. package/dist/tools/sourcebuild.js +481 -0
  186. package/dist/tools/sourcebuild.js.map +1 -0
  187. package/dist/tools/sourcecommit.d.ts +13 -0
  188. package/dist/tools/sourcecommit.js +228 -0
  189. package/dist/tools/sourcecommit.js.map +1 -0
  190. package/dist/tools/sourcedeploy.d.ts +3 -0
  191. package/dist/tools/sourcedeploy.js +448 -0
  192. package/dist/tools/sourcedeploy.js.map +1 -0
  193. package/dist/tools/sourcepipeline.d.ts +12 -0
  194. package/dist/tools/sourcepipeline.js +357 -0
  195. package/dist/tools/sourcepipeline.js.map +1 -0
  196. package/dist/tools/storage-archive.d.ts +3 -0
  197. package/dist/tools/storage-archive.js +337 -0
  198. package/dist/tools/storage-archive.js.map +1 -0
  199. package/dist/tools/storage-nas.d.ts +3 -0
  200. package/dist/tools/storage-nas.js +288 -0
  201. package/dist/tools/storage-nas.js.map +1 -0
  202. package/dist/tools/storage-ncloud.d.ts +3 -0
  203. package/dist/tools/storage-ncloud.js +864 -0
  204. package/dist/tools/storage-ncloud.js.map +1 -0
  205. package/dist/tools/storage-object.d.ts +3 -0
  206. package/dist/tools/storage-object.js +982 -0
  207. package/dist/tools/storage-object.js.map +1 -0
  208. package/dist/tools/sub-account.d.ts +3 -0
  209. package/dist/tools/sub-account.js +311 -0
  210. package/dist/tools/sub-account.js.map +1 -0
  211. package/dist/tools/target-group.d.ts +3 -0
  212. package/dist/tools/target-group.js +207 -0
  213. package/dist/tools/target-group.js.map +1 -0
  214. package/dist/tools/vpc-peering.d.ts +3 -0
  215. package/dist/tools/vpc-peering.js +109 -0
  216. package/dist/tools/vpc-peering.js.map +1 -0
  217. package/dist/tools/vpc.d.ts +3 -0
  218. package/dist/tools/vpc.js +194 -0
  219. package/dist/tools/vpc.js.map +1 -0
  220. package/package.json +53 -0
@@ -0,0 +1,982 @@
1
+ import crypto from "node:crypto";
2
+ import { z } from "zod";
3
+ /**
4
+ * Parse S3 XML list buckets response into a structured object.
5
+ */
6
+ function parseListBucketsXml(xml) {
7
+ const buckets = [];
8
+ const bucketRegex = /<Bucket>\s*<Name>(.*?)<\/Name>\s*<CreationDate>(.*?)<\/CreationDate>\s*<\/Bucket>/gs;
9
+ let match;
10
+ while ((match = bucketRegex.exec(xml)) !== null) {
11
+ buckets.push({ name: match[1], creationDate: match[2] });
12
+ }
13
+ return { buckets };
14
+ }
15
+ /**
16
+ * Parse S3 XML list objects response into a structured object.
17
+ */
18
+ function parseListObjectsXml(xml) {
19
+ const nameMatch = xml.match(/<Name>(.*?)<\/Name>/);
20
+ const prefixMatch = xml.match(/<Prefix>(.*?)<\/Prefix>/);
21
+ const truncatedMatch = xml.match(/<IsTruncated>(.*?)<\/IsTruncated>/);
22
+ const contents = [];
23
+ const contentsRegex = /<Contents>\s*<Key>(.*?)<\/Key>\s*<LastModified>(.*?)<\/LastModified>.*?<Size>(.*?)<\/Size>.*?<StorageClass>(.*?)<\/StorageClass>\s*<\/Contents>/gs;
24
+ let match;
25
+ while ((match = contentsRegex.exec(xml)) !== null) {
26
+ contents.push({
27
+ key: match[1],
28
+ lastModified: match[2],
29
+ size: match[3],
30
+ storageClass: match[4],
31
+ });
32
+ }
33
+ const commonPrefixes = [];
34
+ const prefixRegex = /<CommonPrefixes>\s*<Prefix>(.*?)<\/Prefix>\s*<\/CommonPrefixes>/gs;
35
+ while ((match = prefixRegex.exec(xml)) !== null) {
36
+ commonPrefixes.push(match[1]);
37
+ }
38
+ return {
39
+ name: nameMatch?.[1] ?? "",
40
+ prefix: prefixMatch?.[1] ?? "",
41
+ isTruncated: truncatedMatch?.[1] === "true",
42
+ contents,
43
+ commonPrefixes,
44
+ };
45
+ }
46
+ /**
47
+ * Parse S3 XML ACL response into a structured object.
48
+ */
49
+ function parseAclXml(xml) {
50
+ const ownerIdMatch = xml.match(/<Owner>\s*<ID>(.*?)<\/ID>/s);
51
+ const ownerNameMatch = xml.match(/<Owner>.*?<DisplayName>(.*?)<\/DisplayName>/s);
52
+ const grants = [];
53
+ const grantRegex = /<Grant>\s*<Grantee[^>]*>.*?(?:<ID>(.*?)<\/ID>|<URI>(.*?)<\/URI>).*?<\/Grantee>\s*<Permission>(.*?)<\/Permission>\s*<\/Grant>/gs;
54
+ let match;
55
+ while ((match = grantRegex.exec(xml)) !== null) {
56
+ grants.push({
57
+ grantee: match[1] || match[2] || "unknown",
58
+ permission: match[3],
59
+ });
60
+ }
61
+ return {
62
+ owner: {
63
+ id: ownerIdMatch?.[1] ?? "",
64
+ displayName: ownerNameMatch?.[1] ?? "",
65
+ },
66
+ grants,
67
+ };
68
+ }
69
+ /**
70
+ * Parse S3 XML initiate multipart upload response.
71
+ */
72
+ function parseInitiateMultipartXml(xml) {
73
+ const bucketMatch = xml.match(/<Bucket>(.*?)<\/Bucket>/);
74
+ const keyMatch = xml.match(/<Key>(.*?)<\/Key>/);
75
+ const uploadIdMatch = xml.match(/<UploadId>(.*?)<\/UploadId>/);
76
+ return {
77
+ bucket: bucketMatch?.[1] ?? "",
78
+ key: keyMatch?.[1] ?? "",
79
+ uploadId: uploadIdMatch?.[1] ?? "",
80
+ };
81
+ }
82
+ /**
83
+ * Parse S3 XML versioning configuration response.
84
+ */
85
+ function parseVersioningXml(xml) {
86
+ const statusMatch = xml.match(/<Status>(.*?)<\/Status>/);
87
+ return { status: statusMatch?.[1] ?? "Disabled" };
88
+ }
89
+ /**
90
+ * Parse S3 XML list object versions response.
91
+ */
92
+ function parseListObjectVersionsXml(xml) {
93
+ const nameMatch = xml.match(/<Name>(.*?)<\/Name>/);
94
+ const prefixMatch = xml.match(/<ListVersionsResult[^>]*>.*?<Prefix>(.*?)<\/Prefix>/s);
95
+ const truncatedMatch = xml.match(/<IsTruncated>(.*?)<\/IsTruncated>/);
96
+ const keyMarkerMatch = xml.match(/<KeyMarker>(.*?)<\/KeyMarker>/);
97
+ const versionIdMarkerMatch = xml.match(/<VersionIdMarker>(.*?)<\/VersionIdMarker>/);
98
+ const nextKeyMarkerMatch = xml.match(/<NextKeyMarker>(.*?)<\/NextKeyMarker>/);
99
+ const nextVersionIdMarkerMatch = xml.match(/<NextVersionIdMarker>(.*?)<\/NextVersionIdMarker>/);
100
+ const versions = [];
101
+ const versionRegex = /<Version>\s*<Key>(.*?)<\/Key>\s*<VersionId>(.*?)<\/VersionId>\s*<IsLatest>(.*?)<\/IsLatest>\s*<LastModified>(.*?)<\/LastModified>.*?<Size>(.*?)<\/Size>.*?<StorageClass>(.*?)<\/StorageClass>\s*<\/Version>/gs;
102
+ let match;
103
+ while ((match = versionRegex.exec(xml)) !== null) {
104
+ versions.push({
105
+ key: match[1],
106
+ versionId: match[2],
107
+ isLatest: match[3] === "true",
108
+ lastModified: match[4],
109
+ size: match[5],
110
+ storageClass: match[6],
111
+ });
112
+ }
113
+ const deleteMarkers = [];
114
+ const deleteMarkerRegex = /<DeleteMarker>\s*<Key>(.*?)<\/Key>\s*<VersionId>(.*?)<\/VersionId>\s*<IsLatest>(.*?)<\/IsLatest>\s*<LastModified>(.*?)<\/LastModified>.*?<\/DeleteMarker>/gs;
115
+ while ((match = deleteMarkerRegex.exec(xml)) !== null) {
116
+ deleteMarkers.push({
117
+ key: match[1],
118
+ versionId: match[2],
119
+ isLatest: match[3] === "true",
120
+ lastModified: match[4],
121
+ });
122
+ }
123
+ const commonPrefixes = [];
124
+ const prefixRegex = /<CommonPrefixes>\s*<Prefix>(.*?)<\/Prefix>\s*<\/CommonPrefixes>/gs;
125
+ while ((match = prefixRegex.exec(xml)) !== null) {
126
+ commonPrefixes.push(match[1]);
127
+ }
128
+ return {
129
+ name: nameMatch?.[1] ?? "",
130
+ prefix: prefixMatch?.[1] ?? "",
131
+ isTruncated: truncatedMatch?.[1] === "true",
132
+ keyMarker: keyMarkerMatch?.[1] ?? "",
133
+ versionIdMarker: versionIdMarkerMatch?.[1] ?? "",
134
+ nextKeyMarker: nextKeyMarkerMatch?.[1] ?? "",
135
+ nextVersionIdMarker: nextVersionIdMarkerMatch?.[1] ?? "",
136
+ versions,
137
+ deleteMarkers,
138
+ commonPrefixes,
139
+ };
140
+ }
141
+ /**
142
+ * Parse S3 XML ListParts response into a structured object.
143
+ */
144
+ function parseListPartsXml(xml) {
145
+ const bucketMatch = xml.match(/<Bucket>(.*?)<\/Bucket>/);
146
+ const keyMatch = xml.match(/<Key>(.*?)<\/Key>/);
147
+ const uploadIdMatch = xml.match(/<UploadId>(.*?)<\/UploadId>/);
148
+ const truncatedMatch = xml.match(/<IsTruncated>(.*?)<\/IsTruncated>/);
149
+ const nextMarkerMatch = xml.match(/<NextPartNumberMarker>(.*?)<\/NextPartNumberMarker>/);
150
+ const parts = [];
151
+ const partRegex = /<Part>\s*<PartNumber>(\d+)<\/PartNumber>\s*<LastModified>(.*?)<\/LastModified>\s*<ETag>(.*?)<\/ETag>\s*<Size>(\d+)<\/Size>\s*<\/Part>/gs;
152
+ let match;
153
+ while ((match = partRegex.exec(xml)) !== null) {
154
+ parts.push({
155
+ partNumber: parseInt(match[1], 10),
156
+ lastModified: match[2],
157
+ etag: match[3],
158
+ size: match[4],
159
+ });
160
+ }
161
+ return {
162
+ bucket: bucketMatch?.[1] ?? "",
163
+ key: keyMatch?.[1] ?? "",
164
+ uploadId: uploadIdMatch?.[1] ?? "",
165
+ isTruncated: truncatedMatch?.[1] === "true",
166
+ nextPartNumberMarker: nextMarkerMatch?.[1] ?? "",
167
+ parts,
168
+ };
169
+ }
170
+ /**
171
+ * Parse S3 XML ListMultipartUploads response into a structured object.
172
+ */
173
+ function parseListMultipartUploadsXml(xml) {
174
+ const bucketMatch = xml.match(/<Bucket>(.*?)<\/Bucket>/);
175
+ const truncatedMatch = xml.match(/<IsTruncated>(.*?)<\/IsTruncated>/);
176
+ const nextKeyMarkerMatch = xml.match(/<NextKeyMarker>(.*?)<\/NextKeyMarker>/);
177
+ const nextUploadIdMarkerMatch = xml.match(/<NextUploadIdMarker>(.*?)<\/NextUploadIdMarker>/);
178
+ const uploads = [];
179
+ const uploadRegex = /<Upload>\s*<Key>(.*?)<\/Key>\s*<UploadId>(.*?)<\/UploadId>.*?<Initiated>(.*?)<\/Initiated>.*?<StorageClass>(.*?)<\/StorageClass>\s*<\/Upload>/gs;
180
+ let match;
181
+ while ((match = uploadRegex.exec(xml)) !== null) {
182
+ uploads.push({
183
+ key: match[1],
184
+ uploadId: match[2],
185
+ initiated: match[3],
186
+ storageClass: match[4],
187
+ });
188
+ }
189
+ const commonPrefixes = [];
190
+ const prefixRegex = /<CommonPrefixes>\s*<Prefix>(.*?)<\/Prefix>\s*<\/CommonPrefixes>/gs;
191
+ while ((match = prefixRegex.exec(xml)) !== null) {
192
+ commonPrefixes.push(match[1]);
193
+ }
194
+ return {
195
+ bucket: bucketMatch?.[1] ?? "",
196
+ isTruncated: truncatedMatch?.[1] === "true",
197
+ nextKeyMarker: nextKeyMarkerMatch?.[1] ?? "",
198
+ nextUploadIdMarker: nextUploadIdMarkerMatch?.[1] ?? "",
199
+ uploads,
200
+ commonPrefixes,
201
+ };
202
+ }
203
+ export function registerStorageObjectTools(server, client) {
204
+ // ─── Bucket Query Tools ────────────────────────────────────────────────────
205
+ server.tool("ncloud_list_buckets", "List all Object Storage buckets in the current region", {}, async () => {
206
+ try {
207
+ const response = await client.request({ method: "GET" });
208
+ const result = parseListBucketsXml(response.body);
209
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
210
+ }
211
+ catch (error) {
212
+ return { content: [{ type: "text", text: error.message }], isError: true };
213
+ }
214
+ });
215
+ // ─── Bucket Create Tools ───────────────────────────────────────────────────
216
+ server.tool("ncloud_create_bucket", "Create a new Object Storage bucket. Use dryRun=true to preview.", {
217
+ bucketName: z.string({
218
+ required_error: "필수 파라미터 'bucketName'이 누락되었습니다.",
219
+ }).describe("Name of the bucket to create"),
220
+ dryRun: z.boolean().optional().default(false).describe("If true, returns a preview without actually creating"),
221
+ }, async (params) => {
222
+ try {
223
+ if (params.dryRun) {
224
+ const preview = {
225
+ label: "🔍 Dry-Run Preview: Bucket Creation",
226
+ bucketName: params.bucketName,
227
+ region: client.getRegionCode(),
228
+ message: "이 요청은 실제 버킷을 생성하지 않습니다. dryRun=false로 호출하면 생성됩니다.",
229
+ };
230
+ return { content: [{ type: "text", text: JSON.stringify(preview, null, 2) }] };
231
+ }
232
+ await client.request({ method: "PUT", bucket: params.bucketName });
233
+ const summary = {
234
+ 리소스타입: "Bucket",
235
+ 리소스명: params.bucketName,
236
+ 리전: client.getRegionCode(),
237
+ 상태: "created",
238
+ };
239
+ return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
240
+ }
241
+ catch (error) {
242
+ return { content: [{ type: "text", text: error.message }], isError: true };
243
+ }
244
+ });
245
+ // ─── Bucket Destructive Tools ──────────────────────────────────────────────
246
+ server.tool("ncloud_delete_bucket", "⚠️ Destructive: Permanently delete an Object Storage bucket. The bucket must be empty. Set confirm=true to execute.", {
247
+ bucketName: z.string({
248
+ required_error: "필수 파라미터 'bucketName'이 누락되었습니다.",
249
+ }).describe("Name of the bucket to delete"),
250
+ confirm: z.boolean().optional().default(false).describe("Must be true to actually execute the destructive operation"),
251
+ }, async (params) => {
252
+ try {
253
+ if (!params.confirm) {
254
+ const message = `⚠️ This will permanently delete Bucket [${params.bucketName}]. The bucket must be empty. Do you want to proceed? (yes/no)\n\nTo execute, call this tool again with confirm=true.`;
255
+ return { content: [{ type: "text", text: message }] };
256
+ }
257
+ await client.request({ method: "DELETE", bucket: params.bucketName });
258
+ const result = { message: `✅ 버킷 '${params.bucketName}'이(가) 삭제되었습니다.` };
259
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
260
+ }
261
+ catch (error) {
262
+ return { content: [{ type: "text", text: error.message }], isError: true };
263
+ }
264
+ });
265
+ // ─── Object Query Tools ────────────────────────────────────────────────────
266
+ server.tool("ncloud_list_objects", "List objects in an Object Storage bucket", {
267
+ bucketName: z.string({
268
+ required_error: "필수 파라미터 'bucketName'이 누락되었습니다.",
269
+ }).describe("Name of the bucket"),
270
+ prefix: z.string().optional().describe("Limits results to keys beginning with this prefix"),
271
+ delimiter: z.string().optional().describe("Delimiter for grouping keys (commonly '/')"),
272
+ maxKeys: z.number().optional().describe("Maximum number of keys to return (default 1000)"),
273
+ marker: z.string().optional().describe("Marker for pagination (key to start after)"),
274
+ }, async (params) => {
275
+ try {
276
+ const queryParams = {};
277
+ if (params.prefix)
278
+ queryParams["prefix"] = params.prefix;
279
+ if (params.delimiter)
280
+ queryParams["delimiter"] = params.delimiter;
281
+ if (params.maxKeys)
282
+ queryParams["max-keys"] = String(params.maxKeys);
283
+ if (params.marker)
284
+ queryParams["marker"] = params.marker;
285
+ const response = await client.request({
286
+ method: "GET",
287
+ bucket: params.bucketName,
288
+ queryParams,
289
+ });
290
+ const result = parseListObjectsXml(response.body);
291
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
292
+ }
293
+ catch (error) {
294
+ return { content: [{ type: "text", text: error.message }], isError: true };
295
+ }
296
+ });
297
+ server.tool("ncloud_get_object", "Get (download) an object from an Object Storage bucket. Returns the object content as text.", {
298
+ bucketName: z.string({
299
+ required_error: "필수 파라미터 'bucketName'이 누락되었습니다.",
300
+ }).describe("Name of the bucket"),
301
+ key: z.string({
302
+ required_error: "필수 파라미터 'key'가 누락되었습니다.",
303
+ }).describe("Object key (path) to retrieve"),
304
+ }, async (params) => {
305
+ try {
306
+ const response = await client.request({
307
+ method: "GET",
308
+ bucket: params.bucketName,
309
+ key: params.key,
310
+ });
311
+ const result = {
312
+ bucket: params.bucketName,
313
+ key: params.key,
314
+ contentLength: response.headers.get("content-length"),
315
+ contentType: response.headers.get("content-type"),
316
+ lastModified: response.headers.get("last-modified"),
317
+ body: response.body,
318
+ };
319
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
320
+ }
321
+ catch (error) {
322
+ return { content: [{ type: "text", text: error.message }], isError: true };
323
+ }
324
+ });
325
+ // ─── Object Create/Update Tools ────────────────────────────────────────────
326
+ server.tool("ncloud_put_object", "Upload (put) an object to an Object Storage bucket. Use dryRun=true to preview.", {
327
+ bucketName: z.string({
328
+ required_error: "필수 파라미터 'bucketName'이 누락되었습니다.",
329
+ }).describe("Name of the bucket"),
330
+ key: z.string({
331
+ required_error: "필수 파라미터 'key'가 누락되었습니다.",
332
+ }).describe("Object key (path) to upload to"),
333
+ body: z.string({
334
+ required_error: "필수 파라미터 'body'가 누락되었습니다.",
335
+ }).describe("Content to upload as the object body"),
336
+ contentType: z.string().optional().describe("Content-Type header for the object (e.g., 'text/plain', 'application/json')"),
337
+ dryRun: z.boolean().optional().default(false).describe("If true, returns a preview without actually uploading"),
338
+ }, async (params) => {
339
+ try {
340
+ if (params.dryRun) {
341
+ const preview = {
342
+ label: "🔍 Dry-Run Preview: Object Upload",
343
+ bucketName: params.bucketName,
344
+ key: params.key,
345
+ contentType: params.contentType ?? "application/octet-stream",
346
+ bodySize: `${params.body.length} bytes`,
347
+ message: "이 요청은 실제 오브젝트를 업로드하지 않습니다. dryRun=false로 호출하면 업로드됩니다.",
348
+ };
349
+ return { content: [{ type: "text", text: JSON.stringify(preview, null, 2) }] };
350
+ }
351
+ const headers = {};
352
+ if (params.contentType) {
353
+ headers["content-type"] = params.contentType;
354
+ }
355
+ await client.request({
356
+ method: "PUT",
357
+ bucket: params.bucketName,
358
+ key: params.key,
359
+ headers,
360
+ body: params.body,
361
+ });
362
+ const summary = {
363
+ 리소스타입: "Object",
364
+ 버킷: params.bucketName,
365
+ 키: params.key,
366
+ 크기: `${params.body.length} bytes`,
367
+ 상태: "uploaded",
368
+ };
369
+ return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
370
+ }
371
+ catch (error) {
372
+ return { content: [{ type: "text", text: error.message }], isError: true };
373
+ }
374
+ });
375
+ // ─── Object Destructive Tools ──────────────────────────────────────────────
376
+ server.tool("ncloud_delete_object", "⚠️ Destructive: Permanently delete an object from an Object Storage bucket. Set confirm=true to execute.", {
377
+ bucketName: z.string({
378
+ required_error: "필수 파라미터 'bucketName'이 누락되었습니다.",
379
+ }).describe("Name of the bucket"),
380
+ key: z.string({
381
+ required_error: "필수 파라미터 'key'가 누락되었습니다.",
382
+ }).describe("Object key (path) to delete"),
383
+ confirm: z.boolean().optional().default(false).describe("Must be true to actually execute the destructive operation"),
384
+ }, async (params) => {
385
+ try {
386
+ if (!params.confirm) {
387
+ const message = `⚠️ This will permanently delete Object [${params.bucketName}/${params.key}]. Do you want to proceed? (yes/no)\n\nTo execute, call this tool again with confirm=true.`;
388
+ return { content: [{ type: "text", text: message }] };
389
+ }
390
+ await client.request({
391
+ method: "DELETE",
392
+ bucket: params.bucketName,
393
+ key: params.key,
394
+ });
395
+ const result = { message: `✅ 오브젝트 '${params.bucketName}/${params.key}'이(가) 삭제되었습니다.` };
396
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
397
+ }
398
+ catch (error) {
399
+ return { content: [{ type: "text", text: error.message }], isError: true };
400
+ }
401
+ });
402
+ // ─── Multipart Upload Tools ────────────────────────────────────────────────
403
+ server.tool("ncloud_initiate_multipart_upload", "Initiate a multipart upload for a large object in Object Storage", {
404
+ bucketName: z.string({
405
+ required_error: "필수 파라미터 'bucketName'이 누락되었습니다.",
406
+ }).describe("Name of the bucket"),
407
+ key: z.string({
408
+ required_error: "필수 파라미터 'key'가 누락되었습니다.",
409
+ }).describe("Object key (path) for the multipart upload"),
410
+ contentType: z.string().optional().describe("Content-Type for the object"),
411
+ }, async (params) => {
412
+ try {
413
+ const headers = {};
414
+ if (params.contentType) {
415
+ headers["content-type"] = params.contentType;
416
+ }
417
+ const response = await client.request({
418
+ method: "POST",
419
+ bucket: params.bucketName,
420
+ key: params.key,
421
+ queryParams: { uploads: "" },
422
+ headers,
423
+ });
424
+ const result = parseInitiateMultipartXml(response.body);
425
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
426
+ }
427
+ catch (error) {
428
+ return { content: [{ type: "text", text: error.message }], isError: true };
429
+ }
430
+ });
431
+ server.tool("ncloud_complete_multipart_upload", "Complete a multipart upload by assembling previously uploaded parts", {
432
+ bucketName: z.string({
433
+ required_error: "필수 파라미터 'bucketName'이 누락되었습니다.",
434
+ }).describe("Name of the bucket"),
435
+ key: z.string({
436
+ required_error: "필수 파라미터 'key'가 누락되었습니다.",
437
+ }).describe("Object key (path) for the multipart upload"),
438
+ uploadId: z.string({
439
+ required_error: "필수 파라미터 'uploadId'가 누락되었습니다.",
440
+ }).describe("Upload ID returned from initiate multipart upload"),
441
+ parts: z.array(z.object({
442
+ partNumber: z.number().describe("Part number (1-based)"),
443
+ etag: z.string().describe("ETag returned when the part was uploaded"),
444
+ })).min(1).describe("List of parts with their part numbers and ETags"),
445
+ }, async (params) => {
446
+ try {
447
+ // Build the CompleteMultipartUpload XML body
448
+ const partsXml = params.parts
449
+ .map((p) => `<Part><PartNumber>${p.partNumber}</PartNumber><ETag>${p.etag}</ETag></Part>`)
450
+ .join("");
451
+ const body = `<CompleteMultipartUpload>${partsXml}</CompleteMultipartUpload>`;
452
+ const response = await client.request({
453
+ method: "POST",
454
+ bucket: params.bucketName,
455
+ key: params.key,
456
+ queryParams: { uploadId: params.uploadId },
457
+ headers: { "content-type": "application/xml" },
458
+ body,
459
+ });
460
+ // Parse completion response
461
+ const locationMatch = response.body.match(/<Location>(.*?)<\/Location>/);
462
+ const keyMatch = response.body.match(/<Key>(.*?)<\/Key>/);
463
+ const etagMatch = response.body.match(/<ETag>(.*?)<\/ETag>/);
464
+ const result = {
465
+ location: locationMatch?.[1] ?? "",
466
+ bucket: params.bucketName,
467
+ key: keyMatch?.[1] ?? params.key,
468
+ etag: etagMatch?.[1] ?? "",
469
+ };
470
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
471
+ }
472
+ catch (error) {
473
+ return { content: [{ type: "text", text: error.message }], isError: true };
474
+ }
475
+ });
476
+ server.tool("ncloud_upload_part", "Upload a part in a multipart upload. Use InitiateMultipartUpload first to get an uploadId, then upload parts, then CompleteMultipartUpload.", {
477
+ bucketName: z.string({
478
+ required_error: "필수 파라미터 'bucketName'이 누락되었습니다.",
479
+ }).describe("Name of the bucket"),
480
+ objectName: z.string({
481
+ required_error: "필수 파라미터 'objectName'이 누락되었습니다.",
482
+ }).describe("Object key (path) for the multipart upload"),
483
+ uploadId: z.string({
484
+ required_error: "필수 파라미터 'uploadId'가 누락되었습니다.",
485
+ }).describe("Upload ID returned from initiate multipart upload"),
486
+ partNumber: z.number({
487
+ required_error: "필수 파라미터 'partNumber'가 누락되었습니다.",
488
+ }).min(1).max(10000).describe("Part number (1 to 10000)"),
489
+ body: z.string({
490
+ required_error: "필수 파라미터 'body'가 누락되었습니다.",
491
+ }).describe("Content of this part to upload"),
492
+ }, async (params) => {
493
+ try {
494
+ const response = await client.request({
495
+ method: "PUT",
496
+ bucket: params.bucketName,
497
+ key: params.objectName,
498
+ queryParams: {
499
+ partNumber: String(params.partNumber),
500
+ uploadId: params.uploadId,
501
+ },
502
+ body: params.body,
503
+ });
504
+ const etag = response.headers.get("etag") ?? "";
505
+ const result = {
506
+ bucket: params.bucketName,
507
+ key: params.objectName,
508
+ uploadId: params.uploadId,
509
+ partNumber: params.partNumber,
510
+ etag,
511
+ size: `${params.body.length} bytes`,
512
+ };
513
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
514
+ }
515
+ catch (error) {
516
+ return { content: [{ type: "text", text: error.message }], isError: true };
517
+ }
518
+ });
519
+ server.tool("ncloud_list_parts", "List uploaded parts for a multipart upload in progress", {
520
+ bucketName: z.string({
521
+ required_error: "필수 파라미터 'bucketName'이 누락되었습니다.",
522
+ }).describe("Name of the bucket"),
523
+ objectName: z.string({
524
+ required_error: "필수 파라미터 'objectName'이 누락되었습니다.",
525
+ }).describe("Object key (path) for the multipart upload"),
526
+ uploadId: z.string({
527
+ required_error: "필수 파라미터 'uploadId'가 누락되었습니다.",
528
+ }).describe("Upload ID returned from initiate multipart upload"),
529
+ }, async (params) => {
530
+ try {
531
+ const response = await client.request({
532
+ method: "GET",
533
+ bucket: params.bucketName,
534
+ key: params.objectName,
535
+ queryParams: { uploadId: params.uploadId },
536
+ });
537
+ const result = parseListPartsXml(response.body);
538
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
539
+ }
540
+ catch (error) {
541
+ return { content: [{ type: "text", text: error.message }], isError: true };
542
+ }
543
+ });
544
+ server.tool("ncloud_abort_multipart_upload", "⚠️ Destructive: Abort a multipart upload and delete all uploaded parts. Set confirm=true to execute.", {
545
+ bucketName: z.string({
546
+ required_error: "필수 파라미터 'bucketName'이 누락되었습니다.",
547
+ }).describe("Name of the bucket"),
548
+ objectName: z.string({
549
+ required_error: "필수 파라미터 'objectName'이 누락되었습니다.",
550
+ }).describe("Object key (path) for the multipart upload"),
551
+ uploadId: z.string({
552
+ required_error: "필수 파라미터 'uploadId'가 누락되었습니다.",
553
+ }).describe("Upload ID of the multipart upload to abort"),
554
+ confirm: z.boolean().optional().default(false).describe("Must be true to actually execute the destructive operation"),
555
+ }, async (params) => {
556
+ try {
557
+ if (!params.confirm) {
558
+ const message = `⚠️ This will permanently abort multipart upload [${params.uploadId}] for object [${params.bucketName}/${params.objectName}] and delete all uploaded parts. Do you want to proceed? (yes/no)\n\nTo execute, call this tool again with confirm=true.`;
559
+ return { content: [{ type: "text", text: message }] };
560
+ }
561
+ await client.request({
562
+ method: "DELETE",
563
+ bucket: params.bucketName,
564
+ key: params.objectName,
565
+ queryParams: { uploadId: params.uploadId },
566
+ });
567
+ const result = {
568
+ message: `✅ 멀티파트 업로드 '${params.uploadId}'이(가) 중단되었습니다. 업로드된 파트가 삭제되었습니다.`,
569
+ bucket: params.bucketName,
570
+ key: params.objectName,
571
+ uploadId: params.uploadId,
572
+ };
573
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
574
+ }
575
+ catch (error) {
576
+ return { content: [{ type: "text", text: error.message }], isError: true };
577
+ }
578
+ });
579
+ server.tool("ncloud_list_multipart_uploads", "List in-progress multipart uploads for a bucket", {
580
+ bucketName: z.string({
581
+ required_error: "필수 파라미터 'bucketName'이 누락되었습니다.",
582
+ }).describe("Name of the bucket"),
583
+ prefix: z.string().optional().describe("Limits results to uploads for keys beginning with this prefix"),
584
+ delimiter: z.string().optional().describe("Delimiter for grouping keys (commonly '/')"),
585
+ }, async (params) => {
586
+ try {
587
+ const queryParams = { uploads: "" };
588
+ if (params.prefix)
589
+ queryParams["prefix"] = params.prefix;
590
+ if (params.delimiter)
591
+ queryParams["delimiter"] = params.delimiter;
592
+ const response = await client.request({
593
+ method: "GET",
594
+ bucket: params.bucketName,
595
+ queryParams,
596
+ });
597
+ const result = parseListMultipartUploadsXml(response.body);
598
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
599
+ }
600
+ catch (error) {
601
+ return { content: [{ type: "text", text: error.message }], isError: true };
602
+ }
603
+ });
604
+ // ─── ACL Management Tools ─────────────────────────────────────────────────
605
+ server.tool("ncloud_get_bucket_acl", "Get the access control list (ACL) of an Object Storage bucket", {
606
+ bucketName: z.string({
607
+ required_error: "필수 파라미터 'bucketName'이 누락되었습니다.",
608
+ }).describe("Name of the bucket"),
609
+ }, async (params) => {
610
+ try {
611
+ const response = await client.request({
612
+ method: "GET",
613
+ bucket: params.bucketName,
614
+ queryParams: { acl: "" },
615
+ });
616
+ const result = parseAclXml(response.body);
617
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
618
+ }
619
+ catch (error) {
620
+ return { content: [{ type: "text", text: error.message }], isError: true };
621
+ }
622
+ });
623
+ server.tool("ncloud_put_bucket_acl", "Set the access control list (ACL) of an Object Storage bucket using a canned ACL", {
624
+ bucketName: z.string({
625
+ required_error: "필수 파라미터 'bucketName'이 누락되었습니다.",
626
+ }).describe("Name of the bucket"),
627
+ acl: z.enum(["private", "public-read", "public-read-write", "authenticated-read"], {
628
+ required_error: "필수 파라미터 'acl'이 누락되었습니다.",
629
+ }).describe("Canned ACL to apply (private, public-read, public-read-write, authenticated-read)"),
630
+ }, async (params) => {
631
+ try {
632
+ await client.request({
633
+ method: "PUT",
634
+ bucket: params.bucketName,
635
+ queryParams: { acl: "" },
636
+ headers: { "x-amz-acl": params.acl },
637
+ });
638
+ const result = {
639
+ message: `✅ 버킷 '${params.bucketName}'의 ACL이 '${params.acl}'로 설정되었습니다.`,
640
+ bucket: params.bucketName,
641
+ acl: params.acl,
642
+ };
643
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
644
+ }
645
+ catch (error) {
646
+ return { content: [{ type: "text", text: error.message }], isError: true };
647
+ }
648
+ });
649
+ // ─── Object ACL Management Tools ────────────────────────────────────────────
650
+ server.tool("ncloud_get_object_acl", "Get the access control list (ACL) of an object in Object Storage", {
651
+ bucketName: z.string({
652
+ required_error: "필수 파라미터 'bucketName'이 누락되었습니다.",
653
+ }).describe("Name of the bucket containing the object"),
654
+ objectName: z.string({
655
+ required_error: "필수 파라미터 'objectName'이 누락되었습니다.",
656
+ }).describe("Object key (path) to get ACL for"),
657
+ }, async (params) => {
658
+ try {
659
+ const response = await client.request({
660
+ method: "GET",
661
+ bucket: params.bucketName,
662
+ key: params.objectName,
663
+ queryParams: { acl: "" },
664
+ });
665
+ const result = parseAclXml(response.body);
666
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
667
+ }
668
+ catch (error) {
669
+ return { content: [{ type: "text", text: error.message }], isError: true };
670
+ }
671
+ });
672
+ server.tool("ncloud_put_object_acl", "Set the access control list (ACL) of an object in Object Storage using a canned ACL", {
673
+ bucketName: z.string({
674
+ required_error: "필수 파라미터 'bucketName'이 누락되었습니다.",
675
+ }).describe("Name of the bucket containing the object"),
676
+ objectName: z.string({
677
+ required_error: "필수 파라미터 'objectName'이 누락되었습니다.",
678
+ }).describe("Object key (path) to set ACL for"),
679
+ acl: z.enum(["private", "public-read", "public-read-write", "authenticated-read"], {
680
+ required_error: "필수 파라미터 'acl'이 누락되었습니다.",
681
+ }).describe("Canned ACL to apply (private, public-read, public-read-write, authenticated-read)"),
682
+ }, async (params) => {
683
+ try {
684
+ await client.request({
685
+ method: "PUT",
686
+ bucket: params.bucketName,
687
+ key: params.objectName,
688
+ queryParams: { acl: "" },
689
+ headers: { "x-amz-acl": params.acl },
690
+ });
691
+ const result = {
692
+ message: `✅ 오브젝트 '${params.bucketName}/${params.objectName}'의 ACL이 '${params.acl}'로 설정되었습니다.`,
693
+ bucket: params.bucketName,
694
+ object: params.objectName,
695
+ acl: params.acl,
696
+ };
697
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
698
+ }
699
+ catch (error) {
700
+ return { content: [{ type: "text", text: error.message }], isError: true };
701
+ }
702
+ });
703
+ // ─── Versioning Management Tools ───────────────────────────────────────────
704
+ server.tool("ncloud_get_bucket_versioning", "Get the versioning state of an Object Storage bucket", {
705
+ bucketName: z.string({
706
+ required_error: "필수 파라미터 'bucketName'이 누락되었습니다.",
707
+ }).describe("Name of the bucket to check versioning status"),
708
+ }, async (params) => {
709
+ try {
710
+ const response = await client.request({
711
+ method: "GET",
712
+ bucket: params.bucketName,
713
+ queryParams: { versioning: "" },
714
+ });
715
+ const result = parseVersioningXml(response.body);
716
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
717
+ }
718
+ catch (error) {
719
+ return { content: [{ type: "text", text: error.message }], isError: true };
720
+ }
721
+ });
722
+ server.tool("ncloud_put_bucket_versioning", "Set the versioning state of an Object Storage bucket (Enabled or Suspended)", {
723
+ bucketName: z.string({
724
+ required_error: "필수 파라미터 'bucketName'이 누락되었습니다.",
725
+ }).describe("Name of the bucket"),
726
+ status: z.enum(["Enabled", "Suspended"], {
727
+ required_error: "필수 파라미터 'status'가 누락되었습니다.",
728
+ }).describe("Versioning status to set (Enabled | Suspended)"),
729
+ }, async (params) => {
730
+ try {
731
+ const body = `<VersioningConfiguration><Status>${params.status}</Status></VersioningConfiguration>`;
732
+ await client.request({
733
+ method: "PUT",
734
+ bucket: params.bucketName,
735
+ queryParams: { versioning: "" },
736
+ headers: { "content-type": "application/xml" },
737
+ body,
738
+ });
739
+ const result = {
740
+ message: `✅ 버킷 '${params.bucketName}'의 버전 관리가 '${params.status}'로 설정되었습니다.`,
741
+ bucket: params.bucketName,
742
+ versioningStatus: params.status,
743
+ };
744
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
745
+ }
746
+ catch (error) {
747
+ return { content: [{ type: "text", text: error.message }], isError: true };
748
+ }
749
+ });
750
+ server.tool("ncloud_list_object_versions", "List all versions of objects in a versioning-enabled Object Storage bucket", {
751
+ bucketName: z.string({
752
+ required_error: "필수 파라미터 'bucketName'이 누락되었습니다.",
753
+ }).describe("Name of the bucket"),
754
+ prefix: z.string().optional().describe("Limits results to keys beginning with this prefix"),
755
+ delimiter: z.string().optional().describe("Delimiter for grouping keys (commonly '/')"),
756
+ keyMarker: z.string().optional().describe("Key marker for pagination"),
757
+ versionIdMarker: z.string().optional().describe("Version ID marker for pagination (used with keyMarker)"),
758
+ maxKeys: z.number().optional().describe("Maximum number of keys to return (default 1000)"),
759
+ }, async (params) => {
760
+ try {
761
+ const queryParams = { versions: "" };
762
+ if (params.prefix)
763
+ queryParams["prefix"] = params.prefix;
764
+ if (params.delimiter)
765
+ queryParams["delimiter"] = params.delimiter;
766
+ if (params.keyMarker)
767
+ queryParams["key-marker"] = params.keyMarker;
768
+ if (params.versionIdMarker)
769
+ queryParams["version-id-marker"] = params.versionIdMarker;
770
+ if (params.maxKeys)
771
+ queryParams["max-keys"] = String(params.maxKeys);
772
+ const response = await client.request({
773
+ method: "GET",
774
+ bucket: params.bucketName,
775
+ queryParams,
776
+ });
777
+ const result = parseListObjectVersionsXml(response.body);
778
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
779
+ }
780
+ catch (error) {
781
+ return { content: [{ type: "text", text: error.message }], isError: true };
782
+ }
783
+ });
784
+ // ─── Object Restore Tools ──────────────────────────────────────────────────
785
+ server.tool("ncloud_restore_object", "Restore an object stored in Archive class to make it accessible. The restored copy is available for the specified number of days.", {
786
+ bucketName: z.string({
787
+ required_error: "필수 파라미터 'bucketName'이 누락되었습니다.",
788
+ }).describe("Name of the bucket containing the archived object"),
789
+ objectName: z.string({
790
+ required_error: "필수 파라미터 'objectName'이 누락되었습니다.",
791
+ }).describe("Key (path) of the archived object to restore"),
792
+ days: z.number({
793
+ required_error: "필수 파라미터 'days'가 누락되었습니다.",
794
+ }).min(1, { message: "잘못된 파라미터: 'days'는 1 이상이어야 합니다." })
795
+ .describe("Number of days to keep the restored copy accessible"),
796
+ }, async (params) => {
797
+ try {
798
+ const body = `<RestoreRequest><Days>${params.days}</Days></RestoreRequest>`;
799
+ await client.request({
800
+ method: "POST",
801
+ bucket: params.bucketName,
802
+ key: params.objectName,
803
+ queryParams: { restore: "" },
804
+ headers: { "content-type": "application/xml" },
805
+ body,
806
+ });
807
+ const result = {
808
+ message: `✅ 오브젝트 '${params.bucketName}/${params.objectName}' 복원이 요청되었습니다. 복원 완료까지 수 시간이 소요될 수 있습니다.`,
809
+ bucket: params.bucketName,
810
+ object: params.objectName,
811
+ restoreDays: params.days,
812
+ };
813
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
814
+ }
815
+ catch (error) {
816
+ return { content: [{ type: "text", text: error.message }], isError: true };
817
+ }
818
+ });
819
+ // ─── Bucket Location Tools ─────────────────────────────────────────────────
820
+ server.tool("ncloud_get_bucket_location", "Get the region (location constraint) of an Object Storage bucket", {
821
+ bucketName: z.string({
822
+ required_error: "필수 파라미터 'bucketName'이 누락되었습니다.",
823
+ }).describe("Name of the bucket to get location for"),
824
+ }, async (params) => {
825
+ try {
826
+ const response = await client.request({
827
+ method: "GET",
828
+ bucket: params.bucketName,
829
+ queryParams: { location: "" },
830
+ });
831
+ const locationMatch = response.body.match(/<LocationConstraint>(.*?)<\/LocationConstraint>/);
832
+ const result = {
833
+ bucket: params.bucketName,
834
+ location: locationMatch?.[1] ?? "",
835
+ };
836
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
837
+ }
838
+ catch (error) {
839
+ return { content: [{ type: "text", text: error.message }], isError: true };
840
+ }
841
+ });
842
+ // ─── Object Operations Tools ───────────────────────────────────────────────
843
+ server.tool("ncloud_copy_object", "Copy an object within the same bucket or between different buckets in Object Storage", {
844
+ bucketName: z.string({
845
+ required_error: "필수 파라미터 'bucketName'이 누락되었습니다.",
846
+ }).describe("Destination bucket name"),
847
+ objectName: z.string({
848
+ required_error: "필수 파라미터 'objectName'이 누락되었습니다.",
849
+ }).describe("Destination object key (path)"),
850
+ copySource: z.string({
851
+ required_error: "필수 파라미터 'copySource'가 누락되었습니다.",
852
+ }).describe("Source object in the format /{sourceBucket}/{sourceKey}"),
853
+ }, async (params) => {
854
+ try {
855
+ const response = await client.request({
856
+ method: "PUT",
857
+ bucket: params.bucketName,
858
+ key: params.objectName,
859
+ headers: { "x-amz-copy-source": params.copySource },
860
+ });
861
+ // Parse CopyObjectResult XML
862
+ const lastModifiedMatch = response.body.match(/<LastModified>(.*?)<\/LastModified>/);
863
+ const etagMatch = response.body.match(/<ETag>(.*?)<\/ETag>/);
864
+ const result = {
865
+ bucket: params.bucketName,
866
+ key: params.objectName,
867
+ copySource: params.copySource,
868
+ lastModified: lastModifiedMatch?.[1] ?? "",
869
+ etag: etagMatch?.[1] ?? "",
870
+ };
871
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
872
+ }
873
+ catch (error) {
874
+ return { content: [{ type: "text", text: error.message }], isError: true };
875
+ }
876
+ });
877
+ server.tool("ncloud_head_object", "Retrieve metadata of an object without returning the object body (HEAD request)", {
878
+ bucketName: z.string({
879
+ required_error: "필수 파라미터 'bucketName'이 누락되었습니다.",
880
+ }).describe("Name of the bucket"),
881
+ objectName: z.string({
882
+ required_error: "필수 파라미터 'objectName'이 누락되었습니다.",
883
+ }).describe("Object key (path) to get metadata for"),
884
+ }, async (params) => {
885
+ try {
886
+ const response = await client.request({
887
+ method: "HEAD",
888
+ bucket: params.bucketName,
889
+ key: params.objectName,
890
+ });
891
+ const result = {
892
+ bucket: params.bucketName,
893
+ key: params.objectName,
894
+ contentLength: response.headers.get("content-length"),
895
+ contentType: response.headers.get("content-type"),
896
+ lastModified: response.headers.get("last-modified"),
897
+ etag: response.headers.get("etag"),
898
+ acceptRanges: response.headers.get("accept-ranges"),
899
+ storageClass: response.headers.get("x-amz-storage-class"),
900
+ };
901
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
902
+ }
903
+ catch (error) {
904
+ return { content: [{ type: "text", text: error.message }], isError: true };
905
+ }
906
+ });
907
+ server.tool("ncloud_delete_multiple_objects", "⚠️ Destructive: Delete multiple objects from an Object Storage bucket in a single request. Set confirm=true to execute.", {
908
+ bucketName: z.string({
909
+ required_error: "필수 파라미터 'bucketName'이 누락되었습니다.",
910
+ }).describe("Name of the bucket"),
911
+ objectKeys: z.array(z.string()).min(1, {
912
+ message: "잘못된 파라미터: 'objectKeys'는 최소 1개 이상의 키를 포함해야 합니다.",
913
+ }).describe("Array of object keys to delete"),
914
+ confirm: z.boolean().optional().default(false).describe("Must be true to actually execute the destructive operation"),
915
+ }, async (params) => {
916
+ try {
917
+ if (!params.confirm) {
918
+ const message = `⚠️ This will permanently delete ${params.objectKeys.length} object(s) from Bucket [${params.bucketName}]:\n${params.objectKeys.map((k) => ` - ${k}`).join("\n")}\n\nDo you want to proceed? (yes/no)\n\nTo execute, call this tool again with confirm=true.`;
919
+ return { content: [{ type: "text", text: message }] };
920
+ }
921
+ const objectsXml = params.objectKeys
922
+ .map((key) => `<Object><Key>${key}</Key></Object>`)
923
+ .join("");
924
+ const body = `<Delete><Quiet>false</Quiet>${objectsXml}</Delete>`;
925
+ const contentMd5 = crypto.createHash("md5").update(body).digest("base64");
926
+ const response = await client.request({
927
+ method: "POST",
928
+ bucket: params.bucketName,
929
+ queryParams: { delete: "" },
930
+ headers: {
931
+ "content-type": "application/xml",
932
+ "content-md5": contentMd5,
933
+ },
934
+ body,
935
+ });
936
+ // Parse DeleteResult XML
937
+ const deleted = [];
938
+ const errors = [];
939
+ const deletedMatches = response.body.matchAll(/<Deleted><Key>(.*?)<\/Key><\/Deleted>/g);
940
+ for (const match of deletedMatches) {
941
+ deleted.push(match[1]);
942
+ }
943
+ const errorMatches = response.body.matchAll(/<Error><Key>(.*?)<\/Key><Code>(.*?)<\/Code><Message>(.*?)<\/Message><\/Error>/g);
944
+ for (const match of errorMatches) {
945
+ errors.push({ key: match[1], code: match[2], message: match[3] });
946
+ }
947
+ const result = {
948
+ message: `✅ ${deleted.length}개 오브젝트가 삭제되었습니다.${errors.length > 0 ? ` ${errors.length}개 실패.` : ""}`,
949
+ bucket: params.bucketName,
950
+ deleted,
951
+ errors: errors.length > 0 ? errors : undefined,
952
+ };
953
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
954
+ }
955
+ catch (error) {
956
+ return { content: [{ type: "text", text: error.message }], isError: true };
957
+ }
958
+ });
959
+ server.tool("ncloud_head_bucket", "Check if a bucket exists and you have permission to access it (HEAD request, returns headers only)", {
960
+ bucketName: z.string({
961
+ required_error: "필수 파라미터 'bucketName'이 누락되었습니다.",
962
+ }).describe("Name of the bucket to check"),
963
+ }, async (params) => {
964
+ try {
965
+ const response = await client.request({
966
+ method: "HEAD",
967
+ bucket: params.bucketName,
968
+ });
969
+ const result = {
970
+ bucket: params.bucketName,
971
+ exists: true,
972
+ region: response.headers.get("x-amz-bucket-region"),
973
+ statusCode: response.status,
974
+ };
975
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
976
+ }
977
+ catch (error) {
978
+ return { content: [{ type: "text", text: error.message }], isError: true };
979
+ }
980
+ });
981
+ }
982
+ //# sourceMappingURL=storage-object.js.map