@latellu/atlas-mcp 1.0.4 → 1.0.6

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 +18 -7
  2. package/dist/index.js +103 -48
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -69,17 +69,23 @@ atlas-mcp
69
69
  4. Copy the key — it is only shown once
70
70
 
71
71
  **Key types:**
72
- - `atlas_live_*` — Read-only access (delivery API). Use for fetching content.
73
- - `atlas_mgmt_*` — Read-write access (management API). Use for creating/updating content.
72
+ - `atlas_live_*` — Delivery API key. Required for every **read** tool (`get_workspace_schema`, `list_*`, `get_*`).
73
+ - `atlas_mgmt_*` — Management API key. Required for every **write** tool (`create_*`, `update_*`, `delete_*`, `publish_*`, `unpublish_*`, `archive_*`, `duplicate_*`, `upload_media`).
74
+
75
+ The backend enforces one key type per route group — a management key cannot read `/api/v1/public/*`, and a delivery key cannot write to `/api/v1/manage/*`. **For the full tool set (read + write), configure both keys.** A single key only unlocks the half of the tools matching its type; calling a tool that needs the other key type returns a clear error naming the missing env var.
74
76
 
75
77
  ### Environment Variables
76
78
 
77
79
  | Variable | Required | Description |
78
80
  |----------|----------|-------------|
79
- | `ATLAS_API_KEY` | Yes | Atlas API key (`atlas_live_*` for read-only, `atlas_mgmt_*` for full access) |
81
+ | `ATLAS_API_KEY` | One of the three below | Single key, auto-detected by prefix (`atlas_live_*` enables read tools, `atlas_mgmt_*` enables write tools) — kept for backward compatibility |
82
+ | `ATLAS_LIVE_API_KEY` | No | Delivery key (`atlas_live_*`), explicitly enables read tools |
83
+ | `ATLAS_MGMT_API_KEY` | No | Management key (`atlas_mgmt_*`), explicitly enables write tools |
80
84
  | `ATLAS_API_URL` | No | Atlas API base URL (default: `https://api.atlas.latellu.com`) |
81
85
  | `MCP_ALLOWED_UPLOAD_PATHS` | No | Comma-separated list of allowed directories for media upload |
82
86
 
87
+ At least one of `ATLAS_API_KEY`, `ATLAS_LIVE_API_KEY`, or `ATLAS_MGMT_API_KEY` must be set. Set `ATLAS_LIVE_API_KEY` and `ATLAS_MGMT_API_KEY` together to enable both read and write tools in the same server.
88
+
83
89
  ### Claude Desktop Configuration
84
90
 
85
91
  Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
@@ -91,7 +97,8 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
91
97
  "command": "npx",
92
98
  "args": ["-y", "@latellu/atlas-mcp"],
93
99
  "env": {
94
- "ATLAS_API_KEY": "atlas_mgmt_xxx...",
100
+ "ATLAS_LIVE_API_KEY": "atlas_live_xxx...",
101
+ "ATLAS_MGMT_API_KEY": "atlas_mgmt_xxx...",
95
102
  "MCP_ALLOWED_UPLOAD_PATHS": "/Users/you/Documents,/Users/you/Pictures"
96
103
  }
97
104
  }
@@ -99,6 +106,8 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
99
106
  }
