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.
- package/agent/operations.ts +13 -16
- package/components/Application.ts +188 -7
- package/components/ApplicationScope.ts +3 -0
- package/components/Scope.ts +12 -0
- package/components/anthropic/index.ts +9 -2
- package/components/bedrock/index.ts +25 -2
- package/components/componentLoader.ts +56 -7
- package/components/componentSecrets.ts +451 -0
- package/components/deploymentRecorder.ts +7 -1
- package/components/mcp/adapters/fastify.ts +3 -0
- package/components/mcp/adapters/harperHttp.ts +3 -0
- package/components/mcp/audit.ts +21 -6
- package/components/mcp/customResourceRegistry.ts +17 -38
- package/components/mcp/listChanged.ts +37 -20
- package/components/mcp/quota.ts +145 -0
- package/components/mcp/rateLimit.ts +152 -14
- package/components/mcp/resources.ts +2 -4
- package/components/mcp/toolRegistry.ts +73 -4
- package/components/mcp/tools/application.ts +77 -8
- package/components/mcp/tools/operations.ts +106 -36
- package/components/mcp/tools/schemas/operationDescriptions.ts +4 -4
- package/components/mcp/tools/schemas/operations.ts +5 -0
- package/components/mcp/transport.ts +55 -4
- package/components/ollama/index.ts +6 -2
- package/components/openai/index.ts +9 -2
- package/components/operations.js +204 -10
- package/components/operationsValidation.js +173 -0
- package/components/secretOperations.ts +498 -0
- package/config/configUtils.ts +25 -1
- package/config-app.schema.json +81 -0
- package/config-root.schema.json +8 -1
- package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -3
- package/dataLayer/readAuditLog.ts +14 -0
- package/dataLayer/schemaDescribe.ts +1 -0
- package/dist/agent/operations.d.ts +7 -4
- package/dist/agent/operations.js +13 -15
- package/dist/agent/operations.js.map +1 -1
- package/dist/components/Application.d.ts +25 -2
- package/dist/components/Application.js +166 -7
- package/dist/components/Application.js.map +1 -1
- package/dist/components/ApplicationScope.d.ts +2 -0
- package/dist/components/ApplicationScope.js +3 -0
- package/dist/components/ApplicationScope.js.map +1 -1
- package/dist/components/Scope.d.ts +8 -0
- package/dist/components/Scope.js +11 -0
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/anthropic/index.d.ts +4 -1
- package/dist/components/anthropic/index.js +4 -2
- package/dist/components/anthropic/index.js.map +1 -1
- package/dist/components/bedrock/index.d.ts +4 -1
- package/dist/components/bedrock/index.js +22 -2
- package/dist/components/bedrock/index.js.map +1 -1
- package/dist/components/componentLoader.d.ts +2 -0
- package/dist/components/componentLoader.js +52 -10
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/componentSecrets.d.ts +63 -0
- package/dist/components/componentSecrets.js +405 -0
- package/dist/components/componentSecrets.js.map +1 -0
- package/dist/components/deploymentRecorder.d.ts +3 -0
- package/dist/components/deploymentRecorder.js +4 -3
- package/dist/components/deploymentRecorder.js.map +1 -1
- package/dist/components/mcp/adapters/fastify.d.ts +2 -0
- package/dist/components/mcp/adapters/fastify.js +1 -0
- package/dist/components/mcp/adapters/fastify.js.map +1 -1
- package/dist/components/mcp/adapters/harperHttp.d.ts +2 -0
- package/dist/components/mcp/adapters/harperHttp.js +1 -0
- package/dist/components/mcp/adapters/harperHttp.js.map +1 -1
- package/dist/components/mcp/audit.d.ts +2 -2
- package/dist/components/mcp/audit.js +20 -5
- package/dist/components/mcp/audit.js.map +1 -1
- package/dist/components/mcp/customResourceRegistry.d.ts +5 -12
- package/dist/components/mcp/customResourceRegistry.js +15 -30
- package/dist/components/mcp/customResourceRegistry.js.map +1 -1
- package/dist/components/mcp/listChanged.d.ts +7 -2
- package/dist/components/mcp/listChanged.js +28 -0
- package/dist/components/mcp/listChanged.js.map +1 -1
- package/dist/components/mcp/quota.d.ts +31 -0
- package/dist/components/mcp/quota.js +155 -0
- package/dist/components/mcp/quota.js.map +1 -0
- package/dist/components/mcp/rateLimit.d.ts +19 -3
- package/dist/components/mcp/rateLimit.js +129 -13
- package/dist/components/mcp/rateLimit.js.map +1 -1
- package/dist/components/mcp/resources.js.map +1 -1
- package/dist/components/mcp/toolRegistry.d.ts +39 -1
- package/dist/components/mcp/toolRegistry.js +60 -5
- package/dist/components/mcp/toolRegistry.js.map +1 -1
- package/dist/components/mcp/tools/application.d.ts +8 -1
- package/dist/components/mcp/tools/application.js +65 -5
- package/dist/components/mcp/tools/application.js.map +1 -1
- package/dist/components/mcp/tools/operations.d.ts +15 -3
- package/dist/components/mcp/tools/operations.js +99 -38
- package/dist/components/mcp/tools/operations.js.map +1 -1
- package/dist/components/mcp/tools/schemas/operationDescriptions.js +4 -4
- package/dist/components/mcp/tools/schemas/operationDescriptions.js.map +1 -1
- package/dist/components/mcp/tools/schemas/operations.js +4 -0
- package/dist/components/mcp/tools/schemas/operations.js.map +1 -1
- package/dist/components/mcp/transport.d.ts +6 -0
- package/dist/components/mcp/transport.js +47 -3
- package/dist/components/mcp/transport.js.map +1 -1
- package/dist/components/ollama/index.d.ts +4 -1
- package/dist/components/ollama/index.js +4 -2
- package/dist/components/ollama/index.js.map +1 -1
- package/dist/components/openai/index.d.ts +4 -1
- package/dist/components/openai/index.js +4 -2
- package/dist/components/openai/index.js.map +1 -1
- package/dist/components/operations.d.ts +39 -0
- package/dist/components/operations.js +194 -2
- package/dist/components/operations.js.map +1 -1
- package/dist/components/operationsValidation.d.ts +38 -0
- package/dist/components/operationsValidation.js +155 -0
- package/dist/components/operationsValidation.js.map +1 -1
- package/dist/components/secretOperations.d.ts +113 -0
- package/dist/components/secretOperations.js +475 -0
- package/dist/components/secretOperations.js.map +1 -0
- package/dist/config/configUtils.d.ts +4 -1
- package/dist/config/configUtils.js +18 -1
- package/dist/config/configUtils.js.map +1 -1
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -3
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js.map +1 -1
- package/dist/dataLayer/readAuditLog.js +11 -0
- package/dist/dataLayer/readAuditLog.js.map +1 -1
- package/dist/dataLayer/schemaDescribe.js +2 -0
- package/dist/dataLayer/schemaDescribe.js.map +1 -1
- package/dist/globals.d.ts +1 -1
- package/dist/globals.js +1 -0
- package/dist/globals.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/json/systemSchema.json +38 -0
- package/dist/resources/DatabaseTransaction.d.ts +31 -3
- package/dist/resources/DatabaseTransaction.js +190 -29
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/LMDBTransaction.d.ts +1 -1
- package/dist/resources/LMDBTransaction.js +43 -5
- package/dist/resources/LMDBTransaction.js.map +1 -1
- package/dist/resources/PrimaryRocksDatabase.d.ts +51 -0
- package/dist/resources/PrimaryRocksDatabase.js +194 -0
- package/dist/resources/PrimaryRocksDatabase.js.map +1 -0
- package/dist/resources/RecordEncoder.d.ts +1 -0
- package/dist/resources/RecordEncoder.js +81 -75
- package/dist/resources/RecordEncoder.js.map +1 -1
- package/dist/resources/RequestTarget.d.ts +19 -0
- package/dist/resources/RequestTarget.js.map +1 -1
- package/dist/resources/Resource.d.ts +0 -1
- package/dist/resources/Resource.js +103 -16
- package/dist/resources/Resource.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +6 -2
- package/dist/resources/ResourceInterface.js.map +1 -1
- package/dist/resources/Resources.js.map +1 -1
- package/dist/resources/Table.d.ts +6 -4
- package/dist/resources/Table.js +247 -24
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/read.js +25 -28
- package/dist/resources/analytics/read.js.map +1 -1
- package/dist/resources/blob.js +165 -22
- package/dist/resources/blob.js.map +1 -1
- package/dist/resources/crdt.d.ts +2 -0
- package/dist/resources/crdt.js +45 -36
- package/dist/resources/crdt.js.map +1 -1
- package/dist/resources/databases.d.ts +1 -0
- package/dist/resources/databases.js +134 -7
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/graphql.js +8 -0
- package/dist/resources/graphql.js.map +1 -1
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +35 -5
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +239 -14
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
- package/dist/resources/jsResource.js +6 -0
- package/dist/resources/jsResource.js.map +1 -1
- package/dist/resources/loadEnv.js +30 -3
- package/dist/resources/loadEnv.js.map +1 -1
- package/dist/resources/login.js +2 -2
- package/dist/resources/login.js.map +1 -1
- package/dist/resources/models/Models.js +14 -3
- package/dist/resources/models/Models.js.map +1 -1
- package/dist/resources/models/embedHook.js +10 -2
- package/dist/resources/models/embedHook.js.map +1 -1
- package/dist/resources/models/openaiStream.d.ts +56 -0
- package/dist/resources/models/openaiStream.js +94 -0
- package/dist/resources/models/openaiStream.js.map +1 -0
- package/dist/resources/replayLogs.js +4 -3
- package/dist/resources/replayLogs.js.map +1 -1
- package/dist/resources/search.d.ts +1 -1
- package/dist/resources/search.js +104 -11
- package/dist/resources/search.js.map +1 -1
- package/dist/resources/secretDecryptor.d.ts +54 -0
- package/dist/resources/secretDecryptor.js +131 -0
- package/dist/resources/secretDecryptor.js.map +1 -0
- package/dist/resources/tracked.js +15 -5
- package/dist/resources/tracked.js.map +1 -1
- package/dist/security/auth.js +71 -8
- package/dist/security/auth.js.map +1 -1
- package/dist/security/jsLoader.js +11 -0
- package/dist/security/jsLoader.js.map +1 -1
- package/dist/security/tokenAuthentication.d.ts +9 -1
- package/dist/security/tokenAuthentication.js +31 -1
- package/dist/security/tokenAuthentication.js.map +1 -1
- package/dist/server/DurableSubscriptionsSession.d.ts +1 -1
- package/dist/server/DurableSubscriptionsSession.js +8 -4
- package/dist/server/DurableSubscriptionsSession.js.map +1 -1
- package/dist/server/REST.js +28 -9
- package/dist/server/REST.js.map +1 -1
- package/dist/server/fastifyRoutes.js +11 -1
- package/dist/server/fastifyRoutes.js.map +1 -1
- package/dist/server/graphqlQuerying.js +4 -3
- package/dist/server/graphqlQuerying.js.map +1 -1
- package/dist/server/http.d.ts +40 -2
- package/dist/server/http.js +560 -77
- package/dist/server/http.js.map +1 -1
- package/dist/server/itc/serverHandlers.js +64 -0
- package/dist/server/itc/serverHandlers.js.map +1 -1
- package/dist/server/liveSubscriptionAuth.d.ts +12 -0
- package/dist/server/liveSubscriptionAuth.js +140 -0
- package/dist/server/liveSubscriptionAuth.js.map +1 -0
- package/dist/server/loadRootComponents.js +2 -1
- package/dist/server/loadRootComponents.js.map +1 -1
- package/dist/server/middlewareChain.d.ts +44 -1
- package/dist/server/middlewareChain.js +86 -10
- package/dist/server/middlewareChain.js.map +1 -1
- package/dist/server/mqtt.js +2 -1
- package/dist/server/mqtt.js.map +1 -1
- package/dist/server/operationsServer.js +1 -1
- package/dist/server/operationsServer.js.map +1 -1
- package/dist/server/serverHelpers/Headers.d.ts +9 -0
- package/dist/server/serverHelpers/Headers.js +22 -0
- package/dist/server/serverHelpers/Headers.js.map +1 -1
- package/dist/server/serverHelpers/JSONStream.js +3 -3
- package/dist/server/serverHelpers/JSONStream.js.map +1 -1
- package/dist/server/serverHelpers/Request.d.ts +52 -1
- package/dist/server/serverHelpers/Request.js +122 -1
- package/dist/server/serverHelpers/Request.js.map +1 -1
- package/dist/server/serverHelpers/contentTypes.js +3 -8
- package/dist/server/serverHelpers/contentTypes.js.map +1 -1
- package/dist/server/serverHelpers/progressEmitter.d.ts +19 -0
- package/dist/server/serverHelpers/progressEmitter.js +52 -4
- package/dist/server/serverHelpers/progressEmitter.js.map +1 -1
- package/dist/server/serverHelpers/registeredOperations.d.ts +40 -0
- package/dist/server/serverHelpers/registeredOperations.js +286 -0
- package/dist/server/serverHelpers/registeredOperations.js.map +1 -0
- package/dist/server/serverHelpers/serverHandlers.js +11 -5
- package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
- package/dist/server/serverHelpers/serverUtilities.d.ts +1 -0
- package/dist/server/serverHelpers/serverUtilities.js +67 -5
- package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/server/serverHelpers/uwsServer.d.ts +80 -0
- package/dist/server/serverHelpers/uwsServer.js +433 -0
- package/dist/server/serverHelpers/uwsServer.js.map +1 -0
- package/dist/server/static.d.ts +0 -15
- package/dist/server/static.js +161 -2
- package/dist/server/static.js.map +1 -1
- package/dist/server/status/index.d.ts +4 -1
- package/dist/server/status/index.js +64 -4
- package/dist/server/status/index.js.map +1 -1
- package/dist/server/threads/manageThreads.d.ts +10 -0
- package/dist/server/threads/manageThreads.js +107 -4
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/server/threads/socketRouter.js +5 -1
- package/dist/server/threads/socketRouter.js.map +1 -1
- package/dist/server/threads/threadServer.js +92 -14
- package/dist/server/threads/threadServer.js.map +1 -1
- package/dist/upgrade/directives/5-2-0.d.ts +7 -0
- package/dist/upgrade/directives/5-2-0.js +108 -0
- package/dist/upgrade/directives/5-2-0.js.map +1 -0
- package/dist/upgrade/directives/directivesController.js +2 -1
- package/dist/upgrade/directives/directivesController.js.map +1 -1
- package/dist/utility/common_utils.js +5 -0
- package/dist/utility/common_utils.js.map +1 -1
- package/dist/utility/envFile.d.ts +41 -0
- package/dist/utility/envFile.js +221 -0
- package/dist/utility/envFile.js.map +1 -0
- package/dist/utility/globalSchema.d.ts +9 -0
- package/dist/utility/hdbTerms.d.ts +25 -0
- package/dist/utility/hdbTerms.js +31 -0
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/logging/harper_logger.d.ts +21 -0
- package/dist/utility/logging/harper_logger.js +160 -8
- package/dist/utility/logging/harper_logger.js.map +1 -1
- package/dist/utility/logging/logRotator.js +29 -17
- package/dist/utility/logging/logRotator.js.map +1 -1
- package/dist/utility/logging/readLog.d.ts +1 -1
- package/dist/utility/logging/readLog.js +305 -2
- package/dist/utility/logging/readLog.js.map +1 -1
- package/dist/utility/operationPermissions.d.ts +18 -0
- package/dist/utility/operationPermissions.js +35 -1
- package/dist/utility/operationPermissions.js.map +1 -1
- package/dist/utility/operation_authorization.d.ts +17 -0
- package/dist/utility/operation_authorization.js +38 -0
- package/dist/utility/operation_authorization.js.map +1 -1
- package/dist/utility/secretEnvelope.d.ts +30 -0
- package/dist/utility/secretEnvelope.js +94 -0
- package/dist/utility/secretEnvelope.js.map +1 -0
- package/dist/utility/signalling.d.ts +7 -0
- package/dist/utility/signalling.js +16 -0
- package/dist/utility/signalling.js.map +1 -1
- package/dist/validation/configValidator.js +70 -24
- package/dist/validation/configValidator.js.map +1 -1
- package/index.ts +4 -0
- package/json/systemSchema.json +38 -0
- package/npm-shrinkwrap.json +15066 -9347
- package/package.json +13 -8
- package/resources/DESIGN.md +15 -12
- package/resources/DatabaseTransaction.ts +206 -37
- package/resources/LMDBTransaction.ts +43 -9
- package/resources/PrimaryRocksDatabase.ts +201 -0
- package/resources/RecordEncoder.ts +81 -69
- package/resources/RequestTarget.ts +21 -0
- package/resources/Resource.ts +109 -25
- package/resources/ResourceInterface.ts +6 -2
- package/resources/Resources.ts +1 -3
- package/resources/Table.ts +238 -29
- package/resources/analytics/read.ts +25 -30
- package/resources/blob.ts +159 -23
- package/resources/crdt.ts +37 -31
- package/resources/databases.ts +143 -6
- package/resources/graphql.ts +8 -0
- package/resources/indexes/HierarchicalNavigableSmallWorld.ts +251 -17
- package/resources/jsResource.ts +6 -0
- package/resources/loadEnv.ts +34 -3
- package/resources/login.ts +2 -2
- package/resources/models/Models.ts +19 -3
- package/resources/models/embedHook.ts +13 -2
- package/resources/models/openaiStream.ts +133 -0
- package/resources/replayLogs.ts +4 -3
- package/resources/search.ts +113 -11
- package/resources/secretDecryptor.ts +159 -0
- package/resources/tracked.ts +24 -17
- package/security/auth.ts +70 -10
- package/security/jsLoader.ts +12 -0
- package/security/tokenAuthentication.ts +42 -1
- package/server/DESIGN.md +10 -0
- package/server/DurableSubscriptionsSession.ts +8 -4
- package/server/REST.ts +26 -9
- package/server/fastifyRoutes.ts +10 -1
- package/server/graphqlQuerying.ts +4 -3
- package/server/http.ts +563 -77
- package/server/itc/serverHandlers.js +70 -0
- package/server/liveSubscriptionAuth.ts +152 -0
- package/server/loadRootComponents.js +2 -1
- package/server/middlewareChain.ts +107 -17
- package/server/mqtt.ts +2 -1
- package/server/operationsServer.ts +2 -2
- package/server/serverHelpers/Headers.ts +24 -0
- package/server/serverHelpers/JSONStream.ts +3 -3
- package/server/serverHelpers/Request.ts +127 -0
- package/server/serverHelpers/contentTypes.ts +3 -8
- package/server/serverHelpers/progressEmitter.ts +55 -5
- package/server/serverHelpers/registeredOperations.ts +259 -0
- package/server/serverHelpers/serverHandlers.js +11 -5
- package/server/serverHelpers/serverUtilities.ts +88 -5
- package/server/serverHelpers/uwsServer.ts +488 -0
- package/server/static.ts +180 -2
- package/server/status/index.ts +72 -4
- package/server/threads/manageThreads.js +102 -4
- package/server/threads/socketRouter.ts +5 -1
- package/server/threads/threadServer.js +93 -10
- package/studio/web/assets/{Chat-SdD1EZca.js → Chat-Cv_2paZE.js} +2 -2
- package/studio/web/assets/{Chat-SdD1EZca.js.map → Chat-Cv_2paZE.js.map} +1 -1
- package/studio/web/assets/{FloatingChat-GMq6tHK9.js → FloatingChat-CPyPIcVR.js} +4 -4
- package/studio/web/assets/{FloatingChat-GMq6tHK9.js.map → FloatingChat-CPyPIcVR.js.map} +1 -1
- package/studio/web/assets/{applications-B5hhv7uA.js → applications-C0jT2vdZ.js} +2 -2
- package/studio/web/assets/{applications-B5hhv7uA.js.map → applications-C0jT2vdZ.js.map} +1 -1
- package/studio/web/assets/{index-VFcpNWgq.js → index-D_GKOkhn.js} +6 -6
- package/studio/web/assets/index-D_GKOkhn.js.map +1 -0
- package/studio/web/assets/{index.lazy-DgcJojIA.js → index.lazy-CAmCIA65.js} +4 -4
- package/studio/web/assets/{index.lazy-DgcJojIA.js.map → index.lazy-CAmCIA65.js.map} +1 -1
- package/studio/web/assets/{profile-BqN-8vzm.js → profile-CaF-4aZe.js} +2 -2
- package/studio/web/assets/{profile-BqN-8vzm.js.map → profile-CaF-4aZe.js.map} +1 -1
- package/studio/web/assets/{setComponentFile-CHFvFq9u.js → setComponentFile-DLW1DHCt.js} +2 -2
- package/studio/web/assets/{setComponentFile-CHFvFq9u.js.map → setComponentFile-DLW1DHCt.js.map} +1 -1
- package/studio/web/assets/{setup-CQ3vWJSr.js → setup-TGCErIhq.js} +2 -2
- package/studio/web/assets/{setup-CQ3vWJSr.js.map → setup-TGCErIhq.js.map} +1 -1
- package/studio/web/assets/{status-BsW5fjuh.js → status-DG0Maoao.js} +2 -2
- package/studio/web/assets/{status-BsW5fjuh.js.map → status-DG0Maoao.js.map} +1 -1
- package/studio/web/assets/{swagger-ui-react-DITdeB9-.js → swagger-ui-react-8T4SgQ1m.js} +2 -2
- package/studio/web/assets/{swagger-ui-react-DITdeB9-.js.map → swagger-ui-react-8T4SgQ1m.js.map} +1 -1
- package/studio/web/assets/{tsMode-Dyph-OUs.js → tsMode-DJ6Sl24Q.js} +2 -2
- package/studio/web/assets/{tsMode-Dyph-OUs.js.map → tsMode-DJ6Sl24Q.js.map} +1 -1
- package/studio/web/assets/{useEntityRestURL-D0QTUqex.js → useEntityRestURL-JO2mfWTQ.js} +2 -2
- package/studio/web/assets/{useEntityRestURL-D0QTUqex.js.map → useEntityRestURL-JO2mfWTQ.js.map} +1 -1
- package/studio/web/index.html +1 -1
- package/upgrade/directives/5-2-0.ts +82 -0
- package/upgrade/directives/directivesController.ts +2 -1
- package/utility/common_utils.ts +5 -0
- package/utility/envFile.ts +218 -0
- package/utility/hdbTerms.ts +31 -0
- package/utility/logging/harper_logger.ts +156 -8
- package/utility/logging/logRotator.ts +24 -14
- package/utility/logging/readLog.ts +323 -2
- package/utility/operationPermissions.ts +35 -1
- package/utility/operation_authorization.ts +74 -1
- package/utility/secretEnvelope.ts +113 -0
- package/utility/signalling.ts +15 -0
- package/validation/configValidator.ts +78 -25
- 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
|
-
|
|
156
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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))
|
|
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
|
-
|
|
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)
|
|
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.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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 {
|
|
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
|
-
//
|
|
34
|
-
//
|
|
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
|
|
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
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
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
|
-
|
|
286
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|