god-code 0.5.4__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. {god_code-0.5.4 → god_code-0.6.0}/CLAUDE.md +10 -0
  2. {god_code-0.5.4 → god_code-0.6.0}/PKG-INFO +5 -3
  3. {god_code-0.5.4 → god_code-0.6.0}/README.md +78 -5
  4. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/cli.py +24 -1
  5. god_code-0.6.0/godot_agent/mcp_server.py +393 -0
  6. {god_code-0.5.4 → god_code-0.6.0}/pyproject.toml +6 -3
  7. god_code-0.6.0/skills/god-code-setup/SKILL.md +123 -0
  8. {god_code-0.5.4 → god_code-0.6.0}/tests/test_package_compatibility.py +2 -2
  9. {god_code-0.5.4 → god_code-0.6.0}/.github/workflows/publish.yml +0 -0
  10. {god_code-0.5.4 → god_code-0.6.0}/.gitignore +0 -0
  11. {god_code-0.5.4 → god_code-0.6.0}/AGENTS.md +0 -0
  12. {god_code-0.5.4 → god_code-0.6.0}/CHANGELOG.md +0 -0
  13. {god_code-0.5.4 → god_code-0.6.0}/CONTRIBUTING.md +0 -0
  14. {god_code-0.5.4 → god_code-0.6.0}/LICENSE +0 -0
  15. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/__init__.py +0 -0
  16. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/agents/__init__.py +0 -0
  17. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/agents/configs.py +0 -0
  18. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/agents/dispatcher.py +0 -0
  19. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/agents/results.py +0 -0
  20. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/godot/__init__.py +0 -0
  21. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/godot/collision_planner.py +0 -0
  22. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/godot/consistency_checker.py +0 -0
  23. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/godot/dependency_graph.py +0 -0
  24. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/godot/gdscript_linter.py +0 -0
  25. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/godot/impact_analysis.py +0 -0
  26. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/godot/pattern_advisor.py +0 -0
  27. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/godot/project.py +0 -0
  28. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/godot/resource_validator.py +0 -0
  29. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/godot/scene_parser.py +0 -0
  30. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/godot/scene_writer.py +0 -0
  31. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/godot/tscn_validator.py +0 -0
  32. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/llm/__init__.py +0 -0
  33. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/llm/adapters/__init__.py +0 -0
  34. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/llm/adapters/anthropic.py +0 -0
  35. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/llm/adapters/base.py +0 -0
  36. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/llm/adapters/openai.py +0 -0
  37. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/llm/client.py +0 -0
  38. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/llm/streaming.py +0 -0
  39. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/llm/types.py +0 -0
  40. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/llm/vision.py +0 -0
  41. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/prompts/__init__.py +0 -0
  42. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/prompts/assembler.py +0 -0
  43. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/prompts/build_discipline.py +0 -0
  44. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/prompts/godot_playbook.py +0 -0
  45. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/prompts/image_templates.py +0 -0
  46. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/prompts/knowledge_selector.py +0 -0
  47. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/prompts/skill_library.py +0 -0
  48. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/prompts/skill_selector.py +0 -0
  49. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/prompts/system.py +0 -0
  50. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/py.typed +0 -0
  51. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/runtime/__init__.py +0 -0
  52. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/runtime/auth.py +0 -0
  53. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/runtime/config.py +0 -0
  54. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/runtime/context_manager.py +0 -0
  55. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/runtime/design_memory.py +0 -0
  56. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/runtime/engine.py +0 -0
  57. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/runtime/error_loop.py +0 -0
  58. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/runtime/events.py +0 -0
  59. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/runtime/gameplay_reviewer.py +0 -0
  60. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/runtime/modes.py +0 -0
  61. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/runtime/oauth.py +0 -0
  62. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/runtime/playtest_harness.py +0 -0
  63. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/runtime/providers.py +0 -0
  64. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/runtime/quality_gate.py +0 -0
  65. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/runtime/reviewer.py +0 -0
  66. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/runtime/runtime_bridge.py +0 -0
  67. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/runtime/scenario_specs/hud_feedback.json +0 -0
  68. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/runtime/scenario_specs/player_movement.json +0 -0
  69. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/runtime/scenario_specs/scene_transition.json +0 -0
  70. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/runtime/session.py +0 -0
  71. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/security/__init__.py +0 -0
  72. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/security/classifier.py +0 -0
  73. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/security/hooks.py +0 -0
  74. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/security/policies.py +0 -0
  75. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/security/protected_paths.py +0 -0
  76. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/security/tool_pipeline.py +0 -0
  77. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/testing/__init__.py +0 -0
  78. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/testing/scenario_runner.py +0 -0
  79. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/tools/__init__.py +0 -0
  80. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/tools/analysis_tools.py +0 -0
  81. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/tools/base.py +0 -0
  82. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/tools/editor_bridge.py +0 -0
  83. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/tools/file_ops.py +0 -0
  84. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/tools/git.py +0 -0
  85. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/tools/godot_cli.py +0 -0
  86. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/tools/image_gen.py +0 -0
  87. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/tools/list_dir.py +0 -0
  88. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/tools/memory_tool.py +0 -0
  89. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/tools/registry.py +0 -0
  90. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/tools/scene_tools.py +0 -0
  91. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/tools/screenshot.py +0 -0
  92. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/tools/script_tools.py +0 -0
  93. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/tools/search.py +0 -0
  94. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/tools/shell.py +0 -0
  95. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/tools/web_search.py +0 -0
  96. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/tui/__init__.py +0 -0
  97. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/tui/display.py +0 -0
  98. {god_code-0.5.4 → god_code-0.6.0}/godot_agent/tui/input_handler.py +0 -0
  99. {god_code-0.5.4 → god_code-0.6.0}/tests/__init__.py +0 -0
  100. {god_code-0.5.4 → god_code-0.6.0}/tests/agents/test_dispatcher.py +0 -0
  101. {god_code-0.5.4 → god_code-0.6.0}/tests/agents/test_playtest_analyst.py +0 -0
  102. {god_code-0.5.4 → god_code-0.6.0}/tests/e2e/test_planner_worker_reviewer_flow.py +0 -0
  103. {god_code-0.5.4 → god_code-0.6.0}/tests/e2e/test_policy_enforcement.py +0 -0
  104. {god_code-0.5.4 → god_code-0.6.0}/tests/e2e/test_scenario_runner.py +0 -0
  105. {god_code-0.5.4 → god_code-0.6.0}/tests/godot/__init__.py +0 -0
  106. {god_code-0.5.4 → god_code-0.6.0}/tests/godot/test_collision_planner.py +0 -0
  107. {god_code-0.5.4 → god_code-0.6.0}/tests/godot/test_consistency.py +0 -0
  108. {god_code-0.5.4 → god_code-0.6.0}/tests/godot/test_dependency_graph.py +0 -0
  109. {god_code-0.5.4 → god_code-0.6.0}/tests/godot/test_impact_analysis.py +0 -0
  110. {god_code-0.5.4 → god_code-0.6.0}/tests/godot/test_linter.py +0 -0
  111. {god_code-0.5.4 → god_code-0.6.0}/tests/godot/test_pattern_advisor.py +0 -0
  112. {god_code-0.5.4 → god_code-0.6.0}/tests/godot/test_project.py +0 -0
  113. {god_code-0.5.4 → god_code-0.6.0}/tests/godot/test_resource_validator.py +0 -0
  114. {god_code-0.5.4 → god_code-0.6.0}/tests/godot/test_scene_parser.py +0 -0
  115. {god_code-0.5.4 → god_code-0.6.0}/tests/godot/test_scene_writer.py +0 -0
  116. {god_code-0.5.4 → god_code-0.6.0}/tests/godot/test_tscn_validator.py +0 -0
  117. {god_code-0.5.4 → god_code-0.6.0}/tests/llm/__init__.py +0 -0
  118. {god_code-0.5.4 → god_code-0.6.0}/tests/llm/test_adapters.py +0 -0
  119. {god_code-0.5.4 → god_code-0.6.0}/tests/llm/test_client.py +0 -0
  120. {god_code-0.5.4 → god_code-0.6.0}/tests/llm/test_vision.py +0 -0
  121. {god_code-0.5.4 → god_code-0.6.0}/tests/prompts/__init__.py +0 -0
  122. {god_code-0.5.4 → god_code-0.6.0}/tests/prompts/test_knowledge_selector.py +0 -0
  123. {god_code-0.5.4 → god_code-0.6.0}/tests/prompts/test_prompt_assembler.py +0 -0
  124. {god_code-0.5.4 → god_code-0.6.0}/tests/prompts/test_skill_selector.py +0 -0
  125. {god_code-0.5.4 → god_code-0.6.0}/tests/prompts/test_system_prompt.py +0 -0
  126. {god_code-0.5.4 → god_code-0.6.0}/tests/runtime/__init__.py +0 -0
  127. {god_code-0.5.4 → god_code-0.6.0}/tests/runtime/test_config.py +0 -0
  128. {god_code-0.5.4 → god_code-0.6.0}/tests/runtime/test_context_manager.py +0 -0
  129. {god_code-0.5.4 → god_code-0.6.0}/tests/runtime/test_design_memory.py +0 -0
  130. {god_code-0.5.4 → god_code-0.6.0}/tests/runtime/test_engine.py +0 -0
  131. {god_code-0.5.4 → god_code-0.6.0}/tests/runtime/test_error_loop.py +0 -0
  132. {god_code-0.5.4 → god_code-0.6.0}/tests/runtime/test_gameplay_reviewer.py +0 -0
  133. {god_code-0.5.4 → god_code-0.6.0}/tests/runtime/test_mode_restrictions.py +0 -0
  134. {god_code-0.5.4 → god_code-0.6.0}/tests/runtime/test_multi_agent_flow.py +0 -0
  135. {god_code-0.5.4 → god_code-0.6.0}/tests/runtime/test_playtest_harness.py +0 -0
  136. {god_code-0.5.4 → god_code-0.6.0}/tests/runtime/test_quality_gate.py +0 -0
  137. {god_code-0.5.4 → god_code-0.6.0}/tests/runtime/test_reviewer.py +0 -0
  138. {god_code-0.5.4 → god_code-0.6.0}/tests/runtime/test_runtime_bridge.py +0 -0
  139. {god_code-0.5.4 → god_code-0.6.0}/tests/runtime/test_session.py +0 -0
  140. {god_code-0.5.4 → god_code-0.6.0}/tests/security/test_classifier.py +0 -0
  141. {god_code-0.5.4 → god_code-0.6.0}/tests/security/test_hooks.py +0 -0
  142. {god_code-0.5.4 → god_code-0.6.0}/tests/security/test_permissions.py +0 -0
  143. {god_code-0.5.4 → god_code-0.6.0}/tests/security/test_tool_pipeline.py +0 -0
  144. {god_code-0.5.4 → god_code-0.6.0}/tests/test_cli_config_flow.py +0 -0
  145. {god_code-0.5.4 → god_code-0.6.0}/tests/test_e2e.py +0 -0
  146. {god_code-0.5.4 → god_code-0.6.0}/tests/test_runtime_switch_commands.py +0 -0
  147. {god_code-0.5.4 → god_code-0.6.0}/tests/tools/__init__.py +0 -0
  148. {god_code-0.5.4 → god_code-0.6.0}/tests/tools/test_analysis_tools.py +0 -0
  149. {god_code-0.5.4 → god_code-0.6.0}/tests/tools/test_editor_bridge.py +0 -0
  150. {god_code-0.5.4 → god_code-0.6.0}/tests/tools/test_file_ops.py +0 -0
  151. {god_code-0.5.4 → god_code-0.6.0}/tests/tools/test_git.py +0 -0
  152. {god_code-0.5.4 → god_code-0.6.0}/tests/tools/test_godot_cli.py +0 -0
  153. {god_code-0.5.4 → god_code-0.6.0}/tests/tools/test_list_dir.py +0 -0
  154. {god_code-0.5.4 → god_code-0.6.0}/tests/tools/test_memory_tool.py +0 -0
  155. {god_code-0.5.4 → god_code-0.6.0}/tests/tools/test_registry.py +0 -0
  156. {god_code-0.5.4 → god_code-0.6.0}/tests/tools/test_scene_tools.py +0 -0
  157. {god_code-0.5.4 → god_code-0.6.0}/tests/tools/test_script_tools.py +0 -0
  158. {god_code-0.5.4 → god_code-0.6.0}/tests/tools/test_search.py +0 -0
  159. {god_code-0.5.4 → god_code-0.6.0}/tests/tools/test_shell.py +0 -0
  160. {god_code-0.5.4 → god_code-0.6.0}/tests/tui/test_input_handler.py +0 -0
