@opendatalabs/personal-server-ts-core 0.0.1-canary.01ca694 → 0.0.1-canary.052d47e
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 +282 -9
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +867 -163
- package/dist/api/index.js.map +1 -1
- package/dist/auth/request.d.ts.map +1 -1
- package/dist/auth/request.js.map +1 -1
- package/dist/client.d.ts +8 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +21 -3
- package/dist/client.js.map +1 -1
- package/dist/contracts/binary.d.ts +12 -0
- package/dist/contracts/binary.d.ts.map +1 -1
- package/dist/contracts/binary.js +31 -0
- package/dist/contracts/binary.js.map +1 -1
- package/dist/contracts/data.d.ts +51 -5
- package/dist/contracts/data.d.ts.map +1 -1
- package/dist/contracts/data.js +166 -30
- package/dist/contracts/data.js.map +1 -1
- package/dist/contracts/grants.d.ts +7 -5
- package/dist/contracts/grants.d.ts.map +1 -1
- package/dist/contracts/grants.js +60 -37
- package/dist/contracts/grants.js.map +1 -1
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js +1 -1
- package/dist/contracts/index.js.map +1 -1
- package/dist/derivatives/api.d.ts +48 -0
- package/dist/derivatives/api.d.ts.map +1 -0
- package/dist/derivatives/api.js +366 -0
- package/dist/derivatives/api.js.map +1 -0
- package/dist/derivatives/compute.d.ts +106 -0
- package/dist/derivatives/compute.d.ts.map +1 -0
- package/dist/derivatives/compute.js +414 -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 +10 -0
- package/dist/derivatives/index.d.ts.map +1 -0
- package/dist/derivatives/index.js +10 -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 +64 -0
- package/dist/derivatives/prompt.d.ts.map +1 -0
- package/dist/derivatives/prompt.js +193 -0
- package/dist/derivatives/prompt.js.map +1 -0
- package/dist/derivatives/registration.d.ts +54 -0
- package/dist/derivatives/registration.d.ts.map +1 -0
- package/dist/derivatives/registration.js +180 -0
- package/dist/derivatives/registration.js.map +1 -0
- package/dist/derivatives/scheduler.d.ts +84 -0
- package/dist/derivatives/scheduler.d.ts.map +1 -0
- package/dist/derivatives/scheduler.js +257 -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 +77 -0
- package/dist/derivatives/store.js.map +1 -0
- package/dist/derivatives/types.d.ts +92 -0
- package/dist/derivatives/types.d.ts.map +1 -0
- package/dist/derivatives/types.js +27 -0
- package/dist/derivatives/types.js.map +1 -0
- package/dist/errors/catalog.d.ts +75 -0
- package/dist/errors/catalog.d.ts.map +1 -1
- package/dist/errors/catalog.js +104 -0
- package/dist/errors/catalog.js.map +1 -1
- package/dist/grants/index.d.ts +2 -1
- package/dist/grants/index.d.ts.map +1 -1
- package/dist/grants/index.js +1 -1
- package/dist/grants/index.js.map +1 -1
- package/dist/keys/index.d.ts +1 -0
- package/dist/keys/index.d.ts.map +1 -1
- package/dist/keys/index.js +1 -1
- package/dist/keys/index.js.map +1 -1
- 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 +2 -2
- package/dist/keys/server-account.d.ts.map +1 -1
- 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/logging/access-log.d.ts +10 -1
- package/dist/logging/access-log.d.ts.map +1 -1
- package/dist/mcp/activity.d.ts.map +1 -1
- package/dist/mcp/activity.js.map +1 -1
- package/dist/mcp/connection-api.d.ts.map +1 -1
- package/dist/mcp/connection-api.js +0 -1
- package/dist/mcp/connection-api.js.map +1 -1
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1 -0
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/read-client.d.ts +61 -1
- package/dist/mcp/read-client.d.ts.map +1 -1
- package/dist/mcp/read-client.js +397 -42
- package/dist/mcp/read-client.js.map +1 -1
- 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.map +1 -1
- package/dist/mcp/search/index.js +108 -21
- package/dist/mcp/search/index.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +27 -9
- package/dist/mcp/server.js.map +1 -1
- 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/tools.d.ts +29 -3
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +515 -48
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp/types.d.ts.map +1 -1
- 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/policy/data-read.d.ts +18 -2
- package/dist/policy/data-read.d.ts.map +1 -1
- package/dist/policy/data-read.js +80 -49
- package/dist/policy/data-read.js.map +1 -1
- 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 +3 -1
- package/dist/policy/index.d.ts.map +1 -1
- package/dist/policy/index.js +3 -1
- package/dist/policy/index.js.map +1 -1
- 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 +159 -32
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/ports/index.js +6 -5
- package/dist/ports/index.js.map +1 -1
- package/dist/schemas/server-config.d.ts +49 -4
- package/dist/schemas/server-config.d.ts.map +1 -1
- package/dist/schemas/server-config.js +95 -13
- package/dist/schemas/server-config.js.map +1 -1
- package/dist/signing/index.d.ts +2 -2
- package/dist/signing/index.d.ts.map +1 -1
- package/dist/signing/index.js +1 -1
- package/dist/signing/index.js.map +1 -1
- package/dist/signing/request-signer.d.ts +2 -0
- package/dist/signing/request-signer.d.ts.map +1 -1
- package/dist/signing/request-signer.js +1 -0
- package/dist/signing/request-signer.js.map +1 -1
- package/dist/signing/signer.d.ts +27 -25
- package/dist/signing/signer.d.ts.map +1 -1
- package/dist/signing/signer.js +27 -51
- package/dist/signing/signer.js.map +1 -1
- package/dist/storage/adapters/index.d.ts +1 -1
- package/dist/storage/adapters/index.d.ts.map +1 -1
- package/dist/storage/adapters/index.js +1 -1
- package/dist/storage/adapters/index.js.map +1 -1
- package/dist/storage/adapters/interface.d.ts +10 -0
- package/dist/storage/adapters/interface.d.ts.map +1 -1
- package/dist/storage/adapters/sdk.d.ts +11 -1
- package/dist/storage/adapters/sdk.d.ts.map +1 -1
- package/dist/storage/adapters/sdk.js +38 -3
- package/dist/storage/adapters/sdk.js.map +1 -1
- package/dist/storage/adapters/vana.d.ts +3 -0
- package/dist/storage/adapters/vana.d.ts.map +1 -1
- package/dist/storage/adapters/vana.js +95 -2
- package/dist/storage/adapters/vana.js.map +1 -1
- package/dist/storage/blocks/errors.d.ts.map +1 -1
- package/dist/storage/blocks/errors.js.map +1 -1
- package/dist/storage/blocks/index.d.ts +2 -0
- package/dist/storage/blocks/index.d.ts.map +1 -1
- package/dist/storage/blocks/index.js +2 -0
- package/dist/storage/blocks/index.js.map +1 -1
- 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 +7 -0
- package/dist/storage/blocks/types.d.ts.map +1 -1
- package/dist/storage/index/manager.d.ts +30 -2
- package/dist/storage/index/manager.d.ts.map +1 -1
- package/dist/storage/index/schema.d.ts +1 -1
- package/dist/storage/index/schema.js +1 -1
- package/dist/storage/index/types.d.ts +6 -1
- package/dist/storage/index/types.d.ts.map +1 -1
- 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 +16 -4
- package/dist/sync/engine/sync-manager.d.ts.map +1 -1
- package/dist/sync/engine/sync-manager.js +93 -32
- package/dist/sync/engine/sync-manager.js.map +1 -1
- package/dist/sync/index.d.ts +7 -0
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +7 -0
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/issues.d.ts.map +1 -1
- package/dist/sync/issues.js +21 -3
- package/dist/sync/issues.js.map +1 -1
- 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/workers/delete.d.ts +152 -31
- package/dist/sync/workers/delete.d.ts.map +1 -1
- package/dist/sync/workers/delete.js +457 -55
- package/dist/sync/workers/delete.js.map +1 -1
- package/dist/sync/workers/download.d.ts +108 -27
- package/dist/sync/workers/download.d.ts.map +1 -1
- package/dist/sync/workers/download.js +321 -126
- package/dist/sync/workers/download.js.map +1 -1
- package/dist/sync/workers/upload.d.ts +65 -10
- package/dist/sync/workers/upload.d.ts.map +1 -1
- package/dist/sync/workers/upload.js +371 -42
- package/dist/sync/workers/upload.js.map +1 -1
- 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 +3 -0
- package/dist/test-utils/index.d.ts.map +1 -1
- package/dist/test-utils/index.js +3 -0
- package/dist/test-utils/index.js.map +1 -1
- 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 +6 -0
- package/dist/test-utils/wallet.d.ts.map +1 -1
- package/dist/test-utils/wallet.js +46 -0
- package/dist/test-utils/wallet.js.map +1 -1
- 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 +19 -3
package/dist/mcp/tools.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* MCP tool surface. Phase 1 / §4 of 260604-PLAN-vana-mcp-personal-server.md.
|
|
3
3
|
*
|
|
4
4
|
* Read-only, small, and only over scopes that the connection's grants cover.
|
|
5
|
-
* No write tools. No grant-
|
|
5
|
+
* No write tools. No grant-approval tools. No raw owner/admin status tools.
|
|
6
6
|
*
|
|
7
7
|
* Every tool that returns data first picks a covering grant from the
|
|
8
8
|
* connection's grant list, signs a Web3Signed request as the per-connection
|
|
@@ -12,10 +12,16 @@
|
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
import { McpDataReadError } from "./read-client.js";
|
|
14
14
|
import { MiniSearchIndex } from "./search/index.js";
|
|
15
|
+
import { MAX_SELECTED_BLOCK_IDS } from "../storage/blocks/index.js";
|
|
16
|
+
import { rawScopeFileInputSchema, rawScopeMetadata, rawScopeResourceUri, } from "./resources.js";
|
|
15
17
|
const STRUCTURED_CONTENT_TEXT_LIMIT_BYTES = 64 * 1024;
|
|
16
18
|
const resultTextEncoder = new TextEncoder();
|
|
19
|
+
/**
|
|
20
|
+
* Tool payloads are machine-read, never displayed as-is: pretty-printing them
|
|
21
|
+
* inflated the wire size 1.37x-1.63x on real scopes for no reader benefit.
|
|
22
|
+
*/
|
|
17
23
|
function textResult(value, isError = false) {
|
|
18
|
-
const text = typeof value === "string" ? value : JSON.stringify(value
|
|
24
|
+
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
19
25
|
const textBytes = resultTextEncoder.encode(text).byteLength;
|
|
20
26
|
const structuredContent = !isError &&
|
|
21
27
|
textBytes <= STRUCTURED_CONTENT_TEXT_LIMIT_BYTES &&
|
|
@@ -60,24 +66,89 @@ function uniqueSources(connection) {
|
|
|
60
66
|
}
|
|
61
67
|
return Array.from(set).sort();
|
|
62
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Block ids a caller may name in one block-addressed read. Bounded so a model
|
|
71
|
+
* cannot turn `blockIds` into an unbounded fan-out; `maxBytes` still applies on
|
|
72
|
+
* top of it.
|
|
73
|
+
*/
|
|
74
|
+
function normalizeBlockIdsInput(value) {
|
|
75
|
+
if (!Array.isArray(value))
|
|
76
|
+
return [];
|
|
77
|
+
const blockIds = [];
|
|
78
|
+
const seen = new Set();
|
|
79
|
+
for (const raw of value) {
|
|
80
|
+
if (typeof raw !== "string")
|
|
81
|
+
continue;
|
|
82
|
+
const blockId = raw.trim();
|
|
83
|
+
if (!blockId || blockId.length > BLOCK_ID_MAX_CHARS || seen.has(blockId)) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
seen.add(blockId);
|
|
87
|
+
blockIds.push(blockId);
|
|
88
|
+
if (blockIds.length >= MAX_SELECTED_BLOCK_IDS)
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
return blockIds;
|
|
92
|
+
}
|
|
93
|
+
function normalizeScopeListInput(value) {
|
|
94
|
+
if (!Array.isArray(value))
|
|
95
|
+
return [];
|
|
96
|
+
const scopes = [];
|
|
97
|
+
const seen = new Set();
|
|
98
|
+
for (const raw of value) {
|
|
99
|
+
if (typeof raw !== "string")
|
|
100
|
+
continue;
|
|
101
|
+
const scope = raw.trim();
|
|
102
|
+
if (!scope || scope.length > SEARCH_SCOPE_MAX_CHARS || seen.has(scope)) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
seen.add(scope);
|
|
106
|
+
scopes.push(scope);
|
|
107
|
+
}
|
|
108
|
+
return scopes;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Hard ceiling the MCP dispatcher enforces on any tool call (see
|
|
112
|
+
* `clampToolTimeout` in `server.ts`, which imports this).
|
|
113
|
+
*
|
|
114
|
+
* Tools must never advertise a larger `timeoutMs` than the dispatcher allows: a
|
|
115
|
+
* client that trusts the advertised maximum gets killed with a hard
|
|
116
|
+
* `tool_timeout` instead of the graceful "budget exhausted, here are partial
|
|
117
|
+
* results plus nextSearchCursor" path the tools implement.
|
|
118
|
+
*/
|
|
119
|
+
export const MAX_MCP_TOOL_TIMEOUT_MS = 90_000;
|
|
63
120
|
const DEFAULT_SEARCH_LIMIT = 5;
|
|
64
121
|
const MAX_SEARCH_LIMIT = 50;
|
|
65
|
-
const
|
|
66
|
-
const
|
|
122
|
+
const MAX_SEARCH_SCOPES = 200;
|
|
123
|
+
const DEFAULT_SEARCH_MAX_SCOPES = MAX_SEARCH_SCOPES;
|
|
67
124
|
const DEFAULT_SEARCH_TIMEOUT_MS = 30_000;
|
|
68
|
-
const MAX_SEARCH_TIMEOUT_MS =
|
|
125
|
+
const MAX_SEARCH_TIMEOUT_MS = MAX_MCP_TOOL_TIMEOUT_MS;
|
|
69
126
|
const DEFAULT_SEARCH_DISCOVERY_TIMEOUT_MS = 2_000;
|
|
70
|
-
const MAX_SEARCH_TOTAL_TIMEOUT_MS =
|
|
127
|
+
const MAX_SEARCH_TOTAL_TIMEOUT_MS = MAX_MCP_TOOL_TIMEOUT_MS;
|
|
71
128
|
const DEFAULT_READ_SCOPE_MAX_BYTES = 64 * 1024;
|
|
72
129
|
const MAX_READ_SCOPE_MAX_BYTES = 5 * 1024 * 1024;
|
|
73
130
|
const DEFAULT_READ_SCOPE_TIMEOUT_MS = 60_000;
|
|
74
|
-
const MAX_READ_SCOPE_TIMEOUT_MS =
|
|
131
|
+
const MAX_READ_SCOPE_TIMEOUT_MS = MAX_MCP_TOOL_TIMEOUT_MS;
|
|
132
|
+
const DEFAULT_READ_FILE_TIMEOUT_MS = 60_000;
|
|
133
|
+
const MAX_READ_FILE_TIMEOUT_MS = MAX_MCP_TOOL_TIMEOUT_MS;
|
|
134
|
+
const DEFAULT_READ_FILE_MAX_BYTES = 10 * 1024 * 1024;
|
|
135
|
+
const MAX_READ_FILE_MAX_BYTES = 25 * 1024 * 1024;
|
|
75
136
|
const DEFAULT_SEARCH_MAX_BYTES = 64 * 1024;
|
|
76
137
|
const MAX_SEARCH_MAX_BYTES = 1024 * 1024;
|
|
77
138
|
const DEFAULT_SCOPE_METADATA_TIMEOUT_MS = 2_000;
|
|
78
|
-
const
|
|
139
|
+
const DEFAULT_LIST_BLOCKS_LIMIT = 100;
|
|
140
|
+
const MAX_LIST_BLOCKS_LIMIT = 500;
|
|
141
|
+
const LIST_BLOCKS_TIMEOUT_MS = 15_000;
|
|
142
|
+
/**
|
|
143
|
+
* Safety net, not a search budget: a scope is scanned page by page until the
|
|
144
|
+
* result limit or the time budget is hit, and this only stops a pathological
|
|
145
|
+
* scope from monopolising the run. Pages beyond it are resumable through
|
|
146
|
+
* `nextSearchCursor`, so raising it costs latency, never correctness.
|
|
147
|
+
*/
|
|
148
|
+
const SEARCH_MAX_PAGES_PER_SCOPE = 256;
|
|
79
149
|
const SEARCH_QUERY_MAX_CHARS = 256;
|
|
80
150
|
const SEARCH_SCOPE_MAX_CHARS = 128;
|
|
151
|
+
const BLOCK_ID_MAX_CHARS = 128;
|
|
81
152
|
const SEARCH_REQUESTED_SCOPES_LIMIT = MAX_SEARCH_SCOPES * 2;
|
|
82
153
|
const SEARCH_SKIPPED_SCOPES_LIMIT = 50;
|
|
83
154
|
const SEARCH_PREVIEW_CHARS = 600;
|
|
@@ -403,7 +474,7 @@ const listGrantedSources = {
|
|
|
403
474
|
const listGrantedScopes = {
|
|
404
475
|
name: "list_granted_scopes",
|
|
405
476
|
title: "List granted scopes",
|
|
406
|
-
description: "List granted scopes
|
|
477
|
+
description: "List granted scopes, readiness, size, and recommendedAccess. Call first.",
|
|
407
478
|
inputSchema: {},
|
|
408
479
|
async handler(_args, { connection, readClient }) {
|
|
409
480
|
const grantedScopes = uniqueScopes(connection);
|
|
@@ -451,24 +522,70 @@ const listGrantedScopes = {
|
|
|
451
522
|
return textResult({ scopes: scopeEntries });
|
|
452
523
|
},
|
|
453
524
|
};
|
|
525
|
+
const requestScopeAccess = {
|
|
526
|
+
name: "request_scope_access",
|
|
527
|
+
title: "Request scope access",
|
|
528
|
+
description: "Check which requested scopes are missing from this MCP grant and tell the user to approve them in Vana.",
|
|
529
|
+
inputSchema: {
|
|
530
|
+
scopes: z
|
|
531
|
+
.array(z.string().min(1).max(SEARCH_SCOPE_MAX_CHARS))
|
|
532
|
+
.min(1)
|
|
533
|
+
.max(SEARCH_REQUESTED_SCOPES_LIMIT)
|
|
534
|
+
.describe("Exact scope ids the MCP client wants to read."),
|
|
535
|
+
reason: z
|
|
536
|
+
.string()
|
|
537
|
+
.max(500)
|
|
538
|
+
.optional()
|
|
539
|
+
.describe("Brief reason to show the user."),
|
|
540
|
+
},
|
|
541
|
+
async handler(args, { connection }) {
|
|
542
|
+
const requestedScopes = normalizeScopeListInput(args.scopes);
|
|
543
|
+
const grantedRequestedScopes = requestedScopes.filter((scope) => Boolean(resolveGrantForScope(connection, scope)));
|
|
544
|
+
const missingScopes = requestedScopes.filter((scope) => !resolveGrantForScope(connection, scope));
|
|
545
|
+
const reason = typeof args.reason === "string" ? args.reason.trim() : "";
|
|
546
|
+
const approvalRequired = missingScopes.length > 0;
|
|
547
|
+
return textResult({
|
|
548
|
+
approvalRequired,
|
|
549
|
+
client: {
|
|
550
|
+
id: connection.id,
|
|
551
|
+
displayName: connection.displayName,
|
|
552
|
+
status: connection.status,
|
|
553
|
+
},
|
|
554
|
+
requestedScopes,
|
|
555
|
+
grantedRequestedScopes,
|
|
556
|
+
missingScopes,
|
|
557
|
+
grantedScopes: uniqueScopes(connection),
|
|
558
|
+
reason: reason || undefined,
|
|
559
|
+
nextAction: approvalRequired
|
|
560
|
+
? "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."
|
|
561
|
+
: "No new grant is needed for the requested scopes.",
|
|
562
|
+
});
|
|
563
|
+
},
|
|
564
|
+
};
|
|
454
565
|
const readScope = {
|
|
455
566
|
name: "read_scope",
|
|
456
567
|
title: "Read scope",
|
|
457
|
-
description: "Read
|
|
568
|
+
description: "Read approved scope blocks. Pass blockIds to fetch exact blocks; otherwise page with nextCursor.",
|
|
458
569
|
inputSchema: {
|
|
459
|
-
scope: z
|
|
460
|
-
.string()
|
|
461
|
-
.min(1)
|
|
462
|
-
.describe("Exact scope id, e.g. 'instagram.profile'."),
|
|
570
|
+
scope: z.string().min(1).describe("Exact scope id."),
|
|
463
571
|
cursor: z.string().min(1).optional(),
|
|
572
|
+
blockIds: z
|
|
573
|
+
.array(z.string().min(1).max(BLOCK_ID_MAX_CHARS))
|
|
574
|
+
.min(1)
|
|
575
|
+
.max(MAX_SELECTED_BLOCK_IDS)
|
|
576
|
+
.optional()
|
|
577
|
+
.describe("Block ids from a search hit's blockRef or list_scope_blocks. Ignores cursor; maxBytes still applies."),
|
|
464
578
|
maxBytes: z.number().int().min(1).max(MAX_READ_SCOPE_MAX_BYTES).optional(),
|
|
465
579
|
timeoutMs: z
|
|
466
580
|
.number()
|
|
467
581
|
.int()
|
|
468
582
|
.min(1000)
|
|
469
583
|
.max(MAX_READ_SCOPE_TIMEOUT_MS)
|
|
584
|
+
.optional(),
|
|
585
|
+
payment: z
|
|
586
|
+
.string()
|
|
470
587
|
.optional()
|
|
471
|
-
.describe("
|
|
588
|
+
.describe("Base64 X-PAYMENT proof (x402). Provide it after a prior call returned payment_required."),
|
|
472
589
|
},
|
|
473
590
|
async handler(args, { connection, readClient }) {
|
|
474
591
|
const scope = typeof args.scope === "string" ? args.scope : null;
|
|
@@ -484,6 +601,7 @@ const readScope = {
|
|
|
484
601
|
}, true);
|
|
485
602
|
}
|
|
486
603
|
const cursor = typeof args.cursor === "string" ? args.cursor : undefined;
|
|
604
|
+
const blockIds = normalizeBlockIdsInput(args.blockIds);
|
|
487
605
|
const maxBytes = typeof args.maxBytes === "number"
|
|
488
606
|
? clampInteger(args.maxBytes, DEFAULT_READ_SCOPE_MAX_BYTES, 1, MAX_READ_SCOPE_MAX_BYTES)
|
|
489
607
|
: DEFAULT_READ_SCOPE_MAX_BYTES;
|
|
@@ -516,12 +634,15 @@ const readScope = {
|
|
|
516
634
|
// Advisory only — serve the read without guidance.
|
|
517
635
|
});
|
|
518
636
|
}
|
|
637
|
+
const payment = typeof args.payment === "string" ? args.payment : undefined;
|
|
519
638
|
try {
|
|
520
639
|
const result = await withTimeout(readClient.readScopeBlocks({
|
|
521
640
|
scope,
|
|
522
641
|
grantId: grant.grantId,
|
|
523
642
|
cursor,
|
|
524
643
|
maxBytes,
|
|
644
|
+
payment,
|
|
645
|
+
...(blockIds.length > 0 ? { blockIds } : {}),
|
|
525
646
|
}), timeoutMs, `read blocks for ${scope}`);
|
|
526
647
|
const nextCursor = result.nextCursor ?? null;
|
|
527
648
|
return textResult({
|
|
@@ -540,6 +661,7 @@ const readScope = {
|
|
|
540
661
|
maxBytes,
|
|
541
662
|
timeoutMs,
|
|
542
663
|
returnedBlocks: result.blocks.length,
|
|
664
|
+
...(blockIds.length > 0 ? { requestedBlockIds: blockIds } : {}),
|
|
543
665
|
},
|
|
544
666
|
});
|
|
545
667
|
}
|
|
@@ -552,6 +674,30 @@ const readScope = {
|
|
|
552
674
|
}, true);
|
|
553
675
|
}
|
|
554
676
|
if (err instanceof McpDataReadError) {
|
|
677
|
+
if (err.status === 402) {
|
|
678
|
+
const body = err.body;
|
|
679
|
+
const challenge = body?.error?.details?.challenge;
|
|
680
|
+
// Only a genuine PAYMENT_REQUIRED with a challenge is signable. A
|
|
681
|
+
// gateway failure that also surfaces as 402 (e.g. PAYMENT_GATEWAY_ERROR
|
|
682
|
+
// — insufficient escrow balance) is NOT signable; presenting it as a
|
|
683
|
+
// challenge would make the client sign an error envelope in a loop.
|
|
684
|
+
if (body?.error?.errorCode === "PAYMENT_REQUIRED" && challenge) {
|
|
685
|
+
return textResult({
|
|
686
|
+
payment_required: true,
|
|
687
|
+
status: 402,
|
|
688
|
+
challenge,
|
|
689
|
+
message: "This read requires payment. Sign the x402 challenge and call read_scope again with the `payment` argument.",
|
|
690
|
+
}, true);
|
|
691
|
+
}
|
|
692
|
+
// Gateway-side payment failure — surface the real error + remediation.
|
|
693
|
+
return textResult({
|
|
694
|
+
error: "payment_failed",
|
|
695
|
+
status: 402,
|
|
696
|
+
errorCode: body?.error?.errorCode ?? "PAYMENT_ERROR",
|
|
697
|
+
message: body?.error?.message ??
|
|
698
|
+
"Payment could not be completed (e.g. insufficient escrow balance). This is not a signable challenge; resolve the gateway error and retry.",
|
|
699
|
+
}, true);
|
|
700
|
+
}
|
|
555
701
|
if (err.status === 503) {
|
|
556
702
|
return textResult({
|
|
557
703
|
error: "bounded_data_unavailable",
|
|
@@ -568,6 +714,97 @@ const readScope = {
|
|
|
568
714
|
}
|
|
569
715
|
},
|
|
570
716
|
};
|
|
717
|
+
const listScopeBlocks = {
|
|
718
|
+
name: "list_scope_blocks",
|
|
719
|
+
title: "List scope blocks",
|
|
720
|
+
description: "Scope table of contents: block ids, paths, sizes. Feed ids to read_scope blockIds.",
|
|
721
|
+
inputSchema: {
|
|
722
|
+
scope: z.string().min(1).describe("Exact scope id."),
|
|
723
|
+
offset: z.number().int().min(0).optional(),
|
|
724
|
+
limit: z.number().int().min(1).max(MAX_LIST_BLOCKS_LIMIT).optional(),
|
|
725
|
+
},
|
|
726
|
+
async handler(args, { connection, readClient }) {
|
|
727
|
+
const scope = typeof args.scope === "string" ? args.scope : null;
|
|
728
|
+
if (!scope) {
|
|
729
|
+
return textResult({ error: "scope is required and must be a string" }, true);
|
|
730
|
+
}
|
|
731
|
+
const grant = resolveGrantForScope(connection, scope);
|
|
732
|
+
if (!grant) {
|
|
733
|
+
return textResult({
|
|
734
|
+
error: "scope_not_granted",
|
|
735
|
+
message: `Scope '${scope}' is not covered by any grant on this MCP connection.`,
|
|
736
|
+
grantedScopes: uniqueScopes(connection),
|
|
737
|
+
}, true);
|
|
738
|
+
}
|
|
739
|
+
if (typeof readClient.readBlockManifest !== "function") {
|
|
740
|
+
return textResult({
|
|
741
|
+
scope,
|
|
742
|
+
blocks: [],
|
|
743
|
+
manifestAvailable: false,
|
|
744
|
+
reason: "this server build does not expose block manifests",
|
|
745
|
+
nextStep: "Use read_scope with nextCursor, or search_personal_context.",
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
const offset = clampInteger(args.offset, 0, 0, Number.MAX_SAFE_INTEGER);
|
|
749
|
+
const limit = clampInteger(args.limit, DEFAULT_LIST_BLOCKS_LIMIT, 1, MAX_LIST_BLOCKS_LIMIT);
|
|
750
|
+
try {
|
|
751
|
+
const manifest = await withTimeout(readClient.readBlockManifest({ scope, grantId: grant.grantId }), LIST_BLOCKS_TIMEOUT_MS, `read block manifest for ${scope}`);
|
|
752
|
+
// Scopes stored before block sidecars existed, and scopes still
|
|
753
|
+
// indexing, simply have no manifest. That is not an error.
|
|
754
|
+
if (!manifest) {
|
|
755
|
+
return textResult({
|
|
756
|
+
scope,
|
|
757
|
+
grantId: grant.grantId,
|
|
758
|
+
blocks: [],
|
|
759
|
+
manifestAvailable: false,
|
|
760
|
+
reason: "no block manifest for this scope yet; it may still be indexing",
|
|
761
|
+
nextStep: "Use read_scope with nextCursor, or search_personal_context.",
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
const page = manifest.blocks.slice(offset, offset + limit);
|
|
765
|
+
const nextOffset = offset + page.length;
|
|
766
|
+
return textResult({
|
|
767
|
+
scope,
|
|
768
|
+
grantId: grant.grantId,
|
|
769
|
+
collectedAt: manifest.collectedAt,
|
|
770
|
+
contentKind: manifest.contentKind,
|
|
771
|
+
manifestAvailable: true,
|
|
772
|
+
totalBlocks: manifest.blocks.length,
|
|
773
|
+
blocks: page.map((block) => ({
|
|
774
|
+
id: block.id,
|
|
775
|
+
path: block.path,
|
|
776
|
+
sizeBytes: block.sizeBytes,
|
|
777
|
+
...(block.itemCount === undefined
|
|
778
|
+
? {}
|
|
779
|
+
: { itemCount: block.itemCount }),
|
|
780
|
+
})),
|
|
781
|
+
warnings: manifest.warnings,
|
|
782
|
+
page: {
|
|
783
|
+
offset,
|
|
784
|
+
limit,
|
|
785
|
+
returnedBlocks: page.length,
|
|
786
|
+
...(nextOffset < manifest.blocks.length ? { nextOffset } : {}),
|
|
787
|
+
},
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
catch (err) {
|
|
791
|
+
if (err instanceof OperationTimeoutError) {
|
|
792
|
+
return textResult({
|
|
793
|
+
error: "scope_blocks_timeout",
|
|
794
|
+
message: err.message,
|
|
795
|
+
timeoutMs: err.timeoutMs,
|
|
796
|
+
}, true);
|
|
797
|
+
}
|
|
798
|
+
if (err instanceof McpDataReadError) {
|
|
799
|
+
return textResult({ error: "data_read_failed", status: err.status, body: err.body }, true);
|
|
800
|
+
}
|
|
801
|
+
return textResult({
|
|
802
|
+
error: "tool_handler_error",
|
|
803
|
+
message: err instanceof Error ? err.message : String(err),
|
|
804
|
+
}, true);
|
|
805
|
+
}
|
|
806
|
+
},
|
|
807
|
+
};
|
|
571
808
|
function encodeSearchCursor(payload) {
|
|
572
809
|
const json = JSON.stringify(payload);
|
|
573
810
|
// TextEncoder + Uint8Array → base64url without Node.js Buffer dependency
|
|
@@ -610,28 +847,18 @@ function decodeSearchCursor(raw) {
|
|
|
610
847
|
const searchPersonalContext = {
|
|
611
848
|
name: "search_personal_context",
|
|
612
849
|
title: "Search personal context",
|
|
613
|
-
description: "Search approved scopes
|
|
850
|
+
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.",
|
|
614
851
|
inputSchema: {
|
|
615
852
|
query: z.string().min(1).max(SEARCH_QUERY_MAX_CHARS),
|
|
616
853
|
scopes: z
|
|
617
854
|
.array(z.string().min(1).max(SEARCH_SCOPE_MAX_CHARS))
|
|
618
855
|
.max(SEARCH_REQUESTED_SCOPES_LIMIT)
|
|
619
856
|
.optional(),
|
|
620
|
-
cursor: z
|
|
621
|
-
.string()
|
|
622
|
-
.min(1)
|
|
623
|
-
.optional()
|
|
624
|
-
.describe("Continuation cursor from a prior search response's nextSearchCursor."),
|
|
857
|
+
cursor: z.string().min(1).optional(),
|
|
625
858
|
maxResults: z.number().int().min(1).max(MAX_SEARCH_LIMIT).optional(),
|
|
626
859
|
limit: z.number().int().min(1).max(MAX_SEARCH_LIMIT).optional(),
|
|
627
860
|
maxScopes: z.number().int().min(1).max(MAX_SEARCH_SCOPES).optional(),
|
|
628
|
-
timeoutMs: z
|
|
629
|
-
.number()
|
|
630
|
-
.int()
|
|
631
|
-
.min(1000)
|
|
632
|
-
.max(MAX_SEARCH_TIMEOUT_MS)
|
|
633
|
-
.optional()
|
|
634
|
-
.describe("Wall-clock budget in ms. Capped at 300000 by the server."),
|
|
861
|
+
timeoutMs: z.number().int().min(1000).max(MAX_SEARCH_TIMEOUT_MS).optional(),
|
|
635
862
|
maxBytes: z.number().int().min(1).max(MAX_SEARCH_MAX_BYTES).optional(),
|
|
636
863
|
},
|
|
637
864
|
async handler(args, { connection, readClient }) {
|
|
@@ -668,6 +895,11 @@ const searchPersonalContext = {
|
|
|
668
895
|
const matches = [];
|
|
669
896
|
const searchedScopes = [];
|
|
670
897
|
const truncatedScopes = [];
|
|
898
|
+
// Chargeable scopes hit on a paid (self-signing) session. Search is a free
|
|
899
|
+
// discovery/preview surface and does not settle x402 payments — it surfaces
|
|
900
|
+
// these explicitly so the client retrieves them via read_scope with a
|
|
901
|
+
// `payment` proof, instead of burying the 402 as an opaque scope error.
|
|
902
|
+
const paymentRequiredScopes = [];
|
|
671
903
|
const errors = [...resolved.errors];
|
|
672
904
|
// When resuming, start from the scope index encoded in the cursor
|
|
673
905
|
const startScopeIndex = resumeCursor
|
|
@@ -687,7 +919,12 @@ const searchPersonalContext = {
|
|
|
687
919
|
const grant = resolveGrantForScope(connection, scope);
|
|
688
920
|
if (!grant)
|
|
689
921
|
continue;
|
|
690
|
-
|
|
922
|
+
// The index preview path bypasses the data API auth port (and thus
|
|
923
|
+
// x402). Skip it when reads are payment-enforced so paid content is never
|
|
924
|
+
// previewed for free — search falls back to the paid bounded block read.
|
|
925
|
+
if (!resumeCursor &&
|
|
926
|
+
!readClient.enforcesPayment &&
|
|
927
|
+
typeof readClient.searchScopeIndex === "function") {
|
|
691
928
|
const remainingMs = deadline - Date.now();
|
|
692
929
|
if (remainingMs <= 0) {
|
|
693
930
|
nextSearchCursor = encodeSearchCursor({ scopeIndex });
|
|
@@ -746,7 +983,6 @@ const searchPersonalContext = {
|
|
|
746
983
|
// Use block cursor from resume only for the first scope we process
|
|
747
984
|
let cursor = scopeIndex === startScopeIndex ? initialBlockCursor : undefined;
|
|
748
985
|
let pageCount = 0;
|
|
749
|
-
let scopeHasMatch = false;
|
|
750
986
|
let scopeTruncated = false;
|
|
751
987
|
let scopeSearched = false;
|
|
752
988
|
try {
|
|
@@ -777,11 +1013,18 @@ const searchPersonalContext = {
|
|
|
777
1013
|
}), perScopeTimeoutMs, `read blocks for ${scope}`);
|
|
778
1014
|
const pageText = blockPageSearchText(result.blocks);
|
|
779
1015
|
const { documents, textById } = blockPageSearchDocuments(scope, result.blocks);
|
|
1016
|
+
// Every hit the page can still contribute, not one per page: the
|
|
1017
|
+
// index is thrown away with the page, so a discarded hit is a block
|
|
1018
|
+
// the caller can never reach again.
|
|
780
1019
|
const pageHits = documents.length > 0
|
|
781
|
-
? MiniSearchIndex.build(documents).search({
|
|
1020
|
+
? MiniSearchIndex.build(documents).search({
|
|
1021
|
+
query,
|
|
1022
|
+
limit: limit - matches.length,
|
|
1023
|
+
})
|
|
782
1024
|
: [];
|
|
783
|
-
const pageHit
|
|
784
|
-
|
|
1025
|
+
for (const pageHit of pageHits) {
|
|
1026
|
+
if (matches.length >= limit)
|
|
1027
|
+
break;
|
|
785
1028
|
const hitText = textById.get(pageHit.id) ?? pageText;
|
|
786
1029
|
matches.push({
|
|
787
1030
|
scope,
|
|
@@ -790,8 +1033,12 @@ const searchPersonalContext = {
|
|
|
790
1033
|
preview: previewMatch(hitText, miniSearchMatchIndex(hitText, pageHit.terms)),
|
|
791
1034
|
searchedChars: pageText.length,
|
|
792
1035
|
truncated: Boolean(result.nextCursor),
|
|
1036
|
+
// The id of the block that matched, so the caller can fetch it
|
|
1037
|
+
// with read_scope({ blockIds }) instead of walking cursors.
|
|
1038
|
+
...(pageHit.blockRef ? { blockRef: pageHit.blockRef } : {}),
|
|
1039
|
+
score: pageHit.score,
|
|
1040
|
+
terms: pageHit.terms,
|
|
793
1041
|
});
|
|
794
|
-
scopeHasMatch = true;
|
|
795
1042
|
}
|
|
796
1043
|
if (result.nextCursor) {
|
|
797
1044
|
cursor = result.nextCursor;
|
|
@@ -814,23 +1061,39 @@ const searchPersonalContext = {
|
|
|
814
1061
|
}
|
|
815
1062
|
}
|
|
816
1063
|
catch (err) {
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
error
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
}
|
|
1064
|
+
const paymentChallenge = err instanceof McpDataReadError &&
|
|
1065
|
+
err.status === 402 &&
|
|
1066
|
+
err.body?.error?.errorCode ===
|
|
1067
|
+
"PAYMENT_REQUIRED";
|
|
1068
|
+
if (paymentChallenge) {
|
|
1069
|
+
// Paid session hit a chargeable scope. Surface it as payment-required
|
|
1070
|
+
// (retrieve via read_scope with `payment`) rather than a scope error.
|
|
1071
|
+
// A gateway 402 (PAYMENT_GATEWAY_ERROR) falls through to `errors`.
|
|
1072
|
+
if (!paymentRequiredScopes.includes(scope)) {
|
|
1073
|
+
paymentRequiredScopes.push(scope);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
else {
|
|
1077
|
+
errors.push({
|
|
1078
|
+
scope,
|
|
1079
|
+
error: err instanceof OperationTimeoutError
|
|
1080
|
+
? "scope_search_timeout"
|
|
1081
|
+
: "scope_read_failed",
|
|
1082
|
+
status: err instanceof McpDataReadError ? err.status : undefined,
|
|
1083
|
+
bodyPreview: stringifyPreview(err instanceof McpDataReadError
|
|
1084
|
+
? err.body
|
|
1085
|
+
: err instanceof OperationTimeoutError
|
|
1086
|
+
? err.message
|
|
1087
|
+
: "unexpected scope read failure"),
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
829
1090
|
}
|
|
830
1091
|
if (matches.length >= limit) {
|
|
831
|
-
// Result limit hit — if more scopes remain, offer cursor for next scope
|
|
1092
|
+
// Result limit hit — if more scopes remain, offer cursor for next scope.
|
|
1093
|
+
// A cursor set inside the scope wins: it still has unread pages, and
|
|
1094
|
+
// pointing past it would silently drop them.
|
|
832
1095
|
const nextScopeIndex = scopeIndex + 1;
|
|
833
|
-
if (nextScopeIndex < resolved.scopes.length) {
|
|
1096
|
+
if (!nextSearchCursor && nextScopeIndex < resolved.scopes.length) {
|
|
834
1097
|
nextSearchCursor = encodeSearchCursor({
|
|
835
1098
|
scopeIndex: nextScopeIndex,
|
|
836
1099
|
});
|
|
@@ -839,6 +1102,12 @@ const searchPersonalContext = {
|
|
|
839
1102
|
}
|
|
840
1103
|
}
|
|
841
1104
|
const elapsedMs = Date.now() - startedAt;
|
|
1105
|
+
// Matches accumulate in page order, so a weak early hit would otherwise
|
|
1106
|
+
// outrank a strong later one. Scores are only comparable within one page's
|
|
1107
|
+
// index, but best-first is still strictly better than page order for the
|
|
1108
|
+
// caller. Unscored matches (budget-exhausted fallback path) keep their
|
|
1109
|
+
// relative order at the end.
|
|
1110
|
+
matches.sort((a, b) => (b.score ?? -1) - (a.score ?? -1));
|
|
842
1111
|
return textResult({
|
|
843
1112
|
query,
|
|
844
1113
|
results: matches,
|
|
@@ -852,6 +1121,13 @@ const searchPersonalContext = {
|
|
|
852
1121
|
],
|
|
853
1122
|
truncatedScopes,
|
|
854
1123
|
errors,
|
|
1124
|
+
...(paymentRequiredScopes.length > 0
|
|
1125
|
+
? {
|
|
1126
|
+
payment_required: true,
|
|
1127
|
+
paymentRequiredScopes,
|
|
1128
|
+
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).",
|
|
1129
|
+
}
|
|
1130
|
+
: {}),
|
|
855
1131
|
...(nextSearchCursor ? { nextSearchCursor } : {}),
|
|
856
1132
|
elapsedMs,
|
|
857
1133
|
limits: {
|
|
@@ -870,10 +1146,201 @@ const searchPersonalContext = {
|
|
|
870
1146
|
});
|
|
871
1147
|
},
|
|
872
1148
|
};
|
|
1149
|
+
const getScopeFile = {
|
|
1150
|
+
name: "get_scope_file",
|
|
1151
|
+
title: "Get scope file",
|
|
1152
|
+
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.",
|
|
1153
|
+
inputSchema: {
|
|
1154
|
+
...rawScopeFileInputSchema,
|
|
1155
|
+
// x402 proof (see read_scope's `payment`); provide after a payment_required.
|
|
1156
|
+
payment: z.string().optional(),
|
|
1157
|
+
timeoutMs: z
|
|
1158
|
+
.number()
|
|
1159
|
+
.int()
|
|
1160
|
+
.min(1000)
|
|
1161
|
+
.max(MAX_READ_FILE_TIMEOUT_MS)
|
|
1162
|
+
.optional(),
|
|
1163
|
+
},
|
|
1164
|
+
async handler(args, { connection, readClient }) {
|
|
1165
|
+
const scope = typeof args.scope === "string" ? args.scope : null;
|
|
1166
|
+
if (!scope) {
|
|
1167
|
+
return textResult({ error: "scope is required and must be a string" }, true);
|
|
1168
|
+
}
|
|
1169
|
+
const grant = resolveGrantForScope(connection, scope);
|
|
1170
|
+
if (!grant) {
|
|
1171
|
+
return textResult({
|
|
1172
|
+
error: "scope_not_granted",
|
|
1173
|
+
message: `Scope '${scope}' is not covered by any grant on this MCP connection.`,
|
|
1174
|
+
grantedScopes: uniqueScopes(connection),
|
|
1175
|
+
}, true);
|
|
1176
|
+
}
|
|
1177
|
+
const at = typeof args.at === "string" ? args.at : undefined;
|
|
1178
|
+
const fileId = typeof args.fileId === "string" ? args.fileId : undefined;
|
|
1179
|
+
const includeContent = typeof args.includeContent === "boolean" ? args.includeContent : false;
|
|
1180
|
+
const maxBytes = clampInteger(args.maxBytes, DEFAULT_READ_FILE_MAX_BYTES, 1, MAX_READ_FILE_MAX_BYTES);
|
|
1181
|
+
const timeoutMs = clampInteger(args.timeoutMs, DEFAULT_READ_FILE_TIMEOUT_MS, 1000, MAX_READ_FILE_TIMEOUT_MS);
|
|
1182
|
+
const resourceUri = rawScopeResourceUri({ scope, at, fileId });
|
|
1183
|
+
const payment = typeof args.payment === "string" ? args.payment : undefined;
|
|
1184
|
+
try {
|
|
1185
|
+
const raw = await withTimeout(readClient.readRawScopeFile({
|
|
1186
|
+
scope,
|
|
1187
|
+
grantId: grant.grantId,
|
|
1188
|
+
at,
|
|
1189
|
+
fileId,
|
|
1190
|
+
payment,
|
|
1191
|
+
}), timeoutMs, `read raw file for ${scope}`);
|
|
1192
|
+
const metadata = rawScopeMetadata(raw);
|
|
1193
|
+
const base = {
|
|
1194
|
+
...metadata,
|
|
1195
|
+
grantId: grant.grantId,
|
|
1196
|
+
resourceUri,
|
|
1197
|
+
includeContent,
|
|
1198
|
+
maxBytes,
|
|
1199
|
+
timeoutMs,
|
|
1200
|
+
};
|
|
1201
|
+
// A paid read settles a SINGLE-USE x402 payment for this fetch. A resource
|
|
1202
|
+
// link would force a second, unpaid resources/read → another 402 that
|
|
1203
|
+
// cannot be satisfied, so a paid read returns the bytes inline (regardless
|
|
1204
|
+
// of includeContent) — BUT the hard size cap is still enforced to protect
|
|
1205
|
+
// server/client memory and the MCP transport. An oversized paid file
|
|
1206
|
+
// returns a structured error, never an inline dump; clients size maxBytes
|
|
1207
|
+
// from the payment challenge's `sizeBytes` before paying.
|
|
1208
|
+
const paidRead = Boolean(payment);
|
|
1209
|
+
if (paidRead && raw.sizeBytes > maxBytes) {
|
|
1210
|
+
return textResult({
|
|
1211
|
+
error: "paid_file_exceeds_max_bytes",
|
|
1212
|
+
status: 413,
|
|
1213
|
+
sizeBytes: raw.sizeBytes,
|
|
1214
|
+
maxBytes,
|
|
1215
|
+
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.",
|
|
1216
|
+
}, true);
|
|
1217
|
+
}
|
|
1218
|
+
if (!paidRead && (!includeContent || raw.sizeBytes > maxBytes)) {
|
|
1219
|
+
return {
|
|
1220
|
+
content: [
|
|
1221
|
+
{
|
|
1222
|
+
type: "text",
|
|
1223
|
+
text: JSON.stringify({
|
|
1224
|
+
...base,
|
|
1225
|
+
contentIncluded: false,
|
|
1226
|
+
reason: !includeContent
|
|
1227
|
+
? "includeContent=false"
|
|
1228
|
+
: "file exceeds maxBytes for inline MCP tool response",
|
|
1229
|
+
nextStep: "Use resources/read with resourceUri if your MCP client supports resources, or retry with a higher maxBytes if appropriate.",
|
|
1230
|
+
}),
|
|
1231
|
+
},
|
|
1232
|
+
{
|
|
1233
|
+
type: "resource_link",
|
|
1234
|
+
uri: resourceUri,
|
|
1235
|
+
name: raw.filename ?? scope,
|
|
1236
|
+
title: raw.filename ?? scope,
|
|
1237
|
+
description: `Raw file for ${scope}`,
|
|
1238
|
+
mimeType: raw.mimeType,
|
|
1239
|
+
size: raw.sizeBytes,
|
|
1240
|
+
},
|
|
1241
|
+
],
|
|
1242
|
+
structuredContent: {
|
|
1243
|
+
...base,
|
|
1244
|
+
contentIncluded: false,
|
|
1245
|
+
},
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
return {
|
|
1249
|
+
content: [
|
|
1250
|
+
{
|
|
1251
|
+
type: "text",
|
|
1252
|
+
text: JSON.stringify({
|
|
1253
|
+
...base,
|
|
1254
|
+
contentIncluded: true,
|
|
1255
|
+
resourceType: "blob",
|
|
1256
|
+
}),
|
|
1257
|
+
},
|
|
1258
|
+
{
|
|
1259
|
+
type: "resource",
|
|
1260
|
+
resource: {
|
|
1261
|
+
uri: resourceUri,
|
|
1262
|
+
mimeType: raw.mimeType,
|
|
1263
|
+
blob: raw.contentBase64,
|
|
1264
|
+
_meta: metadata,
|
|
1265
|
+
},
|
|
1266
|
+
},
|
|
1267
|
+
],
|
|
1268
|
+
structuredContent: {
|
|
1269
|
+
...base,
|
|
1270
|
+
contentIncluded: true,
|
|
1271
|
+
resourceType: "blob",
|
|
1272
|
+
},
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
catch (err) {
|
|
1276
|
+
if (err instanceof OperationTimeoutError) {
|
|
1277
|
+
return textResult({
|
|
1278
|
+
error: "scope_file_timeout",
|
|
1279
|
+
message: err.message,
|
|
1280
|
+
timeoutMs: err.timeoutMs,
|
|
1281
|
+
}, true);
|
|
1282
|
+
}
|
|
1283
|
+
if (err instanceof McpDataReadError) {
|
|
1284
|
+
if (err.status === 402) {
|
|
1285
|
+
const body = err.body;
|
|
1286
|
+
const challenge = body?.error?.details?.challenge;
|
|
1287
|
+
// Only a genuine PAYMENT_REQUIRED with a challenge is signable; a
|
|
1288
|
+
// gateway 402 (e.g. insufficient escrow) is surfaced as an error.
|
|
1289
|
+
if (body?.error?.errorCode === "PAYMENT_REQUIRED" && challenge) {
|
|
1290
|
+
// Surface the file size so the client can set maxBytes >= sizeBytes
|
|
1291
|
+
// BEFORE paying — a paid read must fit the inline size cap (the
|
|
1292
|
+
// single-use payment can't fund a second resources/read).
|
|
1293
|
+
let meta = null;
|
|
1294
|
+
try {
|
|
1295
|
+
meta = await readClient.getScopeMetadata(scope);
|
|
1296
|
+
}
|
|
1297
|
+
catch {
|
|
1298
|
+
meta = null;
|
|
1299
|
+
}
|
|
1300
|
+
const sizeBytes = meta?.sizeBytes;
|
|
1301
|
+
return textResult({
|
|
1302
|
+
payment_required: true,
|
|
1303
|
+
status: 402,
|
|
1304
|
+
challenge,
|
|
1305
|
+
...(typeof sizeBytes === "number"
|
|
1306
|
+
? { sizeBytes, recommendedMaxBytes: sizeBytes }
|
|
1307
|
+
: {}),
|
|
1308
|
+
message: "This file requires payment. Sign the x402 challenge and call get_scope_file again with the `payment` argument" +
|
|
1309
|
+
(typeof sizeBytes === "number"
|
|
1310
|
+
? ` and maxBytes >= ${sizeBytes}.`
|
|
1311
|
+
: "."),
|
|
1312
|
+
}, true);
|
|
1313
|
+
}
|
|
1314
|
+
return textResult({
|
|
1315
|
+
error: "payment_failed",
|
|
1316
|
+
status: 402,
|
|
1317
|
+
errorCode: body?.error?.errorCode ?? "PAYMENT_ERROR",
|
|
1318
|
+
message: body?.error?.message ??
|
|
1319
|
+
"Payment could not be completed. This is not a signable challenge; resolve the gateway error and retry.",
|
|
1320
|
+
}, true);
|
|
1321
|
+
}
|
|
1322
|
+
return textResult({
|
|
1323
|
+
error: err.status === 404
|
|
1324
|
+
? "scope_file_not_found"
|
|
1325
|
+
: "scope_file_read_failed",
|
|
1326
|
+
status: err.status,
|
|
1327
|
+
body: err.body,
|
|
1328
|
+
}, true);
|
|
1329
|
+
}
|
|
1330
|
+
return textResult({
|
|
1331
|
+
error: "tool_handler_error",
|
|
1332
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1333
|
+
}, true);
|
|
1334
|
+
}
|
|
1335
|
+
},
|
|
1336
|
+
};
|
|
873
1337
|
export const MCP_TOOLS = [
|
|
874
1338
|
listGrantedSources,
|
|
875
1339
|
listGrantedScopes,
|
|
1340
|
+
requestScopeAccess,
|
|
876
1341
|
readScope,
|
|
1342
|
+
listScopeBlocks,
|
|
1343
|
+
getScopeFile,
|
|
877
1344
|
searchPersonalContext,
|
|
878
1345
|
];
|
|
879
1346
|
//# sourceMappingURL=tools.js.map
|