diff --git a/.changeset/bound-execute-result-size.md b/.changeset/bound-execute-result-size.md deleted file mode 100644 index d0d5a82..0000000 --- a/.changeset/bound-execute-result-size.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"@iqai/mcp-debank": patch ---- - -Bound the `execute` result size so whale-wallet queries can't overflow the consuming model's context window. - -The `execute` tool serialized the guest `run()` return value with no size limit and returned it verbatim to the MCP client. For large wallets (e.g. vitalik.eth), whole-portfolio queries — "all token holdings across all chains", "all NFT holdings", "all approvals on Ethereum" — produced payloads of ~500k tokens, which overflowed the consuming agent's context window before the summarizing model call could even run. - -There is now a generic size budget enforced at the result boundary (`DEBANK_MCP_EXECUTE_RESULT_BUDGET_CHARS`, default 200,000 chars ≈ 50k tokens). When a result exceeds it, the envelope metadata (`ok` / `error` / `log_lines` / `err_lines`) is kept intact and the bulk `result` is truncated — arrays keep the first N elements, objects trim their largest arrays, strings keep a prefix — with a structured `_truncated: { shown, total, reason, hint }` signal attached so the consumer can answer "showing top N of M — refine to see more" instead of crashing. Results under budget are returned byte-for-byte unchanged. diff --git a/.changeset/nft-per-chain-fanout.md b/.changeset/nft-per-chain-fanout.md deleted file mode 100644 index a8efdfe..0000000 --- a/.changeset/nft-per-chain-fanout.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@iqai/mcp-debank": minor ---- - -Fetch whale NFT holdings by fanning out per-chain instead of the slow server-side aggregate. - -`getUserAllNftList` previously called DeBank's `/user/all_nft_list`, which aggregates across every chain server-side and takes 10–20 s for active wallets — routinely timing out to nothing for whales (e.g. vitalik.eth). It now discovers the wallet's used chains (`used_chain_list`) and fans out the fast per-chain `/user/nft_list` calls in parallel with bounded concurrency (8), so wall-clock is ≈ (chains / 8) × ~1.5 s. - -The method also degrades gracefully instead of failing all-or-nothing: a chain that errors or is too slow is dropped and reported, and the method enforces its own soft deadline (~75% of the call's timeout budget) so it returns the chains that landed *before* the client's hard abort fires — partial-on-timeout, mirroring the result-size-budget philosophy. - -**Return-shape change (guest-facing):** `getUserAllNftList` now resolves to `{ nfts, chains, partial, chains_skipped }` instead of a bare `UserNFT[]`. Read `result.nfts` for the holdings (each NFT is stamped with its `chain`); `partial: true` with `chains_skipped` names the chains to retry (narrow via `chain_ids`, or use `getUserNftList` per chain). The tool description, cookbook, and instructions are updated accordingly. diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d6d85f..d5e7057 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # @iqai/mcp-debank +## 2.2.0 + +### Minor Changes + +- 304039f: Fetch whale NFT holdings by fanning out per-chain instead of the slow server-side aggregate. + + `getUserAllNftList` previously called DeBank's `/user/all_nft_list`, which aggregates across every chain server-side and takes 10–20 s for active wallets — routinely timing out to nothing for whales (e.g. vitalik.eth). It now discovers the wallet's used chains (`used_chain_list`) and fans out the fast per-chain `/user/nft_list` calls in parallel with bounded concurrency (8), so wall-clock is ≈ (chains / 8) × ~1.5 s. + + The method also degrades gracefully instead of failing all-or-nothing: a chain that errors or is too slow is dropped and reported, and the method enforces its own soft deadline (~75% of the call's timeout budget) so it returns the chains that landed _before_ the client's hard abort fires — partial-on-timeout, mirroring the result-size-budget philosophy. + + **Return-shape change (guest-facing):** `getUserAllNftList` now resolves to `{ nfts, chains, partial, chains_skipped }` instead of a bare `UserNFT[]`. Read `result.nfts` for the holdings (each NFT is stamped with its `chain`); `partial: true` with `chains_skipped` names the chains to retry (narrow via `chain_ids`, or use `getUserNftList` per chain). The tool description, cookbook, and instructions are updated accordingly. + +### Patch Changes + +- ca6d059: Bound the `execute` result size so whale-wallet queries can't overflow the consuming model's context window. + + The `execute` tool serialized the guest `run()` return value with no size limit and returned it verbatim to the MCP client. For large wallets (e.g. vitalik.eth), whole-portfolio queries — "all token holdings across all chains", "all NFT holdings", "all approvals on Ethereum" — produced payloads of ~500k tokens, which overflowed the consuming agent's context window before the summarizing model call could even run. + + There is now a generic size budget enforced at the result boundary (`DEBANK_MCP_EXECUTE_RESULT_BUDGET_CHARS`, default 200,000 chars ≈ 50k tokens). When a result exceeds it, the envelope metadata (`ok` / `error` / `log_lines` / `err_lines`) is kept intact and the bulk `result` is truncated — arrays keep the first N elements, objects trim their largest arrays, strings keep a prefix — with a structured `_truncated: { shown, total, reason, hint }` signal attached so the consumer can answer "showing top N of M — refine to see more" instead of crashing. Results under budget are returned byte-for-byte unchanged. + ## 2.1.1 ### Patch Changes diff --git a/package.json b/package.json index 9d85d98..d3997bc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@iqai/mcp-debank", - "version": "2.1.1", + "version": "2.2.0", "description": "Standalone MCP server for DeBank data access", "main": "dist/index.js", "type": "module",