@agent-native/core 0.22.12 → 0.22.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.
Files changed (38) hide show
  1. package/dist/agent/engine/builder-engine.d.ts +1 -1
  2. package/dist/agent/model-config.d.ts +6 -6
  3. package/dist/agent/model-config.js +3 -3
  4. package/dist/agent/model-config.js.map +1 -1
  5. package/dist/chat-threads/store.d.ts.map +1 -1
  6. package/dist/chat-threads/store.js +6 -2
  7. package/dist/chat-threads/store.js.map +1 -1
  8. package/dist/db/client.d.ts +1 -0
  9. package/dist/db/client.d.ts.map +1 -1
  10. package/dist/db/client.js +33 -17
  11. package/dist/db/client.js.map +1 -1
  12. package/dist/db/create-get-db.d.ts.map +1 -1
  13. package/dist/db/create-get-db.js +2 -8
  14. package/dist/db/create-get-db.js.map +1 -1
  15. package/dist/mcp/build-server.d.ts +2 -0
  16. package/dist/mcp/build-server.d.ts.map +1 -1
  17. package/dist/mcp/build-server.js +59 -3
  18. package/dist/mcp/build-server.js.map +1 -1
  19. package/dist/mcp/builtin-tools.d.ts.map +1 -1
  20. package/dist/mcp/builtin-tools.js +2 -4
  21. package/dist/mcp/builtin-tools.js.map +1 -1
  22. package/dist/mcp/connect-route.d.ts.map +1 -1
  23. package/dist/mcp/connect-route.js +14 -0
  24. package/dist/mcp/connect-route.js.map +1 -1
  25. package/dist/mcp/server.d.ts.map +1 -1
  26. package/dist/mcp/server.js +16 -1
  27. package/dist/mcp/server.js.map +1 -1
  28. package/dist/server/better-auth-instance.d.ts.map +1 -1
  29. package/dist/server/better-auth-instance.js +2 -1
  30. package/dist/server/better-auth-instance.js.map +1 -1
  31. package/dist/server/embed-session.d.ts.map +1 -1
  32. package/dist/server/embed-session.js +55 -2
  33. package/dist/server/embed-session.js.map +1 -1
  34. package/dist/vite/client.d.ts +2 -0
  35. package/dist/vite/client.d.ts.map +1 -1
  36. package/dist/vite/client.js +5 -0
  37. package/dist/vite/client.js.map +1 -1
  38. package/package.json +1 -1
@@ -58,6 +58,19 @@ function deriveOrigin(event) {
58
58
  (host && /^(localhost|127\.0\.0\.1)(:|$)/.test(host) ? "http" : "https");
59
59
  return host ? `${proto}://${host}` : "";
60
60
  }
