@dollhousemcp/mcp-server 2.0.35 → 2.0.37
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/CHANGELOG.md +11 -0
- package/dist/elements/base/BaseElementManager.d.ts.map +1 -1
- package/dist/elements/base/BaseElementManager.js +6 -2
- package/dist/elements/memories/Memory.d.ts.map +1 -1
- package/dist/elements/memories/Memory.js +9 -2
- package/dist/elements/memories/MemoryManager.d.ts +13 -0
- package/dist/elements/memories/MemoryManager.d.ts.map +1 -1
- package/dist/elements/memories/MemoryManager.js +57 -44
- package/dist/elements/memories/constants.js +2 -2
- package/dist/elements/templates/Template.d.ts +2 -0
- package/dist/elements/templates/Template.d.ts.map +1 -1
- package/dist/elements/templates/Template.js +9 -1
- package/dist/generated/version.d.ts +2 -2
- package/dist/generated/version.js +3 -3
- package/dist/handlers/GitHubAuthHandler.d.ts +12 -0
- package/dist/handlers/GitHubAuthHandler.d.ts.map +1 -1
- package/dist/handlers/GitHubAuthHandler.js +303 -60
- package/dist/handlers/element-crud/editElement.d.ts.map +1 -1
- package/dist/handlers/element-crud/editElement.js +24 -11
- package/dist/handlers/mcp-aql/MCPAQLHandler.d.ts +62 -0
- package/dist/handlers/mcp-aql/MCPAQLHandler.d.ts.map +1 -1
- package/dist/handlers/mcp-aql/MCPAQLHandler.js +209 -34
- package/dist/security/contentValidator.d.ts +32 -1
- package/dist/security/contentValidator.d.ts.map +1 -1
- package/dist/security/contentValidator.js +79 -33
- package/dist/security/secureYamlParser.d.ts.map +1 -1
- package/dist/security/secureYamlParser.js +5 -2
- package/dist/security/tokenManager.d.ts +1 -1
- package/dist/security/tokenManager.d.ts.map +1 -1
- package/dist/security/tokenManager.js +10 -7
- package/dist/storage/MemoryMetadataExtractor.d.ts +4 -1
- package/dist/storage/MemoryMetadataExtractor.d.ts.map +1 -1
- package/dist/storage/MemoryMetadataExtractor.js +7 -3
- package/dist/utils/pathSecurity.d.ts +3 -1
- package/dist/utils/pathSecurity.d.ts.map +1 -1
- package/dist/utils/pathSecurity.js +38 -2
- package/dist/web/routes/setupRoutes.d.ts +23 -7
- package/dist/web/routes/setupRoutes.d.ts.map +1 -1
- package/dist/web/routes/setupRoutes.js +138 -20
- package/oauth-helper.mjs +233 -92
- package/package.json +1 -1
- package/server.json +2 -2
package/oauth-helper.mjs
CHANGED
|
@@ -13,20 +13,38 @@
|
|
|
13
13
|
* between tool calls, breaking background OAuth polling.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import { fileURLToPath } from 'url';
|
|
17
16
|
import { dirname, join } from 'path';
|
|
18
17
|
import fs from 'fs/promises';
|
|
19
18
|
import fsSync from 'fs';
|
|
20
19
|
import { homedir } from 'os';
|
|
21
20
|
|
|
22
|
-
// Get the directory of this script
|
|
23
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
24
|
-
const __dirname = dirname(__filename);
|
|
25
|
-
|
|
26
21
|
// Constants
|
|
27
22
|
const DEFAULT_POLL_INTERVAL = 5;
|
|
28
23
|
const DEFAULT_EXPIRES_IN = 900; // 15 minutes
|
|
29
24
|
const MAX_TOKEN_SIZE = 10000; // Maximum reasonable token size
|
|
25
|
+
const DOLLHOUSE_HOME_DIR = process.env.DOLLHOUSE_HOME_DIR || homedir();
|
|
26
|
+
const AUTH_DIR = join(DOLLHOUSE_HOME_DIR, '.dollhouse', '.auth');
|
|
27
|
+
const PID_FILE = join(AUTH_DIR, 'oauth-helper.pid');
|
|
28
|
+
const STATE_FILE = join(AUTH_DIR, 'oauth-helper-state.json');
|
|
29
|
+
const RESULT_FILE = join(AUTH_DIR, 'oauth-helper-result.json');
|
|
30
|
+
const LOG_FILE = join(DOLLHOUSE_HOME_DIR, '.dollhouse', 'oauth-helper.log');
|
|
31
|
+
const FLOW_ID = process.env.DOLLHOUSE_OAUTH_HELPER_FLOW_ID || '';
|
|
32
|
+
const TOKEN_URL = process.env.DOLLHOUSE_OAUTH_TOKEN_URL || 'https://github.com/login/oauth/access_token';
|
|
33
|
+
const LOG_ENABLED = process.env.DOLLHOUSE_OAUTH_DEBUG === 'true';
|
|
34
|
+
|
|
35
|
+
const RESULT_MESSAGES = {
|
|
36
|
+
success: 'OAuth helper completed successfully.',
|
|
37
|
+
expired_token: 'Device code expired before authorization completed.',
|
|
38
|
+
access_denied: 'User denied the GitHub authorization request.',
|
|
39
|
+
timeout: 'Authorization timed out before the user completed GitHub authorization.',
|
|
40
|
+
token_storage_failed: 'OAuth token could not be stored securely.',
|
|
41
|
+
network_failure: 'Too many network errors while contacting the OAuth token endpoint.',
|
|
42
|
+
fatal_error: 'OAuth helper stopped after an unrecoverable error.',
|
|
43
|
+
interrupted: 'OAuth helper was interrupted before authentication completed.',
|
|
44
|
+
unknown_response: 'OAuth token endpoint returned an unrecognized response.',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const ALLOWED_RESULT_ERROR_CODES = new Set(Object.keys(RESULT_MESSAGES));
|
|
30
48
|
|
|
31
49
|
// Parse command line arguments
|
|
32
50
|
const args = process.argv.slice(2);
|
|
@@ -36,8 +54,8 @@ if (args.length < 4) {
|
|
|
36
54
|
}
|
|
37
55
|
|
|
38
56
|
const [deviceCode, intervalStr, expiresInStr, clientId] = args;
|
|
39
|
-
const
|
|
40
|
-
const expiresIn = parseInt(expiresInStr, 10) || DEFAULT_EXPIRES_IN;
|
|
57
|
+
const pollIntervalSeconds = Number.parseInt(intervalStr, 10) || DEFAULT_POLL_INTERVAL;
|
|
58
|
+
const expiresIn = Number.parseInt(expiresInStr, 10) || DEFAULT_EXPIRES_IN;
|
|
41
59
|
|
|
42
60
|
// Validate client ID is provided (no hardcoded fallback)
|
|
43
61
|
if (!clientId || clientId === 'undefined') {
|
|
@@ -50,10 +68,6 @@ if (!clientId || clientId === 'undefined') {
|
|
|
50
68
|
process.exit(1);
|
|
51
69
|
}
|
|
52
70
|
|
|
53
|
-
// Log file for debugging (optional, can be disabled in production)
|
|
54
|
-
const LOG_FILE = join(homedir(), '.dollhouse', 'oauth-helper.log');
|
|
55
|
-
const LOG_ENABLED = process.env.DOLLHOUSE_OAUTH_DEBUG === 'true';
|
|
56
|
-
|
|
57
71
|
async function log(message) {
|
|
58
72
|
if (!LOG_ENABLED) return;
|
|
59
73
|
|
|
@@ -86,13 +100,18 @@ async function log(message) {
|
|
|
86
100
|
}
|
|
87
101
|
}
|
|
88
102
|
|
|
103
|
+
function sanitizeDiagnostic(value) {
|
|
104
|
+
return String(value ?? '')
|
|
105
|
+
.replace(/\bgithub_pat_\w+\b/g, '[REDACTED_GITHUB_TOKEN]')
|
|
106
|
+
.replace(/\bgh[a-z]_\w+\b/gi, '[REDACTED_GITHUB_TOKEN]')
|
|
107
|
+
.replaceAll(deviceCode, '[REDACTED_DEVICE_CODE]');
|
|
108
|
+
}
|
|
109
|
+
|
|
89
110
|
async function sleep(ms) {
|
|
90
111
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
91
112
|
}
|
|
92
113
|
|
|
93
114
|
async function pollGitHub(deviceCode, clientId) {
|
|
94
|
-
const TOKEN_URL = 'https://github.com/login/oauth/access_token';
|
|
95
|
-
|
|
96
115
|
try {
|
|
97
116
|
const response = await fetch(TOKEN_URL, {
|
|
98
117
|
method: 'POST',
|
|
@@ -124,74 +143,187 @@ async function storeToken(token) {
|
|
|
124
143
|
|
|
125
144
|
try {
|
|
126
145
|
// Import the compiled TokenManager
|
|
127
|
-
const { TokenManager } = await import('./dist/security/tokenManager.js');
|
|
146
|
+
const { TokenManager } = await import(new URL('./dist/security/tokenManager.js', import.meta.url).href);
|
|
128
147
|
|
|
129
|
-
// Store the token using the secure storage mechanism
|
|
130
|
-
|
|
148
|
+
// Store the token using the secure file-backed storage mechanism.
|
|
149
|
+
const tokenManager = new TokenManager(createHelperFileOperations());
|
|
150
|
+
await tokenManager.storeGitHubToken(token);
|
|
131
151
|
await log('Token stored successfully using TokenManager');
|
|
132
152
|
return true;
|
|
133
|
-
} catch {
|
|
134
|
-
await log(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
153
|
+
} catch (error) {
|
|
154
|
+
await log(`Failed to store token using TokenManager: ${sanitizeDiagnostic(error instanceof Error ? error.message : 'Unknown error')}`);
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function createHelperFileOperations() {
|
|
160
|
+
// Paths are fixed by AUTH_DIR constants, so this standalone helper only needs
|
|
161
|
+
// the small FileOperations surface TokenManager uses for secure storage.
|
|
162
|
+
return {
|
|
163
|
+
async createDirectory(directoryPath) {
|
|
164
|
+
await fs.mkdir(directoryPath, { recursive: true });
|
|
165
|
+
},
|
|
166
|
+
async readFile(filePath) {
|
|
167
|
+
return fs.readFile(filePath, 'utf8');
|
|
168
|
+
},
|
|
169
|
+
async writeFile(filePath, content) {
|
|
170
|
+
await fs.writeFile(filePath, content, { encoding: 'utf8' });
|
|
171
|
+
},
|
|
172
|
+
async deleteFile(filePath) {
|
|
173
|
+
await fs.unlink(filePath);
|
|
174
|
+
},
|
|
175
|
+
async chmod(filePath, mode) {
|
|
176
|
+
await fs.chmod(filePath, mode);
|
|
177
|
+
},
|
|
178
|
+
async exists(filePath) {
|
|
179
|
+
try {
|
|
180
|
+
await fs.access(filePath);
|
|
181
|
+
return true;
|
|
182
|
+
} catch {
|
|
183
|
+
return false;
|
|
149
184
|
}
|
|
150
|
-
|
|
151
|
-
// Write token with secure permissions
|
|
152
|
-
await fs.writeFile(tempTokenFile, token, { mode: 0o600 });
|
|
153
|
-
|
|
154
|
-
// Verify file permissions
|
|
155
|
-
await fs.chmod(tempTokenFile, 0o600);
|
|
156
|
-
|
|
157
|
-
await log('Token written to fallback file with secure permissions');
|
|
158
|
-
return true;
|
|
159
|
-
} catch (fallbackError) {
|
|
160
|
-
await log('Fallback storage also failed');
|
|
161
|
-
throw fallbackError;
|
|
162
185
|
}
|
|
163
|
-
}
|
|
186
|
+
};
|
|
164
187
|
}
|
|
165
188
|
|
|
166
189
|
function cleanupPidFileSync() {
|
|
167
190
|
try {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
fsSync.unlinkSync(pidFile);
|
|
191
|
+
if (pidFileBelongsToThisHelperSync()) {
|
|
192
|
+
fsSync.unlinkSync(PID_FILE);
|
|
171
193
|
}
|
|
172
|
-
} catch
|
|
194
|
+
} catch {
|
|
195
|
+
// Ignore cleanup errors
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function cleanupStateFileSync() {
|
|
200
|
+
try {
|
|
201
|
+
if (stateFileBelongsToThisHelperSync()) {
|
|
202
|
+
fsSync.unlinkSync(STATE_FILE);
|
|
203
|
+
}
|
|
204
|
+
} catch {
|
|
173
205
|
// Ignore cleanup errors
|
|
174
206
|
}
|
|
175
207
|
}
|
|
176
208
|
|
|
177
209
|
async function cleanupPidFile() {
|
|
178
210
|
try {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
211
|
+
if (await pidFileBelongsToThisHelper()) {
|
|
212
|
+
await fs.unlink(PID_FILE).catch(() => {});
|
|
213
|
+
await log('PID file cleaned up');
|
|
214
|
+
} else {
|
|
215
|
+
await log('PID file belongs to another helper flow; leaving it in place');
|
|
216
|
+
}
|
|
217
|
+
} catch {
|
|
183
218
|
// Ignore cleanup errors
|
|
184
219
|
}
|
|
185
220
|
}
|
|
186
221
|
|
|
222
|
+
async function cleanupStateFile() {
|
|
223
|
+
try {
|
|
224
|
+
if (await stateFileBelongsToThisHelper()) {
|
|
225
|
+
await fs.unlink(STATE_FILE).catch(() => {});
|
|
226
|
+
await log('OAuth helper state file cleaned up');
|
|
227
|
+
} else {
|
|
228
|
+
await log('OAuth helper state belongs to another flow; leaving it in place');
|
|
229
|
+
}
|
|
230
|
+
} catch {
|
|
231
|
+
// Ignore cleanup errors
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function buildTerminalResult(status, attempts, errorCode = '') {
|
|
236
|
+
const safeErrorCode = ALLOWED_RESULT_ERROR_CODES.has(errorCode) ? errorCode : 'fatal_error';
|
|
237
|
+
const result = {
|
|
238
|
+
status,
|
|
239
|
+
attempts,
|
|
240
|
+
completedAt: new Date().toISOString(),
|
|
241
|
+
pid: process.pid
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
if (FLOW_ID) {
|
|
245
|
+
result.flowId = FLOW_ID;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (status !== 'success') {
|
|
249
|
+
result.errorCode = safeErrorCode;
|
|
250
|
+
result.message = RESULT_MESSAGES[safeErrorCode];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return { result, safeErrorCode };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function writeTerminalResult(status, attempts, errorCode = '') {
|
|
257
|
+
try {
|
|
258
|
+
await fs.mkdir(AUTH_DIR, { recursive: true, mode: 0o700 });
|
|
259
|
+
const { result, safeErrorCode } = buildTerminalResult(status, attempts, errorCode);
|
|
260
|
+
|
|
261
|
+
await fs.writeFile(RESULT_FILE, JSON.stringify(result, null, 2), { mode: 0o600 });
|
|
262
|
+
const resultSuffix = status === 'success' ? '' : `/${safeErrorCode}`;
|
|
263
|
+
await log(`Terminal result written: ${status}${resultSuffix}`);
|
|
264
|
+
} catch {
|
|
265
|
+
await log('Failed to write terminal result');
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function writeTerminalResultSync(status, attempts, errorCode = '') {
|
|
270
|
+
try {
|
|
271
|
+
fsSync.mkdirSync(AUTH_DIR, { recursive: true, mode: 0o700 });
|
|
272
|
+
const { result } = buildTerminalResult(status, attempts, errorCode);
|
|
273
|
+
|
|
274
|
+
fsSync.writeFileSync(RESULT_FILE, JSON.stringify(result, null, 2), { mode: 0o600 });
|
|
275
|
+
} catch {
|
|
276
|
+
// Ignore cleanup/status errors during process termination
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function pidFileBelongsToThisHelper() {
|
|
281
|
+
if (!FLOW_ID) return true;
|
|
282
|
+
try {
|
|
283
|
+
const pid = (await fs.readFile(PID_FILE, 'utf8')).trim();
|
|
284
|
+
return pid === String(process.pid);
|
|
285
|
+
} catch {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function pidFileBelongsToThisHelperSync() {
|
|
291
|
+
if (!FLOW_ID) return fsSync.existsSync(PID_FILE);
|
|
292
|
+
try {
|
|
293
|
+
const pid = fsSync.readFileSync(PID_FILE, 'utf8').trim();
|
|
294
|
+
return pid === String(process.pid);
|
|
295
|
+
} catch {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async function stateFileBelongsToThisHelper() {
|
|
301
|
+
if (!FLOW_ID) return true;
|
|
302
|
+
try {
|
|
303
|
+
const state = JSON.parse(await fs.readFile(STATE_FILE, 'utf8'));
|
|
304
|
+
return state?.flowId === FLOW_ID &&
|
|
305
|
+
(typeof state.pid !== 'number' || state.pid === process.pid);
|
|
306
|
+
} catch {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function stateFileBelongsToThisHelperSync() {
|
|
312
|
+
if (!FLOW_ID) return fsSync.existsSync(STATE_FILE);
|
|
313
|
+
try {
|
|
314
|
+
const state = JSON.parse(fsSync.readFileSync(STATE_FILE, 'utf8'));
|
|
315
|
+
return state?.flowId === FLOW_ID &&
|
|
316
|
+
(typeof state.pid !== 'number' || state.pid === process.pid);
|
|
317
|
+
} catch {
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
187
322
|
async function writePidFile() {
|
|
188
323
|
try {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
await fs.mkdir(pidDir, { recursive: true, mode: 0o700 });
|
|
193
|
-
await fs.writeFile(pidFile, process.pid.toString(), { mode: 0o600 });
|
|
194
|
-
await log(`PID file written: ${pidFile}`);
|
|
324
|
+
await fs.mkdir(AUTH_DIR, { recursive: true, mode: 0o700 });
|
|
325
|
+
await fs.writeFile(PID_FILE, process.pid.toString(), { mode: 0o600 });
|
|
326
|
+
await log(`PID file written: ${PID_FILE}`);
|
|
195
327
|
} catch {
|
|
196
328
|
await log('Failed to write PID file');
|
|
197
329
|
}
|
|
@@ -200,7 +332,7 @@ async function writePidFile() {
|
|
|
200
332
|
async function main() {
|
|
201
333
|
await log(`[START] OAuth helper started - PID: ${process.pid}`);
|
|
202
334
|
await log('[CONFIG] Device code received');
|
|
203
|
-
await log(`[CONFIG] Poll interval: ${
|
|
335
|
+
await log(`[CONFIG] Poll interval: ${pollIntervalSeconds}s, Expires in: ${expiresIn}s`);
|
|
204
336
|
await log(`[CONFIG] Node version: ${process.version}`);
|
|
205
337
|
await log(`[CONFIG] Platform: ${process.platform}`);
|
|
206
338
|
// Never log client ID
|
|
@@ -219,6 +351,7 @@ async function main() {
|
|
|
219
351
|
const timeout = startTime + (expiresIn * 1000);
|
|
220
352
|
let attempts = 0;
|
|
221
353
|
let consecutiveErrors = 0;
|
|
354
|
+
let currentPollIntervalMs = pollIntervalSeconds * 1000;
|
|
222
355
|
const MAX_CONSECUTIVE_ERRORS = 5;
|
|
223
356
|
|
|
224
357
|
// Set up cleanup on exit - use synchronous cleanup for exit event
|
|
@@ -233,14 +366,26 @@ async function main() {
|
|
|
233
366
|
});
|
|
234
367
|
|
|
235
368
|
process.on('SIGINT', () => {
|
|
369
|
+
writeTerminalResultSync('failed', attempts, 'interrupted');
|
|
370
|
+
cleanupStateFileSync();
|
|
236
371
|
cleanupPidFileSync();
|
|
237
|
-
process.exit(
|
|
372
|
+
process.exit(1);
|
|
238
373
|
});
|
|
239
374
|
|
|
240
375
|
process.on('SIGTERM', () => {
|
|
376
|
+
writeTerminalResultSync('failed', attempts, 'interrupted');
|
|
377
|
+
cleanupStateFileSync();
|
|
241
378
|
cleanupPidFileSync();
|
|
242
|
-
process.exit(
|
|
379
|
+
process.exit(1);
|
|
243
380
|
});
|
|
381
|
+
|
|
382
|
+
async function finish(status, errorCode, exitCode) {
|
|
383
|
+
clearInterval(heartbeatInterval);
|
|
384
|
+
await writeTerminalResult(status, attempts, errorCode);
|
|
385
|
+
await cleanupStateFile();
|
|
386
|
+
await cleanupPidFile();
|
|
387
|
+
process.exit(exitCode);
|
|
388
|
+
}
|
|
244
389
|
|
|
245
390
|
while (Date.now() < timeout) {
|
|
246
391
|
attempts++;
|
|
@@ -258,51 +403,51 @@ async function main() {
|
|
|
258
403
|
break;
|
|
259
404
|
|
|
260
405
|
case 'slow_down':
|
|
261
|
-
// GitHub
|
|
262
|
-
|
|
263
|
-
|
|
406
|
+
// GitHub asks clients to add 5s to the polling interval, then wait
|
|
407
|
+
// the updated interval before the next request.
|
|
408
|
+
currentPollIntervalMs += 5000;
|
|
409
|
+
await log(`[RATE_LIMIT] GitHub requested slower polling - increasing interval to ${currentPollIntervalMs / 1000}s`);
|
|
410
|
+
await sleep(currentPollIntervalMs);
|
|
264
411
|
continue;
|
|
265
412
|
|
|
266
413
|
case 'expired_token':
|
|
267
414
|
await log('OAUTH_HELPER_264: Device code expired - authentication window closed');
|
|
268
415
|
console.error('OAUTH_EXPIRED: Device code expired at line 264 - authentication window closed');
|
|
269
|
-
|
|
270
|
-
await cleanupPidFile();
|
|
271
|
-
process.exit(1);
|
|
416
|
+
return finish('expired', 'expired_token', 1);
|
|
272
417
|
|
|
273
418
|
case 'access_denied':
|
|
274
419
|
await log('OAUTH_HELPER_270: User denied authorization request');
|
|
275
420
|
console.error('OAUTH_ACCESS_DENIED: User denied authorization at line 270');
|
|
276
|
-
|
|
277
|
-
await cleanupPidFile();
|
|
278
|
-
process.exit(1);
|
|
421
|
+
return finish('denied', 'access_denied', 1);
|
|
279
422
|
|
|
280
423
|
default:
|
|
281
424
|
await log('OAUTH_HELPER_276: Unknown error from GitHub during device flow polling');
|
|
282
425
|
await log('[ERROR] GitHub returned an unrecognized OAuth polling response');
|
|
283
426
|
console.error('OAUTH_UNKNOWN_RESPONSE: Unknown GitHub OAuth response at line 276');
|
|
427
|
+
return finish('failed', 'unknown_response', 1);
|
|
284
428
|
}
|
|
285
429
|
} else if (response.access_token) {
|
|
286
430
|
// Success! We got the token
|
|
287
431
|
await log('[SUCCESS] ✅ Token received from GitHub!');
|
|
288
432
|
consecutiveErrors = 0; // Reset error counter
|
|
289
433
|
|
|
290
|
-
|
|
291
|
-
|
|
434
|
+
let stored = false;
|
|
435
|
+
try {
|
|
436
|
+
stored = await storeToken(response.access_token);
|
|
437
|
+
} catch {
|
|
438
|
+
console.error('OAUTH_TOKEN_STORAGE_FAILED: Failed to store authentication token securely');
|
|
439
|
+
return finish('failed', 'token_storage_failed', 1);
|
|
440
|
+
}
|
|
292
441
|
|
|
293
442
|
if (stored) {
|
|
294
443
|
await log('[SUCCESS] ✅ OAuth authentication completed successfully');
|
|
295
444
|
await log(`[STATS] Total attempts: ${attempts}, Time elapsed: ${Math.round((Date.now() - startTime) / 1000)}s`);
|
|
296
445
|
console.log('✅ GitHub authentication successful! Token has been stored.');
|
|
297
|
-
|
|
298
|
-
await cleanupPidFile();
|
|
299
|
-
process.exit(0);
|
|
446
|
+
return finish('success', 'success', 0);
|
|
300
447
|
} else {
|
|
301
448
|
await log('[ERROR] ❌ Failed to store token');
|
|
302
449
|
console.error('❌ Failed to store authentication token');
|
|
303
|
-
|
|
304
|
-
await cleanupPidFile();
|
|
305
|
-
process.exit(1);
|
|
450
|
+
return finish('failed', 'token_storage_failed', 1);
|
|
306
451
|
}
|
|
307
452
|
} else {
|
|
308
453
|
// Reset error counter on successful communication
|
|
@@ -327,37 +472,33 @@ async function main() {
|
|
|
327
472
|
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
328
473
|
await log('OAUTH_HELPER_323: Too many consecutive network errors, exiting');
|
|
329
474
|
console.error(`OAUTH_NETWORK_FAILURE: Too many network errors (${MAX_CONSECUTIVE_ERRORS}) at line 323 - check internet connection`);
|
|
330
|
-
|
|
331
|
-
await cleanupPidFile();
|
|
332
|
-
process.exit(1);
|
|
475
|
+
return finish('failed', 'network_failure', 1);
|
|
333
476
|
}
|
|
334
477
|
} else {
|
|
335
478
|
// Non-network error, likely fatal
|
|
336
|
-
await log(
|
|
479
|
+
await log(`OAUTH_HELPER_330: Non-recoverable error: ${sanitizeDiagnostic(error instanceof Error ? error.message : 'Unknown error')}`);
|
|
337
480
|
console.error('OAUTH_FATAL_ERROR: Non-recoverable error at line 330');
|
|
338
|
-
|
|
339
|
-
await cleanupPidFile();
|
|
340
|
-
process.exit(1);
|
|
481
|
+
return finish('failed', 'fatal_error', 1);
|
|
341
482
|
}
|
|
342
483
|
}
|
|
343
484
|
|
|
344
485
|
// Wait before next poll
|
|
345
|
-
await sleep(
|
|
486
|
+
await sleep(currentPollIntervalMs);
|
|
346
487
|
}
|
|
347
488
|
|
|
348
489
|
// Timeout reached
|
|
349
490
|
await log('OAUTH_HELPER_342: OAuth authorization timed out');
|
|
350
491
|
await log(`[STATS] Total attempts: ${attempts}, Time elapsed: ${Math.round((Date.now() - startTime) / 1000)}s`);
|
|
351
492
|
console.error(`OAUTH_TIMEOUT: Authorization timed out at line 342 after ${Math.round((Date.now() - startTime) / 1000)}s - user did not authorize in time`);
|
|
352
|
-
|
|
353
|
-
await cleanupPidFile();
|
|
354
|
-
process.exit(1);
|
|
493
|
+
return finish('timeout', 'timeout', 1);
|
|
355
494
|
}
|
|
356
495
|
|
|
357
496
|
// Run the main function
|
|
358
|
-
main().catch(async () => {
|
|
359
|
-
await log(
|
|
497
|
+
main().catch(async (error) => {
|
|
498
|
+
await log(`Fatal error: ${sanitizeDiagnostic(error instanceof Error ? error.message : 'Unknown error')}`);
|
|
360
499
|
console.error('Fatal error in OAuth helper');
|
|
500
|
+
await writeTerminalResult('failed', 0, 'fatal_error');
|
|
501
|
+
await cleanupStateFile();
|
|
361
502
|
await cleanupPidFile();
|
|
362
503
|
process.exit(1);
|
|
363
504
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dollhousemcp/mcp-server",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.37",
|
|
4
4
|
"description": "DollhouseMCP - A Model Context Protocol (MCP) server that enables dynamic AI persona management from markdown files, allowing Claude and other compatible AI assistants to activate and switch between different behavioral personas.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
package/server.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "io.github.DollhouseMCP/mcp-server",
|
|
4
4
|
"title": "DollhouseMCP",
|
|
5
5
|
"description": "OSS to create Personas, Skills, Templates, Agents, and Memories to customize your AI experience.",
|
|
6
|
-
"version": "2.0.
|
|
6
|
+
"version": "2.0.37",
|
|
7
7
|
"homepage": "https://dollhousemcp.com",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
{
|
|
30
30
|
"registryType": "npm",
|
|
31
31
|
"identifier": "@dollhousemcp/mcp-server",
|
|
32
|
-
"version": "2.0.
|
|
32
|
+
"version": "2.0.37",
|
|
33
33
|
"transport": {
|
|
34
34
|
"type": "stdio"
|
|
35
35
|
}
|