@latellu/atlas-mcp 1.0.3 → 1.0.5

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 (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +64 -35
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -172,7 +172,7 @@ AI: → Calls upload_media with file_path='/Users/you/Pictures/tokyo.png'
172
172
 
173
173
  ### Best Practices
174
174
 
175
- 1. **Scope API keys**: Use minimal scopes required
175
+ 1. **Scope API keys**: For management keys, grant only the scopes each tool needs — `content:write` for create/update/delete/reorder, `content:publish` for publish/unpublish/archive/schedule, `media:write` for uploads/deletes. Leave unused scopes off.
176
176
  2. **Restrict upload paths**: Only allow necessary directories
177
177
  3. **Rotate keys**: Regularly rotate API keys
178
178
  4. **Monitor usage**: Review MCP tool calls for suspicious activity
package/dist/index.js CHANGED
@@ -5,13 +5,7 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
9
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
10
- }) : x)(function(x) {
11
- if (typeof require !== "undefined") return require.apply(this, arguments);
12
- throw Error('Dynamic require of "' + x + '" is not supported');
13
- });
14
- var __commonJS = (cb, mod) => function __require2() {
8
+ var __commonJS = (cb, mod) => function __require() {
15
9
  try {
16
10
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
17
11
  } catch (e) {
@@ -3231,8 +3225,8 @@ var require_utils = __commonJS({
3231
3225
  }
3232
3226
  return ind;
3233
3227
  }
3234
- function removeDotSegments(path) {
3235
- let input = path;
3228
+ function removeDotSegments(path2) {
3229
+ let input = path2;
3236
3230
  const output = [];
3237
3231
  let nextSlash = -1;
3238
3232
  let len = 0;
@@ -3484,8 +3478,8 @@ var require_schemes = __commonJS({
3484
3478
  wsComponent.secure = void 0;
3485
3479
  }
3486
3480
  if (wsComponent.resourceName) {
3487
- const [path, query] = wsComponent.resourceName.split("?");
3488
- wsComponent.path = path && path !== "/" ? path : void 0;
3481
+ const [path2, query] = wsComponent.resourceName.split("?");
3482
+ wsComponent.path = path2 && path2 !== "/" ? path2 : void 0;
3489
3483
  wsComponent.query = query;
3490
3484
  wsComponent.resourceName = void 0;
3491
3485
  }
@@ -12536,6 +12530,30 @@ var StdioServerTransport = class {
12536
12530
  import { z as z2 } from "zod";
12537
12531
 
12538
12532
  // src/client.ts
12533
+ function detectMimeType(fileName) {
12534
+ const ext = fileName.slice(fileName.lastIndexOf(".")).toLowerCase();
12535
+ switch (ext) {
12536
+ case ".jpg":
12537
+ case ".jpeg":
12538
+ return "image/jpeg";
12539
+ case ".png":
12540
+ return "image/png";
12541
+ case ".gif":
12542
+ return "image/gif";
12543
+ case ".webp":
12544
+ return "image/webp";
12545
+ case ".mp4":
12546
+ return "video/mp4";
12547
+ case ".webm":
12548
+ return "video/webm";
12549
+ case ".mov":
12550
+ return "video/quicktime";
12551
+ case ".pdf":
12552
+ return "application/pdf";
12553
+ default:
12554
+ return "application/octet-stream";
12555
+ }
12556
+ }
12539
12557
  var AtlasClient = class {
12540
12558
  baseUrl;
12541
12559
  apiKey;
@@ -12543,8 +12561,8 @@ var AtlasClient = class {
12543
12561
  this.baseUrl = baseUrl;
12544
12562
  this.apiKey = apiKey;
12545
12563
  }
12546
- async request(path, options) {
12547
- const url = `${this.baseUrl}${path}`;
12564
+ async request(path2, options) {
12565
+ const url = `${this.baseUrl}${path2}`;
12548
12566
  const response = await fetch(url, {
12549
12567
  ...options,
12550
12568
  headers: {
@@ -12553,6 +12571,20 @@ var AtlasClient = class {
12553
12571
  ...options?.headers
12554
12572
  }
12555
12573
  });
12574
+ return this.handleResponse(response);
12575
+ }
12576
+ // Write requests to /api/v1/manage/* carry an Idempotency-Key so that
12577
+ // AI-agent-driven retries don't risk double-create/double-publish.
12578
+ async writeRequest(path2, options) {
12579
+ return this.request(path2, {
12580
+ ...options,
12581
+ headers: {
12582
+ "Idempotency-Key": crypto.randomUUID(),
12583
+ ...options.headers
12584
+ }
12585
+ });
12586
+ }
12587
+ async handleResponse(response) {
12556
12588
  if (!response.ok) {
12557
12589
  const error = await response.json().catch(() => ({}));
12558
12590
  throw new Error(
@@ -12591,39 +12623,39 @@ var AtlasClient = class {
12591
12623
  return this.request(`/api/v1/public/entries/${slug}`);
12592
12624
  }
12593
12625
  async createEntry(contentType, data, slug) {
12594
- return this.request(`/api/v1/manage/content-types/${contentType}/entries`, {
12626
+ return this.writeRequest(`/api/v1/manage/content-types/${contentType}/entries`, {
12595
12627
  method: "POST",
12596
12628
  body: JSON.stringify({ slug, data })
12597
12629
  });
12598
12630
  }
12599
12631
  async updateEntry(contentType, slug, data) {
12600
- return this.request(`/api/v1/manage/content-types/${contentType}/entries/${slug}`, {
12632
+ return this.writeRequest(`/api/v1/manage/content-types/${contentType}/entries/${slug}`, {
12601
12633
  method: "PUT",
12602
12634
  body: JSON.stringify({ data })
12603
12635
  });
12604
12636
  }
12605
12637
  async deleteEntry(contentType, slug) {
12606
- return this.request(`/api/v1/manage/content-types/${contentType}/entries/${slug}`, {
12638
+ return this.writeRequest(`/api/v1/manage/content-types/${contentType}/entries/${slug}`, {
12607
12639
  method: "DELETE"
12608
12640
  });
12609
12641
  }
12610
12642
  async publishEntry(contentType, slug) {
12611
- return this.request(`/api/v1/manage/content-types/${contentType}/entries/${slug}/publish`, {
12643
+ return this.writeRequest(`/api/v1/manage/content-types/${contentType}/entries/${slug}/publish`, {
12612
12644
  method: "PATCH"
12613
12645
  });
12614
12646
  }
12615
12647
  async unpublishEntry(contentType, slug) {
12616
- return this.request(`/api/v1/manage/content-types/${contentType}/entries/${slug}/unpublish`, {
12648
+ return this.writeRequest(`/api/v1/manage/content-types/${contentType}/entries/${slug}/unpublish`, {
12617
12649
  method: "PATCH"
12618
12650
  });
12619
12651
  }
12620
12652
  async archiveEntry(contentType, slug) {
12621
- return this.request(`/api/v1/manage/content-types/${contentType}/entries/${slug}/archive`, {
12653
+ return this.writeRequest(`/api/v1/manage/content-types/${contentType}/entries/${slug}/archive`, {
12622
12654
  method: "PATCH"
12623
12655
  });
12624
12656
  }
12625
12657
  async duplicateEntry(contentType, slug) {
12626
- return this.request(`/api/v1/manage/content-types/${contentType}/entries/${slug}/duplicate`, {
12658
+ return this.writeRequest(`/api/v1/manage/content-types/${contentType}/entries/${slug}/duplicate`, {
12627
12659
  method: "POST"
12628
12660
  });
12629
12661
  }
@@ -12638,24 +12670,24 @@ var AtlasClient = class {
12638
12670
  return this.request(`/api/v1/public/pages/${slug}`);
12639
12671
  }
12640
12672
  async createPage(data) {
12641
- return this.request("/api/v1/manage/pages", {
12673
+ return this.writeRequest("/api/v1/manage/pages", {
12642
12674
  method: "POST",
12643
12675
  body: JSON.stringify(data)
12644
12676
  });
12645
12677
  }
12646
12678
  async updatePage(slug, data) {
12647
- return this.request(`/api/v1/manage/pages/${slug}`, {
12679
+ return this.writeRequest(`/api/v1/manage/pages/${slug}`, {
12648
12680
  method: "PUT",
12649
12681
  body: JSON.stringify(data)
12650
12682
  });
12651
12683
  }
12652
12684
  async deletePage(slug) {
12653
- return this.request(`/api/v1/manage/pages/${slug}`, {
12685
+ return this.writeRequest(`/api/v1/manage/pages/${slug}`, {
12654
12686
  method: "DELETE"
12655
12687
  });
12656
12688
  }
12657
12689
  async publishPage(slug) {
12658
- return this.request(`/api/v1/manage/pages/${slug}/publish`, {
12690
+ return this.writeRequest(`/api/v1/manage/pages/${slug}/publish`, {
12659
12691
  method: "PATCH"
12660
12692
  });
12661
12693
  }
@@ -12665,38 +12697,35 @@ var AtlasClient = class {
12665
12697
  }
12666
12698
  async uploadMedia(filePath, folder, altText) {
12667
12699
  const fs = await import("fs");
12668
- const path = await import("path");
12700
+ const path2 = await import("path");
12669
12701
  if (!fs.existsSync(filePath)) {
12670
12702
  throw new Error(`File not found: ${filePath}`);
12671
12703
  }
12672
- const fileName = path.basename(filePath);
12704
+ const fileName = path2.basename(filePath);
12673
12705
  const fileBuffer = fs.readFileSync(filePath);
12706
+ const mimeType = detectMimeType(fileName);
12674
12707
  const formData = new FormData();
12675
- formData.append("file", new Blob([fileBuffer]), fileName);
12708
+ formData.append("file", new Blob([fileBuffer], { type: mimeType }), fileName);
12676
12709
  if (folder) formData.append("folder", folder);
12677
12710
  if (altText) formData.append("alt_text", altText);
12678
12711
  const response = await fetch(`${this.baseUrl}/api/v1/manage/media/upload`, {
12679
12712
  method: "POST",
12680
12713
  headers: {
12681
- "X-API-Key": this.apiKey
12714
+ "X-API-Key": this.apiKey,
12715
+ "Idempotency-Key": crypto.randomUUID()
12682
12716
  },
12683
12717
  body: formData
12684
12718
  });
12685
- if (!response.ok) {
12686
- const error = await response.json().catch(() => ({}));
12687
- throw new Error(`Upload failed: ${response.status} - ${error.message || response.statusText}`);
12688
- }
12689
- const data = await response.json();
12690
- return data.data || data;
12719
+ return this.handleResponse(response);
12691
12720
  }
12692
12721
  };
12693
12722
 
12694
12723
  // src/security.ts
12724
+ import path from "node:path";
12695
12725
  function validateFilePath(filePath, allowedPaths) {
12696
12726
  if (allowedPaths.length === 0) {
12697
12727
  return;
12698
12728
  }
12699
- const path = __require("path");
12700
12729
  const absPath = path.resolve(filePath);
12701
12730
  const isAllowed = allowedPaths.some((allowed) => {
12702
12731
  const absAllowed = path.resolve(allowed);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@latellu/atlas-mcp",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },