@softtechai/quickmcp 1.1.1 → 1.1.3
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/package.json +1 -1
- package/quickmcp-direct-stdio.js +66 -18
package/package.json
CHANGED
package/quickmcp-direct-stdio.js
CHANGED
|
@@ -80,6 +80,16 @@ const wantsWeb = argv.includes('--web') || argv.includes('-w') || argv.includes(
|
|
|
80
80
|
const noWeb = argv.includes('--no-web');
|
|
81
81
|
const portArg = argv.find(a => a.startsWith('--port='));
|
|
82
82
|
const dataDirArg = argv.find(a => a.startsWith('--data-dir='));
|
|
83
|
+
|
|
84
|
+
function isProcessAlive(pid) {
|
|
85
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
86
|
+
try {
|
|
87
|
+
process.kill(pid, 0);
|
|
88
|
+
return true;
|
|
89
|
+
} catch (_) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
83
93
|
// Default behavior: Web UI enabled unless explicitly disabled
|
|
84
94
|
if (noWeb || process.env.QUICKMCP_ENABLE_WEB === '0' || process.env.QUICKMCP_DISABLE_WEB === '1') {
|
|
85
95
|
process.env.QUICKMCP_ENABLE_WEB = '0';
|
|
@@ -156,38 +166,69 @@ if (process.env.QUICKMCP_ENABLE_WEB === '1') {
|
|
|
156
166
|
const preferredPort = parseInt(process.env.PORT || '3000', 10);
|
|
157
167
|
const lockPath = path.join(runDir, `ui-${preferredPort}.lock`);
|
|
158
168
|
let hasLock = false;
|
|
159
|
-
try {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
169
|
+
const cleanup = () => { try { fs.unlinkSync(lockPath); } catch {} };
|
|
170
|
+
const tryAcquireLock = () => {
|
|
171
|
+
try {
|
|
172
|
+
const fd = fs.openSync(lockPath, 'wx');
|
|
173
|
+
fs.writeFileSync(fd, String(process.pid), 'utf8');
|
|
174
|
+
fs.closeSync(fd);
|
|
175
|
+
hasLock = true;
|
|
176
|
+
process.once('exit', cleanup);
|
|
177
|
+
process.once('SIGINT', () => { cleanup(); process.exit(0); });
|
|
178
|
+
process.once('SIGTERM', () => { cleanup(); process.exit(0); });
|
|
179
|
+
} catch (e) {
|
|
180
|
+
if (e && e.code === 'EEXIST') {
|
|
181
|
+
let ownerPid = NaN;
|
|
182
|
+
try {
|
|
183
|
+
ownerPid = parseInt(String(fs.readFileSync(lockPath, 'utf8')).trim(), 10);
|
|
184
|
+
} catch (_) {}
|
|
185
|
+
|
|
186
|
+
if (!isProcessAlive(ownerPid)) {
|
|
187
|
+
// Lock file exists but owner is gone (or lock format is empty/invalid): reclaim it.
|
|
188
|
+
try {
|
|
189
|
+
fs.unlinkSync(lockPath);
|
|
190
|
+
const fd = fs.openSync(lockPath, 'wx');
|
|
191
|
+
fs.writeFileSync(fd, String(process.pid), 'utf8');
|
|
192
|
+
fs.closeSync(fd);
|
|
193
|
+
hasLock = true;
|
|
194
|
+
process.once('exit', cleanup);
|
|
195
|
+
process.once('SIGINT', () => { cleanup(); process.exit(0); });
|
|
196
|
+
process.once('SIGTERM', () => { cleanup(); process.exit(0); });
|
|
197
|
+
logger.error(`[QuickMCP] Reclaimed stale UI lock: ${lockPath}`);
|
|
198
|
+
return;
|
|
199
|
+
} catch (reclaimErr) {
|
|
200
|
+
logger.error('[QuickMCP] Failed to reclaim stale UI lock (continuing without UI):', reclaimErr && reclaimErr.message ? reclaimErr.message : reclaimErr);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
logger.error(`[QuickMCP] UI lock held by pid ${ownerPid}; skipping Web UI in this process`);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
logger.error('[QuickMCP] UI lock error (continuing without UI):', e && e.message ? e.message : e);
|
|
170
210
|
}
|
|
171
|
-
|
|
172
|
-
|
|
211
|
+
};
|
|
212
|
+
tryAcquireLock();
|
|
173
213
|
|
|
174
214
|
if (!hasLock) {
|
|
175
|
-
logger.error('[QuickMCP] UI
|
|
215
|
+
logger.error('[QuickMCP] Web UI not started in this process');
|
|
176
216
|
} else {
|
|
177
217
|
// Only start Web UI if preferred port is actually free.
|
|
178
218
|
const probe = net.createServer();
|
|
179
219
|
probe.once('error', (err) => {
|
|
180
220
|
if (err && (err.code === 'EADDRINUSE' || err.code === 'EACCES')) {
|
|
181
221
|
// Port is busy or not permitted; skip starting Web UI to avoid crashing Claude session
|
|
222
|
+
logger.error(`[QuickMCP] Web UI port ${preferredPort} unavailable (${err.code}); skipping Web UI`);
|
|
182
223
|
try { fs.unlinkSync(lockPath); } catch {}
|
|
183
224
|
return;
|
|
184
225
|
}
|
|
185
226
|
// For other errors, still try starting server; better to attempt than silently skip for unknown cases
|
|
186
|
-
safeStartWebServer();
|
|
227
|
+
safeStartWebServer(preferredPort);
|
|
187
228
|
});
|
|
188
229
|
probe.once('listening', () => {
|
|
189
230
|
probe.close(() => {
|
|
190
|
-
safeStartWebServer();
|
|
231
|
+
safeStartWebServer(preferredPort);
|
|
191
232
|
});
|
|
192
233
|
});
|
|
193
234
|
try {
|
|
@@ -203,12 +244,13 @@ if (process.env.QUICKMCP_ENABLE_WEB === '1') {
|
|
|
203
244
|
}
|
|
204
245
|
|
|
205
246
|
// Start the web server but swallow a race-condition EADDRINUSE from Express listen
|
|
206
|
-
function safeStartWebServer() {
|
|
247
|
+
function safeStartWebServer(port) {
|
|
207
248
|
let handled = false;
|
|
208
249
|
const handler = (err) => {
|
|
209
250
|
if (!handled && err && err.code === 'EADDRINUSE' && err.syscall === 'listen') {
|
|
210
251
|
handled = true;
|
|
211
252
|
// Swallow this once so the STDIO server keeps running; another instance already owns :3000
|
|
253
|
+
logger.error(`[QuickMCP] Web UI port ${port || process.env.PORT || 3000} became busy during startup; continuing without UI`);
|
|
212
254
|
process.removeListener('uncaughtException', handler);
|
|
213
255
|
return;
|
|
214
256
|
}
|
|
@@ -219,7 +261,13 @@ function safeStartWebServer() {
|
|
|
219
261
|
// Install one-time handler to catch immediate async throw from Express
|
|
220
262
|
process.prependOnceListener('uncaughtException', handler);
|
|
221
263
|
try {
|
|
222
|
-
|
|
264
|
+
logger.error(`[QuickMCP] Starting Web UI at http://localhost:${port || process.env.PORT || 3000}`);
|
|
265
|
+
const webModule = require('./dist/server/server.js');
|
|
266
|
+
if (webModule && typeof webModule.startServer === 'function') {
|
|
267
|
+
webModule.startServer();
|
|
268
|
+
} else {
|
|
269
|
+
throw new Error('dist/server/server.js does not export startServer()');
|
|
270
|
+
}
|
|
223
271
|
} catch (e) {
|
|
224
272
|
// Synchronous load error
|
|
225
273
|
process.removeListener('uncaughtException', handler);
|