@holin-work/holin-cli 1.3.4 → 1.3.6

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.
Files changed (2) hide show
  1. package/bin/holin-cli.js +52 -16
  2. package/package.json +1 -1
package/bin/holin-cli.js CHANGED
@@ -218,10 +218,18 @@ function createProxyServer() {
218
218
 
219
219
  log(`[proxy] → ${req.method} ${req.url} body=${bodyPreview}`);
220
220
 
221
- // Block OAuth discovery probes — return 401 so platform knows auth is required
221
+ // OAuth discovery probes — return standard protected-resource metadata
222
+ // so the host knows this MCP server requires authentication even when connected.
223
+ // RFC 9728: /.well-known/oauth-protected-resource must return 200 + JSON describing the auth requirement.
224
+ // Returning 401 here causes some hosts to show "does not require authentication".
222
225
  if (req.url.startsWith("/.well-known/")) {
223
- const body = JSON.stringify({ error: "unauthorized" });
224
- res.writeHead(401, { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) });
226
+ const body = JSON.stringify({
227
+ resource: `http://${PROXY_HOST}:${PROXY_PORT}`,
228
+ authorization_servers: [],
229
+ bearer_methods_supported: ["header"],
230
+ resource_documentation: "https://holin.work/docs/api",
231
+ });
232
+ res.writeHead(200, { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) });
225
233
  res.end(body);
226
234
  return;
227
235
  }
@@ -557,10 +565,46 @@ function cmdProxyStart() {
557
565
  process.on("SIGTERM", cleanupAndExit);
558
566
  }
559
567
 
568
+ /**
569
+ * Wait until the proxy port is actually accepting TCP connections.
570
+ * Polls every 200ms up to `timeoutMs`. Resolves when the port is reachable,
571
+ * rejects on timeout.
572
+ */
573
+ function waitForPort(port, host, timeoutMs) {
574
+ return new Promise((resolve, reject) => {
575
+ const deadline = Date.now() + timeoutMs;
576
+ function attempt() {
577
+ const req = http.get({ hostname: host, port, path: "/health", timeout: 500 }, (res) => {
578
+ res.resume(); // drain response body
579
+ req.destroy();
580
+ resolve();
581
+ });
582
+ req.on("error", () => {
583
+ req.destroy();
584
+ if (Date.now() >= deadline) {
585
+ reject(new Error(`Proxy port ${port} not reachable within ${timeoutMs}ms. Check log: ${LOG_FILE}`));
586
+ } else {
587
+ setTimeout(attempt, 200);
588
+ }
589
+ });
590
+ req.on("timeout", () => {
591
+ req.destroy();
592
+ if (Date.now() >= deadline) {
593
+ reject(new Error(`Proxy port ${port} not reachable within ${timeoutMs}ms. Check log: ${LOG_FILE}`));
594
+ } else {
595
+ setTimeout(attempt, 200);
596
+ }
597
+ });
598
+ }
599
+ attempt();
600
+ });
601
+ }
602
+
560
603
  function startProxyDaemon() {
561
604
  return new Promise((resolve, reject) => {
562
605
  if (isProxyRunning()) {
563
- resolve();
606
+ // Proxy process exists per pid file — also verify port is actually listening
607
+ waitForPort(PROXY_PORT, PROXY_HOST, 3000).then(resolve).catch(resolve); // best-effort
564
608
  return;
565
609
  }
566
610
 
@@ -574,18 +618,10 @@ function startProxyDaemon() {
574
618
 
575
619
  child.unref();
576
620
 
577
- // Poll for pid file up to 3s to confirm proxy is listening
578
- let attempts = 0;
579
- const poll = setInterval(() => {
580
- attempts++;
581
- if (isProxyRunning()) {
582
- clearInterval(poll);
583
- resolve();
584
- } else if (attempts >= 15) {
585
- clearInterval(poll);
586
- reject(new Error(`Proxy did not start within 3s. Check log: ${LOG_FILE}`));
587
- }
588
- }, 200);
621
+ // Wait until the proxy port is actually accepting connections (up to 10s).
622
+ // This replaces the old pid-file poll which only confirmed the process existed,
623
+ // not that it was ready to serve — causing ECONNREFUSED races on slow machines.
624
+ waitForPort(PROXY_PORT, PROXY_HOST, 10000).then(resolve).catch(reject);
589
625
  });
590
626
  }
591
627
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holin-work/holin-cli",
3
- "version": "1.3.4",
3
+ "version": "1.3.6",
4
4
  "description": "Holin CLI — Accio plugin authorization tool for ICBU batch listing",
5
5
  "type": "module",
6
6
  "bin": {