chore(unity): 固定Unity CLI Loop本地包并移除UnitySkills依赖
This commit is contained in:
456
Client/Packages/io.github.hatayama.uloopmcp/CHANGELOG.md
Normal file
456
Client/Packages/io.github.hatayama.uloopmcp/CHANGELOG.md
Normal file
@@ -0,0 +1,456 @@
|
||||
# Changelog
|
||||
|
||||
## [2.2.0](https://github.com/hatayama/unity-cli-loop/compare/v2.1.10...v2.2.0) (2026-06-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Skills use the project-matched CLI by default ([#1404](https://github.com/hatayama/unity-cli-loop/issues/1404)) ([5bb9d49](https://github.com/hatayama/unity-cli-loop/commit/5bb9d49c59e4b6166a5bb926a7c6e7be690e230f))
|
||||
|
||||
## [2.1.10](https://github.com/hatayama/unity-cli-loop/compare/v2.1.9...v2.1.10) (2026-06-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* get-logs works with active Console search filters ([#1379](https://github.com/hatayama/unity-cli-loop/issues/1379)) ([941cb2f](https://github.com/hatayama/unity-cli-loop/commit/941cb2fb1888a943f4218d51325f33aecaf6e57f))
|
||||
* Unity 6.5 projects compile without metadata type conflicts ([#1377](https://github.com/hatayama/unity-cli-loop/issues/1377)) ([4a33119](https://github.com/hatayama/unity-cli-loop/commit/4a3311984c0c3ea11b27dff24b1987cb1a3de383))
|
||||
|
||||
## [2.1.9](https://github.com/hatayama/unity-cli-loop/compare/v2.1.8...v2.1.9) (2026-06-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Respect UI raycast priority in simulate-mouse-ui ([#1318](https://github.com/hatayama/unity-cli-loop/issues/1318)) ([acb40a6](https://github.com/hatayama/unity-cli-loop/commit/acb40a68a0e4723148892e961c3aeb95ddd32f90))
|
||||
|
||||
## [2.1.8](https://github.com/hatayama/unity-cli-loop/compare/v2.1.7...v2.1.8) (2026-06-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Avoid dependency conflicts from bundled metadata DLLs ([#1288](https://github.com/hatayama/unity-cli-loop/issues/1288)) ([636439d](https://github.com/hatayama/unity-cli-loop/commit/636439d4ca5170623e5ceeb017fe8ba12d9c4c60))
|
||||
|
||||
## [2.1.7](https://github.com/hatayama/unity-cli-loop/compare/v2.1.6...v2.1.7) (2026-06-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Multiline dynamic code snippets work reliably on Windows ([#1319](https://github.com/hatayama/unity-cli-loop/issues/1319)) ([aff5ddd](https://github.com/hatayama/unity-cli-loop/commit/aff5dddf53f586c2da19a571784549eb4f4079d8))
|
||||
|
||||
## [2.1.6](https://github.com/hatayama/unity-cli-loop/compare/v2.1.5...v2.1.6) (2026-06-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Input simulation overlays stay usable without shipping runtime DLLs ([#1285](https://github.com/hatayama/unity-cli-loop/issues/1285)) ([e271514](https://github.com/hatayama/unity-cli-loop/commit/e2715140af298ab3698a8d02efd1cd319d448670))
|
||||
|
||||
## [2.1.5](https://github.com/hatayama/unity-cli-loop/compare/v2.1.4...v2.1.5) (2026-06-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Interrupted PlayMode tests no longer leave domain reload disabled ([#1255](https://github.com/hatayama/unity-cli-loop/issues/1255)) ([551bb38](https://github.com/hatayama/unity-cli-loop/commit/551bb382c21abc4785b1b23fbb52e0e48e134906))
|
||||
|
||||
## [2.1.4](https://github.com/hatayama/unity-cli-loop/compare/v2.1.3...v2.1.4) (2026-05-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Make bundled skill guidance more concise ([#1178](https://github.com/hatayama/unity-cli-loop/issues/1178)) ([dbcc38e](https://github.com/hatayama/unity-cli-loop/commit/dbcc38e7d239ba3203685b0fc606d661e5d454b0))
|
||||
|
||||
## [2.1.3](https://github.com/hatayama/unity-cli-loop/compare/v2.1.2...v2.1.3) (2026-05-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **build:** exclude Editor-only assemblies from player builds ([#1120](https://github.com/hatayama/unity-cli-loop/issues/1120)) ([768b49d](https://github.com/hatayama/unity-cli-loop/commit/768b49d447126cb04f98d1fe493c162fdcec6c28))
|
||||
* Unity 6.5 projects compile and keep fast code execution available ([#1119](https://github.com/hatayama/unity-cli-loop/issues/1119)) ([1a27478](https://github.com/hatayama/unity-cli-loop/commit/1a274786e8f53afe1a74bf7b66b894842310944f))
|
||||
* Unity no longer imports stale empty folders ([#1124](https://github.com/hatayama/unity-cli-loop/issues/1124)) ([45a57c3](https://github.com/hatayama/unity-cli-loop/commit/45a57c30c1803d93d4ef823e4937d1ed7c39d53d))
|
||||
* Unity stays responsive during domain reload recovery ([#1121](https://github.com/hatayama/unity-cli-loop/issues/1121)) ([5f5ab9d](https://github.com/hatayama/unity-cli-loop/commit/5f5ab9de8bf26972aaa9ef6a971324c907d01375))
|
||||
|
||||
## [2.1.2](https://github.com/hatayama/unity-cli-loop/compare/v2.1.1...v2.1.2) (2026-05-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Global skill installs work without manual folder moves ([#1112](https://github.com/hatayama/unity-cli-loop/issues/1112)) ([498e428](https://github.com/hatayama/unity-cli-loop/commit/498e428958dd4d38ee83e5ca6d027eed9be9901d))
|
||||
|
||||
## [2.1.1](https://github.com/hatayama/unity-cli-loop/compare/v2.1.0...v2.1.1) (2026-05-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow installation without Unity Test Framework ([#1062](https://github.com/hatayama/unity-cli-loop/issues/1062)) ([45d58b9](https://github.com/hatayama/unity-cli-loop/commit/45d58b944da687f8505457d1a32361c188f119a5))
|
||||
|
||||
## [2.1.0](https://github.com/hatayama/unity-cli-loop/compare/v2.0.4...v2.1.0) (2026-04-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* UI automation can see targets clearly and bypass blocked raycasts ([#996](https://github.com/hatayama/unity-cli-loop/issues/996)) ([fe43abe](https://github.com/hatayama/unity-cli-loop/commit/fe43abea6b8e2ce02cc540b3d553b8d07da1ddc0))
|
||||
* Unity Menu Commands Consolidated to Dynamic Code Execution ([#994](https://github.com/hatayama/unity-cli-loop/issues/994)) ([ea6c95b](https://github.com/hatayama/unity-cli-loop/commit/ea6c95bb0240872f41a8ab63761657fb7f3d4fc4))
|
||||
* Unity startup recovery does less blocking work ([#990](https://github.com/hatayama/unity-cli-loop/issues/990)) ([63ce4db](https://github.com/hatayama/unity-cli-loop/commit/63ce4db9ff4c6fa15d0a2a11f99c1b0387fc8a1b))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* AI selects the right tool for selected GameObject inspection ([#1003](https://github.com/hatayama/unity-cli-loop/issues/1003)) ([1621d1a](https://github.com/hatayama/unity-cli-loop/commit/1621d1a2d508bf46878340e6c5df76731be872b9))
|
||||
* Dynamic code starts reliably on Windows PCs ([#1006](https://github.com/hatayama/unity-cli-loop/issues/1006)) ([68893da](https://github.com/hatayama/unity-cli-loop/commit/68893da351df60be7af3300f668d5ab111cd5be2))
|
||||
* Improve uloop skill guidance and hide internal CLI metadata ([#993](https://github.com/hatayama/unity-cli-loop/issues/993)) ([ec2ba7a](https://github.com/hatayama/unity-cli-loop/commit/ec2ba7a09a9484d4869b1d7aff5338dca9a272d6))
|
||||
* Make dynamic code compilation easier to maintain ([#1007](https://github.com/hatayama/unity-cli-loop/issues/1007)) ([b966dba](https://github.com/hatayama/unity-cli-loop/commit/b966dbac38bbdaaea87c0d1a62493d9c2b08e195))
|
||||
* Settings opens faster and tool toggles stay scoped ([#992](https://github.com/hatayama/unity-cli-loop/issues/992)) ([26745ad](https://github.com/hatayama/unity-cli-loop/commit/26745ade840f6cdd6322719fa83cc4de473b1b65))
|
||||
* Skill improvements for selected Hierarchy inspection ([#1002](https://github.com/hatayama/unity-cli-loop/issues/1002)) ([626f67c](https://github.com/hatayama/unity-cli-loop/commit/626f67c1841c74650185b37c3d94efe6c2ce4367))
|
||||
* Test runs avoid unsaved editor-change prompts ([#998](https://github.com/hatayama/unity-cli-loop/issues/998)) ([1db7c37](https://github.com/hatayama/unity-cli-loop/commit/1db7c376d8bf78a50e41abb4a4ead4b5bee40077))
|
||||
* uloop launch docs now explain Unity startup waiting ([#997](https://github.com/hatayama/unity-cli-loop/issues/997)) ([bef8f2f](https://github.com/hatayama/unity-cli-loop/commit/bef8f2fa8cfd69cea58b66c0060baa8801dd1d45))
|
||||
|
||||
## [2.0.4](https://github.com/hatayama/unity-cli-loop/compare/v2.0.3...v2.0.4) (2026-04-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Dynamic code execution stays fast with system .NET 6 installed ([#988](https://github.com/hatayama/unity-cli-loop/issues/988)) ([81d4d02](https://github.com/hatayama/unity-cli-loop/commit/81d4d02ebf36f6bbbcea6a01dd9aba845426f13f))
|
||||
|
||||
## [2.0.3](https://github.com/hatayama/unity-cli-loop/compare/v2.0.2...v2.0.3) (2026-04-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Setup now keeps third-party skills when switching folder layouts ([#980](https://github.com/hatayama/unity-cli-loop/issues/980)) ([adfc628](https://github.com/hatayama/unity-cli-loop/commit/adfc62852b8f79b46ca02c6ed6f0fc64c1d000db))
|
||||
|
||||
## [2.0.2](https://github.com/hatayama/unity-cli-loop/compare/v2.0.1...v2.0.2) (2026-04-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Setup and Settings now clean up grouped skill folders and avoid rerunning skill checks after CLI updates ([#978](https://github.com/hatayama/unity-cli-loop/issues/978)) ([51fe1f5](https://github.com/hatayama/unity-cli-loop/commit/51fe1f5384a7596823fceaaa1c583e788a352196))
|
||||
|
||||
## [2.0.1](https://github.com/hatayama/unity-cli-loop/compare/v2.0.0...v2.0.1) (2026-04-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve `uloop launch` startup behavior by keeping launch progress visible and waiting for the correct Unity project without false warnings ([#965](https://github.com/hatayama/unity-cli-loop/issues/965), [#973](https://github.com/hatayama/unity-cli-loop/issues/973), [#974](https://github.com/hatayama/unity-cli-loop/issues/974), [#975](https://github.com/hatayama/unity-cli-loop/issues/975))
|
||||
* remove the skill grouping option in Setup and Settings so skills install directly under `skills/`, and stop showing first-run setup screens during updates ([#964](https://github.com/hatayama/unity-cli-loop/issues/964), [#968](https://github.com/hatayama/unity-cli-loop/issues/968), [#977](https://github.com/hatayama/unity-cli-loop/issues/977))
|
||||
|
||||
## [2.0.0](https://github.com/hatayama/unity-cli-loop/compare/v1.7.3...v2.0.0) (2026-04-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* execute-dynamic-code now runs more than 6x faster ([#901](https://github.com/hatayama/unity-cli-loop/issues/901)) ([f48cdaa](https://github.com/hatayama/unity-cli-loop/commit/f48cdaaee5e3df0f4035a8281a6b7fe8511df04c))
|
||||
* Setup Wizard now handles first-time skill installation, grouping skills into a subfolder, target detection, status reporting, and startup behavior more clearly and reliably ([#927](https://github.com/hatayama/unity-cli-loop/issues/927), [#950](https://github.com/hatayama/unity-cli-loop/issues/950), [#951](https://github.com/hatayama/unity-cli-loop/issues/951), [#952](https://github.com/hatayama/unity-cli-loop/issues/952), [#953](https://github.com/hatayama/unity-cli-loop/issues/953), [#954](https://github.com/hatayama/unity-cli-loop/issues/954), [#963](https://github.com/hatayama/unity-cli-loop/issues/963), [#922](https://github.com/hatayama/unity-cli-loop/issues/922))
|
||||
* Windows users can follow PowerShell-specific PlayMode automation examples ([#947](https://github.com/hatayama/unity-cli-loop/issues/947)) ([59e50b4](https://github.com/hatayama/unity-cli-loop/commit/59e50b4e26f4e49c08761108737ae5a702a22788))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow installation without the new Input System package ([#938](https://github.com/hatayama/unity-cli-loop/issues/938)) ([b08d899](https://github.com/hatayama/unity-cli-loop/commit/b08d899856d8c760aae7856b33f6a5bb3cc06d7f))
|
||||
* compile commands stay more reliable while the Unity server recovers ([#925](https://github.com/hatayama/unity-cli-loop/issues/925)) ([0f63ed5](https://github.com/hatayama/unity-cli-loop/commit/0f63ed5fdd9dc20ddc6b005dc1c7a4d9d1900090))
|
||||
* Dynamic code commands recover more cleanly after Unity restarts ([#944](https://github.com/hatayama/unity-cli-loop/issues/944)) ([bdbe286](https://github.com/hatayama/unity-cli-loop/commit/bdbe286d710bb2c7415b4f401d3b65e8c98f9e13))
|
||||
* invalid EditMode test requests now return a clear error during play mode ([#940](https://github.com/hatayama/unity-cli-loop/issues/940)) ([ed0c7ea](https://github.com/hatayama/unity-cli-loop/commit/ed0c7eaebfa5430fbdf95bfbd77f36e3fa500f2c))
|
||||
* make MCP deprecation easier to notice in the settings window ([#948](https://github.com/hatayama/unity-cli-loop/issues/948)) ([0ace70d](https://github.com/hatayama/unity-cli-loop/commit/0ace70d944fca67172ce24d3bca84b91a76c36a0))
|
||||
* make the setup and settings windows easier to use ([#932](https://github.com/hatayama/unity-cli-loop/issues/932)) ([6d61a7d](https://github.com/hatayama/unity-cli-loop/commit/6d61a7dccc418b2c8a41cab4132accf27780afd9))
|
||||
* prevent installation errors when the Unity Test Framework package is missing ([#939](https://github.com/hatayama/unity-cli-loop/issues/939)) ([958e4b5](https://github.com/hatayama/unity-cli-loop/commit/958e4b5050f64a47dff00575c670ebf5a9ad3196))
|
||||
* `uloop launch` now waits for Unity to finish starting ([#955](https://github.com/hatayama/unity-cli-loop/issues/955)) ([9514cc9](https://github.com/hatayama/unity-cli-loop/commit/9514cc90dc1427452ff3f998bb5b1e956901e86d))
|
||||
|
||||
## [1.7.3](https://github.com/hatayama/unity-cli-loop/compare/v1.7.2...v1.7.3) (2026-04-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Avoid false "Unity not running" errors when the editor is still open ([#919](https://github.com/hatayama/unity-cli-loop/issues/919)) ([5376279](https://github.com/hatayama/unity-cli-loop/commit/53762791b70192efcf7fea2fcdf8dc8d91aa0c2d))
|
||||
* show the setup wizard when the package version changes ([#920](https://github.com/hatayama/unity-cli-loop/issues/920)) ([4f99cbc](https://github.com/hatayama/unity-cli-loop/commit/4f99cbc777d5eb1867815a3f22965205f0e01a69))
|
||||
* stabilize npm publishing and remove CLI bin warning ([#918](https://github.com/hatayama/unity-cli-loop/issues/918)) ([1b7a7f1](https://github.com/hatayama/unity-cli-loop/commit/1b7a7f12865767f5faf27529c5aa7a8502cb9a42))
|
||||
|
||||
## [1.7.2](https://github.com/hatayama/unity-cli-loop/compare/v1.7.1...v1.7.2) (2026-04-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove deprecation warning from uloop update ([#913](https://github.com/hatayama/unity-cli-loop/issues/913)) ([61ed144](https://github.com/hatayama/unity-cli-loop/commit/61ed144ffa1c0b8e2b6a10cb9eb587dada3fc1c6))
|
||||
|
||||
## [1.7.1](https://github.com/hatayama/unity-cli-loop/compare/v1.7.0...v1.7.1) (2026-04-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* avoid misleading Unity editor availability errors ([#911](https://github.com/hatayama/unity-cli-loop/issues/911)) ([4c2b7a6](https://github.com/hatayama/unity-cli-loop/commit/4c2b7a63bd4a31f0e84fa0964a28c268b33eb9c3))
|
||||
* require skills directories for auto-install detection ([#912](https://github.com/hatayama/unity-cli-loop/issues/912)) ([8832050](https://github.com/hatayama/unity-cli-loop/commit/8832050f7f4d9b94072ee33be2be0aeba90edf44))
|
||||
* unblock TypeScript server dependency updates by resolving npm audit findings ([#907](https://github.com/hatayama/unity-cli-loop/issues/907)) ([ffa3863](https://github.com/hatayama/unity-cli-loop/commit/ffa386365f31a8e908d2059e986218e1b113bd70))
|
||||
|
||||
## [1.7.0](https://github.com/hatayama/unity-cli-loop/compare/v1.6.4...v1.7.0) (2026-04-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Improve execute-dynamic-code performance by pre-resolving using directives ([#889](https://github.com/hatayama/unity-cli-loop/issues/889)) ([ba94c26](https://github.com/hatayama/unity-cli-loop/commit/ba94c268edaa948d53952ae76baa2c99b8ba1d85))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* restore Unity settings after interrupted atomic writes ([#898](https://github.com/hatayama/unity-cli-loop/issues/898)) ([887800d](https://github.com/hatayama/unity-cli-loop/commit/887800dc18caa695ee3926827ac51dfe7a18183a))
|
||||
|
||||
## [1.6.4](https://github.com/hatayama/unity-cli-loop/compare/v1.6.3...v1.6.4) (2026-04-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Deduplicate assembly references by name and add architecture overview to CLAUDE.md ([#887](https://github.com/hatayama/unity-cli-loop/issues/887)) ([cb0415e](https://github.com/hatayama/unity-cli-loop/commit/cb0415ed913be4fcf3cb8b90a3d54e3622cc9489))
|
||||
|
||||
## [1.6.3](https://github.com/hatayama/unity-cli-loop/compare/v1.6.2...v1.6.3) (2026-04-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Detect and auto-recover from silent MCP server loop exit ([#871](https://github.com/hatayama/unity-cli-loop/issues/871)) ([d0430c8](https://github.com/hatayama/unity-cli-loop/commit/d0430c8e0ff1d71e37dabef9099c654565368f61))
|
||||
* Hide --port option from help, docs, and skill descriptions ([#873](https://github.com/hatayama/unity-cli-loop/issues/873)) ([eec4831](https://github.com/hatayama/unity-cli-loop/commit/eec48313e3c17d12de424d1eeab3b1a967b1c346))
|
||||
* Prevent CLI from connecting to wrong Unity instance via stale port ([#875](https://github.com/hatayama/unity-cli-loop/issues/875)) ([2e577c8](https://github.com/hatayama/unity-cli-loop/commit/2e577c834e23706bc692d2303d3db417c6ef6098))
|
||||
|
||||
## [1.6.2](https://github.com/hatayama/unity-cli-loop/compare/v1.6.1...v1.6.2) (2026-03-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Prevent SetupWizardWindow from showing on package upgrade ([#861](https://github.com/hatayama/unity-cli-loop/issues/861)) ([358a32f](https://github.com/hatayama/unity-cli-loop/commit/358a32f8b8821d02e529be104762a3fc59de45a6))
|
||||
|
||||
## [1.6.1](https://github.com/hatayama/unity-cli-loop/compare/v1.6.0...v1.6.1) (2026-03-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Prevent SetupWizardWindow from showing for existing users on upgrade ([#857](https://github.com/hatayama/unity-cli-loop/issues/857)) ([c9d4346](https://github.com/hatayama/unity-cli-loop/commit/c9d43466dfb52d074e609631eac8b9b1876565e8))
|
||||
|
||||
## [1.6.0](https://github.com/hatayama/unity-cli-loop/compare/v1.5.1...v1.6.0) (2026-03-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add SetupWizardWindow for step-by-step onboarding ([#855](https://github.com/hatayama/unity-cli-loop/issues/855)) ([a723059](https://github.com/hatayama/unity-cli-loop/commit/a723059b0eebeeaf2b58663ba39293b9453afcca))
|
||||
* Extract server status/controls UI into standalone ServerEditorWindow ([#853](https://github.com/hatayama/unity-cli-loop/issues/853)) ([7934fba](https://github.com/hatayama/unity-cli-loop/commit/7934fba02f42e5e042f891b25a0322ea310889fd))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* correct skill directory mapping to match CLI target-config ([#852](https://github.com/hatayama/unity-cli-loop/issues/852)) ([4818f52](https://github.com/hatayama/unity-cli-loop/commit/4818f528102def6b7fd4d3071bd88e6400f44a12))
|
||||
|
||||
## [1.5.1](https://github.com/hatayama/unity-cli-loop/compare/v1.5.0...v1.5.1) (2026-03-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* clarify execute-dynamic-code skill parameters ([#847](https://github.com/hatayama/unity-cli-loop/issues/847)) ([89bbff8](https://github.com/hatayama/unity-cli-loop/commit/89bbff8fbe9107a20981d239a19f7dccf5e5ae34))
|
||||
|
||||
## [1.5.0](https://github.com/hatayama/unity-cli-loop/compare/v1.4.0...v1.5.0) (2026-03-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Remove 3 redundant MCP tools and add Design Philosophy section ([#837](https://github.com/hatayama/unity-cli-loop/issues/837)) ([11412b6](https://github.com/hatayama/unity-cli-loop/commit/11412b6de429023b630a24cd3fbf685ae7274048))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* replay progress bar always appearing at 100% ([#839](https://github.com/hatayama/unity-cli-loop/issues/839)) ([2880453](https://github.com/hatayama/unity-cli-loop/commit/2880453c747fd66092081f97982867368fdb8997))
|
||||
|
||||
## [1.4.0](https://github.com/hatayama/unity-cli-loop/compare/v1.3.0...v1.4.0) (2026-03-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Migrate dynamic code compilation from Roslyn to AssemblyBuilder with enhanced security ([#829](https://github.com/hatayama/unity-cli-loop/issues/829)) ([0ab2b87](https://github.com/hatayama/unity-cli-loop/commit/0ab2b8768ea286e3aecae8bd866ee72e2550ce48))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* refine MCP tool settings UI ([#836](https://github.com/hatayama/unity-cli-loop/issues/836)) ([16c3c87](https://github.com/hatayama/unity-cli-loop/commit/16c3c87e3b34ca1c3750f0923ec2ecbbfe27d82f))
|
||||
* suppress idle overlay reactivation after mouse release during input replay ([#827](https://github.com/hatayama/unity-cli-loop/issues/827)) ([7133e9c](https://github.com/hatayama/unity-cli-loop/commit/7133e9c1a53473b25bcdf6f5712d5806c2147da6))
|
||||
|
||||
## [1.3.0](https://github.com/hatayama/unity-cli-loop/compare/v1.2.1...v1.3.0) (2026-03-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add mouse input visualization overlay with prefab workflow ([#806](https://github.com/hatayama/unity-cli-loop/issues/806)) ([c531459](https://github.com/hatayama/unity-cli-loop/commit/c531459ac9e40c5e72f8fa250a909ec166ac5b50))
|
||||
* Input recording/replay system ([#814](https://github.com/hatayama/unity-cli-loop/issues/814)) ([d7a7f58](https://github.com/hatayama/unity-cli-loop/commit/d7a7f58096020a76caa4fe04392f96558a91f6c0))
|
||||
* Replace runtime overlay generation with Prefab-based architecture and improve visualization ([#811](https://github.com/hatayama/unity-cli-loop/issues/811)) ([3ff8b09](https://github.com/hatayama/unity-cli-loop/commit/3ff8b090041360483b6637eae5157e4084121a9c))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* clean up keyboard overlay preview badges ([#813](https://github.com/hatayama/unity-cli-loop/issues/813)) ([4caf06a](https://github.com/hatayama/unity-cli-loop/commit/4caf06a3b7860cd63f0285aae2d39054a3ea7269))
|
||||
* Remove HideFlags.DontSave from overlay canvas to fix PlayMode exit cleanup ([#812](https://github.com/hatayama/unity-cli-loop/issues/812)) ([3957641](https://github.com/hatayama/unity-cli-loop/commit/395764134e80c131b6d70a63ce7806c2fd578032))
|
||||
|
||||
## [1.2.1](https://github.com/hatayama/unity-cli-loop/compare/v1.2.0...v1.2.1) (2026-03-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* narrow EditorDialog preprocessor guard to UNITY_6000_3_OR_NEWER ([#804](https://github.com/hatayama/unity-cli-loop/issues/804)) ([65fce9e](https://github.com/hatayama/unity-cli-loop/commit/65fce9e0ce67fc40cbc818322c175de0aeefd889))
|
||||
* Remove redundant .meta files causing import warnings ([#802](https://github.com/hatayama/unity-cli-loop/issues/802)) ([7d662cd](https://github.com/hatayama/unity-cli-loop/commit/7d662cd9d5de78204339903ee8e0c7521cba40dc))
|
||||
|
||||
## [1.2.0](https://github.com/hatayama/unity-cli-loop/compare/v1.1.0...v1.2.0) (2026-03-18)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add simulate-mouse-input tool and split simulate-mouse into UI/Input System tools ([#799](https://github.com/hatayama/unity-cli-loop/issues/799)) ([a465640](https://github.com/hatayama/unity-cli-loop/commit/a465640ffa16a0d136956937e25dedaa61998b7a))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Use informational dialog icon for skill installation success on Unity 6+ ([#797](https://github.com/hatayama/unity-cli-loop/issues/797)) ([0326b38](https://github.com/hatayama/unity-cli-loop/commit/0326b38b1bd3e0089d17744a043b1fb38a7943fa))
|
||||
|
||||
## [1.1.0](https://github.com/hatayama/unity-cli-loop/compare/v1.0.2...v1.1.0) (2026-03-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* keyboard simulation ([#783](https://github.com/hatayama/unity-cli-loop/issues/783)) ([8d632c4](https://github.com/hatayama/unity-cli-loop/commit/8d632c4fa55b03b72fdf4ce75feca11e3ffb1060))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fix submenu misrender in SkillsTarget dropdown ([#787](https://github.com/hatayama/unity-cli-loop/issues/787)) ([28731df](https://github.com/hatayama/unity-cli-loop/commit/28731df6c48681d445e47e9bc38eb521b9fd7b23))
|
||||
* Windows CLI build support ([#794](https://github.com/hatayama/unity-cli-loop/issues/794)) ([ba7d382](https://github.com/hatayama/unity-cli-loop/commit/ba7d3823762b8bc11ebe57e64caf84d1b86a5faa))
|
||||
|
||||
## [1.0.2](https://github.com/hatayama/unity-cli-loop/compare/v1.0.1...v1.0.2) (2026-03-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update repository URLs from uLoopMCP to unity-cli-loop ([#779](https://github.com/hatayama/unity-cli-loop/issues/779)) ([35e56a0](https://github.com/hatayama/unity-cli-loop/commit/35e56a0751f0ec0a57b025f9a539d5918328ba0b))
|
||||
|
||||
## [1.0.1](https://github.com/hatayama/unity-cli-loop/compare/v1.0.0...v1.0.1) (2026-03-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Add missing .meta files for playmode skill references ([#773](https://github.com/hatayama/unity-cli-loop/issues/773)) ([19dcf5d](https://github.com/hatayama/unity-cli-loop/commit/19dcf5d0b3aa9eeef7de62c9f7003b3457beab7a))
|
||||
|
||||
## [0.70.1](https://github.com/hatayama/uLoopMCP/compare/v0.70.0...v0.70.1) (2026-03-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Classify csc.rsp compiler diagnostics correctly in get-logs LogType filter ([#761](https://github.com/hatayama/uLoopMCP/issues/761)) ([#767](https://github.com/hatayama/uLoopMCP/issues/767)) ([7069c5f](https://github.com/hatayama/uLoopMCP/commit/7069c5f5a0b7d646060200373515d65fa8d24809))
|
||||
|
||||
## [0.70.0](https://github.com/hatayama/uLoopMCP/compare/v0.69.6...v0.70.0) (2026-03-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add simulate-mouse tool for PlayMode UI interaction ([#759](https://github.com/hatayama/uLoopMCP/issues/759)) ([5679f34](https://github.com/hatayama/uLoopMCP/commit/5679f342932a67aecefbff804bd7265fdd6ec00d))
|
||||
|
||||
## [0.69.6](https://github.com/hatayama/uLoopMCP/compare/v0.69.5...v0.69.6) (2026-03-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* apply context: fork to uloop-execute-dynamic-code skill and add wiring references ([#743](https://github.com/hatayama/uLoopMCP/issues/743)) ([4ab452b](https://github.com/hatayama/uLoopMCP/commit/4ab452ba9efde76704b18d58d403761df2128941))
|
||||
|
||||
## [0.69.5](https://github.com/hatayama/uLoopMCP/compare/v0.69.4...v0.69.5) (2026-03-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* isolate Roslyn dependencies via shared registry ([#741](https://github.com/hatayama/uLoopMCP/issues/741)) ([c96a3a2](https://github.com/hatayama/uLoopMCP/commit/c96a3a2e06a268cc4daa5cb0eb9936b638abf5bb))
|
||||
|
||||
## [0.69.4](https://github.com/hatayama/uLoopMCP/compare/v0.69.3...v0.69.4) (2026-03-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Standardize SKILL.md descriptions and reduce verbosity ([#733](https://github.com/hatayama/uLoopMCP/issues/733)) ([a743607](https://github.com/hatayama/uLoopMCP/commit/a743607a9bfa6ecdf5c31946f3776f662f4e0e15))
|
||||
* Sync lint-staged version in package.json with package-lock.json ([#735](https://github.com/hatayama/uLoopMCP/issues/735)) ([60eff43](https://github.com/hatayama/uLoopMCP/commit/60eff43cf8762f1075b3cb8e214d483aaf55af6e))
|
||||
|
||||
## [0.69.3](https://github.com/hatayama/uLoopMCP/compare/v0.69.2...v0.69.3) (2026-03-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Handle Win32Exception in skills install process start ([#730](https://github.com/hatayama/uLoopMCP/issues/730)) ([cd7a4a3](https://github.com/hatayama/uLoopMCP/commit/cd7a4a3a19ab1d4830d1131074caeab4e85969e1))
|
||||
* Update hono and @hono/node-server to resolve high severity vulnerabilities ([#731](https://github.com/hatayama/uLoopMCP/issues/731)) ([135044a](https://github.com/hatayama/uLoopMCP/commit/135044ae761ef50144fb3a23c5118d6e53367ffe))
|
||||
|
||||
## [0.69.2](https://github.com/hatayama/uLoopMCP/compare/v0.69.1...v0.69.2) (2026-03-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Prevent rename migration from shadowing legacy settings migration ([#725](https://github.com/hatayama/uLoopMCP/issues/725)) ([b03337b](https://github.com/hatayama/uLoopMCP/commit/b03337be8892cc807e8f6a47beab023c7d985bc9))
|
||||
|
||||
## [0.69.1](https://github.com/hatayama/uLoopMCP/compare/v0.69.0...v0.69.1) (2026-03-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Add GetVersionTool to core package and improve CLI error handling ([#723](https://github.com/hatayama/uLoopMCP/issues/723)) ([21afcc8](https://github.com/hatayama/uLoopMCP/commit/21afcc899d7b8857737a7bda9480ccb447f9083c))
|
||||
|
||||
## [0.69.0](https://github.com/hatayama/uLoopMCP/compare/v0.68.3...v0.69.0) (2026-03-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add delete button for MCP configuration ([#718](https://github.com/hatayama/uLoopMCP/issues/718)) ([3e4c4fb](https://github.com/hatayama/uLoopMCP/commit/3e4c4fb907b1fc446e19f3eba98bf6afc174a3ee))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Prevent CLI from sending commands to wrong Unity instance ([#719](https://github.com/hatayama/uLoopMCP/issues/719)) ([d0e47b3](https://github.com/hatayama/uLoopMCP/commit/d0e47b392677af36dfba740e9c7e80579877bad8))
|
||||
* Prevent toggle ChangeEvent from collapsing parent Foldout ([#721](https://github.com/hatayama/uLoopMCP/issues/721)) ([f6caf9b](https://github.com/hatayama/uLoopMCP/commit/f6caf9b7066f5f4587788c7e3b2d9e8ce0062aa3))
|
||||
* Rename settings.security.json to settings.permissions.json ([#713](https://github.com/hatayama/uLoopMCP/issues/713)) ([ae1a21a](https://github.com/hatayama/uLoopMCP/commit/ae1a21af2a575a94fd96476feb2fe05bfbb77f88))
|
||||
|
||||
## [0.68.3](https://github.com/hatayama/uLoopMCP/compare/v0.68.2...v0.68.3) (2026-03-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Group help commands by category in uloop -h ([#708](https://github.com/hatayama/uLoopMCP/issues/708)) ([dbfe539](https://github.com/hatayama/uLoopMCP/commit/dbfe5394172a80f550304b95787d36d4046d0404))
|
||||
|
||||
## [0.68.2](https://github.com/hatayama/uLoopMCP/compare/v0.68.1...v0.68.2) (2026-03-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Hide disabled tools from CLI help and shell completion output ([#705](https://github.com/hatayama/uLoopMCP/issues/705)) ([3f49750](https://github.com/hatayama/uLoopMCP/commit/3f4975078e324b56e4363c510c6081b7373afa63))
|
||||
|
||||
## [0.68.1](https://github.com/hatayama/uLoopMCP/compare/v0.68.0...v0.68.1) (2026-03-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* stabilize Tool Settings toggle interaction during UI refresh ([#702](https://github.com/hatayama/uLoopMCP/issues/702)) ([6b4ba25](https://github.com/hatayama/uLoopMCP/commit/6b4ba25d2d7821abecf0a990c7021a9c5de64518))
|
||||
|
||||
## [0.68.0](https://github.com/hatayama/uLoopMCP/compare/v0.67.5...v0.68.0) (2026-02-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add per-tool enable/disable toggle for MCP tools ([#698](https://github.com/hatayama/uLoopMCP/issues/698)) ([5ca8b4c](https://github.com/hatayama/uLoopMCP/commit/5ca8b4ca99ed618148a93d74a9dd76fb6f0cff60))
|
||||
* Migrate security settings to project-scoped .uloop/settings.security.json ([#696](https://github.com/hatayama/uLoopMCP/issues/696)) ([222d46e](https://github.com/hatayama/uLoopMCP/commit/222d46e485b5b0006f8ed9e74e19e191938f2010))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Improve tool settings UI message to mention context window benefit ([#700](https://github.com/hatayama/uLoopMCP/issues/700)) ([040c8bd](https://github.com/hatayama/uLoopMCP/commit/040c8bd8687d8925219285f3b401c3d6900a5f3e))
|
||||
* Rename capture-window MCP tool to screenshot ([#701](https://github.com/hatayama/uLoopMCP/issues/701)) ([b178bc8](https://github.com/hatayama/uLoopMCP/commit/b178bc899a87c9ad4fce86adc27611c56a9f2811))
|
||||
|
||||
## [0.67.5](https://github.com/hatayama/uLoopMCP/compare/v0.67.4...v0.67.5) (2026-02-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add meta ([#692](https://github.com/hatayama/uLoopMCP/issues/692)) ([34b29de](https://github.com/hatayama/uLoopMCP/commit/34b29de1ea497e8755f6c8cf711eebf7b1378f15))
|
||||
|
||||
## [0.67.4](https://github.com/hatayama/uLoopMCP/compare/v0.67.3...v0.67.4) (2026-02-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* correct SKILL.md parameter docs to match C# implementations ([#689](https://github.com/hatayama/uLoopMCP/issues/689)) ([e76ab29](https://github.com/hatayama/uLoopMCP/commit/e76ab29372c28f6513016d11b9fb7cb18e6f025f))
|
||||
* Fix incomplete property serialization in ComponentPropertySerializer ([#691](https://github.com/hatayama/uLoopMCP/issues/691)) ([2db6eb3](https://github.com/hatayama/uLoopMCP/commit/2db6eb3f63865ab6e56c41cabbbe3a37d183b164))
|
||||
|
||||
## [0.67.3](https://github.com/hatayama/uLoopMCP/compare/v0.67.2...v0.67.3) (2026-02-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update READMEs with --project-path option and comprehensive CLI reference ([#687](https://github.com/hatayama/uLoopMCP/issues/687)) ([7829c9d](https://github.com/hatayama/uLoopMCP/commit/7829c9dc6e21bc117b353251b71eab7bbe95b942))
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4ca30975116b542e8867604b2096e31b
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Client/Packages/io.github.hatayama.uloopmcp/Editor.meta
Normal file
8
Client/Packages/io.github.hatayama.uloopmcp/Editor.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15701dfe5e2e04a058ba013c82937a72
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dcc9f571c2a194f2785077de39c4f31c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a66a2fc90232147fda7ad7d724b6ba38
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception thrown when tool parameter JSON fails type validation.
|
||||
/// </summary>
|
||||
public class ParameterValidationException : Exception
|
||||
{
|
||||
public ParameterValidationException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public ParameterValidationException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7bc2a292b0560427bb23904f40141e37
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,447 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// Current client context for JSON-RPC processing
|
||||
/// </summary>
|
||||
public class ClientExecutionContext
|
||||
{
|
||||
public string Endpoint { get; }
|
||||
|
||||
public ClientExecutionContext(string endpoint)
|
||||
{
|
||||
Endpoint = endpoint;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class specialized in handling JSON-RPC 2.0 processing
|
||||
///
|
||||
/// Design document reference: Packages/src/Editor/ARCHITECTURE.md
|
||||
///
|
||||
/// Related classes:
|
||||
/// - UnityApiHandler: Executes Unity commands based on JSON-RPC requests
|
||||
/// - McpBridgeServer: TCP server that receives JSON-RPC messages from TypeScript
|
||||
/// - MainThreadSwitcher: Ensures Unity API calls run on the main thread
|
||||
/// - JsonRpcRequest: Request model for JSON-RPC 2.0 protocol
|
||||
/// - ClientExecutionContext: Thread-local context for tracking current client
|
||||
///
|
||||
/// Processing flow:
|
||||
/// 1. Receives JSON message from McpBridgeServer
|
||||
/// 2. Parses and validates JSON-RPC 2.0 format
|
||||
/// 3. Sets client context for the current thread
|
||||
/// 4. Delegates to UnityApiHandler for command execution
|
||||
/// 5. Formats response according to JSON-RPC 2.0 specification
|
||||
/// 6. Returns JSON response to be sent back to client
|
||||
/// </summary>
|
||||
public static class JsonRpcProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Current client context for async operations
|
||||
/// </summary>
|
||||
private static readonly AsyncLocal<ClientExecutionContext> _currentClientContext = new();
|
||||
|
||||
/// <summary>
|
||||
/// Get current client context (ProcessID and Endpoint)
|
||||
/// </summary>
|
||||
public static ClientExecutionContext CurrentClientContext => _currentClientContext.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Process JSON-RPC request and generate response with client context
|
||||
/// </summary>
|
||||
public static async Task<string> ProcessRequest(string jsonRequest, string clientEndpoint)
|
||||
{
|
||||
var context = new ClientExecutionContext(clientEndpoint);
|
||||
_currentClientContext.Value = context;
|
||||
|
||||
try
|
||||
{
|
||||
return await ProcessRequest(jsonRequest);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_currentClientContext.Value = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process JSON-RPC request and generate response
|
||||
/// </summary>
|
||||
public static async Task<string> ProcessRequest(string jsonRequest)
|
||||
{
|
||||
try
|
||||
{
|
||||
JsonRpcRequest request = ParseRequest(jsonRequest);
|
||||
|
||||
if (request.IsNotification)
|
||||
{
|
||||
ProcessNotification(request);
|
||||
return null;
|
||||
}
|
||||
|
||||
return await ProcessRpcRequest(request, jsonRequest);
|
||||
}
|
||||
catch (JsonReaderException ex)
|
||||
{
|
||||
return CreateErrorResponse(null, ex);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return CreateErrorResponse(null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse JSON-RPC request string into structured object
|
||||
/// </summary>
|
||||
private static JsonRpcRequest ParseRequest(string jsonRequest)
|
||||
{
|
||||
JObject request = JObject.Parse(jsonRequest);
|
||||
return new JsonRpcRequest
|
||||
{
|
||||
Method = request["method"]?.ToString(),
|
||||
Params = request["params"],
|
||||
Id = request["id"]?.ToObject<object>(),
|
||||
UloopMetadata = ParseUloopMetadata(request["x-uloop"])
|
||||
};
|
||||
}
|
||||
|
||||
private static JsonRpcRequestUloopMetadata ParseUloopMetadata(JToken metadataToken)
|
||||
{
|
||||
if (metadataToken == null || metadataToken.Type == JTokenType.Null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new JsonRpcRequestUloopMetadata
|
||||
{
|
||||
ExpectedProjectRoot = metadataToken["expectedProjectRoot"]?.ToString(),
|
||||
ExpectedServerSessionId = metadataToken["expectedServerSessionId"]?.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process notification (fire-and-forget)
|
||||
///
|
||||
/// Note: Notifications are one-way messages without response.
|
||||
/// Currently handles:
|
||||
/// - focus-window: Brings Unity Editor window to foreground (used after request timeout)
|
||||
/// </summary>
|
||||
private static void ProcessNotification(JsonRpcRequest request)
|
||||
{
|
||||
if (string.IsNullOrEmpty(request.Method))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (request.Method)
|
||||
{
|
||||
case "focus-window":
|
||||
HandleFocusWindowNotification();
|
||||
break;
|
||||
default:
|
||||
// Unknown notification - ignore silently
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle focus-window notification.
|
||||
/// Note: focus-window is now handled at OS level by TypeScript MCP server using launch-unity package.
|
||||
/// This notification handler remains for protocol compatibility but does nothing.
|
||||
/// </summary>
|
||||
private static void HandleFocusWindowNotification()
|
||||
{
|
||||
// Intentionally empty - focus-window is handled by TypeScript MCP server at OS level
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process RPC request and return response JSON
|
||||
/// </summary>
|
||||
private static async Task<string> ProcessRpcRequest(JsonRpcRequest request, string originalJson)
|
||||
{
|
||||
Stopwatch requestStopwatch = Stopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
ValidateClientIdentityIfNeeded(request);
|
||||
|
||||
Stopwatch mainThreadWaitStopwatch = Stopwatch.StartNew();
|
||||
await MainThreadSwitcher.SwitchToMainThread();
|
||||
mainThreadWaitStopwatch.Stop();
|
||||
|
||||
Stopwatch toolStopwatch = Stopwatch.StartNew();
|
||||
BaseToolResponse result = await ExecuteMethod(request.Method, request.Params);
|
||||
toolStopwatch.Stop();
|
||||
|
||||
AppendExecuteDynamicCodeTimingsIfSupported(
|
||||
result,
|
||||
mainThreadWaitStopwatch.Elapsed.TotalMilliseconds,
|
||||
toolStopwatch.Elapsed.TotalMilliseconds,
|
||||
requestStopwatch.Elapsed.TotalMilliseconds);
|
||||
string response = CreateSuccessResponse(request.Id, result);
|
||||
return response;
|
||||
}
|
||||
catch (JsonSerializationException ex)
|
||||
{
|
||||
UnityEngine.Debug.LogError($"[JsonRpcProcessor] JSON serialization error: {ex.Message}\nStack trace: {ex.StackTrace}");
|
||||
return CreateErrorResponse(request.Id, ex);
|
||||
}
|
||||
catch (ParameterValidationException ex)
|
||||
{
|
||||
LogParameterValidationException(ex);
|
||||
return CreateErrorResponse(request.Id, ex);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UnityEngine.Debug.LogError($"[JsonRpcProcessor] Error: {ex.Message}\nStack trace: {ex.StackTrace}");
|
||||
return CreateErrorResponse(request.Id, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateClientIdentityIfNeeded(JsonRpcRequest request)
|
||||
{
|
||||
JsonRpcRequestIdentityValidator.Validate(
|
||||
request?.UloopMetadata,
|
||||
McpEditorSettings.GetProjectRootPath(),
|
||||
McpEditorSettings.GetServerSessionId());
|
||||
}
|
||||
|
||||
private static void LogParameterValidationException(ParameterValidationException exception)
|
||||
{
|
||||
if (JsonRpcRequestIdentityValidator.IsExpectedRetryableFailure(exception))
|
||||
{
|
||||
#if ULOOPMCP_DEBUG
|
||||
UnityEngine.Debug.LogWarning(
|
||||
$"[JsonRpcProcessor] Expected retryable parameter validation event: {exception.Message}");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
UnityEngine.Debug.LogError(
|
||||
$"[JsonRpcProcessor] Parameter validation error: {exception.Message}\nStack trace: {exception.StackTrace}");
|
||||
}
|
||||
|
||||
private static void AppendExecuteDynamicCodeTimingsIfSupported(
|
||||
BaseToolResponse result,
|
||||
double mainThreadWaitMilliseconds,
|
||||
double toolTotalMilliseconds,
|
||||
double requestTotalMilliseconds)
|
||||
{
|
||||
if (result is not ExecuteDynamicCodeResponse executeDynamicCodeResponse)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ExecuteDynamicCodeResponseTimingAugmenter.AppendTimingEntries(
|
||||
executeDynamicCodeResponse,
|
||||
mainThreadWaitMilliseconds,
|
||||
toolTotalMilliseconds,
|
||||
requestTotalMilliseconds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create JSON-RPC success response
|
||||
/// </summary>
|
||||
/// <param name="id">Request ID - must be same type as received (string/number/null per JSON-RPC spec)</param>
|
||||
/// <param name="result">Command execution result</param>
|
||||
private static string CreateSuccessResponse(object id, BaseToolResponse result)
|
||||
{
|
||||
JsonSerializerSettings settings = new JsonSerializerSettings
|
||||
{
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
|
||||
MaxDepth = McpServerConfig.DEFAULT_JSON_MAX_DEPTH
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
JsonRpcSuccessResponse response = new JsonRpcSuccessResponse(
|
||||
McpServerConfig.JSONRPC_VERSION,
|
||||
id,
|
||||
result
|
||||
);
|
||||
return JsonConvert.SerializeObject(response, Formatting.None, settings);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Return safe fallback response for any serialization errors
|
||||
object fallbackResult = new
|
||||
{
|
||||
error = "Serialization failed - returning safe fallback",
|
||||
commandType = result != null ? result.GetType().Name : "unknown"
|
||||
};
|
||||
|
||||
JsonRpcSuccessResponse fallbackResponse = new JsonRpcSuccessResponse(
|
||||
McpServerConfig.JSONRPC_VERSION,
|
||||
id,
|
||||
fallbackResult
|
||||
);
|
||||
return JsonConvert.SerializeObject(fallbackResponse, Formatting.None);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create JSON-RPC error response
|
||||
/// </summary>
|
||||
/// <param name="id">Request ID - must be same type as received (string/number/null per JSON-RPC spec)</param>
|
||||
/// <param name="ex">Exception to convert to error response</param>
|
||||
private static string CreateErrorResponse(object id, Exception ex)
|
||||
{
|
||||
// Centralize exception -> user-facing message via UserFriendlyErrorConverter
|
||||
UserFriendlyErrorConverter handler = new UserFriendlyErrorConverter();
|
||||
UserFriendlyErrorDto exceptionResponse = handler.ProcessException(ex);
|
||||
|
||||
// Map UserFriendlyErrorDto to JsonRpcError
|
||||
string errorMessage = exceptionResponse.FriendlyMessage;
|
||||
|
||||
JsonRpcErrorData errorData;
|
||||
if (ex is McpSecurityException secEx)
|
||||
{
|
||||
errorData = new SecurityBlockedErrorData(secEx.ToolName, secEx.SecurityReason, exceptionResponse.Explanation ?? ex.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
errorData = new InternalErrorData(exceptionResponse.Explanation ?? ex.Message);
|
||||
}
|
||||
|
||||
JsonRpcErrorResponse errorResponse = new JsonRpcErrorResponse(
|
||||
McpServerConfig.JSONRPC_VERSION,
|
||||
id,
|
||||
new JsonRpcError(McpServerConfig.INTERNAL_ERROR_CODE, errorMessage, errorData)
|
||||
);
|
||||
|
||||
JsonSerializerSettings settings = new JsonSerializerSettings
|
||||
{
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
|
||||
MaxDepth = McpServerConfig.DEFAULT_JSON_MAX_DEPTH
|
||||
};
|
||||
|
||||
return JsonConvert.SerializeObject(errorResponse, Formatting.None, settings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute appropriate handler according to method name
|
||||
/// Use new command-based structure
|
||||
/// </summary>
|
||||
private static async Task<BaseToolResponse> ExecuteMethod(string method, JToken paramsToken)
|
||||
{
|
||||
return await UnityApiHandler.ExecuteCommandAsync(method, paramsToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constants for JSON-RPC error types
|
||||
/// </summary>
|
||||
public static class JsonRpcErrorTypes
|
||||
{
|
||||
public const string SecurityBlocked = "security_blocked";
|
||||
public const string InternalError = "internal_error";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for JSON-RPC error data
|
||||
/// </summary>
|
||||
public abstract class JsonRpcErrorData
|
||||
{
|
||||
public abstract string type { get; }
|
||||
|
||||
public string message { get; protected set; }
|
||||
|
||||
protected JsonRpcErrorData(string message)
|
||||
{
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Error data for security blocked commands
|
||||
/// </summary>
|
||||
public class SecurityBlockedErrorData : JsonRpcErrorData
|
||||
{
|
||||
public override string type => JsonRpcErrorTypes.SecurityBlocked;
|
||||
|
||||
public string command { get; }
|
||||
|
||||
public string reason { get; }
|
||||
|
||||
public SecurityBlockedErrorData(string command, string reason, string message) : base(message)
|
||||
{
|
||||
this.command = command;
|
||||
this.reason = reason;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Error data for internal errors
|
||||
/// </summary>
|
||||
public class InternalErrorData : JsonRpcErrorData
|
||||
{
|
||||
public override string type => JsonRpcErrorTypes.InternalError;
|
||||
|
||||
public InternalErrorData(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// JSON-RPC error object
|
||||
/// </summary>
|
||||
public class JsonRpcError
|
||||
{
|
||||
public int code { get; }
|
||||
|
||||
public string message { get; }
|
||||
|
||||
public JsonRpcErrorData data { get; }
|
||||
|
||||
public JsonRpcError(int code, string message, JsonRpcErrorData data)
|
||||
{
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// JSON-RPC success response
|
||||
/// </summary>
|
||||
public class JsonRpcSuccessResponse
|
||||
{
|
||||
public string jsonrpc { get; }
|
||||
|
||||
public object id { get; }
|
||||
|
||||
public object result { get; }
|
||||
|
||||
public JsonRpcSuccessResponse(string jsonRpc, object id, object result)
|
||||
{
|
||||
this.jsonrpc = jsonRpc;
|
||||
this.id = id;
|
||||
this.result = result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// JSON-RPC error response
|
||||
/// </summary>
|
||||
public class JsonRpcErrorResponse
|
||||
{
|
||||
public string jsonrpc { get; }
|
||||
|
||||
public object id { get; }
|
||||
|
||||
public JsonRpcError error { get; }
|
||||
|
||||
public JsonRpcErrorResponse(string jsonRpc, object id, JsonRpcError error)
|
||||
{
|
||||
this.jsonrpc = jsonRpc;
|
||||
this.id = id;
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3c8feac02c96413ba4dde4ba5990ef8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,37 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a parsed JSON-RPC request
|
||||
/// </summary>
|
||||
internal class JsonRpcRequest
|
||||
{
|
||||
public string Method { get; set; }
|
||||
/// <summary>
|
||||
/// JSON-RPC 2.0 spec allows params to be object, array, or null.
|
||||
/// We use JToken to accept any format, then convert to strongly-typed
|
||||
/// schema classes (e.g. PingSchema) in AbstractUnityCommand.ConvertToSchema.
|
||||
/// This provides flexibility at the protocol layer and type safety at the command layer.
|
||||
/// </summary>
|
||||
public JToken Params { get; set; }
|
||||
/// <summary>
|
||||
/// JSON-RPC 2.0 spec requires id type to match the request.
|
||||
/// Must be string, number, or null - same as received.
|
||||
/// </summary>
|
||||
public object Id { get; set; }
|
||||
public JsonRpcRequestUloopMetadata UloopMetadata { get; set; }
|
||||
/// <summary>
|
||||
/// JSON-RPC 2.0 notification flag. True when id is null/missing.
|
||||
/// Notifications are fire-and-forget messages that don't expect a response.
|
||||
/// Regular requests (with id) expect a response, notifications do not.
|
||||
/// </summary>
|
||||
public bool IsNotification => Id == null;
|
||||
}
|
||||
|
||||
internal class JsonRpcRequestUloopMetadata
|
||||
{
|
||||
public string ExpectedProjectRoot { get; set; }
|
||||
public string ExpectedServerSessionId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b3307d57ac9144e00842cc0e4a068b06
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
internal static class JsonRpcRequestIdentityValidator
|
||||
{
|
||||
internal const string ServerSessionChangedMessage = "Unity CLI Loop server session changed. Retry the command.";
|
||||
|
||||
public static void Validate(
|
||||
JsonRpcRequestUloopMetadata metadata,
|
||||
string actualProjectRoot,
|
||||
string actualServerSessionId)
|
||||
{
|
||||
if (metadata == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(metadata.ExpectedProjectRoot))
|
||||
{
|
||||
throw new ParameterValidationException("Invalid x-uloop metadata: expectedProjectRoot is required.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(metadata.ExpectedServerSessionId))
|
||||
{
|
||||
throw new ParameterValidationException("Invalid x-uloop metadata: expectedServerSessionId is required.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(actualProjectRoot))
|
||||
{
|
||||
throw new ParameterValidationException("Fast project validation is unavailable. Restart Unity CLI Loop and retry.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(actualServerSessionId))
|
||||
{
|
||||
throw new ParameterValidationException(ServerSessionChangedMessage);
|
||||
}
|
||||
|
||||
if (!string.Equals(metadata.ExpectedProjectRoot, actualProjectRoot, StringComparison.Ordinal))
|
||||
{
|
||||
throw new ParameterValidationException("Connected Unity instance belongs to a different project.");
|
||||
}
|
||||
|
||||
if (!string.Equals(metadata.ExpectedServerSessionId, actualServerSessionId, StringComparison.Ordinal))
|
||||
{
|
||||
throw new ParameterValidationException(ServerSessionChangedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool IsExpectedRetryableFailure(ParameterValidationException exception)
|
||||
{
|
||||
if (exception == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return string.Equals(
|
||||
exception.Message,
|
||||
ServerSessionChangedMessage,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 105ea7a6425e04852b1f41d84dabc8ec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 66830065e21e249c0876bf2ef4f0776b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1511443bffd3044d8a38e727e688c81f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,102 @@
|
||||
using System;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// Response schema for ClearConsole command
|
||||
/// Provides type-safe response structure for console clearing operation
|
||||
/// </summary>
|
||||
public class ClearConsoleResponse : BaseToolResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the console clear operation was successful
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of logs that were cleared from the console
|
||||
/// </summary>
|
||||
public int ClearedLogCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Breakdown of cleared logs by type
|
||||
/// </summary>
|
||||
public ClearedLogCounts ClearedCounts { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Message describing the clear operation result
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Error message if the operation failed
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new ClearConsoleResponse for successful operation
|
||||
/// </summary>
|
||||
public ClearConsoleResponse(bool success, int clearedLogCount, ClearedLogCounts clearedCounts, string message)
|
||||
{
|
||||
Success = success;
|
||||
ClearedLogCount = clearedLogCount;
|
||||
ClearedCounts = clearedCounts ?? new ClearedLogCounts();
|
||||
Message = message ?? string.Empty;
|
||||
ErrorMessage = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new ClearConsoleResponse for failed operation
|
||||
/// </summary>
|
||||
public ClearConsoleResponse(string errorMessage)
|
||||
{
|
||||
Success = false;
|
||||
ClearedLogCount = 0;
|
||||
ClearedCounts = new ClearedLogCounts();
|
||||
Message = string.Empty;
|
||||
ErrorMessage = errorMessage ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parameterless constructor for JSON deserialization
|
||||
/// </summary>
|
||||
public ClearConsoleResponse()
|
||||
{
|
||||
ClearedCounts = new ClearedLogCounts();
|
||||
Message = string.Empty;
|
||||
ErrorMessage = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Breakdown of cleared logs by type
|
||||
/// </summary>
|
||||
public class ClearedLogCounts
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of error logs that were cleared
|
||||
/// </summary>
|
||||
public int ErrorCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of warning logs that were cleared
|
||||
/// </summary>
|
||||
public int WarningCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of info logs that were cleared
|
||||
/// </summary>
|
||||
public int LogCount { get; set; }
|
||||
|
||||
public ClearedLogCounts()
|
||||
{
|
||||
}
|
||||
|
||||
public ClearedLogCounts(int errorCount, int warningCount, int logCount)
|
||||
{
|
||||
ErrorCount = errorCount;
|
||||
WarningCount = warningCount;
|
||||
LogCount = logCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dbbf8d96e00c54171b03c9c0d42822dc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// Schema for ClearConsole command parameters
|
||||
/// Provides type-safe parameter access for clearing Unity Console
|
||||
/// Related classes:
|
||||
/// - ConsoleUtility: Service layer for console operations
|
||||
/// - ClearConsoleResponse: Type-safe response structure
|
||||
/// - ClearConsoleCommand: Command implementation
|
||||
/// </summary>
|
||||
public class ClearConsoleSchema : BaseToolSchema
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether to add a confirmation log message after clearing
|
||||
/// </summary>
|
||||
[Description("Whether to add a confirmation log message after clearing")]
|
||||
public bool AddConfirmationMessage { get; set; } = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94a230c702f0e4d75b03dc706886b935
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// ClearConsole tool handler - Type-safe implementation using Schema and Response
|
||||
/// Clears Unity console logs for clean development workflow
|
||||
/// Related classes: ClearConsoleUseCase, ConsoleUtility, ClearConsoleSchema, ClearConsoleResponse
|
||||
/// </summary>
|
||||
[McpTool(Description = "Clear Unity console logs")]
|
||||
public class ClearConsoleTool : AbstractUnityTool<ClearConsoleSchema, ClearConsoleResponse>
|
||||
{
|
||||
public override string ToolName => "clear-console";
|
||||
|
||||
/// <summary>
|
||||
/// Execute console clear tool
|
||||
/// </summary>
|
||||
/// <param name="parameters">Type-safe parameters</param>
|
||||
/// <param name="cancellationToken">Cancellation token for timeout control</param>
|
||||
/// <returns>Clear operation result</returns>
|
||||
protected override async Task<ClearConsoleResponse> ExecuteAsync(ClearConsoleSchema parameters, CancellationToken cancellationToken)
|
||||
{
|
||||
// Create and execute ClearConsoleUseCase instance
|
||||
ClearConsoleUseCase useCase = new();
|
||||
return await useCase.ExecuteAsync(parameters, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b72387c853bad490fb5cb20f551f0781
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fc3fddf04059a4303be8a7f27aa53052
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
name: uloop-clear-console
|
||||
description: "Clear Unity Console entries. Use before compile, tests, or debugging when stale logs would hide the current result."
|
||||
---
|
||||
|
||||
# uloop clear-console
|
||||
|
||||
Clear Unity console logs.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
uloop clear-console [--add-confirmation-message]
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `--add-confirmation-message` | boolean | `false` | Add confirmation message after clearing |
|
||||
|
||||
## Global Options
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--project-path <path>` | Optional. Use only when the target Unity project is not the current directory. |
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
# Clear console
|
||||
uloop clear-console
|
||||
|
||||
# Clear with confirmation
|
||||
uloop clear-console --add-confirmation-message
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
Returns JSON with:
|
||||
- `Success` (boolean): Whether the clear operation succeeded
|
||||
- `ClearedLogCount` (number): Total number of log entries that were cleared
|
||||
- `ClearedCounts` (object): Breakdown by log type
|
||||
- `ErrorCount` (number): Errors cleared
|
||||
- `WarningCount` (number): Warnings cleared
|
||||
- `LogCount` (number): Info logs cleared
|
||||
- `Message` (string): Description of the result; carries the failure summary when the operation fails (e.g. `"Failed to clear console: ..."`)
|
||||
- `ErrorMessage` (string): Currently always empty for this tool — read `Message` for failure details
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f1d3cbc9cba645b398b9f4bbe9537c4
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 90328140d69f84f1e925caf7f710c0eb
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents information about a Unity component
|
||||
/// </summary>
|
||||
public class ComponentInfo
|
||||
{
|
||||
public string type { get; set; }
|
||||
public string fullTypeName { get; set; }
|
||||
public ComponentPropertyInfo[] properties { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a property of a Unity component
|
||||
/// </summary>
|
||||
public class ComponentPropertyInfo
|
||||
{
|
||||
public string name { get; set; }
|
||||
public string type { get; set; }
|
||||
public object value { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b8f54071c76614440b5775885e181d95
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8a3c04e7ca654807956364da02c4e85
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,208 @@
|
||||
#nullable enable
|
||||
#if ULOOPMCP_HAS_INPUT_SYSTEM
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
// Shared helper for applying Input System state changes at the correct update phase.
|
||||
// Both keyboard and mouse simulation need frame-precise timing so that
|
||||
// wasPressedThisFrame / wasReleasedThisFrame detect the injected state.
|
||||
internal static class InputSystemUpdateHelper
|
||||
{
|
||||
public static Task ApplyOnNextConfiguredUpdate(Action apply, CancellationToken ct)
|
||||
{
|
||||
InputUpdateType targetUpdateType = InputUpdateTypeResolver.Resolve();
|
||||
if (InputUpdateTypeResolver.RequiresExplicitUpdate())
|
||||
{
|
||||
return ApplyOnExplicitUpdate(apply, targetUpdateType, ct);
|
||||
}
|
||||
|
||||
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
|
||||
CancellationTokenRegistration registration = default;
|
||||
Action? callback = null;
|
||||
|
||||
callback = () =>
|
||||
{
|
||||
InputUpdateType currentUpdateType = InputState.currentUpdateType;
|
||||
if (!InputUpdateTypeResolver.IsMatch(currentUpdateType, targetUpdateType))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Assert(callback != null, "callback must be assigned before subscription");
|
||||
InputSystem.onBeforeUpdate -= callback;
|
||||
registration.Dispose();
|
||||
apply();
|
||||
tcs.TrySetResult(true);
|
||||
};
|
||||
|
||||
InputSystem.onBeforeUpdate += callback;
|
||||
if (ct.CanBeCanceled)
|
||||
{
|
||||
registration = ct.Register(() =>
|
||||
{
|
||||
Debug.Assert(callback != null, "callback must be assigned before cancellation");
|
||||
InputSystem.onBeforeUpdate -= callback;
|
||||
tcs.TrySetCanceled(ct);
|
||||
});
|
||||
}
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public static int GetMinimumObservationFrameCount()
|
||||
{
|
||||
if (!InputUpdateTypeResolver.RequiresExplicitUpdate())
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
InputUpdateType targetUpdateType = InputUpdateTypeResolver.Resolve();
|
||||
if (targetUpdateType != InputUpdateType.Manual)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Manual-mode projects often call InputSystem.Update from their own Update loop,
|
||||
// so zero-duration taps need one extra frame to remain visible to gameplay code.
|
||||
return 3;
|
||||
}
|
||||
|
||||
public static async Task WaitForObservationFrames(CancellationToken ct)
|
||||
{
|
||||
await WaitForRuntimeFrames(GetMinimumObservationFrameCount(), ct);
|
||||
}
|
||||
|
||||
public static async Task WaitForPressLifetime(float duration, CancellationToken ct)
|
||||
{
|
||||
int minimumObservationFrames = GetMinimumObservationFrameCount();
|
||||
int startFrameCount = Time.frameCount;
|
||||
float startTime = Time.realtimeSinceStartup;
|
||||
float elapsed = 0f;
|
||||
int observedFrames = 0;
|
||||
|
||||
while (observedFrames < minimumObservationFrames || elapsed < duration)
|
||||
{
|
||||
await EditorDelay.DelayFrame(1, ct);
|
||||
observedFrames = Time.frameCount - startFrameCount;
|
||||
elapsed = Time.realtimeSinceStartup - startTime;
|
||||
}
|
||||
}
|
||||
|
||||
public static void RunExplicitUpdate(InputUpdateType targetUpdateType)
|
||||
{
|
||||
InputSettings? settings = InputSystem.settings;
|
||||
if (settings == null)
|
||||
{
|
||||
InputSystem.Update();
|
||||
return;
|
||||
}
|
||||
|
||||
InputSettings.UpdateMode originalUpdateMode = settings.updateMode;
|
||||
InputSettings.UpdateMode targetUpdateMode = GetExplicitUpdateMode(targetUpdateType, originalUpdateMode);
|
||||
if (targetUpdateMode == originalUpdateMode)
|
||||
{
|
||||
InputSystem.Update();
|
||||
return;
|
||||
}
|
||||
|
||||
settings.updateMode = targetUpdateMode;
|
||||
try
|
||||
{
|
||||
InputSystem.Update();
|
||||
}
|
||||
finally
|
||||
{
|
||||
settings.updateMode = originalUpdateMode;
|
||||
}
|
||||
}
|
||||
|
||||
private static Task ApplyOnExplicitUpdate(Action apply, InputUpdateType targetUpdateType, CancellationToken ct)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
|
||||
CancellationTokenRegistration registration = default;
|
||||
Action? callback = null;
|
||||
|
||||
callback = () =>
|
||||
{
|
||||
InputUpdateType currentUpdateType = InputState.currentUpdateType;
|
||||
if (!InputUpdateTypeResolver.IsMatch(currentUpdateType, targetUpdateType))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Assert(callback != null, "callback must be assigned before subscription");
|
||||
InputSystem.onBeforeUpdate -= callback;
|
||||
registration.Dispose();
|
||||
apply();
|
||||
tcs.TrySetResult(true);
|
||||
};
|
||||
|
||||
InputSystem.onBeforeUpdate += callback;
|
||||
if (ct.CanBeCanceled)
|
||||
{
|
||||
registration = ct.Register(() =>
|
||||
{
|
||||
Debug.Assert(callback != null, "callback must be assigned before cancellation");
|
||||
InputSystem.onBeforeUpdate -= callback;
|
||||
tcs.TrySetCanceled(ct);
|
||||
});
|
||||
}
|
||||
|
||||
RunExplicitUpdate(targetUpdateType);
|
||||
if (!tcs.Task.IsCompleted)
|
||||
{
|
||||
Debug.Assert(callback != null, "callback must be assigned before explicit update fallback");
|
||||
InputSystem.onBeforeUpdate -= callback;
|
||||
registration.Dispose();
|
||||
apply();
|
||||
RunExplicitUpdate(targetUpdateType);
|
||||
tcs.TrySetResult(true);
|
||||
}
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
private static InputSettings.UpdateMode GetExplicitUpdateMode(
|
||||
InputUpdateType targetUpdateType,
|
||||
InputSettings.UpdateMode fallbackUpdateMode)
|
||||
{
|
||||
if (targetUpdateType == InputUpdateType.Dynamic)
|
||||
{
|
||||
return InputSettings.UpdateMode.ProcessEventsInDynamicUpdate;
|
||||
}
|
||||
|
||||
if (targetUpdateType == InputUpdateType.Fixed)
|
||||
{
|
||||
return InputSettings.UpdateMode.ProcessEventsInFixedUpdate;
|
||||
}
|
||||
|
||||
if (targetUpdateType == InputUpdateType.Manual)
|
||||
{
|
||||
return InputSettings.UpdateMode.ProcessEventsManually;
|
||||
}
|
||||
|
||||
return fallbackUpdateMode;
|
||||
}
|
||||
|
||||
private static async Task WaitForRuntimeFrames(int frameCount, CancellationToken ct)
|
||||
{
|
||||
int startFrameCount = Time.frameCount;
|
||||
int observedFrames = 0;
|
||||
|
||||
while (observedFrames < frameCount)
|
||||
{
|
||||
await EditorDelay.DelayFrame(1, ct);
|
||||
observedFrames = Time.frameCount - startFrameCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5d2cdea3342ed49e2a971e1766cbafa7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,64 @@
|
||||
#nullable enable
|
||||
#if ULOOPMCP_HAS_INPUT_SYSTEM
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
// Projects can process Input System events in dynamic, fixed, or manual mode.
|
||||
// Input simulation must follow that configured loop to avoid frame mismatches.
|
||||
internal static class InputUpdateTypeResolver
|
||||
{
|
||||
public static InputUpdateType Resolve()
|
||||
{
|
||||
InputSettings? settings = InputSystem.settings;
|
||||
if (settings == null)
|
||||
{
|
||||
return InputUpdateType.Dynamic;
|
||||
}
|
||||
|
||||
InputSettings.UpdateMode updateMode = settings.updateMode;
|
||||
switch (updateMode)
|
||||
{
|
||||
case InputSettings.UpdateMode.ProcessEventsInFixedUpdate:
|
||||
// Paused screens commonly set timeScale to 0, which stops fixed ticks entirely.
|
||||
// Falling back to Dynamic keeps input simulation responsive for those menus.
|
||||
return IsPausedFixedUpdate(settings) ? InputUpdateType.Dynamic : InputUpdateType.Fixed;
|
||||
|
||||
case InputSettings.UpdateMode.ProcessEventsManually:
|
||||
return InputUpdateType.Manual;
|
||||
|
||||
default:
|
||||
return InputUpdateType.Dynamic;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsMatch(InputUpdateType current, InputUpdateType expected)
|
||||
{
|
||||
return (current & expected) == expected;
|
||||
}
|
||||
|
||||
public static bool RequiresExplicitUpdate()
|
||||
{
|
||||
InputSettings? settings = InputSystem.settings;
|
||||
if (settings == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (settings.updateMode == InputSettings.UpdateMode.ProcessEventsManually)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return IsPausedFixedUpdate(settings);
|
||||
}
|
||||
|
||||
private static bool IsPausedFixedUpdate(InputSettings settings)
|
||||
{
|
||||
return settings.updateMode == InputSettings.UpdateMode.ProcessEventsInFixedUpdate && Time.timeScale <= 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 67072e4564d6345d58ceff0440cbfeda
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: acb202afcf62f40b88d846c3f1e0c373
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,102 @@
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// Compile error or warning information
|
||||
/// </summary>
|
||||
public class CompileIssue
|
||||
{
|
||||
public string Message { get; set; }
|
||||
public string File { get; set; }
|
||||
public int Line { get; set; }
|
||||
|
||||
public CompileIssue(string message, string file, int line)
|
||||
{
|
||||
Message = message;
|
||||
File = file;
|
||||
Line = line;
|
||||
}
|
||||
|
||||
public CompileIssue() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response schema for Compile command
|
||||
/// Provides type-safe response structure
|
||||
/// </summary>
|
||||
public class CompileResponse : BaseToolResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether compilation was successful.
|
||||
/// Null is used when the tool cannot reliably determine the result yet (e.g., forced recompile/domain reload),
|
||||
/// to avoid misleading clients into treating "0 errors" as a confirmed success.
|
||||
/// </summary>
|
||||
public bool? Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of compilation errors.
|
||||
/// Null is used when the tool intentionally does not provide details (e.g., forced recompile),
|
||||
/// because Unity reports errors/warnings after domain reload and clients should fetch logs later.
|
||||
/// </summary>
|
||||
public int? ErrorCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of compilation warnings.
|
||||
/// Null is used when the tool intentionally does not provide details (e.g., forced recompile),
|
||||
/// because Unity reports errors/warnings after domain reload and clients should fetch logs later.
|
||||
/// </summary>
|
||||
public int? WarningCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Compilation errors.
|
||||
/// Null is used when the tool intentionally does not provide details (e.g., forced recompile),
|
||||
/// because Unity reports errors/warnings after domain reload and clients should fetch logs later.
|
||||
/// </summary>
|
||||
public CompileIssue[] Errors { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Compilation warnings.
|
||||
/// Null is used when the tool intentionally does not provide details (e.g., forced recompile),
|
||||
/// because Unity reports errors/warnings after domain reload and clients should fetch logs later.
|
||||
/// </summary>
|
||||
public CompileIssue[] Warnings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional message for additional information
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Unity project root path (from Application.dataPath).
|
||||
/// Set only when WaitForDomainReload=true so that TS/CLI can locate lock files
|
||||
/// in the correct project even when connected to a remote Unity instance.
|
||||
/// </summary>
|
||||
public string ProjectRoot { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new CompileResponse
|
||||
/// </summary>
|
||||
public CompileResponse(
|
||||
bool? success,
|
||||
int? errorCount,
|
||||
int? warningCount,
|
||||
CompileIssue[] errors,
|
||||
CompileIssue[] warnings,
|
||||
string message = null
|
||||
)
|
||||
{
|
||||
Success = success;
|
||||
ErrorCount = errorCount;
|
||||
WarningCount = warningCount;
|
||||
Errors = errors;
|
||||
Warnings = warnings;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parameterless constructor for JSON deserialization
|
||||
/// </summary>
|
||||
public CompileResponse()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 56090f2379ae3428bbe733a4db41e344
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// Schema for Compile command parameters
|
||||
/// Provides type-safe parameter access with default values
|
||||
/// </summary>
|
||||
public class CompileSchema : BaseToolSchema
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether to perform forced recompilation
|
||||
/// </summary>
|
||||
[Description("Whether to perform forced recompilation. Note: Force compilation results do not include error/warning messages in the response. Use get-logs tool after execution to retrieve compilation messages.")]
|
||||
public bool ForceRecompile { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to wait for domain reload completion before the caller returns.
|
||||
/// </summary>
|
||||
[Description("Whether to wait for domain reload completion before returning.")]
|
||||
public bool WaitForDomainReload { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Internal request identifier used for delayed result recovery across domain reload.
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public string RequestId { get; set; } = "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2581c0d32a90546fd9978a2f938e2f6f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// Compile tool handler - Type-safe implementation using Schema and Response
|
||||
/// Handles Unity project compilation with optional force recompile
|
||||
/// Related classes: CompileUseCase, CompilationStateValidationService, CompilationExecutionService
|
||||
/// </summary>
|
||||
[McpTool(Description = "Execute Unity project compilation")]
|
||||
public class CompileTool : AbstractUnityTool<CompileSchema, CompileResponse>
|
||||
{
|
||||
public override string ToolName => "compile";
|
||||
|
||||
/// <summary>
|
||||
/// Execute compile tool - delegates to CompileUseCase
|
||||
/// </summary>
|
||||
/// <param name="parameters">Type-safe parameters</param>
|
||||
/// <param name="cancellationToken">Cancellation token for timeout control</param>
|
||||
/// <returns>Compile result</returns>
|
||||
protected override async Task<CompileResponse> ExecuteAsync(CompileSchema parameters, CancellationToken cancellationToken)
|
||||
{
|
||||
// Create and execute CompileUseCase instance
|
||||
CompileUseCase useCase = new();
|
||||
return await useCase.ExecuteAsync(parameters, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 60e9cabfcc7d047cbab5dfd6e9b06904
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1186b025586e04dd794dace399e9ce35
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,70 @@
|
||||
---
|
||||
name: uloop-compile
|
||||
description: "Compile the Unity project and report errors/warnings. Use after C# edits or when a full Domain Reload compile is needed."
|
||||
---
|
||||
|
||||
# uloop compile
|
||||
|
||||
Execute Unity project compilation.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
uloop compile [--force-recompile] [--wait-for-domain-reload]
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `--force-recompile` | boolean | `false` | Force full recompilation (triggers Domain Reload) |
|
||||
| `--wait-for-domain-reload` | boolean | `false` | Wait until Domain Reload completes before returning |
|
||||
|
||||
## Global Options
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--project-path <path>` | Optional. Use only when the target Unity project is not the current directory. |
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
# Check compilation
|
||||
uloop compile
|
||||
|
||||
# Force full recompilation
|
||||
uloop compile --force-recompile
|
||||
|
||||
# Force recompilation and wait for Domain Reload completion
|
||||
uloop compile --force-recompile true --wait-for-domain-reload true
|
||||
|
||||
# Wait for Domain Reload completion even without force recompilation
|
||||
uloop compile --force-recompile false --wait-for-domain-reload true
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
Returns JSON:
|
||||
- `Success`: boolean
|
||||
- `ErrorCount`: number
|
||||
- `WarningCount`: number
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Diagnose the failure mode before retrying.
|
||||
|
||||
**Stale lock files** (CLI hangs or shows "Unity is busy" while Unity Editor *is* running):
|
||||
|
||||
```bash
|
||||
uloop fix
|
||||
```
|
||||
|
||||
This removes any leftover lock files (`compiling.lock`, `domainreload.lock`, `serverstarting.lock`) from the Unity project's Temp directory. Then retry `uloop compile`.
|
||||
|
||||
**Unity Editor not running** (CLI returns a connection failure and no Unity process is alive):
|
||||
|
||||
```bash
|
||||
uloop launch
|
||||
```
|
||||
|
||||
`uloop launch` auto-detects the project at the current working directory and opens it in the matching Unity Editor version. After Unity finishes launching, retry `uloop compile`.
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e7fbf71d98ab425486f6bd68b7a6612
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a76e166c9de484f20b07f88305dd4b6b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
public class ControlPlayModeResponse : BaseToolResponse
|
||||
{
|
||||
public bool IsPlaying { get; set; }
|
||||
public bool IsPaused { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 752e3f7c18a5649d8b6a555add78f1d7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
public enum PlayModeAction
|
||||
{
|
||||
Play = 0,
|
||||
Stop = 1,
|
||||
Pause = 2
|
||||
}
|
||||
|
||||
public class ControlPlayModeSchema : BaseToolSchema
|
||||
{
|
||||
[Description("Action to perform: Play(0) - Start play mode, Stop(1) - Stop play mode, Pause(2) - Pause play mode")]
|
||||
public PlayModeAction Action { get; set; } = PlayModeAction.Play;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5634451b383ae4ff8b0a1cd00500f342
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
[McpTool(Description = "Control Unity Editor play mode (play/stop/pause)")]
|
||||
public class ControlPlayModeTool : AbstractUnityTool<ControlPlayModeSchema, ControlPlayModeResponse>
|
||||
{
|
||||
public override string ToolName => "control-play-mode";
|
||||
|
||||
protected override async Task<ControlPlayModeResponse> ExecuteAsync(ControlPlayModeSchema parameters, CancellationToken cancellationToken)
|
||||
{
|
||||
ControlPlayModeUseCase useCase = new();
|
||||
return await useCase.ExecuteAsync(parameters, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bdce84d8140ea44e9a5fbc8b1e1cf5d6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8809b197b1dbb42fbbabc04674f5baa9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
name: uloop-control-play-mode
|
||||
description: "Control Unity Editor Play Mode. Use to start, stop, or pause Play Mode for runtime behavior checks and frame inspection."
|
||||
---
|
||||
|
||||
# uloop control-play-mode
|
||||
|
||||
Control Unity Editor play mode (play/stop/pause).
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
uloop control-play-mode [options]
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `--action` | string | `Play` | Action to perform: `Play`, `Stop`, `Pause` |
|
||||
|
||||
## Global Options
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--project-path <path>` | Optional. Use only when the target Unity project is not the current directory. |
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
# Start play mode
|
||||
uloop control-play-mode --action Play
|
||||
|
||||
# Stop play mode
|
||||
uloop control-play-mode --action Stop
|
||||
|
||||
# Pause play mode
|
||||
uloop control-play-mode --action Pause
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
Returns JSON with the current play mode state:
|
||||
- `IsPlaying`: Whether Unity is currently in play mode
|
||||
- `IsPaused`: Whether play mode is paused
|
||||
- `Message`: Description of the action performed
|
||||
|
||||
## Notes
|
||||
|
||||
- Play action starts the game in the Unity Editor (also resumes from pause)
|
||||
- Stop action exits play mode and returns to edit mode
|
||||
- Pause action pauses the game while remaining in play mode
|
||||
- Useful for automated testing workflows
|
||||
|
||||
- PlayMode entry may complete on the next editor frame. If a PlayMode-dependent command reports "PlayMode is not active" immediately after `--action Play`, wait briefly and retry.
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1e1425714444f43d1aab052bf7101893
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a4d88782b067f473a8c1bf230936d8f0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,122 @@
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using System.Threading;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
// Related classes:
|
||||
// - IUnityTool: The interface that this class implements.
|
||||
// - UnityToolRegistry: Registers and manages instances of tool implementations.
|
||||
// - ToolParameterSchemaGenerator: Generates the JSON schema for tool parameters.
|
||||
/// <summary>
|
||||
/// Abstract base class for type-safe Unity tools using Schema and Response types
|
||||
/// </summary>
|
||||
/// <typeparam name="TSchema">Schema type for tool parameters</typeparam>
|
||||
/// <typeparam name="TResponse">Response type for tool results</typeparam>
|
||||
public abstract class AbstractUnityTool<TSchema, TResponse> : IUnityTool
|
||||
where TSchema : BaseToolSchema, new()
|
||||
where TResponse : BaseToolResponse
|
||||
{
|
||||
public abstract string ToolName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Automatically generates parameter schema from TSchema type
|
||||
/// </summary>
|
||||
public virtual ToolParameterSchema ParameterSchema =>
|
||||
ToolParameterSchemaGenerator.FromDto<TSchema>();
|
||||
|
||||
/// <summary>
|
||||
/// Execute tool with type-safe Schema parameters
|
||||
/// Note: This method is called from the main thread context.
|
||||
/// MainThreadSwitcher.SwitchToMainThread() is already handled by the upper layer (JsonRpcProcessor),
|
||||
/// so individual tools do not need to call it again.
|
||||
/// </summary>
|
||||
/// <param name="parameters">Strongly typed parameters</param>
|
||||
/// <param name="cancellationToken">Cancellation token for timeout control</param>
|
||||
/// <returns>Strongly typed tool execution result</returns>
|
||||
protected abstract Task<TResponse> ExecuteAsync(TSchema parameters, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// IUnityTool implementation - converts JToken to Schema and returns BaseToolResponse
|
||||
/// </summary>
|
||||
public async Task<BaseToolResponse> ExecuteAsync(JToken paramsToken)
|
||||
{
|
||||
// Convert JToken to strongly typed Schema
|
||||
TSchema parameters = ConvertToSchema(paramsToken);
|
||||
|
||||
// Execute with type-safe parameters
|
||||
TResponse response = await ExecuteAsync(parameters, CancellationToken.None);
|
||||
|
||||
// Return as BaseToolResponse for IUnityTool interface compatibility
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert JToken to strongly typed Schema with default value fallback
|
||||
/// </summary>
|
||||
private TSchema ConvertToSchema(JToken paramsToken)
|
||||
{
|
||||
if (paramsToken == null || paramsToken.Type == JTokenType.Null)
|
||||
{
|
||||
// Return default instance if no parameters provided
|
||||
return new TSchema();
|
||||
}
|
||||
|
||||
// Create JsonSerializerSettings with CamelCasePropertyNamesContractResolver
|
||||
// This allows TypeScript side to use camelCase while C# uses PascalCase
|
||||
JsonSerializerSettings settings = new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
|
||||
};
|
||||
|
||||
// Create JsonSerializer with custom settings
|
||||
JsonSerializer serializer = JsonSerializer.Create(settings);
|
||||
|
||||
// Try to deserialize from JToken with custom serializer
|
||||
TSchema schema;
|
||||
try
|
||||
{
|
||||
schema = paramsToken.ToObject<TSchema>(serializer);
|
||||
}
|
||||
catch (JsonSerializationException ex)
|
||||
{
|
||||
// Create detailed error message for type mismatches
|
||||
string errorMessage = $"Parameter type mismatch for tool '{ToolName}': {ex.Message}";
|
||||
|
||||
// Check for specific Dictionary<string, object> conversion errors
|
||||
if (ex.Message.Contains("Dictionary") && ex.Message.Contains("Error converting value"))
|
||||
{
|
||||
string received = paramsToken["parameters"]?.ToString() ?? paramsToken["Parameters"]?.ToString() ?? "null";
|
||||
errorMessage =
|
||||
"Parameter 'Parameters' must be an object, not a string. " +
|
||||
"Do: omit 'Parameters' or use {}. " +
|
||||
"Don't: \"{}\" (string). " +
|
||||
$"Received: {received}";
|
||||
}
|
||||
|
||||
throw new ParameterValidationException(errorMessage, ex);
|
||||
}
|
||||
|
||||
// If deserialization returns null, create default instance
|
||||
if (schema == null)
|
||||
{
|
||||
schema = new TSchema();
|
||||
}
|
||||
|
||||
// Apply default values for null properties
|
||||
return ApplyDefaultValues(schema);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply default values to Schema properties if they are null
|
||||
/// Override this method to provide custom default value logic
|
||||
/// </summary>
|
||||
protected virtual TSchema ApplyDefaultValues(TSchema schema)
|
||||
{
|
||||
// Default implementation - return as is
|
||||
// Subclasses can override to apply specific default values
|
||||
return schema;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d4b53392372a454e954428caf82258a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// UseCase base class - Responsible for temporal cohesion
|
||||
/// New instances are created each time and disposed after Execute completion
|
||||
/// Related classes: AbstractUnityTool, BaseToolSchema, BaseToolResponse
|
||||
/// </summary>
|
||||
/// <typeparam name="TSchema">Schema type for tool parameters</typeparam>
|
||||
/// <typeparam name="TResponse">Response type for tool results</typeparam>
|
||||
public abstract class AbstractUseCase<TSchema, TResponse>
|
||||
where TSchema : BaseToolSchema
|
||||
where TResponse : BaseToolResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// UseCase execution method - The only public method
|
||||
/// Responsible for temporal cohesion (executing multiple operations in sequence)
|
||||
/// </summary>
|
||||
/// <param name="parameters">Type-safe parameters</param>
|
||||
/// <param name="cancellationToken">Cancellation control token</param>
|
||||
/// <returns>Type-safe execution result</returns>
|
||||
public abstract Task<TResponse> ExecuteAsync(TSchema parameters, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9a74e8d5c037f4277a284f8f686edc05
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// Base response class for all Unity MCP tool responses
|
||||
/// </summary>
|
||||
public abstract class BaseToolResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// uLoopMCP server version for CLI version compatibility check
|
||||
/// </summary>
|
||||
public string Ver => McpVersion.VERSION;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a54be1e158eda4064aa08ab9da92d980
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// Base schema class for all Unity MCP tool parameters
|
||||
/// </summary>
|
||||
public abstract class BaseToolSchema
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c705d28d63c1b4fb5aa6d7797c95631d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,114 @@
|
||||
using UnityEditor;
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// SessionState management class for forced recompilation
|
||||
/// Preserves request information even when assembly reload occurs
|
||||
/// </summary>
|
||||
public static class CompileSessionState
|
||||
{
|
||||
// Note: Compile requests are now managed via McpSessionManager
|
||||
|
||||
/// <summary>
|
||||
/// Forced recompilation request information
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class CompileRequestInfo
|
||||
{
|
||||
public string requestId;
|
||||
public bool forceRecompile;
|
||||
public string clientEndpoint;
|
||||
public DateTime requestTime;
|
||||
public bool isCompleted;
|
||||
|
||||
public CompileRequestInfo(string requestId, bool forceRecompile, string clientEndpoint)
|
||||
{
|
||||
this.requestId = requestId;
|
||||
this.forceRecompile = forceRecompile;
|
||||
this.clientEndpoint = clientEndpoint;
|
||||
this.requestTime = DateTime.Now;
|
||||
this.isCompleted = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save forced recompilation request
|
||||
/// </summary>
|
||||
public static void SaveCompileRequest(string requestId, bool forceRecompile, string clientEndpoint = "unknown")
|
||||
{
|
||||
if (!forceRecompile) return; // Don't save normal compilation
|
||||
|
||||
CompileRequestInfo requestInfo = new CompileRequestInfo(requestId, forceRecompile, clientEndpoint);
|
||||
string json = JsonConvert.SerializeObject(requestInfo);
|
||||
|
||||
McpEditorSettings.SetCompileRequestJson(requestId, json);
|
||||
McpEditorSettings.AddPendingCompileRequest(requestId);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get pending request IDs
|
||||
/// </summary>
|
||||
public static string[] GetPendingRequestIds()
|
||||
{
|
||||
return McpEditorSettings.GetPendingCompileRequestIds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get saved request information
|
||||
/// </summary>
|
||||
public static CompileRequestInfo GetCompileRequest(string requestId)
|
||||
{
|
||||
string json = McpEditorSettings.GetCompileRequestJson(requestId);
|
||||
if (string.IsNullOrEmpty(json)) return null;
|
||||
|
||||
try
|
||||
{
|
||||
return JsonConvert.DeserializeObject<CompileRequestInfo>(json);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mark request as completed
|
||||
/// </summary>
|
||||
public static void MarkRequestCompleted(string requestId)
|
||||
{
|
||||
CompileRequestInfo requestInfo = GetCompileRequest(requestId);
|
||||
if (requestInfo == null) return;
|
||||
|
||||
requestInfo.isCompleted = true;
|
||||
string json = JsonConvert.SerializeObject(requestInfo);
|
||||
McpEditorSettings.SetCompileRequestJson(requestId, json);
|
||||
McpEditorSettings.RemovePendingCompileRequest(requestId);
|
||||
|
||||
}
|
||||
|
||||
// Note: RemoveFromPendingRequests is now handled by McpSessionManager
|
||||
|
||||
/// <summary>
|
||||
/// Start forced recompilation
|
||||
/// </summary>
|
||||
public static void StartForceRecompile()
|
||||
{
|
||||
UnityEditor.Compilation.CompilationPipeline.RequestScriptCompilation(
|
||||
UnityEditor.Compilation.RequestScriptCompilationOptions.CleanBuildCache
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear SessionState
|
||||
/// </summary>
|
||||
public static void ClearAll()
|
||||
{
|
||||
McpEditorSettings.ClearAllCompileRequests();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 43fb73081d49d4a309e18955e541493f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// Static class for managing custom tools
|
||||
/// Allows users to register and manage their own tools
|
||||
/// </summary>
|
||||
public static class CustomToolManager
|
||||
{
|
||||
private static UnityToolRegistry _sharedRegistry;
|
||||
|
||||
internal static event Action OnToolsChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Get shared registry (lazy initialization)
|
||||
/// </summary>
|
||||
private static UnityToolRegistry SharedRegistry
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_sharedRegistry == null)
|
||||
{
|
||||
_sharedRegistry = new UnityToolRegistry();
|
||||
// Standard tools are automatically registered in UnityToolRegistry constructor
|
||||
}
|
||||
return _sharedRegistry;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register custom tool
|
||||
/// </summary>
|
||||
/// <param name="tool">Tool to register</param>
|
||||
public static void RegisterCustomTool(IUnityTool tool)
|
||||
{
|
||||
SharedRegistry.RegisterTool(tool);
|
||||
|
||||
// Notify tool changes for manual registration
|
||||
NotifyToolChanges();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregister custom tool
|
||||
/// </summary>
|
||||
/// <param name="toolName">Name of tool to unregister</param>
|
||||
public static void UnregisterCustomTool(string toolName)
|
||||
{
|
||||
SharedRegistry.UnregisterTool(toolName);
|
||||
|
||||
// Notify tool changes for manual unregistration
|
||||
NotifyToolChanges();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get list of all registered tools (standard + custom)
|
||||
/// </summary>
|
||||
/// <returns>Array of tool information</returns>
|
||||
public static ToolInfo[] GetRegisteredCustomTools()
|
||||
{
|
||||
return SharedRegistry.GetRegisteredTools();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if specified tool is registered
|
||||
/// </summary>
|
||||
/// <param name="toolName">Tool name</param>
|
||||
/// <returns>True if registered</returns>
|
||||
public static bool IsCustomToolRegistered(string toolName)
|
||||
{
|
||||
return SharedRegistry.IsToolRegistered(toolName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get internal registry (for MCP server)
|
||||
/// </summary>
|
||||
/// <returns>UnityToolRegistry instance</returns>
|
||||
internal static UnityToolRegistry GetRegistry()
|
||||
{
|
||||
return SharedRegistry;
|
||||
}
|
||||
|
||||
internal static UnityToolRegistry TryGetRegistry()
|
||||
{
|
||||
return _sharedRegistry;
|
||||
}
|
||||
|
||||
internal static void WarmupRegistry()
|
||||
{
|
||||
_ = SharedRegistry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Debug: Get detailed registry information
|
||||
/// </summary>
|
||||
/// <returns>Debug information</returns>
|
||||
public static string GetDebugInfo()
|
||||
{
|
||||
ToolInfo[] tools = SharedRegistry.GetRegisteredTools();
|
||||
string[] toolNames = new string[tools.Length];
|
||||
for (int i = 0; i < tools.Length; i++)
|
||||
{
|
||||
toolNames[i] = tools[i].Name;
|
||||
}
|
||||
return $"Registry instance: {SharedRegistry.GetHashCode()}, Tools: [{string.Join(", ", toolNames)}]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manually notify tool changes to MCP clients
|
||||
/// Public API for users to trigger notifications when needed
|
||||
/// </summary>
|
||||
public static void NotifyToolChanges()
|
||||
{
|
||||
UnityToolRegistry.TriggerToolsChangedNotification();
|
||||
OnToolsChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dd9b6252fa10a4edc9a5f2ee0140365d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,79 @@
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// Constants for parameter schema property names
|
||||
/// Ensures consistency between Unity and TypeScript sides
|
||||
/// </summary>
|
||||
public static class ParameterSchemaConstants
|
||||
{
|
||||
public const string TYPE_PROPERTY = "Type";
|
||||
public const string DESCRIPTION_PROPERTY = "Description";
|
||||
public const string DEFAULT_VALUE_PROPERTY = "DefaultValue";
|
||||
public const string ENUM_PROPERTY = "Enum";
|
||||
public const string PROPERTIES_PROPERTY = "Properties";
|
||||
public const string REQUIRED_PROPERTY = "Required";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base interface for Unity MCP tool handlers
|
||||
/// Following the Open-Closed principle, when adding new tools,
|
||||
/// create a new class that implements this interface
|
||||
/// </summary>
|
||||
public interface IUnityTool
|
||||
{
|
||||
/// <summary>
|
||||
/// Get tool name
|
||||
/// </summary>
|
||||
string ToolName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get parameter schema information for TypeScript side
|
||||
/// </summary>
|
||||
ToolParameterSchema ParameterSchema { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Execute tool
|
||||
/// </summary>
|
||||
/// <param name="paramsToken">JSON token for parameters</param>
|
||||
/// <returns>Execution result</returns>
|
||||
Task<BaseToolResponse> ExecuteAsync(JToken paramsToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parameter schema definition for tools (immutable)
|
||||
/// </summary>
|
||||
public class ToolParameterSchema
|
||||
{
|
||||
public readonly Dictionary<string, ParameterInfo> Properties;
|
||||
public readonly string[] Required;
|
||||
|
||||
public ToolParameterSchema(Dictionary<string, ParameterInfo> properties = null, string[] required = null)
|
||||
{
|
||||
Properties = properties ?? new Dictionary<string, ParameterInfo>();
|
||||
Required = required ?? new string[0];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Individual parameter information (immutable)
|
||||
/// </summary>
|
||||
public class ParameterInfo
|
||||
{
|
||||
public readonly string Type;
|
||||
public readonly string Description;
|
||||
public readonly object DefaultValue;
|
||||
public readonly string[] Enum;
|
||||
|
||||
public ParameterInfo(string type, string description, object defaultValue = null, string[] enumValues = null)
|
||||
{
|
||||
Type = type;
|
||||
Description = description;
|
||||
DefaultValue = defaultValue;
|
||||
Enum = enumValues;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f9cf1a64741648cb8533b76ec6f3a87
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute to mark classes for automatic MCP tool registration
|
||||
/// Classes marked with this attribute will be automatically registered as MCP commands
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class McpToolAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets whether this tool should only be displayed in development mode
|
||||
/// </summary>
|
||||
public bool DisplayDevelopmentOnly { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specific security setting required to execute this command
|
||||
/// </summary>
|
||||
public SecuritySettings RequiredSecuritySetting { get; set; } = SecuritySettings.None;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the description of this tool
|
||||
/// </summary>
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize McpTool attribute
|
||||
/// </summary>
|
||||
public McpToolAttribute()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9f72309eee6a49528d19b182ff51ad6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,64 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
// Instantiates the InputVisualizationCanvas prefab and manages its lifecycle.
|
||||
internal static class OverlayCanvasFactory
|
||||
{
|
||||
private const string CANVAS_PREFAB_PATH = "Packages/io.github.hatayama.uloopmcp/Runtime/Common/InputVisualizationCanvas.prefab";
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void ResetStaticFields()
|
||||
{
|
||||
_instance = null;
|
||||
}
|
||||
|
||||
private static InputVisualizationCanvas _instance;
|
||||
|
||||
public static InputVisualizationCanvas VisualizationCanvas
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureExists();
|
||||
Debug.Assert(_instance != null, "InputVisualizationCanvas instance must exist after EnsureExists");
|
||||
return _instance!;
|
||||
}
|
||||
}
|
||||
|
||||
public static void EnsureExists()
|
||||
{
|
||||
if (_instance != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Domain Reload resets _instance but DontDestroyOnLoad objects survive; reclaim one and destroy duplicates
|
||||
InputVisualizationCanvas[] existing =
|
||||
Object.FindObjectsByType<InputVisualizationCanvas>(FindObjectsSortMode.None);
|
||||
for (int i = 0; i < existing.Length; i++)
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = existing[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
Object.DestroyImmediate(existing[i].gameObject);
|
||||
}
|
||||
}
|
||||
if (_instance != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(CANVAS_PREFAB_PATH);
|
||||
Debug.Assert(prefab != null, $"InputVisualizationCanvas prefab not found at {CANVAS_PREFAB_PATH}");
|
||||
|
||||
GameObject go = (GameObject)PrefabUtility.InstantiatePrefab(prefab);
|
||||
Object.DontDestroyOnLoad(go);
|
||||
_instance = go.GetComponent<InputVisualizationCanvas>();
|
||||
Debug.Assert(_instance != null, "InputVisualizationCanvas component not found on prefab");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 76e5da6abcd484cbcbdbbf462a6fdd4b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,52 @@
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic data model representing service execution result
|
||||
/// Used in Application Service Layer
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of result data</typeparam>
|
||||
public class ServiceResult<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether execution was successful
|
||||
/// </summary>
|
||||
public bool Success { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Data of execution result
|
||||
/// </summary>
|
||||
public T Data { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Error message when execution fails
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create ServiceResult
|
||||
/// </summary>
|
||||
/// <param name="success">Execution result</param>
|
||||
/// <param name="data">Result data</param>
|
||||
/// <param name="errorMessage">Error message (null on success)</param>
|
||||
public ServiceResult(bool success, T data = default, string errorMessage = null)
|
||||
{
|
||||
Success = success;
|
||||
Data = data;
|
||||
ErrorMessage = errorMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create success result
|
||||
/// </summary>
|
||||
/// <param name="data">Result data</param>
|
||||
/// <returns>ServiceResult representing success</returns>
|
||||
public static ServiceResult<T> SuccessResult(T data) => new(true, data);
|
||||
|
||||
/// <summary>
|
||||
/// Create failure result
|
||||
/// </summary>
|
||||
/// <param name="errorMessage">Error message</param>
|
||||
/// <returns>ServiceResult representing failure</returns>
|
||||
public static ServiceResult<T> FailureResult(string errorMessage) => new(false, default, errorMessage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de2101885e97d4499889ddbb6bb6e358
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,54 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
internal static class ToolExecutionAvailability
|
||||
{
|
||||
internal static bool ShouldReportDependencyUnavailableBeforeDisabled(string toolName)
|
||||
{
|
||||
return ShouldReportDependencyUnavailableBeforeDisabled(
|
||||
toolName,
|
||||
isTestFrameworkAvailable: IsTestFrameworkAvailable);
|
||||
}
|
||||
|
||||
internal static bool ShouldReportDependencyUnavailableBeforeDisabled(
|
||||
string toolName,
|
||||
bool isTestFrameworkAvailable)
|
||||
{
|
||||
Debug.Assert(!string.IsNullOrEmpty(toolName), "toolName must not be null or empty");
|
||||
|
||||
return toolName == McpConstants.TOOL_NAME_RUN_TESTS && !isTestFrameworkAvailable;
|
||||
}
|
||||
|
||||
internal static bool ShouldExposeInRegisteredTools(string toolName)
|
||||
{
|
||||
return ShouldExposeInRegisteredTools(
|
||||
toolName,
|
||||
isToolEnabled: ToolSettings.IsToolEnabled(toolName),
|
||||
isTestFrameworkAvailable: IsTestFrameworkAvailable);
|
||||
}
|
||||
|
||||
internal static bool ShouldExposeInRegisteredTools(
|
||||
string toolName,
|
||||
bool isToolEnabled,
|
||||
bool isTestFrameworkAvailable)
|
||||
{
|
||||
Debug.Assert(!string.IsNullOrEmpty(toolName), "toolName must not be null or empty");
|
||||
|
||||
return isToolEnabled ||
|
||||
ShouldReportDependencyUnavailableBeforeDisabled(toolName, isTestFrameworkAvailable);
|
||||
}
|
||||
|
||||
internal static bool IsTestFrameworkAvailable
|
||||
{
|
||||
get
|
||||
{
|
||||
#if ULOOPMCP_HAS_TEST_FRAMEWORK
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 12a3cd3bb7213f14ab18fa845fe17f13
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,169 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class to generate ToolParameterSchema from DTO classes
|
||||
/// Eliminates duplication between DTO default values and schema definitions
|
||||
/// </summary>
|
||||
public static class ToolParameterSchemaGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generate ToolParameterSchema from DTO type using reflection
|
||||
/// </summary>
|
||||
/// <typeparam name="TDto">DTO type</typeparam>
|
||||
/// <returns>Generated parameter schema</returns>
|
||||
public static ToolParameterSchema FromDto<TDto>() where TDto : class, new()
|
||||
{
|
||||
Type dtoType = typeof(TDto);
|
||||
Dictionary<string, ParameterInfo> properties = new Dictionary<string, ParameterInfo>();
|
||||
List<string> required = new List<string>();
|
||||
|
||||
// Create instance to get default values
|
||||
TDto defaultInstance = new TDto();
|
||||
|
||||
foreach (PropertyInfo property in dtoType.GetProperties())
|
||||
{
|
||||
// Skip properties without public setters
|
||||
if (!property.CanWrite || property.SetMethod?.IsPublic != true)
|
||||
continue;
|
||||
|
||||
BrowsableAttribute browsableAttribute = property.GetCustomAttribute<BrowsableAttribute>();
|
||||
if (browsableAttribute != null && !browsableAttribute.Browsable)
|
||||
continue;
|
||||
|
||||
// Get JSON property name (fallback to property name)
|
||||
string parameterName = GetJsonPropertyName(property);
|
||||
|
||||
// Get property type information
|
||||
string parameterType = GetParameterType(property.PropertyType);
|
||||
|
||||
// Get description from attributes
|
||||
string description = GetDescription(property);
|
||||
|
||||
// Get default value from instance
|
||||
object defaultValue = property.GetValue(defaultInstance);
|
||||
|
||||
// Get enum values if applicable
|
||||
string[] enumValues = GetEnumValues(property.PropertyType);
|
||||
|
||||
// Check if property is required
|
||||
bool isRequired = IsRequired(property);
|
||||
if (isRequired)
|
||||
{
|
||||
required.Add(parameterName);
|
||||
}
|
||||
|
||||
// Create parameter info
|
||||
ParameterInfo paramInfo = new ParameterInfo(
|
||||
parameterType,
|
||||
description,
|
||||
defaultValue,
|
||||
enumValues
|
||||
);
|
||||
|
||||
properties[parameterName] = paramInfo;
|
||||
}
|
||||
|
||||
return new ToolParameterSchema(properties, required.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get JSON property name from JsonProperty attribute or property name
|
||||
/// </summary>
|
||||
private static string GetJsonPropertyName(PropertyInfo property)
|
||||
{
|
||||
JsonPropertyAttribute jsonAttr = property.GetCustomAttribute<JsonPropertyAttribute>();
|
||||
return jsonAttr?.PropertyName ?? property.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert .NET type to parameter type string
|
||||
/// </summary>
|
||||
private static string GetParameterType(Type type)
|
||||
{
|
||||
// Handle nullable types
|
||||
Type underlyingType = Nullable.GetUnderlyingType(type) ?? type;
|
||||
|
||||
if (underlyingType == typeof(string))
|
||||
return "string";
|
||||
if (underlyingType == typeof(bool))
|
||||
return "boolean";
|
||||
if (underlyingType == typeof(int) || underlyingType == typeof(long) ||
|
||||
underlyingType == typeof(float) || underlyingType == typeof(double) ||
|
||||
underlyingType == typeof(decimal))
|
||||
return "number";
|
||||
if (underlyingType.IsArray || (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>)))
|
||||
return "array";
|
||||
if (underlyingType.IsEnum)
|
||||
return "string"; // Enums are treated as strings with enum constraints
|
||||
if (IsDictionaryType(underlyingType))
|
||||
return "object";
|
||||
|
||||
return "string"; // Default fallback
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if type is a dictionary type (Dictionary, IDictionary, IReadOnlyDictionary)
|
||||
/// </summary>
|
||||
private static bool IsDictionaryType(Type type)
|
||||
{
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
Type genericDef = type.GetGenericTypeDefinition();
|
||||
if (genericDef == typeof(Dictionary<,>) ||
|
||||
genericDef == typeof(IDictionary<,>) ||
|
||||
genericDef == typeof(IReadOnlyDictionary<,>))
|
||||
return true;
|
||||
}
|
||||
return type.GetInterfaces().Any(i =>
|
||||
i.IsGenericType &&
|
||||
(i.GetGenericTypeDefinition() == typeof(IDictionary<,>) ||
|
||||
i.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get description from Description attribute or generate default
|
||||
/// </summary>
|
||||
private static string GetDescription(PropertyInfo property)
|
||||
{
|
||||
DescriptionAttribute descAttr = property.GetCustomAttribute<DescriptionAttribute>();
|
||||
if (descAttr != null)
|
||||
return descAttr.Description;
|
||||
|
||||
// Generate default description
|
||||
return $"Parameter: {property.Name}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get enum values if property type is enum
|
||||
/// </summary>
|
||||
private static string[] GetEnumValues(Type type)
|
||||
{
|
||||
Type underlyingType = Nullable.GetUnderlyingType(type) ?? type;
|
||||
|
||||
if (underlyingType.IsEnum)
|
||||
{
|
||||
return Enum.GetNames(underlyingType);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if property is required using custom Required attribute
|
||||
/// For now, returns false as Unity doesn't support DataAnnotations
|
||||
/// Can be extended with custom attributes if needed
|
||||
/// </summary>
|
||||
private static bool IsRequired(PropertyInfo property)
|
||||
{
|
||||
// TODO: Implement custom Required attribute if needed
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b9d06a624dd5d4129b95d39ea144a412
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,424 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Stopwatch = System.Diagnostics.Stopwatch;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
// Related classes:
|
||||
// - UnityToolExecutor: Uses this registry to execute _tools.
|
||||
// - IUnityTool: The interface for all _tools stored in this registry.
|
||||
// - AbstractUnityTool: The base class for most tool implementations.
|
||||
// - McpToolAttribute: Attribute used to discover and register _tools automatically.
|
||||
/// <summary>
|
||||
/// Unity MCP tool registry class
|
||||
/// Supports dynamic tool registration, allowing users to add their own _tools
|
||||
/// </summary>
|
||||
public class UnityToolRegistry
|
||||
{
|
||||
private readonly Dictionary<string, IUnityTool> _tools = new();
|
||||
|
||||
/// <summary>
|
||||
/// Singleton instance for global access
|
||||
/// </summary>
|
||||
public static UnityToolRegistry Instance { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// Auto-registers standard _tools
|
||||
/// </summary>
|
||||
public UnityToolRegistry()
|
||||
{
|
||||
Instance = this;
|
||||
RegisterDefaultTools();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register standard _tools
|
||||
/// </summary>
|
||||
private void RegisterDefaultTools()
|
||||
{
|
||||
// Register _tools with attribute-based discovery
|
||||
RegisterToolsWithAttributes();
|
||||
|
||||
// Manual registration for _tools without attributes (for backward compatibility)
|
||||
RegisterManualTools();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register _tools using attribute-based discovery
|
||||
/// </summary>
|
||||
private void RegisterToolsWithAttributes()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get all assemblies in the current domain
|
||||
Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies();
|
||||
|
||||
List<Type> toolTypes = new List<Type>();
|
||||
|
||||
foreach (Assembly assembly in assemblies)
|
||||
{
|
||||
// Find all types with McpTool attribute that implement IUnityTool
|
||||
Type[] types = assembly.GetTypes()
|
||||
.Where(type => type.GetCustomAttribute<McpToolAttribute>() != null)
|
||||
.Where(type => typeof(IUnityTool).IsAssignableFrom(type))
|
||||
.Where(type => !type.IsAbstract && !type.IsInterface)
|
||||
.ToArray();
|
||||
|
||||
toolTypes.AddRange(types);
|
||||
}
|
||||
|
||||
// Register all _tools - filtering will be handled by TypeScript side
|
||||
foreach (Type type in toolTypes)
|
||||
{
|
||||
// Security: Validate type before creating instance
|
||||
if (!IsValidToolType(type))
|
||||
{
|
||||
UnityEngine.Debug.LogWarning($"{McpConstants.SECURITY_LOG_PREFIX} Skipping invalid tool type: {type.FullName}");
|
||||
continue;
|
||||
}
|
||||
|
||||
IUnityTool tool = (IUnityTool)Activator.CreateInstance(type);
|
||||
RegisterTool(tool);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register _tools manually (for backward compatibility)
|
||||
/// </summary>
|
||||
private void RegisterManualTools()
|
||||
{
|
||||
// Only register _tools that don't have the McpTool attribute
|
||||
// This prevents double registration
|
||||
|
||||
if (!IsToolTypeRegistered<PingTool>())
|
||||
{
|
||||
RegisterTool(new PingTool());
|
||||
}
|
||||
|
||||
if (!IsToolTypeRegistered<CompileTool>())
|
||||
{
|
||||
RegisterTool(new CompileTool());
|
||||
}
|
||||
|
||||
if (!IsToolTypeRegistered<GetLogsTool>())
|
||||
{
|
||||
RegisterTool(new GetLogsTool());
|
||||
}
|
||||
|
||||
if (!IsToolTypeRegistered<RunTestsTool>())
|
||||
{
|
||||
RegisterTool(new RunTestsTool());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a tool type is already registered
|
||||
/// </summary>
|
||||
private bool IsToolTypeRegistered<T>() where T : IUnityTool
|
||||
{
|
||||
return _tools.Values.Any(tool => tool.GetType() == typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Security: Validate if the type is safe to instantiate
|
||||
/// </summary>
|
||||
private bool IsValidToolType(Type type)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Must implement IUnityTool
|
||||
if (!typeof(IUnityTool).IsAssignableFrom(type))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Must not be abstract or interface
|
||||
if (type.IsAbstract || type.IsInterface)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Must have McpTool attribute
|
||||
if (type.GetCustomAttribute<McpToolAttribute>() == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Must have parameterless constructor
|
||||
if (type.GetConstructor(Type.EmptyTypes) == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UnityEngine.Debug.LogError($"{McpConstants.SECURITY_LOG_PREFIX} Error validating tool type {type?.FullName}: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register tool
|
||||
/// </summary>
|
||||
/// <param name="tool">Tool to register</param>
|
||||
public void RegisterTool(IUnityTool tool)
|
||||
{
|
||||
if (tool == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tool));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(tool.ToolName))
|
||||
{
|
||||
throw new ArgumentException("Tool name cannot be null or empty", nameof(tool));
|
||||
}
|
||||
|
||||
_tools[tool.ToolName] = tool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregister tool
|
||||
/// </summary>
|
||||
/// <param name="toolName">Name of tool to unregister</param>
|
||||
public void UnregisterTool(string toolName)
|
||||
{
|
||||
_tools.Remove(toolName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute tool
|
||||
/// </summary>
|
||||
/// <param name="toolName">Tool name</param>
|
||||
/// <param name="paramsToken">Parameters</param>
|
||||
/// <returns>Execution result</returns>
|
||||
/// <exception cref="ArgumentException">When tool is unknown</exception>
|
||||
/// <exception cref="McpSecurityException">When tool is blocked by security settings</exception>
|
||||
public async Task<BaseToolResponse> ExecuteToolAsync(string toolName, JToken paramsToken)
|
||||
{
|
||||
if (!_tools.TryGetValue(toolName, out IUnityTool tool))
|
||||
{
|
||||
throw new ArgumentException($"Unknown tool: {toolName}");
|
||||
}
|
||||
|
||||
if (!ToolSettings.IsToolEnabled(toolName)
|
||||
&& !ToolExecutionAvailability.ShouldReportDependencyUnavailableBeforeDisabled(toolName))
|
||||
{
|
||||
throw new ToolDisabledException(toolName);
|
||||
}
|
||||
|
||||
// Security check - validate tool before execution
|
||||
if (!McpSecurityChecker.IsToolAllowed(toolName))
|
||||
{
|
||||
throw new McpSecurityException(toolName, "Tool is blocked by security settings");
|
||||
}
|
||||
|
||||
Stopwatch mainThreadHopStopwatch = Stopwatch.StartNew();
|
||||
await MainThreadSwitcher.SwitchToMainThread();
|
||||
mainThreadHopStopwatch.Stop();
|
||||
|
||||
Stopwatch toolBodyStopwatch = Stopwatch.StartNew();
|
||||
BaseToolResponse response = await tool.ExecuteAsync(paramsToken);
|
||||
toolBodyStopwatch.Stop();
|
||||
|
||||
AppendExecuteDynamicCodeTimingsIfSupported(
|
||||
response,
|
||||
$"[Perf] RegistryMainThreadHop: {mainThreadHopStopwatch.Elapsed.TotalMilliseconds:F1}ms",
|
||||
$"[Perf] ToolBody: {toolBodyStopwatch.Elapsed.TotalMilliseconds:F1}ms");
|
||||
return response;
|
||||
}
|
||||
|
||||
private static void AppendExecuteDynamicCodeTimingsIfSupported(
|
||||
BaseToolResponse response,
|
||||
params string[] timingEntries)
|
||||
{
|
||||
if (response is not ExecuteDynamicCodeResponse executeDynamicCodeResponse)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (executeDynamicCodeResponse.Timings == null)
|
||||
{
|
||||
executeDynamicCodeResponse.Timings = new List<string>();
|
||||
}
|
||||
|
||||
executeDynamicCodeResponse.Timings.AddRange(timingEntries);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get detailed information of registered _tools
|
||||
/// </summary>
|
||||
/// <returns>Array of tool information</returns>
|
||||
public ToolInfo[] GetRegisteredTools()
|
||||
{
|
||||
return _tools.Values
|
||||
.Where(tool => ToolExecutionAvailability.ShouldExposeInRegisteredTools(tool.ToolName))
|
||||
.Select(tool =>
|
||||
{
|
||||
// Check if tool has McpTool attribute with DisplayDevelopmentOnly
|
||||
bool displayDevelopmentOnly = false;
|
||||
McpToolAttribute attribute = tool.GetType().GetCustomAttribute<McpToolAttribute>();
|
||||
if (attribute != null)
|
||||
{
|
||||
displayDevelopmentOnly = attribute.DisplayDevelopmentOnly;
|
||||
}
|
||||
|
||||
// Check security settings
|
||||
bool isAllowed = McpSecurityChecker.IsToolAllowed(tool.ToolName);
|
||||
|
||||
// Get description from attribute
|
||||
string description = attribute?.Description ?? "";
|
||||
|
||||
// Modify description for blocked _tools
|
||||
if (!isAllowed)
|
||||
{
|
||||
description = $"[BLOCKED] {description} - Blocked by security settings";
|
||||
}
|
||||
|
||||
return new ToolInfo(tool.ToolName, description, tool.ParameterSchema, displayDevelopmentOnly);
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all tools including disabled ones. Used by Tool Settings UI.
|
||||
/// </summary>
|
||||
public ToolInfo[] GetAllRegisteredToolInfos()
|
||||
{
|
||||
return _tools.Values.Select(tool =>
|
||||
{
|
||||
McpToolAttribute attribute = tool.GetType().GetCustomAttribute<McpToolAttribute>();
|
||||
string description = attribute?.Description ?? "";
|
||||
bool displayDevelopmentOnly = attribute?.DisplayDevelopmentOnly ?? false;
|
||||
return new ToolInfo(tool.ToolName, description, tool.ParameterSchema, displayDevelopmentOnly);
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
public ToolSettingsCatalogItem[] GetToolSettingsCatalog()
|
||||
{
|
||||
return _tools.Values.Select(tool =>
|
||||
{
|
||||
Type toolType = tool.GetType();
|
||||
McpToolAttribute attribute = toolType.GetCustomAttribute<McpToolAttribute>();
|
||||
string description = attribute?.Description ?? "";
|
||||
bool displayDevelopmentOnly = attribute?.DisplayDevelopmentOnly ?? false;
|
||||
bool isThirdParty = IsThirdPartyAssembly(toolType.Assembly.GetName().Name);
|
||||
|
||||
return new ToolSettingsCatalogItem(
|
||||
tool.ToolName,
|
||||
description,
|
||||
displayDevelopmentOnly,
|
||||
isThirdParty);
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a tool belongs to a third-party assembly.
|
||||
/// </summary>
|
||||
public bool IsThirdPartyTool(string toolName)
|
||||
{
|
||||
if (!_tools.TryGetValue(toolName, out IUnityTool tool))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
string assemblyName = tool.GetType().Assembly.GetName().Name;
|
||||
return IsThirdPartyAssembly(assemblyName);
|
||||
}
|
||||
|
||||
private static bool IsThirdPartyAssembly(string assemblyName)
|
||||
{
|
||||
return assemblyName != "uLoopMCP.Editor";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get tool type by name for security checking
|
||||
/// </summary>
|
||||
/// <param name="toolName">Tool name</param>
|
||||
/// <returns>Tool type or null if not found</returns>
|
||||
public Type GetToolType(string toolName)
|
||||
{
|
||||
if (_tools.TryGetValue(toolName, out IUnityTool tool))
|
||||
{
|
||||
return tool.GetType();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if specified tool is registered
|
||||
/// </summary>
|
||||
/// <param name="toolName">Tool name</param>
|
||||
/// <returns>True if registered</returns>
|
||||
public bool IsToolRegistered(string toolName)
|
||||
{
|
||||
return _tools.ContainsKey(toolName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manually trigger _tools changed notification
|
||||
/// Used for manual notifications and post-compilation notifications
|
||||
/// </summary>
|
||||
public static void TriggerToolsChangedNotification()
|
||||
{
|
||||
// Call the public method in McpServerController
|
||||
McpServerController.TriggerToolChangeNotification();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class representing tool information
|
||||
/// </summary>
|
||||
public class ToolInfo
|
||||
{
|
||||
[JsonProperty("name")] public string Name { get; }
|
||||
|
||||
[JsonProperty("description")] public string Description { get; }
|
||||
|
||||
[JsonProperty("parameterSchema")] public ToolParameterSchema ParameterSchema { get; }
|
||||
|
||||
[JsonProperty("displayDevelopmentOnly")] public bool DisplayDevelopmentOnly { get; }
|
||||
|
||||
public ToolInfo(string name, string description, ToolParameterSchema parameterSchema, bool displayDevelopmentOnly = false)
|
||||
{
|
||||
Name = name;
|
||||
Description = description;
|
||||
ParameterSchema = parameterSchema;
|
||||
DisplayDevelopmentOnly = displayDevelopmentOnly;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lightweight registry metadata used by Tool Settings UI without generating parameter schemas.
|
||||
/// </summary>
|
||||
public class ToolSettingsCatalogItem
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly string Description;
|
||||
public readonly bool DisplayDevelopmentOnly;
|
||||
public readonly bool IsThirdParty;
|
||||
|
||||
public ToolSettingsCatalogItem(
|
||||
string name,
|
||||
string description,
|
||||
bool displayDevelopmentOnly,
|
||||
bool isThirdParty)
|
||||
{
|
||||
Name = name;
|
||||
Description = description;
|
||||
DisplayDevelopmentOnly = displayDevelopmentOnly;
|
||||
IsThirdParty = isThirdParty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b13155ca2a4cd42b2b0c2997a7925511
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,43 @@
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// Data model representing validation result
|
||||
/// Used in Application Service Layer
|
||||
/// </summary>
|
||||
public class ValidationResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether validation was successful
|
||||
/// </summary>
|
||||
public bool IsValid { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Error message when validation fails
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create ValidationResult
|
||||
/// </summary>
|
||||
/// <param name="isValid">Validation result</param>
|
||||
/// <param name="errorMessage">Error message (null on success)</param>
|
||||
public ValidationResult(bool isValid, string errorMessage = null)
|
||||
{
|
||||
IsValid = isValid;
|
||||
ErrorMessage = errorMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create success result
|
||||
/// </summary>
|
||||
/// <returns>ValidationResult representing success</returns>
|
||||
public static ValidationResult Success() => new(true);
|
||||
|
||||
/// <summary>
|
||||
/// Create failure result
|
||||
/// </summary>
|
||||
/// <param name="errorMessage">Error message</param>
|
||||
/// <returns>ValidationResult representing failure</returns>
|
||||
public static ValidationResult Failure(string errorMessage) => new(false, errorMessage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de99bb9f98ad74025bd8be4e41a953af
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dbe57138c4a024b33bfe815d180e9068
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// Response for debug sleep tool execution
|
||||
/// Related classes:
|
||||
/// - SleepTool: Implementation that generates this response
|
||||
/// - SleepSchema: Input parameters for the sleep tool
|
||||
/// - BaseToolResponse: Base class providing timing information
|
||||
/// </summary>
|
||||
public class SleepResponse : BaseToolResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Message indicating the result of the sleep operation
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Actual number of seconds the tool slept before completion or timeout
|
||||
/// </summary>
|
||||
public int ActualSleepSeconds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the operation completed successfully or was cancelled
|
||||
/// </summary>
|
||||
public bool WasCancelled { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 28eaf066ee0a94a999dd67f9f4baabcd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// Schema for debug sleep tool parameters
|
||||
/// Related classes:
|
||||
/// - SleepTool: Implementation of the debug sleep tool
|
||||
/// - SleepResponse: Response structure for the sleep tool
|
||||
/// </summary>
|
||||
public class SleepSchema : BaseToolSchema
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of seconds to sleep for testing purposes (default: 15)
|
||||
/// </summary>
|
||||
[Description("Number of seconds to sleep for testing purposes (default: 15)")]
|
||||
public int SleepSeconds { get; set; } = 15;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 65adc65e8b27d41e889d4ccb67be267b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// Debug tool that sleeps for specified duration to test timeout functionality
|
||||
/// Related classes:
|
||||
/// - SleepSchema: Input parameters including sleep duration and timeout
|
||||
/// - SleepResponse: Output containing results and timing information
|
||||
/// - AbstractUnityTool: Base class providing timeout infrastructure
|
||||
/// </summary>
|
||||
[McpTool(
|
||||
DisplayDevelopmentOnly = true,
|
||||
Description = "Debug tool that sleeps for specified duration to test timeout functionality"
|
||||
)]
|
||||
public class SleepTool : AbstractUnityTool<SleepSchema, SleepResponse>
|
||||
{
|
||||
public override string ToolName => "debug-sleep";
|
||||
|
||||
protected override async Task<SleepResponse> ExecuteAsync(SleepSchema parameters, CancellationToken cancellationToken)
|
||||
{
|
||||
int actualSleepSeconds = 0;
|
||||
|
||||
// Sleep 1 second at a time, checking for cancellation between each sleep
|
||||
for (int i = 0; i < parameters.SleepSeconds; i++)
|
||||
{
|
||||
// Check for cancellation before each sleep iteration
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Sleep for 1 second with cancellation support
|
||||
await Task.Delay(1000, cancellationToken);
|
||||
|
||||
actualSleepSeconds = i + 1;
|
||||
}
|
||||
|
||||
return new SleepResponse
|
||||
{
|
||||
Message = $"Successfully slept for {actualSleepSeconds} seconds",
|
||||
ActualSleepSeconds = actualSleepSeconds,
|
||||
WasCancelled = false
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3819fd5fb05cb4309a2229a93f79f74a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae802852e2dfd47dc979543d41bb00d2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,31 @@
|
||||
# execute-dynamic-code design notes
|
||||
|
||||
This directory owns the user-facing `execute-dynamic-code` tool contract.
|
||||
The public contract must stay stable even when the internal compiler strategy changes.
|
||||
|
||||
## Fast path intent
|
||||
|
||||
- The preferred compilation path is Unity-bundled Roslyn.
|
||||
- `SharedRoslynCompilerWorkerHost` keeps a warm compiler process so structural cache misses stay fast.
|
||||
- The shared worker is an optimization only. It must not own response shaping, security validation, or tool-specific policy.
|
||||
|
||||
## Prewarm intent
|
||||
|
||||
- Auto prewarm exists to hide first-use compiler startup cost after server startup or recovery.
|
||||
- Prewarm must go through the same runtime facade used by normal requests.
|
||||
- Prewarm must be single-flight. It should never create a parallel execution lane or bypass runtime idleness checks.
|
||||
- Prewarm is allowed to improve latency only. It must not change observable tool behavior.
|
||||
|
||||
## Fallback intent
|
||||
|
||||
- Preferred order is: shared Roslyn worker -> one-shot Roslyn -> AssemblyBuilder fallback.
|
||||
- Fallback changes performance, not semantics.
|
||||
- Any fallback from the ideal fast path is a maintainer-visible error condition and must be logged through `DynamicCompilationHealthMonitor`.
|
||||
|
||||
## Invariants
|
||||
|
||||
- Security validation always runs in Unity after compilation and before execution.
|
||||
- In restricted mode, metadata validation and IL validation must both complete before `Assembly.Load`.
|
||||
- Tool entry points and use cases depend on runtime-facing facades, not compiler backend details.
|
||||
- Worker state is disposable. Domain reload or worker protocol failure must be handled by rebuilding state, not by preserving it.
|
||||
- The compile backend may change, but `ExecuteDynamicCodeResponse` shape and user-facing behavior must remain stable.
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a32019de73d24a92b23228d40898acf
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,98 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// Response for dynamic code execution tool
|
||||
|
||||
/// Related classes: ExecuteDynamicCodeTool, ExecuteDynamicCodeSchema
|
||||
/// </summary>
|
||||
public class ExecuteDynamicCodeResponse : BaseToolResponse
|
||||
{
|
||||
private const bool EmitTimingsInJsonResponses = false;
|
||||
|
||||
/// <summary>Execution success flag</summary>
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>Execution result</summary>
|
||||
public string Result { get; set; }
|
||||
|
||||
/// <summary>Log messages</summary>
|
||||
public List<string> Logs { get; set; } = new();
|
||||
|
||||
/// <summary>Compilation errors</summary>
|
||||
public List<CompilationErrorDto> CompilationErrors { get; set; } = new();
|
||||
|
||||
/// <summary>Error message (on failure)</summary>
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
/// <summary>Current security level</summary>
|
||||
public string SecurityLevel { get; set; }
|
||||
|
||||
/// <summary>Error message (alias for ErrorMessage)</summary>
|
||||
public string Error
|
||||
{
|
||||
get => ErrorMessage;
|
||||
set => ErrorMessage = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Code formatted for compilation
|
||||
/// (After extracting/moving using statements and applying class/method wrapping)
|
||||
/// Allows checking the actual compiled code during debugging
|
||||
/// </summary>
|
||||
public string UpdatedCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Summary of diagnostics (unique count, total count, first error brief)
|
||||
/// </summary>
|
||||
public string DiagnosticsSummary { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Structured diagnostics for rich clients (line/column/code/message/hint/suggestions)
|
||||
/// </summary>
|
||||
public List<CompilationErrorDto> Diagnostics { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Lightweight internal timings for benchmark comparison.
|
||||
/// </summary>
|
||||
public List<string> Timings { get; set; } = new();
|
||||
|
||||
// Keep timings available in memory for diagnostics and readiness decisions
|
||||
// while avoiding noisy default payloads for normal execute-dynamic-code users.
|
||||
public bool ShouldSerializeTimings()
|
||||
{
|
||||
return EmitTimingsInJsonResponses;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DTO for compilation error information
|
||||
/// </summary>
|
||||
public class CompilationErrorDto
|
||||
{
|
||||
/// <summary>Error message</summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>Line number</summary>
|
||||
public int Line { get; set; }
|
||||
|
||||
/// <summary>Column number</summary>
|
||||
public int Column { get; set; }
|
||||
|
||||
/// <summary>Compiler error code (e.g., CS0103)</summary>
|
||||
public string ErrorCode { get; set; }
|
||||
|
||||
/// <summary>Optional hint for resolving the error</summary>
|
||||
public string Hint { get; set; }
|
||||
|
||||
/// <summary>Suggested fixes (e.g., add using or qualify)</summary>
|
||||
public List<string> Suggestions { get; set; } = new();
|
||||
|
||||
/// <summary>Context lines around the error with a caret pointer</summary>
|
||||
public string Context { get; set; }
|
||||
|
||||
/// <summary>Pointer column for caret rendering (1-based)</summary>
|
||||
public int PointerColumn { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 88dbc381725914ce488e21b9b27a2cb8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
internal static class ExecuteDynamicCodeResponseTimingAugmenter
|
||||
{
|
||||
public static void AppendTimingEntries(
|
||||
ExecuteDynamicCodeResponse response,
|
||||
double mainThreadWaitMilliseconds,
|
||||
double toolTotalMilliseconds,
|
||||
double requestTotalMilliseconds)
|
||||
{
|
||||
if (response == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.Timings == null)
|
||||
{
|
||||
response.Timings = new List<string>();
|
||||
}
|
||||
|
||||
response.Timings.Add($"[Perf] MainThreadWait: {mainThreadWaitMilliseconds:F1}ms");
|
||||
response.Timings.Add($"[Perf] ToolTotal: {toolTotalMilliseconds:F1}ms");
|
||||
response.Timings.Add($"[Perf] RequestTotal: {requestTotalMilliseconds:F1}ms");
|
||||
response.Timings.AddRange(DynamicCodeStartupTelemetry.CreateTimingEntries());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8683636c860b448cf93c98208c36946b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,65 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// Parameter schema for dynamic code execution tool
|
||||
/// Related classes: ExecuteDynamicCodeTool, ExecuteDynamicCodeResponse
|
||||
/// </summary>
|
||||
public class ExecuteDynamicCodeSchema : BaseToolSchema
|
||||
{
|
||||
/// <summary>C# code to execute</summary>
|
||||
[Description(@"Editor automation only — no file I/O, no script authoring.
|
||||
|
||||
Direct statements only (no classes/namespaces/methods); return is optional (auto 'return null;' if omitted).
|
||||
|
||||
You may include using directives at the top; they are hoisted above the wrapper.
|
||||
Example:
|
||||
using UnityEngine;
|
||||
var x = Mathf.PI;
|
||||
return x;
|
||||
|
||||
Do:
|
||||
- Prefab/material wiring (PrefabUtility)
|
||||
- AddComponent + reference wiring (SerializedObject)
|
||||
- Scene/hierarchy edits
|
||||
|
||||
Don’t:
|
||||
- System.IO.* (File/Directory/Path)
|
||||
- AssetDatabase.CreateFolder / file writes
|
||||
- Create/edit .cs/.asmdef (use Terminal/IDE instead)
|
||||
|
||||
Need files/dirs? Run terminal commands.")]
|
||||
public string Code { get; set; } = "";
|
||||
|
||||
/// <summary>Runtime parameters (advanced; usually unnecessary)</summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Advanced)]
|
||||
[Description(@"Advanced, optional parameters passed to the snippet.
|
||||
|
||||
- For throwaway snippets, prefer defining locals directly in code
|
||||
- Keys are not preserved; values map to parameters[""param0""], parameters[""param1""], ...
|
||||
- Use when injecting UnityEngine.Object or sensitive values, or reusing a snippet with varying data
|
||||
- Use object values supported by the Editor; prefer fully-qualified type names for ambiguous refs (e.g., UnityEngine.Object)
|
||||
|
||||
- Input type must be an OBJECT. Strings like ""{}"" are invalid.
|
||||
- Do: omit 'Parameters' entirely, or use {}
|
||||
- Don't: ""{}"" (string), ""{""a"":1}"" (string)
|
||||
|
||||
Examples:
|
||||
OK: ""Parameters"": {}
|
||||
OK: (omit the 'Parameters' property)
|
||||
NG: ""Parameters"": ""{}""")]
|
||||
public Dictionary<string, object> Parameters { get; set; } = new();
|
||||
|
||||
/// <summary>Compile only (do not execute)</summary>
|
||||
[Description(@"Compile only (no execution).
|
||||
|
||||
- Uses Roslyn validation to surface diagnostics
|
||||
- For new MonoBehaviours: create .cs → compile (mcp__uLoopMCP__compile with ForceRecompile=false) → ensure ErrorCount=0 → AddComponent")]
|
||||
public bool CompileOnly { get; set; } = false;
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Advanced)]
|
||||
public bool YieldToForegroundRequests { get; set; } = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96f1941ea53e64fbf9dca3d1b273ae72
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,47 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// MCP Dynamic C# Code Execution Tool.
|
||||
/// Delegates workflow orchestration to the dedicated execute-dynamic-code use case.
|
||||
/// </summary>
|
||||
[McpTool(Description = @"Execute C# code dynamically in Unity Editor for editor automation.
|
||||
|
||||
Use find-game-objects first for basic selected GameObject discovery or property inspection. Use this after built-in inspection tools are not enough or when you need to modify Unity state.
|
||||
|
||||
Direct statements only (no classes/namespaces/methods); return is optional (auto 'return null;' if omitted).
|
||||
|
||||
You may include using directives at the top; they are hoisted above the wrapper.
|
||||
Example:
|
||||
using UnityEngine;
|
||||
var x = Mathf.PI;
|
||||
return x;
|
||||
|
||||
Do:
|
||||
- Prefab/material wiring (PrefabUtility)
|
||||
- AddComponent + reference wiring (SerializedObject)
|
||||
- Scene/hierarchy edits
|
||||
|
||||
Don't:
|
||||
- System.IO.* (File/Directory/Path)
|
||||
- AssetDatabase.CreateFolder / file writes
|
||||
- Create/edit .cs/.asmdef (use Terminal/IDE instead)
|
||||
|
||||
Need files/dirs? Run terminal commands.
|
||||
|
||||
See examples at {project_root}/.claude/skills/unity-cli-loop/uloop-execute-dynamic-code/examples/")]
|
||||
public class ExecuteDynamicCodeTool : AbstractUnityTool<ExecuteDynamicCodeSchema, ExecuteDynamicCodeResponse>
|
||||
{
|
||||
public override string ToolName => "execute-dynamic-code";
|
||||
|
||||
protected override async Task<ExecuteDynamicCodeResponse> ExecuteAsync(
|
||||
ExecuteDynamicCodeSchema parameters,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
IExecuteDynamicCodeUseCase useCase = await DynamicCodeServices.GetExecuteDynamicCodeUseCaseAsync();
|
||||
return await useCase.ExecuteAsync(parameters, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c0a0005c984014fe9ae1227c1e458432
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e4545560316c242f08f6034dc0c57177
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user