@softtechai/quickmcp 1.1.0 → 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/README.md CHANGED
@@ -479,7 +479,7 @@ QuickMCP, NPM üzerinden çalıştırıldığında artık varsayılan olarak web
479
479
  ### Hızlı başlatma
480
480
 
481
481
  ```bash
482
- npx -y @softtech/quickmcp
482
+ npx -y @softtechai/quickmcp
483
483
  # UI -> http://localhost:3000
484
484
  # Integrated MCP sidecar -> :3001
485
485
  ```
@@ -493,7 +493,7 @@ npx -y @softtech/quickmcp
493
493
  Örnek:
494
494
 
495
495
  ```bash
496
- npx -y @softtech/quickmcp --port=4000 --data-dir=./data
496
+ npx -y @softtechai/quickmcp --port=4000 --data-dir=./data
497
497
  ```
498
498
 
499
499
  ### Ortam değişkenleri
@@ -506,7 +506,7 @@ npx -y @softtech/quickmcp --port=4000 --data-dir=./data
506
506
  Örnek:
507
507
 
508
508
  ```bash
509
- PORT=4000 QUICKMCP_DATA_DIR=./data npx -y @softtech/quickmcp
509
+ PORT=4000 QUICKMCP_DATA_DIR=./data npx -y @softtechai/quickmcp
510
510
  ```
511
511
 
512
512
  > Not: Web UI, stdio tabanlı MCP sunucusunun yanında yan servis olarak çalışır; Claude Desktop entegrasyonu ile çakışmaz.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softtechai/quickmcp",
3
- "version": "1.1.0",
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": {
@@ -40,6 +40,7 @@
40
40
  "better-sqlite3": "^12.4.1",
41
41
  "cors": "^2.8.5",
42
42
  "csv-parser": "^3.0.0",
43
+ "dotenv": "^16.4.5",
43
44
  "exceljs": "^4.4.0",
44
45
  "express": "^4.18.0",
45
46
  "imapflow": "^1.2.6",
@@ -63,7 +64,6 @@
63
64
  "@types/ws": "^8.5.0",
64
65
  "@typescript-eslint/eslint-plugin": "^6.0.0",
65
66
  "@typescript-eslint/parser": "^6.0.0",
66
- "dotenv": "^16.4.5",
67
67
  "eslint": "^8.0.0",
68
68
  "tsx": "^4.0.0",
69
69
  "typescript": "^5.0.0"
@@ -4,12 +4,19 @@ const path = require('path');
4
4
  const fs = require('fs');
5
5
  const os = require('os');
6
6
  const net = require('net');
7
- const dotenv = require('dotenv');
7
+ let dotenv = null;
8
+ try {
9
+ dotenv = require('dotenv');
10
+ } catch (_) {
11
+ dotenv = null;
12
+ }
8
13
 
9
- // Load .env from script directory first (works with Claude Desktop custom CWD),
10
- // then fallback to process CWD.
11
- dotenv.config({ path: path.join(__dirname, '.env') });
12
- dotenv.config();
14
+ if (dotenv) {
15
+ // Load .env from script directory first (works with Claude Desktop custom CWD),
16
+ // then fallback to process CWD.
17
+ dotenv.config({ path: path.join(__dirname, '.env') });
18
+ dotenv.config();
19
+ }
13
20
 
14
21
  let logger;
15
22
  try {
@@ -73,6 +80,16 @@ const wantsWeb = argv.includes('--web') || argv.includes('-w') || argv.includes(
73
80
  const noWeb = argv.includes('--no-web');
74
81
  const portArg = argv.find(a => a.startsWith('--port='));
75
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
+ }
76
93
  // Default behavior: Web UI enabled unless explicitly disabled
77
94
  if (noWeb || process.env.QUICKMCP_ENABLE_WEB === '0' || process.env.QUICKMCP_DISABLE_WEB === '1') {
78
95
  process.env.QUICKMCP_ENABLE_WEB = '0';
@@ -149,38 +166,69 @@ if (process.env.QUICKMCP_ENABLE_WEB === '1') {
149
166
  const preferredPort = parseInt(process.env.PORT || '3000', 10);
150
167
  const lockPath = path.join(runDir, `ui-${preferredPort}.lock`);
151
168
  let hasLock = false;
152
- try {
153
- const fd = fs.openSync(lockPath, 'wx');
154
- fs.closeSync(fd);
155
- hasLock = true;
156
- const cleanup = () => { try { fs.unlinkSync(lockPath); } catch {} };
157
- process.once('exit', cleanup);
158
- process.once('SIGINT', () => { cleanup(); process.exit(0); });
159
- process.once('SIGTERM', () => { cleanup(); process.exit(0); });
160
- } catch (e) {
161
- if (e && e.code !== 'EEXIST') {
162
- 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);
163
210
  }
164
- // Another process holds the UI lock; skip starting UI in this process
165
- }
211
+ };
212
+ tryAcquireLock();
166
213
 
167
214
  if (!hasLock) {
168
- 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');
169
216
  } else {
170
217
  // Only start Web UI if preferred port is actually free.
171
218
  const probe = net.createServer();
172
219
  probe.once('error', (err) => {
173
220
  if (err && (err.code === 'EADDRINUSE' || err.code === 'EACCES')) {
174
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`);
175
223
  try { fs.unlinkSync(lockPath); } catch {}
176
224
  return;
177
225
  }
178
226
  // For other errors, still try starting server; better to attempt than silently skip for unknown cases
179
- safeStartWebServer();
227
+ safeStartWebServer(preferredPort);
180
228
  });
181
229
  probe.once('listening', () => {
182
230
  probe.close(() => {
183
- safeStartWebServer();
231
+ safeStartWebServer(preferredPort);
184
232
  });
185
233
  });
186
234
  try {
@@ -196,12 +244,13 @@ if (process.env.QUICKMCP_ENABLE_WEB === '1') {
196
244
  }
197
245
 
198
246
  // Start the web server but swallow a race-condition EADDRINUSE from Express listen
199
- function safeStartWebServer() {
247
+ function safeStartWebServer(port) {
200
248
  let handled = false;
201
249
  const handler = (err) => {
202
250
  if (!handled && err && err.code === 'EADDRINUSE' && err.syscall === 'listen') {
203
251
  handled = true;
204
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`);
205
254
  process.removeListener('uncaughtException', handler);
206
255
  return;
207
256
  }
@@ -212,6 +261,7 @@ function safeStartWebServer() {
212
261
  // Install one-time handler to catch immediate async throw from Express
213
262
  process.prependOnceListener('uncaughtException', handler);
214
263
  try {
264
+ logger.error(`[QuickMCP] Starting Web UI at http://localhost:${port || process.env.PORT || 3000}`);
215
265
  require('./dist/server/server.js');
216
266
  } catch (e) {
217
267
  // Synchronous load error