alepha 0.20.3 → 0.20.4

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 (217) hide show
  1. package/dist/api/audits/index.d.ts.map +1 -1
  2. package/dist/api/files/index.d.ts.map +1 -1
  3. package/dist/api/jobs/index.d.ts +14 -14
  4. package/dist/api/jobs/index.d.ts.map +1 -1
  5. package/dist/api/keys/index.d.ts +4 -4
  6. package/dist/api/organizations/index.d.ts.map +1 -1
  7. package/dist/api/parameters/index.d.ts +8 -3
  8. package/dist/api/parameters/index.d.ts.map +1 -1
  9. package/dist/api/parameters/index.js +20 -4
  10. package/dist/api/parameters/index.js.map +1 -1
  11. package/dist/api/payments/index.d.ts.map +1 -1
  12. package/dist/api/users/index.browser.js +6 -0
  13. package/dist/api/users/index.browser.js.map +1 -1
  14. package/dist/api/users/index.d.ts +5037 -139
  15. package/dist/api/users/index.d.ts.map +1 -1
  16. package/dist/api/users/index.js +58 -10
  17. package/dist/api/users/index.js.map +1 -1
  18. package/dist/bucket/index.d.ts +77 -107
  19. package/dist/bucket/index.d.ts.map +1 -1
  20. package/dist/bucket/index.js +148 -4
  21. package/dist/bucket/index.js.map +1 -1
  22. package/dist/bucket/index.workerd.js +7 -1
  23. package/dist/bucket/index.workerd.js.map +1 -1
  24. package/dist/cache/core/index.d.ts +26 -0
  25. package/dist/cache/core/index.d.ts.map +1 -1
  26. package/dist/cache/core/index.js +11 -1
  27. package/dist/cache/core/index.js.map +1 -1
  28. package/dist/cache/core/index.workerd.js +11 -1
  29. package/dist/cache/core/index.workerd.js.map +1 -1
  30. package/dist/cli/config/index.d.ts +7 -5
  31. package/dist/cli/config/index.d.ts.map +1 -1
  32. package/dist/cli/config/index.js +2 -3
  33. package/dist/cli/config/index.js.map +1 -1
  34. package/dist/cli/core/index.d.ts +420 -13
  35. package/dist/cli/core/index.d.ts.map +1 -1
  36. package/dist/cli/core/index.js +22 -511
  37. package/dist/cli/core/index.js.map +1 -1
  38. package/dist/cli/devtools/index.d.ts +4 -8
  39. package/dist/cli/devtools/index.d.ts.map +1 -1
  40. package/dist/cli/devtools/index.js +13 -15
  41. package/dist/cli/devtools/index.js.map +1 -1
  42. package/dist/cli/platform/index.d.ts +10 -13
  43. package/dist/cli/platform/index.d.ts.map +1 -1
  44. package/dist/cli/platform/index.js +18 -15
  45. package/dist/cli/platform/index.js.map +1 -1
  46. package/dist/cli/vendor/index.d.ts +10 -13
  47. package/dist/cli/vendor/index.d.ts.map +1 -1
  48. package/dist/cli/vendor/index.js +16 -13
  49. package/dist/cli/vendor/index.js.map +1 -1
  50. package/dist/core/index.browser.js +27 -3
  51. package/dist/core/index.browser.js.map +1 -1
  52. package/dist/core/index.d.ts +6 -3
  53. package/dist/core/index.d.ts.map +1 -1
  54. package/dist/core/index.js +27 -3
  55. package/dist/core/index.js.map +1 -1
  56. package/dist/core/index.native.js +27 -3
  57. package/dist/core/index.native.js.map +1 -1
  58. package/dist/core/index.workerd.js +27 -3
  59. package/dist/core/index.workerd.js.map +1 -1
  60. package/dist/datetime/index.d.ts +69 -10
  61. package/dist/datetime/index.d.ts.map +1 -1
  62. package/dist/datetime/index.js +135 -13
  63. package/dist/datetime/index.js.map +1 -1
  64. package/dist/email/smtp/index.js +10636 -2
  65. package/dist/email/smtp/index.js.map +1 -1
  66. package/dist/fake/index.d.ts +8085 -4
  67. package/dist/fake/index.d.ts.map +1 -1
  68. package/dist/fake/index.js +33554 -3
  69. package/dist/fake/index.js.map +1 -1
  70. package/dist/lock/core/index.d.ts +30 -2
  71. package/dist/lock/core/index.d.ts.map +1 -1
  72. package/dist/lock/core/index.js +35 -12
  73. package/dist/lock/core/index.js.map +1 -1
  74. package/dist/mcp/index.d.ts +238 -31
  75. package/dist/mcp/index.d.ts.map +1 -1
  76. package/dist/mcp/index.js +198 -71
  77. package/dist/mcp/index.js.map +1 -1
  78. package/dist/orm/core/index.browser.js +1 -1
  79. package/dist/orm/core/index.browser.js.map +1 -1
  80. package/dist/orm/core/index.bun.js +4 -3
  81. package/dist/orm/core/index.bun.js.map +1 -1
  82. package/dist/orm/core/index.d.ts +4877 -9
  83. package/dist/orm/core/index.d.ts.map +1 -1
  84. package/dist/orm/core/index.js +4 -3
  85. package/dist/orm/core/index.js.map +1 -1
  86. package/dist/orm/postgres/index.d.ts +608 -1
  87. package/dist/orm/postgres/index.d.ts.map +1 -1
  88. package/dist/react/core/index.d.ts +102 -1
  89. package/dist/react/core/index.d.ts.map +1 -1
  90. package/dist/react/core/index.js +65 -1
  91. package/dist/react/core/index.js.map +1 -1
  92. package/dist/react/form/index.d.ts +6 -0
  93. package/dist/react/form/index.d.ts.map +1 -1
  94. package/dist/react/form/index.js +7 -7
  95. package/dist/react/form/index.js.map +1 -1
  96. package/dist/react/i18n/index.d.ts +7 -1
  97. package/dist/react/i18n/index.d.ts.map +1 -1
  98. package/dist/react/i18n/index.js +6 -0
  99. package/dist/react/i18n/index.js.map +1 -1
  100. package/dist/react/router/index.browser.js +20 -2
  101. package/dist/react/router/index.browser.js.map +1 -1
  102. package/dist/react/router/index.d.ts +36 -4
  103. package/dist/react/router/index.d.ts.map +1 -1
  104. package/dist/react/router/index.js +20 -2
  105. package/dist/react/router/index.js.map +1 -1
  106. package/dist/react/testing/chunk-6Ep1yQYe.js +16 -0
  107. package/dist/react/testing/index.d.ts +411 -1
  108. package/dist/react/testing/index.d.ts.map +1 -1
  109. package/dist/react/testing/index.js +12293 -13
  110. package/dist/react/testing/index.js.map +1 -1
  111. package/dist/react/ui/index.d.ts +195 -1
  112. package/dist/react/ui/index.d.ts.map +1 -1
  113. package/dist/react/ui/index.js +61 -1
  114. package/dist/react/ui/index.js.map +1 -1
  115. package/dist/scheduler/index.d.ts +84 -3
  116. package/dist/scheduler/index.d.ts.map +1 -1
  117. package/dist/scheduler/index.js +390 -1
  118. package/dist/scheduler/index.js.map +1 -1
  119. package/dist/scheduler/index.workerd.js +390 -1
  120. package/dist/scheduler/index.workerd.js.map +1 -1
  121. package/dist/security/index.d.ts +325 -2
  122. package/dist/security/index.d.ts.map +1 -1
  123. package/dist/security/index.js +1361 -2
  124. package/dist/security/index.js.map +1 -1
  125. package/dist/server/auth/index.d.ts +1054 -1
  126. package/dist/server/auth/index.d.ts.map +1 -1
  127. package/dist/server/auth/index.js +1223 -1
  128. package/dist/server/auth/index.js.map +1 -1
  129. package/dist/server/core/index.browser.js +10 -3
  130. package/dist/server/core/index.browser.js.map +1 -1
  131. package/dist/server/core/index.d.ts.map +1 -1
  132. package/dist/server/core/index.js +28 -5
  133. package/dist/server/core/index.js.map +1 -1
  134. package/dist/server/metrics/index.d.ts +514 -1
  135. package/dist/server/metrics/index.d.ts.map +1 -1
  136. package/dist/server/metrics/index.js +4374 -4
  137. package/dist/server/metrics/index.js.map +1 -1
  138. package/dist/server/swagger/index.d.ts.map +1 -1
  139. package/dist/server/swagger/index.js +3 -4
  140. package/dist/server/swagger/index.js.map +1 -1
  141. package/dist/websocket/index.browser.js +11 -5
  142. package/dist/websocket/index.browser.js.map +1 -1
  143. package/dist/websocket/index.d.ts +3 -1
  144. package/dist/websocket/index.d.ts.map +1 -1
  145. package/dist/websocket/index.js +21 -6
  146. package/dist/websocket/index.js.map +1 -1
  147. package/package.json +671 -263
  148. package/src/api/parameters/services/ParameterProvider.ts +21 -4
  149. package/src/api/users/__tests__/SessionService.spec.ts +99 -0
  150. package/src/api/users/__tests__/UserJobs.spec.ts +67 -0
  151. package/src/api/users/atoms/realmAuthSettingsAtom.ts +15 -0
  152. package/src/api/users/entities/sessions.ts +6 -0
  153. package/src/api/users/jobs/UserJobs.ts +44 -17
  154. package/src/api/users/providers/RealmProvider.ts +4 -0
  155. package/src/api/users/services/SessionService.ts +27 -0
  156. package/src/bucket/__tests__/NodeS3BucketProvider.spec.ts +74 -0
  157. package/src/bucket/index.ts +19 -2
  158. package/src/bucket/primitives/$bucket.ts +9 -1
  159. package/src/bucket/providers/CloudflareR2Provider.ts +2 -137
  160. package/src/bucket/providers/NodeS3BucketProvider.ts +218 -0
  161. package/src/cache/core/index.ts +29 -0
  162. package/src/cache/core/primitives/$cache.ts +14 -1
  163. package/src/cli/config/defineConfig.ts +13 -15
  164. package/src/cli/core/__tests__/init.spec.ts +6 -7
  165. package/src/cli/core/services/ProjectScaffolder.ts +18 -14
  166. package/src/cli/core/tasks/BuildCloudflareTask.ts +5 -0
  167. package/src/cli/core/templates/agentMd.ts +2 -10
  168. package/src/cli/core/templates/saasAdminLayoutTsx.ts +3 -3
  169. package/src/cli/devtools/index.ts +12 -26
  170. package/src/cli/platform/index.ts +15 -24
  171. package/src/cli/vendor/atoms/vendorOptions.ts +1 -1
  172. package/src/cli/vendor/index.ts +14 -23
  173. package/src/core/Alepha.ts +11 -1
  174. package/src/core/helpers/ref.ts +18 -0
  175. package/src/core/index.shared.ts +1 -0
  176. package/src/core/providers/SchemaValidator.ts +9 -1
  177. package/src/core/providers/TypeProvider.ts +1 -2
  178. package/src/datetime/REFACTORING.md +118 -0
  179. package/src/datetime/providers/DateTimeProvider.ts +203 -24
  180. package/src/lock/core/index.ts +31 -0
  181. package/src/lock/core/primitives/$lock.ts +14 -1
  182. package/src/mcp/__tests__/jsonrpc.spec.ts +1 -1
  183. package/src/mcp/helpers/jsonrpc.ts +26 -1
  184. package/src/mcp/index.ts +10 -5
  185. package/src/mcp/interfaces/McpTypes.ts +83 -6
  186. package/src/mcp/primitives/$prompt.ts +18 -1
  187. package/src/mcp/primitives/$resource.ts +18 -1
  188. package/src/mcp/primitives/$tool.ts +83 -7
  189. package/src/mcp/providers/McpServerProvider.ts +74 -16
  190. package/src/mcp/transports/StreamableHttpMcpTransport.ts +226 -0
  191. package/src/orm/REFACTORING.md +330 -0
  192. package/src/orm/core/primitives/$transactional.ts +11 -0
  193. package/src/orm/core/schemas/updateSchema.ts +1 -1
  194. package/src/orm/core/services/PgRelationManager.ts +4 -2
  195. package/src/react/core/__tests__/useQuery.browser.spec.tsx +86 -0
  196. package/src/react/core/hooks/useQuery.ts +153 -0
  197. package/src/react/core/index.ts +1 -0
  198. package/src/react/form/services/FormModel.ts +15 -6
  199. package/src/react/form/services/parseField.ts +8 -0
  200. package/src/react/i18n/providers/I18nProvider.ts +8 -2
  201. package/src/react/router/__tests__/$page.spec.tsx +0 -16
  202. package/src/react/router/__tests__/ssr.spec.tsx +339 -0
  203. package/src/react/router/primitives/$page.ts +28 -4
  204. package/src/react/router/providers/ReactPageProvider.ts +27 -9
  205. package/src/react/ui/atoms/uiThemeListAtom.ts +36 -0
  206. package/src/react/ui/index.ts +6 -0
  207. package/src/react/ui/services/SchemaControl.ts +209 -0
  208. package/src/security/primitives/$issuer.ts +6 -3
  209. package/src/server/core/__tests__/ServerRouterProvider-serializationError.spec.ts +75 -0
  210. package/src/server/core/__tests__/ServerRouterProvider-validationError.spec.ts +306 -0
  211. package/src/server/core/errors/ValidationError.ts +13 -1
  212. package/src/server/core/primitives/$action.ts +16 -5
  213. package/src/server/core/providers/ServerRouterProvider.ts +26 -4
  214. package/src/server/swagger/providers/ServerSwaggerProvider.ts +5 -7
  215. package/src/websocket/providers/NodeWebSocketServerProvider.ts +10 -4
  216. package/src/websocket/services/WebSocketClient.ts +11 -5
  217. package/src/mcp/transports/SseMcpTransport.ts +0 -182
