@exposify/express 0.3.9 → 0.3.10

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
@@ -173,6 +173,13 @@ signs in with your app's own login, with no code:
173
173
  `login: { path, fields, body? }` names another route; `login: false` turns
174
174
  it off. Routers introspection can't see (sub-apps) need `mounts`.
175
175
 
176
+ **No login found, and no sign-in method in the dashboard?** Then agents use
177
+ your app signed out: `/mcp` doesn't ask them to sign in, and your own checks
178
+ decide what they can do. The startup log says which it is
179
+ (`[agentready] How agents sign in: ...`), and so does the dashboard's "How
180
+ agents sign in" page. Adding a sign-in method there takes effect within a
181
+ minute.
182
+
176
183
  ## "Click Connect and log in" (OAuth)
177
184
 
178
185
  By default the MCP client has to be handed a token by hand. If your app's
package/dist/index.cjs CHANGED
@@ -31,6 +31,7 @@ __export(index_exports, {
31
31
  routesFromExpress: () => routesFromExpress
32
32
  });
33
33
  module.exports = __toCommonJS(index_exports);
34
+ var import_node_fs = require("fs");
34
35
  var import_node_path3 = require("path");
35
36
  var import_core6 = require("@exposify/core");
36
37
  var import_node2 = require("@exposify/core/node");
@@ -569,13 +570,25 @@ function aiReady(options = {}) {
569
570
  const given = options.login;
570
571
  const value = given ? { path: given.path, fields: given.fields.map((name) => ({ name, kind: (0, import_core6.fieldKind)(name) })), body: given.body ?? "json" } : findExpressLogin(app, mounts)?.login ?? null;
571
572
  loginFound = { value };
572
- if (value) {
573
- cfg.signedOutPaths = [value.path];
574
- if (logReady) console.log(`${LOG_PREFIX} AI assistants sign in with your app's own login (POST ${value.path}) at ${MCP_PATH}/login`);
575
- }
573
+ if (value) cfg.signedOutPaths = [value.path];
576
574
  }
577
575
  return loginFound.value;
578
576
  };
