From 4adbcc19c4f193348114e1df9014cf95ec23f552 Mon Sep 17 00:00:00 2001 From: Billy Yuen Date: Tue, 16 Dec 2025 11:01:30 -0800 Subject: [PATCH 1/3] Fix MCP tool registration to use Zod schemas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Import zod/v4 for MCP SDK compatibility - Add optional account_id parameter to list_positions tool for filtering - Update dependencies: MCP SDK to 1.25.1 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- package-lock.json | 372 +++++++++++++++++++++++++------------------- package.json | 2 +- src/commands/mcp.ts | 14 +- 3 files changed, 225 insertions(+), 163 deletions(-) diff --git a/package-lock.json b/package-lock.json index fa8ade2..c7976df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "open": "^10.2.0", "ora": "^8.2.0", "prettier": "^3.6.2", - "snaptrade-typescript-sdk": "^9.0.146", + "snaptrade-typescript-sdk": "^9.0.11", "string-width": "^7.1.0", "yahoo-finance2": "^3.6.0", "zod": "^3.25.76" @@ -71,6 +71,27 @@ "integrity": "sha512-4nMhecpGlPi0cSzT67L+Tm+GOJqvuk8gqHBziqcUQOarnuIax1z96/gJHCSIz2Z0zhxE6Rzwb3IZXPtFh51j+w==", "license": "MIT" }, + "node_modules/@hono/node-server": { + "version": "1.19.7", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.7.tgz", + "integrity": "sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@inquirer/checkbox": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.9.tgz", @@ -117,19 +138,19 @@ } }, "node_modules/@inquirer/core": { - "version": "10.1.14", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.14.tgz", - "integrity": "sha512-Ma+ZpOJPewtIYl6HZHZckeX1STvDnHTCB2GVINNUlSEn2Am6LddWwfPkIGY0IUFVjUUrr/93XlBwTK6mfLjf0A==", + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", "license": "MIT", "dependencies": { - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", - "ansi-escapes": "^4.3.2", + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", "cli-width": "^4.1.0", "mute-stream": "^2.0.0", "signal-exit": "^4.1.0", "wrap-ansi": "^6.2.0", - "yoctocolors-cjs": "^2.1.2" + "yoctocolors-cjs": "^2.1.3" }, "engines": { "node": ">=18" @@ -144,14 +165,14 @@ } }, "node_modules/@inquirer/editor": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.14.tgz", - "integrity": "sha512-yd2qtLl4QIIax9DTMZ1ZN2pFrrj+yL3kgIWxm34SS6uwCr0sIhsNyudUjAo5q3TqI03xx4SEBkUJqZuAInp9uA==", + "version": "4.2.23", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7", - "external-editor": "^3.1.0" + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" }, "engines": { "node": ">=18" @@ -187,10 +208,31 @@ } } }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, "node_modules/@inquirer/figures": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.12.tgz", - "integrity": "sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==", + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", "license": "MIT", "engines": { "node": ">=18" @@ -359,9 +401,9 @@ } }, "node_modules/@inquirer/type": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz", - "integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", "license": "MIT", "engines": { "node": ">=18" @@ -404,12 +446,14 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.17.3", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.3.tgz", - "integrity": "sha512-JPwUKWSsbzx+DLFznf/QZ32Qa+ptfbUlHhRLrBQBAFu9iI1iYvizM4p+zhhRDceSsPutXp4z+R/HPVphlIiclg==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.1.tgz", + "integrity": "sha512-yO28oVFFC7EBoiKdAn+VqRm+plcfv4v0xp6osG/VsCB0NlPZWi87ajbCZZ8f/RvOFLEu7//rSRmuZZ7lMoe3gQ==", "license": "MIT", "dependencies": { - "ajv": "^6.12.6", + "@hono/node-server": "^1.19.7", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", @@ -417,13 +461,27 @@ "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", + "jose": "^6.1.1", + "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.0" }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } } }, "node_modules/@tsconfig/node10": { @@ -532,21 +590,38 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { "type": "github", "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -611,35 +686,27 @@ } }, "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", - "debug": "^4.4.0", + "debug": "^4.4.3", "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", + "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { "node": ">=18" - } - }, - "node_modules/body-parser/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/bundle-name": { @@ -708,9 +775,9 @@ } }, "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", "license": "MIT" }, "node_modules/cli-cursor": { @@ -928,9 +995,9 @@ } }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1215,31 +1282,27 @@ "node": ">= 0.6" } }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "license": "MIT", - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "license": "MIT" + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" }, "node_modules/fetch-mock-cache": { "version": "2.1.3", @@ -1311,9 +1374,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "funding": [ { "type": "individual", @@ -1331,9 +1394,9 @@ } }, "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -1473,29 +1536,34 @@ "node": ">= 0.4" } }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "node_modules/hono": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.1.tgz", + "integrity": "sha512-KsFcH0xxHes0J4zaQgWbYwmz3UPOOskdqZmItstUG93+Wk1ePBLkLGwbP9zlmh1BFUiL8Qp+Xfu9P7feJWpGNg==", "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, + "peer": true, "engines": { - "node": ">= 0.8" + "node": ">=16.9.0" } }, - "node_modules/http-errors/node_modules/statuses": { + "node_modules/http-errors": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/humanize-url": { @@ -1511,15 +1579,19 @@ } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", + "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/inherits": { @@ -1633,12 +1705,27 @@ "node": ">=16" } }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, "node_modules/log-symbols": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", @@ -1895,15 +1982,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -2026,28 +2104,25 @@ } }, "node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" } }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, "engines": { "node": ">=0.10.0" } @@ -2298,9 +2373,9 @@ } }, "node_modules/snaptrade-typescript-sdk": { - "version": "9.0.146", - "resolved": "https://registry.npmjs.org/snaptrade-typescript-sdk/-/snaptrade-typescript-sdk-9.0.146.tgz", - "integrity": "sha512-eVCtEiXMooOwHfjwjZMwht5Ph6PypV+CPT8KzYVUqkVuDjNLbCBJcPMv4OAOCcNj+SdHCXGU0mjU6ptzV3jiVg==", + "version": "9.0.158", + "resolved": "https://registry.npmjs.org/snaptrade-typescript-sdk/-/snaptrade-typescript-sdk-9.0.158.tgz", + "integrity": "sha512-fWn/uuQoE8Y/8IYeOxksCHICMJKuuFwXQ37orQAQz0+72nANsyaOuEOy84DZVymXeoPUQUmmTNgizZvlfDMSHA==", "license": "Unlicense", "dependencies": { "axios": "1.10.0" @@ -2416,18 +2491,6 @@ "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", "license": "MIT" }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "license": "MIT", - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -2606,15 +2669,6 @@ "node": ">= 0.8" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, "node_modules/url-parse": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", @@ -2752,9 +2806,9 @@ } }, "node_modules/yoctocolors-cjs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", - "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", "license": "MIT", "engines": { "node": ">=18" @@ -2773,12 +2827,12 @@ } }, "node_modules/zod-to-json-schema": { - "version": "3.24.6", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.0.tgz", + "integrity": "sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==", "license": "ISC", "peerDependencies": { - "zod": "^3.24.1" + "zod": "^3.25 || ^4" } } } diff --git a/package.json b/package.json index ea3b04a..40c6816 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "open": "^10.2.0", "ora": "^8.2.0", "prettier": "^3.6.2", - "snaptrade-typescript-sdk": "^9.0.146", + "snaptrade-typescript-sdk": "^9.0.11", "string-width": "^7.1.0", "yahoo-finance2": "^3.6.0", "zod": "^3.25.76" diff --git a/src/commands/mcp.ts b/src/commands/mcp.ts index 1f49a8d..f80b9e2 100644 --- a/src/commands/mcp.ts +++ b/src/commands/mcp.ts @@ -3,6 +3,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js" import { Command } from "commander"; import type { OptionsPosition, Position } from "snaptrade-typescript-sdk"; import { Snaptrade } from "snaptrade-typescript-sdk"; +import * as z from "zod/v4"; import { loadOrRegisterUser } from "../utils/user.ts"; export function mcpCommand(snaptrade: Snaptrade): Command { @@ -66,14 +67,21 @@ export function mcpCommand(snaptrade: Snaptrade): Command { title: "List all positions", description: "List all positions across all broker accounts that the user has already configured via the SnapTrade CLI. The result can be used to calculate asset allocation. Equity, option, and crypto positions are supported.", - inputSchema: {}, + inputSchema: { + account_id: z.string().optional().describe("Optional account ID to filter positions for a specific account"), + }, }, - async () => { + async (params) => { const accounts = ( await snaptrade.accountInformation.listUserAccounts(user) ).data; + + const filteredAccounts = params.account_id + ? accounts.filter((account) => account.id === params.account_id) + : accounts; + const positionResponse = await Promise.all( - accounts.map( + filteredAccounts.map( async (account) => await Promise.all([ snaptrade.accountInformation.getUserAccountPositions({ From e10eb131802f7960c01ea3940c59af7e2ba6ed89 Mon Sep 17 00:00:00 2001 From: Billy Yuen Date: Thu, 22 Jan 2026 22:01:53 -0800 Subject: [PATCH 2/3] Make account_id required for list_positions MCP tool Prevents Claude Desktop crashes when listing positions across all accounts. Co-Authored-By: Claude Opus 4.5 --- src/commands/mcp.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/commands/mcp.ts b/src/commands/mcp.ts index f80b9e2..03b972a 100644 --- a/src/commands/mcp.ts +++ b/src/commands/mcp.ts @@ -64,11 +64,11 @@ export function mcpCommand(snaptrade: Snaptrade): Command { server.registerTool( "list_positions", { - title: "List all positions", + title: "List positions for an account", description: - "List all positions across all broker accounts that the user has already configured via the SnapTrade CLI. The result can be used to calculate asset allocation. Equity, option, and crypto positions are supported.", + "List all positions for a specific broker account. The result can be used to calculate asset allocation. Equity, option, and crypto positions are supported. Use list_accounts first to get the account ID.", inputSchema: { - account_id: z.string().optional().describe("Optional account ID to filter positions for a specific account"), + account_id: z.string().describe("Account ID to list positions for (required)"), }, }, async (params) => { @@ -76,9 +76,7 @@ export function mcpCommand(snaptrade: Snaptrade): Command { await snaptrade.accountInformation.listUserAccounts(user) ).data; - const filteredAccounts = params.account_id - ? accounts.filter((account) => account.id === params.account_id) - : accounts; + const filteredAccounts = accounts.filter((account) => account.id === params.account_id); const positionResponse = await Promise.all( filteredAccounts.map( From 5167d67938116df9d918299803bedc00e008223c Mon Sep 17 00:00:00 2001 From: Billy Yuen Date: Thu, 26 Feb 2026 21:40:04 -0800 Subject: [PATCH 3/3] Add refresh command and improve quote symbol handling - Add CLI `refresh` command to trigger broker data sync - Add `refresh_connection` MCP tool - Fix Yahoo Finance symbol mapping (BRKB -> BRK-B, etc.) - Skip unresolvable symbols (SPAXX, FDRXX, etc.) - Add verbose error logging for Yahoo Finance failures Co-Authored-By: Claude Opus 4.6 --- src/commands/index.ts | 2 ++ src/commands/mcp.ts | 37 ++++++++++++++++++++++ src/commands/refresh.ts | 56 +++++++++++++++++++++++++++++++++ src/utils/quotes.ts | 69 ++++++++++++++++++++++++++++++++++------- 4 files changed, 153 insertions(+), 11 deletions(-) create mode 100644 src/commands/refresh.ts diff --git a/src/commands/index.ts b/src/commands/index.ts index ab9d21f..82eb114 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -11,6 +11,7 @@ import { recentOrdersCommand } from "./recentOrders.ts"; import { accountsCommand } from "./accounts.ts"; import { quoteCommand } from "./quote.ts"; import { reconnectCommand } from "./reconnect.ts"; +import { refreshCommand } from "./refresh.ts"; import { brokersCommand } from "./brokers.ts"; import { ordersCommand } from "./orders.ts"; import { instrumentsCommand } from "./instruments.ts"; @@ -22,6 +23,7 @@ export function registerCommands(program: Command, snaptrade: Snaptrade): void { program.addCommand(brokersCommand(snaptrade)); program.addCommand(connectCommand(snaptrade)); program.addCommand(reconnectCommand(snaptrade)); + program.addCommand(refreshCommand(snaptrade)); program.addCommand(disconnectCommand(snaptrade)); program.addCommand(connectionsCommand(snaptrade)); program.addCommand(accountsCommand(snaptrade)); diff --git a/src/commands/mcp.ts b/src/commands/mcp.ts index 03b972a..361d62e 100644 --- a/src/commands/mcp.ts +++ b/src/commands/mcp.ts @@ -110,6 +110,43 @@ export function mcpCommand(snaptrade: Snaptrade): Command { } ); + server.registerTool( + "refresh_connection", + { + title: "Refresh a broker connection", + description: + "Trigger a data refresh for a broker connection. This forces the broker to sync the latest account data (positions, balances, orders, etc.). Use list_connections first to get the connection ID.", + inputSchema: { + connection_id: z.string().describe("Connection ID to refresh (use list_connections to get the ID)"), + }, + }, + async (params) => { + const response = + await snaptrade.connections.refreshBrokerageAuthorization({ + ...user, + authorizationId: params.connection_id, + }); + + const detail = (response.data as any)?.detail; + return { + content: [ + { + type: "text", + text: JSON.stringify( + { + status: "success", + connection_id: params.connection_id, + ...(detail ? { detail } : {}), + }, + undefined, + 2 + ), + }, + ], + }; + } + ); + // Start receiving messages on stdin and sending messages on stdout const transport = new StdioServerTransport(); await server.connect(transport); diff --git a/src/commands/refresh.ts b/src/commands/refresh.ts new file mode 100644 index 0000000..eb3aeca --- /dev/null +++ b/src/commands/refresh.ts @@ -0,0 +1,56 @@ +import { select } from "@inquirer/prompts"; +import chalk from "chalk"; +import { Command } from "commander"; +import { Snaptrade } from "snaptrade-typescript-sdk"; +import { loadOrRegisterUser } from "../utils/user.ts"; + +export function refreshCommand(snaptrade: Snaptrade): Command { + return new Command("refresh") + .description("Trigger a data refresh for a broker connection") + .argument("[connectionId]", "Connection ID to refresh") + .action(async (connectionId: string | undefined) => { + const user = await loadOrRegisterUser(snaptrade); + + const authorizationId = await (async () => { + if (connectionId) { + return connectionId; + } + const connections = ( + await snaptrade.connections.listBrokerageAuthorizations(user) + ).data; + + if (connections.length === 0) { + return null; + } + + if (connections.length === 1) { + return connections[0].id; + } + + return select({ + message: "Select a connection to refresh", + choices: connections.map((conn) => ({ + name: `${conn.brokerage?.display_name}${conn.disabled ? " (disabled)" : ""}`, + value: conn.id, + })), + }); + })(); + + if (!authorizationId) { + console.log("No connections found. Use `snaptrade connect` to add one."); + return; + } + + const response = + await snaptrade.connections.refreshBrokerageAuthorization({ + ...user, + authorizationId, + }); + + const detail = (response.data as any)?.detail; + console.log( + chalk.green("Refresh triggered successfully.") + + (detail ? ` ${detail}` : "") + ); + }); +} diff --git a/src/utils/quotes.ts b/src/utils/quotes.ts index 58b18b4..ae269bb 100644 --- a/src/utils/quotes.ts +++ b/src/utils/quotes.ts @@ -1,5 +1,8 @@ +import chalk from "chalk"; import { yf } from "./yahooFinance.ts"; +const isVerbose = process.argv.includes("--verbose"); + type YahooQuote = { regularMarketPrice?: number; currency?: string; @@ -7,12 +10,36 @@ type YahooQuote = { [key: string]: unknown; }; +/** + * Map broker-reported symbols to their Yahoo Finance equivalents. + * Brokers like Fidelity strip special characters (e.g. BRKB instead of BRK-B). + */ +const SYMBOL_MAP: Record = { + BRKB: "BRK-B", + BRKA: "BRK-A", +}; + +/** + * Symbols that cannot be resolved on Yahoo Finance (money market funds, sweep accounts, etc.). + * These are silently skipped rather than causing failed lookups. + */ +const UNRESOLVABLE_SYMBOLS = new Set([ + "SPAXX", + "FDRXX", + "FCASH", + "VMFXX", + "SWVXX", +]); + /** * Yahoo Finance treats OCC option symbols without spaces (e.g., AAPL 250118C00100000 -> AAPL250118C00100000). - * This helper removes spaces to match Yahoo's expected format. + * This helper removes spaces and applies known symbol mappings. + * Returns null for symbols that cannot be resolved on Yahoo Finance. */ -function sanitizeYahooSymbol(symbol: string): string { - return symbol.replaceAll(" ", ""); +function sanitizeYahooSymbol(symbol: string): string | null { + const stripped = symbol.replaceAll(" ", ""); + if (UNRESOLVABLE_SYMBOLS.has(stripped)) return null; + return SYMBOL_MAP[stripped] ?? stripped; } /** @@ -27,11 +54,21 @@ async function getYahooQuotesForSymbols( ): Promise> { if (!symbols.length) return {}; - const sanitized = symbols.map(sanitizeYahooSymbol); + // Build mapping from sanitized symbol back to original, filtering out unresolvable symbols + const sanitizedToOriginal: Record = {}; + const sanitizedSymbols: string[] = []; + for (const sym of symbols) { + const sanitized = sanitizeYahooSymbol(sym); + if (sanitized === null) continue; + sanitizedToOriginal[sanitized] = sym; + sanitizedSymbols.push(sanitized); + } + + if (!sanitizedSymbols.length) return {}; // yahoo-finance2 can take an array of symbols and return an object map const quotes = (await yf.quote( - sanitized, + sanitizedSymbols, { fields, return: "object", @@ -44,10 +81,8 @@ async function getYahooQuotesForSymbols( // Map results back to the original symbols const byOriginal: Record = {}; - for (let i = 0; i < symbols.length; i++) { - const original = symbols[i]; - const key = sanitized[i]; - byOriginal[original] = quotes?.[key]; + for (const [sanitized, original] of Object.entries(sanitizedToOriginal)) { + byOriginal[original] = quotes?.[sanitized]; } return byOriginal; } @@ -89,7 +124,13 @@ export async function getLastQuotes( } } return result; - } catch (_) { + } catch (error) { + if (isVerbose) { + console.error( + chalk.yellow("Yahoo Finance error (getLastQuotes):"), + error + ); + } return {}; } } @@ -126,7 +167,13 @@ export async function getFullQuotes( } } return result; - } catch (_) { + } catch (error) { + if (isVerbose) { + console.error( + chalk.yellow("Yahoo Finance error (getFullQuotes):"), + error + ); + } return {}; } }