sunpeak 0.19.2 → 0.19.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +6 -4
  2. package/bin/commands/dev.mjs +1 -1
  3. package/bin/commands/inspect.mjs +1 -1
  4. package/bin/commands/new.mjs +9 -5
  5. package/bin/commands/start.mjs +3 -1
  6. package/bin/commands/test-init.mjs +478 -76
  7. package/bin/commands/test.mjs +357 -4
  8. package/bin/lib/eval/eval-reporter.mjs +105 -0
  9. package/bin/lib/eval/eval-runner.mjs +310 -0
  10. package/bin/lib/eval/eval-types.d.mts +168 -0
  11. package/bin/lib/eval/eval-vitest-plugin.mjs +158 -0
  12. package/bin/lib/eval/model-registry.mjs +73 -0
  13. package/bin/lib/sandbox-server.mjs +5 -2
  14. package/bin/sunpeak.js +1 -0
  15. package/dist/chatgpt/index.cjs +1 -1
  16. package/dist/chatgpt/index.js +1 -1
  17. package/dist/claude/index.cjs +1 -1
  18. package/dist/claude/index.js +1 -1
  19. package/dist/host/chatgpt/index.cjs +1 -1
  20. package/dist/host/chatgpt/index.js +1 -1
  21. package/dist/index.cjs +134 -124
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.ts +3 -1
  24. package/dist/index.js +71 -62
  25. package/dist/index.js.map +1 -1
  26. package/dist/inspector/index.cjs +1 -1
  27. package/dist/inspector/index.js +1 -1
  28. package/dist/{inspector-Cdo5BK2D.js → inspector-D5DckQuU.js} +236 -98
  29. package/dist/inspector-D5DckQuU.js.map +1 -0
  30. package/dist/{inspector-8nPV2A-z.cjs → inspector-jY9O18z9.cjs} +237 -99
  31. package/dist/inspector-jY9O18z9.cjs.map +1 -0
  32. package/dist/mcp/index.cjs +237 -140
  33. package/dist/mcp/index.cjs.map +1 -1
  34. package/dist/mcp/index.d.ts +1 -1
  35. package/dist/mcp/index.js +230 -134
  36. package/dist/mcp/index.js.map +1 -1
  37. package/dist/mcp/production-server.d.ts +31 -0
  38. package/dist/{protocol-C7kTcBr_.cjs → protocol-C8pFDmcy.cjs} +8194 -8187
  39. package/dist/protocol-C8pFDmcy.cjs.map +1 -0
  40. package/dist/{protocol-BfAACnv0.js → protocol-CRqiPTLT.js} +8186 -8185
  41. package/dist/protocol-CRqiPTLT.js.map +1 -0
  42. package/dist/{use-app-CfP9VypY.js → use-app-Bfargfa3.js} +194 -94
  43. package/dist/use-app-Bfargfa3.js.map +1 -0
  44. package/dist/{use-app-CzcYw1Kz.cjs → use-app-CbsBEmwv.cjs} +254 -148
  45. package/dist/use-app-CbsBEmwv.cjs.map +1 -0
  46. package/package.json +27 -3
  47. package/template/README.md +17 -7
  48. package/template/_gitignore +2 -0
  49. package/template/dist/albums/albums.html +15 -15
  50. package/template/dist/albums/albums.json +1 -1
  51. package/template/dist/carousel/carousel.html +19 -19
  52. package/template/dist/carousel/carousel.json +1 -1
  53. package/template/dist/map/map.html +14 -14
  54. package/template/dist/map/map.json +1 -1
  55. package/template/dist/review/review.html +11 -11
  56. package/template/dist/review/review.json +1 -1
  57. package/template/node_modules/.bin/vitest +2 -2
  58. package/template/node_modules/.vite/deps/_metadata.json +3 -3
  59. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps.js +192 -91
  60. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps.js.map +1 -1
  61. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_app-bridge.js +231 -92
  62. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_app-bridge.js.map +1 -1
  63. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_react.js +208 -105
  64. package/template/node_modules/.vite-mcp/deps/@modelcontextprotocol_ext-apps_react.js.map +1 -1
  65. package/template/node_modules/.vite-mcp/deps/_metadata.json +25 -25
  66. package/template/node_modules/.vite-mcp/deps/{protocol-B_qKkui_.js → protocol-BqGB4zBx.js} +45 -45
  67. package/template/node_modules/.vite-mcp/deps/protocol-BqGB4zBx.js.map +1 -0
  68. package/template/node_modules/.vite-mcp/deps/vitest.js +7 -7
  69. package/template/node_modules/.vite-mcp/deps/vitest.js.map +1 -1
  70. package/template/tests/e2e/visual.spec.ts-snapshots/albums-dark-chatgpt-darwin.png +0 -0
  71. package/template/tests/e2e/visual.spec.ts-snapshots/albums-dark-claude-darwin.png +0 -0
  72. package/template/tests/e2e/visual.spec.ts-snapshots/albums-fullscreen-chatgpt-darwin.png +0 -0
  73. package/template/tests/e2e/visual.spec.ts-snapshots/albums-fullscreen-claude-darwin.png +0 -0
  74. package/template/tests/e2e/visual.spec.ts-snapshots/albums-light-chatgpt-darwin.png +0 -0
  75. package/template/tests/e2e/visual.spec.ts-snapshots/albums-light-claude-darwin.png +0 -0
  76. package/template/tests/e2e/visual.spec.ts-snapshots/albums-page-light-chatgpt-darwin.png +0 -0
  77. package/template/tests/e2e/visual.spec.ts-snapshots/albums-page-light-claude-darwin.png +0 -0
  78. package/template/tests/evals/.env.example +5 -0
  79. package/template/tests/evals/albums.eval.ts +28 -0
  80. package/template/tests/evals/carousel.eval.ts +26 -0
  81. package/template/tests/evals/eval.config.ts +26 -0
  82. package/template/tests/evals/map.eval.ts +23 -0
  83. package/template/tests/evals/review.eval.ts +48 -0
  84. package/dist/inspector-8nPV2A-z.cjs.map +0 -1
  85. package/dist/inspector-Cdo5BK2D.js.map +0 -1
  86. package/dist/protocol-BfAACnv0.js.map +0 -1
  87. package/dist/protocol-C7kTcBr_.cjs.map +0 -1
  88. package/dist/use-app-CfP9VypY.js.map +0 -1
  89. package/dist/use-app-CzcYw1Kz.cjs.map +0 -1
  90. package/template/node_modules/.vite-mcp/deps/protocol-B_qKkui_.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../chunk-9hOWP6kD.cjs");
