@agent-native/core 0.22.18 → 0.22.20

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 (72) hide show
  1. package/dist/client/agent-chat.d.ts.map +1 -1
  2. package/dist/client/agent-chat.js +19 -6
  3. package/dist/client/agent-chat.js.map +1 -1
  4. package/dist/client/embed-auth.d.ts +5 -0
  5. package/dist/client/embed-auth.d.ts.map +1 -1
  6. package/dist/client/embed-auth.js +181 -12
  7. package/dist/client/embed-auth.js.map +1 -1
  8. package/dist/client/index.d.ts +1 -1
  9. package/dist/client/index.d.ts.map +1 -1
  10. package/dist/client/index.js +1 -1
  11. package/dist/client/index.js.map +1 -1
  12. package/dist/client/mcp-app-host.d.ts +5 -0
  13. package/dist/client/mcp-app-host.d.ts.map +1 -1
  14. package/dist/client/mcp-app-host.js +267 -2
  15. package/dist/client/mcp-app-host.js.map +1 -1
  16. package/dist/client/mcp-apps/McpAppRenderer.d.ts +3 -0
  17. package/dist/client/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  18. package/dist/client/mcp-apps/McpAppRenderer.js +86 -9
  19. package/dist/client/mcp-apps/McpAppRenderer.js.map +1 -1
  20. package/dist/client/theme.js +1 -1
  21. package/dist/client/theme.js.map +1 -1
  22. package/dist/client/vite-dev-recovery-script.d.ts.map +1 -1
  23. package/dist/client/vite-dev-recovery-script.js +9 -0
  24. package/dist/client/vite-dev-recovery-script.js.map +1 -1
  25. package/dist/deploy/build.d.ts.map +1 -1
  26. package/dist/deploy/build.js +73 -5
  27. package/dist/deploy/build.js.map +1 -1
  28. package/dist/index.browser.d.ts +1 -1
  29. package/dist/index.browser.d.ts.map +1 -1
  30. package/dist/index.browser.js +1 -1
  31. package/dist/index.browser.js.map +1 -1
  32. package/dist/mcp/build-server.d.ts.map +1 -1
  33. package/dist/mcp/build-server.js +73 -8
  34. package/dist/mcp/build-server.js.map +1 -1
  35. package/dist/mcp/builtin-tools.d.ts.map +1 -1
  36. package/dist/mcp/builtin-tools.js +6 -3
  37. package/dist/mcp/builtin-tools.js.map +1 -1
  38. package/dist/mcp/embed-app.d.ts +2 -2
  39. package/dist/mcp/embed-app.d.ts.map +1 -1
  40. package/dist/mcp/embed-app.js +426 -23
  41. package/dist/mcp/embed-app.js.map +1 -1
  42. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  43. package/dist/server/core-routes-plugin.js +37 -10
  44. package/dist/server/core-routes-plugin.js.map +1 -1
  45. package/dist/server/create-server.d.ts.map +1 -1
  46. package/dist/server/create-server.js +21 -7
  47. package/dist/server/create-server.js.map +1 -1
  48. package/dist/server/embed-route.d.ts.map +1 -1
  49. package/dist/server/embed-route.js +62 -21
  50. package/dist/server/embed-route.js.map +1 -1
  51. package/dist/server/open-route.d.ts.map +1 -1
  52. package/dist/server/open-route.js +7 -2
  53. package/dist/server/open-route.js.map +1 -1
  54. package/dist/server/security-headers.d.ts.map +1 -1
  55. package/dist/server/security-headers.js +9 -1
  56. package/dist/server/security-headers.js.map +1 -1
  57. package/dist/server/ssr-handler.d.ts +2 -0
  58. package/dist/server/ssr-handler.d.ts.map +1 -1
  59. package/dist/server/ssr-handler.js +66 -11
  60. package/dist/server/ssr-handler.js.map +1 -1
  61. package/dist/shared/mcp-embed-headers.d.ts +12 -0
  62. package/dist/shared/mcp-embed-headers.d.ts.map +1 -0
  63. package/dist/shared/mcp-embed-headers.js +51 -0
  64. package/dist/shared/mcp-embed-headers.js.map +1 -0
  65. package/dist/vite/client.d.ts.map +1 -1
  66. package/dist/vite/client.js +23 -0
  67. package/dist/vite/client.js.map +1 -1
  68. package/docs/content/actions.md +15 -5
  69. package/docs/content/client.md +6 -3
  70. package/docs/content/external-agents.md +61 -41
  71. package/docs/content/mcp-protocol.md +42 -7
  72. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"create-server.d.ts","sourceRoot":"","sources":["../../src/server/create-server.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,YAAY,EAOb,MAAM,IAAI,CAAC;AAUZ,MAAM,WAAW,YAAY;IAC3B,8DAA8D;IAC9D,GAAG,EAAE,MAAM,CAAC;IACZ,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,gFAAgF;IAChF,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,KAAK,CAAC;IACvC,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,uHAAuH;IACvH,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;CAC1B;AA0BD;;GAEG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,KAAK,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,GAC1C,OAAO,CAAC,IAAI,CAAC,CAoDf;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;IAClC,MAAM,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;CACzC;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,OAAO,GAAE,mBAAwB,GAChC,kBAAkB,CA8KpB"}