@@ -144,6 +144,16 @@ Smart compression at 75% (787K tokens):
144
144
 
145
145
  ## Development Rules
146
146
 
147
+ ### CRITICAL: Python 3.9 Compatibility
148
+ `pyproject.toml` MUST keep `requires-python = ">=3.9"`. macOS ships Python 3.9 via Xcode — users run `pip install god-code` with it. Never change this.
149
+
150
+ Rules:
151
+ - No `dataclass(slots=True)` — Python 3.10+ only
152
+ - No `match`/`case` statements — Python 3.10+ only
153
+ - Every `.py` file MUST have `from __future__ import annotations` as first import
154
+ - Use `eval_type_backport` dependency for pydantic `str | None` on 3.9
155
+ - **Before every release**: verify `grep "requires-python" pyproject.toml` shows `>=3.9`
156
+
147
157
  ### Adding a New Tool
148
158
  1. Create in `godot_agent/tools/your_tool.py` inheriting `BaseTool`
149
159
  2. Define `Input`/`Output` as pydantic `BaseModel` (all fields must have defaults for strict mode)
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: god-code
3
- Version: 0.5.4
3
+ Version: 0.6.0
4
4
  Summary: AI coding agent specialized for Godot game development
5
- Project-URL: Homepage, https://github.com/chuisiufai/god-code
6
- Project-URL: Repository, https://github.com/chuisiufai/god-code
5
+ Project-URL: Homepage, https://github.com/888wing/god-code
6
+ Project-URL: Repository, https://github.com/888wing/god-code
7
7
  Author: chuisiufai
