portals-mcp 1.0.0 → 1.0.2

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/dist/api.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import fetch from "node-fetch";
2
- const API_BASE = "https://theportal.to";
2
+ const API_BASE = "http://theportal.to";
3
3
  if (!API_BASE) {
4
4
  throw new Error("API_BASE_URL env var is required");
5
5
  }
@@ -13,26 +13,19 @@ export function getAccessKey() {
13
13
  }
14
14
  return accessKey;
15
15
  }
16
- export async function getRoomData(roomId) {
17
- const res = await fetch(`${API_BASE}/api/v2/mcp/get-room-data`, {
16
+ export async function uploadRoomData(roomId, data) {
17
+ const res = await fetch(`${API_BASE}/api/v2/mcp/upload-room-data`, {
18
18
  method: "POST",
19
- headers: { "Content-Type": "application/json" },
20
- body: JSON.stringify({ roomId, accessKey: getAccessKey() }),
21
- });
22
- const json = await res.json();
23
- if (!res.ok)
24
- throw new Error(json.message ?? json.error ?? "Failed to get room data");
25
- return json.data;
26
- }
27
- export async function setRoomData(roomId, data) {
28
- const res = await fetch(`${API_BASE}/api/v2/mcp/set-room-data`, {
29
- method: "POST",
30
- headers: { "Content-Type": "application/json" },
31
- body: JSON.stringify({ roomId, accessKey: getAccessKey(), data }),
19
+ headers: {
20
+ "x-room-id": roomId,
21
+ "x-access-key": getAccessKey(),
22
+ "Content-Type": "application/octet-stream",
23
+ },
24
+ body: data,
32
25
  });
33
26
  const json = await res.json();
34
27
  if (!res.ok)
35
- throw new Error(json.message ?? json.error ?? "Failed to set room data");
28
+ throw new Error(json.message ?? json.error ?? "Failed to upload room data");
36
29
  }
37
30
  export async function validateAccessKey(accessKey) {
38
31
  const res = await fetch(`${API_BASE}/api/v2/mcp/verify-access-key`, {
@@ -45,14 +38,18 @@ export async function validateAccessKey(accessKey) {
45
38
  throw new Error(json.message ?? json.error ?? "Invalid access key");
46
39
  return json.data.uid;
47
40
  }
48
- export async function getUserRooms() {
49
- const res = await fetch(`${API_BASE}/api/v2/mcp/get-user-rooms`, {
50
- method: "POST",
51
- headers: { "Content-Type": "application/json" },
52
- body: JSON.stringify({ accessKey: getAccessKey() }),
41
+ export async function downloadRoomData(roomId) {
42
+ const res = await fetch(`${API_BASE}/api/v2/mcp/download-room-data`, {
43
+ method: "GET",
44
+ headers: {
45
+ "x-room-id": roomId,
46
+ "x-access-key": getAccessKey(),
47
+ },
53
48
  });
54
- const json = await res.json();
55
- if (!res.ok)
56
- throw new Error(json.message ?? json.error ?? "Failed to get user rooms");
57
- return json.data;
49
+ if (!res.ok) {
50
+ const json = await res.json();
51
+ throw new Error(json.message ?? json.error ?? "Failed to download room data");
52
+ }
53
+ const arrayBuffer = await res.arrayBuffer();
54
+ return Buffer.from(arrayBuffer);
58
55
  }
package/dist/auth.js CHANGED
@@ -1,76 +1,16 @@
1
1
  import { createServer } from "node:http";
2
2
  import { exec } from "node:child_process";
3
3
  import { validateAccessKey } from "./api.js";
4
- const FORM_HTML = `<!DOCTYPE html>
5
- <html lang="en">
6
- <head>
7
- <meta charset="UTF-8">
8
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
9
- <title>Portals MCP — Authenticate</title>
10
- <style>
11
- * { box-sizing: border-box; margin: 0; padding: 0; }
12
- body { font-family: system-ui, sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; background: #0f172a; color: #e2e8f0; }
13
- .card { background: #1e293b; border-radius: 12px; padding: 2rem; width: 100%; max-width: 400px; box-shadow: 0 4px 24px rgba(0,0,0,.4); }
14
- h1 { font-size: 1.25rem; margin-bottom: 1.5rem; text-align: center; }
15
- label { display: block; font-size: 0.875rem; margin-bottom: 0.5rem; color: #94a3b8; }
16
- input { width: 100%; padding: 0.625rem 0.75rem; border-radius: 6px; border: 1px solid #334155; background: #0f172a; color: #e2e8f0; font-size: 1rem; outline: none; }
17
- input:focus { border-color: #6366f1; }
18
- button { width: 100%; margin-top: 1rem; padding: 0.625rem; border: none; border-radius: 6px; background: #6366f1; color: #fff; font-size: 1rem; cursor: pointer; }
19
- button:hover { background: #4f46e5; }
20
- .msg { margin-top: 1rem; text-align: center; font-size: 0.875rem; }
21
- .msg.error { color: #f87171; }
22
- .msg.success { color: #4ade80; }
23
- </style>
24
- </head>
25
- <body>
26
- <div class="card">
27
- <h1>Portals MCP — Authenticate</h1>
28
- <form id="form">
29
- <label for="key">Access Key</label>
30
- <input id="key" type="password" placeholder="Paste your access key" autofocus required />
31
- <button type="submit">Authenticate</button>
32
- </form>
33
- <div id="msg" class="msg"></div>
34
- </div>
35
- <script>
36
- const form = document.getElementById("form");
37
- const msg = document.getElementById("msg");
38
- form.addEventListener("submit", async (e) => {
39
- e.preventDefault();
40
- msg.textContent = "";
41
- msg.className = "msg";
42
- const key = document.getElementById("key").value.trim();
43
- if (!key) return;
44
- try {
45
- const res = await fetch("/callback", {
46
- method: "POST",
47
- headers: { "Content-Type": "application/json" },
48
- body: JSON.stringify({ accessKey: key }),
49
- });
50
- const data = await res.json();
51
- if (res.ok) {
52
- msg.textContent = data.message;
53
- msg.className = "msg success";
54
- form.querySelector("button").disabled = true;
55
- form.querySelector("input").disabled = true;
56
- } else {
57
- msg.textContent = data.message;
58
- msg.className = "msg error";
59
- }
60
- } catch {
61
- msg.textContent = "Could not reach the server.";
62
- msg.className = "msg error";
63
- }
64
- });
65
- </script>
66
- </body>
67
- </html>`;
4
+ const PORTAL_URL = "https://theportal.to/mcp";
68
5
  export function authenticateViaBrowser() {
69
6
  return new Promise((resolve, reject) => {
70
7
  const httpServer = createServer(async (req, res) => {
71
- if (req.method === "GET" && (req.url === "/" || req.url === "")) {
72
- res.writeHead(200, { "Content-Type": "text/html" });
73
- res.end(FORM_HTML);
8
+ res.setHeader("Access-Control-Allow-Origin", "*");
9
+ res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
10
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
11
+ if (req.method === "OPTIONS") {
12
+ res.writeHead(204);
13
+ res.end();
74
14
  return;
75
15
  }
76
16
  if (req.method === "POST" && req.url === "/callback") {
@@ -87,7 +27,7 @@ export function authenticateViaBrowser() {
87
27
  resolve({ uid, accessKey });
88
28
  }
89
29
  catch (err) {
90
- const message = err instanceof Error ? err.message : "Invalid access key. Please try again.";
30
+ const message = err instanceof Error ? err.message : "Invalid access key.";
91
31
  res.writeHead(401, { "Content-Type": "application/json" });
92
32
  res.end(JSON.stringify({ message }));
93
33
  }
@@ -106,8 +46,8 @@ export function authenticateViaBrowser() {
106
46
  reject(new Error("Failed to start auth server"));
107
47
  return;
108
48
  }
109
- const url = `http://localhost:${addr.port}`;
110
- exec(`open "${url}"`);
49
+ const callbackPort = addr.port;
50
+ exec(`open "${PORTAL_URL}?callbackPort=${callbackPort}"`);
111
51
  });
112
52
  });
113
53
  }
package/dist/tools.js CHANGED
@@ -1,5 +1,8 @@
1
1
  import { z } from "zod";
2
- import { getRoomData, setRoomData, setAccessKey, getUserRooms } from "./api.js";
2
+ import { readFile, writeFile, mkdtemp } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import { uploadRoomData, setAccessKey, downloadRoomData } from "./api.js";
3
6
  import { authenticateViaBrowser } from "./auth.js";
4
7
  export function registerTools(server) {
5
8
  // ── Auth ──
@@ -15,75 +18,24 @@ export function registerTools(server) {
15
18
  return { content: [{ type: "text", text: "Authentication failed." }], isError: true };
16
19
  }
17
20
  });
18
- server.registerTool("list_my_rooms", {
19
- description: "List all room IDs where you are an owner or admin",
20
- }, async () => {
21
- const rooms = await getUserRooms();
22
- return { content: [{ type: "text", text: JSON.stringify(rooms, null, 2) }] };
23
- });
24
21
  // ── Read tools ──
25
- server.registerTool("get_room_items", {
26
- description: "Get all items in a room",
27
- inputSchema: { roomId: z.string() },
28
- }, async ({ roomId }) => {
29
- const data = await getRoomData(roomId);
30
- return { content: [{ type: "text", text: JSON.stringify(data.roomItems, null, 2) }] };
31
- });
32
- server.registerTool("get_room_settings", {
33
- description: "Get room settings",
34
- inputSchema: { roomId: z.string() },
35
- }, async ({ roomId }) => {
36
- const data = await getRoomData(roomId);
37
- return { content: [{ type: "text", text: JSON.stringify(data.settings, null, 2) }] };
38
- });
39
- server.registerTool("get_room_tasks", {
40
- description: "Get room tasks",
41
- inputSchema: { roomId: z.string() },
42
- }, async ({ roomId }) => {
43
- const data = await getRoomData(roomId);
44
- return { content: [{ type: "text", text: JSON.stringify(data.roomTasks, null, 2) }] };
45
- });
46
- server.registerTool("get_room_quests", {
47
- description: "Get quest definitions for a room",
22
+ server.registerTool("get_room_data", {
23
+ description: "Download all room data to a temporary JSON file and return the file path. Use the file path to read the data.",
48
24
  inputSchema: { roomId: z.string() },
49
25
  }, async ({ roomId }) => {
50
- const data = await getRoomData(roomId);
51
- return { content: [{ type: "text", text: JSON.stringify(data.quests, null, 2) }] };
52
- });
53
- server.registerTool("get_all_room_data", {
54
- description: "Get items, settings, tasks, and quests for a room in one call",
55
- inputSchema: { roomId: z.string() },
56
- }, async ({ roomId }) => {
57
- const data = await getRoomData(roomId);
58
- return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
26
+ const buffer = await downloadRoomData(roomId);
27
+ const dir = await mkdtemp(join(tmpdir(), "portals-"));
28
+ const filePath = join(dir, `room-data-${roomId}.json`);
29
+ await writeFile(filePath, buffer);
30
+ return { content: [{ type: "text", text: `Room data saved to: ${filePath}` }] };
59
31
  });
60
32
  // ── Write tools ──
61
- server.registerTool("set_room_items", {
62
- description: "Replace the entire room items object",
63
- inputSchema: { roomId: z.string(), data: z.any() },
64
- }, async ({ roomId, data }) => {
65
- await setRoomData(roomId, { roomItems: data });
66
- return { content: [{ type: "text", text: "OK" }] };
67
- });
68
- server.registerTool("set_room_settings", {
69
- description: "Replace the entire room settings object",
70
- inputSchema: { roomId: z.string(), data: z.any() },
71
- }, async ({ roomId, data }) => {
72
- await setRoomData(roomId, { settings: data });
73
- return { content: [{ type: "text", text: "OK" }] };
74
- });
75
- server.registerTool("set_room_tasks", {
76
- description: "Replace the entire room tasks object",
77
- inputSchema: { roomId: z.string(), data: z.any() },
78
- }, async ({ roomId, data }) => {
79
- await setRoomData(roomId, { roomTasks: data });
80
- return { content: [{ type: "text", text: "OK" }] };
81
- });
82
- server.registerTool("set_room_quests", {
83
- description: "Replace the entire quests object",
84
- inputSchema: { roomId: z.string(), data: z.any() },
85
- }, async ({ roomId, data }) => {
86
- await setRoomData(roomId, { quests: data });
33
+ server.registerTool("set_room_data", {
34
+ description: "Replace the entire room data. Provide an absolute path to a JSON file. JSON structure should be like : { roomItems: {...}, settings: {...}, roomTasks: {...}, quests: {...} }",
35
+ inputSchema: { roomId: z.string(), filePath: z.string() },
36
+ }, async ({ roomId, filePath }) => {
37
+ const data = await readFile(filePath);
38
+ await uploadRoomData(roomId, data);
87
39
  return { content: [{ type: "text", text: "OK" }] };
88
40
  });
89
41
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portals-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",