@softtechai/quickmcp 1.1.1 → 1.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softtechai/quickmcp",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "An application to generate MCP servers from various data sources and test them",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -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
- const fd = fs.openSync(lockPath, 'wx');
161
- fs.closeSync(fd);
162
- hasLock = true;
163
- const cleanup = () => { try { fs.unlinkSync(lockPath); } catch {} };
164
- process.once('exit', cleanup);
165
- process.once('SIGINT', () => { cleanup(); process.exit(0); });
166
- process.once('SIGTERM', () => { cleanup(); process.exit(0); });
167
- } catch (e) {
168
- if (e && e.code !== 'EEXIST') {
169
- logger.error('[QuickMCP] UI lock error (continuing without UI):', e.message || e);
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
- // Another process holds the UI lock; skip starting UI in this process
172
- }
211
+ };
212
+ tryAcquireLock();
173
213
 
174
214
  if (!hasLock) {
175
- logger.error('[QuickMCP] UI lock held by another process; skipping Web UI in this process');
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,6 +261,7 @@ function safeStartWebServer() {
219
261
  // Install one-time handler to catch immediate async throw from Express
220
262
  process.prependOnceListener('uncaughtException', handler);
221
263
  try {
264
+ logger.error(`[QuickMCP] Starting Web UI at http://localhost:${port || process.env.PORT || 3000}`);
222
265
  require('./dist/server/server.js');
223
266
  } catch (e) {
224
267
  // Synchronous load error