aiquila-mcp 0.3.11 → 0.3.12
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/auth/store.js +38 -2
- package/dist/transports/http.js +10 -5
- package/package.json +1 -1
package/dist/auth/store.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { readFileSync, writeFileSync, mkdirSync, renameSync, unlinkSync } from 'node:fs';
|
|
4
|
-
import { join } from 'node:path';
|
|
4
|
+
import { join, dirname } from 'node:path';
|
|
5
5
|
import { logger } from '../logger.js';
|
|
6
6
|
const CODE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
7
7
|
const REFRESH_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
@@ -9,6 +9,39 @@ const DEFAULT_STATE_DIR = '/app/state';
|
|
|
9
9
|
function stateDir() {
|
|
10
10
|
return (process.env.MCP_AUTH_STATE_DIR ?? DEFAULT_STATE_DIR).replace(/\/+$/, '');
|
|
11
11
|
}
|
|
12
|
+
// True once we've emitted the loud "state dir not writable" warning, so the
|
|
13
|
+
// startup probe and the first failed persist warn at most once between them —
|
|
14
|
+
// subsequent persist failures drop to debug to avoid flooding the logs.
|
|
15
|
+
let warnedUnwritable = false;
|
|
16
|
+
/**
|
|
17
|
+
* Operator-facing remediation for an unwritable state directory. The fix is to
|
|
18
|
+
* recreate the (root-owned) Docker named volume so a fresh one inherits the
|
|
19
|
+
* image's node ownership — `docker compose exec ... chown` cannot be used here
|
|
20
|
+
* because the container would otherwise be in a crash loop. See discussion #342.
|
|
21
|
+
*/
|
|
22
|
+
export function stateUnwritableMessage(dir, code) {
|
|
23
|
+
return (`State directory ${dir} is not writable${code ? ` (${code})` : ''} — ` +
|
|
24
|
+
`OAuth tokens will NOT persist; clients must re-authenticate after every restart. ` +
|
|
25
|
+
`To restore persistence, recreate the state volume (this clears existing tokens; ` +
|
|
26
|
+
`clients re-authenticate once):\n` +
|
|
27
|
+
` docker compose down mcp && docker volume rm <project>_mcp_state && docker compose up -d mcp\n` +
|
|
28
|
+
`See https://github.com/elgorro/aiquila/discussions/342`);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Marks the unwritable-state warning as already emitted (called by the startup
|
|
32
|
+
* probe in the HTTP transport) so the first failed persist does not warn twice.
|
|
33
|
+
*/
|
|
34
|
+
export function markStateUnwritableWarned() {
|
|
35
|
+
warnedUnwritable = true;
|
|
36
|
+
}
|
|
37
|
+
function warnPersistFailed(dir, code, err) {
|
|
38
|
+
if (warnedUnwritable) {
|
|
39
|
+
logger.debug({ dir, code, err }, '[state] persist failed — state dir not writable');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
warnedUnwritable = true;
|
|
43
|
+
logger.warn({ dir, code }, stateUnwritableMessage(dir, code));
|
|
44
|
+
}
|
|
12
45
|
function ensureDir(dir) {
|
|
13
46
|
try {
|
|
14
47
|
mkdirSync(dir, { recursive: true });
|
|
@@ -49,7 +82,10 @@ function saveJson(filePath, data) {
|
|
|
49
82
|
catch {
|
|
50
83
|
// ignore cleanup failure
|
|
51
84
|
}
|
|
52
|
-
|
|
85
|
+
// Graceful degradation: a persist failure must never crash a request or the
|
|
86
|
+
// process. The server keeps running with in-memory state (tokens just won't
|
|
87
|
+
// survive a restart) and warns the operator how to fix it. See discussion #342.
|
|
88
|
+
warnPersistFailed(dirname(filePath), err.code, err);
|
|
53
89
|
}
|
|
54
90
|
}
|
|
55
91
|
export class StateDirNotWritableError extends Error {
|
package/dist/transports/http.js
CHANGED
|
@@ -7,7 +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
|
+
import { probeStateDir, StateDirNotWritableError, stateUnwritableMessage, markStateUnwritableWarned, } from '../auth/store.js';
|
|
11
11
|
import { loginHandler } from '../auth/login.js';
|
|
12
12
|
import { logger } from '../logger.js';
|
|
13
13
|
import { fetchStatus } from '../client/ocs.js';
|
|
@@ -182,11 +182,16 @@ export async function startHttp() {
|
|
|
182
182
|
}
|
|
183
183
|
catch (err) {
|
|
184
184
|
if (err instanceof StateDirNotWritableError) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
185
|
+
// Degrade gracefully rather than crash-loop: the server still serves
|
|
186
|
+
// requests, it just can't persist OAuth tokens until the operator fixes
|
|
187
|
+
// the volume. Crashing here was un-fixable under `restart: unless-stopped`
|
|
188
|
+
// because `docker compose exec` needs a running container (discussion #342).
|
|
189
|
+
logger.warn({ dir: err.dir, code: err.cause.code }, stateUnwritableMessage(err.dir, err.cause.code));
|
|
190
|
+
markStateUnwritableWarned();
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
throw err;
|
|
188
194
|
}
|
|
189
|
-
throw err;
|
|
190
195
|
}
|
|
191
196
|
const provider = new NextcloudOAuthProvider();
|
|
192
197
|
// When gated dynamic registration is desired, require a bearer token on POST /register.
|