3
- const require_protocol = require("../protocol-C7kTcBr_.cjs");
3
+ const require_protocol = require("../protocol-C8pFDmcy.cjs");
4
4
  let node_http = require("node:http");
5
5
  let node_url = require("node:url");
6
6
  let node_fs = require("node:fs");
@@ -8684,12 +8684,12 @@ var StreamableHTTPServerTransport = class {
8684
8684
  }
8685
8685
  };
8686
8686
  //#endregion
8687
- //#region ../../node_modules/.pnpm/@modelcontextprotocol+ext-apps@1.3.2_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6__react-_38e5f6c50a4ca9cf3f1dffc73a60c951/node_modules/@modelcontextprotocol/ext-apps/dist/src/server/index.js
8688
- var M = require_protocol.union([require_protocol.literal("light"), require_protocol.literal("dark")]).describe("Color theme preference for the host environment."), G = require_protocol.union([
8687
+ //#region ../../node_modules/.pnpm/@modelcontextprotocol+ext-apps@1.5.0_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6__react-_f4871531d9cf52c692eb6edc1ee416ef/node_modules/@modelcontextprotocol/ext-apps/dist/src/server/index.js
8688
+ var S = require_protocol.union([require_protocol.literal("light"), require_protocol.literal("dark")]).describe("Color theme preference for the host environment."), W = require_protocol.union([
8689
8689
  require_protocol.literal("inline"),
8690
8690
  require_protocol.literal("fullscreen"),
8691
8691
  require_protocol.literal("pip")
8692
- ]).describe("Display mode for UI presentation."), p = require_protocol.union([
8692
+ ]).describe("Display mode for UI presentation."), i = require_protocol.union([
8693
8693
  require_protocol.literal("--color-background-primary"),
8694
8694
  require_protocol.literal("--color-background-secondary"),
8695
8695
  require_protocol.literal("--color-background-tertiary"),
@@ -8766,7 +8766,7 @@ var M = require_protocol.union([require_protocol.literal("light"), require_proto
8766
8766
  require_protocol.literal("--shadow-sm"),
8767
8767
  require_protocol.literal("--shadow-md"),
8768
8768
  require_protocol.literal("--shadow-lg")
8769
- ]).describe("CSS variable keys available to MCP apps for theming."), r = require_protocol.record(p.describe(`Style variables for theming MCP apps.
8769
+ ]).describe("CSS variable keys available to MCP apps for theming."), n = require_protocol.record(i.describe(`Style variables for theming MCP apps.
8770
8770
 
8771
8771
  Individual style keys are optional - hosts may provide any subset of these values.
8772
8772
  Values are strings containing CSS values (colors, sizes, font stacks, etc.).
@@ -8796,7 +8796,7 @@ require_protocol.object({
8796
8796
  method: require_protocol.literal("ui/notifications/sandbox-proxy-ready"),
8797
8797
  params: require_protocol.object({})
8798
8798
  });
8799
- var B = require_protocol.object({
8799
+ var K = require_protocol.object({
8800
8800
  connectDomains: require_protocol.array(require_protocol.string()).optional().describe(`Origins for network requests (fetch/XHR/WebSocket).
8801
8801
 
8802
8802
  - Maps to CSP \`connect-src\` directive
@@ -8804,7 +8804,7 @@ var B = require_protocol.object({
8804
8804
  resourceDomains: require_protocol.array(require_protocol.string()).optional().describe("Origins for static resources (images, scripts, stylesheets, fonts, media).\n\n- Maps to CSP `img-src`, `script-src`, `style-src`, `font-src`, `media-src` directives\n- Wildcard subdomains supported: `https://*.example.com`\n- Empty or omitted → no network resources (secure default)"),
8805
8805
  frameDomains: require_protocol.array(require_protocol.string()).optional().describe("Origins for nested iframes.\n\n- Maps to CSP `frame-src` directive\n- Empty or omitted → no nested iframes allowed (`frame-src 'none'`)"),
8806
8806
  baseUriDomains: require_protocol.array(require_protocol.string()).optional().describe("Allowed base URIs for the document.\n\n- Maps to CSP `base-uri` directive\n- Empty or omitted → only same origin allowed (`base-uri 'self'`)")
8807
- }), K = require_protocol.object({
8807
+ }), N = require_protocol.object({
8808
8808
  camera: require_protocol.object({}).optional().describe("Request camera access.\n\nMaps to Permission Policy `camera` feature."),
8809
8809
  microphone: require_protocol.object({}).optional().describe("Request microphone access.\n\nMaps to Permission Policy `microphone` feature."),
8810
8810
  geolocation: require_protocol.object({}).optional().describe("Request geolocation access.\n\nMaps to Permission Policy `geolocation` feature."),
@@ -8829,8 +8829,8 @@ require_protocol.object({
8829
8829
  method: require_protocol.literal("ui/notifications/tool-cancelled"),
8830
8830
  params: require_protocol.object({ reason: require_protocol.string().optional().describe("Optional reason for the cancellation (e.g., \"user action\", \"timeout\").") })
8831
8831
  });
8832
- var b = require_protocol.object({ fonts: require_protocol.string().optional() }), g = require_protocol.object({
8833
- variables: r.optional().describe("CSS variables for theming the app."),
8832
+ var b = require_protocol.object({ fonts: require_protocol.string().optional() }), C = require_protocol.object({
8833
+ variables: n.optional().describe("CSS variables for theming the app."),
8834
8834
  css: b.optional().describe("CSS blocks that apps can inject.")
8835
8835
  });
8836
8836
  require_protocol.object({
@@ -8838,7 +8838,7 @@ require_protocol.object({
8838
8838
  params: require_protocol.object({})
8839
8839
  });
8840
8840
  require_protocol.record(require_protocol.string(), require_protocol.unknown());
8841
- var z$1 = require_protocol.object({
8841
+ var q = require_protocol.object({
8842
8842
  text: require_protocol.object({}).optional().describe("Host supports text content blocks."),
8843
8843
  image: require_protocol.object({}).optional().describe("Host supports image content blocks."),
8844
8844
  audio: require_protocol.object({}).optional().describe("Host supports audio content blocks."),
@@ -8850,7 +8850,7 @@ require_protocol.object({
8850
8850
  method: require_protocol.literal("ui/notifications/request-teardown"),
8851
8851
  params: require_protocol.object({}).optional()
8852
8852
  });
8853
- var S = require_protocol.object({
8853
+ var y = require_protocol.object({
8854
8854
  experimental: require_protocol.object({}).optional().describe("Experimental features (structure TBD)."),
8855
8855
  openLinks: require_protocol.object({}).optional().describe("Host supports opening external URLs."),
8856
8856
  downloadFile: require_protocol.object({}).optional().describe("Host supports file downloads via ui/download-file."),
@@ -8858,23 +8858,23 @@ var S = require_protocol.object({
8858
8858
  serverResources: require_protocol.object({ listChanged: require_protocol.boolean().optional().describe("Host supports resources/list_changed notifications.") }).optional().describe("Host can proxy resource reads to the MCP server."),
8859
8859
  logging: require_protocol.object({}).optional().describe("Host accepts log messages."),
8860
8860
  sandbox: require_protocol.object({
8861
- permissions: K.optional().describe("Permissions granted by the host (camera, microphone, geolocation)."),
8862
- csp: B.optional().describe("CSP domains approved by the host.")
8861
+ permissions: N.optional().describe("Permissions granted by the host (camera, microphone, geolocation)."),
8862
+ csp: K.optional().describe("CSP domains approved by the host.")
8863
8863
  }).optional().describe("Sandbox configuration applied by the host."),
8864
- updateModelContext: z$1.optional().describe("Host accepts context updates (ui/update-model-context) to be included in the model's context for future turns."),
8865
- message: z$1.optional().describe("Host supports receiving content messages (ui/message) from the view.")
8866
- }), y = require_protocol.object({
8864
+ updateModelContext: q.optional().describe("Host accepts context updates (ui/update-model-context) to be included in the model's context for future turns."),
8865
+ message: q.optional().describe("Host supports receiving content messages (ui/message) from the view.")
8866
+ }), v = require_protocol.object({
8867
8867
  experimental: require_protocol.object({}).optional().describe("Experimental features (structure TBD)."),
8868
8868
  tools: require_protocol.object({ listChanged: require_protocol.boolean().optional().describe("App supports tools/list_changed notifications.") }).optional().describe("App exposes MCP-style tools that the host can call."),
8869
- availableDisplayModes: require_protocol.array(G).optional().describe("Display modes the app supports.")
8869
+ availableDisplayModes: require_protocol.array(W).optional().describe("Display modes the app supports.")
8870
8870
  });
8871
8871
  require_protocol.object({
8872
8872
  method: require_protocol.literal("ui/notifications/initialized"),
8873
8873
  params: require_protocol.object({}).optional()
8874
8874
  });
8875
8875
  require_protocol.object({
8876
- csp: B.optional().describe("Content Security Policy configuration for UI resources."),
8877
- permissions: K.optional().describe("Sandbox permissions requested by the UI resource."),
8876
+ csp: K.optional().describe("Content Security Policy configuration for UI resources."),
8877
+ permissions: N.optional().describe("Sandbox permissions requested by the UI resource."),
8878
8878
  domain: require_protocol.string().optional().describe(`Dedicated origin for view sandbox.
8879
8879
 
8880
8880
  Useful when views need stable, dedicated origins for OAuth callbacks, CORS policies, or API key allowlists.
@@ -8894,13 +8894,13 @@ Boolean requesting whether a visible border and background is provided by the ho
8894
8894
  });
8895
8895
  require_protocol.object({
8896
8896
  method: require_protocol.literal("ui/request-display-mode"),
8897
- params: require_protocol.object({ mode: G.describe("The display mode being requested.") })
8897
+ params: require_protocol.object({ mode: W.describe("The display mode being requested.") })
8898
8898
  });
8899
- require_protocol.object({ mode: G.describe("The display mode that was actually set. May differ from requested if not supported.") }).passthrough();
8900
- var C = require_protocol.union([require_protocol.literal("model"), require_protocol.literal("app")]).describe("Tool visibility scope - who can access the tool.");
8899
+ require_protocol.object({ mode: W.describe("The display mode that was actually set. May differ from requested if not supported.") }).passthrough();
8900
+ var f = require_protocol.union([require_protocol.literal("model"), require_protocol.literal("app")]).describe("Tool visibility scope - who can access the tool.");
8901
8901
  require_protocol.object({
8902
8902
  resourceUri: require_protocol.string().optional(),
8903
- visibility: require_protocol.array(C).optional().describe(`Who can access this tool. Default: ["model", "app"]
8903
+ visibility: require_protocol.array(f).optional().describe(`Who can access this tool. Default: ["model", "app"]
8904
8904
  - "model": Tool visible to and callable by the agent
8905
8905
  - "app": Tool callable by the app from this server only`)
8906
8906
  });
@@ -8921,8 +8921,8 @@ require_protocol.object({
8921
8921
  params: require_protocol.object({
8922
8922
  html: require_protocol.string().describe("HTML content to load into the inner iframe."),
8923
8923
  sandbox: require_protocol.string().optional().describe("Optional override for the inner iframe's sandbox attribute."),
8924
- csp: B.optional().describe("CSP configuration from resource metadata."),
8925
- permissions: K.optional().describe("Sandbox permissions from resource metadata.")
8924
+ csp: K.optional().describe("CSP configuration from resource metadata."),
8925
+ permissions: N.optional().describe("Sandbox permissions from resource metadata.")
8926
8926
  })
8927
8927
  });
8928
8928
  require_protocol.object({
@@ -8934,10 +8934,10 @@ var E = require_protocol.object({
8934
8934
  id: require_protocol.RequestIdSchema.optional().describe("JSON-RPC id of the tools/call request."),
8935
8935
  tool: require_protocol.ToolSchema.describe("Tool definition including name, inputSchema, etc.")
8936
8936
  }).optional().describe("Metadata of the tool call that instantiated this App."),
8937
- theme: M.optional().describe("Current color theme preference."),
8938
- styles: g.optional().describe("Style configuration for theming the app."),
8939
- displayMode: G.optional().describe("How the UI is currently displayed."),
8940
- availableDisplayModes: require_protocol.array(G).optional().describe("Display modes the host supports."),
8937
+ theme: S.optional().describe("Current color theme preference."),
8938
+ styles: C.optional().describe("Style configuration for theming the app."),
8939
+ displayMode: W.optional().describe("How the UI is currently displayed."),
8940
+ availableDisplayModes: require_protocol.array(W).optional().describe("Display modes the host supports."),
8941
8941
  containerDimensions: require_protocol.union([require_protocol.object({ height: require_protocol.number().describe("Fixed container height in pixels.") }), require_protocol.object({ maxHeight: require_protocol.union([require_protocol.number(), require_protocol._undefined()]).optional().describe("Maximum container height in pixels.") })]).and(require_protocol.union([require_protocol.object({ width: require_protocol.number().describe("Fixed container width in pixels.") }), require_protocol.object({ maxWidth: require_protocol.union([require_protocol.number(), require_protocol._undefined()]).optional().describe("Maximum container width in pixels.") })])).optional().describe(`Container dimensions. Represents the dimensions of the iframe or other
8942
8942
  container holding the app. Specify either width or maxWidth, and either height or maxHeight.`),
8943
8943
  locale: require_protocol.string().optional().describe("User's language and region preference in BCP 47 format."),
@@ -8974,45 +8974,45 @@ require_protocol.object({
8974
8974
  method: require_protocol.literal("ui/initialize"),
8975
8975
  params: require_protocol.object({
8976
8976
  appInfo: require_protocol.ImplementationSchema.describe("App identification (name and version)."),
8977
- appCapabilities: y.describe("Features and capabilities this app provides."),
8977
+ appCapabilities: v.describe("Features and capabilities this app provides."),
8978
8978
  protocolVersion: require_protocol.string().describe("Protocol version this app supports.")
8979
8979
  })
8980
8980
  });
8981
8981
  require_protocol.object({
8982
8982
  protocolVersion: require_protocol.string().describe("Negotiated protocol version string (e.g., \"2025-11-21\")."),
8983
8983
  hostInfo: require_protocol.ImplementationSchema.describe("Host application identification and version."),
8984
- hostCapabilities: S.describe("Features and capabilities provided by the host."),
8984
+ hostCapabilities: y.describe("Features and capabilities provided by the host."),
8985
8985
  hostContext: E.describe("Rich context about the host environment.")
8986
8986
  }).passthrough();
8987
- var U = "ui/resourceUri", f = "text/html;profile=mcp-app";
8988
- function fZ(Z, $, J, X) {
8989
- let V = J._meta, W = V.ui, L = V[U], D = V;
8990
- if (W?.resourceUri && !L) D = {
8987
+ var M = "ui/resourceUri", u = "text/html;profile=mcp-app";
8988
+ function hZ(Z, $, J, X) {
8989
+ let V = J._meta, D = V.ui, G = V[M], L = V;
8990
+ if (D?.resourceUri && !G) L = {
8991
8991
  ...V,
8992
- [U]: W.resourceUri
8992
+ [M]: D.resourceUri
8993
8993
  };
8994
- else if (L && !W?.resourceUri) D = {
8994
+ else if (G && !D?.resourceUri) L = {
8995
8995
  ...V,
8996
8996
  ui: {
8997
- ...W,
8998
- resourceUri: L
8997
+ ...D,
8998
+ resourceUri: G
8999
8999
  }
9000
9000
  };
9001
9001
  return Z.registerTool($, {
9002
9002
  ...J,
9003
- _meta: D
9003
+ _meta: L
9004
9004
  }, X);
9005
9005
  }
9006
- function uZ(Z, $, J, X, V) {
9006
+ function mZ(Z, $, J, X, V) {
9007
9007
  return Z.registerResource($, J, {
9008
- mimeType: f,
9008
+ mimeType: u,
9009
9009
  ...X
9010
9010
  }, V);
9011
9011
  }
9012
- var zQ = "io.modelcontextprotocol/ui";
9013
- function dZ(Z) {
9012
+ var qQ = "io.modelcontextprotocol/ui";
9013
+ function pZ(Z) {
9014
9014
  if (!Z) return;
9015
- return Z.extensions?.[zQ];
9015
+ return Z.extensions?.[qQ];
9016
9016
  }
9017
9017
  //#endregion
9018
9018
  //#region src/mcp/resolve-domain.ts
@@ -9327,7 +9327,7 @@ function createAppServer(config, simulations, viteMode) {
9327
9327
  registeredUriSet.add(uri);
9328
9328
  const listMeta = viteMode ? injectViteCSP(resourceMeta) : resourceMeta;
9329
9329
  const handle = mcpServer.registerResource(resourceName, uri, {
9330
- mimeType: f,
9330
+ mimeType: u,
9331
9331
  description: resource.description,
9332
9332
  _meta: listMeta
9333
9333
  }, async (readUri, extra) => {
@@ -9346,7 +9346,7 @@ function createAppServer(config, simulations, viteMode) {
9346
9346
  console.log(`[MCP] ReadResource: ${readUri.href} → ${sizeKB}KB${servedVite ? " (vite)" : " (built)"}`);
9347
9347
  return { contents: [{
9348
9348
  uri: readUri.href,
9349
- mimeType: f,
9349
+ mimeType: u,
9350
9350
  text: content,
9351
9351
  _meta: readMeta
9352
9352
  }] };
@@ -9360,7 +9360,7 @@ function createAppServer(config, simulations, viteMode) {
9360
9360
  ...toolMeta.ui?.visibility ? { visibility: toolMeta.ui.visibility } : {}
9361
9361
  }
9362
9362
  };
9363
- const toolHandle = fZ(mcpServer, tool.name, {
9363
+ const toolHandle = hZ(mcpServer, tool.name, {
9364
9364
  description: tool.description,
9365
9365
  inputSchema: zod.z.object({}).passthrough(),
9366
9366
  ...simulation.outputSchema ? { outputSchema: simulation.outputSchema } : {},
@@ -9519,6 +9519,11 @@ async function handleMcpRequest(req, res, config, simulations, viteMode) {
9519
9519
  const extra = parsed.method === "resources/read" ? ` uri=${JSON.stringify(parsed.params?.uri)}` : "";
9520
9520
  console.log(`[MCP] ← ${parsed.method}${extra}${sidStr}`);
9521
9521
  }
9522
+ if (process.env.SUNPEAK_LOG_HEADERS) {
9523
+ const headerEntries = {};
9524
+ for (const [key, value] of Object.entries(req.headers)) if (value != null) headerEntries[key] = Array.isArray(value) ? value.join(", ") : value;
9525
+ console.log(`[MCP] Headers: ${JSON.stringify(headerEntries, null, 2)}`);
9526
+ }
9522
9527
  } catch {
9523
9528
  res.writeHead(400).end("Invalid JSON");
9524
9529
  return;
@@ -9563,6 +9568,7 @@ async function handleMcpRequest(req, res, config, simulations, viteMode) {
9563
9568
  return;
9564
9569
  }
9565
9570
  if (error.message?.includes("Server not initialized")) return;
9571
+ if (error.message?.includes("Only one SSE stream")) return;
9566
9572
  const id = transport.sessionId;
9567
9573
  console.error(`[MCP] Transport error${id ? ` (${id.substring(0, 8)}...)` : ""}:`, error);
9568
9574
  };
@@ -9647,7 +9653,7 @@ function runMCPServer(config) {
9647
9653
  viteServer.ws.handleUpgrade(request, socket, head);
9648
9654
  });
9649
9655
  httpServer.on("clientError", (err, socket) => {
9650
- if (err.code === "ECONNRESET") {} else if (err.code === "HPE_INVALID_METHOD" && "rawPacket" in err && Buffer.isBuffer(err.rawPacket) && err.rawPacket[0] === 22) console.error("Received HTTPS request on HTTP server. If you're using ngrok, make sure the upstream is http:// (not https://). Example: ngrok http 8000");
9656
+ if (err.code === "ECONNRESET") {} else if (err.code === "HPE_INVALID_METHOD" && "rawPacket" in err && Buffer.isBuffer(err.rawPacket) && err.rawPacket[0] >= 20 && err.rawPacket[0] <= 24) console.error("Received HTTPS request on HTTP server. If you're using ngrok, make sure the upstream is http:// (not https://). Example: ngrok http 8000\nIf this only happens in Safari, use Chrome for `sunpeak dev` instead. Safari is not compatible with the dev server. Production deploys (`sunpeak start`) work in all browsers.");
9651
9657
  else console.error("HTTP client error", err);
9652
9658
  socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
9653
9659
  });
@@ -9741,6 +9747,20 @@ function log(level, msg, extra) {
9741
9747
  else console.log(extra ? `${prefix} ${msg} ${JSON.stringify(extra)}` : `${prefix} ${msg}`);
9742
9748
  }
9743
9749
  }
9750
+ /** Build an InternalServerConfig from any handler config + detected client name. */
9751
+ function toInternalConfig(config, clientName) {
9752
+ return {
9753
+ name: config.name,
9754
+ version: config.version,
9755
+ serverInfo: config.serverInfo,
9756
+ tools: config.tools,
9757
+ resources: config.resources,
9758
+ serverUrl: config.serverUrl,
9759
+ enableJsonResponse: config.enableJsonResponse,
9760
+ stateless: config.stateless,
9761
+ _clientName: clientName
9762
+ };
9763
+ }
9744
9764
  /**
9745
9765
  * Create an MCP server with production tool handlers and pre-built resources.
9746
9766
  *
@@ -9749,6 +9769,7 @@ function log(level, msg, extra) {
9749
9769
  */
9750
9770
  function createProductionMcpServer(config) {
9751
9771
  const { name = "sunpeak-app", version = "0.1.0", serverInfo, tools, resources, serverUrl } = config;
9772
+ const clientName = config._clientName;
9752
9773
  const mcpServer = new McpServer({
9753
9774
  name: serverInfo?.name ?? name,
9754
9775
  version: serverInfo?.version ?? version,
@@ -9764,23 +9785,9 @@ function createProductionMcpServer(config) {
9764
9785
  resources: {},
9765
9786
  tools: {}
9766
9787
  } });
9767
- let clientName;
9768
- mcpServer.server.oninitialized = () => {
9769
- clientName = mcpServer.server.getClientVersion()?.name;
9770
- for (const handle of resourceHandles) {
9771
- const currentMeta = handle.metadata?._meta;
9772
- const resolved = injectResolvedDomain(currentMeta, clientName) ?? currentMeta;
9773
- const withDefault = serverUrl ? injectDefaultDomain(resolved, clientName, serverUrl) : resolved;
9774
- if (withDefault !== resolved) handle.update({ metadata: {
9775
- ...handle.metadata,
9776
- _meta: withDefault
9777
- } });
9778
- }
9779
- };
9780
9788
  const resourceByName = /* @__PURE__ */ new Map();
9781
9789
  for (const res of resources) resourceByName.set(res.name, res);
9782
9790
  const registeredResources = /* @__PURE__ */ new Set();
9783
- const resourceHandles = [];
9784
9791
  let toolCount = 0;
9785
9792
  for (const tool of tools) {
9786
9793
  const makeCallback = () => {
@@ -9808,20 +9815,17 @@ function createProductionMcpServer(config) {
9808
9815
  if (res) {
9809
9816
  if (!registeredResources.has(res.uri)) {
9810
9817
  registeredResources.add(res.uri);
9811
- const handle = uZ(mcpServer, res.name, res.uri, {
9818
+ const resolvedMeta = injectResolvedDomain(res._meta, clientName) ?? res._meta;
9819
+ const finalMeta = serverUrl ? injectDefaultDomain(resolvedMeta, clientName, serverUrl) : resolvedMeta;
9820
+ mZ(mcpServer, res.name, res.uri, {
9812
9821
  description: res.description,
9813
- _meta: res._meta
9814
- }, async () => {
9815
- const resolved = injectResolvedDomain(res._meta, clientName) ?? res._meta;
9816
- const readMeta = serverUrl ? injectDefaultDomain(resolved, clientName, serverUrl) : resolved;
9817
- return { contents: [{
9818
- uri: res.uri,
9819
- mimeType: f,
9820
- text: res.html,
9821
- _meta: readMeta
9822
- }] };
9823
- });
9824
- resourceHandles.push(handle);
9822
+ _meta: finalMeta
9823
+ }, async () => ({ contents: [{
9824
+ uri: res.uri,
9825
+ mimeType: u,
9826
+ text: res.html,
9827
+ _meta: finalMeta
9828
+ }] }));
9825
9829
  }
9826
9830
  const toolConfig = {
9827
9831
  title: tool.tool.title,
@@ -9837,7 +9841,7 @@ function createProductionMcpServer(config) {
9837
9841
  }
9838
9842
  }
9839
9843
  };
9840
- fZ(mcpServer, tool.name, toolConfig, makeCallback());
9844
+ hZ(mcpServer, tool.name, toolConfig, makeCallback());
9841
9845
  } else {
9842
9846
  const cb = makeCallback();
9843
9847
  const toolConfig = {
@@ -9864,6 +9868,20 @@ var SESSION_IDLE_TIMEOUT_MS = 300 * 1e3;
9864
9868
  function isJsonRpcMessage(value) {
9865
9869
  return typeof value === "object" && value !== null && typeof value.method === "string";
9866
9870
  }
9871
+ function detectClientFromHeaders(headers) {
9872
+ const get = (name) => {
9873
+ if (headers instanceof Headers) return headers.get(name) ?? void 0;
9874
+ const raw = headers[name];
9875
+ return typeof raw === "string" ? raw : Array.isArray(raw) ? raw[0] : void 0;
9876
+ };
9877
+ const ua = get("user-agent");
9878
+ if (ua) {
9879
+ if (/claude/i.test(ua)) return "claude";
9880
+ if (/openai/i.test(ua)) return "openai-mcp";
9881
+ }
9882
+ if (get("x-anthropic-client")) return "claude";
9883
+ if (get("x-openai-session")) return "openai-mcp";
9884
+ }
9867
9885
  var CORS_HEADERS = {
9868
9886
  "Access-Control-Allow-Origin": "*",
9869
9887
  "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
@@ -9934,25 +9952,14 @@ async function pipeWebResponseToNode(webResponse, res) {
9934
9952
  * ```
9935
9953
  */
9936
9954
  function createMcpHandler(config) {
9937
- const sessions = /* @__PURE__ */ new Map();
9938
9955
  const authFn = config.auth;
9939
- setInterval(() => {
9940
- const now = Date.now();
9941
- for (const [id, session] of sessions) if (now - session.lastActivity > SESSION_IDLE_TIMEOUT_MS) {
9942
- log("info", `Session expired: ${id.substring(0, 8)}...`, {
9943
- sessionId: id,
9944
- active: sessions.size - 1
9945
- });
9946
- session.server.close();
9947
- }
9948
- }, 6e4).unref();
9949
- return async (req, res) => {
9950
- if (!req.url) return;
9951
- if (new node_url.URL(req.url, `http://${req.headers.host ?? "localhost"}`).pathname !== MCP_PATH) return;
9956
+ async function preamble(req, res) {
9957
+ if (!req.url) return null;
9958
+ if (new node_url.URL(req.url, `http://${req.headers.host ?? "localhost"}`).pathname !== MCP_PATH) return null;
9952
9959
  if (req.method === "OPTIONS") {
9953
9960
  res.writeHead(204, CORS_HEADERS);
9954
9961
  res.end();
9955
- return;
9962
+ return null;
9956
9963
  }
9957
9964
  let authInfo;
9958
9965
  if (authFn) {
@@ -9963,7 +9970,7 @@ function createMcpHandler(config) {
9963
9970
  "WWW-Authenticate": "Bearer"
9964
9971
  });
9965
9972
  res.end("Unauthorized");
9966
- return;
9973
+ return null;
9967
9974
  }
9968
9975
  authInfo = result;
9969
9976
  }
@@ -9986,12 +9993,58 @@ function createMcpHandler(config) {
9986
9993
  const extra = parsedBody.method === "resources/read" ? ` uri=${JSON.stringify(parsedBody.params?.uri)}` : "";
9987
9994
  log("info", `← ${parsedBody.method}${extra}${sidStr}`);
9988
9995
  }
9996
+ if (process.env.SUNPEAK_LOG_HEADERS) {
9997
+ const headerEntries = {};
9998
+ for (const [key, value] of Object.entries(req.headers)) if (value != null) headerEntries[key] = Array.isArray(value) ? value.join(", ") : value;
9999
+ log("info", `Headers: ${JSON.stringify(headerEntries, null, 2)}`);
10000
+ }
9989
10001
  } catch {
9990
10002
  res.writeHead(400).end("Invalid JSON");
9991
- return;
10003
+ return null;
9992
10004
  }
9993
10005
  }
9994
- const webRequest = nodeReqToWebRequest(req);
10006
+ return {
10007
+ authInfo,
10008
+ parsedBody,
10009
+ webRequest: nodeReqToWebRequest(req)
10010
+ };
10011
+ }
10012
+ if (config.stateless) return async (req, res) => {
10013
+ const ctx = await preamble(req, res);
10014
+ if (!ctx) return;
10015
+ if (req.method !== "POST") {
10016
+ res.writeHead(405, CORS_HEADERS);
10017
+ res.end("Method Not Allowed: stateless mode only supports POST");
10018
+ return;
10019
+ }
10020
+ const server = createProductionMcpServer(toInternalConfig(config, detectClientFromHeaders(req.headers)));
10021
+ const transport = new WebStandardStreamableHTTPServerTransport({
10022
+ sessionIdGenerator: void 0,
10023
+ enableJsonResponse: config.enableJsonResponse ?? true
10024
+ });
10025
+ transport.onerror = (error) => {
10026
+ log("error", "Transport error (stateless)", { error: String(error) });
10027
+ };
10028
+ await server.connect(transport);
10029
+ await pipeWebResponseToNode(addCorsHeaders(await transport.handleRequest(ctx.webRequest, {
10030
+ parsedBody: ctx.parsedBody,
10031
+ authInfo: ctx.authInfo
10032
+ })), res);
10033
+ };
10034
+ const sessions = /* @__PURE__ */ new Map();
10035
+ setInterval(() => {
10036
+ const now = Date.now();
10037
+ for (const [id, session] of sessions) if (now - session.lastActivity > SESSION_IDLE_TIMEOUT_MS) {
10038
+ log("info", `Session expired: ${id.substring(0, 8)}...`, {
10039
+ sessionId: id,
10040
+ active: sessions.size - 1
10041
+ });
10042
+ session.server.close();
10043
+ }
10044
+ }, 6e4).unref();
10045
+ return async (req, res) => {
10046
+ const ctx = await preamble(req, res);
10047
+ if (!ctx) return;
9995
10048
  const sessionId = req.headers["mcp-session-id"];
9996
10049
  if (sessionId) {
9997
10050
  const session = sessions.get(sessionId);
@@ -10000,14 +10053,14 @@ function createMcpHandler(config) {
10000
10053
  return;
10001
10054
  }
10002
10055
  session.lastActivity = Date.now();
10003
- await pipeWebResponseToNode(addCorsHeaders(await session.transport.handleRequest(webRequest, {
10004
- parsedBody,
10005
- authInfo
10056
+ await pipeWebResponseToNode(addCorsHeaders(await session.transport.handleRequest(ctx.webRequest, {
10057
+ parsedBody: ctx.parsedBody,
10058
+ authInfo: ctx.authInfo
10006
10059
  })), res);
10007
10060
  return;
10008
10061
  }
10009
10062
  if (req.method === "POST") {
10010
- const server = createProductionMcpServer(config);
10063
+ const server = createProductionMcpServer(toInternalConfig(config, detectClientFromHeaders(req.headers)));
10011
10064
  const transport = new WebStandardStreamableHTTPServerTransport({
10012
10065
  sessionIdGenerator: () => (0, node_crypto.randomUUID)(),
10013
10066
  enableJsonResponse: config.enableJsonResponse ?? true,
@@ -10048,9 +10101,9 @@ function createMcpHandler(config) {
10048
10101
  }
10049
10102
  };
10050
10103
  await server.connect(transport);
10051
- await pipeWebResponseToNode(addCorsHeaders(await transport.handleRequest(webRequest, {
10052
- parsedBody,
10053
- authInfo
10104
+ await pipeWebResponseToNode(addCorsHeaders(await transport.handleRequest(ctx.webRequest, {
10105
+ parsedBody: ctx.parsedBody,
10106
+ authInfo: ctx.authInfo
10054
10107
  })), res);
10055
10108
  return;
10056
10109
  }
@@ -10088,19 +10141,8 @@ function createMcpHandler(config) {
10088
10141
  * ```
10089
10142
  */
10090
10143
  function createHandler(config) {
10091
- const sessions = /* @__PURE__ */ new Map();
10092
10144
  const authFn = config.auth;
10093
- setInterval(() => {
10094
- const now = Date.now();
10095
- for (const [id, session] of sessions) if (now - session.lastActivity > SESSION_IDLE_TIMEOUT_MS) {
10096
- log("info", `Session expired: ${id.substring(0, 8)}...`, {
10097
- sessionId: id,
10098
- active: sessions.size - 1
10099
- });
10100
- session.server.close();
10101
- }
10102
- }, 6e4).unref();
10103
- return async (req) => {
10145
+ async function webPreamble(req) {
10104
10146
  if (req.method === "OPTIONS") return new Response(null, {
10105
10147
  status: 204,
10106
10148
  headers: CORS_HEADERS
@@ -10118,31 +10160,75 @@ function createHandler(config) {
10118
10160
  authInfo = result;
10119
10161
  }
10120
10162
  let parsedBody;
10121
- if (req.method === "POST") try {
10122
- parsedBody = await req.json();
10123
- } catch {
10124
- return new Response("Invalid JSON", { status: 400 });
10163
+ if (req.method === "POST") {
10164
+ try {
10165
+ parsedBody = await req.json();
10166
+ } catch {
10167
+ return new Response("Invalid JSON", { status: 400 });
10168
+ }
10169
+ if (isJsonRpcMessage(parsedBody)) {
10170
+ const sid = req.headers.get("mcp-session-id");
10171
+ const sidStr = sid ? ` (${sid.substring(0, 8)}...)` : "";
10172
+ const extra = parsedBody.method === "resources/read" ? ` uri=${JSON.stringify(parsedBody.params?.uri)}` : "";
10173
+ log("info", `← ${parsedBody.method}${extra}${sidStr}`);
10174
+ }
10175
+ if (typeof globalThis.process !== "undefined" && globalThis.process.env?.SUNPEAK_LOG_HEADERS) {
10176
+ const headerEntries = {};
10177
+ req.headers.forEach((value, key) => {
10178
+ headerEntries[key] = value;
10179
+ });
10180
+ log("info", `Headers: ${JSON.stringify(headerEntries, null, 2)}`);
10181
+ }
10182
+ }
10183
+ return {
10184
+ authInfo,
10185
+ parsedBody
10186
+ };
10187
+ }
10188
+ if (config.stateless) return async (req) => {
10189
+ const ctx = await webPreamble(req);
10190
+ if (ctx instanceof Response) return ctx;
10191
+ if (req.method !== "POST") return addCorsHeaders(new Response("Method Not Allowed: stateless mode only supports POST", { status: 405 }));
10192
+ const server = createProductionMcpServer(toInternalConfig(config, detectClientFromHeaders(req.headers)));
10193
+ const transport = new WebStandardStreamableHTTPServerTransport({
10194
+ sessionIdGenerator: void 0,
10195
+ enableJsonResponse: config.enableJsonResponse ?? true
10196
+ });
10197
+ transport.onerror = (error) => {
10198
+ log("error", "Transport error (stateless)", { error: String(error) });
10199
+ };
10200
+ await server.connect(transport);
10201
+ return addCorsHeaders(await transport.handleRequest(req, {
10202
+ parsedBody: ctx.parsedBody,
10203
+ authInfo: ctx.authInfo
10204
+ }));
10205
+ };
10206
+ const sessions = /* @__PURE__ */ new Map();
10207
+ setInterval(() => {
10208
+ const now = Date.now();
10209
+ for (const [id, session] of sessions) if (now - session.lastActivity > SESSION_IDLE_TIMEOUT_MS) {
10210
+ log("info", `Session expired: ${id.substring(0, 8)}...`, {
10211
+ sessionId: id,
10212
+ active: sessions.size - 1
10213
+ });
10214
+ session.server.close();
10125
10215
  }
10216
+ }, 6e4).unref();
10217
+ return async (req) => {
10218
+ const ctx = await webPreamble(req);
10219
+ if (ctx instanceof Response) return ctx;
10126
10220
  const sessionId = req.headers.get("mcp-session-id");
10127
10221
  if (sessionId) {
10128
10222
  const session = sessions.get(sessionId);
10129
10223
  if (!session) return new Response("Unknown session", { status: 404 });
10130
10224
  session.lastActivity = Date.now();
10131
10225
  return addCorsHeaders(await session.transport.handleRequest(req, {
10132
- parsedBody,
10133
- authInfo
10226
+ parsedBody: ctx.parsedBody,
10227
+ authInfo: ctx.authInfo
10134
10228
  }));
10135
10229
  }
10136
10230
  if (req.method === "POST") {
10137
- const { name, version, serverInfo, tools, resources, serverUrl } = config;
10138
- const server = createProductionMcpServer({
10139
- name,
10140
- version,
10141
- serverInfo,
10142
- tools,
10143
- resources,
10144
- serverUrl
10145
- });
10231
+ const server = createProductionMcpServer(toInternalConfig(config, detectClientFromHeaders(req.headers)));
10146
10232
  const transport = new WebStandardStreamableHTTPServerTransport({
10147
10233
  sessionIdGenerator: () => (0, node_crypto.randomUUID)(),
10148
10234
  enableJsonResponse: config.enableJsonResponse ?? true,
@@ -10166,16 +10252,26 @@ function createHandler(config) {
10166
10252
  }
10167
10253
  });
10168
10254
  transport.onerror = (error) => {
10169
- log("error", "Transport error", { error: String(error) });
10255
+ const id = transport.sessionId;
10256
+ log("error", `Transport error${id ? ` (${id.substring(0, 8)}...)` : ""}`, {
10257
+ sessionId: id,
10258
+ error: String(error)
10259
+ });
10170
10260
  };
10171
10261
  transport.onclose = () => {
10172
10262
  const id = transport.sessionId;
10173
- if (id && sessions.has(id)) sessions.delete(id);
10263
+ if (id && sessions.has(id)) {
10264
+ sessions.delete(id);
10265
+ log("info", `Session closed: ${id.substring(0, 8)}...`, {
10266
+ sessionId: id,
10267
+ active: sessions.size
10268
+ });
10269
+ }
10174
10270
  };
10175
10271
  await server.connect(transport);
10176
10272
  return addCorsHeaders(await transport.handleRequest(req, {
10177
- parsedBody,
10178
- authInfo
10273
+ parsedBody: ctx.parsedBody,
10274
+ authInfo: ctx.authInfo
10179
10275
  }));
10180
10276
  }
10181
10277
  return new Response("Bad Request: session ID required", { status: 400 });
@@ -10264,7 +10360,7 @@ function startProductionHttpServer(config, portOrOptions) {
10264
10360
  }
10265
10361
  });
10266
10362
  httpServer.on("clientError", (err, socket) => {
10267
- if (err.code === "HPE_INVALID_METHOD" && "rawPacket" in err && Buffer.isBuffer(err.rawPacket) && err.rawPacket[0] === 22) log("error", "Received HTTPS request on HTTP server. If you're using ngrok, make sure the upstream is http:// (not https://). Example: ngrok http 8000");
10363
+ if (err.code === "ECONNRESET") {} else if (err.code === "HPE_INVALID_METHOD" && "rawPacket" in err && Buffer.isBuffer(err.rawPacket) && err.rawPacket[0] >= 20 && err.rawPacket[0] <= 24) log("error", "Received HTTPS request on HTTP server. If you're using ngrok, make sure the upstream is http:// (not https://). Example: ngrok http 8000");
10268
10364
  else log("error", "HTTP client error", { error: err.message });
10269
10365
  socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
10270
10366
  });
@@ -10299,22 +10395,23 @@ function startProductionHttpServer(config, portOrOptions) {
10299
10395
  process.on("SIGINT", () => void shutdown());
10300
10396
  }
10301
10397
  //#endregion
10302
- exports.EXTENSION_ID = zQ;
10398
+ exports.EXTENSION_ID = qQ;
10303
10399
  exports.FAVICON_BASE64 = FAVICON_BASE64;
10304
10400
  exports.FAVICON_BUFFER = FAVICON_BUFFER;
10305
10401
  exports.FAVICON_DATA_URI = FAVICON_DATA_URI;
10306
- exports.RESOURCE_MIME_TYPE = f;
10307
- exports.RESOURCE_URI_META_KEY = U;
10402
+ exports.RESOURCE_MIME_TYPE = u;
10403
+ exports.RESOURCE_URI_META_KEY = M;
10308
10404
  exports.computeChatGPTDomain = computeChatGPTDomain;
10309
10405
  exports.computeClaudeDomain = computeClaudeDomain;
10310
10406
  exports.createHandler = createHandler;
10311
10407
  exports.createMcpHandler = createMcpHandler;
10312
10408
  exports.createProductionMcpServer = createProductionMcpServer;
10313
- exports.getUiCapability = dZ;
10409
+ exports.detectClientFromHeaders = detectClientFromHeaders;
10410
+ exports.getUiCapability = pZ;
10314
10411
  exports.injectDefaultDomain = injectDefaultDomain;
10315
10412
  exports.injectResolvedDomain = injectResolvedDomain;
10316
- exports.registerAppResource = uZ;
10317
- exports.registerAppTool = fZ;
10413
+ exports.registerAppResource = mZ;
10414
+ exports.registerAppTool = hZ;
10318
10415
  exports.resolveDomain = resolveDomain;
10319
10416
  exports.runMCPServer = runMCPServer;
10320
10417
  exports.setJsonLogging = setJsonLogging;