harper 5.1.20 → 5.2.0-alpha.5

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 (395) hide show
  1. package/agent/operations.ts +13 -16
  2. package/components/Application.ts +188 -7
  3. package/components/ApplicationScope.ts +3 -0
  4. package/components/Scope.ts +12 -0
  5. package/components/anthropic/index.ts +9 -2
  6. package/components/bedrock/index.ts +25 -2
  7. package/components/componentLoader.ts +56 -7
  8. package/components/componentSecrets.ts +451 -0
  9. package/components/deploymentRecorder.ts +7 -1
  10. package/components/mcp/adapters/fastify.ts +3 -0
  11. package/components/mcp/adapters/harperHttp.ts +3 -0
  12. package/components/mcp/audit.ts +21 -6
  13. package/components/mcp/customResourceRegistry.ts +17 -38
  14. package/components/mcp/listChanged.ts +37 -20
  15. package/components/mcp/quota.ts +145 -0
  16. package/components/mcp/rateLimit.ts +152 -14
  17. package/components/mcp/resources.ts +2 -4
  18. package/components/mcp/toolRegistry.ts +73 -4
  19. package/components/mcp/tools/application.ts +77 -8
  20. package/components/mcp/tools/operations.ts +106 -36
  21. package/components/mcp/tools/schemas/operationDescriptions.ts +4 -4
  22. package/components/mcp/tools/schemas/operations.ts +5 -0
  23. package/components/mcp/transport.ts +55 -4
  24. package/components/ollama/index.ts +6 -2
  25. package/components/openai/index.ts +9 -2
  26. package/components/operations.js +204 -10
  27. package/components/operationsValidation.js +173 -0
  28. package/components/secretOperations.ts +498 -0
  29. package/config/configUtils.ts +25 -1
  30. package/config-app.schema.json +81 -0
  31. package/config-root.schema.json +8 -1
  32. package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -3
  33. package/dataLayer/readAuditLog.ts +14 -0
  34. package/dataLayer/schemaDescribe.ts +1 -0
  35. package/dist/agent/operations.d.ts +7 -4
  36. package/dist/agent/operations.js +13 -15
  37. package/dist/agent/operations.js.map +1 -1
  38. package/dist/components/Application.d.ts +25 -2
  39. package/dist/components/Application.js +166 -7
  40. package/dist/components/Application.js.map +1 -1
  41. package/dist/components/ApplicationScope.d.ts +2 -0
  42. package/dist/components/ApplicationScope.js +3 -0
  43. package/dist/components/ApplicationScope.js.map +1 -1
  44. package/dist/components/Scope.d.ts +8 -0
  45. package/dist/components/Scope.js +11 -0
  46. package/dist/components/Scope.js.map +1 -1
  47. package/dist/components/anthropic/index.d.ts +4 -1
  48. package/dist/components/anthropic/index.js +4 -2
  49. package/dist/components/anthropic/index.js.map +1 -1
  50. package/dist/components/bedrock/index.d.ts +4 -1
  51. package/dist/components/bedrock/index.js +22 -2
  52. package/dist/components/bedrock/index.js.map +1 -1
  53. package/dist/components/componentLoader.d.ts +2 -0
  54. package/dist/components/componentLoader.js +52 -10
  55. package/dist/components/componentLoader.js.map +1 -1
  56. package/dist/components/componentSecrets.d.ts +63 -0
  57. package/dist/components/componentSecrets.js +405 -0
  58. package/dist/components/componentSecrets.js.map +1 -0
  59. package/dist/components/deploymentRecorder.d.ts +3 -0
  60. package/dist/components/deploymentRecorder.js +4 -3
  61. package/dist/components/deploymentRecorder.js.map +1 -1
  62. package/dist/components/mcp/adapters/fastify.d.ts +2 -0
  63. package/dist/components/mcp/adapters/fastify.js +1 -0
  64. package/dist/components/mcp/adapters/fastify.js.map +1 -1
  65. package/dist/components/mcp/adapters/harperHttp.d.ts +2 -0
  66. package/dist/components/mcp/adapters/harperHttp.js +1 -0
  67. package/dist/components/mcp/adapters/harperHttp.js.map +1 -1
  68. package/dist/components/mcp/audit.d.ts +2 -2
  69. package/dist/components/mcp/audit.js +20 -5
  70. package/dist/components/mcp/audit.js.map +1 -1
  71. package/dist/components/mcp/customResourceRegistry.d.ts +5 -12
  72. package/dist/components/mcp/customResourceRegistry.js +15 -30
  73. package/dist/components/mcp/customResourceRegistry.js.map +1 -1
  74. package/dist/components/mcp/listChanged.d.ts +7 -2
  75. package/dist/components/mcp/listChanged.js +28 -0
  76. package/dist/components/mcp/listChanged.js.map +1 -1
  77. package/dist/components/mcp/quota.d.ts +31 -0
  78. package/dist/components/mcp/quota.js +155 -0
  79. package/dist/components/mcp/quota.js.map +1 -0
  80. package/dist/components/mcp/rateLimit.d.ts +19 -3
  81. package/dist/components/mcp/rateLimit.js +129 -13
  82. package/dist/components/mcp/rateLimit.js.map +1 -1
  83. package/dist/components/mcp/resources.js.map +1 -1
  84. package/dist/components/mcp/toolRegistry.d.ts +39 -1
  85. package/dist/components/mcp/toolRegistry.js +60 -5
  86. package/dist/components/mcp/toolRegistry.js.map +1 -1
  87. package/dist/components/mcp/tools/application.d.ts +8 -1
  88. package/dist/components/mcp/tools/application.js +65 -5
  89. package/dist/components/mcp/tools/application.js.map +1 -1
  90. package/dist/components/mcp/tools/operations.d.ts +15 -3
  91. package/dist/components/mcp/tools/operations.js +99 -38
  92. package/dist/components/mcp/tools/operations.js.map +1 -1
  93. package/dist/components/mcp/tools/schemas/operationDescriptions.js +4 -4
  94. package/dist/components/mcp/tools/schemas/operationDescriptions.js.map +1 -1
  95. package/dist/components/mcp/tools/schemas/operations.js +4 -0
  96. package/dist/components/mcp/tools/schemas/operations.js.map +1 -1
  97. package/dist/components/mcp/transport.d.ts +6 -0
  98. package/dist/components/mcp/transport.js +47 -3
  99. package/dist/components/mcp/transport.js.map +1 -1
  100. package/dist/components/ollama/index.d.ts +4 -1
  101. package/dist/components/ollama/index.js +4 -2
  102. package/dist/components/ollama/index.js.map +1 -1
  103. package/dist/components/openai/index.d.ts +4 -1
  104. package/dist/components/openai/index.js +4 -2
  105. package/dist/components/openai/index.js.map +1 -1
  106. package/dist/components/operations.d.ts +39 -0
  107. package/dist/components/operations.js +194 -2
  108. package/dist/components/operations.js.map +1 -1
  109. package/dist/components/operationsValidation.d.ts +38 -0
  110. package/dist/components/operationsValidation.js +155 -0
  111. package/dist/components/operationsValidation.js.map +1 -1
  112. package/dist/components/secretOperations.d.ts +113 -0
  113. package/dist/components/secretOperations.js +475 -0
  114. package/dist/components/secretOperations.js.map +1 -0
  115. package/dist/config/configUtils.d.ts +4 -1
  116. package/dist/config/configUtils.js +18 -1
  117. package/dist/config/configUtils.js.map +1 -1
  118. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -3
  119. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js.map +1 -1
  120. package/dist/dataLayer/readAuditLog.js +11 -0
  121. package/dist/dataLayer/readAuditLog.js.map +1 -1
  122. package/dist/dataLayer/schemaDescribe.js +2 -0
  123. package/dist/dataLayer/schemaDescribe.js.map +1 -1
  124. package/dist/globals.d.ts +1 -1
  125. package/dist/globals.js +1 -0
  126. package/dist/globals.js.map +1 -1
  127. package/dist/index.d.ts +3 -0
  128. package/dist/index.js +1 -0
  129. package/dist/index.js.map +1 -1
  130. package/dist/json/systemSchema.json +38 -0
  131. package/dist/resources/DatabaseTransaction.d.ts +31 -3
  132. package/dist/resources/DatabaseTransaction.js +190 -29
  133. package/dist/resources/DatabaseTransaction.js.map +1 -1
  134. package/dist/resources/LMDBTransaction.d.ts +1 -1
  135. package/dist/resources/LMDBTransaction.js +43 -5
  136. package/dist/resources/LMDBTransaction.js.map +1 -1
  137. package/dist/resources/PrimaryRocksDatabase.d.ts +51 -0
  138. package/dist/resources/PrimaryRocksDatabase.js +194 -0
  139. package/dist/resources/PrimaryRocksDatabase.js.map +1 -0
  140. package/dist/resources/RecordEncoder.d.ts +1 -0
  141. package/dist/resources/RecordEncoder.js +81 -75
  142. package/dist/resources/RecordEncoder.js.map +1 -1
  143. package/dist/resources/RequestTarget.d.ts +19 -0
  144. package/dist/resources/RequestTarget.js.map +1 -1
  145. package/dist/resources/Resource.d.ts +0 -1
  146. package/dist/resources/Resource.js +103 -16
  147. package/dist/resources/Resource.js.map +1 -1
  148. package/dist/resources/ResourceInterface.d.ts +6 -2
  149. package/dist/resources/ResourceInterface.js.map +1 -1
  150. package/dist/resources/Resources.js.map +1 -1
  151. package/dist/resources/Table.d.ts +6 -4
  152. package/dist/resources/Table.js +247 -24
  153. package/dist/resources/Table.js.map +1 -1
  154. package/dist/resources/analytics/read.js +25 -28
  155. package/dist/resources/analytics/read.js.map +1 -1
  156. package/dist/resources/blob.js +165 -22
  157. package/dist/resources/blob.js.map +1 -1
  158. package/dist/resources/crdt.d.ts +2 -0
  159. package/dist/resources/crdt.js +45 -36
  160. package/dist/resources/crdt.js.map +1 -1
  161. package/dist/resources/databases.d.ts +1 -0
  162. package/dist/resources/databases.js +134 -7
  163. package/dist/resources/databases.js.map +1 -1
  164. package/dist/resources/graphql.js +8 -0
  165. package/dist/resources/graphql.js.map +1 -1
  166. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +35 -5
  167. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +239 -14
  168. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
  169. package/dist/resources/jsResource.js +6 -0
  170. package/dist/resources/jsResource.js.map +1 -1
  171. package/dist/resources/loadEnv.js +30 -3
  172. package/dist/resources/loadEnv.js.map +1 -1
  173. package/dist/resources/login.js +2 -2
  174. package/dist/resources/login.js.map +1 -1
  175. package/dist/resources/models/Models.js +14 -3
  176. package/dist/resources/models/Models.js.map +1 -1
  177. package/dist/resources/models/embedHook.js +10 -2
  178. package/dist/resources/models/embedHook.js.map +1 -1
  179. package/dist/resources/models/openaiStream.d.ts +56 -0
  180. package/dist/resources/models/openaiStream.js +94 -0
  181. package/dist/resources/models/openaiStream.js.map +1 -0
  182. package/dist/resources/replayLogs.js +4 -3
  183. package/dist/resources/replayLogs.js.map +1 -1
  184. package/dist/resources/search.d.ts +1 -1
  185. package/dist/resources/search.js +104 -11
  186. package/dist/resources/search.js.map +1 -1
  187. package/dist/resources/secretDecryptor.d.ts +54 -0
  188. package/dist/resources/secretDecryptor.js +131 -0
  189. package/dist/resources/secretDecryptor.js.map +1 -0
  190. package/dist/resources/tracked.js +15 -5
  191. package/dist/resources/tracked.js.map +1 -1
  192. package/dist/security/auth.js +71 -8
  193. package/dist/security/auth.js.map +1 -1
  194. package/dist/security/jsLoader.js +11 -0
  195. package/dist/security/jsLoader.js.map +1 -1
  196. package/dist/security/tokenAuthentication.d.ts +9 -1
  197. package/dist/security/tokenAuthentication.js +31 -1
  198. package/dist/security/tokenAuthentication.js.map +1 -1
  199. package/dist/server/DurableSubscriptionsSession.d.ts +1 -1
  200. package/dist/server/DurableSubscriptionsSession.js +8 -4
  201. package/dist/server/DurableSubscriptionsSession.js.map +1 -1
  202. package/dist/server/REST.js +28 -9
  203. package/dist/server/REST.js.map +1 -1
  204. package/dist/server/fastifyRoutes.js +11 -1
  205. package/dist/server/fastifyRoutes.js.map +1 -1
  206. package/dist/server/graphqlQuerying.js +4 -3
  207. package/dist/server/graphqlQuerying.js.map +1 -1
  208. package/dist/server/http.d.ts +40 -2
  209. package/dist/server/http.js +560 -77
  210. package/dist/server/http.js.map +1 -1
  211. package/dist/server/itc/serverHandlers.js +64 -0
  212. package/dist/server/itc/serverHandlers.js.map +1 -1
  213. package/dist/server/liveSubscriptionAuth.d.ts +12 -0
  214. package/dist/server/liveSubscriptionAuth.js +140 -0
  215. package/dist/server/liveSubscriptionAuth.js.map +1 -0
  216. package/dist/server/loadRootComponents.js +2 -1
  217. package/dist/server/loadRootComponents.js.map +1 -1
  218. package/dist/server/middlewareChain.d.ts +44 -1
  219. package/dist/server/middlewareChain.js +86 -10
  220. package/dist/server/middlewareChain.js.map +1 -1
  221. package/dist/server/mqtt.js +2 -1
  222. package/dist/server/mqtt.js.map +1 -1
  223. package/dist/server/operationsServer.js +1 -1
  224. package/dist/server/operationsServer.js.map +1 -1
  225. package/dist/server/serverHelpers/Headers.d.ts +9 -0
  226. package/dist/server/serverHelpers/Headers.js +22 -0
  227. package/dist/server/serverHelpers/Headers.js.map +1 -1
  228. package/dist/server/serverHelpers/JSONStream.js +3 -3
  229. package/dist/server/serverHelpers/JSONStream.js.map +1 -1
  230. package/dist/server/serverHelpers/Request.d.ts +52 -1
  231. package/dist/server/serverHelpers/Request.js +122 -1
  232. package/dist/server/serverHelpers/Request.js.map +1 -1
  233. package/dist/server/serverHelpers/contentTypes.js +3 -8
  234. package/dist/server/serverHelpers/contentTypes.js.map +1 -1
  235. package/dist/server/serverHelpers/progressEmitter.d.ts +19 -0
  236. package/dist/server/serverHelpers/progressEmitter.js +52 -4
  237. package/dist/server/serverHelpers/progressEmitter.js.map +1 -1
  238. package/dist/server/serverHelpers/registeredOperations.d.ts +40 -0
  239. package/dist/server/serverHelpers/registeredOperations.js +286 -0
  240. package/dist/server/serverHelpers/registeredOperations.js.map +1 -0
  241. package/dist/server/serverHelpers/serverHandlers.js +11 -5
  242. package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
  243. package/dist/server/serverHelpers/serverUtilities.d.ts +1 -0
  244. package/dist/server/serverHelpers/serverUtilities.js +67 -5
  245. package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
  246. package/dist/server/serverHelpers/uwsServer.d.ts +80 -0
  247. package/dist/server/serverHelpers/uwsServer.js +433 -0
  248. package/dist/server/serverHelpers/uwsServer.js.map +1 -0
  249. package/dist/server/static.d.ts +0 -15
  250. package/dist/server/static.js +161 -2
  251. package/dist/server/static.js.map +1 -1
  252. package/dist/server/status/index.d.ts +4 -1
  253. package/dist/server/status/index.js +64 -4
  254. package/dist/server/status/index.js.map +1 -1
  255. package/dist/server/threads/manageThreads.d.ts +10 -0
  256. package/dist/server/threads/manageThreads.js +107 -4
  257. package/dist/server/threads/manageThreads.js.map +1 -1
  258. package/dist/server/threads/socketRouter.js +5 -1
  259. package/dist/server/threads/socketRouter.js.map +1 -1
  260. package/dist/server/threads/threadServer.js +92 -14
  261. package/dist/server/threads/threadServer.js.map +1 -1
  262. package/dist/upgrade/directives/5-2-0.d.ts +7 -0
  263. package/dist/upgrade/directives/5-2-0.js +108 -0
  264. package/dist/upgrade/directives/5-2-0.js.map +1 -0
  265. package/dist/upgrade/directives/directivesController.js +2 -1
  266. package/dist/upgrade/directives/directivesController.js.map +1 -1
  267. package/dist/utility/common_utils.js +5 -0
  268. package/dist/utility/common_utils.js.map +1 -1
  269. package/dist/utility/envFile.d.ts +41 -0
  270. package/dist/utility/envFile.js +221 -0
  271. package/dist/utility/envFile.js.map +1 -0
  272. package/dist/utility/globalSchema.d.ts +9 -0
  273. package/dist/utility/hdbTerms.d.ts +25 -0
  274. package/dist/utility/hdbTerms.js +31 -0
  275. package/dist/utility/hdbTerms.js.map +1 -1
  276. package/dist/utility/logging/harper_logger.d.ts +21 -0
  277. package/dist/utility/logging/harper_logger.js +160 -8
  278. package/dist/utility/logging/harper_logger.js.map +1 -1
  279. package/dist/utility/logging/logRotator.js +29 -17
  280. package/dist/utility/logging/logRotator.js.map +1 -1
  281. package/dist/utility/logging/readLog.d.ts +1 -1
  282. package/dist/utility/logging/readLog.js +305 -2
  283. package/dist/utility/logging/readLog.js.map +1 -1
  284. package/dist/utility/operationPermissions.d.ts +18 -0
  285. package/dist/utility/operationPermissions.js +35 -1
  286. package/dist/utility/operationPermissions.js.map +1 -1
  287. package/dist/utility/operation_authorization.d.ts +17 -0
  288. package/dist/utility/operation_authorization.js +38 -0
  289. package/dist/utility/operation_authorization.js.map +1 -1
  290. package/dist/utility/secretEnvelope.d.ts +30 -0
  291. package/dist/utility/secretEnvelope.js +94 -0
  292. package/dist/utility/secretEnvelope.js.map +1 -0
  293. package/dist/utility/signalling.d.ts +7 -0
  294. package/dist/utility/signalling.js +16 -0
  295. package/dist/utility/signalling.js.map +1 -1
  296. package/dist/validation/configValidator.js +70 -24
  297. package/dist/validation/configValidator.js.map +1 -1
  298. package/index.ts +4 -0
  299. package/json/systemSchema.json +38 -0
  300. package/npm-shrinkwrap.json +15066 -9347
  301. package/package.json +13 -8
  302. package/resources/DESIGN.md +15 -12
  303. package/resources/DatabaseTransaction.ts +206 -37
  304. package/resources/LMDBTransaction.ts +43 -9
  305. package/resources/PrimaryRocksDatabase.ts +201 -0
  306. package/resources/RecordEncoder.ts +81 -69
  307. package/resources/RequestTarget.ts +21 -0
  308. package/resources/Resource.ts +109 -25
  309. package/resources/ResourceInterface.ts +6 -2
  310. package/resources/Resources.ts +1 -3
  311. package/resources/Table.ts +238 -29
  312. package/resources/analytics/read.ts +25 -30
  313. package/resources/blob.ts +159 -23
  314. package/resources/crdt.ts +37 -31
  315. package/resources/databases.ts +143 -6
  316. package/resources/graphql.ts +8 -0
  317. package/resources/indexes/HierarchicalNavigableSmallWorld.ts +251 -17
  318. package/resources/jsResource.ts +6 -0
  319. package/resources/loadEnv.ts +34 -3
  320. package/resources/login.ts +2 -2
  321. package/resources/models/Models.ts +19 -3
  322. package/resources/models/embedHook.ts +13 -2
  323. package/resources/models/openaiStream.ts +133 -0
  324. package/resources/replayLogs.ts +4 -3
  325. package/resources/search.ts +113 -11
  326. package/resources/secretDecryptor.ts +159 -0
  327. package/resources/tracked.ts +24 -17
  328. package/security/auth.ts +70 -10
  329. package/security/jsLoader.ts +12 -0
  330. package/security/tokenAuthentication.ts +42 -1
  331. package/server/DESIGN.md +10 -0
  332. package/server/DurableSubscriptionsSession.ts +8 -4
  333. package/server/REST.ts +26 -9
  334. package/server/fastifyRoutes.ts +10 -1
  335. package/server/graphqlQuerying.ts +4 -3
  336. package/server/http.ts +563 -77
  337. package/server/itc/serverHandlers.js +70 -0
  338. package/server/liveSubscriptionAuth.ts +152 -0
  339. package/server/loadRootComponents.js +2 -1
  340. package/server/middlewareChain.ts +107 -17
  341. package/server/mqtt.ts +2 -1
  342. package/server/operationsServer.ts +2 -2
  343. package/server/serverHelpers/Headers.ts +24 -0
  344. package/server/serverHelpers/JSONStream.ts +3 -3
  345. package/server/serverHelpers/Request.ts +127 -0
  346. package/server/serverHelpers/contentTypes.ts +3 -8
  347. package/server/serverHelpers/progressEmitter.ts +55 -5
  348. package/server/serverHelpers/registeredOperations.ts +259 -0
  349. package/server/serverHelpers/serverHandlers.js +11 -5
  350. package/server/serverHelpers/serverUtilities.ts +88 -5
  351. package/server/serverHelpers/uwsServer.ts +488 -0
  352. package/server/static.ts +180 -2
  353. package/server/status/index.ts +72 -4
  354. package/server/threads/manageThreads.js +102 -4
  355. package/server/threads/socketRouter.ts +5 -1
  356. package/server/threads/threadServer.js +93 -10
  357. package/studio/web/assets/{Chat-SdD1EZca.js → Chat-Cv_2paZE.js} +2 -2
  358. package/studio/web/assets/{Chat-SdD1EZca.js.map → Chat-Cv_2paZE.js.map} +1 -1
  359. package/studio/web/assets/{FloatingChat-GMq6tHK9.js → FloatingChat-CPyPIcVR.js} +4 -4
  360. package/studio/web/assets/{FloatingChat-GMq6tHK9.js.map → FloatingChat-CPyPIcVR.js.map} +1 -1
  361. package/studio/web/assets/{applications-B5hhv7uA.js → applications-C0jT2vdZ.js} +2 -2
  362. package/studio/web/assets/{applications-B5hhv7uA.js.map → applications-C0jT2vdZ.js.map} +1 -1
  363. package/studio/web/assets/{index-VFcpNWgq.js → index-D_GKOkhn.js} +6 -6
  364. package/studio/web/assets/index-D_GKOkhn.js.map +1 -0
  365. package/studio/web/assets/{index.lazy-DgcJojIA.js → index.lazy-CAmCIA65.js} +4 -4
  366. package/studio/web/assets/{index.lazy-DgcJojIA.js.map → index.lazy-CAmCIA65.js.map} +1 -1
  367. package/studio/web/assets/{profile-BqN-8vzm.js → profile-CaF-4aZe.js} +2 -2
  368. package/studio/web/assets/{profile-BqN-8vzm.js.map → profile-CaF-4aZe.js.map} +1 -1
  369. package/studio/web/assets/{setComponentFile-CHFvFq9u.js → setComponentFile-DLW1DHCt.js} +2 -2
  370. package/studio/web/assets/{setComponentFile-CHFvFq9u.js.map → setComponentFile-DLW1DHCt.js.map} +1 -1
  371. package/studio/web/assets/{setup-CQ3vWJSr.js → setup-TGCErIhq.js} +2 -2
  372. package/studio/web/assets/{setup-CQ3vWJSr.js.map → setup-TGCErIhq.js.map} +1 -1
  373. package/studio/web/assets/{status-BsW5fjuh.js → status-DG0Maoao.js} +2 -2
  374. package/studio/web/assets/{status-BsW5fjuh.js.map → status-DG0Maoao.js.map} +1 -1
  375. package/studio/web/assets/{swagger-ui-react-DITdeB9-.js → swagger-ui-react-8T4SgQ1m.js} +2 -2
  376. package/studio/web/assets/{swagger-ui-react-DITdeB9-.js.map → swagger-ui-react-8T4SgQ1m.js.map} +1 -1
  377. package/studio/web/assets/{tsMode-Dyph-OUs.js → tsMode-DJ6Sl24Q.js} +2 -2
  378. package/studio/web/assets/{tsMode-Dyph-OUs.js.map → tsMode-DJ6Sl24Q.js.map} +1 -1
  379. package/studio/web/assets/{useEntityRestURL-D0QTUqex.js → useEntityRestURL-JO2mfWTQ.js} +2 -2
  380. package/studio/web/assets/{useEntityRestURL-D0QTUqex.js.map → useEntityRestURL-JO2mfWTQ.js.map} +1 -1
  381. package/studio/web/index.html +1 -1
  382. package/upgrade/directives/5-2-0.ts +82 -0
  383. package/upgrade/directives/directivesController.ts +2 -1
  384. package/utility/common_utils.ts +5 -0
  385. package/utility/envFile.ts +218 -0
  386. package/utility/hdbTerms.ts +31 -0
  387. package/utility/logging/harper_logger.ts +156 -8
  388. package/utility/logging/logRotator.ts +24 -14
  389. package/utility/logging/readLog.ts +323 -2
  390. package/utility/operationPermissions.ts +35 -1
  391. package/utility/operation_authorization.ts +74 -1
  392. package/utility/secretEnvelope.ts +113 -0
  393. package/utility/signalling.ts +15 -0
  394. package/validation/configValidator.ts +78 -25
  395. package/studio/web/assets/index-VFcpNWgq.js.map +0 -1