100
107
  ```
101
108
 
109
+ Write-only setups (no read tools needed) can still set just `ATLAS_API_KEY` to a single `atlas_mgmt_*` key.
110
+
102
111
  ### Cursor Configuration
103
112
 
104
113
  Add to `.cursor/mcp.json`:
@@ -110,7 +119,8 @@ Add to `.cursor/mcp.json`:
110
119
  "command": "npx",
111
120
  "args": ["-y", "@latellu/atlas-mcp"],
112
121
  "env": {
113
- "ATLAS_API_KEY": "atlas_mgmt_xxx..."
122
+ "ATLAS_LIVE_API_KEY": "atlas_live_xxx...",
123
+ "ATLAS_MGMT_API_KEY": "atlas_mgmt_xxx..."
114
124
  }
115
125
  }
116
126
  }
@@ -159,8 +169,9 @@ AI: → Calls upload_media with file_path='/Users/you/Pictures/tokyo.png'
159
169
  ### Permission Model
160
170
 
161
171
  **Layer 1: API Key**
162
- - `atlas_live_*` — Read-only access (delivery API)
163
- - `atlas_mgmt_*` — Read-write access (management API)
172
+ - `atlas_live_*` — Read tools only (delivery API)
173
+ - `atlas_mgmt_*` — Write tools only (management API)
174
+ - Configure both (`ATLAS_LIVE_API_KEY` + `ATLAS_MGMT_API_KEY`) for the full tool set
164
175
 
165
176
  **Layer 2: Filesystem Permission**
166
177
  - Configurable via `MCP_ALLOWED_UPLOAD_PATHS` env var
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,29 +12530,70 @@ 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
- apiKey;
12542
- constructor(baseUrl, apiKey) {
12559
+ liveKey;
12560
+ mgmtKey;
12561
+ constructor(baseUrl, keys) {
12543
12562
  this.baseUrl = baseUrl;
12544
- this.apiKey = apiKey;
12563
+ this.liveKey = keys.liveKey;
12564
+ this.mgmtKey = keys.mgmtKey;
12545
12565
  }
12546
- async request(path, options) {
12547
- const url = `${this.baseUrl}${path}`;
12566
+ async httpRequest(path2, apiKey, options) {
12567
+ const url = `${this.baseUrl}${path2}`;
12548
12568
  const response = await fetch(url, {
12549
12569
  ...options,
12550
12570
  headers: {
12551
12571
  "Content-Type": "application/json",
12552
- "X-API-Key": this.apiKey,
12572
+ "X-API-Key": apiKey,
12553
12573
  ...options?.headers
12554
12574
  }
12555
12575
  });
12556
12576
  return this.handleResponse(response);
12557
12577
  }
12558
- // Write requests to /api/v1/manage/* carry an Idempotency-Key so that
12559
- // AI-agent-driven retries don't risk double-create/double-publish.
12560
- async writeRequest(path, options) {
12561
- return this.request(path, {
12578
+ // Reads against /api/v1/public/* require a delivery key (atlas_live_*).
12579
+ async publicRequest(path2, options) {
12580
+ if (!this.liveKey) {
12581
+ throw new Error(
12582
+ "This operation requires a delivery API key (atlas_live_*). Set ATLAS_LIVE_API_KEY (or ATLAS_API_KEY to an atlas_live_* key) to enable read operations."
12583
+ );
12584
+ }
12585
+ return this.httpRequest(path2, this.liveKey, options);
12586
+ }
12587
+ // Writes against /api/v1/manage/* require a management key (atlas_mgmt_*)
12588
+ // and carry an Idempotency-Key so AI-agent-driven retries don't risk
12589
+ // double-create/double-publish.
12590
+ async manageRequest(path2, options) {
12591
+ if (!this.mgmtKey) {
12592
+ throw new Error(
12593
+ "This operation requires a management API key (atlas_mgmt_*). Set ATLAS_MGMT_API_KEY (or ATLAS_API_KEY to an atlas_mgmt_* key) to enable write operations."
12594
+ );
12595
+ }
12596
+ return this.httpRequest(path2, this.mgmtKey, {
12562
12597
  ...options,
12563
12598
  headers: {
12564
12599
  "Idempotency-Key": crypto.randomUUID(),
@@ -12578,7 +12613,7 @@ var AtlasClient = class {
12578
12613
  }
12579
12614
  // Schema endpoints
12580
12615
  async getWorkspaceSchema() {
12581
- return this.request("/api/v1/public/schema");
12616
+ return this.publicRequest("/api/v1/public/schema");
12582
12617
  }
12583
12618
  async listContentTypes() {
12584
12619
  const schema = await this.getWorkspaceSchema();
@@ -12599,45 +12634,45 @@ var AtlasClient = class {
12599
12634
  if (params?.page) searchParams.set("page", params.page.toString());
12600
12635
  if (params?.limit) searchParams.set("limit", params.limit.toString());
12601
12636
  if (params?.status) searchParams.set("status", params.status);
12602
- return this.request(`/api/v1/public/entries?${searchParams.toString()}`);
12637
+ return this.publicRequest(`/api/v1/public/entries?${searchParams.toString()}`);
12603
12638
  }
12604
12639
  async getEntry(contentType, slug) {
12605
- return this.request(`/api/v1/public/entries/${slug}`);
12640
+ return this.publicRequest(`/api/v1/public/entries/${slug}`);
12606
12641
  }
12607
12642
  async createEntry(contentType, data, slug) {
12608
- return this.writeRequest(`/api/v1/manage/content-types/${contentType}/entries`, {
12643
+ return this.manageRequest(`/api/v1/manage/content-types/${contentType}/entries`, {
12609
12644
  method: "POST",
12610
12645
  body: JSON.stringify({ slug, data })
12611
12646
  });
12612
12647
  }
12613
12648
  async updateEntry(contentType, slug, data) {
12614
- return this.writeRequest(`/api/v1/manage/content-types/${contentType}/entries/${slug}`, {
12649
+ return this.manageRequest(`/api/v1/manage/content-types/${contentType}/entries/${slug}`, {
12615
12650
  method: "PUT",
12616
12651
  body: JSON.stringify({ data })
12617
12652
  });
12618
12653
  }
12619
12654
  async deleteEntry(contentType, slug) {
12620
- return this.writeRequest(`/api/v1/manage/content-types/${contentType}/entries/${slug}`, {
12655
+ return this.manageRequest(`/api/v1/manage/content-types/${contentType}/entries/${slug}`, {
12621
12656
  method: "DELETE"
12622
12657
  });
12623
12658
  }
12624
12659
  async publishEntry(contentType, slug) {
12625
- return this.writeRequest(`/api/v1/manage/content-types/${contentType}/entries/${slug}/publish`, {
12660
+ return this.manageRequest(`/api/v1/manage/content-types/${contentType}/entries/${slug}/publish`, {
12626
12661
  method: "PATCH"
12627
12662
  });
12628
12663
  }
12629
12664
  async unpublishEntry(contentType, slug) {
12630
- return this.writeRequest(`/api/v1/manage/content-types/${contentType}/entries/${slug}/unpublish`, {
12665
+ return this.manageRequest(`/api/v1/manage/content-types/${contentType}/entries/${slug}/unpublish`, {
12631
12666
  method: "PATCH"
12632
12667
  });
12633
12668
  }
12634
12669
  async archiveEntry(contentType, slug) {
12635
- return this.writeRequest(`/api/v1/manage/content-types/${contentType}/entries/${slug}/archive`, {
12670
+ return this.manageRequest(`/api/v1/manage/content-types/${contentType}/entries/${slug}/archive`, {
12636
12671
  method: "PATCH"
12637
12672
  });
12638
12673
  }
12639
12674
  async duplicateEntry(contentType, slug) {
12640
- return this.writeRequest(`/api/v1/manage/content-types/${contentType}/entries/${slug}/duplicate`, {
12675
+ return this.manageRequest(`/api/v1/manage/content-types/${contentType}/entries/${slug}/duplicate`, {
12641
12676
  method: "POST"
12642
12677
  });
12643
12678
  }
@@ -12646,53 +12681,59 @@ var AtlasClient = class {
12646
12681
  const searchParams = new URLSearchParams();
12647
12682
  if (params?.page) searchParams.set("page", params.page.toString());
12648
12683
  if (params?.limit) searchParams.set("limit", params.limit.toString());
12649
- return this.request(`/api/v1/public/pages?${searchParams.toString()}`);
12684
+ return this.publicRequest(`/api/v1/public/pages?${searchParams.toString()}`);
12650
12685
  }
12651
12686
  async getPage(slug) {
12652
- return this.request(`/api/v1/public/pages/${slug}`);
12687
+ return this.publicRequest(`/api/v1/public/pages/${slug}`);
12653
12688
  }
12654
12689
  async createPage(data) {
12655
- return this.writeRequest("/api/v1/manage/pages", {
12690
+ return this.manageRequest("/api/v1/manage/pages", {
12656
12691
  method: "POST",
12657
12692
  body: JSON.stringify(data)
12658
12693
  });
12659
12694
  }
12660
12695
  async updatePage(slug, data) {
12661
- return this.writeRequest(`/api/v1/manage/pages/${slug}`, {
12696
+ return this.manageRequest(`/api/v1/manage/pages/${slug}`, {
12662
12697
  method: "PUT",
12663
12698
  body: JSON.stringify(data)
12664
12699
  });
12665
12700
  }
12666
12701
  async deletePage(slug) {
12667
- return this.writeRequest(`/api/v1/manage/pages/${slug}`, {
12702
+ return this.manageRequest(`/api/v1/manage/pages/${slug}`, {
12668
12703
  method: "DELETE"
12669
12704
  });
12670
12705
  }
12671
12706
  async publishPage(slug) {
12672
- return this.writeRequest(`/api/v1/manage/pages/${slug}/publish`, {
12707
+ return this.manageRequest(`/api/v1/manage/pages/${slug}/publish`, {
12673
12708
  method: "PATCH"
12674
12709
  });
12675
12710
  }
12676
12711
  // Media endpoints
12677
12712
  async getMedia(id) {
12678
- return this.request(`/api/v1/public/media/${id}`);
12713
+ return this.publicRequest(`/api/v1/public/media/${id}`);
12679
12714
  }
12680
12715
  async uploadMedia(filePath, folder, altText) {
12716
+ if (!this.mgmtKey) {
12717
+ throw new Error(
12718
+ "This operation requires a management API key (atlas_mgmt_*). Set ATLAS_MGMT_API_KEY (or ATLAS_API_KEY to an atlas_mgmt_* key) to enable write operations."
12719
+ );
12720
+ }
12681
12721
  const fs = await import("fs");
12682
- const path = await import("path");
12722
+ const path2 = await import("path");
12683
12723
  if (!fs.existsSync(filePath)) {
12684
12724
  throw new Error(`File not found: ${filePath}`);
12685
12725
  }
12686
- const fileName = path.basename(filePath);
12726
+ const fileName = path2.basename(filePath);
12687
12727
  const fileBuffer = fs.readFileSync(filePath);
12728
+ const mimeType = detectMimeType(fileName);
12688
12729
  const formData = new FormData();
12689
- formData.append("file", new Blob([fileBuffer]), fileName);
12730
+ formData.append("file", new Blob([fileBuffer], { type: mimeType }), fileName);
12690
12731
  if (folder) formData.append("folder", folder);
12691
12732
  if (altText) formData.append("alt_text", altText);
12692
12733
  const response = await fetch(`${this.baseUrl}/api/v1/manage/media/upload`, {
12693
12734
  method: "POST",
12694
12735
  headers: {
12695
- "X-API-Key": this.apiKey,
12736
+ "X-API-Key": this.mgmtKey,
12696
12737
  "Idempotency-Key": crypto.randomUUID()
12697
12738
  },
12698
12739
  body: formData
@@ -12702,11 +12743,11 @@ var AtlasClient = class {
12702
12743
  };
12703
12744
 
12704
12745
  // src/security.ts
12746
+ import path from "node:path";
12705
12747
  function validateFilePath(filePath, allowedPaths) {
12706
12748
  if (allowedPaths.length === 0) {
12707
12749
  return;
12708
12750
  }
12709
- const path = __require("path");
12710
12751
  const absPath = path.resolve(filePath);
12711
12752
  const isAllowed = allowedPaths.some((allowed) => {
12712
12753
  const absAllowed = path.resolve(allowed);
@@ -12721,13 +12762,25 @@ function validateFilePath(filePath, allowedPaths) {
12721
12762
 
12722
12763
  // src/index.ts
12723
12764
  var ATLAS_API_URL = process.env.ATLAS_API_URL || "https://api.atlas.latellu.com";
12724
- var ATLAS_API_KEY = process.env.ATLAS_API_KEY;
12725
12765
  var MCP_ALLOWED_UPLOAD_PATHS = process.env.MCP_ALLOWED_UPLOAD_PATHS ? process.env.MCP_ALLOWED_UPLOAD_PATHS.split(",").map((p) => p.trim()) : [];
12726
- if (!ATLAS_API_KEY) {
12727
- console.error("ATLAS_API_KEY environment variable is required");
12766
+ var LIVE_KEY_PREFIX = "atlas_live_";
12767
+ var MGMT_KEY_PREFIX = "atlas_mgmt_";
12768
+ function resolveKeys() {
12769
+ const legacyCandidates = [process.env.ATLAS_LIVE_API_KEY, process.env.ATLAS_MGMT_API_KEY, process.env.ATLAS_API_KEY].filter(
12770
+ (v) => !!v
12771
+ );
12772
+ const liveKey2 = process.env.ATLAS_LIVE_API_KEY ?? legacyCandidates.find((k) => k.startsWith(LIVE_KEY_PREFIX));
12773
+ const mgmtKey2 = process.env.ATLAS_MGMT_API_KEY ?? legacyCandidates.find((k) => k.startsWith(MGMT_KEY_PREFIX));
12774
+ return { liveKey: liveKey2, mgmtKey: mgmtKey2 };
12775
+ }
12776
+ var { liveKey, mgmtKey } = resolveKeys();
12777
+ if (!liveKey && !mgmtKey) {
12778
+ console.error(
12779
+ "No valid Atlas API key found. Set ATLAS_API_KEY to an atlas_live_* or atlas_mgmt_* key, or set ATLAS_LIVE_API_KEY / ATLAS_MGMT_API_KEY explicitly (both, for full read+write access)."
12780
+ );
12728
12781
  process.exit(1);
12729
12782
  }
12730
- var atlasClient = new AtlasClient(ATLAS_API_URL, ATLAS_API_KEY);
12783
+ var atlasClient = new AtlasClient(ATLAS_API_URL, { liveKey, mgmtKey });
12731
12784
  var server = new McpServer({
12732
12785
  name: "atlas-mcp",
12733
12786
  version: "1.0.0"
@@ -13260,7 +13313,9 @@ server.tool(
13260
13313
  async function main() {
13261
13314
  const transport = new StdioServerTransport();
13262
13315
  await server.connect(transport);
13263
- console.error("Atlas MCP server started");
13316
+ console.error(
13317
+ `Atlas MCP server started (read tools: ${liveKey ? "enabled" : "disabled, needs atlas_live_*"}, write tools: ${mgmtKey ? "enabled" : "disabled, needs atlas_mgmt_*"})`
13318
+ );
13264
13319
  }
13265
13320
  main().catch((error) => {
13266
13321
  console.error("Fatal error:", error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@latellu/atlas-mcp",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },