@softtechai/quickmcp 1.0.12 → 1.0.13

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.0.12",
3
+ "version": "1.0.13",
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": {
@@ -59,24 +59,49 @@ if (process.env.QUICKMCP_ENABLE_WEB === '1') {
59
59
  const uploadsDir = path.join(runDir, 'uploads');
60
60
  try { fs.mkdirSync(uploadsDir, { recursive: true }); } catch {}
61
61
  if (!process.env.QUICKMCP_UPLOAD_DIR) process.env.QUICKMCP_UPLOAD_DIR = uploadsDir;
62
+
63
+ // Acquire a simple lock so only one process owns the UI (port 3000)
64
+ const lockPath = path.join(runDir, 'ui-3000.lock');
65
+ let hasLock = false;
66
+ try {
67
+ const fd = fs.openSync(lockPath, 'wx');
68
+ fs.closeSync(fd);
69
+ hasLock = true;
70
+ const cleanup = () => { try { fs.unlinkSync(lockPath); } catch {} };
71
+ process.once('exit', cleanup);
72
+ process.once('SIGINT', () => { cleanup(); process.exit(0); });
73
+ process.once('SIGTERM', () => { cleanup(); process.exit(0); });
74
+ } catch (e) {
75
+ if (e && e.code !== 'EEXIST') {
76
+ console.error('[QuickMCP] UI lock error (continuing without UI):', e.message || e);
77
+ }
78
+ // Another process holds the UI lock; skip starting UI in this process
79
+ }
80
+
81
+ if (!hasLock) {
82
+ return; // do not try to start UI
83
+ }
84
+
62
85
  // Only start Web UI if preferred port is actually free.
63
86
  const preferredPort = parseInt(process.env.PORT || '3000', 10);
64
87
  const probe = net.createServer();
65
88
  probe.once('error', (err) => {
66
89
  if (err && (err.code === 'EADDRINUSE' || err.code === 'EACCES')) {
67
90
  // Port is busy or not permitted; skip starting Web UI to avoid crashing Claude session
91
+ try { fs.unlinkSync(lockPath); } catch {}
68
92
  return;
69
93
  }
70
94
  // For other errors, still try starting server; better to attempt than silently skip for unknown cases
71
- try { require('./dist/web/server.js'); } catch (e) { console.error('[QuickMCP] Failed to start Web UI:', e && e.message); }
95
+ safeStartWebServer();
72
96
  });
73
97
  probe.once('listening', () => {
74
98
  probe.close(() => {
75
- try { require('./dist/web/server.js'); } catch (e) { console.error('[QuickMCP] Failed to start Web UI:', e && e.message); }
99
+ safeStartWebServer();
76
100
  });
77
101
  });
78
102
  try {
79
- probe.listen(preferredPort, '127.0.0.1');
103
+ // Probe on IPv6 unspecified to mirror Express default, falling back to IPv4 if needed
104
+ probe.listen(preferredPort, '::');
80
105
  } catch (_e) {
81
106
  // If probing throws synchronously (rare), just skip UI
82
107
  }
@@ -85,6 +110,37 @@ if (process.env.QUICKMCP_ENABLE_WEB === '1') {
85
110
  }
86
111
  }
87
112
 
113
+ // Start the web server but swallow a race-condition EADDRINUSE from Express listen
114
+ function safeStartWebServer() {
115
+ let handled = false;
116
+ const handler = (err) => {
117
+ if (!handled && err && err.code === 'EADDRINUSE' && err.syscall === 'listen') {
118
+ handled = true;
119
+ // Swallow this once so the STDIO server keeps running; another instance already owns :3000
120
+ process.removeListener('uncaughtException', handler);
121
+ return;
122
+ }
123
+ // Not our case; restore default behavior
124
+ process.removeListener('uncaughtException', handler);
125
+ throw err;
126
+ };
127
+ // Install one-time handler to catch immediate async throw from Express
128
+ process.prependOnceListener('uncaughtException', handler);
129
+ try {
130
+ require('./dist/web/server.js');
131
+ } catch (e) {
132
+ // Synchronous load error
133
+ process.removeListener('uncaughtException', handler);
134
+ console.error('[QuickMCP] Failed to start Web UI:', e && e.message);
135
+ }
136
+ // Remove handler on next tick if nothing happened, to avoid swallowing unrelated errors later
137
+ setImmediate(() => {
138
+ if (!handled) {
139
+ process.removeListener('uncaughtException', handler);
140
+ }
141
+ });
142
+ }
143
+
88
144
  // Diagnostics: print environment and mssql details to help debug Claude Desktop
89
145
  try {
90
146
  const resolvedDynExec = require.resolve('./dist/dynamic-mcp-executor.js');