1
+ {"version":3,"file":"create-server.d.ts","sourceRoot":"","sources":["../../src/server/create-server.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,YAAY,EAOb,MAAM,IAAI,CAAC;AAgBZ,MAAM,WAAW,YAAY;IAC3B,8DAA8D;IAC9D,GAAG,EAAE,MAAM,CAAC;IACZ,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,gFAAgF;IAChF,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,KAAK,CAAC;IACvC,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,uHAAuH;IACvH,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;CAC1B;AA0BD;;GAEG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,KAAK,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,GAC1C,OAAO,CAAC,IAAI,CAAC,CAoDf;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;IAClC,MAAM,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;CACzC;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,OAAO,GAAE,mBAAwB,GAChC,kBAAkB,CAiMpB"}
@@ -4,6 +4,8 @@ import { agentEnv } from "../shared/agent-env.js";
4
4
  import { readBody } from "../server/h3-helpers.js";
5
5
  import { getAllowedCorsOrigin, readCorsAllowedOrigins, } from "./cors-origins.js";
6
6
  import { isEnvVarWriteAllowed } from "./env-var-writes.js";
7
+ import { EMBED_TARGET_HEADER } from "../shared/embed-auth.js";
8
+ import { isMcpEmbedCorsOrigin, MCP_EMBED_CORS_ALLOW_HEADERS, shouldAllowMcpEmbedCredentials, } from "../shared/mcp-embed-headers.js";
7
9
  /**
8
10
  * Parse a .env file into key-value pairs, preserving comments and empty lines for roundtrip.
9
11
  */
@@ -112,6 +114,14 @@ export function createServer(options = {}) {
112
114
  app.use(defineEventHandler((event) => {
113
115
  const requestOrigin = getRequestHeader(event, "origin");
114
116
  const method = getMethod(event);
117
+ const requestedHeaders = String(getRequestHeader(event, "access-control-request-headers") ?? "")
118
+ .toLowerCase()
119
+ .split(",")
120
+ .map((header) => header.trim());
121
+ const embedCorsRequest = isMcpEmbedCorsOrigin(requestOrigin) &&
122
+ (requestedHeaders.includes(EMBED_TARGET_HEADER.toLowerCase()) ||
123
+ Boolean(getRequestHeader(event, EMBED_TARGET_HEADER)) ||
124
+ Boolean(getRequestHeader(event, "authorization")));
115
125
  /**
116
126
  * Decide whether the requesting origin is allowed. We never fall back
117
127
  * to "the first allowlist entry" when the origin isn't in the list —
@@ -120,11 +130,13 @@ export function createServer(options = {}) {
120
130
  * permissive enough that some clients followed through with the
121
131
  * credentialed request.
122
132
  */
123
- const allowedOrigin = getAllowedCorsOrigin(requestOrigin, {
124
- allowedOrigins,
125
- allowAnyOriginWhenNoAllowlist: !isProduction,
126
- allowLocalhostWhenNoAllowlist: true,
127
- });
133
+ const allowedOrigin = embedCorsRequest
134
+ ? requestOrigin
135
+ : getAllowedCorsOrigin(requestOrigin, {
136
+ allowedOrigins,
137
+ allowAnyOriginWhenNoAllowlist: !isProduction,
138
+ allowLocalhostWhenNoAllowlist: true,
139
+ });
128
140
  // No origin header at all (same-origin fetch, server-to-server) and
129
141
  // no allowlist → fall through with `*`-equivalent behaviour: omit
130
142
  // ACAO entirely and let the browser apply its same-origin default.
@@ -136,7 +148,9 @@ export function createServer(options = {}) {
136
148
  // apps that share a same-site cookie with the web app). The
137
149
  // wildcard `*` is spec-incompatible with credentials, so only
138
150
  // set this when we're echoing a concrete origin.
139
- setResponseHeader(event, "Access-Control-Allow-Credentials", "true");
151
+ if (shouldAllowMcpEmbedCredentials(allowedOrigin)) {
152
+ setResponseHeader(event, "Access-Control-Allow-Credentials", "true");
153
+ }
140
154
  }
141
155
  else if (!requestOrigin) {
142
156
  // No origin header — preserve the legacy permissive behaviour for
@@ -145,7 +159,7 @@ export function createServer(options = {}) {
145
159
  setResponseHeader(event, "Access-Control-Allow-Origin", "*");
146
160
  }
147
161
  setResponseHeader(event, "Access-Control-Allow-Methods", "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS");
148
- setResponseHeader(event, "Access-Control-Allow-Headers", "Content-Type,Authorization,X-Requested-With,X-Request-Source,X-Agent-Native-CSRF");
162
+ setResponseHeader(event, "Access-Control-Allow-Headers", MCP_EMBED_CORS_ALLOW_HEADERS);
149
163
  if (method === "OPTIONS") {
150
164
  // Reject preflights from disallowed cross-origin callers. We only
151
165
  // 204 if either (a) there was no Origin header (same-origin or
@@ -1 +1 @@
1
- {"version":3,"file":"create-server.js","sourceRoot":"","sources":["../../src/server/create-server.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,YAAY,EACZ,kBAAkB,EAClB,SAAS,EACT,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,GAElB,MAAM,IAAI,CAAC;AACZ,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,EACL,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AA0B3D;;GAEG;AACH,SAAS,YAAY,CAAC,OAAe;IACnC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAClD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,OAAO,KAAK,CAAC,CAAC;YAAE,SAAS;QAC7B,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7C,IAAI,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC9C,2BAA2B;QAC3B,IACE,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YAC9C,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAC9C,CAAC;YACD,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7B,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAAe,EACf,IAA2C;IAE3C,gEAAgE;IAChE,KAAK,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,IAAI,EAAE,CAAC;QAClC,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CACb,6BAA6B,GAAG,mDAAmD,CACpF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;IAE9B,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,IAAI,CAAC;QACH,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,yBAAyB;IAC3B,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAE7D,iCAAiC;IACjC,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACjC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QACrD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,OAAO,KAAK,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QAChC,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7C,IAAI,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;YAClC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACtB,OAAO,GAAG,GAAG,IAAI,KAAK,EAAE,CAAC;QAC3B,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;IAEH,kBAAkB;IAClB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,SAAS,EAAE,CAAC;QACrC,OAAO,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,KAAK,EAAE,CAAC,CAAC;IAClC,CAAC;IAED,0BAA0B;IAC1B,IAAI,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,IAAI,CAAC;IAE3C,IAAI,CAAC;QACH,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACzD,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,+DAA+D;IACjE,CAAC;AACH,CAAC;AAOD;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAC1B,UAA+B,EAAE;IAEjC,MAAM,GAAG,GAAG,SAAS,CAAC;QACpB,OAAO,CAAC,KAAK,EAAE,KAAK;YAClB,yFAAyF;YACzF,MAAM,GAAG,GAAG,KAA8B,CAAC;YAC3C,MAAM,IAAI,GAAG,GAAG,EAAE,IAAI,IAAK,GAAG,EAAE,KAA+B,EAAE,IAAI,CAAC;YACtE,IAAI,IAAI,KAAK,YAAY,IAAI,IAAI,KAAK,cAAc;gBAAE,OAAO;YAC7D,IAAI,GAAG,EAAE,OAAO,KAAK,SAAS;gBAAE,OAAO;YACvC,OAAO,CAAC,KAAK,CACX,gCAAgC,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,EAAE,EAC5D,KAAK,CACN,CAAC;QACJ,CAAC;KACF,CAAC,CAAC;IAEH,kBAAkB;IAClB,IAAI,OAAO,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QAC3B,MAAM,cAAc,GAAG,sBAAsB,EAAE,CAAC;QAChD,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAC;QAE3D;;;;WAIG;QACH,GAAG,CAAC,GAAG,CACL,kBAAkB,CAAC,CAAC,KAAK,EAAE,EAAE;YAC3B,MAAM,aAAa,GAAG,gBAAgB,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;YACxD,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;YAEhC;;;;;;;eAOG;YACH,MAAM,aAAa,GAAG,oBAAoB,CAAC,aAAa,EAAE;gBACxD,cAAc;gBACd,6BAA6B,EAAE,CAAC,YAAY;gBAC5C,6BAA6B,EAAE,IAAI;aACpC,CAAC,CAAC;YACH,oEAAoE;YACpE,kEAAkE;YAClE,mEAAmE;YAEnE,IAAI,aAAa,EAAE,CAAC;gBAClB,iBAAiB,CACf,KAAK,EACL,6BAA6B,EAC7B,aAAa,CACd,CAAC;gBACF,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;gBAC3C,6DAA6D;gBAC7D,8DAA8D;gBAC9D,4DAA4D;gBAC5D,8DAA8D;gBAC9D,iDAAiD;gBACjD,iBAAiB,CAAC,KAAK,EAAE,kCAAkC,EAAE,MAAM,CAAC,CAAC;YACvE,CAAC;iBAAM,IAAI,CAAC,aAAa,EAAE,CAAC;gBAC1B,kEAAkE;gBAClE,gEAAgE;gBAChE,2CAA2C;gBAC3C,iBAAiB,CAAC,KAAK,EAAE,6BAA6B,EAAE,GAAG,CAAC,CAAC;YAC/D,CAAC;YAED,iBAAiB,CACf,KAAK,EACL,8BAA8B,EAC9B,wCAAwC,CACzC,CAAC;YACF,iBAAiB,CACf,KAAK,EACL,8BAA8B,EAC9B,kFAAkF,CACnF,CAAC;YAEF,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,kEAAkE;gBAClE,+DAA+D;gBAC/D,mEAAmE;gBACnE,iEAAiE;gBACjE,qEAAqE;gBACrE,IAAI,aAAa,IAAI,CAAC,aAAa,EAAE,CAAC;oBACpC,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;gBAC7C,CAAC;gBACD,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;YAC7C,CAAC;QACH,CAAC,CAAC,CACH,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAEhB,eAAe;IACf,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,CACR,qBAAqB,EACrB,kBAAkB,CAAC,GAAG,EAAE;YACtB,MAAM,OAAO,GACX,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,MAAM,CAAC;YAC5D,OAAO,EAAE,OAAO,EAAE,CAAC;QACrB,CAAC,CAAC,CACH,CAAC;IACJ,CAAC;IAED,4BAA4B;IAC5B,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAEhC,MAAM,CAAC,GAAG,CACR,2BAA2B,EAC3B,kBAAkB,CAAC,GAAG,EAAE;YACtB,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;gBAC3B,GAAG,EAAE,GAAG,CAAC,GAAG;gBACZ,KAAK,EAAE,GAAG,CAAC,KAAK;gBAChB,QAAQ,EAAE,GAAG,CAAC,QAAQ,IAAI,KAAK;gBAC/B,UAAU,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC;gBAClC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACpD,CAAC,CAAC,CAAC;QACN,CAAC,CAAC,CACH,CAAC;QAEF,MAAM,CAAC,IAAI,CACT,yBAAyB,EACzB,kBAAkB,CAAC,KAAK,EAAE,KAAc,EAAE,EAAE;YAC1C,kEAAkE;YAClE,0DAA0D;YAC1D,IAAI,CAAC,oBAAoB,EAAE,EAAE,CAAC;gBAC5B,iBAAiB,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;gBAC9B,OAAO;oBACL,KAAK,EACH,0JAA0J;iBAC7J,CAAC;YACJ,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,CAAC;YACnC,MAAM,EAAE,IAAI,EAAE,GAAG,IAEhB,CAAC;YAEF,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC9C,iBAAiB,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;gBAC9B,OAAO,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC;YAC1C,CAAC;YAED,6CAA6C;YAC7C,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACvD,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC5D,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC1B,iBAAiB,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;gBAC9B,OAAO,EAAE,KAAK,EAAE,mCAAmC,EAAE,CAAC;YACxD,CAAC;YAED,qBAAqB;YACrB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,CAAC;YACjD,MAAM,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YAEvC,oEAAoE;YACpE,KAAK,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,QAAQ,EAAE,CAAC;gBACtC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YAC3B,CAAC;YAED,mDAAmD;YACnD,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YAE3B,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/C,CAAC,CAAC,CACH,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;AACzB,CAAC","sourcesContent":["import {\n createApp,\n createRouter,\n defineEventHandler,\n getMethod,\n getRequestHeader,\n setResponseHeader,\n setResponseStatus,\n type H3Event,\n} from \"h3\";\nimport path from \"path\";\nimport { agentEnv } from \"../shared/agent-env.js\";\nimport { readBody } from \"../server/h3-helpers.js\";\nimport {\n getAllowedCorsOrigin,\n readCorsAllowedOrigins,\n} from \"./cors-origins.js\";\nimport { isEnvVarWriteAllowed } from \"./env-var-writes.js\";\n\nexport interface EnvKeyConfig {\n /** Environment variable name (e.g. \"HUBSPOT_ACCESS_TOKEN\") */\n key: string;\n /** Human-readable label (e.g. \"HubSpot\") */\n label: string;\n /** Whether this key is required for the app to function */\n required?: boolean;\n /** Optional UI hint shown next to the field describing where to find this value. */\n helpText?: string;\n}\n\nexport interface CreateServerOptions {\n /** CORS options. Ignored (H3 handles CORS via middleware). Default: enabled. */\n cors?: Record<string, unknown> | false;\n /** JSON body parser limit. Kept for API compatibility (H3 uses readBody). */\n jsonLimit?: string;\n /** Custom ping message. Default: reads PING_MESSAGE env var, falls back to \"pong\" */\n pingMessage?: string;\n /** Disable the /_agent-native/ping health check. Default: false */\n disablePing?: boolean;\n /** Env key configuration for the settings UI. Enables /_agent-native/env-status and /_agent-native/env-vars routes. */\n envKeys?: EnvKeyConfig[];\n}\n\n/**\n * Parse a .env file into key-value pairs, preserving comments and empty lines for roundtrip.\n */\nfunction parseEnvFile(content: string): Map<string, string> {\n const vars = new Map<string, string>();\n for (const line of content.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n const eqIndex = trimmed.indexOf(\"=\");\n if (eqIndex === -1) continue;\n const key = trimmed.slice(0, eqIndex).trim();\n let value = trimmed.slice(eqIndex + 1).trim();\n // Strip surrounding quotes\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n vars.set(key, value);\n }\n return vars;\n}\n\n/**\n * Upsert vars into a .env file, preserving existing structure.\n */\nexport async function upsertEnvFile(\n envPath: string,\n vars: Array<{ key: string; value: string }>,\n): Promise<void> {\n // Sanitize: reject values that could inject additional env vars\n for (const { key, value } of vars) {\n if (/[\\n\\r\\0]/.test(value)) {\n throw new Error(\n `Invalid env var value for ${key}: must not contain newlines or control characters`,\n );\n }\n }\n\n const fs = await import(\"fs\");\n\n let content = \"\";\n try {\n content = fs.readFileSync(envPath, \"utf-8\");\n } catch {\n // File doesn't exist yet\n }\n\n const lines = content.split(\"\\n\");\n const remaining = new Map(vars.map((v) => [v.key, v.value]));\n\n // Update existing lines in place\n const updated = lines.map((line) => {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) return line;\n const eqIndex = trimmed.indexOf(\"=\");\n if (eqIndex === -1) return line;\n const key = trimmed.slice(0, eqIndex).trim();\n if (remaining.has(key)) {\n const value = remaining.get(key)!;\n remaining.delete(key);\n return `${key}=${value}`;\n }\n return line;\n });\n\n // Append new vars\n for (const [key, value] of remaining) {\n updated.push(`${key}=${value}`);\n }\n\n // Ensure trailing newline\n let result = updated.join(\"\\n\");\n if (!result.endsWith(\"\\n\")) result += \"\\n\";\n\n try {\n fs.mkdirSync(path.dirname(envPath), { recursive: true });\n fs.writeFileSync(envPath, result);\n } catch {\n // Edge runtimes don't have writable filesystem — skip silently\n }\n}\n\nexport interface CreateServerResult {\n app: ReturnType<typeof createApp>;\n router: ReturnType<typeof createRouter>;\n}\n\n/**\n * Create a pre-configured H3 app with standard agent-native setup:\n * - CORS headers via middleware\n * - /_agent-native/ping health check\n * - /_agent-native/env-status and /_agent-native/env-vars (when envKeys is provided)\n *\n * Returns { app, router } — mount routes on `router`.\n */\nexport function createServer(\n options: CreateServerOptions = {},\n): CreateServerResult {\n const app = createApp({\n onError(error, event) {\n // Suppress connection-reset errors — client disconnected mid-request (tab close, reload)\n const err = error as NodeJS.ErrnoException;\n const code = err?.code || (err?.cause as NodeJS.ErrnoException)?.code;\n if (code === \"ECONNRESET\" || code === \"ECONNABORTED\") return;\n if (err?.message === \"aborted\") return;\n console.error(\n `[agent-native] Server error: ${event.method} ${event.path}`,\n error,\n );\n },\n });\n\n // CORS middleware\n if (options.cors !== false) {\n const allowedOrigins = readCorsAllowedOrigins();\n const isProduction = process.env.NODE_ENV === \"production\";\n\n /**\n * When CORS_ALLOWED_ORIGINS is unset, production only allows trusted\n * localhost/native desktop origins. Development keeps the legacy \"echo\n * any origin\" behavior so local tools and docs previews keep working.\n */\n app.use(\n defineEventHandler((event) => {\n const requestOrigin = getRequestHeader(event, \"origin\");\n const method = getMethod(event);\n\n /**\n * Decide whether the requesting origin is allowed. We never fall back\n * to \"the first allowlist entry\" when the origin isn't in the list —\n * that previously sent `Access-Control-Allow-Origin: <other-origin>`\n * with credentials enabled to attacker-controlled origins, which was\n * permissive enough that some clients followed through with the\n * credentialed request.\n */\n const allowedOrigin = getAllowedCorsOrigin(requestOrigin, {\n allowedOrigins,\n allowAnyOriginWhenNoAllowlist: !isProduction,\n allowLocalhostWhenNoAllowlist: true,\n });\n // No origin header at all (same-origin fetch, server-to-server) and\n // no allowlist → fall through with `*`-equivalent behaviour: omit\n // ACAO entirely and let the browser apply its same-origin default.\n\n if (allowedOrigin) {\n setResponseHeader(\n event,\n \"Access-Control-Allow-Origin\",\n allowedOrigin,\n );\n setResponseHeader(event, \"Vary\", \"Origin\");\n // A specific origin means we can honor credentialed requests\n // (fetch with `credentials: \"include\"` — used by desktop tray\n // apps that share a same-site cookie with the web app). The\n // wildcard `*` is spec-incompatible with credentials, so only\n // set this when we're echoing a concrete origin.\n setResponseHeader(event, \"Access-Control-Allow-Credentials\", \"true\");\n } else if (!requestOrigin) {\n // No origin header — preserve the legacy permissive behaviour for\n // tools/scripts that hit the API directly (no credentialed CORS\n // semantics apply when there's no Origin).\n setResponseHeader(event, \"Access-Control-Allow-Origin\", \"*\");\n }\n\n setResponseHeader(\n event,\n \"Access-Control-Allow-Methods\",\n \"GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS\",\n );\n setResponseHeader(\n event,\n \"Access-Control-Allow-Headers\",\n \"Content-Type,Authorization,X-Requested-With,X-Request-Source,X-Agent-Native-CSRF\",\n );\n\n if (method === \"OPTIONS\") {\n // Reject preflights from disallowed cross-origin callers. We only\n // 204 if either (a) there was no Origin header (same-origin or\n // direct script invocation) or (b) the origin was in the allowlist\n // / dev fallback above. Otherwise we 403 so the browser surfaces\n // a hard CORS failure rather than blindly retrying with credentials.\n if (requestOrigin && !allowedOrigin) {\n return new Response(null, { status: 403 });\n }\n return new Response(null, { status: 204 });\n }\n }),\n );\n }\n\n const router = createRouter();\n app.use(router);\n\n // Health check\n if (!options.disablePing) {\n router.get(\n \"/_agent-native/ping\",\n defineEventHandler(() => {\n const message =\n options.pingMessage ?? process.env.PING_MESSAGE ?? \"pong\";\n return { message };\n }),\n );\n }\n\n // Env key management routes\n if (options.envKeys) {\n const envKeys = options.envKeys;\n\n router.get(\n \"/_agent-native/env-status\",\n defineEventHandler(() => {\n return envKeys.map((cfg) => ({\n key: cfg.key,\n label: cfg.label,\n required: cfg.required ?? false,\n configured: !!process.env[cfg.key],\n ...(cfg.helpText ? { helpText: cfg.helpText } : {}),\n }));\n }),\n );\n\n router.post(\n \"/_agent-native/env-vars\",\n defineEventHandler(async (event: H3Event) => {\n // Env vars are deployment-wide globals — see isEnvVarWriteAllowed\n // above. Disable the endpoint on any multi-tenant deploy.\n if (!isEnvVarWriteAllowed()) {\n setResponseStatus(event, 403);\n return {\n error:\n \"env-vars endpoint disabled on multi-tenant deployments. Use saveCredential(key, value, { userEmail, orgId, scope: 'org' }) to store per-org credentials.\",\n };\n }\n\n const body = await readBody(event);\n const { vars } = body as {\n vars?: Array<{ key: string; value: string }>;\n };\n\n if (!Array.isArray(vars) || vars.length === 0) {\n setResponseStatus(event, 400);\n return { error: \"vars array required\" };\n }\n\n // Only allow keys that are in the env config\n const allowedKeys = new Set(envKeys.map((k) => k.key));\n const filtered = vars.filter((v) => allowedKeys.has(v.key));\n if (filtered.length === 0) {\n setResponseStatus(event, 400);\n return { error: \"No recognized env keys in request\" };\n }\n\n // Write to .env file\n const envPath = path.join(process.cwd(), \".env\");\n await upsertEnvFile(envPath, filtered);\n\n // Update process.env so the app picks up the new values immediately\n for (const { key, value } of filtered) {\n process.env[key] = value;\n }\n\n // Notify parent (Builder or frame) via postMessage\n agentEnv.setVars(filtered);\n\n return { saved: filtered.map((v) => v.key) };\n }),\n );\n }\n\n return { app, router };\n}\n"]}
1
+ {"version":3,"file":"create-server.js","sourceRoot":"","sources":["../../src/server/create-server.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n createApp,\n createRouter,\n defineEventHandler,\n getMethod,\n getRequestHeader,\n setResponseHeader,\n setResponseStatus,\n type H3Event,\n} from \"h3\";\nimport path from \"path\";\nimport { agentEnv } from \"../shared/agent-env.js\";\nimport { readBody } from \"../server/h3-helpers.js\";\nimport {\n getAllowedCorsOrigin,\n readCorsAllowedOrigins,\n} from \"./cors-origins.js\";\nimport { isEnvVarWriteAllowed } from \"./env-var-writes.js\";\nimport { EMBED_TARGET_HEADER } from \"../shared/embed-auth.js\";\nimport {\n isMcpEmbedCorsOrigin,\n MCP_EMBED_CORS_ALLOW_HEADERS,\n shouldAllowMcpEmbedCredentials,\n} from \"../shared/mcp-embed-headers.js\";\n\nexport interface EnvKeyConfig {\n /** Environment variable name (e.g. \"HUBSPOT_ACCESS_TOKEN\") */\n key: string;\n /** Human-readable label (e.g. \"HubSpot\") */\n label: string;\n /** Whether this key is required for the app to function */\n required?: boolean;\n /** Optional UI hint shown next to the field describing where to find this value. */\n helpText?: string;\n}\n\nexport interface CreateServerOptions {\n /** CORS options. Ignored (H3 handles CORS via middleware). Default: enabled. */\n cors?: Record<string, unknown> | false;\n /** JSON body parser limit. Kept for API compatibility (H3 uses readBody). */\n jsonLimit?: string;\n /** Custom ping message. Default: reads PING_MESSAGE env var, falls back to \"pong\" */\n pingMessage?: string;\n /** Disable the /_agent-native/ping health check. Default: false */\n disablePing?: boolean;\n /** Env key configuration for the settings UI. Enables /_agent-native/env-status and /_agent-native/env-vars routes. */\n envKeys?: EnvKeyConfig[];\n}\n\n/**\n * Parse a .env file into key-value pairs, preserving comments and empty lines for roundtrip.\n */\nfunction parseEnvFile(content: string): Map<string, string> {\n const vars = new Map<string, string>();\n for (const line of content.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n const eqIndex = trimmed.indexOf(\"=\");\n if (eqIndex === -1) continue;\n const key = trimmed.slice(0, eqIndex).trim();\n let value = trimmed.slice(eqIndex + 1).trim();\n // Strip surrounding quotes\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n vars.set(key, value);\n }\n return vars;\n}\n\n/**\n * Upsert vars into a .env file, preserving existing structure.\n */\nexport async function upsertEnvFile(\n envPath: string,\n vars: Array<{ key: string; value: string }>,\n): Promise<void> {\n // Sanitize: reject values that could inject additional env vars\n for (const { key, value } of vars) {\n if (/[\\n\\r\\0]/.test(value)) {\n throw new Error(\n `Invalid env var value for ${key}: must not contain newlines or control characters`,\n );\n }\n }\n\n const fs = await import(\"fs\");\n\n let content = \"\";\n try {\n content = fs.readFileSync(envPath, \"utf-8\");\n } catch {\n // File doesn't exist yet\n }\n\n const lines = content.split(\"\\n\");\n const remaining = new Map(vars.map((v) => [v.key, v.value]));\n\n // Update existing lines in place\n const updated = lines.map((line) => {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) return line;\n const eqIndex = trimmed.indexOf(\"=\");\n if (eqIndex === -1) return line;\n const key = trimmed.slice(0, eqIndex).trim();\n if (remaining.has(key)) {\n const value = remaining.get(key)!;\n remaining.delete(key);\n return `${key}=${value}`;\n }\n return line;\n });\n\n // Append new vars\n for (const [key, value] of remaining) {\n updated.push(`${key}=${value}`);\n }\n\n // Ensure trailing newline\n let result = updated.join(\"\\n\");\n if (!result.endsWith(\"\\n\")) result += \"\\n\";\n\n try {\n fs.mkdirSync(path.dirname(envPath), { recursive: true });\n fs.writeFileSync(envPath, result);\n } catch {\n // Edge runtimes don't have writable filesystem — skip silently\n }\n}\n\nexport interface CreateServerResult {\n app: ReturnType<typeof createApp>;\n router: ReturnType<typeof createRouter>;\n}\n\n/**\n * Create a pre-configured H3 app with standard agent-native setup:\n * - CORS headers via middleware\n * - /_agent-native/ping health check\n * - /_agent-native/env-status and /_agent-native/env-vars (when envKeys is provided)\n *\n * Returns { app, router } — mount routes on `router`.\n */\nexport function createServer(\n options: CreateServerOptions = {},\n): CreateServerResult {\n const app = createApp({\n onError(error, event) {\n // Suppress connection-reset errors — client disconnected mid-request (tab close, reload)\n const err = error as NodeJS.ErrnoException;\n const code = err?.code || (err?.cause as NodeJS.ErrnoException)?.code;\n if (code === \"ECONNRESET\" || code === \"ECONNABORTED\") return;\n if (err?.message === \"aborted\") return;\n console.error(\n `[agent-native] Server error: ${event.method} ${event.path}`,\n error,\n );\n },\n });\n\n // CORS middleware\n if (options.cors !== false) {\n const allowedOrigins = readCorsAllowedOrigins();\n const isProduction = process.env.NODE_ENV === \"production\";\n\n /**\n * When CORS_ALLOWED_ORIGINS is unset, production only allows trusted\n * localhost/native desktop origins. Development keeps the legacy \"echo\n * any origin\" behavior so local tools and docs previews keep working.\n */\n app.use(\n defineEventHandler((event) => {\n const requestOrigin = getRequestHeader(event, \"origin\");\n const method = getMethod(event);\n const requestedHeaders = String(\n getRequestHeader(event, \"access-control-request-headers\") ?? \"\",\n )\n .toLowerCase()\n .split(\",\")\n .map((header) => header.trim());\n const embedCorsRequest =\n isMcpEmbedCorsOrigin(requestOrigin) &&\n (requestedHeaders.includes(EMBED_TARGET_HEADER.toLowerCase()) ||\n Boolean(getRequestHeader(event, EMBED_TARGET_HEADER)) ||\n Boolean(getRequestHeader(event, \"authorization\")));\n\n /**\n * Decide whether the requesting origin is allowed. We never fall back\n * to \"the first allowlist entry\" when the origin isn't in the list —\n * that previously sent `Access-Control-Allow-Origin: <other-origin>`\n * with credentials enabled to attacker-controlled origins, which was\n * permissive enough that some clients followed through with the\n * credentialed request.\n */\n const allowedOrigin = embedCorsRequest\n ? requestOrigin\n : getAllowedCorsOrigin(requestOrigin, {\n allowedOrigins,\n allowAnyOriginWhenNoAllowlist: !isProduction,\n allowLocalhostWhenNoAllowlist: true,\n });\n // No origin header at all (same-origin fetch, server-to-server) and\n // no allowlist → fall through with `*`-equivalent behaviour: omit\n // ACAO entirely and let the browser apply its same-origin default.\n\n if (allowedOrigin) {\n setResponseHeader(\n event,\n \"Access-Control-Allow-Origin\",\n allowedOrigin,\n );\n setResponseHeader(event, \"Vary\", \"Origin\");\n // A specific origin means we can honor credentialed requests\n // (fetch with `credentials: \"include\"` — used by desktop tray\n // apps that share a same-site cookie with the web app). The\n // wildcard `*` is spec-incompatible with credentials, so only\n // set this when we're echoing a concrete origin.\n if (shouldAllowMcpEmbedCredentials(allowedOrigin)) {\n setResponseHeader(\n event,\n \"Access-Control-Allow-Credentials\",\n \"true\",\n );\n }\n } else if (!requestOrigin) {\n // No origin header — preserve the legacy permissive behaviour for\n // tools/scripts that hit the API directly (no credentialed CORS\n // semantics apply when there's no Origin).\n setResponseHeader(event, \"Access-Control-Allow-Origin\", \"*\");\n }\n\n setResponseHeader(\n event,\n \"Access-Control-Allow-Methods\",\n \"GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS\",\n );\n setResponseHeader(\n event,\n \"Access-Control-Allow-Headers\",\n MCP_EMBED_CORS_ALLOW_HEADERS,\n );\n\n if (method === \"OPTIONS\") {\n // Reject preflights from disallowed cross-origin callers. We only\n // 204 if either (a) there was no Origin header (same-origin or\n // direct script invocation) or (b) the origin was in the allowlist\n // / dev fallback above. Otherwise we 403 so the browser surfaces\n // a hard CORS failure rather than blindly retrying with credentials.\n if (requestOrigin && !allowedOrigin) {\n return new Response(null, { status: 403 });\n }\n return new Response(null, { status: 204 });\n }\n }),\n );\n }\n\n const router = createRouter();\n app.use(router);\n\n // Health check\n if (!options.disablePing) {\n router.get(\n \"/_agent-native/ping\",\n defineEventHandler(() => {\n const message =\n options.pingMessage ?? process.env.PING_MESSAGE ?? \"pong\";\n return { message };\n }),\n );\n }\n\n // Env key management routes\n if (options.envKeys) {\n const envKeys = options.envKeys;\n\n router.get(\n \"/_agent-native/env-status\",\n defineEventHandler(() => {\n return envKeys.map((cfg) => ({\n key: cfg.key,\n label: cfg.label,\n required: cfg.required ?? false,\n configured: !!process.env[cfg.key],\n ...(cfg.helpText ? { helpText: cfg.helpText } : {}),\n }));\n }),\n );\n\n router.post(\n \"/_agent-native/env-vars\",\n defineEventHandler(async (event: H3Event) => {\n // Env vars are deployment-wide globals — see isEnvVarWriteAllowed\n // above. Disable the endpoint on any multi-tenant deploy.\n if (!isEnvVarWriteAllowed()) {\n setResponseStatus(event, 403);\n return {\n error:\n \"env-vars endpoint disabled on multi-tenant deployments. Use saveCredential(key, value, { userEmail, orgId, scope: 'org' }) to store per-org credentials.\",\n };\n }\n\n const body = await readBody(event);\n const { vars } = body as {\n vars?: Array<{ key: string; value: string }>;\n };\n\n if (!Array.isArray(vars) || vars.length === 0) {\n setResponseStatus(event, 400);\n return { error: \"vars array required\" };\n }\n\n // Only allow keys that are in the env config\n const allowedKeys = new Set(envKeys.map((k) => k.key));\n const filtered = vars.filter((v) => allowedKeys.has(v.key));\n if (filtered.length === 0) {\n setResponseStatus(event, 400);\n return { error: \"No recognized env keys in request\" };\n }\n\n // Write to .env file\n const envPath = path.join(process.cwd(), \".env\");\n await upsertEnvFile(envPath, filtered);\n\n // Update process.env so the app picks up the new values immediately\n for (const { key, value } of filtered) {\n process.env[key] = value;\n }\n\n // Notify parent (Builder or frame) via postMessage\n agentEnv.setVars(filtered);\n\n return { saved: filtered.map((v) => v.key) };\n }),\n );\n }\n\n return { app, router };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"embed-route.d.ts","sourceRoot":"","sources":["../../src/server/embed-route.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAQlC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAgE7C,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,MAAM,WAAW,sBAAsB;IACrC,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;CACtE;AAED,wBAAgB,4BAA4B,CAC1C,OAAO,GAAE,sBAA2B,2FAoDrC"}
1
+ {"version":3,"file":"embed-route.d.ts","sourceRoot":"","sources":["../../src/server/embed-route.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAclC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AA8G7C,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAG1D;AAUD,MAAM,WAAW,sBAAsB;IACrC,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;CACtE;AAED,wBAAgB,4BAA4B,CAC1C,OAAO,GAAE,sBAA2B,2FAiErC"}
@@ -1,7 +1,8 @@
1
- import { defineEventHandler, getMethod, getQuery, setResponseHeader } from "h3";
1
+ import { defineEventHandler, getHeader, getMethod, getQuery, setResponseHeader, } from "h3";
2
2
  import { consumeEmbedSessionTicket, normalizeEmbedTargetPath, setEmbedSessionCookie, signEmbedSessionToken, } from "./embed-session.js";
3
3
  import { getConfiguredAppBasePath } from "./app-base-path.js";
4
- import { EMBED_MODE_QUERY_PARAM, EMBED_START_PATH, EMBED_TOKEN_QUERY_PARAM, } from "../shared/embed-auth.js";
4
+ import { EMBED_MODE_QUERY_PARAM, EMBED_START_PATH, EMBED_TOKEN_QUERY_PARAM, MCP_APP_CHAT_BRIDGE_QUERY_PARAM, } from "../shared/embed-auth.js";
5
+ import { isClaudeMcpContentOrigin, MCP_EMBED_CORS_ALLOW_HEADERS, } from "../shared/mcp-embed-headers.js";
5
6
  import { withCollapsedAgentSidebarParam } from "../shared/agent-sidebar-url.js";
6
7
  function withConfiguredBasePath(path) {
7
8
  const base = getConfiguredAppBasePath();
@@ -11,20 +12,18 @@ function withConfiguredBasePath(path) {
11
12
  return path;
12
13
  return `${base}${path}`;
13
14
  }
14
- function appendEmbedParams(target, token) {
15
+ function appendEmbedParams(target, token, chatBridgeActive = false) {
15
16
  const url = new URL(target, "http://agent-native.invalid");
16
17
  url.searchParams.set(EMBED_MODE_QUERY_PARAM, "1");
17
18
  url.searchParams.set(EMBED_TOKEN_QUERY_PARAM, token);
19
+ if (chatBridgeActive) {
20
+ url.searchParams.set(MCP_APP_CHAT_BRIDGE_QUERY_PARAM, "1");
21
+ }
18
22
  return `${url.pathname}${url.search}${url.hash}`;
19
23
  }
20
24
  function redirectWithStagedCookies(event, location, status = 302) {
21
25
  setEmbedStartResponseHeaders(event);
22
- const headers = new Headers({
23
- "Cross-Origin-Embedder-Policy": "require-corp",
24
- "Cross-Origin-Opener-Policy": "same-origin",
25
- "Cross-Origin-Resource-Policy": "cross-origin",
26
- Location: location,
27
- });
26
+ const headers = embedStartResponseHeaders(event, { Location: location });
28
27
  const staged = event.res?.headers?.getSetCookie?.() ?? [];
29
28
  for (const cookie of staged)
30
29
  headers.append("set-cookie", cookie);
@@ -35,42 +34,82 @@ function setEmbedStartResponseHeaders(event) {
35
34
  setResponseHeader(event, "Cross-Origin-Embedder-Policy", "require-corp");
36
35
  setResponseHeader(event, "Cross-Origin-Opener-Policy", "same-origin");
37
36
  setResponseHeader(event, "Cross-Origin-Resource-Policy", "cross-origin");
37
+ const origin = embedStartCorsOrigin(event);
38
+ if (origin) {
39
+ setResponseHeader(event, "Access-Control-Allow-Origin", origin);
40
+ setResponseHeader(event, "Vary", "Origin");
41
+ setResponseHeader(event, "Access-Control-Allow-Methods", "GET,HEAD,OPTIONS");
42
+ setResponseHeader(event, "Access-Control-Allow-Headers", MCP_EMBED_CORS_ALLOW_HEADERS);
43
+ setResponseHeader(event, "Access-Control-Expose-Headers", "Location");
44
+ }
45
+ }
46
+ function embedStartCorsOrigin(event) {
47
+ const origin = getHeader(event, "origin");
48
+ return isClaudeMcpContentOrigin(origin) ? origin : null;
49
+ }
50
+ function embedStartResponseHeaders(event, init = {}) {
51
+ const headers = new Headers({
52
+ "Cross-Origin-Embedder-Policy": "require-corp",
53
+ "Cross-Origin-Opener-Policy": "same-origin",
54
+ "Cross-Origin-Resource-Policy": "cross-origin",
55
+ ...init,
56
+ });
57
+ const origin = embedStartCorsOrigin(event);
58
+ if (origin) {
59
+ headers.set("Access-Control-Allow-Origin", origin);
60
+ headers.set("Vary", "Origin");
61
+ headers.set("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS");
62
+ headers.set("Access-Control-Allow-Headers", MCP_EMBED_CORS_ALLOW_HEADERS);
63
+ headers.set("Access-Control-Expose-Headers", "Location");
64
+ }
65
+ return headers;
38
66
  }
39
67
  function textResponse(event, message, status) {
40
68
  setEmbedStartResponseHeaders(event);
41
69
  return new Response(message, {
42
70
  status,
43
- headers: {
71
+ headers: embedStartResponseHeaders(event, {
44
72
  "Content-Type": "text/plain; charset=utf-8",
45
- "Cross-Origin-Embedder-Policy": "require-corp",
46
- "Cross-Origin-Opener-Policy": "same-origin",
47
- "Cross-Origin-Resource-Policy": "cross-origin",
48
- },
73
+ }),
49
74
  });
50
75
  }
51
76
  export function buildEmbedStartPath(ticket) {
52
77
  const qs = new URLSearchParams({ ticket });
53
78
  return `${getConfiguredAppBasePath()}${EMBED_START_PATH}?${qs}`;
54
79
  }
80
+ function firstQueryValue(value) {
81
+ return typeof value === "string"
82
+ ? value
83
+ : Array.isArray(value) && typeof value[0] === "string"
84
+ ? value[0]
85
+ : "";
86
+ }
55
87
  export function createEmbedStartRouteHandler(options = {}) {
56
88
  return defineEventHandler(async (event) => {
57
89
  const method = getMethod(event);
90
+ if (method === "OPTIONS") {
91
+ setEmbedStartResponseHeaders(event);
92
+ return new Response(null, {
93
+ status: 204,
94
+ headers: embedStartResponseHeaders(event, {
95
+ "Cache-Control": "no-store",
96
+ }),
97
+ });
98
+ }
58
99
  if (method === "HEAD") {
59
100
  setEmbedStartResponseHeaders(event);
60
101
  return new Response(null, {
61
102
  status: 204,
62
- headers: {
103
+ headers: embedStartResponseHeaders(event, {
63
104
  "Cache-Control": "no-store",
64
- "Cross-Origin-Embedder-Policy": "require-corp",
65
- "Cross-Origin-Opener-Policy": "same-origin",
66
- "Cross-Origin-Resource-Policy": "cross-origin",
67
- },
105
+ }),
68
106
  });
69
107
  }
70
108
  if (method !== "GET") {
71
109
  return textResponse(event, "Method not allowed", 405);
72
110
  }
73
- const rawTicket = getQuery(event)?.ticket;
111
+ const query = getQuery(event) ?? {};
112
+ const rawTicket = query.ticket;
74
113
  const ticket = Array.isArray(rawTicket) ? rawTicket[0] : rawTicket;
75
114
  const existingSession = await options
76
115
  .getExistingSession?.(event)
@@ -93,7 +132,9 @@ export function createEmbedStartRouteHandler(options = {}) {
93
132
  });
94
133
  setEmbedSessionCookie(event, token);
95
134
  setResponseHeader(event, "Referrer-Policy", "no-referrer");
96
- const location = withConfiguredBasePath(withCollapsedAgentSidebarParam(appendEmbedParams(target, token)));
135
+ const chatBridgeActive = firstQueryValue(query[MCP_APP_CHAT_BRIDGE_QUERY_PARAM]) === "1" ||
136
+ firstQueryValue(query[MCP_APP_CHAT_BRIDGE_QUERY_PARAM]) === "true";
137
+ const location = withConfiguredBasePath(withCollapsedAgentSidebarParam(appendEmbedParams(target, token, chatBridgeActive)));
97
138
  return redirectWithStagedCookies(event, location);
98
139
  });
99
140
  }
@@ -1 +1 @@
1
- {"version":3,"file":"embed-route.js","sourceRoot":"","sources":["../../src/server/embed-route.ts"],"names":[],"mappings":"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","sourcesContent":["import type { H3Event } from \"h3\";\nimport { defineEventHandler, getMethod, getQuery, setResponseHeader } from \"h3\";\nimport {\n consumeEmbedSessionTicket,\n normalizeEmbedTargetPath,\n setEmbedSessionCookie,\n signEmbedSessionToken,\n} from \"./embed-session.js\";\nimport type { AuthSession } from \"./auth.js\";\nimport { getConfiguredAppBasePath } from \"./app-base-path.js\";\nimport {\n EMBED_MODE_QUERY_PARAM,\n EMBED_START_PATH,\n EMBED_TOKEN_QUERY_PARAM,\n} from \"../shared/embed-auth.js\";\nimport { withCollapsedAgentSidebarParam } from \"../shared/agent-sidebar-url.js\";\n\nfunction withConfiguredBasePath(path: string): string {\n const base = getConfiguredAppBasePath();\n if (!base) return path;\n if (path === base || path.startsWith(`${base}/`)) return path;\n return `${base}${path}`;\n}\n\nfunction appendEmbedParams(target: string, token: string): string {\n const url = new URL(target, \"http://agent-native.invalid\");\n url.searchParams.set(EMBED_MODE_QUERY_PARAM, \"1\");\n url.searchParams.set(EMBED_TOKEN_QUERY_PARAM, token);\n return `${url.pathname}${url.search}${url.hash}`;\n}\n\nfunction redirectWithStagedCookies(\n event: H3Event,\n location: string,\n status = 302,\n): Response {\n setEmbedStartResponseHeaders(event);\n const headers = new Headers({\n \"Cross-Origin-Embedder-Policy\": \"require-corp\",\n \"Cross-Origin-Opener-Policy\": \"same-origin\",\n \"Cross-Origin-Resource-Policy\": \"cross-origin\",\n Location: location,\n });\n const staged = event.res?.headers?.getSetCookie?.() ?? [];\n for (const cookie of staged) headers.append(\"set-cookie\", cookie);\n headers.set(\"Referrer-Policy\", \"no-referrer\");\n return new Response(\"\", { status, headers });\n}\n\nfunction setEmbedStartResponseHeaders(event: H3Event): void {\n setResponseHeader(event, \"Cross-Origin-Embedder-Policy\", \"require-corp\");\n setResponseHeader(event, \"Cross-Origin-Opener-Policy\", \"same-origin\");\n setResponseHeader(event, \"Cross-Origin-Resource-Policy\", \"cross-origin\");\n}\n\nfunction textResponse(\n event: H3Event,\n message: string,\n status: number,\n): Response {\n setEmbedStartResponseHeaders(event);\n return new Response(message, {\n status,\n headers: {\n \"Content-Type\": \"text/plain; charset=utf-8\",\n \"Cross-Origin-Embedder-Policy\": \"require-corp\",\n \"Cross-Origin-Opener-Policy\": \"same-origin\",\n \"Cross-Origin-Resource-Policy\": \"cross-origin\",\n },\n });\n}\n\nexport function buildEmbedStartPath(ticket: string): string {\n const qs = new URLSearchParams({ ticket });\n return `${getConfiguredAppBasePath()}${EMBED_START_PATH}?${qs}`;\n}\n\nexport interface EmbedStartRouteOptions {\n getExistingSession?: (event: H3Event) => Promise<AuthSession | null>;\n}\n\nexport function createEmbedStartRouteHandler(\n options: EmbedStartRouteOptions = {},\n) {\n return defineEventHandler(async (event: H3Event) => {\n const method = getMethod(event);\n if (method === \"HEAD\") {\n setEmbedStartResponseHeaders(event);\n return new Response(null, {\n status: 204,\n headers: {\n \"Cache-Control\": \"no-store\",\n \"Cross-Origin-Embedder-Policy\": \"require-corp\",\n \"Cross-Origin-Opener-Policy\": \"same-origin\",\n \"Cross-Origin-Resource-Policy\": \"cross-origin\",\n },\n });\n }\n\n if (method !== \"GET\") {\n return textResponse(event, \"Method not allowed\", 405);\n }\n\n const rawTicket = getQuery(event)?.ticket;\n const ticket = Array.isArray(rawTicket) ? rawTicket[0] : rawTicket;\n const existingSession = await options\n .getExistingSession?.(event)\n .catch(() => null);\n const consumed = await consumeEmbedSessionTicket(ticket, {\n expectedOrgId: existingSession?.orgId ?? null,\n });\n if (!consumed) {\n return textResponse(event, \"Invalid or expired embed session.\", 401);\n }\n\n const target = normalizeEmbedTargetPath(consumed.targetPath);\n if (!target) {\n return textResponse(event, \"Invalid embed target.\", 400);\n }\n\n const token = signEmbedSessionToken({\n ownerEmail: consumed.ownerEmail,\n orgId: consumed.orgId,\n targetPath: target,\n scope: consumed.scope,\n });\n setEmbedSessionCookie(event, token);\n setResponseHeader(event, \"Referrer-Policy\", \"no-referrer\");\n\n const location = withConfiguredBasePath(\n withCollapsedAgentSidebarParam(appendEmbedParams(target, token)),\n );\n return redirectWithStagedCookies(event, location);\n });\n}\n"]}
1
+ {"version":3,"file":"embed-route.js","sourceRoot":"","sources":["../../src/server/embed-route.ts"],"names":[],"mappings":"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","sourcesContent":["import type { H3Event } from \"h3\";\nimport {\n defineEventHandler,\n getHeader,\n getMethod,\n getQuery,\n setResponseHeader,\n} from \"h3\";\nimport {\n consumeEmbedSessionTicket,\n normalizeEmbedTargetPath,\n setEmbedSessionCookie,\n signEmbedSessionToken,\n} from \"./embed-session.js\";\nimport type { AuthSession } from \"./auth.js\";\nimport { getConfiguredAppBasePath } from \"./app-base-path.js\";\nimport {\n EMBED_MODE_QUERY_PARAM,\n EMBED_START_PATH,\n EMBED_TOKEN_QUERY_PARAM,\n MCP_APP_CHAT_BRIDGE_QUERY_PARAM,\n} from \"../shared/embed-auth.js\";\nimport {\n isClaudeMcpContentOrigin,\n MCP_EMBED_CORS_ALLOW_HEADERS,\n} from \"../shared/mcp-embed-headers.js\";\nimport { withCollapsedAgentSidebarParam } from \"../shared/agent-sidebar-url.js\";\n\nfunction withConfiguredBasePath(path: string): string {\n const base = getConfiguredAppBasePath();\n if (!base) return path;\n if (path === base || path.startsWith(`${base}/`)) return path;\n return `${base}${path}`;\n}\n\nfunction appendEmbedParams(\n target: string,\n token: string,\n chatBridgeActive = false,\n): string {\n const url = new URL(target, \"http://agent-native.invalid\");\n url.searchParams.set(EMBED_MODE_QUERY_PARAM, \"1\");\n url.searchParams.set(EMBED_TOKEN_QUERY_PARAM, token);\n if (chatBridgeActive) {\n url.searchParams.set(MCP_APP_CHAT_BRIDGE_QUERY_PARAM, \"1\");\n }\n return `${url.pathname}${url.search}${url.hash}`;\n}\n\nfunction redirectWithStagedCookies(\n event: H3Event,\n location: string,\n status = 302,\n): Response {\n setEmbedStartResponseHeaders(event);\n const headers = embedStartResponseHeaders(event, { Location: location });\n const staged = event.res?.headers?.getSetCookie?.() ?? [];\n for (const cookie of staged) headers.append(\"set-cookie\", cookie);\n headers.set(\"Referrer-Policy\", \"no-referrer\");\n return new Response(\"\", { status, headers });\n}\n\nfunction setEmbedStartResponseHeaders(event: H3Event): void {\n setResponseHeader(event, \"Cross-Origin-Embedder-Policy\", \"require-corp\");\n setResponseHeader(event, \"Cross-Origin-Opener-Policy\", \"same-origin\");\n setResponseHeader(event, \"Cross-Origin-Resource-Policy\", \"cross-origin\");\n const origin = embedStartCorsOrigin(event);\n if (origin) {\n setResponseHeader(event, \"Access-Control-Allow-Origin\", origin);\n setResponseHeader(event, \"Vary\", \"Origin\");\n setResponseHeader(\n event,\n \"Access-Control-Allow-Methods\",\n \"GET,HEAD,OPTIONS\",\n );\n setResponseHeader(\n event,\n \"Access-Control-Allow-Headers\",\n MCP_EMBED_CORS_ALLOW_HEADERS,\n );\n setResponseHeader(event, \"Access-Control-Expose-Headers\", \"Location\");\n }\n}\n\nfunction embedStartCorsOrigin(event: H3Event): string | null {\n const origin = getHeader(event, \"origin\");\n return isClaudeMcpContentOrigin(origin) ? origin : null;\n}\n\nfunction embedStartResponseHeaders(\n event: H3Event,\n init: Record<string, string> = {},\n): Headers {\n const headers = new Headers({\n \"Cross-Origin-Embedder-Policy\": \"require-corp\",\n \"Cross-Origin-Opener-Policy\": \"same-origin\",\n \"Cross-Origin-Resource-Policy\": \"cross-origin\",\n ...init,\n });\n const origin = embedStartCorsOrigin(event);\n if (origin) {\n headers.set(\"Access-Control-Allow-Origin\", origin);\n headers.set(\"Vary\", \"Origin\");\n headers.set(\"Access-Control-Allow-Methods\", \"GET,HEAD,OPTIONS\");\n headers.set(\"Access-Control-Allow-Headers\", MCP_EMBED_CORS_ALLOW_HEADERS);\n headers.set(\"Access-Control-Expose-Headers\", \"Location\");\n }\n return headers;\n}\n\nfunction textResponse(\n event: H3Event,\n message: string,\n status: number,\n): Response {\n setEmbedStartResponseHeaders(event);\n return new Response(message, {\n status,\n headers: embedStartResponseHeaders(event, {\n \"Content-Type\": \"text/plain; charset=utf-8\",\n }),\n });\n}\n\nexport function buildEmbedStartPath(ticket: string): string {\n const qs = new URLSearchParams({ ticket });\n return `${getConfiguredAppBasePath()}${EMBED_START_PATH}?${qs}`;\n}\n\nfunction firstQueryValue(value: unknown): string {\n return typeof value === \"string\"\n ? value\n : Array.isArray(value) && typeof value[0] === \"string\"\n ? value[0]\n : \"\";\n}\n\nexport interface EmbedStartRouteOptions {\n getExistingSession?: (event: H3Event) => Promise<AuthSession | null>;\n}\n\nexport function createEmbedStartRouteHandler(\n options: EmbedStartRouteOptions = {},\n) {\n return defineEventHandler(async (event: H3Event) => {\n const method = getMethod(event);\n if (method === \"OPTIONS\") {\n setEmbedStartResponseHeaders(event);\n return new Response(null, {\n status: 204,\n headers: embedStartResponseHeaders(event, {\n \"Cache-Control\": \"no-store\",\n }),\n });\n }\n\n if (method === \"HEAD\") {\n setEmbedStartResponseHeaders(event);\n return new Response(null, {\n status: 204,\n headers: embedStartResponseHeaders(event, {\n \"Cache-Control\": \"no-store\",\n }),\n });\n }\n\n if (method !== \"GET\") {\n return textResponse(event, \"Method not allowed\", 405);\n }\n\n const query = getQuery(event) ?? {};\n const rawTicket = query.ticket;\n const ticket = Array.isArray(rawTicket) ? rawTicket[0] : rawTicket;\n const existingSession = await options\n .getExistingSession?.(event)\n .catch(() => null);\n const consumed = await consumeEmbedSessionTicket(ticket, {\n expectedOrgId: existingSession?.orgId ?? null,\n });\n if (!consumed) {\n return textResponse(event, \"Invalid or expired embed session.\", 401);\n }\n\n const target = normalizeEmbedTargetPath(consumed.targetPath);\n if (!target) {\n return textResponse(event, \"Invalid embed target.\", 400);\n }\n\n const token = signEmbedSessionToken({\n ownerEmail: consumed.ownerEmail,\n orgId: consumed.orgId,\n targetPath: target,\n scope: consumed.scope,\n });\n setEmbedSessionCookie(event, token);\n setResponseHeader(event, \"Referrer-Policy\", \"no-referrer\");\n\n const chatBridgeActive =\n firstQueryValue(query[MCP_APP_CHAT_BRIDGE_QUERY_PARAM]) === \"1\" ||\n firstQueryValue(query[MCP_APP_CHAT_BRIDGE_QUERY_PARAM]) === \"true\";\n const location = withConfiguredBasePath(\n withCollapsedAgentSidebarParam(\n appendEmbedParams(target, token, chatBridgeActive),\n ),\n );\n return redirectWithStagedCookies(event, location);\n });\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"open-route.d.ts","sourceRoot":"","sources":["../../src/server/open-route.ts"],"names":[],"mappings":"AA0DA,MAAM,WAAW,gBAAgB;IAC/B;;yEAEqE;IACrE,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE;QACzB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAChC,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACjC;AA6DD,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,gBAAqB,2FAgIpE"}
1
+ {"version":3,"file":"open-route.d.ts","sourceRoot":"","sources":["../../src/server/open-route.ts"],"names":[],"mappings":"AA4DA,MAAM,WAAW,gBAAgB;IAC/B;;yEAEqE;IACrE,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE;QACzB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAChC,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACjC;AA6DD,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,gBAAqB,2FAoIpE"}
@@ -2,7 +2,7 @@ import { defineEventHandler, getMethod } from "h3";
2
2
  import { getSession, getConfiguredLoginHtml } from "./auth.js";
3
3
  import { appStatePut, appStateGet } from "../application-state/store.js";
4
4
  import { AGENT_SIDEBAR_QUERY_PARAM, withCollapsedAgentSidebarParam, } from "../shared/agent-sidebar-url.js";
5
- import { EMBED_MODE_QUERY_PARAM, EMBED_TOKEN_QUERY_PARAM, } from "../shared/embed-auth.js";
5
+ import { EMBED_MODE_QUERY_PARAM, EMBED_TOKEN_QUERY_PARAM, MCP_APP_CHAT_BRIDGE_QUERY_PARAM, } from "../shared/embed-auth.js";
6
6
  import { getConfiguredAppBasePath } from "./app-base-path.js";
7
7
  /** Query keys that are route control, not navigation payload. */
8
8
  const RESERVED = new Set([
@@ -12,6 +12,7 @@ const RESERVED = new Set([
12
12
  "compose",
13
13
  EMBED_MODE_QUERY_PARAM,
14
14
  EMBED_TOKEN_QUERY_PARAM,
15
+ MCP_APP_CHAT_BRIDGE_QUERY_PARAM,
15
16
  AGENT_SIDEBAR_QUERY_PARAM,
16
17
  ]);
17
18
  // Control-char guard (NUL..US + DEL). Defined via codepoints so the source
@@ -196,7 +197,11 @@ export function createOpenRouteHandler(options = {}) {
196
197
  }
197
198
  target = appendSearchParams(target, filters);
198
199
  const embedParams = new URLSearchParams();
199
- for (const key of [EMBED_MODE_QUERY_PARAM, EMBED_TOKEN_QUERY_PARAM]) {
200
+ for (const key of [
201
+ EMBED_MODE_QUERY_PARAM,
202
+ EMBED_TOKEN_QUERY_PARAM,
203
+ MCP_APP_CHAT_BRIDGE_QUERY_PARAM,
204
+ ]) {
200
205
  const value = search.get(key);
201
206
  if (value)
202
207
  embedParams.set(key, value);
@@ -1 +1 @@
1
- {"version":3,"file":"open-route.js","sourceRoot":"","sources":["../../src/server/open-route.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `/_agent-native/open` — the stable deep-link route.\n *\n * An external coding agent (Claude Code / Cowork / Codex) surfaces an\n * \"Open in <app> →\" link (built by an action's `link` builder, see\n * `deep-link.ts`). When the user clicks it in any browser / inline webview,\n * this route:\n * 1. Resolves the *browser* session (NOT the agent token) — so the record\n * always lands where the human is logged in.\n * 2. When unauthenticated, serves the same sign-in form the auth guard\n * would, *at this same URL*. The login form's success handler reloads\n * `window.location.href`, so the now-authenticated request re-enters\n * this route and proceeds. No `?next=` plumbing needed.\n * 3. Writes the existing one-shot `navigate` application-state command (the\n * exact key the UI already drains every 2s — we don't invent a new\n * navigation mechanism, we bridge to it), plus an optional `compose-<id>`\n * draft.\n * 4. 302-redirects to the rendered SPA view so the page loads immediately;\n * the polled `navigate` command then applies record-level focus.\n *\n * The link itself is a pure pointer (view + record ids + filters) and carries\n * no privileged state.\n */\nimport type { H3Event } from \"h3\";\nimport { defineEventHandler, getMethod } from \"h3\";\nimport { getSession, getConfiguredLoginHtml } from \"./auth.js\";\nimport { appStatePut, appStateGet } from \"../application-state/store.js\";\nimport {\n AGENT_SIDEBAR_QUERY_PARAM,\n withCollapsedAgentSidebarParam,\n} from \"../shared/agent-sidebar-url.js\";\nimport {\n EMBED_MODE_QUERY_PARAM,\n EMBED_TOKEN_QUERY_PARAM,\n} from \"../shared/embed-auth.js\";\nimport { getConfiguredAppBasePath } from \"./app-base-path.js\";\n\n/** Query keys that are route control, not navigation payload. */\nconst RESERVED = new Set([\n \"app\",\n \"view\",\n \"to\",\n \"compose\",\n EMBED_MODE_QUERY_PARAM,\n EMBED_TOKEN_QUERY_PARAM,\n AGENT_SIDEBAR_QUERY_PARAM,\n]);\n\n// Control-char guard (NUL..US + DEL). Defined via codepoints so the source\n// file stays plain ASCII.\nconst CONTROL_CHARS = new RegExp(\"[\\\\u0000-\\\\u001f\\\\u007f]\");\n\n// Compose-draft id charset. Mirrors `sanitizeDraftId` in\n// templates/mail/actions/manage-draft.ts so the id we concatenate into the\n// `compose-<id>` application-state key can't escape the key namespace\n// (path-traversal / key injection guard).\nconst COMPOSE_ID = /^[a-zA-Z0-9_-]{1,64}$/;\n\nexport interface OpenRouteOptions {\n /** Per-template override that turns the parsed deep-link params into the\n * client-side SPA path to redirect to. Return `null` to use the default\n * (`/<view>`). Filter params (`f_*`) are appended automatically. */\n resolveOpenPath?: (params: {\n app?: string;\n view?: string;\n params: Record<string, string>;\n }) => string | null | undefined;\n}\n\nfunction getRequestUrl(event: H3Event): string {\n const mountedPathname = (event as any).context?._mountedPathname;\n if (typeof mountedPathname === \"string\" && mountedPathname) {\n return `${mountedPathname}${(event as any).url?.search ?? \"\"}`;\n }\n return (event as any).node?.req?.url ?? (event as any).path ?? \"/\";\n}\n\n/** Decode a base64url string to UTF-8 (Node Buffer; this route is Node-only). */\nfunction decodeBase64Url(input: string): string {\n return Buffer.from(input, \"base64url\").toString(\"utf8\");\n}\n\n/**\n * Normalize a candidate redirect path to a safe, same-origin, leading-slash\n * relative path. Rejects absolute URLs, scheme-relative `//host`, and control\n * chars (open-redirect guard). Returns `null` when unsafe.\n */\nfunction safeRelativePath(raw: string | undefined | null): string | null {\n if (!raw) return null;\n if (CONTROL_CHARS.test(raw)) return null;\n if (!raw.startsWith(\"/\")) return null;\n if (raw.startsWith(\"//\") || raw.startsWith(\"/\\\\\")) return null;\n if (/^\\/[a-z][a-z0-9+.-]*:/i.test(raw)) return null;\n return raw;\n}\n\nfunction redirect(location: string): Response {\n // Native web Response (not h3 v2's reworked sendRedirect) — matches the\n // redirect pattern used elsewhere in auth.ts.\n return new Response(\"\", { status: 302, headers: { Location: location } });\n}\n\nfunction appendSearchParams(target: string, params: URLSearchParams): string {\n if (!params.toString()) return target;\n try {\n const url = new URL(target, \"http://an.invalid\");\n for (const [k, v] of params.entries()) url.searchParams.set(k, v);\n return `${url.pathname}${url.search}${url.hash}`;\n } catch {\n return target;\n }\n}\n\nfunction withConfiguredRedirectBasePath(target: string): string {\n const base = getConfiguredAppBasePath();\n if (!base) return target;\n try {\n const url = new URL(target, \"http://an.invalid\");\n if (url.pathname === base || url.pathname.startsWith(`${base}/`)) {\n return `${url.pathname}${url.search}${url.hash}`;\n }\n url.pathname = url.pathname === \"/\" ? base : `${base}${url.pathname}`;\n return `${url.pathname}${url.search}${url.hash}`;\n } catch {\n return target;\n }\n}\n\nexport function createOpenRouteHandler(options: OpenRouteOptions = {}) {\n return defineEventHandler(async (event: H3Event) => {\n const method = getMethod(event);\n if (method !== \"GET\" && method !== \"HEAD\") {\n return new Response(JSON.stringify({ error: \"Method not allowed\" }), {\n status: 405,\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n\n const rawUrl = getRequestUrl(event);\n let search: URLSearchParams;\n try {\n search = new URL(rawUrl, \"http://an.invalid\").searchParams;\n } catch {\n search = new URLSearchParams();\n }\n\n const app = search.get(\"app\") ?? undefined;\n const view = search.get(\"view\") ?? undefined;\n const toParam = search.get(\"to\") ?? undefined;\n const compose = search.get(\"compose\") ?? undefined;\n\n // Resolve the BROWSER session. When unauthenticated, serve the same login\n // form the guard would — at this URL — so the post-login reload returns\n // here authenticated.\n const session = await getSession(event);\n if (!session?.email) {\n const html = getConfiguredLoginHtml(event);\n if (html) {\n return new Response(html, {\n status: 200,\n headers: { \"Content-Type\": \"text/html; charset=utf-8\" },\n });\n }\n // No auth guard configured (fully open app) — best effort: still send\n // the user to the view; nothing to scope the navigate write to.\n }\n\n // Build the navigation payload from every non-reserved query param\n // (record ids + filters: threadId, eventId, dashboardId, f_*, ...).\n const navParams: Record<string, string> = {};\n for (const [k, v] of search.entries()) {\n if (RESERVED.has(k)) continue;\n navParams[k] = v;\n }\n const navPayload: Record<string, unknown> = { ...navParams };\n if (view) navPayload.view = view;\n\n if (session?.email) {\n try {\n await appStatePut(session.email, \"navigate\", navPayload, {\n requestSource: \"deep-link\",\n });\n if (compose) {\n try {\n const draft = JSON.parse(decodeBase64Url(compose));\n // Validate the id before using it as a key segment. An unsafe id\n // could escape the `compose-` namespace and clobber an unrelated\n // application-state key; skip the write (the view still opens),\n // mirroring the malformed-payload branch below.\n if (\n draft &&\n typeof draft === \"object\" &&\n typeof draft.id === \"string\" &&\n COMPOSE_ID.test(draft.id)\n ) {\n const composeKey = `compose-${draft.id}`;\n // A compact deep link may carry only `{ id, subject }` when the\n // full draft was too large to inline in the URL. The complete\n // draft is already persisted at `compose-<id>` by manage-draft\n // on create/update. Never let the truncated stub overwrite that\n // richer saved draft (would silently lose body / recipients /\n // reply metadata). Only write when the payload actually carries\n // content, or when nothing is saved yet (composer still opens).\n const hasContent =\n (typeof draft.body === \"string\" && draft.body.length > 0) ||\n !!draft.to ||\n !!draft.cc ||\n !!draft.bcc ||\n !!draft.html ||\n !!draft.replyToThreadId;\n const existing = hasContent\n ? null\n : await appStateGet(session.email, composeKey);\n if (hasContent || !existing) {\n await appStatePut(session.email, composeKey, draft, {\n requestSource: \"deep-link\",\n });\n }\n }\n } catch {\n // Malformed compose payload — skip; the view still opens.\n }\n }\n } catch {\n // App-state write failure shouldn't 500 the click; the redirect\n // below still lands the user on the right view.\n }\n }\n\n // Resolve the SPA path to redirect to.\n let target =\n safeRelativePath(toParam) ??\n safeRelativePath(\n options.resolveOpenPath?.({ app, view, params: navParams }) ??\n (view ? `/${view}` : null),\n ) ??\n \"/\";\n\n // Forward filter params (f_*) onto the redirect so dashboards/lists open\n // pre-filtered even before the navigate command is drained.\n const filters = new URLSearchParams();\n for (const [k, v] of search.entries()) {\n if (k.startsWith(\"f_\")) filters.set(k, v);\n }\n target = appendSearchParams(target, filters);\n const embedParams = new URLSearchParams();\n for (const key of [EMBED_MODE_QUERY_PARAM, EMBED_TOKEN_QUERY_PARAM]) {\n const value = search.get(key);\n if (value) embedParams.set(key, value);\n }\n target = appendSearchParams(target, embedParams);\n target = withCollapsedAgentSidebarParam(target);\n target = withConfiguredRedirectBasePath(target);\n\n return redirect(target);\n });\n}\n"]}
1
+ {"version":3,"file":"open-route.js","sourceRoot":"","sources":["../../src/server/open-route.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `/_agent-native/open` — the stable deep-link route.\n *\n * An external coding agent (Claude Code / Cowork / Codex) surfaces an\n * \"Open in <app> →\" link (built by an action's `link` builder, see\n * `deep-link.ts`). When the user clicks it in any browser / inline webview,\n * this route:\n * 1. Resolves the *browser* session (NOT the agent token) — so the record\n * always lands where the human is logged in.\n * 2. When unauthenticated, serves the same sign-in form the auth guard\n * would, *at this same URL*. The login form's success handler reloads\n * `window.location.href`, so the now-authenticated request re-enters\n * this route and proceeds. No `?next=` plumbing needed.\n * 3. Writes the existing one-shot `navigate` application-state command (the\n * exact key the UI already drains every 2s — we don't invent a new\n * navigation mechanism, we bridge to it), plus an optional `compose-<id>`\n * draft.\n * 4. 302-redirects to the rendered SPA view so the page loads immediately;\n * the polled `navigate` command then applies record-level focus.\n *\n * The link itself is a pure pointer (view + record ids + filters) and carries\n * no privileged state.\n */\nimport type { H3Event } from \"h3\";\nimport { defineEventHandler, getMethod } from \"h3\";\nimport { getSession, getConfiguredLoginHtml } from \"./auth.js\";\nimport { appStatePut, appStateGet } from \"../application-state/store.js\";\nimport {\n AGENT_SIDEBAR_QUERY_PARAM,\n withCollapsedAgentSidebarParam,\n} from \"../shared/agent-sidebar-url.js\";\nimport {\n EMBED_MODE_QUERY_PARAM,\n EMBED_TOKEN_QUERY_PARAM,\n MCP_APP_CHAT_BRIDGE_QUERY_PARAM,\n} from \"../shared/embed-auth.js\";\nimport { getConfiguredAppBasePath } from \"./app-base-path.js\";\n\n/** Query keys that are route control, not navigation payload. */\nconst RESERVED = new Set([\n \"app\",\n \"view\",\n \"to\",\n \"compose\",\n EMBED_MODE_QUERY_PARAM,\n EMBED_TOKEN_QUERY_PARAM,\n MCP_APP_CHAT_BRIDGE_QUERY_PARAM,\n AGENT_SIDEBAR_QUERY_PARAM,\n]);\n\n// Control-char guard (NUL..US + DEL). Defined via codepoints so the source\n// file stays plain ASCII.\nconst CONTROL_CHARS = new RegExp(\"[\\\\u0000-\\\\u001f\\\\u007f]\");\n\n// Compose-draft id charset. Mirrors `sanitizeDraftId` in\n// templates/mail/actions/manage-draft.ts so the id we concatenate into the\n// `compose-<id>` application-state key can't escape the key namespace\n// (path-traversal / key injection guard).\nconst COMPOSE_ID = /^[a-zA-Z0-9_-]{1,64}$/;\n\nexport interface OpenRouteOptions {\n /** Per-template override that turns the parsed deep-link params into the\n * client-side SPA path to redirect to. Return `null` to use the default\n * (`/<view>`). Filter params (`f_*`) are appended automatically. */\n resolveOpenPath?: (params: {\n app?: string;\n view?: string;\n params: Record<string, string>;\n }) => string | null | undefined;\n}\n\nfunction getRequestUrl(event: H3Event): string {\n const mountedPathname = (event as any).context?._mountedPathname;\n if (typeof mountedPathname === \"string\" && mountedPathname) {\n return `${mountedPathname}${(event as any).url?.search ?? \"\"}`;\n }\n return (event as any).node?.req?.url ?? (event as any).path ?? \"/\";\n}\n\n/** Decode a base64url string to UTF-8 (Node Buffer; this route is Node-only). */\nfunction decodeBase64Url(input: string): string {\n return Buffer.from(input, \"base64url\").toString(\"utf8\");\n}\n\n/**\n * Normalize a candidate redirect path to a safe, same-origin, leading-slash\n * relative path. Rejects absolute URLs, scheme-relative `//host`, and control\n * chars (open-redirect guard). Returns `null` when unsafe.\n */\nfunction safeRelativePath(raw: string | undefined | null): string | null {\n if (!raw) return null;\n if (CONTROL_CHARS.test(raw)) return null;\n if (!raw.startsWith(\"/\")) return null;\n if (raw.startsWith(\"//\") || raw.startsWith(\"/\\\\\")) return null;\n if (/^\\/[a-z][a-z0-9+.-]*:/i.test(raw)) return null;\n return raw;\n}\n\nfunction redirect(location: string): Response {\n // Native web Response (not h3 v2's reworked sendRedirect) — matches the\n // redirect pattern used elsewhere in auth.ts.\n return new Response(\"\", { status: 302, headers: { Location: location } });\n}\n\nfunction appendSearchParams(target: string, params: URLSearchParams): string {\n if (!params.toString()) return target;\n try {\n const url = new URL(target, \"http://an.invalid\");\n for (const [k, v] of params.entries()) url.searchParams.set(k, v);\n return `${url.pathname}${url.search}${url.hash}`;\n } catch {\n return target;\n }\n}\n\nfunction withConfiguredRedirectBasePath(target: string): string {\n const base = getConfiguredAppBasePath();\n if (!base) return target;\n try {\n const url = new URL(target, \"http://an.invalid\");\n if (url.pathname === base || url.pathname.startsWith(`${base}/`)) {\n return `${url.pathname}${url.search}${url.hash}`;\n }\n url.pathname = url.pathname === \"/\" ? base : `${base}${url.pathname}`;\n return `${url.pathname}${url.search}${url.hash}`;\n } catch {\n return target;\n }\n}\n\nexport function createOpenRouteHandler(options: OpenRouteOptions = {}) {\n return defineEventHandler(async (event: H3Event) => {\n const method = getMethod(event);\n if (method !== \"GET\" && method !== \"HEAD\") {\n return new Response(JSON.stringify({ error: \"Method not allowed\" }), {\n status: 405,\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n\n const rawUrl = getRequestUrl(event);\n let search: URLSearchParams;\n try {\n search = new URL(rawUrl, \"http://an.invalid\").searchParams;\n } catch {\n search = new URLSearchParams();\n }\n\n const app = search.get(\"app\") ?? undefined;\n const view = search.get(\"view\") ?? undefined;\n const toParam = search.get(\"to\") ?? undefined;\n const compose = search.get(\"compose\") ?? undefined;\n\n // Resolve the BROWSER session. When unauthenticated, serve the same login\n // form the guard would — at this URL — so the post-login reload returns\n // here authenticated.\n const session = await getSession(event);\n if (!session?.email) {\n const html = getConfiguredLoginHtml(event);\n if (html) {\n return new Response(html, {\n status: 200,\n headers: { \"Content-Type\": \"text/html; charset=utf-8\" },\n });\n }\n // No auth guard configured (fully open app) — best effort: still send\n // the user to the view; nothing to scope the navigate write to.\n }\n\n // Build the navigation payload from every non-reserved query param\n // (record ids + filters: threadId, eventId, dashboardId, f_*, ...).\n const navParams: Record<string, string> = {};\n for (const [k, v] of search.entries()) {\n if (RESERVED.has(k)) continue;\n navParams[k] = v;\n }\n const navPayload: Record<string, unknown> = { ...navParams };\n if (view) navPayload.view = view;\n\n if (session?.email) {\n try {\n await appStatePut(session.email, \"navigate\", navPayload, {\n requestSource: \"deep-link\",\n });\n if (compose) {\n try {\n const draft = JSON.parse(decodeBase64Url(compose));\n // Validate the id before using it as a key segment. An unsafe id\n // could escape the `compose-` namespace and clobber an unrelated\n // application-state key; skip the write (the view still opens),\n // mirroring the malformed-payload branch below.\n if (\n draft &&\n typeof draft === \"object\" &&\n typeof draft.id === \"string\" &&\n COMPOSE_ID.test(draft.id)\n ) {\n const composeKey = `compose-${draft.id}`;\n // A compact deep link may carry only `{ id, subject }` when the\n // full draft was too large to inline in the URL. The complete\n // draft is already persisted at `compose-<id>` by manage-draft\n // on create/update. Never let the truncated stub overwrite that\n // richer saved draft (would silently lose body / recipients /\n // reply metadata). Only write when the payload actually carries\n // content, or when nothing is saved yet (composer still opens).\n const hasContent =\n (typeof draft.body === \"string\" && draft.body.length > 0) ||\n !!draft.to ||\n !!draft.cc ||\n !!draft.bcc ||\n !!draft.html ||\n !!draft.replyToThreadId;\n const existing = hasContent\n ? null\n : await appStateGet(session.email, composeKey);\n if (hasContent || !existing) {\n await appStatePut(session.email, composeKey, draft, {\n requestSource: \"deep-link\",\n });\n }\n }\n } catch {\n // Malformed compose payload — skip; the view still opens.\n }\n }\n } catch {\n // App-state write failure shouldn't 500 the click; the redirect\n // below still lands the user on the right view.\n }\n }\n\n // Resolve the SPA path to redirect to.\n let target =\n safeRelativePath(toParam) ??\n safeRelativePath(\n options.resolveOpenPath?.({ app, view, params: navParams }) ??\n (view ? `/${view}` : null),\n ) ??\n \"/\";\n\n // Forward filter params (f_*) onto the redirect so dashboards/lists open\n // pre-filtered even before the navigate command is drained.\n const filters = new URLSearchParams();\n for (const [k, v] of search.entries()) {\n if (k.startsWith(\"f_\")) filters.set(k, v);\n }\n target = appendSearchParams(target, filters);\n const embedParams = new URLSearchParams();\n for (const key of [\n EMBED_MODE_QUERY_PARAM,\n EMBED_TOKEN_QUERY_PARAM,\n MCP_APP_CHAT_BRIDGE_QUERY_PARAM,\n ]) {\n const value = search.get(key);\n if (value) embedParams.set(key, value);\n }\n target = appendSearchParams(target, embedParams);\n target = withCollapsedAgentSidebarParam(target);\n target = withConfiguredRedirectBasePath(target);\n\n return redirect(target);\n });\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"security-headers.d.ts","sourceRoot":"","sources":["../../src/server/security-headers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AA8BH;;;;;;GAMG;AACH,wBAAgB,+BAA+B,8EA6B9C"}
1
+ {"version":3,"file":"security-headers.d.ts","sourceRoot":"","sources":["../../src/server/security-headers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAkCH;;;;;;GAMG;AACH,wBAAgB,+BAA+B,8EA4C9C"}
@@ -49,8 +49,9 @@
49
49
  * iframe editor and template embed use cases. COOP + CORP without COEP gives
50
50
  * us most of the protection.
51
51
  */
52
- import { defineEventHandler, setResponseHeader } from "h3";
52
+ import { defineEventHandler, getHeader, setResponseHeader } from "h3";
53
53
  import { requestHasEmbedAuthMarker } from "./embed-session.js";
54
+ import { isClaudeMcpContentOrigin, MCP_EMBED_CORS_ALLOW_HEADERS, } from "../shared/mcp-embed-headers.js";
54
55
  const HSTS = "max-age=31536000; includeSubDomains; preload";
55
56
  const PERMISSIONS_POLICY = "camera=(), microphone=(self), geolocation=(), screen-wake-lock=()";
56
57
  /**
@@ -86,6 +87,7 @@ export function createSecurityHeadersMiddleware() {
86
87
  const isProduction = process.env.NODE_ENV === "production";
87
88
  return defineEventHandler((event) => {
88
89
  const embedFrameRequest = requestHasEmbedAuthMarker(event);
90
+ const requestOrigin = getHeader(event, "origin");
89
91
  setResponseHeader(event, "X-Content-Type-Options", "nosniff");
90
92
  if (isProduction && !embedFrameRequest) {
91
93
  setResponseHeader(event, "X-Frame-Options", "DENY");
@@ -97,6 +99,12 @@ export function createSecurityHeadersMiddleware() {
97
99
  setResponseHeader(event, "Cross-Origin-Embedder-Policy", "require-corp");
98
100
  }
99
101
  setResponseHeader(event, "Cross-Origin-Resource-Policy", embedFrameRequest ? "cross-origin" : "same-site");
102
+ if (embedFrameRequest && isClaudeMcpContentOrigin(requestOrigin)) {
103
+ setResponseHeader(event, "Access-Control-Allow-Origin", requestOrigin);
104
+ setResponseHeader(event, "Vary", "Origin");
105
+ setResponseHeader(event, "Access-Control-Allow-Methods", "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS");
106
+ setResponseHeader(event, "Access-Control-Allow-Headers", MCP_EMBED_CORS_ALLOW_HEADERS);
107
+ }
100
108
  if (isHttpsRequest(event)) {
101
109
  setResponseHeader(event, "Strict-Transport-Security", HSTS);
102
110
  }
@@ -1 +1 @@
1
- {"version":3,"file":"security-headers.js","sourceRoot":"","sources":["../../src/server/security-headers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,IAAI,CAAC;AAC3D,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAE/D,MAAM,IAAI,GAAG,8CAA8C,CAAC;AAC5D,MAAM,kBAAkB,GACtB,mEAAmE,CAAC;AAEtE;;;;;GAKG;AACH,SAAS,cAAc,CAAC,KAAU;IAChC,MAAM,GAAG,GACP,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,mBAAmB,CAAC;QAChD,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,mBAAmB,CAAC,CAAC;IAC7C,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,OAAO;QACjE,OAAO,IAAI,CAAC;IACd,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1D,uDAAuD;IACvD,MAAM,KAAK,GAAG,KAAK,EAAE,GAAG,EAAE,QAAQ,CAAC;IACnC,IAAI,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACpC,2DAA2D;IAC3D,IAAI,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,SAAS;QAAE,OAAO,IAAI,CAAC;IACzD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,+BAA+B;IAC7C,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAC;IAC3D,OAAO,kBAAkB,CAAC,CAAC,KAAK,EAAE,EAAE;QAClC,MAAM,iBAAiB,GAAG,yBAAyB,CAAC,KAAK,CAAC,CAAC;QAC3D,iBAAiB,CAAC,KAAK,EAAE,wBAAwB,EAAE,SAAS,CAAC,CAAC;QAC9D,IAAI,YAAY,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACvC,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,EAAE,MAAM,CAAC,CAAC;QACtD,CAAC;QACD,iBAAiB,CACf,KAAK,EACL,iBAAiB,EACjB,iBAAiB,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,iCAAiC,CACtE,CAAC;QACF,iBAAiB,CAAC,KAAK,EAAE,oBAAoB,EAAE,kBAAkB,CAAC,CAAC;QACnE,iBAAiB,CAAC,KAAK,EAAE,4BAA4B,EAAE,aAAa,CAAC,CAAC;QACtE,IAAI,iBAAiB,EAAE,CAAC;YACtB,iBAAiB,CAAC,KAAK,EAAE,8BAA8B,EAAE,cAAc,CAAC,CAAC;QAC3E,CAAC;QACD,iBAAiB,CACf,KAAK,EACL,8BAA8B,EAC9B,iBAAiB,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,WAAW,CACjD,CAAC;QACF,IAAI,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,iBAAiB,CAAC,KAAK,EAAE,2BAA2B,EAAE,IAAI,CAAC,CAAC;QAC9D,CAAC;QACD,2EAA2E;QAC3E,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * Security response headers middleware.\n *\n * Sets a baseline set of \"no-brainer\" security headers on every framework HTTP\n * response. These headers are layered defenses: each one mitigates a specific\n * class of attack, and together they harden the surface against clickjacking,\n * MIME-sniffing, referrer leakage, mixed-content downgrades, and cross-origin\n * window/embed access.\n *\n * The headers we emit:\n *\n * - `Strict-Transport-Security` — forces HTTPS for the browser's lifetime\n * of the cached value, preventing SSL-strip MITM. Only emitted when the\n * request scheme is `https` (we don't want to break local-dev HTTP, and\n * emitting HSTS over HTTP is a no-op per the spec but causes confusion).\n * - `X-Content-Type-Options: nosniff` — disables browser MIME sniffing so\n * a tool /render route serving user-authored HTML can't be misinterpreted\n * as some other content type by a clever Accept header.\n * - `X-Frame-Options: DENY` — prevents the entire app from being iframed by\n * other origins (clickjacking the agent chat, booking pages, etc.). The\n * tool /render endpoint and any other route that legitimately needs to be\n * embedded in the same-origin app shell can opt out by setting its own\n * header inside the route handler — h3's `setResponseHeader` overwrites,\n * so a route emitting `SAMEORIGIN` wins over our middleware default.\n * We skip this header entirely in dev (NODE_ENV !== \"production\") so the\n * desktop app's local dev frame (localhost:3334) can iframe templates\n * running on other localhost ports (e.g. mail at 8085).\n * - `Referrer-Policy: strict-origin-when-cross-origin` — strips path/query\n * from outbound Referer headers when the request crosses origin, so a\n * public-share viewer's outbound link clicks never leak the share token.\n * - `Permissions-Policy: camera=(), microphone=(self), geolocation=(),\n * screen-wake-lock=()` — allows the app shell to request microphone access\n * for composer dictation while keeping camera/location/wake-lock blocked\n * by default. Templates that need broader media capture for recording UI\n * override this on their own routes.\n * - `Cross-Origin-Opener-Policy: same-origin` — isolates window.opener so\n * a popup-window opener reference can't read or modify our document.\n * - `Cross-Origin-Embedder-Policy: require-corp` — emitted only for\n * validated MCP embed-session page loads. COEP hosts such as Claude's MCP\n * Apps proxy require framed cross-origin documents to opt in explicitly.\n * - `Cross-Origin-Resource-Policy: same-site` — prevents other origins from\n * embedding our endpoints as `<img>` / `<script>` / `<audio>`, blocking\n * the simplest data-leak chain when combined with auth cookies. Validated\n * MCP embed-session page loads use `cross-origin` so COEP hosts such as\n * Claude's MCP Apps proxy can frame the short-lived app document.\n *\n * NOTE: We don't set `Cross-Origin-Embedder-Policy` because it requires every\n * embedded subresource to opt in via CORP/CORS, which would break Builder's\n * iframe editor and template embed use cases. COOP + CORP without COEP gives\n * us most of the protection.\n */\n\nimport { defineEventHandler, setResponseHeader } from \"h3\";\nimport { requestHasEmbedAuthMarker } from \"./embed-session.js\";\n\nconst HSTS = \"max-age=31536000; includeSubDomains; preload\";\nconst PERMISSIONS_POLICY =\n \"camera=(), microphone=(self), geolocation=(), screen-wake-lock=()\";\n\n/**\n * Returns true when the request was received over HTTPS. We trust both the\n * underlying connection (when the server is terminating TLS itself) and the\n * `x-forwarded-proto` header (set by Netlify, Vercel, Cloudflare, and any\n * other reverse proxy that fronts the framework).\n */\nfunction isHttpsRequest(event: any): boolean {\n const xfp =\n event?.node?.req?.headers?.[\"x-forwarded-proto\"] ??\n event?.headers?.get?.(\"x-forwarded-proto\");\n if (typeof xfp === \"string\" && xfp.split(\",\")[0].trim() === \"https\")\n return true;\n if (Array.isArray(xfp) && xfp[0] === \"https\") return true;\n // h3 sets `event.url.protocol` to \"http:\" or \"https:\".\n const proto = event?.url?.protocol;\n if (proto === \"https:\") return true;\n // Direct Node `req.connection.encrypted` (older runtimes).\n if (event?.node?.req?.connection?.encrypted) return true;\n return false;\n}\n\n/**\n * Create the security-headers h3 middleware. Mount this BEFORE other route\n * handlers so the headers are present on every response (including 4xx/5xx\n * error pages). Route handlers that need to relax a specific header (e.g.\n * `X-Frame-Options: SAMEORIGIN` on the tool render route) can call\n * `setResponseHeader` after this runs — the latest write wins.\n */\nexport function createSecurityHeadersMiddleware() {\n const isProduction = process.env.NODE_ENV === \"production\";\n return defineEventHandler((event) => {\n const embedFrameRequest = requestHasEmbedAuthMarker(event);\n setResponseHeader(event, \"X-Content-Type-Options\", \"nosniff\");\n if (isProduction && !embedFrameRequest) {\n setResponseHeader(event, \"X-Frame-Options\", \"DENY\");\n }\n setResponseHeader(\n event,\n \"Referrer-Policy\",\n embedFrameRequest ? \"no-referrer\" : \"strict-origin-when-cross-origin\",\n );\n setResponseHeader(event, \"Permissions-Policy\", PERMISSIONS_POLICY);\n setResponseHeader(event, \"Cross-Origin-Opener-Policy\", \"same-origin\");\n if (embedFrameRequest) {\n setResponseHeader(event, \"Cross-Origin-Embedder-Policy\", \"require-corp\");\n }\n setResponseHeader(\n event,\n \"Cross-Origin-Resource-Policy\",\n embedFrameRequest ? \"cross-origin\" : \"same-site\",\n );\n if (isHttpsRequest(event)) {\n setResponseHeader(event, \"Strict-Transport-Security\", HSTS);\n }\n // Continue to the next handler — we only set headers, don't return a body.\n return undefined;\n });\n}\n"]}
1
+ {"version":3,"file":"security-headers.js","sourceRoot":"","sources":["../../src/server/security-headers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Security response headers middleware.\n *\n * Sets a baseline set of \"no-brainer\" security headers on every framework HTTP\n * response. These headers are layered defenses: each one mitigates a specific\n * class of attack, and together they harden the surface against clickjacking,\n * MIME-sniffing, referrer leakage, mixed-content downgrades, and cross-origin\n * window/embed access.\n *\n * The headers we emit:\n *\n * - `Strict-Transport-Security` — forces HTTPS for the browser's lifetime\n * of the cached value, preventing SSL-strip MITM. Only emitted when the\n * request scheme is `https` (we don't want to break local-dev HTTP, and\n * emitting HSTS over HTTP is a no-op per the spec but causes confusion).\n * - `X-Content-Type-Options: nosniff` — disables browser MIME sniffing so\n * a tool /render route serving user-authored HTML can't be misinterpreted\n * as some other content type by a clever Accept header.\n * - `X-Frame-Options: DENY` — prevents the entire app from being iframed by\n * other origins (clickjacking the agent chat, booking pages, etc.). The\n * tool /render endpoint and any other route that legitimately needs to be\n * embedded in the same-origin app shell can opt out by setting its own\n * header inside the route handler — h3's `setResponseHeader` overwrites,\n * so a route emitting `SAMEORIGIN` wins over our middleware default.\n * We skip this header entirely in dev (NODE_ENV !== \"production\") so the\n * desktop app's local dev frame (localhost:3334) can iframe templates\n * running on other localhost ports (e.g. mail at 8085).\n * - `Referrer-Policy: strict-origin-when-cross-origin` — strips path/query\n * from outbound Referer headers when the request crosses origin, so a\n * public-share viewer's outbound link clicks never leak the share token.\n * - `Permissions-Policy: camera=(), microphone=(self), geolocation=(),\n * screen-wake-lock=()` — allows the app shell to request microphone access\n * for composer dictation while keeping camera/location/wake-lock blocked\n * by default. Templates that need broader media capture for recording UI\n * override this on their own routes.\n * - `Cross-Origin-Opener-Policy: same-origin` — isolates window.opener so\n * a popup-window opener reference can't read or modify our document.\n * - `Cross-Origin-Embedder-Policy: require-corp` — emitted only for\n * validated MCP embed-session page loads. COEP hosts such as Claude's MCP\n * Apps proxy require framed cross-origin documents to opt in explicitly.\n * - `Cross-Origin-Resource-Policy: same-site` — prevents other origins from\n * embedding our endpoints as `<img>` / `<script>` / `<audio>`, blocking\n * the simplest data-leak chain when combined with auth cookies. Validated\n * MCP embed-session page loads use `cross-origin` so COEP hosts such as\n * Claude's MCP Apps proxy can frame the short-lived app document.\n *\n * NOTE: We don't set `Cross-Origin-Embedder-Policy` because it requires every\n * embedded subresource to opt in via CORP/CORS, which would break Builder's\n * iframe editor and template embed use cases. COOP + CORP without COEP gives\n * us most of the protection.\n */\n\nimport { defineEventHandler, getHeader, setResponseHeader } from \"h3\";\nimport { requestHasEmbedAuthMarker } from \"./embed-session.js\";\nimport {\n isClaudeMcpContentOrigin,\n MCP_EMBED_CORS_ALLOW_HEADERS,\n} from \"../shared/mcp-embed-headers.js\";\n\nconst HSTS = \"max-age=31536000; includeSubDomains; preload\";\nconst PERMISSIONS_POLICY =\n \"camera=(), microphone=(self), geolocation=(), screen-wake-lock=()\";\n\n/**\n * Returns true when the request was received over HTTPS. We trust both the\n * underlying connection (when the server is terminating TLS itself) and the\n * `x-forwarded-proto` header (set by Netlify, Vercel, Cloudflare, and any\n * other reverse proxy that fronts the framework).\n */\nfunction isHttpsRequest(event: any): boolean {\n const xfp =\n event?.node?.req?.headers?.[\"x-forwarded-proto\"] ??\n event?.headers?.get?.(\"x-forwarded-proto\");\n if (typeof xfp === \"string\" && xfp.split(\",\")[0].trim() === \"https\")\n return true;\n if (Array.isArray(xfp) && xfp[0] === \"https\") return true;\n // h3 sets `event.url.protocol` to \"http:\" or \"https:\".\n const proto = event?.url?.protocol;\n if (proto === \"https:\") return true;\n // Direct Node `req.connection.encrypted` (older runtimes).\n if (event?.node?.req?.connection?.encrypted) return true;\n return false;\n}\n\n/**\n * Create the security-headers h3 middleware. Mount this BEFORE other route\n * handlers so the headers are present on every response (including 4xx/5xx\n * error pages). Route handlers that need to relax a specific header (e.g.\n * `X-Frame-Options: SAMEORIGIN` on the tool render route) can call\n * `setResponseHeader` after this runs — the latest write wins.\n */\nexport function createSecurityHeadersMiddleware() {\n const isProduction = process.env.NODE_ENV === \"production\";\n return defineEventHandler((event) => {\n const embedFrameRequest = requestHasEmbedAuthMarker(event);\n const requestOrigin = getHeader(event, \"origin\");\n setResponseHeader(event, \"X-Content-Type-Options\", \"nosniff\");\n if (isProduction && !embedFrameRequest) {\n setResponseHeader(event, \"X-Frame-Options\", \"DENY\");\n }\n setResponseHeader(\n event,\n \"Referrer-Policy\",\n embedFrameRequest ? \"no-referrer\" : \"strict-origin-when-cross-origin\",\n );\n setResponseHeader(event, \"Permissions-Policy\", PERMISSIONS_POLICY);\n setResponseHeader(event, \"Cross-Origin-Opener-Policy\", \"same-origin\");\n if (embedFrameRequest) {\n setResponseHeader(event, \"Cross-Origin-Embedder-Policy\", \"require-corp\");\n }\n setResponseHeader(\n event,\n \"Cross-Origin-Resource-Policy\",\n embedFrameRequest ? \"cross-origin\" : \"same-site\",\n );\n if (embedFrameRequest && isClaudeMcpContentOrigin(requestOrigin)) {\n setResponseHeader(event, \"Access-Control-Allow-Origin\", requestOrigin);\n setResponseHeader(event, \"Vary\", \"Origin\");\n setResponseHeader(\n event,\n \"Access-Control-Allow-Methods\",\n \"GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS\",\n );\n setResponseHeader(\n event,\n \"Access-Control-Allow-Headers\",\n MCP_EMBED_CORS_ALLOW_HEADERS,\n );\n }\n if (isHttpsRequest(event)) {\n setResponseHeader(event, \"Strict-Transport-Security\", HSTS);\n }\n // Continue to the next handler — we only set headers, don't return a body.\n return undefined;\n });\n}\n"]}
@@ -1,3 +1,5 @@
1
+ export declare const DEFAULT_SSR_CACHE_CONTROL = "public, max-age=5, stale-while-revalidate=604800, stale-if-error=3600";
2
+ export declare const AUTHENTICATED_SSR_CACHE_CONTROL = "private, max-age=5, stale-while-revalidate=604800, stale-if-error=3600";
1
3
  /**
2
4
  * Create an h3 catch-all that hands page routes to React Router and
3
5
  * returns 404 for framework / asset paths that React Router doesn't own.
@@ -1 +1 @@
1
- {"version":3,"file":"ssr-handler.d.ts","sourceRoot":"","sources":["../../src/server/ssr-handler.ts"],"names":[],"mappings":"AAgMA;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,2FAgE5E"}
1
+ {"version":3,"file":"ssr-handler.d.ts","sourceRoot":"","sources":["../../src/server/ssr-handler.ts"],"names":[],"mappings":"AA4BA,eAAO,MAAM,yBAAyB,0EACmC,CAAC;AAC1E,eAAO,MAAM,+BAA+B,2EAC8B,CAAC;AA0O3E;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,2FAqE5E"}