package/dist/mcp/index.js CHANGED
@@ -1,9 +1,25 @@
1
- import { $atom, $inject, $module, $state, Alepha, AlephaError, KIND, Primitive, createPrimitive, t } from "alepha";
1
+ import { $atom, $inject, $module, $state, Alepha, AlephaError, KIND, Primitive, TypeBoxError, createPrimitive, t } from "alepha";
2
2
  import { $logger } from "alepha/logger";
3
3
  import { $route } from "alepha/server";
4
4
  //#region ../../src/mcp/helpers/jsonrpc.ts
5
5
  const JSONRPC_VERSION = "2.0";
6
- const MCP_PROTOCOL_VERSION = "2024-11-05";
6
+ /**
7
+ * The latest MCP protocol revision Alepha targets.
8
+ * See {@link SUPPORTED_PROTOCOL_VERSIONS} for the full negotiation list.
9
+ */
10
+ const MCP_PROTOCOL_VERSION = "2025-11-25";
11
+ /**
12
+ * Protocol versions Alepha will accept during `initialize` negotiation,
13
+ * highest preference first. The server echoes back whichever version the
14
+ * client requested if it appears here, otherwise picks the first entry.
15
+ */
16
+ const SUPPORTED_PROTOCOL_VERSIONS = [
17
+ "2025-11-25",
18
+ "2025-06-18",
19
+ "2025-03-26",
20
+ "2024-11-05"
21
+ ];
22
+ const isSupportedProtocolVersion = (v) => typeof v === "string" && SUPPORTED_PROTOCOL_VERSIONS.includes(v);
7
23
  const JsonRpcErrorCodes = {
8
24
  PARSE_ERROR: -32700,
9
25
  INVALID_REQUEST: -32600,
@@ -170,6 +186,18 @@ var McpServerProvider = class {
170
186
  resources = /* @__PURE__ */ new Map();
171
187
  prompts = /* @__PURE__ */ new Map();
172
188
  initialized = false;
189
+ /**
190
+ * Protocol version negotiated with the client during `initialize`.
191
+ * Used by transports to validate the `MCP-Protocol-Version` header on
192
+ * subsequent HTTP requests (per spec 2025-06-18+).
193
+ */
194
+ negotiatedVersion = MCP_PROTOCOL_VERSION;
195
+ /**
196
+ * Server identity returned during `initialize`. Consumers may override
197
+ * fields directly (e.g. `mcpServer.serverInfo = { name: "roadmap-mcp",
198
+ * version: "0.20.3", description: "..." }`) — the `description` field
199
+ * is supported per spec 2025-11-25 (minor change #2).
200
+ */
173
201
  serverInfo = {
174
202
  name: "alepha-mcp",
175
203
  version: "1.0.0"
@@ -298,13 +326,17 @@ var McpServerProvider = class {
298
326
  }
299
327
  }
300
328
  handleInitialize(params) {
329
+ const requested = params.protocolVersion;
330
+ const negotiated = isSupportedProtocolVersion(requested) ? requested : SUPPORTED_PROTOCOL_VERSIONS[0];
301
331
  this.log.info("MCP client initializing", {
302
332
  clientInfo: params.clientInfo,
303
- protocolVersion: params.protocolVersion
333
+ requestedProtocolVersion: requested,
334
+ negotiatedProtocolVersion: negotiated
304
335
  });
305
336
  this.initialized = true;
337
+ this.negotiatedVersion = negotiated;
306
338
  return {
307
- protocolVersion: MCP_PROTOCOL_VERSION,
339
+ protocolVersion: negotiated,
308
340
  capabilities: this.getCapabilities(),
309
341
  serverInfo: this.serverInfo
310
342
  };
@@ -322,11 +354,28 @@ var McpServerProvider = class {
322
354
  if (!tool) throw new McpToolNotFoundError(name);
323
355
  try {
324
356
  const result = await tool.execute(args, context);
325
- return { content: [{
357
+ const callResult = { content: [{
326
358
  type: "text",
327
359
  text: typeof result === "string" ? result : JSON.stringify(result ?? null)
328
360
  }] };
361
+ if (tool.hasOutputSchema() && result !== void 0) callResult.structuredContent = result;
362
+ return callResult;
329
363
  } catch (error) {
364
+ if (error instanceof TypeBoxError) {
365
+ const path = error.value?.path || "/";
366
+ const message = error.value?.message || error.message;
367
+ return {
368
+ content: [{
369
+ type: "text",
370
+ text: `Validation error at ${path}: ${message}`
371
+ }],
372
+ structuredContent: { errors: [{
373
+ path,
374
+ message
375
+ }] },
376
+ isError: true
377
+ };
378
+ }
330
379
  return {
331
380
  content: [{
332
381
  type: "text",
@@ -456,11 +505,14 @@ var PromptPrimitive = class extends Primitive {
456
505
  * Convert the prompt to an MCP prompt descriptor for protocol messages.
457
506
  */
458
507
  toDescriptor() {
459
- return {
508
+ const descriptor = {
460
509
  name: this.name,
461
510
  description: this.description,
462
511
  arguments: this.options.args ? this.schemaToArguments(this.options.args) : []
463
512
  };
513
+ if (this.options.title) descriptor.title = this.options.title;
514
+ if (this.options.icons && this.options.icons.length > 0) descriptor.icons = this.options.icons;
515
+ return descriptor;
464
516
  }
465
517
  /**
466
518
  * Convert a TypeBox schema to an array of prompt arguments.
@@ -561,12 +613,15 @@ var ResourcePrimitive = class extends Primitive {
561
613
  * Convert the resource to an MCP resource descriptor for protocol messages.
562
614
  */
563
615
  toDescriptor() {
564
- return {
616
+ const descriptor = {
565
617
  uri: this.uri,
566
618
  name: this.name,
567
619
  description: this.description,
568
620
  mimeType: this.mimeType
569
621
  };
622
+ if (this.options.title) descriptor.title = this.options.title;
623
+ if (this.options.icons && this.options.icons.length > 0) descriptor.icons = this.options.icons;
624
+ return descriptor;
570
625
  }
571
626
  };
572
627
  $resource[KIND] = ResourcePrimitive;
@@ -633,6 +688,14 @@ var ToolPrimitive = class extends Primitive {
633
688
  get description() {
634
689
  return this.options.description;
635
690
  }
691
+ /**
692
+ * Whether the tool declared a result schema. When true, `tools/call`
693
+ * responses include `structuredContent` populated with the validated
694
+ * result (spec 2025-06-18).
695
+ */
696
+ hasOutputSchema() {
697
+ return !!this.options.schema?.result;
698
+ }
636
699
  onInit() {
637
700
  this.mcpServer.registerTool(this);
638
701
  }
@@ -655,33 +718,57 @@ var ToolPrimitive = class extends Primitive {
655
718
  }
656
719
  /**
657
720
  * Convert the tool to an MCP tool descriptor for protocol messages.
721
+ *
722
+ * Emits the spec 2025-11-25 surface: `title`, `annotations`, `icons`,
723
+ * and (when `schema.result` is defined) `outputSchema` so the server
724
+ * can populate `structuredContent` on call results.
658
725
  */
659
726
  toDescriptor() {
660
- return {
727
+ const inputSchema = this.options.schema?.params ? this.schemaToJsonSchema(this.options.schema.params) : {
728
+ type: "object",
729
+ properties: {},
730
+ required: []
731
+ };
732
+ const descriptor = {
661
733
  name: this.name,
662
734
  description: this.description,
663
- inputSchema: this.options.schema?.params ? this.schemaToJsonSchema(this.options.schema.params) : {
735
+ inputSchema
736
+ };
737
+ if (this.options.title) descriptor.title = this.options.title;
738
+ if (this.options.annotations) descriptor.annotations = this.options.annotations;
739
+ if (this.options.icons && this.options.icons.length > 0) descriptor.icons = this.options.icons;
740
+ if (this.options.schema?.result) {
741
+ const out = this.propertyToJsonSchema(this.options.schema.result);
742
+ descriptor.outputSchema = typeof out === "object" && out !== null && "type" in out ? out : {
664
743
  type: "object",
665
744
  properties: {},
666
745
  required: []
667
- }
668
- };
746
+ };
747
+ }
748
+ return descriptor;
669
749
  }
670
750
  /**
671
751
  * Convert a TypeBox schema to JSON Schema format.
752
+ *
753
+ * Emits the 2020-12 dialect annotation at the root (spec 2025-11-25 /
754
+ * SEP-1613 — JSON Schema 2020-12 is the default dialect for MCP).
755
+ * The TypeBox shapes Alepha emits today are already 2020-12-compatible;
756
+ * this is just the dialect declaration.
672
757
  */
673
- schemaToJsonSchema(schema) {
758
+ schemaToJsonSchema(schema, options) {
674
759
  const properties = {};
675
760
  const required = [];
676
761
  for (const [key, propSchema] of Object.entries(schema.properties)) {
677
762
  properties[key] = this.propertyToJsonSchema(propSchema);
678
763
  if (!t.schema.isOptional(propSchema)) required.push(key);
679
764
  }
680
- return {
765
+ const result = {
681
766
  type: "object",
682
767
  properties,
683
768
  required
684
769
  };
770
+ if (options?.root !== false) result.$schema = "https://json-schema.org/draft/2020-12/schema";
771
+ return result;
685
772
  }
686
773
  /**
687
774
  * Convert a single property schema to JSON Schema format.
@@ -707,7 +794,7 @@ var ToolPrimitive = class extends Primitive {
707
794
  else if (t.schema.isArray(schema)) {
708
795
  result.type = "array";
709
796
  if ("items" in schema) result.items = this.propertyToJsonSchema(schema.items);
710
- } else if (t.schema.isObject(schema)) Object.assign(result, this.schemaToJsonSchema(schema));
797
+ } else if (t.schema.isObject(schema)) Object.assign(result, this.schemaToJsonSchema(schema, { root: false }));
711
798
  else if (t.schema.isUnsafe(schema) || t.schema.isOptional(schema)) {
712
799
  const schemaAny = schema;
713
800
  if (schemaAny.type === "string") {
@@ -726,32 +813,59 @@ var ToolPrimitive = class extends Primitive {
726
813
  };
727
814
  $tool[KIND] = ToolPrimitive;
728
815
  //#endregion
729
- //#region ../../src/mcp/transports/SseMcpTransport.ts
730
- const mcpSseOptions = $atom({
731
- name: "alepha.mcp.sse.options",
732
- description: "Configuration options for the MCP SSE transport.",
733
- schema: t.object({
734
- /**
735
- * Path for the MCP SSE endpoint.
736
- */
737
- path: t.text({ default: "/mcp" }) }),
738
- default: { path: "/mcp" }
816
+ //#region ../../src/mcp/transports/StreamableHttpMcpTransport.ts
817
+ const mcpStreamableHttpOptions = $atom({
818
+ name: "alepha.mcp.streamableHttp.options",
819
+ description: "Configuration options for the MCP Streamable HTTP transport.",
820
+ schema: t.object({
821
+ /**
822
+ * Path for the MCP endpoint. Single endpoint for both requests and
823
+ * (optional) server-streamed responses, per spec 2025-03-26+.
824
+ */
825
+ path: t.text({ default: "/mcp" }),
826
+ /**
827
+ * Allow-list of `Origin` header values accepted on incoming requests.
828
+ * Empty array (default) means "allow any". When set, browser-originated
829
+ * requests with a non-matching `Origin` are rejected with 403 Forbidden,
830
+ * blocking DNS-rebinding attacks against localhost MCP servers.
831
+ *
832
+ * Server-to-server callers (no `Origin` header) are always allowed.
833
+ *
834
+ * Spec 2025-11-25, PR #1439.
835
+ */
836
+ allowedOrigins: t.array(t.text(), { default: [] })
837
+ }),
838
+ default: {
839
+ path: "/mcp",
840
+ allowedOrigins: []
841
+ }
739
842
  });
843
+ const mcpSseOptions = mcpStreamableHttpOptions;
740
844
  /**
741
- * SSE (Server-Sent Events) transport for MCP communication.
845
+ * Streamable HTTP transport for MCP communication.
846
+ *
847
+ * Implements the 2025-03-26+ Streamable HTTP transport: a single `/mcp`
848
+ * endpoint that accepts JSON-RPC over POST and returns either
849
+ * `application/json` (single response, the default) or
850
+ * `text/event-stream` (when the server wants to stream multiple messages).
742
851
  *
743
- * This transport uses HTTP with SSE for server-to-client messages
744
- * and POST requests for client-to-server messages.
852
+ * Designed for serverless deployment (Cloudflare Workers, etc.) there is
853
+ * no long-lived GET stream. GET on the endpoint returns 405 Method Not
854
+ * Allowed; clients that want server-initiated push must rely on the POST
855
+ * response stream when the server upgrades to SSE for that particular call.
745
856
  *
746
- * Endpoints:
747
- * - GET /mcp - SSE stream for server events
748
- * - POST /mcp - JSON-RPC request endpoint
857
+ * Spec compliance:
858
+ * - 2025-06-18: validates `MCP-Protocol-Version` header on every request
859
+ * after `initialize` against the version negotiated and stored on
860
+ * `McpServerProvider`.
861
+ * - 2025-11-25: rejects requests with a non-allow-listed `Origin` header
862
+ * (PR #1439). See {@link mcpStreamableHttpOptions.allowedOrigins}.
749
863
  *
750
864
  * @example
751
865
  * ```ts
752
866
  * import { Alepha, run } from "alepha";
753
867
  * import { AlephaServer } from "alepha/server";
754
- * import { AlephaMcp, AlephaMcpSse } from "alepha/mcp";
868
+ * import { AlephaMcp, StreamableHttpMcpTransport } from "alepha/mcp";
755
869
  *
756
870
  * class MyTools {
757
871
  * // ... tool definitions
@@ -761,50 +875,36 @@ path: t.text({ default: "/mcp" }) }),
761
875
  * Alepha.create()
762
876
  * .with(AlephaServer)
763
877
  * .with(AlephaMcp)
764
- * .with(AlephaMcpSse)
878
+ * .with(StreamableHttpMcpTransport)
765
879
  * .with(MyTools)
766
880
  * );
767
881
  * ```
768
882
  */
769
- var SseMcpTransport = class {
883
+ var StreamableHttpMcpTransport = class {
770
884
  log = $logger();
771
- options = $state(mcpSseOptions);
885
+ options = $state(mcpStreamableHttpOptions);
772
886
  mcpServer = $inject(McpServerProvider);
773
887
  /**
774
- * SSE endpoint for server-to-client messages.
775
- *
776
- * Returns a text/event-stream response with server capabilities
777
- * and keeps the connection open for notifications.
888
+ * GET on the MCP endpoint is not supported in this transport. Returning
889
+ * 405 (rather than serving the legacy two-endpoint SSE pattern) is the
890
+ * spec-allowed response for servers that don't offer server-initiated
891
+ * push outside of an active POST.
778
892
  */
779
- sse = $route({
893
+ notAllowed = $route({
780
894
  method: "GET",
781
895
  path: this.options.path,
782
- handler: async (request) => {
783
- this.log.debug("MCP SSE connection established");
784
- const encoder = new TextEncoder();
785
- const stream = new ReadableStream({
786
- start: (controller) => {
787
- const endpointEvent = this.formatSseEvent("endpoint", `${this.options.path}`);
788
- controller.enqueue(encoder.encode(endpointEvent));
789
- const capabilitiesNotification = createNotification("notifications/capabilities", { capabilities: this.mcpServer.getCapabilities() });
790
- const capabilitiesEvent = this.formatSseEvent("message", JSON.stringify(capabilitiesNotification));
791
- controller.enqueue(encoder.encode(capabilitiesEvent));
792
- },
793
- cancel: () => {
794
- this.log.debug("MCP SSE connection closed");
795
- }
796
- });
797
- request.reply.status = 200;
798
- request.reply.headers = {
799
- "content-type": "text/event-stream",
800
- "cache-control": "no-cache",
801
- connection: "keep-alive"
802
- };
803
- request.reply.body = stream;
896
+ handler: (request) => {
897
+ request.reply.status = 405;
898
+ request.reply.headers.allow = "POST";
899
+ request.reply.headers["content-type"] = "application/json";
900
+ request.reply.body = JSON.stringify({ error: "Method Not Allowed. Use POST for MCP messages." });
804
901
  }
805
902
  });
806
903
  /**
807
904
  * POST endpoint for client-to-server JSON-RPC messages.
905
+ * Returns `application/json` for single responses; tools that need to
906
+ * stream progress would upgrade to `text/event-stream` (deferred until a
907
+ * concrete need exists).
808
908
  */
809
909
  message = $route({
810
910
  method: "POST",
@@ -812,13 +912,40 @@ var SseMcpTransport = class {
812
912
  schema: { body: t.json() },
813
913
  handler: async (request) => {
814
914
  try {
915
+ const originRaw = request.headers.origin;
916
+ const origin = Array.isArray(originRaw) ? originRaw[0] : originRaw;
917
+ if (origin && this.options.allowedOrigins.length > 0 && !this.options.allowedOrigins.includes(origin)) {
918
+ this.log.warn("Rejected MCP request with non-allowed Origin", {
919
+ origin,
920
+ allowed: this.options.allowedOrigins
921
+ });
922
+ request.reply.status = 403;
923
+ request.reply.headers["content-type"] = "application/json";
924
+ request.reply.body = JSON.stringify({ error: "Forbidden: Origin not allowed" });
925
+ return;
926
+ }
815
927
  const body = typeof request.body === "string" ? request.body : JSON.stringify(request.body);
816
928
  this.log.debug("MCP request body", {
817
929
  body,
818
930
  bodyType: typeof request.body
819
931
  });
820
932
  const rpcRequest = parseMessage(body);
821
- const context = { headers: { ...request.headers } };
933
+ const headers = { ...request.headers };
934
+ if (rpcRequest.method !== "initialize") {
935
+ const headerRaw = headers["mcp-protocol-version"];
936
+ const headerVersion = Array.isArray(headerRaw) ? headerRaw[0] : headerRaw;
937
+ if (headerVersion && headerVersion !== this.mcpServer.negotiatedVersion) {
938
+ this.log.warn("MCP-Protocol-Version header mismatch", {
939
+ header: headerVersion,
940
+ negotiated: this.mcpServer.negotiatedVersion
941
+ });
942
+ request.reply.status = 400;
943
+ request.reply.headers["content-type"] = "application/json";
944
+ request.reply.body = JSON.stringify({ error: `MCP-Protocol-Version mismatch: expected ${this.mcpServer.negotiatedVersion}, got ${headerVersion}` });
945
+ return;
946
+ }
947
+ }
948
+ const context = { headers };
822
949
  const response = await this.mcpServer.handleMessage(rpcRequest, context);
823
950
  if (response) {
824
951
  request.reply.headers["content-type"] = "application/json";
@@ -837,13 +964,13 @@ var SseMcpTransport = class {
837
964
  }
838
965
  }
839
966
  });
840
- /**
841
- * Format a message as an SSE event.
842
- */
843
- formatSseEvent(event, data) {
844
- return `event: ${event}\ndata: ${data}\n\n`;
845
- }
846
967
  };
968
+ /**
969
+ * @deprecated Use {@link StreamableHttpMcpTransport}. The 2024-11-05
970
+ * two-endpoint HTTP+SSE pattern was replaced by Streamable HTTP in spec
971
+ * 2025-03-26. This alias is preserved for one release to ease migration.
972
+ */
973
+ const SseMcpTransport = StreamableHttpMcpTransport;
847
974
  //#endregion
848
975
  //#region ../../src/mcp/index.ts
849
976
  /**
@@ -854,7 +981,7 @@ var SseMcpTransport = class {
854
981
  * - MCP tool definitions
855
982
  * - MCP prompt definitions
856
983
  * - JSON-RPC protocol
857
- * - SSE and Stdio transports
984
+ * - Streamable HTTP transport (spec 2025-03-26+)
858
985
  *
859
986
  * @module alepha.mcp
860
987
  */
@@ -866,9 +993,9 @@ const AlephaMcp = $module({
866
993
  $prompt
867
994
  ],
868
995
  services: [McpServerProvider],
869
- variants: [SseMcpTransport]
996
+ variants: [StreamableHttpMcpTransport]
870
997
  });
871
998
  //#endregion
872
- export { $prompt, $resource, $tool, AlephaMcp, JSONRPC_VERSION, JsonRpcErrorCodes, JsonRpcParseError, MCP_PROTOCOL_VERSION, McpError, McpErrorCodes, McpForbiddenError, McpInvalidParamsError, McpMethodNotFoundError, McpPromptNotFoundError, McpResourceNotFoundError, McpServerProvider, McpToolNotFoundError, McpUnauthorizedError, PromptPrimitive, ResourcePrimitive, SseMcpTransport, ToolPrimitive, createErrorResponse, createInternalError, createInvalidParamsError, createInvalidRequestError, createMethodNotFoundError, createNotification, createParseError, createResponse, isNotification, isValidJsonRpcRequest, mcpSseOptions, parseMessage };
999
+ export { $prompt, $resource, $tool, AlephaMcp, JSONRPC_VERSION, JsonRpcErrorCodes, JsonRpcParseError, MCP_PROTOCOL_VERSION, McpError, McpErrorCodes, McpForbiddenError, McpInvalidParamsError, McpMethodNotFoundError, McpPromptNotFoundError, McpResourceNotFoundError, McpServerProvider, McpToolNotFoundError, McpUnauthorizedError, PromptPrimitive, ResourcePrimitive, SUPPORTED_PROTOCOL_VERSIONS, SseMcpTransport, StreamableHttpMcpTransport, ToolPrimitive, createErrorResponse, createInternalError, createInvalidParamsError, createInvalidRequestError, createMethodNotFoundError, createNotification, createParseError, createResponse, isNotification, isSupportedProtocolVersion, isValidJsonRpcRequest, mcpSseOptions, mcpStreamableHttpOptions, parseMessage };
873
1000
 
874
1001
  //# sourceMappingURL=index.js.map