8
8
  License: GPL-3.0-or-later
9
9
  License-File: LICENSE
@@ -19,3 +19,5 @@ Provides-Extra: dev
19
19
  Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
20
20
  Requires-Dist: pytest-mock>=3.14; extra == 'dev'
21
21
  Requires-Dist: pytest>=8.2; extra == 'dev'
22
+ Provides-Extra: mcp
23
+ Requires-Dist: mcp[cli]>=1.12; extra == 'mcp'
@@ -25,15 +25,34 @@ AI coding agent specialized for Godot 4.4 game development. Unlike generic codin
25
25
  pip install god-code
26
26
  ```
27
27
 
28
- Or from source:
28
+ With MCP support (for Claude Code / Codex integration):
29
29
 
30
30
  ```bash
31
- git clone https://github.com/chuisiufai/god-code.git
32
- cd god-code
33
- pip install -e ".[dev]"
31
+ pip install god-code[mcp]
32
+ ```
33
+
34
+ Requires Python 3.9+.
35
+
36
+ ### Claude Code Skill (one-click setup)
37
+
38
+ If you use Claude Code, install the god-code skill for automated setup:
39
+
40
+ ```bash
41
+ # Copy the skill to your Claude Code skills directory
42
+ mkdir -p ~/.claude/skills/god-code-setup
43
+ curl -sL https://raw.githubusercontent.com/888wing/god-code/main/skills/god-code-setup/SKILL.md \
44
+ -o ~/.claude/skills/god-code-setup/SKILL.md
34
45
  ```
35
46
 
36
- Requires Python 3.12+.
47
+ Then in Claude Code, just say: **"install god-code and configure MCP"** — it will handle everything automatically.
48
+
49
+ ### From Source
50
+
51
+ ```bash
52
+ git clone https://github.com/888wing/god-code.git
53
+ cd god-code
54
+ pip install -e ".[dev,mcp]"
55
+ ```
37
56
 
38
57
  ## Quick Start
39
58
 
@@ -190,6 +209,60 @@ God Code executes tools on your local machine. The LLM decides which tools to ca
190
209
 
191
210
  **API keys**: Stored in `~/.config/god-code/config.json` with `600` permissions. Never committed to git.
192
211
 
212
+ ## MCP Server (for Claude Code / Codex / AI Agents)
213
+
214
+ God Code can run as an MCP (Model Context Protocol) server, exposing 20 Godot tools directly to AI agents. **No LLM middleman, zero token cost** — tools run locally.
215
+
216
+ ### Install
217
+
218
+ ```bash
219
+ pip install god-code[mcp]
220
+ ```
221
+
222
+ ### Configure in Claude Code
223
+
224
+ Add to `~/.claude.json` or Claude Desktop config:
225
+
226
+ ```json
227
+ {
228
+ "mcpServers": {
229
+ "god-code": {
230
+ "command": "god-code",
231
+ "args": ["mcp", "--project", "/path/to/your/godot/project"]
232
+ }
233
+ }
234
+ }
235
+ ```
236
+
237
+ ### Available MCP Tools
238
+
239
+ | Tool | Description |
240
+ |------|-------------|
241
+ | `validate_project` | Run Godot headless validation, return errors/warnings |
242
+ | `validate_tscn` | Check .tscn format, optionally auto-fix ordering |
243
+ | `lint_script` | GDScript style, naming, type annotation checks |
244
+ | `check_consistency` | Cross-file collision/signal/resource consistency |
245
+ | `plan_collision` | Generate standard 8-layer collision config |
246
+ | `analyze_dependencies` | Build project-wide dependency graph |
247
+ | `suggest_patterns` | Object pool, component, state machine suggestions |
248
+ | `parse_scene` | Parse .tscn into structured node tree |
249
+ | `project_info` | Read project.godot metadata |
250
+ | `godot_knowledge` | Query Godot 4.4 Playbook (17 knowledge sections) |
251
+ | `generate_sprite` | AI pixel art generation + post-processing |
252
+ | `validate_resources` | Check all res:// paths exist |
253
+
254
+ ### How it works
255
+
256
+ ```
257
+ Claude Code / Codex
258
+ ↓ (MCP protocol over stdio)
259
+ god-code mcp process (local)
260
+ ↓ (direct function calls)
261
+ Godot analysis tools (no LLM needed)
262
+ ```
263
+
264
+ The AI agent gets Godot-native intelligence without you paying for an extra LLM layer.
265
+
193
266
  ## Architecture
194
267
 
195
268
  ```