577
+ let stacks;
578
+ const stacksOf = () => {
579
+ if (!stacks) {
580
+ try {
581
+ stacks = (0, import_core6.authStacksOf)((0, import_core6.dependencyNames)(JSON.parse((0, import_node_fs.readFileSync)((0, import_node_path3.join)(cwd, "package.json"), "utf8")))).map((s) => s.pkg);
582
+ } catch {
583
+ stacks = [];
584
+ }
585
+ }
586
+ return stacks;
587
+ };
588
+ const report = (req, resource) => {
589
+ const login = loginOf(req.app);
590
+ return (0, import_core6.reportAppLogin)(cfg.auth, resource, login, { ...login ? { where: `POST ${login.path}` } : {}, stacks: stacksOf(), log: logReady });
591
+ };
579
592
  const appLoginFor = (req, login) => ({
580
593
  label: `POST ${login.path}`,
581
594
  fields: login.fields,
@@ -633,7 +646,9 @@ function aiReady(options = {}) {
633
646
  }
634
647
  if (brokerGate && (path === PRM_ROOT || path === `${PRM_ROOT}${MCP_PATH}`)) {
635
648
  const resource = resourceOf(req, path, MCP_PATH);
636
- void (0, import_core6.reportAppLogin)(cfg.auth, resource, loginOf(req.app)).then(() => sendWebResponse(res, brokerGate.metadataResponse(req.method, resource)));
649
+ void report(req, resource).then(
650
+ (verdict) => verdict === "open" ? void res.writeHead(404).end("not found") : sendWebResponse(res, brokerGate.metadataResponse(req.method, resource))
651
+ );
637
652
  return;
638
653
  }
639
654
  if (path !== MCP_PATH && !path.startsWith(`${MCP_PATH}/`)) return next();
@@ -649,11 +664,13 @@ function aiReady(options = {}) {
649
664
  console.warn("[agentready] oauth gate failed:", err);
650
665
  if (!res.headersSent) res.writeHead(503).end("authorization check failed");
651
666
  return null;
652
- }) : brokerGate ? (0, import_core6.reportAppLogin)(cfg.auth, resourceOf(req, path, MCP_PATH), loginOf(req.app)).then(() => brokerGate.check(headersOf(req), resourceOf(req, path, MCP_PATH))).then(async (result) => {
667
+ }) : brokerGate ? report(req, resourceOf(req, path, MCP_PATH)).then(async (verdict) => {
668
+ if (verdict === "open") return {};
669
+ const result = await brokerGate.check(headersOf(req), resourceOf(req, path, MCP_PATH));
653
670
  if (result.ok) return {};
654
671
  await sendWebResponse(res, result.response);
655
672
  return null;
656
- }, (err) => {
673
+ }).catch((err) => {
657
674
  console.warn("[agentready] broker check failed:", err);
658
675
  if (!res.headersSent) res.writeHead(503).end("authorization check failed");
659
676
  return null;
package/dist/index.js CHANGED
@@ -6,11 +6,14 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
6
6
  });
7
7
 
8
8
  // src/index.ts
9
+ import { readFileSync } from "fs";
9
10
  import { join as join3 } from "path";
10
11
  import {
12
+ authStacksOf,
11
13
  countTools,
12
14
  createBrokerGate,
13
15
  createOAuthGate,
16
+ dependencyNames,
14
17
  describeAuth,
15
18
  formatFailed,
16
19
  formatReady,
@@ -574,13 +577,25 @@ function aiReady(options = {}) {
574
577
  const given = options.login;
575
578
  const value = given ? { path: given.path, fields: given.fields.map((name) => ({ name, kind: fieldKind(name) })), body: given.body ?? "json" } : findExpressLogin(app, mounts)?.login ?? null;
576
579
  loginFound = { value };
577
- if (value) {
578
- cfg.signedOutPaths = [value.path];
579
- if (logReady) console.log(`${LOG_PREFIX} AI assistants sign in with your app's own login (POST ${value.path}) at ${MCP_PATH}/login`);
580
- }
580
+ if (value) cfg.signedOutPaths = [value.path];
581
581
  }
582
582
  return loginFound.value;
583
583
  };
584
+ let stacks;
585
+ const stacksOf = () => {
586
+ if (!stacks) {
587
+ try {
588
+ stacks = authStacksOf(dependencyNames(JSON.parse(readFileSync(join3(cwd, "package.json"), "utf8")))).map((s) => s.pkg);
589
+ } catch {
590
+ stacks = [];
591
+ }
592
+ }
593
+ return stacks;
594
+ };
595
+ const report = (req, resource) => {
596
+ const login = loginOf(req.app);
597
+ return reportAppLogin(cfg.auth, resource, login, { ...login ? { where: `POST ${login.path}` } : {}, stacks: stacksOf(), log: logReady });
598
+ };
584
599
  const appLoginFor = (req, login) => ({
585
600
  label: `POST ${login.path}`,
586
601
  fields: login.fields,
@@ -638,7 +653,9 @@ function aiReady(options = {}) {
638
653
  }
639
654
  if (brokerGate && (path === PRM_ROOT || path === `${PRM_ROOT}${MCP_PATH}`)) {
640
655
  const resource = resourceOf(req, path, MCP_PATH);
641
- void reportAppLogin(cfg.auth, resource, loginOf(req.app)).then(() => sendWebResponse(res, brokerGate.metadataResponse(req.method, resource)));
656
+ void report(req, resource).then(
657
+ (verdict) => verdict === "open" ? void res.writeHead(404).end("not found") : sendWebResponse(res, brokerGate.metadataResponse(req.method, resource))
658
+ );
642
659
  return;
643
660
  }
644
661
  if (path !== MCP_PATH && !path.startsWith(`${MCP_PATH}/`)) return next();
@@ -654,11 +671,13 @@ function aiReady(options = {}) {
654
671
  console.warn("[agentready] oauth gate failed:", err);
655
672
  if (!res.headersSent) res.writeHead(503).end("authorization check failed");
656
673
  return null;
657
- }) : brokerGate ? reportAppLogin(cfg.auth, resourceOf(req, path, MCP_PATH), loginOf(req.app)).then(() => brokerGate.check(headersOf(req), resourceOf(req, path, MCP_PATH))).then(async (result) => {
674
+ }) : brokerGate ? report(req, resourceOf(req, path, MCP_PATH)).then(async (verdict) => {
675
+ if (verdict === "open") return {};
676
+ const result = await brokerGate.check(headersOf(req), resourceOf(req, path, MCP_PATH));
658
677
  if (result.ok) return {};
659
678
  await sendWebResponse(res, result.response);
660
679
  return null;
661
- }, (err) => {
680
+ }).catch((err) => {
662
681
  console.warn("[agentready] broker check failed:", err);
663
682
  if (!res.headersSent) res.writeHead(503).end("authorization check failed");
664
683
  return null;
@@ -2077,9 +2077,21 @@ function requireHttpsBaseUrl(optionName, value) {
2077
2077
  // ../core/src/hosted-resolver.ts
2078
2078
  var DEFAULT_TIMEOUT_MS = 5e3;
2079
2079
  var REPORT_TIMEOUT_MS = 3e3;
2080
+ var REPORT_REFRESH_MS = 6e4;
2080
2081
  var DEFAULT_MAX_CACHE_MS = 3e4;
2081
2082
  var EXPIRY_SKEW_MS = 6e4;
2082
2083
  var MAX_CACHE_ENTRIES = 500;
2084
+ function signInMethodsOf(body) {
2085
+ const methods = body?.signIn?.methods;
2086
+ if (!Array.isArray(methods)) return null;
2087
+ return methods.map((m) => {
2088
+ const { kind, label } = m && typeof m === "object" ? m : {};
2089
+ return {
2090
+ kind: kind === "app-login" || kind === "oauth" || kind === "session" ? kind : "other",
2091
+ label: typeof label === "string" && label.trim() ? label.trim().slice(0, 100) : "a sign-in method"
2092
+ };
2093
+ });
2094
+ }
2083
2095
  function createHostedAuthResolver(config) {
2084
2096
  const givenKey = config.key ?? config.licenseKey;
2085
2097
  const projectId = config.projectId ?? (config.key ? parseExposifyKey(config.key)?.projectId : void 0);
@@ -2173,7 +2185,7 @@ function createHostedAuthResolver(config) {
2173
2185
  }
2174
2186
  const sessionWrites = /* @__PURE__ */ new Map();
2175
2187
  const reported = /* @__PURE__ */ new Map();
2176
- const apiUrl = config.apiUrl ?? readEnv(API_URL_ENV) ?? DEFAULT_API_URL;
2188
+ const apiUrl = requireHttpsBaseUrl("apiUrl", config.apiUrl ?? readEnv(API_URL_ENV) ?? DEFAULT_API_URL);
2177
2189
  const info = {
2178
2190
  issuer,
2179
2191
  resource: config.resource,
@@ -2222,21 +2234,34 @@ function createHostedAuthResolver(config) {
2222
2234
  },
2223
2235
  reportLogin(report) {
2224
2236
  const key = JSON.stringify(report);
2225
- let pending = reported.get(key);
2226
- if (!pending) {
2227
- pending = (async () => {
2228
- const res = await fetchImpl(`${apiUrl.replace(/\/+$/, "")}/api/v1/app/login`, {
2229
- method: "PUT",
2230
- headers: { "content-type": "application/json", authorization: `Bearer ${licenseKey}` },
2231
- body: JSON.stringify(report),
2232
- signal: AbortSignal.timeout(REPORT_TIMEOUT_MS)
2233
- });
2234
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
2235
- })();
2236
- reported.set(key, pending);
2237
+ const send2 = async () => {
2238
+ const res = await fetchImpl(`${apiUrl.replace(/\/+$/, "")}/api/v1/app/login`, {
2239
+ method: "PUT",
2240
+ headers: { "content-type": "application/json", authorization: `Bearer ${licenseKey}` },
2241
+ body: JSON.stringify(report),
2242
+ signal: AbortSignal.timeout(REPORT_TIMEOUT_MS)
2243
+ });
2244
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
2245
+ return signInMethodsOf(await res.json().catch(() => null));
2246
+ };
2247
+ const entry2 = reported.get(key);
2248
+ if (!entry2) {
2249
+ const pending = send2();
2250
+ reported.set(key, { answer: pending, at: Date.now() });
2237
2251
  pending.catch(() => reported.delete(key));
2252
+ return pending;
2253
+ }
2254
+ if (!entry2.refreshing && Date.now() - entry2.at >= REPORT_REFRESH_MS) {
2255
+ entry2.refreshing = true;
2256
+ send2().then(
2257
+ (methods) => reported.set(key, { answer: Promise.resolve(methods), at: Date.now() }),
2258
+ () => {
2259
+ entry2.at = Date.now();
2260
+ entry2.refreshing = false;
2261
+ }
2262
+ );
2238
2263
  }
2239
- return pending;
2264
+ return entry2.answer;
2240
2265
  }
2241
2266
  };
2242
2267
  Object.defineProperty(resolver, HOSTED_RESOLVER, { value: info });
@@ -6483,6 +6508,11 @@ function applyOpenApi(routes, doc) {
6483
6508
 
6484
6509
  // ../core/src/own-login.ts
6485
6510
  init_cjs_shims();
6511
+
6512
+ // ../core/src/startup.ts
6513
+ init_cjs_shims();
6514
+
6515
+ // ../core/src/own-login.ts
6486
6516
  var PASSWORD_NAME = /^(password|passwd|pass|pwd|pin|passcode|secret)$/i;
6487
6517
  var PASSWORD_PART = /(password|passwd|passcode)/i;
6488
6518
  var CONFIRM_NAME = /(confirm|repeat|again|verify|retype|new).*pass|pass.*(confirm|repeat|again|2)$/i;
@@ -2081,9 +2081,21 @@ function requireHttpsBaseUrl(optionName, value) {
2081
2081
  // ../core/src/hosted-resolver.ts
2082
2082
  var DEFAULT_TIMEOUT_MS = 5e3;
2083
2083
  var REPORT_TIMEOUT_MS = 3e3;
2084
+ var REPORT_REFRESH_MS = 6e4;
2084
2085
  var DEFAULT_MAX_CACHE_MS = 3e4;
2085
2086
  var EXPIRY_SKEW_MS = 6e4;
2086
2087
  var MAX_CACHE_ENTRIES = 500;
2088
+ function signInMethodsOf(body) {
2089
+ const methods = body?.signIn?.methods;
2090
+ if (!Array.isArray(methods)) return null;
2091
+ return methods.map((m) => {
2092
+ const { kind, label } = m && typeof m === "object" ? m : {};
2093
+ return {
2094
+ kind: kind === "app-login" || kind === "oauth" || kind === "session" ? kind : "other",
2095
+ label: typeof label === "string" && label.trim() ? label.trim().slice(0, 100) : "a sign-in method"
2096
+ };
2097
+ });
2098
+ }
2087
2099
  function createHostedAuthResolver(config) {
2088
2100
  const givenKey = config.key ?? config.licenseKey;
2089
2101
  const projectId = config.projectId ?? (config.key ? parseExposifyKey(config.key)?.projectId : void 0);
@@ -2177,7 +2189,7 @@ function createHostedAuthResolver(config) {
2177
2189
  }
2178
2190
  const sessionWrites = /* @__PURE__ */ new Map();
2179
2191
  const reported = /* @__PURE__ */ new Map();
2180
- const apiUrl = config.apiUrl ?? readEnv(API_URL_ENV) ?? DEFAULT_API_URL;
2192
+ const apiUrl = requireHttpsBaseUrl("apiUrl", config.apiUrl ?? readEnv(API_URL_ENV) ?? DEFAULT_API_URL);
2181
2193
  const info = {
2182
2194
  issuer,
2183
2195
  resource: config.resource,
@@ -2226,21 +2238,34 @@ function createHostedAuthResolver(config) {
2226
2238
  },
2227
2239
  reportLogin(report) {
2228
2240
  const key = JSON.stringify(report);
2229
- let pending = reported.get(key);
2230
- if (!pending) {
2231
- pending = (async () => {
2232
- const res = await fetchImpl(`${apiUrl.replace(/\/+$/, "")}/api/v1/app/login`, {
2233
- method: "PUT",
2234
- headers: { "content-type": "application/json", authorization: `Bearer ${licenseKey}` },
2235
- body: JSON.stringify(report),
2236
- signal: AbortSignal.timeout(REPORT_TIMEOUT_MS)
2237
- });
2238
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
2239
- })();
2240
- reported.set(key, pending);
2241
+ const send2 = async () => {
2242
+ const res = await fetchImpl(`${apiUrl.replace(/\/+$/, "")}/api/v1/app/login`, {
2243
+ method: "PUT",
2244
+ headers: { "content-type": "application/json", authorization: `Bearer ${licenseKey}` },
2245
+ body: JSON.stringify(report),
2246
+ signal: AbortSignal.timeout(REPORT_TIMEOUT_MS)
2247
+ });
2248
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
2249
+ return signInMethodsOf(await res.json().catch(() => null));
2250
+ };
2251
+ const entry2 = reported.get(key);
2252
+ if (!entry2) {
2253
+ const pending = send2();
2254
+ reported.set(key, { answer: pending, at: Date.now() });
2241
2255
  pending.catch(() => reported.delete(key));
2256
+ return pending;
2257
+ }
2258
+ if (!entry2.refreshing && Date.now() - entry2.at >= REPORT_REFRESH_MS) {
2259
+ entry2.refreshing = true;
2260
+ send2().then(
2261
+ (methods) => reported.set(key, { answer: Promise.resolve(methods), at: Date.now() }),
2262
+ () => {
2263
+ entry2.at = Date.now();
2264
+ entry2.refreshing = false;
2265
+ }
2266
+ );
2242
2267
  }
2243
- return pending;
2268
+ return entry2.answer;
2244
2269
  }
2245
2270
  };
2246
2271
  Object.defineProperty(resolver, HOSTED_RESOLVER, { value: info });
@@ -6487,6 +6512,11 @@ function applyOpenApi(routes, doc) {
6487
6512
 
6488
6513
  // ../core/src/own-login.ts
6489
6514
  init_esm_shims();
6515
+
6516
+ // ../core/src/startup.ts
6517
+ init_esm_shims();
6518
+
6519
+ // ../core/src/own-login.ts
6490
6520
  var PASSWORD_NAME = /^(password|passwd|pass|pwd|pin|passcode|secret)$/i;
6491
6521
  var PASSWORD_PART = /(password|passwd|passcode)/i;
6492
6522
  var CONFIRM_NAME = /(confirm|repeat|again|verify|retype|new).*pass|pass.*(confirm|repeat|again|2)$/i;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exposify/express",
3
- "version": "0.3.9",
3
+ "version": "0.3.10",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -26,8 +26,8 @@
26
26
  "prepack": "yarn build"
27
27
  },
28
28
  "dependencies": {
29
- "@exposify/cli": "0.0.1",
30
- "@exposify/core": "0.3.13"
29
+ "@exposify/cli": "0.0.2",
30
+ "@exposify/core": "0.3.14"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "express": ">=4 <6"