diff --git a/change/@itwin-map-layers-2eb91816-915b-4597-b434-ae4c0b597ea9.json b/change/@itwin-map-layers-2eb91816-915b-4597-b434-ae4c0b597ea9.json new file mode 100644 index 000000000..e07ea575e --- /dev/null +++ b/change/@itwin-map-layers-2eb91816-915b-4597-b434-ae4c0b597ea9.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Added MCP (Model Context Protocol) server exposing map-layer operations as MCP tools.", + "packageName": "@itwin/map-layers", + "email": "jean-luc.deziel@bentley.com", + "dependentChangeType": "patch" +} diff --git a/packages/itwin/map-layers/package.json b/packages/itwin/map-layers/package.json index 5825eab49..90fa62df2 100644 --- a/packages/itwin/map-layers/package.json +++ b/packages/itwin/map-layers/package.json @@ -5,6 +5,18 @@ "main": "lib/cjs/map-layers.js", "module": "lib/esm/map-layers.js", "typings": "lib/cjs/map-layers", + "exports": { + ".": { + "import": "./lib/esm/map-layers.js", + "require": "./lib/cjs/map-layers.js", + "types": "./lib/cjs/map-layers.d.ts" + }, + "./mcp": { + "import": "./lib/esm/mcp/index.js", + "require": "./lib/cjs/mcp/index.js", + "types": "./lib/cjs/mcp/index.d.ts" + } + }, "license": "MIT", "scripts": { "build": "npm run -s copy:locale && npm run -s build:cjs && npm run -s build:esm", @@ -39,7 +51,9 @@ "dependencies": { "@itwin/itwinui-icons-color-react": "^2.1.0", "@itwin/itwinui-icons-react": "^2.10.0", - "react-beautiful-dnd": "^13.1.1" + "@modelcontextprotocol/sdk": "^1.12.1", + "react-beautiful-dnd": "^13.1.1", + "zod": "~3.24.0" }, "devDependencies": { "@itwin/appui-abstract": "^5.0.0", diff --git a/packages/itwin/map-layers/pnpm-lock.yaml b/packages/itwin/map-layers/pnpm-lock.yaml index 260971c19..d9a9229c1 100644 --- a/packages/itwin/map-layers/pnpm-lock.yaml +++ b/packages/itwin/map-layers/pnpm-lock.yaml @@ -15,9 +15,15 @@ importers: '@itwin/itwinui-icons-react': specifier: ^2.10.0 version: 2.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@modelcontextprotocol/sdk': + specifier: ^1.12.1 + version: 1.12.1 react-beautiful-dnd: specifier: ^13.1.1 version: 13.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + zod: + specifier: ~3.24.0 + version: 3.24.0 devDependencies: '@itwin/appui-abstract': specifier: ^5.0.0 @@ -723,6 +729,10 @@ packages: '@microsoft/tsdoc@0.15.1': resolution: {integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==} + '@modelcontextprotocol/sdk@1.12.1': + resolution: {integrity: sha512-KG1CZhZfWg+u8pxeM/mByJDScJSrjjxLc8fwQqbsS8xCjBmQfMNEBTotYdNanKekepnfRI85GtgQlctLFpcYPw==} + engines: {node: '>=18'} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -764,36 +774,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.6': resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.6': resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.6': resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.6': resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.6': resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-win32-arm64@2.5.6': resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} @@ -864,66 +880,79 @@ packages: resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.57.1': resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.57.1': resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.57.1': resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.57.1': resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.57.1': resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.57.1': resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.57.1': resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.57.1': resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.57.1': resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.57.1': resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.57.1': resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.57.1': resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.57.1': resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} @@ -1220,6 +1249,10 @@ packages: '@yarnpkg/lockfile@1.1.0': resolution: {integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1377,6 +1410,10 @@ packages: bidi-js@1.0.3: resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} + brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -1396,6 +1433,10 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + 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'} @@ -1483,6 +1524,26 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + content-disposition@1.0.1: + resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + 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'} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + cpx2@3.0.2: resolution: {integrity: sha512-xVmdulZJVGSV+c8KkZ9IQY+RgyL9sGeVqScI2e7NtsEY9SVKcQXM4v0/9OLU0W0YtL9nmmqrtWjs5rpvgHn9Hg==} engines: {node: '>=6.5'} @@ -1563,6 +1624,15 @@ packages: supports-color: optional: true + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decamelize@4.0.0: resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} engines: {node: '>=10'} @@ -1585,6 +1655,10 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -1634,6 +1708,9 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + emoji-regex-xs@1.0.0: resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} @@ -1643,6 +1720,10 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -1698,6 +1779,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@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -1833,10 +1917,32 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventsource-parser@3.0.6: + resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + express-rate-limit@7.5.1: + resolution: {integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1870,6 +1976,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-index@0.1.1: resolution: {integrity: sha512-uJ5vWrfBKMcE6y2Z8834dwEZj9mNGxYa3t3I53OwFeuZ8D9oc2E5zcsrkuhX6h4iYrjhiv0T3szQmxlAV9uxDg==} @@ -1919,6 +2029,14 @@ packages: resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} engines: {node: '>= 6'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + fs-extra@10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} @@ -2079,6 +2197,10 @@ packages: html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -2096,6 +2218,10 @@ packages: i18next@21.10.0: resolution: {integrity: sha512-YeuIBmFsGjUfO3qBmMOc0rQaun4mIpGKET5WDwvu8lU7gvwpcariZLNtL0Fzj+zazcHUrlXHiptcFhBMFaxzfg==} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + ignore-styles@5.0.1: resolution: {integrity: sha512-gQQmIznCETPLEzfg1UH4Cs2oRq+HBPl8quroEUNXT8oybEG7/0lqI3dGgDSRry6B9HcCXw3PVkFFS0FF3CMddg==} @@ -2139,6 +2265,10 @@ packages: inversify@6.0.2: resolution: {integrity: sha512-i9m8j/7YIv4mDuYXUAcrpKPSaju/CIly9AHK5jvCBeoiM/2KEsuCQTTP+rzSWWpLYWRukdXFSl6ZTk2/uumbiA==} + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -2213,6 +2343,9 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + 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'} @@ -2433,9 +2566,17 @@ packages: mdurl@2.0.0: resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + memoize-one@5.2.1: resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -2463,10 +2604,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.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + minimatch@10.0.1: resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==} engines: {node: 20 || >=22} @@ -2518,6 +2667,10 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} @@ -2569,6 +2722,10 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + 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==} @@ -2617,6 +2774,10 @@ packages: parse5@8.0.0: resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2640,6 +2801,9 @@ packages: resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} engines: {node: 20 || >=22} + path-to-regexp@8.3.0: + resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -2661,6 +2825,10 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -2691,6 +2859,10 @@ packages: protocols@2.0.2: resolution: {integrity: sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==} + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} @@ -2702,6 +2874,10 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + qs@6.15.0: + resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} + engines: {node: '>=0.6'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -2714,6 +2890,14 @@ packages: randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + react-autosuggest@10.1.0: resolution: {integrity: sha512-/azBHmc6z/31s/lBf6irxPf/7eejQdR0IqnZUzjdSibtlS8+Rw/R79pgDAo6Ft5QqCUTyEQ+f0FhL+1olDQ8OA==} peerDependencies: @@ -2866,6 +3050,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-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -2887,6 +3075,9 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + sass-embedded-android-arm64@1.87.0: resolution: {integrity: sha512-uqeZoBuXm3W2KhxolScAAfWOLHL21e50g7AxlLmG0he7WZsWw6e9kSnmq301iLIFp4kvmXYXbXbNKAeu9ItRYA==} engines: {node: '>=14.0.0'} @@ -3044,9 +3235,17 @@ packages: engines: {node: '>=10'} hasBin: true + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -3059,6 +3258,9 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + shallow-equal@1.2.1: resolution: {integrity: sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==} @@ -3133,6 +3335,10 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} @@ -3248,6 +3454,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + tough-cookie@6.0.0: resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} engines: {node: '>=16'} @@ -3299,6 +3509,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -3381,6 +3595,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'} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -3400,6 +3618,10 @@ packages: varint@6.0.0: resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==} + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + vfile-message@4.0.2: resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} @@ -3600,6 +3822,14 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zod-to-json-schema@3.25.1: + resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} + peerDependencies: + zod: ^3.25 || ^4 + + zod@3.24.0: + resolution: {integrity: sha512-Hz+wiY8yD0VLA2k/+nsg2Abez674dDGTai33SwNvMPuf9uIrBC9eFgIMQxBBbHFxVXi8W+5nX9DcAh9YNSQm/w==} + zustand@4.5.6: resolution: {integrity: sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==} engines: {node: '>=12.7.0'} @@ -4193,6 +4423,22 @@ snapshots: '@microsoft/tsdoc@0.15.1': {} + '@modelcontextprotocol/sdk@1.12.1': + dependencies: + ajv: 6.12.6 + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + express: 5.2.1 + express-rate-limit: 7.5.1(express@5.2.1) + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 3.24.0 + zod-to-json-schema: 3.25.1(zod@3.24.0) + transitivePeerDependencies: + - supports-color + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -4685,6 +4931,11 @@ snapshots: '@yarnpkg/lockfile@1.1.0': {} + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + acorn-jsx@5.3.2(acorn@8.14.1): dependencies: acorn: 8.14.1 @@ -4857,6 +5108,20 @@ snapshots: dependencies: require-from-string: 2.0.2 + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.15.0 + raw-body: 3.0.2 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -4877,6 +5142,8 @@ snapshots: buffer-from@1.1.2: {} + bytes@3.1.2: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -4953,6 +5220,19 @@ snapshots: concat-map@0.0.1: {} + content-disposition@1.0.1: {} + + content-type@1.0.5: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + cpx2@3.0.2: dependencies: co: 4.6.0 @@ -5063,6 +5343,10 @@ snapshots: optionalDependencies: supports-color: 8.1.1 + debug@4.4.3: + dependencies: + ms: 2.1.3 + decamelize@4.0.0: {} decimal.js@10.6.0: {} @@ -5083,6 +5367,8 @@ snapshots: delayed-stream@1.0.0: {} + depd@2.0.0: {} + dequal@2.0.3: {} detect-libc@2.1.2: @@ -5127,12 +5413,16 @@ snapshots: eastasianwidth@0.2.0: {} + ee-first@1.1.1: {} + emoji-regex-xs@1.0.0: {} emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} + encodeurl@2.0.0: {} + entities@4.5.0: {} entities@6.0.0: {} @@ -5270,6 +5560,8 @@ snapshots: escalade@3.2.0: {} + escape-html@1.0.3: {} + escape-string-regexp@4.0.0: {} eslint-config-prettier@10.1.2(eslint@9.25.1): @@ -5473,8 +5765,53 @@ snapshots: esutils@2.0.3: {} + etag@1.8.1: {} + + eventsource-parser@3.0.6: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.0.6 + expect-type@1.3.0: {} + express-rate-limit@7.5.1(express@5.2.1): + dependencies: + express: 5.2.1 + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.0.1 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.0(supports-color@8.1.1) + 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.0 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -5505,6 +5842,17 @@ snapshots: dependencies: to-regex-range: 5.0.1 + finalhandler@2.1.1: + dependencies: + debug: 4.4.0(supports-color@8.1.1) + 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-index@0.1.1: {} find-up@5.0.0: @@ -5547,6 +5895,10 @@ snapshots: es-set-tostringtag: 2.1.0 mime-types: 2.1.35 + forwarded@0.2.0: {} + + fresh@2.0.0: {} + fs-extra@10.1.0: dependencies: graceful-fs: 4.2.11 @@ -5741,6 +6093,14 @@ snapshots: html-void-elements@3.0.0: {} + 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 + http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 @@ -5769,6 +6129,10 @@ snapshots: dependencies: '@babel/runtime': 7.27.1 + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + ignore-styles@5.0.1: {} ignore@5.3.2: {} @@ -5804,6 +6168,8 @@ snapshots: inversify@6.0.2: optional: true + ipaddr.js@1.9.1: {} + is-array-buffer@3.0.5: dependencies: call-bind: 1.0.8 @@ -5878,6 +6244,8 @@ snapshots: is-potential-custom-element-name@1.0.1: {} + is-promise@4.0.0: {} + is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -6131,8 +6499,12 @@ snapshots: mdurl@2.0.0: {} + media-typer@1.1.0: {} + memoize-one@5.2.1: {} + merge-descriptors@2.0.0: {} + merge2@1.4.1: {} micromark-util-character@2.1.1: @@ -6159,10 +6531,16 @@ snapshots: 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.2: + dependencies: + mime-db: 1.54.0 + minimatch@10.0.1: dependencies: brace-expansion: 2.0.1 @@ -6229,6 +6607,8 @@ snapshots: natural-compare@1.4.0: {} + negotiator@1.0.0: {} + node-addon-api@7.1.1: optional: true @@ -6282,6 +6662,10 @@ snapshots: obug@2.1.1: {} + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -6341,6 +6725,8 @@ snapshots: dependencies: entities: 6.0.0 + parseurl@1.3.3: {} + path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -6359,6 +6745,8 @@ snapshots: lru-cache: 11.1.0 minipass: 7.1.2 + path-to-regexp@8.3.0: {} + path-type@4.0.0: {} pathe@2.0.3: {} @@ -6371,6 +6759,8 @@ snapshots: picomatch@4.0.3: {} + pkce-challenge@5.0.1: {} + possible-typed-array-names@1.1.0: {} postcss@8.5.6: @@ -6399,12 +6789,21 @@ snapshots: protocols@2.0.2: {} + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + proxy-from-env@1.1.0: {} punycode.js@2.3.1: {} punycode@2.3.1: {} + qs@6.15.0: + dependencies: + side-channel: 1.1.0 + queue-microtask@1.2.3: {} raf-schd@4.0.3: {} @@ -6417,6 +6816,15 @@ snapshots: dependencies: safe-buffer: 5.2.1 + range-parser@1.2.1: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + react-autosuggest@10.1.0(react@18.3.1): dependencies: es6-promise: 4.2.8 @@ -6618,6 +7026,16 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.57.1 fsevents: 2.3.3 + router@2.2.0: + dependencies: + debug: 4.4.0(supports-color@8.1.1) + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.3.0 + transitivePeerDependencies: + - supports-color + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -6647,6 +7065,8 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 + safer-buffer@2.1.2: {} + sass-embedded-android-arm64@1.87.0: optional: true @@ -6772,10 +7192,35 @@ snapshots: semver@7.7.1: {} + 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.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + serialize-javascript@6.0.2: dependencies: randombytes: 2.1.0 + 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-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -6798,6 +7243,8 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.1 + setprototypeof@1.2.0: {} + shallow-equal@1.2.1: {} shebang-command@2.0.0: @@ -6877,6 +7324,8 @@ snapshots: stackback@0.0.2: {} + statuses@2.0.2: {} + std-env@3.10.0: {} string-argv@0.3.2: {} @@ -7009,6 +7458,8 @@ snapshots: dependencies: is-number: 7.0.0 + toidentifier@1.0.1: {} + tough-cookie@6.0.0: dependencies: tldts: 7.0.22 @@ -7060,6 +7511,12 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -7158,6 +7615,8 @@ snapshots: universalify@2.0.1: {} + unpipe@1.0.0: {} + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -7175,6 +7634,8 @@ snapshots: varint@6.0.0: optional: true + vary@1.1.2: {} + vfile-message@4.0.2: dependencies: '@types/unist': 3.0.3 @@ -7380,6 +7841,12 @@ snapshots: yocto-queue@0.1.0: {} + zod-to-json-schema@3.25.1(zod@3.24.0): + dependencies: + zod: 3.24.0 + + zod@3.24.0: {} + zustand@4.5.6(@types/react@18.3.20)(immer@10.1.1)(react@18.3.1): dependencies: use-sync-external-store: 1.5.0(react@18.3.1) diff --git a/packages/itwin/map-layers/src/mcp/index.ts b/packages/itwin/map-layers/src/mcp/index.ts new file mode 100644 index 000000000..bbb7478eb --- /dev/null +++ b/packages/itwin/map-layers/src/mcp/index.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ + +/** + * @itwin/map-layers/mcp — public API surface. + * + * Import from `@itwin/map-layers/mcp` to get the MCP tool functions, + * viewport accessor, and MCP server instance. + */ + +export { + openMapLayersWidget, + toggleBackgroundMap, + setBaseMapType, + setMapTransparency, + toggleTerrain, + getMapLayerInfo, + // attachMapLayer, // DISABLED — SSRF & credential-leakage risk (see server.ts) + detachMapLayer, + setMapLayerVisibility, +} from "./tools"; + +export type { MapLayerInfo } from "./tools"; + +export { setViewportAccessor } from "./viewport"; +export { server } from "./server"; diff --git a/packages/itwin/map-layers/src/mcp/server.ts b/packages/itwin/map-layers/src/mcp/server.ts new file mode 100644 index 000000000..0c286dd58 --- /dev/null +++ b/packages/itwin/map-layers/src/mcp/server.ts @@ -0,0 +1,218 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ + +declare const process: { env: Record }; + +/** + * Map Layers MCP Server + * + * Exposes iTwin.js map-layer operations as MCP tools. + * + * This module only **defines** the server and its tool registrations. + */ + +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { getViewportAccessor } from "./viewport"; +import { + // attachMapLayer, // DISABLED — see attach_map_layer tool comment below + detachMapLayer, + getMapLayerInfo, + openMapLayersWidget, + setBaseMapType, + setMapLayerVisibility, + setMapTransparency, + toggleBackgroundMap, + toggleTerrain, +} from "./tools"; + +// --------------------------------------------------------------------------- +// Server +// --------------------------------------------------------------------------- + +const SERVER_VERSION = + process.env.MAP_LAYERS_MCP_VERSION ?? + process.env.npm_package_version ?? + "0.0.0"; + +export const server = new McpServer({ + name: "map-layers-mcp", + version: SERVER_VERSION, +}); + +// ── 1. open_map_layers_widget ──────────────────────────────────────────────── + +server.tool( + "open_map_layers_widget", + "Opens the Map Layers widget panel in the iTwin.js frontstage UI so the user can manage map layers visually.", + {}, + async () => { + try { + const msg = openMapLayersWidget(); + return { content: [{ type: "text" as const, text: msg }] }; + } catch (e: any) { + return { content: [{ type: "text" as const, text: JSON.stringify({ action: "open_map_layers_widget", error: e.message }) }], isError: true }; + } + }, +); + +// ── 2. toggle_background_map ───────────────────────────────────────────────── + +server.tool( + "toggle_background_map", + "Toggles the background map on or off. If 'enabled' is provided, sets it to that state; otherwise toggles.", + { + enabled: z.boolean().optional().describe("If provided, force the background map to this state (true = on, false = off). Omit to toggle."), + }, + async ({ enabled }) => { + try { + const result = toggleBackgroundMap(getViewportAccessor(), enabled); + return { content: [{ type: "text" as const, text: JSON.stringify(result) }] }; + } catch (e: any) { + return { content: [{ type: "text" as const, text: JSON.stringify({ action: "toggle_background_map", error: e.message }) }], isError: true }; + } + }, +); + +// ── 3. set_base_map_type ───────────────────────────────────────────────────── + +server.tool( + "set_base_map_type", + "Changes the base map to one of the well-known types: aerial, hybrid, street, or a solid color fill.", + { + type: z.enum(["aerial", "hybrid", "street", "color"]).describe("The base map type to set."), + colorTbgr: z.number().optional().describe("TBGR color integer when type is 'color'. Ignored otherwise."), + }, + async ({ type, colorTbgr }) => { + try { + const result = setBaseMapType(getViewportAccessor(), type, colorTbgr); + return { content: [{ type: "text" as const, text: JSON.stringify(result) }] }; + } catch (e: any) { + return { content: [{ type: "text" as const, text: JSON.stringify({ action: "set_base_map_type", error: e.message }) }], isError: true }; + } + }, +); + +// ── 4. set_map_transparency ────────────────────────────────────────────────── + +server.tool( + "set_map_transparency", + "Sets the transparency of the background map. 0 = fully opaque, 1 = fully transparent.", + { + transparency: z.number().min(0).max(1).describe("Transparency value from 0.0 (opaque) to 1.0 (transparent)."), + }, + async ({ transparency }) => { + try { + const result = setMapTransparency(getViewportAccessor(), transparency); + return { content: [{ type: "text" as const, text: JSON.stringify(result) }] }; + } catch (e: any) { + return { content: [{ type: "text" as const, text: JSON.stringify({ action: "set_map_transparency", error: e.message }) }], isError: true }; + } + }, +); + +// ── 5. toggle_terrain ──────────────────────────────────────────────────────── + +server.tool( + "toggle_terrain", + "Toggles terrain display on or off. If 'enabled' is provided, sets it to that state; otherwise toggles.", + { + enabled: z.boolean().optional().describe("If provided, force terrain to this state (true = on, false = off). Omit to toggle."), + }, + async ({ enabled }) => { + try { + const result = toggleTerrain(getViewportAccessor(), enabled); + return { content: [{ type: "text" as const, text: JSON.stringify(result) }] }; + } catch (e: any) { + return { content: [{ type: "text" as const, text: JSON.stringify({ action: "toggle_terrain", error: e.message }) }], isError: true }; + } + }, +); + +// ── 6. get_map_layer_info ──────────────────────────────────────────────────── + +server.tool( + "get_map_layer_info", + "Returns detailed information about all attached map layers (background and overlay) and whether the background map is enabled.", + {}, + async () => { + try { + const result = getMapLayerInfo(getViewportAccessor()); + return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] }; + } catch (e: any) { + return { content: [{ type: "text" as const, text: JSON.stringify({ action: "get_map_layer_info", error: e.message }) }], isError: true }; + } + }, +); + +// ── 7. attach_map_layer ────────────────────────────────────────────────────── +// DISABLED: This tool accepts an arbitrary URL and plaintext credentials, which +// poses SSRF and credential-leakage risks when the MCP server is exposed remotely. +// Before re-enabling, add URL allowlisting/validation (block private IPs, file:// +// schemes, etc.), sanitize error messages so they never echo back the raw URL or +// credentials, and consider a credential-store reference instead of inline secrets. +// +// server.tool( +// "attach_map_layer", +// "Attaches a new map layer to the viewport by URL. Supports WMS, WMTS, ArcGIS, ArcGISFeature, and TileURL formats.", +// { +// url: z.string().describe("The map layer service URL."), +// name: z.string().describe("Display name for the layer."), +// formatId: z +// .enum(["WMS", "WMTS", "ArcGIS", "ArcGISFeature", "TileURL"]) +// .optional() +// .describe("The map service format. Defaults to 'WMS'."), +// isOverlay: z.boolean().optional().describe("If true, attach as overlay; otherwise as background layer. Default: false."), +// userName: z.string().optional().describe("Optional username for authenticated layers."), +// password: z.string().optional().describe("Optional password for authenticated layers."), +// }, +// async ({ url, name, formatId, isOverlay, userName, password }) => { +// try { +// const result = attachMapLayer(getViewportAccessor(), url, name, formatId, isOverlay, userName, password); +// return { content: [{ type: "text" as const, text: JSON.stringify(result) }] }; +// } catch (e: any) { +// return { content: [{ type: "text" as const, text: JSON.stringify({ action: "attach_map_layer", error: e.message }) }], isError: true }; +// } +// }, +// ); + +// ── 8. detach_map_layer ────────────────────────────────────────────────────── + +server.tool( + "detach_map_layer", + "Detaches (removes) a map layer by name. If multiple layers share the same name, all are removed.", + { + name: z.string().describe("The name of the map layer to detach."), + isOverlay: z.boolean().optional().describe("If specified, only detach from overlay or background layers. Omit to search both."), + }, + async ({ name, isOverlay }) => { + try { + const result = detachMapLayer(getViewportAccessor(), name, isOverlay); + return { content: [{ type: "text" as const, text: JSON.stringify(result) }] }; + } catch (e: any) { + return { content: [{ type: "text" as const, text: JSON.stringify({ action: "detach_map_layer", error: e.message }) }], isError: true }; + } + }, +); + +// ── 9. set_map_layer_visibility ────────────────────────────────────────────── + +server.tool( + "set_map_layer_visibility", + "Sets the visibility of a specific map layer identified by name.", + { + name: z.string().describe("The name of the map layer to update."), + visible: z.boolean().describe("Whether the layer should be visible."), + isOverlay: z.boolean().optional().describe("If specified, only search overlay or background layers. Omit to search both."), + }, + async ({ name, visible, isOverlay }) => { + try { + const result = setMapLayerVisibility(getViewportAccessor(), name, visible, isOverlay); + return { content: [{ type: "text" as const, text: JSON.stringify(result) }] }; + } catch (e: any) { + return { content: [{ type: "text" as const, text: JSON.stringify({ action: "set_map_layer_visibility", error: e.message }) }], isError: true }; + } + }, +); diff --git a/packages/itwin/map-layers/src/mcp/tools.ts b/packages/itwin/map-layers/src/mcp/tools.ts new file mode 100644 index 000000000..b27bb50ef --- /dev/null +++ b/packages/itwin/map-layers/src/mcp/tools.ts @@ -0,0 +1,310 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ + +/** + * MCP tool functions that operate on an iTwin.js ScreenViewport. + * + * These are pure functions designed to be called from the MCP server handlers + * or directly from an in-process consumer. Since this file lives inside the + * `@itwin/map-layers` package it has direct access to iTwin.js types. + */ + +import type { ScreenViewport } from "@itwin/core-frontend"; +import { + BackgroundMapProvider, + BackgroundMapType, + BaseMapLayerSettings, + ColorDef, +} from "@itwin/core-common"; +import { MapLayerSource } from "@itwin/core-frontend"; +import { UiFramework, WidgetState } from "@itwin/appui-react"; + +// ── Types ──────────────────────────────────────────────────────────────────── + +export interface MapLayerInfo { + name: string; + source: string; + visible: boolean; + transparency: number; + isOverlay: boolean; + layerIndex: number; + subLayers?: unknown[]; +} + +// ── Helpers ────────────────────────────────────────────────────────────────── + +function requireViewport(vp: ScreenViewport | undefined): ScreenViewport { + if (!vp) { + throw new Error("No active viewport available."); + } + return vp; +} + +function gatherLayers(vp: ScreenViewport, isOverlay: boolean): MapLayerInfo[] { + const layers = isOverlay + ? vp.displayStyle.settings.mapImagery.overlayLayers + : vp.displayStyle.settings.mapImagery.backgroundLayers; + + return layers.map((layer, idx) => ({ + name: layer.name, + source: (layer as any).source ?? (layer as any).modelId ?? "", + visible: layer.visible, + transparency: layer.transparency, + isOverlay, + layerIndex: idx, + subLayers: + (layer as any).subLayers?.map((s: any) => + typeof s.toJSON === "function" ? s.toJSON() : s, + ) ?? undefined, + })); +} + +// ── 1. open_map_layers_widget ──────────────────────────────────────────────── + +/** + * Opens (activates) the Map Layers widget in the frontstage. + */ +export function openMapLayersWidget(): string { + const widgetId = "map-layers:mapLayersWidget"; + UiFramework.frontstages.activeFrontstageDef + ?.findWidgetDef(widgetId) + ?.setWidgetState(WidgetState.Open); + return `Map Layers widget opened (id: ${widgetId}).`; +} + +// ── 2. toggle_background_map ───────────────────────────────────────────────── + +/** + * Toggles background map visibility on/off, or sets it to a specific state. + */ +export function toggleBackgroundMap( + vp: ScreenViewport | undefined, + enabled?: boolean, +): { backgroundMapEnabled: boolean } { + const viewport = requireViewport(vp); + const newState = enabled ?? !viewport.viewFlags.backgroundMap; + viewport.viewFlags = viewport.viewFlags.with("backgroundMap", newState); + return { backgroundMapEnabled: newState }; +} + +// ── 3. set_base_map_type ───────────────────────────────────────────────────── + +/** + * Sets the base map to one of the well-known Bing providers or a solid color. + */ +export function setBaseMapType( + vp: ScreenViewport | undefined, + type: "aerial" | "hybrid" | "street" | "color", + colorTbgr?: number, +): { baseMap: string } { + const viewport = requireViewport(vp); + + if (type === "color") { + const color = + colorTbgr !== undefined + ? ColorDef.fromJSON(colorTbgr) + : ColorDef.fromJSON(0); + viewport.displayStyle.backgroundMapBase = color; + return { baseMap: `color (TBGR: ${color.toJSON()})` }; + } + + const bgTypeMap: Record = { + aerial: BackgroundMapType.Aerial, + hybrid: BackgroundMapType.Hybrid, + street: BackgroundMapType.Street, + }; + const bgType = bgTypeMap[type] ?? BackgroundMapType.Hybrid; + const provider = BackgroundMapProvider.fromJSON({ + name: "BingProvider", + type: bgType, + }); + const settings = BaseMapLayerSettings.fromProvider(provider); + viewport.displayStyle.backgroundMapBase = settings; + return { baseMap: type }; +} + +// ── 4. set_map_transparency ────────────────────────────────────────────────── + +/** + * Sets the background map transparency (0 = fully opaque, 1 = fully transparent). + */ +export function setMapTransparency( + vp: ScreenViewport | undefined, + transparency: number, +): { transparency: number } { + const viewport = requireViewport(vp); + const clamped = Math.max(0, Math.min(1, transparency)); + viewport.changeBackgroundMapProps({ transparency: clamped }); + return { transparency: clamped }; +} + +// ── 5. toggle_terrain ──────────────────────────────────────────────────────── + +/** + * Toggles terrain display on/off, or sets it to a specific state. + */ +export function toggleTerrain( + vp: ScreenViewport | undefined, + enabled?: boolean, +): { terrainEnabled: boolean } { + const viewport = requireViewport(vp); + const currentlyEnabled = + (viewport.view as any)?.getDisplayStyle3d?.()?.settings?.backgroundMap + ?.applyTerrain ?? false; + const newState = enabled ?? !currentlyEnabled; + viewport.changeBackgroundMapProps({ applyTerrain: newState }); + return { terrainEnabled: newState }; +} + +// ── 6. get_map_layer_info ──────────────────────────────────────────────────── + +/** + * Returns information about all attached map layers (both background and overlay). + */ +export function getMapLayerInfo(vp: ScreenViewport | undefined): { + backgroundLayers: MapLayerInfo[]; + overlayLayers: MapLayerInfo[]; + backgroundMapEnabled: boolean; +} { + const viewport = requireViewport(vp); + const backgroundLayers = gatherLayers(viewport, false); + const overlayLayers = gatherLayers(viewport, true); + return { + backgroundLayers, + overlayLayers, + backgroundMapEnabled: viewport.viewFlags.backgroundMap, + }; +} + +// ── 7. attach_map_layer ────────────────────────────────────────────────────── + +/** + * Attaches a new map layer to the viewport by URL. + */ +export function attachMapLayer( + vp: ScreenViewport | undefined, + url: string, + name: string, + formatId?: string, + isOverlay?: boolean, + userName?: string, + password?: string, +): { attached: boolean; name: string; isOverlay: boolean } { + const viewport = requireViewport(vp); + const overlay = isOverlay ?? false; + + const source = MapLayerSource.fromJSON({ + url, + name, + formatId: formatId ?? "WMS", + }); + if (!source) { + throw new Error(`Failed to create map layer source from URL: ${url}`); + } + + if (userName) source.userName = userName; + if (password) source.password = password; + + const settings = source.toLayerSettings(); + if (!settings) { + throw new Error(`Failed to create layer settings for source: ${name}`); + } + + viewport.displayStyle.attachMapLayer({ + settings, + mapLayerIndex: { index: -1, isOverlay: overlay }, + }); + + return { attached: true, name, isOverlay: overlay }; +} + +// ── 8. detach_map_layer ────────────────────────────────────────────────────── + +/** + * Detaches a map layer by name (and optionally by overlay flag). + * If multiple layers match, all are detached. + */ +export function detachMapLayer( + vp: ScreenViewport | undefined, + name: string, + isOverlay?: boolean, +): { detached: string[] } { + const viewport = requireViewport(vp); + const detached: string[] = []; + + const tryDetach = (overlay: boolean) => { + const layers = overlay + ? viewport.displayStyle.settings.mapImagery.overlayLayers + : viewport.displayStyle.settings.mapImagery.backgroundLayers; + + // Iterate in reverse so index removal is safe + for (let i = layers.length - 1; i >= 0; i--) { + if (layers[i].name === name) { + viewport.displayStyle.detachMapLayerByIndex({ + index: i, + isOverlay: overlay, + }); + detached.push( + `${name} (${overlay ? "overlay" : "background"}, index ${i})`, + ); + } + } + }; + + if (isOverlay === undefined) { + tryDetach(false); + tryDetach(true); + } else { + tryDetach(isOverlay); + } + + if (detached.length === 0) { + throw new Error(`No map layer found with name "${name}".`); + } + return { detached }; +} + +// ── 9. set_map_layer_visibility ────────────────────────────────────────────── + +/** + * Sets the visibility of a specific map layer identified by name. + */ +export function setMapLayerVisibility( + vp: ScreenViewport | undefined, + name: string, + visible: boolean, + isOverlay?: boolean, +): { name: string; visible: boolean; updated: number } { + const viewport = requireViewport(vp); + let updated = 0; + + const trySetVisibility = (overlay: boolean) => { + const layers = overlay + ? viewport.displayStyle.settings.mapImagery.overlayLayers + : viewport.displayStyle.settings.mapImagery.backgroundLayers; + + for (let i = 0; i < layers.length; i++) { + if (layers[i].name === name) { + viewport.displayStyle.changeMapLayerProps( + { visible }, + { index: i, isOverlay: overlay }, + ); + updated++; + } + } + }; + + if (isOverlay === undefined) { + trySetVisibility(false); + trySetVisibility(true); + } else { + trySetVisibility(isOverlay); + } + + if (updated === 0) { + throw new Error(`No map layer found with name "${name}".`); + } + return { name, visible, updated }; +} diff --git a/packages/itwin/map-layers/src/mcp/viewport.ts b/packages/itwin/map-layers/src/mcp/viewport.ts new file mode 100644 index 000000000..180892ea6 --- /dev/null +++ b/packages/itwin/map-layers/src/mcp/viewport.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ + +/** + * Shared viewport accessor used by both the MCP server and tool functions. + * This module has no side effects and is safe to import from any context. + */ + +import type { ScreenViewport } from "@itwin/core-frontend"; + +type ViewportAccessor = () => ScreenViewport | undefined; +let _getViewport: ViewportAccessor = () => undefined; + +/** Set the viewport accessor so MCP tool functions can reach the live viewport. */ +export function setViewportAccessor(fn: ViewportAccessor): void { + _getViewport = fn; +} + +/** Get the current viewport via the registered accessor. */ +export function getViewportAccessor(): ScreenViewport | undefined { + return _getViewport(); +}