@codemation/host 0.6.0 → 0.7.0

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 (223) hide show
  1. package/CHANGELOG.md +431 -0
  2. package/LICENSE +1 -37
  3. package/dist/{ApiPaths-CLTHphYZ.js → ApiPaths-Dv1dcHu_.js} +4 -4
  4. package/dist/ApiPaths-Dv1dcHu_.js.map +1 -0
  5. package/dist/{AppConfigFactory-C6q-CSKb.js → AppConfigFactory-Cx4qQvRk.js} +112 -52
  6. package/dist/AppConfigFactory-Cx4qQvRk.js.map +1 -0
  7. package/dist/{AppConfigFactory-YnveXE9k.d.ts → AppConfigFactory-DnLoQ9Li.d.ts} +8490 -5548
  8. package/dist/{AppContainerFactory-qaqc-R1D.js → AppContainerFactory-DqKYCRNP.js} +7641 -2083
  9. package/dist/AppContainerFactory-DqKYCRNP.js.map +1 -0
  10. package/dist/{CodemationAppContext-DRu1Dpri.d.ts → CodemationAppContext-CKVv9W9q.d.ts} +8 -4
  11. package/dist/{CodemationAuthoring.types-fBRppnmi.d.ts → CodemationAuthoring.types-DA3G3s6d.d.ts} +25 -5
  12. package/dist/{CodemationAuthoring.types-DZl-sJaM.js → CodemationAuthoring.types-NGkBcmmT.js} +18 -6
  13. package/dist/CodemationAuthoring.types-NGkBcmmT.js.map +1 -0
  14. package/dist/{CodemationConfigNormalizer-DVko3cVN.d.ts → CodemationConfigNormalizer-BAKjetJ6.d.ts} +3 -3
  15. package/dist/{CodemationConsumerConfigLoader-BeAUS144.js → CodemationConsumerConfigLoader-GYpBBvqE.js} +79 -10
  16. package/dist/CodemationConsumerConfigLoader-GYpBBvqE.js.map +1 -0
  17. package/dist/{CodemationConsumerConfigLoader-DJWr86f-.d.ts → CodemationConsumerConfigLoader-nxOqvv46.d.ts} +17 -2
  18. package/dist/{CodemationPluginListMerger-B-W5Fa_X.js → CodemationPluginListMerger-D1B1IEbt.js} +1 -1
  19. package/dist/{CodemationPluginListMerger-B-W5Fa_X.js.map → CodemationPluginListMerger-D1B1IEbt.js.map} +1 -1
  20. package/dist/{CodemationPluginListMerger-DGc-jfa2.d.ts → CodemationPluginListMerger-DKLAHT2b.d.ts} +123 -16
  21. package/dist/CodemationTsyringeTypeInfoRegistrar-Bj6FJYFz.js +97 -0
  22. package/dist/CodemationTsyringeTypeInfoRegistrar-Bj6FJYFz.js.map +1 -0
  23. package/dist/{CodemationWhitelabelConfig-CWbcyQqn.d.ts → CodemationWhitelabelConfig-Ca2mCUeC.d.ts} +2 -2
  24. package/dist/{CollectionContracts.types-DdpHft0i.d.ts → CollectionContracts.types-DDyFYT_D.d.ts} +1 -1
  25. package/dist/{CredentialContractsRegistry-DrMIDSw8.d.ts → CredentialContractsRegistry-Bq2bq28t.d.ts} +2 -2
  26. package/dist/{CredentialServices-UfvHB-rN.d.ts → CredentialServices-Be2I60Th.d.ts} +65 -20
  27. package/dist/{CredentialServices-CgxwguAv.js → CredentialServices-Dk8yypeL.js} +310 -51
  28. package/dist/CredentialServices-Dk8yypeL.js.map +1 -0
  29. package/dist/InternalHonoApiRouteRegistrar-Ce1yxpnO.d.ts +17 -0
  30. package/dist/InternalPingRegistrar-DY3kSfxP.js +221 -0
  31. package/dist/InternalPingRegistrar-DY3kSfxP.js.map +1 -0
  32. package/dist/{ItemsInputNormalizer-C-KHg9Mo.d.ts → ItemsInputNormalizer-_RwIfRIQ.d.ts} +89 -25
  33. package/dist/{LogLevelPolicyFactory-CampWO0l.d.ts → LogLevelPolicyFactory-ewCHLDLn.d.ts} +2 -2
  34. package/dist/{PublicFrontendBootstrap-DzBgwOnG.d.ts → PublicFrontendBootstrap-Cev3qK46.d.ts} +9 -2
  35. package/dist/PublicFrontendBootstrapFactory-CY2FS-5g.d.ts +82 -0
  36. package/dist/{PublicFrontendBootstrapJsonCodec-Cl_DLRh0.d.ts → PublicFrontendBootstrapJsonCodec-CXG9Dxft.d.ts} +3 -3
  37. package/dist/{PublicFrontendBootstrapJsonCodec-DzqvA0uo.js → PublicFrontendBootstrapJsonCodec-CegIF_ne.js} +7 -2
  38. package/dist/PublicFrontendBootstrapJsonCodec-CegIF_ne.js.map +1 -0
  39. package/dist/ServerLoggerFactory-Ckk52S3w.js +223 -0
  40. package/dist/ServerLoggerFactory-Ckk52S3w.js.map +1 -0
  41. package/dist/{TelemetryContracts-DbaNomrH.d.ts → TelemetryContracts-BtDx84Cp.d.ts} +13 -4
  42. package/dist/{WorkflowPolicyUiPresentationFactory-DQEY-h_S.d.ts → WorkflowPolicyUiPresentationFactory-6MyjCvBO.d.ts} +2 -2
  43. package/dist/{WorkflowPolicyUiPresentationFactory-DhPqQ9aB.js → WorkflowPolicyUiPresentationFactory-Bb-ae_Zh.js} +1 -1
  44. package/dist/{WorkflowPolicyUiPresentationFactory-DhPqQ9aB.js.map → WorkflowPolicyUiPresentationFactory-Bb-ae_Zh.js.map} +1 -1
  45. package/dist/{WorkflowViewContracts-CzK2KFuz.d.ts → WorkflowViewContracts-B7aFQcIw.d.ts} +10 -1
  46. package/dist/authoring.d.ts +5 -5
  47. package/dist/authoring.js +1 -1
  48. package/dist/client.d.ts +4 -4
  49. package/dist/client.js +2 -2
  50. package/dist/consumer.d.ts +6 -6
  51. package/dist/consumer.js +2 -2
  52. package/dist/credentials.d.ts +6 -6
  53. package/dist/credentials.js +1 -1
  54. package/dist/devServerSidecar.d.ts +2 -2
  55. package/dist/devServerSidecar.js +1 -94
  56. package/dist/devServerSidecar.js.map +1 -1
  57. package/dist/dto.d.ts +6 -6
  58. package/dist/{index-BbBk26m0.d.ts → index-DilAYwnH.d.ts} +49 -3
  59. package/dist/index.d.ts +110 -21
  60. package/dist/index.js +15 -13
  61. package/dist/mapping.d.ts +2 -2
  62. package/dist/mapping.js +1 -1
  63. package/dist/nextServer.d.ts +43 -88
  64. package/dist/nextServer.js +9 -7
  65. package/dist/pairing.d.ts +93 -0
  66. package/dist/pairing.js +5 -0
  67. package/dist/pairing.types-snfZ_OzB.d.ts +19 -0
  68. package/dist/{persistenceServer-CmsIKnO9.js → persistenceServer-C-hH4z6l.js} +2 -2
  69. package/dist/{persistenceServer-CmsIKnO9.js.map → persistenceServer-C-hH4z6l.js.map} +1 -1
  70. package/dist/persistenceServer-CeTHtC6E.d.ts +30 -0
  71. package/dist/persistenceServer.d.ts +8 -8
  72. package/dist/persistenceServer.js +3 -3
  73. package/dist/{server-MUNGsBYK.d.ts → server-C4bS62rg.d.ts} +21 -6
  74. package/dist/{server-CJFfY67o.js → server-Y7kxwtCK.js} +7 -6
  75. package/dist/{server-CJFfY67o.js.map → server-Y7kxwtCK.js.map} +1 -1
  76. package/dist/server.d.ts +14 -14
  77. package/dist/server.js +13 -11
  78. package/package.json +29 -42
  79. package/prisma/migrations/20260519000000_workflow_audit_log/migration.sql +23 -0
  80. package/prisma/migrations/20260519100000_storage_growth_fixes/migration.sql +61 -0
  81. package/prisma/migrations.sqlite/20260519000000_workflow_audit_log/migration.sql +21 -0
  82. package/prisma/migrations.sqlite/20260519100000_storage_growth_fixes/migration.sql +29 -0
  83. package/prisma/schema.postgresql.prisma +55 -17
  84. package/prisma/schema.sqlite.prisma +55 -17
  85. package/prisma-generated/prisma-postgresql-client/edge.js +33 -5
  86. package/prisma-generated/prisma-postgresql-client/index-browser.js +29 -1
  87. package/prisma-generated/prisma-postgresql-client/index.d.ts +8933 -5716
  88. package/prisma-generated/prisma-postgresql-client/index.js +33 -5
  89. package/prisma-generated/prisma-postgresql-client/package.json +1 -1
  90. package/prisma-generated/prisma-postgresql-client/schema.prisma +38 -0
  91. package/prisma-generated/prisma-sqlite-client/edge.js +33 -5
  92. package/prisma-generated/prisma-sqlite-client/index-browser.js +29 -1
  93. package/prisma-generated/prisma-sqlite-client/index.d.ts +8925 -5713
  94. package/prisma-generated/prisma-sqlite-client/index.js +33 -5
  95. package/prisma-generated/prisma-sqlite-client/package.json +1 -1
  96. package/prisma-generated/prisma-sqlite-client/schema.prisma +38 -0
  97. package/scripts/check-collections.mjs +18 -0
  98. package/scripts/generate-prisma-clients.mjs +20 -11
  99. package/src/application/WorkflowAuditLogPruneScheduler.ts +96 -0
  100. package/src/application/auth/AuthenticatedPrincipal.ts +4 -0
  101. package/src/application/commands/StartWorkflowRunCommandHandler.ts +4 -0
  102. package/src/application/contracts/WorkflowViewContracts.ts +6 -0
  103. package/src/application/contracts/WorkflowWebsocketMessage.ts +3 -1
  104. package/src/application/mapping/WorkflowDefinitionMapper.ts +40 -1
  105. package/src/application/runs/WorkflowRunRetentionPruneScheduler.ts +7 -1
  106. package/src/application/telemetry/OtelExecutionTelemetry.types.ts +5 -0
  107. package/src/application/telemetry/OtelExecutionTelemetryFactory.ts +4 -0
  108. package/src/application/telemetry/StoredTelemetrySpanScope.ts +6 -2
  109. package/src/application/telemetry/TelemetryRetentionTimestampFactory.ts +27 -17
  110. package/src/application/telemetry/TelemetrySpanPublisher.ts +11 -0
  111. package/src/application/websocket/TelemetrySpanWebsocketRelay.ts +31 -0
  112. package/src/applicationTokens.ts +20 -1
  113. package/src/audit/IAuditEmitter.ts +32 -0
  114. package/src/audit/PrismaWorkflowAuditLogRepository.ts +34 -0
  115. package/src/audit/WorkflowAuditLogWriter.ts +125 -0
  116. package/src/auth/managed/ManagedAuthConfig.ts +29 -0
  117. package/src/auth/managed/ManagedAuthMiddleware.ts +52 -0
  118. package/src/auth/managed/ManagedCorsMiddleware.ts +43 -0
  119. package/src/auth/managed/ManagedModeBootGuard.ts +27 -0
  120. package/src/auth/managed/index.ts +5 -0
  121. package/src/bootstrap/AppContainerFactory.ts +277 -29
  122. package/src/bootstrap/AppContainerLifecycle.ts +31 -0
  123. package/src/bootstrap/perf/BootTimer.ts +168 -0
  124. package/src/bootstrap/runtime/AppConfigFactory.ts +21 -65
  125. package/src/bootstrap/runtime/FrontendRuntime.ts +4 -1
  126. package/src/bootstrap/runtime/WorkerRuntime.ts +2 -1
  127. package/src/credentials/BrokerClient.ts +49 -0
  128. package/src/credentials/BrokerRefreshError.ts +12 -0
  129. package/src/credentials/BrokerRefreshInvalidGrantError.ts +13 -0
  130. package/src/credentials/ControlPlaneCatalogFetcher.ts +261 -0
  131. package/src/credentials/CredentialOAuth2MaterialReader.ts +136 -0
  132. package/src/credentials/InternalCredentialsListRegistrar.ts +48 -0
  133. package/src/credentials/InternalCredentialsPushRegistrar.ts +125 -0
  134. package/src/credentials/LocalOAuthFlowExecutor.ts +316 -0
  135. package/src/credentials/ManagedOAuthFlowExecutor.ts +94 -0
  136. package/src/credentials/ManagedOAuthRefreshInvalidGrantError.ts +13 -0
  137. package/src/credentials/catalogTypes.ts +4 -0
  138. package/src/credentials/refresh/CredentialDisconnectedError.ts +11 -0
  139. package/src/domain/credentials/CredentialBindingService.ts +54 -2
  140. package/src/domain/credentials/CredentialKeyRotatedError.ts +22 -0
  141. package/src/domain/credentials/CredentialSecretCipher.ts +68 -6
  142. package/src/domain/credentials/CredentialTypeRegistryImpl.ts +117 -10
  143. package/src/domain/credentials/OAuth2RedirectUriResolver.ts +79 -0
  144. package/src/domain/credentials/WorkflowCredentialNodeResolver.ts +14 -5
  145. package/src/domain/telemetry/TelemetryContracts.ts +7 -1
  146. package/src/domain/workflows/WorkflowActivationPreflight.ts +24 -1
  147. package/src/domain/workflows/WorkflowActivationPreflightRules.ts +40 -1
  148. package/src/index.ts +6 -0
  149. package/src/infrastructure/binary/LocalFilesystemBinaryStorageRegistry.ts +29 -1
  150. package/src/infrastructure/binary/S3BinaryStorage.ts +169 -0
  151. package/src/infrastructure/binary/S3BinaryStorageConfig.ts +17 -0
  152. package/src/infrastructure/config/CodemationPluginRegistrar.ts +3 -1
  153. package/src/infrastructure/persistence/CodemationDatabaseUrlParser.ts +41 -0
  154. package/src/infrastructure/persistence/InMemoryTelemetryArtifactStore.ts +8 -3
  155. package/src/infrastructure/persistence/PrismaMigrationDeployer.ts +21 -13
  156. package/src/infrastructure/persistence/PrismaTelemetryArtifactStore.ts +43 -8
  157. package/src/infrastructure/persistence/PrismaWorkflowRunRepository.ts +26 -3
  158. package/src/infrastructure/persistence/PrismaWorkflowSnapshotRepository.ts +48 -0
  159. package/src/mcp/AgentMcpIntegrationImpl.ts +344 -0
  160. package/src/mcp/McpClientFactory.ts +29 -0
  161. package/src/mcp/McpConnectionPool.ts +184 -0
  162. package/src/mcp/McpConnectionPool.types.ts +12 -0
  163. package/src/mcp/McpServerCatalog.ts +104 -0
  164. package/src/mcp/index.ts +5 -0
  165. package/src/pairing/HmacRequestSigner.ts +32 -0
  166. package/src/pairing/IncomingHmacVerifier.ts +82 -0
  167. package/src/pairing/InternalHmacAuthMiddleware.ts +33 -0
  168. package/src/pairing/InternalPingRegistrar.ts +25 -0
  169. package/src/pairing/PairedFetch.ts +33 -0
  170. package/src/pairing/PairingConfigFactory.ts +35 -0
  171. package/src/pairing/PairingConfigToken.ts +6 -0
  172. package/src/pairing/index.ts +14 -0
  173. package/src/pairing/pairing.types.ts +18 -0
  174. package/src/pairing.ts +17 -0
  175. package/src/persistenceServer.ts +1 -0
  176. package/src/presentation/config/AppConfig.ts +7 -1
  177. package/src/presentation/config/CodemationAuthConfig.ts +1 -1
  178. package/src/presentation/config/CodemationAuthoring.types.ts +54 -5
  179. package/src/presentation/config/CodemationConfig.ts +3 -0
  180. package/src/presentation/config/CodemationConfigNormalizer.ts +39 -1
  181. package/src/presentation/config/CodemationPlugin.ts +2 -1
  182. package/src/presentation/frontend/CodemationFrontendAuthSnapshot.ts +5 -0
  183. package/src/presentation/frontend/CodemationFrontendAuthSnapshotFactory.ts +7 -1
  184. package/src/presentation/frontend/PublicFrontendBootstrap.ts +2 -0
  185. package/src/presentation/frontend/PublicFrontendBootstrapFactory.ts +5 -1
  186. package/src/presentation/frontend/PublicFrontendBootstrapJsonCodec.ts +4 -1
  187. package/src/presentation/http/ApiPaths.ts +4 -4
  188. package/src/presentation/http/ServerHttpErrorResponseFactory.ts +39 -2
  189. package/src/presentation/http/hono/CodemationHonoApiAppFactory.ts +33 -8
  190. package/src/presentation/http/hono/InternalHonoApiRouteRegistrar.ts +12 -0
  191. package/src/presentation/http/hono/registrars/ManagedMeHonoApiRouteRegistrar.ts +35 -0
  192. package/src/presentation/http/hono/registrars/OAuth2HonoApiRouteRegistrar.ts +2 -2
  193. package/src/presentation/http/routeHandlers/CredentialHttpRouteHandler.ts +28 -0
  194. package/src/presentation/http/routeHandlers/OAuth2HttpRouteHandlerFactory.ts +98 -41
  195. package/src/presentation/server/CodemationConsumerConfigLoader.ts +54 -7
  196. package/src/presentation/server/CodemationPluginDiscovery.ts +5 -0
  197. package/src/presentation/server/WorkflowDefinitionExportsResolver.ts +18 -0
  198. package/src/presentation/server/WorkflowModulePathFinder.ts +12 -1
  199. package/src/presentation/websocket/ManagedWebsocketAuthenticator.ts +50 -0
  200. package/src/presentation/websocket/WebsocketAuthenticator.types.ts +12 -0
  201. package/src/presentation/websocket/WorkflowWebsocketServer.ts +24 -3
  202. package/src/process/ExecaProcessRunner.ts +41 -0
  203. package/src/process/ProcessRunner.types.ts +39 -0
  204. package/src/server.ts +2 -0
  205. package/src/workflows/InternalWorkflowActivationRegistrar.ts +42 -0
  206. package/src/workflows/InternalWorkflowDetailRegistrar.ts +33 -0
  207. package/src/workflows/InternalWorkflowTestRunRegistrar.ts +91 -0
  208. package/src/workflows/InternalWorkflowsListRegistrar.ts +28 -0
  209. package/src/workflows/discovery/WorkflowDirectoryDiscoverer.ts +79 -0
  210. package/tsconfig.json +2 -0
  211. package/vitest.shared.ts +5 -0
  212. package/dist/ApiPaths-CLTHphYZ.js.map +0 -1
  213. package/dist/AppConfigFactory-C6q-CSKb.js.map +0 -1
  214. package/dist/AppContainerFactory-qaqc-R1D.js.map +0 -1
  215. package/dist/CodemationAuthoring.types-DZl-sJaM.js.map +0 -1
  216. package/dist/CodemationConsumerConfigLoader-BeAUS144.js.map +0 -1
  217. package/dist/CredentialServices-CgxwguAv.js.map +0 -1
  218. package/dist/PublicFrontendBootstrapFactory-Cb2pLmDd.d.ts +0 -45
  219. package/dist/PublicFrontendBootstrapJsonCodec-DzqvA0uo.js.map +0 -1
  220. package/dist/ServerLoggerFactory-BKSIh9Xv.js +0 -98
  221. package/dist/ServerLoggerFactory-BKSIh9Xv.js.map +0 -1
  222. package/dist/persistenceServer-vtJAGDat.d.ts +0 -9
  223. package/src/domain/credentials/OAuth2ConnectServiceFactory.ts +0 -411