@@ -702,7 +702,7 @@ def _run_setup_wizard(config_path: Path | None = None) -> None:
702
702
  click.echo()
703
703
 
704
704
 
705
- _VERSION = "0.5.4"
705
+ _VERSION = "0.6.0"
706
706
 
707
707
 
708
708
  def _check_update() -> None:
@@ -1657,5 +1657,28 @@ def info(project: str):
1657
1657
  click.echo(f" {name} -> {path}")
1658
1658
 
1659
1659
 
1660
+ @main.command("mcp")
1661
+ @click.option("--project", "-p", default=".", help="Path to Godot project root")
1662
+ def mcp_command(project: str):
1663
+ """Start MCP server (for Claude Code, Codex, and other AI agents).
1664
+
1665
+ Exposes god-code's Godot tools via Model Context Protocol over stdio.
1666
+ No LLM needed — tools run locally, zero token cost.
1667
+
1668
+ Configure in Claude Code:
1669
+ \b
1670
+ {
1671
+ "mcpServers": {
1672
+ "god-code": {
1673
+ "command": "god-code",
1674
+ "args": ["mcp", "--project", "/path/to/project"]
1675
+ }
1676
+ }
1677
+ }
1678
+ """
1679
+ from godot_agent.mcp_server import run_mcp_server
1680
+ run_mcp_server(project_path=project)
1681
+
1682
+
1660
1683
  if __name__ == "__main__":
1661
1684
  main()
