auvrynt 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (364) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +239 -0
  3. package/dist/blender-client.js +109 -0
  4. package/dist/blender-tools.js +930 -0
  5. package/dist/cli.js +370 -0
  6. package/dist/config.js +211 -0
  7. package/dist/connection-status.js +91 -0
  8. package/dist/db/client.js +22 -0
  9. package/dist/db/schema.js +29 -0
  10. package/dist/dotnet-tools.js +223 -0
  11. package/dist/git-worktrees.js +134 -0
  12. package/dist/git.js +41 -0
  13. package/dist/godot-animation-tilemap.js +82 -0
  14. package/dist/godot-csharp-build.js +93 -0
  15. package/dist/godot-csharp-project.js +157 -0
  16. package/dist/godot-csharp-runner.js +146 -0
  17. package/dist/godot-csharp-semantic.js +161 -0
  18. package/dist/godot-csharp-validate.js +71 -0
  19. package/dist/godot-dotnet-env.js +169 -0
  20. package/dist/godot-editor-bridge.js +140 -0
  21. package/dist/godot-gdscript.js +824 -0
  22. package/dist/godot-project-settings.js +223 -0
  23. package/dist/godot-runtime-testing.js +249 -0
  24. package/dist/godot-tools.js +183 -0
  25. package/dist/image-tools.js +157 -0
  26. package/dist/logger.js +76 -0
  27. package/dist/oauth-provider.js +266 -0
  28. package/dist/pi-tools.js +81 -0
  29. package/dist/processes.js +218 -0
  30. package/dist/review-checkpoints.js +142 -0
  31. package/dist/roots.js +53 -0
  32. package/dist/search-discovery.js +271 -0
  33. package/dist/serena-manager.js +443 -0
  34. package/dist/serena-tools.js +411 -0
  35. package/dist/server.js +2835 -0
  36. package/dist/skills.js +45 -0
  37. package/dist/ui/.vite/manifest.json +2520 -0
  38. package/dist/ui/assets/abap-CLvhMVsD.js +1 -0
  39. package/dist/ui/assets/actionscript-3--17pq3dv.js +1 -0
  40. package/dist/ui/assets/ada-C5qYipkI.js +1 -0
  41. package/dist/ui/assets/andromeeda-vGVdxbeo.js +1 -0
  42. package/dist/ui/assets/angular-html-BJqQFYI6.js +1 -0
  43. package/dist/ui/assets/angular-ts-CMJECO0Y.js +1 -0
  44. package/dist/ui/assets/apache-U0d_L8uA.js +1 -0
  45. package/dist/ui/assets/apex-CGTLDQj6.js +1 -0
  46. package/dist/ui/assets/apl-DWeXpBBr.js +1 -0
  47. package/dist/ui/assets/applescript-CCn79oCD.js +1 -0
  48. package/dist/ui/assets/ara-4CJ0cIlV.js +1 -0
  49. package/dist/ui/assets/asciidoc-DE70LPWp.js +1 -0
  50. package/dist/ui/assets/asm-Cmm7eHzH.js +1 -0
  51. package/dist/ui/assets/astro-CLbttLOq.js +1 -0
  52. package/dist/ui/assets/aurora-x-CDeNXAV0.js +1 -0
  53. package/dist/ui/assets/awk-BWXHIvNe.js +1 -0
  54. package/dist/ui/assets/ayu-dark-DluEY0Gj.js +1 -0
  55. package/dist/ui/assets/ayu-light-C3h-C4tm.js +1 -0
  56. package/dist/ui/assets/ayu-mirage-Bqwy1Gya.js +1 -0
  57. package/dist/ui/assets/ballerina-B7ZEbQpA.js +1 -0
  58. package/dist/ui/assets/bat-Bo4NYOV-.js +1 -0
  59. package/dist/ui/assets/beancount-D-usSTwE.js +1 -0
  60. package/dist/ui/assets/berry-DKpUyyne.js +1 -0
  61. package/dist/ui/assets/bibtex-Ci_nEsc7.js +1 -0
  62. package/dist/ui/assets/bicep-CUHmPFLl.js +1 -0
  63. package/dist/ui/assets/bird2-C2hNVINV.js +1 -0
  64. package/dist/ui/assets/blade-BOzY0P2V.js +1 -0
  65. package/dist/ui/assets/bsl-BkkzgIyY.js +1 -0
  66. package/dist/ui/assets/c-BG-8uJvh.js +1 -0
  67. package/dist/ui/assets/c3-CnJL0r0V.js +1 -0
  68. package/dist/ui/assets/cadence-CQ2zXKGN.js +1 -0
  69. package/dist/ui/assets/cairo-DLTphjLi.js +1 -0
  70. package/dist/ui/assets/catppuccin-frappe-3VR1Za6u.js +1 -0
  71. package/dist/ui/assets/catppuccin-latte-DwIHMF0Q.js +1 -0
  72. package/dist/ui/assets/catppuccin-macchiato-DYnBP6_5.js +1 -0
  73. package/dist/ui/assets/catppuccin-mocha-DYhrFGRu.js +1 -0
  74. package/dist/ui/assets/clarity-SemFz856.js +1 -0
  75. package/dist/ui/assets/clojure-DqKBuwfJ.js +1 -0
  76. package/dist/ui/assets/cmake-Bj61d0ZC.js +1 -0
  77. package/dist/ui/assets/cobol-I_17IuGe.js +1 -0
  78. package/dist/ui/assets/codeowners-C8r90Shi.js +1 -0
  79. package/dist/ui/assets/codeql-oeQT6MSM.js +1 -0
  80. package/dist/ui/assets/coffee-C0B8hmok.js +1 -0
  81. package/dist/ui/assets/common-lisp-Cv5bFMCO.js +1 -0
  82. package/dist/ui/assets/coq-BrsZFFmf.js +1 -0
  83. package/dist/ui/assets/cpp-DDJXt6Vs.js +1 -0
  84. package/dist/ui/assets/crystal-Bexqace6.js +1 -0
  85. package/dist/ui/assets/csharp-Ct8U2NOr.js +1 -0
  86. package/dist/ui/assets/css-DSQhw_9R.js +1 -0
  87. package/dist/ui/assets/csv-Dx-8-gkx.js +1 -0
  88. package/dist/ui/assets/cue-CE9AQfxI.js +1 -0
  89. package/dist/ui/assets/cypher-ClKdZ_lG.js +1 -0
  90. package/dist/ui/assets/d-qD-0Kul2.js +1 -0
  91. package/dist/ui/assets/dark-plus-Cs2F2srj.js +1 -0
  92. package/dist/ui/assets/dart-DkHntEIa.js +1 -0
  93. package/dist/ui/assets/dax-BkyTk9wS.js +1 -0
  94. package/dist/ui/assets/desktop-Dlh5hvp9.js +1 -0
  95. package/dist/ui/assets/diff-woXpYk--.js +1 -0
  96. package/dist/ui/assets/docker-IyjqRm3v.js +1 -0
  97. package/dist/ui/assets/dotenv-_5a1GRtc.js +1 -0
  98. package/dist/ui/assets/dracula-BHWKrbxM.js +1 -0
  99. package/dist/ui/assets/dracula-soft-5eyTD99u.js +1 -0
  100. package/dist/ui/assets/dream-maker-DW3nJb8Q.js +1 -0
  101. package/dist/ui/assets/edge-Z8YYkHOr.js +1 -0
  102. package/dist/ui/assets/elixir-CG9uKtAF.js +1 -0
  103. package/dist/ui/assets/elm-GfWG2IfT.js +1 -0
  104. package/dist/ui/assets/emacs-lisp-C9PiwqqW.js +1 -0
  105. package/dist/ui/assets/erb-xe89e73J.js +1 -0
  106. package/dist/ui/assets/erlang-Cphh6RMH.js +1 -0
  107. package/dist/ui/assets/everforest-dark-sB-x3p7T.js +1 -0
  108. package/dist/ui/assets/everforest-light-Df2xbC6M.js +1 -0
  109. package/dist/ui/assets/fennel-DQxkIbk2.js +1 -0
  110. package/dist/ui/assets/fish-BJitypiv.js +1 -0
  111. package/dist/ui/assets/fluent-C03EYrpw.js +1 -0
  112. package/dist/ui/assets/fortran-fixed-form-DEKoE2YW.js +1 -0
  113. package/dist/ui/assets/fortran-free-form-CYNrtFtB.js +1 -0
  114. package/dist/ui/assets/fsharp-D13ZGOAj.js +1 -0
  115. package/dist/ui/assets/gdresource-C0sCabJj.js +1 -0
  116. package/dist/ui/assets/gdscript-Cp2uCuqX.js +1 -0
  117. package/dist/ui/assets/gdshader-CBce3t8t.js +1 -0
  118. package/dist/ui/assets/genie-CV2tkWYe.js +1 -0
  119. package/dist/ui/assets/gherkin-DExj1W_8.js +1 -0
  120. package/dist/ui/assets/git-commit-BSykSTBG.js +1 -0
  121. package/dist/ui/assets/git-rebase-CoTL_uSE.js +1 -0
  122. package/dist/ui/assets/github-dark-C-LZuMrd.js +1 -0
  123. package/dist/ui/assets/github-dark-default-DXG-b-1a.js +1 -0
  124. package/dist/ui/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
  125. package/dist/ui/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
  126. package/dist/ui/assets/github-light-EUqPIrTm.js +1 -0
  127. package/dist/ui/assets/github-light-default-BXViO-2h.js +1 -0
  128. package/dist/ui/assets/github-light-high-contrast-B68TUdTA.js +1 -0
  129. package/dist/ui/assets/gleam-CSRkHgEL.js +1 -0
  130. package/dist/ui/assets/glimmer-js-DAn4UCSu.js +1 -0
  131. package/dist/ui/assets/glimmer-ts-Tjyi6Gh5.js +1 -0
  132. package/dist/ui/assets/glsl-CtTDZoqR.js +1 -0
  133. package/dist/ui/assets/gn-ilITqXS6.js +1 -0
  134. package/dist/ui/assets/gnuplot-7GGW24-e.js +1 -0
  135. package/dist/ui/assets/go-BJwz_mda.js +1 -0
  136. package/dist/ui/assets/graphql-D5gcC_Jk.js +1 -0
  137. package/dist/ui/assets/groovy-CacY0gHj.js +1 -0
  138. package/dist/ui/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
  139. package/dist/ui/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
  140. package/dist/ui/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
  141. package/dist/ui/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
  142. package/dist/ui/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
  143. package/dist/ui/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
  144. package/dist/ui/assets/hack-BJXY27dt.js +1 -0
  145. package/dist/ui/assets/haml-BLfFoZf7.js +1 -0
  146. package/dist/ui/assets/handlebars-Dw6CWkvN.js +1 -0
  147. package/dist/ui/assets/haskell-D8IpX4py.js +1 -0
  148. package/dist/ui/assets/haxe-OTjmBuCE.js +1 -0
  149. package/dist/ui/assets/hcl-Dh228itO.js +1 -0
  150. package/dist/ui/assets/heavy-payload-Dcg0-F-S.js +4 -0
  151. package/dist/ui/assets/hjson-CxZEssPk.js +1 -0
  152. package/dist/ui/assets/hlsl-Cvrh5tZx.js +1 -0
  153. package/dist/ui/assets/horizon-CE9ld1lL.js +1 -0
  154. package/dist/ui/assets/horizon-bright-Br1oVSNq.js +1 -0
  155. package/dist/ui/assets/houston-CsvMBhTu.js +1 -0
  156. package/dist/ui/assets/html-CAcAWvoN.js +1 -0
  157. package/dist/ui/assets/html-derivative-8aSW2E_7.js +1 -0
  158. package/dist/ui/assets/http-B2YR_3V1.js +1 -0
  159. package/dist/ui/assets/hurl-BnDJ9Zhf.js +1 -0
  160. package/dist/ui/assets/hxml-B0Qn7Nwc.js +1 -0
  161. package/dist/ui/assets/hy-CZbG8q4J.js +1 -0
  162. package/dist/ui/assets/imba-DsUTQ-LC.js +1 -0
  163. package/dist/ui/assets/ini-B5eOa1yu.js +1 -0
  164. package/dist/ui/assets/java-Doxi4W8u.js +1 -0
  165. package/dist/ui/assets/javascript-BbRdEqti.js +1 -0
  166. package/dist/ui/assets/jinja-CS3Tpv_Q.js +1 -0
  167. package/dist/ui/assets/jison-BkPGYVHR.js +1 -0
  168. package/dist/ui/assets/json-BMt1m3m8.js +1 -0
  169. package/dist/ui/assets/json5-BR5RXkoi.js +1 -0
  170. package/dist/ui/assets/jsonc-CYpm1nAK.js +1 -0
  171. package/dist/ui/assets/jsonl-CmCQp5Yx.js +1 -0
  172. package/dist/ui/assets/jsonnet-CJTPZ8u_.js +1 -0
  173. package/dist/ui/assets/jssm-DXw9l8Rf.js +1 -0
  174. package/dist/ui/assets/jsx-CQKrJXXY.js +1 -0
  175. package/dist/ui/assets/julia-BuN7i2du.js +1 -0
  176. package/dist/ui/assets/just-BswEPFPu.js +1 -0
  177. package/dist/ui/assets/kanagawa-dragon-CXtmUGW6.js +1 -0
  178. package/dist/ui/assets/kanagawa-lotus-BN08jTvb.js +1 -0
  179. package/dist/ui/assets/kanagawa-wave-CTweb8Dz.js +1 -0
  180. package/dist/ui/assets/kdl-CsD5j6eV.js +1 -0
  181. package/dist/ui/assets/kotlin-DhhofPvG.js +1 -0
  182. package/dist/ui/assets/kusto-BUv0MjJC.js +1 -0
  183. package/dist/ui/assets/laserwave-C_8bwKvT.js +1 -0
  184. package/dist/ui/assets/latex-DU2VyFfA.js +1 -0
  185. package/dist/ui/assets/lean-CewbzKMR.js +1 -0
  186. package/dist/ui/assets/less-DVTAwKKz.js +1 -0
  187. package/dist/ui/assets/light-plus-DVQuIRkW.js +1 -0
  188. package/dist/ui/assets/liquid-Cmz1L3dq.js +1 -0
  189. package/dist/ui/assets/llvm-Cm23YOpf.js +1 -0
  190. package/dist/ui/assets/log-BNLmms1o.js +1 -0
  191. package/dist/ui/assets/logo-Cluzi2Zq.js +1 -0
  192. package/dist/ui/assets/lua-CfVXhkaJ.js +1 -0
  193. package/dist/ui/assets/luau-CNKltnaQ.js +1 -0
  194. package/dist/ui/assets/make-Dixweg8N.js +1 -0
  195. package/dist/ui/assets/markdown-BYOwaDjH.js +1 -0
  196. package/dist/ui/assets/marko-C5bfqGiA.js +1 -0
  197. package/dist/ui/assets/material-theme-Bm3Qr25_.js +1 -0
  198. package/dist/ui/assets/material-theme-darker-2IIEA8gg.js +1 -0
  199. package/dist/ui/assets/material-theme-lighter-uhdI0v04.js +1 -0
  200. package/dist/ui/assets/material-theme-ocean-CHQ94UKr.js +1 -0
  201. package/dist/ui/assets/material-theme-palenight-B5W6OYN7.js +1 -0
  202. package/dist/ui/assets/matlab-D7qyCx1q.js +1 -0
  203. package/dist/ui/assets/mdc-CVBXYfyq.js +1 -0
  204. package/dist/ui/assets/mdx-DQZ5AkYe.js +1 -0
  205. package/dist/ui/assets/mermaid-Bk4SNUv9.js +1 -0
  206. package/dist/ui/assets/min-dark-BSWPekZh.js +1 -0
  207. package/dist/ui/assets/min-light-DDpmG2fV.js +1 -0
  208. package/dist/ui/assets/mipsasm-BMqwQI7S.js +1 -0
  209. package/dist/ui/assets/mojo-BgCJLMeH.js +1 -0
  210. package/dist/ui/assets/monokai-CdkpiU2Y.js +1 -0
  211. package/dist/ui/assets/moonbit-CaWjb8XO.js +1 -0
  212. package/dist/ui/assets/move-B1IS1UjX.js +1 -0
  213. package/dist/ui/assets/narrat-_X_XdTYD.js +1 -0
  214. package/dist/ui/assets/nextflow-Bbiyy34d.js +1 -0
  215. package/dist/ui/assets/nextflow-groovy-Dc_ddanL.js +1 -0
  216. package/dist/ui/assets/nginx-CzJafxLa.js +1 -0
  217. package/dist/ui/assets/night-owl-DhmEMT88.js +1 -0
  218. package/dist/ui/assets/night-owl-light-eJ-hLW7d.js +1 -0
  219. package/dist/ui/assets/nim-DABKtHtM.js +1 -0
  220. package/dist/ui/assets/nix-IvuFDN5E.js +1 -0
  221. package/dist/ui/assets/nord-Cb4Vim4T.js +1 -0
  222. package/dist/ui/assets/nushell-DcLAeLz5.js +1 -0
  223. package/dist/ui/assets/objective-c-D1A_Heim.js +1 -0
  224. package/dist/ui/assets/objective-cpp-BsSzOQcm.js +1 -0
  225. package/dist/ui/assets/ocaml-O90oeIOV.js +1 -0
  226. package/dist/ui/assets/odin-B1RWQWA5.js +1 -0
  227. package/dist/ui/assets/one-dark-pro-CLwyXe_n.js +1 -0
  228. package/dist/ui/assets/one-light-D7Lr4KcI.js +1 -0
  229. package/dist/ui/assets/openscad-BUDT5pXO.js +1 -0
  230. package/dist/ui/assets/pascal-4ZHwLPI5.js +1 -0
  231. package/dist/ui/assets/perl-CBh7L3lZ.js +1 -0
  232. package/dist/ui/assets/php-DFbI9q5p.js +1 -0
  233. package/dist/ui/assets/pierre-dark-sU4Zdns8.js +1 -0
  234. package/dist/ui/assets/pierre-dark-soft-HXgun5vs.js +1 -0
  235. package/dist/ui/assets/pierre-light-DWBk51d8.js +1 -0
  236. package/dist/ui/assets/pierre-light-soft-CodEzPxf.js +1 -0
  237. package/dist/ui/assets/pkl-ot-7Btpt.js +1 -0
  238. package/dist/ui/assets/plastic-DQwYfKfQ.js +1 -0
  239. package/dist/ui/assets/plsql-DGHpHOYJ.js +1 -0
  240. package/dist/ui/assets/po-BiJDBrnU.js +1 -0
  241. package/dist/ui/assets/poimandres-DRFjx7u4.js +1 -0
  242. package/dist/ui/assets/polar-C7UOKdEL.js +1 -0
  243. package/dist/ui/assets/postcss-BXeXVLqQ.js +1 -0
  244. package/dist/ui/assets/powerquery-DNMTfnFr.js +1 -0
  245. package/dist/ui/assets/powershell-DshXNtvi.js +1 -0
  246. package/dist/ui/assets/prisma-BsRQq5mF.js +1 -0
  247. package/dist/ui/assets/prolog-iXnhIJG7.js +1 -0
  248. package/dist/ui/assets/proto-DB4EqR-F.js +1 -0
  249. package/dist/ui/assets/pug-C_PxC7gO.js +1 -0
  250. package/dist/ui/assets/puppet-CDv2pdJW.js +1 -0
  251. package/dist/ui/assets/purescript-9MfHhQsQ.js +1 -0
  252. package/dist/ui/assets/python-gzcpVVnB.js +1 -0
  253. package/dist/ui/assets/qml-DtngoFFK.js +1 -0
  254. package/dist/ui/assets/qmldir-DCQb3MpD.js +1 -0
  255. package/dist/ui/assets/qss-Fe1Jh2GI.js +1 -0
  256. package/dist/ui/assets/r-DohkcevW.js +1 -0
  257. package/dist/ui/assets/racket-DcIDlBhZ.js +1 -0
  258. package/dist/ui/assets/raku-B3gFvitq.js +1 -0
  259. package/dist/ui/assets/razor-C4t8J0_E.js +1 -0
  260. package/dist/ui/assets/red-CJ3rzSJv.js +1 -0
  261. package/dist/ui/assets/reg-CRGYupPL.js +1 -0
  262. package/dist/ui/assets/regexp-OGbTbjgY.js +1 -0
  263. package/dist/ui/assets/rel-BtDbiS_P.js +1 -0
  264. package/dist/ui/assets/review-payload-C5Qz60Kq.js +1 -0
  265. package/dist/ui/assets/riscv-Ckw8ddFX.js +1 -0
  266. package/dist/ui/assets/ron-VUp2lXgN.js +1 -0
  267. package/dist/ui/assets/rose-pine-BthvhNj6.js +1 -0
  268. package/dist/ui/assets/rose-pine-dawn-Dg85fqjY.js +1 -0
  269. package/dist/ui/assets/rose-pine-moon-hon4tzzS.js +1 -0
  270. package/dist/ui/assets/rosmsg-CAekHB0j.js +1 -0
  271. package/dist/ui/assets/rst-CfBbbcvC.js +1 -0
  272. package/dist/ui/assets/ruby-Bw0mpxHY.js +1 -0
  273. package/dist/ui/assets/rust-Cfkwpbl8.js +1 -0
  274. package/dist/ui/assets/sas-Cs3s9JOf.js +1 -0
  275. package/dist/ui/assets/sass-DXrisJhu.js +1 -0
  276. package/dist/ui/assets/scala-DKOlJaKm.js +1 -0
  277. package/dist/ui/assets/scheme-DQCgrYNe.js +1 -0
  278. package/dist/ui/assets/scss-sRxNJS54.js +1 -0
  279. package/dist/ui/assets/sdbl-bTVj8UrX.js +1 -0
  280. package/dist/ui/assets/shaderlab-TOUzSsQk.js +1 -0
  281. package/dist/ui/assets/shellscript-B0X6wh0R.js +1 -0
  282. package/dist/ui/assets/shellsession-D2FdOioR.js +1 -0
  283. package/dist/ui/assets/slack-dark-DnToyrRv.js +1 -0
  284. package/dist/ui/assets/slack-ochin-B2OO5cIa.js +1 -0
  285. package/dist/ui/assets/smalltalk-B16xEiuN.js +1 -0
  286. package/dist/ui/assets/snazzy-light-4G7pJPwS.js +1 -0
  287. package/dist/ui/assets/solarized-dark-DV17i1UV.js +1 -0
  288. package/dist/ui/assets/solarized-light-DSh2HLQt.js +1 -0
  289. package/dist/ui/assets/solidity-CKzVLygQ.js +1 -0
  290. package/dist/ui/assets/soy-s6g9OjJ1.js +1 -0
  291. package/dist/ui/assets/sparql-D_iOobhT.js +1 -0
  292. package/dist/ui/assets/splunk-BC2Px7Mm.js +1 -0
  293. package/dist/ui/assets/sql-DVTnh5V-.js +1 -0
  294. package/dist/ui/assets/ssh-config-BgfXC-Er.js +1 -0
  295. package/dist/ui/assets/stata-DFIDiXF9.js +1 -0
  296. package/dist/ui/assets/stylus-B6D30XZt.js +1 -0
  297. package/dist/ui/assets/surrealql-DVfrsLyg.js +1 -0
  298. package/dist/ui/assets/svelte-D5GsxESq.js +1 -0
  299. package/dist/ui/assets/swift-DonLKvLd.js +1 -0
  300. package/dist/ui/assets/synthwave-84-nFMaYfgc.js +1 -0
  301. package/dist/ui/assets/system-verilog-DJ5XKQeo.js +1 -0
  302. package/dist/ui/assets/systemd-BxMlprV5.js +1 -0
  303. package/dist/ui/assets/talonscript-CohzipZa.js +1 -0
  304. package/dist/ui/assets/tasl-DMoTqEGO.js +1 -0
  305. package/dist/ui/assets/tcl-CZd0xW_V.js +1 -0
  306. package/dist/ui/assets/templ-DI-Zyqzd.js +1 -0
  307. package/dist/ui/assets/terraform-DswuEJGm.js +1 -0
  308. package/dist/ui/assets/tex-WHHmlbJI.js +1 -0
  309. package/dist/ui/assets/tokyo-night-oM2G3aXe.js +1 -0
  310. package/dist/ui/assets/toml-CcmNWLt0.js +1 -0
  311. package/dist/ui/assets/ts-tags-Dwpo2RFJ.js +1 -0
  312. package/dist/ui/assets/tsv-sltzmVWM.js +1 -0
  313. package/dist/ui/assets/tsx-ph_idaAW.js +1 -0
  314. package/dist/ui/assets/turtle-ByJddavk.js +1 -0
  315. package/dist/ui/assets/twig-YO17jFjR.js +1 -0
  316. package/dist/ui/assets/typescript-CbAxjhkl.js +1 -0
  317. package/dist/ui/assets/typespec-B88KGewJ.js +1 -0
  318. package/dist/ui/assets/typst-DI99ib-x.js +1 -0
  319. package/dist/ui/assets/useFileDiffInstance-B2DLX3Vg.js +244 -0
  320. package/dist/ui/assets/v-DETTlOr0.js +1 -0
  321. package/dist/ui/assets/vala-zf12oZj6.js +1 -0
  322. package/dist/ui/assets/vb-Djn5o6TS.js +1 -0
  323. package/dist/ui/assets/verilog-CiiDBU1e.js +1 -0
  324. package/dist/ui/assets/vesper-D5bVUKB1.js +1 -0
  325. package/dist/ui/assets/vhdl-BroJfC0k.js +1 -0
  326. package/dist/ui/assets/viml-DvXPmvsu.js +1 -0
  327. package/dist/ui/assets/vitesse-black-fwtXNY1n.js +1 -0
  328. package/dist/ui/assets/vitesse-dark-BZCL-v6S.js +1 -0
  329. package/dist/ui/assets/vitesse-light-VbXTXTou.js +1 -0
  330. package/dist/ui/assets/vue-CraHL-Ps.js +1 -0
  331. package/dist/ui/assets/vue-html-e9tyZNtS.js +1 -0
  332. package/dist/ui/assets/vue-vine-CofmxaUb.js +1 -0
  333. package/dist/ui/assets/vyper-CgoNMtux.js +1 -0
  334. package/dist/ui/assets/wasm-BnjxR4X6.js +1 -0
  335. package/dist/ui/assets/wasm-ByWQv1Qj.js +1 -0
  336. package/dist/ui/assets/wenyan-C8pVoKbM.js +1 -0
  337. package/dist/ui/assets/wgsl-BsKzXJz4.js +1 -0
  338. package/dist/ui/assets/wikitext-ClFFjSW2.js +1 -0
  339. package/dist/ui/assets/wit-DdvCle-K.js +1 -0
  340. package/dist/ui/assets/wolfram-DLL8P-h_.js +1 -0
  341. package/dist/ui/assets/workspace-app-8--oTbCd.css +1 -0
  342. package/dist/ui/assets/workspace-app-C5QsE314.js +118 -0
  343. package/dist/ui/assets/xml-BWSdPZHm.js +1 -0
  344. package/dist/ui/assets/xsl-BtCt7Hx8.js +1 -0
  345. package/dist/ui/assets/yaml-BZiTw7cQ.js +1 -0
  346. package/dist/ui/assets/zenscript-BnlCZFoB.js +1 -0
  347. package/dist/ui/assets/zig-CMLA9XwU.js +1 -0
  348. package/dist/ui/workspace-app.html +15 -0
  349. package/dist/user-config.js +57 -0
  350. package/dist/view-image.js +104 -0
  351. package/dist/web-tools.js +200 -0
  352. package/dist/window-capture.js +74 -0
  353. package/dist/workspace-store.js +125 -0
  354. package/dist/workspaces.js +226 -0
  355. package/docs/DEVELOPMENT_TOOLS.md +95 -0
  356. package/docs/assets/auvrynt-logo-light.png +0 -0
  357. package/docs/assets/auvrynt-screenshot.png +0 -0
  358. package/docs/chatgpt-coding-workflow.md +143 -0
  359. package/docs/configuration.md +132 -0
  360. package/docs/gotchas.md +215 -0
  361. package/docs/security.md +100 -0
  362. package/docs/setup.md +132 -0
  363. package/package.json +70 -0
  364. package/scripts/dev-server.mjs +120 -0