@@ -136,9 +136,42 @@ export interface ToolDef extends ToolDescriptor {
136
136
 
137
137
  const registry = new Map<string, ToolDef>();
138
138
 
139
+ /**
140
+ * Dynamic, per-profile tool source. A profile may register a provider that
141
+ * computes its tool set *lazily* — walked on every `tools/list` / `tools/call`
142
+ * rather than snapshotted into the static registry at registration time. The
143
+ * operations profile uses this so component operations registered after MCP
144
+ * boot (`server.registerOperation`, e.g. the built-in agent's `agent_prompt`)
145
+ * still surface once allow-listed, instead of being missed by a one-time walk
146
+ * of `OPERATION_FUNCTION_MAP` — see components/mcp/tools/operations.ts (#1562).
147
+ *
148
+ * On a name collision, a statically-registered tool (`addTool`) for the same
149
+ * profile wins over a provider entry — a curated tool overrides a generic
150
+ * same-named provider tool.
151
+ */
152
+ export interface ProfileToolProvider {
153
+ /** Every tool the provider currently exposes for its profile. */
154
+ list(): ToolDef[];
155
+ /** Resolve a single tool by name, or `undefined` if not currently exposed. */
156
+ get(name: string): ToolDef | undefined;
157
+ }
158
+
159
+ const profileProviders = new Map<McpProfile, ProfileToolProvider>();
160
+
139
161
  /** Per-session pagination cache. Invalidated when a fresh `tools/list` (no cursor) call recomputes. */
140
162
  const sessionListCache = new Map<string, { tools: ToolDescriptor[] }>();
141
163
 
164
+ /**
165
+ * Install (or, with `undefined`, remove) the dynamic tool provider for a
166
+ * profile. Idempotent — re-installing swaps the provider. Drops the pagination
167
+ * caches so the next `tools/list` recomputes against the new provider.
168
+ */
169
+ export function setProfileToolProvider(profile: McpProfile, provider: ProfileToolProvider | undefined): void {
170
+ if (provider) profileProviders.set(profile, provider);
171
+ else profileProviders.delete(profile);
172
+ sessionListCache.clear();
173
+ }
174
+
142
175
  export function addTool(def: ToolDef): void {
143
176
  if (!def?.name) throw new Error('addTool: name is required');
144
177
  registry.set(def.name, def);
@@ -152,8 +185,33 @@ export function removeTool(name: string): boolean {
152
185
  return existed;
153
186
  }
154
187
 
155
- export function getTool(name: string): ToolDef | undefined {
156
- return registry.get(name);
188
+ /**
189
+ * Resolve a tool by name for `tools/call`. Pass the caller's `profile` so
190
+ * resolution is profile-scoped: the flat name→def registry is a single
191
+ * namespace shared by both profiles, so without scoping a static tool in one
192
+ * profile shadows a same-named provider tool in another — the shadowed tool
193
+ * then lists but is uncallable (the transport rejects the wrong-profile def).
194
+ *
195
+ * With a profile: a static registration of that profile wins over its provider
196
+ * entry (curated tool overrides a generic same-named provider tool), then the
197
+ * profile's provider, then any static registration by name (harmless — the
198
+ * transport re-checks `tool.profile`). Without a profile (internal/tests):
199
+ * static registry first, then any provider.
200
+ */
201
+ export function getTool(name: string, profile?: McpProfile): ToolDef | undefined {
202
+ const statik = registry.get(name);
203
+ if (profile !== undefined) {
204
+ if (statik?.profile === profile) return statik;
205
+ const def = profileProviders.get(profile)?.get(name);
206
+ if (def) return def;
207
+ return statik;
208
+ }
209
+ if (statik) return statik;
210
+ for (const provider of profileProviders.values()) {
211
+ const def = provider.get(name);
212
+ if (def) return def;
213
+ }
214
+ return undefined;
157
215
  }
158
216
 
159
217
  /**
@@ -199,6 +257,7 @@ export function clearSessionCache(sessionId: string): void {
199
257
  /** Test seam: drop all registrations. */
200
258
  export function _resetRegistryForTest(): void {
201
259
  registry.clear();
260
+ profileProviders.clear();
202
261
  sessionListCache.clear();
203
262
  }
204
263
 
@@ -258,9 +317,19 @@ export function listTools(args: ListToolsArgs): ListToolsResult {
258
317
  }
259
318
 
260
319
  function computeFilteredList(user: AuthedUser, profile: McpProfile): ToolDescriptor[] {
261
- const out: ToolDescriptor[] = [];
320
+ // Merge the profile's dynamic provider (if any) with its statically-registered
321
+ // tools, deduping by name. Provider entries are inserted first so a static
322
+ // registration of the same name overrides them (curated wins — see getTool).
323
+ const byName = new Map<string, ToolDef>();
324
+ const provider = profileProviders.get(profile);
325
+ if (provider) {
326
+ for (const def of provider.list()) byName.set(def.name, def);
327
+ }
262
328
  for (const def of registry.values()) {
263
- if (def.profile !== profile) continue;
329
+ if (def.profile === profile) byName.set(def.name, def);
330
+ }
331
+ const out: ToolDescriptor[] = [];
332
+ for (const def of byName.values()) {
264
333
  if (!def.visibleTo(user)) continue;
265
334
  out.push({
266
335
  name: def.name,
@@ -163,7 +163,34 @@ interface ToolAnnotationsLike {
163
163
  openWorldHint?: boolean;
164
164
  }
165
165
 
166
- type ResourcesRegistry = Map<string, ResourceRegistryEntry>;
166
+ /**
167
+ * True when `pattern`'s only dynamic segment is a single trailing `:id` param (e.g. `widget/:id`).
168
+ * That is the ONLY shape the verb handlers below bind — `target.id = args.id`, treating `:id` as
169
+ * the record itself — so a differently-named param (`:widgetId`), more than one param, a `*wildcard`
170
+ * segment, or an `:id` that is NOT the final segment (`widget/:id/action`, where the route names a
171
+ * sub-resource, not the widget) would advertise an `id` input the handler never actually threads
172
+ * onto the real segment(s).
173
+ */
174
+ function isSimpleIdRoute(pattern: string): boolean {
175
+ const segments = pattern.split('/');
176
+ if (segments[segments.length - 1] !== ':id') return false;
177
+ let paramCount = 0;
178
+ for (const segment of segments) {
179
+ if (segment.startsWith('*')) return false;
180
+ if (segment.startsWith(':')) {
181
+ if (segment !== ':id') return false;
182
+ paramCount++;
183
+ }
184
+ }
185
+ return paramCount === 1;
186
+ }
187
+
188
+ /** A compiled parameterised route (e.g. `/widget/:id`), stored outside the base Map. */
189
+ interface ParamRouteEntry {
190
+ pattern: string;
191
+ entry: ResourceRegistryEntry;
192
+ }
193
+ type ResourcesRegistry = Map<string, ResourceRegistryEntry> & { paramRoutes?: ParamRouteEntry[] };
167
194
  type RequestTargetCtor = new () => Record<string, unknown> & {
168
195
  conditions?: unknown[];
169
196
  operator?: string;
@@ -228,9 +255,15 @@ function loadRequestTarget(): RequestTargetCtor | undefined {
228
255
  *
229
256
  * Falls back to the registration-time class only if the registry lookup fails
230
257
  * (e.g. the entry was removed), so a stale tool still dispatches *something*.
258
+ *
259
+ * Parameterised routes (e.g. `/widget/:id`) live outside the base Map in
260
+ * `paramRoutes`, so a plain `Map.get(path)` always misses for them — checked
261
+ * there too, or the live-dispatch guarantee above silently doesn't apply to
262
+ * param-route resources.
231
263
  */
232
264
  function liveResource(path: string, fallback: ResourceClassLike): ResourceClassLike {
233
- const entry = loadResources()?.get(path);
265
+ const registry = loadResources();
266
+ const entry = registry?.get(path) ?? registry?.paramRoutes?.find((route) => route.pattern === path)?.entry;
234
267
  return (entry?.Resource as ResourceClassLike | undefined) ?? fallback;
235
268
  }
236
269
 
@@ -953,8 +986,7 @@ function makeCustomResourceReader(path: string, capturedClass: ResourceClassLike
953
986
  const Ctor = ResourceClass as unknown as new (id: unknown, ctx: unknown) => Record<string, unknown>;
954
987
  const instance = new Ctor(undefined, buildContext(context.user));
955
988
  const method = instance[methodName] as
956
- | ((p: Record<string, string>, ctx: ResourceReadContext) => CustomResourceReadResult)
957
- | undefined;
989
+ ((p: Record<string, string>, ctx: ResourceReadContext) => CustomResourceReadResult) | undefined;
958
990
  if (typeof method !== 'function') {
959
991
  throw new Error(`method '${methodName}' is not a function on the constructed Resource`);
960
992
  }
@@ -1107,22 +1139,45 @@ function buildApplicationTools(resources: ResourcesRegistry): void {
1107
1139
  const claimedSuffixes = new Set<string>();
1108
1140
  let toolsRegistered = 0;
1109
1141
  let resourcesConsidered = 0;
1110
- for (const [path, entry] of resources) {
1142
+
1143
+ // How much of a param route the GENERATED verb handlers can bind:
1144
+ // 'id' — single-`:id` routes: get/update/patch/delete bind `target.id = args.id`,
1145
+ // but makeCreateHandler forces `target.isCollection = true` (never binds id)
1146
+ // and makeSearchHandler is collection-scoped, so create/search are dropped.
1147
+ // 'none' — multi-segment/named-wildcard routes: no generated handler can bind the
1148
+ // segments yet, so ALL generated verbs are dropped. Author-defined
1149
+ // mcpTools/mcpPrompts carry their own schemas and handler methods, so they
1150
+ // register regardless of binding mode.
1151
+ const considerEntry = (
1152
+ path: string,
1153
+ entry: ResourceRegistryEntry | undefined,
1154
+ paramBinding?: 'id' | 'none'
1155
+ ): void => {
1156
+ if (!entry) return;
1111
1157
  resourcesConsidered++;
1112
- if (!shouldEnumerate(entry)) continue;
1158
+ if (!shouldEnumerate(entry)) return;
1113
1159
  const ResourceClass = entry.Resource;
1114
1160
  // @hidden type-level: suppress the Resource from MCP tool listing entirely.
1115
1161
  // Data remains accessible via direct query/RBAC; only descriptive surfaces drop it.
1116
1162
  if (ResourceClass?.hidden === true) {
1117
1163
  harperLogger.trace(`MCP application: '/${path}' suppressed from tool listing (@hidden)`);
1118
- continue;
1164
+ return;
1119
1165
  }
1120
1166
  const verbs = detectVerbs(ResourceClass);
1167
+ if (paramBinding === 'id') {
1168
+ verbs.search = false;
1169
+ verbs.create = false;
1170
+ } else if (paramBinding === 'none') {
1171
+ harperLogger.trace(
1172
+ `MCP application: '/${path}' generated verb tools skipped — multi-segment/named-wildcard binding not yet supported`
1173
+ );
1174
+ verbs.get = verbs.search = verbs.create = verbs.updatePut = verbs.updatePatch = verbs.delete = false;
1175
+ }
1121
1176
  const hasVerbs = verbs.get || verbs.search || verbs.create || verbs.updatePut || verbs.updatePatch || verbs.delete;
1122
1177
  const hasCustomTools = Array.isArray(ResourceClass?.mcpTools) && ResourceClass.mcpTools.length > 0;
1123
1178
  const hasCustomPrompts = Array.isArray(ResourceClass?.mcpPrompts) && ResourceClass.mcpPrompts.length > 0;
1124
1179
  const hasCustomResources = Array.isArray(ResourceClass?.mcpResources) && ResourceClass.mcpResources.length > 0;
1125
- if (!hasVerbs && !hasCustomTools && !hasCustomPrompts && !hasCustomResources) continue;
1180
+ if (!hasVerbs && !hasCustomTools && !hasCustomPrompts && !hasCustomResources) return;
1126
1181
  const databaseName = ResourceClass?.databaseName;
1127
1182
  const tableName = ResourceClass?.tableName;
1128
1183
  const suffix = uniqueSuffix(path, databaseName, claimedSuffixes);
@@ -1142,7 +1197,21 @@ function buildApplicationTools(resources: ResourcesRegistry): void {
1142
1197
  toolsRegistered += registerCustomMcpTools(ResourceClass, path);
1143
1198
  registerCustomMcpPrompts(ResourceClass, path);
1144
1199
  registerCustomMcpResources(ResourceClass, path);
1200
+ };
1201
+
1202
+ for (const [path, entry] of resources) considerEntry(path, entry);
1203
+
1204
+ // Parameterised routes (e.g. `/widget/:id`) live OUTSIDE the base Map: Resources.setParamRoute
1205
+ // stores them in `paramRoutes` and returns before the Map insert, so the loop above never sees
1206
+ // them. Without this, a custom resource declaring `static path = '/widget/:id'` produces ZERO MCP
1207
+ // tools — even though it appears in the OpenAPI document, which already iterates `paramRoutes`.
1208
+ // Enumerate them so the tool surface matches the REST surface; the binding mode restricts the
1209
+ // GENERATED verb tools to what their handlers actually bind (see considerEntry), while custom
1210
+ // mcpTools/mcpPrompts register on every route shape.
1211
+ for (const route of resources.paramRoutes ?? []) {
1212
+ considerEntry(route.pattern, route.entry, isSimpleIdRoute(route.pattern) ? 'id' : 'none');
1145
1213
  }
1214
+
1146
1215
  harperLogger.info(
1147
1216
  `MCP application profile: considered ${resourcesConsidered} resource(s), registered ${toolsRegistered} tool(s)`
1148
1217
  );
@@ -1,7 +1,17 @@
1
1
  /**
2
- * Operations-profile tool generation. Walks Harper's `OPERATION_FUNCTION_MAP`
3
- * and registers one MCP tool per operation that survives the
4
- * `mcp.operations.allow` / `deny` filter.
2
+ * Operations-profile tool generation. Exposes one MCP tool per Harper
3
+ * operation that survives the `mcp.operations.allow` / `deny` filter, computed
4
+ * *lazily* by walking Harper's live `OPERATION_FUNCTION_MAP` on each
5
+ * `tools/list` / `tools/call`.
6
+ *
7
+ * The list is computed lazily (not snapshotted at registration) because
8
+ * components register their operations via `server.registerOperation` during
9
+ * `startOnMainThread`, which runs AFTER the MCP operations profile registers
10
+ * (see components/mcp/index.ts → registerMcpProfile). A one-time walk at
11
+ * registration time would miss every component-registered operation (e.g. the
12
+ * built-in agent's `agent_prompt`), so `mcp.operations.allow` listing them
13
+ * would silently have no effect — #1562. Walking the live map per request
14
+ * removes the ordering dependence and stays consistent as components load.
5
15
  *
6
16
  * The default v1 allow list is read-only and intentionally narrow:
7
17
  * `describe_*`, `list_*`, `search_*`, plus an explicit set of safe
@@ -26,12 +36,21 @@
26
36
  import * as env from '../../../utility/environment/environmentManager.ts';
27
37
  import { CONFIG_PARAMS } from '../../../utility/hdbTerms.ts';
28
38
  import harperLogger from '../../../utility/logging/harper_logger.ts';
29
- import { addTool, canRoleInvokeOperation, type AuthedUser, type ToolResult } from '../toolRegistry.ts';
39
+ import {
40
+ canRoleInvokeOperation,
41
+ setProfileToolProvider,
42
+ type AuthedUser,
43
+ type ProfileToolProvider,
44
+ type ToolDef,
45
+ type ToolResult,
46
+ } from '../toolRegistry.ts';
30
47
  import { OPERATION_INPUT_SCHEMAS, PERMISSIVE_SCHEMA } from './schemas/operations.ts';
31
48
  import { OPERATION_DESCRIPTIONS } from './schemas/operationDescriptions.ts';
32
49
 
33
- // Eager-resolved at module load. The map is built at Harper boot and
34
- // doesn't mutate, so caching here is safe.
50
+ // Resolved from Harper's server-helpers graph on demand. The map is built at
51
+ // Harper boot but keeps mutating as components register operations during
52
+ // `startOnMainThread` — the provider below re-reads it per request rather than
53
+ // snapshotting (#1562).
35
54
  type OperationFunction = (json: object) => unknown | Promise<unknown>;
36
55
  type OperationFunctionMap = Map<string, { operation_function: OperationFunction }>;
37
56
 
@@ -115,6 +134,25 @@ export const DEFAULT_ALLOW: readonly string[] = [
115
134
  'read_audit_log',
116
135
  ];
117
136
 
137
+ /**
138
+ * Operations excluded from the DEFAULT allow surface even when a glob matches
139
+ * (`list_*` would otherwise pull in `list_secrets`). The secrets store is key
140
+ * custody management, not data reads — none of it belongs on an LLM-facing
141
+ * surface by default, same rationale as the sensitive `get_*` exclusions
142
+ * above. Operators can still expose these deliberately via
143
+ * `mcp.operations.allow` (an explicit allow list replaces the default and is
144
+ * not filtered by this set), where the audit-log redaction of
145
+ * `value`/`values`/`envelope` applies.
146
+ */
147
+ export const DEFAULT_EXCLUDED: ReadonlySet<string> = new Set([
148
+ 'set_secret',
149
+ 'grant_secret',
150
+ 'revoke_secret',
151
+ 'list_secrets',
152
+ 'delete_secret',
153
+ 'get_secrets_public_key',
154
+ ]);
155
+
118
156
  /**
119
157
  * Operations that carry `destructiveHint: true` when opted into the allow
120
158
  * list. The hint lets MCP clients surface a confirmation prompt before
@@ -202,7 +240,9 @@ function matchesAny(operation: string, patterns: readonly string[] | undefined):
202
240
  }
203
241
 
204
242
  function isOperationAllowed(operation: string, config: OperationsConfig): boolean {
205
- const allowList = config.allow && config.allow.length > 0 ? config.allow : DEFAULT_ALLOW;
243
+ const usingDefaultAllow = !(config.allow && config.allow.length > 0);
244
+ if (usingDefaultAllow && DEFAULT_EXCLUDED.has(operation)) return false;
245
+ const allowList = usingDefaultAllow ? DEFAULT_ALLOW : config.allow;
206
246
  if (!matchesAny(operation, allowList)) return false;
207
247
  if (matchesAny(operation, config.deny)) return false;
208
248
  return true;
@@ -277,35 +317,65 @@ function makeOperationToolHandler(operationName: string) {
277
317
  }
278
318
 
279
319
  /**
280
- * Idempotent registration: walk the op map, register every operation that
281
- * passes the allow/deny filter. Safe to invoke multiple times — `addTool`
282
- * is `Map.set`-backed.
320
+ * Build the `ToolDef` for one operation. Cheap and stateless — a def is a pure
321
+ * function of the operation name (its schema, description, annotations, RBAC
322
+ * predicate, and handler don't depend on the allow/deny config, which only
323
+ * decides *whether* the op is exposed, checked per request in the provider).
324
+ * `tools/list` isn't a hot path, so the provider rebuilds defs per call rather
325
+ * than caching (no module-level state to leak or stale-cache across tests).
326
+ */
327
+ function buildOperationToolDef(operationName: string): ToolDef {
328
+ const inputSchema = OPERATION_INPUT_SCHEMAS[operationName] ?? PERMISSIVE_SCHEMA;
329
+ const annotations: { readOnlyHint?: boolean; destructiveHint?: boolean; idempotentHint?: boolean } = {};
330
+ if (isReadOnly(operationName)) annotations.readOnlyHint = true;
331
+ if (isDestructive(operationName)) annotations.destructiveHint = true;
332
+ if (isIdempotent(operationName)) annotations.idempotentHint = true;
333
+ return {
334
+ name: operationName,
335
+ description: buildDescription(operationName, operationName in OPERATION_INPUT_SCHEMAS),
336
+ inputSchema,
337
+ profile: 'operations',
338
+ ...(Object.keys(annotations).length > 0 ? { annotations } : {}),
339
+ visibleTo: (user) => canRoleInvokeOperation(user, operationName),
340
+ handler: makeOperationToolHandler(operationName),
341
+ };
342
+ }
343
+
344
+ /**
345
+ * Lazy operations-profile tool provider. Consulted by the registry on every
346
+ * `tools/list` / `tools/call`, so it reflects the live `OPERATION_FUNCTION_MAP`
347
+ * — including operations registered by components after MCP boot (#1562).
348
+ */
349
+ const operationsToolProvider: ProfileToolProvider = {
350
+ list(): ToolDef[] {
351
+ const opMap = getOperationFunctionMap();
352
+ if (!opMap) {
353
+ harperLogger.warn('MCP operations profile: OPERATION_FUNCTION_MAP not available; no tools listed');
354
+ return [];
355
+ }
356
+ const config = getOperationsConfig();
357
+ const defs: ToolDef[] = [];
358
+ for (const operationName of opMap.keys()) {
359
+ if (!isOperationAllowed(operationName, config)) continue;
360
+ defs.push(buildOperationToolDef(operationName));
361
+ }
362
+ return defs;
363
+ },
364
+ get(operationName: string): ToolDef | undefined {
365
+ const opMap = getOperationFunctionMap();
366
+ if (!opMap || !opMap.has(operationName)) return undefined;
367
+ if (!isOperationAllowed(operationName, getOperationsConfig())) return undefined;
368
+ return buildOperationToolDef(operationName);
369
+ },
370
+ };
371
+
372
+ /**
373
+ * Install the operations-profile tool provider. The provider is walked lazily
374
+ * per request rather than snapshotting the op map here, so component operations
375
+ * registered after this runs still surface once allow-listed (#1562).
376
+ * Idempotent — re-installing the provider is a no-op swap.
283
377
  */
284
378
  export function registerOperationsTools(): void {
285
- const opMap = getOperationFunctionMap();
286
- if (!opMap) {
287
- harperLogger.warn('MCP operations profile: OPERATION_FUNCTION_MAP not available; no tools registered');
288
- return;
289
- }
290
- const config = getOperationsConfig();
291
- let registered = 0;
292
- for (const operationName of opMap.keys()) {
293
- if (!isOperationAllowed(operationName, config)) continue;
294
- const inputSchema = OPERATION_INPUT_SCHEMAS[operationName] ?? PERMISSIVE_SCHEMA;
295
- const annotations: { readOnlyHint?: boolean; destructiveHint?: boolean; idempotentHint?: boolean } = {};
296
- if (isReadOnly(operationName)) annotations.readOnlyHint = true;
297
- if (isDestructive(operationName)) annotations.destructiveHint = true;
298
- if (isIdempotent(operationName)) annotations.idempotentHint = true;
299
- addTool({
300
- name: operationName,
301
- description: buildDescription(operationName, operationName in OPERATION_INPUT_SCHEMAS),
302
- inputSchema,
303
- profile: 'operations',
304
- ...(Object.keys(annotations).length > 0 ? { annotations } : {}),
305
- visibleTo: (user) => canRoleInvokeOperation(user, operationName),
306
- handler: makeOperationToolHandler(operationName),
307
- });
308
- registered++;
309
- }
310
- harperLogger.info(`MCP operations profile: registered ${registered} tool(s)`);
379
+ setProfileToolProvider('operations', operationsToolProvider);
380
+ harperLogger.info('MCP operations profile: lazy tool provider installed');
311
381
  }
@@ -86,7 +86,7 @@ export const OPERATION_DESCRIPTIONS: Record<string, string> = {
86
86
  'Returns one background job by id, with status and result payload (export, backup, deploy, restart, csv loads).',
87
87
  // get_status: server/status/index.ts:104 — Status KV entry; aggregated when id omitted.
88
88
  get_status:
89
- 'Returns one entry from the in-memory status KV that components publish health and progress to. With no id, returns aggregated status across threads. Safe to poll. Use system_information for server-level health.',
89
+ 'Returns one entry from the in-memory status KV that components publish health and progress to. With no id, returns aggregated status across threads; pass middleware:true to also include the resolved HTTP/upgrade/WebSocket middleware chain order per port. Safe to poll. Use system_information for server-level health.',
90
90
  // get_analytics: resources/analytics/read.ts:44 — Metric series read with filtering + windowing.
91
91
  get_analytics:
92
92
  'Returns analytics metric values with optional attribute filtering, time windowing, and result coalescing. Pair with list_metrics to discover available metrics.',
@@ -192,15 +192,15 @@ export const OPERATION_DESCRIPTIONS: Record<string, string> = {
192
192
  // drop_role: security/role.ts:126 — Delete a role; refuses if assigned.
193
193
  drop_role:
194
194
  'Deletes a role. Refused if any active user is still assigned to the role — drop or reassign those users first.',
195
- // create_authentication_tokens: security/tokenAuthentication.ts:86 — Mint operation + refresh token pair.
195
+ // create_authentication_tokens: security/tokenAuthentication.ts:108 — Mint operation + refresh token pair, or (purpose: 'login') a login-exchange token.
196
196
  create_authentication_tokens:
197
- 'Creates a JWT operation token and a refresh token after validating credentials. Stores the refresh token on the user record.',
197
+ "Creates a JWT operation token and a refresh token after validating credentials. Stores the refresh token on the user record. With purpose: 'login', instead mints a single short-lived login-exchange token (not usable as a Bearer API credential) intended for the `login` operation.",
198
198
  // refresh_operation_token: security/tokenAuthentication.ts:171 — Mint new operation token from refresh.
199
199
  refresh_operation_token:
200
200
  'Issues a new operation token using a valid refresh token, without re-authenticating with username/password.',
201
201
  // login: security/auth.ts:371 — Session-based login.
202
202
  login:
203
- 'Authenticates the caller and creates a session entry. Requires sessions to be enabled in Harper configuration.',
203
+ 'Authenticates the caller and creates a session entry. Accepts username/password, or a `token` from create_authentication_tokens with purpose: "login" to exchange it for the session cookie. Requires sessions to be enabled in Harper configuration.',
204
204
  // logout: security/auth.ts:381 — Session-based logout.
205
205
  logout: "Clears the caller's session. Requires sessions to be enabled.",
206
206
 
@@ -268,6 +268,11 @@ export const OPERATION_INPUT_SCHEMAS: Record<string, object> = {
268
268
  type: 'string',
269
269
  description: 'Status entry id. When omitted, returns aggregated status across threads.',
270
270
  },
271
+ middleware: {
272
+ type: 'boolean',
273
+ description:
274
+ 'When true (and no id), include the resolved HTTP/upgrade/WebSocket middleware chain order per port.',
275
+ },
271
276
  },
272
277
  },
273
278
  get_analytics: {
@@ -30,7 +30,8 @@ import { emitAuditEntry } from './audit.ts';
30
30
  import { emitMcpLogToSession, isValidMcpLogLevel, setSessionLogLevel } from './logging.ts';
31
31
  import { decodeCursor } from './pagination.ts';
32
32
  import { seedSessionSnapshot } from './listChanged.ts';
33
- import { tryAdmit } from './rateLimit.ts';
33
+ import { tryAdmit, resolveClientIdentity } from './rateLimit.ts';
34
+ import { checkDurableQuota } from './quota.ts';
34
35
  import { deleteSession, loadSession, saveSession, touchSession, type McpSessionRecord } from './session.ts';
35
36
  import { listResources, listResourceTemplates, readResource, completeResourceArgument } from './resources.ts';
36
37
  import { ensureApplicationToolsFresh } from './tools/application.ts';
@@ -84,6 +85,12 @@ export interface NormRequest {
84
85
  */
85
86
  userObject?: AuthedUser;
86
87
  profile: McpProfile;
88
+ /**
89
+ * Client socket IP, for per-client rate limiting and the durable quota
90
+ * hook (#1610). Adapters populate from `request.ip`; identity resolution
91
+ * (socket vs trusted header) happens in `resolveClientIdentity`.
92
+ */
93
+ clientIp?: string;
87
94
  }
88
95
 
89
96
  export interface NormResponse {
@@ -498,7 +505,7 @@ async function dispatchToolsCall(
498
505
  if (!name) {
499
506
  return jsonResponse(200, buildError(messageId, ERROR_CODES.INVALID_PARAMS, 'tools/call requires params.name'));
500
507
  }
501
- const tool = getTool(name);
508
+ const tool = getTool(name, request.profile);
502
509
  if (!tool || tool.profile !== request.profile) {
503
510
  return jsonResponse(200, buildError(messageId, ERROR_CODES.METHOD_NOT_FOUND, `Unknown tool: ${name}`));
504
511
  }
@@ -513,8 +520,11 @@ async function dispatchToolsCall(
513
520
 
514
521
  // Rate limit check — admit-or-deny BEFORE invoking the handler. Failures
515
522
  // surface as `isError: true` with `kind: 'rate_limited'` (NOT a JSON-RPC
516
- // error) so the LLM sees and can back off / try later.
517
- const decision = tryAdmit(session.id, name, request.profile);
523
+ // error) so the LLM sees and can back off / try later. Client identity
524
+ // engages the per-client bucket, the scope that survives session cycling
525
+ // by anonymous clients (#1610).
526
+ const clientIdentity = resolveClientIdentity(request.headers, request.clientIp, request.profile);
527
+ const decision = tryAdmit(session.id, name, request.profile, clientIdentity);
518
528
  if (!decision.allowed) {
519
529
  // Non-strict tsconfig doesn't narrow the discriminated union here.
520
530
  const denied = decision as { allowed: false; reason: string };
@@ -553,6 +563,47 @@ async function dispatchToolsCall(
553
563
  return jsonResponse(200, buildSuccess(messageId, toolResult));
554
564
  }
555
565
 
566
+ // Durable quota hook (#1610): operator-implemented policy behind config
567
+ // (e.g. a persisted per-IP daily counter). Runs AFTER the cheap in-memory
568
+ // admit so a rate-limited client can't spam the (possibly table-backed)
569
+ // hook, and BEFORE the handler. Fail-closed; see quota.ts.
570
+ const quota = await checkDurableQuota({
571
+ identity: clientIdentity,
572
+ tool: name,
573
+ user,
574
+ profile: request.profile,
575
+ sessionId: session.id,
576
+ });
577
+ if (quota.allowed === false) {
578
+ decision.release();
579
+ const toolResult: ToolResult = {
580
+ isError: true,
581
+ content: [
582
+ {
583
+ type: 'text',
584
+ text: JSON.stringify({
585
+ kind: 'quota_exceeded',
586
+ tool: name,
587
+ message: quota.message ?? 'MCP quota exceeded',
588
+ ...(quota.retryAfterSeconds !== undefined ? { retryAfterSeconds: quota.retryAfterSeconds } : {}),
589
+ }),
590
+ },
591
+ ],
592
+ };
593
+ emitAuditEntry({
594
+ timestamp: new Date(callStartedAt).toISOString(),
595
+ profile: request.profile,
596
+ sessionId: session.id,
597
+ tool: name,
598
+ user: user.username ?? request.user,
599
+ args: args as object,
600
+ status: 'quota_exceeded',
601
+ durationMs: 0,
602
+ });
603
+ emitMcpLogToSession(session.id, 'notice', { kind: 'quota_exceeded', tool: name }, 'mcp.quota');
604
+ return jsonResponse(200, buildSuccess(messageId, toolResult));
605
+ }
606
+
556
607
  // Per-call cancellation (#1349 §3.3): an inbound `notifications/cancelled`
557
608
  // referencing this request id aborts `signal`. Registered for the call's
558
609
  // lifetime, removed in `finally`.
@@ -147,7 +147,7 @@ export class OllamaBackend implements ModelBackend {
147
147
  signal,
148
148
  });
149
149
  if (!res.ok) {
150
- throw new OllamaBackendError(`Ollama ${path} returned HTTP ${res.status}`);
150
+ throw new OllamaBackendError(`Ollama ${path} returned HTTP ${res.status}`, res.status);
151
151
  }
152
152
  return res;
153
153
  }
@@ -169,9 +169,13 @@ export function registerOllamaBackend(args: {
169
169
  }
170
170
 
171
171
  export class OllamaBackendError extends ServerError {
172
- constructor(message: string) {
172
+ /** HTTP status returned by the upstream provider, when the failure came from an HTTP response.
173
+ * Distinct from ServerError's statusCode, which is Harper's own response status (#1593). */
174
+ declare upstreamStatus?: number;
175
+ constructor(message: string, upstreamStatus?: number) {
173
176
  super(message);
174
177
  this.name = 'OllamaBackendError';
178
+ if (upstreamStatus !== undefined) this.upstreamStatus = upstreamStatus;
175
179
  }
176
180
  }
177
181
 
@@ -262,7 +262,10 @@ export class OpenAIBackend implements ModelBackend {
262
262
  // doesn't leak request content. Cap length defensively against a
263
263
  // misbehaving compat shim. Falls back to status-only if the body
264
264
  // isn't JSON or doesn't have the envelope.
265
- throw new OpenAIBackendError(`OpenAI ${path} returned HTTP ${res.status}${await readErrorSuffix(res)}`);
265
+ throw new OpenAIBackendError(
266
+ `OpenAI ${path} returned HTTP ${res.status}${await readErrorSuffix(res)}`,
267
+ res.status
268
+ );
266
269
  }
267
270
  return res;
268
271
  }
@@ -306,9 +309,13 @@ export function registerOpenAIBackend(args: {
306
309
  }
307
310
 
308
311
  export class OpenAIBackendError extends ServerError {
309
- constructor(message: string) {
312
+ /** HTTP status returned by the upstream provider, when the failure came from an HTTP response.
313
+ * Distinct from ServerError's statusCode, which is Harper's own response status (#1593). */
314
+ declare upstreamStatus?: number;
315
+ constructor(message: string, upstreamStatus?: number) {
310
316
  super(message);
311
317
  this.name = 'OpenAIBackendError';
318
+ if (upstreamStatus !== undefined) this.upstreamStatus = upstreamStatus;
312
319
  }
313
320
  }
314
321