@seleniumbox/sbox-mcp 0.4.0 → 0.4.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/README.md CHANGED
@@ -56,6 +56,14 @@ Then use:
56
56
 
57
57
  Open Cursor from the directory that contains `node_modules`.
58
58
 
59
+ **If you see `ENOTEMPTY: directory not empty, rmdir`** when using npx, the npx cache is in a bad state. Clear it and try again:
60
+
61
+ ```bash
62
+ rm -rf ~/.npm/_npx
63
+ ```
64
+
65
+ Then restart Cursor or reload MCP.
66
+
59
67
  ## Required environment variables
60
68
 
61
69
  | Variable | Default | Description |
@@ -64,11 +72,16 @@ Open Cursor from the directory that contains `node_modules`.
64
72
 
65
73
  Optional:
66
74
 
67
- | Variable | Default | Description |
68
- |-------------------------|---------|--------------------------------------------|
69
- | `PORT` / `MCP_PORT` | `3344` | Only used when running the REST server. |
70
- | `MCP_DEBUG` / `DEBUG` | off | Set to `1` or `true` for request logging. |
71
- | `MCP_REQUEST_TIMEOUT_MS`| `30000` | Timeout for outbound SBOX API requests. |
75
+ | Variable | Default | Description |
76
+ |-------------------------|-------------|-----------------------------------------------------------------------------|
77
+ | `PORT` / `MCP_PORT` | `3344` | Only used when running the REST server. |
78
+ | `MCP_DEBUG` / `DEBUG` | off | Set to `1` or `true` for request logging. |
79
+ | `MCP_REQUEST_TIMEOUT_MS`| `30000` | Timeout for outbound SBOX API requests. |
80
+ | `SBOX_MCP_TOKEN_FILE` | see below | Override path for the persisted session token file (default: `~/.sbox-mcp/token`). |
81
+
82
+ ## Token cache
83
+
84
+ After you log in via **sbox_open_login**, the session token is stored in memory and **on disk** so it survives MCP process restarts (e.g. when the IDE starts a new process per prompt). Default location: `~/.sbox-mcp/token`. The cache directory is created with mode `0700` and the token file with `0600`. Set `SBOX_MCP_TOKEN_FILE` to use a different path (only the token file; the directory is inferred for `last_poll_id`). If an API call returns 401 or “invalid token”, the cache is cleared so the next run will prompt for login again.
72
85
 
73
86
  ## Authentication flow
74
87
 
package/dist/index.js CHANGED
@@ -10,9 +10,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
11
  const path_1 = __importDefault(require("path"));
12
12
  const index_1 = require("./mcp/tools/index");
13
- // Resolve SDK via Node's module resolution so it works when npx hoists deps (e.g. .npm/_npx/.../node_modules)
14
- const sdkPackagePath = require.resolve("@modelcontextprotocol/sdk/package.json");
15
- const sdkServerDir = path_1.default.join(path_1.default.dirname(sdkPackagePath), "dist", "cjs", "server");
13
+ // Resolve SDK server dir via the package's "server" export (dist/cjs/server) so it works when npx hoists deps
14
+ const sdkServerDir = path_1.default.dirname(require.resolve("@modelcontextprotocol/sdk/server"));
16
15
  const { McpServer } = require(path_1.default.join(sdkServerDir, "mcp.js"));
17
16
  const { StdioServerTransport } = require(path_1.default.join(sdkServerDir, "stdio.js"));