61
+ function isLoopbackOrigin(origin) {
62
+ try {
63
+ const hostname = new URL(origin).hostname;
64
+ return (hostname === "localhost" ||
65
+ hostname === "127.0.0.1" ||
66
+ hostname === "::1" ||
67
+ hostname === "[::1]" ||
68
+ hostname.startsWith("127."));
69
+ }
70
+ catch {
71
+ return false;
72
+ }
73
+ }
61
74
  function normalizeBasePath(raw) {
62
75
  const trimmed = (raw ?? "").trim();
63
76
  if (!trimmed || trimmed === "/")
@@ -96,6 +109,7 @@ function canUseDevOpenConnect(event) {
96
109
  // misconfigured public deploy with no secret thus can't unlock dev-open
97
110
  // by spoofing `Host: localhost`.
98
111
  return (isLoopbackRequest(event) &&
112
+ isLoopbackOrigin(deriveOrigin(event)) &&
99
113
  !process.env.A2A_SECRET?.trim() &&
100
114
  !process.env.ACCESS_TOKEN?.trim() &&
101
115
  !process.env.ACCESS_TOKENS?.trim());
@@ -1 +1 @@
1
- {"version":3,"file":"connect-route.js","sourceRoot":"","sources":["../../src/mcp/connect-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,EACL,UAAU,EACV,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACL,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,sBAAsB,EACtB,oBAAoB,EACpB,qBAAqB,EACrB,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,EACtB,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAE5B,gDAAgD;AAChD,MAAM,sBAAsB,GAAG,CAAC,CAAC;AAEjC,6DAA6D;AAC7D,MAAM,YAAY,GAAG,2BAA2B,CAAC;AASjD,SAAS,IAAI,CAAC,IAAa,EAAE,MAAM,GAAG,GAAG;IACvC,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE;QACxC,MAAM;QACN,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;KAChD,CAAC,CAAC;AACL,CAAC;AAED,SAAS,IAAI,CAAC,IAAY,EAAE,MAAM,GAAG,GAAG;IACtC,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE;QACxB,MAAM;QACN,OAAO,EAAE,EAAE,cAAc,EAAE,0BAA0B,EAAE;KACxD,CAAC,CAAC;AACL,CAAC;AAED;8EAC8E;AAC9E,SAAS,YAAY,CAAC,KAAc;IAClC,MAAM,cAAc,GAAG,SAAS,CAAC,KAAK,EAAE,mBAAmB,CAAC,CAAC;IAC7D,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,EAAE,kBAAkB,CAAC,IAAI,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC9E,MAAM,KAAK,GACT,cAAc,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE;QACrC,CAAC,IAAI,IAAI,gCAAgC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAC3E,OAAO,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AAC1C,CAAC;AAED,SAAS,iBAAiB,CAAC,GAAuB;IAChD,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACnC,IAAI,CAAC,OAAO,IAAI,OAAO,KAAK,GAAG;QAAE,OAAO,EAAE,CAAC;IAC3C,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,CAAC;IACpE,OAAO,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACvC,CAAC;AAED,SAAS,kBAAkB;IACzB,OAAO,iBAAiB,CACtB,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAC5D,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,QAAgB,EAAE,IAAY;IACjD,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3B,IAAI,IAAI,KAAK,GAAG;QAAE,OAAO,QAAQ,CAAC;IAClC,OAAO,GAAG,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;AAClE,CAAC;AAED,SAAS,QAAQ,CAAC,MAAc,EAAE,OAA+B;IAC/D,IAAI,OAAO,CAAC,KAAK;QAAE,OAAO,OAAO,CAAC,KAAK,CAAC;IACxC,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC;QACnC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC,OAAO,IAAI,KAAK,CAAC;IAClC,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,MAAc,EAAE,OAA+B;IACjE,OAAO,gBAAgB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;AACrD,CAAC;AAED,SAAS,oBAAoB,CAAC,KAAc;IAC1C,qEAAqE;IACrE,wEAAwE;IACxE,iEAAiE;IACjE,wEAAwE;IACxE,iCAAiC;IACjC,OAAO,CACL,iBAAiB,CAAC,KAAK,CAAC;QACxB,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,EAAE;QAC/B,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE;QACjC,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,EAAE,CACnC,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,CAAC;SACL,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7B,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,gBAAgB,CAC7B,KAAyB;IAEzB,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,IAAI,CAAC;QACH,OAAO,CAAC,MAAM,YAAY,CAAC,KAAK,CAAC,CAAC,IAAI,SAAS,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,KAAc;IAClC,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACxB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAAE,OAAO,sBAAsB,CAAC;IACvD,OAAO,IAAI,CAAC,GAAG,CACb,kBAAkB,EAClB,IAAI,CAAC,GAAG,CAAC,kBAAkB,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAC5C,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,gBAAgB,CAAC,MAK/B;IACC,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvD,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;IACzB,0EAA0E;IAC1E,2EAA2E;IAC3E,yEAAyE;IACzE,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE;QACnE,kBAAkB,EAAE,IAAI;QACxB,SAAS,EAAE,GAAG,MAAM,CAAC,OAAO,GAAG;QAC/B,WAAW,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,iBAAiB,EAAE;KAC/C,CAAC,CAAC;IACH,MAAM,iBAAiB,CAAC;QACtB,GAAG;QACH,UAAU,EAAE,MAAM,CAAC,KAAK;QACxB,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,IAAI;QAC3B,KAAK,EAAE,MAAM,CAAC,KAAK;KACpB,CAAC,CAAC;IACH,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;AACxB,CAAC;AAED,SAAS,gBAAgB,CACvB,MAAc,EACd,OAA+B,EAC/B,IAA6C;IAE7C,MAAM,MAAM,GAAG,GAAG,MAAM,oBAAoB,CAAC;IAC7C,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,IAAI,IAAI,CAAC,KAAK;QAAE,OAAO,CAAC,aAAa,GAAG,UAAU,IAAI,CAAC,KAAK,EAAE,CAAC;IAC/D,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;QACnC,OAAO,CAAC,4BAA4B,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC;IAC1D,CAAC;IACD,OAAO;QACL,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE;QACvB,MAAM;QACN,UAAU,EAAE,IAAI;QAChB,cAAc,EAAE;YACd,IAAI,EAAE,MAAe;YACrB,GAAG,EAAE,MAAM;YACX,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACpD;QACD,GAAG,EAAE,wBAAwB,MAAM,EAAE;KACtC,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,6CAA6C;AAC7C,8EAA8E;AAE9E,SAAS,kBAAkB,CAAC,SAAiB,EAAE,UAAkB;IAC/D,OAAO,eAAe,SAAS;;yEAEwC,UAAU;;0BAEzD,UAAU;;;;;OAK7B,CAAC;AACR,CAAC;AAED,SAAS,iBAAiB,CAAC,MAO1B;IACC,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GACnE,MAAM,CAAC;IACT,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IACpC,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;IACpC,MAAM,MAAM,GAAG,GAAG,MAAM,oBAAoB,CAAC;IAC7C,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,YAAY,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC1C,MAAM,iBAAiB,GAAG,UAAU,CAClC,mCAAmC,QAAQ,IAAI,MAAM,EAAE,CACxD,CAAC;IACF,MAAM,YAAY,GAAG,UAAU,CAAC,kCAAkC,MAAM,EAAE,CAAC,CAAC;IAC5E,MAAM,iBAAiB,GAAG,UAAU,CAClC,8BAA8B,QAAQ,8CAA8C,MAAM,kBAAkB,CAC7G,CAAC;IACF,MAAM,YAAY,GAAG,kBAAkB,CACrC,YAAY,EACZ,qCAAqC,CACtC,CAAC;IACF,MAAM,WAAW,GAAG,kBAAkB,CACpC,WAAW,EACX,oCAAoC,CACrC,CAAC;IACF,MAAM,YAAY,GAChB,QAAQ,IAAI,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACtE,OAAO;;;;;iBAKQ,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA+ShB,YAAY;;;mCAGe,OAAO,KAAK,OAAO;;;;;4EAKsB,OAAO;;UAEzE,WAAW;;;;;;;;;UASX,YAAY,CAAC,CAAC,CAAC,aAAa,OAAO,sBAAsB,CAAC,CAAC,CAAC,OAAO,OAAO,yBAAyB;;mCAE1E,SAAS;;;;8CAIE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ;;6CAE7B,YAAY;;;;;;+BAM1B,UAAU;;;;;;;;;;;;;;;;;;;uDAmBc,OAAO;;;;;;;;;kHASoD,OAAO;;;;;;;;;;;;;;;;gCAgBzF,iBAAiB;;uFAEsC,YAAY;;;;2BAIxE,YAAY;;;;;;gCAMP,iBAAiB;;;;;uEAKsB,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;;wCAE1D,YAAY,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,yBAAyB;wCAClE,YAAY,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,kCAAkC;;;;;;oDAM5D,YAAY,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,yBAAyB;;;;;;;;;;;;;uEAa1C,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAkC9E,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,eAAe,EAAE,4BAA4B,CAAC,CAAC;oBACrE,IAAI,CAAC,SAAS,CAAC,YAAY,IAAI,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAgNhD,CAAC;AACT,CAAC;AAED,8EAA8E;AAC9E,2EAA2E;AAC3E,8EAA8E;AAE9E;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,KAAc,EACd,OAAe,EACf,UAAkC,EAAE;IAEpC,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IAChC,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IACnC,MAAM,QAAQ,GAAG,kBAAkB,EAAE,CAAC;IACtC,MAAM,MAAM,GAAG,GAAG,MAAM,GAAG,QAAQ,EAAE,CAAC;IACtC,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CACzE,MAAM,EACN,EAAE,CACH,CAAC;IAEF,yEAAyE;IACzE,IAAI,GAAG,KAAK,EAAE,EAAE,CAAC;QACf,IAAI,MAAM,KAAK,KAAK,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YAC1C,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,EAAE,GAAG,CAAC,CAAC;QACpD,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;YACpB,oEAAoE;YACpE,iEAAiE;YACjE,MAAM,SAAS,GAAG,sBAAsB,CAAC,KAAK,CAAC,CAAC;YAChD,IAAI,SAAS;gBAAE,OAAO,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;YAC3C,8DAA8D;YAC9D,OAAO,IAAI,CACT,iBAAiB,CAAC;gBAChB,eAAe,EAAE,QAAQ;gBACzB,KAAK,EAAE,sBAAsB;gBAC7B,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;gBACrD,MAAM;gBACN,QAAQ,EAAE,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC;gBACrC,QAAQ,EAAE,IAAI;aACf,CAAC,CACH,CAAC;QACJ,CAAC;QACD,IAAI,QAAQ,GAAkB,IAAI,CAAC;QACnC,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,IAAI,GAAG,CACf,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,KAAK,CAAC,IAAI,IAAI,GAAG,EACzC,mBAAmB,CACpB,CAAC;YACF,MAAM,GAAG,GAAG,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;YAC5C,IAAI,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC;gBAAE,QAAQ,GAAG,GAAG,CAAC;QACpD,CAAC;QAAC,MAAM,CAAC;YACP,QAAQ,GAAG,IAAI,CAAC;QAClB,CAAC;QACD,OAAO,IAAI,CACT,iBAAiB,CAAC;YAChB,eAAe,EAAE,QAAQ;YACzB,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;YACrD,MAAM;YACN,QAAQ,EAAE,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC;YACrC,QAAQ;SACT,CAAC,CACH,CAAC;IACJ,CAAC;IAED,yEAAyE;IACzE,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;QACrB,IAAI,MAAM,KAAK,MAAM;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,EAAE,GAAG,CAAC,CAAC;QACzE,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,OAAO,EAAE,KAAK;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,GAAG,CAAC,CAAC;QACjE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC;YAC5B,IAAI,oBAAoB,CAAC,KAAK,CAAC,EAAE,CAAC;gBAChC,OAAO,IAAI,CACT,gBAAgB,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,UAAU,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CACjE,CAAC;YACJ,CAAC;YACD,OAAO,IAAI,CACT;gBACE,KAAK,EACH,mFAAmF;aACtF,EACD,GAAG,CACJ,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAG5D,CAAC;QACF,MAAM,KAAK,GACT,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;YACjD,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;YACjC,CAAC,CAAC,IAAI,CAAC;QACX,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC3C,IAAI,CAAC;YACH,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,gBAAgB,CAAC;gBACvC,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,KAAK;gBACL,OAAO;aACR,CAAC,CAAC;YACH,OAAO,IAAI,CAAC,gBAAgB,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;QAC5D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,uBAAuB,EAAE,EAAE,GAAG,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,IAAI,GAAG,KAAK,eAAe,EAAE,CAAC;QAC5B,IAAI,MAAM,KAAK,MAAM;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,EAAE,GAAG,CAAC,CAAC;QACzE,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,gBAAgB,EAAE,CAAC;YACrC,MAAM,eAAe,GAAG,GAAG,MAAM,4BAA4B,CAAC;YAC9D,OAAO,IAAI,CAAC;gBACV,WAAW,EAAE,GAAG,CAAC,UAAU;gBAC3B,SAAS,EAAE,GAAG,CAAC,QAAQ;gBACvB,gBAAgB,EAAE,eAAe;gBACjC,yBAAyB,EAAE,GAAG,eAAe,cAAc,GAAG,CAAC,QAAQ,EAAE;gBACzE,QAAQ,EAAE,sBAAsB;gBAChC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,kBAAkB,GAAG,IAAI,CAAC;aAClD,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,IAAI,GAAG,EAAE,OAAO,KAAK,cAAc,EAAE,CAAC;gBACpC,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,kCAAkC,EAAE,EAAE,GAAG,CAAC,CAAC;YAClE,CAAC;YACD,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,8BAA8B,EAAE,EAAE,GAAG,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,IAAI,GAAG,KAAK,mBAAmB,EAAE,CAAC;QAChC,IAAI,MAAM,KAAK,MAAM;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,EAAE,GAAG,CAAC,CAAC;QACzE,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,OAAO,EAAE,KAAK;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,GAAG,CAAC,CAAC;QACjE,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAE5D,CAAC;QACF,MAAM,QAAQ,GACZ,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAClE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YACjC,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,EAAE,GAAG,CAAC,CAAC;QACpD,CAAC;QACD,MAAM,KAAK,GACT,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE;YACvD,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE;YACtB,CAAC,CAAC,IAAI,CAAC;QACX,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,QAAQ,EAAE,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACvE,IAAI,MAAM,KAAK,WAAW,EAAE,CAAC;YAC3B,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,sBAAsB,EAAE,EAAE,GAAG,CAAC,CAAC;QACtD,CAAC;QACD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,+BAA+B,EAAE,EAAE,GAAG,CAAC,CAAC;QAC/D,CAAC;QACD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,oCAAoC,EAAE,EAAE,GAAG,CAAC,CAAC;QACpE,CAAC;QACD,OAAO,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;IACtC,CAAC;IAED,yEAAyE;IACzE,IAAI,GAAG,KAAK,cAAc,EAAE,CAAC;QAC3B,IAAI,MAAM,KAAK,MAAM;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,EAAE,GAAG,CAAC,CAAC;QACzE,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAE5D,CAAC;QACF,MAAM,UAAU,GACd,OAAO,IAAI,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/D,IAAI,CAAC,UAAU;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,sBAAsB,EAAE,EAAE,GAAG,CAAC,CAAC;QACrE,MAAM,GAAG,GAAG,MAAM,aAAa,CAAC,UAAU,CAAC,CAAC;QAC5C,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,GAAG,CAAC,CAAC;QACpD,IAAI,GAAG,CAAC,MAAM,KAAK,UAAU;YAAE,OAAO,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;QACnE,IACE,GAAG,CAAC,MAAM,KAAK,SAAS;YACxB,CAAC,GAAG,CAAC,SAAS,IAAI,IAAI,IAAI,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,EACrD,CAAC;YACD,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS;gBAAE,KAAK,gBAAgB,CAAC,UAAU,CAAC,CAAC;YAChE,OAAO,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QACrC,CAAC;QACD,IACE,GAAG,CAAC,MAAM,KAAK,SAAS;YACxB,GAAG,CAAC,MAAM,KAAK,SAAS;YACxB,CAAC,GAAG,CAAC,UAAU,EACf,CAAC;YACD,OAAO,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QACrC,CAAC;QACD,iEAAiE;QACjE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC;YAC5B,IAAI,oBAAoB,CAAC,KAAK,CAAC,EAAE,CAAC;gBAChC,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CACtC,UAAU,EACV,YAAY,UAAU,EAAE,EAAE,CAC3B,CAAC;gBACF,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACd,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,UAAU,CAAC,CAAC;oBAC9C,IAAI,KAAK,EAAE,MAAM,KAAK,UAAU;wBAAE,OAAO,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;oBACtE,OAAO,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;gBACrC,CAAC;gBACD,OAAO,IAAI,CAAC;oBACV,MAAM,EAAE,UAAU;oBAClB,GAAG,gBAAgB,CAAC,MAAM,EAAE,OAAO,EAAE;wBACnC,UAAU,EAAE,GAAG,CAAC,UAAU;qBAC3B,CAAC;iBACH,CAAC,CAAC;YACL,CAAC;YACD,OAAO,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,2BAA2B,EAAE,EAAE,GAAG,CAAC,CAAC;QAC5E,CAAC;QACD,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;YACzB,wEAAwE;YACxE,8DAA8D;YAC9D,MAAM,OAAO,GAAG,MAAM,sBAAsB,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;YAC9D,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,UAAU,CAAC,CAAC;gBAC9C,IAAI,KAAK,EAAE,MAAM,KAAK,UAAU;oBAAE,OAAO,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;gBACtE,OAAO,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;YACrC,CAAC;YACD,IAAI,KAAa,CAAC;YAClB,IAAI,CAAC;gBACH,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC,OAAO,CAAC,KAAK,IAAI,SAAS,CAAC,CAAC;gBACrE,KAAK,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,UAAW,EAAE,SAAS,EAAE,SAAS,EAAE;oBACpE,kBAAkB,EAAE,IAAI;oBACxB,SAAS,EAAE,GAAG,sBAAsB,GAAG;oBACvC,WAAW,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,iBAAiB,EAAE;iBAC/C,CAAC,CAAC;gBACH,MAAM,iBAAiB,CAAC;oBACtB,GAAG;oBACH,UAAU,EAAE,OAAO,CAAC,UAAW;oBAC/B,KAAK,EAAE,OAAO,CAAC,KAAK;oBACpB,KAAK,EAAE,mBAAmB;iBAC3B,CAAC,CAAC;gBACH,IAAI,CAAC,CAAC,MAAM,oBAAoB,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;oBACnD,OAAO,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;gBACrC,CAAC;YACH,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,qBAAqB,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;gBAC7C,MAAM,GAAG,CAAC;YACZ,CAAC;YACD,OAAO,IAAI,CAAC;gBACV,MAAM,EAAE,UAAU;gBAClB,GAAG,gBAAgB,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC;aAChD,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,uBAAuB,EAAE,EAAE,GAAG,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,IAAI,MAAM,KAAK,KAAK;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,EAAE,GAAG,CAAC,CAAC;QACxE,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,OAAO,EAAE,KAAK;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,GAAG,CAAC,CAAC;QACjE,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC7C,OAAO,IAAI,CAAC;YACV,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACvB,EAAE,EAAE,CAAC,CAAC,EAAE;gBACR,KAAK,EAAE,CAAC,CAAC,KAAK;gBACd,SAAS,EAAE,CAAC,CAAC,SAAS;gBACtB,UAAU,EAAE,CAAC,CAAC,UAAU;gBACxB,SAAS,EAAE,CAAC,CAAC,SAAS;aACvB,CAAC,CAAC;SACJ,CAAC,CAAC;IACL,CAAC;IAED,yEAAyE;IACzE,IAAI,GAAG,KAAK,gBAAgB,EAAE,CAAC;QAC7B,IAAI,MAAM,KAAK,MAAM;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,EAAE,GAAG,CAAC,CAAC;QACzE,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,OAAO,EAAE,KAAK;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,GAAG,CAAC,CAAC;QACjE,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAE5D,CAAC;QACF,MAAM,EAAE,GAAG,OAAO,IAAI,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtD,IAAI,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE,GAAG,CAAC,CAAC;QACpD,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACrD,OAAO,IAAI,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,GAAG,CAAC,CAAC;AAC3C,CAAC","sourcesContent":["/**\n * `/_agent-native/mcp/connect` — frictionless external-agent connection.\n *\n * A logged-in user on a deployed agent-native app (e.g. mail.agent-native.com)\n * mints a per-user, scoped, revocable MCP bearer token WITHOUT ever copying a\n * shared deployment secret. Two surfaces:\n *\n * 1. Browser — `GET /connect` renders a minimal in-app page (same inline\n * HTML approach as the auth pages). The Authorize button POSTs to\n * `/connect/token`, then shows the ready-to-paste `.mcp.json` entry, the\n * `agent-native connect <origin>` one-liner, and the user's existing\n * tokens with Revoke buttons.\n * 2. CLI — an OAuth-2.0-device-authorization-style flow:\n * POST /connect/device/start (unauth) → device_code + user_code\n * GET /connect?user_code=… (browser) → user signs in & approves\n * POST /connect/device/authorize (session) → binds user to the code\n * POST /connect/device/poll (unauth) → mints + returns the token\n *\n * The minted token reuses the existing A2A signer (`signA2AToken`) — no new\n * crypto. We only add a random `jti` + `scope: \"mcp-connect\"` claim so it can\n * be revoked. `verifyAuth` already verifies A2A_SECRET JWTs and extracts\n * `sub`/`org_domain`, so a minted token works against `/_agent-native/mcp`\n * with no verify changes for the happy path (the revoke check is the only\n * addition there).\n *\n * Node-only (crypto + the A2A signer), bundled alongside the other framework\n * routes. Dialect-agnostic SQL lives in `connect-store.ts`.\n */\n\nimport type { H3Event } from \"h3\";\nimport { getMethod, getHeader } from \"h3\";\nimport { readBody } from \"../server/h3-helpers.js\";\nimport {\n getSession,\n getConfiguredLoginHtml,\n isLoopbackRequest,\n} from \"../server/auth.js\";\nimport { signA2AToken } from \"../a2a/client.js\";\nimport { getOrgDomain } from \"../org/context.js\";\nimport { randomUUID } from \"node:crypto\";\nimport {\n recordMintedToken,\n listTokens,\n revokeToken,\n createDeviceCode,\n getDeviceCode,\n approveDeviceCode,\n consumeDeviceCode,\n claimDeviceCodeForMint,\n finishDeviceCodeMint,\n releaseDeviceCodeMint,\n expireDeviceCode,\n MCP_CONNECT_SCOPE,\n DEFAULT_TOKEN_TTL_DAYS,\n MIN_TOKEN_TTL_DAYS,\n MAX_TOKEN_TTL_DAYS,\n DEVICE_CODE_TTL_MS,\n} from \"./connect-store.js\";\n\n/** Device-flow poll interval hint (seconds). */\nconst DEVICE_POLL_INTERVAL_S = 3;\n\n// Human-typable user code: 8 base32 chars, dashed XXXX-XXXX.\nconst USER_CODE_RE = /^[A-Z2-7]{4}-[A-Z2-7]{4}$/;\n\nexport interface McpConnectRouteOptions {\n /** App id (directory under apps/, e.g. `mail`). Used for the server name. */\n appId?: string;\n /** Human app name shown on the connect page. */\n appName?: string;\n}\n\nfunction json(body: unknown, status = 200): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\nfunction html(body: string, status = 200): Response {\n return new Response(body, {\n status,\n headers: { \"Content-Type\": \"text/html; charset=utf-8\" },\n });\n}\n\n/** Derive the running app's origin from request headers (same logic mountMCP\n * uses) — `https` in prod / for non-loopback hosts, `http` for localhost. */\nfunction deriveOrigin(event: H3Event): string {\n const forwardedProto = getHeader(event, \"x-forwarded-proto\");\n const host = getHeader(event, \"x-forwarded-host\") || getHeader(event, \"host\");\n const proto =\n forwardedProto?.split(\",\")[0]?.trim() ||\n (host && /^(localhost|127\\.0\\.0\\.1)(:|$)/.test(host) ? \"http\" : \"https\");\n return host ? `${proto}://${host}` : \"\";\n}\n\nfunction normalizeBasePath(raw: string | undefined): string {\n const trimmed = (raw ?? \"\").trim();\n if (!trimmed || trimmed === \"/\") return \"\";\n const withSlash = trimmed.startsWith(\"/\") ? trimmed : `/${trimmed}`;\n return withSlash.replace(/\\/+$/, \"\");\n}\n\nfunction configuredBasePath(): string {\n return normalizeBasePath(\n process.env.APP_BASE_PATH || process.env.VITE_APP_BASE_PATH,\n );\n}\n\nfunction joinAppPath(basePath: string, path: string): string {\n if (!basePath) return path;\n if (path === \"/\") return basePath;\n return `${basePath}${path.startsWith(\"/\") ? path : `/${path}`}`;\n}\n\nfunction appLabel(origin: string, options: McpConnectRouteOptions): string {\n if (options.appId) return options.appId;\n try {\n const h = new URL(origin).hostname;\n return h.split(\".\")[0] || h;\n } catch {\n return options.appName || \"app\";\n }\n}\n\nfunction serverName(origin: string, options: McpConnectRouteOptions): string {\n return `agent-native-${appLabel(origin, options)}`;\n}\n\nfunction canUseDevOpenConnect(event: H3Event): boolean {\n // Loopback determined from the real socket peer (isLoopbackRequest →\n // getRequestIP without xForwardedFor), NOT a parsed `Host` header — the\n // header is client-controlled, and it also handles IPv6 `::1`. A\n // misconfigured public deploy with no secret thus can't unlock dev-open\n // by spoofing `Host: localhost`.\n return (\n isLoopbackRequest(event) &&\n !process.env.A2A_SECRET?.trim() &&\n !process.env.ACCESS_TOKEN?.trim() &&\n !process.env.ACCESS_TOKENS?.trim()\n );\n}\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n\n/**\n * Resolve the org domain for a session. Used as the JWT `org_domain` claim so\n * the receiving MCP endpoint can map it back to an org id (same as A2A). Best\n * effort — a missing org just yields a user-scoped (no-org) token.\n */\nasync function resolveOrgDomain(\n orgId: string | undefined,\n): Promise<string | undefined> {\n if (!orgId) return undefined;\n try {\n return (await getOrgDomain(orgId)) ?? undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction clampTtlDays(input: unknown): number {\n const n = Number(input);\n if (!Number.isFinite(n)) return DEFAULT_TOKEN_TTL_DAYS;\n return Math.min(\n MAX_TOKEN_TTL_DAYS,\n Math.max(MIN_TOKEN_TTL_DAYS, Math.floor(n)),\n );\n}\n\n/**\n * Mint a connect-scoped JWT and record it. The JWT is signed by the existing\n * A2A signer (HS256 over A2A_SECRET); we add a random `jti` and\n * `scope: \"mcp-connect\"` so the token is individually revocable. The token\n * value is returned to the caller exactly once and never persisted.\n */\nasync function mintConnectToken(params: {\n email: string;\n orgId: string | undefined;\n label: string | null;\n ttlDays: number;\n}): Promise<{ token: string; jti: string }> {\n const orgDomain = await resolveOrgDomain(params.orgId);\n const jti = randomUUID();\n // signA2AToken signs { sub: email, org_domain? } over A2A_SECRET (global)\n // or the org secret. We extend its claims via the standard jose builder by\n // re-using the same signer with extra claims threaded through `options`.\n const token = await signA2AToken(params.email, orgDomain, undefined, {\n preferGlobalSecret: true,\n expiresIn: `${params.ttlDays}d`,\n extraClaims: { jti, scope: MCP_CONNECT_SCOPE },\n });\n await recordMintedToken({\n jti,\n ownerEmail: params.email,\n orgId: params.orgId ?? null,\n label: params.label,\n });\n return { token, jti };\n}\n\nfunction mcpResultPayload(\n appUrl: string,\n options: McpConnectRouteOptions,\n auth: { token?: string; ownerEmail?: string },\n) {\n const mcpUrl = `${appUrl}/_agent-native/mcp`;\n const name = serverName(appUrl, options);\n const headers: Record<string, string> = {};\n if (auth.token) headers.Authorization = `Bearer ${auth.token}`;\n if (!auth.token && auth.ownerEmail) {\n headers[\"X-Agent-Native-Owner-Email\"] = auth.ownerEmail;\n }\n return {\n token: auth.token ?? \"\",\n mcpUrl,\n serverName: name,\n mcpServerEntry: {\n type: \"http\" as const,\n url: mcpUrl,\n ...(Object.keys(headers).length ? { headers } : {}),\n },\n cli: `agent-native connect ${appUrl}`,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Connect page (server-rendered HTML string)\n// ---------------------------------------------------------------------------\n\nfunction agentNativeMarkSvg(className: string, gradientId: string): string {\n return `<svg class=\"${className}\" width=\"114\" height=\"66\" viewBox=\"0 0 114 66\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\">\n <path d=\"M24.5537 65.7695H0L15.0859 39.4619L37.708 0L60.4912 39.4619H39.6396L24.5537 65.7695Z\" fill=\"white\"/>\n <path d=\"M89.446 0H114L76.2921 65.7704H51.7383L89.446 0Z\" fill=\"url(#${gradientId})\"/>\n <defs>\n <linearGradient id=\"${gradientId}\" x1=\"101.702\" y1=\"67.4791\" x2=\"113.672\" y2=\"-37.4275\" gradientUnits=\"userSpaceOnUse\">\n <stop stop-color=\"#00B5FF\"/>\n <stop offset=\"1\" stop-color=\"#48FFE4\"/>\n </linearGradient>\n </defs>\n</svg>`;\n}\n\nfunction renderConnectPage(params: {\n connectBasePath: string;\n email: string;\n appName: string;\n appUrl: string;\n serverId: string;\n userCode: string | null;\n}): string {\n const { connectBasePath, email, appName, appUrl, serverId, userCode } =\n params;\n const safeEmail = escapeHtml(email);\n const safeApp = escapeHtml(appName);\n const mcpUrl = `${appUrl}/_agent-native/mcp`;\n const safeMcpUrl = escapeHtml(mcpUrl);\n const safeServerId = escapeHtml(serverId);\n const safeClaudeCodeCmd = escapeHtml(\n `claude mcp add --transport http ${serverId} ${mcpUrl}`,\n );\n const safeCodexCmd = escapeHtml(`npx @agent-native/core connect ${appUrl}`);\n const safeGenericConfig = escapeHtml(\n `{\\n \"mcpServers\": {\\n \"${serverId}\": {\\n \"type\": \"http\",\\n \"url\": \"${mcpUrl}\"\\n }\\n }\\n}`,\n );\n const brandMarkSvg = agentNativeMarkSvg(\n \"brand-mark\",\n \"agent-native-connect-brand-gradient\",\n );\n const flowMarkSvg = agentNativeMarkSvg(\n \"flow-mark\",\n \"agent-native-connect-flow-gradient\",\n );\n const safeUserCode =\n userCode && USER_CODE_RE.test(userCode) ? escapeHtml(userCode) : \"\";\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Connect ${safeApp}</title>\n<style>\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n :root {\n color-scheme: dark;\n --bg: #09090b; --panel: #121214; --panel-2: #0c0c0e;\n --panel-soft: rgba(255,255,255,0.025);\n --border: rgba(255,255,255,0.075); --border-strong: rgba(255,255,255,0.14);\n --text: #f7f7f8; --muted: #a1a1aa; --subtle: #74747d;\n --accent: #f4f4f5; --accent-fg: #09090b;\n --ring: rgba(250,250,250,0.55);\n --error: #fca5a5; --error-bg: rgba(127,29,29,0.18);\n --ok: #86efac; --ok-bg: rgba(20,83,45,0.12); --ok-border: rgba(134,239,172,0.18);\n }\n html, body { -webkit-font-smoothing: antialiased; }\n body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n background: linear-gradient(180deg, #101013 0%, var(--bg) 58%);\n color: var(--text); display: flex; align-items: center;\n justify-content: center; min-height: 100vh; padding: 1.5rem 1rem;\n }\n .card {\n width: 100%; max-width: 440px;\n background: var(--panel); border: 1px solid var(--border);\n border-radius: 8px; box-shadow: 0 1px 0 rgba(255,255,255,0.04) inset,\n 0 30px 90px rgba(0,0,0,0.5);\n padding: 1.25rem;\n }\n .topbar {\n display: flex; align-items: center; justify-content: space-between;\n gap: 0.75rem; margin-bottom: 1.75rem;\n }\n .brand-lockup {\n display: flex; align-items: center; gap: 0.55rem;\n color: var(--muted); font-size: 0.78rem; font-weight: 600;\n }\n .brand-mark { width: 18px; height: auto; display: block; }\n .app-pill {\n max-width: 50%; border: 1px solid var(--border);\n border-radius: 999px; padding: 0.28rem 0.55rem;\n color: var(--subtle); font-size: 0.72rem; line-height: 1;\n overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n }\n .hero { padding: 0 0.75rem; text-align: center; }\n .flow {\n display: flex; align-items: center; justify-content: center;\n gap: 0; margin: 0 auto 1.1rem; width: fit-content;\n }\n .flow .tile {\n width: 42px; height: 42px; border-radius: 8px;\n display: flex; align-items: center; justify-content: center;\n background: var(--panel-2); border: 1px solid var(--border-strong);\n color: var(--text); flex-shrink: 0;\n }\n .flow-mark { width: 26px; height: auto; display: block; }\n .flow .agent-symbol {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.95rem; font-weight: 700; letter-spacing: -0.04em;\n }\n .flow .conn {\n width: 30px; height: 1px; flex-shrink: 0;\n background: linear-gradient(90deg, transparent, var(--border-strong), transparent);\n background-position: center;\n }\n .eyebrow {\n text-align: center; font-size: 0.72rem; font-weight: 600;\n letter-spacing: 0.08em; text-transform: uppercase;\n color: var(--subtle); margin-bottom: 0.55rem;\n }\n h1 {\n text-align: center; font-size: 1.45rem; font-weight: 680;\n line-height: 1.25; margin-bottom: 0.7rem;\n letter-spacing: -0.01em;\n }\n .identity {\n display: flex; flex-wrap: wrap; align-items: center; justify-content: center;\n gap: 0.25rem 0.45rem; color: var(--subtle); font-size: 0.78rem;\n line-height: 1.35; margin: 0 auto 1.5rem; max-width: 34ch;\n }\n .identity strong { color: var(--muted); font-weight: 600; }\n .device-strip {\n display: flex; align-items: center; justify-content: space-between;\n gap: 0.75rem; border: 1px solid var(--border);\n border-radius: 8px; padding: 0.5rem 0.65rem; margin: 0 0 0.9rem;\n background: var(--panel-soft); color: var(--muted);\n }\n .device-strip .label {\n font-size: 0.76rem; font-weight: 560; color: var(--subtle);\n }\n .device-strip .value {\n font-size: 0.78rem; font-weight: 650;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n letter-spacing: 0.08em; color: var(--muted);\n }\n button {\n cursor: pointer; font: inherit; font-weight: 600; border: none;\n border-radius: 8px; padding: 0.78rem 1rem;\n }\n button:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }\n .primary {\n background: var(--accent); color: var(--accent-fg); width: 100%;\n font-size: 0.95rem;\n }\n .primary:hover:not(:disabled) { background: #e4e4e7; }\n .primary:disabled { opacity: 0.55; cursor: default; }\n .ghost {\n background: transparent; color: var(--muted);\n border: 1px solid var(--border-strong); padding: 0.35rem 0.7rem;\n font-size: 0.78rem; font-weight: 500; border-radius: 8px;\n }\n .ghost:hover:not(:disabled) { color: var(--text); border-color: var(--subtle); }\n pre {\n background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px;\n padding: 0.9rem; font-size: 0.78rem; line-height: 1.5; overflow-x: auto;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n color: #d4d4d8; margin: 0.5rem 0 1rem;\n }\n /* Advanced disclosure */\n .advanced { margin: 0 0 1rem; }\n .advanced > summary {\n list-style: none; cursor: pointer; user-select: none;\n display: flex; align-items: center; justify-content: center; gap: 0.35rem;\n color: var(--subtle); font-size: 0.8rem; font-weight: 500;\n padding: 0.5rem 0; text-align: center;\n }\n .advanced > summary::-webkit-details-marker { display: none; }\n .advanced > summary:hover { color: var(--muted); }\n .advanced > summary:focus-visible { outline: 2px solid var(--ring);\n outline-offset: 2px; border-radius: 6px; }\n .advanced > summary .chev {\n width: 7px; height: 7px; border-right: 1.5px solid currentColor;\n border-bottom: 1.5px solid currentColor; transform: rotate(45deg);\n transition: transform 0.15s ease; margin-top: -3px;\n }\n .advanced[open] > summary .chev { transform: rotate(225deg); margin-top: 2px; }\n .advanced-body {\n padding: 0.85rem 0.1rem 0.25rem;\n }\n .field { margin-bottom: 0.9rem; }\n .field:last-child { margin-bottom: 0; }\n .field label { display: block; font-size: 0.78rem; color: var(--muted);\n margin-bottom: 0.35rem; }\n .field input {\n width: 100%; padding: 0.6rem 0.7rem; font: inherit; color: var(--text);\n background: var(--panel-2); border: 1px solid var(--border-strong);\n border-radius: 8px;\n }\n .field input:focus-visible {\n outline: none; border-color: var(--ring);\n box-shadow: 0 0 0 3px rgba(250,250,250,0.12);\n }\n .connections {\n margin-top: 1.1rem; border-top: 1px solid var(--border);\n padding-top: 0.35rem;\n }\n .connections > summary {\n list-style: none; cursor: pointer; user-select: none;\n display: flex; align-items: center; gap: 0.55rem;\n min-height: 2.2rem; color: var(--muted); font-size: 0.82rem;\n }\n .connections > summary::-webkit-details-marker { display: none; }\n .connections > summary:focus-visible {\n outline: 2px solid var(--ring); outline-offset: 2px; border-radius: 6px;\n }\n .connections-title { font-weight: 600; color: var(--muted); }\n .connections-state {\n margin-left: auto; color: var(--subtle); font-size: 0.73rem;\n border: 1px solid var(--border); border-radius: 999px;\n padding: 0.18rem 0.45rem; line-height: 1;\n }\n .connections .chev {\n width: 7px; height: 7px; border-right: 1.5px solid currentColor;\n border-bottom: 1.5px solid currentColor; transform: rotate(45deg);\n transition: transform 0.15s ease; margin: -3px 0 0 0.15rem;\n }\n .connections[open] .chev { transform: rotate(225deg); margin-top: 2px; }\n .token-list { padding-top: 0.4rem; }\n .tok { display: flex; align-items: center; justify-content: space-between;\n gap: 0.75rem; padding: 0.6rem 0; border-bottom: 1px solid var(--border);\n font-size: 0.83rem; }\n .tok:last-child { border-bottom: none; }\n .tok .meta { color: var(--subtle); font-size: 0.74rem; margin-top: 0.1rem; }\n .tok.revoked { opacity: 0.45; }\n .empty-state {\n color: var(--subtle); font-size: 0.78rem; line-height: 1.45;\n padding: 0.3rem 0 0.45rem;\n }\n .msg { font-size: 0.83rem; padding: 0.7rem 0.8rem; border-radius: 8px;\n margin-bottom: 0.9rem; display: none; line-height: 1.4; }\n .msg.err { display: block; color: var(--error); background: var(--error-bg);\n border: 1px solid rgba(252,165,165,0.16); }\n .msg.ok { display: block; color: var(--ok); background: var(--ok-bg);\n border: 1px solid var(--ok-border); }\n .result-panel { padding-top: 0.15rem; }\n .result-title {\n color: var(--text); font-size: 0.95rem; font-weight: 650;\n text-align: center; margin-bottom: 0.35rem;\n }\n .result-copy {\n color: var(--muted); font-size: 0.83rem; line-height: 1.45;\n text-align: center; margin: 0 auto 0.85rem; max-width: 34ch;\n }\n .section-label {\n color: var(--subtle); font-size: 0.7rem; font-weight: 650;\n letter-spacing: 0.08em; text-transform: uppercase; margin-top: 0.85rem;\n }\n @media (max-width: 480px) {\n body { align-items: flex-start; padding: 0.75rem; }\n .card { padding: 1rem; }\n .hero { padding: 0; }\n .topbar { margin-bottom: 1.35rem; }\n h1 { font-size: 1.3rem; }\n .app-pill { max-width: 46%; }\n pre { font-size: 0.72rem; }\n }\n /* MCP URL display + per-host tabs (the non-dev path). */\n .mcp-url-block { margin: 0 0 1rem; }\n .url-row {\n display: flex; align-items: center; gap: 0.5rem;\n background: var(--panel-2); border: 1px solid var(--border-strong);\n border-radius: 8px; padding: 0.45rem 0.5rem 0.45rem 0.75rem;\n }\n .url-row code {\n flex: 1 1 auto; min-width: 0; overflow-x: auto; white-space: nowrap;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.78rem; color: var(--text);\n }\n .url-row .ghost { flex: 0 0 auto; }\n .hosts { margin: 0 0 1rem; }\n .tabs {\n display: flex; flex-wrap: wrap; gap: 0.25rem;\n border-bottom: 1px solid var(--border); margin-bottom: 0.75rem;\n padding-bottom: 0.4rem;\n }\n .tab {\n background: transparent; color: var(--subtle);\n border: 1px solid transparent;\n padding: 0.35rem 0.65rem; font-size: 0.8rem; font-weight: 600;\n border-radius: 6px;\n }\n .tab:hover { color: var(--muted); background: var(--panel-soft); }\n .tab.is-active {\n color: var(--text); background: var(--panel-2);\n border-color: var(--border-strong);\n }\n .tab-panel { display: none; }\n .tab-panel.is-active { display: block; }\n .tab-panel ol { margin: 0 0 0.6rem 1.1rem; padding: 0; }\n .tab-panel li {\n margin-bottom: 0.3rem; font-size: 0.86rem; line-height: 1.5;\n color: var(--muted);\n }\n .tab-panel li strong { color: var(--text); font-weight: 650; }\n .tab-panel a {\n color: var(--text); text-decoration: underline;\n text-underline-offset: 2px;\n }\n .tab-panel p {\n font-size: 0.84rem; color: var(--muted); margin: 0.4rem 0;\n line-height: 1.5;\n }\n .tab-panel .hint {\n font-size: 0.78rem; color: var(--subtle); margin-top: 0.5rem;\n }\n .tab-panel code {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.78rem; color: var(--text);\n background: var(--panel-2); padding: 0.05rem 0.3rem;\n border-radius: 4px;\n }\n .tab-panel pre { margin: 0.4rem 0 0.5rem; }\n /* Per-tab primary CTA — visually distinct from the static-token mint\n * button below. Either a link (Open Claude →) or a copy command button.\n */\n .primary-link {\n display: inline-flex; align-items: center; justify-content: center;\n gap: 0.35rem; min-height: 36px; padding: 0.45rem 0.85rem;\n background: var(--accent); color: var(--accent-fg);\n border: 1px solid var(--accent); border-radius: 8px;\n font-size: 0.86rem; font-weight: 650; text-decoration: none;\n cursor: pointer; width: 100%; text-align: center;\n margin: 0.5rem 0 0.2rem;\n }\n .primary-link:hover { background: #e4e4e7; border-color: #e4e4e7; }\n .primary-link.compact { width: auto; min-width: 0; }\n .copy-flash {\n color: var(--ok) !important;\n border-color: var(--ok-border) !important;\n }\n .static-token-mint .static-token-body { padding-top: 0.5rem; }\n .static-token-mint > summary .connections-state {\n font-style: normal;\n }\n @media (min-width: 560px) {\n .card { max-width: 580px; }\n }\n .hidden { display: none !important; }\n</style>\n</head>\n<body>\n<div class=\"card\">\n <div class=\"topbar\">\n <div class=\"brand-lockup\">\n ${brandMarkSvg}\n <span>Agent Native</span>\n </div>\n <div class=\"app-pill\" title=\"${safeApp}\">${safeApp}</div>\n </div>\n\n <div class=\"hero\">\n <!-- \"Connect an external agent\" is kept as the accessible consent label. -->\n <div class=\"flow\" role=\"img\" aria-label=\"Connect an external agent to ${safeApp}\">\n <span class=\"tile\" aria-hidden=\"true\">\n ${flowMarkSvg}\n </span>\n <span class=\"conn\" aria-hidden=\"true\"></span>\n <span class=\"tile\" aria-hidden=\"true\">\n <span class=\"agent-symbol\">&lt;/&gt;</span>\n </span>\n </div>\n\n <div class=\"eyebrow\">Connect an external agent</div>\n <h1>${safeUserCode ? `Authorize ${safeApp} from your terminal?` : `Use ${safeApp} from your AI assistant`}</h1>\n <p class=\"identity\">\n <span>Signed in as <strong>${safeEmail}</strong></span>\n </p>\n </div>\n\n <div id=\"codeCallout\" class=\"device-strip ${safeUserCode ? \"\" : \"hidden\"}\">\n <span class=\"label\">Device code</span>\n <span class=\"value\" id=\"userCodeValue\">${safeUserCode}</span>\n </div>\n\n <div class=\"mcp-url-block\">\n <div class=\"section-label\">Your MCP URL</div>\n <div class=\"url-row\">\n <code id=\"mcpUrlValue\">${safeMcpUrl}</code>\n <button type=\"button\" class=\"ghost\" data-copy=\"mcpUrlValue\" aria-label=\"Copy MCP URL\">Copy</button>\n </div>\n </div>\n\n <div class=\"hosts\">\n <div class=\"section-label\">Pick your AI assistant</div>\n <div class=\"tabs\" role=\"tablist\" aria-label=\"Choose your AI assistant\">\n <button type=\"button\" class=\"tab is-active\" role=\"tab\" data-tab=\"claude\" aria-selected=\"true\">Claude</button>\n <button type=\"button\" class=\"tab\" role=\"tab\" data-tab=\"chatgpt\" aria-selected=\"false\">ChatGPT</button>\n <button type=\"button\" class=\"tab\" role=\"tab\" data-tab=\"cursor\" aria-selected=\"false\">Cursor</button>\n <button type=\"button\" class=\"tab\" role=\"tab\" data-tab=\"claude-code\" aria-selected=\"false\">Claude Code</button>\n <button type=\"button\" class=\"tab\" role=\"tab\" data-tab=\"codex\" aria-selected=\"false\">Codex</button>\n <button type=\"button\" class=\"tab\" role=\"tab\" data-tab=\"other\" aria-selected=\"false\">Other</button>\n </div>\n <div class=\"tab-panel is-active\" role=\"tabpanel\" data-panel=\"claude\">\n <ol>\n <li>Open <strong>Customize → Connectors</strong> in Claude.</li>\n <li>Click the <strong>+</strong> button → <strong>Add custom connector</strong>.</li>\n <li>Paste the MCP URL above, name it <strong>${safeApp}</strong>, click <strong>Connect</strong>.</li>\n <li>On the consent page, click <strong>Authorize</strong> to approve <code>mcp:read</code>, <code>mcp:write</code>, <code>mcp:apps</code>.</li>\n </ol>\n <a class=\"primary-link\" href=\"https://claude.ai/customize/connectors\" target=\"_blank\" rel=\"noopener noreferrer\">Open Claude → Connectors</a>\n <p class=\"hint\">Works in Claude web and Claude Desktop. Inline MCP Apps (charts, dashboards, drafts) render automatically inside the chat.</p>\n </div>\n <div class=\"tab-panel\" role=\"tabpanel\" data-panel=\"chatgpt\">\n <ol>\n <li>In ChatGPT, open <strong>Settings → Apps</strong> (Business/Enterprise/Edu workspaces with developer mode enabled).</li>\n <li>Scroll to <strong>Advanced settings → Create app</strong>, paste the MCP URL above, name it <strong>${safeApp}</strong>.</li>\n <li>Click <strong>Connect</strong>, sign in with your Agent-Native account, and approve <code>mcp:read</code>, <code>mcp:write</code>, <code>mcp:apps</code>.</li>\n </ol>\n <a class=\"primary-link\" href=\"https://chatgpt.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Open ChatGPT</a>\n <p class=\"hint\"><strong>Got \"Connector name already exists\" but don't see it under Enabled apps?</strong> ChatGPT saves a hidden draft the moment you click Create — even if you closed the OAuth popup before approving. In <strong>Settings → Apps</strong>, scroll past Enabled apps to the <strong>Drafts</strong> section (\"Private apps you've created in developer mode\"). Click the draft and either press <strong>Connect</strong> to finish OAuth, or use the <strong>⋯ → Delete</strong> menu and re-create. Workspace admins may also need to enable custom connectors under org settings; each member still authorizes their own account.</p>\n </div>\n <div class=\"tab-panel\" role=\"tabpanel\" data-panel=\"cursor\">\n <ol>\n <li>Open <strong>Cursor → Settings → MCP</strong>.</li>\n <li>Click <strong>Add MCP Server</strong>, paste the MCP URL above, save.</li>\n <li>When prompted, sign in with your Agent-Native account and approve the MCP scopes.</li>\n </ol>\n <p class=\"hint\">Cursor supports remote-OAuth MCP servers, same paste-URL flow as Claude — no terminal needed.</p>\n </div>\n <div class=\"tab-panel\" role=\"tabpanel\" data-panel=\"claude-code\">\n <p>In your terminal, run:</p>\n <pre id=\"claudeCodeCmd\">${safeClaudeCodeCmd}</pre>\n <button type=\"button\" class=\"primary-link compact\" data-copy=\"claudeCodeCmd\">Copy command</button>\n <p class=\"hint\">Then inside Claude Code type <code>/mcp</code>, choose <strong>${safeServerId}</strong>, and click <strong>Authenticate</strong>. Claude completes the OAuth flow itself — no static token needed.</p>\n </div>\n <div class=\"tab-panel\" role=\"tabpanel\" data-panel=\"codex\">\n <p>In your terminal, run:</p>\n <pre id=\"codexCmd\">${safeCodexCmd}</pre>\n <button type=\"button\" class=\"primary-link compact\" data-copy=\"codexCmd\">Copy command</button>\n <p class=\"hint\">Opens this page in your browser and writes Codex's <code>~/.codex/config.toml</code> automatically. The same command works for Claude Cowork and Goose.</p>\n </div>\n <div class=\"tab-panel\" role=\"tabpanel\" data-panel=\"other\">\n <p>Any MCP-compatible client with remote-OAuth support: paste the MCP URL above. For clients without OAuth, paste this <code>.mcp.json</code> snippet and generate a static bearer below:</p>\n <pre id=\"genericConfig\">${safeGenericConfig}</pre>\n <button type=\"button\" class=\"primary-link compact\" data-copy=\"genericConfig\">Copy config</button>\n </div>\n </div>\n\n <details id=\"staticTokenMint\" class=\"connections static-token-mint\"${safeUserCode ? \" open\" : \"\"}>\n <summary>\n <span class=\"connections-title\">${safeUserCode ? \"Authorize this device\" : \"Generate a static token\"}</span>\n <span class=\"connections-state\">${safeUserCode ? \"From your terminal\" : \"Advanced — clients without OAuth\"}</span>\n <span class=\"chev\" aria-hidden=\"true\"></span>\n </summary>\n <div class=\"static-token-body\">\n <div id=\"msg\" class=\"msg\"></div>\n <div id=\"mintForm\">\n <button id=\"authorizeBtn\" class=\"primary\">${safeUserCode ? \"Authorize device\" : \"Create connection token\"}</button>\n <details class=\"advanced\">\n <summary>\n Advanced options\n <span class=\"chev\" aria-hidden=\"true\"></span>\n </summary>\n <div class=\"advanced-body\">\n <div class=\"field\">\n <label for=\"label\">Label (optional)</label>\n <input id=\"label\" type=\"text\" placeholder=\"e.g. Claude Code on my laptop\" maxlength=\"120\" />\n </div>\n <div class=\"field\">\n <label for=\"ttl\">Expires in (days, 1–365)</label>\n <input id=\"ttl\" type=\"number\" min=\"1\" max=\"365\" value=\"${DEFAULT_TOKEN_TTL_DAYS}\" />\n </div>\n </div>\n </details>\n </div>\n <div id=\"result\" class=\"result-panel hidden\">\n <div class=\"result-title\">Connection token created</div>\n <p class=\"result-copy\" id=\"resultMsg\">Paste this into your agent's MCP config. The token is shown only once.</p>\n <div class=\"section-label\">MCP config</div>\n <pre id=\"mcpJson\"></pre>\n <details class=\"advanced\">\n <summary>\n Terminal alternative\n <span class=\"chev\" aria-hidden=\"true\"></span>\n </summary>\n <div class=\"advanced-body\">\n <pre id=\"cliLine\"></pre>\n </div>\n </details>\n </div>\n </div>\n </details>\n\n <details id=\"connections\" class=\"connections\">\n <summary>\n <span class=\"connections-title\">Existing connections</span>\n <span id=\"connectionsState\" class=\"connections-state\">Checking</span>\n <span class=\"chev\" aria-hidden=\"true\"></span>\n </summary>\n <div id=\"tokenList\" class=\"token-list\"><div class=\"empty-state\">Checking connections...</div></div>\n </details>\n</div>\n<script>\n(function () {\n var BASE = ${JSON.stringify(joinAppPath(connectBasePath, \"/_agent-native/mcp/connect\"))};\n var USER_CODE = ${JSON.stringify(safeUserCode || null)};\n var msgEl = document.getElementById(\"msg\");\n var connectionsEl = document.getElementById(\"connections\");\n var connectionsStateEl = document.getElementById(\"connectionsState\");\n\n // Tab switching for the per-host instructions block.\n var tabBtns = document.querySelectorAll(\".tabs .tab\");\n var tabPanels = document.querySelectorAll(\".tab-panel\");\n for (var i = 0; i < tabBtns.length; i++) {\n tabBtns[i].addEventListener(\"click\", function (ev) {\n var btn = ev.currentTarget;\n var name = btn.getAttribute(\"data-tab\");\n for (var j = 0; j < tabBtns.length; j++) {\n var active = tabBtns[j] === btn;\n tabBtns[j].classList.toggle(\"is-active\", active);\n tabBtns[j].setAttribute(\"aria-selected\", active ? \"true\" : \"false\");\n }\n for (var k = 0; k < tabPanels.length; k++) {\n tabPanels[k].classList.toggle(\n \"is-active\",\n tabPanels[k].getAttribute(\"data-panel\") === name,\n );\n }\n });\n }\n\n // Copy buttons — any element with data-copy=\"<id>\" copies that node's text.\n document.addEventListener(\"click\", function (ev) {\n var btn = ev.target && ev.target.closest && ev.target.closest(\"[data-copy]\");\n if (!btn) return;\n var node = document.getElementById(btn.getAttribute(\"data-copy\"));\n if (!node || !navigator.clipboard) return;\n navigator.clipboard.writeText(node.textContent || \"\").then(function () {\n var prev = btn.textContent;\n btn.textContent = \"Copied\";\n btn.classList.add(\"copy-flash\");\n setTimeout(function () {\n btn.textContent = prev;\n btn.classList.remove(\"copy-flash\");\n }, 1400);\n });\n });\n function showMsg(text, kind) {\n msgEl.textContent = text;\n msgEl.className = \"msg \" + (kind || \"err\");\n }\n function clearMsg() { msgEl.className = \"msg\"; msgEl.textContent = \"\"; }\n\n function renderResult(data) {\n document.getElementById(\"mintForm\").classList.add(\"hidden\");\n var entry = {};\n entry[data.serverName] = data.mcpServerEntry;\n document.getElementById(\"mcpJson\").textContent =\n JSON.stringify({ mcpServers: entry }, null, 2);\n document.getElementById(\"cliLine\").textContent = data.cli;\n document.getElementById(\"result\").classList.remove(\"hidden\");\n }\n\n async function postJson(path, body) {\n var res = await fetch(BASE + path, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"same-origin\",\n body: JSON.stringify(body || {})\n });\n var data = null;\n try { data = await res.json(); } catch (e) {}\n return { ok: res.ok, status: res.status, data: data };\n }\n\n async function loadTokens() {\n var listEl = document.getElementById(\"tokenList\");\n try {\n var res = await fetch(BASE + \"/tokens\", { credentials: \"same-origin\" });\n if (!res.ok) {\n connectionsStateEl.textContent = \"Unavailable\";\n listEl.innerHTML = '<div class=\"empty-state\">Could not load connections.</div>';\n return;\n }\n var data = await res.json();\n var tokens = (data && data.tokens) || [];\n if (!tokens.length) {\n connectionsStateEl.textContent = \"None\";\n connectionsEl.open = false;\n listEl.innerHTML = '<div class=\"empty-state\">Created connections will appear here for revoking later.</div>';\n return;\n }\n var activeCount = tokens.filter(function (t) { return !t.revokedAt; }).length;\n connectionsStateEl.textContent = activeCount === 1 ? \"1 active\" : activeCount + \" active\";\n listEl.innerHTML = \"\";\n tokens.forEach(function (t) {\n var div = document.createElement(\"div\");\n div.className = \"tok\" + (t.revokedAt ? \" revoked\" : \"\");\n var when = t.createdAt ? new Date(t.createdAt).toLocaleString() : \"\";\n var used = t.lastUsedAt ? \" · last used \" + new Date(t.lastUsedAt).toLocaleString() : \"\";\n var left = document.createElement(\"div\");\n var label = document.createElement(\"div\");\n label.textContent = t.label || \"(unlabeled)\";\n var meta = document.createElement(\"div\");\n meta.className = \"meta\";\n meta.textContent = (t.revokedAt ? \"Revoked · \" : \"Created \") + when + used;\n left.appendChild(label); left.appendChild(meta);\n div.appendChild(left);\n if (!t.revokedAt) {\n var btn = document.createElement(\"button\");\n btn.className = \"ghost\";\n btn.textContent = \"Revoke\";\n btn.onclick = async function () {\n btn.disabled = true;\n var r = await postJson(\"/tokens/revoke\", { id: t.id });\n if (r.ok) { loadTokens(); }\n else { btn.disabled = false; showMsg(\"Could not revoke token.\"); }\n };\n div.appendChild(btn);\n }\n listEl.appendChild(div);\n });\n } catch (e) {\n connectionsStateEl.textContent = \"Unavailable\";\n listEl.innerHTML = '<div class=\"empty-state\">Could not load connections.</div>';\n }\n }\n\n document.getElementById(\"authorizeBtn\").onclick = async function () {\n var btn = this;\n btn.disabled = true;\n clearMsg();\n var label = document.getElementById(\"label\").value || undefined;\n var ttlDays = parseInt(document.getElementById(\"ttl\").value, 10) || undefined;\n try {\n if (USER_CODE) {\n var a = await postJson(\"/device/authorize\", { user_code: USER_CODE });\n if (!a.ok) {\n btn.disabled = false;\n showMsg((a.data && a.data.error) || \"Could not authorize this device code.\");\n return;\n }\n showMsg(\"Device authorized — finishing connection… you can return to your terminal.\", \"ok\");\n btn.classList.add(\"hidden\");\n document.getElementById(\"mintForm\").classList.add(\"hidden\");\n var cc = document.getElementById(\"codeCallout\");\n if (cc) cc.classList.add(\"hidden\");\n // The token is minted a few seconds later, when the CLI next polls\n // /device/poll — so a single loadTokens() here runs BEFORE the row\n // exists and the list would wrongly read \"No connections yet\" until\n // a manual reload. Snapshot the EXISTING non-revoked token ids first\n // so we announce \"Connected\" only when THIS device's freshly-minted\n // token appears — a user who already has tokens must not get a false\n // success the instant they authorize.\n var priorIds = {};\n try {\n var pr = await fetch(BASE + \"/tokens\", { credentials: \"same-origin\" });\n if (pr.ok) {\n var pd = await pr.json();\n ((pd && pd.tokens) || []).forEach(function (t) {\n if (!t.revokedAt) priorIds[t.id] = true;\n });\n }\n } catch (e) {}\n loadTokens();\n var tries = 0;\n var iv = setInterval(async function () {\n tries++;\n try {\n var res = await fetch(BASE + \"/tokens\", { credentials: \"same-origin\" });\n if (res.ok) {\n var data = await res.json();\n var fresh = ((data && data.tokens) || []).filter(function (t) {\n return !t.revokedAt && !priorIds[t.id];\n });\n if (fresh.length > 0) {\n clearInterval(iv);\n showMsg(\"Connected. This device can now act as you — manage or revoke it below.\", \"ok\");\n loadTokens();\n return;\n }\n }\n } catch (e) {}\n if (tries >= 30) {\n // No new token appeared in the window — e.g. the loopback\n // dev-open path writes a header-only config and never mints.\n // Don't claim \"Connected\" (we couldn't confirm a device token);\n // keep the \"authorized\" message and just refresh the list.\n clearInterval(iv);\n loadTokens();\n }\n }, 2000);\n return;\n } else {\n var m = await postJson(\"/token\", { label: label, ttlDays: ttlDays });\n if (!m.ok) {\n btn.disabled = false;\n showMsg((m.data && m.data.error) || \"Could not create token.\");\n return;\n }\n renderResult(m.data);\n }\n loadTokens();\n } catch (e) {\n btn.disabled = false;\n showMsg(\"Network error. Please try again.\");\n }\n };\n\n loadTokens();\n})();\n</script>\n</body>\n</html>`;\n}\n\n// ---------------------------------------------------------------------------\n// Handler — single entry point; core-routes-plugin dispatches the subpath.\n// ---------------------------------------------------------------------------\n\n/**\n * Handle a `/_agent-native/mcp/connect[...]` request. `subpath` is the part\n * after `/connect` (empty string = the page itself, otherwise e.g.\n * `/token`, `/device/start`). The core-routes-plugin computes it from the\n * stripped event path so this module stays mount-agnostic.\n */\nexport async function handleMcpConnect(\n event: H3Event,\n subpath: string,\n options: McpConnectRouteOptions = {},\n): Promise<Response> {\n const method = getMethod(event);\n const origin = deriveOrigin(event);\n const basePath = configuredBasePath();\n const appUrl = `${origin}${basePath}`;\n const sub = (\"/\" + subpath.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\")).replace(\n /^\\/$/,\n \"\",\n );\n\n // ---- The connect page (GET) ------------------------------------------\n if (sub === \"\") {\n if (method !== \"GET\" && method !== \"HEAD\") {\n return json({ error: \"Method not allowed\" }, 405);\n }\n const session = await getSession(event);\n if (!session?.email) {\n // Serve the SAME login form the guard would, at this same URL — the\n // login form reloads window.location so we re-enter here authed.\n const loginHtml = getConfiguredLoginHtml(event);\n if (loginHtml) return html(loginHtml, 200);\n // Fully-open app (no auth guard): nothing to scope a mint to.\n return html(\n renderConnectPage({\n connectBasePath: basePath,\n email: \"(no auth configured)\",\n appName: options.appName || appLabel(appUrl, options),\n appUrl,\n serverId: serverName(appUrl, options),\n userCode: null,\n }),\n );\n }\n let userCode: string | null = null;\n try {\n const u = new URL(\n event.node?.req?.url ?? event.path ?? \"/\",\n \"http://an.invalid\",\n );\n const raw = u.searchParams.get(\"user_code\");\n if (raw && USER_CODE_RE.test(raw)) userCode = raw;\n } catch {\n userCode = null;\n }\n return html(\n renderConnectPage({\n connectBasePath: basePath,\n email: session.email,\n appName: options.appName || appLabel(appUrl, options),\n appUrl,\n serverId: serverName(appUrl, options),\n userCode,\n }),\n );\n }\n\n // ---- POST /token (session-required) ---------------------------------\n if (sub === \"/token\") {\n if (method !== \"POST\") return json({ error: \"Method not allowed\" }, 405);\n const session = await getSession(event);\n if (!session?.email) return json({ error: \"Unauthorized\" }, 401);\n if (!process.env.A2A_SECRET) {\n if (canUseDevOpenConnect(event)) {\n return json(\n mcpResultPayload(appUrl, options, { ownerEmail: session.email }),\n );\n }\n return json(\n {\n error:\n \"This deployment has no A2A_SECRET configured, so connect tokens cannot be minted.\",\n },\n 503,\n );\n }\n const body = ((await readBody(event).catch(() => ({}))) ?? {}) as {\n label?: unknown;\n ttlDays?: unknown;\n };\n const label =\n typeof body.label === \"string\" && body.label.trim()\n ? body.label.trim().slice(0, 120)\n : null;\n const ttlDays = clampTtlDays(body.ttlDays);\n try {\n const { token } = await mintConnectToken({\n email: session.email,\n orgId: session.orgId,\n label,\n ttlDays,\n });\n return json(mcpResultPayload(appUrl, options, { token }));\n } catch {\n return json({ error: \"Failed to mint token.\" }, 500);\n }\n }\n\n // ---- POST /device/start (UNAUTH) ------------------------------------\n if (sub === \"/device/start\") {\n if (method !== \"POST\") return json({ error: \"Method not allowed\" }, 405);\n try {\n const row = await createDeviceCode();\n const verificationUri = `${appUrl}/_agent-native/mcp/connect`;\n return json({\n device_code: row.deviceCode,\n user_code: row.userCode,\n verification_uri: verificationUri,\n verification_uri_complete: `${verificationUri}?user_code=${row.userCode}`,\n interval: DEVICE_POLL_INTERVAL_S,\n expires_in: Math.floor(DEVICE_CODE_TTL_MS / 1000),\n });\n } catch (err: any) {\n if (err?.message === \"RATE_LIMITED\") {\n return json({ error: \"Rate limited. Try again shortly.\" }, 429);\n }\n return json({ error: \"Could not start device flow.\" }, 500);\n }\n }\n\n // ---- POST /device/authorize (session-required) ----------------------\n if (sub === \"/device/authorize\") {\n if (method !== \"POST\") return json({ error: \"Method not allowed\" }, 405);\n const session = await getSession(event);\n if (!session?.email) return json({ error: \"Unauthorized\" }, 401);\n const body = ((await readBody(event).catch(() => ({}))) ?? {}) as {\n user_code?: unknown;\n };\n const userCode =\n typeof body.user_code === \"string\" ? body.user_code.trim() : \"\";\n if (!USER_CODE_RE.test(userCode)) {\n return json({ error: \"Invalid user code.\" }, 400);\n }\n const orgId =\n typeof session.orgId === \"string\" && session.orgId.trim()\n ? session.orgId.trim()\n : null;\n const result = await approveDeviceCode(userCode, session.email, orgId);\n if (result === \"not_found\") {\n return json({ error: \"Unknown device code.\" }, 404);\n }\n if (result === \"expired\") {\n return json({ error: \"This device code has expired.\" }, 410);\n }\n if (result === \"already\") {\n return json({ error: \"This device code was already used.\" }, 409);\n }\n return json({ status: \"approved\" });\n }\n\n // ---- POST /device/poll (UNAUTH) -------------------------------------\n if (sub === \"/device/poll\") {\n if (method !== \"POST\") return json({ error: \"Method not allowed\" }, 405);\n const body = ((await readBody(event).catch(() => ({}))) ?? {}) as {\n device_code?: unknown;\n };\n const deviceCode =\n typeof body.device_code === \"string\" ? body.device_code : \"\";\n if (!deviceCode) return json({ error: \"device_code required\" }, 400);\n const row = await getDeviceCode(deviceCode);\n if (!row) return json({ status: \"not_found\" }, 404);\n if (row.status === \"consumed\") return json({ status: \"consumed\" });\n if (\n row.status === \"expired\" ||\n (row.expiresAt != null && row.expiresAt < Date.now())\n ) {\n if (row.status !== \"expired\") void expireDeviceCode(deviceCode);\n return json({ status: \"expired\" });\n }\n if (\n row.status === \"pending\" ||\n row.status === \"minting\" ||\n !row.ownerEmail\n ) {\n return json({ status: \"pending\" });\n }\n // status === \"approved\" && ownerEmail bound → mint exactly once.\n if (!process.env.A2A_SECRET) {\n if (canUseDevOpenConnect(event)) {\n const consumed = await consumeDeviceCode(\n deviceCode,\n `dev-open-${randomUUID()}`,\n );\n if (!consumed) {\n const fresh = await getDeviceCode(deviceCode);\n if (fresh?.status === \"consumed\") return json({ status: \"consumed\" });\n return json({ status: \"pending\" });\n }\n return json({\n status: \"approved\",\n ...mcpResultPayload(appUrl, options, {\n ownerEmail: row.ownerEmail,\n }),\n });\n }\n return json({ status: \"error\", error: \"A2A_SECRET not configured\" }, 503);\n }\n try {\n const jti = randomUUID();\n // Claim a retryable minting state first. If signing or recording fails,\n // release the row back to approved so the CLI can poll again.\n const claimed = await claimDeviceCodeForMint(deviceCode, jti);\n if (!claimed) {\n const fresh = await getDeviceCode(deviceCode);\n if (fresh?.status === \"consumed\") return json({ status: \"consumed\" });\n return json({ status: \"pending\" });\n }\n let token: string;\n try {\n const orgDomain = await resolveOrgDomain(claimed.orgId ?? undefined);\n token = await signA2AToken(claimed.ownerEmail!, orgDomain, undefined, {\n preferGlobalSecret: true,\n expiresIn: `${DEFAULT_TOKEN_TTL_DAYS}d`,\n extraClaims: { jti, scope: MCP_CONNECT_SCOPE },\n });\n await recordMintedToken({\n jti,\n ownerEmail: claimed.ownerEmail!,\n orgId: claimed.orgId,\n label: \"Device connection\",\n });\n if (!(await finishDeviceCodeMint(deviceCode, jti))) {\n return json({ status: \"pending\" });\n }\n } catch (err) {\n await releaseDeviceCodeMint(deviceCode, jti);\n throw err;\n }\n return json({\n status: \"approved\",\n ...mcpResultPayload(appUrl, options, { token }),\n });\n } catch {\n return json({ status: \"error\", error: \"Failed to mint token.\" }, 500);\n }\n }\n\n // ---- GET /tokens (session-required) ---------------------------------\n if (sub === \"/tokens\") {\n if (method !== \"GET\") return json({ error: \"Method not allowed\" }, 405);\n const session = await getSession(event);\n if (!session?.email) return json({ error: \"Unauthorized\" }, 401);\n const rows = await listTokens(session.email);\n return json({\n tokens: rows.map((r) => ({\n id: r.id,\n label: r.label,\n createdAt: r.createdAt,\n lastUsedAt: r.lastUsedAt,\n revokedAt: r.revokedAt,\n })),\n });\n }\n\n // ---- POST /tokens/revoke (session-required) -------------------------\n if (sub === \"/tokens/revoke\") {\n if (method !== \"POST\") return json({ error: \"Method not allowed\" }, 405);\n const session = await getSession(event);\n if (!session?.email) return json({ error: \"Unauthorized\" }, 401);\n const body = ((await readBody(event).catch(() => ({}))) ?? {}) as {\n id?: unknown;\n };\n const id = typeof body.id === \"string\" ? body.id : \"\";\n if (!id) return json({ error: \"id required\" }, 400);\n const revoked = await revokeToken(session.email, id);\n return json({ ok: revoked });\n }\n\n return json({ error: \"Not found\" }, 404);\n}\n"]}
1
+ {"version":3,"file":"connect-route.js","sourceRoot":"","sources":["../../src/mcp/connect-route.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `/_agent-native/mcp/connect` — frictionless external-agent connection.\n *\n * A logged-in user on a deployed agent-native app (e.g. mail.agent-native.com)\n * mints a per-user, scoped, revocable MCP bearer token WITHOUT ever copying a\n * shared deployment secret. Two surfaces:\n *\n * 1. Browser — `GET /connect` renders a minimal in-app page (same inline\n * HTML approach as the auth pages). The Authorize button POSTs to\n * `/connect/token`, then shows the ready-to-paste `.mcp.json` entry, the\n * `agent-native connect <origin>` one-liner, and the user's existing\n * tokens with Revoke buttons.\n * 2. CLI — an OAuth-2.0-device-authorization-style flow:\n * POST /connect/device/start (unauth) → device_code + user_code\n * GET /connect?user_code=… (browser) → user signs in & approves\n * POST /connect/device/authorize (session) → binds user to the code\n * POST /connect/device/poll (unauth) → mints + returns the token\n *\n * The minted token reuses the existing A2A signer (`signA2AToken`) — no new\n * crypto. We only add a random `jti` + `scope: \"mcp-connect\"` claim so it can\n * be revoked. `verifyAuth` already verifies A2A_SECRET JWTs and extracts\n * `sub`/`org_domain`, so a minted token works against `/_agent-native/mcp`\n * with no verify changes for the happy path (the revoke check is the only\n * addition there).\n *\n * Node-only (crypto + the A2A signer), bundled alongside the other framework\n * routes. Dialect-agnostic SQL lives in `connect-store.ts`.\n */\n\nimport type { H3Event } from \"h3\";\nimport { getMethod, getHeader } from \"h3\";\nimport { readBody } from \"../server/h3-helpers.js\";\nimport {\n getSession,\n getConfiguredLoginHtml,\n isLoopbackRequest,\n} from \"../server/auth.js\";\nimport { signA2AToken } from \"../a2a/client.js\";\nimport { getOrgDomain } from \"../org/context.js\";\nimport { randomUUID } from \"node:crypto\";\nimport {\n recordMintedToken,\n listTokens,\n revokeToken,\n createDeviceCode,\n getDeviceCode,\n approveDeviceCode,\n consumeDeviceCode,\n claimDeviceCodeForMint,\n finishDeviceCodeMint,\n releaseDeviceCodeMint,\n expireDeviceCode,\n MCP_CONNECT_SCOPE,\n DEFAULT_TOKEN_TTL_DAYS,\n MIN_TOKEN_TTL_DAYS,\n MAX_TOKEN_TTL_DAYS,\n DEVICE_CODE_TTL_MS,\n} from \"./connect-store.js\";\n\n/** Device-flow poll interval hint (seconds). */\nconst DEVICE_POLL_INTERVAL_S = 3;\n\n// Human-typable user code: 8 base32 chars, dashed XXXX-XXXX.\nconst USER_CODE_RE = /^[A-Z2-7]{4}-[A-Z2-7]{4}$/;\n\nexport interface McpConnectRouteOptions {\n /** App id (directory under apps/, e.g. `mail`). Used for the server name. */\n appId?: string;\n /** Human app name shown on the connect page. */\n appName?: string;\n}\n\nfunction json(body: unknown, status = 200): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\nfunction html(body: string, status = 200): Response {\n return new Response(body, {\n status,\n headers: { \"Content-Type\": \"text/html; charset=utf-8\" },\n });\n}\n\n/** Derive the running app's origin from request headers (same logic mountMCP\n * uses) — `https` in prod / for non-loopback hosts, `http` for localhost. */\nfunction deriveOrigin(event: H3Event): string {\n const forwardedProto = getHeader(event, \"x-forwarded-proto\");\n const host = getHeader(event, \"x-forwarded-host\") || getHeader(event, \"host\");\n const proto =\n forwardedProto?.split(\",\")[0]?.trim() ||\n (host && /^(localhost|127\\.0\\.0\\.1)(:|$)/.test(host) ? \"http\" : \"https\");\n return host ? `${proto}://${host}` : \"\";\n}\n\nfunction isLoopbackOrigin(origin: string): boolean {\n try {\n const hostname = new URL(origin).hostname;\n return (\n hostname === \"localhost\" ||\n hostname === \"127.0.0.1\" ||\n hostname === \"::1\" ||\n hostname === \"[::1]\" ||\n hostname.startsWith(\"127.\")\n );\n } catch {\n return false;\n }\n}\n\nfunction normalizeBasePath(raw: string | undefined): string {\n const trimmed = (raw ?? \"\").trim();\n if (!trimmed || trimmed === \"/\") return \"\";\n const withSlash = trimmed.startsWith(\"/\") ? trimmed : `/${trimmed}`;\n return withSlash.replace(/\\/+$/, \"\");\n}\n\nfunction configuredBasePath(): string {\n return normalizeBasePath(\n process.env.APP_BASE_PATH || process.env.VITE_APP_BASE_PATH,\n );\n}\n\nfunction joinAppPath(basePath: string, path: string): string {\n if (!basePath) return path;\n if (path === \"/\") return basePath;\n return `${basePath}${path.startsWith(\"/\") ? path : `/${path}`}`;\n}\n\nfunction appLabel(origin: string, options: McpConnectRouteOptions): string {\n if (options.appId) return options.appId;\n try {\n const h = new URL(origin).hostname;\n return h.split(\".\")[0] || h;\n } catch {\n return options.appName || \"app\";\n }\n}\n\nfunction serverName(origin: string, options: McpConnectRouteOptions): string {\n return `agent-native-${appLabel(origin, options)}`;\n}\n\nfunction canUseDevOpenConnect(event: H3Event): boolean {\n // Loopback determined from the real socket peer (isLoopbackRequest →\n // getRequestIP without xForwardedFor), NOT a parsed `Host` header — the\n // header is client-controlled, and it also handles IPv6 `::1`. A\n // misconfigured public deploy with no secret thus can't unlock dev-open\n // by spoofing `Host: localhost`.\n return (\n isLoopbackRequest(event) &&\n isLoopbackOrigin(deriveOrigin(event)) &&\n !process.env.A2A_SECRET?.trim() &&\n !process.env.ACCESS_TOKEN?.trim() &&\n !process.env.ACCESS_TOKENS?.trim()\n );\n}\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n\n/**\n * Resolve the org domain for a session. Used as the JWT `org_domain` claim so\n * the receiving MCP endpoint can map it back to an org id (same as A2A). Best\n * effort — a missing org just yields a user-scoped (no-org) token.\n */\nasync function resolveOrgDomain(\n orgId: string | undefined,\n): Promise<string | undefined> {\n if (!orgId) return undefined;\n try {\n return (await getOrgDomain(orgId)) ?? undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction clampTtlDays(input: unknown): number {\n const n = Number(input);\n if (!Number.isFinite(n)) return DEFAULT_TOKEN_TTL_DAYS;\n return Math.min(\n MAX_TOKEN_TTL_DAYS,\n Math.max(MIN_TOKEN_TTL_DAYS, Math.floor(n)),\n );\n}\n\n/**\n * Mint a connect-scoped JWT and record it. The JWT is signed by the existing\n * A2A signer (HS256 over A2A_SECRET); we add a random `jti` and\n * `scope: \"mcp-connect\"` so the token is individually revocable. The token\n * value is returned to the caller exactly once and never persisted.\n */\nasync function mintConnectToken(params: {\n email: string;\n orgId: string | undefined;\n label: string | null;\n ttlDays: number;\n}): Promise<{ token: string; jti: string }> {\n const orgDomain = await resolveOrgDomain(params.orgId);\n const jti = randomUUID();\n // signA2AToken signs { sub: email, org_domain? } over A2A_SECRET (global)\n // or the org secret. We extend its claims via the standard jose builder by\n // re-using the same signer with extra claims threaded through `options`.\n const token = await signA2AToken(params.email, orgDomain, undefined, {\n preferGlobalSecret: true,\n expiresIn: `${params.ttlDays}d`,\n extraClaims: { jti, scope: MCP_CONNECT_SCOPE },\n });\n await recordMintedToken({\n jti,\n ownerEmail: params.email,\n orgId: params.orgId ?? null,\n label: params.label,\n });\n return { token, jti };\n}\n\nfunction mcpResultPayload(\n appUrl: string,\n options: McpConnectRouteOptions,\n auth: { token?: string; ownerEmail?: string },\n) {\n const mcpUrl = `${appUrl}/_agent-native/mcp`;\n const name = serverName(appUrl, options);\n const headers: Record<string, string> = {};\n if (auth.token) headers.Authorization = `Bearer ${auth.token}`;\n if (!auth.token && auth.ownerEmail) {\n headers[\"X-Agent-Native-Owner-Email\"] = auth.ownerEmail;\n }\n return {\n token: auth.token ?? \"\",\n mcpUrl,\n serverName: name,\n mcpServerEntry: {\n type: \"http\" as const,\n url: mcpUrl,\n ...(Object.keys(headers).length ? { headers } : {}),\n },\n cli: `agent-native connect ${appUrl}`,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Connect page (server-rendered HTML string)\n// ---------------------------------------------------------------------------\n\nfunction agentNativeMarkSvg(className: string, gradientId: string): string {\n return `<svg class=\"${className}\" width=\"114\" height=\"66\" viewBox=\"0 0 114 66\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\">\n <path d=\"M24.5537 65.7695H0L15.0859 39.4619L37.708 0L60.4912 39.4619H39.6396L24.5537 65.7695Z\" fill=\"white\"/>\n <path d=\"M89.446 0H114L76.2921 65.7704H51.7383L89.446 0Z\" fill=\"url(#${gradientId})\"/>\n <defs>\n <linearGradient id=\"${gradientId}\" x1=\"101.702\" y1=\"67.4791\" x2=\"113.672\" y2=\"-37.4275\" gradientUnits=\"userSpaceOnUse\">\n <stop stop-color=\"#00B5FF\"/>\n <stop offset=\"1\" stop-color=\"#48FFE4\"/>\n </linearGradient>\n </defs>\n</svg>`;\n}\n\nfunction renderConnectPage(params: {\n connectBasePath: string;\n email: string;\n appName: string;\n appUrl: string;\n serverId: string;\n userCode: string | null;\n}): string {\n const { connectBasePath, email, appName, appUrl, serverId, userCode } =\n params;\n const safeEmail = escapeHtml(email);\n const safeApp = escapeHtml(appName);\n const mcpUrl = `${appUrl}/_agent-native/mcp`;\n const safeMcpUrl = escapeHtml(mcpUrl);\n const safeServerId = escapeHtml(serverId);\n const safeClaudeCodeCmd = escapeHtml(\n `claude mcp add --transport http ${serverId} ${mcpUrl}`,\n );\n const safeCodexCmd = escapeHtml(`npx @agent-native/core connect ${appUrl}`);\n const safeGenericConfig = escapeHtml(\n `{\\n \"mcpServers\": {\\n \"${serverId}\": {\\n \"type\": \"http\",\\n \"url\": \"${mcpUrl}\"\\n }\\n }\\n}`,\n );\n const brandMarkSvg = agentNativeMarkSvg(\n \"brand-mark\",\n \"agent-native-connect-brand-gradient\",\n );\n const flowMarkSvg = agentNativeMarkSvg(\n \"flow-mark\",\n \"agent-native-connect-flow-gradient\",\n );\n const safeUserCode =\n userCode && USER_CODE_RE.test(userCode) ? escapeHtml(userCode) : \"\";\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Connect ${safeApp}</title>\n<style>\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n :root {\n color-scheme: dark;\n --bg: #09090b; --panel: #121214; --panel-2: #0c0c0e;\n --panel-soft: rgba(255,255,255,0.025);\n --border: rgba(255,255,255,0.075); --border-strong: rgba(255,255,255,0.14);\n --text: #f7f7f8; --muted: #a1a1aa; --subtle: #74747d;\n --accent: #f4f4f5; --accent-fg: #09090b;\n --ring: rgba(250,250,250,0.55);\n --error: #fca5a5; --error-bg: rgba(127,29,29,0.18);\n --ok: #86efac; --ok-bg: rgba(20,83,45,0.12); --ok-border: rgba(134,239,172,0.18);\n }\n html, body { -webkit-font-smoothing: antialiased; }\n body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n background: linear-gradient(180deg, #101013 0%, var(--bg) 58%);\n color: var(--text); display: flex; align-items: center;\n justify-content: center; min-height: 100vh; padding: 1.5rem 1rem;\n }\n .card {\n width: 100%; max-width: 440px;\n background: var(--panel); border: 1px solid var(--border);\n border-radius: 8px; box-shadow: 0 1px 0 rgba(255,255,255,0.04) inset,\n 0 30px 90px rgba(0,0,0,0.5);\n padding: 1.25rem;\n }\n .topbar {\n display: flex; align-items: center; justify-content: space-between;\n gap: 0.75rem; margin-bottom: 1.75rem;\n }\n .brand-lockup {\n display: flex; align-items: center; gap: 0.55rem;\n color: var(--muted); font-size: 0.78rem; font-weight: 600;\n }\n .brand-mark { width: 18px; height: auto; display: block; }\n .app-pill {\n max-width: 50%; border: 1px solid var(--border);\n border-radius: 999px; padding: 0.28rem 0.55rem;\n color: var(--subtle); font-size: 0.72rem; line-height: 1;\n overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n }\n .hero { padding: 0 0.75rem; text-align: center; }\n .flow {\n display: flex; align-items: center; justify-content: center;\n gap: 0; margin: 0 auto 1.1rem; width: fit-content;\n }\n .flow .tile {\n width: 42px; height: 42px; border-radius: 8px;\n display: flex; align-items: center; justify-content: center;\n background: var(--panel-2); border: 1px solid var(--border-strong);\n color: var(--text); flex-shrink: 0;\n }\n .flow-mark { width: 26px; height: auto; display: block; }\n .flow .agent-symbol {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.95rem; font-weight: 700; letter-spacing: -0.04em;\n }\n .flow .conn {\n width: 30px; height: 1px; flex-shrink: 0;\n background: linear-gradient(90deg, transparent, var(--border-strong), transparent);\n background-position: center;\n }\n .eyebrow {\n text-align: center; font-size: 0.72rem; font-weight: 600;\n letter-spacing: 0.08em; text-transform: uppercase;\n color: var(--subtle); margin-bottom: 0.55rem;\n }\n h1 {\n text-align: center; font-size: 1.45rem; font-weight: 680;\n line-height: 1.25; margin-bottom: 0.7rem;\n letter-spacing: -0.01em;\n }\n .identity {\n display: flex; flex-wrap: wrap; align-items: center; justify-content: center;\n gap: 0.25rem 0.45rem; color: var(--subtle); font-size: 0.78rem;\n line-height: 1.35; margin: 0 auto 1.5rem; max-width: 34ch;\n }\n .identity strong { color: var(--muted); font-weight: 600; }\n .device-strip {\n display: flex; align-items: center; justify-content: space-between;\n gap: 0.75rem; border: 1px solid var(--border);\n border-radius: 8px; padding: 0.5rem 0.65rem; margin: 0 0 0.9rem;\n background: var(--panel-soft); color: var(--muted);\n }\n .device-strip .label {\n font-size: 0.76rem; font-weight: 560; color: var(--subtle);\n }\n .device-strip .value {\n font-size: 0.78rem; font-weight: 650;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n letter-spacing: 0.08em; color: var(--muted);\n }\n button {\n cursor: pointer; font: inherit; font-weight: 600; border: none;\n border-radius: 8px; padding: 0.78rem 1rem;\n }\n button:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }\n .primary {\n background: var(--accent); color: var(--accent-fg); width: 100%;\n font-size: 0.95rem;\n }\n .primary:hover:not(:disabled) { background: #e4e4e7; }\n .primary:disabled { opacity: 0.55; cursor: default; }\n .ghost {\n background: transparent; color: var(--muted);\n border: 1px solid var(--border-strong); padding: 0.35rem 0.7rem;\n font-size: 0.78rem; font-weight: 500; border-radius: 8px;\n }\n .ghost:hover:not(:disabled) { color: var(--text); border-color: var(--subtle); }\n pre {\n background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px;\n padding: 0.9rem; font-size: 0.78rem; line-height: 1.5; overflow-x: auto;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n color: #d4d4d8; margin: 0.5rem 0 1rem;\n }\n /* Advanced disclosure */\n .advanced { margin: 0 0 1rem; }\n .advanced > summary {\n list-style: none; cursor: pointer; user-select: none;\n display: flex; align-items: center; justify-content: center; gap: 0.35rem;\n color: var(--subtle); font-size: 0.8rem; font-weight: 500;\n padding: 0.5rem 0; text-align: center;\n }\n .advanced > summary::-webkit-details-marker { display: none; }\n .advanced > summary:hover { color: var(--muted); }\n .advanced > summary:focus-visible { outline: 2px solid var(--ring);\n outline-offset: 2px; border-radius: 6px; }\n .advanced > summary .chev {\n width: 7px; height: 7px; border-right: 1.5px solid currentColor;\n border-bottom: 1.5px solid currentColor; transform: rotate(45deg);\n transition: transform 0.15s ease; margin-top: -3px;\n }\n .advanced[open] > summary .chev { transform: rotate(225deg); margin-top: 2px; }\n .advanced-body {\n padding: 0.85rem 0.1rem 0.25rem;\n }\n .field { margin-bottom: 0.9rem; }\n .field:last-child { margin-bottom: 0; }\n .field label { display: block; font-size: 0.78rem; color: var(--muted);\n margin-bottom: 0.35rem; }\n .field input {\n width: 100%; padding: 0.6rem 0.7rem; font: inherit; color: var(--text);\n background: var(--panel-2); border: 1px solid var(--border-strong);\n border-radius: 8px;\n }\n .field input:focus-visible {\n outline: none; border-color: var(--ring);\n box-shadow: 0 0 0 3px rgba(250,250,250,0.12);\n }\n .connections {\n margin-top: 1.1rem; border-top: 1px solid var(--border);\n padding-top: 0.35rem;\n }\n .connections > summary {\n list-style: none; cursor: pointer; user-select: none;\n display: flex; align-items: center; gap: 0.55rem;\n min-height: 2.2rem; color: var(--muted); font-size: 0.82rem;\n }\n .connections > summary::-webkit-details-marker { display: none; }\n .connections > summary:focus-visible {\n outline: 2px solid var(--ring); outline-offset: 2px; border-radius: 6px;\n }\n .connections-title { font-weight: 600; color: var(--muted); }\n .connections-state {\n margin-left: auto; color: var(--subtle); font-size: 0.73rem;\n border: 1px solid var(--border); border-radius: 999px;\n padding: 0.18rem 0.45rem; line-height: 1;\n }\n .connections .chev {\n width: 7px; height: 7px; border-right: 1.5px solid currentColor;\n border-bottom: 1.5px solid currentColor; transform: rotate(45deg);\n transition: transform 0.15s ease; margin: -3px 0 0 0.15rem;\n }\n .connections[open] .chev { transform: rotate(225deg); margin-top: 2px; }\n .token-list { padding-top: 0.4rem; }\n .tok { display: flex; align-items: center; justify-content: space-between;\n gap: 0.75rem; padding: 0.6rem 0; border-bottom: 1px solid var(--border);\n font-size: 0.83rem; }\n .tok:last-child { border-bottom: none; }\n .tok .meta { color: var(--subtle); font-size: 0.74rem; margin-top: 0.1rem; }\n .tok.revoked { opacity: 0.45; }\n .empty-state {\n color: var(--subtle); font-size: 0.78rem; line-height: 1.45;\n padding: 0.3rem 0 0.45rem;\n }\n .msg { font-size: 0.83rem; padding: 0.7rem 0.8rem; border-radius: 8px;\n margin-bottom: 0.9rem; display: none; line-height: 1.4; }\n .msg.err { display: block; color: var(--error); background: var(--error-bg);\n border: 1px solid rgba(252,165,165,0.16); }\n .msg.ok { display: block; color: var(--ok); background: var(--ok-bg);\n border: 1px solid var(--ok-border); }\n .result-panel { padding-top: 0.15rem; }\n .result-title {\n color: var(--text); font-size: 0.95rem; font-weight: 650;\n text-align: center; margin-bottom: 0.35rem;\n }\n .result-copy {\n color: var(--muted); font-size: 0.83rem; line-height: 1.45;\n text-align: center; margin: 0 auto 0.85rem; max-width: 34ch;\n }\n .section-label {\n color: var(--subtle); font-size: 0.7rem; font-weight: 650;\n letter-spacing: 0.08em; text-transform: uppercase; margin-top: 0.85rem;\n }\n @media (max-width: 480px) {\n body { align-items: flex-start; padding: 0.75rem; }\n .card { padding: 1rem; }\n .hero { padding: 0; }\n .topbar { margin-bottom: 1.35rem; }\n h1 { font-size: 1.3rem; }\n .app-pill { max-width: 46%; }\n pre { font-size: 0.72rem; }\n }\n /* MCP URL display + per-host tabs (the non-dev path). */\n .mcp-url-block { margin: 0 0 1rem; }\n .url-row {\n display: flex; align-items: center; gap: 0.5rem;\n background: var(--panel-2); border: 1px solid var(--border-strong);\n border-radius: 8px; padding: 0.45rem 0.5rem 0.45rem 0.75rem;\n }\n .url-row code {\n flex: 1 1 auto; min-width: 0; overflow-x: auto; white-space: nowrap;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.78rem; color: var(--text);\n }\n .url-row .ghost { flex: 0 0 auto; }\n .hosts { margin: 0 0 1rem; }\n .tabs {\n display: flex; flex-wrap: wrap; gap: 0.25rem;\n border-bottom: 1px solid var(--border); margin-bottom: 0.75rem;\n padding-bottom: 0.4rem;\n }\n .tab {\n background: transparent; color: var(--subtle);\n border: 1px solid transparent;\n padding: 0.35rem 0.65rem; font-size: 0.8rem; font-weight: 600;\n border-radius: 6px;\n }\n .tab:hover { color: var(--muted); background: var(--panel-soft); }\n .tab.is-active {\n color: var(--text); background: var(--panel-2);\n border-color: var(--border-strong);\n }\n .tab-panel { display: none; }\n .tab-panel.is-active { display: block; }\n .tab-panel ol { margin: 0 0 0.6rem 1.1rem; padding: 0; }\n .tab-panel li {\n margin-bottom: 0.3rem; font-size: 0.86rem; line-height: 1.5;\n color: var(--muted);\n }\n .tab-panel li strong { color: var(--text); font-weight: 650; }\n .tab-panel a {\n color: var(--text); text-decoration: underline;\n text-underline-offset: 2px;\n }\n .tab-panel p {\n font-size: 0.84rem; color: var(--muted); margin: 0.4rem 0;\n line-height: 1.5;\n }\n .tab-panel .hint {\n font-size: 0.78rem; color: var(--subtle); margin-top: 0.5rem;\n }\n .tab-panel code {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.78rem; color: var(--text);\n background: var(--panel-2); padding: 0.05rem 0.3rem;\n border-radius: 4px;\n }\n .tab-panel pre { margin: 0.4rem 0 0.5rem; }\n /* Per-tab primary CTA — visually distinct from the static-token mint\n * button below. Either a link (Open Claude →) or a copy command button.\n */\n .primary-link {\n display: inline-flex; align-items: center; justify-content: center;\n gap: 0.35rem; min-height: 36px; padding: 0.45rem 0.85rem;\n background: var(--accent); color: var(--accent-fg);\n border: 1px solid var(--accent); border-radius: 8px;\n font-size: 0.86rem; font-weight: 650; text-decoration: none;\n cursor: pointer; width: 100%; text-align: center;\n margin: 0.5rem 0 0.2rem;\n }\n .primary-link:hover { background: #e4e4e7; border-color: #e4e4e7; }\n .primary-link.compact { width: auto; min-width: 0; }\n .copy-flash {\n color: var(--ok) !important;\n border-color: var(--ok-border) !important;\n }\n .static-token-mint .static-token-body { padding-top: 0.5rem; }\n .static-token-mint > summary .connections-state {\n font-style: normal;\n }\n @media (min-width: 560px) {\n .card { max-width: 580px; }\n }\n .hidden { display: none !important; }\n</style>\n</head>\n<body>\n<div class=\"card\">\n <div class=\"topbar\">\n <div class=\"brand-lockup\">\n ${brandMarkSvg}\n <span>Agent Native</span>\n </div>\n <div class=\"app-pill\" title=\"${safeApp}\">${safeApp}</div>\n </div>\n\n <div class=\"hero\">\n <!-- \"Connect an external agent\" is kept as the accessible consent label. -->\n <div class=\"flow\" role=\"img\" aria-label=\"Connect an external agent to ${safeApp}\">\n <span class=\"tile\" aria-hidden=\"true\">\n ${flowMarkSvg}\n </span>\n <span class=\"conn\" aria-hidden=\"true\"></span>\n <span class=\"tile\" aria-hidden=\"true\">\n <span class=\"agent-symbol\">&lt;/&gt;</span>\n </span>\n </div>\n\n <div class=\"eyebrow\">Connect an external agent</div>\n <h1>${safeUserCode ? `Authorize ${safeApp} from your terminal?` : `Use ${safeApp} from your AI assistant`}</h1>\n <p class=\"identity\">\n <span>Signed in as <strong>${safeEmail}</strong></span>\n </p>\n </div>\n\n <div id=\"codeCallout\" class=\"device-strip ${safeUserCode ? \"\" : \"hidden\"}\">\n <span class=\"label\">Device code</span>\n <span class=\"value\" id=\"userCodeValue\">${safeUserCode}</span>\n </div>\n\n <div class=\"mcp-url-block\">\n <div class=\"section-label\">Your MCP URL</div>\n <div class=\"url-row\">\n <code id=\"mcpUrlValue\">${safeMcpUrl}</code>\n <button type=\"button\" class=\"ghost\" data-copy=\"mcpUrlValue\" aria-label=\"Copy MCP URL\">Copy</button>\n </div>\n </div>\n\n <div class=\"hosts\">\n <div class=\"section-label\">Pick your AI assistant</div>\n <div class=\"tabs\" role=\"tablist\" aria-label=\"Choose your AI assistant\">\n <button type=\"button\" class=\"tab is-active\" role=\"tab\" data-tab=\"claude\" aria-selected=\"true\">Claude</button>\n <button type=\"button\" class=\"tab\" role=\"tab\" data-tab=\"chatgpt\" aria-selected=\"false\">ChatGPT</button>\n <button type=\"button\" class=\"tab\" role=\"tab\" data-tab=\"cursor\" aria-selected=\"false\">Cursor</button>\n <button type=\"button\" class=\"tab\" role=\"tab\" data-tab=\"claude-code\" aria-selected=\"false\">Claude Code</button>\n <button type=\"button\" class=\"tab\" role=\"tab\" data-tab=\"codex\" aria-selected=\"false\">Codex</button>\n <button type=\"button\" class=\"tab\" role=\"tab\" data-tab=\"other\" aria-selected=\"false\">Other</button>\n </div>\n <div class=\"tab-panel is-active\" role=\"tabpanel\" data-panel=\"claude\">\n <ol>\n <li>Open <strong>Customize → Connectors</strong> in Claude.</li>\n <li>Click the <strong>+</strong> button → <strong>Add custom connector</strong>.</li>\n <li>Paste the MCP URL above, name it <strong>${safeApp}</strong>, click <strong>Connect</strong>.</li>\n <li>On the consent page, click <strong>Authorize</strong> to approve <code>mcp:read</code>, <code>mcp:write</code>, <code>mcp:apps</code>.</li>\n </ol>\n <a class=\"primary-link\" href=\"https://claude.ai/customize/connectors\" target=\"_blank\" rel=\"noopener noreferrer\">Open Claude → Connectors</a>\n <p class=\"hint\">Works in Claude web and Claude Desktop. Inline MCP Apps (charts, dashboards, drafts) render automatically inside the chat.</p>\n </div>\n <div class=\"tab-panel\" role=\"tabpanel\" data-panel=\"chatgpt\">\n <ol>\n <li>In ChatGPT, open <strong>Settings → Apps</strong> (Business/Enterprise/Edu workspaces with developer mode enabled).</li>\n <li>Scroll to <strong>Advanced settings → Create app</strong>, paste the MCP URL above, name it <strong>${safeApp}</strong>.</li>\n <li>Click <strong>Connect</strong>, sign in with your Agent-Native account, and approve <code>mcp:read</code>, <code>mcp:write</code>, <code>mcp:apps</code>.</li>\n </ol>\n <a class=\"primary-link\" href=\"https://chatgpt.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Open ChatGPT</a>\n <p class=\"hint\"><strong>Got \"Connector name already exists\" but don't see it under Enabled apps?</strong> ChatGPT saves a hidden draft the moment you click Create — even if you closed the OAuth popup before approving. In <strong>Settings → Apps</strong>, scroll past Enabled apps to the <strong>Drafts</strong> section (\"Private apps you've created in developer mode\"). Click the draft and either press <strong>Connect</strong> to finish OAuth, or use the <strong>⋯ → Delete</strong> menu and re-create. Workspace admins may also need to enable custom connectors under org settings; each member still authorizes their own account.</p>\n </div>\n <div class=\"tab-panel\" role=\"tabpanel\" data-panel=\"cursor\">\n <ol>\n <li>Open <strong>Cursor → Settings → MCP</strong>.</li>\n <li>Click <strong>Add MCP Server</strong>, paste the MCP URL above, save.</li>\n <li>When prompted, sign in with your Agent-Native account and approve the MCP scopes.</li>\n </ol>\n <p class=\"hint\">Cursor supports remote-OAuth MCP servers, same paste-URL flow as Claude — no terminal needed.</p>\n </div>\n <div class=\"tab-panel\" role=\"tabpanel\" data-panel=\"claude-code\">\n <p>In your terminal, run:</p>\n <pre id=\"claudeCodeCmd\">${safeClaudeCodeCmd}</pre>\n <button type=\"button\" class=\"primary-link compact\" data-copy=\"claudeCodeCmd\">Copy command</button>\n <p class=\"hint\">Then inside Claude Code type <code>/mcp</code>, choose <strong>${safeServerId}</strong>, and click <strong>Authenticate</strong>. Claude completes the OAuth flow itself — no static token needed.</p>\n </div>\n <div class=\"tab-panel\" role=\"tabpanel\" data-panel=\"codex\">\n <p>In your terminal, run:</p>\n <pre id=\"codexCmd\">${safeCodexCmd}</pre>\n <button type=\"button\" class=\"primary-link compact\" data-copy=\"codexCmd\">Copy command</button>\n <p class=\"hint\">Opens this page in your browser and writes Codex's <code>~/.codex/config.toml</code> automatically. The same command works for Claude Cowork and Goose.</p>\n </div>\n <div class=\"tab-panel\" role=\"tabpanel\" data-panel=\"other\">\n <p>Any MCP-compatible client with remote-OAuth support: paste the MCP URL above. For clients without OAuth, paste this <code>.mcp.json</code> snippet and generate a static bearer below:</p>\n <pre id=\"genericConfig\">${safeGenericConfig}</pre>\n <button type=\"button\" class=\"primary-link compact\" data-copy=\"genericConfig\">Copy config</button>\n </div>\n </div>\n\n <details id=\"staticTokenMint\" class=\"connections static-token-mint\"${safeUserCode ? \" open\" : \"\"}>\n <summary>\n <span class=\"connections-title\">${safeUserCode ? \"Authorize this device\" : \"Generate a static token\"}</span>\n <span class=\"connections-state\">${safeUserCode ? \"From your terminal\" : \"Advanced — clients without OAuth\"}</span>\n <span class=\"chev\" aria-hidden=\"true\"></span>\n </summary>\n <div class=\"static-token-body\">\n <div id=\"msg\" class=\"msg\"></div>\n <div id=\"mintForm\">\n <button id=\"authorizeBtn\" class=\"primary\">${safeUserCode ? \"Authorize device\" : \"Create connection token\"}</button>\n <details class=\"advanced\">\n <summary>\n Advanced options\n <span class=\"chev\" aria-hidden=\"true\"></span>\n </summary>\n <div class=\"advanced-body\">\n <div class=\"field\">\n <label for=\"label\">Label (optional)</label>\n <input id=\"label\" type=\"text\" placeholder=\"e.g. Claude Code on my laptop\" maxlength=\"120\" />\n </div>\n <div class=\"field\">\n <label for=\"ttl\">Expires in (days, 1–365)</label>\n <input id=\"ttl\" type=\"number\" min=\"1\" max=\"365\" value=\"${DEFAULT_TOKEN_TTL_DAYS}\" />\n </div>\n </div>\n </details>\n </div>\n <div id=\"result\" class=\"result-panel hidden\">\n <div class=\"result-title\">Connection token created</div>\n <p class=\"result-copy\" id=\"resultMsg\">Paste this into your agent's MCP config. The token is shown only once.</p>\n <div class=\"section-label\">MCP config</div>\n <pre id=\"mcpJson\"></pre>\n <details class=\"advanced\">\n <summary>\n Terminal alternative\n <span class=\"chev\" aria-hidden=\"true\"></span>\n </summary>\n <div class=\"advanced-body\">\n <pre id=\"cliLine\"></pre>\n </div>\n </details>\n </div>\n </div>\n </details>\n\n <details id=\"connections\" class=\"connections\">\n <summary>\n <span class=\"connections-title\">Existing connections</span>\n <span id=\"connectionsState\" class=\"connections-state\">Checking</span>\n <span class=\"chev\" aria-hidden=\"true\"></span>\n </summary>\n <div id=\"tokenList\" class=\"token-list\"><div class=\"empty-state\">Checking connections...</div></div>\n </details>\n</div>\n<script>\n(function () {\n var BASE = ${JSON.stringify(joinAppPath(connectBasePath, \"/_agent-native/mcp/connect\"))};\n var USER_CODE = ${JSON.stringify(safeUserCode || null)};\n var msgEl = document.getElementById(\"msg\");\n var connectionsEl = document.getElementById(\"connections\");\n var connectionsStateEl = document.getElementById(\"connectionsState\");\n\n // Tab switching for the per-host instructions block.\n var tabBtns = document.querySelectorAll(\".tabs .tab\");\n var tabPanels = document.querySelectorAll(\".tab-panel\");\n for (var i = 0; i < tabBtns.length; i++) {\n tabBtns[i].addEventListener(\"click\", function (ev) {\n var btn = ev.currentTarget;\n var name = btn.getAttribute(\"data-tab\");\n for (var j = 0; j < tabBtns.length; j++) {\n var active = tabBtns[j] === btn;\n tabBtns[j].classList.toggle(\"is-active\", active);\n tabBtns[j].setAttribute(\"aria-selected\", active ? \"true\" : \"false\");\n }\n for (var k = 0; k < tabPanels.length; k++) {\n tabPanels[k].classList.toggle(\n \"is-active\",\n tabPanels[k].getAttribute(\"data-panel\") === name,\n );\n }\n });\n }\n\n // Copy buttons — any element with data-copy=\"<id>\" copies that node's text.\n document.addEventListener(\"click\", function (ev) {\n var btn = ev.target && ev.target.closest && ev.target.closest(\"[data-copy]\");\n if (!btn) return;\n var node = document.getElementById(btn.getAttribute(\"data-copy\"));\n if (!node || !navigator.clipboard) return;\n navigator.clipboard.writeText(node.textContent || \"\").then(function () {\n var prev = btn.textContent;\n btn.textContent = \"Copied\";\n btn.classList.add(\"copy-flash\");\n setTimeout(function () {\n btn.textContent = prev;\n btn.classList.remove(\"copy-flash\");\n }, 1400);\n });\n });\n function showMsg(text, kind) {\n msgEl.textContent = text;\n msgEl.className = \"msg \" + (kind || \"err\");\n }\n function clearMsg() { msgEl.className = \"msg\"; msgEl.textContent = \"\"; }\n\n function renderResult(data) {\n document.getElementById(\"mintForm\").classList.add(\"hidden\");\n var entry = {};\n entry[data.serverName] = data.mcpServerEntry;\n document.getElementById(\"mcpJson\").textContent =\n JSON.stringify({ mcpServers: entry }, null, 2);\n document.getElementById(\"cliLine\").textContent = data.cli;\n document.getElementById(\"result\").classList.remove(\"hidden\");\n }\n\n async function postJson(path, body) {\n var res = await fetch(BASE + path, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"same-origin\",\n body: JSON.stringify(body || {})\n });\n var data = null;\n try { data = await res.json(); } catch (e) {}\n return { ok: res.ok, status: res.status, data: data };\n }\n\n async function loadTokens() {\n var listEl = document.getElementById(\"tokenList\");\n try {\n var res = await fetch(BASE + \"/tokens\", { credentials: \"same-origin\" });\n if (!res.ok) {\n connectionsStateEl.textContent = \"Unavailable\";\n listEl.innerHTML = '<div class=\"empty-state\">Could not load connections.</div>';\n return;\n }\n var data = await res.json();\n var tokens = (data && data.tokens) || [];\n if (!tokens.length) {\n connectionsStateEl.textContent = \"None\";\n connectionsEl.open = false;\n listEl.innerHTML = '<div class=\"empty-state\">Created connections will appear here for revoking later.</div>';\n return;\n }\n var activeCount = tokens.filter(function (t) { return !t.revokedAt; }).length;\n connectionsStateEl.textContent = activeCount === 1 ? \"1 active\" : activeCount + \" active\";\n listEl.innerHTML = \"\";\n tokens.forEach(function (t) {\n var div = document.createElement(\"div\");\n div.className = \"tok\" + (t.revokedAt ? \" revoked\" : \"\");\n var when = t.createdAt ? new Date(t.createdAt).toLocaleString() : \"\";\n var used = t.lastUsedAt ? \" · last used \" + new Date(t.lastUsedAt).toLocaleString() : \"\";\n var left = document.createElement(\"div\");\n var label = document.createElement(\"div\");\n label.textContent = t.label || \"(unlabeled)\";\n var meta = document.createElement(\"div\");\n meta.className = \"meta\";\n meta.textContent = (t.revokedAt ? \"Revoked · \" : \"Created \") + when + used;\n left.appendChild(label); left.appendChild(meta);\n div.appendChild(left);\n if (!t.revokedAt) {\n var btn = document.createElement(\"button\");\n btn.className = \"ghost\";\n btn.textContent = \"Revoke\";\n btn.onclick = async function () {\n btn.disabled = true;\n var r = await postJson(\"/tokens/revoke\", { id: t.id });\n if (r.ok) { loadTokens(); }\n else { btn.disabled = false; showMsg(\"Could not revoke token.\"); }\n };\n div.appendChild(btn);\n }\n listEl.appendChild(div);\n });\n } catch (e) {\n connectionsStateEl.textContent = \"Unavailable\";\n listEl.innerHTML = '<div class=\"empty-state\">Could not load connections.</div>';\n }\n }\n\n document.getElementById(\"authorizeBtn\").onclick = async function () {\n var btn = this;\n btn.disabled = true;\n clearMsg();\n var label = document.getElementById(\"label\").value || undefined;\n var ttlDays = parseInt(document.getElementById(\"ttl\").value, 10) || undefined;\n try {\n if (USER_CODE) {\n var a = await postJson(\"/device/authorize\", { user_code: USER_CODE });\n if (!a.ok) {\n btn.disabled = false;\n showMsg((a.data && a.data.error) || \"Could not authorize this device code.\");\n return;\n }\n showMsg(\"Device authorized — finishing connection… you can return to your terminal.\", \"ok\");\n btn.classList.add(\"hidden\");\n document.getElementById(\"mintForm\").classList.add(\"hidden\");\n var cc = document.getElementById(\"codeCallout\");\n if (cc) cc.classList.add(\"hidden\");\n // The token is minted a few seconds later, when the CLI next polls\n // /device/poll — so a single loadTokens() here runs BEFORE the row\n // exists and the list would wrongly read \"No connections yet\" until\n // a manual reload. Snapshot the EXISTING non-revoked token ids first\n // so we announce \"Connected\" only when THIS device's freshly-minted\n // token appears — a user who already has tokens must not get a false\n // success the instant they authorize.\n var priorIds = {};\n try {\n var pr = await fetch(BASE + \"/tokens\", { credentials: \"same-origin\" });\n if (pr.ok) {\n var pd = await pr.json();\n ((pd && pd.tokens) || []).forEach(function (t) {\n if (!t.revokedAt) priorIds[t.id] = true;\n });\n }\n } catch (e) {}\n loadTokens();\n var tries = 0;\n var iv = setInterval(async function () {\n tries++;\n try {\n var res = await fetch(BASE + \"/tokens\", { credentials: \"same-origin\" });\n if (res.ok) {\n var data = await res.json();\n var fresh = ((data && data.tokens) || []).filter(function (t) {\n return !t.revokedAt && !priorIds[t.id];\n });\n if (fresh.length > 0) {\n clearInterval(iv);\n showMsg(\"Connected. This device can now act as you — manage or revoke it below.\", \"ok\");\n loadTokens();\n return;\n }\n }\n } catch (e) {}\n if (tries >= 30) {\n // No new token appeared in the window — e.g. the loopback\n // dev-open path writes a header-only config and never mints.\n // Don't claim \"Connected\" (we couldn't confirm a device token);\n // keep the \"authorized\" message and just refresh the list.\n clearInterval(iv);\n loadTokens();\n }\n }, 2000);\n return;\n } else {\n var m = await postJson(\"/token\", { label: label, ttlDays: ttlDays });\n if (!m.ok) {\n btn.disabled = false;\n showMsg((m.data && m.data.error) || \"Could not create token.\");\n return;\n }\n renderResult(m.data);\n }\n loadTokens();\n } catch (e) {\n btn.disabled = false;\n showMsg(\"Network error. Please try again.\");\n }\n };\n\n loadTokens();\n})();\n</script>\n</body>\n</html>`;\n}\n\n// ---------------------------------------------------------------------------\n// Handler — single entry point; core-routes-plugin dispatches the subpath.\n// ---------------------------------------------------------------------------\n\n/**\n * Handle a `/_agent-native/mcp/connect[...]` request. `subpath` is the part\n * after `/connect` (empty string = the page itself, otherwise e.g.\n * `/token`, `/device/start`). The core-routes-plugin computes it from the\n * stripped event path so this module stays mount-agnostic.\n */\nexport async function handleMcpConnect(\n event: H3Event,\n subpath: string,\n options: McpConnectRouteOptions = {},\n): Promise<Response> {\n const method = getMethod(event);\n const origin = deriveOrigin(event);\n const basePath = configuredBasePath();\n const appUrl = `${origin}${basePath}`;\n const sub = (\"/\" + subpath.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\")).replace(\n /^\\/$/,\n \"\",\n );\n\n // ---- The connect page (GET) ------------------------------------------\n if (sub === \"\") {\n if (method !== \"GET\" && method !== \"HEAD\") {\n return json({ error: \"Method not allowed\" }, 405);\n }\n const session = await getSession(event);\n if (!session?.email) {\n // Serve the SAME login form the guard would, at this same URL — the\n // login form reloads window.location so we re-enter here authed.\n const loginHtml = getConfiguredLoginHtml(event);\n if (loginHtml) return html(loginHtml, 200);\n // Fully-open app (no auth guard): nothing to scope a mint to.\n return html(\n renderConnectPage({\n connectBasePath: basePath,\n email: \"(no auth configured)\",\n appName: options.appName || appLabel(appUrl, options),\n appUrl,\n serverId: serverName(appUrl, options),\n userCode: null,\n }),\n );\n }\n let userCode: string | null = null;\n try {\n const u = new URL(\n event.node?.req?.url ?? event.path ?? \"/\",\n \"http://an.invalid\",\n );\n const raw = u.searchParams.get(\"user_code\");\n if (raw && USER_CODE_RE.test(raw)) userCode = raw;\n } catch {\n userCode = null;\n }\n return html(\n renderConnectPage({\n connectBasePath: basePath,\n email: session.email,\n appName: options.appName || appLabel(appUrl, options),\n appUrl,\n serverId: serverName(appUrl, options),\n userCode,\n }),\n );\n }\n\n // ---- POST /token (session-required) ---------------------------------\n if (sub === \"/token\") {\n if (method !== \"POST\") return json({ error: \"Method not allowed\" }, 405);\n const session = await getSession(event);\n if (!session?.email) return json({ error: \"Unauthorized\" }, 401);\n if (!process.env.A2A_SECRET) {\n if (canUseDevOpenConnect(event)) {\n return json(\n mcpResultPayload(appUrl, options, { ownerEmail: session.email }),\n );\n }\n return json(\n {\n error:\n \"This deployment has no A2A_SECRET configured, so connect tokens cannot be minted.\",\n },\n 503,\n );\n }\n const body = ((await readBody(event).catch(() => ({}))) ?? {}) as {\n label?: unknown;\n ttlDays?: unknown;\n };\n const label =\n typeof body.label === \"string\" && body.label.trim()\n ? body.label.trim().slice(0, 120)\n : null;\n const ttlDays = clampTtlDays(body.ttlDays);\n try {\n const { token } = await mintConnectToken({\n email: session.email,\n orgId: session.orgId,\n label,\n ttlDays,\n });\n return json(mcpResultPayload(appUrl, options, { token }));\n } catch {\n return json({ error: \"Failed to mint token.\" }, 500);\n }\n }\n\n // ---- POST /device/start (UNAUTH) ------------------------------------\n if (sub === \"/device/start\") {\n if (method !== \"POST\") return json({ error: \"Method not allowed\" }, 405);\n try {\n const row = await createDeviceCode();\n const verificationUri = `${appUrl}/_agent-native/mcp/connect`;\n return json({\n device_code: row.deviceCode,\n user_code: row.userCode,\n verification_uri: verificationUri,\n verification_uri_complete: `${verificationUri}?user_code=${row.userCode}`,\n interval: DEVICE_POLL_INTERVAL_S,\n expires_in: Math.floor(DEVICE_CODE_TTL_MS / 1000),\n });\n } catch (err: any) {\n if (err?.message === \"RATE_LIMITED\") {\n return json({ error: \"Rate limited. Try again shortly.\" }, 429);\n }\n return json({ error: \"Could not start device flow.\" }, 500);\n }\n }\n\n // ---- POST /device/authorize (session-required) ----------------------\n if (sub === \"/device/authorize\") {\n if (method !== \"POST\") return json({ error: \"Method not allowed\" }, 405);\n const session = await getSession(event);\n if (!session?.email) return json({ error: \"Unauthorized\" }, 401);\n const body = ((await readBody(event).catch(() => ({}))) ?? {}) as {\n user_code?: unknown;\n };\n const userCode =\n typeof body.user_code === \"string\" ? body.user_code.trim() : \"\";\n if (!USER_CODE_RE.test(userCode)) {\n return json({ error: \"Invalid user code.\" }, 400);\n }\n const orgId =\n typeof session.orgId === \"string\" && session.orgId.trim()\n ? session.orgId.trim()\n : null;\n const result = await approveDeviceCode(userCode, session.email, orgId);\n if (result === \"not_found\") {\n return json({ error: \"Unknown device code.\" }, 404);\n }\n if (result === \"expired\") {\n return json({ error: \"This device code has expired.\" }, 410);\n }\n if (result === \"already\") {\n return json({ error: \"This device code was already used.\" }, 409);\n }\n return json({ status: \"approved\" });\n }\n\n // ---- POST /device/poll (UNAUTH) -------------------------------------\n if (sub === \"/device/poll\") {\n if (method !== \"POST\") return json({ error: \"Method not allowed\" }, 405);\n const body = ((await readBody(event).catch(() => ({}))) ?? {}) as {\n device_code?: unknown;\n };\n const deviceCode =\n typeof body.device_code === \"string\" ? body.device_code : \"\";\n if (!deviceCode) return json({ error: \"device_code required\" }, 400);\n const row = await getDeviceCode(deviceCode);\n if (!row) return json({ status: \"not_found\" }, 404);\n if (row.status === \"consumed\") return json({ status: \"consumed\" });\n if (\n row.status === \"expired\" ||\n (row.expiresAt != null && row.expiresAt < Date.now())\n ) {\n if (row.status !== \"expired\") void expireDeviceCode(deviceCode);\n return json({ status: \"expired\" });\n }\n if (\n row.status === \"pending\" ||\n row.status === \"minting\" ||\n !row.ownerEmail\n ) {\n return json({ status: \"pending\" });\n }\n // status === \"approved\" && ownerEmail bound → mint exactly once.\n if (!process.env.A2A_SECRET) {\n if (canUseDevOpenConnect(event)) {\n const consumed = await consumeDeviceCode(\n deviceCode,\n `dev-open-${randomUUID()}`,\n );\n if (!consumed) {\n const fresh = await getDeviceCode(deviceCode);\n if (fresh?.status === \"consumed\") return json({ status: \"consumed\" });\n return json({ status: \"pending\" });\n }\n return json({\n status: \"approved\",\n ...mcpResultPayload(appUrl, options, {\n ownerEmail: row.ownerEmail,\n }),\n });\n }\n return json({ status: \"error\", error: \"A2A_SECRET not configured\" }, 503);\n }\n try {\n const jti = randomUUID();\n // Claim a retryable minting state first. If signing or recording fails,\n // release the row back to approved so the CLI can poll again.\n const claimed = await claimDeviceCodeForMint(deviceCode, jti);\n if (!claimed) {\n const fresh = await getDeviceCode(deviceCode);\n if (fresh?.status === \"consumed\") return json({ status: \"consumed\" });\n return json({ status: \"pending\" });\n }\n let token: string;\n try {\n const orgDomain = await resolveOrgDomain(claimed.orgId ?? undefined);\n token = await signA2AToken(claimed.ownerEmail!, orgDomain, undefined, {\n preferGlobalSecret: true,\n expiresIn: `${DEFAULT_TOKEN_TTL_DAYS}d`,\n extraClaims: { jti, scope: MCP_CONNECT_SCOPE },\n });\n await recordMintedToken({\n jti,\n ownerEmail: claimed.ownerEmail!,\n orgId: claimed.orgId,\n label: \"Device connection\",\n });\n if (!(await finishDeviceCodeMint(deviceCode, jti))) {\n return json({ status: \"pending\" });\n }\n } catch (err) {\n await releaseDeviceCodeMint(deviceCode, jti);\n throw err;\n }\n return json({\n status: \"approved\",\n ...mcpResultPayload(appUrl, options, { token }),\n });\n } catch {\n return json({ status: \"error\", error: \"Failed to mint token.\" }, 500);\n }\n }\n\n // ---- GET /tokens (session-required) ---------------------------------\n if (sub === \"/tokens\") {\n if (method !== \"GET\") return json({ error: \"Method not allowed\" }, 405);\n const session = await getSession(event);\n if (!session?.email) return json({ error: \"Unauthorized\" }, 401);\n const rows = await listTokens(session.email);\n return json({\n tokens: rows.map((r) => ({\n id: r.id,\n label: r.label,\n createdAt: r.createdAt,\n lastUsedAt: r.lastUsedAt,\n revokedAt: r.revokedAt,\n })),\n });\n }\n\n // ---- POST /tokens/revoke (session-required) -------------------------\n if (sub === \"/tokens/revoke\") {\n if (method !== \"POST\") return json({ error: \"Method not allowed\" }, 405);\n const session = await getSession(event);\n if (!session?.email) return json({ error: \"Unauthorized\" }, 401);\n const body = ((await readBody(event).catch(() => ({}))) ?? {}) as {\n id?: unknown;\n };\n const id = typeof body.id === \"string\" ? body.id : \"\";\n if (!id) return json({ error: \"id required\" }, 400);\n const revoked = await revokeToken(session.email, id);\n return json({ ok: revoked });\n }\n\n return json({ error: \"Not found\" }, 404);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAWlC,OAAO,EACL,yBAAyB,EACzB,UAAU,EACV,eAAe,EACf,sBAAsB,EACtB,kBAAkB,EAClB,KAAK,SAAS,EACd,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;AAM3B,OAAO,EACL,yBAAyB,EACzB,UAAU,EACV,eAAe,EACf,sBAAsB,EACtB,kBAAkB,GACnB,CAAC;AACF,YAAY,EAAE,SAAS,EAAE,iBAAiB,EAAE,cAAc,EAAE,CAAC;AA2G7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,SAAS,GAChB,OAAO,CAAC,QAAQ,GAAG,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAAC,CA2H5D;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,QAAQ,CACtB,QAAQ,EAAE,GAAG,EACb,MAAM,EAAE,SAAS,EACjB,WAAW,SAAmB,GAC7B,IAAI,CAYN"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAWlC,OAAO,EACL,yBAAyB,EACzB,UAAU,EACV,eAAe,EACf,sBAAsB,EACtB,kBAAkB,EAClB,KAAK,SAAS,EACd,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;AAM3B,OAAO,EACL,yBAAyB,EACzB,UAAU,EACV,eAAe,EACf,sBAAsB,EACtB,kBAAkB,GACnB,CAAC;AACF,YAAY,EAAE,SAAS,EAAE,iBAAiB,EAAE,cAAc,EAAE,CAAC;AA2H7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,SAAS,GAChB,OAAO,CAAC,QAAQ,GAAG,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAAC,CA4H5D;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,QAAQ,CACtB,QAAQ,EAAE,GAAG,EACb,MAAM,EAAE,SAAS,EACjB,WAAW,SAAmB,GAC7B,IAAI,CAYN"}
@@ -44,6 +44,21 @@ function deriveRequestMeta(event) {
44
44
  : undefined;
45
45
  return { origin, target };
46
46
  }
47
+ function isLoopbackOrigin(origin) {
48
+ if (!origin)
49
+ return false;
50
+ try {
51
+ const hostname = new URL(origin).hostname;
52
+ return (hostname === "localhost" ||
53
+ hostname === "127.0.0.1" ||
54
+ hostname === "::1" ||
55
+ hostname === "[::1]" ||
56
+ hostname.startsWith("127."));
57
+ }
58
+ catch {
59
+ return false;
60
+ }
61
+ }
47
62
  /**
48
63
  * Reconstruct a Web Standard `Request` for the web-standard MCP transport.
49
64
  *
@@ -149,7 +164,7 @@ export async function handleMcpRequest(event, config) {
149
164
  // `fullSurface` would otherwise escalate to the full mutating surface.
150
165
  const requestMeta = deriveRequestMeta(event);
151
166
  const authResult = await verifyAuth(authHeader, ownerEmailHeader, {
152
- allowDevOpen: isLoopbackRequest(event),
167
+ allowDevOpen: isLoopbackRequest(event) && isLoopbackOrigin(requestMeta.origin),
153
168
  resourceUrl: getMcpOAuthResource(event),
154
169
  });
155
170
  if (!authResult.authed) {
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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","sourcesContent":["import type { H3Event } from \"h3\";\nimport { getH3App } from \"../server/framework-request-handler.js\";\nimport {\n defineEventHandler,\n setResponseStatus,\n setResponseHeader,\n getMethod,\n getRequestHeader,\n} from \"h3\";\nimport { readBody } from \"../server/h3-helpers.js\";\nimport { isLoopbackRequest } from \"../server/auth.js\";\nimport {\n createMCPServerForRequest,\n verifyAuth,\n getAccessTokens,\n resolveOrgIdFromDomain,\n buildLinkArtifacts,\n type MCPConfig,\n type MCPCallerIdentity,\n type MCPRequestMeta,\n} from \"./build-server.js\";\nimport { buildMcpOAuthChallenge, getMcpOAuthResource } from \"./oauth-route.js\";\n\n// Re-export the shared MCP server builder + types so the stdio transport and\n// any (future) external importer of `@agent-native/core/mcp` keep resolving\n// against `./server.js` exactly as before this refactor.\nexport {\n createMCPServerForRequest,\n verifyAuth,\n getAccessTokens,\n resolveOrgIdFromDomain,\n buildLinkArtifacts,\n};\nexport type { MCPConfig, MCPCallerIdentity, MCPRequestMeta };\n\n// ---------------------------------------------------------------------------\n// Runtime detection — Node fast-path vs. web-standard fallback\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the underlying Node `http` req/res pair if (and only if) we're\n * running on a real Node HTTP server (local dev, `node` Nitro preset). On the\n * web-standard runtime (Nitro 3 / Netlify web runtime, Cloudflare, Deno, Bun)\n * BOTH of these are undefined — that's the signal to take the web fallback\n * instead of returning 501.\n */\nfunction getNodeReqRes(event: H3Event): {\n nodeReq: any | undefined;\n nodeRes: any | undefined;\n} {\n const e = event as any;\n const nodeReq = e.node?.req ?? e.req?.runtime?.node?.req;\n const nodeRes = e.node?.res ?? e.req?.runtime?.node?.res;\n return { nodeReq, nodeRes };\n}\n\n/**\n * Derive the request origin + the markdown deep-link target from the inbound\n * headers. Identical logic for both the Node and web paths so the absolute\n * deep-link URLs in tool results are computed the same way regardless of\n * runtime.\n */\nfunction deriveRequestMeta(event: H3Event): MCPRequestMeta {\n const forwardedProto = getRequestHeader(event, \"x-forwarded-proto\");\n const host = getRequestHeader(event, \"host\");\n const proto =\n forwardedProto?.split(\",\")[0]?.trim() ||\n (host && /^(localhost|127\\.0\\.0\\.1)(:|$)/.test(host) ? \"http\" : \"https\");\n const origin = host ? `${proto}://${host}` : undefined;\n const targetHeader = getRequestHeader(\n event,\n \"x-agent-native-open-target\",\n )?.toLowerCase();\n const target =\n targetHeader === \"desktop\" ||\n targetHeader === \"terminal\" ||\n targetHeader === \"browser\"\n ? (targetHeader as MCPRequestMeta[\"target\"])\n : undefined;\n return { origin, target };\n}\n\n/**\n * Reconstruct a Web Standard `Request` for the web-standard MCP transport.\n *\n * On the web runtime h3 v2 exposes the real web `Request` as `event.req`; we\n * prefer it (its `method` / `headers` are exactly what the client sent). But\n * the framework middleware rewrites `event.req.url` when it strips a mount\n * prefix, and the transport reads `req.method` + `req.headers` (never the\n * body — we pass that via `parsedBody`), so we always synthesize a clean\n * `Request` with the verified method + a fresh `Headers` copy. The URL is\n * cosmetic for the SDK (it only does `new URL(req.url)` for `requestInfo`),\n * so a best-effort absolute URL derived from the inbound host is sufficient\n * and never throws.\n */\nfunction buildWebRequest(event: H3Event, method: string): Request {\n const src = (event as any).req as Request | undefined;\n\n const headers = new Headers();\n if (src?.headers && typeof src.headers.forEach === \"function\") {\n src.headers.forEach((value, key) => headers.set(key, value));\n } else {\n const rawHeaders = (event as any).node?.req?.headers as\n | Record<string, string | string[] | undefined>\n | undefined;\n if (rawHeaders) {\n for (const [key, value] of Object.entries(rawHeaders)) {\n if (value == null) continue;\n headers.set(key, Array.isArray(value) ? value.join(\", \") : value);\n }\n }\n }\n\n // The SDK requires Accept + Content-Type to advertise both JSON and SSE on\n // a POST. Real MCP clients (Claude Code, `agent-native connect`) always\n // send these; we never inject/alter them — if they're absent the SDK\n // returns its spec-mandated 406/415, identical to the Node path.\n\n const host = headers.get(\"host\") || \"localhost\";\n const forwardedProto = headers.get(\"x-forwarded-proto\");\n const proto =\n forwardedProto?.split(\",\")[0]?.trim() ||\n (/^(localhost|127\\.0\\.0\\.1)(:|$)/.test(host) ? \"http\" : \"https\");\n let url = `${proto}://${host}/_agent-native/mcp`;\n try {\n if (src?.url) url = new URL(src.url).href;\n } catch {\n // keep the synthesized URL\n }\n\n // No body here on purpose: the JSON-RPC payload is forwarded via the\n // transport's `parsedBody` option (the same mechanism the Node transport\n // uses), so the request stream is never read twice.\n return new Request(url, { method, headers });\n}\n\n// ---------------------------------------------------------------------------\n// handleMcpRequest — runtime-agnostic MCP request handler\n// ---------------------------------------------------------------------------\n\n/**\n * Handle a single `{routePrefix}/mcp` request on either runtime.\n *\n * - **Node fast-path** (real Node HTTP server): unchanged — delegate to the\n * SDK's `StreamableHTTPServerTransport.handleRequest(nodeReq, nodeRes,\n * body)`, which writes directly to the Node response (full protocol incl.\n * SSE).\n * - **Web-standard fallback** (Nitro 3 / Netlify web runtime, Cloudflare,\n * Deno, Bun — where there is no Node req/res): build the SAME MCP `Server`\n * from the SAME config + identity, drive it through the SDK's\n * `WebStandardStreamableHTTPServerTransport` (which the Node transport is\n * itself just a thin wrapper around), and return the resulting Web\n * `Response` as a normal h3 return value.\n *\n * Auth, the `runWithRequestContext` identity wrap, the deep-link `_meta` /\n * markdown append, `requestMeta` origin/target derivation and the stateless\n * semantics are IDENTICAL on both paths because both build the same server\n * via `createMCPServerForRequest` and both transports funnel into the same\n * `WebStandardStreamableHTTPServerTransport.handleRequest(webRequest, {\n * parsedBody })` with the same options.\n *\n * Returns:\n * - `undefined` when the request targets a sub-route (so management/status\n * routes mounted under `/_agent-native/mcp/*` handle it themselves) — the\n * h3 mount falls through to the next handler.\n * - a Web `Response` (web fallback) or a string/object (Node path /\n * auth-error path) otherwise. The Node path also sets `_handled` so h3\n * doesn't double-write.\n */\nexport async function handleMcpRequest(\n event: H3Event,\n config: MCPConfig,\n): Promise<Response | string | { error: string } | undefined> {\n const pathname = event.url?.pathname || \"/\";\n const subpath = pathname.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n if (subpath) {\n // Let management/status routes mounted under /_agent-native/mcp/* handle\n // their own requests instead of treating them as MCP protocol traffic.\n return undefined;\n }\n\n const method = getMethod(event);\n\n // Auth check — extracts the caller's identity from the JWT (`sub`), or, on\n // the static-token / dev-open path, from the forwarded\n // `X-Agent-Native-Owner-Email` hint the stdio proxy sends (the\n // `agent-native mcp install` flow). Without this the install flow would run\n // every tool unscoped (userEmail === undefined).\n const authHeader = getRequestHeader(event, \"authorization\");\n const ownerEmailHeader = getRequestHeader(\n event,\n \"x-agent-native-owner-email\",\n );\n // Gate header-only dev-open on the REAL socket peer, never a parsed\n // `Host` header (client-controlled — an attacker could send\n // `Host: localhost`). A deployed app missing A2A_SECRET / ACCESS_TOKEN\n // must fail closed rather than trust a spoofable owner-email header that\n // `fullSurface` would otherwise escalate to the full mutating surface.\n const requestMeta = deriveRequestMeta(event);\n const authResult = await verifyAuth(authHeader, ownerEmailHeader, {\n allowDevOpen: isLoopbackRequest(event),\n resourceUrl: getMcpOAuthResource(event),\n });\n if (!authResult.authed) {\n setResponseStatus(event, 401);\n setResponseHeader(event, \"WWW-Authenticate\", buildMcpOAuthChallenge(event));\n return { error: \"Unauthorized\" };\n }\n\n // Stateless mode: only POST is meaningful\n if (method === \"DELETE\") {\n setResponseStatus(event, 204);\n return \"\";\n }\n\n if (method !== \"POST\" && method !== \"GET\") {\n setResponseStatus(event, 405);\n return { error: \"Method not allowed\" };\n }\n\n // Read body for POST (GET has no body). Read it via the h3 helper exactly\n // once; both transports accept it as a pre-parsed body so the request\n // stream is never consumed twice.\n const body = method === \"POST\" ? await readBody(event) : undefined;\n\n // Per-request stateless transport + server. Both runtimes build the SAME\n // server from the SAME config + verified identity + request meta, so\n // tools/list, tools/call, and the deep-link `_meta` are identical. A\n // connected real caller (connect-minted token / `mcp install` /\n // ACCESS_TOKEN / production) gets the full action surface even in local\n // dev; unauthenticated dev probes stay sparse. See `external-agents` skill.\n const server = await createMCPServerForRequest(config, authResult.identity, {\n ...requestMeta,\n fullSurface: authResult.fullSurface === true,\n });\n\n const { nodeReq, nodeRes } = getNodeReqRes(event);\n\n if (nodeReq && nodeRes) {\n // ---- Node fast-path (UNCHANGED behavior) --------------------------------\n const { StreamableHTTPServerTransport } =\n await import(\"@modelcontextprotocol/sdk/server/streamableHttp.js\");\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: undefined, // stateless\n });\n await server.connect(transport);\n try {\n // The SDK transport writes directly to the Node response. Node-only by\n // construction; we only reach here when real Node req/res exist.\n await transport.handleRequest(nodeReq, nodeRes, body);\n } catch (err: any) {\n // The SDK transport writes directly to the Node response. If the socket\n // is already closed/ended (client disconnected, or the host stream\n // layer also flushed), Node throws ERR_STREAM_WRITE_AFTER_END *after*\n // the MCP payload was already delivered correctly. Swallow that benign\n // post-flush write so an external agent disconnecting mid-stream can\n // never take down the server process; rethrow anything else.\n if (err?.code !== \"ERR_STREAM_WRITE_AFTER_END\") throw err;\n if (process.env.DEBUG)\n console.log(\n \"[mcp] ignored post-flush ERR_STREAM_WRITE_AFTER_END (client disconnected)\",\n );\n }\n // Prevent H3 from double-writing the response\n (event as any)._handled = true;\n return undefined;\n }\n\n // ---- Web-standard fallback (Nitro 3 / Netlify web runtime, CF, Deno,\n // Bun) ---------------------------------------------------------------------\n //\n // `StreamableHTTPServerTransport` is itself just a thin wrapper that\n // converts the Node req/res to a web Request/Response and delegates to\n // `WebStandardStreamableHTTPServerTransport.handleRequest(webRequest, {\n // parsedBody })`. Using the web transport directly with the SAME options +\n // the same pre-read `parsedBody` produces byte-identical protocol output\n // (including the deep-link `_meta` built inside createMCPServerForRequest),\n // and works on every web runtime because it returns a Web `Response`\n // (JSON for request/response, or an SSE `ReadableStream` body which h3\n // streams natively).\n const { WebStandardStreamableHTTPServerTransport } =\n await import(\"@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js\");\n const transport = new WebStandardStreamableHTTPServerTransport({\n sessionIdGenerator: undefined, // stateless — same as the Node path\n });\n await server.connect(transport);\n const webRequest = buildWebRequest(event, method);\n // `parsedBody: undefined` would make the SDK try to read `req.json()`; our\n // synthesized request has no body, so only pass the option for POST (where\n // we actually have a parsed body). For GET the transport reads no body.\n const response = await transport.handleRequest(\n webRequest,\n method === \"POST\" ? { parsedBody: body } : undefined,\n );\n return response;\n}\n\n// ---------------------------------------------------------------------------\n// mountMCP — register MCP Streamable HTTP endpoint on H3/Nitro\n// ---------------------------------------------------------------------------\n\n/**\n * Mount an MCP remote server on an H3/Nitro app.\n *\n * Endpoint: `{routePrefix}/mcp` (default `/_agent-native/mcp`)\n *\n * Uses stateless Streamable HTTP transport — no in-memory sessions,\n * compatible with serverless deployments. Runtime-agnostic: a real Node\n * server uses the SDK's Node transport; the web-standard runtime (Nitro 3 /\n * Netlify web runtime, Cloudflare, Deno, Bun) uses the SDK's web-standard\n * transport. Both build the same server and produce identical JSON-RPC\n * output.\n *\n * Auth: Bearer token matching ACCESS_TOKEN/ACCESS_TOKENS or JWT via A2A_SECRET.\n * No auth required when neither is configured (dev mode).\n */\nexport function mountMCP(\n nitroApp: any,\n config: MCPConfig,\n routePrefix = \"/_agent-native\",\n): void {\n getH3App(nitroApp).use(\n `${routePrefix}/mcp`,\n defineEventHandler(async (event) => {\n return handleMcpRequest(event as H3Event, config);\n }),\n );\n\n if (process.env.DEBUG)\n console.log(\n `[mcp] Mounted MCP server at ${routePrefix}/mcp (${Object.keys(config.actions).length} tools${config.askAgent ? \" + ask-agent\" : \"\"})`,\n );\n}\n"]}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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","sourcesContent":["import type { H3Event } from \"h3\";\nimport { getH3App } from \"../server/framework-request-handler.js\";\nimport {\n defineEventHandler,\n setResponseStatus,\n setResponseHeader,\n getMethod,\n getRequestHeader,\n} from \"h3\";\nimport { readBody } from \"../server/h3-helpers.js\";\nimport { isLoopbackRequest } from \"../server/auth.js\";\nimport {\n createMCPServerForRequest,\n verifyAuth,\n getAccessTokens,\n resolveOrgIdFromDomain,\n buildLinkArtifacts,\n type MCPConfig,\n type MCPCallerIdentity,\n type MCPRequestMeta,\n} from \"./build-server.js\";\nimport { buildMcpOAuthChallenge, getMcpOAuthResource } from \"./oauth-route.js\";\n\n// Re-export the shared MCP server builder + types so the stdio transport and\n// any (future) external importer of `@agent-native/core/mcp` keep resolving\n// against `./server.js` exactly as before this refactor.\nexport {\n createMCPServerForRequest,\n verifyAuth,\n getAccessTokens,\n resolveOrgIdFromDomain,\n buildLinkArtifacts,\n};\nexport type { MCPConfig, MCPCallerIdentity, MCPRequestMeta };\n\n// ---------------------------------------------------------------------------\n// Runtime detection — Node fast-path vs. web-standard fallback\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the underlying Node `http` req/res pair if (and only if) we're\n * running on a real Node HTTP server (local dev, `node` Nitro preset). On the\n * web-standard runtime (Nitro 3 / Netlify web runtime, Cloudflare, Deno, Bun)\n * BOTH of these are undefined — that's the signal to take the web fallback\n * instead of returning 501.\n */\nfunction getNodeReqRes(event: H3Event): {\n nodeReq: any | undefined;\n nodeRes: any | undefined;\n} {\n const e = event as any;\n const nodeReq = e.node?.req ?? e.req?.runtime?.node?.req;\n const nodeRes = e.node?.res ?? e.req?.runtime?.node?.res;\n return { nodeReq, nodeRes };\n}\n\n/**\n * Derive the request origin + the markdown deep-link target from the inbound\n * headers. Identical logic for both the Node and web paths so the absolute\n * deep-link URLs in tool results are computed the same way regardless of\n * runtime.\n */\nfunction deriveRequestMeta(event: H3Event): MCPRequestMeta {\n const forwardedProto = getRequestHeader(event, \"x-forwarded-proto\");\n const host = getRequestHeader(event, \"host\");\n const proto =\n forwardedProto?.split(\",\")[0]?.trim() ||\n (host && /^(localhost|127\\.0\\.0\\.1)(:|$)/.test(host) ? \"http\" : \"https\");\n const origin = host ? `${proto}://${host}` : undefined;\n const targetHeader = getRequestHeader(\n event,\n \"x-agent-native-open-target\",\n )?.toLowerCase();\n const target =\n targetHeader === \"desktop\" ||\n targetHeader === \"terminal\" ||\n targetHeader === \"browser\"\n ? (targetHeader as MCPRequestMeta[\"target\"])\n : undefined;\n return { origin, target };\n}\n\nfunction isLoopbackOrigin(origin: string | undefined): boolean {\n if (!origin) return false;\n try {\n const hostname = new URL(origin).hostname;\n return (\n hostname === \"localhost\" ||\n hostname === \"127.0.0.1\" ||\n hostname === \"::1\" ||\n hostname === \"[::1]\" ||\n hostname.startsWith(\"127.\")\n );\n } catch {\n return false;\n }\n}\n\n/**\n * Reconstruct a Web Standard `Request` for the web-standard MCP transport.\n *\n * On the web runtime h3 v2 exposes the real web `Request` as `event.req`; we\n * prefer it (its `method` / `headers` are exactly what the client sent). But\n * the framework middleware rewrites `event.req.url` when it strips a mount\n * prefix, and the transport reads `req.method` + `req.headers` (never the\n * body — we pass that via `parsedBody`), so we always synthesize a clean\n * `Request` with the verified method + a fresh `Headers` copy. The URL is\n * cosmetic for the SDK (it only does `new URL(req.url)` for `requestInfo`),\n * so a best-effort absolute URL derived from the inbound host is sufficient\n * and never throws.\n */\nfunction buildWebRequest(event: H3Event, method: string): Request {\n const src = (event as any).req as Request | undefined;\n\n const headers = new Headers();\n if (src?.headers && typeof src.headers.forEach === \"function\") {\n src.headers.forEach((value, key) => headers.set(key, value));\n } else {\n const rawHeaders = (event as any).node?.req?.headers as\n | Record<string, string | string[] | undefined>\n | undefined;\n if (rawHeaders) {\n for (const [key, value] of Object.entries(rawHeaders)) {\n if (value == null) continue;\n headers.set(key, Array.isArray(value) ? value.join(\", \") : value);\n }\n }\n }\n\n // The SDK requires Accept + Content-Type to advertise both JSON and SSE on\n // a POST. Real MCP clients (Claude Code, `agent-native connect`) always\n // send these; we never inject/alter them — if they're absent the SDK\n // returns its spec-mandated 406/415, identical to the Node path.\n\n const host = headers.get(\"host\") || \"localhost\";\n const forwardedProto = headers.get(\"x-forwarded-proto\");\n const proto =\n forwardedProto?.split(\",\")[0]?.trim() ||\n (/^(localhost|127\\.0\\.0\\.1)(:|$)/.test(host) ? \"http\" : \"https\");\n let url = `${proto}://${host}/_agent-native/mcp`;\n try {\n if (src?.url) url = new URL(src.url).href;\n } catch {\n // keep the synthesized URL\n }\n\n // No body here on purpose: the JSON-RPC payload is forwarded via the\n // transport's `parsedBody` option (the same mechanism the Node transport\n // uses), so the request stream is never read twice.\n return new Request(url, { method, headers });\n}\n\n// ---------------------------------------------------------------------------\n// handleMcpRequest — runtime-agnostic MCP request handler\n// ---------------------------------------------------------------------------\n\n/**\n * Handle a single `{routePrefix}/mcp` request on either runtime.\n *\n * - **Node fast-path** (real Node HTTP server): unchanged — delegate to the\n * SDK's `StreamableHTTPServerTransport.handleRequest(nodeReq, nodeRes,\n * body)`, which writes directly to the Node response (full protocol incl.\n * SSE).\n * - **Web-standard fallback** (Nitro 3 / Netlify web runtime, Cloudflare,\n * Deno, Bun — where there is no Node req/res): build the SAME MCP `Server`\n * from the SAME config + identity, drive it through the SDK's\n * `WebStandardStreamableHTTPServerTransport` (which the Node transport is\n * itself just a thin wrapper around), and return the resulting Web\n * `Response` as a normal h3 return value.\n *\n * Auth, the `runWithRequestContext` identity wrap, the deep-link `_meta` /\n * markdown append, `requestMeta` origin/target derivation and the stateless\n * semantics are IDENTICAL on both paths because both build the same server\n * via `createMCPServerForRequest` and both transports funnel into the same\n * `WebStandardStreamableHTTPServerTransport.handleRequest(webRequest, {\n * parsedBody })` with the same options.\n *\n * Returns:\n * - `undefined` when the request targets a sub-route (so management/status\n * routes mounted under `/_agent-native/mcp/*` handle it themselves) — the\n * h3 mount falls through to the next handler.\n * - a Web `Response` (web fallback) or a string/object (Node path /\n * auth-error path) otherwise. The Node path also sets `_handled` so h3\n * doesn't double-write.\n */\nexport async function handleMcpRequest(\n event: H3Event,\n config: MCPConfig,\n): Promise<Response | string | { error: string } | undefined> {\n const pathname = event.url?.pathname || \"/\";\n const subpath = pathname.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n if (subpath) {\n // Let management/status routes mounted under /_agent-native/mcp/* handle\n // their own requests instead of treating them as MCP protocol traffic.\n return undefined;\n }\n\n const method = getMethod(event);\n\n // Auth check — extracts the caller's identity from the JWT (`sub`), or, on\n // the static-token / dev-open path, from the forwarded\n // `X-Agent-Native-Owner-Email` hint the stdio proxy sends (the\n // `agent-native mcp install` flow). Without this the install flow would run\n // every tool unscoped (userEmail === undefined).\n const authHeader = getRequestHeader(event, \"authorization\");\n const ownerEmailHeader = getRequestHeader(\n event,\n \"x-agent-native-owner-email\",\n );\n // Gate header-only dev-open on the REAL socket peer, never a parsed\n // `Host` header (client-controlled — an attacker could send\n // `Host: localhost`). A deployed app missing A2A_SECRET / ACCESS_TOKEN\n // must fail closed rather than trust a spoofable owner-email header that\n // `fullSurface` would otherwise escalate to the full mutating surface.\n const requestMeta = deriveRequestMeta(event);\n const authResult = await verifyAuth(authHeader, ownerEmailHeader, {\n allowDevOpen:\n isLoopbackRequest(event) && isLoopbackOrigin(requestMeta.origin),\n resourceUrl: getMcpOAuthResource(event),\n });\n if (!authResult.authed) {\n setResponseStatus(event, 401);\n setResponseHeader(event, \"WWW-Authenticate\", buildMcpOAuthChallenge(event));\n return { error: \"Unauthorized\" };\n }\n\n // Stateless mode: only POST is meaningful\n if (method === \"DELETE\") {\n setResponseStatus(event, 204);\n return \"\";\n }\n\n if (method !== \"POST\" && method !== \"GET\") {\n setResponseStatus(event, 405);\n return { error: \"Method not allowed\" };\n }\n\n // Read body for POST (GET has no body). Read it via the h3 helper exactly\n // once; both transports accept it as a pre-parsed body so the request\n // stream is never consumed twice.\n const body = method === \"POST\" ? await readBody(event) : undefined;\n\n // Per-request stateless transport + server. Both runtimes build the SAME\n // server from the SAME config + verified identity + request meta, so\n // tools/list, tools/call, and the deep-link `_meta` are identical. A\n // connected real caller (connect-minted token / `mcp install` /\n // ACCESS_TOKEN / production) gets the full action surface even in local\n // dev; unauthenticated dev probes stay sparse. See `external-agents` skill.\n const server = await createMCPServerForRequest(config, authResult.identity, {\n ...requestMeta,\n fullSurface: authResult.fullSurface === true,\n });\n\n const { nodeReq, nodeRes } = getNodeReqRes(event);\n\n if (nodeReq && nodeRes) {\n // ---- Node fast-path (UNCHANGED behavior) --------------------------------\n const { StreamableHTTPServerTransport } =\n await import(\"@modelcontextprotocol/sdk/server/streamableHttp.js\");\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: undefined, // stateless\n });\n await server.connect(transport);\n try {\n // The SDK transport writes directly to the Node response. Node-only by\n // construction; we only reach here when real Node req/res exist.\n await transport.handleRequest(nodeReq, nodeRes, body);\n } catch (err: any) {\n // The SDK transport writes directly to the Node response. If the socket\n // is already closed/ended (client disconnected, or the host stream\n // layer also flushed), Node throws ERR_STREAM_WRITE_AFTER_END *after*\n // the MCP payload was already delivered correctly. Swallow that benign\n // post-flush write so an external agent disconnecting mid-stream can\n // never take down the server process; rethrow anything else.\n if (err?.code !== \"ERR_STREAM_WRITE_AFTER_END\") throw err;\n if (process.env.DEBUG)\n console.log(\n \"[mcp] ignored post-flush ERR_STREAM_WRITE_AFTER_END (client disconnected)\",\n );\n }\n // Prevent H3 from double-writing the response\n (event as any)._handled = true;\n return undefined;\n }\n\n // ---- Web-standard fallback (Nitro 3 / Netlify web runtime, CF, Deno,\n // Bun) ---------------------------------------------------------------------\n //\n // `StreamableHTTPServerTransport` is itself just a thin wrapper that\n // converts the Node req/res to a web Request/Response and delegates to\n // `WebStandardStreamableHTTPServerTransport.handleRequest(webRequest, {\n // parsedBody })`. Using the web transport directly with the SAME options +\n // the same pre-read `parsedBody` produces byte-identical protocol output\n // (including the deep-link `_meta` built inside createMCPServerForRequest),\n // and works on every web runtime because it returns a Web `Response`\n // (JSON for request/response, or an SSE `ReadableStream` body which h3\n // streams natively).\n const { WebStandardStreamableHTTPServerTransport } =\n await import(\"@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js\");\n const transport = new WebStandardStreamableHTTPServerTransport({\n sessionIdGenerator: undefined, // stateless — same as the Node path\n });\n await server.connect(transport);\n const webRequest = buildWebRequest(event, method);\n // `parsedBody: undefined` would make the SDK try to read `req.json()`; our\n // synthesized request has no body, so only pass the option for POST (where\n // we actually have a parsed body). For GET the transport reads no body.\n const response = await transport.handleRequest(\n webRequest,\n method === \"POST\" ? { parsedBody: body } : undefined,\n );\n return response;\n}\n\n// ---------------------------------------------------------------------------\n// mountMCP — register MCP Streamable HTTP endpoint on H3/Nitro\n// ---------------------------------------------------------------------------\n\n/**\n * Mount an MCP remote server on an H3/Nitro app.\n *\n * Endpoint: `{routePrefix}/mcp` (default `/_agent-native/mcp`)\n *\n * Uses stateless Streamable HTTP transport — no in-memory sessions,\n * compatible with serverless deployments. Runtime-agnostic: a real Node\n * server uses the SDK's Node transport; the web-standard runtime (Nitro 3 /\n * Netlify web runtime, Cloudflare, Deno, Bun) uses the SDK's web-standard\n * transport. Both build the same server and produce identical JSON-RPC\n * output.\n *\n * Auth: Bearer token matching ACCESS_TOKEN/ACCESS_TOKENS or JWT via A2A_SECRET.\n * No auth required when neither is configured (dev mode).\n */\nexport function mountMCP(\n nitroApp: any,\n config: MCPConfig,\n routePrefix = \"/_agent-native\",\n): void {\n getH3App(nitroApp).use(\n `${routePrefix}/mcp`,\n defineEventHandler(async (event) => {\n return handleMcpRequest(event as H3Event, config);\n }),\n );\n\n if (process.env.DEBUG)\n console.log(\n `[mcp] Mounted MCP server at ${routePrefix}/mcp (${Object.keys(config.actions).length} tools${config.askAgent ? \" + ask-agent\" : \"\"})`,\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"better-auth-instance.d.ts","sourceRoot":"","sources":["../../src/server/better-auth-instance.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"better-auth-instance.d.ts","sourceRoot":"","sources":["../../src/server/better-auth-instance.ts"],"names":[],"mappings":"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"}
@@ -21,7 +21,7 @@ import { saveOAuthTokens } from "../oauth-tokens/store.js";
21
21
  import { identify, track } from "../tracking/index.js";
22
22
  import { resolveAuthCookieNamespace } from "./cookie-namespace.js";
23
23
  import { getWorkspaceA2ADerivedSecret } from "./derived-secret.js";
24
- import { getDialect, getDatabaseUrl, getDatabaseAuthToken, pgPoolOptions, neonPoolMax, } from "../db/client.js";
24
+ import { getDialect, getDatabaseUrl, getDatabaseAuthToken, pgPoolOptions, neonPoolMax, attachNeonPoolErrorLogger, } from "../db/client.js";
25
25
  import { pgTable, text as pgText, timestamp as pgTimestamp, boolean as pgBoolean, } from "drizzle-orm/pg-core";
26
26
  import { sqliteTable, text as sqliteText, integer as sqliteInteger, } from "drizzle-orm/sqlite-core";
27
27
  // ---------------------------------------------------------------------------
@@ -739,6 +739,7 @@ async function buildDatabaseConfig(dialect) {
739
739
  connectionString: url,
740
740
  max: neonPoolMax(),
741
741
  });
742
+ attachNeonPoolErrorLogger(_neonAuthPool, "db/neon-auth");
742
743
  const { drizzle } = await import("drizzle-orm/neon-serverless");
743
744
  const db = drizzle(_neonAuthPool, { schema: pgAuthSchema });
744
745
  const { drizzleAdapter } = await import("better-auth/adapters/drizzle");