@@ -0,0 +1,104 @@
1
+ import { inject, injectable } from "@codemation/core";
2
+ import type { McpServerDeclaration } from "@codemation/core";
3
+ import { ApplicationTokens } from "../applicationTokens";
4
+ import type { LoggerFactory } from "../application/logging/Logger";
5
+ import type { AppConfig } from "../presentation/config/AppConfig";
6
+
7
+ export type McpServerDeclarationSource = "plugin" | "config" | "controlPlane";
8
+
9
+ const SOURCE_PRIORITY: Record<McpServerDeclarationSource, number> = {
10
+ plugin: 0,
11
+ config: 1,
12
+ controlPlane: 2,
13
+ };
14
+
15
+ const ID_PATTERN = /^[a-z0-9-]+$/;
16
+
17
+ type CatalogEntry = Readonly<{
18
+ decl: McpServerDeclaration;
19
+ source: McpServerDeclarationSource;
20
+ }>;
21
+
22
+ @injectable()
23
+ export class McpServerCatalog {
24
+ private readonly entries = new Map<string, CatalogEntry>();
25
+ private readonly bySource = new Map<McpServerDeclarationSource, Set<string>>();
26
+ private readonly env: NodeJS.ProcessEnv;
27
+
28
+ constructor(
29
+ @inject(ApplicationTokens.LoggerFactory) private readonly loggers: LoggerFactory,
30
+ @inject(ApplicationTokens.AppConfig) appConfig: AppConfig,
31
+ ) {
32
+ this.env = appConfig.env;
33
+ }
34
+
35
+ merge(source: McpServerDeclarationSource, declarations: ReadonlyArray<McpServerDeclaration>): void {
36
+ const logger = this.loggers.create("McpServerCatalog");
37
+ for (const decl of declarations) {
38
+ if (!this.validate(decl, source, logger)) {
39
+ continue;
40
+ }
41
+ const existing = this.entries.get(decl.id);
42
+ if (existing) {
43
+ if (SOURCE_PRIORITY[source] <= SOURCE_PRIORITY[existing.source]) {
44
+ logger.warn(
45
+ `McpServerCatalog: id collision — lower-priority source "${source}" ignored for id "${decl.id}" (current source: "${existing.source}")`,
46
+ );
47
+ continue;
48
+ }
49
+ logger.warn(
50
+ `McpServerCatalog: id "${decl.id}" shadowed — "${existing.source}" overridden by higher-priority source "${source}"`,
51
+ );
52
+ this.bySource.get(existing.source)?.delete(decl.id);
53
+ }
54
+ this.entries.set(decl.id, { decl, source });
55
+ if (!this.bySource.has(source)) {
56
+ this.bySource.set(source, new Set());
57
+ }
58
+ this.bySource.get(source)!.add(decl.id);
59
+ }
60
+ }
61
+
62
+ get(id: string): McpServerDeclaration | undefined {
63
+ return this.entries.get(id)?.decl;
64
+ }
65
+
66
+ getAll(): readonly McpServerDeclaration[] {
67
+ return [...this.entries.values()].map((entry) => entry.decl);
68
+ }
69
+
70
+ clear(source: McpServerDeclarationSource): void {
71
+ const ids = this.bySource.get(source);
72
+ if (!ids) {
73
+ return;
74
+ }
75
+ for (const id of ids) {
76
+ this.entries.delete(id);
77
+ }
78
+ this.bySource.delete(source);
79
+ }
80
+
81
+ private validate(
82
+ decl: McpServerDeclaration,
83
+ source: McpServerDeclarationSource,
84
+ logger: ReturnType<LoggerFactory["create"]>,
85
+ ): boolean {
86
+ if (!ID_PATTERN.test(decl.id)) {
87
+ logger.warn(
88
+ `McpServerCatalog: declaration from "${source}" has invalid id "${decl.id}" (must match /^[a-z0-9-]+$/) — skipped`,
89
+ );
90
+ return false;
91
+ }
92
+
93
+ if ((decl.transport as string) === "stdio") {
94
+ if (this.env.CODEMATION_ALLOW_STDIO_MCP !== "true") {
95
+ logger.warn(
96
+ `McpServerCatalog: declaration "${decl.id}" from "${source}" uses stdio transport which is disabled (set CODEMATION_ALLOW_STDIO_MCP=true to allow) — skipped`,
97
+ );
98
+ return false;
99
+ }
100
+ }
101
+
102
+ return true;
103
+ }
104
+ }
@@ -0,0 +1,5 @@
1
+ export { McpServerCatalog, type McpServerDeclarationSource } from "./McpServerCatalog";
2
+ export { McpConnectionPool } from "./McpConnectionPool";
3
+ export type { McpPoolEntry, McpToolSet, MCPClient } from "./McpConnectionPool.types";
4
+ export { DefaultMcpClientFactory, type McpClientFactory, type McpClientOpenArgs } from "./McpClientFactory";
5
+ export { AgentMcpIntegrationImpl } from "./AgentMcpIntegrationImpl";
@@ -0,0 +1,32 @@
1
+ import { createHmac, createHash, randomBytes } from "node:crypto";
2
+ import { inject, injectable } from "@codemation/core";
3
+ import type { PairingConfig } from "./pairing.types";
4
+ import { PairingConfigToken } from "./PairingConfigToken";
5
+
6
+ export interface SignedHeaders {
7
+ readonly Authorization: string;
8
+ }
9
+
10
+ @injectable()
11
+ export class HmacRequestSigner {
12
+ constructor(@inject(PairingConfigToken) private readonly config: PairingConfig) {}
13
+
14
+ sign(method: string, urlOrPath: string, body: string): SignedHeaders {
15
+ const ts = Math.floor(Date.now() / 1000);
16
+ const nonce = randomBytes(16).toString("base64");
17
+
18
+ const parsed = new URL(urlOrPath, "http://placeholder");
19
+ const path = (parsed.pathname + parsed.search).toLowerCase();
20
+
21
+ const bodyHash = createHash("sha256").update(body, "utf8").digest("hex");
22
+ const baseString = [method.toUpperCase(), path, ts, nonce, bodyHash].join("\n");
23
+
24
+ // eslint-disable-next-line codemation/no-buffer-everything -- pairing secret is 32 bytes, never large
25
+ const secretBytes = Buffer.from(this.config.pairingSecret, "base64");
26
+ const sig = createHmac("sha256", secretBytes).update(baseString, "utf8").digest("base64");
27
+
28
+ return {
29
+ Authorization: `Codemation-Hmac v=1,workspaceId=${this.config.workspaceId},ts=${ts},nonce=${nonce},sig=${sig}`,
30
+ };
31
+ }
32
+ }
@@ -0,0 +1,82 @@
1
+ import { createHmac, createHash, timingSafeEqual } from "node:crypto";
2
+ import { inject, injectable } from "@codemation/core";
3
+ import type { PairingConfig, PairingVerificationResult } from "./pairing.types";
4
+ import { PairingConfigToken } from "./PairingConfigToken";
5
+
6
+ /**
7
+ * Verifies incoming HMAC-signed requests from the control plane.
8
+ * Mirrors the control-plane HmacVerifier — both sides follow docs/pairing-protocol.md.
9
+ */
10
+ @injectable()
11
+ export class IncomingHmacVerifier {
12
+ private readonly usedNonces = new Map<string, number>();
13
+ private readonly nonceTtlSeconds = 600; // 10 minutes
14
+
15
+ constructor(@inject(PairingConfigToken) private readonly config: PairingConfig) {}
16
+
17
+ verify(method: string, url: string, body: string, authHeader: string | null): PairingVerificationResult {
18
+ if (!this.config.pairingSecret || this.config.pairingSecret.trim().length === 0) {
19
+ throw new Error("IncomingHmacVerifier: pairingSecret is not configured — cannot verify HMAC requests.");
20
+ }
21
+
22
+ if (!authHeader?.startsWith("Codemation-Hmac ")) {
23
+ return { failure: "missing" };
24
+ }
25
+
26
+ const parts = this.parseHeader(authHeader);
27
+ if (!parts) return { failure: "missing" };
28
+ if (parts.v !== "1") return { failure: "version" };
29
+
30
+ const nowSec = Math.floor(Date.now() / 1000);
31
+ if (Math.abs(nowSec - parts.ts) > 300) return { failure: "expired" };
32
+
33
+ if (parts.workspaceId !== this.config.workspaceId) return { failure: "workspace" };
34
+
35
+ const parsed = new URL(url, "http://placeholder");
36
+ const path = (parsed.pathname + parsed.search).toLowerCase();
37
+ const bodyHash = createHash("sha256").update(body, "utf8").digest("hex");
38
+ const baseString = [method.toUpperCase(), path, parts.ts, parts.nonce, bodyHash].join("\n");
39
+
40
+ // eslint-disable-next-line codemation/no-buffer-everything -- pairing secret is 32 bytes, never large
41
+ const secretBytes = Buffer.from(this.config.pairingSecret, "base64");
42
+ const expected = createHmac("sha256", secretBytes).update(baseString, "utf8").digest("base64");
43
+
44
+ const expectedBuf = Buffer.from(expected);
45
+ const actualBuf = Buffer.from(parts.sig);
46
+ if (expectedBuf.length !== actualBuf.length || !timingSafeEqual(expectedBuf, actualBuf)) {
47
+ return { failure: "signature" };
48
+ }
49
+
50
+ this.pruneExpiredNonces(nowSec);
51
+ const nonceKey = `${parts.workspaceId}:${parts.nonce}`;
52
+ if (this.usedNonces.has(nonceKey)) return { failure: "replay" };
53
+ this.usedNonces.set(nonceKey, nowSec + this.nonceTtlSeconds);
54
+
55
+ return { workspaceId: parts.workspaceId };
56
+ }
57
+
58
+ private parseHeader(header: string): {
59
+ v: string;
60
+ workspaceId: string;
61
+ ts: number;
62
+ nonce: string;
63
+ sig: string;
64
+ } | null {
65
+ const payload = header.slice("Codemation-Hmac ".length);
66
+ const fields: Record<string, string> = {};
67
+ for (const part of payload.split(",")) {
68
+ const eq = part.indexOf("=");
69
+ if (eq === -1) return null;
70
+ fields[part.slice(0, eq).trim()] = part.slice(eq + 1).trim();
71
+ }
72
+ const { v, workspaceId, ts, nonce, sig } = fields;
73
+ if (!v || !workspaceId || !ts || !nonce || !sig) return null;
74
+ return { v, workspaceId, ts: Number(ts), nonce, sig };
75
+ }
76
+
77
+ private pruneExpiredNonces(nowSec: number): void {
78
+ for (const [key, expiry] of this.usedNonces.entries()) {
79
+ if (expiry <= nowSec) this.usedNonces.delete(key);
80
+ }
81
+ }
82
+ }
@@ -0,0 +1,33 @@
1
+ import { inject, injectable } from "@codemation/core";
2
+ import type { Context, MiddlewareHandler, Next } from "hono";
3
+ import { IncomingHmacVerifier } from "./IncomingHmacVerifier";
4
+
5
+ /**
6
+ * Hono middleware that verifies HMAC-signed requests on /internal/* routes.
7
+ * Rejects with 401 on any auth failure (failure mode is never leaked).
8
+ *
9
+ * Downstream handlers read the consumed body from `c.get("body")` when needed —
10
+ * do NOT call `c.req.text()` again after this middleware runs.
11
+ */
12
+ @injectable()
13
+ export class InternalHmacAuthMiddleware {
14
+ constructor(@inject(IncomingHmacVerifier) private readonly verifier: IncomingHmacVerifier) {}
15
+
16
+ handle(): MiddlewareHandler {
17
+ return async (c: Context, next: Next) => {
18
+ const body = c.req.method === "GET" || c.req.method === "HEAD" ? "" : await c.req.text();
19
+
20
+ const result = this.verifier.verify(c.req.method, c.req.url, body, c.req.header("authorization") ?? null);
21
+
22
+ if ("failure" in result) {
23
+ return c.json({ error: "Unauthorized" }, 401);
24
+ }
25
+
26
+ // Body stored for downstream handlers that need it (e.g., credential push).
27
+ // Access via c.get("body") — do NOT call c.req.text() again.
28
+ // workspaceId is available from PairingConfig since installation has a single workspace.
29
+ c.set("body" as never, body);
30
+ await next();
31
+ };
32
+ }
33
+ }
@@ -0,0 +1,25 @@
1
+ import { inject, injectable } from "@codemation/core";
2
+ import type { Hono } from "hono";
3
+ import { InternalHmacAuthMiddleware } from "./InternalHmacAuthMiddleware";
4
+ import type { InternalHonoApiRouteRegistrar } from "../presentation/http/hono/InternalHonoApiRouteRegistrar";
5
+ import type { PairingConfig } from "./pairing.types";
6
+ import { PairingConfigToken } from "./PairingConfigToken";
7
+
8
+ /**
9
+ * Registers GET /internal/ping — a smoke-test endpoint for verifying workspace pairing.
10
+ * Returns { pong: true, workspaceId } when the HMAC signature validates correctly.
11
+ */
12
+ @injectable()
13
+ export class InternalPingRegistrar implements InternalHonoApiRouteRegistrar {
14
+ constructor(
15
+ @inject(InternalHmacAuthMiddleware) private readonly hmacMiddleware: InternalHmacAuthMiddleware,
16
+ @inject(PairingConfigToken) private readonly pairingConfig: PairingConfig,
17
+ ) {}
18
+
19
+ register(app: Hono): void {
20
+ const { workspaceId } = this.pairingConfig;
21
+ app.get("/internal/ping", this.hmacMiddleware.handle(), (c) => {
22
+ return c.json({ pong: true, workspaceId });
23
+ });
24
+ }
25
+ }
@@ -0,0 +1,33 @@
1
+ import { inject, injectable } from "@codemation/core";
2
+ import { HmacRequestSigner } from "./HmacRequestSigner";
3
+
4
+ /**
5
+ * Thin fetch wrapper that automatically HMAC-signs outgoing requests
6
+ * to the control plane using the workspace's pairing secret.
7
+ *
8
+ * Use this for any server-to-server request from the installation to the CP.
9
+ */
10
+ @injectable()
11
+ export class PairedFetch {
12
+ constructor(@inject(HmacRequestSigner) private readonly signer: HmacRequestSigner) {}
13
+
14
+ async get(url: string): Promise<Response> {
15
+ const headers = this.signer.sign("GET", url, "");
16
+ return fetch(url, { method: "GET", headers: { ...headers } });
17
+ }
18
+
19
+ async post(url: string, body: unknown): Promise<Response> {
20
+ const bodyString = JSON.stringify(body);
21
+ const headers = this.signer.sign("POST", url, bodyString);
22
+ return fetch(url, {
23
+ method: "POST",
24
+ headers: { ...headers, "Content-Type": "application/json" },
25
+ body: bodyString,
26
+ });
27
+ }
28
+
29
+ async delete(url: string): Promise<Response> {
30
+ const headers = this.signer.sign("DELETE", url, "");
31
+ return fetch(url, { method: "DELETE", headers: { ...headers } });
32
+ }
33
+ }
@@ -0,0 +1,35 @@
1
+ import type { PairingConfig } from "./pairing.types";
2
+
3
+ /**
4
+ * Reads pairing configuration from environment variables.
5
+ *
6
+ * Required env vars when pairing is enabled:
7
+ * WORKSPACE_ID — the workspace's database ID
8
+ * WORKSPACE_PAIRING_SECRET — base64-encoded 32-byte shared secret
9
+ * CONTROL_PLANE_URL — base URL of the control plane API
10
+ *
11
+ * Returns null if any required variable is absent (pairing disabled).
12
+ * See docs/pairing-protocol.md for full bootstrap instructions.
13
+ */
14
+ export class PairingConfigFactory {
15
+ create(env: Readonly<NodeJS.ProcessEnv>): PairingConfig | null {
16
+ const workspaceId = env["WORKSPACE_ID"];
17
+ const pairingSecret = env["WORKSPACE_PAIRING_SECRET"];
18
+ const controlPlaneUrl = env["CONTROL_PLANE_URL"];
19
+
20
+ if (!workspaceId || !pairingSecret || !controlPlaneUrl) {
21
+ return null;
22
+ }
23
+
24
+ // eslint-disable-next-line codemation/no-buffer-everything -- pairing secret is always 32 bytes; bounded by validation below.
25
+ const decoded = Buffer.from(pairingSecret, "base64");
26
+ if (decoded.length !== 32) {
27
+ throw new Error(
28
+ `WORKSPACE_PAIRING_SECRET must be a base64-encoded 32-byte value (got ${decoded.length} bytes). ` +
29
+ `Generate a valid secret with: openssl rand -base64 32`,
30
+ );
31
+ }
32
+
33
+ return { workspaceId, pairingSecret, controlPlaneUrl };
34
+ }
35
+ }
@@ -0,0 +1,6 @@
1
+ import type { TypeToken } from "@codemation/core";
2
+ import type { PairingConfig } from "./pairing.types";
3
+
4
+ // Symbol token so the DI container can inject PairingConfig.
5
+ // Registered by PairingConfigFactory in the composition root.
6
+ export const PairingConfigToken = Symbol.for("codemation.pairing.PairingConfig") as TypeToken<PairingConfig>;
@@ -0,0 +1,14 @@
1
+ export { HmacRequestSigner } from "./HmacRequestSigner";
2
+ export type { SignedHeaders } from "./HmacRequestSigner";
3
+ export { PairedFetch } from "./PairedFetch";
4
+ export { IncomingHmacVerifier } from "./IncomingHmacVerifier";
5
+ export { InternalHmacAuthMiddleware } from "./InternalHmacAuthMiddleware";
6
+ export { InternalPingRegistrar } from "./InternalPingRegistrar";
7
+ export { PairingConfigFactory } from "./PairingConfigFactory";
8
+ export { PairingConfigToken } from "./PairingConfigToken";
9
+ export type {
10
+ PairingConfig,
11
+ PairingVerificationResult,
12
+ PairingVerificationFailure,
13
+ PairingVerificationSuccess,
14
+ } from "./pairing.types";
@@ -0,0 +1,18 @@
1
+ export interface PairingConfig {
2
+ /** The workspace's database ID. */
3
+ readonly workspaceId: string;
4
+ /** Base64-encoded 32-byte raw secret shared with the control plane. */
5
+ readonly pairingSecret: string;
6
+ /** Base URL of the control plane API, e.g. https://api.codemation.io */
7
+ readonly controlPlaneUrl: string;
8
+ }
9
+
10
+ export type PairingVerificationFailure = {
11
+ readonly failure: "missing" | "version" | "expired" | "workspace" | "signature" | "replay";
12
+ };
13
+
14
+ export type PairingVerificationSuccess = {
15
+ readonly workspaceId: string;
16
+ };
17
+
18
+ export type PairingVerificationResult = PairingVerificationSuccess | PairingVerificationFailure;
package/src/pairing.ts ADDED
@@ -0,0 +1,17 @@
1
+ // Subpath entry point: @codemation/host/pairing
2
+ export {
3
+ HmacRequestSigner,
4
+ PairedFetch,
5
+ IncomingHmacVerifier,
6
+ InternalHmacAuthMiddleware,
7
+ InternalPingRegistrar,
8
+ PairingConfigFactory,
9
+ PairingConfigToken,
10
+ } from "./pairing/index";
11
+ export type {
12
+ PairingConfig,
13
+ PairingVerificationResult,
14
+ PairingVerificationFailure,
15
+ PairingVerificationSuccess,
16
+ SignedHeaders,
17
+ } from "./pairing/index";
@@ -2,4 +2,5 @@ export { CodemationPostgresPrismaClientFactory } from "./infrastructure/persiste
2
2
  export type { AppPersistenceConfig } from "./presentation/config/AppConfig";
