@opendatalabs/personal-server-ts-core 0.0.1-canary.00328bf
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/dist/api/index.d.ts +408 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +1257 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/raw-envelope.d.ts +3 -0
- package/dist/api/raw-envelope.d.ts.map +1 -0
- package/dist/api/raw-envelope.js +215 -0
- package/dist/api/raw-envelope.js.map +1 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +2 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/request.d.ts +28 -0
- package/dist/auth/request.d.ts.map +1 -0
- package/dist/auth/request.js +106 -0
- package/dist/auth/request.js.map +1 -0
- package/dist/client.d.ts +233 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +243 -0
- package/dist/client.js.map +1 -0
- package/dist/config/defaults.d.ts +12 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +12 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +3 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/paths.d.ts +9 -0
- package/dist/config/paths.d.ts.map +1 -0
- package/dist/config/paths.js +14 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/contracts/access-logs.d.ts +8 -0
- package/dist/contracts/access-logs.d.ts.map +1 -0
- package/dist/contracts/access-logs.js +7 -0
- package/dist/contracts/access-logs.js.map +1 -0
- package/dist/contracts/auth-device.d.ts +70 -0
- package/dist/contracts/auth-device.d.ts.map +1 -0
- package/dist/contracts/auth-device.js +163 -0
- package/dist/contracts/auth-device.js.map +1 -0
- package/dist/contracts/binary.d.ts +66 -0
- package/dist/contracts/binary.d.ts.map +1 -0
- package/dist/contracts/binary.js +123 -0
- package/dist/contracts/binary.js.map +1 -0
- package/dist/contracts/config.d.ts +5 -0
- package/dist/contracts/config.d.ts.map +1 -0
- package/dist/contracts/config.js +30 -0
- package/dist/contracts/config.js.map +1 -0
- package/dist/contracts/data.d.ts +165 -0
- package/dist/contracts/data.d.ts.map +1 -0
- package/dist/contracts/data.js +371 -0
- package/dist/contracts/data.js.map +1 -0
- package/dist/contracts/grants.d.ts +45 -0
- package/dist/contracts/grants.d.ts.map +1 -0
- package/dist/contracts/grants.js +177 -0
- package/dist/contracts/grants.js.map +1 -0
- package/dist/contracts/http.d.ts +21 -0
- package/dist/contracts/http.d.ts.map +1 -0
- package/dist/contracts/http.js +49 -0
- package/dist/contracts/http.js.map +1 -0
- package/dist/contracts/index.d.ts +16 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +10 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/contracts/oauth.d.ts +38 -0
- package/dist/contracts/oauth.d.ts.map +1 -0
- package/dist/contracts/oauth.js +130 -0
- package/dist/contracts/oauth.js.map +1 -0
- package/dist/contracts/sync.d.ts +10 -0
- package/dist/contracts/sync.d.ts.map +1 -0
- package/dist/contracts/sync.js +35 -0
- package/dist/contracts/sync.js.map +1 -0
- package/dist/derivatives/answer-shape.d.ts +83 -0
- package/dist/derivatives/answer-shape.d.ts.map +1 -0
- package/dist/derivatives/answer-shape.js +320 -0
- package/dist/derivatives/answer-shape.js.map +1 -0
- package/dist/derivatives/api.d.ts +48 -0
- package/dist/derivatives/api.d.ts.map +1 -0
- package/dist/derivatives/api.js +374 -0
- package/dist/derivatives/api.js.map +1 -0
- package/dist/derivatives/compute.d.ts +112 -0
- package/dist/derivatives/compute.d.ts.map +1 -0
- package/dist/derivatives/compute.js +476 -0
- package/dist/derivatives/compute.js.map +1 -0
- package/dist/derivatives/e2ee/aad.d.ts +22 -0
- package/dist/derivatives/e2ee/aad.d.ts.map +1 -0
- package/dist/derivatives/e2ee/aad.js +32 -0
- package/dist/derivatives/e2ee/aad.js.map +1 -0
- package/dist/derivatives/e2ee/attestation.d.ts +134 -0
- package/dist/derivatives/e2ee/attestation.d.ts.map +1 -0
- package/dist/derivatives/e2ee/attestation.js +248 -0
- package/dist/derivatives/e2ee/attestation.js.map +1 -0
- package/dist/derivatives/e2ee/index.d.ts +6 -0
- package/dist/derivatives/e2ee/index.d.ts.map +1 -0
- package/dist/derivatives/e2ee/index.js +6 -0
- package/dist/derivatives/e2ee/index.js.map +1 -0
- package/dist/derivatives/e2ee/jcs.d.ts +17 -0
- package/dist/derivatives/e2ee/jcs.d.ts.map +1 -0
- package/dist/derivatives/e2ee/jcs.js +55 -0
- package/dist/derivatives/e2ee/jcs.js.map +1 -0
- package/dist/derivatives/e2ee/phala.d.ts +62 -0
- package/dist/derivatives/e2ee/phala.d.ts.map +1 -0
- package/dist/derivatives/e2ee/phala.js +168 -0
- package/dist/derivatives/e2ee/phala.js.map +1 -0
- package/dist/derivatives/e2ee/suite.d.ts +56 -0
- package/dist/derivatives/e2ee/suite.d.ts.map +1 -0
- package/dist/derivatives/e2ee/suite.js +164 -0
- package/dist/derivatives/e2ee/suite.js.map +1 -0
- package/dist/derivatives/index.d.ts +11 -0
- package/dist/derivatives/index.d.ts.map +1 -0
- package/dist/derivatives/index.js +11 -0
- package/dist/derivatives/index.js.map +1 -0
- package/dist/derivatives/inference.d.ts +151 -0
- package/dist/derivatives/inference.d.ts.map +1 -0
- package/dist/derivatives/inference.js +262 -0
- package/dist/derivatives/inference.js.map +1 -0
- package/dist/derivatives/prompt.d.ts +99 -0
- package/dist/derivatives/prompt.d.ts.map +1 -0
- package/dist/derivatives/prompt.js +281 -0
- package/dist/derivatives/prompt.js.map +1 -0
- package/dist/derivatives/registration.d.ts +56 -0
- package/dist/derivatives/registration.d.ts.map +1 -0
- package/dist/derivatives/registration.js +187 -0
- package/dist/derivatives/registration.js.map +1 -0
- package/dist/derivatives/scheduler.d.ts +114 -0
- package/dist/derivatives/scheduler.d.ts.map +1 -0
- package/dist/derivatives/scheduler.js +331 -0
- package/dist/derivatives/scheduler.js.map +1 -0
- package/dist/derivatives/store.d.ts +14 -0
- package/dist/derivatives/store.d.ts.map +1 -0
- package/dist/derivatives/store.js +79 -0
- package/dist/derivatives/store.js.map +1 -0
- package/dist/derivatives/types.d.ts +102 -0
- package/dist/derivatives/types.d.ts.map +1 -0
- package/dist/derivatives/types.js +29 -0
- package/dist/derivatives/types.js.map +1 -0
- package/dist/errors/catalog.d.ts +125 -0
- package/dist/errors/catalog.d.ts.map +1 -0
- package/dist/errors/catalog.js +198 -0
- package/dist/errors/catalog.js.map +1 -0
- package/dist/errors/pdpp-catalog.d.ts +36 -0
- package/dist/errors/pdpp-catalog.d.ts.map +1 -0
- package/dist/errors/pdpp-catalog.js +82 -0
- package/dist/errors/pdpp-catalog.js.map +1 -0
- package/dist/grants/index.d.ts +5 -0
- package/dist/grants/index.d.ts.map +1 -0
- package/dist/grants/index.js +6 -0
- package/dist/grants/index.js.map +1 -0
- package/dist/grants/pdpp-binding-store.d.ts +58 -0
- package/dist/grants/pdpp-binding-store.d.ts.map +1 -0
- package/dist/grants/pdpp-binding-store.js +97 -0
- package/dist/grants/pdpp-binding-store.js.map +1 -0
- package/dist/grants/pdpp-binding.d.ts +179 -0
- package/dist/grants/pdpp-binding.d.ts.map +1 -0
- package/dist/grants/pdpp-binding.js +214 -0
- package/dist/grants/pdpp-binding.js.map +1 -0
- package/dist/keys/index.d.ts +3 -0
- package/dist/keys/index.d.ts.map +1 -0
- package/dist/keys/index.js +2 -0
- package/dist/keys/index.js.map +1 -0
- package/dist/keys/public-only-account.d.ts +8 -0
- package/dist/keys/public-only-account.d.ts.map +1 -0
- package/dist/keys/public-only-account.js +21 -0
- package/dist/keys/public-only-account.js.map +1 -0
- package/dist/keys/server-account.d.ts +24 -0
- package/dist/keys/server-account.d.ts.map +1 -0
- package/dist/keys/server-account.js +2 -0
- package/dist/keys/server-account.js.map +1 -0
- package/dist/lineage/attestation.d.ts +42 -0
- package/dist/lineage/attestation.d.ts.map +1 -0
- package/dist/lineage/attestation.js +25 -0
- package/dist/lineage/attestation.js.map +1 -0
- package/dist/lineage/gateway.d.ts +105 -0
- package/dist/lineage/gateway.d.ts.map +1 -0
- package/dist/lineage/gateway.js +242 -0
- package/dist/lineage/gateway.js.map +1 -0
- package/dist/lineage/index.d.ts +5 -0
- package/dist/lineage/index.d.ts.map +1 -0
- package/dist/lineage/index.js +5 -0
- package/dist/lineage/index.js.map +1 -0
- package/dist/lineage/lineage.d.ts +141 -0
- package/dist/lineage/lineage.d.ts.map +1 -0
- package/dist/lineage/lineage.js +255 -0
- package/dist/lineage/lineage.js.map +1 -0
- package/dist/logger/index.d.ts +9 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +5 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logging/access-log.d.ts +23 -0
- package/dist/logging/access-log.d.ts.map +1 -0
- package/dist/logging/access-log.js +2 -0
- package/dist/logging/access-log.js.map +1 -0
- package/dist/logging/access-reader.d.ts +14 -0
- package/dist/logging/access-reader.d.ts.map +1 -0
- package/dist/logging/access-reader.js +2 -0
- package/dist/logging/access-reader.js.map +1 -0
- package/dist/mcp/activity.d.ts +68 -0
- package/dist/mcp/activity.d.ts.map +1 -0
- package/dist/mcp/activity.js +111 -0
- package/dist/mcp/activity.js.map +1 -0
- package/dist/mcp/builder-registration.d.ts +24 -0
- package/dist/mcp/builder-registration.d.ts.map +1 -0
- package/dist/mcp/builder-registration.js +89 -0
- package/dist/mcp/builder-registration.js.map +1 -0
- package/dist/mcp/connection-api.d.ts +201 -0
- package/dist/mcp/connection-api.d.ts.map +1 -0
- package/dist/mcp/connection-api.js +506 -0
- package/dist/mcp/connection-api.js.map +1 -0
- package/dist/mcp/grantee.d.ts +52 -0
- package/dist/mcp/grantee.d.ts.map +1 -0
- package/dist/mcp/grantee.js +91 -0
- package/dist/mcp/grantee.js.map +1 -0
- package/dist/mcp/index.d.ts +12 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +12 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/read-client.d.ts +176 -0
- package/dist/mcp/read-client.d.ts.map +1 -0
- package/dist/mcp/read-client.js +579 -0
- package/dist/mcp/read-client.js.map +1 -0
- package/dist/mcp/resources.d.ts +37 -0
- package/dist/mcp/resources.d.ts.map +1 -0
- package/dist/mcp/resources.js +114 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/search/index.d.ts +34 -0
- package/dist/mcp/search/index.d.ts.map +1 -0
- package/dist/mcp/search/index.js +156 -0
- package/dist/mcp/search/index.js.map +1 -0
- package/dist/mcp/server.d.ts +72 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +447 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/session.d.ts +160 -0
- package/dist/mcp/session.d.ts.map +1 -0
- package/dist/mcp/session.js +243 -0
- package/dist/mcp/session.js.map +1 -0
- package/dist/mcp/store.d.ts +13 -0
- package/dist/mcp/store.d.ts.map +1 -0
- package/dist/mcp/store.js +119 -0
- package/dist/mcp/store.js.map +1 -0
- package/dist/mcp/token-expiry.d.ts +38 -0
- package/dist/mcp/token-expiry.d.ts.map +1 -0
- package/dist/mcp/token-expiry.js +55 -0
- package/dist/mcp/token-expiry.js.map +1 -0
- package/dist/mcp/tools.d.ts +95 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +1381 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/mcp/types.d.ts +135 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +24 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/payment/index.d.ts +2 -0
- package/dist/payment/index.d.ts.map +1 -0
- package/dist/payment/index.js +2 -0
- package/dist/payment/index.js.map +1 -0
- package/dist/payment/x402.d.ts +155 -0
- package/dist/payment/x402.d.ts.map +1 -0
- package/dist/payment/x402.js +402 -0
- package/dist/payment/x402.js.map +1 -0
- package/dist/pdpp/approval.d.ts +182 -0
- package/dist/pdpp/approval.d.ts.map +1 -0
- package/dist/pdpp/approval.js +337 -0
- package/dist/pdpp/approval.js.map +1 -0
- package/dist/pdpp/client-identity.d.ts +88 -0
- package/dist/pdpp/client-identity.d.ts.map +1 -0
- package/dist/pdpp/client-identity.js +183 -0
- package/dist/pdpp/client-identity.js.map +1 -0
- package/dist/pdpp/client-metadata.d.ts +95 -0
- package/dist/pdpp/client-metadata.d.ts.map +1 -0
- package/dist/pdpp/client-metadata.js +96 -0
- package/dist/pdpp/client-metadata.js.map +1 -0
- package/dist/pdpp/declaration.d.ts +118 -0
- package/dist/pdpp/declaration.d.ts.map +1 -0
- package/dist/pdpp/declaration.js +549 -0
- package/dist/pdpp/declaration.js.map +1 -0
- package/dist/pdpp/index.d.ts +35 -0
- package/dist/pdpp/index.d.ts.map +1 -0
- package/dist/pdpp/index.js +22 -0
- package/dist/pdpp/index.js.map +1 -0
- package/dist/pdpp/issuance.d.ts +91 -0
- package/dist/pdpp/issuance.d.ts.map +1 -0
- package/dist/pdpp/issuance.js +124 -0
- package/dist/pdpp/issuance.js.map +1 -0
- package/dist/pdpp/pkce.d.ts +53 -0
- package/dist/pdpp/pkce.d.ts.map +1 -0
- package/dist/pdpp/pkce.js +109 -0
- package/dist/pdpp/pkce.js.map +1 -0
- package/dist/pdpp/redirect.d.ts +60 -0
- package/dist/pdpp/redirect.d.ts.map +1 -0
- package/dist/pdpp/redirect.js +125 -0
- package/dist/pdpp/redirect.js.map +1 -0
- package/dist/pdpp/resolve.d.ts +53 -0
- package/dist/pdpp/resolve.d.ts.map +1 -0
- package/dist/pdpp/resolve.js +218 -0
- package/dist/pdpp/resolve.js.map +1 -0
- package/dist/pdpp/review.d.ts +155 -0
- package/dist/pdpp/review.d.ts.map +1 -0
- package/dist/pdpp/review.js +142 -0
- package/dist/pdpp/review.js.map +1 -0
- package/dist/pdpp/selection.d.ts +43 -0
- package/dist/pdpp/selection.d.ts.map +1 -0
- package/dist/pdpp/selection.js +211 -0
- package/dist/pdpp/selection.js.map +1 -0
- package/dist/pdpp/store.d.ts +204 -0
- package/dist/pdpp/store.d.ts.map +1 -0
- package/dist/pdpp/store.js +501 -0
- package/dist/pdpp/store.js.map +1 -0
- package/dist/pdpp/tokens.d.ts +149 -0
- package/dist/pdpp/tokens.d.ts.map +1 -0
- package/dist/pdpp/tokens.js +387 -0
- package/dist/pdpp/tokens.js.map +1 -0
- package/dist/pdpp/types.d.ts +258 -0
- package/dist/pdpp/types.d.ts.map +1 -0
- package/dist/pdpp/types.js +40 -0
- package/dist/pdpp/types.js.map +1 -0
- package/dist/pdpp-version.d.ts +28 -0
- package/dist/pdpp-version.d.ts.map +1 -0
- package/dist/pdpp-version.js +28 -0
- package/dist/pdpp-version.js.map +1 -0
- package/dist/policy/data-read.d.ts +56 -0
- package/dist/policy/data-read.d.ts.map +1 -0
- package/dist/policy/data-read.js +176 -0
- package/dist/policy/data-read.js.map +1 -0
- package/dist/policy/data-write.d.ts +69 -0
- package/dist/policy/data-write.d.ts.map +1 -0
- package/dist/policy/data-write.js +131 -0
- package/dist/policy/data-write.js.map +1 -0
- package/dist/policy/index.d.ts +4 -0
- package/dist/policy/index.d.ts.map +1 -0
- package/dist/policy/index.js +4 -0
- package/dist/policy/index.js.map +1 -0
- package/dist/policy/signed-artifacts.d.ts +17 -0
- package/dist/policy/signed-artifacts.d.ts.map +1 -0
- package/dist/policy/signed-artifacts.js +110 -0
- package/dist/policy/signed-artifacts.js.map +1 -0
- package/dist/ports/index.d.ts +243 -0
- package/dist/ports/index.d.ts.map +1 -0
- package/dist/ports/index.js +7 -0
- package/dist/ports/index.js.map +1 -0
- package/dist/ports/pdpp-auth.d.ts +103 -0
- package/dist/ports/pdpp-auth.d.ts.map +1 -0
- package/dist/ports/pdpp-auth.js +37 -0
- package/dist/ports/pdpp-auth.js.map +1 -0
- package/dist/ports/pdpp-auth.test-utils.d.ts +9 -0
- package/dist/ports/pdpp-auth.test-utils.d.ts.map +1 -0
- package/dist/ports/pdpp-auth.test-utils.js +18 -0
- package/dist/ports/pdpp-auth.test-utils.js.map +1 -0
- package/dist/schemas/server-config.d.ts +190 -0
- package/dist/schemas/server-config.d.ts.map +1 -0
- package/dist/schemas/server-config.js +282 -0
- package/dist/schemas/server-config.js.map +1 -0
- package/dist/signing/index.d.ts +3 -0
- package/dist/signing/index.d.ts.map +1 -0
- package/dist/signing/index.js +3 -0
- package/dist/signing/index.js.map +1 -0
- package/dist/signing/request-signer.d.ts +20 -0
- package/dist/signing/request-signer.d.ts.map +1 -0
- package/dist/signing/request-signer.js +23 -0
- package/dist/signing/request-signer.js.map +1 -0
- package/dist/signing/signer.d.ts +40 -0
- package/dist/signing/signer.d.ts.map +1 -0
- package/dist/signing/signer.js +56 -0
- package/dist/signing/signer.js.map +1 -0
- package/dist/storage/adapters/index.d.ts +4 -0
- package/dist/storage/adapters/index.d.ts.map +1 -0
- package/dist/storage/adapters/index.js +3 -0
- package/dist/storage/adapters/index.js.map +1 -0
- package/dist/storage/adapters/interface.d.ts +57 -0
- package/dist/storage/adapters/interface.d.ts.map +1 -0
- package/dist/storage/adapters/interface.js +2 -0
- package/dist/storage/adapters/interface.js.map +1 -0
- package/dist/storage/adapters/sdk.d.ts +23 -0
- package/dist/storage/adapters/sdk.d.ts.map +1 -0
- package/dist/storage/adapters/sdk.js +73 -0
- package/dist/storage/adapters/sdk.js.map +1 -0
- package/dist/storage/adapters/vana.d.ts +12 -0
- package/dist/storage/adapters/vana.d.ts.map +1 -0
- package/dist/storage/adapters/vana.js +111 -0
- package/dist/storage/adapters/vana.js.map +1 -0
- package/dist/storage/blocks/build.d.ts +20 -0
- package/dist/storage/blocks/build.d.ts.map +1 -0
- package/dist/storage/blocks/build.js +378 -0
- package/dist/storage/blocks/build.js.map +1 -0
- package/dist/storage/blocks/cursor.d.ts +33 -0
- package/dist/storage/blocks/cursor.d.ts.map +1 -0
- package/dist/storage/blocks/cursor.js +142 -0
- package/dist/storage/blocks/cursor.js.map +1 -0
- package/dist/storage/blocks/errors.d.ts +6 -0
- package/dist/storage/blocks/errors.d.ts.map +1 -0
- package/dist/storage/blocks/errors.js +9 -0
- package/dist/storage/blocks/errors.js.map +1 -0
- package/dist/storage/blocks/index.d.ts +6 -0
- package/dist/storage/blocks/index.d.ts.map +1 -0
- package/dist/storage/blocks/index.js +5 -0
- package/dist/storage/blocks/index.js.map +1 -0
- package/dist/storage/blocks/select.d.ts +29 -0
- package/dist/storage/blocks/select.d.ts.map +1 -0
- package/dist/storage/blocks/select.js +120 -0
- package/dist/storage/blocks/select.js.map +1 -0
- package/dist/storage/blocks/types.d.ts +56 -0
- package/dist/storage/blocks/types.d.ts.map +1 -0
- package/dist/storage/blocks/types.js +2 -0
- package/dist/storage/blocks/types.js.map +1 -0
- package/dist/storage/hierarchy/index.d.ts +3 -0
- package/dist/storage/hierarchy/index.d.ts.map +1 -0
- package/dist/storage/hierarchy/index.js +2 -0
- package/dist/storage/hierarchy/index.js.map +1 -0
- package/dist/storage/hierarchy/manager.d.ts +9 -0
- package/dist/storage/hierarchy/manager.d.ts.map +1 -0
- package/dist/storage/hierarchy/manager.js +2 -0
- package/dist/storage/hierarchy/manager.js.map +1 -0
- package/dist/storage/hierarchy/paths.d.ts +11 -0
- package/dist/storage/hierarchy/paths.d.ts.map +1 -0
- package/dist/storage/hierarchy/paths.js +43 -0
- package/dist/storage/hierarchy/paths.js.map +1 -0
- package/dist/storage/index/index.d.ts +3 -0
- package/dist/storage/index/index.d.ts.map +1 -0
- package/dist/storage/index/index.js +2 -0
- package/dist/storage/index/index.js.map +1 -0
- package/dist/storage/index/manager.d.ts +63 -0
- package/dist/storage/index/manager.d.ts.map +1 -0
- package/dist/storage/index/manager.js +2 -0
- package/dist/storage/index/manager.js.map +1 -0
- package/dist/storage/index/schema.d.ts +2 -0
- package/dist/storage/index/schema.d.ts.map +1 -0
- package/dist/storage/index/schema.js +2 -0
- package/dist/storage/index/schema.js.map +1 -0
- package/dist/storage/index/types.d.ts +37 -0
- package/dist/storage/index/types.d.ts.map +1 -0
- package/dist/storage/index/types.js +2 -0
- package/dist/storage/index/types.js.map +1 -0
- package/dist/storage/pdpp-records/cursor.d.ts +27 -0
- package/dist/storage/pdpp-records/cursor.d.ts.map +1 -0
- package/dist/storage/pdpp-records/cursor.js +49 -0
- package/dist/storage/pdpp-records/cursor.js.map +1 -0
- package/dist/storage/pdpp-records/enforcement.d.ts +37 -0
- package/dist/storage/pdpp-records/enforcement.d.ts.map +1 -0
- package/dist/storage/pdpp-records/enforcement.js +79 -0
- package/dist/storage/pdpp-records/enforcement.js.map +1 -0
- package/dist/storage/pdpp-records/index.d.ts +8 -0
- package/dist/storage/pdpp-records/index.d.ts.map +1 -0
- package/dist/storage/pdpp-records/index.js +8 -0
- package/dist/storage/pdpp-records/index.js.map +1 -0
- package/dist/storage/pdpp-records/interface.d.ts +49 -0
- package/dist/storage/pdpp-records/interface.d.ts.map +1 -0
- package/dist/storage/pdpp-records/interface.js +2 -0
- package/dist/storage/pdpp-records/interface.js.map +1 -0
- package/dist/storage/pdpp-records/memory-store.d.ts +9 -0
- package/dist/storage/pdpp-records/memory-store.d.ts.map +1 -0
- package/dist/storage/pdpp-records/memory-store.js +0 -0
- package/dist/storage/pdpp-records/memory-store.js.map +1 -0
- package/dist/storage/pdpp-records/record-key.d.ts +52 -0
- package/dist/storage/pdpp-records/record-key.d.ts.map +1 -0
- package/dist/storage/pdpp-records/record-key.js +97 -0
- package/dist/storage/pdpp-records/record-key.js.map +1 -0
- package/dist/storage/pdpp-records/stream-declaration.d.ts +43 -0
- package/dist/storage/pdpp-records/stream-declaration.d.ts.map +1 -0
- package/dist/storage/pdpp-records/stream-declaration.js +17 -0
- package/dist/storage/pdpp-records/stream-declaration.js.map +1 -0
- package/dist/storage/pdpp-records/types.d.ts +93 -0
- package/dist/storage/pdpp-records/types.d.ts.map +1 -0
- package/dist/storage/pdpp-records/types.js +17 -0
- package/dist/storage/pdpp-records/types.js.map +1 -0
- package/dist/storage/preview.d.ts +12 -0
- package/dist/storage/preview.d.ts.map +1 -0
- package/dist/storage/preview.js +239 -0
- package/dist/storage/preview.js.map +1 -0
- package/dist/sync/cursor.d.ts +10 -0
- package/dist/sync/cursor.d.ts.map +1 -0
- package/dist/sync/cursor.js +2 -0
- package/dist/sync/cursor.js.map +1 -0
- package/dist/sync/data-point-feed.d.ts +35 -0
- package/dist/sync/data-point-feed.d.ts.map +1 -0
- package/dist/sync/data-point-feed.js +122 -0
- package/dist/sync/data-point-feed.js.map +1 -0
- package/dist/sync/data-point-id.d.ts +7 -0
- package/dist/sync/data-point-id.d.ts.map +1 -0
- package/dist/sync/data-point-id.js +16 -0
- package/dist/sync/data-point-id.js.map +1 -0
- package/dist/sync/delete-data-port.d.ts +50 -0
- package/dist/sync/delete-data-port.d.ts.map +1 -0
- package/dist/sync/delete-data-port.js +244 -0
- package/dist/sync/delete-data-port.js.map +1 -0
- package/dist/sync/engine/sync-manager.d.ts +58 -0
- package/dist/sync/engine/sync-manager.d.ts.map +1 -0
- package/dist/sync/engine/sync-manager.js +285 -0
- package/dist/sync/engine/sync-manager.js.map +1 -0
- package/dist/sync/index.d.ts +15 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +14 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/sync/issues.d.ts +55 -0
- package/dist/sync/issues.d.ts.map +1 -0
- package/dist/sync/issues.js +168 -0
- package/dist/sync/issues.js.map +1 -0
- package/dist/sync/pdpp-import.d.ts +192 -0
- package/dist/sync/pdpp-import.d.ts.map +1 -0
- package/dist/sync/pdpp-import.js +422 -0
- package/dist/sync/pdpp-import.js.map +1 -0
- package/dist/sync/pending-blob-deletions.d.ts +29 -0
- package/dist/sync/pending-blob-deletions.d.ts.map +1 -0
- package/dist/sync/pending-blob-deletions.js +116 -0
- package/dist/sync/pending-blob-deletions.js.map +1 -0
- package/dist/sync/retry-memory.d.ts +33 -0
- package/dist/sync/retry-memory.d.ts.map +1 -0
- package/dist/sync/retry-memory.js +68 -0
- package/dist/sync/retry-memory.js.map +1 -0
- package/dist/sync/scope-deletions.d.ts +155 -0
- package/dist/sync/scope-deletions.d.ts.map +1 -0
- package/dist/sync/scope-deletions.js +225 -0
- package/dist/sync/scope-deletions.js.map +1 -0
- package/dist/sync/tombstone.d.ts +27 -0
- package/dist/sync/tombstone.d.ts.map +1 -0
- package/dist/sync/tombstone.js +28 -0
- package/dist/sync/tombstone.js.map +1 -0
- package/dist/sync/types.d.ts +27 -0
- package/dist/sync/types.d.ts.map +1 -0
- package/dist/sync/types.js +2 -0
- package/dist/sync/types.js.map +1 -0
- package/dist/sync/workers/delete.d.ts +163 -0
- package/dist/sync/workers/delete.d.ts.map +1 -0
- package/dist/sync/workers/delete.js +474 -0
- package/dist/sync/workers/delete.js.map +1 -0
- package/dist/sync/workers/download.d.ts +159 -0
- package/dist/sync/workers/download.d.ts.map +1 -0
- package/dist/sync/workers/download.js +731 -0
- package/dist/sync/workers/download.js.map +1 -0
- package/dist/sync/workers/upload.d.ts +98 -0
- package/dist/sync/workers/upload.d.ts.map +1 -0
- package/dist/sync/workers/upload.js +412 -0
- package/dist/sync/workers/upload.js.map +1 -0
- package/dist/test-utils/e2ee-fixtures.d.ts +94 -0
- package/dist/test-utils/e2ee-fixtures.d.ts.map +1 -0
- package/dist/test-utils/e2ee-fixtures.js +116 -0
- package/dist/test-utils/e2ee-fixtures.js.map +1 -0
- package/dist/test-utils/e2ee-gateway.d.ts +61 -0
- package/dist/test-utils/e2ee-gateway.d.ts.map +1 -0
- package/dist/test-utils/e2ee-gateway.js +248 -0
- package/dist/test-utils/e2ee-gateway.js.map +1 -0
- package/dist/test-utils/index.d.ts +5 -0
- package/dist/test-utils/index.d.ts.map +1 -0
- package/dist/test-utils/index.js +5 -0
- package/dist/test-utils/index.js.map +1 -0
- package/dist/test-utils/memory-storage.d.ts +12 -0
- package/dist/test-utils/memory-storage.d.ts.map +1 -0
- package/dist/test-utils/memory-storage.js +146 -0
- package/dist/test-utils/memory-storage.js.map +1 -0
- package/dist/test-utils/wallet.d.ts +47 -0
- package/dist/test-utils/wallet.d.ts.map +1 -0
- package/dist/test-utils/wallet.js +98 -0
- package/dist/test-utils/wallet.js.map +1 -0
- package/dist/write/attribution.d.ts +185 -0
- package/dist/write/attribution.d.ts.map +1 -0
- package/dist/write/attribution.js +450 -0
- package/dist/write/attribution.js.map +1 -0
- package/dist/write/authorize.d.ts +77 -0
- package/dist/write/authorize.d.ts.map +1 -0
- package/dist/write/authorize.js +109 -0
- package/dist/write/authorize.js.map +1 -0
- package/dist/write/index.d.ts +5 -0
- package/dist/write/index.d.ts.map +1 -0
- package/dist/write/index.js +5 -0
- package/dist/write/index.js.map +1 -0
- package/dist/write/route.d.ts +61 -0
- package/dist/write/route.d.ts.map +1 -0
- package/dist/write/route.js +132 -0
- package/dist/write/route.js.map +1 -0
- package/dist/write/session.d.ts +96 -0
- package/dist/write/session.d.ts.map +1 -0
- package/dist/write/session.js +136 -0
- package/dist/write/session.js.map +1 -0
- package/package.json +175 -0
|
@@ -0,0 +1,1381 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tool surface. Phase 1 / §4 of 260604-PLAN-vana-mcp-personal-server.md.
|
|
3
|
+
*
|
|
4
|
+
* Read-only, small, and only over scopes that the connection's grants cover.
|
|
5
|
+
* No write tools. No grant-approval tools. No raw owner/admin status tools.
|
|
6
|
+
*
|
|
7
|
+
* Every tool that returns data first picks a covering grant from the
|
|
8
|
+
* connection's grant list, signs a Web3Signed request as the per-connection
|
|
9
|
+
* grantee, and uses the bounded sidecar read path — so reads are grant-gated,
|
|
10
|
+
* access-logged, and safe for large scopes.
|
|
11
|
+
*/
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
import { MCP_SCOPE_ACCESS_REQUEST_LIMIT, } from "./types.js";
|
|
14
|
+
import { McpDataReadError } from "./read-client.js";
|
|
15
|
+
import { MiniSearchIndex } from "./search/index.js";
|
|
16
|
+
import { MAX_SELECTED_BLOCK_IDS } from "../storage/blocks/index.js";
|
|
17
|
+
import { rawScopeFileInputSchema, rawScopeMetadata, rawScopeResourceUri, } from "./resources.js";
|
|
18
|
+
/** Tool names the read path stamps onto access records. */
|
|
19
|
+
const READ_SCOPE_TOOL = "read_scope";
|
|
20
|
+
const SEARCH_PERSONAL_CONTEXT_TOOL = "search_personal_context";
|
|
21
|
+
const STRUCTURED_CONTENT_TEXT_LIMIT_BYTES = 64 * 1024;
|
|
22
|
+
const resultTextEncoder = new TextEncoder();
|
|
23
|
+
/**
|
|
24
|
+
* Tool payloads are machine-read, never displayed as-is: pretty-printing them
|
|
25
|
+
* inflated the wire size 1.37x-1.63x on real scopes for no reader benefit.
|
|
26
|
+
*/
|
|
27
|
+
function textResult(value, isError = false) {
|
|
28
|
+
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
29
|
+
const textBytes = resultTextEncoder.encode(text).byteLength;
|
|
30
|
+
const structuredContent = !isError &&
|
|
31
|
+
textBytes <= STRUCTURED_CONTENT_TEXT_LIMIT_BYTES &&
|
|
32
|
+
typeof value === "object" &&
|
|
33
|
+
value !== null &&
|
|
34
|
+
!Array.isArray(value)
|
|
35
|
+
? value
|
|
36
|
+
: undefined;
|
|
37
|
+
return {
|
|
38
|
+
content: [
|
|
39
|
+
{
|
|
40
|
+
type: "text",
|
|
41
|
+
text,
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
...(structuredContent ? { structuredContent } : {}),
|
|
45
|
+
...(isError ? { isError: true } : {}),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function uniqueScopes(connection) {
|
|
49
|
+
const set = new Set();
|
|
50
|
+
for (const grant of connection.grants) {
|
|
51
|
+
for (const scope of grant.scopes)
|
|
52
|
+
set.add(scope);
|
|
53
|
+
}
|
|
54
|
+
return Array.from(set).sort();
|
|
55
|
+
}
|
|
56
|
+
function uniqueSources(connection) {
|
|
57
|
+
const set = new Set();
|
|
58
|
+
for (const grant of connection.grants) {
|
|
59
|
+
const sourceId = typeof grant.sourceId === "string" ? grant.sourceId.trim() : "";
|
|
60
|
+
if (sourceId) {
|
|
61
|
+
set.add(sourceId);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
for (const scope of grant.scopes) {
|
|
65
|
+
const [scopeSourceId] = scope.split(".");
|
|
66
|
+
if (scopeSourceId && scopeSourceId !== "*") {
|
|
67
|
+
set.add(scopeSourceId);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return Array.from(set).sort();
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Block ids a caller may name in one block-addressed read. Bounded so a model
|
|
75
|
+
* cannot turn `blockIds` into an unbounded fan-out; `maxBytes` still applies on
|
|
76
|
+
* top of it.
|
|
77
|
+
*/
|
|
78
|
+
function normalizeBlockIdsInput(value) {
|
|
79
|
+
if (!Array.isArray(value))
|
|
80
|
+
return [];
|
|
81
|
+
const blockIds = [];
|
|
82
|
+
const seen = new Set();
|
|
83
|
+
for (const raw of value) {
|
|
84
|
+
if (typeof raw !== "string")
|
|
85
|
+
continue;
|
|
86
|
+
const blockId = raw.trim();
|
|
87
|
+
if (!blockId || blockId.length > BLOCK_ID_MAX_CHARS || seen.has(blockId)) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
seen.add(blockId);
|
|
91
|
+
blockIds.push(blockId);
|
|
92
|
+
if (blockIds.length >= MAX_SELECTED_BLOCK_IDS)
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
return blockIds;
|
|
96
|
+
}
|
|
97
|
+
function normalizeScopeListInput(value) {
|
|
98
|
+
if (!Array.isArray(value))
|
|
99
|
+
return [];
|
|
100
|
+
const scopes = [];
|
|
101
|
+
const seen = new Set();
|
|
102
|
+
for (const raw of value) {
|
|
103
|
+
if (typeof raw !== "string")
|
|
104
|
+
continue;
|
|
105
|
+
const scope = raw.trim();
|
|
106
|
+
if (!scope || scope.length > SEARCH_SCOPE_MAX_CHARS || seen.has(scope)) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
seen.add(scope);
|
|
110
|
+
scopes.push(scope);
|
|
111
|
+
}
|
|
112
|
+
return scopes;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Hard ceiling the MCP dispatcher enforces on any tool call (see
|
|
116
|
+
* `clampToolTimeout` in `server.ts`, which imports this).
|
|
117
|
+
*
|
|
118
|
+
* Tools must never advertise a larger `timeoutMs` than the dispatcher allows: a
|
|
119
|
+
* client that trusts the advertised maximum gets killed with a hard
|
|
120
|
+
* `tool_timeout` instead of the graceful "budget exhausted, here are partial
|
|
121
|
+
* results plus nextSearchCursor" path the tools implement.
|
|
122
|
+
*/
|
|
123
|
+
export const MAX_MCP_TOOL_TIMEOUT_MS = 90_000;
|
|
124
|
+
const DEFAULT_SEARCH_LIMIT = 5;
|
|
125
|
+
const MAX_SEARCH_LIMIT = 50;
|
|
126
|
+
const MAX_SEARCH_SCOPES = 200;
|
|
127
|
+
const DEFAULT_SEARCH_MAX_SCOPES = MAX_SEARCH_SCOPES;
|
|
128
|
+
const DEFAULT_SEARCH_TIMEOUT_MS = 30_000;
|
|
129
|
+
const MAX_SEARCH_TIMEOUT_MS = MAX_MCP_TOOL_TIMEOUT_MS;
|
|
130
|
+
const DEFAULT_SEARCH_DISCOVERY_TIMEOUT_MS = 2_000;
|
|
131
|
+
const MAX_SEARCH_TOTAL_TIMEOUT_MS = MAX_MCP_TOOL_TIMEOUT_MS;
|
|
132
|
+
const DEFAULT_READ_SCOPE_MAX_BYTES = 64 * 1024;
|
|
133
|
+
const MAX_READ_SCOPE_MAX_BYTES = 5 * 1024 * 1024;
|
|
134
|
+
const DEFAULT_READ_SCOPE_TIMEOUT_MS = 60_000;
|
|
135
|
+
const MAX_READ_SCOPE_TIMEOUT_MS = MAX_MCP_TOOL_TIMEOUT_MS;
|
|
136
|
+
const DEFAULT_READ_FILE_TIMEOUT_MS = 60_000;
|
|
137
|
+
const MAX_READ_FILE_TIMEOUT_MS = MAX_MCP_TOOL_TIMEOUT_MS;
|
|
138
|
+
const DEFAULT_READ_FILE_MAX_BYTES = 10 * 1024 * 1024;
|
|
139
|
+
const MAX_READ_FILE_MAX_BYTES = 25 * 1024 * 1024;
|
|
140
|
+
const DEFAULT_SEARCH_MAX_BYTES = 64 * 1024;
|
|
141
|
+
const MAX_SEARCH_MAX_BYTES = 1024 * 1024;
|
|
142
|
+
const DEFAULT_SCOPE_METADATA_TIMEOUT_MS = 2_000;
|
|
143
|
+
const DEFAULT_LIST_BLOCKS_LIMIT = 100;
|
|
144
|
+
const MAX_LIST_BLOCKS_LIMIT = 500;
|
|
145
|
+
const LIST_BLOCKS_TIMEOUT_MS = 15_000;
|
|
146
|
+
/**
|
|
147
|
+
* Safety net, not a search budget: a scope is scanned page by page until the
|
|
148
|
+
* result limit or the time budget is hit, and this only stops a pathological
|
|
149
|
+
* scope from monopolising the run. Pages beyond it are resumable through
|
|
150
|
+
* `nextSearchCursor`, so raising it costs latency, never correctness.
|
|
151
|
+
*/
|
|
152
|
+
const SEARCH_MAX_PAGES_PER_SCOPE = 256;
|
|
153
|
+
const SEARCH_QUERY_MAX_CHARS = 256;
|
|
154
|
+
const SEARCH_SCOPE_MAX_CHARS = 128;
|
|
155
|
+
const BLOCK_ID_MAX_CHARS = 128;
|
|
156
|
+
const SEARCH_REQUESTED_SCOPES_LIMIT = MAX_SEARCH_SCOPES * 2;
|
|
157
|
+
const SEARCH_SKIPPED_SCOPES_LIMIT = 50;
|
|
158
|
+
const SEARCH_PREVIEW_CHARS = 600;
|
|
159
|
+
const LIST_SCOPES_LIMIT = 200;
|
|
160
|
+
const scopeAccessRequestInputShape = {
|
|
161
|
+
scopes: z
|
|
162
|
+
.array(z.string().min(1).max(SEARCH_SCOPE_MAX_CHARS))
|
|
163
|
+
.min(1)
|
|
164
|
+
.max(MCP_SCOPE_ACCESS_REQUEST_LIMIT)
|
|
165
|
+
.describe("Exact scope ids the MCP client wants to read."),
|
|
166
|
+
reason: z
|
|
167
|
+
.string()
|
|
168
|
+
.max(500)
|
|
169
|
+
.optional()
|
|
170
|
+
.describe("Brief reason to show the user."),
|
|
171
|
+
};
|
|
172
|
+
export function parseScopeAccessRequestArguments(value) {
|
|
173
|
+
const parsed = z.object(scopeAccessRequestInputShape).safeParse(value);
|
|
174
|
+
if (!parsed.success)
|
|
175
|
+
return null;
|
|
176
|
+
const scopes = normalizeScopeListInput(parsed.data.scopes);
|
|
177
|
+
if (scopes.length === 0)
|
|
178
|
+
return null;
|
|
179
|
+
const reason = parsed.data.reason?.trim();
|
|
180
|
+
return { scopes, ...(reason ? { reason } : {}) };
|
|
181
|
+
}
|
|
182
|
+
// Size thresholds for sizeClass classification (raw envelope bytes)
|
|
183
|
+
const SIZE_CLASS_TINY_BYTES = 10_000;
|
|
184
|
+
const SIZE_CLASS_SMALL_BYTES = 100_000;
|
|
185
|
+
const SIZE_CLASS_MEDIUM_BYTES = 1_000_000;
|
|
186
|
+
const SIZE_CLASS_LARGE_BYTES = 10_000_000;
|
|
187
|
+
class OperationTimeoutError extends Error {
|
|
188
|
+
operation;
|
|
189
|
+
timeoutMs;
|
|
190
|
+
constructor(operation, timeoutMs) {
|
|
191
|
+
super(`${operation} timed out after ${timeoutMs}ms`);
|
|
192
|
+
this.operation = operation;
|
|
193
|
+
this.timeoutMs = timeoutMs;
|
|
194
|
+
this.name = "OperationTimeoutError";
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Resolve a concrete requested scope to a covering grant id from the
|
|
199
|
+
* connection's approved grants. A scope is "covered" when an approved grant's
|
|
200
|
+
* scopes either contains the exact scope or contains a wildcard like
|
|
201
|
+
* `instagram.*` that matches a concrete child scope like `instagram.profile`.
|
|
202
|
+
*
|
|
203
|
+
* Mirrors `scopeCoveredByGrant` semantics from `policy/data-read.ts` so the
|
|
204
|
+
* tool surface never offers Claude a scope its grant won't actually pass
|
|
205
|
+
* server-side.
|
|
206
|
+
*/
|
|
207
|
+
export function resolveGrantForScope(connection, scope) {
|
|
208
|
+
for (const grant of connection.grants) {
|
|
209
|
+
for (const granted of grant.scopes) {
|
|
210
|
+
if (granted === scope) {
|
|
211
|
+
return { grantId: grant.grantId, scopes: grant.scopes };
|
|
212
|
+
}
|
|
213
|
+
if (granted.endsWith(".*")) {
|
|
214
|
+
const prefix = granted.slice(0, -2);
|
|
215
|
+
if (scope.startsWith(`${prefix}.`)) {
|
|
216
|
+
return { grantId: grant.grantId, scopes: grant.scopes };
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (granted === "*") {
|
|
220
|
+
return { grantId: grant.grantId, scopes: grant.scopes };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
function clampInteger(value, fallback, min, max) {
|
|
227
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
228
|
+
return fallback;
|
|
229
|
+
}
|
|
230
|
+
return Math.min(max, Math.max(min, Math.trunc(value)));
|
|
231
|
+
}
|
|
232
|
+
function wildcardPrefix(scope) {
|
|
233
|
+
if (scope === "*")
|
|
234
|
+
return "";
|
|
235
|
+
if (scope.endsWith(".*"))
|
|
236
|
+
return `${scope.slice(0, -2)}.`;
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
function truncateLabel(value) {
|
|
240
|
+
return value.length > SEARCH_SCOPE_MAX_CHARS
|
|
241
|
+
? `${value.slice(0, SEARCH_SCOPE_MAX_CHARS)}...`
|
|
242
|
+
: value;
|
|
243
|
+
}
|
|
244
|
+
function stringifyPreview(value) {
|
|
245
|
+
if (typeof value === "string") {
|
|
246
|
+
return value.slice(0, SEARCH_PREVIEW_CHARS);
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
return JSON.stringify(value)?.slice(0, SEARCH_PREVIEW_CHARS) ?? "";
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return String(value).slice(0, SEARCH_PREVIEW_CHARS);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function collectSearchText(value) {
|
|
256
|
+
const chunks = [];
|
|
257
|
+
function visit(node) {
|
|
258
|
+
if (typeof node === "string") {
|
|
259
|
+
chunks.push(node);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (typeof node === "number" ||
|
|
263
|
+
typeof node === "boolean" ||
|
|
264
|
+
node === null) {
|
|
265
|
+
chunks.push(String(node));
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (Array.isArray(node)) {
|
|
269
|
+
for (const item of node)
|
|
270
|
+
visit(item);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (typeof node === "object" && node !== null) {
|
|
274
|
+
for (const [key, item] of Object.entries(node)) {
|
|
275
|
+
chunks.push(key);
|
|
276
|
+
visit(item);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
visit(value);
|
|
281
|
+
return chunks.join("\n");
|
|
282
|
+
}
|
|
283
|
+
function blockPageSearchText(blocks) {
|
|
284
|
+
return blocks
|
|
285
|
+
.map((block) => `${block.path}\n${collectSearchText(block.value)}`)
|
|
286
|
+
.join("\n\n");
|
|
287
|
+
}
|
|
288
|
+
function blockPageSearchDocuments(scope, blocks) {
|
|
289
|
+
const textById = new Map();
|
|
290
|
+
const documents = blocks.map((block, index) => {
|
|
291
|
+
const id = `${scope}:${block.id || index}`;
|
|
292
|
+
const text = `${block.path}\n${collectSearchText(block.value)}`;
|
|
293
|
+
textById.set(id, text);
|
|
294
|
+
return {
|
|
295
|
+
id,
|
|
296
|
+
scope,
|
|
297
|
+
title: block.path,
|
|
298
|
+
text,
|
|
299
|
+
preview: text.slice(0, SEARCH_PREVIEW_CHARS),
|
|
300
|
+
blockRef: block.id,
|
|
301
|
+
};
|
|
302
|
+
});
|
|
303
|
+
return { documents, textById };
|
|
304
|
+
}
|
|
305
|
+
function previewMatch(text, matchIndex) {
|
|
306
|
+
const halfWindow = Math.floor(SEARCH_PREVIEW_CHARS / 2);
|
|
307
|
+
const start = Math.max(0, matchIndex - halfWindow);
|
|
308
|
+
const end = Math.min(text.length, start + SEARCH_PREVIEW_CHARS);
|
|
309
|
+
const prefix = start > 0 ? "..." : "";
|
|
310
|
+
const suffix = end < text.length ? "..." : "";
|
|
311
|
+
return `${prefix}${text.slice(start, end)}${suffix}`;
|
|
312
|
+
}
|
|
313
|
+
function miniSearchMatchIndex(text, terms) {
|
|
314
|
+
const normalized = text.toLowerCase();
|
|
315
|
+
for (const term of terms) {
|
|
316
|
+
const index = normalized.indexOf(term.toLowerCase());
|
|
317
|
+
if (index >= 0)
|
|
318
|
+
return index;
|
|
319
|
+
}
|
|
320
|
+
return 0;
|
|
321
|
+
}
|
|
322
|
+
async function withTimeout(promise, timeoutMs, operation) {
|
|
323
|
+
let timeout;
|
|
324
|
+
try {
|
|
325
|
+
return await Promise.race([
|
|
326
|
+
promise,
|
|
327
|
+
new Promise((_resolve, reject) => {
|
|
328
|
+
timeout = setTimeout(() => reject(new OperationTimeoutError(operation, timeoutMs)), timeoutMs);
|
|
329
|
+
}),
|
|
330
|
+
]);
|
|
331
|
+
}
|
|
332
|
+
finally {
|
|
333
|
+
if (timeout)
|
|
334
|
+
clearTimeout(timeout);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function normalizeRequestedScopes(value, connection) {
|
|
338
|
+
const input = Array.isArray(value) ? value : uniqueScopes(connection);
|
|
339
|
+
const requestedScopes = [];
|
|
340
|
+
const seen = new Set();
|
|
341
|
+
const skippedScopes = [];
|
|
342
|
+
const scanned = Math.min(input.length, SEARCH_REQUESTED_SCOPES_LIMIT);
|
|
343
|
+
let invalidCount = 0;
|
|
344
|
+
for (let index = 0; index < scanned; index += 1) {
|
|
345
|
+
const scope = input[index];
|
|
346
|
+
if (typeof scope !== "string") {
|
|
347
|
+
invalidCount += 1;
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
const trimmed = scope.trim();
|
|
351
|
+
if (!trimmed || trimmed.length > SEARCH_SCOPE_MAX_CHARS) {
|
|
352
|
+
invalidCount += 1;
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
if (seen.has(trimmed))
|
|
356
|
+
continue;
|
|
357
|
+
seen.add(trimmed);
|
|
358
|
+
requestedScopes.push(trimmed);
|
|
359
|
+
}
|
|
360
|
+
if (input.length > SEARCH_REQUESTED_SCOPES_LIMIT) {
|
|
361
|
+
skippedScopes.push(`${input.length - SEARCH_REQUESTED_SCOPES_LIMIT} requested scopes omitted by input cap`);
|
|
362
|
+
}
|
|
363
|
+
if (invalidCount > 0) {
|
|
364
|
+
skippedScopes.push(`${invalidCount} invalid requested scopes omitted`);
|
|
365
|
+
}
|
|
366
|
+
return { requestedScopes, skippedScopes };
|
|
367
|
+
}
|
|
368
|
+
function classifySizeBytes(sizeBytes) {
|
|
369
|
+
if (sizeBytes === undefined)
|
|
370
|
+
return "unknown";
|
|
371
|
+
if (sizeBytes < SIZE_CLASS_TINY_BYTES)
|
|
372
|
+
return "tiny";
|
|
373
|
+
if (sizeBytes < SIZE_CLASS_SMALL_BYTES)
|
|
374
|
+
return "small";
|
|
375
|
+
if (sizeBytes < SIZE_CLASS_MEDIUM_BYTES)
|
|
376
|
+
return "medium";
|
|
377
|
+
if (sizeBytes < SIZE_CLASS_LARGE_BYTES)
|
|
378
|
+
return "large";
|
|
379
|
+
return "huge";
|
|
380
|
+
}
|
|
381
|
+
// Directive guidance the MCP client (e.g. Claude) uses to pick read_scope vs
|
|
382
|
+
// search_personal_context for a scope WITHOUT operator intervention. The signal
|
|
383
|
+
// must be unambiguous: a misnamed flag here (the old `searchRecommended: false`
|
|
384
|
+
// on large scopes) reads to a model as "don't search → read the whole thing",
|
|
385
|
+
// which is exactly the slow path — a full read of a large scope returns many
|
|
386
|
+
// pages it must walk via nextCursor and tends to stall the agent loop.
|
|
387
|
+
function recommendAccessForScope(sizeClass, hasBlocks) {
|
|
388
|
+
if (!hasBlocks) {
|
|
389
|
+
return {
|
|
390
|
+
recommendedAccess: "wait",
|
|
391
|
+
reason: "bounded block data is still indexing or unavailable; retry shortly before reading or searching",
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
if (sizeClass === "large" || sizeClass === "huge") {
|
|
395
|
+
return {
|
|
396
|
+
recommendedAccess: "search",
|
|
397
|
+
reason: `scope is ${sizeClass}; prefer search_personal_context and name this scope explicitly. A full read_scope returns many pages you must walk via nextCursor.`,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
if (sizeClass === "unknown") {
|
|
401
|
+
return {
|
|
402
|
+
recommendedAccess: "search",
|
|
403
|
+
reason: "scope size is unknown; prefer search_personal_context and name this scope explicitly",
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
return {
|
|
407
|
+
recommendedAccess: "read",
|
|
408
|
+
reason: `scope is ${sizeClass}; read_scope returns it in one or few pages. search_personal_context also works for targeted lookups.`,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
async function resolveSearchScopes({ connection, maxScopes, discoveryTimeoutMs, readClient, requestedScopes, }) {
|
|
412
|
+
const scopes = new Set();
|
|
413
|
+
const skippedScopes = [];
|
|
414
|
+
let skippedScopeOverflow = 0;
|
|
415
|
+
const errors = [];
|
|
416
|
+
let wildcardDiscoveries = 0;
|
|
417
|
+
function skipScope(scope) {
|
|
418
|
+
if (skippedScopes.length < SEARCH_SKIPPED_SCOPES_LIMIT) {
|
|
419
|
+
skippedScopes.push(truncateLabel(scope));
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
skippedScopeOverflow += 1;
|
|
423
|
+
}
|
|
424
|
+
function addScope(scope) {
|
|
425
|
+
if (scopes.has(scope))
|
|
426
|
+
return;
|
|
427
|
+
if (scopes.size >= maxScopes) {
|
|
428
|
+
skipScope(scope);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
scopes.add(scope);
|
|
432
|
+
}
|
|
433
|
+
for (const requested of requestedScopes) {
|
|
434
|
+
if (scopes.size >= maxScopes) {
|
|
435
|
+
skipScope(requested);
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
const grant = resolveGrantForScope(connection, requested);
|
|
439
|
+
if (!grant) {
|
|
440
|
+
errors.push({ scope: requested, error: "scope_not_granted" });
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
const prefix = wildcardPrefix(requested);
|
|
444
|
+
if (prefix === null) {
|
|
445
|
+
addScope(requested);
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
if (wildcardDiscoveries >= maxScopes) {
|
|
449
|
+
skipScope(`${requested} (wildcard discovery cap reached)`);
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
wildcardDiscoveries += 1;
|
|
453
|
+
try {
|
|
454
|
+
const listed = await withTimeout(readClient.listScopes({
|
|
455
|
+
scopePrefix: prefix,
|
|
456
|
+
limit: LIST_SCOPES_LIMIT,
|
|
457
|
+
}), discoveryTimeoutMs, `list scopes for ${requested}`);
|
|
458
|
+
for (const summary of listed.scopes) {
|
|
459
|
+
if (resolveGrantForScope(connection, summary.scope)) {
|
|
460
|
+
addScope(summary.scope);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
if (listed.total > listed.scopes.length) {
|
|
464
|
+
skipScope(`${requested} (more than ${listed.scopes.length})`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
catch (err) {
|
|
468
|
+
errors.push({
|
|
469
|
+
scope: requested,
|
|
470
|
+
error: err instanceof OperationTimeoutError
|
|
471
|
+
? "scope_list_timeout"
|
|
472
|
+
: "scope_list_failed",
|
|
473
|
+
status: err instanceof McpDataReadError ? err.status : undefined,
|
|
474
|
+
bodyPreview: stringifyPreview(err instanceof McpDataReadError
|
|
475
|
+
? err.body
|
|
476
|
+
: err instanceof OperationTimeoutError
|
|
477
|
+
? err.message
|
|
478
|
+
: "unexpected scope list failure"),
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
if (skippedScopeOverflow > 0) {
|
|
483
|
+
skippedScopes.push(`${skippedScopeOverflow} additional skipped scopes omitted`);
|
|
484
|
+
}
|
|
485
|
+
return {
|
|
486
|
+
scopes: Array.from(scopes),
|
|
487
|
+
skippedScopes: Array.from(new Set(skippedScopes)),
|
|
488
|
+
errors,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
const listGrantedSources = {
|
|
492
|
+
name: "list_granted_sources",
|
|
493
|
+
title: "List granted sources",
|
|
494
|
+
description: "List granted data source ids. Use list_granted_scopes for scope readiness and size.",
|
|
495
|
+
inputSchema: {},
|
|
496
|
+
async handler(_args, { connection }) {
|
|
497
|
+
return textResult({ sources: uniqueSources(connection) });
|
|
498
|
+
},
|
|
499
|
+
};
|
|
500
|
+
const listGrantedScopes = {
|
|
501
|
+
name: "list_granted_scopes",
|
|
502
|
+
title: "List granted scopes",
|
|
503
|
+
description: "List granted scopes, readiness, size, and recommendedAccess. Call first.",
|
|
504
|
+
inputSchema: {},
|
|
505
|
+
async handler(_args, { connection, readClient }) {
|
|
506
|
+
const grantedScopes = uniqueScopes(connection);
|
|
507
|
+
const scopeEntries = await Promise.all(grantedScopes.map(async (scope) => {
|
|
508
|
+
let meta = null;
|
|
509
|
+
if (typeof readClient.getScopeMetadata === "function") {
|
|
510
|
+
try {
|
|
511
|
+
meta = await withTimeout(readClient.getScopeMetadata(scope), DEFAULT_SCOPE_METADATA_TIMEOUT_MS, `scope metadata for ${scope}`);
|
|
512
|
+
}
|
|
513
|
+
catch {
|
|
514
|
+
return {
|
|
515
|
+
scope,
|
|
516
|
+
source: scope.split(".")[0],
|
|
517
|
+
dataStatus: "indexing",
|
|
518
|
+
sizeClass: "unknown",
|
|
519
|
+
recommendedAccess: "wait",
|
|
520
|
+
reason: "scope metadata is still indexing or temporarily unavailable; retry shortly",
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
if (!meta) {
|
|
525
|
+
return {
|
|
526
|
+
scope,
|
|
527
|
+
dataStatus: "needs_refresh",
|
|
528
|
+
sizeClass: "unknown",
|
|
529
|
+
recommendedAccess: "wait",
|
|
530
|
+
reason: "no local data found; refresh your data connection",
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
const sizeClass = classifySizeBytes(meta.sizeBytes);
|
|
534
|
+
const { recommendedAccess, reason } = recommendAccessForScope(sizeClass, meta.hasBlocks);
|
|
535
|
+
return {
|
|
536
|
+
scope,
|
|
537
|
+
source: scope.split(".")[0],
|
|
538
|
+
collectedAt: meta.collectedAt,
|
|
539
|
+
dataStatus: meta.hasBlocks
|
|
540
|
+
? "ready"
|
|
541
|
+
: "indexing",
|
|
542
|
+
sizeBytes: meta.sizeBytes,
|
|
543
|
+
sizeClass,
|
|
544
|
+
recommendedAccess,
|
|
545
|
+
reason,
|
|
546
|
+
};
|
|
547
|
+
}));
|
|
548
|
+
return textResult({ scopes: scopeEntries });
|
|
549
|
+
},
|
|
550
|
+
};
|
|
551
|
+
const requestScopeAccess = {
|
|
552
|
+
name: "request_scope_access",
|
|
553
|
+
title: "Request scope access",
|
|
554
|
+
description: "Record missing scopes on this MCP connection for explicit owner approval in Vana.",
|
|
555
|
+
inputSchema: scopeAccessRequestInputShape,
|
|
556
|
+
async handler(args, { connection, requestScopeAccess }) {
|
|
557
|
+
const requestedScopes = normalizeScopeListInput(args.scopes);
|
|
558
|
+
let effectiveConnection = connection;
|
|
559
|
+
let grantedRequestedScopes = requestedScopes.filter((scope) => Boolean(resolveGrantForScope(effectiveConnection, scope)));
|
|
560
|
+
let missingScopes = requestedScopes.filter((scope) => !resolveGrantForScope(connection, scope));
|
|
561
|
+
const reason = typeof args.reason === "string" ? args.reason.trim() : "";
|
|
562
|
+
const approvalRequired = missingScopes.length > 0;
|
|
563
|
+
const alreadyRecorded = approvalRequired &&
|
|
564
|
+
missingScopes.every((scope) => connection.scopeAccessRequest?.scopes.includes(scope));
|
|
565
|
+
let requestRecorded = alreadyRecorded;
|
|
566
|
+
if (approvalRequired && requestScopeAccess) {
|
|
567
|
+
const outcome = await requestScopeAccess({
|
|
568
|
+
scopes: missingScopes,
|
|
569
|
+
...(reason ? { reason } : {}),
|
|
570
|
+
});
|
|
571
|
+
effectiveConnection = outcome.connection;
|
|
572
|
+
requestRecorded = outcome.requestRecorded;
|
|
573
|
+
grantedRequestedScopes = requestedScopes.filter((scope) => Boolean(resolveGrantForScope(effectiveConnection, scope)));
|
|
574
|
+
missingScopes = requestedScopes.filter((scope) => !resolveGrantForScope(effectiveConnection, scope));
|
|
575
|
+
}
|
|
576
|
+
const finalApprovalRequired = missingScopes.length > 0;
|
|
577
|
+
return textResult({
|
|
578
|
+
approvalRequired: finalApprovalRequired,
|
|
579
|
+
client: {
|
|
580
|
+
id: connection.id,
|
|
581
|
+
displayName: connection.displayName,
|
|
582
|
+
status: connection.status,
|
|
583
|
+
},
|
|
584
|
+
requestedScopes,
|
|
585
|
+
grantedRequestedScopes,
|
|
586
|
+
missingScopes,
|
|
587
|
+
grantedScopes: uniqueScopes(effectiveConnection),
|
|
588
|
+
reason: reason || undefined,
|
|
589
|
+
requestRecorded: finalApprovalRequired && requestRecorded,
|
|
590
|
+
nextAction: finalApprovalRequired
|
|
591
|
+
? requestRecorded
|
|
592
|
+
? "The request is waiting for owner approval in Vana. Access remains unchanged until the owner approves it."
|
|
593
|
+
: "Ask the user to open Vana's Personal Server page and add the missing scopes to this MCP client. This tool cannot grant access by itself."
|
|
594
|
+
: "No new grant is needed for the requested scopes.",
|
|
595
|
+
});
|
|
596
|
+
},
|
|
597
|
+
};
|
|
598
|
+
const readScope = {
|
|
599
|
+
name: READ_SCOPE_TOOL,
|
|
600
|
+
title: "Read scope",
|
|
601
|
+
description: "Read approved scope blocks. Pass blockIds to fetch exact blocks; otherwise page with nextCursor.",
|
|
602
|
+
inputSchema: {
|
|
603
|
+
scope: z.string().min(1).describe("Exact scope id."),
|
|
604
|
+
cursor: z.string().min(1).optional(),
|
|
605
|
+
blockIds: z
|
|
606
|
+
.array(z.string().min(1).max(BLOCK_ID_MAX_CHARS))
|
|
607
|
+
.min(1)
|
|
608
|
+
.max(MAX_SELECTED_BLOCK_IDS)
|
|
609
|
+
.optional()
|
|
610
|
+
.describe("Block ids from a search hit's blockRef or list_scope_blocks. Ignores cursor; maxBytes still applies."),
|
|
611
|
+
maxBytes: z.number().int().min(1).max(MAX_READ_SCOPE_MAX_BYTES).optional(),
|
|
612
|
+
timeoutMs: z
|
|
613
|
+
.number()
|
|
614
|
+
.int()
|
|
615
|
+
.min(1000)
|
|
616
|
+
.max(MAX_READ_SCOPE_TIMEOUT_MS)
|
|
617
|
+
.optional(),
|
|
618
|
+
payment: z
|
|
619
|
+
.string()
|
|
620
|
+
.optional()
|
|
621
|
+
.describe("Base64 X-PAYMENT proof (x402). Provide it after a prior call returned payment_required."),
|
|
622
|
+
},
|
|
623
|
+
async handler(args, { connection, readClient }) {
|
|
624
|
+
const scope = typeof args.scope === "string" ? args.scope : null;
|
|
625
|
+
if (!scope) {
|
|
626
|
+
return textResult({ error: "scope is required and must be a string" }, true);
|
|
627
|
+
}
|
|
628
|
+
const grant = resolveGrantForScope(connection, scope);
|
|
629
|
+
if (!grant) {
|
|
630
|
+
return textResult({
|
|
631
|
+
error: "scope_not_granted",
|
|
632
|
+
message: `Scope '${scope}' is not covered by any grant on this MCP connection.`,
|
|
633
|
+
grantedScopes: uniqueScopes(connection),
|
|
634
|
+
}, true);
|
|
635
|
+
}
|
|
636
|
+
const cursor = typeof args.cursor === "string" ? args.cursor : undefined;
|
|
637
|
+
const blockIds = normalizeBlockIdsInput(args.blockIds);
|
|
638
|
+
const maxBytes = typeof args.maxBytes === "number"
|
|
639
|
+
? clampInteger(args.maxBytes, DEFAULT_READ_SCOPE_MAX_BYTES, 1, MAX_READ_SCOPE_MAX_BYTES)
|
|
640
|
+
: DEFAULT_READ_SCOPE_MAX_BYTES;
|
|
641
|
+
const timeoutMs = clampInteger(args.timeoutMs, DEFAULT_READ_SCOPE_TIMEOUT_MS, 1000, MAX_READ_SCOPE_TIMEOUT_MS);
|
|
642
|
+
// In-band steer: the model may call read_scope on a large scope without
|
|
643
|
+
// first consulting list_granted_scopes. Fetch lightweight planning metadata
|
|
644
|
+
// (storage-index only, no network/auth) opportunistically so we can attach a
|
|
645
|
+
// `guidance` hint when it is already available. This is advisory only: it
|
|
646
|
+
// must never delay the read or consume the read timeout budget.
|
|
647
|
+
let guidance;
|
|
648
|
+
if (typeof readClient.getScopeMetadata === "function") {
|
|
649
|
+
void withTimeout(readClient.getScopeMetadata(scope), DEFAULT_SCOPE_METADATA_TIMEOUT_MS, `scope metadata for ${scope}`)
|
|
650
|
+
.then((meta) => {
|
|
651
|
+
if (!meta)
|
|
652
|
+
return;
|
|
653
|
+
const sizeClass = classifySizeBytes(meta.sizeBytes);
|
|
654
|
+
const recommendation = recommendAccessForScope(sizeClass, meta.hasBlocks);
|
|
655
|
+
// Only steer when search is the better tool; a normal small read gets
|
|
656
|
+
// no extra noise.
|
|
657
|
+
if (recommendation.recommendedAccess === "search") {
|
|
658
|
+
guidance = {
|
|
659
|
+
sizeClass,
|
|
660
|
+
sizeBytes: meta.sizeBytes,
|
|
661
|
+
recommendedAccess: recommendation.recommendedAccess,
|
|
662
|
+
reason: recommendation.reason,
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
})
|
|
666
|
+
.catch(() => {
|
|
667
|
+
// Advisory only — serve the read without guidance.
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
const payment = typeof args.payment === "string" ? args.payment : undefined;
|
|
671
|
+
try {
|
|
672
|
+
const result = await withTimeout(readClient.readScopeBlocks({
|
|
673
|
+
scope,
|
|
674
|
+
grantId: grant.grantId,
|
|
675
|
+
cursor,
|
|
676
|
+
maxBytes,
|
|
677
|
+
payment,
|
|
678
|
+
tool: READ_SCOPE_TOOL,
|
|
679
|
+
...(blockIds.length > 0 ? { blockIds } : {}),
|
|
680
|
+
}), timeoutMs, `read blocks for ${scope}`);
|
|
681
|
+
const nextCursor = result.nextCursor ?? null;
|
|
682
|
+
return textResult({
|
|
683
|
+
scope,
|
|
684
|
+
grantId: grant.grantId,
|
|
685
|
+
collectedAt: result.collectedAt,
|
|
686
|
+
contentKind: result.contentKind,
|
|
687
|
+
blocks: result.blocks,
|
|
688
|
+
...(result.nextCursor ? { nextCursor: result.nextCursor } : {}),
|
|
689
|
+
...(guidance ? { guidance } : {}),
|
|
690
|
+
warnings: result.warnings,
|
|
691
|
+
page: {
|
|
692
|
+
cursor: cursor ?? null,
|
|
693
|
+
nextCursor,
|
|
694
|
+
hasMore: Boolean(result.nextCursor),
|
|
695
|
+
maxBytes,
|
|
696
|
+
timeoutMs,
|
|
697
|
+
returnedBlocks: result.blocks.length,
|
|
698
|
+
...(blockIds.length > 0 ? { requestedBlockIds: blockIds } : {}),
|
|
699
|
+
},
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
catch (err) {
|
|
703
|
+
if (err instanceof OperationTimeoutError) {
|
|
704
|
+
return textResult({
|
|
705
|
+
error: "scope_read_timeout",
|
|
706
|
+
message: err.message,
|
|
707
|
+
timeoutMs: err.timeoutMs,
|
|
708
|
+
}, true);
|
|
709
|
+
}
|
|
710
|
+
if (err instanceof McpDataReadError) {
|
|
711
|
+
if (err.status === 402) {
|
|
712
|
+
const body = err.body;
|
|
713
|
+
const challenge = body?.error?.details?.challenge;
|
|
714
|
+
// Only a genuine PAYMENT_REQUIRED with a challenge is signable. A
|
|
715
|
+
// gateway failure that also surfaces as 402 (e.g. PAYMENT_GATEWAY_ERROR
|
|
716
|
+
// — insufficient escrow balance) is NOT signable; presenting it as a
|
|
717
|
+
// challenge would make the client sign an error envelope in a loop.
|
|
718
|
+
if (body?.error?.errorCode === "PAYMENT_REQUIRED" && challenge) {
|
|
719
|
+
return textResult({
|
|
720
|
+
payment_required: true,
|
|
721
|
+
status: 402,
|
|
722
|
+
challenge,
|
|
723
|
+
message: "This read requires payment. Sign the x402 challenge and call read_scope again with the `payment` argument.",
|
|
724
|
+
}, true);
|
|
725
|
+
}
|
|
726
|
+
// Gateway-side payment failure — surface the real error + remediation.
|
|
727
|
+
return textResult({
|
|
728
|
+
error: "payment_failed",
|
|
729
|
+
status: 402,
|
|
730
|
+
errorCode: body?.error?.errorCode ?? "PAYMENT_ERROR",
|
|
731
|
+
message: body?.error?.message ??
|
|
732
|
+
"Payment could not be completed (e.g. insufficient escrow balance). This is not a signable challenge; resolve the gateway error and retry.",
|
|
733
|
+
}, true);
|
|
734
|
+
}
|
|
735
|
+
if (err.status === 503) {
|
|
736
|
+
return textResult({
|
|
737
|
+
error: "bounded_data_unavailable",
|
|
738
|
+
status: err.status,
|
|
739
|
+
body: err.body,
|
|
740
|
+
}, true);
|
|
741
|
+
}
|
|
742
|
+
return textResult({ error: "data_read_failed", status: err.status, body: err.body }, true);
|
|
743
|
+
}
|
|
744
|
+
return textResult({
|
|
745
|
+
error: "tool_handler_error",
|
|
746
|
+
message: err instanceof Error ? err.message : String(err),
|
|
747
|
+
}, true);
|
|
748
|
+
}
|
|
749
|
+
},
|
|
750
|
+
};
|
|
751
|
+
const listScopeBlocks = {
|
|
752
|
+
name: "list_scope_blocks",
|
|
753
|
+
title: "List scope blocks",
|
|
754
|
+
description: "Scope table of contents: block ids, paths, sizes. Feed ids to read_scope blockIds.",
|
|
755
|
+
inputSchema: {
|
|
756
|
+
scope: z.string().min(1).describe("Exact scope id."),
|
|
757
|
+
offset: z.number().int().min(0).optional(),
|
|
758
|
+
limit: z.number().int().min(1).max(MAX_LIST_BLOCKS_LIMIT).optional(),
|
|
759
|
+
},
|
|
760
|
+
async handler(args, { connection, readClient }) {
|
|
761
|
+
const scope = typeof args.scope === "string" ? args.scope : null;
|
|
762
|
+
if (!scope) {
|
|
763
|
+
return textResult({ error: "scope is required and must be a string" }, true);
|
|
764
|
+
}
|
|
765
|
+
const grant = resolveGrantForScope(connection, scope);
|
|
766
|
+
if (!grant) {
|
|
767
|
+
return textResult({
|
|
768
|
+
error: "scope_not_granted",
|
|
769
|
+
message: `Scope '${scope}' is not covered by any grant on this MCP connection.`,
|
|
770
|
+
grantedScopes: uniqueScopes(connection),
|
|
771
|
+
}, true);
|
|
772
|
+
}
|
|
773
|
+
if (typeof readClient.readBlockManifest !== "function") {
|
|
774
|
+
return textResult({
|
|
775
|
+
scope,
|
|
776
|
+
blocks: [],
|
|
777
|
+
manifestAvailable: false,
|
|
778
|
+
reason: "this server build does not expose block manifests",
|
|
779
|
+
nextStep: "Use read_scope with nextCursor, or search_personal_context.",
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
const offset = clampInteger(args.offset, 0, 0, Number.MAX_SAFE_INTEGER);
|
|
783
|
+
const limit = clampInteger(args.limit, DEFAULT_LIST_BLOCKS_LIMIT, 1, MAX_LIST_BLOCKS_LIMIT);
|
|
784
|
+
try {
|
|
785
|
+
const manifest = await withTimeout(readClient.readBlockManifest({ scope, grantId: grant.grantId }), LIST_BLOCKS_TIMEOUT_MS, `read block manifest for ${scope}`);
|
|
786
|
+
// Scopes stored before block sidecars existed, and scopes still
|
|
787
|
+
// indexing, simply have no manifest. That is not an error.
|
|
788
|
+
if (!manifest) {
|
|
789
|
+
return textResult({
|
|
790
|
+
scope,
|
|
791
|
+
grantId: grant.grantId,
|
|
792
|
+
blocks: [],
|
|
793
|
+
manifestAvailable: false,
|
|
794
|
+
reason: "no block manifest for this scope yet; it may still be indexing",
|
|
795
|
+
nextStep: "Use read_scope with nextCursor, or search_personal_context.",
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
const page = manifest.blocks.slice(offset, offset + limit);
|
|
799
|
+
const nextOffset = offset + page.length;
|
|
800
|
+
return textResult({
|
|
801
|
+
scope,
|
|
802
|
+
grantId: grant.grantId,
|
|
803
|
+
collectedAt: manifest.collectedAt,
|
|
804
|
+
contentKind: manifest.contentKind,
|
|
805
|
+
manifestAvailable: true,
|
|
806
|
+
totalBlocks: manifest.blocks.length,
|
|
807
|
+
blocks: page.map((block) => ({
|
|
808
|
+
id: block.id,
|
|
809
|
+
path: block.path,
|
|
810
|
+
sizeBytes: block.sizeBytes,
|
|
811
|
+
...(block.itemCount === undefined
|
|
812
|
+
? {}
|
|
813
|
+
: { itemCount: block.itemCount }),
|
|
814
|
+
})),
|
|
815
|
+
warnings: manifest.warnings,
|
|
816
|
+
page: {
|
|
817
|
+
offset,
|
|
818
|
+
limit,
|
|
819
|
+
returnedBlocks: page.length,
|
|
820
|
+
...(nextOffset < manifest.blocks.length ? { nextOffset } : {}),
|
|
821
|
+
},
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
catch (err) {
|
|
825
|
+
if (err instanceof OperationTimeoutError) {
|
|
826
|
+
return textResult({
|
|
827
|
+
error: "scope_blocks_timeout",
|
|
828
|
+
message: err.message,
|
|
829
|
+
timeoutMs: err.timeoutMs,
|
|
830
|
+
}, true);
|
|
831
|
+
}
|
|
832
|
+
if (err instanceof McpDataReadError) {
|
|
833
|
+
return textResult({ error: "data_read_failed", status: err.status, body: err.body }, true);
|
|
834
|
+
}
|
|
835
|
+
return textResult({
|
|
836
|
+
error: "tool_handler_error",
|
|
837
|
+
message: err instanceof Error ? err.message : String(err),
|
|
838
|
+
}, true);
|
|
839
|
+
}
|
|
840
|
+
},
|
|
841
|
+
};
|
|
842
|
+
function encodeSearchCursor(payload) {
|
|
843
|
+
const json = JSON.stringify(payload);
|
|
844
|
+
// TextEncoder + Uint8Array → base64url without Node.js Buffer dependency
|
|
845
|
+
const bytes = new TextEncoder().encode(json);
|
|
846
|
+
let binary = "";
|
|
847
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
848
|
+
binary += String.fromCharCode(bytes[i]);
|
|
849
|
+
}
|
|
850
|
+
return btoa(binary)
|
|
851
|
+
.replace(/\+/g, "-")
|
|
852
|
+
.replace(/\//g, "_")
|
|
853
|
+
.replace(/=+$/, "");
|
|
854
|
+
}
|
|
855
|
+
function decodeSearchCursor(raw) {
|
|
856
|
+
try {
|
|
857
|
+
const base64 = raw.replace(/-/g, "+").replace(/_/g, "/");
|
|
858
|
+
const binary = atob(base64);
|
|
859
|
+
const bytes = new Uint8Array(binary.length);
|
|
860
|
+
for (let i = 0; i < binary.length; i++) {
|
|
861
|
+
bytes[i] = binary.charCodeAt(i);
|
|
862
|
+
}
|
|
863
|
+
const json = new TextDecoder().decode(bytes);
|
|
864
|
+
const payload = JSON.parse(json);
|
|
865
|
+
if (typeof payload === "object" &&
|
|
866
|
+
payload !== null &&
|
|
867
|
+
typeof payload.scopeIndex === "number") {
|
|
868
|
+
return {
|
|
869
|
+
scopeIndex: payload.scopeIndex,
|
|
870
|
+
blockCursor: typeof payload.blockCursor === "string"
|
|
871
|
+
? payload.blockCursor
|
|
872
|
+
: undefined,
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
return null;
|
|
876
|
+
}
|
|
877
|
+
catch {
|
|
878
|
+
return null;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
const searchPersonalContext = {
|
|
882
|
+
name: SEARCH_PERSONAL_CONTEXT_TOOL,
|
|
883
|
+
title: "Search personal context",
|
|
884
|
+
description: "Search approved scopes. Omit scopes for default sweep; name scopes for targeted search. Continue with nextSearchCursor. Free discovery/preview — it does not settle payments; chargeable scopes are returned in paymentRequiredScopes, fetch those via read_scope with a `payment` proof.",
|
|
885
|
+
inputSchema: {
|
|
886
|
+
query: z.string().min(1).max(SEARCH_QUERY_MAX_CHARS),
|
|
887
|
+
scopes: z
|
|
888
|
+
.array(z.string().min(1).max(SEARCH_SCOPE_MAX_CHARS))
|
|
889
|
+
.max(SEARCH_REQUESTED_SCOPES_LIMIT)
|
|
890
|
+
.optional(),
|
|
891
|
+
cursor: z.string().min(1).optional(),
|
|
892
|
+
maxResults: z.number().int().min(1).max(MAX_SEARCH_LIMIT).optional(),
|
|
893
|
+
limit: z.number().int().min(1).max(MAX_SEARCH_LIMIT).optional(),
|
|
894
|
+
maxScopes: z.number().int().min(1).max(MAX_SEARCH_SCOPES).optional(),
|
|
895
|
+
timeoutMs: z.number().int().min(1000).max(MAX_SEARCH_TIMEOUT_MS).optional(),
|
|
896
|
+
maxBytes: z.number().int().min(1).max(MAX_SEARCH_MAX_BYTES).optional(),
|
|
897
|
+
},
|
|
898
|
+
async handler(args, { connection, readClient }) {
|
|
899
|
+
const query = typeof args.query === "string" ? args.query.trim() : "";
|
|
900
|
+
if (!query) {
|
|
901
|
+
return textResult({ error: "query is required and must be a non-empty string" }, true);
|
|
902
|
+
}
|
|
903
|
+
if (query.length > SEARCH_QUERY_MAX_CHARS) {
|
|
904
|
+
return textResult({
|
|
905
|
+
error: "query_too_long",
|
|
906
|
+
maxQueryChars: SEARCH_QUERY_MAX_CHARS,
|
|
907
|
+
}, true);
|
|
908
|
+
}
|
|
909
|
+
const normalizedScopes = normalizeRequestedScopes(args.scopes, connection);
|
|
910
|
+
const requestedScopes = normalizedScopes.requestedScopes;
|
|
911
|
+
// maxResults takes precedence over deprecated limit for forward compat
|
|
912
|
+
const limit = clampInteger(args.maxResults ?? args.limit, DEFAULT_SEARCH_LIMIT, 1, MAX_SEARCH_LIMIT);
|
|
913
|
+
const maxScopes = clampInteger(args.maxScopes, DEFAULT_SEARCH_MAX_SCOPES, 1, MAX_SEARCH_SCOPES);
|
|
914
|
+
const timeoutMs = clampInteger(args.timeoutMs, DEFAULT_SEARCH_TIMEOUT_MS, 1000, MAX_SEARCH_TIMEOUT_MS);
|
|
915
|
+
const maxBytes = clampInteger(args.maxBytes, DEFAULT_SEARCH_MAX_BYTES, 1, MAX_SEARCH_MAX_BYTES);
|
|
916
|
+
const totalTimeoutMs = Math.min(MAX_SEARCH_TOTAL_TIMEOUT_MS, timeoutMs);
|
|
917
|
+
const startedAt = Date.now();
|
|
918
|
+
const deadline = startedAt + totalTimeoutMs;
|
|
919
|
+
const discoveryTimeoutMs = Math.min(totalTimeoutMs, DEFAULT_SEARCH_DISCOVERY_TIMEOUT_MS);
|
|
920
|
+
// Decode continuation cursor if provided
|
|
921
|
+
const resumeCursor = typeof args.cursor === "string" ? decodeSearchCursor(args.cursor) : null;
|
|
922
|
+
const resolved = await resolveSearchScopes({
|
|
923
|
+
connection,
|
|
924
|
+
maxScopes,
|
|
925
|
+
discoveryTimeoutMs,
|
|
926
|
+
readClient,
|
|
927
|
+
requestedScopes,
|
|
928
|
+
});
|
|
929
|
+
const matches = [];
|
|
930
|
+
const searchedScopes = [];
|
|
931
|
+
const truncatedScopes = [];
|
|
932
|
+
// Chargeable scopes hit on a paid (self-signing) session. Search is a free
|
|
933
|
+
// discovery/preview surface and does not settle x402 payments — it surfaces
|
|
934
|
+
// these explicitly so the client retrieves them via read_scope with a
|
|
935
|
+
// `payment` proof, instead of burying the 402 as an opaque scope error.
|
|
936
|
+
const paymentRequiredScopes = [];
|
|
937
|
+
const errors = [...resolved.errors];
|
|
938
|
+
// When resuming, start from the scope index encoded in the cursor
|
|
939
|
+
const startScopeIndex = resumeCursor
|
|
940
|
+
? Math.max(0, Math.min(resumeCursor.scopeIndex, resolved.scopes.length))
|
|
941
|
+
: 0;
|
|
942
|
+
const initialBlockCursor = resumeCursor?.blockCursor ?? undefined;
|
|
943
|
+
let nextSearchCursor;
|
|
944
|
+
for (let scopeIndex = startScopeIndex; scopeIndex < resolved.scopes.length; scopeIndex += 1) {
|
|
945
|
+
const scope = resolved.scopes[scopeIndex];
|
|
946
|
+
const remainingBeforeScope = deadline - Date.now();
|
|
947
|
+
if (remainingBeforeScope <= 0) {
|
|
948
|
+
// Budget exhausted — offer cursor to continue from this scope
|
|
949
|
+
nextSearchCursor = encodeSearchCursor({ scopeIndex });
|
|
950
|
+
errors.push({ scope, error: "search_total_timeout" });
|
|
951
|
+
break;
|
|
952
|
+
}
|
|
953
|
+
const grant = resolveGrantForScope(connection, scope);
|
|
954
|
+
if (!grant)
|
|
955
|
+
continue;
|
|
956
|
+
// The index preview path bypasses the data API auth port (and thus
|
|
957
|
+
// x402). Skip it when reads are payment-enforced so paid content is never
|
|
958
|
+
// previewed for free — search falls back to the paid bounded block read.
|
|
959
|
+
if (!resumeCursor &&
|
|
960
|
+
!readClient.enforcesPayment &&
|
|
961
|
+
typeof readClient.searchScopeIndex === "function") {
|
|
962
|
+
const remainingMs = deadline - Date.now();
|
|
963
|
+
if (remainingMs <= 0) {
|
|
964
|
+
nextSearchCursor = encodeSearchCursor({ scopeIndex });
|
|
965
|
+
errors.push({ scope, error: "search_total_timeout" });
|
|
966
|
+
break;
|
|
967
|
+
}
|
|
968
|
+
const remainingScopes = resolved.scopes.length - scopeIndex;
|
|
969
|
+
const perScopeTimeoutMs = Math.min(Math.max(250, Math.floor(remainingMs / Math.max(1, remainingScopes))), remainingMs);
|
|
970
|
+
try {
|
|
971
|
+
const indexed = await withTimeout(readClient.searchScopeIndex({
|
|
972
|
+
scope,
|
|
973
|
+
grantId: grant.grantId,
|
|
974
|
+
query,
|
|
975
|
+
maxResults: limit - matches.length,
|
|
976
|
+
}), perScopeTimeoutMs, `search index for ${scope}`);
|
|
977
|
+
if (indexed.status === "hit") {
|
|
978
|
+
searchedScopes.push(scope);
|
|
979
|
+
for (const hit of indexed.hits) {
|
|
980
|
+
if (matches.length >= limit)
|
|
981
|
+
break;
|
|
982
|
+
matches.push({
|
|
983
|
+
scope,
|
|
984
|
+
grantId: grant.grantId,
|
|
985
|
+
preview: hit.preview ?? hit.title ?? "",
|
|
986
|
+
searchedChars: 0,
|
|
987
|
+
truncated: false,
|
|
988
|
+
blockRef: hit.blockRef,
|
|
989
|
+
score: hit.score,
|
|
990
|
+
terms: hit.terms,
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
if (matches.length >= limit) {
|
|
994
|
+
const nextScopeIndex = scopeIndex + 1;
|
|
995
|
+
if (nextScopeIndex < resolved.scopes.length) {
|
|
996
|
+
nextSearchCursor = encodeSearchCursor({
|
|
997
|
+
scopeIndex: nextScopeIndex,
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
break;
|
|
1001
|
+
}
|
|
1002
|
+
continue;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
catch (err) {
|
|
1006
|
+
errors.push({
|
|
1007
|
+
scope,
|
|
1008
|
+
error: err instanceof OperationTimeoutError
|
|
1009
|
+
? "scope_index_timeout"
|
|
1010
|
+
: "scope_index_failed",
|
|
1011
|
+
bodyPreview: stringifyPreview(err instanceof OperationTimeoutError
|
|
1012
|
+
? err.message
|
|
1013
|
+
: "unexpected scope index failure"),
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
// Use block cursor from resume only for the first scope we process
|
|
1018
|
+
let cursor = scopeIndex === startScopeIndex ? initialBlockCursor : undefined;
|
|
1019
|
+
let pageCount = 0;
|
|
1020
|
+
let scopeTruncated = false;
|
|
1021
|
+
let scopeSearched = false;
|
|
1022
|
+
try {
|
|
1023
|
+
while (pageCount < SEARCH_MAX_PAGES_PER_SCOPE) {
|
|
1024
|
+
const remainingMs = deadline - Date.now();
|
|
1025
|
+
if (remainingMs <= 0) {
|
|
1026
|
+
// Budget hit mid-scope — encode cursor pointing at this scope + block
|
|
1027
|
+
nextSearchCursor = encodeSearchCursor({
|
|
1028
|
+
scopeIndex,
|
|
1029
|
+
blockCursor: cursor,
|
|
1030
|
+
});
|
|
1031
|
+
errors.push({ scope, error: "search_total_timeout" });
|
|
1032
|
+
scopeTruncated = Boolean(cursor);
|
|
1033
|
+
break;
|
|
1034
|
+
}
|
|
1035
|
+
pageCount += 1;
|
|
1036
|
+
if (!scopeSearched) {
|
|
1037
|
+
searchedScopes.push(scope);
|
|
1038
|
+
scopeSearched = true;
|
|
1039
|
+
}
|
|
1040
|
+
const remainingScopes = resolved.scopes.length - scopeIndex;
|
|
1041
|
+
const perScopeTimeoutMs = Math.min(Math.max(250, Math.floor(remainingMs / Math.max(1, remainingScopes))), remainingMs);
|
|
1042
|
+
const result = await withTimeout(readClient.readScopeBlocks({
|
|
1043
|
+
scope,
|
|
1044
|
+
grantId: grant.grantId,
|
|
1045
|
+
cursor,
|
|
1046
|
+
maxBytes,
|
|
1047
|
+
tool: SEARCH_PERSONAL_CONTEXT_TOOL,
|
|
1048
|
+
}), perScopeTimeoutMs, `read blocks for ${scope}`);
|
|
1049
|
+
const pageText = blockPageSearchText(result.blocks);
|
|
1050
|
+
const { documents, textById } = blockPageSearchDocuments(scope, result.blocks);
|
|
1051
|
+
// Every hit the page can still contribute, not one per page: the
|
|
1052
|
+
// index is thrown away with the page, so a discarded hit is a block
|
|
1053
|
+
// the caller can never reach again.
|
|
1054
|
+
const pageHits = documents.length > 0
|
|
1055
|
+
? MiniSearchIndex.build(documents).search({
|
|
1056
|
+
query,
|
|
1057
|
+
limit: limit - matches.length,
|
|
1058
|
+
})
|
|
1059
|
+
: [];
|
|
1060
|
+
for (const pageHit of pageHits) {
|
|
1061
|
+
if (matches.length >= limit)
|
|
1062
|
+
break;
|
|
1063
|
+
const hitText = textById.get(pageHit.id) ?? pageText;
|
|
1064
|
+
matches.push({
|
|
1065
|
+
scope,
|
|
1066
|
+
grantId: grant.grantId,
|
|
1067
|
+
collectedAt: result.collectedAt,
|
|
1068
|
+
preview: previewMatch(hitText, miniSearchMatchIndex(hitText, pageHit.terms)),
|
|
1069
|
+
searchedChars: pageText.length,
|
|
1070
|
+
truncated: Boolean(result.nextCursor),
|
|
1071
|
+
// The id of the block that matched, so the caller can fetch it
|
|
1072
|
+
// with read_scope({ blockIds }) instead of walking cursors.
|
|
1073
|
+
...(pageHit.blockRef ? { blockRef: pageHit.blockRef } : {}),
|
|
1074
|
+
score: pageHit.score,
|
|
1075
|
+
terms: pageHit.terms,
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
if (result.nextCursor) {
|
|
1079
|
+
cursor = result.nextCursor;
|
|
1080
|
+
if (pageCount >= SEARCH_MAX_PAGES_PER_SCOPE ||
|
|
1081
|
+
matches.length >= limit) {
|
|
1082
|
+
scopeTruncated = true;
|
|
1083
|
+
// More pages remain in this scope — cursor points here
|
|
1084
|
+
nextSearchCursor = encodeSearchCursor({
|
|
1085
|
+
scopeIndex,
|
|
1086
|
+
blockCursor: cursor,
|
|
1087
|
+
});
|
|
1088
|
+
break;
|
|
1089
|
+
}
|
|
1090
|
+
continue;
|
|
1091
|
+
}
|
|
1092
|
+
break;
|
|
1093
|
+
}
|
|
1094
|
+
if (scopeTruncated) {
|
|
1095
|
+
truncatedScopes.push(scope);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
catch (err) {
|
|
1099
|
+
const paymentChallenge = err instanceof McpDataReadError &&
|
|
1100
|
+
err.status === 402 &&
|
|
1101
|
+
err.body?.error?.errorCode ===
|
|
1102
|
+
"PAYMENT_REQUIRED";
|
|
1103
|
+
if (paymentChallenge) {
|
|
1104
|
+
// Paid session hit a chargeable scope. Surface it as payment-required
|
|
1105
|
+
// (retrieve via read_scope with `payment`) rather than a scope error.
|
|
1106
|
+
// A gateway 402 (PAYMENT_GATEWAY_ERROR) falls through to `errors`.
|
|
1107
|
+
if (!paymentRequiredScopes.includes(scope)) {
|
|
1108
|
+
paymentRequiredScopes.push(scope);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
else {
|
|
1112
|
+
errors.push({
|
|
1113
|
+
scope,
|
|
1114
|
+
error: err instanceof OperationTimeoutError
|
|
1115
|
+
? "scope_search_timeout"
|
|
1116
|
+
: "scope_read_failed",
|
|
1117
|
+
status: err instanceof McpDataReadError ? err.status : undefined,
|
|
1118
|
+
bodyPreview: stringifyPreview(err instanceof McpDataReadError
|
|
1119
|
+
? err.body
|
|
1120
|
+
: err instanceof OperationTimeoutError
|
|
1121
|
+
? err.message
|
|
1122
|
+
: "unexpected scope read failure"),
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
if (matches.length >= limit) {
|
|
1127
|
+
// Result limit hit — if more scopes remain, offer cursor for next scope.
|
|
1128
|
+
// A cursor set inside the scope wins: it still has unread pages, and
|
|
1129
|
+
// pointing past it would silently drop them.
|
|
1130
|
+
const nextScopeIndex = scopeIndex + 1;
|
|
1131
|
+
if (!nextSearchCursor && nextScopeIndex < resolved.scopes.length) {
|
|
1132
|
+
nextSearchCursor = encodeSearchCursor({
|
|
1133
|
+
scopeIndex: nextScopeIndex,
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
break;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
const elapsedMs = Date.now() - startedAt;
|
|
1140
|
+
// Matches accumulate in page order, so a weak early hit would otherwise
|
|
1141
|
+
// outrank a strong later one. Scores are only comparable within one page's
|
|
1142
|
+
// index, but best-first is still strictly better than page order for the
|
|
1143
|
+
// caller. Unscored matches (budget-exhausted fallback path) keep their
|
|
1144
|
+
// relative order at the end.
|
|
1145
|
+
matches.sort((a, b) => (b.score ?? -1) - (a.score ?? -1));
|
|
1146
|
+
return textResult({
|
|
1147
|
+
query,
|
|
1148
|
+
results: matches,
|
|
1149
|
+
// Backward-compatible alias for existing clients/tests. `results` is the
|
|
1150
|
+
// canonical field, but removing `matches` is a separate migration.
|
|
1151
|
+
matches,
|
|
1152
|
+
searchedScopes,
|
|
1153
|
+
skippedScopes: [
|
|
1154
|
+
...normalizedScopes.skippedScopes,
|
|
1155
|
+
...resolved.skippedScopes,
|
|
1156
|
+
],
|
|
1157
|
+
truncatedScopes,
|
|
1158
|
+
errors,
|
|
1159
|
+
...(paymentRequiredScopes.length > 0
|
|
1160
|
+
? {
|
|
1161
|
+
payment_required: true,
|
|
1162
|
+
paymentRequiredScopes,
|
|
1163
|
+
paymentHint: "These scopes are chargeable; search does not settle x402 payments. Retrieve each with read_scope (it returns a payment_required challenge; sign it and re-call read_scope with the `payment` argument).",
|
|
1164
|
+
}
|
|
1165
|
+
: {}),
|
|
1166
|
+
...(nextSearchCursor ? { nextSearchCursor } : {}),
|
|
1167
|
+
elapsedMs,
|
|
1168
|
+
limits: {
|
|
1169
|
+
maxResults: limit,
|
|
1170
|
+
maxScopes,
|
|
1171
|
+
maxRequestedScopes: SEARCH_REQUESTED_SCOPES_LIMIT,
|
|
1172
|
+
timeoutMs,
|
|
1173
|
+
discoveryTimeoutMs,
|
|
1174
|
+
queryChars: SEARCH_QUERY_MAX_CHARS,
|
|
1175
|
+
scopeChars: SEARCH_SCOPE_MAX_CHARS,
|
|
1176
|
+
previewChars: SEARCH_PREVIEW_CHARS,
|
|
1177
|
+
requestedBytesPerPage: maxBytes,
|
|
1178
|
+
maxPagesPerScope: SEARCH_MAX_PAGES_PER_SCOPE,
|
|
1179
|
+
totalTimeoutMs,
|
|
1180
|
+
},
|
|
1181
|
+
});
|
|
1182
|
+
},
|
|
1183
|
+
};
|
|
1184
|
+
const getScopeFile = {
|
|
1185
|
+
name: "get_scope_file",
|
|
1186
|
+
title: "Get scope file",
|
|
1187
|
+
description: "Fetch an approved file/PDF scope. Defaults to metadata plus a resource link; set includeContent=true only when the client supports inline resource blobs. Use search_personal_context for extracted text.",
|
|
1188
|
+
inputSchema: {
|
|
1189
|
+
...rawScopeFileInputSchema,
|
|
1190
|
+
// x402 proof (see read_scope's `payment`); provide after a payment_required.
|
|
1191
|
+
payment: z.string().optional(),
|
|
1192
|
+
timeoutMs: z
|
|
1193
|
+
.number()
|
|
1194
|
+
.int()
|
|
1195
|
+
.min(1000)
|
|
1196
|
+
.max(MAX_READ_FILE_TIMEOUT_MS)
|
|
1197
|
+
.optional(),
|
|
1198
|
+
},
|
|
1199
|
+
async handler(args, { connection, readClient }) {
|
|
1200
|
+
const scope = typeof args.scope === "string" ? args.scope : null;
|
|
1201
|
+
if (!scope) {
|
|
1202
|
+
return textResult({ error: "scope is required and must be a string" }, true);
|
|
1203
|
+
}
|
|
1204
|
+
const grant = resolveGrantForScope(connection, scope);
|
|
1205
|
+
if (!grant) {
|
|
1206
|
+
return textResult({
|
|
1207
|
+
error: "scope_not_granted",
|
|
1208
|
+
message: `Scope '${scope}' is not covered by any grant on this MCP connection.`,
|
|
1209
|
+
grantedScopes: uniqueScopes(connection),
|
|
1210
|
+
}, true);
|
|
1211
|
+
}
|
|
1212
|
+
const at = typeof args.at === "string" ? args.at : undefined;
|
|
1213
|
+
const fileId = typeof args.fileId === "string" ? args.fileId : undefined;
|
|
1214
|
+
const includeContent = typeof args.includeContent === "boolean" ? args.includeContent : false;
|
|
1215
|
+
const maxBytes = clampInteger(args.maxBytes, DEFAULT_READ_FILE_MAX_BYTES, 1, MAX_READ_FILE_MAX_BYTES);
|
|
1216
|
+
const timeoutMs = clampInteger(args.timeoutMs, DEFAULT_READ_FILE_TIMEOUT_MS, 1000, MAX_READ_FILE_TIMEOUT_MS);
|
|
1217
|
+
const resourceUri = rawScopeResourceUri({ scope, at, fileId });
|
|
1218
|
+
const payment = typeof args.payment === "string" ? args.payment : undefined;
|
|
1219
|
+
try {
|
|
1220
|
+
const raw = await withTimeout(readClient.readRawScopeFile({
|
|
1221
|
+
scope,
|
|
1222
|
+
grantId: grant.grantId,
|
|
1223
|
+
at,
|
|
1224
|
+
fileId,
|
|
1225
|
+
payment,
|
|
1226
|
+
}), timeoutMs, `read raw file for ${scope}`);
|
|
1227
|
+
const metadata = rawScopeMetadata(raw);
|
|
1228
|
+
const base = {
|
|
1229
|
+
...metadata,
|
|
1230
|
+
grantId: grant.grantId,
|
|
1231
|
+
resourceUri,
|
|
1232
|
+
includeContent,
|
|
1233
|
+
maxBytes,
|
|
1234
|
+
timeoutMs,
|
|
1235
|
+
};
|
|
1236
|
+
// A paid read settles a SINGLE-USE x402 payment for this fetch. A resource
|
|
1237
|
+
// link would force a second, unpaid resources/read → another 402 that
|
|
1238
|
+
// cannot be satisfied, so a paid read returns the bytes inline (regardless
|
|
1239
|
+
// of includeContent) — BUT the hard size cap is still enforced to protect
|
|
1240
|
+
// server/client memory and the MCP transport. An oversized paid file
|
|
1241
|
+
// returns a structured error, never an inline dump; clients size maxBytes
|
|
1242
|
+
// from the payment challenge's `sizeBytes` before paying.
|
|
1243
|
+
const paidRead = Boolean(payment);
|
|
1244
|
+
if (paidRead && raw.sizeBytes > maxBytes) {
|
|
1245
|
+
return textResult({
|
|
1246
|
+
error: "paid_file_exceeds_max_bytes",
|
|
1247
|
+
status: 413,
|
|
1248
|
+
sizeBytes: raw.sizeBytes,
|
|
1249
|
+
maxBytes,
|
|
1250
|
+
message: "Payment settled, but the file exceeds maxBytes and is not returned inline. Retry with a larger maxBytes (and a fresh payment); files above the inline ceiling cannot be delivered over MCP.",
|
|
1251
|
+
}, true);
|
|
1252
|
+
}
|
|
1253
|
+
if (!paidRead && (!includeContent || raw.sizeBytes > maxBytes)) {
|
|
1254
|
+
return {
|
|
1255
|
+
content: [
|
|
1256
|
+
{
|
|
1257
|
+
type: "text",
|
|
1258
|
+
text: JSON.stringify({
|
|
1259
|
+
...base,
|
|
1260
|
+
contentIncluded: false,
|
|
1261
|
+
reason: !includeContent
|
|
1262
|
+
? "includeContent=false"
|
|
1263
|
+
: "file exceeds maxBytes for inline MCP tool response",
|
|
1264
|
+
nextStep: "Use resources/read with resourceUri if your MCP client supports resources, or retry with a higher maxBytes if appropriate.",
|
|
1265
|
+
}),
|
|
1266
|
+
},
|
|
1267
|
+
{
|
|
1268
|
+
type: "resource_link",
|
|
1269
|
+
uri: resourceUri,
|
|
1270
|
+
name: raw.filename ?? scope,
|
|
1271
|
+
title: raw.filename ?? scope,
|
|
1272
|
+
description: `Raw file for ${scope}`,
|
|
1273
|
+
mimeType: raw.mimeType,
|
|
1274
|
+
size: raw.sizeBytes,
|
|
1275
|
+
},
|
|
1276
|
+
],
|
|
1277
|
+
structuredContent: {
|
|
1278
|
+
...base,
|
|
1279
|
+
contentIncluded: false,
|
|
1280
|
+
},
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
return {
|
|
1284
|
+
content: [
|
|
1285
|
+
{
|
|
1286
|
+
type: "text",
|
|
1287
|
+
text: JSON.stringify({
|
|
1288
|
+
...base,
|
|
1289
|
+
contentIncluded: true,
|
|
1290
|
+
resourceType: "blob",
|
|
1291
|
+
}),
|
|
1292
|
+
},
|
|
1293
|
+
{
|
|
1294
|
+
type: "resource",
|
|
1295
|
+
resource: {
|
|
1296
|
+
uri: resourceUri,
|
|
1297
|
+
mimeType: raw.mimeType,
|
|
1298
|
+
blob: raw.contentBase64,
|
|
1299
|
+
_meta: metadata,
|
|
1300
|
+
},
|
|
1301
|
+
},
|
|
1302
|
+
],
|
|
1303
|
+
structuredContent: {
|
|
1304
|
+
...base,
|
|
1305
|
+
contentIncluded: true,
|
|
1306
|
+
resourceType: "blob",
|
|
1307
|
+
},
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
catch (err) {
|
|
1311
|
+
if (err instanceof OperationTimeoutError) {
|
|
1312
|
+
return textResult({
|
|
1313
|
+
error: "scope_file_timeout",
|
|
1314
|
+
message: err.message,
|
|
1315
|
+
timeoutMs: err.timeoutMs,
|
|
1316
|
+
}, true);
|
|
1317
|
+
}
|
|
1318
|
+
if (err instanceof McpDataReadError) {
|
|
1319
|
+
if (err.status === 402) {
|
|
1320
|
+
const body = err.body;
|
|
1321
|
+
const challenge = body?.error?.details?.challenge;
|
|
1322
|
+
// Only a genuine PAYMENT_REQUIRED with a challenge is signable; a
|
|
1323
|
+
// gateway 402 (e.g. insufficient escrow) is surfaced as an error.
|
|
1324
|
+
if (body?.error?.errorCode === "PAYMENT_REQUIRED" && challenge) {
|
|
1325
|
+
// Surface the file size so the client can set maxBytes >= sizeBytes
|
|
1326
|
+
// BEFORE paying — a paid read must fit the inline size cap (the
|
|
1327
|
+
// single-use payment can't fund a second resources/read).
|
|
1328
|
+
let meta = null;
|
|
1329
|
+
try {
|
|
1330
|
+
meta = await readClient.getScopeMetadata(scope);
|
|
1331
|
+
}
|
|
1332
|
+
catch {
|
|
1333
|
+
meta = null;
|
|
1334
|
+
}
|
|
1335
|
+
const sizeBytes = meta?.sizeBytes;
|
|
1336
|
+
return textResult({
|
|
1337
|
+
payment_required: true,
|
|
1338
|
+
status: 402,
|
|
1339
|
+
challenge,
|
|
1340
|
+
...(typeof sizeBytes === "number"
|
|
1341
|
+
? { sizeBytes, recommendedMaxBytes: sizeBytes }
|
|
1342
|
+
: {}),
|
|
1343
|
+
message: "This file requires payment. Sign the x402 challenge and call get_scope_file again with the `payment` argument" +
|
|
1344
|
+
(typeof sizeBytes === "number"
|
|
1345
|
+
? ` and maxBytes >= ${sizeBytes}.`
|
|
1346
|
+
: "."),
|
|
1347
|
+
}, true);
|
|
1348
|
+
}
|
|
1349
|
+
return textResult({
|
|
1350
|
+
error: "payment_failed",
|
|
1351
|
+
status: 402,
|
|
1352
|
+
errorCode: body?.error?.errorCode ?? "PAYMENT_ERROR",
|
|
1353
|
+
message: body?.error?.message ??
|
|
1354
|
+
"Payment could not be completed. This is not a signable challenge; resolve the gateway error and retry.",
|
|
1355
|
+
}, true);
|
|
1356
|
+
}
|
|
1357
|
+
return textResult({
|
|
1358
|
+
error: err.status === 404
|
|
1359
|
+
? "scope_file_not_found"
|
|
1360
|
+
: "scope_file_read_failed",
|
|
1361
|
+
status: err.status,
|
|
1362
|
+
body: err.body,
|
|
1363
|
+
}, true);
|
|
1364
|
+
}
|
|
1365
|
+
return textResult({
|
|
1366
|
+
error: "tool_handler_error",
|
|
1367
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1368
|
+
}, true);
|
|
1369
|
+
}
|
|
1370
|
+
},
|
|
1371
|
+
};
|
|
1372
|
+
export const MCP_TOOLS = [
|
|
1373
|
+
listGrantedSources,
|
|
1374
|
+
listGrantedScopes,
|
|
1375
|
+
requestScopeAccess,
|
|
1376
|
+
readScope,
|
|
1377
|
+
listScopeBlocks,
|
|
1378
|
+
getScopeFile,
|
|
1379
|
+
searchPersonalContext,
|
|
1380
|
+
];
|
|
1381
|
+
//# sourceMappingURL=tools.js.map
|