@wildo-ai/saas-backend-lib 1.1.0 → 1.1.2

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 (265) hide show
  1. package/dist/esm/base-classes/services/controllers/controller-resource.backend.service.d.ts +14 -3
  2. package/dist/esm/base-classes/services/controllers/controller-resource.backend.service.d.ts.map +1 -1
  3. package/dist/esm/base-classes/services/controllers/controller-resource.backend.service.js +115 -22
  4. package/dist/esm/base-classes/services/controllers/controller-resource.backend.service.js.map +1 -1
  5. package/dist/esm/base-classes/services/controllers/controllers-registry-handler.backend.service.d.ts +3 -1
  6. package/dist/esm/base-classes/services/controllers/controllers-registry-handler.backend.service.d.ts.map +1 -1
  7. package/dist/esm/base-classes/services/controllers/controllers-registry-handler.backend.service.js +8 -3
  8. package/dist/esm/base-classes/services/controllers/controllers-registry-handler.backend.service.js.map +1 -1
  9. package/dist/esm/base-classes/services/repositories/field-clearing-partition.backend.d.ts +11 -0
  10. package/dist/esm/base-classes/services/repositories/field-clearing-partition.backend.d.ts.map +1 -1
  11. package/dist/esm/base-classes/services/repositories/field-clearing-partition.backend.js +11 -2
  12. package/dist/esm/base-classes/services/repositories/field-clearing-partition.backend.js.map +1 -1
  13. package/dist/esm/base-classes/services/repositories/http-api/http-api-resource-repository.backend.service.d.ts +1 -0
  14. package/dist/esm/base-classes/services/repositories/http-api/http-api-resource-repository.backend.service.d.ts.map +1 -1
  15. package/dist/esm/base-classes/services/repositories/http-api/http-api-resource-repository.backend.service.js +6 -1
  16. package/dist/esm/base-classes/services/repositories/http-api/http-api-resource-repository.backend.service.js.map +1 -1
  17. package/dist/esm/base-classes/services/repositories/http-api/odata-query-compiler.backend.d.ts.map +1 -1
  18. package/dist/esm/base-classes/services/repositories/http-api/odata-query-compiler.backend.js +16 -0
  19. package/dist/esm/base-classes/services/repositories/http-api/odata-query-compiler.backend.js.map +1 -1
  20. package/dist/esm/base-classes/services/repositories/infrastructure-tier-contract.backend.d.ts +20 -6
  21. package/dist/esm/base-classes/services/repositories/infrastructure-tier-contract.backend.d.ts.map +1 -1
  22. package/dist/esm/base-classes/services/repositories/infrastructure-tier-contract.backend.js +88 -32
  23. package/dist/esm/base-classes/services/repositories/infrastructure-tier-contract.backend.js.map +1 -1
  24. package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-repository.backend.service.d.ts +3 -1
  25. package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-repository.backend.service.d.ts.map +1 -1
  26. package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-repository.backend.service.js +32 -7
  27. package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-repository.backend.service.js.map +1 -1
  28. package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-source.backend.port.d.ts +8 -5
  29. package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-source.backend.port.d.ts.map +1 -1
  30. package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-source.backend.port.js.map +1 -1
  31. package/dist/esm/base-classes/services/repositories/postgresql-resource-repository.backend.service.d.ts +31 -0
  32. package/dist/esm/base-classes/services/repositories/postgresql-resource-repository.backend.service.d.ts.map +1 -1
  33. package/dist/esm/base-classes/services/repositories/postgresql-resource-repository.backend.service.js +127 -5
  34. package/dist/esm/base-classes/services/repositories/postgresql-resource-repository.backend.service.js.map +1 -1
  35. package/dist/esm/base-classes/services/repositories/repositories-registry-handler.backend.service.d.ts +71 -1
  36. package/dist/esm/base-classes/services/repositories/repositories-registry-handler.backend.service.d.ts.map +1 -1
  37. package/dist/esm/base-classes/services/repositories/repositories-registry-handler.backend.service.js +62 -2
  38. package/dist/esm/base-classes/services/repositories/repositories-registry-handler.backend.service.js.map +1 -1
  39. package/dist/esm/base-classes/services/repositories/repository-resource.backend.service.d.ts +21 -0
  40. package/dist/esm/base-classes/services/repositories/repository-resource.backend.service.d.ts.map +1 -1
  41. package/dist/esm/base-classes/services/repositories/repository-resource.backend.service.js +126 -4
  42. package/dist/esm/base-classes/services/repositories/repository-resource.backend.service.js.map +1 -1
  43. package/dist/esm/base-classes/services/services/resource-operation-implementation-coverage.backend.utils.d.ts +3 -26
  44. package/dist/esm/base-classes/services/services/resource-operation-implementation-coverage.backend.utils.d.ts.map +1 -1
  45. package/dist/esm/base-classes/services/services/resource-operation-implementation-coverage.backend.utils.js +3 -68
  46. package/dist/esm/base-classes/services/services/resource-operation-implementation-coverage.backend.utils.js.map +1 -1
  47. package/dist/esm/base-classes/services/services/services-registry-handler.backend.service.d.ts.map +1 -1
  48. package/dist/esm/base-classes/services/services/services-registry-handler.backend.service.js +4 -2
  49. package/dist/esm/base-classes/services/services/services-registry-handler.backend.service.js.map +1 -1
  50. package/dist/esm/billing/services/custom-impl/billing-account.mutations.custom-impl.backend.service.js +6 -6
  51. package/dist/esm/billing/services/custom-impl/billing-account.mutations.custom-impl.backend.service.js.map +1 -1
  52. package/dist/esm/compliance/audit-trails/services/audit-event-dispatcher.backend.service.d.ts +38 -0
  53. package/dist/esm/compliance/audit-trails/services/audit-event-dispatcher.backend.service.d.ts.map +1 -1
  54. package/dist/esm/compliance/audit-trails/services/audit-event-dispatcher.backend.service.js +40 -0
  55. package/dist/esm/compliance/audit-trails/services/audit-event-dispatcher.backend.service.js.map +1 -1
  56. package/dist/esm/compliance/audit-trails/services/audit-logs.backend.service.d.ts +20 -0
  57. package/dist/esm/compliance/audit-trails/services/audit-logs.backend.service.d.ts.map +1 -1
  58. package/dist/esm/compliance/audit-trails/services/audit-logs.backend.service.js +28 -0
  59. package/dist/esm/compliance/audit-trails/services/audit-logs.backend.service.js.map +1 -1
  60. package/dist/esm/compliance/audit-trails/services/siem-export-config-cache-invalidation.custom-impl.backend.service.d.ts +12 -0
  61. package/dist/esm/compliance/audit-trails/services/siem-export-config-cache-invalidation.custom-impl.backend.service.d.ts.map +1 -0
  62. package/dist/esm/compliance/audit-trails/services/siem-export-config-cache-invalidation.custom-impl.backend.service.js +118 -0
  63. package/dist/esm/compliance/audit-trails/services/siem-export-config-cache-invalidation.custom-impl.backend.service.js.map +1 -0
  64. package/dist/esm/compliance/audit-trails/services/siem-webhook-delivery.backend.service.d.ts.map +1 -1
  65. package/dist/esm/compliance/audit-trails/services/siem-webhook-delivery.backend.service.js +8 -5
  66. package/dist/esm/compliance/audit-trails/services/siem-webhook-delivery.backend.service.js.map +1 -1
  67. package/dist/esm/compliance/audit-trails/services/utils/ocsf-activity-mapping.backend.utils.d.ts.map +1 -1
  68. package/dist/esm/compliance/audit-trails/services/utils/ocsf-activity-mapping.backend.utils.js +14 -0
  69. package/dist/esm/compliance/audit-trails/services/utils/ocsf-activity-mapping.backend.utils.js.map +1 -1
  70. package/dist/esm/config/custom/application-config-controller.backend.service.d.ts.map +1 -1
  71. package/dist/esm/config/custom/application-config-controller.backend.service.js +7 -1
  72. package/dist/esm/config/custom/application-config-controller.backend.service.js.map +1 -1
  73. package/dist/esm/config/models/app-config.backend.schemas.d.ts +4 -0
  74. package/dist/esm/config/models/app-config.backend.schemas.d.ts.map +1 -1
  75. package/dist/esm/config/services/app-configuration.backend.utils.d.ts +10 -1
  76. package/dist/esm/config/services/app-configuration.backend.utils.d.ts.map +1 -1
  77. package/dist/esm/config/services/app-configuration.backend.utils.js +13 -2
  78. package/dist/esm/config/services/app-configuration.backend.utils.js.map +1 -1
  79. package/dist/esm/data-seeding/services/shared/data-seed-scope-iteration.shared.utils.d.ts +2 -2
  80. package/dist/esm/data-seeding/services/shared/data-seed-scope-iteration.shared.utils.js +2 -2
  81. package/dist/esm/data-seeding/services/shared/data-seed-scope-iteration.shared.utils.js.map +1 -1
  82. package/dist/esm/databases/models/filter.backend.schemas.js +2 -2
  83. package/dist/esm/databases/models/filter.backend.schemas.js.map +1 -1
  84. package/dist/esm/databases/services/aggregation/aggregation-authorization.backend.service.d.ts.map +1 -1
  85. package/dist/esm/databases/services/aggregation/aggregation-authorization.backend.service.js +20 -1
  86. package/dist/esm/databases/services/aggregation/aggregation-authorization.backend.service.js.map +1 -1
  87. package/dist/esm/databases/services/redis.service.d.ts +27 -0
  88. package/dist/esm/databases/services/redis.service.d.ts.map +1 -1
  89. package/dist/esm/databases/services/redis.service.js +29 -0
  90. package/dist/esm/databases/services/redis.service.js.map +1 -1
  91. package/dist/esm/errors/services/error-handler.backend.service.d.ts.map +1 -1
  92. package/dist/esm/errors/services/error-handler.backend.service.js +5 -0
  93. package/dist/esm/errors/services/error-handler.backend.service.js.map +1 -1
  94. package/dist/esm/external-data/controllers/external-data-admin.backend.contracts.d.ts +41 -0
  95. package/dist/esm/external-data/controllers/external-data-admin.backend.contracts.d.ts.map +1 -0
  96. package/dist/esm/external-data/controllers/external-data-admin.backend.contracts.js +40 -0
  97. package/dist/esm/external-data/controllers/external-data-admin.backend.contracts.js.map +1 -0
  98. package/dist/esm/external-data/controllers/external-data-admin.backend.controller.d.ts +64 -0
  99. package/dist/esm/external-data/controllers/external-data-admin.backend.controller.d.ts.map +1 -0
  100. package/dist/esm/external-data/controllers/external-data-admin.backend.controller.js +193 -0
  101. package/dist/esm/external-data/controllers/external-data-admin.backend.controller.js.map +1 -0
  102. package/dist/esm/external-data/external-data-pipeline-run.backend.service.d.ts +13 -2
  103. package/dist/esm/external-data/external-data-pipeline-run.backend.service.d.ts.map +1 -1
  104. package/dist/esm/external-data/external-data-pipeline-run.backend.service.js +8 -2
  105. package/dist/esm/external-data/external-data-pipeline-run.backend.service.js.map +1 -1
  106. package/dist/esm/external-data/external-data-transform.backend.d.ts.map +1 -1
  107. package/dist/esm/external-data/external-data-transform.backend.js +7 -1
  108. package/dist/esm/external-data/external-data-transform.backend.js.map +1 -1
  109. package/dist/esm/external-data/models/external-data-pipeline-state.backend.schemas.d.ts +2 -0
  110. package/dist/esm/external-data/models/external-data-pipeline-state.backend.schemas.d.ts.map +1 -1
  111. package/dist/esm/external-data/models/external-data-pipeline-state.backend.schemas.js +13 -0
  112. package/dist/esm/external-data/models/external-data-pipeline-state.backend.schemas.js.map +1 -1
  113. package/dist/esm/external-providers/services/external-providers-registry.backend.service.d.ts +14 -0
  114. package/dist/esm/external-providers/services/external-providers-registry.backend.service.d.ts.map +1 -1
  115. package/dist/esm/external-providers/services/external-providers-registry.backend.service.js +6 -0
  116. package/dist/esm/external-providers/services/external-providers-registry.backend.service.js.map +1 -1
  117. package/dist/esm/files/controllers/file.controller.d.ts +24 -1
  118. package/dist/esm/files/controllers/file.controller.d.ts.map +1 -1
  119. package/dist/esm/files/controllers/file.controller.js +66 -2
  120. package/dist/esm/files/controllers/file.controller.js.map +1 -1
  121. package/dist/esm/files/handlers/file-upload-route-locals.backend.types.d.ts +17 -0
  122. package/dist/esm/files/handlers/file-upload-route-locals.backend.types.d.ts.map +1 -0
  123. package/dist/esm/files/handlers/file-upload-route-locals.backend.types.js +1 -0
  124. package/dist/esm/files/handlers/file-upload-route-locals.backend.types.js.map +1 -0
  125. package/dist/esm/files/handlers/resource-file-upload-handler.backend.service.d.ts +5 -1
  126. package/dist/esm/files/handlers/resource-file-upload-handler.backend.service.d.ts.map +1 -1
  127. package/dist/esm/files/handlers/resource-file-upload-handler.backend.service.js +42 -3
  128. package/dist/esm/files/handlers/resource-file-upload-handler.backend.service.js.map +1 -1
  129. package/dist/esm/files/services/file-operation-handler.backend.service.d.ts +18 -0
  130. package/dist/esm/files/services/file-operation-handler.backend.service.d.ts.map +1 -1
  131. package/dist/esm/files/services/file-operation-handler.backend.service.js +36 -28
  132. package/dist/esm/files/services/file-operation-handler.backend.service.js.map +1 -1
  133. package/dist/esm/files/services/file-upload-grant.backend.service.d.ts +163 -0
  134. package/dist/esm/files/services/file-upload-grant.backend.service.d.ts.map +1 -0
  135. package/dist/esm/files/services/file-upload-grant.backend.service.js +442 -0
  136. package/dist/esm/files/services/file-upload-grant.backend.service.js.map +1 -0
  137. package/dist/esm/files/services/files.backend.service.d.ts +19 -0
  138. package/dist/esm/files/services/files.backend.service.d.ts.map +1 -1
  139. package/dist/esm/files/services/files.backend.service.js +78 -15
  140. package/dist/esm/files/services/files.backend.service.js.map +1 -1
  141. package/dist/esm/files/utils/file-uploader.utils.d.ts.map +1 -1
  142. package/dist/esm/files/utils/file-uploader.utils.js +12 -0
  143. package/dist/esm/files/utils/file-uploader.utils.js.map +1 -1
  144. package/dist/esm/flows-actors/a2a/services/a2a-task-cancellation.backend.service.js +5 -5
  145. package/dist/esm/flows-actors/a2a/services/a2a-task-cancellation.backend.service.js.map +1 -1
  146. package/dist/esm/flows-actors/services/agents.backend.service.d.ts +79 -0
  147. package/dist/esm/flows-actors/services/agents.backend.service.d.ts.map +1 -1
  148. package/dist/esm/flows-actors/services/agents.backend.service.js +319 -42
  149. package/dist/esm/flows-actors/services/agents.backend.service.js.map +1 -1
  150. package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.d.ts +151 -41
  151. package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.d.ts.map +1 -1
  152. package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.js +300 -139
  153. package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.js.map +1 -1
  154. package/dist/esm/index.d.ts +4 -1
  155. package/dist/esm/index.d.ts.map +1 -1
  156. package/dist/esm/index.js +14 -1
  157. package/dist/esm/index.js.map +1 -1
  158. package/dist/esm/inject/models/container.d.ts.map +1 -1
  159. package/dist/esm/inject/models/container.js +4 -0
  160. package/dist/esm/inject/models/container.js.map +1 -1
  161. package/dist/esm/inject/models/service-types.d.ts +3 -0
  162. package/dist/esm/inject/models/service-types.d.ts.map +1 -1
  163. package/dist/esm/inject/models/service-types.js +2 -0
  164. package/dist/esm/inject/models/service-types.js.map +1 -1
  165. package/dist/esm/mcp/controllers/mcp.controller.d.ts.map +1 -1
  166. package/dist/esm/mcp/controllers/mcp.controller.js +12 -4
  167. package/dist/esm/mcp/controllers/mcp.controller.js.map +1 -1
  168. package/dist/esm/mcp/models/mcp-tool.backend.schemas.d.ts +34 -0
  169. package/dist/esm/mcp/models/mcp-tool.backend.schemas.d.ts.map +1 -1
  170. package/dist/esm/mcp/models/mcp-tool.backend.schemas.js +42 -0
  171. package/dist/esm/mcp/models/mcp-tool.backend.schemas.js.map +1 -1
  172. package/dist/esm/mcp/services/mcp-server.backend.service.d.ts +29 -1
  173. package/dist/esm/mcp/services/mcp-server.backend.service.d.ts.map +1 -1
  174. package/dist/esm/mcp/services/mcp-server.backend.service.js +131 -3
  175. package/dist/esm/mcp/services/mcp-server.backend.service.js.map +1 -1
  176. package/dist/esm/mcp/services/mcp-tool-adapter.backend.service.d.ts +64 -2
  177. package/dist/esm/mcp/services/mcp-tool-adapter.backend.service.d.ts.map +1 -1
  178. package/dist/esm/mcp/services/mcp-tool-adapter.backend.service.js +130 -6
  179. package/dist/esm/mcp/services/mcp-tool-adapter.backend.service.js.map +1 -1
  180. package/dist/esm/notifications/services/notifications-dispatcher.backend.service.d.ts +9 -0
  181. package/dist/esm/notifications/services/notifications-dispatcher.backend.service.d.ts.map +1 -1
  182. package/dist/esm/notifications/services/notifications-dispatcher.backend.service.js +11 -36
  183. package/dist/esm/notifications/services/notifications-dispatcher.backend.service.js.map +1 -1
  184. package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.backend.service.d.ts.map +1 -1
  185. package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.backend.service.js +9 -7
  186. package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.backend.service.js.map +1 -1
  187. package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.utils.d.ts +5 -5
  188. package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.utils.d.ts.map +1 -1
  189. package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.utils.js +14 -13
  190. package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.utils.js.map +1 -1
  191. package/dist/esm/notifications/webhooks/outbound/m2m-webhook-signed-request.backend.utils.d.ts +2 -1
  192. package/dist/esm/notifications/webhooks/outbound/m2m-webhook-signed-request.backend.utils.d.ts.map +1 -1
  193. package/dist/esm/notifications/webhooks/outbound/m2m-webhook-signed-request.backend.utils.js +2 -2
  194. package/dist/esm/notifications/webhooks/outbound/m2m-webhook-signed-request.backend.utils.js.map +1 -1
  195. package/dist/esm/notifications/webhooks/outbound/services/webhook-config-test-endpoint.custom-impl.backend.service.d.ts.map +1 -1
  196. package/dist/esm/notifications/webhooks/outbound/services/webhook-config-test-endpoint.custom-impl.backend.service.js +9 -7
  197. package/dist/esm/notifications/webhooks/outbound/services/webhook-config-test-endpoint.custom-impl.backend.service.js.map +1 -1
  198. package/dist/esm/organizations/services/organization-unit-custom-implementation.backend.service.d.ts.map +1 -1
  199. package/dist/esm/organizations/services/organization-unit-custom-implementation.backend.service.js +4 -1
  200. package/dist/esm/organizations/services/organization-unit-custom-implementation.backend.service.js.map +1 -1
  201. package/dist/esm/requests/service/websocket.service.d.ts.map +1 -1
  202. package/dist/esm/requests/service/websocket.service.js +56 -7
  203. package/dist/esm/requests/service/websocket.service.js.map +1 -1
  204. package/dist/esm/resources/models/custom-service-implementations.backend.definition.d.ts.map +1 -1
  205. package/dist/esm/resources/models/custom-service-implementations.backend.definition.js +2 -0
  206. package/dist/esm/resources/models/custom-service-implementations.backend.definition.js.map +1 -1
  207. package/dist/esm/resources/services/resource-id.backend.service.js +3 -3
  208. package/dist/esm/resources/services/resource-id.backend.service.js.map +1 -1
  209. package/dist/esm/resources/services/resources-registry.backend.service.js +1 -1
  210. package/dist/esm/resources/services/resources-registry.backend.service.js.map +1 -1
  211. package/dist/esm/runtime/services/execution-context-creator.backend.service.d.ts.map +1 -1
  212. package/dist/esm/runtime/services/execution-context-creator.backend.service.js +13 -7
  213. package/dist/esm/runtime/services/execution-context-creator.backend.service.js.map +1 -1
  214. package/dist/esm/runtime/services/start-application.backend.service.d.ts +28 -0
  215. package/dist/esm/runtime/services/start-application.backend.service.d.ts.map +1 -1
  216. package/dist/esm/runtime/services/start-application.backend.service.js +82 -0
  217. package/dist/esm/runtime/services/start-application.backend.service.js.map +1 -1
  218. package/dist/esm/security/authentications/models/jwt.backend.schemas.d.ts +3 -23
  219. package/dist/esm/security/authentications/models/jwt.backend.schemas.d.ts.map +1 -1
  220. package/dist/esm/security/authentications/models/jwt.backend.schemas.js +20 -45
  221. package/dist/esm/security/authentications/models/jwt.backend.schemas.js.map +1 -1
  222. package/dist/esm/security/authentications/services/auth-methods/auth-method-management.backend.service.d.ts +9 -1
  223. package/dist/esm/security/authentications/services/auth-methods/auth-method-management.backend.service.d.ts.map +1 -1
  224. package/dist/esm/security/authentications/services/auth-methods/auth-method-management.backend.service.js +9 -1
  225. package/dist/esm/security/authentications/services/auth-methods/auth-method-management.backend.service.js.map +1 -1
  226. package/dist/esm/security/authentications/services/auth-methods/auth-mfa.backend.service.d.ts.map +1 -1
  227. package/dist/esm/security/authentications/services/auth-methods/auth-mfa.backend.service.js +5 -3
  228. package/dist/esm/security/authentications/services/auth-methods/auth-mfa.backend.service.js.map +1 -1
  229. package/dist/esm/security/authentications/services/auth-token-issuer.backend.service.d.ts +25 -1
  230. package/dist/esm/security/authentications/services/auth-token-issuer.backend.service.d.ts.map +1 -1
  231. package/dist/esm/security/authentications/services/auth-token-issuer.backend.service.js +55 -4
  232. package/dist/esm/security/authentications/services/auth-token-issuer.backend.service.js.map +1 -1
  233. package/dist/esm/security/authorizations/initiator-organization-confinement.backend.d.ts.map +1 -1
  234. package/dist/esm/security/authorizations/initiator-organization-confinement.backend.js +24 -1
  235. package/dist/esm/security/authorizations/initiator-organization-confinement.backend.js.map +1 -1
  236. package/dist/esm/security/authorizations/privileged-access-review.backend.utils.d.ts +117 -0
  237. package/dist/esm/security/authorizations/privileged-access-review.backend.utils.d.ts.map +1 -0
  238. package/dist/esm/security/authorizations/privileged-access-review.backend.utils.js +104 -0
  239. package/dist/esm/security/authorizations/privileged-access-review.backend.utils.js.map +1 -0
  240. package/dist/esm/security/authorizations/services/authorizations.backend.service.d.ts +101 -6
  241. package/dist/esm/security/authorizations/services/authorizations.backend.service.d.ts.map +1 -1
  242. package/dist/esm/security/authorizations/services/authorizations.backend.service.js +210 -30
  243. package/dist/esm/security/authorizations/services/authorizations.backend.service.js.map +1 -1
  244. package/dist/esm/security/consumable-tokens/batches/consumable-tokens-cleanup.batch.backend.service.d.ts +18 -1
  245. package/dist/esm/security/consumable-tokens/batches/consumable-tokens-cleanup.batch.backend.service.d.ts.map +1 -1
  246. package/dist/esm/security/consumable-tokens/batches/consumable-tokens-cleanup.batch.backend.service.js +110 -74
  247. package/dist/esm/security/consumable-tokens/batches/consumable-tokens-cleanup.batch.backend.service.js.map +1 -1
  248. package/dist/esm/security/consumable-tokens/consumable-token-models.exports.d.ts +22 -0
  249. package/dist/esm/security/consumable-tokens/consumable-token-models.exports.d.ts.map +1 -0
  250. package/dist/esm/security/consumable-tokens/consumable-token-models.exports.js +22 -0
  251. package/dist/esm/security/consumable-tokens/consumable-token-models.exports.js.map +1 -0
  252. package/dist/esm/security/consumable-tokens/models/consumable-tokens.backend.resources-config.schemas.d.ts +17 -3
  253. package/dist/esm/security/consumable-tokens/models/consumable-tokens.backend.resources-config.schemas.d.ts.map +1 -1
  254. package/dist/esm/security/consumable-tokens/models/consumable-tokens.backend.resources-config.schemas.js +12 -0
  255. package/dist/esm/security/consumable-tokens/models/consumable-tokens.backend.resources-config.schemas.js.map +1 -1
  256. package/dist/esm/security/consumable-tokens/services/consumable-token.backend.service.d.ts +20 -0
  257. package/dist/esm/security/consumable-tokens/services/consumable-token.backend.service.d.ts.map +1 -1
  258. package/dist/esm/security/consumable-tokens/services/consumable-token.backend.service.js +58 -5
  259. package/dist/esm/security/consumable-tokens/services/consumable-token.backend.service.js.map +1 -1
  260. package/dist/esm/security/jwt/services/jwt.backend.service.d.ts.map +1 -1
  261. package/dist/esm/security/jwt/services/jwt.backend.service.js +1 -18
  262. package/dist/esm/security/jwt/services/jwt.backend.service.js.map +1 -1
  263. package/dist/tsconfig.build.tsbuildinfo +1 -1
  264. package/package.json +15 -9
  265. package/dist/esm/.builder.pid +0 -9
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-tool.backend.schemas.d.ts","sourceRoot":"","sources":["../../../../../src/mcp/models/mcp-tool.backend.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE3E;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAW5C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAIjE;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,iDAAiD;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,oGAAoG;IACpG,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;;;;;;OAWG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAC;IACb,kGAAkG;IAClG,WAAW,EAAE,MAAM,CAAC;IACpB,iGAAiG;IACjG,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,4DAA4D;IAC5D,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC;;;;OAIG;IACH,YAAY,EAAE,YAAY,CAAC;IAC3B,mEAAmE;IACnE,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,0BAA0B,GAAG,CACvC,YAAY,EAAE,YAAY,EAC1B,mBAAmB,EAAE,MAAM,KACxB;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,YAAY,EAAE,CAAC;IAC/B,kFAAkF;IAClF,mBAAmB,CAAC,EAAE,0BAA0B,CAAC;IACjD;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,WAAW,CAAC,oBAAoB,CAAC,CAAC;IACpD;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB"}
1
+ {"version":3,"file":"mcp-tool.backend.schemas.d.ts","sourceRoot":"","sources":["../../../../../src/mcp/models/mcp-tool.backend.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE3E;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAW5C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,4BAA4B,mBAAmB,CAAC;AAE7D,mGAAmG;AACnG,wBAAgB,iCAAiC,CAAC,yBAAyB,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,MAAM,CAEtH;AAED;;;;GAIG;AACH,wBAAgB,iCAAiC,CAAC,aAAa,EAAE,MAAM,GAAG;IAAE,yBAAyB,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAOrJ;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAIjE;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,iDAAiD;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,oGAAoG;IACpG,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;;;;;;OAWG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAC;IACb,kGAAkG;IAClG,WAAW,EAAE,MAAM,CAAC;IACpB,iGAAiG;IACjG,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,4DAA4D;IAC5D,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC;;;;OAIG;IACH,YAAY,EAAE,YAAY,CAAC;IAC3B,mEAAmE;IACnE,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,0BAA0B,GAAG,CACvC,YAAY,EAAE,YAAY,EAC1B,mBAAmB,EAAE,MAAM,KACxB;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,YAAY,EAAE,CAAC;IAC/B,kFAAkF;IAClF,mBAAmB,CAAC,EAAE,0BAA0B,CAAC;IACjD;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,WAAW,CAAC,oBAAoB,CAAC,CAAC;IACpD;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB"}
@@ -48,6 +48,48 @@ const MCP_TOOL_NAME_ALLOWED_CHARACTERS = /^[A-Za-z0-9_.-]+$/;
48
48
  * re-checked here: `buildResourceToolDescriptors` already de-dupes by name within a resource, and
49
49
  * the `__`-in-resource-identifier guard keeps names from colliding across resources.
50
50
  */
51
+ /**
52
+ * The DERIVED upload-grant tool (issue #312, D11).
53
+ *
54
+ * For every exposed default CREATE / UPDATE operation whose request carries a user-uploadable file
55
+ * field, the catalogue also advertises `<resource>__<operation>.upload_grant.<fieldSegment>`, e.g.
56
+ * `todos__create.upload_grant.attachments`. Calling it mints a single-use upload grant for that field
57
+ * (see `FileUploadGrantBackendService`); it never carries bytes.
58
+ *
59
+ * ## Why dots
60
+ *
61
+ * `parseToolName` splits on the FIRST `__`, so everything after it is the "operation". Operation
62
+ * identifiers are snake_case and never contain `.`, while the naming guidance allows `.` — so a dotted
63
+ * suffix is unambiguous, cannot collide with an authored custom operation, and needs no second
64
+ * separator. Rejected: `_upload_grant_` (collides with a legitimately named custom operation, and
65
+ * refusing that operation would make the wrong party pay) and a standalone `files__…` tool taking the
66
+ * resource in its arguments (would have to re-implement the parent's exposure / instance / scope
67
+ * gating instead of inheriting it).
68
+ *
69
+ * The field segment is the SAME kebab-case segment the upload ROUTE uses (`…/files/<segment>`), so a
70
+ * tool name and its route agree by construction; resolution maps the segment back by recomputing it
71
+ * over the resource's uploadable fields rather than by inverting the transform.
72
+ */
73
+ export const MCP_UPLOAD_GRANT_TOOL_MARKER = '.upload_grant.';
74
+ /** `<parentOperation>.upload_grant.<fieldSegment>` — the operation part of a derived tool name. */
75
+ export function buildUploadGrantToolOperationPart(parentOperationIdentifier, fieldRouteSegment) {
76
+ return `${parentOperationIdentifier}${MCP_UPLOAD_GRANT_TOOL_MARKER}${fieldRouteSegment}`;
77
+ }
78
+ /**
79
+ * Split the operation part of a derived tool name, or `undefined` when it is not one. Strict on shape:
80
+ * exactly one marker, a non-empty snake_case parent and a non-empty kebab-case segment — anything
81
+ * else is an ordinary (possibly custom) operation identifier and is left to the ordinary resolver.
82
+ */
83
+ export function parseUploadGrantToolOperationPart(operationPart) {
84
+ const at = operationPart.indexOf(MCP_UPLOAD_GRANT_TOOL_MARKER);
85
+ if (at <= 0)
86
+ return undefined;
87
+ const parentOperationIdentifier = operationPart.slice(0, at);
88
+ const fieldRouteSegment = operationPart.slice(at + MCP_UPLOAD_GRANT_TOOL_MARKER.length);
89
+ if (!/^[a-z0-9_]+$/.test(parentOperationIdentifier) || !/^[a-z0-9-]+$/.test(fieldRouteSegment))
90
+ return undefined;
91
+ return { parentOperationIdentifier, fieldRouteSegment };
92
+ }
51
93
  export function isSpecConformantMcpToolName(name) {
52
94
  return name.length > 0
53
95
  && name.length <= MCP_TOOL_NAME_MAX_LENGTH
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-tool.backend.schemas.js","sourceRoot":"","sources":["../../../../../src/mcp/models/mcp-tool.backend.schemas.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAE5C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,CAAC;AAE5C;;;;;;GAMG;AACH,MAAM,gCAAgC,GAAG,mBAAmB,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,2BAA2B,CAAC,IAAY;IACtD,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC;WACjB,IAAI,CAAC,MAAM,IAAI,wBAAwB;WACvC,gCAAgC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACnD,CAAC","sourcesContent":["import { ResourceType, ResourcePrimaryScope } from '@wildo-ai/saas-models';\n\n/**\n * Tool-name separator between the resource identifier and the operation\n * identifier: `<resourceIdentifier>__<operationIdentifier>` (e.g. `todos__create`).\n *\n * A DOUBLE underscore is used deliberately: resource identifiers themselves can\n * contain single underscores (`organization_members`), so a single-underscore\n * separator would be ambiguous. The double underscore is the first-slice identity\n * scheme; a `variantKey` suffix (`<resource>__<operation>__<variantKey>`) is a\n * follow-up once non-default variants are exposed (see the MCP child plan).\n */\nexport const MCP_TOOL_NAME_SEPARATOR = '__';\n\n/**\n * Upper bound on a tool name, from the naming guidance `2025-11-25` added (SEP-986).\n *\n * \"Tool names SHOULD be between 1 and 128 characters in length (inclusive).\"\n * spec-cite: MCP 2025-11-25 — Tools § Data Types → Tool Names\n */\nexport const MCP_TOOL_NAME_MAX_LENGTH = 128;\n\n/**\n * The character set the same section allows.\n *\n * \"The following SHOULD be the only allowed characters: uppercase and lowercase ASCII letters (A-Z,\n * a-z), digits (0-9), underscore (_), hyphen (-), and dot (.)\"\n * spec-cite: MCP 2025-11-25 — Tools § Data Types → Tool Names\n */\nconst MCP_TOOL_NAME_ALLOWED_CHARACTERS = /^[A-Za-z0-9_.-]+$/;\n\n/**\n * Does this tool name satisfy the `2025-11-25` naming guidance?\n *\n * # Why a server-side gate for a SHOULD\n *\n * A tool name here is `<resourceIdentifier>__<operationIdentifier>`, and BOTH halves are authored by\n * the application, not by the engine. Nothing else constrains them: a resource identifier is a\n * TypeScript string and a custom operation identifier is whatever its author typed. So the guidance\n * binds an app developer who has never read it, through a name they did not know they were composing.\n *\n * The failure it prevents is not local. A name is not rejected by the tool that carries it — it is\n * carried inside a `tools/list` RESULT, so a client strict about the guidance can reject the whole\n * CATALOGUE, and every other tool in the application disappears with it. Refusing the one bad\n * descriptor is therefore the smaller failure by a wide margin, and it names the offender in a log\n * line instead of leaving an app developer to explain why one client sees no tools at all.\n *\n * Measured before choosing fail-closed: every tool name this repository actually produces matches\n * `[A-Za-z0-9_]+__[A-Za-z0-9_]+` (todos__create, user_preferences__read, todos__recent_todos, …), so\n * the gate withdraws nothing that exists today. It is a guard against a future name, not a migration.\n *\n * Uniqueness — the fourth SHOULD in that section — is enforced elsewhere and deliberately not\n * re-checked here: `buildResourceToolDescriptors` already de-dupes by name within a resource, and\n * the `__`-in-resource-identifier guard keeps names from colliding across resources.\n */\nexport function isSpecConformantMcpToolName(name: string): boolean {\n return name.length > 0\n && name.length <= MCP_TOOL_NAME_MAX_LENGTH\n && MCP_TOOL_NAME_ALLOWED_CHARACTERS.test(name);\n}\n\n/**\n * Agent-facing behavioural hints on a tool, mirroring the MCP `ToolAnnotations`\n * shape. Advisory only — they never affect authorization (which is always\n * re-checked at `callTool` time). Derived from the operation's core identifier +\n * `riskLevel`.\n */\nexport interface McpToolAnnotations {\n /** Human-readable title for display surfaces. */\n title?: string;\n /** True when the operation only reads (READ/LIST/SEARCH/COUNT). */\n readOnlyHint?: boolean;\n /** True when the operation may destroy/irreversibly mutate data (DELETE, or HIGH/CRITICAL risk). */\n destructiveHint?: boolean;\n /**\n * True when repeating the call with the same arguments has no ADDITIONAL effect.\n *\n * \"(This property is meaningful only when `readOnlyHint == false`)\" and its default is `false`, so\n * it is emitted ONLY for write operations — advertising it on a read tool would state something\n * the specification defines as meaningless there.\n * spec-cite: MCP schema — `ToolAnnotations.idempotentHint`\n */\n idempotentHint?: boolean;\n /**\n * True when the tool may reach an \"open world\" of external entities.\n *\n * **Its default is `true`**, which is why omitting it was not neutral: every tool this server\n * exposed was advertised as potentially reaching arbitrary external systems. A Wildo tool operates\n * on this application's OWN resources through its own authorization — a closed domain — so the\n * honest value is `false`, and it is the difference between an agent treating a tool call as a\n * local state change and treating it as a call out to the internet.\n * spec-cite: MCP schema — `ToolAnnotations.openWorldHint` (\"If false, the tool's domain of\n * interaction is closed. For example, the world of a web search tool is open, whereas that of a\n * memory tool is not.\")\n */\n openWorldHint?: boolean;\n}\n\n/**\n * A single MCP tool descriptor as returned by `tools/list`. `inputSchema` is a\n * JSON Schema object derived from the operation `requestDto`.\n */\nexport interface McpToolDescriptor {\n /** `<resourceIdentifier>__<operationIdentifier>` — unique per exposed default operation. */\n name: string;\n /** Agent-facing prose. Spec-sourced when a description provider is supplied, else synthesized. */\n description: string;\n /** JSON Schema object for the tool arguments (empty-object schema when the op takes no body). */\n inputSchema: Record<string, unknown>;\n /** Advisory behavioural hints (read-only / destructive). */\n annotations?: McpToolAnnotations;\n /**\n * The resource type this tool maps to — retained so `callTool` can re-resolve\n * the operation without re-parsing the name. NOT part of the wire `tools/list`\n * payload (strip before serialising if a transport is added).\n */\n resourceType: ResourceType;\n /** The operation identifier this tool maps to (core or custom). */\n operationIdentifier: string;\n}\n\n/**\n * Resolves agent-facing description prose for one operation.\n *\n * # Why this is INJECTED rather than read from the spec layer directly\n *\n * The MCP child plan says tool descriptions source from the SPEC layer\n * (`ResourceOperationSpecification.purpose` / `whenToUse`). But\n * `@wildo-ai/saas-specifications` is DOWNSTREAM of `saas-backend-lib` in the\n * engine dependency graph — the backend cannot import specs without inverting the\n * layering. So the adapter accepts this provider and the caller (the app /\n * companion, which DOES have spec access) supplies spec-sourced prose. When no\n * provider is given, the adapter synthesizes a description from the operation's\n * own config metadata.\n *\n * The D4 config-local description field (`operation.mcp.description`, a dedicated\n * agent-facing slot on the operation config) has SHIPPED, and the adapter reads it\n * directly with top priority (see `McpToolAdapterBackendService.resolveDescription`).\n * This provider is therefore optional enrichment, consulted only when no authored\n * `mcp.description` is present.\n *\n * @returns the spec prose for the operation, or `undefined` to fall back to synthesis.\n */\nexport type McpToolDescriptionProvider = (\n resourceType: ResourceType,\n operationIdentifier: string,\n) => { purpose?: string; whenToUse?: string } | undefined;\n\n/**\n * Options for building the exposed tool descriptor set.\n */\nexport interface McpToolAdapterBuildOptions {\n /**\n * Optional narrowing allow-list of resource types to enumerate. When omitted,\n * ALL registered ORGANIZATION/APPLICATION-scoped resources are considered.\n *\n * This is a coarse convenience filter, NOT the exposure gate: exposure is\n * already fail-closed per-operation via `operation.mcp.exposed === true` (D4,\n * shipped), so the default (no allow-list) never over-exposes — only operations\n * that explicitly opt in become tools. Pass this only to further restrict the set.\n */\n resourceTypes?: ResourceType[];\n /** Spec-sourced description provider (see {@link McpToolDescriptionProvider}). */\n descriptionProvider?: McpToolDescriptionProvider;\n /**\n * The resource primary scopes the CALLER can reach (progressive principal-awareness). Omit → the\n * default machine-reachable set (ORG / APP), which is the MACHINE principal's set. An ANONYMOUS\n * caller is NOT a machine and reaches nothing; the transport challenges it rather than listing.\n * A user-delegated caller passes the delegated set (adds `USER_SELF`) so their USER_SELF tools are\n * advertised. Derived from the SAME source `callTool`'s exposability gate uses\n * (`mcpReachableScopesForPrincipal`), so advertise ≡ callable per principal.\n */\n reachableScopes?: ReadonlySet<ResourcePrimaryScope>;\n /**\n * The NAMED MCP server instance being served (R-2), or omitted for the DEFAULT `/mcp` server.\n * Filters the catalog to the operations that belong to that instance (`operation.mcp.servers`),\n * composed with the scope + opt-in filters. Membership is exclusive: an operation with no declared\n * `servers` appears ONLY on the default server, and one with declared servers ONLY on those.\n */\n instanceRef?: string;\n}\n"]}
1
+ {"version":3,"file":"mcp-tool.backend.schemas.js","sourceRoot":"","sources":["../../../../../src/mcp/models/mcp-tool.backend.schemas.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAE5C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,CAAC;AAE5C;;;;;;GAMG;AACH,MAAM,gCAAgC,GAAG,mBAAmB,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,gBAAgB,CAAC;AAE7D,mGAAmG;AACnG,MAAM,UAAU,iCAAiC,CAAC,yBAAiC,EAAE,iBAAyB;IAC5G,OAAO,GAAG,yBAAyB,GAAG,4BAA4B,GAAG,iBAAiB,EAAE,CAAC;AAC3F,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iCAAiC,CAAC,aAAqB;IACrE,MAAM,EAAE,GAAG,aAAa,CAAC,OAAO,CAAC,4BAA4B,CAAC,CAAC;IAC/D,IAAI,EAAE,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IAC9B,MAAM,yBAAyB,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC7D,MAAM,iBAAiB,GAAG,aAAa,CAAC,KAAK,CAAC,EAAE,GAAG,4BAA4B,CAAC,MAAM,CAAC,CAAC;IACxF,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,yBAAyB,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,iBAAiB,CAAC;QAAE,OAAO,SAAS,CAAC;IACjH,OAAO,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,IAAY;IACtD,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC;WACjB,IAAI,CAAC,MAAM,IAAI,wBAAwB;WACvC,gCAAgC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACnD,CAAC","sourcesContent":["import { ResourceType, ResourcePrimaryScope } from '@wildo-ai/saas-models';\n\n/**\n * Tool-name separator between the resource identifier and the operation\n * identifier: `<resourceIdentifier>__<operationIdentifier>` (e.g. `todos__create`).\n *\n * A DOUBLE underscore is used deliberately: resource identifiers themselves can\n * contain single underscores (`organization_members`), so a single-underscore\n * separator would be ambiguous. The double underscore is the first-slice identity\n * scheme; a `variantKey` suffix (`<resource>__<operation>__<variantKey>`) is a\n * follow-up once non-default variants are exposed (see the MCP child plan).\n */\nexport const MCP_TOOL_NAME_SEPARATOR = '__';\n\n/**\n * Upper bound on a tool name, from the naming guidance `2025-11-25` added (SEP-986).\n *\n * \"Tool names SHOULD be between 1 and 128 characters in length (inclusive).\"\n * spec-cite: MCP 2025-11-25 — Tools § Data Types → Tool Names\n */\nexport const MCP_TOOL_NAME_MAX_LENGTH = 128;\n\n/**\n * The character set the same section allows.\n *\n * \"The following SHOULD be the only allowed characters: uppercase and lowercase ASCII letters (A-Z,\n * a-z), digits (0-9), underscore (_), hyphen (-), and dot (.)\"\n * spec-cite: MCP 2025-11-25 — Tools § Data Types → Tool Names\n */\nconst MCP_TOOL_NAME_ALLOWED_CHARACTERS = /^[A-Za-z0-9_.-]+$/;\n\n/**\n * Does this tool name satisfy the `2025-11-25` naming guidance?\n *\n * # Why a server-side gate for a SHOULD\n *\n * A tool name here is `<resourceIdentifier>__<operationIdentifier>`, and BOTH halves are authored by\n * the application, not by the engine. Nothing else constrains them: a resource identifier is a\n * TypeScript string and a custom operation identifier is whatever its author typed. So the guidance\n * binds an app developer who has never read it, through a name they did not know they were composing.\n *\n * The failure it prevents is not local. A name is not rejected by the tool that carries it — it is\n * carried inside a `tools/list` RESULT, so a client strict about the guidance can reject the whole\n * CATALOGUE, and every other tool in the application disappears with it. Refusing the one bad\n * descriptor is therefore the smaller failure by a wide margin, and it names the offender in a log\n * line instead of leaving an app developer to explain why one client sees no tools at all.\n *\n * Measured before choosing fail-closed: every tool name this repository actually produces matches\n * `[A-Za-z0-9_]+__[A-Za-z0-9_]+` (todos__create, user_preferences__read, todos__recent_todos, …), so\n * the gate withdraws nothing that exists today. It is a guard against a future name, not a migration.\n *\n * Uniqueness — the fourth SHOULD in that section — is enforced elsewhere and deliberately not\n * re-checked here: `buildResourceToolDescriptors` already de-dupes by name within a resource, and\n * the `__`-in-resource-identifier guard keeps names from colliding across resources.\n */\n/**\n * The DERIVED upload-grant tool (issue #312, D11).\n *\n * For every exposed default CREATE / UPDATE operation whose request carries a user-uploadable file\n * field, the catalogue also advertises `<resource>__<operation>.upload_grant.<fieldSegment>`, e.g.\n * `todos__create.upload_grant.attachments`. Calling it mints a single-use upload grant for that field\n * (see `FileUploadGrantBackendService`); it never carries bytes.\n *\n * ## Why dots\n *\n * `parseToolName` splits on the FIRST `__`, so everything after it is the \"operation\". Operation\n * identifiers are snake_case and never contain `.`, while the naming guidance allows `.` — so a dotted\n * suffix is unambiguous, cannot collide with an authored custom operation, and needs no second\n * separator. Rejected: `_upload_grant_` (collides with a legitimately named custom operation, and\n * refusing that operation would make the wrong party pay) and a standalone `files__…` tool taking the\n * resource in its arguments (would have to re-implement the parent's exposure / instance / scope\n * gating instead of inheriting it).\n *\n * The field segment is the SAME kebab-case segment the upload ROUTE uses (`…/files/<segment>`), so a\n * tool name and its route agree by construction; resolution maps the segment back by recomputing it\n * over the resource's uploadable fields rather than by inverting the transform.\n */\nexport const MCP_UPLOAD_GRANT_TOOL_MARKER = '.upload_grant.';\n\n/** `<parentOperation>.upload_grant.<fieldSegment>` — the operation part of a derived tool name. */\nexport function buildUploadGrantToolOperationPart(parentOperationIdentifier: string, fieldRouteSegment: string): string {\n return `${parentOperationIdentifier}${MCP_UPLOAD_GRANT_TOOL_MARKER}${fieldRouteSegment}`;\n}\n\n/**\n * Split the operation part of a derived tool name, or `undefined` when it is not one. Strict on shape:\n * exactly one marker, a non-empty snake_case parent and a non-empty kebab-case segment — anything\n * else is an ordinary (possibly custom) operation identifier and is left to the ordinary resolver.\n */\nexport function parseUploadGrantToolOperationPart(operationPart: string): { parentOperationIdentifier: string; fieldRouteSegment: string } | undefined {\n const at = operationPart.indexOf(MCP_UPLOAD_GRANT_TOOL_MARKER);\n if (at <= 0) return undefined;\n const parentOperationIdentifier = operationPart.slice(0, at);\n const fieldRouteSegment = operationPart.slice(at + MCP_UPLOAD_GRANT_TOOL_MARKER.length);\n if (!/^[a-z0-9_]+$/.test(parentOperationIdentifier) || !/^[a-z0-9-]+$/.test(fieldRouteSegment)) return undefined;\n return { parentOperationIdentifier, fieldRouteSegment };\n}\n\nexport function isSpecConformantMcpToolName(name: string): boolean {\n return name.length > 0\n && name.length <= MCP_TOOL_NAME_MAX_LENGTH\n && MCP_TOOL_NAME_ALLOWED_CHARACTERS.test(name);\n}\n\n/**\n * Agent-facing behavioural hints on a tool, mirroring the MCP `ToolAnnotations`\n * shape. Advisory only — they never affect authorization (which is always\n * re-checked at `callTool` time). Derived from the operation's core identifier +\n * `riskLevel`.\n */\nexport interface McpToolAnnotations {\n /** Human-readable title for display surfaces. */\n title?: string;\n /** True when the operation only reads (READ/LIST/SEARCH/COUNT). */\n readOnlyHint?: boolean;\n /** True when the operation may destroy/irreversibly mutate data (DELETE, or HIGH/CRITICAL risk). */\n destructiveHint?: boolean;\n /**\n * True when repeating the call with the same arguments has no ADDITIONAL effect.\n *\n * \"(This property is meaningful only when `readOnlyHint == false`)\" and its default is `false`, so\n * it is emitted ONLY for write operations — advertising it on a read tool would state something\n * the specification defines as meaningless there.\n * spec-cite: MCP schema — `ToolAnnotations.idempotentHint`\n */\n idempotentHint?: boolean;\n /**\n * True when the tool may reach an \"open world\" of external entities.\n *\n * **Its default is `true`**, which is why omitting it was not neutral: every tool this server\n * exposed was advertised as potentially reaching arbitrary external systems. A Wildo tool operates\n * on this application's OWN resources through its own authorization — a closed domain — so the\n * honest value is `false`, and it is the difference between an agent treating a tool call as a\n * local state change and treating it as a call out to the internet.\n * spec-cite: MCP schema — `ToolAnnotations.openWorldHint` (\"If false, the tool's domain of\n * interaction is closed. For example, the world of a web search tool is open, whereas that of a\n * memory tool is not.\")\n */\n openWorldHint?: boolean;\n}\n\n/**\n * A single MCP tool descriptor as returned by `tools/list`. `inputSchema` is a\n * JSON Schema object derived from the operation `requestDto`.\n */\nexport interface McpToolDescriptor {\n /** `<resourceIdentifier>__<operationIdentifier>` — unique per exposed default operation. */\n name: string;\n /** Agent-facing prose. Spec-sourced when a description provider is supplied, else synthesized. */\n description: string;\n /** JSON Schema object for the tool arguments (empty-object schema when the op takes no body). */\n inputSchema: Record<string, unknown>;\n /** Advisory behavioural hints (read-only / destructive). */\n annotations?: McpToolAnnotations;\n /**\n * The resource type this tool maps to — retained so `callTool` can re-resolve\n * the operation without re-parsing the name. NOT part of the wire `tools/list`\n * payload (strip before serialising if a transport is added).\n */\n resourceType: ResourceType;\n /** The operation identifier this tool maps to (core or custom). */\n operationIdentifier: string;\n}\n\n/**\n * Resolves agent-facing description prose for one operation.\n *\n * # Why this is INJECTED rather than read from the spec layer directly\n *\n * The MCP child plan says tool descriptions source from the SPEC layer\n * (`ResourceOperationSpecification.purpose` / `whenToUse`). But\n * `@wildo-ai/saas-specifications` is DOWNSTREAM of `saas-backend-lib` in the\n * engine dependency graph — the backend cannot import specs without inverting the\n * layering. So the adapter accepts this provider and the caller (the app /\n * companion, which DOES have spec access) supplies spec-sourced prose. When no\n * provider is given, the adapter synthesizes a description from the operation's\n * own config metadata.\n *\n * The D4 config-local description field (`operation.mcp.description`, a dedicated\n * agent-facing slot on the operation config) has SHIPPED, and the adapter reads it\n * directly with top priority (see `McpToolAdapterBackendService.resolveDescription`).\n * This provider is therefore optional enrichment, consulted only when no authored\n * `mcp.description` is present.\n *\n * @returns the spec prose for the operation, or `undefined` to fall back to synthesis.\n */\nexport type McpToolDescriptionProvider = (\n resourceType: ResourceType,\n operationIdentifier: string,\n) => { purpose?: string; whenToUse?: string } | undefined;\n\n/**\n * Options for building the exposed tool descriptor set.\n */\nexport interface McpToolAdapterBuildOptions {\n /**\n * Optional narrowing allow-list of resource types to enumerate. When omitted,\n * ALL registered ORGANIZATION/APPLICATION-scoped resources are considered.\n *\n * This is a coarse convenience filter, NOT the exposure gate: exposure is\n * already fail-closed per-operation via `operation.mcp.exposed === true` (D4,\n * shipped), so the default (no allow-list) never over-exposes — only operations\n * that explicitly opt in become tools. Pass this only to further restrict the set.\n */\n resourceTypes?: ResourceType[];\n /** Spec-sourced description provider (see {@link McpToolDescriptionProvider}). */\n descriptionProvider?: McpToolDescriptionProvider;\n /**\n * The resource primary scopes the CALLER can reach (progressive principal-awareness). Omit → the\n * default machine-reachable set (ORG / APP), which is the MACHINE principal's set. An ANONYMOUS\n * caller is NOT a machine and reaches nothing; the transport challenges it rather than listing.\n * A user-delegated caller passes the delegated set (adds `USER_SELF`) so their USER_SELF tools are\n * advertised. Derived from the SAME source `callTool`'s exposability gate uses\n * (`mcpReachableScopesForPrincipal`), so advertise ≡ callable per principal.\n */\n reachableScopes?: ReadonlySet<ResourcePrimaryScope>;\n /**\n * The NAMED MCP server instance being served (R-2), or omitted for the DEFAULT `/mcp` server.\n * Filters the catalog to the operations that belong to that instance (`operation.mcp.servers`),\n * composed with the scope + opt-in filters. Membership is exclusive: an operation with no declared\n * `servers` appears ONLY on the default server, and one with declared servers ONLY on those.\n */\n instanceRef?: string;\n}\n"]}
@@ -1,3 +1,5 @@
1
+ import { FileUploadGrantBackendService } from '../../files/services/file-upload-grant.backend.service';
2
+ import { AppConfigurationService } from '../../config/services/app-configuration.backend.service';
1
3
  import type { LoggerService } from '../../monitoring/logger/services/logger.service';
2
4
  import { ErrorBuilderBackendService } from '../../errors/services/error-builder.backend.service';
3
5
  import { ExecutionContext } from '../../runtime/models/execution-context.backend.schemas';
@@ -42,8 +44,10 @@ export declare class McpServerBackendService {
42
44
  protected readonly authorizer: AuthorizationsBackendService;
43
45
  protected readonly servicesHandler: ServicesRegistryHandlerBackendService;
44
46
  protected readonly resourcesRegistry: ResourcesRegistryBackendService;
47
+ protected readonly fileUploadGrantService: FileUploadGrantBackendService;
48
+ protected readonly appConfigService: AppConfigurationService;
45
49
  protected logDebug: (message: string, context?: Record<string, unknown>) => void;
46
- constructor(logger: LoggerService, errorBuilder: ErrorBuilderBackendService, toolAdapter: McpToolAdapterBackendService, authorizer: AuthorizationsBackendService, servicesHandler: ServicesRegistryHandlerBackendService, resourcesRegistry: ResourcesRegistryBackendService);
50
+ constructor(logger: LoggerService, errorBuilder: ErrorBuilderBackendService, toolAdapter: McpToolAdapterBackendService, authorizer: AuthorizationsBackendService, servicesHandler: ServicesRegistryHandlerBackendService, resourcesRegistry: ResourcesRegistryBackendService, fileUploadGrantService: FileUploadGrantBackendService, appConfigService: AppConfigurationService);
47
51
  /**
48
52
  * Return the exposed tool descriptors for the caller's reachable scopes (`options.reachableScopes`).
49
53
  * PROGRESSIVE principal-awareness (C): a caller presenting a valid Bearer bound to this endpoint
@@ -65,6 +69,30 @@ export declare class McpServerBackendService {
65
69
  * @throws NOT_FOUND when the tool name does not map to an exposable operation.
66
70
  */
67
71
  callTool(toolName: string, args: unknown, executionContext: ExecutionContext<any>, options?: McpCallToolOptions): Promise<McpCallToolResult>;
72
+ /**
73
+ * Execute a derived upload-grant tool (issue #312, D11).
74
+ *
75
+ * Authorization is the PARENT's: the caller's context is bound to the parent operation and
76
+ * `authorizeInController` runs on it with the same `resourceId` the parent would take — so minting
77
+ * requires exactly the authority uploading would, which is the invariant the REST door gets from
78
+ * sharing the upload middleware. The upload URL is built from the parent's own route pattern with
79
+ * the parameters the context and the call supply, and the API origin comes from CONFIGURATION,
80
+ * never from a request header.
81
+ */
82
+ private callUploadGrantTool;
83
+ /** The tool arguments minus the resource's own id, which the transport already split out into `options.resourceId`. */
84
+ private withoutInstanceId;
85
+ /**
86
+ * The concrete upload-route PATH (mount prefix included) for one file field of the parent
87
+ * operation, from the parent's own route patterns — the same builder the resource controller used
88
+ * to REGISTER the route, so the grant's `uploadUrl` is a route that exists.
89
+ *
90
+ * A parent can carry several patterns (one per parent-resource combination); the one chosen is the
91
+ * first whose every `{param}` the call can supply: the resource's own id from `resourceId`, and scope
92
+ * / parent ids from the context's `initiatorIds`. Patterns are tried shortest first, so the
93
+ * primary-scope form wins over a nested FK form the MCP call cannot address anyway.
94
+ */
95
+ private resolveUploadRoutePath;
68
96
  /**
69
97
  * Split `<resourceIdentifier>__<operationIdentifier>` on the FIRST double
70
98
  * underscore. Resource identifiers use single underscores only, so the first
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-server.backend.service.d.ts","sourceRoot":"","sources":["../../../../../src/mcp/services/mcp-server.backend.service.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8DAA8D,CAAC;AAClG,OAAO,EAAE,0BAA0B,EAAE,MAAM,kEAAkE,CAAC;AAC9G,OAAO,EAAE,gBAAgB,EAAE,MAAM,qEAAqE,CAAC;AACvG,OAAO,EAAE,4BAA4B,EAAE,MAAM,oFAAoF,CAAC;AAClI,OAAO,EAAE,qCAAqC,EAAE,MAAM,6FAA6F,CAAC;AAGpJ,OAAO,EAAE,+BAA+B,EAAE,MAAM,0EAA0E,CAAC;AAC3H,OAAO,EACL,4BAA4B,EAK7B,MAAM,kEAAkE,CAAC;AAC1E,OAAO,EACL,iBAAiB,EACjB,0BAA0B,EAE3B,MAAM,wDAAwD,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;CACjB;AA0CD,qBACa,uBAAuB;IAIG,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,aAAa;IAC3B,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,0BAA0B;IAChE,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,4BAA4B;IACrD,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,4BAA4B;IAClD,SAAS,CAAC,QAAQ,CAAC,eAAe,EAAE,qCAAqC;IAChF,SAAS,CAAC,QAAQ,CAAC,iBAAiB,EAAE,+BAA+B;IAR3H,SAAS,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;gBAGzB,MAAM,EAAE,aAAa,EACR,YAAY,EAAE,0BAA0B,EAC7C,WAAW,EAAE,4BAA4B,EAClC,UAAU,EAAE,4BAA4B,EAC/B,eAAe,EAAE,qCAAqC,EAC7D,iBAAiB,EAAE,+BAA+B;IAM3H;;;;;;;OAOG;IACI,SAAS,CAAC,OAAO,GAAE,0BAA+B,GAAG,iBAAiB,EAAE;IAI/E;;;;;;;;;;OAUG;IACU,QAAQ,CACnB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,EACb,gBAAgB,EAAE,gBAAgB,CAAC,GAAG,CAAC,EACvC,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC;IA8F7B;;;;OAIG;IACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,OAAO,CAAC,mBAAmB;IAgD3B;;;;;;OAMG;IACH,OAAO,CAAC,yBAAyB;IAIjC,OAAO,CAAC,aAAa;CAatB"}
1
+ {"version":3,"file":"mcp-server.backend.service.d.ts","sourceRoot":"","sources":["../../../../../src/mcp/services/mcp-server.backend.service.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,6BAA6B,EAAE,MAAM,qEAAqE,CAAC;AACpH,OAAO,EAAE,uBAAuB,EAAE,MAAM,sEAAsE,CAAC;AAE/G,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8DAA8D,CAAC;AAClG,OAAO,EAAE,0BAA0B,EAAE,MAAM,kEAAkE,CAAC;AAC9G,OAAO,EAAE,gBAAgB,EAAE,MAAM,qEAAqE,CAAC;AACvG,OAAO,EAAE,4BAA4B,EAAE,MAAM,oFAAoF,CAAC;AAClI,OAAO,EAAE,qCAAqC,EAAE,MAAM,6FAA6F,CAAC;AAGpJ,OAAO,EAAE,+BAA+B,EAAE,MAAM,0EAA0E,CAAC;AAC3H,OAAO,EACL,4BAA4B,EAK7B,MAAM,kEAAkE,CAAC;AAC1E,OAAO,EACL,iBAAiB,EACjB,0BAA0B,EAE3B,MAAM,wDAAwD,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;CACjB;AA0CD,qBACa,uBAAuB;IAIG,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,aAAa;IAC3B,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,0BAA0B;IAChE,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,4BAA4B;IACrD,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,4BAA4B;IAClD,SAAS,CAAC,QAAQ,CAAC,eAAe,EAAE,qCAAqC;IAChF,SAAS,CAAC,QAAQ,CAAC,iBAAiB,EAAE,+BAA+B;IACtE,SAAS,CAAC,QAAQ,CAAC,sBAAsB,EAAE,6BAA6B;IAC9E,SAAS,CAAC,QAAQ,CAAC,gBAAgB,EAAE,uBAAuB;IAV3G,SAAS,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;gBAGzB,MAAM,EAAE,aAAa,EACR,YAAY,EAAE,0BAA0B,EAC7C,WAAW,EAAE,4BAA4B,EAClC,UAAU,EAAE,4BAA4B,EAC/B,eAAe,EAAE,qCAAqC,EAC7D,iBAAiB,EAAE,+BAA+B,EACnD,sBAAsB,EAAE,6BAA6B,EAC3D,gBAAgB,EAAE,uBAAuB;IAM3G;;;;;;;OAOG;IACI,SAAS,CAAC,OAAO,GAAE,0BAA+B,GAAG,iBAAiB,EAAE;IAI/E;;;;;;;;;;OAUG;IACU,QAAQ,CACnB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,EACb,gBAAgB,EAAE,gBAAgB,CAAC,GAAG,CAAC,EACvC,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC;IAsG7B;;;;;;;;;OASG;YACW,mBAAmB;IA0DjC,uHAAuH;IACvH,OAAO,CAAC,iBAAiB;IAQzB;;;;;;;;;OASG;IACH,OAAO,CAAC,sBAAsB;IAuC9B;;;;OAIG;IACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,OAAO,CAAC,mBAAmB;IAgD3B;;;;;;OAMG;IACH,OAAO,CAAC,yBAAyB;IAIjC,OAAO,CAAC,aAAa;CAatB"}
@@ -11,9 +11,12 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
11
  return function (target, key) { decorator(target, key, paramIndex); }
12
12
  };
13
13
  import { injectable, inject } from 'inversify';
14
- import { ResourcesConfig_OperationPathUtils, CoreResourceOperation, ErrorType, ErrorCustomMessageReference, } from '@wildo-ai/saas-models';
14
+ import { ResourcesConfig_OperationPathUtils, CoreResourceOperation, ErrorType, ErrorCustomMessageReference, BackendApiType, MAIN_API_BASE_PATH, FileUploadGrantMintRequestSchema, ResourcesConfigOperationsUrlsBuilderUtils, } from '@wildo-ai/saas-models';
15
15
  import { z } from 'zod';
16
16
  import { SAAS_SERVICE_TYPES } from '../../inject/models/service-types.js';
17
+ import { FileUploadGrantBackendService } from '../../files/services/file-upload-grant.backend.service.js';
18
+ import { AppConfigurationService } from '../../config/services/app-configuration.backend.service.js';
19
+ import { MCP_UPLOAD_GRANT_TOOL_MARKER } from '../../mcp/models/mcp-tool.backend.schemas.js';
17
20
  import { ErrorBuilderBackendService } from '../../errors/services/error-builder.backend.service.js';
18
21
  import { AuthorizationsBackendService } from '../../security/authorizations/services/authorizations.backend.service.js';
19
22
  import { ServicesRegistryHandlerBackendService } from '../../base-classes/services/services/services-registry-handler.backend.service.js';
@@ -68,14 +71,18 @@ let McpServerBackendService = class McpServerBackendService {
68
71
  authorizer;
69
72
  servicesHandler;
70
73
  resourcesRegistry;
74
+ fileUploadGrantService;
75
+ appConfigService;
71
76
  logDebug;
72
- constructor(logger, errorBuilder, toolAdapter, authorizer, servicesHandler, resourcesRegistry) {
77
+ constructor(logger, errorBuilder, toolAdapter, authorizer, servicesHandler, resourcesRegistry, fileUploadGrantService, appConfigService) {
73
78
  this.logger = logger;
74
79
  this.errorBuilder = errorBuilder;
75
80
  this.toolAdapter = toolAdapter;
76
81
  this.authorizer = authorizer;
77
82
  this.servicesHandler = servicesHandler;
78
83
  this.resourcesRegistry = resourcesRegistry;
84
+ this.fileUploadGrantService = fileUploadGrantService;
85
+ this.appConfigService = appConfigService;
79
86
  this.logDebug = this.logger.debugByType('McpServerBackendService');
80
87
  this.logger.setDebugTypeLogging('McpServerBackendService', false);
81
88
  }
@@ -113,6 +120,12 @@ let McpServerBackendService = class McpServerBackendService {
113
120
  // decide callability — the same pair `tools/list` filters on.
114
121
  // Post-authentication by construction: `callTool` is only reached with a validated token.
115
122
  const reachableScopes = mcpReachableScopesForPrincipal(mcpPrincipalForAuthenticatedCaller(executionContext.isUserExecution()));
123
+ // Issue #312, D11: a DERIVED upload-grant tool mints a grant instead of dispatching an operation.
124
+ // It resolves its PARENT through the same adapter gate (exposure, instance, scope inherited), is
125
+ // authorized exactly as the parent would be, then calls the ONE mint function the REST door calls.
126
+ if (operationIdentifier.includes(MCP_UPLOAD_GRANT_TOOL_MARKER)) {
127
+ return this.callUploadGrantTool(toolName, resourceType, operationIdentifier, args, executionContext, options, reachableScopes);
128
+ }
116
129
  const operation = this.toolAdapter.resolveExposableOperation(resourceType, operationIdentifier, reachableScopes, options.instanceRef);
117
130
  if (!operation) {
118
131
  throw this.errorBuilder.buildError(ErrorType.NOT_FOUND, executionContext, {
@@ -177,6 +190,117 @@ let McpServerBackendService = class McpServerBackendService {
177
190
  const safeResult = await toResponseSafeToolOutput(operation, result);
178
191
  return { toolName, result: safeResult };
179
192
  }
193
+ /**
194
+ * Execute a derived upload-grant tool (issue #312, D11).
195
+ *
196
+ * Authorization is the PARENT's: the caller's context is bound to the parent operation and
197
+ * `authorizeInController` runs on it with the same `resourceId` the parent would take — so minting
198
+ * requires exactly the authority uploading would, which is the invariant the REST door gets from
199
+ * sharing the upload middleware. The upload URL is built from the parent's own route pattern with
200
+ * the parameters the context and the call supply, and the API origin comes from CONFIGURATION,
201
+ * never from a request header.
202
+ */
203
+ async callUploadGrantTool(toolName, resourceType, operationPart, args, executionContext, options, reachableScopes) {
204
+ const derived = this.toolAdapter.resolveUploadGrantTool(resourceType, operationPart, reachableScopes, options.instanceRef);
205
+ if (!derived) {
206
+ throw this.errorBuilder.buildError(ErrorType.NOT_FOUND, executionContext, {
207
+ customMessageReference: ErrorCustomMessageReference.SERVICE_NOT_FOUND,
208
+ context: { message: `MCP tool "${toolName}" does not map to an exposable upload-grant tool`, toolName },
209
+ });
210
+ }
211
+ const { parentOperation, fieldName, operationKind } = derived;
212
+ const boundExecutionContext = executionContext.withOperation(parentOperation);
213
+ const resourceId = options.resourceId;
214
+ if (operationKind === CoreResourceOperation.UPDATE && !resourceId) {
215
+ throw this.errorBuilder.buildError(ErrorType.VALIDATION, boundExecutionContext, {
216
+ customMessageReference: ErrorCustomMessageReference.VALIDATION,
217
+ context: { message: 'An upload grant for an UPDATE requires the record id', toolName },
218
+ });
219
+ }
220
+ // Same gate, same order, same arguments as the parent operation.
221
+ await this.authorizer.authorizeInController(resourceId, boundExecutionContext);
222
+ const parsedArgs = FileUploadGrantMintRequestSchema.safeParse(this.withoutInstanceId(args, resourceType));
223
+ if (!parsedArgs.success) {
224
+ throw this.errorBuilder.buildError(ErrorType.VALIDATION, boundExecutionContext, {
225
+ customMessageReference: ErrorCustomMessageReference.VALIDATION,
226
+ context: { toolName, errors: parsedArgs.error.issues.map((issue) => ({ path: issue.path.join('.'), message: issue.message })) },
227
+ });
228
+ }
229
+ const apiPublicUrl = this.appConfigService.config.runtime?.endPoints?.[BackendApiType.MAIN_BACKEND_API]?.publicUrl;
230
+ if (!apiPublicUrl) {
231
+ throw this.errorBuilder.buildError(ErrorType.CONFIGURATION, boundExecutionContext, {
232
+ customMessageReference: ErrorCustomMessageReference.CONFIGURATION,
233
+ context: { message: `runtime.endPoints.${BackendApiType.MAIN_BACKEND_API}.publicUrl is required to mint a file upload grant` },
234
+ });
235
+ }
236
+ const origin = apiPublicUrl.replace(/\/+$/, '');
237
+ // Scope ids come from the CALLER's context: binding an operation onto a context never changes who is calling.
238
+ const uploadPath = this.resolveUploadRoutePath(parentOperation, resourceType, fieldName, operationKind, resourceId, executionContext);
239
+ const grant = await this.fileUploadGrantService.mint({
240
+ executionContext: boundExecutionContext,
241
+ target: { resourceType, fieldName, operation: operationKind, resourceId },
242
+ expiresInMinutes: parsedArgs.data.expiresInMinutes,
243
+ uploadUrl: `${origin}${uploadPath}`,
244
+ apiBaseUrl: `${origin}${MAIN_API_BASE_PATH}`,
245
+ });
246
+ this.logDebug('Executing MCP upload-grant tool call', { toolName, resourceType, fieldName, operationKind, hasResourceId: resourceId !== undefined });
247
+ return { toolName, result: grant };
248
+ }
249
+ /** The tool arguments minus the resource's own id, which the transport already split out into `options.resourceId`. */
250
+ withoutInstanceId(args, resourceType) {
251
+ if (!args || typeof args !== 'object')
252
+ return args ?? {};
253
+ const selfField = this.resourcesRegistry.getAllResourceFieldIdentifiers()[resourceType];
254
+ if (!selfField || !(selfField in args))
255
+ return args;
256
+ const { [selfField]: _dropped, ...rest } = args;
257
+ return rest;
258
+ }
259
+ /**
260
+ * The concrete upload-route PATH (mount prefix included) for one file field of the parent
261
+ * operation, from the parent's own route patterns — the same builder the resource controller used
262
+ * to REGISTER the route, so the grant's `uploadUrl` is a route that exists.
263
+ *
264
+ * A parent can carry several patterns (one per parent-resource combination); the one chosen is the
265
+ * first whose every `{param}` the call can supply: the resource's own id from `resourceId`, and scope
266
+ * / parent ids from the context's `initiatorIds`. Patterns are tried shortest first, so the
267
+ * primary-scope form wins over a nested FK form the MCP call cannot address anyway.
268
+ */
269
+ resolveUploadRoutePath(parentOperation, resourceType, fieldName, operationKind, resourceId, executionContext) {
270
+ const selfField = this.resourcesRegistry.getAllResourceFieldIdentifiers()[resourceType];
271
+ const initiatorInfos = [...(('initiatorInfos' in parentOperation ? parentOperation.initiatorInfos : undefined) ?? [])]
272
+ .sort((a, b) => (a.parameters?.length ?? 0) - (b.parameters?.length ?? 0));
273
+ const supplied = {
274
+ ...(executionContext.initiatorIds ?? {}),
275
+ ...(selfField && resourceId ? { [selfField]: resourceId } : {}),
276
+ };
277
+ for (const info of initiatorInfos) {
278
+ const route = ResourcesConfigOperationsUrlsBuilderUtils.buildFileUploadRouteForOperation({
279
+ operationRouteUrl: info.url,
280
+ operationParameters: info.parameters ?? [],
281
+ targetResourceType: resourceType,
282
+ targetResourceFieldIdentifier: selfField ?? 'id',
283
+ fieldName,
284
+ requiresTargetResourceId: operationKind === CoreResourceOperation.UPDATE,
285
+ isFrontend: false,
286
+ });
287
+ let unresolved = false;
288
+ const path = route.url.replace(/\{([^}]+)\}/g, (_m, param) => {
289
+ const value = supplied[param];
290
+ if (typeof value !== 'string' || value.length === 0) {
291
+ unresolved = true;
292
+ return `{${param}}`;
293
+ }
294
+ return encodeURIComponent(value);
295
+ });
296
+ if (!unresolved)
297
+ return path;
298
+ }
299
+ throw this.errorBuilder.buildError(ErrorType.CONFIGURATION, executionContext, {
300
+ customMessageReference: ErrorCustomMessageReference.CONFIGURATION,
301
+ context: { message: 'No upload route of this operation can be addressed from the MCP call', resourceType, fieldName, operationKind, patterns: initiatorInfos.map((i) => i.url) },
302
+ });
303
+ }
180
304
  /**
181
305
  * Split `<resourceIdentifier>__<operationIdentifier>` on the FIRST double
182
306
  * underscore. Resource identifiers use single underscores only, so the first
@@ -276,11 +400,15 @@ McpServerBackendService = __decorate([
276
400
  __param(3, inject(SAAS_SERVICE_TYPES.AuthorizationsService)),
277
401
  __param(4, inject(SAAS_SERVICE_TYPES.ServicesRegistryHandlerService)),
278
402
  __param(5, inject(SAAS_SERVICE_TYPES.ResourceRegistryService)),
403
+ __param(6, inject(SAAS_SERVICE_TYPES.FileUploadGrantService)),
404
+ __param(7, inject(SAAS_SERVICE_TYPES.AppConfigService)),
279
405
  __metadata("design:paramtypes", [Function, ErrorBuilderBackendService,
280
406
  McpToolAdapterBackendService,
281
407
  AuthorizationsBackendService,
282
408
  ServicesRegistryHandlerBackendService,
283
- ResourcesRegistryBackendService])
409
+ ResourcesRegistryBackendService,
410
+ FileUploadGrantBackendService,
411
+ AppConfigurationService])
284
412
  ], McpServerBackendService);
285
413
  export { McpServerBackendService };
286
414
  //# sourceMappingURL=mcp-server.backend.service.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-server.backend.service.js","sourceRoot":"","sources":["../../../../../src/mcp/services/mcp-server.backend.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { injectable, inject } from 'inversify';\nimport {\n ResourceType,\n ResourceConfiguration_OperationPath,\n ResourcesConfig_OperationPathUtils,\n CoreResourceOperation,\n ErrorType,\n ErrorCustomMessageReference,\n} from '@wildo-ai/saas-models';\nimport { z } from 'zod';\nimport { SAAS_SERVICE_TYPES } from '~wildo-backend-lib/inject/models/service-types';\nimport type { LoggerService } from '~wildo-backend-lib/monitoring/logger/services/logger.service';\nimport { ErrorBuilderBackendService } from '~wildo-backend-lib/errors/services/error-builder.backend.service';\nimport { ExecutionContext } from '~wildo-backend-lib/runtime/models/execution-context.backend.schemas';\nimport { AuthorizationsBackendService } from '~wildo-backend-lib/security/authorizations/services/authorizations.backend.service';\nimport { ServicesRegistryHandlerBackendService } from '~wildo-backend-lib/base-classes/services/services/services-registry-handler.backend.service';\nimport { toResponseSafeToolOutput } from '~wildo-backend-lib/base-classes/services/controllers/utils/operation-response-serializer.backend.utils';\nimport { normalizeCollectionQueryRequestData } from '~wildo-backend-lib/base-classes/services/controllers/controller-resource.backend.service';\nimport { ResourcesRegistryBackendService } from '~wildo-backend-lib/resources/services/resources-registry.backend.service';\nimport {\n McpToolAdapterBackendService,\n mcpReachableScopesForPrincipal,\n mcpPrincipalForAuthenticatedCaller,\n resolveEffectiveMcpOperation,\n isCollectionQueryMcpOperation,\n} from '~wildo-backend-lib/mcp/services/mcp-tool-adapter.backend.service';\nimport {\n McpToolDescriptor,\n McpToolAdapterBuildOptions,\n MCP_TOOL_NAME_SEPARATOR,\n} from '~wildo-backend-lib/mcp/models/mcp-tool.backend.schemas';\n\n/**\n * Per-call options for {@link McpServerBackendService.callTool}.\n */\nexport interface McpCallToolOptions {\n /**\n * Target resource id for resource-specific operations (READ / UPDATE / DELETE\n * and instance-scoped custom operations). Omit for collection operations\n * (CREATE / LIST / SEARCH / COUNT), which authorize on roles + parent scope only.\n */\n resourceId?: string;\n /**\n * The NAMED MCP server instance this call arrived on (R-2), or omitted for the default `/mcp`\n * server. MUST mirror the transport's route: the exposability gate below re-resolves the tool\n * against this instance, so a tool curated onto another server is NOT callable here even by name.\n */\n instanceRef?: string;\n}\n\n/**\n * Result of a successful `tools/call`. `result` is the operation output projected\n * through the operation's response DTO (backend-only/secret fields stripped, dates\n * JSON-normalized — see `callTool`), returned WITHOUT MCP `content` framing: the\n * `{ content: [...], isError }` envelope is applied by the transport\n * ({@link MCPControllerBackendService.handleToolsCall}). Keeping this service\n * transport-agnostic lets the same `callTool` back another transport unchanged.\n */\nexport interface McpCallToolResult {\n toolName: string;\n result: unknown;\n}\n\n/**\n * The MCP server core: lists resource-operation tools and executes them against\n * the SERVICE layer with per-call authorization. Transport-agnostic — the\n * JSON-RPC / Streamable-HTTP wire layer + OAuth 2.1 resource-server auth\n * (RFC 9728 metadata, RFC 8707 audience validation) live in\n * {@link MCPControllerBackendService}; this core carries no wire concern so the\n * same `listTools`/`callTool` can back another transport unchanged.\n *\n * # Boundaries\n *\n * - `listTools()` delegates to {@link McpToolAdapterBackendService}.\n * - `callTool()` resolves the target operation, RE-RUNS `authorizeInController`\n * against the caller's execution context (fail-closed), then invokes the\n * service pipeline DIRECTLY via {@link ServicesRegistryHandlerBackendService}\n * (never re-HTTP). The service pipeline enforces `requiredFeatures` /\n * `enabledCondition` downstream.\n * - KNOWN GAP (tracked — `.claude/plans/mcp-server-followups.md` Item 5): the\n * per-route HTTP rate-limit middleware is bypassed on this direct dispatch path,\n * so an operation's `rateLimit` policy is not yet enforced here. The planned fix\n * is a per-principal quota shared with the A2A dispatch surface.\n * - Server-initiated streaming (SSE / notifications / progress) is intentionally\n * absent: every exposed operation is synchronous request/response, for which a\n * single JSON response is Streamable-HTTP-compliant (Item 1, deferred).\n */\n/**\n * Operations whose RESULT is the paginated `{ data, pagination }` envelope, for OUTPUT.\n *\n * Deliberately NOT the same set as {@link isCollectionQueryOperation}: `COUNT` takes the collection\n * argument envelope but RETURNS A SCALAR, so asking the repository to wrap it would hand back a\n * shape its response DTO does not declare — trading one contract mismatch for another.\n *\n * Mirrors `needsPagination` in `ControllerResourceBackendService`, which is where this decision has\n * always been made for the HTTP surface. The two must agree: they select the shape the SAME response\n * DTO is asked to validate.\n */\nfunction isPaginationWrappedOperation(effectiveOperation: string): boolean {\n return effectiveOperation === CoreResourceOperation.LIST\n || effectiveOperation === CoreResourceOperation.SEARCH;\n}\n\n@injectable()\nexport class McpServerBackendService {\n protected logDebug: (message: string, context?: Record<string, unknown>) => void;\n\n constructor(\n @inject(SAAS_SERVICE_TYPES.Logger) protected readonly logger: LoggerService,\n @inject(SAAS_SERVICE_TYPES.ErrorBuilderService) protected readonly errorBuilder: ErrorBuilderBackendService,\n @inject(SAAS_SERVICE_TYPES.McpToolAdapter) protected readonly toolAdapter: McpToolAdapterBackendService,\n @inject(SAAS_SERVICE_TYPES.AuthorizationsService) protected readonly authorizer: AuthorizationsBackendService,\n @inject(SAAS_SERVICE_TYPES.ServicesRegistryHandlerService) protected readonly servicesHandler: ServicesRegistryHandlerBackendService,\n @inject(SAAS_SERVICE_TYPES.ResourceRegistryService) protected readonly resourcesRegistry: ResourcesRegistryBackendService,\n ) {\n this.logDebug = this.logger.debugByType('McpServerBackendService');\n this.logger.setDebugTypeLogging('McpServerBackendService', false);\n }\n\n /**\n * Return the exposed tool descriptors for the caller's reachable scopes (`options.reachableScopes`).\n * PROGRESSIVE principal-awareness (C): a caller presenting a valid Bearer bound to this endpoint\n * passes its principal's scopes, so a user-delegated caller additionally sees its USER_SELF tools.\n * Omitting the scopes yields the MACHINE catalog; an ANONYMOUS caller passes an EMPTY set and the\n * transport challenges it rather than serving the empty page. Authorization is still enforced\n * fail-closed at `callTool` time — the catalog is a discovery aid, never an authorization grant.\n */\n public listTools(options: McpToolAdapterBuildOptions = {}): McpToolDescriptor[] {\n return this.toolAdapter.buildToolDescriptors(options);\n }\n\n /**\n * Execute one tool: resolve → authorize (fail-closed) → dispatch to the service layer.\n *\n * @param toolName - `<resourceIdentifier>__<operationIdentifier>`.\n * @param args - the tool arguments (the operation request body).\n * @param executionContext - the caller's context (a machine principal for api-key\n * / client-credentials callers; a user-delegated context once OIDC lands).\n * @param options - per-call options ({@link McpCallToolOptions}).\n * @throws AUTHORIZATION when the principal is not permitted to run the operation.\n * @throws NOT_FOUND when the tool name does not map to an exposable operation.\n */\n public async callTool(\n toolName: string,\n args: unknown,\n executionContext: ExecutionContext<any>,\n options: McpCallToolOptions = {},\n ): Promise<McpCallToolResult> {\n const { resourceType, operationIdentifier } = this.parseToolName(toolName);\n\n // Single source of exposability truth: the adapter decides whether this resource+operation is a\n // valid, default URL-bearing tool REACHABLE BY THIS CALLER. Progressive principal-awareness — a\n // user-delegated principal (USER_REQUEST, carries a `userId`) reaches USER_SELF operations; a\n // machine principal reaches ORG/APP only. Rejecting here (before authorization) prevents a crafted\n // name from reaching a scope the caller cannot use (e.g. a machine hitting USER_SELF → always 403).\n // Derived from the SAME source `tools/list` filters on, so advertise ≡ callable per principal.\n // R-2: also gated on the server INSTANCE the call arrived on, so `(principal, instance)` together\n // decide callability — the same pair `tools/list` filters on.\n // Post-authentication by construction: `callTool` is only reached with a validated token.\n const reachableScopes = mcpReachableScopesForPrincipal(mcpPrincipalForAuthenticatedCaller(executionContext.isUserExecution()));\n const operation = this.toolAdapter.resolveExposableOperation(resourceType, operationIdentifier, reachableScopes, options.instanceRef);\n if (!operation) {\n throw this.errorBuilder.buildError(ErrorType.NOT_FOUND, executionContext, {\n customMessageReference: ErrorCustomMessageReference.SERVICE_NOT_FOUND,\n context: { message: `MCP tool \"${toolName}\" does not map to an exposable resource operation`, toolName },\n });\n }\n\n // Bind the resolved operation onto the caller's context (preserves principal\n // identity + roles) so the authorizer and the service pipeline both see the\n // operation actually being executed.\n const boundExecutionContext = executionContext.withOperation(operation);\n\n const idOrIds = options.resourceId;\n\n // Fail-closed per-operation authorization: reads `operation.roles` +\n // `executionContext.initiatorRoles` / scope. Throws AUTHORIZATION on denial.\n await this.authorizer.authorizeInController(idOrIds, boundExecutionContext);\n\n // Dispatch to the service layer directly (NOT re-HTTP). The service pipeline\n // enforces enabledCondition / requiredFeatures downstream.\n //\n // Dispatch the EXACT operation we resolved + authorized + advertised (the URL-bearing default\n // from `resolveExposableOperation`), via `extractOperationPath`. We deliberately do NOT\n // re-resolve through `getServiceOperationPathDefault`: for a resource that declares BOTH a\n // URL-bearing default AND an INTERNAL_CALL default for the same operation identifier, that\n // helper PREFERS the INTERNAL_CALL variant — which would execute a different variant (different\n // `requestDto` validating `args`, different side effects / custom-impl) than the one\n // `authorizeInController` just checked and than the `inputSchema` advertised to the agent.\n // Using the resolved operation's own path mirrors how the HTTP controllers dispatch the URL\n // operation and keeps authorize ≡ advertise ≡ execute on one variant.\n const operationPath: ResourceConfiguration_OperationPath<any, any> = ResourcesConfig_OperationPathUtils.extractOperationPath(operation);\n\n const dispatchArgs = this.toDispatchArguments(operation, args, boundExecutionContext);\n\n this.logDebug('Executing MCP tool call', { toolName, resourceType, operationIdentifier, hasResourceId: idOrIds !== undefined });\n\n /**\n * Request the pagination WRAPPER for collection operations, exactly as the HTTP controller does.\n *\n * The repository's `list` returns a BARE ARRAY unless the caller asks for the envelope\n * (`options.paginated`), and the decision to ask is made in `ControllerResourceBackendService`:\n * `needsPagination = LIST || SEARCH` → `{ paginated: true }`. That is a CONTROLLER-tier\n * decision, and this path does not go through the controller — so `tools/call` dispatched a\n * paginated LIST without ever requesting pagination, the repository returned a bare array, and\n * the operation's RESPONSE DTO (which for a paginated list declares `{ data, pagination }`)\n * rejected it. Every paginated `*__list` tool answered `-32603` with\n * `expected array, received undefined` at `data`.\n *\n * Two things hid it. `*__search` was unaffected — the search path builds its own envelope — and\n * the one LIST that worked (`todosRelationships__list`) is UNPAGINATED, so a bare array is\n * exactly what its response DTO declares. So the failure looked resource-specific when it was\n * operation-wide.\n *\n * This is the OUTPUT half of the defect family the input normalisation already fixed on\n * 2026-08-01: \"the advertised contract is not the enforced contract\". `tools/list` advertises a\n * paginated collection envelope because the response DTO says so; the dispatch has to request\n * the shape that DTO describes, or the two tiers disagree about what the operation returns.\n */\n const needsPaginationWrapper = isPaginationWrappedOperation(this.resolveEffectiveOperation(operation));\n\n const result = await this.servicesHandler.doByOperationPath(\n operationPath,\n boundExecutionContext,\n idOrIds,\n dispatchArgs,\n needsPaginationWrapper ? { paginated: true } : undefined,\n );\n\n // Project the raw service result through the operation's RESPONSE DTO — the SAME\n // output-serialization boundary the HTTP controller (`sendResponse`) and the A2A tool\n // path use — BEFORE it crosses to the external MCP agent. `doByOperationPath` returns the\n // internal DTO, which still carries `.isBackendOnly()` / write-only-secret fields and raw\n // `Date`/`ObjectId` instances; returning it verbatim would leak backend-only/secret material\n // into the agent's model context. `toResponseSafeToolOutput` strips + JSON-normalizes so the\n // MCP tool result is byte-identical to what a REST client would receive.\n const safeResult = await toResponseSafeToolOutput(operation, result);\n\n return { toolName, result: safeResult };\n }\n\n /**\n * Split `<resourceIdentifier>__<operationIdentifier>` on the FIRST double\n * underscore. Resource identifiers use single underscores only, so the first\n * `__` unambiguously delimits the resource from the operation.\n */\n /**\n * Bring the agent's raw `arguments` onto the SAME input contract the HTTP controller produces\n * before dispatch. Added 2026-08-01; until then `tools/call` passed `args` through untouched.\n *\n * Two distinct defects that fixed, both of the \"advertised contract is not the enforced\n * contract\" family:\n *\n * 1. **Collection tools silently ignored the agent's filter.** `tools/list` advertises an\n * `inputSchema` derived from the operation's `requestDto`, which for LIST / SEARCH is keyed\n * `{ filters, pagination, sorting }`. The service reads `{ filter, page, limit, sort,\n * searchQuery }`. The HTTP controller bridges the two; MCP did not, so an agent that\n * followed the schema it was given received the UNFILTERED collection while believing it was\n * filtered — the worst shape for an LLM caller, which then reasons over the wrong rows.\n * {@link normalizeCollectionQueryRequestData} is now the single authority both surfaces use,\n * and it accepts either vocabulary. It also carries the filter ALLOW-LIST, which matters\n * here: the repository's `sanitizeRequestFilter` returns the filter UNCHANGED when an\n * operation declares no `filterFields`, so for a surface with no strict DTO parse in front\n * of it this is the layer that stops an arbitrary key reaching the database.\n * 2. **The published request contract was not enforced.** Every other operation now gets the\n * controller's strict `requestDto.parse`, so a key the tool never advertised is dropped at\n * the boundary that advertised it, rather than three layers away. Writes were already safe —\n * the repository re-parses against the same DTO\n * (`repositories-registry-handler.backend.service.ts`) — but relying on a distant guard for\n * an external protocol surface is defence by coincidence: nothing tells a future refactor of\n * that method that MCP depends on it.\n *\n * Parse failures are deliberately NOT swallowed. An agent that sends a malformed argument should\n * be told, so the model can correct itself; silently dropping it teaches the model nothing and\n * leaves it reasoning about an operation it did not actually perform.\n */\n private toDispatchArguments(\n operation: {\n requestDto?: z.ZodTypeAny;\n resourceOperationLike?: unknown;\n operationIdentifier: unknown;\n resourceIdentifier: unknown;\n },\n args: unknown,\n executionContext: ExecutionContext<any>,\n ): unknown {\n const effectiveOperation = this.resolveEffectiveOperation(operation);\n const isCollectionOperation = isCollectionQueryMcpOperation(effectiveOperation);\n\n if (isCollectionOperation) {\n const rawArguments = (args && typeof args === 'object' && !Array.isArray(args))\n ? args as Record<string, unknown>\n : {};\n const resourceOperations = this.resourcesRegistry\n .getResourceConfiguration(operation.resourceIdentifier as ResourceType)\n .operations;\n return normalizeCollectionQueryRequestData(\n rawArguments,\n operation as never,\n effectiveOperation,\n resourceOperations as never,\n );\n }\n\n if (!operation.requestDto) return args;\n\n try {\n return operation.requestDto.parse(args);\n } catch (error) {\n if (error instanceof z.ZodError) {\n throw this.errorBuilder.buildError(ErrorType.VALIDATION, executionContext, {\n customMessageReference: ErrorCustomMessageReference.CONTROLLER_RESOURCE_VALIDATION_FAILED,\n stack: error.stack,\n context: {\n message: 'MCP tool arguments do not satisfy the operation request contract advertised by tools/list',\n operationIdentifier: operation.operationIdentifier,\n zodIssues: error.issues,\n },\n });\n }\n throw error;\n }\n }\n\n /**\n * The operation this tool really performs — a custom operation declares what it behaves LIKE via\n * `resourceOperationLike`, and both the input normalisation and the output envelope must key on\n * the same answer. Derived once here because they previously derived it separately, and a\n * custom LIST-like operation that only one of them recognised would be normalised as a collection\n * on the way in and dispatched without pagination on the way out.\n */\n private resolveEffectiveOperation(operation: { resourceOperationLike?: unknown; operationIdentifier: unknown }): string {\n return resolveEffectiveMcpOperation(operation);\n }\n\n private parseToolName(toolName: string): { resourceType: ResourceType; operationIdentifier: string } {\n const separatorIndex = toolName.indexOf(MCP_TOOL_NAME_SEPARATOR);\n if (separatorIndex <= 0 || separatorIndex + MCP_TOOL_NAME_SEPARATOR.length >= toolName.length) {\n throw this.errorBuilder.buildError(ErrorType.VALIDATION, undefined, {\n customMessageReference: ErrorCustomMessageReference.SERVICE_NOT_FOUND,\n context: { message: `Malformed MCP tool name \"${toolName}\" — expected \"<resource>${MCP_TOOL_NAME_SEPARATOR}<operation>\"`, toolName },\n });\n }\n return {\n resourceType: toolName.slice(0, separatorIndex) as ResourceType,\n operationIdentifier: toolName.slice(separatorIndex + MCP_TOOL_NAME_SEPARATOR.length),\n };\n }\n}\n"]}
1
+ {"version":3,"file":"mcp-server.backend.service.js","sourceRoot":"","sources":["../../../../../src/mcp/services/mcp-server.backend.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { injectable, inject } from 'inversify';\nimport {\n ResourceType,\n ResourceConfiguration_OperationPath,\n ResourcesConfig_OperationPathUtils,\n CoreResourceOperation,\n ErrorType,\n ErrorCustomMessageReference,\n BackendApiType,\n MAIN_API_BASE_PATH,\n FileUploadGrantMintRequestSchema,\n ResourcesConfigOperationsUrlsBuilderUtils,\n type ResourcePrimaryScope,\n type ResourceConfiguration_Operation,\n type FileUploadGrantOperation,\n} from '@wildo-ai/saas-models';\nimport { z } from 'zod';\nimport { SAAS_SERVICE_TYPES } from '~wildo-backend-lib/inject/models/service-types';\nimport { FileUploadGrantBackendService } from '~wildo-backend-lib/files/services/file-upload-grant.backend.service';\nimport { AppConfigurationService } from '~wildo-backend-lib/config/services/app-configuration.backend.service';\nimport { MCP_UPLOAD_GRANT_TOOL_MARKER } from '~wildo-backend-lib/mcp/models/mcp-tool.backend.schemas';\nimport type { LoggerService } from '~wildo-backend-lib/monitoring/logger/services/logger.service';\nimport { ErrorBuilderBackendService } from '~wildo-backend-lib/errors/services/error-builder.backend.service';\nimport { ExecutionContext } from '~wildo-backend-lib/runtime/models/execution-context.backend.schemas';\nimport { AuthorizationsBackendService } from '~wildo-backend-lib/security/authorizations/services/authorizations.backend.service';\nimport { ServicesRegistryHandlerBackendService } from '~wildo-backend-lib/base-classes/services/services/services-registry-handler.backend.service';\nimport { toResponseSafeToolOutput } from '~wildo-backend-lib/base-classes/services/controllers/utils/operation-response-serializer.backend.utils';\nimport { normalizeCollectionQueryRequestData } from '~wildo-backend-lib/base-classes/services/controllers/controller-resource.backend.service';\nimport { ResourcesRegistryBackendService } from '~wildo-backend-lib/resources/services/resources-registry.backend.service';\nimport {\n McpToolAdapterBackendService,\n mcpReachableScopesForPrincipal,\n mcpPrincipalForAuthenticatedCaller,\n resolveEffectiveMcpOperation,\n isCollectionQueryMcpOperation,\n} from '~wildo-backend-lib/mcp/services/mcp-tool-adapter.backend.service';\nimport {\n McpToolDescriptor,\n McpToolAdapterBuildOptions,\n MCP_TOOL_NAME_SEPARATOR,\n} from '~wildo-backend-lib/mcp/models/mcp-tool.backend.schemas';\n\n/**\n * Per-call options for {@link McpServerBackendService.callTool}.\n */\nexport interface McpCallToolOptions {\n /**\n * Target resource id for resource-specific operations (READ / UPDATE / DELETE\n * and instance-scoped custom operations). Omit for collection operations\n * (CREATE / LIST / SEARCH / COUNT), which authorize on roles + parent scope only.\n */\n resourceId?: string;\n /**\n * The NAMED MCP server instance this call arrived on (R-2), or omitted for the default `/mcp`\n * server. MUST mirror the transport's route: the exposability gate below re-resolves the tool\n * against this instance, so a tool curated onto another server is NOT callable here even by name.\n */\n instanceRef?: string;\n}\n\n/**\n * Result of a successful `tools/call`. `result` is the operation output projected\n * through the operation's response DTO (backend-only/secret fields stripped, dates\n * JSON-normalized — see `callTool`), returned WITHOUT MCP `content` framing: the\n * `{ content: [...], isError }` envelope is applied by the transport\n * ({@link MCPControllerBackendService.handleToolsCall}). Keeping this service\n * transport-agnostic lets the same `callTool` back another transport unchanged.\n */\nexport interface McpCallToolResult {\n toolName: string;\n result: unknown;\n}\n\n/**\n * The MCP server core: lists resource-operation tools and executes them against\n * the SERVICE layer with per-call authorization. Transport-agnostic — the\n * JSON-RPC / Streamable-HTTP wire layer + OAuth 2.1 resource-server auth\n * (RFC 9728 metadata, RFC 8707 audience validation) live in\n * {@link MCPControllerBackendService}; this core carries no wire concern so the\n * same `listTools`/`callTool` can back another transport unchanged.\n *\n * # Boundaries\n *\n * - `listTools()` delegates to {@link McpToolAdapterBackendService}.\n * - `callTool()` resolves the target operation, RE-RUNS `authorizeInController`\n * against the caller's execution context (fail-closed), then invokes the\n * service pipeline DIRECTLY via {@link ServicesRegistryHandlerBackendService}\n * (never re-HTTP). The service pipeline enforces `requiredFeatures` /\n * `enabledCondition` downstream.\n * - KNOWN GAP (tracked — `.claude/plans/mcp-server-followups.md` Item 5): the\n * per-route HTTP rate-limit middleware is bypassed on this direct dispatch path,\n * so an operation's `rateLimit` policy is not yet enforced here. The planned fix\n * is a per-principal quota shared with the A2A dispatch surface.\n * - Server-initiated streaming (SSE / notifications / progress) is intentionally\n * absent: every exposed operation is synchronous request/response, for which a\n * single JSON response is Streamable-HTTP-compliant (Item 1, deferred).\n */\n/**\n * Operations whose RESULT is the paginated `{ data, pagination }` envelope, for OUTPUT.\n *\n * Deliberately NOT the same set as {@link isCollectionQueryOperation}: `COUNT` takes the collection\n * argument envelope but RETURNS A SCALAR, so asking the repository to wrap it would hand back a\n * shape its response DTO does not declare — trading one contract mismatch for another.\n *\n * Mirrors `needsPagination` in `ControllerResourceBackendService`, which is where this decision has\n * always been made for the HTTP surface. The two must agree: they select the shape the SAME response\n * DTO is asked to validate.\n */\nfunction isPaginationWrappedOperation(effectiveOperation: string): boolean {\n return effectiveOperation === CoreResourceOperation.LIST\n || effectiveOperation === CoreResourceOperation.SEARCH;\n}\n\n@injectable()\nexport class McpServerBackendService {\n protected logDebug: (message: string, context?: Record<string, unknown>) => void;\n\n constructor(\n @inject(SAAS_SERVICE_TYPES.Logger) protected readonly logger: LoggerService,\n @inject(SAAS_SERVICE_TYPES.ErrorBuilderService) protected readonly errorBuilder: ErrorBuilderBackendService,\n @inject(SAAS_SERVICE_TYPES.McpToolAdapter) protected readonly toolAdapter: McpToolAdapterBackendService,\n @inject(SAAS_SERVICE_TYPES.AuthorizationsService) protected readonly authorizer: AuthorizationsBackendService,\n @inject(SAAS_SERVICE_TYPES.ServicesRegistryHandlerService) protected readonly servicesHandler: ServicesRegistryHandlerBackendService,\n @inject(SAAS_SERVICE_TYPES.ResourceRegistryService) protected readonly resourcesRegistry: ResourcesRegistryBackendService,\n @inject(SAAS_SERVICE_TYPES.FileUploadGrantService) protected readonly fileUploadGrantService: FileUploadGrantBackendService,\n @inject(SAAS_SERVICE_TYPES.AppConfigService) protected readonly appConfigService: AppConfigurationService,\n ) {\n this.logDebug = this.logger.debugByType('McpServerBackendService');\n this.logger.setDebugTypeLogging('McpServerBackendService', false);\n }\n\n /**\n * Return the exposed tool descriptors for the caller's reachable scopes (`options.reachableScopes`).\n * PROGRESSIVE principal-awareness (C): a caller presenting a valid Bearer bound to this endpoint\n * passes its principal's scopes, so a user-delegated caller additionally sees its USER_SELF tools.\n * Omitting the scopes yields the MACHINE catalog; an ANONYMOUS caller passes an EMPTY set and the\n * transport challenges it rather than serving the empty page. Authorization is still enforced\n * fail-closed at `callTool` time — the catalog is a discovery aid, never an authorization grant.\n */\n public listTools(options: McpToolAdapterBuildOptions = {}): McpToolDescriptor[] {\n return this.toolAdapter.buildToolDescriptors(options);\n }\n\n /**\n * Execute one tool: resolve → authorize (fail-closed) → dispatch to the service layer.\n *\n * @param toolName - `<resourceIdentifier>__<operationIdentifier>`.\n * @param args - the tool arguments (the operation request body).\n * @param executionContext - the caller's context (a machine principal for api-key\n * / client-credentials callers; a user-delegated context once OIDC lands).\n * @param options - per-call options ({@link McpCallToolOptions}).\n * @throws AUTHORIZATION when the principal is not permitted to run the operation.\n * @throws NOT_FOUND when the tool name does not map to an exposable operation.\n */\n public async callTool(\n toolName: string,\n args: unknown,\n executionContext: ExecutionContext<any>,\n options: McpCallToolOptions = {},\n ): Promise<McpCallToolResult> {\n const { resourceType, operationIdentifier } = this.parseToolName(toolName);\n\n // Single source of exposability truth: the adapter decides whether this resource+operation is a\n // valid, default URL-bearing tool REACHABLE BY THIS CALLER. Progressive principal-awareness — a\n // user-delegated principal (USER_REQUEST, carries a `userId`) reaches USER_SELF operations; a\n // machine principal reaches ORG/APP only. Rejecting here (before authorization) prevents a crafted\n // name from reaching a scope the caller cannot use (e.g. a machine hitting USER_SELF → always 403).\n // Derived from the SAME source `tools/list` filters on, so advertise ≡ callable per principal.\n // R-2: also gated on the server INSTANCE the call arrived on, so `(principal, instance)` together\n // decide callability — the same pair `tools/list` filters on.\n // Post-authentication by construction: `callTool` is only reached with a validated token.\n const reachableScopes = mcpReachableScopesForPrincipal(mcpPrincipalForAuthenticatedCaller(executionContext.isUserExecution()));\n\n // Issue #312, D11: a DERIVED upload-grant tool mints a grant instead of dispatching an operation.\n // It resolves its PARENT through the same adapter gate (exposure, instance, scope inherited), is\n // authorized exactly as the parent would be, then calls the ONE mint function the REST door calls.\n if (operationIdentifier.includes(MCP_UPLOAD_GRANT_TOOL_MARKER)) {\n return this.callUploadGrantTool(toolName, resourceType, operationIdentifier, args, executionContext, options, reachableScopes);\n }\n\n const operation = this.toolAdapter.resolveExposableOperation(resourceType, operationIdentifier, reachableScopes, options.instanceRef);\n if (!operation) {\n throw this.errorBuilder.buildError(ErrorType.NOT_FOUND, executionContext, {\n customMessageReference: ErrorCustomMessageReference.SERVICE_NOT_FOUND,\n context: { message: `MCP tool \"${toolName}\" does not map to an exposable resource operation`, toolName },\n });\n }\n\n // Bind the resolved operation onto the caller's context (preserves principal\n // identity + roles) so the authorizer and the service pipeline both see the\n // operation actually being executed.\n const boundExecutionContext = executionContext.withOperation(operation);\n\n const idOrIds = options.resourceId;\n\n // Fail-closed per-operation authorization: reads `operation.roles` +\n // `executionContext.initiatorRoles` / scope. Throws AUTHORIZATION on denial.\n await this.authorizer.authorizeInController(idOrIds, boundExecutionContext);\n\n // Dispatch to the service layer directly (NOT re-HTTP). The service pipeline\n // enforces enabledCondition / requiredFeatures downstream.\n //\n // Dispatch the EXACT operation we resolved + authorized + advertised (the URL-bearing default\n // from `resolveExposableOperation`), via `extractOperationPath`. We deliberately do NOT\n // re-resolve through `getServiceOperationPathDefault`: for a resource that declares BOTH a\n // URL-bearing default AND an INTERNAL_CALL default for the same operation identifier, that\n // helper PREFERS the INTERNAL_CALL variant — which would execute a different variant (different\n // `requestDto` validating `args`, different side effects / custom-impl) than the one\n // `authorizeInController` just checked and than the `inputSchema` advertised to the agent.\n // Using the resolved operation's own path mirrors how the HTTP controllers dispatch the URL\n // operation and keeps authorize ≡ advertise ≡ execute on one variant.\n const operationPath: ResourceConfiguration_OperationPath<any, any> = ResourcesConfig_OperationPathUtils.extractOperationPath(operation);\n\n const dispatchArgs = this.toDispatchArguments(operation, args, boundExecutionContext);\n\n this.logDebug('Executing MCP tool call', { toolName, resourceType, operationIdentifier, hasResourceId: idOrIds !== undefined });\n\n /**\n * Request the pagination WRAPPER for collection operations, exactly as the HTTP controller does.\n *\n * The repository's `list` returns a BARE ARRAY unless the caller asks for the envelope\n * (`options.paginated`), and the decision to ask is made in `ControllerResourceBackendService`:\n * `needsPagination = LIST || SEARCH` → `{ paginated: true }`. That is a CONTROLLER-tier\n * decision, and this path does not go through the controller — so `tools/call` dispatched a\n * paginated LIST without ever requesting pagination, the repository returned a bare array, and\n * the operation's RESPONSE DTO (which for a paginated list declares `{ data, pagination }`)\n * rejected it. Every paginated `*__list` tool answered `-32603` with\n * `expected array, received undefined` at `data`.\n *\n * Two things hid it. `*__search` was unaffected — the search path builds its own envelope — and\n * the one LIST that worked (`todosRelationships__list`) is UNPAGINATED, so a bare array is\n * exactly what its response DTO declares. So the failure looked resource-specific when it was\n * operation-wide.\n *\n * This is the OUTPUT half of the defect family the input normalisation already fixed on\n * 2026-08-01: \"the advertised contract is not the enforced contract\". `tools/list` advertises a\n * paginated collection envelope because the response DTO says so; the dispatch has to request\n * the shape that DTO describes, or the two tiers disagree about what the operation returns.\n */\n const needsPaginationWrapper = isPaginationWrappedOperation(this.resolveEffectiveOperation(operation));\n\n const result = await this.servicesHandler.doByOperationPath(\n operationPath,\n boundExecutionContext,\n idOrIds,\n dispatchArgs,\n needsPaginationWrapper ? { paginated: true } : undefined,\n );\n\n // Project the raw service result through the operation's RESPONSE DTO — the SAME\n // output-serialization boundary the HTTP controller (`sendResponse`) and the A2A tool\n // path use — BEFORE it crosses to the external MCP agent. `doByOperationPath` returns the\n // internal DTO, which still carries `.isBackendOnly()` / write-only-secret fields and raw\n // `Date`/`ObjectId` instances; returning it verbatim would leak backend-only/secret material\n // into the agent's model context. `toResponseSafeToolOutput` strips + JSON-normalizes so the\n // MCP tool result is byte-identical to what a REST client would receive.\n const safeResult = await toResponseSafeToolOutput(operation, result);\n\n return { toolName, result: safeResult };\n }\n\n /**\n * Execute a derived upload-grant tool (issue #312, D11).\n *\n * Authorization is the PARENT's: the caller's context is bound to the parent operation and\n * `authorizeInController` runs on it with the same `resourceId` the parent would take — so minting\n * requires exactly the authority uploading would, which is the invariant the REST door gets from\n * sharing the upload middleware. The upload URL is built from the parent's own route pattern with\n * the parameters the context and the call supply, and the API origin comes from CONFIGURATION,\n * never from a request header.\n */\n private async callUploadGrantTool(\n toolName: string,\n resourceType: ResourceType,\n operationPart: string,\n args: unknown,\n executionContext: ExecutionContext<any>,\n options: McpCallToolOptions,\n reachableScopes: ReadonlySet<ResourcePrimaryScope>,\n ): Promise<McpCallToolResult> {\n const derived = this.toolAdapter.resolveUploadGrantTool(resourceType, operationPart, reachableScopes, options.instanceRef);\n if (!derived) {\n throw this.errorBuilder.buildError(ErrorType.NOT_FOUND, executionContext, {\n customMessageReference: ErrorCustomMessageReference.SERVICE_NOT_FOUND,\n context: { message: `MCP tool \"${toolName}\" does not map to an exposable upload-grant tool`, toolName },\n });\n }\n const { parentOperation, fieldName, operationKind } = derived;\n const boundExecutionContext = executionContext.withOperation(parentOperation);\n const resourceId = options.resourceId;\n if (operationKind === CoreResourceOperation.UPDATE && !resourceId) {\n throw this.errorBuilder.buildError(ErrorType.VALIDATION, boundExecutionContext, {\n customMessageReference: ErrorCustomMessageReference.VALIDATION,\n context: { message: 'An upload grant for an UPDATE requires the record id', toolName },\n });\n }\n\n // Same gate, same order, same arguments as the parent operation.\n await this.authorizer.authorizeInController(resourceId, boundExecutionContext);\n\n const parsedArgs = FileUploadGrantMintRequestSchema.safeParse(this.withoutInstanceId(args, resourceType));\n if (!parsedArgs.success) {\n throw this.errorBuilder.buildError(ErrorType.VALIDATION, boundExecutionContext, {\n customMessageReference: ErrorCustomMessageReference.VALIDATION,\n context: { toolName, errors: parsedArgs.error.issues.map((issue) => ({ path: issue.path.join('.'), message: issue.message })) },\n });\n }\n\n const apiPublicUrl = this.appConfigService.config.runtime?.endPoints?.[BackendApiType.MAIN_BACKEND_API]?.publicUrl;\n if (!apiPublicUrl) {\n throw this.errorBuilder.buildError(ErrorType.CONFIGURATION, boundExecutionContext, {\n customMessageReference: ErrorCustomMessageReference.CONFIGURATION,\n context: { message: `runtime.endPoints.${BackendApiType.MAIN_BACKEND_API}.publicUrl is required to mint a file upload grant` },\n });\n }\n const origin = apiPublicUrl.replace(/\\/+$/, '');\n // Scope ids come from the CALLER's context: binding an operation onto a context never changes who is calling.\n const uploadPath = this.resolveUploadRoutePath(parentOperation, resourceType, fieldName, operationKind, resourceId, executionContext);\n const grant = await this.fileUploadGrantService.mint({\n executionContext: boundExecutionContext,\n target: { resourceType, fieldName, operation: operationKind, resourceId },\n expiresInMinutes: parsedArgs.data.expiresInMinutes,\n uploadUrl: `${origin}${uploadPath}`,\n apiBaseUrl: `${origin}${MAIN_API_BASE_PATH}`,\n });\n this.logDebug('Executing MCP upload-grant tool call', { toolName, resourceType, fieldName, operationKind, hasResourceId: resourceId !== undefined });\n return { toolName, result: grant };\n }\n\n /** The tool arguments minus the resource's own id, which the transport already split out into `options.resourceId`. */\n private withoutInstanceId(args: unknown, resourceType: ResourceType): unknown {\n if (!args || typeof args !== 'object') return args ?? {};\n const selfField = this.resourcesRegistry.getAllResourceFieldIdentifiers()[resourceType];\n if (!selfField || !(selfField in (args as Record<string, unknown>))) return args;\n const { [selfField]: _dropped, ...rest } = args as Record<string, unknown>;\n return rest;\n }\n\n /**\n * The concrete upload-route PATH (mount prefix included) for one file field of the parent\n * operation, from the parent's own route patterns — the same builder the resource controller used\n * to REGISTER the route, so the grant's `uploadUrl` is a route that exists.\n *\n * A parent can carry several patterns (one per parent-resource combination); the one chosen is the\n * first whose every `{param}` the call can supply: the resource's own id from `resourceId`, and scope\n * / parent ids from the context's `initiatorIds`. Patterns are tried shortest first, so the\n * primary-scope form wins over a nested FK form the MCP call cannot address anyway.\n */\n private resolveUploadRoutePath(\n parentOperation: ResourceConfiguration_Operation<any, any, any>,\n resourceType: ResourceType,\n fieldName: string,\n operationKind: FileUploadGrantOperation,\n resourceId: string | undefined,\n executionContext: ExecutionContext<any>,\n ): string {\n const selfField = this.resourcesRegistry.getAllResourceFieldIdentifiers()[resourceType];\n const initiatorInfos = [...(('initiatorInfos' in parentOperation ? parentOperation.initiatorInfos : undefined) ?? [])]\n .sort((a, b) => (a.parameters?.length ?? 0) - (b.parameters?.length ?? 0));\n const supplied: Record<string, string | undefined> = {\n ...(executionContext.initiatorIds as Record<string, string | undefined> | undefined ?? {}),\n ...(selfField && resourceId ? { [selfField]: resourceId } : {}),\n };\n for (const info of initiatorInfos) {\n const route = ResourcesConfigOperationsUrlsBuilderUtils.buildFileUploadRouteForOperation({\n operationRouteUrl: info.url,\n operationParameters: info.parameters ?? [],\n targetResourceType: resourceType,\n targetResourceFieldIdentifier: selfField ?? 'id',\n fieldName,\n requiresTargetResourceId: operationKind === CoreResourceOperation.UPDATE,\n isFrontend: false,\n });\n let unresolved = false;\n const path = route.url.replace(/\\{([^}]+)\\}/g, (_m, param: string) => {\n const value = supplied[param];\n if (typeof value !== 'string' || value.length === 0) { unresolved = true; return `{${param}}`; }\n return encodeURIComponent(value);\n });\n if (!unresolved) return path;\n }\n throw this.errorBuilder.buildError(ErrorType.CONFIGURATION, executionContext, {\n customMessageReference: ErrorCustomMessageReference.CONFIGURATION,\n context: { message: 'No upload route of this operation can be addressed from the MCP call', resourceType, fieldName, operationKind, patterns: initiatorInfos.map((i) => i.url) },\n });\n }\n\n /**\n * Split `<resourceIdentifier>__<operationIdentifier>` on the FIRST double\n * underscore. Resource identifiers use single underscores only, so the first\n * `__` unambiguously delimits the resource from the operation.\n */\n /**\n * Bring the agent's raw `arguments` onto the SAME input contract the HTTP controller produces\n * before dispatch. Added 2026-08-01; until then `tools/call` passed `args` through untouched.\n *\n * Two distinct defects that fixed, both of the \"advertised contract is not the enforced\n * contract\" family:\n *\n * 1. **Collection tools silently ignored the agent's filter.** `tools/list` advertises an\n * `inputSchema` derived from the operation's `requestDto`, which for LIST / SEARCH is keyed\n * `{ filters, pagination, sorting }`. The service reads `{ filter, page, limit, sort,\n * searchQuery }`. The HTTP controller bridges the two; MCP did not, so an agent that\n * followed the schema it was given received the UNFILTERED collection while believing it was\n * filtered — the worst shape for an LLM caller, which then reasons over the wrong rows.\n * {@link normalizeCollectionQueryRequestData} is now the single authority both surfaces use,\n * and it accepts either vocabulary. It also carries the filter ALLOW-LIST, which matters\n * here: the repository's `sanitizeRequestFilter` returns the filter UNCHANGED when an\n * operation declares no `filterFields`, so for a surface with no strict DTO parse in front\n * of it this is the layer that stops an arbitrary key reaching the database.\n * 2. **The published request contract was not enforced.** Every other operation now gets the\n * controller's strict `requestDto.parse`, so a key the tool never advertised is dropped at\n * the boundary that advertised it, rather than three layers away. Writes were already safe —\n * the repository re-parses against the same DTO\n * (`repositories-registry-handler.backend.service.ts`) — but relying on a distant guard for\n * an external protocol surface is defence by coincidence: nothing tells a future refactor of\n * that method that MCP depends on it.\n *\n * Parse failures are deliberately NOT swallowed. An agent that sends a malformed argument should\n * be told, so the model can correct itself; silently dropping it teaches the model nothing and\n * leaves it reasoning about an operation it did not actually perform.\n */\n private toDispatchArguments(\n operation: {\n requestDto?: z.ZodTypeAny;\n resourceOperationLike?: unknown;\n operationIdentifier: unknown;\n resourceIdentifier: unknown;\n },\n args: unknown,\n executionContext: ExecutionContext<any>,\n ): unknown {\n const effectiveOperation = this.resolveEffectiveOperation(operation);\n const isCollectionOperation = isCollectionQueryMcpOperation(effectiveOperation);\n\n if (isCollectionOperation) {\n const rawArguments = (args && typeof args === 'object' && !Array.isArray(args))\n ? args as Record<string, unknown>\n : {};\n const resourceOperations = this.resourcesRegistry\n .getResourceConfiguration(operation.resourceIdentifier as ResourceType)\n .operations;\n return normalizeCollectionQueryRequestData(\n rawArguments,\n operation as never,\n effectiveOperation,\n resourceOperations as never,\n );\n }\n\n if (!operation.requestDto) return args;\n\n try {\n return operation.requestDto.parse(args);\n } catch (error) {\n if (error instanceof z.ZodError) {\n throw this.errorBuilder.buildError(ErrorType.VALIDATION, executionContext, {\n customMessageReference: ErrorCustomMessageReference.CONTROLLER_RESOURCE_VALIDATION_FAILED,\n stack: error.stack,\n context: {\n message: 'MCP tool arguments do not satisfy the operation request contract advertised by tools/list',\n operationIdentifier: operation.operationIdentifier,\n zodIssues: error.issues,\n },\n });\n }\n throw error;\n }\n }\n\n /**\n * The operation this tool really performs — a custom operation declares what it behaves LIKE via\n * `resourceOperationLike`, and both the input normalisation and the output envelope must key on\n * the same answer. Derived once here because they previously derived it separately, and a\n * custom LIST-like operation that only one of them recognised would be normalised as a collection\n * on the way in and dispatched without pagination on the way out.\n */\n private resolveEffectiveOperation(operation: { resourceOperationLike?: unknown; operationIdentifier: unknown }): string {\n return resolveEffectiveMcpOperation(operation);\n }\n\n private parseToolName(toolName: string): { resourceType: ResourceType; operationIdentifier: string } {\n const separatorIndex = toolName.indexOf(MCP_TOOL_NAME_SEPARATOR);\n if (separatorIndex <= 0 || separatorIndex + MCP_TOOL_NAME_SEPARATOR.length >= toolName.length) {\n throw this.errorBuilder.buildError(ErrorType.VALIDATION, undefined, {\n customMessageReference: ErrorCustomMessageReference.SERVICE_NOT_FOUND,\n context: { message: `Malformed MCP tool name \"${toolName}\" — expected \"<resource>${MCP_TOOL_NAME_SEPARATOR}<operation>\"`, toolName },\n });\n }\n return {\n resourceType: toolName.slice(0, separatorIndex) as ResourceType,\n operationIdentifier: toolName.slice(separatorIndex + MCP_TOOL_NAME_SEPARATOR.length),\n };\n }\n}\n"]}
@@ -1,4 +1,5 @@
1
- import { ResourceType, ResourceConfiguration_Operation, ResourcePrimaryScope } from '@wildo-ai/saas-models';
1
+ import { ResourceType, ResourceConfiguration_Operation, ResourcePrimaryScope, type FileUploadGrantOperation } from '@wildo-ai/saas-models';
2
+ import type { FileFieldDetectionBackendService } from '../../files/services/file-field-detection.backend.service';
2
3
  import type { LoggerService } from '../../monitoring/logger/services/logger.service';
3
4
  import { ResourcesRegistryBackendService } from '../../resources/services/resources-registry.backend.service';
4
5
  import { McpToolDescriptor, McpToolAdapterBuildOptions } from '../../mcp/models/mcp-tool.backend.schemas';
@@ -60,6 +61,12 @@ export declare function resolveEffectiveMcpOperation(operation: {
60
61
  * would ask the repository to wrap a scalar in a pagination envelope its response DTO never declares.
61
62
  */
62
63
  export declare function isCollectionQueryMcpOperation(effectiveOperation: string): boolean;
64
+ /**
65
+ * The kebab-case segment a file field gets in its upload ROUTE (`…/files/<segment>`) — the same
66
+ * transform `ResourcesConfigOperationsUrlsBuilderUtils.buildFileUploadRouteForOperation` applies, so
67
+ * a derived tool name and the route it mints for agree by construction.
68
+ */
69
+ export declare function fileFieldRouteSegment(fieldName: string): string;
63
70
  /**
64
71
  * Adapts registered resource operations into MCP tool descriptors.
65
72
  *
@@ -100,8 +107,24 @@ export declare function isCollectionQueryMcpOperation(effectiveOperation: string
100
107
  export declare class McpToolAdapterBackendService {
101
108
  protected readonly logger: LoggerService;
102
109
  protected readonly resourcesRegistry: ResourcesRegistryBackendService;
110
+ /**
111
+ * Detects the user-uploadable file fields a resource carries — the same detector the resource
112
+ * controller uses to REGISTER upload routes, so a derived upload-grant tool is advertised for
113
+ * exactly the fields that have a route. Optional only so harnesses that never look at file
114
+ * fields can construct the adapter; the container always binds it, and without it no derived
115
+ * tool is advertised or callable (fail-closed, logged once).
116
+ */
117
+ protected readonly fileFieldDetection?: FileFieldDetectionBackendService;
103
118
  protected logDebug: (message: string, context?: Record<string, unknown>) => void;
104
- constructor(logger: LoggerService, resourcesRegistry: ResourcesRegistryBackendService);
119
+ constructor(logger: LoggerService, resourcesRegistry: ResourcesRegistryBackendService,
120
+ /**
121
+ * Detects the user-uploadable file fields a resource carries — the same detector the resource
122
+ * controller uses to REGISTER upload routes, so a derived upload-grant tool is advertised for
123
+ * exactly the fields that have a route. Optional only so harnesses that never look at file
124
+ * fields can construct the adapter; the container always binds it, and without it no derived
125
+ * tool is advertised or callable (fail-closed, logged once).
126
+ */
127
+ fileFieldDetection?: FileFieldDetectionBackendService);
105
128
  /**
106
129
  * Build the MCP tool descriptor set for the requested resources, filtered to the caller's reachable
107
130
  * scopes (`options.reachableScopes`, default = machine-reachable ORG/APP). A user-delegated caller
@@ -195,6 +218,45 @@ export declare class McpToolAdapterBackendService {
195
218
  * build their tool descriptors, sorted by operation identifier.
196
219
  */
197
220
  private buildResourceToolDescriptors;
221
+ /**
222
+ * The user-uploadable file fields a CREATE / UPDATE operation carries; `[]` for every other operation
223
+ * (a READ or DELETE never receives a file) and whenever no detector is bound.
224
+ */
225
+ private uploadableFileFieldsFor;
226
+ private warnedNoFileFieldDetection;
227
+ /**
228
+ * Debug, not warn: the container always binds the detector, so in production this never fires; where
229
+ * it does fire — a harness that constructs the adapter bare — a warning would be noise about a
230
+ * dependency the harness deliberately left out.
231
+ */
232
+ private warnOnceNoFileFieldDetection;
233
+ /**
234
+ * Append, to the PARENT tool's description, where each file field's `fileId` comes from. Engine-
235
+ * appended for the same reason as the envelope note: how a file reaches a resource is a framework
236
+ * fact, not app prose, and an agent reading `attachments.fileId: string` would otherwise have no way
237
+ * to learn that the value cannot be invented.
238
+ */
239
+ private withUploadGrantNote;
240
+ /**
241
+ * The derived upload-grant descriptor. Its arguments are the parent's instance id (UPDATE only —
242
+ * the same id the parent advertises, split out by the transport exactly the same way) and an
243
+ * optional lifetime bounded by the engine ceiling. Nothing about the file itself: bytes never
244
+ * travel through a tool call, which is the whole reason this tool exists.
245
+ */
246
+ private buildUploadGrantDescriptor;
247
+ /**
248
+ * Resolve a DERIVED upload-grant tool back to its parent operation and file field — the callable
249
+ * half of `buildToolDescriptors`' derived emission, kept symmetric with it: the parent is resolved
250
+ * through `resolveExposableOperation` (so exposure, instance and principal-scope gating are
251
+ * inherited verbatim), the field must be one of the resource's uploadable file fields, and the
252
+ * composed name must satisfy the naming guidance. `undefined` for anything that is not a well-formed,
253
+ * advertised derived tool.
254
+ */
255
+ resolveUploadGrantTool(resourceType: ResourceType, operationPart: string, reachableScopes: ReadonlySet<ResourcePrimaryScope>, instanceRef?: string): {
256
+ parentOperation: ResourceConfiguration_Operation<any, any, any>;
257
+ fieldName: string;
258
+ operationKind: FileUploadGrantOperation;
259
+ } | undefined;
198
260
  /** `<resourceIdentifier>__<operationIdentifier>`. */
199
261
  private buildToolName;
200
262
  /**