@@ -0,0 +1,393 @@
1
+ """MCP Server — exposes god-code's Godot tools to AI agents (Claude Code, Codex, etc).
2
+
3
+ Usage:
4
+ god-code mcp --project /path/to/godot/project
5
+
6
+ Runs as a local stdio process. No backend server needed.
7
+ 12 analysis tools + 8 write/execute tools = full game development capability.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import logging
14
+ from pathlib import Path
15
+
16
+ from mcp.server.fastmcp import FastMCP
17
+
18
+ log = logging.getLogger(__name__)
19
+
20
+ mcp = FastMCP("god-code")
21
+
22
+ _project_root: Path | None = None
23
+
24
+
25
+ def set_mcp_project_root(path: Path) -> None:
26
+ global _project_root
27
+ _project_root = path.resolve()
28
+
29
+
30
+ def _root() -> Path:
31
+ return _project_root or Path.cwd()
32
+
33
+
34
+ def _godot_path() -> str:
35
+ config_file = Path.home() / ".config" / "god-code" / "config.json"
36
+ if config_file.exists():
37
+ return json.loads(config_file.read_text()).get("godot_path", "godot")
38
+ return "godot"
39
+
40
+
41
+ # ═══════════════════════════════════════════════════════════════════
42
+ # ANALYSIS TOOLS (read-only, zero side effects)
43
+ # ═══════════════════════════════════════════════════════════════════
44
+
45
+ @mcp.tool()
46
+ async def validate_project(project_path: str = "") -> dict:
47
+ """Run Godot headless validation. Returns errors and warnings."""
48
+ from godot_agent.runtime.error_loop import validate_project as _validate, format_validation_for_llm
49
+ root = project_path or str(_root())
50
+ result = await _validate(root, _godot_path(), timeout=30)
51
+ return {
52
+ "success": result.success,
53
+ "errors": [{"file": e.file, "line": e.line, "message": e.message} for e in result.errors],
54
+ "warnings": [{"file": w.file, "line": w.line, "message": w.message} for w in result.warnings],
55
+ "summary": format_validation_for_llm(result),
56
+ }
57
+
58
+
59
+ @mcp.tool()
60
+ def validate_tscn(file_path: str, auto_fix: bool = False) -> dict:
61
+ """Validate .tscn format. Optionally auto-fix ordering issues (sub_resource before node, load_steps count)."""
62
+ from godot_agent.godot.tscn_validator import validate_tscn as _validate, validate_and_fix
63
+ text = Path(file_path).read_text(errors="replace")
64
+ if auto_fix:
65
+ fixed_text, issues = validate_and_fix(text)
66
+ if fixed_text != text:
67
+ Path(file_path).write_text(fixed_text)
68
+ return {"fixed": fixed_text != text, "remaining_issues": [str(i) for i in issues]}
69
+ issues = _validate(text)
70
+ return {"issues": [str(i) for i in issues], "has_errors": any(i.severity == "error" for i in issues)}
71
+
72
+
73
+ @mcp.tool()
74
+ def lint_script(file_path: str) -> dict:
75
+ """Lint GDScript for naming, ordering, type annotations, and anti-patterns."""
76
+ from godot_agent.godot.gdscript_linter import lint_gdscript, format_lint_report
77
+ text = Path(file_path).read_text(errors="replace")
78
+ issues = lint_gdscript(text, filename=Path(file_path).name)
79
+ return {
80
+ "report": format_lint_report(issues, Path(file_path).name),
81
+ "error_count": sum(1 for i in issues if i.severity == "error"),
82
+ "warning_count": sum(1 for i in issues if i.severity == "warning"),
83
+ }
84
+
85
+
86
+ @mcp.tool()
87
+ def check_consistency(project_path: str = "") -> dict:
88
+ """Check cross-file consistency: collision layers, signals, resource paths, groups."""
89
+ from godot_agent.godot.consistency_checker import check_consistency as _check, format_consistency_report
90
+ root = Path(project_path) if project_path else _root()
91
+ issues = _check(root)
92
+ return {"report": format_consistency_report(issues), "error_count": sum(1 for i in issues if i.severity == "error")}
93
+
94
+
95
+ @mcp.tool()
96
+ def plan_collision(entities: list[str]) -> dict:
97
+ """Generate standard collision layer/mask config. Entities: player, enemy, player_bullet, enemy_bullet, terrain, pickup, trigger, interactable."""
98
+ from godot_agent.godot.collision_planner import plan_game_collisions, format_collision_plan
99
+ configs = plan_game_collisions(entities)
100
+ return {
101
+ "configs": [{"entity": c.entity_type, "layer": c.layer, "bitmask": c.layer_bitmask,
102
+ "mask_layers": c.mask_layers, "mask_bitmask": c.mask_bitmask, "gdscript": c.to_gdscript()}
103
+ for c in configs],
104
+ "plan": format_collision_plan(configs),
105
+ }
106
+
107
+
108
+ @mcp.tool()
109
+ def analyze_dependencies(project_path: str = "") -> dict:
110
+ """Build project dependency graph (scenes → scripts → resources)."""
111
+ from godot_agent.godot.dependency_graph import build_dependency_graph
112
+ graph = build_dependency_graph(Path(project_path) if project_path else _root())
113
+ return {"summary": graph.format_summary(), "autoloads": graph.autoloads, "orphans": graph.orphans()}
114
+
115
+
116
+ @mcp.tool()
117
+ def suggest_patterns(project_path: str = "") -> dict:
118
+ """Suggest design patterns: object pool, component, state machine."""
119
+ from godot_agent.godot.pattern_advisor import analyze_project, format_advice
120
+ advice = analyze_project(Path(project_path) if project_path else _root())
121
+ return {"report": format_advice(advice), "count": len(advice)}
122
+
123
+
124
+ @mcp.tool()
125
+ def parse_scene(file_path: str) -> dict:
126
+ """Parse .tscn into structured data: nodes, resources, connections, node paths."""
127
+ from godot_agent.godot.scene_parser import parse_tscn
128
+ scene = parse_tscn(Path(file_path).read_text(errors="replace"))
129
+ return {
130
+ "nodes": [{"name": n.name, "type": n.type, "parent": n.parent, "properties": n.properties} for n in scene.nodes],
131
+ "ext_resources": [{"type": r.type, "path": r.path, "id": r.id} for r in scene.ext_resources],
132
+ "connections": [{"signal": c.signal, "from": c.from_node, "to": c.to_node, "method": c.method} for c in scene.connections],
133
+ "node_paths": scene.node_paths(),
134
+ }
135
+
136
+
137
+ @mcp.tool()
138
+ def project_info(project_path: str = "") -> dict:
139
+ """Read project.godot: name, version, autoloads, resolution, renderer."""
140
+ from godot_agent.godot.project import parse_project_godot
141
+ proj = parse_project_godot((Path(project_path) if project_path else _root()) / "project.godot")
142
+ return {"name": proj.name, "version": proj.version, "main_scene": proj.main_scene,
143
+ "resolution": f"{proj.viewport_width}x{proj.viewport_height}", "autoloads": proj.autoloads}
144
+
145
+
146
+ @mcp.tool()
147
+ def godot_knowledge(topic: str) -> dict:
148
+ """Query Godot 4.4 Playbook. Topics: collision, physics, signal, animation, ui, input, performance, resource, autoload, shader, etc."""
149
+ from godot_agent.prompts.knowledge_selector import select_sections
150
+ sections = select_sections(topic, max_sections=3)
151
+ return {"sections": [{"title": t, "content": c} for t, c in sections]}
152
+
153
+
154
+ @mcp.tool()
155
+ def validate_resources(file_path: str, project_path: str = "") -> dict:
156
+ """Check all ext_resource paths in a .tscn exist on disk."""
157
+ from godot_agent.godot.resource_validator import validate_resources as _validate
158
+ issues = _validate(Path(file_path), project_root=Path(project_path) if project_path else _root())
159
+ return {"valid": len(issues) == 0, "missing": issues}
160
+
161
+
162
+ # ═══════════════════════════════════════════════════════════════════
163
+ # WRITE TOOLS (modify files with format protection)
164
+ # ═══════════════════════════════════════════════════════════════════
165
+
166
+ @mcp.tool()
167
+ def write_scene(file_path: str, content: str) -> dict:
168
+ """Write a .tscn file with automatic format validation and fix.
169
+
170
+ Validates the content before writing. Auto-fixes sub_resource ordering and load_steps count.
171
+ Use this instead of writing .tscn files directly to prevent format errors.
172
+ """
173
+ from godot_agent.godot.tscn_validator import validate_and_fix
174
+ fixed_text, issues = validate_and_fix(content)
175
+ errors = [i for i in issues if i.severity == "error"]
176
+ if errors:
177
+ return {"written": False, "errors": [str(e) for e in errors]}
178
+ Path(file_path).parent.mkdir(parents=True, exist_ok=True)
179
+ Path(file_path).write_text(fixed_text, encoding="utf-8")
180
+ return {"written": True, "path": file_path, "auto_fixed": fixed_text != content,
181
+ "warnings": [str(i) for i in issues if i.severity == "warning"]}
182
+
183
+
184
+ @mcp.tool()
185
+ def add_scene_node(file_path: str, parent: str, name: str, node_type: str,
186
+ properties: dict[str, str] | None = None) -> dict:
187
+ """Add a node to an existing .tscn scene file.
188
+
189
+ Args:
190
+ file_path: Path to .tscn file
191
+ parent: Parent node path ("." for root's child, "NodeName" for deeper)
192
+ name: New node name (PascalCase)
193
+ node_type: Godot node type (Node2D, Sprite2D, Area2D, Label, etc.)
194
+ properties: Optional property dict (e.g., {"position": "Vector2(100, 200)"})
195
+ """
196
+ from godot_agent.godot.scene_writer import add_node
197
+ from godot_agent.godot.tscn_validator import validate_and_fix
198
+ text = Path(file_path).read_text(errors="replace")
199
+ new_text = add_node(text, parent=parent, name=name, type=node_type, properties=properties)
200
+ fixed, issues = validate_and_fix(new_text)
201
+ Path(file_path).write_text(fixed, encoding="utf-8")
202
+ return {"added": True, "node": f"{name} ({node_type})", "parent": parent,
203
+ "warnings": [str(i) for i in issues if i.severity == "warning"]}
204
+
205
+
206
+ @mcp.tool()
207
+ def set_scene_property(file_path: str, node_name: str, key: str, value: str) -> dict:
208
+ """Set or update a property on a node in a .tscn file.
209
+
210
+ Args:
211
+ file_path: Path to .tscn file
212
+ node_name: Name of the node to modify
213
+ key: Property name (e.g., "position", "text", "color", "visible")
214
+ value: Property value as Godot string (e.g., "Vector2(10, 20)", '"Hello"', "true")
215
+ """
216
+ from godot_agent.godot.scene_writer import set_node_property
217
+ text = Path(file_path).read_text(errors="replace")
218
+ new_text = set_node_property(text, node_name=node_name, key=key, value=value)
219
+ Path(file_path).write_text(new_text, encoding="utf-8")
220
+ return {"set": True, "node": node_name, "property": f"{key} = {value}"}
221
+
222
+
223
+ @mcp.tool()
224
+ def add_signal_connection(file_path: str, signal_name: str, from_node: str,
225
+ to_node: str, method: str) -> dict:
226
+ """Add a signal connection to a .tscn file.
227
+
228
+ Args:
229
+ signal_name: Signal name (e.g., "pressed", "body_entered", "timeout")
230
+ from_node: Source node path
231
+ to_node: Target node path
232
+ method: Handler method name (e.g., "_on_button_pressed")
233
+ """
234
+ from godot_agent.godot.scene_writer import add_connection
235
+ text = Path(file_path).read_text(errors="replace")
236
+ new_text = add_connection(text, signal_name=signal_name, from_node=from_node,
237
+ to_node=to_node, method=method)
238
+ Path(file_path).write_text(new_text, encoding="utf-8")
239
+ return {"connected": True, "signal": signal_name, "from": from_node, "to": to_node, "method": method}
240
+
241
+
242
+ @mcp.tool()
243
+ def write_script(file_path: str, content: str, lint: bool = True) -> dict:
244
+ """Write a GDScript file with optional lint validation.
245
+
246
+ Use this instead of writing .gd files directly. Runs the linter and returns issues.
247
+ """
248
+ Path(file_path).parent.mkdir(parents=True, exist_ok=True)
249
+ Path(file_path).write_text(content, encoding="utf-8")
250
+ result = {"written": True, "path": file_path}
251
+ if lint:
252
+ from godot_agent.godot.gdscript_linter import lint_gdscript
253
+ issues = lint_gdscript(content, filename=Path(file_path).name)
254
+ result["lint_errors"] = sum(1 for i in issues if i.severity == "error")
255
+ result["lint_warnings"] = sum(1 for i in issues if i.severity == "warning")
256
+ if issues:
257
+ result["lint_issues"] = [str(i) for i in issues[:10]]
258
+ return result
259
+
260
+
261
+ # ═══════════════════════════════════════════════════════════════════
262
+ # EXECUTION TOOLS (run Godot, take screenshots)
263
+ # ═══════════════════════════════════════════════════════════════════
264
+
265
+ @mcp.tool()
266
+ async def run_gut_tests(test_script: str = "", project_path: str = "") -> dict:
267
+ """Run GUT tests in headless mode. Returns pass/fail results.
268
+
269
+ Args:
270
+ test_script: Specific test script (e.g., "res://tests/test_battle.gd"). Empty = run all.
271
+ project_path: Godot project root. Empty = use configured root.
272
+ """
273
+ import asyncio
274
+ from godot_agent.tools.godot_cli import build_gut_command, parse_godot_output
275
+ root = project_path or str(_root())
276
+ cmd = build_gut_command(_godot_path(), test_script or None)
277
+ proc = await asyncio.create_subprocess_exec(
278
+ *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, cwd=root)
279
+ stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=120)
280
+ output = stdout.decode(errors="replace") + "\n" + stderr.decode(errors="replace")
281
+ report = parse_godot_output(output)
282
+ return {
283
+ "exit_code": proc.returncode or 0,
284
+ "passed": proc.returncode == 0,
285
+ "errors": [{"file": e.file, "line": e.line, "message": e.message} for e in report.errors],
286
+ "output_tail": output[-500:],
287
+ }
288
+
289
+
290
+ @mcp.tool()
291
+ async def screenshot_scene(scene_path: str, output_path: str = "", delay_ms: int = 1000,
292
+ project_path: str = "") -> dict:
293
+ """Take a screenshot of a Godot scene using headless rendering.
294
+
295
+ Args:
296
+ scene_path: res:// path to the scene (e.g., "res://scenes/main.tscn")
297
+ output_path: Where to save PNG. Empty = temp file.
298
+ delay_ms: Wait time before capture (ms). Default 1000.
299
+ """
300
+ import asyncio
301
+ import tempfile
302
+ from godot_agent.tools.godot_cli import build_screenshot_script
303
+ from godot_agent.llm.vision import encode_image
304
+
305
+ root = project_path or str(_root())
306
+ if not output_path:
307
+ output_path = str(Path(tempfile.mkdtemp()) / "screenshot.png")
308
+
309
+ script = build_screenshot_script(scene_path, output_path, delay_ms)
310
+ script_path = str(Path(tempfile.mkdtemp()) / "capture.gd")
311
+ Path(script_path).write_text(script)
312
+
313
+ proc = await asyncio.create_subprocess_exec(
314
+ _godot_path(), "--headless", "-s", script_path,
315
+ stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, cwd=root)
316
+ await asyncio.wait_for(proc.communicate(), timeout=30)
317
+
318
+ if not Path(output_path).exists():
319
+ return {"success": False, "error": "Screenshot not created. Scene may have failed to load."}
320
+
321
+ b64 = encode_image(output_path)
322
+ return {"success": True, "path": output_path, "image_base64": b64}
323
+
324
+
325
+ @mcp.tool()
326
+ async def run_scene(scene_path: str = "", project_path: str = "", timeout_seconds: int = 5) -> dict:
327
+ """Run a Godot scene briefly to verify it loads without errors.
328
+
329
+ Args:
330
+ scene_path: res:// path. Empty = main scene from project.godot.
331
+ timeout_seconds: How long to let it run before stopping (default 5).
332
+ """
333
+ import asyncio
334
+ root = project_path or str(_root())
335
+ cmd = [_godot_path(), "--headless"]
336
+ if scene_path:
337
+ cmd.extend(["--scene", scene_path])
338
+ cmd.append("--quit-after")
339
+ cmd.append(str(timeout_seconds))
340
+
341
+ proc = await asyncio.create_subprocess_exec(
342
+ *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, cwd=root)
343
+ try:
344
+ stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout_seconds + 10)
345
+ output = stdout.decode(errors="replace") + "\n" + stderr.decode(errors="replace")
346
+ except asyncio.TimeoutError:
347
+ proc.kill()
348
+ output = "Timed out"
349
+
350
+ from godot_agent.runtime.error_loop import parse_godot_output
351
+ errors = parse_godot_output(output)
352
+ return {
353
+ "loaded": not any(e.level == "ERROR" for e in errors),
354
+ "errors": [{"file": e.file, "line": e.line, "message": e.message} for e in errors if e.level == "ERROR"],
355
+ "exit_code": proc.returncode,
356
+ }
357
+
358
+
359
+ # ═══════════════════════════════════════════════════════════════════
360
+ # SPRITE GENERATION
361
+ # ═══════════════════════════════════════════════════════════════════
362
+
363
+ @mcp.tool()
364
+ async def generate_sprite(
365
+ subject: str, output_path: str, size: int = 32, style: str = "pixel_modern",
366
+ facing: str = "front", category: str = "character", extra: str = "",
367
+ ) -> dict:
368
+ """Generate pixel art sprite with AI. Pipeline: generate → chroma key → crop → resize.
369
+
370
+ Args:
371
+ subject: What to draw (e.g., "fire mage", "health potion", "slime enemy")
372
+ output_path: Where to save PNG
373
+ size: Pixel size (8/16/24/32/48/64/128)
374
+ style: pixel_8bit, pixel_16bit, pixel_modern, chibi, minimal
375
+ facing: front, left, right, back
376
+ category: character, enemy, boss, item, projectile, ui_icon, effect
377
+ """
378
+ from godot_agent.tools.image_gen import GenerateSpriteTool
379
+ tool = GenerateSpriteTool()
380
+ result = await tool.execute(tool.Input(
381
+ subject=subject, output_path=output_path, size=size,
382
+ style=style, facing=facing, category=category, extra=extra))
383
+ if result.error:
384
+ return {"error": result.error}
385
+ return {"path": result.output.path, "width": result.output.width,
386
+ "height": result.output.height, "prompt_used": result.output.prompt_used}
387
+
388
+
389
+ def run_mcp_server(project_path: str | None = None) -> None:
390
+ """Entry point: god-code mcp [--project PATH]"""
391
+ if project_path:
392
+ set_mcp_project_root(Path(project_path))
393
+ mcp.run(transport="stdio")
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "god-code"
3
- version = "0.5.4"
3
+ version = "0.6.0"
4
4
  description = "AI coding agent specialized for Godot game development"
5
5
  requires-python = ">=3.9"
6
6
  license = {text = "GPL-3.0-or-later"}
@@ -16,6 +16,9 @@ dependencies = [
16
16
  ]
17
17
 
18
18
  [project.optional-dependencies]
19
+ mcp = [
20
+ "mcp[cli]>=1.12",
21
+ ]
19
22
  dev = [
20
23
  "pytest>=8.2",
21
24
  "pytest-asyncio>=0.23",
@@ -26,8 +29,8 @@ dev = [
26
29
  god-code = "godot_agent.cli:main"
27
30
 
28
31
  [project.urls]
29
- Homepage = "https://github.com/chuisiufai/god-code"
30
- Repository = "https://github.com/chuisiufai/god-code"
32
+ Homepage = "https://github.com/888wing/god-code"
33
+ Repository = "https://github.com/888wing/god-code"
31
34
 
32
35
  [tool.hatch.build.targets.wheel]
33
36
  packages = ["godot_agent"]