aiquila-mcp 0.3.8 → 0.3.10

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.
@@ -14,7 +14,9 @@ function ensureDir(dir) {
14
14
  mkdirSync(dir, { recursive: true });
15
15
  }
16
16
  catch (err) {
17
- logger.warn({ dir, err }, '[state] Failed to create state directory');
17
+ // Best-effort: probeStateDir() (called from startup) is the authoritative
18
+ // check. Logging here would spam non-HTTP transports that never persist.
19
+ logger.debug({ dir, err }, '[state] mkdir failed (probe will report fatal if writes break)');
18
20
  }
19
21
  }
20
22
  function loadJson(filePath) {
@@ -41,13 +43,50 @@ function saveJson(filePath, data) {
41
43
  renameSync(tmp, filePath);
42
44
  }
43
45
  catch (err) {
44
- logger.warn({ file: filePath, err }, '[state] Failed to save state file');
45
46
  try {
46
47
  unlinkSync(tmp);
47
48
  }
48
49
  catch {
49
50
  // ignore cleanup failure
50
51
  }
52
+ throw err;
53
+ }
54
+ }
55
+ export class StateDirNotWritableError extends Error {
56
+ dir;
57
+ cause;
58
+ constructor(dir, cause) {
59
+ super(`State directory ${dir} is not writable (${cause.code}). ` +
60
+ `Fix volume ownership, then restart. For Docker named volumes:\n` +
61
+ ` docker compose exec -u 0 mcp chown -R node:node ${dir}\n` +
62
+ ` docker compose restart mcp`);
63
+ this.dir = dir;
64
+ this.cause = cause;
65
+ this.name = 'StateDirNotWritableError';
66
+ }
67
+ }
68
+ /**
69
+ * Verifies the state directory is usable before the server starts accepting
70
+ * requests. Writes and removes a sentinel file. Throws StateDirNotWritableError
71
+ * on any filesystem permission / read-only / out-of-space failure so the
72
+ * caller can log a fatal-level remediation message and exit.
73
+ */
74
+ export function probeStateDir(dir = stateDir()) {
75
+ try {
76
+ mkdirSync(dir, { recursive: true });
77
+ const probe = join(dir, '.write-probe');
78
+ writeFileSync(probe, String(process.pid), 'utf8');
79
+ unlinkSync(probe);
80
+ }
81
+ catch (err) {
82
+ const errno = err;
83
+ if (errno.code === 'EACCES' ||
84
+ errno.code === 'EPERM' ||
85
+ errno.code === 'EROFS' ||
86
+ errno.code === 'ENOSPC') {
87
+ throw new StateDirNotWritableError(dir, errno);
88
+ }
89
+ throw err;
51
90
  }
52
91
  }
53
92
  export class ClientsStore {
@@ -7,6 +7,7 @@ import { mcpAuthRouter } from '@modelcontextprotocol/sdk/server/auth/router.js';
7
7
  import { requireBearerAuth } from '@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js';
8
8
  import { createServer, SERVER_VERSION } from '../server.js';
9
9
  import { NextcloudOAuthProvider } from '../auth/provider.js';
10
+ import { probeStateDir, StateDirNotWritableError } from '../auth/store.js';
10
11
  import { loginHandler } from '../auth/login.js';
11
12
  import { logger } from '../logger.js';
12
13
  import { fetchStatus } from '../client/ocs.js';
@@ -176,6 +177,17 @@ export async function startHttp() {
176
177
  if (!registrationEnabled && !hasStaticClient) {
177
178
  logger.warn('No clients configured. Set MCP_CLIENT_ID or MCP_REGISTRATION_ENABLED=true');
178
179
  }
180
+ try {
181
+ probeStateDir();
182
+ }
183
+ catch (err) {
184
+ if (err instanceof StateDirNotWritableError) {
185
+ logger.fatal({ dir: err.dir, code: err.cause.code }, `[startup] State directory is not writable — refresh tokens cannot be persisted. ` +
186
+ `Fix volume ownership and restart:\n docker compose exec -u 0 mcp chown -R node:node ${err.dir}\n docker compose restart mcp`);
187
+ process.exit(1);
188
+ }
189
+ throw err;
190
+ }
179
191
  const provider = new NextcloudOAuthProvider();
180
192
  // When gated dynamic registration is desired, require a bearer token on POST /register.
181
193
  if (registrationEnabled && registrationToken) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiquila-mcp",
3
- "version": "0.3.8",
3
+ "version": "0.3.10",
4
4
  "description": "AIquila - MCP server for Nextcloud integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",