@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.
Files changed (322) hide show
  1. package/dist/api/index.d.ts +282 -9
  2. package/dist/api/index.d.ts.map +1 -1
  3. package/dist/api/index.js +867 -163
  4. package/dist/api/index.js.map +1 -1
  5. package/dist/auth/request.d.ts.map +1 -1
  6. package/dist/auth/request.js.map +1 -1
  7. package/dist/client.d.ts +8 -1
  8. package/dist/client.d.ts.map +1 -1
  9. package/dist/client.js +21 -3
  10. package/dist/client.js.map +1 -1
  11. package/dist/contracts/binary.d.ts +12 -0
  12. package/dist/contracts/binary.d.ts.map +1 -1
  13. package/dist/contracts/binary.js +31 -0
  14. package/dist/contracts/binary.js.map +1 -1
  15. package/dist/contracts/data.d.ts +51 -5
  16. package/dist/contracts/data.d.ts.map +1 -1
  17. package/dist/contracts/data.js +166 -30
  18. package/dist/contracts/data.js.map +1 -1
  19. package/dist/contracts/grants.d.ts +7 -5
  20. package/dist/contracts/grants.d.ts.map +1 -1
  21. package/dist/contracts/grants.js +60 -37
  22. package/dist/contracts/grants.js.map +1 -1
  23. package/dist/contracts/index.d.ts +1 -1
  24. package/dist/contracts/index.d.ts.map +1 -1
  25. package/dist/contracts/index.js +1 -1
  26. package/dist/contracts/index.js.map +1 -1
  27. package/dist/derivatives/api.d.ts +48 -0
  28. package/dist/derivatives/api.d.ts.map +1 -0
  29. package/dist/derivatives/api.js +366 -0
  30. package/dist/derivatives/api.js.map +1 -0
  31. package/dist/derivatives/compute.d.ts +106 -0
  32. package/dist/derivatives/compute.d.ts.map +1 -0
  33. package/dist/derivatives/compute.js +414 -0
  34. package/dist/derivatives/compute.js.map +1 -0
  35. package/dist/derivatives/e2ee/aad.d.ts +22 -0
  36. package/dist/derivatives/e2ee/aad.d.ts.map +1 -0
  37. package/dist/derivatives/e2ee/aad.js +32 -0
  38. package/dist/derivatives/e2ee/aad.js.map +1 -0
  39. package/dist/derivatives/e2ee/attestation.d.ts +134 -0
  40. package/dist/derivatives/e2ee/attestation.d.ts.map +1 -0
  41. package/dist/derivatives/e2ee/attestation.js +248 -0
  42. package/dist/derivatives/e2ee/attestation.js.map +1 -0
  43. package/dist/derivatives/e2ee/index.d.ts +6 -0
  44. package/dist/derivatives/e2ee/index.d.ts.map +1 -0
  45. package/dist/derivatives/e2ee/index.js +6 -0
  46. package/dist/derivatives/e2ee/index.js.map +1 -0
  47. package/dist/derivatives/e2ee/jcs.d.ts +17 -0
  48. package/dist/derivatives/e2ee/jcs.d.ts.map +1 -0
  49. package/dist/derivatives/e2ee/jcs.js +55 -0
  50. package/dist/derivatives/e2ee/jcs.js.map +1 -0
  51. package/dist/derivatives/e2ee/phala.d.ts +62 -0
  52. package/dist/derivatives/e2ee/phala.d.ts.map +1 -0
  53. package/dist/derivatives/e2ee/phala.js +168 -0
  54. package/dist/derivatives/e2ee/phala.js.map +1 -0
  55. package/dist/derivatives/e2ee/suite.d.ts +56 -0
  56. package/dist/derivatives/e2ee/suite.d.ts.map +1 -0
  57. package/dist/derivatives/e2ee/suite.js +164 -0
  58. package/dist/derivatives/e2ee/suite.js.map +1 -0
  59. package/dist/derivatives/index.d.ts +10 -0
  60. package/dist/derivatives/index.d.ts.map +1 -0
  61. package/dist/derivatives/index.js +10 -0
  62. package/dist/derivatives/index.js.map +1 -0
  63. package/dist/derivatives/inference.d.ts +151 -0
  64. package/dist/derivatives/inference.d.ts.map +1 -0
  65. package/dist/derivatives/inference.js +262 -0
  66. package/dist/derivatives/inference.js.map +1 -0
  67. package/dist/derivatives/prompt.d.ts +64 -0
  68. package/dist/derivatives/prompt.d.ts.map +1 -0
  69. package/dist/derivatives/prompt.js +193 -0
  70. package/dist/derivatives/prompt.js.map +1 -0
  71. package/dist/derivatives/registration.d.ts +54 -0
  72. package/dist/derivatives/registration.d.ts.map +1 -0
  73. package/dist/derivatives/registration.js +180 -0
  74. package/dist/derivatives/registration.js.map +1 -0
  75. package/dist/derivatives/scheduler.d.ts +84 -0
  76. package/dist/derivatives/scheduler.d.ts.map +1 -0
  77. package/dist/derivatives/scheduler.js +257 -0
  78. package/dist/derivatives/scheduler.js.map +1 -0
  79. package/dist/derivatives/store.d.ts +14 -0
  80. package/dist/derivatives/store.d.ts.map +1 -0
  81. package/dist/derivatives/store.js +77 -0
  82. package/dist/derivatives/store.js.map +1 -0
  83. package/dist/derivatives/types.d.ts +92 -0
  84. package/dist/derivatives/types.d.ts.map +1 -0
  85. package/dist/derivatives/types.js +27 -0
  86. package/dist/derivatives/types.js.map +1 -0
  87. package/dist/errors/catalog.d.ts +75 -0
  88. package/dist/errors/catalog.d.ts.map +1 -1
  89. package/dist/errors/catalog.js +104 -0
  90. package/dist/errors/catalog.js.map +1 -1
  91. package/dist/grants/index.d.ts +2 -1
  92. package/dist/grants/index.d.ts.map +1 -1
  93. package/dist/grants/index.js +1 -1
  94. package/dist/grants/index.js.map +1 -1
  95. package/dist/keys/index.d.ts +1 -0
  96. package/dist/keys/index.d.ts.map +1 -1
  97. package/dist/keys/index.js +1 -1
  98. package/dist/keys/index.js.map +1 -1
  99. package/dist/keys/public-only-account.d.ts +8 -0
  100. package/dist/keys/public-only-account.d.ts.map +1 -0
  101. package/dist/keys/public-only-account.js +21 -0
  102. package/dist/keys/public-only-account.js.map +1 -0
  103. package/dist/keys/server-account.d.ts +2 -2
  104. package/dist/keys/server-account.d.ts.map +1 -1
  105. package/dist/lineage/attestation.d.ts +42 -0
  106. package/dist/lineage/attestation.d.ts.map +1 -0
  107. package/dist/lineage/attestation.js +25 -0
  108. package/dist/lineage/attestation.js.map +1 -0
  109. package/dist/lineage/gateway.d.ts +105 -0
  110. package/dist/lineage/gateway.d.ts.map +1 -0
  111. package/dist/lineage/gateway.js +242 -0
  112. package/dist/lineage/gateway.js.map +1 -0
  113. package/dist/lineage/index.d.ts +5 -0
  114. package/dist/lineage/index.d.ts.map +1 -0
  115. package/dist/lineage/index.js +5 -0
  116. package/dist/lineage/index.js.map +1 -0
  117. package/dist/lineage/lineage.d.ts +141 -0
  118. package/dist/lineage/lineage.d.ts.map +1 -0
  119. package/dist/lineage/lineage.js +255 -0
  120. package/dist/lineage/lineage.js.map +1 -0
  121. package/dist/logging/access-log.d.ts +10 -1
  122. package/dist/logging/access-log.d.ts.map +1 -1
  123. package/dist/mcp/activity.d.ts.map +1 -1
  124. package/dist/mcp/activity.js.map +1 -1
  125. package/dist/mcp/connection-api.d.ts.map +1 -1
  126. package/dist/mcp/connection-api.js +0 -1
  127. package/dist/mcp/connection-api.js.map +1 -1
  128. package/dist/mcp/index.d.ts +1 -0
  129. package/dist/mcp/index.d.ts.map +1 -1
  130. package/dist/mcp/index.js +1 -0
  131. package/dist/mcp/index.js.map +1 -1
  132. package/dist/mcp/read-client.d.ts +61 -1
  133. package/dist/mcp/read-client.d.ts.map +1 -1
  134. package/dist/mcp/read-client.js +397 -42
  135. package/dist/mcp/read-client.js.map +1 -1
  136. package/dist/mcp/resources.d.ts +37 -0
  137. package/dist/mcp/resources.d.ts.map +1 -0
  138. package/dist/mcp/resources.js +114 -0
  139. package/dist/mcp/resources.js.map +1 -0
  140. package/dist/mcp/search/index.d.ts.map +1 -1
  141. package/dist/mcp/search/index.js +108 -21
  142. package/dist/mcp/search/index.js.map +1 -1
  143. package/dist/mcp/server.d.ts.map +1 -1
  144. package/dist/mcp/server.js +27 -9
  145. package/dist/mcp/server.js.map +1 -1
  146. package/dist/mcp/session.d.ts +160 -0
  147. package/dist/mcp/session.d.ts.map +1 -0
  148. package/dist/mcp/session.js +243 -0
  149. package/dist/mcp/session.js.map +1 -0
  150. package/dist/mcp/tools.d.ts +29 -3
  151. package/dist/mcp/tools.d.ts.map +1 -1
  152. package/dist/mcp/tools.js +515 -48
  153. package/dist/mcp/tools.js.map +1 -1
  154. package/dist/mcp/types.d.ts.map +1 -1
  155. package/dist/payment/index.d.ts +2 -0
  156. package/dist/payment/index.d.ts.map +1 -0
  157. package/dist/payment/index.js +2 -0
  158. package/dist/payment/index.js.map +1 -0
  159. package/dist/payment/x402.d.ts +155 -0
  160. package/dist/payment/x402.d.ts.map +1 -0
  161. package/dist/payment/x402.js +402 -0
  162. package/dist/payment/x402.js.map +1 -0
  163. package/dist/policy/data-read.d.ts +18 -2
  164. package/dist/policy/data-read.d.ts.map +1 -1
  165. package/dist/policy/data-read.js +80 -49
  166. package/dist/policy/data-read.js.map +1 -1
  167. package/dist/policy/data-write.d.ts +69 -0
  168. package/dist/policy/data-write.d.ts.map +1 -0
  169. package/dist/policy/data-write.js +131 -0
  170. package/dist/policy/data-write.js.map +1 -0
  171. package/dist/policy/index.d.ts +3 -1
  172. package/dist/policy/index.d.ts.map +1 -1
  173. package/dist/policy/index.js +3 -1
  174. package/dist/policy/index.js.map +1 -1
  175. package/dist/policy/signed-artifacts.d.ts +17 -0
  176. package/dist/policy/signed-artifacts.d.ts.map +1 -0
  177. package/dist/policy/signed-artifacts.js +110 -0
  178. package/dist/policy/signed-artifacts.js.map +1 -0
  179. package/dist/ports/index.d.ts +159 -32
  180. package/dist/ports/index.d.ts.map +1 -1
  181. package/dist/ports/index.js +6 -5
  182. package/dist/ports/index.js.map +1 -1
  183. package/dist/schemas/server-config.d.ts +49 -4
  184. package/dist/schemas/server-config.d.ts.map +1 -1
  185. package/dist/schemas/server-config.js +95 -13
  186. package/dist/schemas/server-config.js.map +1 -1
  187. package/dist/signing/index.d.ts +2 -2
  188. package/dist/signing/index.d.ts.map +1 -1
  189. package/dist/signing/index.js +1 -1
  190. package/dist/signing/index.js.map +1 -1
  191. package/dist/signing/request-signer.d.ts +2 -0
  192. package/dist/signing/request-signer.d.ts.map +1 -1
  193. package/dist/signing/request-signer.js +1 -0
  194. package/dist/signing/request-signer.js.map +1 -1
  195. package/dist/signing/signer.d.ts +27 -25
  196. package/dist/signing/signer.d.ts.map +1 -1
  197. package/dist/signing/signer.js +27 -51
  198. package/dist/signing/signer.js.map +1 -1
  199. package/dist/storage/adapters/index.d.ts +1 -1
  200. package/dist/storage/adapters/index.d.ts.map +1 -1
  201. package/dist/storage/adapters/index.js +1 -1
  202. package/dist/storage/adapters/index.js.map +1 -1
  203. package/dist/storage/adapters/interface.d.ts +10 -0
  204. package/dist/storage/adapters/interface.d.ts.map +1 -1
  205. package/dist/storage/adapters/sdk.d.ts +11 -1
  206. package/dist/storage/adapters/sdk.d.ts.map +1 -1
  207. package/dist/storage/adapters/sdk.js +38 -3
  208. package/dist/storage/adapters/sdk.js.map +1 -1
  209. package/dist/storage/adapters/vana.d.ts +3 -0
  210. package/dist/storage/adapters/vana.d.ts.map +1 -1
  211. package/dist/storage/adapters/vana.js +95 -2
  212. package/dist/storage/adapters/vana.js.map +1 -1
  213. package/dist/storage/blocks/errors.d.ts.map +1 -1
  214. package/dist/storage/blocks/errors.js.map +1 -1
  215. package/dist/storage/blocks/index.d.ts +2 -0
  216. package/dist/storage/blocks/index.d.ts.map +1 -1
  217. package/dist/storage/blocks/index.js +2 -0
  218. package/dist/storage/blocks/index.js.map +1 -1
  219. package/dist/storage/blocks/select.d.ts +29 -0
  220. package/dist/storage/blocks/select.d.ts.map +1 -0
  221. package/dist/storage/blocks/select.js +120 -0
  222. package/dist/storage/blocks/select.js.map +1 -0
  223. package/dist/storage/blocks/types.d.ts +7 -0
  224. package/dist/storage/blocks/types.d.ts.map +1 -1
  225. package/dist/storage/index/manager.d.ts +30 -2
  226. package/dist/storage/index/manager.d.ts.map +1 -1
  227. package/dist/storage/index/schema.d.ts +1 -1
  228. package/dist/storage/index/schema.js +1 -1
  229. package/dist/storage/index/types.d.ts +6 -1
  230. package/dist/storage/index/types.d.ts.map +1 -1
  231. package/dist/sync/data-point-feed.d.ts +35 -0
  232. package/dist/sync/data-point-feed.d.ts.map +1 -0
  233. package/dist/sync/data-point-feed.js +122 -0
  234. package/dist/sync/data-point-feed.js.map +1 -0
  235. package/dist/sync/data-point-id.d.ts +7 -0
  236. package/dist/sync/data-point-id.d.ts.map +1 -0
  237. package/dist/sync/data-point-id.js +16 -0
  238. package/dist/sync/data-point-id.js.map +1 -0
  239. package/dist/sync/delete-data-port.d.ts +50 -0
  240. package/dist/sync/delete-data-port.d.ts.map +1 -0
  241. package/dist/sync/delete-data-port.js +244 -0
  242. package/dist/sync/delete-data-port.js.map +1 -0
  243. package/dist/sync/engine/sync-manager.d.ts +16 -4
  244. package/dist/sync/engine/sync-manager.d.ts.map +1 -1
  245. package/dist/sync/engine/sync-manager.js +93 -32
  246. package/dist/sync/engine/sync-manager.js.map +1 -1
  247. package/dist/sync/index.d.ts +7 -0
  248. package/dist/sync/index.d.ts.map +1 -1
  249. package/dist/sync/index.js +7 -0
  250. package/dist/sync/index.js.map +1 -1
  251. package/dist/sync/issues.d.ts.map +1 -1
  252. package/dist/sync/issues.js +21 -3
  253. package/dist/sync/issues.js.map +1 -1
  254. package/dist/sync/pending-blob-deletions.d.ts +29 -0
  255. package/dist/sync/pending-blob-deletions.d.ts.map +1 -0
  256. package/dist/sync/pending-blob-deletions.js +116 -0
  257. package/dist/sync/pending-blob-deletions.js.map +1 -0
  258. package/dist/sync/retry-memory.d.ts +33 -0
  259. package/dist/sync/retry-memory.d.ts.map +1 -0
  260. package/dist/sync/retry-memory.js +68 -0
  261. package/dist/sync/retry-memory.js.map +1 -0
  262. package/dist/sync/scope-deletions.d.ts +155 -0
  263. package/dist/sync/scope-deletions.d.ts.map +1 -0
  264. package/dist/sync/scope-deletions.js +225 -0
  265. package/dist/sync/scope-deletions.js.map +1 -0
  266. package/dist/sync/tombstone.d.ts +27 -0
  267. package/dist/sync/tombstone.d.ts.map +1 -0
  268. package/dist/sync/tombstone.js +28 -0
  269. package/dist/sync/tombstone.js.map +1 -0
  270. package/dist/sync/workers/delete.d.ts +152 -31
  271. package/dist/sync/workers/delete.d.ts.map +1 -1
  272. package/dist/sync/workers/delete.js +457 -55
  273. package/dist/sync/workers/delete.js.map +1 -1
  274. package/dist/sync/workers/download.d.ts +108 -27
  275. package/dist/sync/workers/download.d.ts.map +1 -1
  276. package/dist/sync/workers/download.js +321 -126
  277. package/dist/sync/workers/download.js.map +1 -1
  278. package/dist/sync/workers/upload.d.ts +65 -10
  279. package/dist/sync/workers/upload.d.ts.map +1 -1
  280. package/dist/sync/workers/upload.js +371 -42
  281. package/dist/sync/workers/upload.js.map +1 -1
  282. package/dist/test-utils/e2ee-fixtures.d.ts +94 -0
  283. package/dist/test-utils/e2ee-fixtures.d.ts.map +1 -0
  284. package/dist/test-utils/e2ee-fixtures.js +116 -0
  285. package/dist/test-utils/e2ee-fixtures.js.map +1 -0
  286. package/dist/test-utils/e2ee-gateway.d.ts +61 -0
  287. package/dist/test-utils/e2ee-gateway.d.ts.map +1 -0
  288. package/dist/test-utils/e2ee-gateway.js +248 -0
  289. package/dist/test-utils/e2ee-gateway.js.map +1 -0
  290. package/dist/test-utils/index.d.ts +3 -0
  291. package/dist/test-utils/index.d.ts.map +1 -1
  292. package/dist/test-utils/index.js +3 -0
  293. package/dist/test-utils/index.js.map +1 -1
  294. package/dist/test-utils/memory-storage.d.ts +12 -0
  295. package/dist/test-utils/memory-storage.d.ts.map +1 -0
  296. package/dist/test-utils/memory-storage.js +146 -0
  297. package/dist/test-utils/memory-storage.js.map +1 -0
  298. package/dist/test-utils/wallet.d.ts +6 -0
  299. package/dist/test-utils/wallet.d.ts.map +1 -1
  300. package/dist/test-utils/wallet.js +46 -0
  301. package/dist/test-utils/wallet.js.map +1 -1
  302. package/dist/write/attribution.d.ts +185 -0
  303. package/dist/write/attribution.d.ts.map +1 -0
  304. package/dist/write/attribution.js +450 -0
  305. package/dist/write/attribution.js.map +1 -0
  306. package/dist/write/authorize.d.ts +77 -0
  307. package/dist/write/authorize.d.ts.map +1 -0
  308. package/dist/write/authorize.js +109 -0
  309. package/dist/write/authorize.js.map +1 -0
  310. package/dist/write/index.d.ts +5 -0
  311. package/dist/write/index.d.ts.map +1 -0
  312. package/dist/write/index.js +5 -0
  313. package/dist/write/index.js.map +1 -0
  314. package/dist/write/route.d.ts +61 -0
  315. package/dist/write/route.d.ts.map +1 -0
  316. package/dist/write/route.js +132 -0
  317. package/dist/write/route.js.map +1 -0
  318. package/dist/write/session.d.ts +96 -0
  319. package/dist/write/session.d.ts.map +1 -0
  320. package/dist/write/session.js +136 -0
  321. package/dist/write/session.js.map +1 -0
  322. 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-management tools. No raw owner/admin status tools.
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, null, 2);
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 DEFAULT_SEARCH_MAX_SCOPES = 10;
66
- const MAX_SEARCH_SCOPES = 50;
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 = 300_000;
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 = 300_000;
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 = 300_000;
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 SEARCH_MAX_PAGES_PER_SCOPE = 16;
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 with dataStatus, sizeClass, sizeBytes, and recommendedAccess ('read' for small scopes, 'search' for large/huge scopes, 'wait' while indexing). Call first, then follow recommendedAccess.",
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 one full approved scope as bounded blocks. Best for tiny/small/medium scopes (see sizeClass from list_granted_scopes). For large/huge scopes prefer search_personal_context — a full read returns many pages you must walk via nextCursor and is slow. The response includes a `guidance` hint when a scope is large enough that search is the better tool.",
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("Wall-clock read budget in ms. Capped at 300000 by the server."),
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 by keyword — the preferred way to pull from large/huge scopes without reading every page. Omit scopes to sweep all small ready data; name scopes (including large ones) to target them. Continue with nextSearchCursor.",
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
- if (!resumeCursor && typeof readClient.searchScopeIndex === "function") {
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({ query, limit: 1 })
1020
+ ? MiniSearchIndex.build(documents).search({
1021
+ query,
1022
+ limit: limit - matches.length,
1023
+ })
782
1024
  : [];
783
- const pageHit = pageHits[0];
784
- if (pageHit && matches.length < limit && !scopeHasMatch) {
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
- errors.push({
818
- scope,
819
- error: err instanceof OperationTimeoutError
820
- ? "scope_search_timeout"
821
- : "scope_read_failed",
822
- status: err instanceof McpDataReadError ? err.status : undefined,
823
- bodyPreview: stringifyPreview(err instanceof McpDataReadError
824
- ? err.body
825
- : err instanceof OperationTimeoutError
826
- ? err.message
827
- : "unexpected scope read failure"),
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