18
17
  const server = new McpServer({
@@ -10,8 +10,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
11
  const path_1 = __importDefault(require("path"));
12
12
  const index_1 = require("./mcp/tools/index");
13
- const sdkPackagePath = require.resolve("@modelcontextprotocol/sdk/package.json");
14
- const sdkServerDir = path_1.default.join(path_1.default.dirname(sdkPackagePath), "dist", "cjs", "server");
13
+ const sdkServerDir = path_1.default.dirname(require.resolve("@modelcontextprotocol/sdk/server"));
15
14
  const { McpServer } = require(path_1.default.join(sdkServerDir, "mcp.js"));
16
15
  const { StdioServerTransport } = require(path_1.default.join(sdkServerDir, "stdio.js"));
17
16
  const server = new McpServer({
@@ -13,8 +13,7 @@ const express_1 = require("express");
13
13
  const path_1 = __importDefault(require("path"));
14
14
  const crypto_1 = require("crypto");
15
15
  const index_1 = require("../mcp/tools/index");
16
- const sdkPackagePath = require.resolve("@modelcontextprotocol/sdk/package.json");
17
- const sdkServerDir = path_1.default.join(path_1.default.dirname(sdkPackagePath), "dist", "cjs", "server");
16
+ const sdkServerDir = path_1.default.dirname(require.resolve("@modelcontextprotocol/sdk/server"));
18
17
  const { McpServer } = require(path_1.default.join(sdkServerDir, "mcp.js"));
19
18
  const { StreamableHTTPServerTransport } = require(path_1.default.join(sdkServerDir, "streamableHttp.js"));
20
19
  const sessionMap = new Map();
@@ -1,6 +1,7 @@
1
1
  /**
2
- * In-memory cache for the SBOX session token. Set on successful sbox_open_login,
2
+ * In-memory and file-backed cache for the SBOX session token. Set on successful sbox_open_login,
3
3
  * used by other tools when token is not passed. Cleared on 401/invalid token.
4
+ * File persistence ensures the token survives MCP process restarts (e.g. new process per IDE prompt).
4
5
  * lastPollId is set when we return device-flow (browser didn't open) so sbox_complete_login can use it.
5
6
  */
6
7
  export declare function getCachedToken(): string | null;
@@ -1,9 +1,13 @@
1
1
  "use strict";
2
2
  /**
3
- * In-memory cache for the SBOX session token. Set on successful sbox_open_login,
3
+ * In-memory and file-backed cache for the SBOX session token. Set on successful sbox_open_login,
4
4
  * used by other tools when token is not passed. Cleared on 401/invalid token.
5
+ * File persistence ensures the token survives MCP process restarts (e.g. new process per IDE prompt).
5
6
  * lastPollId is set when we return device-flow (browser didn't open) so sbox_complete_login can use it.
6
7
  */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
7
11
  Object.defineProperty(exports, "__esModule", { value: true });
8
12
  exports.getCachedToken = getCachedToken;
9
13
  exports.setCachedToken = setCachedToken;
@@ -11,22 +15,95 @@ exports.clearCachedToken = clearCachedToken;
11
15
  exports.setLastPollId = setLastPollId;
12
16
  exports.getLastPollId = getLastPollId;
13
17
  exports.resolveToken = resolveToken;
18
+ const fs_1 = __importDefault(require("fs"));
19
+ const path_1 = __importDefault(require("path"));
20
+ const os_1 = __importDefault(require("os"));
14
21
  let cachedToken = null;
15
22
  let lastPollId = null;
23
+ let memoryLoadedFromFile = false;
24
+ function getTokenFilePath() {
25
+ if (process.env.SBOX_MCP_TOKEN_FILE)
26
+ return process.env.SBOX_MCP_TOKEN_FILE;
27
+ const dir = path_1.default.join(os_1.default.homedir(), ".sbox-mcp");
28
+ return path_1.default.join(dir, "token");
29
+ }
30
+ function loadTokenFromFile() {
31
+ if (memoryLoadedFromFile)
32
+ return;
33
+ memoryLoadedFromFile = true;
34
+ try {
35
+ const filePath = getTokenFilePath();
36
+ if (fs_1.default.existsSync(filePath)) {
37
+ const raw = fs_1.default.readFileSync(filePath, "utf-8").trim();
38
+ if (raw)
39
+ cachedToken = raw;
40
+ }
41
+ }
42
+ catch {
43
+ // ignore read errors (e.g. no file, permissions)
44
+ }
45
+ }
46
+ function saveTokenToFile(token) {
47
+ try {
48
+ const filePath = getTokenFilePath();
49
+ if (token) {
50
+ const dir = path_1.default.dirname(filePath);
51
+ if (!fs_1.default.existsSync(dir))
52
+ fs_1.default.mkdirSync(dir, { recursive: true, mode: 0o700 });
53
+ fs_1.default.writeFileSync(filePath, token, { mode: 0o600, encoding: "utf-8" });
54
+ }
55
+ else if (fs_1.default.existsSync(filePath)) {
56
+ fs_1.default.unlinkSync(filePath);
57
+ }
58
+ }
59
+ catch {
60
+ // ignore write errors
61
+ }
62
+ }
16
63
  function getCachedToken() {
64
+ loadTokenFromFile();
17
65
  return cachedToken;
18
66
  }
19
67
  function setCachedToken(token) {
20
68
  cachedToken = token;
69
+ saveTokenToFile(token);
21
70
  }
22
71
  function clearCachedToken() {
23
72
  cachedToken = null;
73
+ saveTokenToFile(null);
74
+ }
75
+ const LAST_POLL_ID_FILE = "last_poll_id";
76
+ function getCacheDir() {
77
+ if (process.env.SBOX_MCP_TOKEN_FILE)
78
+ return path_1.default.dirname(process.env.SBOX_MCP_TOKEN_FILE);
79
+ return path_1.default.join(os_1.default.homedir(), ".sbox-mcp");
24
80
  }
25
81
  function setLastPollId(pollId) {
26
82
  lastPollId = pollId;
83
+ try {
84
+ const dir = getCacheDir();
85
+ if (!fs_1.default.existsSync(dir))
86
+ fs_1.default.mkdirSync(dir, { recursive: true, mode: 0o700 });
87
+ fs_1.default.writeFileSync(path_1.default.join(dir, LAST_POLL_ID_FILE), pollId, { mode: 0o600, encoding: "utf-8" });
88
+ }
89
+ catch {
90
+ // ignore
91
+ }
27
92
  }
28
93
  function getLastPollId() {
29
- return lastPollId;
94
+ if (lastPollId)
95
+ return lastPollId;
96
+ try {
97
+ const filePath = path_1.default.join(getCacheDir(), LAST_POLL_ID_FILE);
98
+ if (fs_1.default.existsSync(filePath)) {
99
+ lastPollId = fs_1.default.readFileSync(filePath, "utf-8").trim() || null;
100
+ return lastPollId;
101
+ }
102
+ }
103
+ catch {
104
+ // ignore
105
+ }
106
+ return null;
30
107
  }
31
108
  /** Resolve token: prefer provided token, else cached. Returns null if neither. */
32
109
  function resolveToken(provided) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seleniumbox/sbox-mcp",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "SBOX MCP – Model Context Protocol server for Selenium Box.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {