diff --git a/package.json b/package.json index ef3ac82..e372983 100644 --- a/package.json +++ b/package.json @@ -151,6 +151,7 @@ "@js-joda/timezone": "2.5.0", "@logseq/react-tweet-embed": "1.3.1-1", "@logseq/simple-wave-record": "^0.0.3", + "@modelcontextprotocol/sdk": "^1.27.1", "@sentry/react": "^10.51.0", "@sqlite.org/sqlite-wasm": "3.51.2-build9", "@tabler/icons-react": "3.44.0", @@ -198,7 +199,8 @@ "uniqolor": "1.1.1", "url": "^0.11.4", "util": "^0.12.5", - "yup": "^1.7.1" + "yup": "^1.7.1", + "zod": "^4.3.6" }, "optionalDependencies": { "@zvec/bindings-darwin-arm64": "0.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 295b97f..7dc7a51 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -104,6 +104,9 @@ importers: '@logseq/simple-wave-record': specifier: ^0.0.3 version: 0.0.3 + '@modelcontextprotocol/sdk': + specifier: ^1.27.1 + version: 1.30.0(zod@4.4.3) '@sentry/react': specifier: ^10.51.0 version: 10.51.0(react@19.2.6) @@ -248,6 +251,9 @@ importers: yup: specifier: ^1.7.1 version: 1.7.1 + zod: + specifier: ^4.3.6 + version: 4.4.3 devDependencies: '@capacitor/assets': specifier: ^3.0.5 @@ -798,6 +804,12 @@ packages: resolution: {integrity: sha512-VEPdHzwelZ12hEX18BHduqxMZGolcUsrbeokHYxOUIm8X2+M7nx5QPtPeQgRxR9XjhdLv4/7DD5BWOlSrJ3k7Q==} engines: {node: '>=12.0.0'} + '@hono/node-server@2.1.1': + resolution: {integrity: sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg==} + engines: {node: '>=20'} + peerDependencies: + hono: ^4 + '@hookform/resolvers@5.4.0': resolution: {integrity: sha512-EIsqr/t/qbinPIhGjMdtvutIN1Kk4uwbROE9/UQ93CAVGR7GkA7Y92+fX80OzXi/OB67jVFYwKGO1WzkxmkFZw==} peerDependencies: @@ -920,6 +932,16 @@ packages: resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} hasBin: true + '@modelcontextprotocol/sdk@1.30.0': + resolution: {integrity: sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + '@napi-rs/wasm-runtime@1.1.4': resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} peerDependencies: @@ -1470,6 +1492,10 @@ packages: abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + acorn-import-phases@1.0.4: resolution: {integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==} engines: {node: '>=10.13.0'} @@ -1504,6 +1530,14 @@ packages: ajv: optional: true + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + ajv-keywords@5.1.0: resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} peerDependencies: @@ -1724,6 +1758,10 @@ packages: bl@5.1.0: resolution: {integrity: sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==} + body-parser@2.3.0: + resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} + engines: {node: '>=18'} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -1775,6 +1813,10 @@ packages: buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1947,6 +1989,18 @@ packages: console-control-strings@1.1.0: resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + content-type@2.1.0: + resolution: {integrity: sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==} + engines: {node: '>=18'} + conventional-changelog-angular@5.0.13: resolution: {integrity: sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==} engines: {node: '>=10'} @@ -2012,6 +2066,14 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + copy-props@4.0.0: resolution: {integrity: sha512-bVWtw1wQLzzKiYROtvNlbJgxgBYt2bMJpkCbKmXM3xyijvcjjWXEk5nyrrT3bgJ7ODb19ZohE2T0Y3FgNPyoTw==} engines: {node: '>= 10.13.0'} @@ -2019,6 +2081,10 @@ packages: core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + cosmiconfig@7.1.0: resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} engines: {node: '>=10'} @@ -2187,6 +2253,10 @@ packages: delegates@1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dependency-graph@1.0.0: resolution: {integrity: sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==} engines: {node: '>=4'} @@ -2270,6 +2340,9 @@ packages: earcut@3.0.2: resolution: {integrity: sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==} + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + electron-to-chromium@1.5.349: resolution: {integrity: sha512-QsWVGyRuY07Aqb234QytTfwd5d9AJlfNIQ5wIOl1L+PZDzI9d9+Fn0FRale/QYlFxt/bUnB0/nLd1jFPGxGK1A==} @@ -2283,6 +2356,10 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} @@ -2335,6 +2412,9 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} @@ -2355,6 +2435,10 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + eventemitter3@5.0.4: resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} @@ -2365,6 +2449,14 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + eventsource-parser@3.1.1: + resolution: {integrity: sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + execa@1.0.0: resolution: {integrity: sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==} engines: {node: '>=6'} @@ -2381,6 +2473,16 @@ packages: resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} engines: {node: '>=0.10.0'} + express-rate-limit@8.6.2: + resolution: {integrity: sha512-YH4ru+eOJxQABscKFfRCy9R7x9QFGdezclVMwwgFFndzS2Xnm0uo6B0ABZsLhcpeptGv2qvuJVWlQr9gQZoC3A==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -2450,6 +2552,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + find-up@2.1.0: resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} engines: {node: '>=4'} @@ -2501,9 +2607,17 @@ packages: resolution: {integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==} engines: {node: '>=14.0.0'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + fraction.js@5.3.4: resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} @@ -2766,6 +2880,10 @@ packages: resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} engines: {node: '>=0.10.0'} + hono@4.13.5: + resolution: {integrity: sha512-O6+/eCYRkzzzy0rPWwKLiGBR1nFuUPZynnwjxN1MBA62NNqbT0wQEzQyK2gSO5yDIDB336sXQleAhOHrzlYyKw==} + engines: {node: '>=16.9.0'} + hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} @@ -2784,6 +2902,10 @@ packages: htmlparser2@3.10.1: resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + https-proxy-agent@5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} @@ -2792,6 +2914,10 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + iconv-lite@0.7.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} + engines: {node: '>=0.10.0'} + idb@5.0.6: resolution: {integrity: sha512-/PFvOWPzRcEPmlDt5jEvzVZVs0wyd/EvGvkDIcbBpGuMMLQKrTPG0TxvE2UJtgZtCQCmOtM2QD7yQJBVEjKGOw==} @@ -2865,6 +2991,14 @@ packages: resolution: {integrity: sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==} engines: {node: '>=4'} + ip-address@10.5.0: + resolution: {integrity: sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + is-absolute@1.0.0: resolution: {integrity: sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==} engines: {node: '>=0.10.0'} @@ -2972,6 +3106,9 @@ packages: resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} engines: {node: '>=0.10.0'} + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -3054,6 +3191,9 @@ packages: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true + jose@6.2.10: + resolution: {integrity: sha512-iiW7J9qRFlGxvCOIBDBDxFePQSn7ZMAnrYGhrrOo6siO/MIqwfyilLR27pkfDgUk+raLuzADS8A3S/KLBisc0g==} + js-binary-schema-parser@2.0.3: resolution: {integrity: sha512-xezGJmOb4lk/M1ZZLTR/jaBHQ4gG/lqQnJqdIv4721DMggsa1bDVlHXNeHYogaIEHD9vCRv0fcL4hMA+Coarkg==} @@ -3084,6 +3224,9 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + json-stringify-safe@5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} @@ -3349,6 +3492,10 @@ packages: mdn-data@2.27.1: resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + media-typer@1.1.1: + resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==} + engines: {node: '>= 0.8'} + mem@4.3.0: resolution: {integrity: sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==} engines: {node: '>=6'} @@ -3365,6 +3512,10 @@ packages: resolution: {integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==} engines: {node: '>=10'} + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -3386,6 +3537,10 @@ packages: resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} engines: {node: '>= 0.6'} + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -3493,6 +3648,10 @@ packages: engines: {node: '>=16.0.0'} hasBin: true + negotiator@1.1.0: + resolution: {integrity: sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==} + engines: {node: '>=18'} + neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} @@ -3597,6 +3756,10 @@ packages: obliterator@1.6.1: resolution: {integrity: sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==} + on-finished@2.4.1: + 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==} @@ -3684,6 +3847,10 @@ packages: parse-svg-path@0.1.2: resolution: {integrity: sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==} + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + path-complete-extname@1.0.0: resolution: {integrity: sha512-CVjiWcMRdGU8ubs08YQVzhutOR5DEfO97ipRIlOGMK5Bek5nQySknBpuxVAVJ36hseTNs+vdIcv57ZrWxH7zvg==} @@ -3730,6 +3897,9 @@ packages: resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} engines: {node: 18 || 20 || >=22} + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + path-type@3.0.0: resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} engines: {node: '>=4'} @@ -3786,6 +3956,10 @@ packages: pixi.js@8.18.1: resolution: {integrity: sha512-6LUPWYgulZhp/w4kam2XHXB0QedISZIqrJbRdHLLQ3csn5a38uzKxAp6B5j6s89QFYaIJbg95kvgTRcbgpO1ow==} + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} @@ -4126,6 +4300,10 @@ packages: property-expr@2.0.6: resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==} + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + pump@3.0.4: resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} @@ -4144,6 +4322,10 @@ packages: resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} engines: {node: '>=0.6'} + qs@6.15.3: + resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} + engines: {node: '>=0.6'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -4151,6 +4333,14 @@ packages: resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} engines: {node: '>=8'} + range-parser@1.3.0: + resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -4353,6 +4543,10 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -4411,6 +4605,14 @@ packages: peerDependencies: '@capacitor/core': '>=7.0.0' + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} @@ -4421,6 +4623,9 @@ packages: setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + shadow-cljs-jar@1.3.4: resolution: {integrity: sha512-cZB2pzVXBnhpJ6PQdsjO+j/MksR28mv4QD/hP/2y1fsIa9Z9RutYgh3N34FZ8Ktl4puAXaIGlct+gMCJ5BmwmA==} @@ -4473,6 +4678,10 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -4557,6 +4766,10 @@ packages: split@1.0.1: resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + stream-buffers@2.2.0: resolution: {integrity: sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==} engines: {node: '>= 0.10.0'} @@ -4799,6 +5012,10 @@ packages: resolution: {integrity: sha512-y8MN937s/HVhEoBU1SxfHC+wxCHkV1a9gW8eAdTadYh/bGyesZIVcbjI+mSpFbSVwQici/XjBjuUyri1dnXwBw==} engines: {node: '>=10.13.0'} + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + toposort@2.0.2: resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==} @@ -4862,6 +5079,10 @@ packages: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + typedarray-to-buffer@3.1.5: resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} @@ -4917,6 +5138,10 @@ packages: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + untildify@4.0.0: resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} engines: {node: '>=8'} @@ -5004,6 +5229,10 @@ packages: resolution: {integrity: sha512-aeVK81SIuT6aMJfNo9Vte8Dw0/FZINGBV8BfCraGtqVxIeLAEhJyoWs8SmvRVmXfGss2PmmOwZCuBPbZR+IYWg==} engines: {node: '>= 10.13.0'} + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + vfile-message@2.0.4: resolution: {integrity: sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==} @@ -5280,6 +5509,14 @@ packages: yup@1.7.1: resolution: {integrity: sha512-GKHFX2nXul2/4Dtfxhozv701jLQHdf6J34YDh2cEkpqoo8le5Mg6/LrdseVLrFarmFygZTlfIhHx/QKfb/QWXw==} + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + zwitch@1.0.5: resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==} @@ -5938,6 +6175,10 @@ snapshots: '@highlightjs/cdn-assets@11.11.1': {} + '@hono/node-server@2.1.1(hono@4.13.5)': + dependencies: + hono: 4.13.5 + '@hookform/resolvers@5.4.0(react-hook-form@7.81.0(react@19.2.6))': dependencies: '@standard-schema/utils': 0.3.0 @@ -6145,6 +6386,28 @@ snapshots: - supports-color optional: true + '@modelcontextprotocol/sdk@1.30.0(zod@4.4.3)': + dependencies: + '@hono/node-server': 2.1.1(hono@4.13.5) + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.1.1 + express: 5.2.1 + express-rate-limit: 8.6.2(express@5.2.1) + hono: 4.13.5 + jose: 6.2.10 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.4.3 + zod-to-json-schema: 3.25.2(zod@4.4.3) + transitivePeerDependencies: + - supports-color + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: '@emnapi/core': 1.10.0 @@ -6689,6 +6952,11 @@ snapshots: abbrev@1.1.1: optional: true + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.1.0 + acorn-import-phases@1.0.4(acorn@8.16.0): dependencies: acorn: 8.16.0 @@ -6717,6 +6985,10 @@ snapshots: optionalDependencies: ajv: 8.20.0 + ajv-formats@3.0.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + ajv-keywords@5.1.0(ajv@8.20.0): dependencies: ajv: 8.20.0 @@ -6914,6 +7186,20 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + body-parser@2.3.0: + dependencies: + bytes: 3.1.2 + content-type: 2.1.0 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + on-finished: 2.4.1 + qs: 6.15.3 + raw-body: 3.0.2 + type-is: 2.1.0 + transitivePeerDependencies: + - supports-color + boolbase@1.0.0: {} bowser@2.14.1: {} @@ -6975,6 +7261,8 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + bytes@3.1.2: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -7154,6 +7442,12 @@ snapshots: console-control-strings@1.1.0: optional: true + content-disposition@1.1.0: {} + + content-type@1.0.5: {} + + content-type@2.1.0: {} + conventional-changelog-angular@5.0.13: dependencies: compare-func: 2.0.0 @@ -7255,6 +7549,10 @@ snapshots: convert-source-map@2.0.0: {} + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + copy-props@4.0.0: dependencies: each-props: 3.0.0 @@ -7262,6 +7560,11 @@ snapshots: core-util-is@1.0.3: {} + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + cosmiconfig@7.1.0: dependencies: '@types/parse-json': 4.0.2 @@ -7451,6 +7754,8 @@ snapshots: delegates@1.0.0: optional: true + depd@2.0.0: {} + dependency-graph@1.0.0: {} detect-file@1.0.0: {} @@ -7546,6 +7851,8 @@ snapshots: earcut@3.0.2: {} + ee-first@1.1.1: {} + electron-to-chromium@1.5.349: {} elementtree@0.1.7: @@ -7556,6 +7863,8 @@ snapshots: emoji-regex@8.0.0: {} + encodeurl@2.0.0: {} + end-of-stream@1.4.5: dependencies: once: 1.4.0 @@ -7593,6 +7902,8 @@ snapshots: escalade@3.2.0: {} + escape-html@1.0.3: {} + escape-string-regexp@1.0.5: {} eslint-scope@5.1.1: @@ -7608,6 +7919,8 @@ snapshots: estraverse@5.3.0: {} + etag@1.8.1: {} + eventemitter3@5.0.4: {} events-universal@1.0.1: @@ -7618,6 +7931,12 @@ snapshots: events@3.3.0: {} + eventsource-parser@3.1.1: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.1.1 + execa@1.0.0: dependencies: cross-spawn: 6.0.6 @@ -7638,6 +7957,47 @@ snapshots: dependencies: homedir-polyfill: 1.0.3 + express-rate-limit@8.6.2(express@5.2.1): + dependencies: + debug: 4.4.3 + express: 5.2.1 + ip-address: 10.5.0 + transitivePeerDependencies: + - supports-color + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.3.0 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.1 + range-parser: 1.3.0 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.1.0 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + extend@3.0.2: {} fancy-log@2.0.0: @@ -7706,6 +8066,17 @@ snapshots: dependencies: to-regex-range: 5.0.1 + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + find-up@2.1.0: dependencies: locate-path: 2.0.0 @@ -7762,8 +8133,12 @@ snapshots: dezalgo: 1.0.4 once: 1.4.0 + forwarded@0.2.0: {} + fraction.js@5.3.4: {} + fresh@2.0.0: {} + fs-constants@1.0.0: {} fs-extra@10.1.0: @@ -8081,6 +8456,8 @@ snapshots: dependencies: parse-passwd: 1.0.0 + hono@4.13.5: {} + hosted-git-info@2.8.9: {} hosted-git-info@4.1.0: @@ -8103,6 +8480,14 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 @@ -8115,6 +8500,10 @@ snapshots: dependencies: safer-buffer: 2.1.2 + iconv-lite@0.7.3: + dependencies: + safer-buffer: 2.1.2 + idb@5.0.6: {} ieee754@1.2.1: {} @@ -8166,6 +8555,10 @@ snapshots: invert-kv@2.0.0: {} + ip-address@10.5.0: {} + + ipaddr.js@1.9.1: {} + is-absolute@1.0.0: dependencies: is-relative: 1.0.0 @@ -8247,6 +8640,8 @@ snapshots: is-plain-object@5.0.0: {} + is-promise@4.0.0: {} + is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -8312,6 +8707,8 @@ snapshots: jiti@2.7.0: {} + jose@6.2.10: {} + js-binary-schema-parser@2.0.3: {} js-cookie@3.0.8: {} @@ -8330,6 +8727,8 @@ snapshots: json-schema-traverse@1.0.0: {} + json-schema-typed@8.0.2: {} + json-stringify-safe@5.0.1: {} json5@2.2.3: {} @@ -8559,6 +8958,8 @@ snapshots: mdn-data@2.27.1: {} + media-typer@1.1.1: {} + mem@4.3.0: dependencies: map-age-cleaner: 0.1.3 @@ -8596,6 +8997,8 @@ snapshots: type-fest: 0.18.1 yargs-parser: 20.2.9 + merge-descriptors@2.0.0: {} + merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -8620,6 +9023,10 @@ snapshots: mime-db@1.54.0: {} + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + mimic-fn@2.1.0: {} mimic-response@2.1.0: @@ -8713,6 +9120,10 @@ snapshots: transitivePeerDependencies: - supports-color + negotiator@1.1.0: + dependencies: + content-type: 2.1.0 + neo-async@2.6.2: {} nice-try@1.0.5: {} @@ -8815,6 +9226,10 @@ snapshots: obliterator@1.6.1: {} + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -8904,6 +9319,8 @@ snapshots: parse-svg-path@0.1.2: {} + parseurl@1.3.3: {} + path-complete-extname@1.0.0: {} path-exists@3.0.0: {} @@ -8936,6 +9353,8 @@ snapshots: lru-cache: 11.3.5 minipass: 7.1.3 + path-to-regexp@8.4.2: {} + path-type@3.0.0: dependencies: pify: 3.0.0 @@ -8989,6 +9408,8 @@ snapshots: parse-svg-path: 0.1.2 tiny-lru: 11.4.7 + pkce-challenge@5.0.1: {} + pkg-dir@4.2.0: dependencies: find-up: 4.1.0 @@ -9314,6 +9735,11 @@ snapshots: property-expr@2.0.6: {} + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + pump@3.0.4: dependencies: end-of-stream: 1.4.5 @@ -9327,10 +9753,24 @@ snapshots: dependencies: side-channel: 1.1.0 + qs@6.15.3: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + queue-microtask@1.2.3: {} quick-lru@4.0.1: {} + range-parser@1.3.0: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + unpipe: 1.0.0 + rc@1.2.8: dependencies: deep-extend: 0.6.0 @@ -9558,6 +9998,16 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.17 '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.17 + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -9612,6 +10062,31 @@ snapshots: transitivePeerDependencies: - supports-color + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.3.0 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + set-blocking@2.0.0: {} set-function-length@1.2.2: @@ -9625,6 +10100,8 @@ snapshots: setimmediate@1.0.5: {} + setprototypeof@1.2.0: {} + shadow-cljs-jar@1.3.4: {} shadow-cljs@3.4.6: @@ -9701,6 +10178,14 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + signal-exit@3.0.7: {} simple-concat@1.0.1: {} @@ -9785,6 +10270,8 @@ snapshots: dependencies: through: 2.3.8 + statuses@2.0.2: {} + stream-buffers@2.2.0: {} stream-composer@1.0.2: @@ -10123,6 +10610,8 @@ snapshots: - bare-abort-controller - react-native-b4a + toidentifier@1.0.1: {} + toposort@2.0.2: {} tr46@0.0.3: {} @@ -10173,6 +10662,12 @@ snapshots: type-fest@2.19.0: {} + type-is@2.1.0: + dependencies: + content-type: 2.1.0 + media-typer: 1.1.1 + mime-types: 3.0.2 + typedarray-to-buffer@3.1.5: dependencies: is-typedarray: 1.0.0 @@ -10225,6 +10720,8 @@ snapshots: universalify@2.0.1: {} + unpipe@1.0.0: {} + untildify@4.0.0: {} update-browserslist-db@1.2.3(browserslist@4.28.2): @@ -10292,6 +10789,8 @@ snapshots: value-or-function@4.0.0: {} + vary@1.1.2: {} + vfile-message@2.0.4: dependencies: '@types/unist': 2.0.11 @@ -10621,4 +11120,10 @@ snapshots: toposort: 2.0.2 type-fest: 2.19.0 + zod-to-json-schema@3.25.2(zod@4.4.3): + dependencies: + zod: 4.4.3 + + zod@4.4.3: {} + zwitch@1.0.5: {} diff --git a/src/electron/electron/mcp_server.cljs b/src/electron/electron/mcp_server.cljs deleted file mode 100644 index 0c5e595..0000000 --- a/src/electron/electron/mcp_server.cljs +++ /dev/null @@ -1,231 +0,0 @@ -(ns electron.mcp-server - "MCP server routes for the desktop API server." - (:require ["@modelcontextprotocol/sdk/server/mcp.js" :refer [McpServer]] - ["@modelcontextprotocol/sdk/server/streamableHttp.js" :refer [StreamableHTTPServerTransport]] - ["@modelcontextprotocol/sdk/types.js" :refer [isInitializeRequest]] - ["zod/v3" :as z] ;; zod 4 doesn't work w/ mcp - https://github.com/modelcontextprotocol/typescript-sdk/issues/925 - [promesa.core :as p])) - -;; Server util fns -;; =============== -;; "Stores transports by session ID" -(defonce ^:private transports - (atom {})) - -(declare create-mcp-api-server) - -;; See https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http -;; for how to respond to different MCP requests -(defn handle-post-request [api-fn {:keys [port host]} req res] - (let [session-id (aget (.-headers req) "mcp-session-id")] - (js/console.log "POST /mcp request" session-id (pr-str (.-body req))) - (cond - (and session-id (@transports session-id)) - (let [^js transport (@transports session-id)] - (.handleRequest transport (.-raw req) (.-raw res) (.-body req))) - - (and (not session-id) - (isInitializeRequest (.-body req))) - (let [transport (StreamableHTTPServerTransport. - #js {:sessionIdGenerator (comp str random-uuid) - :enableDnsRebindingProtection true - :allowedHosts #js [(str host ":" port)]}) - mcp-server (create-mcp-api-server api-fn)] - (set! (.-onclose transport) - (fn [] - (js/console.log "Transport closed" (.-sessionId transport)) - (swap! transports dissoc (.-sessionId transport)))) - (.connect mcp-server transport) - (.handleRequest transport (.-raw req) (.-raw res) (.-body req)) - (js/console.log "Initialize sessionId" (.-sessionId transport)) - (if (.-sessionId transport) - (swap! transports assoc (.-sessionId transport) transport) - (js/console.error "No sessionId to initialize!")) - res) - - :else - (do - (.code res 400) - (.send res #js {:jsonrpc "2.0" - :error #js {:code -32000 - :message "Bad Request: No valid session ID provided"} - :id nil}))))) - -(defn handle-get-request - [req res] - (let [session-id (aget (.-headers req) "mcp-session-id")] - (js/console.log "GET /mcp" session-id) - (if-let [transport (and session-id (@transports session-id))] - (.handleRequest ^js transport (.-raw req) (.-raw res)) - (-> res (.code 400) (.send "Invalid or missing session ID"))))) - -(defn handle-delete-request - [req res] - (let [session-id (aget (.-headers req) "mcp-session-id")] - (js/console.log "DELETE /mcp" session-id) - (if-let [transport (and session-id (@transports session-id))] - (do - (.close transport) - (-> res (.code 200) (.send #js {:ok true}))) - (-> res (.code 400) (.send "Invalid or missing session ID"))))) - -(defn mcp-error-response [msg] - #js {:content - #js [#js {:type "text" - :text msg}]}) - -(defn mcp-success-response [data] - (clj->js {:content - [{:type "text" - :text (js/JSON.stringify (clj->js data))}]})) - -;; API tool fns -;; ============ -(defn- unexpected-api-error [error] - #js {:content - #js [#js {:type "text" - :text (str "Unexpected API error: " (.-message error))}]}) - -(defn- api-tool - "Calls API method w/ args and returns a MCP response" - [api-fn api-method method-args] - (-> (p/let [body (api-fn api-method method-args)] - (if-let [error (and body (aget body "error"))] - (mcp-error-response (str "API Error: " error)) - (mcp-success-response body))) - (p/catch unexpected-api-error))) - -(defn- api-get-page - [call-api-fn args] - (call-api-fn "logseq.cli.getPageData" [(aget args "pageName")])) - -(defn- api-list-pages - [call-api-fn args] - (call-api-fn "logseq.cli.listPages" [#js {:expand (aget args "expand")}])) - -(defn- api-list-tags - [call-api-fn args] - (call-api-fn "logseq.cli.listTags" [#js {:expand (aget args "expand")}])) - -(defn- api-list-properties - [call-api-fn args] - (call-api-fn "logseq.cli.listProperties" [#js {:expand (aget args "expand")}])) - -(defn- api-search-blocks - [call-api-fn args] - (call-api-fn "logseq.app.search" [(aget args "searchTerm") #js {:enable-snippet? false}])) - -(defn- api-upsert-nodes - [call-api-fn args] - (call-api-fn "logseq.cli.upsertNodes" [(aget args "operations") #js {:dry-run (aget args "dry-run")}])) - -(def ^:large-vars/data-var api-tools - "MCP Tools when calling API server" - {:listPages - {:fn api-list-pages - :config #js {:title "List Pages" - :description "List all pages in a graph" - :inputSchema - #js {:expand (-> (z/boolean) .optional (.describe "Provide additional detail on each page"))}}} - :getPage - {:fn api-get-page - :config #js {:title "Get Page" - :description "Get a page's content including its blocks. A property and a tag are pages." - :inputSchema #js {:pageName (-> (z/string) (.describe "The page's name or uuid"))}}} - :upsertNodes - {:fn api-upsert-nodes - :config - #js {:title "Upsert Nodes" - :description - "This tool must be called at most once per user request. Never re-call it unless explicitly asked. - It takes an object with field :operations, which is an array of operation objects. - Each operation creates or edits a page, block, tag or property. Each operation is a object - that must have :operation, :entityType and :data fields. More about fields in an operation object: - * :operation - Either :add or :edit - * :entityType - What type of node, e.g. :block, :page, :tag or :property - * :id - For :edit, this _must_ be a string uuid. For :add, use a temporary unique string if the new page is referenced by later operations e.g. add blocks - * :data - A map of fields to set or update. This map can have the following keys: - * :title - A page/tag/property's name or a block's content - * :page-id - A page string uuid of a block. Required when adding a block. - * :tags - A list of tags as string uuids - * :property-type - A property's type - * :property-cardinality - A property's cardinality. Must be :one or :many - * :property-classes - A property's list of allowed tags, each being a uuid string or a tag's name - * :class-extends - List of parent tags, each being a uuid string or a tag's name - * :class-properties - A tag's list of properties, each eing a uuid string or a property's name - - Example inputs with their prompt, description and data as clojure EDN: - - Description: This input adds a new block to page with id '119268a6-704f-4e9e-8c34-36dfc6133729' and update the title of a page with uuid '119268a6-704f-4e9e-8c34-36dfc6133729': - - {:operations - [{:operation :add - :entityType :block - :id nil - :data {:page-id \"119268a6-704f-4e9e-8c34-36dfc6133729\" - :title \"New block text\"}} - {:operation :edit - :entity :page - :id \"119268a6-704f-4e9e-8c34-36dfc6133729\" - :data {:title \"Revised page title\"}}]} - - Prompt: Add task 't1' to new page 'Inbox' - Description: This input creates a page 'Inbox' and adds a 't1' block with tag \"00000002-1282-1814-5700-000000000000\" (task) to it: - - {:operations - [{:operation :add - :entityType :page - :id \"temp-Inbox\" - :data {:title \"Inbox\"}} - {:operation :add - :entityType :block - :data {:page-id \"temp-Inbox\" - :title \"t1\" - :tags [\"00000002-1282-1814-5700-000000000000\"]}}]} - - Additional advice for building operations: - * Before creating any page, tag or property, check that it exists with getPage" - :inputSchema - #js {:operations - (z/array - (z/object - #js {:operation (z/enum #js ["add" "edit"]) - :entityType (z/enum #js ["block" "page" "tag" "property"]) - :id (.optional (z/union #js [(z/string) (z/number) (z/null)])) - :data (-> (z/object #js {}) (.passthrough))})) - :dry-run (-> (z/boolean) .optional (.describe "Pretend to do batch update. Does everything except actually commit change to db e.g. validation."))}}} - :searchBlocks - {:fn api-search-blocks - :config #js {:title "Search Blocks" - :description "Search graph for blocks containing search term" - :inputSchema #js {:searchTerm (z/string)}}} - :listTags - {:fn api-list-tags - :config #js {:title "List Tags" - :description "List all tags in a graph" - :inputSchema - #js {:expand (-> (z/boolean) .optional (.describe "Provide additional detail on each tag e.g. their parents (extends) and tag properties"))}}} - :listProperties - {:fn api-list-properties - :config #js {:title "List Properties" - :description "List all properties in a graph" - :inputSchema - #js {:expand (-> (z/boolean) .optional (.describe "Provide additional detail on each property e.g. property type, cardinality"))}}}}) - -(defn call-api-tool [tool-fn api-fn args] - (tool-fn (partial api-tool api-fn) args)) - -;; Server fns -;; ========== -(defn create-mcp-server [] - (McpServer. #js {:name "Logseq MCP Server" - :version "0.1.0"})) - -(defn create-mcp-api-server [api-fn] - (let [mcp-server (create-mcp-server)] - (doseq [[k v] api-tools] - (.registerTool mcp-server - (name k) - (:config v) - (partial call-api-tool (:fn v) api-fn))) - mcp-server)) diff --git a/src/electron/electron/server.cljs b/src/electron/electron/server.cljs index 2c880db..966dd07 100644 --- a/src/electron/electron/server.cljs +++ b/src/electron/electron/server.cljs @@ -9,7 +9,6 @@ [clojure.string :as string] [electron.configs :as cfgs] [electron.logger :as logger] - [electron.mcp-server :as desktop-mcp-server] [electron.utils :as utils] [electron.window :as window] [promesa.core :as p])) @@ -36,8 +35,7 @@ :host (get-host) :port (get-port) :tokens (normalize-tokens (cfgs/get-item :server/tokens)) - :autostart (cfgs/get-item :server/autostart) - :mcp-enabled? (cfgs/get-item :server/mcp-enabled?)})) + :autostart (cfgs/get-item :server/autostart)})) (defn- set-status! ([status] (set-status! status nil)) @@ -141,18 +139,6 @@ (p/catch (fn [^js e] (set-status! :running e)))))) -(defn- initialize-mcp-routes [^js server] - (let [api-fn (fn api-fn [meth args] - (if-let [meth' (resolve-real-api-method meth)] - (invoke-logseq-api! meth' args) - #js {:error (str "No method found for " (pr-str meth))}))] - (logger/debug "[server] MCP routes initialized") - (.post server "/mcp" - #(desktop-mcp-server/handle-post-request api-fn {:port (get-port) - :host (get-host)} %1 %2)) - (.get server "/mcp" desktop-mcp-server/handle-get-request) - (.delete server "/mcp" desktop-mcp-server/handle-delete-request))) - (defn start! [] (-> (p/let [_ (close!) @@ -175,8 +161,6 @@ (string/replace-first "${PORT}" PORT))] (doto rep (.type "text/html") (.send html)))))) - _ (when (:mcp-enabled? @*state) - (initialize-mcp-routes s)) ;; listen port _ (.listen s (bean/->js (select-keys @*state [:host :port])))] (reset! *server s) diff --git a/src/main/frontend/components/server.cljs b/src/main/frontend/components/server.cljs index bb9f1c1..e3ad3c9 100644 --- a/src/main/frontend/components/server.cljs +++ b/src/main/frontend/components/server.cljs @@ -144,7 +144,7 @@ #(js/clearTimeout t)))) []) - (let [{:keys [status error mcp-enabled?]} server-state + (let [{:keys [status error]} server-state status (keyword (util/safe-lower-case status)) running? (= :running status) href (and running? (str "http://" (:host server-state) ":" (:port server-state)))] @@ -192,14 +192,7 @@ (server-status-label status) [:span.flex.flex-col.gap-1.text-xs.font-mono [:a.hover:underline.flex.items-center {:href href} - href (shui/tabler-icon "external-link" {:size 12 :class "inline-block ml-1 pt-[1px]"})] - (when mcp-enabled? - [:a.hover:underline.flex.items-center - {:on-click (fn [] - (util/copy-to-clipboard! (str href "/mcp")) - (notification/show! (t :server/mcp-url-copied) :success))} - (str href "/mcp") - (shui/tabler-icon "copy" {:size 12 :class "inline-block ml-1 mt-[1px]"})])])]] + href (shui/tabler-icon "external-link" {:size 12 :class "inline-block ml-1 pt-[1px]"})]])]] (for [{:keys [hr? title options icon]} items] (cond hr? diff --git a/src/main/frontend/components/settings.cljs b/src/main/frontend/components/settings.cljs index 9e95316..b4a994d 100644 --- a/src/main/frontend/components/settings.cljs +++ b/src/main/frontend/components/settings.cljs @@ -1222,42 +1222,6 @@ [:<>]) -(hsx/defc mcp-server-row - [t] - (let [server-state (rfx/use-sub [:electron/server]) - [checked set-checked!] (hooks/use-state false)] - - (hooks/use-effect! - (fn [] - (let [initial (:mcp-enabled? server-state)] - (set-checked! initial))) - [server-state]) - - (let [on-toggle (fn [] - (let [new-val (not checked)] - (set-checked! new-val) - ;; Enable HTTP server to simplify starting MCP - (when (and new-val (not (storage/get ::storage-spec/http-server-enabled))) - (storage/set ::storage-spec/http-server-enabled true)) - (-> (ipc/ipc :server/set-config {:mcp-enabled? new-val}) - ;; Don't start server if it's not running - (p/then #(when (= "running" (state/get-state [:electron/server :status])) - (p/let [_ (p/delay 1000)] - (ipc/ipc :server/do :restart)))) - (p/catch #(notification/show! (str %) :error)))))] - (toggle "mcp-server" - (t :settings.ai/enable-mcp-server) - checked - on-toggle - [:span.text-sm.opacity-50 - (t :settings.ai/enable-mcp-server-desc)])))) - -(hsx/defc settings-ai - [] - [:div.panel-wrap - (when (util/electron?) - (mcp-server-row t))]) - (hsx/defc ^:large-vars/cleanup-todo settings [_active-tab] (let [current-repo (rfx/use-sub [:git/current-repo]) @@ -1270,11 +1234,8 @@ (fn [] (state/load-app-user-cfgs) (state/load-electron-app-base-info) - (let [active-tab (if (and (= _active-tab :ai) (not (util/electron?))) - :advanced - _active-tab)] - (when (keyword? active-tab) - (reset! *active [active-tab nil]))) + (when (keyword? _active-tab) + (reset! *active [_active-tab nil])) #(state/close-settings!)) []) @@ -1291,9 +1252,6 @@ [:general (t :settings/general) (ui/icon "adjustments")] [:editor (t :settings/editor) (ui/icon "writing")] [:keymap (t :settings/keymap) (ui/icon "keyboard")] - (when (util/electron?) - [:ai (t :settings/ai) (ui/icon "wand")]) - [:advanced (t :settings/advanced) (ui/icon "bulb")] [:features (t :settings/features) (ui/icon "app-feature")] @@ -1348,9 +1306,4 @@ :features (settings-features) - :ai - (if (util/electron?) - (settings-ai) - (settings-advanced)) - nil)]]])) diff --git a/src/main/frontend/worker/db_worker_node.cljs b/src/main/frontend/worker/db_worker_node.cljs index 936860f..d7a55e5 100644 --- a/src/main/frontend/worker/db_worker_node.cljs +++ b/src/main/frontend/worker/db_worker_node.cljs @@ -8,6 +8,7 @@ [frontend.worker.db-worker-node-auth :as auth] [frontend.worker.db-worker-node-lock :as db-lock] [frontend.worker.db-worker-node-ws :as db-ws] + [frontend.worker.mcp-server :as mcp-server] [frontend.worker.platform.node :as platform-node] [frontend.worker.semantic-routes :as semantic-routes] [frontend.worker.state :as worker-state] @@ -676,6 +677,15 @@ (string/starts-with? request-path "/v1/semantic/") (semantic-request-handler req res bound-repo proxy parsed-url request-path method) + (and (= request-path "/mcp") (= method "POST")) + (mcp-server/handle-post-request {:host host :port (effective-port port)} req res) + + (and (= request-path "/mcp") (= method "GET")) + (mcp-server/handle-get-request req res) + + (and (= request-path "/mcp") (= method "DELETE")) + (mcp-server/handle-delete-request req res) + (= request-path "/v1/invoke-batch") (if (= method "POST") (-> (p/let [body ( (z/number) .int .optional (.describe "Max items per page (default 50, max 200)")) + :cursor (-> (z/string) .optional (.describe "Opaque pagination cursor from a previous response's next-cursor"))}) + +(def ^:private time-filter-fields + {:created-after (-> (z/number) .int .optional (.describe "Unix epoch ms; only entities created strictly after this")) + :updated-after (-> (z/number) .int .optional (.describe "Unix epoch ms; only entities updated strictly after this"))}) + +(def ^:private listing-fields (merge pagination-fields time-filter-fields)) + +(def ^:private block-tree-schema + "Self-recursive, so it needs z/lazy -- the thunk closes over this same var, + resolved once def completes, the standard cljs recursive-def idiom." + (z/lazy (fn [] + (z/object #js {:title (z/string) + :children (-> (z/array block-tree-schema) .optional)})))) + +(def ^:private property-value-schema + (-> (z/union #js [(z/string) (z/number) (z/boolean) + (z/array (z/union #js [(z/string) (z/number)]))]) + (.describe (str "For ref properties, an existing entity's UUID, ident, or title. Status/Priority also " + "accept built-in aliases like TODO or HIGH. Class properties like Tags accept one " + "selector or an array of them. Scalar properties take their JSON value directly.")))) + +(def ^:private task-status-field + (-> (z/string) .optional + (.describe "todo|doing|in-review|done|canceled|backlog, or an existing Status choice's UUID/ident/title"))) + +(def ^:private task-priority-field + (-> (z/string) .optional + (.describe "low|medium|high|urgent, or an existing Priority choice's UUID/ident/title"))) + +(def ^:private property-type-field + (-> (z/enum #js ["default" "number" "date" "datetime" "checkbox" "url" "node" "asset" "map" "json" "string"]) + .optional)) + +(def ^:private property-cardinality-field + (-> (z/enum #js ["db.cardinality/one" "db.cardinality/many"]) .optional)) + +;; Per-operation extra fields (beyond the path params every operation gets +;; automatically -- see path-arg-fields below) +;; ============================================================================ +(def ^:private extra-fields + {"listPages" listing-fields + "createPage" {:title (z/string)} + "listPageBlocks" listing-fields + "listPageReferences" listing-fields + "updatePage" {:title (z/string)} + "updateBlock" {:title (z/string)} + "moveBlocks" {:block-ids (z/array (z/string)) + :target-id (z/string) + :position (z/enum #js ["before" "after" "first-child" "last-child"])} + "insertBlockChildren" {:position (z/enum #js ["append" "prepend"]) + :blocks (z/array block-tree-schema)} + "insertBlockTree" {:target-id (z/string) + :position (-> (z/enum #js ["append" "prepend"]) .optional) + :blocks (z/array block-tree-schema)} + "setBlockProperty" {:value property-value-schema} + "batchSetBlockProperty" {:isResetExistingValues (-> (z/boolean) .optional) + :entries (z/array (z/object #js {:block-id (z/string) + :property-id (z/string) + :value property-value-schema}))} + "batchDeleteBlockProperty" {:entries (z/array (z/object #js {:block-id (z/string) :property-id (z/string)}))} + "captureToToday" {:blocks (z/array block-tree-schema)} + "listTasks" (merge listing-fields + {:status task-status-field + :priority task-priority-field + :content (-> (z/string) .optional (.describe "Case-insensitive task title filter"))}) + "createTask" {:title (z/string) + :page-id (-> (z/string) .optional (.describe "Destination page UUID; omit for today's journal")) + :status task-status-field + :priority task-priority-field + :scheduled (-> (z/number) .int .optional (.describe "Scheduled date as YYYYMMDD")) + :deadline (-> (z/number) .int .optional (.describe "Deadline date as YYYYMMDD"))} + "listTags" listing-fields + "createTag" {:title (z/string)} + "listTagObjects" listing-fields + "updateTag" {:title (z/string)} + "listProperties" listing-fields + "createProperty" {:title (z/string) :type property-type-field :cardinality property-cardinality-field} + "updateProperty" {:title (-> (z/string) .optional) :type property-type-field :cardinality property-cardinality-field} + "listAssets" listing-fields + "createAsset" {:file-name (z/string) + :size (-> (z/number) .int (.describe "Exact decoded byte size of the file, max 100MB")) + :checksum (-> (z/string) (.regex (js/RegExp. "^[0-9a-fA-F]{64}$")) + (.describe "SHA-256 hex digest of the decoded file")) + :title (-> (z/string) .optional) + :page-id (-> (z/string) .optional (.describe "Destination page UUID; omit for today's journal")) + :content (-> (z/string) (.describe "Base64-encoded file contents"))} + "searchGraph" (merge listing-fields + {:q (z/string) + :types (-> (z/string) .optional + (.describe "Comma-separated subset of blocks,tags,properties,assets"))})}) + +;; Request construction +;; ==================== +(defn- path-param-names [path] + (mapv second (re-seq #":([^/]+)" path))) + +(defn- path-arg-fields [path] + (into {} + (map (fn [param] + [(keyword param) (-> (z/string) (.describe (str "The " param)))])) + (path-param-names path))) + +(defn- build-path [path args] + (reduce (fn [p param] + (string/replace p (str ":" param) (js/encodeURIComponent (str (get args (keyword param)))))) + path + (path-param-names path))) + +(defn- query-string [args] + (let [pairs (->> args + (remove (fn [[_ v]] (nil? v))) + (map (fn [[k v]] (str (js/encodeURIComponent (name k)) "=" (js/encodeURIComponent (str v))))))] + (if (seq pairs) (str "?" (string/join "&" pairs)) ""))) + +(defn- request-for + [{:keys [operation-id method path]} args] + (let [path-names (set (map keyword (path-param-names path))) + rest-args (apply dissoc args path-names) + url-path (build-path path args)] + (cond + (= operation-id "createAsset") + {:method method + :path (str url-path (query-string (assoc (dissoc rest-args :content) :encoding "base64"))) + :body (:content rest-args) + :content-type "text/plain"} + + (contains? #{"GET" "DELETE"} method) + {:method method :path (str url-path (query-string rest-args)) :body nil :content-type nil} + + :else + {:method method :path url-path :body (js/JSON.stringify (clj->js rest-args)) :content-type "application/json"}))) + +(defn- call-semantic-operation! + [{:keys [host port authorization]} operation args] + (let [{:keys [method path body content-type]} (request-for operation args) + url (str "http://" host ":" port path)] + (-> (js/fetch url (clj->js (cond-> {:method method + :headers (cond-> {} + authorization (assoc "Authorization" authorization) + content-type (assoc "Content-Type" content-type))} + body (assoc :body body)))) + (p/then (fn [^js res] + (p/let [text (.text res)] + {:status (.-status res) :ok (.-ok res) :text text})))))) + +(defn- mcp-result [{:keys [ok text]}] + #js {:content #js [#js {:type "text" :text (if (string/blank? text) "{}" text)}] + :isError (not ok)}) + +(defn- mcp-error-result [error] + #js {:content #js [#js {:type "text" :text (str "Unexpected MCP tool error: " (or (.-message error) error))}] + :isError true}) + +;; Server construction +;; =================== +(defn- register-operation-tool! + [^js mcp-server ctx operation] + (let [{:keys [operation-id]} operation + [title description] (get semantic-routes/operation-docs operation-id) + input-shape (clj->js (merge (path-arg-fields (:path operation)) (get extra-fields operation-id {})))] + (.registerTool mcp-server operation-id + #js {:title title + :description description + :inputSchema input-shape} + (fn [args] + (-> (call-semantic-operation! ctx operation (js->clj args :keywordize-keys true)) + (p/then mcp-result) + (p/catch mcp-error-result)))))) + +(defn create-mcp-api-server + [ctx] + (let [mcp-server (McpServer. #js {:name "Logseq Daemon Semantic API" :version "1.0.0"})] + (doseq [operation semantic-routes/operations] + (register-operation-tool! mcp-server ctx operation)) + mcp-server)) + +;; HTTP glue -- adapted from electron/mcp_server.cljs's Fastify-based version: +;; that one gets req/res already wrapped (`.raw` unwraps back to the +;; underlying Node primitives, `.body` is pre-parsed by Fastify's body +;; parser); the daemon calls this straight from its own raw +;; http.createServer callback, so req/res ARE already the Node primitives +;; StreamableHTTPServerTransport wants, and the body needs parsing here. +;; Auth is the daemon's existing bearer-token check on every route (see +;; db_worker_node.cljs's request-authorized?) -- already done by the time +;; this is reached, so unlike the CF-Worker/Electron versions there's no +;; separate auth check in here. +(defonce ^:private *transports (atom {})) + +(defn- (p/let [{:keys [host port stop!]} (start-daemon! {:root-dir data-dir :repo repo}) + _ (reset! daemon {:stop! stop!}) + token ( (with-mcp-client! + (fn [^js client] + (p/let [^js result (.listTools client)] + (let [tools (js->clj (.-tools result) :keywordize-keys true)] + (is (= (count semantic-routes/operations) (count tools))) + (is (some #(= "createPage" (:name %)) tools)) + (is (some #(= "searchGraph" (:name %)) tools)) + (is (not (some #(= "listGraphs" (:name %)) tools))))))) + (p/finally done)))) + +(deftest mcp-create-and-get-page-round-trip + (async done + (-> (with-mcp-client! + (fn [^js client] + (p/let [^js create-result (.callTool client #js {:name "createPage" + :arguments #js {:title "MCP Test Page"}}) + page (js->clj (js/JSON.parse (tool-text create-result)) :keywordize-keys true) + ^js get-result (.callTool client #js {:name "getPage" + :arguments #js {:page-id (:uuid page)}}) + got (js->clj (js/JSON.parse (tool-text get-result)) :keywordize-keys true)] + (is (not (.-isError create-result))) + (is (= "MCP Test Page" (:title page))) + (is (= "MCP Test Page" (:title got)))))) + (p/finally done)))) + +(deftest mcp-capture-block-tree-round-trip + (async done + (-> (with-mcp-client! + (fn [^js client] + (p/let [^js result (.callTool + client #js {:name "captureToToday" + :arguments #js {:blocks #js [#js {:title "MCP root" + :children #js [#js {:title "MCP child"}]}]}}) + body (js->clj (js/JSON.parse (tool-text result)) :keywordize-keys true) + root (first (:blocks body))] + (is (not (.-isError result))) + (is (= "MCP root" (:title root))) + (is (= "MCP child" (:title (first (:children root)))))))) + (p/finally done)))) + +(deftest mcp-unknown-page-surfaces-as-error-content + (async done + (-> (with-mcp-client! + (fn [^js client] + (p/let [^js result (.callTool client #js {:name "getPage" + :arguments #js {:page-id (str (random-uuid))}})] + (is (true? (.-isError result)))))) + (p/finally done))))