package/dist/server.js ADDED
@@ -0,0 +1,2835 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { readFileSync } from "node:fs";
3
+ import { access, realpath } from "node:fs/promises";
4
+ import { fileURLToPath } from "node:url";
5
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
7
+ import { mcpAuthRouter, getOAuthProtectedResourceMetadataUrl } from "@modelcontextprotocol/sdk/server/auth/router.js";
8
+ import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
9
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
10
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
11
+ import { checkResourceAllowed, resourceUrlFromServerUrl } from "@modelcontextprotocol/sdk/shared/auth-utils.js";
12
+ import { registerAppResource, registerAppTool, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server";
13
+ import express from "express";
14
+ import * as z from "zod/v4";
15
+ import { loadConfig } from "./config.js";
16
+ import { logEvent, requestIp, requestPath, commandPreview, sessionIdPrefix, } from "./logger.js";
17
+ import { editFileTool, findFilesTool, grepFilesTool, listDirectoryTool, readFileTool, runShellTool, writeFileTool, } from "./pi-tools.js";
18
+ import { SingleUserOAuthProvider } from "./oauth-provider.js";
19
+ import { createReviewCheckpointManager } from "./review-checkpoints.js";
20
+ import { formatPathForPrompt } from "./skills.js";
21
+ import { createWorkspaceStore } from "./workspace-store.js";
22
+ import { formatAgentsPath, WorkspaceRegistry } from "./workspaces.js";
23
+ import { executeViewImage } from "./view-image.js";
24
+ import { ProcessManager } from "./processes.js";
25
+ import { getConnectionStatus } from "./connection-status.js";
26
+ import { globFiles, searchText, inspectProject } from "./search-discovery.js";
27
+ import { capturePageScreenshot, inspectPage, startDevServer } from "./web-tools.js";
28
+ import { inspectImage, compareImages, inspectSprite, splitSpriteSheet } from "./image-tools.js";
29
+ import { inspectDotnetProject, dotnetRestore, dotnetBuild, dotnetTest, dotnetRun, dotnetFormat, } from "./dotnet-tools.js";
30
+ import { detectGodotProject, godotRun, inspectGodotScene, } from "./godot-tools.js";
31
+ import { captureWindow } from "./window-capture.js";
32
+ import { inspectGodotDotnetEnvironment } from "./godot-dotnet-env.js";
33
+ import { inspectGodotDotnetProject, godotBuildSolutions, godotDotnetRestore } from "./godot-csharp-project.js";
34
+ import { godotDotnetBuild, godotDotnetClean } from "./godot-csharp-build.js";
35
+ import { godotRunProject, godotRunScene, getGodotRuntimeLogs } from "./godot-csharp-runner.js";
36
+ import { godotValidateProject, godotImportAssets } from "./godot-csharp-validate.js";
37
+ import { godotEditorConnect, godotEditorStatus, godotEditorDisconnect, getBridgeClient } from "./godot-editor-bridge.js";
38
+ import { findCsharpClasses, getCsharpDiagnostics, getExportedProperties, generateCsharpScript } from "./godot-csharp-semantic.js";
39
+ import { getProjectSettings, getInputMap, getAutoloads, applyPixelArtImportPreset, generateVscodeConfig, listExportPresets, } from "./godot-project-settings.js";
40
+ import { getRemoteSceneTree, getRuntimeProperty, getPerformanceMonitors, parseCsharpExceptions, pressAction as godotPressAction, releaseAction as godotReleaseAction, mouseClick as godotMouseClick, assertNodeExists as godotAssertNodeExists, assertProperty as godotAssertProperty, runTestSequence as godotRunTestSequence, exportGodotProject, } from "./godot-runtime-testing.js";
41
+ import { inspectGodotGdscriptEnvironment, getGdscriptDiagnostics, inspectGdscript, createGdscript, attachGdscript, detachGdscript, createGdscriptSignal, createGdscriptSignalHandler, getGlobalClasses, addClassName, removeClassName, addGdscriptAutoload, getAutoloadUsage, inspectToolScript, createEditorPlugin, getGdscriptDependencies, findCyclicScriptDependencies, getGdscriptNodeReferences, getGdscriptLifecycleMethods, inspectGdscriptAwaitUsage, analyzeGdscriptTyping, formatGdscript, detectGdscriptTests, runGdscriptTests, reloadGdscript, setBreakpoint as gdscriptSetBreakpoint, removeBreakpoint as gdscriptRemoveBreakpoint, lspFindSymbol as gdscriptLspFindSymbol, lspGetDefinition as gdscriptLspGetDefinition, } from "./godot-gdscript.js";
42
+ import { blenderPing, blenderGetSceneInfo, blenderGetSceneAudit, blenderGetSelection, blenderGetActiveModeAndStatus, blenderGetConsoleErrors, blenderInspectObject, blenderCreateCube, blenderSelectObject, blenderSelectObjects, blenderTransformObject, blenderDuplicateLinked, blenderJoinObjects, blenderDeleteObjects, blenderCreateCollection, blenderMoveToCollection, blenderSetViewportView, blenderOrbitViewport, blenderPanViewport, blenderZoomViewport, blenderFrameSelected, blenderSetViewportShading, blenderInspectMaterial, blenderInspectGeometryNodes, blenderEditModifier, blenderSetRenderSettings, blenderRenderCamera, blenderRenderObjectIsolation, blenderRenderViewport, blenderSaveCheckpoint, blenderListCheckpoints, blenderRollbackCheckpoint, blenderExecutePython, blenderGetCurrentFile, blenderOpenFile, blenderSaveFile, blenderSaveFileAs, blenderExportGlb, } from "./blender-tools.js";
43
+ import { SerenaManager } from "./serena-manager.js";
44
+ import { registerSerenaTools } from "./serena-tools.js";
45
+ const WORKSPACE_APP_URI = "ui://auvrynt/workspace-app.html";
46
+ const WORKSPACE_APP_MANIFEST_ENTRY = "workspace-app.html";
47
+ const VIEW_IMAGE_TOOL_ANNOTATIONS = {
48
+ readOnlyHint: true,
49
+ destructiveHint: false,
50
+ idempotentHint: true,
51
+ openWorldHint: false,
52
+ };
53
+ const READ_ONLY_ANNOTATIONS = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
54
+ const PROCESS_ANNOTATIONS = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
55
+ const MUTATING_ANNOTATIONS = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
56
+ const WEB_READ_ANNOTATIONS = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true };
57
+ const WEB_WRITE_ANNOTATIONS = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true };
58
+ const WRITE_TOOL_ANNOTATIONS = {
59
+ readOnlyHint: false,
60
+ destructiveHint: true,
61
+ idempotentHint: false,
62
+ openWorldHint: false,
63
+ };
64
+ const EDIT_TOOL_ANNOTATIONS = {
65
+ readOnlyHint: false,
66
+ destructiveHint: true,
67
+ idempotentHint: false,
68
+ openWorldHint: false,
69
+ };
70
+ const SHELL_TOOL_ANNOTATIONS = {
71
+ readOnlyHint: false,
72
+ destructiveHint: true,
73
+ idempotentHint: false,
74
+ openWorldHint: true,
75
+ };
76
+ function shouldAttachWidget(mode, kind) {
77
+ switch (mode) {
78
+ case "off":
79
+ return false;
80
+ case "changes":
81
+ return kind === "workspace" || kind === "show_changes";
82
+ case "full":
83
+ return true;
84
+ }
85
+ }
86
+ function toolWidgetDescriptorMeta(config, kind) {
87
+ if (!shouldAttachWidget(config.widgets, kind))
88
+ return { _meta: {} };
89
+ return {
90
+ _meta: {
91
+ ui: {
92
+ resourceUri: WORKSPACE_APP_URI,
93
+ visibility: ["model"],
94
+ },
95
+ },
96
+ };
97
+ }
98
+ function toolNamesFor(config) {
99
+ return config.toolNaming === "short"
100
+ ? {
101
+ openWorkspace: "open_workspace",
102
+ read: "read",
103
+ write: "write",
104
+ edit: "edit",
105
+ grep: "grep",
106
+ glob: "glob",
107
+ ls: "ls",
108
+ shell: "bash",
109
+ }
110
+ : {
111
+ openWorkspace: "open_workspace",
112
+ read: "read_file",
113
+ write: "write_file",
114
+ edit: "edit_file",
115
+ grep: "grep_files",
116
+ glob: "find_files",
117
+ ls: "list_directory",
118
+ shell: "run_shell",
119
+ };
120
+ }
121
+ function serverInstructions(config, toolNames) {
122
+ const inspection = config.minimalTools
123
+ ? `In minimal tool mode, ${toolNames.grep}, ${toolNames.glob}, and ${toolNames.ls} are disabled; use ${toolNames.shell} with command-line tools such as grep, rg, find, ls, and tree for search and directory inspection. `
124
+ : `Prefer ${toolNames.read}, ${toolNames.grep}, ${toolNames.glob}, and ${toolNames.ls} for file inspection. `;
125
+ const skills = config.skillsEnabled
126
+ ? `When ${toolNames.openWorkspace} returns available skills and a task matches a skill, use ${toolNames.read} to read that skill's path before proceeding. Skill paths may be outside the workspace, but ${toolNames.read} only permits advertised SKILL.md files and files under already-loaded skill directories. `
127
+ : "";
128
+ const agentsMd = `Follow instructions returned by ${toolNames.openWorkspace}. Before working under a path listed in availableAgentsFiles, use ${toolNames.read} to inspect that instruction file and follow it. `;
129
+ const viewImageInstruction = `Use view_image when asked to inspect, describe, analyze, or compare a local image. Call ${toolNames.openWorkspace} first, pass the returned workspaceId, and use a workspace-relative path. Do not use ${toolNames.read} for binary image files. `;
130
+ const showChanges = config.widgets === "changes"
131
+ ? " After creating, editing, or overwriting files, call show_changes once after the related file changes are complete so the user can see the aggregate diff."
132
+ : "";
133
+ const devTools = `
134
+ Process management: Use start_process for long-running servers, apps, and games that must stay running. Use get_process_logs to tail output. Use stop_process when done. Do not use ${toolNames.shell} for commands that block indefinitely.
135
+ Web development: Use start_dev_server to launch a dev server (auto-detects npm run dev), capture_page_screenshot to capture pages with Playwright, inspect_page for structured DOM/accessibility info, test_responsive_page for multi-viewport testing. Requires Playwright; see docs/DEVELOPMENT_TOOLS.md.
136
+ Project discovery: Use inspect_project to detect project type, package manager, and recommended commands. Use glob_files and search_text for structured file search across the workspace.
137
+ Image tools: Use inspect_image for dimensions/format/palette. Use compare_images for pixel-level diff. Use inspect_sprite and split_sprite_sheet for sprite sheets. Do not use view_image for analysis—use inspect_image.
138
+ .NET tools: Use inspect_dotnet_project to read project structure, dotnet_restore/dotnet_build/dotnet_test for CLI operations, dotnet_run to start a .NET app as a persistent process, dotnet_format for formatting.
139
+ Godot tools: Use detect_godot_project when a workspace contains project.godot. Use godot_run to start the game or editor as a persistent process. Use inspect_godot_scene to read .tscn scene trees. Configure the Godot executable via GODOT_EXECUTABLE environment variable.
140
+ Window capture: Use capture_window to screenshot a running application tracked by Auvrynt. Use godot_capture_game specifically for Godot game processes.
141
+ Security: All paths must be workspace-relative. Never access files outside the opened workspace. Do not claim a screenshot or comparison succeeded unless the tool returned successfully.`;
142
+ return `Use Auvrynt as a local coding workspace. Call ${toolNames.openWorkspace} once per project folder or worktree to obtain a workspaceId. Reuse that same workspaceId for all later file, search, edit, write, show-changes, view-image, and shell tools in that folder; do not call ${toolNames.openWorkspace} again unless switching folders/worktrees, changing checkout/worktree mode, the workspaceId is rejected as unknown, or the user explicitly asks to reopen. ${agentsMd}${skills}${inspection}${viewImageInstruction}Prefer ${toolNames.edit} for targeted modifications, ${toolNames.write} only for new files or complete rewrites, and ${toolNames.shell} for tests, builds, git inspection, package scripts, and commands that are better executed by the shell. Do not create or modify files with ${toolNames.shell}; avoid shell redirection, heredocs, tee, sed -i, perl -i, node/python/ruby scripts, or any command whose purpose is to write project files.${showChanges}${devTools}`;
143
+ }
144
+ function resultOutputSchema(extra = {}) {
145
+ return {
146
+ result: z
147
+ .string()
148
+ .describe("Model-readable result text for follow-up reasoning and plain MCP hosts."),
149
+ ...extra,
150
+ };
151
+ }
152
+ const workspaceSkillOutputSchema = z.object({
153
+ name: z.string(),
154
+ description: z.string(),
155
+ path: z.string(),
156
+ });
157
+ const workspaceAgentsFileOutputSchema = z.object({
158
+ path: z.string(),
159
+ content: z.string(),
160
+ });
161
+ const workspaceAvailableAgentsFileOutputSchema = z.object({
162
+ path: z.string(),
163
+ });
164
+ const reviewFileOutputSchema = z.object({
165
+ path: z.string(),
166
+ previousPath: z.string().optional(),
167
+ type: z.enum(["change", "rename-pure", "rename-changed", "new", "deleted"]),
168
+ additions: z.number(),
169
+ removals: z.number(),
170
+ });
171
+ const reviewSummaryOutputSchema = z.object({
172
+ files: z.number(),
173
+ additions: z.number(),
174
+ removals: z.number(),
175
+ });
176
+ function sendJsonRpcError(res, status, code, message) {
177
+ res.status(status).json({
178
+ jsonrpc: "2.0",
179
+ error: { code, message },
180
+ id: null,
181
+ });
182
+ }
183
+ function requestLogFields(req, config) {
184
+ return {
185
+ ip: requestIp(req, config.logging.trustProxy),
186
+ host: req.header("host"),
187
+ userAgent: req.header("user-agent"),
188
+ origin: req.header("origin"),
189
+ referer: req.header("referer"),
190
+ contentLength: req.header("content-length"),
191
+ };
192
+ }
193
+ function logToolCall(config, fields) {
194
+ if (!config.logging.toolCalls)
195
+ return;
196
+ const { command, ...safeFields } = fields;
197
+ logEvent(config.logging, fields.success ? "info" : "warn", "tool_call", {
198
+ ...safeFields,
199
+ commandPreview: config.logging.shellCommands && command ? commandPreview(command) : undefined,
200
+ });
201
+ }
202
+ function contentText(content) {
203
+ return content
204
+ .filter((item) => item.type === "text")
205
+ .map((item) => item.text)
206
+ .join("\n");
207
+ }
208
+ function toolErrorPreview(content) {
209
+ const text = contentText(content).replace(/\s+/g, " ").trim();
210
+ if (!text)
211
+ return undefined;
212
+ return text.length > 240 ? `${text.slice(0, 237)}...` : text;
213
+ }
214
+ function logFailedToolResponse(config, fields, content, startedAt) {
215
+ logToolCall(config, {
216
+ ...fields,
217
+ success: false,
218
+ durationMs: Math.round(performance.now() - startedAt),
219
+ error: toolErrorPreview(content),
220
+ });
221
+ }
222
+ function textBlock(text) {
223
+ return { type: "text", text };
224
+ }
225
+ function textSummary(content) {
226
+ const text = contentText(content);
227
+ return {
228
+ lines: text.length === 0 ? 0 : text.split("\n").length,
229
+ characters: text.length,
230
+ };
231
+ }
232
+ function contentLineCount(content) {
233
+ if (content.length === 0)
234
+ return 0;
235
+ return content.endsWith("\n")
236
+ ? content.slice(0, -1).split("\n").length
237
+ : content.split("\n").length;
238
+ }
239
+ function countDiffStats(diff) {
240
+ if (!diff)
241
+ return { additions: 0, removals: 0 };
242
+ let additions = 0;
243
+ let removals = 0;
244
+ for (const line of diff.split("\n")) {
245
+ if (line.startsWith("+") && !line.startsWith("+++"))
246
+ additions++;
247
+ if (line.startsWith("-") && !line.startsWith("---"))
248
+ removals++;
249
+ }
250
+ return { additions, removals };
251
+ }
252
+ function newFilePatch(path, content) {
253
+ const lines = content.length === 0
254
+ ? []
255
+ : content.endsWith("\n")
256
+ ? content.slice(0, -1).split("\n")
257
+ : content.split("\n");
258
+ const hunkLength = lines.length;
259
+ const hunkRange = hunkLength === 0 ? "+0,0" : `+1,${hunkLength}`;
260
+ const body = lines.map((line) => `+${line}`).join("\n");
261
+ return [
262
+ `diff --git a/${path} b/${path}`,
263
+ "new file mode 100644",
264
+ "index 0000000..0000000",
265
+ "--- /dev/null",
266
+ `+++ b/${path}`,
267
+ `@@ -0,0 ${hunkRange} @@`,
268
+ body,
269
+ ]
270
+ .filter((line) => line.length > 0)
271
+ .join("\n");
272
+ }
273
+ function assetBaseUrl(config) {
274
+ return `${config.publicBaseUrl.replace(/\/+$/, "")}/mcp-app-assets`;
275
+ }
276
+ function uiManifestUrl() {
277
+ return new URL("../dist/ui/.vite/manifest.json", import.meta.url);
278
+ }
279
+ function readWorkspaceAppManifest() {
280
+ return JSON.parse(readFileSync(uiManifestUrl(), "utf8"));
281
+ }
282
+ function getWorkspaceAppManifestEntry() {
283
+ const manifest = readWorkspaceAppManifest();
284
+ const entry = manifest[WORKSPACE_APP_MANIFEST_ENTRY];
285
+ if (!entry?.file) {
286
+ throw new Error(`Missing ${WORKSPACE_APP_MANIFEST_ENTRY} in UI manifest.`);
287
+ }
288
+ return entry;
289
+ }
290
+ function assetUrl(baseUrl, assetPath) {
291
+ return `${baseUrl}/${assetPath.replace(/^\/+/, "")}`;
292
+ }
293
+ function workspaceAppHtml(config) {
294
+ const baseUrl = assetBaseUrl(config);
295
+ const entry = getWorkspaceAppManifestEntry();
296
+ const stylesheets = (entry.css ?? [])
297
+ .map((stylesheet) => ` <link rel="stylesheet" crossorigin href="${assetUrl(baseUrl, stylesheet)}" />`)
298
+ .join("\n");
299
+ return `<!doctype html>
300
+ <html lang="en">
301
+ <head>
302
+ <meta charset="UTF-8" />
303
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
304
+ <title>Auvrynt Workspace</title>
305
+ <script type="module" crossorigin src="${assetUrl(baseUrl, entry.file)}"></script>
306
+ ${stylesheets}
307
+ </head>
308
+ <body>
309
+ <main id="app" class="shell">
310
+ <section class="empty">Waiting for a tool result.</section>
311
+ </main>
312
+ </body>
313
+ </html>`;
314
+ }
315
+ function appCsp(config) {
316
+ const publicBaseUrl = config.publicBaseUrl.replace(/\/+$/, "");
317
+ return {
318
+ resourceDomains: [publicBaseUrl],
319
+ connectDomains: [publicBaseUrl],
320
+ };
321
+ }
322
+ function uiBuildDirectory() {
323
+ return fileURLToPath(new URL("../dist/ui", import.meta.url));
324
+ }
325
+ function setAssetHeaders(res) {
326
+ res.setHeader("Access-Control-Allow-Origin", "*");
327
+ res.setHeader("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS");
328
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Range");
329
+ res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
330
+ }
331
+ async function assertWorkspaceAppAssets() {
332
+ const entry = getWorkspaceAppManifestEntry();
333
+ const candidates = [entry.file, ...(entry.css ?? [])].map((assetPath) => new URL(`../dist/ui/${assetPath}`, import.meta.url));
334
+ for (const candidate of candidates) {
335
+ await access(candidate);
336
+ }
337
+ }
338
+ function createMcpServer(config, workspaces, reviewCheckpoints, serenaManager) {
339
+ const processManager = new ProcessManager(workspaces);
340
+ const toolNames = toolNamesFor(config);
341
+ const server = new McpServer({
342
+ name: "auvrynt",
343
+ title: "Auvrynt",
344
+ version: "0.1.0",
345
+ description: "Secure local coding workspace for MCP clients. Provides workspace-scoped file, search, edit, write, and shell tools.",
346
+ }, {
347
+ instructions: serverInstructions(config, toolNames),
348
+ });
349
+ registerAppResource(server, "Auvrynt Diff Card", WORKSPACE_APP_URI, {
350
+ description: "Interactive card for viewing Auvrynt file diffs.",
351
+ _meta: {
352
+ ui: {
353
+ csp: appCsp(config),
354
+ },
355
+ },
356
+ }, async () => {
357
+ await assertWorkspaceAppAssets();
358
+ return {
359
+ contents: [
360
+ {
361
+ uri: WORKSPACE_APP_URI,
362
+ mimeType: RESOURCE_MIME_TYPE,
363
+ text: workspaceAppHtml(config),
364
+ _meta: {
365
+ ui: {
366
+ csp: appCsp(config),
367
+ },
368
+ },
369
+ },
370
+ ],
371
+ };
372
+ });
373
+ registerAppTool(server, "open_workspace", {
374
+ title: "Open workspace",
375
+ description: "Open a local project directory as a coding workspace. Call this once per project folder or worktree before reading, editing, searching, writing, showing changes, or running commands. Reuse the returned workspaceId for later calls in the same folder; do not call open_workspace again unless switching folders/worktrees, changing checkout/worktree mode, the workspaceId is rejected as unknown, or the user explicitly asks to reopen. By default this opens the actual checkout; set mode=\"worktree\" when the user asks for an isolated or parallel coding session. Returns a workspaceId, loaded root project instructions, and nested instruction file paths the model should read before working in those directories.",
376
+ inputSchema: {
377
+ path: z
378
+ .string()
379
+ .describe("Absolute path, or a leading-tilde home path such as ~/project, to a local project directory inside an allowed root."),
380
+ mode: z
381
+ .enum(["checkout", "worktree"])
382
+ .optional()
383
+ .describe("Defaults to checkout. Use checkout to work in the actual directory. Use worktree to create an isolated managed Git worktree for parallel work."),
384
+ baseRef: z
385
+ .string()
386
+ .optional()
387
+ .describe("Git ref to base a worktree on. Only used with mode=\"worktree\". Defaults to HEAD."),
388
+ },
389
+ outputSchema: {
390
+ workspaceId: z.string(),
391
+ root: z.string(),
392
+ mode: z.enum(["checkout", "worktree"]),
393
+ sourceRoot: z.string().optional(),
394
+ worktree: z
395
+ .object({
396
+ path: z.string(),
397
+ baseRef: z.string(),
398
+ baseSha: z.string(),
399
+ dirtySource: z.boolean(),
400
+ detached: z.boolean(),
401
+ managed: z.boolean(),
402
+ })
403
+ .optional(),
404
+ agentsFiles: z.array(workspaceAgentsFileOutputSchema),
405
+ availableAgentsFiles: z.array(workspaceAvailableAgentsFileOutputSchema),
406
+ skills: z.array(workspaceSkillOutputSchema),
407
+ skillDiagnostics: z.array(z.unknown()),
408
+ instruction: z.string(),
409
+ },
410
+ ...toolWidgetDescriptorMeta(config, "workspace"),
411
+ annotations: { readOnlyHint: true },
412
+ }, async ({ path, mode, baseRef }) => {
413
+ const startedAt = performance.now();
414
+ const { workspace, agentsFiles, availableAgentsFiles } = await workspaces.openWorkspace({ path, mode, baseRef });
415
+ if (config.widgets === "changes") {
416
+ void reviewCheckpoints.initializeWorkspace({
417
+ workspaceId: workspace.id,
418
+ root: workspace.root,
419
+ });
420
+ }
421
+ const visibleSkills = workspace.skills
422
+ .filter((skill) => !skill.disableModelInvocation)
423
+ .map((skill) => ({
424
+ name: skill.name,
425
+ description: skill.description,
426
+ path: formatPathForPrompt(skill.filePath),
427
+ }));
428
+ const loadedAgentsFiles = agentsFiles.map((file) => ({
429
+ path: formatAgentsPath(file.path, workspace.root),
430
+ content: file.content,
431
+ }));
432
+ const availableAgentsFileOutputs = availableAgentsFiles.map((file) => ({
433
+ path: formatAgentsPath(file.path, workspace.root),
434
+ }));
435
+ const instruction = config.skillsEnabled
436
+ ? "Use this workspaceId in all subsequent tool calls for this project. Do not call open_workspace again for this same folder unless this workspaceId stops working, the user asks to reopen, or you switch to a different folder/worktree. Follow loaded agentsFiles instructions. Before working under a path listed in availableAgentsFiles, read that instruction file. When a task matches an available skill in skills, read its path before proceeding."
437
+ : "Use this workspaceId in all subsequent tool calls for this project. Do not call open_workspace again for this same folder unless this workspaceId stops working, the user asks to reopen, or you switch to a different folder/worktree. Follow loaded agentsFiles instructions. Before working under a path listed in availableAgentsFiles, read that instruction file.";
438
+ const resultContent = [
439
+ {
440
+ type: "text",
441
+ text: [
442
+ `Opened workspace ${workspace.id}`,
443
+ `Root: ${workspace.root}`,
444
+ `Mode: ${workspace.mode}`,
445
+ loadedAgentsFiles.length > 0
446
+ ? `Loaded project instructions: ${loadedAgentsFiles.map((file) => file.path).join(", ")}`
447
+ : undefined,
448
+ availableAgentsFileOutputs.length > 0
449
+ ? `Available nested instructions: ${availableAgentsFileOutputs.map((file) => file.path).join(", ")}`
450
+ : undefined,
451
+ visibleSkills.length > 0
452
+ ? `Available skills: ${visibleSkills.map((skill) => skill.name).join(", ")}`
453
+ : undefined,
454
+ instruction,
455
+ ].filter(Boolean).join("\n"),
456
+ },
457
+ ];
458
+ logToolCall(config, {
459
+ tool: "open_workspace",
460
+ workspaceId: workspace.id,
461
+ path: workspace.root,
462
+ success: true,
463
+ durationMs: Math.round(performance.now() - startedAt),
464
+ });
465
+ return {
466
+ content: resultContent,
467
+ _meta: {
468
+ tool: "open_workspace",
469
+ card: {
470
+ workspaceId: workspace.id,
471
+ root: workspace.root,
472
+ path: workspace.root,
473
+ summary: {
474
+ agentsFiles: loadedAgentsFiles.length,
475
+ availableAgentsFiles: availableAgentsFileOutputs.length,
476
+ skills: visibleSkills.length,
477
+ skillDiagnostics: workspace.skillDiagnostics.length,
478
+ },
479
+ },
480
+ },
481
+ structuredContent: {
482
+ workspaceId: workspace.id,
483
+ root: workspace.root,
484
+ mode: workspace.mode,
485
+ sourceRoot: workspace.sourceRoot,
486
+ worktree: workspace.worktree,
487
+ agentsFiles: loadedAgentsFiles,
488
+ availableAgentsFiles: availableAgentsFileOutputs,
489
+ skills: visibleSkills,
490
+ skillDiagnostics: workspace.skillDiagnostics,
491
+ instruction,
492
+ },
493
+ };
494
+ });
495
+ registerAppTool(server, toolNames.read, {
496
+ title: "Read file",
497
+ description: [
498
+ "Read a file inside an open workspace. Use this for file inspection instead of shell commands like cat or sed. Call open_workspace first and pass workspaceId.",
499
+ "Use this tool to inspect relevant AGENTS.md or CLAUDE.md files listed by open_workspace before working in nested directories.",
500
+ config.skillsEnabled
501
+ ? "If available skills were returned and a task matches one, read that skill's path before proceeding. Skill paths may be outside the workspace; only advertised SKILL.md files and files under already-loaded skill directories are readable."
502
+ : "",
503
+ ]
504
+ .filter(Boolean)
505
+ .join(" "),
506
+ inputSchema: {
507
+ workspaceId: z
508
+ .string()
509
+ .describe("Workspace identifier returned by open_workspace."),
510
+ path: z
511
+ .string()
512
+ .describe(config.skillsEnabled
513
+ ? "File path to read, relative to the workspace root. May also be an advertised skill path from open_workspace skills."
514
+ : "File path to read, relative to the workspace root."),
515
+ offset: z
516
+ .number()
517
+ .int()
518
+ .positive()
519
+ .optional()
520
+ .describe("1-indexed line number to start reading from."),
521
+ limit: z
522
+ .number()
523
+ .int()
524
+ .positive()
525
+ .optional()
526
+ .describe("Maximum number of lines to read."),
527
+ },
528
+ outputSchema: resultOutputSchema(),
529
+ ...toolWidgetDescriptorMeta(config, "read"),
530
+ annotations: { readOnlyHint: true },
531
+ }, async ({ workspaceId, ...input }) => {
532
+ const startedAt = performance.now();
533
+ const workspace = workspaces.getWorkspace(workspaceId);
534
+ const readPath = workspaces.resolveReadPath(workspace, input.path);
535
+ const response = await readFileTool({ ...input, path: readPath.absolutePath }, {
536
+ cwd: workspace.root,
537
+ root: workspace.root,
538
+ readRoots: readPath.readRoots,
539
+ });
540
+ if (response.isError) {
541
+ logFailedToolResponse(config, {
542
+ tool: toolNames.read,
543
+ workspaceId,
544
+ path: input.path,
545
+ }, response.content, startedAt);
546
+ return response;
547
+ }
548
+ workspaces.markReadPathLoaded(workspace, readPath);
549
+ const summary = {
550
+ ...textSummary(response.content),
551
+ offset: input.offset ?? 1,
552
+ limited: input.limit !== undefined,
553
+ };
554
+ logToolCall(config, {
555
+ tool: toolNames.read,
556
+ workspaceId,
557
+ path: input.path,
558
+ success: true,
559
+ durationMs: Math.round(performance.now() - startedAt),
560
+ });
561
+ return {
562
+ ...response,
563
+ _meta: {
564
+ tool: toolNames.read,
565
+ card: {
566
+ workspaceId,
567
+ path: input.path,
568
+ summary,
569
+ payload: { content: response.content },
570
+ },
571
+ },
572
+ structuredContent: {
573
+ result: contentText(response.content),
574
+ },
575
+ };
576
+ });
577
+ registerAppTool(server, "view_image", {
578
+ title: "View image",
579
+ description: "View a local image file (.png, .jpg, .jpeg, .webp, .gif) inside an open workspace as visual MCP image content. Use this when asked to inspect, describe, analyze, or compare images. Call open_workspace first and pass workspaceId.",
580
+ inputSchema: {
581
+ workspaceId: z
582
+ .string()
583
+ .describe("Workspace identifier returned by open_workspace."),
584
+ path: z
585
+ .string()
586
+ .describe("Image file path, relative to the workspace root."),
587
+ },
588
+ ...toolWidgetDescriptorMeta(config, "read"),
589
+ annotations: VIEW_IMAGE_TOOL_ANNOTATIONS,
590
+ }, async ({ workspaceId, path }) => {
591
+ const startedAt = performance.now();
592
+ const response = await executeViewImage(workspaces, { workspaceId, path });
593
+ if (response.isError) {
594
+ logFailedToolResponse(config, { tool: "view_image", workspaceId, path }, response.content, startedAt);
595
+ return response;
596
+ }
597
+ logToolCall(config, {
598
+ tool: "view_image",
599
+ workspaceId,
600
+ path,
601
+ success: true,
602
+ durationMs: Math.round(performance.now() - startedAt),
603
+ });
604
+ return response;
605
+ });
606
+ registerAppTool(server, toolNames.write, {
607
+ title: "Write file",
608
+ description: `Create or completely overwrite a file inside an open workspace. Prefer ${toolNames.edit} for targeted changes to existing files. Call open_workspace first and pass workspaceId.`,
609
+ inputSchema: {
610
+ workspaceId: z
611
+ .string()
612
+ .describe("Workspace identifier returned by open_workspace."),
613
+ path: z
614
+ .string()
615
+ .describe("File path to write, relative to the workspace root."),
616
+ content: z.string().describe("Complete new file content."),
617
+ },
618
+ outputSchema: resultOutputSchema(),
619
+ ...toolWidgetDescriptorMeta(config, "write"),
620
+ annotations: WRITE_TOOL_ANNOTATIONS,
621
+ }, async ({ workspaceId, ...input }) => {
622
+ const startedAt = performance.now();
623
+ const workspace = workspaces.getWorkspace(workspaceId);
624
+ workspaces.resolvePath(workspace, input.path);
625
+ const response = await writeFileTool(input, {
626
+ cwd: workspace.root,
627
+ root: workspace.root,
628
+ });
629
+ if (response.isError) {
630
+ logFailedToolResponse(config, {
631
+ tool: toolNames.write,
632
+ workspaceId,
633
+ path: input.path,
634
+ }, response.content, startedAt);
635
+ return response;
636
+ }
637
+ const patch = newFilePatch(input.path, input.content);
638
+ const stats = countDiffStats(patch);
639
+ const summary = {
640
+ ...stats,
641
+ lines: contentLineCount(input.content),
642
+ characters: input.content.length,
643
+ };
644
+ logToolCall(config, {
645
+ tool: toolNames.write,
646
+ workspaceId,
647
+ path: input.path,
648
+ success: true,
649
+ durationMs: Math.round(performance.now() - startedAt),
650
+ });
651
+ return {
652
+ ...response,
653
+ _meta: {
654
+ tool: toolNames.write,
655
+ card: {
656
+ workspaceId,
657
+ path: input.path,
658
+ summary,
659
+ payload: {
660
+ content: response.content,
661
+ patch,
662
+ },
663
+ },
664
+ },
665
+ structuredContent: {
666
+ result: contentText(response.content),
667
+ },
668
+ };
669
+ });
670
+ registerAppTool(server, toolNames.edit, {
671
+ title: "Edit file",
672
+ description: `Edit one file inside an open workspace by replacing exact text blocks. Prefer this over ${toolNames.write} for targeted changes. Each oldText must match a unique, non-overlapping region of the original file; merge nearby changes into one edit and keep oldText as small as possible while still unique. Call open_workspace first and pass workspaceId.`,
673
+ inputSchema: {
674
+ workspaceId: z
675
+ .string()
676
+ .describe("Workspace identifier returned by open_workspace."),
677
+ path: z
678
+ .string()
679
+ .describe("File path to edit, relative to the workspace root."),
680
+ edits: z
681
+ .array(z.object({
682
+ oldText: z
683
+ .string()
684
+ .describe("Exact text to replace. Must match uniquely in the original file."),
685
+ newText: z.string().describe("Replacement text."),
686
+ }))
687
+ .min(1),
688
+ },
689
+ outputSchema: resultOutputSchema({
690
+ status: z.literal("applied"),
691
+ }),
692
+ ...toolWidgetDescriptorMeta(config, "edit"),
693
+ annotations: EDIT_TOOL_ANNOTATIONS,
694
+ }, async ({ workspaceId, ...input }) => {
695
+ const startedAt = performance.now();
696
+ const workspace = workspaces.getWorkspace(workspaceId);
697
+ workspaces.resolvePath(workspace, input.path);
698
+ const response = await editFileTool(input, {
699
+ cwd: workspace.root,
700
+ root: workspace.root,
701
+ });
702
+ if (response.isError) {
703
+ logFailedToolResponse(config, {
704
+ tool: toolNames.edit,
705
+ workspaceId,
706
+ path: input.path,
707
+ }, response.content, startedAt);
708
+ return response;
709
+ }
710
+ const stats = countDiffStats(response.details?.patch ?? response.details?.diff);
711
+ const summary = {
712
+ ...stats,
713
+ editCount: input.edits.length,
714
+ };
715
+ const editResultText = `Edited ${input.path} (+${stats.additions} -${stats.removals}).`;
716
+ const editContent = [textBlock(editResultText)];
717
+ logToolCall(config, {
718
+ tool: toolNames.edit,
719
+ workspaceId,
720
+ path: input.path,
721
+ success: true,
722
+ durationMs: Math.round(performance.now() - startedAt),
723
+ });
724
+ return {
725
+ content: editContent,
726
+ _meta: {
727
+ tool: toolNames.edit,
728
+ card: {
729
+ workspaceId,
730
+ path: input.path,
731
+ summary,
732
+ payload: {
733
+ diff: response.details?.diff,
734
+ patch: response.details?.patch,
735
+ },
736
+ },
737
+ },
738
+ structuredContent: {
739
+ status: "applied",
740
+ result: contentText(editContent),
741
+ },
742
+ };
743
+ });
744
+ if (config.widgets === "changes") {
745
+ registerAppTool(server, "show_changes", {
746
+ title: "Show changes",
747
+ description: "Show aggregate file changes in an open workspace since the last shown checkpoint or since the workspace was opened. After you create, edit, or overwrite files, call this once when the related file changes are complete so the user can inspect the combined diff.",
748
+ inputSchema: {
749
+ workspaceId: z
750
+ .string()
751
+ .describe("Workspace identifier returned by open_workspace."),
752
+ since: z
753
+ .enum(["last_shown", "workspace_open"])
754
+ .optional()
755
+ .describe("Defaults to last_shown. Use workspace_open to compare against the initial open_workspace checkpoint."),
756
+ markReviewed: z
757
+ .boolean()
758
+ .optional()
759
+ .describe("Defaults to true. When true, advances the last shown checkpoint to the current workspace state."),
760
+ },
761
+ outputSchema: resultOutputSchema(),
762
+ ...toolWidgetDescriptorMeta(config, "show_changes"),
763
+ annotations: { readOnlyHint: true },
764
+ }, async ({ workspaceId, since, markReviewed }) => {
765
+ const startedAt = performance.now();
766
+ const workspace = workspaces.getWorkspace(workspaceId);
767
+ const review = await reviewCheckpoints.reviewChanges({
768
+ workspaceId,
769
+ root: workspace.root,
770
+ since: since ?? "last_shown",
771
+ markReviewed: markReviewed ?? true,
772
+ });
773
+ const content = [textBlock(review.result)];
774
+ logToolCall(config, {
775
+ tool: "show_changes",
776
+ workspaceId,
777
+ success: true,
778
+ durationMs: Math.round(performance.now() - startedAt),
779
+ });
780
+ return {
781
+ content,
782
+ _meta: {
783
+ tool: "show_changes",
784
+ card: {
785
+ workspaceId,
786
+ summary: review.summary,
787
+ files: review.files,
788
+ payload: {
789
+ patch: review.patch,
790
+ },
791
+ },
792
+ },
793
+ structuredContent: {
794
+ result: contentText(content),
795
+ },
796
+ };
797
+ });
798
+ }
799
+ if (!config.minimalTools) {
800
+ registerAppTool(server, toolNames.grep, {
801
+ title: config.toolNaming === "short" ? "Grep" : "Grep files",
802
+ description: "Search file contents inside an open workspace. Use this before broad reads when looking for symbols, text, or usage sites. Respects project ignore rules. Call open_workspace first and pass workspaceId.",
803
+ inputSchema: {
804
+ workspaceId: z
805
+ .string()
806
+ .describe("Workspace identifier returned by open_workspace."),
807
+ pattern: z.string().describe("Search pattern."),
808
+ path: z
809
+ .string()
810
+ .optional()
811
+ .describe("Optional path or glob scope relative to the workspace root."),
812
+ include: z.string().optional().describe("Optional include glob."),
813
+ },
814
+ outputSchema: resultOutputSchema(),
815
+ ...toolWidgetDescriptorMeta(config, "search"),
816
+ annotations: { readOnlyHint: true },
817
+ }, async ({ workspaceId, ...input }) => {
818
+ const startedAt = performance.now();
819
+ const workspace = workspaces.getWorkspace(workspaceId);
820
+ if (input.path)
821
+ workspaces.resolvePath(workspace, input.path);
822
+ const response = await grepFilesTool(input, {
823
+ cwd: workspace.root,
824
+ root: workspace.root,
825
+ });
826
+ if (response.isError) {
827
+ logFailedToolResponse(config, {
828
+ tool: toolNames.grep,
829
+ workspaceId,
830
+ path: input.path,
831
+ }, response.content, startedAt);
832
+ return response;
833
+ }
834
+ const summary = {
835
+ pattern: input.pattern,
836
+ scope: input.path ?? ".",
837
+ ...textSummary(response.content),
838
+ };
839
+ logToolCall(config, {
840
+ tool: toolNames.grep,
841
+ workspaceId,
842
+ path: input.path,
843
+ success: true,
844
+ durationMs: Math.round(performance.now() - startedAt),
845
+ });
846
+ return {
847
+ ...response,
848
+ _meta: {
849
+ tool: toolNames.grep,
850
+ card: {
851
+ workspaceId,
852
+ path: input.path,
853
+ summary,
854
+ payload: { content: response.content },
855
+ },
856
+ },
857
+ structuredContent: {
858
+ result: contentText(response.content),
859
+ },
860
+ };
861
+ });
862
+ registerAppTool(server, toolNames.glob, {
863
+ title: config.toolNaming === "short" ? "Glob" : "Find files",
864
+ description: "Find files by glob pattern inside an open workspace. Use this to discover filenames or narrow file sets before reading. Respects project ignore rules. Call open_workspace first and pass workspaceId.",
865
+ inputSchema: {
866
+ workspaceId: z
867
+ .string()
868
+ .describe("Workspace identifier returned by open_workspace."),
869
+ pattern: z.string().describe("File glob pattern."),
870
+ path: z
871
+ .string()
872
+ .optional()
873
+ .describe("Optional path scope relative to the workspace root."),
874
+ },
875
+ outputSchema: resultOutputSchema(),
876
+ ...toolWidgetDescriptorMeta(config, "search"),
877
+ annotations: { readOnlyHint: true },
878
+ }, async ({ workspaceId, ...input }) => {
879
+ const startedAt = performance.now();
880
+ const workspace = workspaces.getWorkspace(workspaceId);
881
+ if (input.path)
882
+ workspaces.resolvePath(workspace, input.path);
883
+ const response = await findFilesTool(input, {
884
+ cwd: workspace.root,
885
+ root: workspace.root,
886
+ });
887
+ if (response.isError) {
888
+ logFailedToolResponse(config, {
889
+ tool: toolNames.glob,
890
+ workspaceId,
891
+ path: input.path,
892
+ }, response.content, startedAt);
893
+ return response;
894
+ }
895
+ const summary = {
896
+ pattern: input.pattern,
897
+ scope: input.path ?? ".",
898
+ ...textSummary(response.content),
899
+ };
900
+ logToolCall(config, {
901
+ tool: toolNames.glob,
902
+ workspaceId,
903
+ path: input.path,
904
+ success: true,
905
+ durationMs: Math.round(performance.now() - startedAt),
906
+ });
907
+ return {
908
+ ...response,
909
+ _meta: {
910
+ tool: toolNames.glob,
911
+ card: {
912
+ workspaceId,
913
+ path: input.path,
914
+ summary,
915
+ payload: { content: response.content },
916
+ },
917
+ },
918
+ structuredContent: {
919
+ result: contentText(response.content),
920
+ },
921
+ };
922
+ });
923
+ registerAppTool(server, toolNames.ls, {
924
+ title: config.toolNaming === "short" ? "Ls" : "List directory",
925
+ description: "List a directory inside an open workspace. Use this for directory inspection before reading files. Call open_workspace first and pass workspaceId.",
926
+ inputSchema: {
927
+ workspaceId: z
928
+ .string()
929
+ .describe("Workspace identifier returned by open_workspace."),
930
+ path: z
931
+ .string()
932
+ .describe("Directory path to list, relative to the workspace root."),
933
+ },
934
+ outputSchema: resultOutputSchema(),
935
+ ...toolWidgetDescriptorMeta(config, "directory"),
936
+ annotations: { readOnlyHint: true },
937
+ }, async ({ workspaceId, ...input }) => {
938
+ const startedAt = performance.now();
939
+ const workspace = workspaces.getWorkspace(workspaceId);
940
+ workspaces.resolvePath(workspace, input.path);
941
+ const response = await listDirectoryTool(input, {
942
+ cwd: workspace.root,
943
+ root: workspace.root,
944
+ });
945
+ if (response.isError) {
946
+ logFailedToolResponse(config, {
947
+ tool: toolNames.ls,
948
+ workspaceId,
949
+ path: input.path,
950
+ }, response.content, startedAt);
951
+ return response;
952
+ }
953
+ const summary = textSummary(response.content);
954
+ logToolCall(config, {
955
+ tool: toolNames.ls,
956
+ workspaceId,
957
+ path: input.path,
958
+ success: true,
959
+ durationMs: Math.round(performance.now() - startedAt),
960
+ });
961
+ return {
962
+ ...response,
963
+ _meta: {
964
+ tool: toolNames.ls,
965
+ card: {
966
+ workspaceId,
967
+ path: input.path,
968
+ summary,
969
+ payload: { content: response.content },
970
+ },
971
+ },
972
+ structuredContent: {
973
+ result: contentText(response.content),
974
+ },
975
+ };
976
+ });
977
+ }
978
+ registerAppTool(server, toolNames.shell, {
979
+ title: config.toolNaming === "short" ? "Bash" : "Run shell",
980
+ description: config.minimalTools
981
+ ? `Run a shell command inside an open workspace. Use only for tests, builds, git inspection, package scripts, search, file discovery, and directory inspection. In minimal tool mode, ${toolNames.grep}, ${toolNames.glob}, and ${toolNames.ls} are disabled; use command-line tools such as grep, rg, find, ls, and tree for those read-only inspection actions. Do not use ${toolNames.shell} to create or modify files. Do not use shell redirection, heredocs, tee, sed -i, perl -i, node/python/ruby scripts, or generated scripts to write project files; use ${toolNames.edit} for targeted changes and ${toolNames.write} for new files or full rewrites. Prefer ${toolNames.read} for direct file reads. Call open_workspace first and pass workspaceId. This is powerful local execution and should only be exposed behind strong authentication.`
982
+ : `Run a shell command inside an open workspace. Use only for tests, builds, git inspection, package scripts, and commands that are better executed by the shell. Do not use ${toolNames.shell} to create or modify files. Do not use shell redirection, heredocs, tee, sed -i, perl -i, node/python/ruby scripts, or generated scripts to write project files; use ${toolNames.edit} for targeted changes and ${toolNames.write} for new files or full rewrites. Prefer ${toolNames.read}, ${toolNames.grep}, ${toolNames.glob}, and ${toolNames.ls} for file inspection. Call open_workspace first and pass workspaceId. This is powerful local execution and should only be exposed behind strong authentication.`,
983
+ inputSchema: {
984
+ workspaceId: z
985
+ .string()
986
+ .describe("Workspace identifier returned by open_workspace."),
987
+ command: z
988
+ .string()
989
+ .describe(`Shell command to run. Must not create or modify project files; use ${toolNames.edit} or ${toolNames.write} for file changes.`),
990
+ workingDirectory: z
991
+ .string()
992
+ .optional()
993
+ .describe("Optional working directory relative to the workspace root. Defaults to the workspace root."),
994
+ timeout: z
995
+ .number()
996
+ .positive()
997
+ .max(300)
998
+ .optional()
999
+ .describe("Timeout in seconds. Defaults to 30, max 300."),
1000
+ },
1001
+ outputSchema: resultOutputSchema(),
1002
+ ...toolWidgetDescriptorMeta(config, "shell"),
1003
+ annotations: SHELL_TOOL_ANNOTATIONS,
1004
+ }, async ({ workspaceId, workingDirectory, ...input }) => {
1005
+ const startedAt = performance.now();
1006
+ const workspace = workspaces.getWorkspace(workspaceId);
1007
+ const cwd = workspaces.resolveWorkingDirectory(workspace, workingDirectory);
1008
+ const response = await runShellTool(input, {
1009
+ cwd,
1010
+ root: workspace.root,
1011
+ });
1012
+ if (response.isError) {
1013
+ logFailedToolResponse(config, {
1014
+ tool: toolNames.shell,
1015
+ workspaceId,
1016
+ workingDirectory: workingDirectory ?? ".",
1017
+ command: input.command,
1018
+ commandLength: input.command.length,
1019
+ }, response.content, startedAt);
1020
+ return response;
1021
+ }
1022
+ const summary = {
1023
+ command: input.command,
1024
+ workingDirectory: workingDirectory ?? ".",
1025
+ ...textSummary(response.content),
1026
+ };
1027
+ logToolCall(config, {
1028
+ tool: toolNames.shell,
1029
+ workspaceId,
1030
+ workingDirectory: workingDirectory ?? ".",
1031
+ command: input.command,
1032
+ commandLength: input.command.length,
1033
+ success: true,
1034
+ durationMs: Math.round(performance.now() - startedAt),
1035
+ });
1036
+ return {
1037
+ ...response,
1038
+ _meta: {
1039
+ tool: toolNames.shell,
1040
+ card: {
1041
+ workspaceId,
1042
+ path: workingDirectory,
1043
+ summary,
1044
+ payload: { content: response.content },
1045
+ },
1046
+ },
1047
+ structuredContent: {
1048
+ result: contentText(response.content),
1049
+ },
1050
+ };
1051
+ });
1052
+ // --- Process Management Tools ---
1053
+ const WORKSPACE_ID_SCHEMA = z.string().describe("Workspace identifier returned by open_workspace.");
1054
+ registerAppTool(server, "get_connection_status", {
1055
+ title: "Connection status",
1056
+ description: "Automatically check which local integrations are connected for this workspace: MCP, Blender, Godot, browser support, Chrome MCP availability, and tracked processes.",
1057
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA },
1058
+ ...toolWidgetDescriptorMeta(config, "read"),
1059
+ annotations: READ_ONLY_ANNOTATIONS,
1060
+ }, async ({ workspaceId }) => {
1061
+ try {
1062
+ const result = await getConnectionStatus(workspaces, processManager, workspaceId);
1063
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1064
+ }
1065
+ catch (error) {
1066
+ const message = error instanceof Error ? error.message : String(error);
1067
+ return { content: [{ type: "text", text: `get_connection_status failed: ${message}` }], isError: true };
1068
+ }
1069
+ });
1070
+ registerAppTool(server, "start_process", {
1071
+ title: "Start process",
1072
+ description: "Start a persistent background process (server, app, game) inside an open workspace. Returns a processId. Use get_process_logs to tail output. Do not use run_shell for long-running processes.",
1073
+ inputSchema: {
1074
+ workspaceId: WORKSPACE_ID_SCHEMA,
1075
+ command: z.string().describe("Shell command to run."),
1076
+ workingDirectory: z.string().optional().describe("Working directory relative to workspace root."),
1077
+ environment: z.record(z.string(), z.string()).optional().describe("Additional environment variables (secrets will be redacted in responses)."),
1078
+ },
1079
+ ...toolWidgetDescriptorMeta(config, "shell"),
1080
+ annotations: PROCESS_ANNOTATIONS,
1081
+ }, async ({ workspaceId, command, workingDirectory, environment }) => {
1082
+ const startedAt = performance.now();
1083
+ try {
1084
+ const result = processManager.startProcess({ workspaceId, command, workingDirectory, environment });
1085
+ logToolCall(config, { tool: "start_process", workspaceId, command, success: true, durationMs: Math.round(performance.now() - startedAt) });
1086
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1087
+ }
1088
+ catch (err) {
1089
+ const msg = err instanceof Error ? err.message : String(err);
1090
+ logToolCall(config, { tool: "start_process", workspaceId, command, success: false, durationMs: Math.round(performance.now() - startedAt), error: msg });
1091
+ return { content: [{ type: "text", text: `start_process failed: ${msg}` }], isError: true };
1092
+ }
1093
+ });
1094
+ registerAppTool(server, "get_process_logs", {
1095
+ title: "Get process logs",
1096
+ description: "Retrieve recent stdout/stderr from a running or exited process started by start_process.",
1097
+ inputSchema: {
1098
+ workspaceId: WORKSPACE_ID_SCHEMA,
1099
+ processId: z.string().describe("Process ID returned by start_process."),
1100
+ lines: z.number().int().optional().describe("Number of recent lines to return (default 100, max 500)."),
1101
+ stream: z.enum(["stdout", "stderr", "both"]).optional().describe("Which stream to return."),
1102
+ },
1103
+ ...toolWidgetDescriptorMeta(config, "shell"),
1104
+ annotations: READ_ONLY_ANNOTATIONS,
1105
+ }, async ({ workspaceId, processId, lines, stream }) => {
1106
+ try {
1107
+ const result = processManager.getProcessLogs({ workspaceId, processId, lines, stream });
1108
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1109
+ }
1110
+ catch (err) {
1111
+ const msg = err instanceof Error ? err.message : String(err);
1112
+ return { content: [{ type: "text", text: `get_process_logs failed: ${msg}` }], isError: true };
1113
+ }
1114
+ });
1115
+ registerAppTool(server, "list_processes", {
1116
+ title: "List processes",
1117
+ description: "List all tracked processes for the current workspace.",
1118
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA },
1119
+ ...toolWidgetDescriptorMeta(config, "shell"),
1120
+ annotations: READ_ONLY_ANNOTATIONS,
1121
+ }, async ({ workspaceId }) => {
1122
+ try {
1123
+ const result = processManager.listProcesses({ workspaceId });
1124
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1125
+ }
1126
+ catch (err) {
1127
+ const msg = err instanceof Error ? err.message : String(err);
1128
+ return { content: [{ type: "text", text: `list_processes failed: ${msg}` }], isError: true };
1129
+ }
1130
+ });
1131
+ registerAppTool(server, "stop_process", {
1132
+ title: "Stop process",
1133
+ description: "Stop a tracked process gracefully. Set force=true to kill the process tree immediately.",
1134
+ inputSchema: {
1135
+ workspaceId: WORKSPACE_ID_SCHEMA,
1136
+ processId: z.string().describe("Process ID returned by start_process."),
1137
+ force: z.boolean().optional().describe("If true, forcibly kill the process tree."),
1138
+ },
1139
+ ...toolWidgetDescriptorMeta(config, "shell"),
1140
+ annotations: PROCESS_ANNOTATIONS,
1141
+ }, async ({ workspaceId, processId, force }) => {
1142
+ try {
1143
+ const result = await processManager.stopProcess({ workspaceId, processId, force });
1144
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1145
+ }
1146
+ catch (err) {
1147
+ const msg = err instanceof Error ? err.message : String(err);
1148
+ return { content: [{ type: "text", text: `stop_process failed: ${msg}` }], isError: true };
1149
+ }
1150
+ });
1151
+ // --- Project Discovery and Search Tools ---
1152
+ registerAppTool(server, "glob_files", {
1153
+ title: "Glob files",
1154
+ description: "Find files by glob pattern within the workspace. Respects common ignore directories. Returns workspace-relative paths.",
1155
+ inputSchema: {
1156
+ workspaceId: WORKSPACE_ID_SCHEMA,
1157
+ pattern: z.string().describe("Glob pattern such as *.ts or src/**/*.json."),
1158
+ basePath: z.string().optional().describe("Base directory relative to workspace root to search in."),
1159
+ maxResults: z.number().int().optional().describe("Maximum results (default 100, max 1000)."),
1160
+ },
1161
+ ...toolWidgetDescriptorMeta(config, "search"),
1162
+ annotations: READ_ONLY_ANNOTATIONS,
1163
+ }, async ({ workspaceId, pattern, basePath, maxResults }) => {
1164
+ try {
1165
+ const result = await globFiles(workspaces, { workspaceId, pattern, basePath, maxResults });
1166
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1167
+ }
1168
+ catch (err) {
1169
+ const msg = err instanceof Error ? err.message : String(err);
1170
+ return { content: [{ type: "text", text: `glob_files failed: ${msg}` }], isError: true };
1171
+ }
1172
+ });
1173
+ registerAppTool(server, "search_text", {
1174
+ title: "Search text",
1175
+ description: "Search for text across workspace files. Returns file path, line, column, match snippet, and context lines.",
1176
+ inputSchema: {
1177
+ workspaceId: WORKSPACE_ID_SCHEMA,
1178
+ query: z.string().describe("Text or pattern to search for."),
1179
+ paths: z.array(z.string()).optional().describe("Limit search to these workspace-relative paths."),
1180
+ filePattern: z.string().optional().describe("Filter by file extension pattern, e.g. *.ts."),
1181
+ caseSensitive: z.boolean().optional().describe("Default false."),
1182
+ maxResults: z.number().int().optional().describe("Max matches (default 50, max 500)."),
1183
+ },
1184
+ ...toolWidgetDescriptorMeta(config, "search"),
1185
+ annotations: READ_ONLY_ANNOTATIONS,
1186
+ }, async ({ workspaceId, query, paths, filePattern, caseSensitive, maxResults }) => {
1187
+ try {
1188
+ const result = await searchText(workspaces, { workspaceId, query, paths, filePattern, caseSensitive, maxResults });
1189
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1190
+ }
1191
+ catch (err) {
1192
+ const msg = err instanceof Error ? err.message : String(err);
1193
+ return { content: [{ type: "text", text: `search_text failed: ${msg}` }], isError: true };
1194
+ }
1195
+ });
1196
+ registerAppTool(server, "inspect_project", {
1197
+ title: "Inspect project",
1198
+ description: "Detect project type, package managers, frameworks, and recommended build/run/test commands. Supports Node.js, TypeScript, .NET, Godot, Python, and Git.",
1199
+ inputSchema: {
1200
+ workspaceId: WORKSPACE_ID_SCHEMA,
1201
+ path: z.string().optional().describe("Subdirectory to inspect, relative to workspace root."),
1202
+ },
1203
+ ...toolWidgetDescriptorMeta(config, "search"),
1204
+ annotations: READ_ONLY_ANNOTATIONS,
1205
+ }, async ({ workspaceId, path }) => {
1206
+ try {
1207
+ const result = await inspectProject(workspaces, { workspaceId, path });
1208
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1209
+ }
1210
+ catch (err) {
1211
+ const msg = err instanceof Error ? err.message : String(err);
1212
+ return { content: [{ type: "text", text: `inspect_project failed: ${msg}` }], isError: true };
1213
+ }
1214
+ });
1215
+ // --- Web Development Tools ---
1216
+ registerAppTool(server, "start_dev_server", {
1217
+ title: "Start dev server",
1218
+ description: "Launch a local web development server as a persistent process. Auto-detects the dev script from the project. Returns a processId for use with get_process_logs and stop_process.",
1219
+ inputSchema: {
1220
+ workspaceId: WORKSPACE_ID_SCHEMA,
1221
+ command: z.string().optional().describe("Override command to run (e.g. 'npm run dev'). Auto-detected if omitted."),
1222
+ workingDirectory: z.string().optional().describe("Working directory relative to workspace root."),
1223
+ port: z.number().int().optional().describe("Expected port for the dev server."),
1224
+ },
1225
+ ...toolWidgetDescriptorMeta(config, "shell"),
1226
+ annotations: PROCESS_ANNOTATIONS,
1227
+ }, async ({ workspaceId, command, workingDirectory, port }) => {
1228
+ try {
1229
+ const result = await startDevServer(workspaces, processManager, { workspaceId, command, workingDirectory, port });
1230
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1231
+ }
1232
+ catch (err) {
1233
+ const msg = err instanceof Error ? err.message : String(err);
1234
+ return { content: [{ type: "text", text: `start_dev_server failed: ${msg}` }], isError: true };
1235
+ }
1236
+ });
1237
+ registerAppTool(server, "capture_page_screenshot", {
1238
+ title: "Capture page screenshot",
1239
+ description: "Screenshot a local or remote web page using Playwright. Saves to workspace and returns as MCP image content. Blocks unsafe schemes and SSRF addresses.",
1240
+ inputSchema: {
1241
+ workspaceId: WORKSPACE_ID_SCHEMA,
1242
+ url: z.string().describe("Page URL (http/https only). Local dev URLs like localhost:3000 are allowed."),
1243
+ outputPath: z.string().describe("Workspace-relative path to save the screenshot PNG."),
1244
+ fullPage: z.boolean().optional().describe("Capture full scrollable page (default false)."),
1245
+ viewportWidth: z.number().int().optional().describe("Viewport width in pixels (default 1280, max 3840)."),
1246
+ viewportHeight: z.number().int().optional().describe("Viewport height in pixels (default 800, max 2160)."),
1247
+ waitUntil: z.enum(["load", "domcontentloaded", "networkidle"]).optional().describe("Page load event to wait for."),
1248
+ delayMs: z.number().int().optional().describe("Additional delay in ms after load (max 5000)."),
1249
+ },
1250
+ ...toolWidgetDescriptorMeta(config, "write"),
1251
+ annotations: WEB_WRITE_ANNOTATIONS,
1252
+ }, async ({ workspaceId, url, outputPath, fullPage, viewportWidth, viewportHeight, waitUntil, delayMs }) => {
1253
+ return capturePageScreenshot(workspaces, { workspaceId, url, outputPath, fullPage, viewportWidth, viewportHeight, waitUntil, delayMs });
1254
+ });
1255
+ registerAppTool(server, "inspect_page", {
1256
+ title: "Inspect page",
1257
+ description: "Inspect DOM structure, headings, buttons, links, and console errors of a web page using Playwright.",
1258
+ inputSchema: {
1259
+ workspaceId: WORKSPACE_ID_SCHEMA,
1260
+ url: z.string().describe("Page URL to inspect."),
1261
+ includeAccessibilityTree: z.boolean().optional().describe("Include accessibility tree."),
1262
+ includeComputedStyles: z.boolean().optional().describe("Include key computed styles."),
1263
+ maxElements: z.number().int().optional().describe("Max elements per category (default 20)."),
1264
+ },
1265
+ ...toolWidgetDescriptorMeta(config, "read"),
1266
+ annotations: WEB_READ_ANNOTATIONS,
1267
+ }, async ({ workspaceId, url, includeAccessibilityTree, includeComputedStyles, maxElements }) => {
1268
+ return inspectPage(workspaces, { workspaceId, url, includeAccessibilityTree, includeComputedStyles, maxElements });
1269
+ });
1270
+ // --- Image Inspection and Comparison Tools ---
1271
+ registerAppTool(server, "inspect_image", {
1272
+ title: "Inspect image",
1273
+ description: "Return format, dimensions, file size, alpha, and basic color info for a local image. Does not resize or recompress.",
1274
+ inputSchema: {
1275
+ workspaceId: WORKSPACE_ID_SCHEMA,
1276
+ path: z.string().describe("Workspace-relative image path."),
1277
+ },
1278
+ ...toolWidgetDescriptorMeta(config, "read"),
1279
+ annotations: READ_ONLY_ANNOTATIONS,
1280
+ }, async ({ workspaceId, path }) => inspectImage(workspaces, { workspaceId, path }));
1281
+ registerAppTool(server, "compare_images", {
1282
+ title: "Compare images",
1283
+ description: "Pixel-level comparison of two workspace images. Returns exact match percentage, changed pixel count, and optional diff PNG.",
1284
+ inputSchema: {
1285
+ workspaceId: WORKSPACE_ID_SCHEMA,
1286
+ referencePath: z.string().describe("Reference image path (workspace-relative)."),
1287
+ candidatePath: z.string().describe("Candidate image path to compare (workspace-relative)."),
1288
+ diffOutputPath: z.string().optional().describe("Workspace-relative path to save the diff image."),
1289
+ threshold: z.number().optional().describe("Per-channel tolerance (0–255, default 0)."),
1290
+ ignoreTransparentPixels: z.boolean().optional().describe("Skip fully transparent pixels in comparison."),
1291
+ },
1292
+ ...toolWidgetDescriptorMeta(config, "write"),
1293
+ annotations: MUTATING_ANNOTATIONS,
1294
+ }, async ({ workspaceId, referencePath, candidatePath, diffOutputPath, threshold, ignoreTransparentPixels }) => compareImages(workspaces, { workspaceId, referencePath, candidatePath, diffOutputPath, threshold, ignoreTransparentPixels }));
1295
+ registerAppTool(server, "inspect_sprite", {
1296
+ title: "Inspect sprite",
1297
+ description: "Analyze a sprite sheet or pixel-art image: dimensions, probable grid, palette, and frame bounding boxes.",
1298
+ inputSchema: {
1299
+ workspaceId: WORKSPACE_ID_SCHEMA,
1300
+ path: z.string().describe("Workspace-relative image path."),
1301
+ expectedCellWidth: z.number().int().optional().describe("Expected cell width for grid detection."),
1302
+ expectedCellHeight: z.number().int().optional().describe("Expected cell height for grid detection."),
1303
+ },
1304
+ ...toolWidgetDescriptorMeta(config, "read"),
1305
+ annotations: READ_ONLY_ANNOTATIONS,
1306
+ }, async ({ workspaceId, path, expectedCellWidth, expectedCellHeight }) => inspectSprite(workspaces, { workspaceId, path, expectedCellWidth, expectedCellHeight }));
1307
+ registerAppTool(server, "split_sprite_sheet", {
1308
+ title: "Split sprite sheet",
1309
+ description: "Split a sprite sheet into individual frame images saved inside the workspace. Does not overwrite existing files.",
1310
+ inputSchema: {
1311
+ workspaceId: WORKSPACE_ID_SCHEMA,
1312
+ path: z.string().describe("Workspace-relative sprite sheet path."),
1313
+ columns: z.number().int().describe("Number of columns."),
1314
+ rows: z.number().int().describe("Number of rows."),
1315
+ outputDirectory: z.string().describe("Workspace-relative output directory."),
1316
+ namingPattern: z.string().optional().describe("Frame filename pattern with {col} and {row} placeholders."),
1317
+ },
1318
+ ...toolWidgetDescriptorMeta(config, "write"),
1319
+ annotations: MUTATING_ANNOTATIONS,
1320
+ }, async ({ workspaceId, path, columns, rows, outputDirectory, namingPattern }) => splitSpriteSheet(workspaces, { workspaceId, path, columns, rows, outputDirectory, namingPattern }));
1321
+ // --- .NET Tools ---
1322
+ registerAppTool(server, "inspect_dotnet_project", {
1323
+ title: "Inspect .NET project",
1324
+ description: "Parse a .csproj or .fsproj file and return SDK style, target frameworks, packages, test framework, and project references.",
1325
+ inputSchema: {
1326
+ workspaceId: WORKSPACE_ID_SCHEMA,
1327
+ projectPath: z.string().describe("Workspace-relative path to .csproj or .fsproj."),
1328
+ },
1329
+ ...toolWidgetDescriptorMeta(config, "read"),
1330
+ annotations: READ_ONLY_ANNOTATIONS,
1331
+ }, async ({ workspaceId, projectPath }) => {
1332
+ try {
1333
+ const result = await inspectDotnetProject(workspaces, { workspaceId, projectPath });
1334
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1335
+ }
1336
+ catch (err) {
1337
+ const msg = err instanceof Error ? err.message : String(err);
1338
+ return { content: [{ type: "text", text: msg }], isError: true };
1339
+ }
1340
+ });
1341
+ registerAppTool(server, "dotnet_restore", {
1342
+ title: "dotnet restore",
1343
+ description: "Run dotnet restore on a .NET project. Returns success, duration, and key output.",
1344
+ inputSchema: {
1345
+ workspaceId: WORKSPACE_ID_SCHEMA,
1346
+ projectPath: z.string().describe("Workspace-relative path to .csproj/.fsproj/.sln."),
1347
+ },
1348
+ ...toolWidgetDescriptorMeta(config, "shell"),
1349
+ annotations: PROCESS_ANNOTATIONS,
1350
+ }, async ({ workspaceId, projectPath }) => {
1351
+ try {
1352
+ const result = await dotnetRestore(workspaces, { workspaceId, projectPath });
1353
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1354
+ }
1355
+ catch (err) {
1356
+ const msg = err instanceof Error ? err.message : String(err);
1357
+ return { content: [{ type: "text", text: msg }], isError: true };
1358
+ }
1359
+ });
1360
+ registerAppTool(server, "dotnet_build", {
1361
+ title: "dotnet build",
1362
+ description: "Build a .NET project. Returns structured errors, warnings, and duration. Does not return raw build output.",
1363
+ inputSchema: {
1364
+ workspaceId: WORKSPACE_ID_SCHEMA,
1365
+ projectPath: z.string().describe("Workspace-relative path to .csproj/.fsproj/.sln."),
1366
+ configuration: z.enum(["Debug", "Release"]).optional().describe("Build configuration."),
1367
+ noRestore: z.boolean().optional().describe("Skip restore step."),
1368
+ },
1369
+ ...toolWidgetDescriptorMeta(config, "shell"),
1370
+ annotations: PROCESS_ANNOTATIONS,
1371
+ }, async ({ workspaceId, projectPath, configuration, noRestore }) => {
1372
+ try {
1373
+ const result = await dotnetBuild(workspaces, { workspaceId, projectPath, configuration, noRestore });
1374
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1375
+ }
1376
+ catch (err) {
1377
+ const msg = err instanceof Error ? err.message : String(err);
1378
+ return { content: [{ type: "text", text: msg }], isError: true };
1379
+ }
1380
+ });
1381
+ registerAppTool(server, "dotnet_test", {
1382
+ title: "dotnet test",
1383
+ description: "Run .NET tests. Returns pass/fail/skip counts, failed test names, and assertion messages.",
1384
+ inputSchema: {
1385
+ workspaceId: WORKSPACE_ID_SCHEMA,
1386
+ projectPath: z.string().describe("Workspace-relative path to test .csproj/.sln."),
1387
+ configuration: z.enum(["Debug", "Release"]).optional(),
1388
+ filter: z.string().optional().describe("Test filter expression."),
1389
+ noBuild: z.boolean().optional(),
1390
+ },
1391
+ ...toolWidgetDescriptorMeta(config, "shell"),
1392
+ annotations: PROCESS_ANNOTATIONS,
1393
+ }, async ({ workspaceId, projectPath, configuration, filter, noBuild }) => {
1394
+ try {
1395
+ const result = await dotnetTest(workspaces, { workspaceId, projectPath, configuration, filter, noBuild });
1396
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1397
+ }
1398
+ catch (err) {
1399
+ const msg = err instanceof Error ? err.message : String(err);
1400
+ return { content: [{ type: "text", text: msg }], isError: true };
1401
+ }
1402
+ });
1403
+ registerAppTool(server, "dotnet_run", {
1404
+ title: "dotnet run",
1405
+ description: "Run a .NET project as a persistent process. Returns a processId. Use get_process_logs to tail output.",
1406
+ inputSchema: {
1407
+ workspaceId: WORKSPACE_ID_SCHEMA,
1408
+ projectPath: z.string().describe("Workspace-relative path to .csproj."),
1409
+ configuration: z.enum(["Debug", "Release"]).optional(),
1410
+ arguments: z.array(z.string()).optional().describe("Arguments to pass to the application."),
1411
+ environment: z.record(z.string(), z.string()).optional().describe("Environment variables."),
1412
+ },
1413
+ ...toolWidgetDescriptorMeta(config, "shell"),
1414
+ annotations: PROCESS_ANNOTATIONS,
1415
+ }, async ({ workspaceId, projectPath, configuration, arguments: args, environment }) => {
1416
+ try {
1417
+ const result = await dotnetRun(workspaces, processManager, { workspaceId, projectPath, configuration, arguments: args, environment });
1418
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1419
+ }
1420
+ catch (err) {
1421
+ const msg = err instanceof Error ? err.message : String(err);
1422
+ return { content: [{ type: "text", text: msg }], isError: true };
1423
+ }
1424
+ });
1425
+ registerAppTool(server, "dotnet_format", {
1426
+ title: "dotnet format",
1427
+ description: "Format a .NET project. Use verifyOnly=true to check without modifying files.",
1428
+ inputSchema: {
1429
+ workspaceId: WORKSPACE_ID_SCHEMA,
1430
+ projectPath: z.string().describe("Workspace-relative path to .csproj/.sln."),
1431
+ verifyOnly: z.boolean().optional().describe("If true, check formatting without modifying files."),
1432
+ },
1433
+ ...toolWidgetDescriptorMeta(config, "shell"),
1434
+ annotations: MUTATING_ANNOTATIONS,
1435
+ }, async ({ workspaceId, projectPath, verifyOnly }) => {
1436
+ try {
1437
+ const result = await dotnetFormat(workspaces, { workspaceId, projectPath, verifyOnly });
1438
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1439
+ }
1440
+ catch (err) {
1441
+ const msg = err instanceof Error ? err.message : String(err);
1442
+ return { content: [{ type: "text", text: msg }], isError: true };
1443
+ }
1444
+ });
1445
+ // --- Godot Tools ---
1446
+ registerAppTool(server, "detect_godot_project", {
1447
+ title: "Detect Godot project",
1448
+ description: "Detect a Godot 4 project in the workspace. Reads project.godot for name, main scene, renderer, features, autoloads, and input actions.",
1449
+ inputSchema: {
1450
+ workspaceId: WORKSPACE_ID_SCHEMA,
1451
+ path: z.string().optional().describe("Subdirectory to search, relative to workspace root."),
1452
+ },
1453
+ ...toolWidgetDescriptorMeta(config, "read"),
1454
+ annotations: READ_ONLY_ANNOTATIONS,
1455
+ }, async ({ workspaceId, path }) => {
1456
+ try {
1457
+ const result = await detectGodotProject(workspaces, { workspaceId, path });
1458
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1459
+ }
1460
+ catch (err) {
1461
+ const msg = err instanceof Error ? err.message : String(err);
1462
+ return { content: [{ type: "text", text: msg }], isError: true };
1463
+ }
1464
+ });
1465
+ registerAppTool(server, "godot_run", {
1466
+ title: "Run Godot",
1467
+ description: "Launch a Godot 4 game or editor as a persistent process. Returns a processId. Configure Godot executable via GODOT_EXECUTABLE env var.",
1468
+ inputSchema: {
1469
+ workspaceId: WORKSPACE_ID_SCHEMA,
1470
+ projectPath: z.string().describe("Workspace-relative path to the project directory (containing project.godot)."),
1471
+ scenePath: z.string().optional().describe("Workspace-relative scene path to run."),
1472
+ editor: z.boolean().optional().describe("Open Godot editor instead of running game."),
1473
+ debug: z.boolean().optional().describe("Enable debug mode."),
1474
+ additionalArguments: z.array(z.string()).optional().describe("Additional CLI arguments."),
1475
+ },
1476
+ ...toolWidgetDescriptorMeta(config, "shell"),
1477
+ annotations: PROCESS_ANNOTATIONS,
1478
+ }, async ({ workspaceId, projectPath, scenePath, editor, debug, additionalArguments }) => {
1479
+ try {
1480
+ const result = await godotRun(workspaces, processManager, { workspaceId, projectPath, scenePath, editor, debug, additionalArguments });
1481
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1482
+ }
1483
+ catch (err) {
1484
+ const msg = err instanceof Error ? err.message : String(err);
1485
+ return { content: [{ type: "text", text: msg }], isError: true };
1486
+ }
1487
+ });
1488
+ registerAppTool(server, "inspect_godot_scene", {
1489
+ title: "Inspect Godot scene",
1490
+ description: "Parse a Godot .tscn text scene file. Returns node tree, types, external resources, signals, and properties. Binary .scn files are not supported.",
1491
+ inputSchema: {
1492
+ workspaceId: WORKSPACE_ID_SCHEMA,
1493
+ scenePath: z.string().describe("Workspace-relative path to .tscn scene file."),
1494
+ },
1495
+ ...toolWidgetDescriptorMeta(config, "read"),
1496
+ annotations: READ_ONLY_ANNOTATIONS,
1497
+ }, async ({ workspaceId, scenePath }) => {
1498
+ try {
1499
+ const result = await inspectGodotScene(workspaces, { workspaceId, scenePath });
1500
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1501
+ }
1502
+ catch (err) {
1503
+ const msg = err instanceof Error ? err.message : String(err);
1504
+ return { content: [{ type: "text", text: msg }], isError: true };
1505
+ }
1506
+ });
1507
+ // --- Window Capture Tools ---
1508
+ registerAppTool(server, "capture_window", {
1509
+ title: "Capture window",
1510
+ description: "Capture a running application window tracked by Auvrynt. Returns the screenshot as MCP image content and saves it in the workspace. Windows only.",
1511
+ inputSchema: {
1512
+ workspaceId: WORKSPACE_ID_SCHEMA,
1513
+ processId: z.string().optional().describe("Process ID from start_process to target."),
1514
+ windowTitle: z.string().optional().describe("Window title substring to target."),
1515
+ outputPath: z.string().describe("Workspace-relative path to save the screenshot PNG."),
1516
+ },
1517
+ ...toolWidgetDescriptorMeta(config, "write"),
1518
+ annotations: MUTATING_ANNOTATIONS,
1519
+ }, async ({ workspaceId, processId, windowTitle, outputPath }) => captureWindow(workspaces, processManager, { workspaceId, processId, windowTitle, outputPath }));
1520
+ registerAppTool(server, "godot_capture_game", {
1521
+ title: "Capture Godot game",
1522
+ description: "Capture a screenshot of a running Godot game tracked by a Auvrynt processId. Wraps capture_window.",
1523
+ inputSchema: {
1524
+ workspaceId: WORKSPACE_ID_SCHEMA,
1525
+ processId: z.string().describe("Process ID from godot_run."),
1526
+ outputPath: z.string().describe("Workspace-relative path to save the screenshot PNG."),
1527
+ },
1528
+ ...toolWidgetDescriptorMeta(config, "write"),
1529
+ annotations: MUTATING_ANNOTATIONS,
1530
+ }, async ({ workspaceId, processId, outputPath }) => captureWindow(workspaces, processManager, { workspaceId, processId, outputPath }));
1531
+ // ──────────────────────────────────────────────────────────────────────────
1532
+ // GODOT C# / .NET TOOLS
1533
+ // ──────────────────────────────────────────────────────────────────────────
1534
+ // Phase 1 & 2: Environment + Project Inspection
1535
+ registerAppTool(server, "godot_dotnet_environment", {
1536
+ title: "Godot .NET environment",
1537
+ description: "Detect and validate the Godot .NET editor, .NET SDK, architecture, and project target frameworks. Run this before the first C# build.",
1538
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, projectPath: z.string().optional() },
1539
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
1540
+ }, async ({ workspaceId, projectPath }) => {
1541
+ try {
1542
+ return { content: [{ type: "text", text: JSON.stringify(await inspectGodotDotnetEnvironment(workspaces, { workspaceId, projectPath }), null, 2) }] };
1543
+ }
1544
+ catch (err) {
1545
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1546
+ }
1547
+ });
1548
+ registerAppTool(server, "inspect_godot_dotnet_project", {
1549
+ title: "Inspect Godot .NET project",
1550
+ description: "Inspect a Godot 4 C# project: project.godot, .csproj, .sln, target frameworks, Godot.NET.Sdk, autoloads, input actions, C# scripts, and whether solution regeneration is needed.",
1551
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, projectPath: z.string().optional() },
1552
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
1553
+ }, async ({ workspaceId, projectPath }) => {
1554
+ try {
1555
+ return { content: [{ type: "text", text: JSON.stringify(await inspectGodotDotnetProject(workspaces, { workspaceId, projectPath }), null, 2) }] };
1556
+ }
1557
+ catch (err) {
1558
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1559
+ }
1560
+ });
1561
+ // Phase 3: Solution generation, restore, build, clean
1562
+ registerAppTool(server, "godot_build_solutions", {
1563
+ title: "Godot build solutions",
1564
+ description: "Run Godot --build-solutions to generate or regenerate the .sln and .csproj for a C# project. Run after adding C# files.",
1565
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, projectPath: z.string() },
1566
+ ...toolWidgetDescriptorMeta(config, "shell"), annotations: PROCESS_ANNOTATIONS,
1567
+ }, async ({ workspaceId, projectPath }) => {
1568
+ try {
1569
+ return { content: [{ type: "text", text: JSON.stringify(await godotBuildSolutions(workspaces, { workspaceId, projectPath }), null, 2) }] };
1570
+ }
1571
+ catch (err) {
1572
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1573
+ }
1574
+ });
1575
+ registerAppTool(server, "godot_dotnet_restore", {
1576
+ title: "dotnet restore (Godot)",
1577
+ description: "Restore NuGet packages for a Godot C# project. Credentials and secrets are redacted.",
1578
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, projectPath: z.string(), lockedMode: z.boolean().optional() },
1579
+ ...toolWidgetDescriptorMeta(config, "shell"), annotations: PROCESS_ANNOTATIONS,
1580
+ }, async ({ workspaceId, projectPath, lockedMode }) => {
1581
+ try {
1582
+ return { content: [{ type: "text", text: JSON.stringify(await godotDotnetRestore(workspaces, { workspaceId, projectPath, lockedMode }), null, 2) }] };
1583
+ }
1584
+ catch (err) {
1585
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1586
+ }
1587
+ });
1588
+ registerAppTool(server, "godot_dotnet_build", {
1589
+ title: "dotnet build (Godot)",
1590
+ description: "Build a Godot C# project. Returns structured C# compiler errors, MSBuild errors, and Godot source-generator issues.",
1591
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, projectPath: z.string(), configuration: z.enum(["Debug", "Release"]).optional(), noRestore: z.boolean().optional() },
1592
+ ...toolWidgetDescriptorMeta(config, "shell"), annotations: PROCESS_ANNOTATIONS,
1593
+ }, async ({ workspaceId, projectPath, configuration, noRestore }) => {
1594
+ try {
1595
+ return { content: [{ type: "text", text: JSON.stringify(await godotDotnetBuild(workspaces, { workspaceId, projectPath, configuration, noRestore }), null, 2) }] };
1596
+ }
1597
+ catch (err) {
1598
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1599
+ }
1600
+ });
1601
+ registerAppTool(server, "godot_dotnet_clean", {
1602
+ title: "dotnet clean (Godot)",
1603
+ description: "Remove Godot C# build outputs (bin/ and obj/). Mutating.",
1604
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, projectPath: z.string(), configuration: z.enum(["Debug", "Release"]).optional() },
1605
+ ...toolWidgetDescriptorMeta(config, "shell"), annotations: MUTATING_ANNOTATIONS,
1606
+ }, async ({ workspaceId, projectPath, configuration }) => {
1607
+ try {
1608
+ return { content: [{ type: "text", text: JSON.stringify(await godotDotnetClean(workspaces, { workspaceId, projectPath, configuration }), null, 2) }] };
1609
+ }
1610
+ catch (err) {
1611
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1612
+ }
1613
+ });
1614
+ // Phase 4 & 5: Running + Runtime Logs
1615
+ registerAppTool(server, "godot_run_project", {
1616
+ title: "Run Godot project",
1617
+ description: "Run a Godot C# project as a persistent process. Returns a processId. Use godot_get_runtime_logs to inspect output.",
1618
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, projectPath: z.string(), debug: z.boolean().optional(), windowed: z.boolean().optional(), resolution: z.object({ width: z.number().int(), height: z.number().int() }).optional(), additionalGodotArguments: z.array(z.string()).optional(), userArguments: z.array(z.string()).optional() },
1619
+ ...toolWidgetDescriptorMeta(config, "shell"), annotations: PROCESS_ANNOTATIONS,
1620
+ }, async ({ workspaceId, projectPath, debug, windowed, resolution, additionalGodotArguments, userArguments }) => {
1621
+ try {
1622
+ return { content: [{ type: "text", text: JSON.stringify(await godotRunProject(workspaces, processManager, { workspaceId, projectPath, debug, windowed, resolution, additionalGodotArguments, userArguments }), null, 2) }] };
1623
+ }
1624
+ catch (err) {
1625
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1626
+ }
1627
+ });
1628
+ registerAppTool(server, "godot_run_scene", {
1629
+ title: "Run Godot scene",
1630
+ description: "Run a single .tscn scene in a Godot C# project as a persistent process.",
1631
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, projectPath: z.string(), scenePath: z.string(), debug: z.boolean().optional() },
1632
+ ...toolWidgetDescriptorMeta(config, "shell"), annotations: PROCESS_ANNOTATIONS,
1633
+ }, async ({ workspaceId, projectPath, scenePath, debug }) => {
1634
+ try {
1635
+ return { content: [{ type: "text", text: JSON.stringify(await godotRunScene(workspaces, processManager, { workspaceId, projectPath, scenePath, debug }), null, 2) }] };
1636
+ }
1637
+ catch (err) {
1638
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1639
+ }
1640
+ });
1641
+ registerAppTool(server, "godot_stop", {
1642
+ title: "Stop Godot process",
1643
+ description: "Gracefully stop a Godot process tracked by Auvrynt for this workspace.",
1644
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, processId: z.string() },
1645
+ ...toolWidgetDescriptorMeta(config, "shell"), annotations: PROCESS_ANNOTATIONS,
1646
+ }, async ({ workspaceId, processId }) => {
1647
+ try {
1648
+ return { content: [{ type: "text", text: JSON.stringify(await processManager.stopProcess({ workspaceId, processId }), null, 2) }] };
1649
+ }
1650
+ catch (err) {
1651
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1652
+ }
1653
+ });
1654
+ registerAppTool(server, "godot_get_runtime_logs", {
1655
+ title: "Get Godot runtime logs",
1656
+ description: "Get structured and categorized runtime logs from a running Godot C# process. Groups C# exceptions, stack traces, Godot errors, and GD.Print output.",
1657
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, processId: z.string(), severity: z.array(z.enum(["error", "warning", "info", "print"])).optional(), lines: z.number().int().optional() },
1658
+ ...toolWidgetDescriptorMeta(config, "shell"), annotations: READ_ONLY_ANNOTATIONS,
1659
+ }, async ({ workspaceId, processId, severity, lines }) => {
1660
+ try {
1661
+ return { content: [{ type: "text", text: JSON.stringify(getGodotRuntimeLogs(processManager, { workspaceId, processId, severity, lines }), null, 2) }] };
1662
+ }
1663
+ catch (err) {
1664
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1665
+ }
1666
+ });
1667
+ // Phase 6: Validation + Import
1668
+ registerAppTool(server, "godot_validate_project", {
1669
+ title: "Validate Godot project",
1670
+ description: "Headless Godot project validation: optionally build C#, detect import errors, missing resources, invalid scripts, and plugin failures.",
1671
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, projectPath: z.string(), buildCsharpFirst: z.boolean().optional() },
1672
+ ...toolWidgetDescriptorMeta(config, "shell"), annotations: PROCESS_ANNOTATIONS,
1673
+ }, async ({ workspaceId, projectPath, buildCsharpFirst }) => {
1674
+ try {
1675
+ return { content: [{ type: "text", text: JSON.stringify(await godotValidateProject(workspaces, { workspaceId, projectPath, buildCsharpFirst }), null, 2) }] };
1676
+ }
1677
+ catch (err) {
1678
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1679
+ }
1680
+ });
1681
+ registerAppTool(server, "godot_import_assets", {
1682
+ title: "Godot import assets",
1683
+ description: "Run Godot in headless editor mode to import pending assets.",
1684
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, projectPath: z.string() },
1685
+ ...toolWidgetDescriptorMeta(config, "shell"), annotations: PROCESS_ANNOTATIONS,
1686
+ }, async ({ workspaceId, projectPath }) => {
1687
+ try {
1688
+ return { content: [{ type: "text", text: JSON.stringify(await godotImportAssets(workspaces, { workspaceId, projectPath }), null, 2) }] };
1689
+ }
1690
+ catch (err) {
1691
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1692
+ }
1693
+ });
1694
+ // Phase 7: Editor Bridge
1695
+ registerAppTool(server, "godot_editor_connect", {
1696
+ title: "Connect Godot editor bridge",
1697
+ description: "Connect Auvrynt to the Godot editor bridge plugin. Enables live scene-tree inspection, property editing, node mutation, and UndoRedo.",
1698
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, projectPath: z.string(), host: z.string().optional(), port: z.number().int().optional(), token: z.string().optional() },
1699
+ ...toolWidgetDescriptorMeta(config, "shell"), annotations: PROCESS_ANNOTATIONS,
1700
+ }, async ({ workspaceId, projectPath, host, port, token }) => {
1701
+ try {
1702
+ return { content: [{ type: "text", text: JSON.stringify(await godotEditorConnect(workspaces, { workspaceId, projectPath, host, port, token }), null, 2) }] };
1703
+ }
1704
+ catch (err) {
1705
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1706
+ }
1707
+ });
1708
+ registerAppTool(server, "godot_editor_status", {
1709
+ title: "Godot editor bridge status",
1710
+ description: "Return current Godot editor bridge connection status.",
1711
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA },
1712
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
1713
+ }, async ({ workspaceId }) => {
1714
+ try {
1715
+ return { content: [{ type: "text", text: JSON.stringify(await godotEditorStatus({ workspaceId }), null, 2) }] };
1716
+ }
1717
+ catch (err) {
1718
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1719
+ }
1720
+ });
1721
+ registerAppTool(server, "godot_editor_disconnect", {
1722
+ title: "Disconnect Godot editor bridge",
1723
+ description: "Disconnect Auvrynt from the Godot editor bridge. Does not close the editor.",
1724
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA },
1725
+ ...toolWidgetDescriptorMeta(config, "shell"), annotations: PROCESS_ANNOTATIONS,
1726
+ }, async ({ workspaceId }) => {
1727
+ return { content: [{ type: "text", text: JSON.stringify(godotEditorDisconnect({ workspaceId }), null, 2) }] };
1728
+ });
1729
+ // Phase 8: Scene Tree Inspection (requires editor bridge)
1730
+ registerAppTool(server, "godot_get_scene_tree", {
1731
+ title: "Get Godot scene tree",
1732
+ description: "Get the edited or remote runtime scene tree via the Godot editor bridge.",
1733
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, mode: z.enum(["edited", "remote"]).optional(), maxDepth: z.number().int().optional() },
1734
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
1735
+ }, async ({ workspaceId, mode, maxDepth }) => {
1736
+ try {
1737
+ const client = getBridgeClient(workspaceId);
1738
+ const result = mode === "remote"
1739
+ ? await getRemoteSceneTree({ workspaceId, maxDepth }, client)
1740
+ : await client.sendRequest("scene.get_tree", { maxDepth });
1741
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1742
+ }
1743
+ catch (err) {
1744
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1745
+ }
1746
+ });
1747
+ // Phase 9: Runtime Properties
1748
+ registerAppTool(server, "godot_get_runtime_property", {
1749
+ title: "Get runtime node property",
1750
+ description: "Read a live runtime property from a node in the running Godot game via the editor bridge.",
1751
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, nodePath: z.string(), property: z.string() },
1752
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
1753
+ }, async ({ workspaceId, nodePath, property }) => {
1754
+ try {
1755
+ const client = getBridgeClient(workspaceId);
1756
+ return { content: [{ type: "text", text: JSON.stringify(await getRuntimeProperty({ workspaceId, nodePath, property }, client), null, 2) }] };
1757
+ }
1758
+ catch (err) {
1759
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1760
+ }
1761
+ });
1762
+ registerAppTool(server, "godot_get_performance_monitors", {
1763
+ title: "Godot performance monitors",
1764
+ description: "Get FPS, physics time, memory, node count, draw calls, and orphan node count from the running game via the editor bridge.",
1765
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA },
1766
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
1767
+ }, async ({ workspaceId }) => {
1768
+ try {
1769
+ const client = getBridgeClient(workspaceId);
1770
+ return { content: [{ type: "text", text: JSON.stringify(await getPerformanceMonitors({ workspaceId }, client), null, 2) }] };
1771
+ }
1772
+ catch (err) {
1773
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1774
+ }
1775
+ });
1776
+ // Phase 10: C# Semantic Tools
1777
+ registerAppTool(server, "godot_find_csharp_class", {
1778
+ title: "Find C# class",
1779
+ description: "Locate a C# class in the workspace. Returns file path, base class, partial status, namespace, [Export] properties, [Signal] delegates, and lifecycle overrides.",
1780
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, className: z.string() },
1781
+ ...toolWidgetDescriptorMeta(config, "search"), annotations: READ_ONLY_ANNOTATIONS,
1782
+ }, async ({ workspaceId, className }) => {
1783
+ try {
1784
+ return { content: [{ type: "text", text: JSON.stringify(await findCsharpClasses(workspaces, { workspaceId, className }), null, 2) }] };
1785
+ }
1786
+ catch (err) {
1787
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1788
+ }
1789
+ });
1790
+ registerAppTool(server, "godot_get_exported_properties", {
1791
+ title: "Get exported C# properties",
1792
+ description: "List all [Export] properties on a C# script file.",
1793
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, scriptPath: z.string() },
1794
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
1795
+ }, async ({ workspaceId, scriptPath }) => {
1796
+ try {
1797
+ return { content: [{ type: "text", text: JSON.stringify(await getExportedProperties(workspaces, { workspaceId, scriptPath }), null, 2) }] };
1798
+ }
1799
+ catch (err) {
1800
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1801
+ }
1802
+ });
1803
+ registerAppTool(server, "godot_get_csharp_diagnostics", {
1804
+ title: "Get C# diagnostics",
1805
+ description: "Scan C# scripts for known anti-patterns and style issues within the workspace.",
1806
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, projectPath: z.string() },
1807
+ ...toolWidgetDescriptorMeta(config, "search"), annotations: READ_ONLY_ANNOTATIONS,
1808
+ }, async ({ workspaceId, projectPath }) => {
1809
+ try {
1810
+ return { content: [{ type: "text", text: JSON.stringify(await getCsharpDiagnostics(workspaces, { workspaceId, projectPath }), null, 2) }] };
1811
+ }
1812
+ catch (err) {
1813
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1814
+ }
1815
+ });
1816
+ // Phase 11: Project Settings & Input Map
1817
+ registerAppTool(server, "godot_get_project_settings", {
1818
+ title: "Get project settings",
1819
+ description: "Read all settings from project.godot.",
1820
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, projectPath: z.string() },
1821
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
1822
+ }, async ({ workspaceId, projectPath }) => {
1823
+ try {
1824
+ return { content: [{ type: "text", text: JSON.stringify(await getProjectSettings(workspaces, { workspaceId, projectPath }), null, 2) }] };
1825
+ }
1826
+ catch (err) {
1827
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1828
+ }
1829
+ });
1830
+ registerAppTool(server, "godot_get_input_map", {
1831
+ title: "Get input map",
1832
+ description: "Return all input actions from the project.godot input map.",
1833
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, projectPath: z.string() },
1834
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
1835
+ }, async ({ workspaceId, projectPath }) => {
1836
+ try {
1837
+ return { content: [{ type: "text", text: JSON.stringify(await getInputMap(workspaces, { workspaceId, projectPath }), null, 2) }] };
1838
+ }
1839
+ catch (err) {
1840
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1841
+ }
1842
+ });
1843
+ registerAppTool(server, "godot_get_autoloads", {
1844
+ title: "Get autoloads",
1845
+ description: "Return all autoloads from project.godot.",
1846
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, projectPath: z.string() },
1847
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
1848
+ }, async ({ workspaceId, projectPath }) => {
1849
+ try {
1850
+ return { content: [{ type: "text", text: JSON.stringify(await getAutoloads(workspaces, { workspaceId, projectPath }), null, 2) }] };
1851
+ }
1852
+ catch (err) {
1853
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1854
+ }
1855
+ });
1856
+ // Phase 12: Pixel Art Import
1857
+ registerAppTool(server, "godot_apply_pixel_art_import_preset", {
1858
+ title: "Apply pixel art import preset",
1859
+ description: "Apply nearest-neighbour / lossless import settings to specified texture paths. Does not affect unspecified textures.",
1860
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, paths: z.array(z.string()) },
1861
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
1862
+ }, async ({ workspaceId, paths }) => {
1863
+ try {
1864
+ return { content: [{ type: "text", text: JSON.stringify(await applyPixelArtImportPreset(workspaces, { workspaceId, paths }), null, 2) }] };
1865
+ }
1866
+ catch (err) {
1867
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1868
+ }
1869
+ });
1870
+ // Phase 13: VS Code Config
1871
+ registerAppTool(server, "godot_generate_vscode_config", {
1872
+ title: "Generate VS Code config",
1873
+ description: "Generate or update .vscode/tasks.json and .vscode/launch.json for Godot C# development. Preserves existing entries.",
1874
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, projectPath: z.string(), godotExecutable: z.string().optional() },
1875
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
1876
+ }, async ({ workspaceId, projectPath, godotExecutable }) => {
1877
+ try {
1878
+ return { content: [{ type: "text", text: JSON.stringify(await generateVscodeConfig(workspaces, { workspaceId, projectPath, godotExecutable }), null, 2) }] };
1879
+ }
1880
+ catch (err) {
1881
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1882
+ }
1883
+ });
1884
+ // Phase 14: Export
1885
+ registerAppTool(server, "godot_list_export_presets", {
1886
+ title: "List export presets",
1887
+ description: "List Godot export presets from export_presets.cfg. Never returns secret credentials.",
1888
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, projectPath: z.string() },
1889
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
1890
+ }, async ({ workspaceId, projectPath }) => {
1891
+ try {
1892
+ return { content: [{ type: "text", text: JSON.stringify(await listExportPresets(workspaces, { workspaceId, projectPath }), null, 2) }] };
1893
+ }
1894
+ catch (err) {
1895
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1896
+ }
1897
+ });
1898
+ registerAppTool(server, "godot_export_project", {
1899
+ title: "Export Godot project",
1900
+ description: "Export a Godot project using a named preset. Output must be inside the workspace. Credentials are never exposed.",
1901
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, projectPath: z.string(), preset: z.string(), outputPath: z.string(), mode: z.enum(["debug", "release"]).optional() },
1902
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
1903
+ }, async ({ workspaceId, projectPath, preset, outputPath, mode }) => {
1904
+ try {
1905
+ return { content: [{ type: "text", text: JSON.stringify(await exportGodotProject(workspaces, { workspaceId, projectPath, preset, outputPath, mode }), null, 2) }] };
1906
+ }
1907
+ catch (err) {
1908
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1909
+ }
1910
+ });
1911
+ // Phase 15: Runtime Assertions & Test Sequence
1912
+ registerAppTool(server, "godot_assert_node_exists", {
1913
+ title: "Assert node exists",
1914
+ description: "Assert that a node exists in the remote runtime scene tree via the editor bridge.",
1915
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, nodePath: z.string() },
1916
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
1917
+ }, async ({ workspaceId, nodePath }) => {
1918
+ try {
1919
+ const client = getBridgeClient(workspaceId);
1920
+ return { content: [{ type: "text", text: JSON.stringify(await godotAssertNodeExists({ workspaceId, nodePath }, client), null, 2) }] };
1921
+ }
1922
+ catch (err) {
1923
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1924
+ }
1925
+ });
1926
+ registerAppTool(server, "godot_assert_property", {
1927
+ title: "Assert runtime property",
1928
+ description: "Assert a runtime node property satisfies a comparison (eq/neq/gt/lt/approx/contains/exists).",
1929
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, nodePath: z.string(), property: z.string(), comparison: z.enum(["eq", "neq", "gt", "lt", "approx", "contains", "exists", "changed"]), expected: z.unknown() },
1930
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
1931
+ }, async ({ workspaceId, nodePath, property, comparison, expected }) => {
1932
+ try {
1933
+ const client = getBridgeClient(workspaceId);
1934
+ return { content: [{ type: "text", text: JSON.stringify(await godotAssertProperty({ workspaceId, nodePath, property, comparison, expected }, client), null, 2) }] };
1935
+ }
1936
+ catch (err) {
1937
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1938
+ }
1939
+ });
1940
+ registerAppTool(server, "godot_run_test_sequence", {
1941
+ title: "Run Godot test sequence",
1942
+ description: "Execute a bounded sequence of gameplay steps (press_action, wait, screenshot, assert_property, assert_node_exists, assert_no_errors). Max 50 steps, 30s total.",
1943
+ inputSchema: {
1944
+ workspaceId: WORKSPACE_ID_SCHEMA,
1945
+ processId: z.string(),
1946
+ steps: z.array(z.object({
1947
+ type: z.string(),
1948
+ action: z.string().optional(),
1949
+ durationMs: z.number().optional(),
1950
+ outputPath: z.string().optional(),
1951
+ nodePath: z.string().optional(),
1952
+ property: z.string().optional(),
1953
+ comparison: z.string().optional(),
1954
+ expected: z.unknown().optional(),
1955
+ })),
1956
+ },
1957
+ ...toolWidgetDescriptorMeta(config, "shell"), annotations: PROCESS_ANNOTATIONS,
1958
+ }, async ({ workspaceId, processId, steps }) => {
1959
+ try {
1960
+ const client = getBridgeClient(workspaceId);
1961
+ return { content: [{ type: "text", text: JSON.stringify(await godotRunTestSequence(workspaces, processManager, { workspaceId, processId, steps: steps }, client), null, 2) }] };
1962
+ }
1963
+ catch (err) {
1964
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1965
+ }
1966
+ });
1967
+ // Phase 16: Input Simulation
1968
+ registerAppTool(server, "godot_press_action", {
1969
+ title: "Press Godot input action",
1970
+ description: "Inject a Godot input action press via the runtime bridge. Only targets Auvrynt-tracked game processes.",
1971
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, processId: z.string(), action: z.string(), durationMs: z.number().optional() },
1972
+ ...toolWidgetDescriptorMeta(config, "shell"), annotations: PROCESS_ANNOTATIONS,
1973
+ }, async ({ workspaceId, processId, action, durationMs }) => {
1974
+ try {
1975
+ const client = getBridgeClient(workspaceId);
1976
+ return { content: [{ type: "text", text: JSON.stringify(await godotPressAction({ workspaceId, processId, action, durationMs }, client), null, 2) }] };
1977
+ }
1978
+ catch (err) {
1979
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1980
+ }
1981
+ });
1982
+ registerAppTool(server, "godot_release_action", {
1983
+ title: "Release Godot input action",
1984
+ description: "Inject a Godot input action release via the runtime bridge. Only targets Auvrynt-tracked game processes.",
1985
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, processId: z.string(), action: z.string() },
1986
+ ...toolWidgetDescriptorMeta(config, "shell"), annotations: PROCESS_ANNOTATIONS,
1987
+ }, async ({ workspaceId, processId, action }) => {
1988
+ try {
1989
+ const client = getBridgeClient(workspaceId);
1990
+ return { content: [{ type: "text", text: JSON.stringify(await godotReleaseAction({ workspaceId, processId, action }, client), null, 2) }] };
1991
+ }
1992
+ catch (err) {
1993
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
1994
+ }
1995
+ });
1996
+ registerAppTool(server, "godot_mouse_click", {
1997
+ title: "Simulate Godot mouse click",
1998
+ description: "Inject a mouse click event at viewport coordinates via the runtime bridge. Only targets Auvrynt-tracked game processes.",
1999
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, processId: z.string(), x: z.number(), y: z.number(), button: z.number().optional() },
2000
+ ...toolWidgetDescriptorMeta(config, "shell"), annotations: PROCESS_ANNOTATIONS,
2001
+ }, async ({ workspaceId, processId, x, y, button }) => {
2002
+ try {
2003
+ const client = getBridgeClient(workspaceId);
2004
+ return { content: [{ type: "text", text: JSON.stringify(await godotMouseClick({ workspaceId, processId, x, y, button }, client), null, 2) }] };
2005
+ }
2006
+ catch (err) {
2007
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2008
+ }
2009
+ });
2010
+ registerAppTool(server, "godot_parse_csharp_exceptions", {
2011
+ title: "Parse C# exceptions from runtime logs",
2012
+ description: "Parse Godot runtime log lines and extract structured C# exception data including stack frames, inner exceptions, and repeat counts.",
2013
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, logLines: z.array(z.string()) },
2014
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
2015
+ }, async ({ logLines }) => {
2016
+ try {
2017
+ return { content: [{ type: "text", text: JSON.stringify(parseCsharpExceptions(logLines), null, 2) }] };
2018
+ }
2019
+ catch (err) {
2020
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2021
+ }
2022
+ });
2023
+ registerAppTool(server, "godot_generate_csharp_script", {
2024
+ title: "Generate C# script template",
2025
+ description: "Generate a Godot C# script boilerplate for a given class name, base type, and optional namespace.",
2026
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, className: z.string(), baseType: z.string().optional(), namespace: z.string().optional() },
2027
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
2028
+ }, async ({ className, baseType, namespace }) => {
2029
+ try {
2030
+ return { content: [{ type: "text", text: await generateCsharpScript({ className, baseType, namespace }) }] };
2031
+ }
2032
+ catch (err) {
2033
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2034
+ }
2035
+ });
2036
+ // ──────────────────────────────────────────────────────────────────────────
2037
+ // GODOT GDSCRIPT TOOLS
2038
+ // ──────────────────────────────────────────────────────────────────────────
2039
+ registerAppTool(server, "godot_gdscript_environment", {
2040
+ title: "Godot GDScript environment",
2041
+ description: "Inspect the GDScript project configuration, Godot executable, and project warnings.",
2042
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, projectPath: z.string().optional() },
2043
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
2044
+ }, async ({ workspaceId, projectPath }) => {
2045
+ try {
2046
+ return { content: [{ type: "text", text: JSON.stringify(await inspectGodotGdscriptEnvironment(workspaces, { workspaceId, projectPath }), null, 2) }] };
2047
+ }
2048
+ catch (err) {
2049
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2050
+ }
2051
+ });
2052
+ registerAppTool(server, "godot_get_gdscript_diagnostics", {
2053
+ title: "Get GDScript diagnostics",
2054
+ description: "Return static-analysis and parser diagnostics for GDScript files.",
2055
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, projectPath: z.string(), scriptPath: z.string().optional(), includeWarnings: z.boolean().optional() },
2056
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
2057
+ }, async ({ workspaceId, projectPath, scriptPath, includeWarnings }) => {
2058
+ try {
2059
+ return { content: [{ type: "text", text: JSON.stringify(await getGdscriptDiagnostics(workspaces, { workspaceId, projectPath, scriptPath, includeWarnings }), null, 2) }] };
2060
+ }
2061
+ catch (err) {
2062
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2063
+ }
2064
+ });
2065
+ registerAppTool(server, "godot_inspect_gdscript", {
2066
+ title: "Inspect GDScript source",
2067
+ description: "Inspect class name, base extends, exported properties, methods, signals, node paths, and preloads.",
2068
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, scriptPath: z.string() },
2069
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
2070
+ }, async ({ workspaceId, scriptPath }) => {
2071
+ try {
2072
+ return { content: [{ type: "text", text: JSON.stringify(await inspectGdscript(workspaces, { workspaceId, scriptPath }), null, 2) }] };
2073
+ }
2074
+ catch (err) {
2075
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2076
+ }
2077
+ });
2078
+ registerAppTool(server, "godot_create_gdscript", {
2079
+ title: "Create GDScript",
2080
+ description: "Create a new GDScript file using empty, node, resource, or plugin templates.",
2081
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, outputPath: z.string(), baseType: z.string(), className: z.string().optional(), toolScript: z.boolean().optional(), template: z.enum(["empty", "node", "resource", "editor_plugin"]).optional() },
2082
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2083
+ }, async ({ workspaceId, outputPath, baseType, className, toolScript, template }) => {
2084
+ try {
2085
+ return { content: [{ type: "text", text: JSON.stringify(await createGdscript(workspaces, { workspaceId, outputPath, baseType, className, toolScript, template }), null, 2) }] };
2086
+ }
2087
+ catch (err) {
2088
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2089
+ }
2090
+ });
2091
+ registerAppTool(server, "godot_attach_gdscript", {
2092
+ title: "Attach GDScript to node",
2093
+ description: "Attach a GDScript script path to a node in the edited scene tree.",
2094
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, nodePath: z.string(), scriptPath: z.string() },
2095
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2096
+ }, async ({ workspaceId, nodePath, scriptPath }) => {
2097
+ try {
2098
+ return { content: [{ type: "text", text: JSON.stringify(await attachGdscript(workspaces, { workspaceId, nodePath, scriptPath }), null, 2) }] };
2099
+ }
2100
+ catch (err) {
2101
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2102
+ }
2103
+ });
2104
+ registerAppTool(server, "godot_detach_gdscript", {
2105
+ title: "Detach GDScript from node",
2106
+ description: "Detach script from a node in the edited scene tree.",
2107
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, nodePath: z.string() },
2108
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2109
+ }, async ({ workspaceId, nodePath }) => {
2110
+ try {
2111
+ return { content: [{ type: "text", text: JSON.stringify(await detachGdscript(workspaces, { workspaceId, nodePath }), null, 2) }] };
2112
+ }
2113
+ catch (err) {
2114
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2115
+ }
2116
+ });
2117
+ registerAppTool(server, "godot_create_gdscript_signal", {
2118
+ title: "Create GDScript signal",
2119
+ description: "Declare a new signal in a GDScript file.",
2120
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, scriptPath: z.string(), signalName: z.string(), parameters: z.array(z.object({ name: z.string(), type: z.string().optional() })).optional() },
2121
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2122
+ }, async ({ workspaceId, scriptPath, signalName, parameters }) => {
2123
+ try {
2124
+ return { content: [{ type: "text", text: JSON.stringify(await createGdscriptSignal(workspaces, { workspaceId, scriptPath, signalName, parameters }), null, 2) }] };
2125
+ }
2126
+ catch (err) {
2127
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2128
+ }
2129
+ });
2130
+ registerAppTool(server, "godot_create_gdscript_signal_handler", {
2131
+ title: "Create signal handler stub",
2132
+ description: "Create a callback method stub for handling connected signals.",
2133
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, scriptPath: z.string(), methodName: z.string(), parameters: z.array(z.object({ name: z.string(), type: z.string().optional() })).optional() },
2134
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2135
+ }, async ({ workspaceId, scriptPath, methodName, parameters }) => {
2136
+ try {
2137
+ return { content: [{ type: "text", text: JSON.stringify(await createGdscriptSignalHandler(workspaces, { workspaceId, scriptPath, methodName, parameters }), null, 2) }] };
2138
+ }
2139
+ catch (err) {
2140
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2141
+ }
2142
+ });
2143
+ registerAppTool(server, "godot_get_global_classes", {
2144
+ title: "Get global classes",
2145
+ description: "List all global script classes defined with class_name.",
2146
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA },
2147
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
2148
+ }, async ({ workspaceId }) => {
2149
+ try {
2150
+ return { content: [{ type: "text", text: JSON.stringify(await getGlobalClasses(workspaces, { workspaceId }), null, 2) }] };
2151
+ }
2152
+ catch (err) {
2153
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2154
+ }
2155
+ });
2156
+ registerAppTool(server, "godot_add_class_name", {
2157
+ title: "Add class_name to script",
2158
+ description: "Declare a new global class_name at the top of a GDScript.",
2159
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, scriptPath: z.string(), className: z.string() },
2160
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2161
+ }, async ({ workspaceId, scriptPath, className }) => {
2162
+ try {
2163
+ return { content: [{ type: "text", text: JSON.stringify(await addClassName(workspaces, { workspaceId, scriptPath, className }), null, 2) }] };
2164
+ }
2165
+ catch (err) {
2166
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2167
+ }
2168
+ });
2169
+ registerAppTool(server, "godot_remove_class_name", {
2170
+ title: "Remove class_name from script",
2171
+ description: "Remove the global class_name declaration from a GDScript.",
2172
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, scriptPath: z.string() },
2173
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2174
+ }, async ({ workspaceId, scriptPath }) => {
2175
+ try {
2176
+ return { content: [{ type: "text", text: JSON.stringify(await removeClassName(workspaces, { workspaceId, scriptPath }), null, 2) }] };
2177
+ }
2178
+ catch (err) {
2179
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2180
+ }
2181
+ });
2182
+ registerAppTool(server, "godot_add_gdscript_autoload", {
2183
+ title: "Add GDScript autoload singleton",
2184
+ description: "Register a script as a global autoload singleton in project.godot.",
2185
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, scriptPath: z.string(), singletonName: z.string() },
2186
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2187
+ }, async ({ workspaceId, scriptPath, singletonName }) => {
2188
+ try {
2189
+ return { content: [{ type: "text", text: JSON.stringify(await addGdscriptAutoload(workspaces, { workspaceId, scriptPath, singletonName }), null, 2) }] };
2190
+ }
2191
+ catch (err) {
2192
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2193
+ }
2194
+ });
2195
+ registerAppTool(server, "godot_get_autoload_usage", {
2196
+ title: "Find autoload references",
2197
+ description: "Search for script/code occurrences referencing an autoload singleton name.",
2198
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, singletonName: z.string() },
2199
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
2200
+ }, async ({ workspaceId, singletonName }) => {
2201
+ try {
2202
+ return { content: [{ type: "text", text: JSON.stringify(await getAutoloadUsage(workspaces, { workspaceId, singletonName }), null, 2) }] };
2203
+ }
2204
+ catch (err) {
2205
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2206
+ }
2207
+ });
2208
+ registerAppTool(server, "godot_inspect_tool_script", {
2209
+ title: "Inspect @tool script",
2210
+ description: "Check if a script is annotated with @tool and list editor methods.",
2211
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, scriptPath: z.string() },
2212
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
2213
+ }, async ({ workspaceId, scriptPath }) => {
2214
+ try {
2215
+ return { content: [{ type: "text", text: JSON.stringify(await inspectToolScript(workspaces, { workspaceId, scriptPath }), null, 2) }] };
2216
+ }
2217
+ catch (err) {
2218
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2219
+ }
2220
+ });
2221
+ registerAppTool(server, "godot_create_editor_plugin", {
2222
+ title: "Create editor plugin",
2223
+ description: "Scaffold a new editor plugin addon folder with cfg and gd scripts.",
2224
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, pluginName: z.string(), description: z.string().optional(), author: z.string().optional(), version: z.string().optional() },
2225
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2226
+ }, async ({ workspaceId, pluginName, description, author, version }) => {
2227
+ try {
2228
+ return { content: [{ type: "text", text: JSON.stringify(await createEditorPlugin(workspaces, { workspaceId, pluginName, description, author, version }), null, 2) }] };
2229
+ }
2230
+ catch (err) {
2231
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2232
+ }
2233
+ });
2234
+ registerAppTool(server, "godot_get_gdscript_dependencies", {
2235
+ title: "Get GDScript load dependencies",
2236
+ description: "List preload/load file dependencies from a script.",
2237
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, scriptPath: z.string() },
2238
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
2239
+ }, async ({ workspaceId, scriptPath }) => {
2240
+ try {
2241
+ return { content: [{ type: "text", text: JSON.stringify(await getGdscriptDependencies(workspaces, { workspaceId, scriptPath }), null, 2) }] };
2242
+ }
2243
+ catch (err) {
2244
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2245
+ }
2246
+ });
2247
+ registerAppTool(server, "godot_find_cyclic_script_dependencies", {
2248
+ title: "Find cyclic preload dependencies",
2249
+ description: "List cyclic script preload dependencies that might crash the editor or build.",
2250
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA },
2251
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
2252
+ }, async ({ workspaceId }) => {
2253
+ try {
2254
+ return { content: [{ type: "text", text: JSON.stringify(await findCyclicScriptDependencies(workspaces, { workspaceId }), null, 2) }] };
2255
+ }
2256
+ catch (err) {
2257
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2258
+ }
2259
+ });
2260
+ registerAppTool(server, "godot_get_gdscript_node_references", {
2261
+ title: "Get script node path references",
2262
+ description: "Scan code for $Node path strings and unique names references.",
2263
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, scriptPath: z.string() },
2264
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
2265
+ }, async ({ workspaceId, scriptPath }) => {
2266
+ try {
2267
+ return { content: [{ type: "text", text: JSON.stringify(await getGdscriptNodeReferences(workspaces, { workspaceId, scriptPath }), null, 2) }] };
2268
+ }
2269
+ catch (err) {
2270
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2271
+ }
2272
+ });
2273
+ registerAppTool(server, "godot_get_gdscript_lifecycle_methods", {
2274
+ title: "Get lifecycle methods",
2275
+ description: "List lifecycle function overrides (_ready, _process) with helper alerts.",
2276
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, scriptPath: z.string() },
2277
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
2278
+ }, async ({ workspaceId, scriptPath }) => {
2279
+ try {
2280
+ return { content: [{ type: "text", text: JSON.stringify(await getGdscriptLifecycleMethods(workspaces, { workspaceId, scriptPath }), null, 2) }] };
2281
+ }
2282
+ catch (err) {
2283
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2284
+ }
2285
+ });
2286
+ registerAppTool(server, "godot_inspect_gdscript_await_usage", {
2287
+ title: "Inspect await statements",
2288
+ description: "Scan code for awaits and list timer/callable safety checks.",
2289
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, scriptPath: z.string() },
2290
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
2291
+ }, async ({ workspaceId, scriptPath }) => {
2292
+ try {
2293
+ return { content: [{ type: "text", text: JSON.stringify(await inspectGdscriptAwaitUsage(workspaces, { workspaceId, scriptPath }), null, 2) }] };
2294
+ }
2295
+ catch (err) {
2296
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2297
+ }
2298
+ });
2299
+ registerAppTool(server, "godot_analyze_gdscript_typing", {
2300
+ title: "Analyze typing coverage",
2301
+ description: "Compute return, variable, and parameter explicit typing coverage percentages.",
2302
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, scriptPath: z.string() },
2303
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
2304
+ }, async ({ workspaceId, scriptPath }) => {
2305
+ try {
2306
+ return { content: [{ type: "text", text: JSON.stringify(await analyzeGdscriptTyping(workspaces, { workspaceId, scriptPath }), null, 2) }] };
2307
+ }
2308
+ catch (err) {
2309
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2310
+ }
2311
+ });
2312
+ registerAppTool(server, "godot_format_gdscript", {
2313
+ title: "Format GDScript",
2314
+ description: "Run formatting layout fixes using gdformat. Use verifyOnly=true for pass/fail audits.",
2315
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, scriptPath: z.string().optional(), verifyOnly: z.boolean().optional() },
2316
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2317
+ }, async ({ workspaceId, scriptPath, verifyOnly }) => {
2318
+ try {
2319
+ return { content: [{ type: "text", text: JSON.stringify(await formatGdscript(workspaces, { workspaceId, scriptPath, verifyOnly }), null, 2) }] };
2320
+ }
2321
+ catch (err) {
2322
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2323
+ }
2324
+ });
2325
+ registerAppTool(server, "godot_detect_gdscript_tests", {
2326
+ title: "Detect test configuration",
2327
+ description: "Inspect folder structure for GUT (Godot Unit Test) framework and script files.",
2328
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, projectPath: z.string() },
2329
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
2330
+ }, async ({ workspaceId, projectPath }) => {
2331
+ try {
2332
+ return { content: [{ type: "text", text: JSON.stringify(await detectGdscriptTests(workspaces, { workspaceId, projectPath }), null, 2) }] };
2333
+ }
2334
+ catch (err) {
2335
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2336
+ }
2337
+ });
2338
+ registerAppTool(server, "godot_run_gdscript_tests", {
2339
+ title: "Run GDScript tests",
2340
+ description: "Run unit/integration tests and return passing, failing, and skipped logs.",
2341
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, projectPath: z.string(), testPath: z.string().optional(), filter: z.string().optional() },
2342
+ ...toolWidgetDescriptorMeta(config, "shell"), annotations: PROCESS_ANNOTATIONS,
2343
+ }, async ({ workspaceId, projectPath, testPath, filter }) => {
2344
+ try {
2345
+ return { content: [{ type: "text", text: JSON.stringify(await runGdscriptTests(workspaces, { workspaceId, projectPath, testPath, filter }), null, 2) }] };
2346
+ }
2347
+ catch (err) {
2348
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2349
+ }
2350
+ });
2351
+ registerAppTool(server, "godot_reload_gdscript", {
2352
+ title: "Reload scripts in editor",
2353
+ description: "Trigger live reload of updated GDScript files inside the active editor bridge.",
2354
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, scriptPath: z.string().optional() },
2355
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2356
+ }, async ({ workspaceId, scriptPath }) => {
2357
+ try {
2358
+ return { content: [{ type: "text", text: JSON.stringify(await reloadGdscript(workspaces, { workspaceId, scriptPath }), null, 2) }] };
2359
+ }
2360
+ catch (err) {
2361
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2362
+ }
2363
+ });
2364
+ registerAppTool(server, "godot_gdscript_set_breakpoint", {
2365
+ title: "Set breakpoint",
2366
+ description: "Declare a breakpoint at a script path line.",
2367
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, scriptPath: z.string(), line: z.number() },
2368
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2369
+ }, async ({ workspaceId, scriptPath, line }) => {
2370
+ try {
2371
+ return { content: [{ type: "text", text: JSON.stringify(await gdscriptSetBreakpoint(workspaces, { workspaceId, scriptPath, line }), null, 2) }] };
2372
+ }
2373
+ catch (err) {
2374
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2375
+ }
2376
+ });
2377
+ registerAppTool(server, "godot_gdscript_remove_breakpoint", {
2378
+ title: "Remove breakpoint",
2379
+ description: "Clear a breakpoint from a script line.",
2380
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, scriptPath: z.string(), line: z.number() },
2381
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2382
+ }, async ({ workspaceId, scriptPath, line }) => {
2383
+ try {
2384
+ return { content: [{ type: "text", text: JSON.stringify(await gdscriptRemoveBreakpoint(workspaces, { workspaceId, scriptPath, line }), null, 2) }] };
2385
+ }
2386
+ catch (err) {
2387
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2388
+ }
2389
+ });
2390
+ registerAppTool(server, "godot_gdscript_lsp_find_symbol", {
2391
+ title: "LSP find GDScript symbol",
2392
+ description: "Find a symbol definition across all GDScript files via the Godot editor LSP bridge.",
2393
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, symbol: z.string() },
2394
+ ...toolWidgetDescriptorMeta(config, "search"), annotations: READ_ONLY_ANNOTATIONS,
2395
+ }, async ({ workspaceId, symbol }) => {
2396
+ try {
2397
+ return { content: [{ type: "text", text: JSON.stringify(await gdscriptLspFindSymbol(workspaces, { workspaceId, symbol }), null, 2) }] };
2398
+ }
2399
+ catch (err) {
2400
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2401
+ }
2402
+ });
2403
+ registerAppTool(server, "godot_gdscript_lsp_get_definition", {
2404
+ title: "LSP get GDScript definition",
2405
+ description: "Get the definition location of a symbol at a specific line/character in a GDScript file via the Godot editor LSP bridge.",
2406
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, scriptPath: z.string(), line: z.number(), character: z.number() },
2407
+ ...toolWidgetDescriptorMeta(config, "search"), annotations: READ_ONLY_ANNOTATIONS,
2408
+ }, async ({ workspaceId, scriptPath, line, character }) => {
2409
+ try {
2410
+ return { content: [{ type: "text", text: JSON.stringify(await gdscriptLspGetDefinition(workspaces, { workspaceId, scriptPath, line, character }), null, 2) }] };
2411
+ }
2412
+ catch (err) {
2413
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
2414
+ }
2415
+ });
2416
+ // ──────────────────────────────────────────────────────────────────────────
2417
+ // BLENDER MCP TOOLS
2418
+ // ──────────────────────────────────────────────────────────────────────────
2419
+ registerAppTool(server, "blender_ping", {
2420
+ title: "Blender Ping",
2421
+ description: "Verify local Blender connection is reachable, returning version and active scene status.",
2422
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA },
2423
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
2424
+ }, async ({ workspaceId }) => blenderPing(workspaces, { workspaceId }));
2425
+ registerAppTool(server, "blender_get_scene_info", {
2426
+ title: "Get Blender Scene Info",
2427
+ description: "Return current active scene properties, frame, and list of objects.",
2428
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA },
2429
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
2430
+ }, async ({ workspaceId }) => blenderGetSceneInfo(workspaces, { workspaceId }));
2431
+ registerAppTool(server, "blender_get_scene_audit", {
2432
+ title: "Blender Low-Poly Scene Audit",
2433
+ description: "Audit scene configuration for low-poly budgets, duplicate assets, and non-manifold mesh objects.",
2434
+ inputSchema: {
2435
+ workspaceId: WORKSPACE_ID_SCHEMA,
2436
+ includeHidden: z.boolean().optional(),
2437
+ includeInstances: z.boolean().optional(),
2438
+ includeMaterials: z.boolean().optional(),
2439
+ includeImages: z.boolean().optional(),
2440
+ includeModifiers: z.boolean().optional(),
2441
+ },
2442
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
2443
+ }, async (input) => blenderGetSceneAudit(workspaces, input));
2444
+ registerAppTool(server, "blender_get_console_errors", {
2445
+ title: "Get Blender console errors",
2446
+ description: "Retrieve recent error and warning messages from the Blender console output.",
2447
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA },
2448
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
2449
+ }, async ({ workspaceId }) => blenderGetConsoleErrors(workspaces, { workspaceId }));
2450
+ registerAppTool(server, "blender_get_selection", {
2451
+ title: "Get Selected Objects",
2452
+ description: "List currently selected objects, active object, and Blender interaction mode.",
2453
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA },
2454
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
2455
+ }, async ({ workspaceId }) => blenderGetSelection(workspaces, { workspaceId }));
2456
+ registerAppTool(server, "blender_get_active_mode_and_status", {
2457
+ title: "Get active mode",
2458
+ description: "Check selection mode, rendering engine, and whether animation is playing.",
2459
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA },
2460
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
2461
+ }, async ({ workspaceId }) => blenderGetActiveModeAndStatus(workspaces, { workspaceId }));
2462
+ registerAppTool(server, "blender_inspect_object", {
2463
+ title: "Inspect Blender Object",
2464
+ description: "Inspect specific object mesh details, applied modifiers, parent/child state, and materials.",
2465
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, objectName: z.string() },
2466
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
2467
+ }, async (input) => blenderInspectObject(workspaces, input));
2468
+ registerAppTool(server, "blender_create_cube", {
2469
+ title: "Create Blender Cube",
2470
+ description: "Create a primitive cube mesh at location.",
2471
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, name: z.string().optional(), x: z.number().optional(), y: z.number().optional(), z: z.number().optional(), size: z.number().optional() },
2472
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2473
+ }, async (input) => blenderCreateCube(workspaces, input));
2474
+ registerAppTool(server, "blender_select_object", {
2475
+ title: "Select Object",
2476
+ description: "Select a single object and optionally set interaction mode.",
2477
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, objectName: z.string(), mode: z.string().optional() },
2478
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2479
+ }, async (input) => blenderSelectObject(workspaces, input));
2480
+ registerAppTool(server, "blender_select_objects", {
2481
+ title: "Select Objects",
2482
+ description: "Select multiple objects by wildcard name, type, collection, or material.",
2483
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, pattern: z.string().optional(), objectType: z.string().optional(), collection: z.string().optional(), material: z.string().optional() },
2484
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2485
+ }, async (input) => blenderSelectObjects(workspaces, input));
2486
+ registerAppTool(server, "blender_transform_object", {
2487
+ title: "Transform Object",
2488
+ description: "Set object local translation, scale, or Euler rotation settings.",
2489
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, objectName: z.string(), location: z.array(z.number()).optional(), rotationEuler: z.array(z.number()).optional(), scale: z.array(z.number()).optional() },
2490
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2491
+ }, async (input) => blenderTransformObject(workspaces, input));
2492
+ registerAppTool(server, "blender_duplicate_linked", {
2493
+ title: "Duplicate Linked",
2494
+ description: "Create linked duplicates of objects.",
2495
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, objectNames: z.array(z.string()), offset: z.array(z.number()).optional() },
2496
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2497
+ }, async (input) => blenderDuplicateLinked(workspaces, input));
2498
+ registerAppTool(server, "blender_join_objects", {
2499
+ title: "Join Objects",
2500
+ description: "Join selected mesh objects into one mesh container.",
2501
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, objectNames: z.array(z.string()), resultName: z.string().optional() },
2502
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2503
+ }, async (input) => blenderJoinObjects(workspaces, input));
2504
+ registerAppTool(server, "blender_delete_objects", {
2505
+ title: "Delete Objects",
2506
+ description: "Remove objects from data dictionary completely.",
2507
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, objectNames: z.array(z.string()) },
2508
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2509
+ }, async (input) => blenderDeleteObjects(workspaces, input));
2510
+ registerAppTool(server, "blender_create_collection", {
2511
+ title: "Create Collection",
2512
+ description: "Create collection node under hierarchy.",
2513
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, name: z.string(), parent: z.string().optional() },
2514
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2515
+ }, async (input) => blenderCreateCollection(workspaces, input));
2516
+ registerAppTool(server, "blender_move_to_collection", {
2517
+ title: "Move Objects to Collection",
2518
+ description: "Move objects to collection and optionally unlink from parent.",
2519
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, objectNames: z.array(z.string()), collectionName: z.string(), unlinkFromOthers: z.boolean().optional() },
2520
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2521
+ }, async (input) => blenderMoveToCollection(workspaces, input));
2522
+ registerAppTool(server, "blender_set_viewport_view", {
2523
+ title: "Set Viewport View",
2524
+ description: "Orient 3D viewport camera view angles.",
2525
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, view: z.string().optional() },
2526
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2527
+ }, async (input) => blenderSetViewportView(workspaces, input));
2528
+ registerAppTool(server, "blender_orbit_viewport", {
2529
+ title: "Orbit Viewport",
2530
+ description: "Orbit camera.",
2531
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, direction: z.string().optional(), steps: z.number().optional() },
2532
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2533
+ }, async (input) => blenderOrbitViewport(workspaces, input));
2534
+ registerAppTool(server, "blender_pan_viewport", {
2535
+ title: "Pan Viewport",
2536
+ description: "Pan camera.",
2537
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, direction: z.string().optional(), steps: z.number().optional() },
2538
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2539
+ }, async (input) => blenderPanViewport(workspaces, input));
2540
+ registerAppTool(server, "blender_zoom_viewport", {
2541
+ title: "Zoom Viewport",
2542
+ description: "Zoom camera.",
2543
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, direction: z.string().optional(), steps: z.number().optional() },
2544
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2545
+ }, async (input) => blenderZoomViewport(workspaces, input));
2546
+ registerAppTool(server, "blender_frame_selected", {
2547
+ title: "Frame Selected",
2548
+ description: "Align view to frame selected elements.",
2549
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA },
2550
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2551
+ }, async ({ workspaceId }) => blenderFrameSelected(workspaces, { workspaceId }));
2552
+ registerAppTool(server, "blender_set_viewport_shading", {
2553
+ title: "Set Viewport Shading",
2554
+ description: "Modify view display shading mode.",
2555
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, mode: z.string().optional() },
2556
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2557
+ }, async (input) => blenderSetViewportShading(workspaces, input));
2558
+ registerAppTool(server, "blender_inspect_material", {
2559
+ title: "Inspect Material",
2560
+ description: "Get material setup details.",
2561
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, materialName: z.string() },
2562
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
2563
+ }, async (input) => blenderInspectMaterial(workspaces, input));
2564
+ registerAppTool(server, "blender_inspect_geometry_nodes", {
2565
+ title: "Inspect Geometry Nodes",
2566
+ description: "Inspect modifier setup.",
2567
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, objectName: z.string() },
2568
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
2569
+ }, async (input) => blenderInspectGeometryNodes(workspaces, input));
2570
+ registerAppTool(server, "blender_edit_modifier", {
2571
+ title: "Edit Modifier Parameters",
2572
+ description: "Modify simple modifier settings.",
2573
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, objectName: z.string(), modifierName: z.string(), properties: z.record(z.string(), z.unknown()) },
2574
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2575
+ }, async (input) => blenderEditModifier(workspaces, input));
2576
+ registerAppTool(server, "blender_set_render_settings", {
2577
+ title: "Set Render Settings",
2578
+ description: "Alter render properties.",
2579
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, engine: z.string().optional(), width: z.number().optional(), height: z.number().optional(), samples: z.number().optional(), denoise: z.boolean().optional() },
2580
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2581
+ }, async (input) => blenderSetRenderSettings(workspaces, input));
2582
+ registerAppTool(server, "blender_render_camera", {
2583
+ title: "Render Camera",
2584
+ description: "Generate render image output for active camera.",
2585
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, cameraName: z.string().optional(), width: z.number().optional(), height: z.number().optional(), samples: z.number().optional() },
2586
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2587
+ }, async (input) => blenderRenderCamera(workspaces, input));
2588
+ registerAppTool(server, "blender_render_object_isolation", {
2589
+ title: "Render Object Isolation",
2590
+ description: "Isolate object render.",
2591
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, objectName: z.string(), width: z.number().optional(), height: z.number().optional(), samples: z.number().optional() },
2592
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2593
+ }, async (input) => blenderRenderObjectIsolation(workspaces, input));
2594
+ registerAppTool(server, "blender_render_viewport", {
2595
+ title: "Render Viewport Screenshot",
2596
+ description: "Capture screenshot of Blender window.",
2597
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, width: z.number().optional(), height: z.number().optional() },
2598
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2599
+ }, async (input) => blenderRenderViewport(workspaces, input));
2600
+ registerAppTool(server, "blender_save_checkpoint", {
2601
+ title: "Save Checkpoint",
2602
+ description: "Save snapshot of active work session.",
2603
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, label: z.string().optional() },
2604
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2605
+ }, async (input) => blenderSaveCheckpoint(workspaces, input));
2606
+ registerAppTool(server, "blender_list_checkpoints", {
2607
+ title: "List Blender Checkpoints",
2608
+ description: "List all saved Blender session checkpoint files.",
2609
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA },
2610
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
2611
+ }, async ({ workspaceId }) => blenderListCheckpoints(workspaces, { workspaceId }));
2612
+ registerAppTool(server, "blender_rollback_checkpoint", {
2613
+ title: "Rollback Checkpoint",
2614
+ description: "Open past checkpoint file.",
2615
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, path: z.string() },
2616
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2617
+ }, async (input) => blenderRollbackCheckpoint(workspaces, input));
2618
+ registerAppTool(server, "blender_execute_python", {
2619
+ title: "Execute python",
2620
+ description: "Run custom Python script on active Blender session.",
2621
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, code: z.string() },
2622
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2623
+ }, async (input) => blenderExecutePython(workspaces, input));
2624
+ registerAppTool(server, "blender_get_current_file", {
2625
+ title: "Get current blend file",
2626
+ description: "Retrieve current file path.",
2627
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA },
2628
+ ...toolWidgetDescriptorMeta(config, "read"), annotations: READ_ONLY_ANNOTATIONS,
2629
+ }, async ({ workspaceId }) => blenderGetCurrentFile(workspaces, { workspaceId }));
2630
+ registerAppTool(server, "blender_open_file", {
2631
+ title: "Open blend file",
2632
+ description: "Load project blend file.",
2633
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, filepath: z.string() },
2634
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2635
+ }, async (input) => blenderOpenFile(workspaces, input));
2636
+ registerAppTool(server, "blender_save_file", {
2637
+ title: "Save blend file",
2638
+ description: "Overwrites current active blend file.",
2639
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA },
2640
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2641
+ }, async ({ workspaceId }) => blenderSaveFile(workspaces, { workspaceId }));
2642
+ registerAppTool(server, "blender_save_file_as", {
2643
+ title: "Save blend file as",
2644
+ description: "Save active session to new file path.",
2645
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, filepath: z.string() },
2646
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2647
+ }, async (input) => blenderSaveFileAs(workspaces, input));
2648
+ registerAppTool(server, "blender_export_glb", {
2649
+ title: "Export GLB asset",
2650
+ description: "Generate GLB 3D scene file.",
2651
+ inputSchema: { workspaceId: WORKSPACE_ID_SCHEMA, filepath: z.string() },
2652
+ ...toolWidgetDescriptorMeta(config, "write"), annotations: MUTATING_ANNOTATIONS,
2653
+ }, async (input) => blenderExportGlb(workspaces, input));
2654
+ registerSerenaTools(server, config, serenaManager, workspaces);
2655
+ return server;
2656
+ }
2657
+ export function createServer(config = loadConfig()) {
2658
+ const allowedHosts = config.allowedHosts.includes("*")
2659
+ ? undefined
2660
+ : Array.from(new Set([config.host, ...config.allowedHosts]));
2661
+ const app = createMcpExpressApp({
2662
+ host: config.host,
2663
+ ...(allowedHosts ? { allowedHosts } : {}),
2664
+ });
2665
+ const transports = new Map();
2666
+ const mcpUrl = new URL("/mcp", config.publicBaseUrl);
2667
+ const resourceServerUrl = resourceUrlFromServerUrl(mcpUrl);
2668
+ const oauthProvider = new SingleUserOAuthProvider(config.oauth, mcpUrl);
2669
+ const bearerAuth = requireBearerAuth({
2670
+ verifier: oauthProvider,
2671
+ requiredScopes: [config.oauth.scopes[0] ?? "auvrynt"],
2672
+ resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(resourceServerUrl),
2673
+ });
2674
+ const workspaceStore = createWorkspaceStore(config.stateDir);
2675
+ const workspaces = new WorkspaceRegistry(config, workspaceStore);
2676
+ const reviewCheckpoints = createReviewCheckpointManager();
2677
+ const serenaConfig = {
2678
+ enabled: config.serena.enabled,
2679
+ executable: config.serena.executable,
2680
+ backend: config.serena.backend,
2681
+ context: config.serena.context,
2682
+ startupTimeoutMs: config.serena.startupTimeoutMs,
2683
+ requestTimeoutMs: config.serena.requestTimeoutMs,
2684
+ idleTimeoutMinutes: config.serena.idleTimeoutMinutes,
2685
+ maxInstances: config.serena.maxInstances,
2686
+ };
2687
+ const serenaManager = new SerenaManager(serenaConfig);
2688
+ if (config.logging.trustProxy) {
2689
+ app.set("trust proxy", true);
2690
+ }
2691
+ app.use((req, res, next) => {
2692
+ const requestId = randomUUID();
2693
+ const startedAt = performance.now();
2694
+ res.locals.requestId = requestId;
2695
+ res.on("finish", () => {
2696
+ const path = requestPath(req);
2697
+ if (!config.logging.requests)
2698
+ return;
2699
+ if (!config.logging.assets && path.startsWith("/mcp-app-assets"))
2700
+ return;
2701
+ logEvent(config.logging, "info", "http_request", {
2702
+ requestId,
2703
+ method: req.method,
2704
+ path,
2705
+ status: res.statusCode,
2706
+ durationMs: Math.round(performance.now() - startedAt),
2707
+ ...requestLogFields(req, config),
2708
+ });
2709
+ });
2710
+ next();
2711
+ });
2712
+ app.use(mcpAuthRouter({
2713
+ provider: oauthProvider,
2714
+ issuerUrl: new URL(config.publicBaseUrl),
2715
+ baseUrl: new URL(config.publicBaseUrl),
2716
+ resourceServerUrl,
2717
+ scopesSupported: config.oauth.scopes,
2718
+ resourceName: "Auvrynt",
2719
+ }));
2720
+ app.options("/mcp-app-assets/{*asset}", (_req, res) => {
2721
+ setAssetHeaders(res);
2722
+ res.sendStatus(204);
2723
+ });
2724
+ app.use("/mcp-app-assets", express.static(uiBuildDirectory(), {
2725
+ immutable: true,
2726
+ maxAge: "1y",
2727
+ fallthrough: false,
2728
+ setHeaders: setAssetHeaders,
2729
+ }));
2730
+ app.get("/healthz", (_req, res) => {
2731
+ res.json({ ok: true, name: "auvrynt" });
2732
+ });
2733
+ app.all("/mcp", async (req, res) => {
2734
+ const requestId = res.locals.requestId;
2735
+ const sessionId = req.header("mcp-session-id");
2736
+ const initializeRequest = req.method === "POST" && isInitializeRequest(req.body);
2737
+ await new Promise((resolve, reject) => {
2738
+ bearerAuth(req, res, (error) => {
2739
+ if (error)
2740
+ reject(error);
2741
+ else
2742
+ resolve();
2743
+ });
2744
+ });
2745
+ if (res.headersSent)
2746
+ return;
2747
+ if (!req.auth?.resource || !checkResourceAllowed({ requestedResource: req.auth.resource, configuredResource: resourceServerUrl })) {
2748
+ logEvent(config.logging, "warn", "auth_denied", {
2749
+ requestId,
2750
+ method: req.method,
2751
+ path: requestPath(req),
2752
+ reason: "invalid_oauth_resource",
2753
+ ...requestLogFields(req, config),
2754
+ });
2755
+ sendJsonRpcError(res, 401, -32001, "Unauthorized");
2756
+ return;
2757
+ }
2758
+ logEvent(config.logging, "debug", "mcp_request", {
2759
+ requestId,
2760
+ method: req.method,
2761
+ sessionIdPresent: Boolean(sessionId),
2762
+ sessionIdPrefix: sessionIdPrefix(sessionId),
2763
+ isInitialize: initializeRequest,
2764
+ });
2765
+ try {
2766
+ let transport;
2767
+ if (sessionId) {
2768
+ transport = transports.get(sessionId);
2769
+ if (!transport) {
2770
+ sendJsonRpcError(res, 404, -32000, "Unknown MCP session");
2771
+ return;
2772
+ }
2773
+ }
2774
+ else if (initializeRequest) {
2775
+ transport = new StreamableHTTPServerTransport({
2776
+ sessionIdGenerator: () => randomUUID(),
2777
+ onsessioninitialized: (newSessionId) => {
2778
+ if (transport)
2779
+ transports.set(newSessionId, transport);
2780
+ logEvent(config.logging, "info", "mcp_session_created", {
2781
+ requestId,
2782
+ sessionIdPrefix: sessionIdPrefix(newSessionId),
2783
+ ...requestLogFields(req, config),
2784
+ });
2785
+ },
2786
+ });
2787
+ transport.onclose = () => {
2788
+ const closedSessionId = transport?.sessionId;
2789
+ if (closedSessionId) {
2790
+ transports.delete(closedSessionId);
2791
+ logEvent(config.logging, "info", "mcp_session_closed", {
2792
+ sessionIdPrefix: sessionIdPrefix(closedSessionId),
2793
+ });
2794
+ }
2795
+ };
2796
+ const server = createMcpServer(config, workspaces, reviewCheckpoints, serenaManager);
2797
+ await server.connect(transport);
2798
+ }
2799
+ else {
2800
+ sendJsonRpcError(res, 400, -32000, "No valid MCP session");
2801
+ return;
2802
+ }
2803
+ await transport.handleRequest(req, res, req.body);
2804
+ }
2805
+ catch (error) {
2806
+ logEvent(config.logging, "error", "mcp_request_error", {
2807
+ requestId,
2808
+ error: error instanceof Error ? error.message : String(error),
2809
+ });
2810
+ if (!res.headersSent) {
2811
+ sendJsonRpcError(res, 500, -32603, "Internal server error");
2812
+ }
2813
+ }
2814
+ });
2815
+ return { app, config };
2816
+ }
2817
+ async function isMainModule() {
2818
+ if (!process.argv[1])
2819
+ return false;
2820
+ const modulePath = await realpath(fileURLToPath(import.meta.url));
2821
+ const entrypointPath = await realpath(process.argv[1]);
2822
+ return modulePath === entrypointPath;
2823
+ }
2824
+ if (await isMainModule()) {
2825
+ const { app, config } = createServer();
2826
+ app.listen(config.port, config.host, () => {
2827
+ console.log(`auvrynt listening on http://${config.host}:${config.port}/mcp`);
2828
+ console.log(`allowed roots: ${config.allowedRoots.join(", ")}`);
2829
+ console.log("auth: oauth owner-token flow required");
2830
+ console.log(`logging: ${config.logging.level} ${config.logging.format}`);
2831
+ console.log(`request logging: ${config.logging.requests ? "enabled" : "disabled"}`);
2832
+ console.log(`asset logging: ${config.logging.assets ? "enabled" : "disabled"}`);
2833
+ console.log(`trust proxy: ${config.logging.trustProxy ? "enabled" : "disabled"}`);
2834
+ });
2835
+ }