From ec058d240d45197d8767dc4272434aadc441db0c Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Wed, 16 Apr 2025 16:59:36 +1000 Subject: [PATCH 01/13] wip 1 --- package.json | 2 +- pnpm-lock.yaml | 343 +---------------------------------------------- src/lib/utils.ts | 27 ++-- 3 files changed, 21 insertions(+), 351 deletions(-) diff --git a/package.json b/package.json index 47cfa99..17700f0 100644 --- a/package.json +++ b/package.json @@ -28,11 +28,11 @@ "check": "prettier --check . && tsc" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.9.0", "express": "^4.21.2", "open": "^10.1.0" }, "devDependencies": { + "@modelcontextprotocol/sdk": "link:/Users/glen/src/hax/mcp/typescript-sdk", "@types/express": "^5.0.0", "@types/node": "^22.13.10", "@types/react": "^19.0.12", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e550c3e..b1813cb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,6 @@ importers: .: dependencies: - '@modelcontextprotocol/sdk': - specifier: ^1.9.0 - version: 1.9.0 express: specifier: ^4.21.2 version: 4.21.2 @@ -18,6 +15,9 @@ importers: specifier: ^10.1.0 version: 10.1.0 devDependencies: + '@modelcontextprotocol/sdk': + specifier: link:/Users/glen/src/hax/mcp/typescript-sdk + version: link:../../hax/mcp/typescript-sdk '@types/express': specifier: ^5.0.0 version: 5.0.0 @@ -217,10 +217,6 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@modelcontextprotocol/sdk@1.9.0': - resolution: {integrity: sha512-Jq2EUCQpe0iyO5FGpzVYDNFR6oR53AIrwph9yWl7uSc7IWUMsrmpmSaTGra5hQNunXpM+9oit85p924jWuHzUA==} - engines: {node: '>=18'} - '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -363,10 +359,6 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} - accepts@2.0.0: - resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} - engines: {node: '>= 0.6'} - ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -396,10 +388,6 @@ packages: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - body-parser@2.1.0: - resolution: {integrity: sha512-/hPxh61E+ll0Ujp24Ilm64cykicul1ypfwjVttduAiEdtnJFvLePSrIPk+HMImtNv5270wOGCb1Tns2rybMkoQ==} - engines: {node: '>=18'} - brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} @@ -452,10 +440,6 @@ packages: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} - content-disposition@1.0.0: - resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} - engines: {node: '>= 0.6'} - content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} @@ -463,18 +447,10 @@ packages: cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} - cookie-signature@1.2.2: - resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} - engines: {node: '>=6.6.0'} - cookie@0.7.1: resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} - cors@2.8.5: - resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} - engines: {node: '>= 0.10'} - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -490,15 +466,6 @@ packages: supports-color: optional: true - debug@4.3.6: - resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.4.0: resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} @@ -576,28 +543,10 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} - eventsource-parser@3.0.0: - resolution: {integrity: sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==} - engines: {node: '>=18.0.0'} - - eventsource@3.0.5: - resolution: {integrity: sha512-LT/5J605bx5SNyE+ITBDiM3FxffBiq9un7Vx0EwMDM3vg8sWKx/tO2zC+LMqZ+smAM0F2hblaDZUVZF0te2pSw==} - engines: {node: '>=18.0.0'} - - express-rate-limit@7.5.0: - resolution: {integrity: sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==} - engines: {node: '>= 16'} - peerDependencies: - express: ^4.11 || 5 || ^5.0.0-beta.1 - express@4.21.2: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} - express@5.0.1: - resolution: {integrity: sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==} - engines: {node: '>= 18'} - fdir@6.4.3: resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==} peerDependencies: @@ -610,10 +559,6 @@ packages: resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} engines: {node: '>= 0.8'} - finalhandler@2.1.0: - resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} - engines: {node: '>= 0.8'} - foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -626,10 +571,6 @@ packages: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} - fresh@2.0.0: - resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} - engines: {node: '>= 0.8'} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -673,14 +614,6 @@ packages: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} - iconv-lite@0.5.2: - resolution: {integrity: sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==} - engines: {node: '>=0.10.0'} - - iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -702,9 +635,6 @@ packages: engines: {node: '>=14.16'} hasBin: true - is-promise@4.0.0: - resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - is-wsl@3.1.0: resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} engines: {node: '>=16'} @@ -744,17 +674,9 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} - media-typer@1.1.0: - resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} - engines: {node: '>= 0.8'} - merge-descriptors@1.0.3: resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} - merge-descriptors@2.0.0: - resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} - engines: {node: '>=18'} - methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} @@ -763,18 +685,10 @@ packages: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} - mime-db@1.54.0: - resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} - engines: {node: '>= 0.6'} - mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} - mime-types@3.0.0: - resolution: {integrity: sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==} - engines: {node: '>= 0.6'} - mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} @@ -791,9 +705,6 @@ packages: ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -804,10 +715,6 @@ packages: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} - negotiator@1.0.0: - resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} - engines: {node: '>= 0.6'} - object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -820,9 +727,6 @@ packages: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - open@10.1.0: resolution: {integrity: sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==} engines: {node: '>=18'} @@ -845,10 +749,6 @@ packages: path-to-regexp@0.1.12: resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} - path-to-regexp@8.2.0: - resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} - engines: {node: '>=16'} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -860,10 +760,6 @@ packages: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} - pkce-challenge@5.0.0: - resolution: {integrity: sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==} - engines: {node: '>=16.20.0'} - postcss-load-config@6.0.1: resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} engines: {node: '>= 18'} @@ -899,10 +795,6 @@ packages: resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} - qs@6.14.0: - resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} - engines: {node: '>=0.6'} - range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -911,10 +803,6 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} - raw-body@3.0.0: - resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} - engines: {node: '>= 0.8'} - react@19.0.0: resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} engines: {node: '>=0.10.0'} @@ -935,10 +823,6 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - router@2.1.0: - resolution: {integrity: sha512-/m/NSLxeYEgWNtyC+WtNHCF7jbGxOibVWKnn+1Psff4dJGOfoXP+MuC/f2CwSmyiHdOIzYnYFp4W6GxWfekaLA==} - engines: {node: '>= 18'} - run-applescript@7.0.0: resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==} engines: {node: '>=18'} @@ -953,18 +837,10 @@ packages: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} - send@1.1.0: - resolution: {integrity: sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==} - engines: {node: '>= 18'} - serve-static@1.16.2: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} - serve-static@2.1.0: - resolution: {integrity: sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==} - engines: {node: '>= 18'} - setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -1081,10 +957,6 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} - type-is@2.0.0: - resolution: {integrity: sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==} - engines: {node: '>= 0.6'} - typescript@5.8.2: resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} engines: {node: '>=14.17'} @@ -1124,17 +996,6 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - zod-to-json-schema@3.24.5: - resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==} - peerDependencies: - zod: ^3.24.1 - - zod@3.24.2: - resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} - snapshots: '@esbuild/aix-ppc64@0.25.1': @@ -1238,21 +1099,6 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@modelcontextprotocol/sdk@1.9.0': - dependencies: - content-type: 1.0.5 - cors: 2.8.5 - cross-spawn: 7.0.6 - eventsource: 3.0.5 - express: 5.0.1 - express-rate-limit: 7.5.0(express@5.0.1) - pkce-challenge: 5.0.0 - raw-body: 3.0.0 - zod: 3.24.2 - zod-to-json-schema: 3.24.5(zod@3.24.2) - transitivePeerDependencies: - - supports-color - '@pkgjs/parseargs@0.11.0': optional: true @@ -1370,11 +1216,6 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 - accepts@2.0.0: - dependencies: - mime-types: 3.0.0 - negotiator: 1.0.0 - ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} @@ -1408,20 +1249,6 @@ snapshots: transitivePeerDependencies: - supports-color - body-parser@2.1.0: - dependencies: - bytes: 3.1.2 - content-type: 1.0.5 - debug: 4.4.0 - http-errors: 2.0.0 - iconv-lite: 0.5.2 - on-finished: 2.4.1 - qs: 6.14.0 - raw-body: 3.0.0 - type-is: 2.0.0 - transitivePeerDependencies: - - supports-color - brace-expansion@2.0.1: dependencies: balanced-match: 1.0.2 @@ -1467,23 +1294,12 @@ snapshots: dependencies: safe-buffer: 5.2.1 - content-disposition@1.0.0: - dependencies: - safe-buffer: 5.2.1 - content-type@1.0.5: {} cookie-signature@1.0.6: {} - cookie-signature@1.2.2: {} - cookie@0.7.1: {} - cors@2.8.5: - dependencies: - object-assign: 4.1.1 - vary: 1.1.2 - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -1496,10 +1312,6 @@ snapshots: dependencies: ms: 2.0.0 - debug@4.3.6: - dependencies: - ms: 2.1.2 - debug@4.4.0: dependencies: ms: 2.1.3 @@ -1575,16 +1387,6 @@ snapshots: etag@1.8.1: {} - eventsource-parser@3.0.0: {} - - eventsource@3.0.5: - dependencies: - eventsource-parser: 3.0.0 - - express-rate-limit@7.5.0(express@5.0.1): - dependencies: - express: 5.0.1 - express@4.21.2: dependencies: accepts: 1.3.8 @@ -1621,43 +1423,6 @@ snapshots: transitivePeerDependencies: - supports-color - express@5.0.1: - dependencies: - accepts: 2.0.0 - body-parser: 2.1.0 - content-disposition: 1.0.0 - content-type: 1.0.5 - cookie: 0.7.1 - cookie-signature: 1.2.2 - debug: 4.3.6 - depd: 2.0.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 2.1.0 - fresh: 2.0.0 - http-errors: 2.0.0 - merge-descriptors: 2.0.0 - methods: 1.1.2 - mime-types: 3.0.0 - on-finished: 2.4.1 - once: 1.4.0 - parseurl: 1.3.3 - proxy-addr: 2.0.7 - qs: 6.13.0 - range-parser: 1.2.1 - router: 2.1.0 - safe-buffer: 5.2.1 - send: 1.1.0 - serve-static: 2.1.0 - setprototypeof: 1.2.0 - statuses: 2.0.1 - type-is: 2.0.0 - utils-merge: 1.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - fdir@6.4.3(picomatch@4.0.2): optionalDependencies: picomatch: 4.0.2 @@ -1674,17 +1439,6 @@ snapshots: transitivePeerDependencies: - supports-color - finalhandler@2.1.0: - dependencies: - debug: 4.4.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.1 - transitivePeerDependencies: - - supports-color - foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -1694,8 +1448,6 @@ snapshots: fresh@0.5.2: {} - fresh@2.0.0: {} - fsevents@2.3.3: optional: true @@ -1752,14 +1504,6 @@ snapshots: dependencies: safer-buffer: 2.1.2 - iconv-lite@0.5.2: - dependencies: - safer-buffer: 2.1.2 - - iconv-lite@0.6.3: - dependencies: - safer-buffer: 2.1.2 - inherits@2.0.4: {} ipaddr.js@1.9.1: {} @@ -1772,8 +1516,6 @@ snapshots: dependencies: is-docker: 3.0.0 - is-promise@4.0.0: {} - is-wsl@3.1.0: dependencies: is-inside-container: 1.0.0 @@ -1802,26 +1544,16 @@ snapshots: media-typer@0.3.0: {} - media-typer@1.1.0: {} - merge-descriptors@1.0.3: {} - merge-descriptors@2.0.0: {} - methods@1.1.2: {} mime-db@1.52.0: {} - mime-db@1.54.0: {} - mime-types@2.1.35: dependencies: mime-db: 1.52.0 - mime-types@3.0.0: - dependencies: - mime-db: 1.54.0 - mime@1.6.0: {} minimatch@9.0.5: @@ -1832,8 +1564,6 @@ snapshots: ms@2.0.0: {} - ms@2.1.2: {} - ms@2.1.3: {} mz@2.7.0: @@ -1844,8 +1574,6 @@ snapshots: negotiator@0.6.3: {} - negotiator@1.0.0: {} - object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -1854,10 +1582,6 @@ snapshots: dependencies: ee-first: 1.1.1 - once@1.4.0: - dependencies: - wrappy: 1.0.2 - open@10.1.0: dependencies: default-browser: 5.2.1 @@ -1878,16 +1602,12 @@ snapshots: path-to-regexp@0.1.12: {} - path-to-regexp@8.2.0: {} - picocolors@1.1.1: {} picomatch@4.0.2: {} pirates@4.0.6: {} - pkce-challenge@5.0.0: {} - postcss-load-config@6.0.1(tsx@4.19.3): dependencies: lilconfig: 3.1.3 @@ -1907,10 +1627,6 @@ snapshots: dependencies: side-channel: 1.1.0 - qs@6.14.0: - dependencies: - side-channel: 1.1.0 - range-parser@1.2.1: {} raw-body@2.5.2: @@ -1920,13 +1636,6 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 - raw-body@3.0.0: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.0 - iconv-lite: 0.6.3 - unpipe: 1.0.0 - react@19.0.0: {} readdirp@4.1.2: {} @@ -1960,12 +1669,6 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.35.0 fsevents: 2.3.3 - router@2.1.0: - dependencies: - is-promise: 4.0.0 - parseurl: 1.3.3 - path-to-regexp: 8.2.0 - run-applescript@7.0.0: {} safe-buffer@5.2.1: {} @@ -1990,23 +1693,6 @@ snapshots: transitivePeerDependencies: - supports-color - send@1.1.0: - dependencies: - debug: 4.4.0 - destroy: 1.2.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 0.5.2 - http-errors: 2.0.0 - mime-types: 2.1.35 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.1 - transitivePeerDependencies: - - supports-color - serve-static@1.16.2: dependencies: encodeurl: 2.0.0 @@ -2016,15 +1702,6 @@ snapshots: transitivePeerDependencies: - supports-color - serve-static@2.1.0: - dependencies: - encodeurl: 2.0.0 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 1.1.0 - transitivePeerDependencies: - - supports-color - setprototypeof@1.2.0: {} shebang-command@2.0.0: @@ -2162,12 +1839,6 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 - type-is@2.0.0: - dependencies: - content-type: 1.0.5 - media-typer: 1.1.0 - mime-types: 3.0.0 - typescript@5.8.2: {} undici-types@6.20.0: {} @@ -2201,11 +1872,3 @@ snapshots: ansi-styles: 6.2.1 string-width: 5.1.2 strip-ansi: 7.1.0 - - wrappy@1.0.2: {} - - zod-to-json-schema@3.24.5(zod@3.24.2): - dependencies: - zod: 3.24.2 - - zod@3.24.2: {} diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 40c744d..940a8ca 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,5 +1,6 @@ import { OAuthClientProvider, UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js' import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js' +import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js' import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js' import { OAuthCallbackServerOptions } from './types' import express from 'express' @@ -79,7 +80,7 @@ export async function connectToRemoteServer( headers: Record, waitForAuthCode: () => Promise, skipBrowserAuth: boolean = false, -): Promise { +): Promise { log(`[${pid}] Connecting to remote server: ${serverUrl}`) const url = new URL(serverUrl) @@ -93,18 +94,23 @@ export async function connectToRemoteServer( ...(init?.headers as Record | undefined), ...headers, ...(tokens?.access_token ? { Authorization: `Bearer ${tokens.access_token}` } : {}), - Accept: "text/event-stream", + Accept: 'text/event-stream', } as Record, - }) - ); + }), + ) }, - }; + } - const transport = new SSEClientTransport(url, { - authProvider, - requestInit: { headers }, - eventSourceInit, - }) + const TESTING_NEW_TRANSPORT = true + const transport = TESTING_NEW_TRANSPORT + ? new StreamableHTTPClientTransport(url, { + sessionId: crypto.randomUUID(), + }) + : new SSEClientTransport(url, { + authProvider, + requestInit: { headers }, + eventSourceInit, + }) try { await transport.start() @@ -126,6 +132,7 @@ export async function connectToRemoteServer( await transport.finishAuth(code) // Create a new transport after auth + // TODO: this needs to be the same transport type as the originals const newTransport = new SSEClientTransport(url, { authProvider, requestInit: { headers } }) await newTransport.start() log('Connected to remote server after authentication') From dee974b8b26b88f74e7763c63b3f7165039885bb Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Wed, 16 Apr 2025 16:59:44 +1000 Subject: [PATCH 02/13] wip 2 --- src/lib/utils.ts | 71 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 18 deletions(-) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 940a8ca..280a489 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -2,6 +2,10 @@ import { OAuthClientProvider, UnauthorizedError } from '@modelcontextprotocol/sd import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js' import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js' import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js' + +// Connection constants +export const REASON_AUTH_NEEDED = 'authentication-needed' +export const REASON_FALLBACK_TO_SSE = 'falling-back-to-sse-transport' import { OAuthCallbackServerOptions } from './types' import express from 'express' import net from 'net' @@ -80,6 +84,7 @@ export async function connectToRemoteServer( headers: Record, waitForAuthCode: () => Promise, skipBrowserAuth: boolean = false, + recursionReasons: Set = new Set(), ): Promise { log(`[${pid}] Connecting to remote server: ${serverUrl}`) const url = new URL(serverUrl) @@ -101,23 +106,47 @@ export async function connectToRemoteServer( }, } - const TESTING_NEW_TRANSPORT = true - const transport = TESTING_NEW_TRANSPORT - ? new StreamableHTTPClientTransport(url, { - sessionId: crypto.randomUUID(), - }) - : new SSEClientTransport(url, { - authProvider, - requestInit: { headers }, - eventSourceInit, - }) + // Choose transport based on recursion history + let transport; + + if (recursionReasons.has(REASON_FALLBACK_TO_SSE)) { + log('Using SSEClientTransport due to previous protocol failure') + transport = new SSEClientTransport(url, { + authProvider, + requestInit: { headers }, + eventSourceInit, + }) + } else { + log('Trying StreamableHTTPClientTransport first') + transport = new StreamableHTTPClientTransport(url, { + sessionId: crypto.randomUUID(), + }) + } try { await transport.start() - log('Connected to remote server') + log(`Connected to remote server using ${transport.constructor.name}`) return transport } catch (error) { - if (error instanceof UnauthorizedError || (error instanceof Error && error.message.includes('Unauthorized'))) { + // Check if it's a 405 Method Not Allowed error or similar protocol issue + if (error instanceof Error && + !recursionReasons.has(REASON_FALLBACK_TO_SSE) && + (error.message.includes('405') || + error.message.includes('Method Not Allowed') || + error.message.toLowerCase().includes('protocol'))) { + + // This condition is already checked above, so we will never reach here with REASON_FALLBACK_TO_SSE + // But keeping it as a safeguard + + log(`Received error: ${error.message}`) + log(`Recursively reconnecting for reason: ${REASON_FALLBACK_TO_SSE}`) + + // Add to recursion reasons set + recursionReasons.add(REASON_FALLBACK_TO_SSE) + + // Recursively call connectToRemoteServer with the updated recursion tracking + return connectToRemoteServer(serverUrl, authProvider, headers, waitForAuthCode, skipBrowserAuth, recursionReasons) + } else if (error instanceof UnauthorizedError || (error instanceof Error && error.message.includes('Unauthorized'))) { if (skipBrowserAuth) { log('Authentication required but skipping browser auth - using shared auth') } else { @@ -131,12 +160,18 @@ export async function connectToRemoteServer( log('Completing authorization...') await transport.finishAuth(code) - // Create a new transport after auth - // TODO: this needs to be the same transport type as the originals - const newTransport = new SSEClientTransport(url, { authProvider, requestInit: { headers } }) - await newTransport.start() - log('Connected to remote server after authentication') - return newTransport + if (recursionReasons.has(REASON_AUTH_NEEDED)) { + const errorMessage = `Already attempted reconnection for reason: ${REASON_AUTH_NEEDED}. Giving up.` + log(errorMessage) + throw new Error(errorMessage) + } + + // Track this reason for recursion + recursionReasons.add(REASON_AUTH_NEEDED) + log(`Recursively reconnecting for reason: ${REASON_AUTH_NEEDED}`) + + // Recursively call connectToRemoteServer with the updated recursion tracking + return connectToRemoteServer(serverUrl, authProvider, headers, waitForAuthCode, skipBrowserAuth, recursionReasons) } catch (authError) { log('Authorization error:', authError) throw authError From f80c6c4850da543e445d93287db6085d2ca1ea21 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Wed, 16 Apr 2025 17:13:19 +1000 Subject: [PATCH 03/13] wip 3 --- src/lib/utils.ts | 67 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 280a489..dde1b4c 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -5,7 +5,10 @@ import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js' // Connection constants export const REASON_AUTH_NEEDED = 'authentication-needed' -export const REASON_FALLBACK_TO_SSE = 'falling-back-to-sse-transport' +export const REASON_TRANSPORT_FALLBACK = 'falling-back-to-alternate-transport' + +// Transport strategy types +export type TransportStrategy = 'sse-only' | 'http-only' | 'sse-first' | 'http-first' import { OAuthCallbackServerOptions } from './types' import express from 'express' import net from 'net' @@ -70,13 +73,15 @@ export function mcpProxy({ transportToClient, transportToServer }: { transportTo } /** - * Creates and connects to a remote SSE server with OAuth authentication + * Creates and connects to a remote server with OAuth authentication * @param serverUrl The URL of the remote server * @param authProvider The OAuth client provider * @param headers Additional headers to send with the request * @param waitForAuthCode Function to wait for the auth code * @param skipBrowserAuth Whether to skip browser auth and use shared auth - * @returns The connected SSE client transport + * @param transportStrategy Strategy for selecting transport type ('sse-only', 'http-only', 'sse-first', 'http-first') + * @param recursionReasons Set of reasons for recursive calls (internal use) + * @returns The connected transport */ export async function connectToRemoteServer( serverUrl: string, @@ -84,6 +89,7 @@ export async function connectToRemoteServer( headers: Record, waitForAuthCode: () => Promise, skipBrowserAuth: boolean = false, + transportStrategy: TransportStrategy = 'http-first', recursionReasons: Set = new Set(), ): Promise { log(`[${pid}] Connecting to remote server: ${serverUrl}`) @@ -106,21 +112,32 @@ export async function connectToRemoteServer( }, } - // Choose transport based on recursion history + // Choose transport based on user strategy and recursion history let transport; + let shouldAttemptFallback = false; - if (recursionReasons.has(REASON_FALLBACK_TO_SSE)) { - log('Using SSEClientTransport due to previous protocol failure') + // If we've already tried falling back once, throw an error + if (recursionReasons.has(REASON_TRANSPORT_FALLBACK)) { + const errorMessage = `Already attempted transport fallback. Giving up.`; + log(errorMessage); + throw new Error(errorMessage); + } + + log(`Using transport strategy: ${transportStrategy}`); + // Determine if we should attempt to fallback on error + shouldAttemptFallback = transportStrategy === 'http-first' || transportStrategy === 'sse-first'; + + // Create transport instance based on the strategy + if (transportStrategy === 'sse-only' || transportStrategy === 'sse-first') { transport = new SSEClientTransport(url, { authProvider, requestInit: { headers }, eventSourceInit, - }) - } else { - log('Trying StreamableHTTPClientTransport first') + }); + } else { // http-only or http-first transport = new StreamableHTTPClientTransport(url, { sessionId: crypto.randomUUID(), - }) + }); } try { @@ -128,24 +145,21 @@ export async function connectToRemoteServer( log(`Connected to remote server using ${transport.constructor.name}`) return transport } catch (error) { - // Check if it's a 405 Method Not Allowed error or similar protocol issue + // Check if it's a protocol error and we should attempt fallback if (error instanceof Error && - !recursionReasons.has(REASON_FALLBACK_TO_SSE) && + shouldAttemptFallback && (error.message.includes('405') || error.message.includes('Method Not Allowed') || error.message.toLowerCase().includes('protocol'))) { - // This condition is already checked above, so we will never reach here with REASON_FALLBACK_TO_SSE - // But keeping it as a safeguard - log(`Received error: ${error.message}`) - log(`Recursively reconnecting for reason: ${REASON_FALLBACK_TO_SSE}`) + log(`Recursively reconnecting for reason: ${REASON_TRANSPORT_FALLBACK}`) // Add to recursion reasons set - recursionReasons.add(REASON_FALLBACK_TO_SSE) + recursionReasons.add(REASON_TRANSPORT_FALLBACK) // Recursively call connectToRemoteServer with the updated recursion tracking - return connectToRemoteServer(serverUrl, authProvider, headers, waitForAuthCode, skipBrowserAuth, recursionReasons) + return connectToRemoteServer(serverUrl, authProvider, headers, waitForAuthCode, skipBrowserAuth, transportStrategy, recursionReasons) } else if (error instanceof UnauthorizedError || (error instanceof Error && error.message.includes('Unauthorized'))) { if (skipBrowserAuth) { log('Authentication required but skipping browser auth - using shared auth') @@ -171,7 +185,7 @@ export async function connectToRemoteServer( log(`Recursively reconnecting for reason: ${REASON_AUTH_NEEDED}`) // Recursively call connectToRemoteServer with the updated recursion tracking - return connectToRemoteServer(serverUrl, authProvider, headers, waitForAuthCode, skipBrowserAuth, recursionReasons) + return connectToRemoteServer(serverUrl, authProvider, headers, waitForAuthCode, skipBrowserAuth, transportStrategy, recursionReasons) } catch (authError) { log('Authorization error:', authError) throw authError @@ -342,6 +356,19 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number, const serverUrl = args[0] const specifiedPort = args[1] ? parseInt(args[1]) : undefined const allowHttp = args.includes('--allow-http') + + // Parse transport strategy + let transportStrategy: TransportStrategy = 'http-first' // Default + const transportIndex = args.indexOf('--transport') + if (transportIndex !== -1 && transportIndex < args.length - 1) { + const strategy = args[transportIndex + 1] + if (strategy === 'sse-only' || strategy === 'http-only' || strategy === 'sse-first' || strategy === 'http-first') { + transportStrategy = strategy as TransportStrategy + log(`Using transport strategy: ${transportStrategy}`) + } else { + log(`Warning: Ignoring invalid transport strategy: ${strategy}. Valid values are: sse-only, http-only, sse-first, http-first`) + } + } if (!serverUrl) { log(usage) @@ -385,7 +412,7 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number, }) } - return { serverUrl, callbackPort, headers } + return { serverUrl, callbackPort, headers, transportStrategy } } /** From 0bf84d5d2280e7412cb788283d72629e4710520b Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Thu, 17 Apr 2025 12:26:47 +1000 Subject: [PATCH 04/13] wip, client now using the util with fallback --- src/client.ts | 127 +++++++++++++++++++------------------------------- 1 file changed, 49 insertions(+), 78 deletions(-) diff --git a/src/client.ts b/src/client.ts index d620884..ac58fb8 100644 --- a/src/client.ts +++ b/src/client.ts @@ -11,17 +11,28 @@ import { EventEmitter } from 'events' import { Client } from '@modelcontextprotocol/sdk/client/index.js' -import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js' import { ListResourcesResultSchema, ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js' -import { UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js' import { NodeOAuthClientProvider } from './lib/node-oauth-client-provider' -import { parseCommandLineArgs, setupSignalHandlers, log, MCP_REMOTE_VERSION, getServerUrlHash } from './lib/utils' +import { + parseCommandLineArgs, + setupSignalHandlers, + log, + MCP_REMOTE_VERSION, + getServerUrlHash, + connectToRemoteServer, + TransportStrategy, +} from './lib/utils' import { coordinateAuth } from './lib/coordination' /** * Main function to run the client */ -async function runClient(serverUrl: string, callbackPort: number, headers: Record) { +async function runClient( + serverUrl: string, + callbackPort: number, + headers: Record, + transportStrategy: TransportStrategy = 'http-first', +) { // Set up event emitter for auth flow const events = new EventEmitter() @@ -57,10 +68,9 @@ async function runClient(serverUrl: string, callbackPort: number, headers: Recor }, ) - // Create the transport factory - const url = new URL(serverUrl) - function initTransport() { - const transport = new SSEClientTransport(url, { authProvider, requestInit: { headers } }) + try { + // Connect to remote server with authentication + const transport = await connectToRemoteServer(serverUrl, authProvider, headers, waitForAuthCode, skipBrowserAuth, transportStrategy) // Set up message and error handlers transport.onmessage = (message) => { @@ -75,89 +85,50 @@ async function runClient(serverUrl: string, callbackPort: number, headers: Recor log('Connection closed.') process.exit(0) } - return transport - } - const transport = initTransport() + // Set up cleanup handler + const cleanup = async () => { + log('\nClosing connection...') + await client.close() + server.close() + } + setupSignalHandlers(cleanup) - // Set up cleanup handler - const cleanup = async () => { - log('\nClosing connection...') - await client.close() - server.close() - } - setupSignalHandlers(cleanup) - - // Try to connect - try { - log('Connecting to server...') + // Connect the client + log('Connecting client...') await client.connect(transport) log('Connected successfully!') - } catch (error) { - if (error instanceof UnauthorizedError || (error instanceof Error && error.message.includes('Unauthorized'))) { - log('Authentication required. Waiting for authorization...') - // Wait for the authorization code from the callback or another instance - const code = await waitForAuthCode() - - try { - log('Completing authorization...') - await transport.finishAuth(code) - - // Reconnect after authorization with a new transport - log('Connecting after authorization...') - await client.connect(initTransport()) - - log('Connected successfully!') - - // Request tools list after auth - log('Requesting tools list...') - const tools = await client.request({ method: 'tools/list' }, ListToolsResultSchema) - log('Tools:', JSON.stringify(tools, null, 2)) - - // Request resources list after auth - log('Requesting resource list...') - const resources = await client.request({ method: 'resources/list' }, ListResourcesResultSchema) - log('Resources:', JSON.stringify(resources, null, 2)) - - log('Listening for messages. Press Ctrl+C to exit.') - } catch (authError) { - log('Authorization error:', authError) - server.close() - process.exit(1) - } - } else { - log('Connection error:', error) - server.close() - process.exit(1) + try { + // Request tools list + log('Requesting tools list...') + const tools = await client.request({ method: 'tools/list' }, ListToolsResultSchema) + log('Tools:', JSON.stringify(tools, null, 2)) + } catch (e) { + log('Error requesting tools list:', e) } - } - try { - // Request tools list - log('Requesting tools list...') - const tools = await client.request({ method: 'tools/list' }, ListToolsResultSchema) - log('Tools:', JSON.stringify(tools, null, 2)) - } catch (e) { - log('Error requesting tools list:', e) - } + try { + // Request resources list + log('Requesting resource list...') + const resources = await client.request({ method: 'resources/list' }, ListResourcesResultSchema) + log('Resources:', JSON.stringify(resources, null, 2)) + } catch (e) { + log('Error requesting resources list:', e) + } - try { - // Request resources list - log('Requesting resource list...') - const resources = await client.request({ method: 'resources/list' }, ListResourcesResultSchema) - log('Resources:', JSON.stringify(resources, null, 2)) - } catch (e) { - log('Error requesting resources list:', e) + log('Listening for messages. Press Ctrl+C to exit.') + } catch (error) { + log('Fatal error:', error) + server.close() + process.exit(1) } - - log('Listening for messages. Press Ctrl+C to exit.') } // Parse command-line arguments and run the client parseCommandLineArgs(process.argv.slice(2), 3333, 'Usage: npx tsx client.ts [callback-port]') - .then(({ serverUrl, callbackPort, headers }) => { - return runClient(serverUrl, callbackPort, headers) + .then(({ serverUrl, callbackPort, headers, transportStrategy }) => { + return runClient(serverUrl, callbackPort, headers, transportStrategy) }) .catch((error) => { console.error('Fatal error:', error) From 14109a309f24594928ee0c8ef8437ae499764ec2 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Thu, 17 Apr 2025 16:04:50 +1000 Subject: [PATCH 05/13] Able to catch exceptions from one transport and fall back to the other --- package.json | 5 ++- pnpm-lock.yaml | 6 +-- src/client.ts | 18 +++++--- src/lib/utils.ts | 104 +++++++++++++++++++++++++++++------------------ src/proxy.ts | 22 +++++++++- 5 files changed, 104 insertions(+), 51 deletions(-) diff --git a/package.json b/package.json index 17700f0..ebf114d 100644 --- a/package.json +++ b/package.json @@ -28,11 +28,11 @@ "check": "prettier --check . && tsc" }, "dependencies": { + "@modelcontextprotocol/sdk": "link:/Users/glen/src/hax/mcp/typescript-sdk", "express": "^4.21.2", "open": "^10.1.0" }, "devDependencies": { - "@modelcontextprotocol/sdk": "link:/Users/glen/src/hax/mcp/typescript-sdk", "@types/express": "^5.0.0", "@types/node": "^22.13.10", "@types/react": "^19.0.12", @@ -54,7 +54,8 @@ "clean": true, "outDir": "dist", "external": [ - "react" + "react", + "@modelcontextprotocol/sdk" ] } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b1813cb..dbdb80d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@modelcontextprotocol/sdk': + specifier: link:/Users/glen/src/hax/mcp/typescript-sdk + version: link:../../hax/mcp/typescript-sdk express: specifier: ^4.21.2 version: 4.21.2 @@ -15,9 +18,6 @@ importers: specifier: ^10.1.0 version: 10.1.0 devDependencies: - '@modelcontextprotocol/sdk': - specifier: link:/Users/glen/src/hax/mcp/typescript-sdk - version: link:../../hax/mcp/typescript-sdk '@types/express': specifier: ^5.0.0 version: 5.0.0 diff --git a/src/client.ts b/src/client.ts index ac58fb8..669fd3b 100644 --- a/src/client.ts +++ b/src/client.ts @@ -70,7 +70,15 @@ async function runClient( try { // Connect to remote server with authentication - const transport = await connectToRemoteServer(serverUrl, authProvider, headers, waitForAuthCode, skipBrowserAuth, transportStrategy) + const transport = await connectToRemoteServer( + client, + serverUrl, + authProvider, + headers, + waitForAuthCode, + skipBrowserAuth, + transportStrategy, + ) // Set up message and error handlers transport.onmessage = (message) => { @@ -94,9 +102,6 @@ async function runClient( } setupSignalHandlers(cleanup) - // Connect the client - log('Connecting client...') - await client.connect(transport) log('Connected successfully!') try { @@ -117,7 +122,10 @@ async function runClient( log('Error requesting resources list:', e) } - log('Listening for messages. Press Ctrl+C to exit.') + // log('Listening for messages. Press Ctrl+C to exit.') + log('Exiting OK...') + server.close() + process.exit(0) } catch (error) { log('Fatal error:', error) server.close() diff --git a/src/lib/utils.ts b/src/lib/utils.ts index dde1b4c..43d2086 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,4 +1,5 @@ import { OAuthClientProvider, UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js' +import { Client } from '@modelcontextprotocol/sdk/client/index.js' import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js' import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js' import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js' @@ -74,6 +75,7 @@ export function mcpProxy({ transportToClient, transportToServer }: { transportTo /** * Creates and connects to a remote server with OAuth authentication + * @param client The client to connect with * @param serverUrl The URL of the remote server * @param authProvider The OAuth client provider * @param headers Additional headers to send with the request @@ -84,6 +86,7 @@ export function mcpProxy({ transportToClient, transportToServer }: { transportTo * @returns The connected transport */ export async function connectToRemoteServer( + client: Client, serverUrl: string, authProvider: OAuthClientProvider, headers: Record, @@ -112,54 +115,68 @@ export async function connectToRemoteServer( }, } - // Choose transport based on user strategy and recursion history - let transport; - let shouldAttemptFallback = false; - - // If we've already tried falling back once, throw an error - if (recursionReasons.has(REASON_TRANSPORT_FALLBACK)) { - const errorMessage = `Already attempted transport fallback. Giving up.`; - log(errorMessage); - throw new Error(errorMessage); - } - - log(`Using transport strategy: ${transportStrategy}`); + log(`Using transport strategy: ${transportStrategy}`) // Determine if we should attempt to fallback on error - shouldAttemptFallback = transportStrategy === 'http-first' || transportStrategy === 'sse-first'; - + // Choose transport based on user strategy and recursion history + const shouldAttemptFallback = transportStrategy === 'http-first' || transportStrategy === 'sse-first' + // Create transport instance based on the strategy - if (transportStrategy === 'sse-only' || transportStrategy === 'sse-first') { - transport = new SSEClientTransport(url, { - authProvider, - requestInit: { headers }, - eventSourceInit, - }); - } else { // http-only or http-first - transport = new StreamableHTTPClientTransport(url, { - sessionId: crypto.randomUUID(), - }); - } + const sseTransport = transportStrategy === 'sse-only' || transportStrategy === 'sse-first' + const transport = sseTransport + ? new SSEClientTransport(url, { + authProvider, + requestInit: { headers }, + eventSourceInit, + }) + : new StreamableHTTPClientTransport(url, { + authProvider, + requestInit: { headers }, + }) try { - await transport.start() + await client.connect(transport) log(`Connected to remote server using ${transport.constructor.name}`) + + if (!sseTransport) { + console.log({ serverCapabilities: await client.getServerCapabilities() }) + } + return transport } catch (error) { + console.log('DID I CATCH OR WHAT?') // Check if it's a protocol error and we should attempt fallback - if (error instanceof Error && - shouldAttemptFallback && - (error.message.includes('405') || - error.message.includes('Method Not Allowed') || - error.message.toLowerCase().includes('protocol'))) { - + if ( + error instanceof Error && + shouldAttemptFallback && + (sseTransport + ? error.message.includes('405') || error.message.includes('Method Not Allowed') + : error.message.includes('404') || error.message.includes('Not Found')) + ) { log(`Received error: ${error.message}`) + + // If we've already tried falling back once, throw an error + if (recursionReasons.has(REASON_TRANSPORT_FALLBACK)) { + const errorMessage = `Already attempted transport fallback. Giving up.` + log(errorMessage) + throw new Error(errorMessage) + } + log(`Recursively reconnecting for reason: ${REASON_TRANSPORT_FALLBACK}`) - + // Add to recursion reasons set recursionReasons.add(REASON_TRANSPORT_FALLBACK) - + // Recursively call connectToRemoteServer with the updated recursion tracking - return connectToRemoteServer(serverUrl, authProvider, headers, waitForAuthCode, skipBrowserAuth, transportStrategy, recursionReasons) + return connectToRemoteServer( + client, + serverUrl, + authProvider, + headers, + waitForAuthCode, + skipBrowserAuth, + sseTransport ? 'http-only' : 'sse-only', + recursionReasons, + ) } else if (error instanceof UnauthorizedError || (error instanceof Error && error.message.includes('Unauthorized'))) { if (skipBrowserAuth) { log('Authentication required but skipping browser auth - using shared auth') @@ -179,13 +196,22 @@ export async function connectToRemoteServer( log(errorMessage) throw new Error(errorMessage) } - + // Track this reason for recursion recursionReasons.add(REASON_AUTH_NEEDED) log(`Recursively reconnecting for reason: ${REASON_AUTH_NEEDED}`) - + // Recursively call connectToRemoteServer with the updated recursion tracking - return connectToRemoteServer(serverUrl, authProvider, headers, waitForAuthCode, skipBrowserAuth, transportStrategy, recursionReasons) + return connectToRemoteServer( + client, + serverUrl, + authProvider, + headers, + waitForAuthCode, + skipBrowserAuth, + transportStrategy, + recursionReasons, + ) } catch (authError) { log('Authorization error:', authError) throw authError @@ -356,7 +382,7 @@ export async function parseCommandLineArgs(args: string[], defaultPort: number, const serverUrl = args[0] const specifiedPort = args[1] ? parseInt(args[1]) : undefined const allowHttp = args.includes('--allow-http') - + // Parse transport strategy let transportStrategy: TransportStrategy = 'http-first' // Default const transportIndex = args.indexOf('--transport') diff --git a/src/proxy.ts b/src/proxy.ts index 9fd87d1..a914bb4 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -11,9 +11,18 @@ import { EventEmitter } from 'events' import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' -import { connectToRemoteServer, log, mcpProxy, parseCommandLineArgs, setupSignalHandlers, getServerUrlHash } from './lib/utils' +import { + connectToRemoteServer, + log, + mcpProxy, + parseCommandLineArgs, + setupSignalHandlers, + getServerUrlHash, + MCP_REMOTE_VERSION, +} from './lib/utils' import { NodeOAuthClientProvider } from './lib/node-oauth-client-provider' import { coordinateAuth } from './lib/coordination' +import { Client } from '@modelcontextprotocol/sdk/client/index.js' /** * Main function to run the proxy @@ -47,8 +56,17 @@ async function runProxy(serverUrl: string, callbackPort: number, headers: Record const localTransport = new StdioServerTransport() try { + const client = new Client( + { + name: 'mcp-remote', + version: MCP_REMOTE_VERSION, + }, + { + capabilities: {}, + }, + ) // Connect to remote server with authentication - const remoteTransport = await connectToRemoteServer(serverUrl, authProvider, headers, waitForAuthCode, skipBrowserAuth) + const remoteTransport = await connectToRemoteServer(client, serverUrl, authProvider, headers, waitForAuthCode, skipBrowserAuth) // Set up bidirectional proxy between local and remote transports mcpProxy({ From a6e6d0f1e8da410e125e5448a04171afa7e89b8f Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Thu, 17 Apr 2025 16:13:41 +1000 Subject: [PATCH 06/13] Refactor auth coordination to be lazy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create lazy auth coordinator that only initializes when needed - Modify connectToRemoteServer to only use auth when receiving an Unauthorized error - Update client.ts and proxy.ts to use the lazy auth approach - Add refactoring plan documentation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- refactoring-plan.md | 40 ++++++++++++++++++++++++++++ src/client.ts | 58 +++++++++++++++++++++++++++++------------ src/lib/coordination.ts | 34 ++++++++++++++++++++++++ src/lib/utils.ts | 26 +++++++++++------- src/proxy.ts | 52 +++++++++++++++++++++++++----------- 5 files changed, 169 insertions(+), 41 deletions(-) create mode 100644 refactoring-plan.md diff --git a/refactoring-plan.md b/refactoring-plan.md new file mode 100644 index 0000000..f38707f --- /dev/null +++ b/refactoring-plan.md @@ -0,0 +1,40 @@ +# Auth Coordination Refactoring Plan + +Currently, both `src/proxy.ts` and `src/client.ts` always run auth coordination before attempting to connect to the server. However, in some cases authentication is not required, and we already have the ability to catch and handle authorization errors in the `connectToRemoteServer` function. + +The plan is to refactor the code so that auth coordination is only invoked when we actually receive an "Unauthorized" error, rather than preemptively setting up auth for all connections. + +## Tasks + +1. [x] **Create a lazy auth coordinator**: Modify `coordinateAuth` function to support lazy initialization, so we can set it up but only use it when needed. + - Added `createLazyAuthCoordinator` function that returns an object with `initializeAuth` method + - Kept original `coordinateAuth` function intact for backward compatibility + +2. [x] **Refactor `connectToRemoteServer`**: Update this function to handle auth lazily: + - Removed the `waitForAuthCode` and `skipBrowserAuth` parameters + - Added a new `authInitializer` parameter that initializes auth when needed + - Only call this initializer when we encounter an "Unauthorized" error + - Created a new type `AuthInitializer` to define the expected interface + +3. [x] **Update client.ts**: Refactor the client to use the new lazy auth approach. + - No longer calling `coordinateAuth` at the beginning + - Created function to initiate auth only when needed + - Pass this function to `connectToRemoteServer` + - Added proper handling of server cleanup + +4. [x] **Update proxy.ts**: Similarly refactor the proxy to use the lazy auth approach. + - No longer calling `coordinateAuth` at the beginning + - Created function to initiate auth only when needed + - Pass this function to `connectToRemoteServer` + - Added proper handling of server cleanup + +5. [ ] **Test both flows**: + - Test with servers requiring authentication + - Test with servers that don't require authentication + +## Benefits + +- Improved efficiency by avoiding unnecessary auth setup when not needed +- Faster startup for connections that don't require auth +- Cleaner separation of concerns +- Reduced complexity in the call flow \ No newline at end of file diff --git a/src/client.ts b/src/client.ts index 669fd3b..ae14109 100644 --- a/src/client.ts +++ b/src/client.ts @@ -22,7 +22,7 @@ import { connectToRemoteServer, TransportStrategy, } from './lib/utils' -import { coordinateAuth } from './lib/coordination' +import { createLazyAuthCoordinator } from './lib/coordination' /** * Main function to run the client @@ -39,8 +39,8 @@ async function runClient( // Get the server URL hash for lockfile operations const serverUrlHash = getServerUrlHash(serverUrl) - // Coordinate authentication with other instances - const { server, waitForAuthCode, skipBrowserAuth } = await coordinateAuth(serverUrlHash, callbackPort, events) + // Create a lazy auth coordinator + const authCoordinator = createLazyAuthCoordinator(serverUrlHash, callbackPort, events) // Create the OAuth client provider const authProvider = new NodeOAuthClientProvider({ @@ -49,14 +49,6 @@ async function runClient( clientName: 'MCP CLI Client', }) - // If auth was completed by another instance, just log that we'll use the auth from disk - if (skipBrowserAuth) { - log('Authentication was completed by another instance - will use tokens from disk...') - // TODO: remove, the callback is happening before the tokens are exchanged - // so we're slightly too early - await new Promise((res) => setTimeout(res, 1_000)) - } - // Create the client const client = new Client( { @@ -68,15 +60,38 @@ async function runClient( }, ) + // Keep track of the server instance for cleanup + let server: any = null + + // Define an auth initializer function + const authInitializer = async () => { + const authState = await authCoordinator.initializeAuth() + + // Store server in outer scope for cleanup + server = authState.server + + // If auth was completed by another instance, just log that we'll use the auth from disk + if (authState.skipBrowserAuth) { + log('Authentication was completed by another instance - will use tokens from disk...') + // TODO: remove, the callback is happening before the tokens are exchanged + // so we're slightly too early + await new Promise((res) => setTimeout(res, 1_000)) + } + + return { + waitForAuthCode: authState.waitForAuthCode, + skipBrowserAuth: authState.skipBrowserAuth + } + } + try { - // Connect to remote server with authentication + // Connect to remote server with lazy authentication const transport = await connectToRemoteServer( client, serverUrl, authProvider, headers, - waitForAuthCode, - skipBrowserAuth, + authInitializer, transportStrategy, ) @@ -98,7 +113,10 @@ async function runClient( const cleanup = async () => { log('\nClosing connection...') await client.close() - server.close() + // If auth was initialized and server was created, close it + if (server) { + server.close() + } } setupSignalHandlers(cleanup) @@ -124,11 +142,17 @@ async function runClient( // log('Listening for messages. Press Ctrl+C to exit.') log('Exiting OK...') - server.close() + // Only close the server if it was initialized + if (server) { + server.close() + } process.exit(0) } catch (error) { log('Fatal error:', error) - server.close() + // Only close the server if it was initialized + if (server) { + server.close() + } process.exit(1) } } diff --git a/src/lib/coordination.ts b/src/lib/coordination.ts index ad1d2f6..ffe0c5b 100644 --- a/src/lib/coordination.ts +++ b/src/lib/coordination.ts @@ -5,6 +5,10 @@ import express from 'express' import { AddressInfo } from 'net' import { log, setupOAuthCallbackServerWithLongPoll } from './utils' +export type AuthCoordinator = { + initializeAuth: () => Promise<{ server: Server; waitForAuthCode: () => Promise; skipBrowserAuth: boolean }> +} + /** * Checks if a process with the given PID is running * @param pid The process ID to check @@ -88,6 +92,36 @@ export async function waitForAuthentication(port: number): Promise { } } +/** + * Creates a lazy auth coordinator that will only initiate auth when needed + * @param serverUrlHash The hash of the server URL + * @param callbackPort The port to use for the callback server + * @param events The event emitter to use for signaling + * @returns An AuthCoordinator object with an initializeAuth method + */ +export function createLazyAuthCoordinator( + serverUrlHash: string, + callbackPort: number, + events: EventEmitter +): AuthCoordinator { + let authState: { server: Server; waitForAuthCode: () => Promise; skipBrowserAuth: boolean } | null = null + + return { + initializeAuth: async () => { + // If auth has already been initialized, return the existing state + if (authState) { + return authState + } + + log('Initializing auth coordination on-demand') + + // Initialize auth using the existing coordinateAuth logic + authState = await coordinateAuth(serverUrlHash, callbackPort, events) + return authState + } + } +} + /** * Coordinates authentication between multiple instances of the client/proxy * @param serverUrlHash The hash of the server URL diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 43d2086..316f9bc 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -73,14 +73,21 @@ export function mcpProxy({ transportToClient, transportToServer }: { transportTo } } +/** + * Type for the auth initialization function + */ +export type AuthInitializer = () => Promise<{ + waitForAuthCode: () => Promise + skipBrowserAuth: boolean +}> + /** * Creates and connects to a remote server with OAuth authentication * @param client The client to connect with * @param serverUrl The URL of the remote server * @param authProvider The OAuth client provider * @param headers Additional headers to send with the request - * @param waitForAuthCode Function to wait for the auth code - * @param skipBrowserAuth Whether to skip browser auth and use shared auth + * @param authInitializer Function to initialize authentication when needed * @param transportStrategy Strategy for selecting transport type ('sse-only', 'http-only', 'sse-first', 'http-first') * @param recursionReasons Set of reasons for recursive calls (internal use) * @returns The connected transport @@ -90,8 +97,7 @@ export async function connectToRemoteServer( serverUrl: string, authProvider: OAuthClientProvider, headers: Record, - waitForAuthCode: () => Promise, - skipBrowserAuth: boolean = false, + authInitializer: AuthInitializer, transportStrategy: TransportStrategy = 'http-first', recursionReasons: Set = new Set(), ): Promise { @@ -143,7 +149,6 @@ export async function connectToRemoteServer( return transport } catch (error) { - console.log('DID I CATCH OR WHAT?') // Check if it's a protocol error and we should attempt fallback if ( error instanceof Error && @@ -172,12 +177,16 @@ export async function connectToRemoteServer( serverUrl, authProvider, headers, - waitForAuthCode, - skipBrowserAuth, + authInitializer, sseTransport ? 'http-only' : 'sse-only', recursionReasons, ) } else if (error instanceof UnauthorizedError || (error instanceof Error && error.message.includes('Unauthorized'))) { + log('Authentication required. Initializing auth...') + + // Initialize authentication on-demand + const { waitForAuthCode, skipBrowserAuth } = await authInitializer() + if (skipBrowserAuth) { log('Authentication required but skipping browser auth - using shared auth') } else { @@ -207,8 +216,7 @@ export async function connectToRemoteServer( serverUrl, authProvider, headers, - waitForAuthCode, - skipBrowserAuth, + authInitializer, transportStrategy, recursionReasons, ) diff --git a/src/proxy.ts b/src/proxy.ts index a914bb4..145cf8c 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -21,7 +21,7 @@ import { MCP_REMOTE_VERSION, } from './lib/utils' import { NodeOAuthClientProvider } from './lib/node-oauth-client-provider' -import { coordinateAuth } from './lib/coordination' +import { createLazyAuthCoordinator } from './lib/coordination' import { Client } from '@modelcontextprotocol/sdk/client/index.js' /** @@ -34,8 +34,8 @@ async function runProxy(serverUrl: string, callbackPort: number, headers: Record // Get the server URL hash for lockfile operations const serverUrlHash = getServerUrlHash(serverUrl) - // Coordinate authentication with other instances - const { server, waitForAuthCode, skipBrowserAuth } = await coordinateAuth(serverUrlHash, callbackPort, events) + // Create a lazy auth coordinator + const authCoordinator = createLazyAuthCoordinator(serverUrlHash, callbackPort, events) // Create the OAuth client provider const authProvider = new NodeOAuthClientProvider({ @@ -44,17 +44,33 @@ async function runProxy(serverUrl: string, callbackPort: number, headers: Record clientName: 'MCP CLI Proxy', }) - // If auth was completed by another instance, just log that we'll use the auth from disk - if (skipBrowserAuth) { - log('Authentication was completed by another instance - will use tokens from disk') - // TODO: remove, the callback is happening before the tokens are exchanged - // so we're slightly too early - await new Promise((res) => setTimeout(res, 1_000)) - } - // Create the STDIO transport for local connections const localTransport = new StdioServerTransport() + // Keep track of the server instance for cleanup + let server: any = null + + // Define an auth initializer function + const authInitializer = async () => { + const authState = await authCoordinator.initializeAuth() + + // Store server in outer scope for cleanup + server = authState.server + + // If auth was completed by another instance, just log that we'll use the auth from disk + if (authState.skipBrowserAuth) { + log('Authentication was completed by another instance - will use tokens from disk') + // TODO: remove, the callback is happening before the tokens are exchanged + // so we're slightly too early + await new Promise((res) => setTimeout(res, 1_000)) + } + + return { + waitForAuthCode: authState.waitForAuthCode, + skipBrowserAuth: authState.skipBrowserAuth + } + } + try { const client = new Client( { @@ -65,8 +81,8 @@ async function runProxy(serverUrl: string, callbackPort: number, headers: Record capabilities: {}, }, ) - // Connect to remote server with authentication - const remoteTransport = await connectToRemoteServer(client, serverUrl, authProvider, headers, waitForAuthCode, skipBrowserAuth) + // Connect to remote server with lazy authentication + const remoteTransport = await connectToRemoteServer(client, serverUrl, authProvider, headers, authInitializer) // Set up bidirectional proxy between local and remote transports mcpProxy({ @@ -84,7 +100,10 @@ async function runProxy(serverUrl: string, callbackPort: number, headers: Record const cleanup = async () => { await remoteTransport.close() await localTransport.close() - server.close() + // Only close the server if it was initialized + if (server) { + server.close() + } } setupSignalHandlers(cleanup) } catch (error) { @@ -111,7 +130,10 @@ to the CA certificate file. If using claude_desktop_config.json, this might look } `) } - server.close() + // Only close the server if it was initialized + if (server) { + server.close() + } process.exit(1) } } From c632b774cbb6f5bb070db14cb23fb4e7594b99f4 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Thu, 17 Apr 2025 16:29:18 +1000 Subject: [PATCH 07/13] removing log that was confusing claude --- src/lib/utils.ts | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 316f9bc..f905ad4 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -143,10 +143,6 @@ export async function connectToRemoteServer( await client.connect(transport) log(`Connected to remote server using ${transport.constructor.name}`) - if (!sseTransport) { - console.log({ serverCapabilities: await client.getServerCapabilities() }) - } - return transport } catch (error) { // Check if it's a protocol error and we should attempt fallback @@ -183,10 +179,10 @@ export async function connectToRemoteServer( ) } else if (error instanceof UnauthorizedError || (error instanceof Error && error.message.includes('Unauthorized'))) { log('Authentication required. Initializing auth...') - + // Initialize authentication on-demand const { waitForAuthCode, skipBrowserAuth } = await authInitializer() - + if (skipBrowserAuth) { log('Authentication required but skipping browser auth - using shared auth') } else { @@ -211,15 +207,7 @@ export async function connectToRemoteServer( log(`Recursively reconnecting for reason: ${REASON_AUTH_NEEDED}`) // Recursively call connectToRemoteServer with the updated recursion tracking - return connectToRemoteServer( - client, - serverUrl, - authProvider, - headers, - authInitializer, - transportStrategy, - recursionReasons, - ) + return connectToRemoteServer(client, serverUrl, authProvider, headers, authInitializer, transportStrategy, recursionReasons) } catch (authError) { log('Authorization error:', authError) throw authError From 423790736437e24d086e39efea60e5e0e85c84dd Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Thu, 17 Apr 2025 16:30:42 +1000 Subject: [PATCH 08/13] removing claude's refactoring plan --- refactoring-plan.md | 40 ---------------------------------------- 1 file changed, 40 deletions(-) delete mode 100644 refactoring-plan.md diff --git a/refactoring-plan.md b/refactoring-plan.md deleted file mode 100644 index f38707f..0000000 --- a/refactoring-plan.md +++ /dev/null @@ -1,40 +0,0 @@ -# Auth Coordination Refactoring Plan - -Currently, both `src/proxy.ts` and `src/client.ts` always run auth coordination before attempting to connect to the server. However, in some cases authentication is not required, and we already have the ability to catch and handle authorization errors in the `connectToRemoteServer` function. - -The plan is to refactor the code so that auth coordination is only invoked when we actually receive an "Unauthorized" error, rather than preemptively setting up auth for all connections. - -## Tasks - -1. [x] **Create a lazy auth coordinator**: Modify `coordinateAuth` function to support lazy initialization, so we can set it up but only use it when needed. - - Added `createLazyAuthCoordinator` function that returns an object with `initializeAuth` method - - Kept original `coordinateAuth` function intact for backward compatibility - -2. [x] **Refactor `connectToRemoteServer`**: Update this function to handle auth lazily: - - Removed the `waitForAuthCode` and `skipBrowserAuth` parameters - - Added a new `authInitializer` parameter that initializes auth when needed - - Only call this initializer when we encounter an "Unauthorized" error - - Created a new type `AuthInitializer` to define the expected interface - -3. [x] **Update client.ts**: Refactor the client to use the new lazy auth approach. - - No longer calling `coordinateAuth` at the beginning - - Created function to initiate auth only when needed - - Pass this function to `connectToRemoteServer` - - Added proper handling of server cleanup - -4. [x] **Update proxy.ts**: Similarly refactor the proxy to use the lazy auth approach. - - No longer calling `coordinateAuth` at the beginning - - Created function to initiate auth only when needed - - Pass this function to `connectToRemoteServer` - - Added proper handling of server cleanup - -5. [ ] **Test both flows**: - - Test with servers requiring authentication - - Test with servers that don't require authentication - -## Benefits - -- Improved efficiency by avoiding unnecessary auth setup when not needed -- Faster startup for connections that don't require auth -- Cleaner separation of concerns -- Reduced complexity in the call flow \ No newline at end of file From cf355dac9c0d47d5d766e1753fe66f10bab449d7 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Thu, 17 Apr 2025 16:49:09 +1000 Subject: [PATCH 09/13] move back to bundling the SDK so I can publish a test version without waiting for them --- package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index ebf114d..17700f0 100644 --- a/package.json +++ b/package.json @@ -28,11 +28,11 @@ "check": "prettier --check . && tsc" }, "dependencies": { - "@modelcontextprotocol/sdk": "link:/Users/glen/src/hax/mcp/typescript-sdk", "express": "^4.21.2", "open": "^10.1.0" }, "devDependencies": { + "@modelcontextprotocol/sdk": "link:/Users/glen/src/hax/mcp/typescript-sdk", "@types/express": "^5.0.0", "@types/node": "^22.13.10", "@types/react": "^19.0.12", @@ -54,8 +54,7 @@ "clean": true, "outDir": "dist", "external": [ - "react", - "@modelcontextprotocol/sdk" + "react" ] } } From d62744526de2f133b5d1c47315d1c8819a1b9e7d Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Thu, 17 Apr 2025 16:49:21 +1000 Subject: [PATCH 10/13] 0.1.0-0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 17700f0..a5efb63 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.0.22", + "version": "0.1.0-0", "description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth", "keywords": [ "mcp", From 6eeb56e9a85ad1fb450c044ddb17870fc6b28523 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Thu, 24 Apr 2025 16:23:19 +1000 Subject: [PATCH 11/13] Adding transport strategy to proxy.ts too --- src/proxy.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/proxy.ts b/src/proxy.ts index 145cf8c..59c2e48 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -19,6 +19,7 @@ import { setupSignalHandlers, getServerUrlHash, MCP_REMOTE_VERSION, + TransportStrategy, } from './lib/utils' import { NodeOAuthClientProvider } from './lib/node-oauth-client-provider' import { createLazyAuthCoordinator } from './lib/coordination' @@ -27,7 +28,7 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js' /** * Main function to run the proxy */ -async function runProxy(serverUrl: string, callbackPort: number, headers: Record) { +async function runProxy(serverUrl: string, callbackPort: number, headers: Record, transportStrategy: TransportStrategy = 'http-first') { // Set up event emitter for auth flow const events = new EventEmitter() @@ -82,7 +83,7 @@ async function runProxy(serverUrl: string, callbackPort: number, headers: Record }, ) // Connect to remote server with lazy authentication - const remoteTransport = await connectToRemoteServer(client, serverUrl, authProvider, headers, authInitializer) + const remoteTransport = await connectToRemoteServer(client, serverUrl, authProvider, headers, authInitializer, transportStrategy) // Set up bidirectional proxy between local and remote transports mcpProxy({ @@ -140,8 +141,8 @@ to the CA certificate file. If using claude_desktop_config.json, this might look // Parse command-line arguments and run the proxy parseCommandLineArgs(process.argv.slice(2), 3334, 'Usage: npx tsx proxy.ts [callback-port]') - .then(({ serverUrl, callbackPort, headers }) => { - return runProxy(serverUrl, callbackPort, headers) + .then(({ serverUrl, callbackPort, headers, transportStrategy }) => { + return runProxy(serverUrl, callbackPort, headers, transportStrategy) }) .catch((error) => { log('Fatal error:', error) From 624d3a883cea8e1e8cae767cd145da7bf2fb40e7 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Thu, 24 Apr 2025 16:25:20 +1000 Subject: [PATCH 12/13] switching to version of the SDK that was released --- package.json | 2 +- pnpm-lock.yaml | 315 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 313 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index a5efb63..1f69e60 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "open": "^10.1.0" }, "devDependencies": { - "@modelcontextprotocol/sdk": "link:/Users/glen/src/hax/mcp/typescript-sdk", + "@modelcontextprotocol/sdk": "^1.10.2", "@types/express": "^5.0.0", "@types/node": "^22.13.10", "@types/react": "^19.0.12", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dbdb80d..48d8491 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,6 @@ importers: .: dependencies: - '@modelcontextprotocol/sdk': - specifier: link:/Users/glen/src/hax/mcp/typescript-sdk - version: link:../../hax/mcp/typescript-sdk express: specifier: ^4.21.2 version: 4.21.2 @@ -18,6 +15,9 @@ importers: specifier: ^10.1.0 version: 10.1.0 devDependencies: + '@modelcontextprotocol/sdk': + specifier: ^1.10.2 + version: 1.10.2 '@types/express': specifier: ^5.0.0 version: 5.0.0 @@ -217,6 +217,10 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@modelcontextprotocol/sdk@1.10.2': + resolution: {integrity: sha512-rb6AMp2DR4SN+kc6L1ta2NCpApyA9WYNx3CrTSZvGxq9wH71bRur+zRqPfg0vQ9mjywR7qZdX2RGHOPq3ss+tA==} + engines: {node: '>=18'} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -359,6 +363,10 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -388,6 +396,10 @@ packages: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + body-parser@2.2.0: + resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} + engines: {node: '>=18'} + brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} @@ -440,6 +452,10 @@ packages: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} + content-disposition@1.0.0: + resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} + engines: {node: '>= 0.6'} + content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} @@ -447,10 +463,18 @@ packages: cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + cookie@0.7.1: resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} + cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -543,10 +567,28 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} + eventsource-parser@3.0.1: + resolution: {integrity: sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.6: + resolution: {integrity: sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA==} + engines: {node: '>=18.0.0'} + + express-rate-limit@7.5.0: + resolution: {integrity: sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==} + engines: {node: '>= 16'} + peerDependencies: + express: ^4.11 || 5 || ^5.0.0-beta.1 + express@4.21.2: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} + express@5.1.0: + resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} + engines: {node: '>= 18'} + fdir@6.4.3: resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==} peerDependencies: @@ -559,6 +601,10 @@ packages: resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} engines: {node: '>= 0.8'} + finalhandler@2.1.0: + resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} + engines: {node: '>= 0.8'} + foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -571,6 +617,10 @@ packages: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -614,6 +664,10 @@ packages: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -635,6 +689,9 @@ packages: engines: {node: '>=14.16'} hasBin: true + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-wsl@3.1.0: resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} engines: {node: '>=16'} @@ -674,9 +731,17 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + merge-descriptors@1.0.3: resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} @@ -685,10 +750,18 @@ packages: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mime-types@3.0.1: + resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==} + engines: {node: '>= 0.6'} + mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} @@ -715,6 +788,10 @@ packages: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -727,6 +804,9 @@ packages: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + open@10.1.0: resolution: {integrity: sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==} engines: {node: '>=18'} @@ -749,6 +829,10 @@ packages: path-to-regexp@0.1.12: resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} + path-to-regexp@8.2.0: + resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} + engines: {node: '>=16'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -760,6 +844,10 @@ packages: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} + pkce-challenge@5.0.0: + resolution: {integrity: sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==} + engines: {node: '>=16.20.0'} + postcss-load-config@6.0.1: resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} engines: {node: '>= 18'} @@ -795,6 +883,10 @@ packages: resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} + qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + engines: {node: '>=0.6'} + range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -803,6 +895,10 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} + raw-body@3.0.0: + resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} + engines: {node: '>= 0.8'} + react@19.0.0: resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} engines: {node: '>=0.10.0'} @@ -823,6 +919,10 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + run-applescript@7.0.0: resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==} engines: {node: '>=18'} @@ -837,10 +937,18 @@ packages: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} + send@1.2.0: + resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} + engines: {node: '>= 18'} + serve-static@1.16.2: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} + serve-static@2.2.0: + resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} + engines: {node: '>= 18'} + setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -957,6 +1065,10 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + typescript@5.8.2: resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} engines: {node: '>=14.17'} @@ -996,6 +1108,17 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + zod-to-json-schema@3.24.5: + resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==} + peerDependencies: + zod: ^3.24.1 + + zod@3.24.3: + resolution: {integrity: sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==} + snapshots: '@esbuild/aix-ppc64@0.25.1': @@ -1099,6 +1222,21 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@modelcontextprotocol/sdk@1.10.2': + dependencies: + content-type: 1.0.5 + cors: 2.8.5 + cross-spawn: 7.0.6 + eventsource: 3.0.6 + express: 5.1.0 + express-rate-limit: 7.5.0(express@5.1.0) + pkce-challenge: 5.0.0 + raw-body: 3.0.0 + zod: 3.24.3 + zod-to-json-schema: 3.24.5(zod@3.24.3) + transitivePeerDependencies: + - supports-color + '@pkgjs/parseargs@0.11.0': optional: true @@ -1216,6 +1354,11 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 + accepts@2.0.0: + dependencies: + mime-types: 3.0.1 + negotiator: 1.0.0 + ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} @@ -1249,6 +1392,20 @@ snapshots: transitivePeerDependencies: - supports-color + body-parser@2.2.0: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.0 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + on-finished: 2.4.1 + qs: 6.14.0 + raw-body: 3.0.0 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + brace-expansion@2.0.1: dependencies: balanced-match: 1.0.2 @@ -1294,12 +1451,23 @@ snapshots: dependencies: safe-buffer: 5.2.1 + content-disposition@1.0.0: + dependencies: + safe-buffer: 5.2.1 + content-type@1.0.5: {} cookie-signature@1.0.6: {} + cookie-signature@1.2.2: {} + cookie@0.7.1: {} + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -1387,6 +1555,16 @@ snapshots: etag@1.8.1: {} + eventsource-parser@3.0.1: {} + + eventsource@3.0.6: + dependencies: + eventsource-parser: 3.0.1 + + express-rate-limit@7.5.0(express@5.1.0): + dependencies: + express: 5.1.0 + express@4.21.2: dependencies: accepts: 1.3.8 @@ -1423,6 +1601,38 @@ snapshots: transitivePeerDependencies: - supports-color + express@5.1.0: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.0 + content-disposition: 1.0.0 + content-type: 1.0.5 + cookie: 0.7.1 + cookie-signature: 1.2.2 + debug: 4.4.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.0 + fresh: 2.0.0 + http-errors: 2.0.0 + merge-descriptors: 2.0.0 + mime-types: 3.0.1 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.14.0 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.0 + serve-static: 2.2.0 + statuses: 2.0.1 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + fdir@6.4.3(picomatch@4.0.2): optionalDependencies: picomatch: 4.0.2 @@ -1439,6 +1649,17 @@ snapshots: transitivePeerDependencies: - supports-color + finalhandler@2.1.0: + dependencies: + debug: 4.4.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -1448,6 +1669,8 @@ snapshots: fresh@0.5.2: {} + fresh@2.0.0: {} + fsevents@2.3.3: optional: true @@ -1504,6 +1727,10 @@ snapshots: dependencies: safer-buffer: 2.1.2 + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + inherits@2.0.4: {} ipaddr.js@1.9.1: {} @@ -1516,6 +1743,8 @@ snapshots: dependencies: is-docker: 3.0.0 + is-promise@4.0.0: {} + is-wsl@3.1.0: dependencies: is-inside-container: 1.0.0 @@ -1544,16 +1773,26 @@ snapshots: media-typer@0.3.0: {} + media-typer@1.1.0: {} + merge-descriptors@1.0.3: {} + merge-descriptors@2.0.0: {} + methods@1.1.2: {} mime-db@1.52.0: {} + mime-db@1.54.0: {} + mime-types@2.1.35: dependencies: mime-db: 1.52.0 + mime-types@3.0.1: + dependencies: + mime-db: 1.54.0 + mime@1.6.0: {} minimatch@9.0.5: @@ -1574,6 +1813,8 @@ snapshots: negotiator@0.6.3: {} + negotiator@1.0.0: {} + object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -1582,6 +1823,10 @@ snapshots: dependencies: ee-first: 1.1.1 + once@1.4.0: + dependencies: + wrappy: 1.0.2 + open@10.1.0: dependencies: default-browser: 5.2.1 @@ -1602,12 +1847,16 @@ snapshots: path-to-regexp@0.1.12: {} + path-to-regexp@8.2.0: {} + picocolors@1.1.1: {} picomatch@4.0.2: {} pirates@4.0.6: {} + pkce-challenge@5.0.0: {} + postcss-load-config@6.0.1(tsx@4.19.3): dependencies: lilconfig: 3.1.3 @@ -1627,6 +1876,10 @@ snapshots: dependencies: side-channel: 1.1.0 + qs@6.14.0: + dependencies: + side-channel: 1.1.0 + range-parser@1.2.1: {} raw-body@2.5.2: @@ -1636,6 +1889,13 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 + raw-body@3.0.0: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + unpipe: 1.0.0 + react@19.0.0: {} readdirp@4.1.2: {} @@ -1669,6 +1929,16 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.35.0 fsevents: 2.3.3 + router@2.2.0: + dependencies: + debug: 4.4.0 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.2.0 + transitivePeerDependencies: + - supports-color + run-applescript@7.0.0: {} safe-buffer@5.2.1: {} @@ -1693,6 +1963,22 @@ snapshots: transitivePeerDependencies: - supports-color + send@1.2.0: + dependencies: + debug: 4.4.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.0 + mime-types: 3.0.1 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + serve-static@1.16.2: dependencies: encodeurl: 2.0.0 @@ -1702,6 +1988,15 @@ snapshots: transitivePeerDependencies: - supports-color + serve-static@2.2.0: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.0 + transitivePeerDependencies: + - supports-color + setprototypeof@1.2.0: {} shebang-command@2.0.0: @@ -1839,6 +2134,12 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.1 + typescript@5.8.2: {} undici-types@6.20.0: {} @@ -1872,3 +2173,11 @@ snapshots: ansi-styles: 6.2.1 string-width: 5.1.2 strip-ansi: 7.1.0 + + wrappy@1.0.2: {} + + zod-to-json-schema@3.24.5(zod@3.24.3): + dependencies: + zod: 3.24.3 + + zod@3.24.3: {} From 1908c203ffc8750a97bea4042ecb8ed00d8641d0 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Thu, 24 Apr 2025 16:26:28 +1000 Subject: [PATCH 13/13] 0.1.0-1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1f69e60..763ceeb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcp-remote", - "version": "0.1.0-0", + "version": "0.1.0-1", "description": "Remote proxy for Model Context Protocol, allowing local-only clients to connect to remote servers using oAuth", "keywords": [ "mcp",