diff --git a/.mcp.json b/.mcp.json index 89193fc..6aa9c05 100644 --- a/.mcp.json +++ b/.mcp.json @@ -2,7 +2,11 @@ "mcpServers": { "ue-docs": { "command": "python", - "args": ["ue_mcp_server.py"] + "args": ["ue_mcp_server.py"], + "env": { + "UE_ENGINE_ROOT": "${UE_ENGINE_ROOT}", + "UE_DOCS_PATH": "${UE_DOCS_PATH}" + } } } } diff --git a/CLAUDE.md b/CLAUDE.md index e334cd6..8edd8c6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,9 +38,11 @@ The last argument is always the output directory. All preceding arguments are in | `get_class_overview(class_name)` | Compact view: description + inherits + property/function name lists | | `get_member(class_name, member_name)` | Full doc for one function or property (all overloads) | | `get_file(relative_path)` | Full `.md` file — use when you need delegates, enums, or multiple classes | -| `search_source(pattern, path_hint)` | Grep UE source `.h` files via `$UE_ENGINE_ROOT` | +| `search_source(pattern, path_hint)` | Grep UE `.h` files in `Engine/Source/` and `Engine/Plugins/`; `path_hint` applied under both; returns up to 40 lines total | -Requires `pip install mcp`. Reads `$UE_DOCS_PATH`; `search_source` also requires `$UE_ENGINE_ROOT`. +Requires `pip install mcp`. Reads `$UE_DOCS_PATH`; `search_source` also requires `$UE_ENGINE_ROOT` (must point to the `Engine/` directory, e.g. `/path/to/UnrealEngine/Engine`). + +`.mcp.json` passes both vars via `${VAR}` interpolation — no paths hardcoded. Typical query flow: `search_types` → `get_class_overview` → `get_member` (~3 calls, ~15 lines vs ~100 lines for a full file read). diff --git a/README.md b/README.md index 13aaa52..0dd4ef2 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,10 @@ pip install mcp ```bash export UE_DOCS_PATH=/path/to/your/generated/docs -export UE_ENGINE_ROOT=/path/to/UnrealEngine # optional, for search_source +export UE_ENGINE_ROOT=/path/to/UnrealEngine/Engine # optional, for search_source ``` -The `.mcp.json` at the repo root registers the server automatically when you open the project in Claude Code. +The `.mcp.json` at the repo root registers the server automatically when you open the project in Claude Code. It reads `UE_DOCS_PATH` and `UE_ENGINE_ROOT` from your environment via `${VAR}` interpolation — no paths are hardcoded. ### Available tools @@ -47,7 +47,7 @@ The `.mcp.json` at the repo root registers the server automatically when you ope | `get_class_overview(class_name)` | Description + base classes + property/function name lists | | `get_member(class_name, member_name)` | Full doc for one function or property (all overloads) | | `get_file(relative_path)` | Full `.md` file — for delegates, enums, or multiple classes | -| `search_source(pattern, path_hint)` | Grep UE source `.h` files via `$UE_ENGINE_ROOT` | +| `search_source(pattern, path_hint)` | Grep UE `.h` files in `Engine/Source/` and `Engine/Plugins/`; returns up to 40 lines | ### Typical query flow diff --git a/ue_mcp_server.py b/ue_mcp_server.py index 92c334e..6f8f766 100644 --- a/ue_mcp_server.py +++ b/ue_mcp_server.py @@ -207,15 +207,26 @@ def get_file(relative_path: str) -> str: def search_source(pattern: str, path_hint: str = "") -> str: """Grep UE source .h files for pattern. Requires $UE_ENGINE_ROOT. path_hint: optional subdirectory under Engine/Source/ (e.g. 'Runtime/Engine'). + Searches both Engine/Source/ and Engine/Plugins/. Returns up to 40 matching lines with file:line context.""" - root = _engine_root() / "Engine" / "Source" - if path_hint: - root = root / path_hint - result = subprocess.run( - ["grep", "-rn", "--include=*.h", "-m", "40", pattern, str(root)], - capture_output=True, text=True, timeout=15 + engine = _engine_root() + candidates = [engine / "Source", engine / "Plugins"] + search_dirs = [d / path_hint if path_hint else d for d in candidates] + search_dirs = [d for d in search_dirs if d.exists()] + if not search_dirs: + return "(no matches)" + grep = subprocess.Popen( + ["grep", "-rn", "--include=*.h", pattern, *map(str, search_dirs)], + stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True ) - return result.stdout or "(no matches)" + lines = [] + for line in grep.stdout: + lines.append(line) + if len(lines) == 40: + grep.kill() + break + grep.wait() + return "".join(lines) or "(no matches)" if __name__ == "__main__":