@holin-work/holin-cli 1.3.5 → 1.3.7
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/bin/holin-cli.js +51 -39
- package/package.json +1 -1
package/bin/holin-cli.js
CHANGED
|
@@ -218,10 +218,37 @@ function createProxyServer() {
|
|
|
218
218
|
|
|
219
219
|
log(`[proxy] → ${req.method} ${req.url} body=${bodyPreview}`);
|
|
220
220
|
|
|
221
|
-
//
|
|
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({
|
|
224
|
-
|
|
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) });
|
|
233
|
+
res.end(body);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Health check endpoint — must be before credential check so waitForPort probes always succeed
|
|
238
|
+
if (req.method === "GET" && req.url === "/health") {
|
|
239
|
+
const creds = readCredentials();
|
|
240
|
+
const body = JSON.stringify({
|
|
241
|
+
status: "ok",
|
|
242
|
+
authenticated: !!(creds?.api_key),
|
|
243
|
+
customer: creds?.customer_name || null,
|
|
244
|
+
plan: creds?.plan || null,
|
|
245
|
+
upstream: UPSTREAM_URL,
|
|
246
|
+
pid: process.pid,
|
|
247
|
+
});
|
|
248
|
+
res.writeHead(200, {
|
|
249
|
+
"Content-Type": "application/json",
|
|
250
|
+
"Content-Length": Buffer.byteLength(body),
|
|
251
|
+
});
|
|
225
252
|
res.end(body);
|
|
226
253
|
return;
|
|
227
254
|
}
|
|
@@ -240,24 +267,6 @@ function createProxyServer() {
|
|
|
240
267
|
return;
|
|
241
268
|
}
|
|
242
269
|
|
|
243
|
-
// Health check endpoint
|
|
244
|
-
if (req.method === "GET" && req.url === "/health") {
|
|
245
|
-
const body = JSON.stringify({
|
|
246
|
-
status: "ok",
|
|
247
|
-
authenticated: !!(creds && creds.api_key),
|
|
248
|
-
customer: creds?.customer_name || null,
|
|
249
|
-
plan: creds?.plan || null,
|
|
250
|
-
upstream: UPSTREAM_URL,
|
|
251
|
-
pid: process.pid,
|
|
252
|
-
});
|
|
253
|
-
res.writeHead(200, {
|
|
254
|
-
"Content-Type": "application/json",
|
|
255
|
-
"Content-Length": Buffer.byteLength(body),
|
|
256
|
-
});
|
|
257
|
-
res.end(body);
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
270
|
// Build upstream request
|
|
262
271
|
// MCP streamable-http has a single endpoint; forward directly to upstream path.
|
|
263
272
|
// Accio requests http://127.0.0.1:18787/mcp → upstream https://api.holin.work/icbu/mcp
|
|
@@ -594,26 +603,29 @@ function waitForPort(port, host, timeoutMs) {
|
|
|
594
603
|
|
|
595
604
|
function startProxyDaemon() {
|
|
596
605
|
return new Promise((resolve, reject) => {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
606
|
+
// Always verify the port is actually reachable first, regardless of pid file.
|
|
607
|
+
// The old proxy may have exited (idle timeout) while the pid file is stale.
|
|
608
|
+
waitForPort(PROXY_PORT, PROXY_HOST, 500)
|
|
609
|
+
.then(() => {
|
|
610
|
+
// Port is already up — existing proxy is healthy, no need to spawn.
|
|
611
|
+
resolve();
|
|
612
|
+
})
|
|
613
|
+
.catch(() => {
|
|
614
|
+
// Port not reachable — spawn a new proxy daemon regardless of pid file state.
|
|
615
|
+
ensureConfigDir();
|
|
616
|
+
const logFd = openSync(LOG_FILE, "a");
|
|
617
|
+
|
|
618
|
+
const child = spawn(process.execPath, [process.argv[1], "proxy", "start", "--daemon"], {
|
|
619
|
+
detached: true,
|
|
620
|
+
stdio: ["ignore", logFd, logFd],
|
|
621
|
+
});
|
|
610
622
|
|
|
611
|
-
|
|
623
|
+
child.unref();
|
|
612
624
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
625
|
+
// Wait until the proxy port is actually accepting connections (up to 10s).
|
|
626
|
+
// Only resolve after a real HTTP response from /health — never resolve on catch.
|
|
627
|
+
waitForPort(PROXY_PORT, PROXY_HOST, 10000).then(resolve).catch(reject);
|
|
628
|
+
});
|
|
617
629
|
});
|
|
618
630
|
}
|
|
619
631
|
|