3
3
  export { AppConfigFactory } from "./bootstrap/runtime/AppConfigFactory";
4
4
  export { PrismaMigrationDeployer } from "./infrastructure/persistence/PrismaMigrationDeployer";
5
+ export { CodemationDatabaseUrlParser } from "./infrastructure/persistence/CodemationDatabaseUrlParser";
5
6
  export type { PrismaDatabaseClient as PrismaClient } from "./infrastructure/persistence/PrismaDatabaseClient";
@@ -1,4 +1,9 @@
1
- import type { AnyCredentialType, CollectionDefinition, WorkflowDefinition } from "@codemation/core";
1
+ import type {
2
+ AnyCredentialType,
3
+ CollectionDefinition,
4
+ McpServerDeclaration,
5
+ WorkflowDefinition,
6
+ } from "@codemation/core";
2
7
  import type { CodemationContainerRegistration } from "../../bootstrap/CodemationContainerRegistration";
3
8
  import type { CodemationPlugin } from "./CodemationPlugin";
4
9
  import type {
@@ -32,6 +37,7 @@ export interface AppConfig {
32
37
  readonly collections: ReadonlyArray<CollectionDefinition>;
33
38
  readonly plugins: ReadonlyArray<CodemationPlugin>;
34
39
  readonly pluginLoadSummary?: ReadonlyArray<AppPluginLoadSummary>;
40
+ readonly mcpServers: ReadonlyArray<McpServerDeclaration>;
35
41
  readonly hasConfiguredCredentialSessionServiceRegistration: boolean;
36
42
  readonly log?: CodemationLogConfig;
37
43
  readonly engineExecutionLimits?: CodemationEngineExecutionLimitsConfig;
@@ -4,7 +4,7 @@ import type { BetterAuthOptions } from "better-auth";
4
4
  * Consumer-declared authentication profile for the hosted UI + HTTP API.
5
5
  * Social provider ids intentionally match Better Auth's provider ids so config stays 1:1 with the auth runtime.
6
6
  */
7
- export type CodemationAuthKind = "local" | "oauth" | "oidc";
7
+ export type CodemationAuthKind = "local" | "oauth" | "oidc" | "managed";
8
8
 
9
9
  export type CodemationAuthOAuthProviderId = Extract<
10
10
  keyof NonNullable<BetterAuthOptions["socialProviders"]>,
@@ -1,4 +1,4 @@
1
- import type { AnyCredentialType, DefinedCollection, DefinedNode } from "@codemation/core";
1
+ import type { AnyCredentialType, DefinedCollection, DefinedNode, McpServerDeclaration } from "@codemation/core";
2
2
  import type { CodemationAppContext } from "./CodemationAppContext";
3
3
  import type {
4
4
  CodemationAppDefinition,
@@ -12,14 +12,20 @@ import type { CodemationWhitelabelConfig } from "./CodemationWhitelabelConfig";
12
12
  export interface FriendlyCodemationDatabaseConfig {
13
13
  readonly kind: "postgresql" | "sqlite";
14
14
  readonly url?: string;
15
+ /** Name of an environment variable whose value is the PostgreSQL connection URL. Co-exclusive with `url`. */
16
+ readonly urlEnv?: string;
15
17
  readonly filePath?: string;
16
18
  }
17
19
 
18
20
  export interface FriendlyCodemationExecutionConfig {
19
21
  readonly mode?: "inline" | "queue";
22
+ /** Name of an environment variable whose value is "inline" or "queue". Co-exclusive with `mode`. */
23
+ readonly modeEnv?: string;
20
24
  readonly queuePrefix?: string;
21
25
  readonly workerQueues?: ReadonlyArray<string>;
22
26
  readonly redisUrl?: string;
27
+ /** Name of an environment variable whose value is the Redis connection URL. Co-exclusive with `redisUrl`. */
28
+ readonly redisUrlEnv?: string;
23
29
  }
24
30
 
25
31
  export interface DefineCodemationAppOptions extends Omit<
@@ -36,6 +42,13 @@ export interface DefineCodemationAppOptions extends Omit<
36
42
  readonly credentials?: ReadonlyArray<AnyCredentialType>;
37
43
  readonly register?: (context: CodemationAppContext) => void;
38
44
  readonly whitelabel?: CodemationWhitelabelConfig;
45
+ /**
46
+ * Path (relative to the consumer project root) to a directory from which workflows are auto-discovered.
47
+ * All `*.ts` / `*.tsx` files (excluding `*.test.*` and `*.d.ts`) are imported and any exported
48
+ * `WorkflowDefinition` values are registered. Co-exclusive with providing this directory in
49
+ * `workflowDiscovery.directories`.
50
+ */
51
+ readonly workflowsDir?: string;
39
52
  }
40
53
 
41
54
  export interface DefinePluginOptions {
@@ -44,6 +57,7 @@ export interface DefinePluginOptions {
44
57
  readonly nodes?: ReadonlyArray<DefinedNode<string, Record<string, unknown>, unknown, unknown>>;
45
58
  readonly collections?: ReadonlyArray<DefinedCollection>;
46
59
  readonly credentials?: ReadonlyArray<AnyCredentialType>;
60
+ readonly mcpServers?: ReadonlyArray<McpServerDeclaration>;
47
61
  readonly register?: (context: CodemationPluginContext) => void | Promise<void>;
48
62
  readonly sandbox?: CodemationConfig;
49
63
  }
@@ -53,13 +67,15 @@ class CodemationAuthoringConfigFactory {
53
67
  const appDefinition = this.createAppDefinition(options);
54
68
  const credentialTypes = [...(options.credentialTypes ?? []), ...(options.credentials ?? [])];
55
69
  const register = this.composeAppRegister(options.register, options.nodes, options.collections);
56
- const { workflows, workflowDiscovery, plugins, runtime, log } = options;
70
+ const { workflows, plugins, runtime, log, mcpServers } = options;
71
+ const workflowDiscovery = this.mergeWorkflowDiscovery(options.workflowDiscovery, options.workflowsDir);
57
72
  return {
58
73
  workflows,
59
74
  workflowDiscovery,
60
75
  plugins,
61
76
  runtime,
62
77
  log,
78
+ mcpServers,
63
79
  app: appDefinition,
64
80
  credentialTypes,
65
81
  register,
@@ -70,6 +86,7 @@ class CodemationAuthoringConfigFactory {
70
86
  return {
71
87
  pluginPackageId: options.pluginPackageId,
72
88
  sandbox: options.sandbox,
89
+ mcpServers: options.mcpServers,
73
90
  async register(context: CodemationPluginContext): Promise<void> {
74
91
  for (const nodeDefinition of options.nodes ?? []) {
75
92
  nodeDefinition.register(context);
@@ -112,9 +129,15 @@ class CodemationAuthoringConfigFactory {
112
129
  sqliteFilePath: database.filePath,
113
130
  };
114
131
  }
132
+ if (database.url !== undefined && database.urlEnv !== undefined) {
133
+ throw new Error(
134
+ "defineCodemationApp: database.url and database.urlEnv are mutually exclusive — provide one or the other.",
135
+ );
136
+ }
137
+ const url = database.urlEnv !== undefined ? process.env[database.urlEnv] : database.url;
115
138
  return {
116
139
  kind: "postgresql",
117
- url: database.url,
140
+ url,
118
141
  };
119
142
  }
120
143
 
@@ -124,11 +147,37 @@ class CodemationAuthoringConfigFactory {
124
147
  if (!execution) {
125
148
  return undefined;
126
149
  }
150
+ if (execution.mode !== undefined && execution.modeEnv !== undefined) {
151
+ throw new Error(
152
+ "defineCodemationApp: execution.mode and execution.modeEnv are mutually exclusive — provide one or the other.",
153
+ );
154
+ }
155
+ if (execution.redisUrl !== undefined && execution.redisUrlEnv !== undefined) {
156
+ throw new Error(
157
+ "defineCodemationApp: execution.redisUrl and execution.redisUrlEnv are mutually exclusive — provide one or the other.",
158
+ );
159
+ }
160
+ const rawMode = execution.modeEnv !== undefined ? process.env[execution.modeEnv] : execution.mode;
161
+ const mode = rawMode === "inline" || rawMode === "queue" ? rawMode : undefined;
162
+ const redisUrl = execution.redisUrlEnv !== undefined ? process.env[execution.redisUrlEnv] : execution.redisUrl;
127
163
  return {
128
- kind: execution.mode,
164
+ kind: mode,
129
165
  queuePrefix: execution.queuePrefix,
130
166
  workerQueues: execution.workerQueues,
131
- redisUrl: execution.redisUrl,
167
+ redisUrl,
168
+ };
169
+ }
170
+
171
+ private static mergeWorkflowDiscovery(
172
+ existing: CodemationConfig["workflowDiscovery"],
173
+ workflowsDir: string | undefined,
174
+ ): CodemationConfig["workflowDiscovery"] {
175
+ if (!workflowsDir) {
176
+ return existing;
177
+ }
178
+ const existingDirectories = existing?.directories ?? [];
179
+ return {
180
+ directories: [...existingDirectories, workflowsDir],
132
181
  };
133
182
  }
134
183
 
@@ -3,6 +3,7 @@ import type {
3
3
  CollectionDefinition,
4
4
  DefinedCollection,
5
5
  EngineExecutionLimitsPolicyConfig,
6
+ McpServerDeclaration,
6
7
  WorkflowDefinition,
7
8
  } from "@codemation/core";
8
9
  import type { CodemationAuthConfig } from "./CodemationAuthConfig";
@@ -80,6 +81,8 @@ export interface CodemationConfig {
80
81
  readonly plugins?: ReadonlyArray<CodemationPlugin>;
81
82
  /** Consumer-defined `CredentialType` entries (see `@codemation/core`), applied when the host loads config. */
82
83
  readonly credentialTypes?: ReadonlyArray<AnyCredentialType>;
84
+ /** MCP server declarations from this config file (merged with plugin and control-plane sources at startup). */
85
+ readonly mcpServers?: ReadonlyArray<McpServerDeclaration>;
83
86
  /** Declared collections available at runtime via `ctx.collections`. Accepts either raw `CollectionDefinition` or `DefinedCollection` (the result of `defineCollection(...)`). */
84
87
  readonly collections?: ReadonlyArray<CollectionDefinition | DefinedCollection>;
85
88
  /** Optional shell whitelabel (product name, logo path). */