diff --git a/README.md b/README.md
index b90ce1e..2d5f2b9 100644
--- a/README.md
+++ b/README.md
@@ -255,3 +255,5 @@ pytest tests/ --cov=godotiq -v
 ## License
 
 MIT
+
+mcp-name: io.github.salvo10f/godotiq
diff --git a/godot-addon/addons/godotiq/godotiq_logger.gd b/godot-addon/addons/godotiq/godotiq_logger.gd
index dfdd675..b1fb629 100644
--- a/godot-addon/addons/godotiq/godotiq_logger.gd
+++ b/godot-addon/addons/godotiq/godotiq_logger.gd
@@ -10,7 +10,7 @@ func _init(server: Node) -> void:
 
 
 func _log_error(function: String, file: String, line: int, code: String, rationale: String, editor_notify: bool, error_type: int, script_backtraces: Array) -> void:
-	if not (file.ends_with(".gd") or file.ends_with(".tscn") or error_type == ERR_SCRIPT):
+	if not (file.ends_with(".gd") or file.ends_with(".tscn")):
 		return
 	var entry := {
 		"file": file,
diff --git a/godot-addon/addons/godotiq/godotiq_plugin.gd b/godot-addon/addons/godotiq/godotiq_plugin.gd
index 300d9d8..de95d67 100644
--- a/godot-addon/addons/godotiq/godotiq_plugin.gd
+++ b/godot-addon/addons/godotiq/godotiq_plugin.gd
@@ -8,6 +8,7 @@ const RUNTIME_PATH := "res://addons/godotiq/godotiq_runtime.gd"
 
 var _server  # WebSocket server node (godotiq_server.gd)
 var _debugger  # EditorDebuggerPlugin (godotiq_debugger.gd)
+var _bottom_panel: Control
 
 
 func _enter_tree() -> void:
@@ -27,18 +28,53 @@ func _enter_tree() -> void:
 	# 4. Register runtime autoload singleton
 	add_autoload_singleton(AUTOLOAD_NAME, RUNTIME_PATH)
 
+	# 5. Add bottom panel
+	_bottom_panel = _create_bottom_panel()
+	add_control_to_bottom_panel(_bottom_panel, "GodotIQ")
+
+	# 6. Give server a reference to update the status label
+	_server.status_label = _bottom_panel.get_node("StatusLabel")
+
 
 func _exit_tree() -> void:
-	# 1. Remove autoload singleton
+	# 1. Remove bottom panel
+	if _bottom_panel:
+		remove_control_from_bottom_panel(_bottom_panel)
+		_bottom_panel.queue_free()
+		_bottom_panel = null
+
+	# 2. Remove autoload singleton
 	remove_autoload_singleton(AUTOLOAD_NAME)
 
-	# 2. Remove and null debugger plugin
+	# 3. Remove and null debugger plugin
 	if _debugger:
 		remove_debugger_plugin(_debugger)
 		_debugger = null
 
-	# 3. Free and null server node
+	# 4. Free and null server node
 	if _server:
 		_server.queue_free()
 		_server = null
 
+
+func _create_bottom_panel() -> Control:
+	var vbox := VBoxContainer.new()
+	vbox.custom_minimum_size = Vector2(0, 80)
+
+	var title := Label.new()
+	title.text = "GodotIQ v0.1.0"
+	title.add_theme_font_size_override("font_size", 16)
+	vbox.add_child(title)
+
+	var status_label := Label.new()
+	status_label.name = "StatusLabel"
+	status_label.text = "Server: Starting..."
+	vbox.add_child(status_label)
+
+	var tools_label := Label.new()
+	tools_label.name = "ToolsLabel"
+	tools_label.text = "Tools: 35 registered"
+	vbox.add_child(tools_label)
+
+	return vbox
+
diff --git a/godot-addon/addons/godotiq/godotiq_runtime.gd b/godot-addon/addons/godotiq/godotiq_runtime.gd
index f846b4b..34c1f6b 100644
--- a/godot-addon/addons/godotiq/godotiq_runtime.gd
+++ b/godot-addon/addons/godotiq/godotiq_runtime.gd
@@ -482,7 +482,7 @@ func _execute_code(params: Dictionary) -> void:
 		EngineDebugger.send_message("godotiq:exec_result", [JSON.stringify({
 			"status": "COMPILE_ERROR",
 			"result": "",
-			"error": "Compilation failed (error %d)" % err,
+			"error": "Compilation failed (error %d: %s)" % [err, error_string(err)],
 		})])
 		return
 
diff --git a/godot-addon/addons/godotiq/godotiq_server.gd b/godot-addon/addons/godotiq/godotiq_server.gd
index 0e1978b..e8344f9 100644
--- a/godot-addon/addons/godotiq/godotiq_server.gd
+++ b/godot-addon/addons/godotiq/godotiq_server.gd
@@ -1129,16 +1129,26 @@ func _handle_exec_editor(peer_id: int, id: String, params: Dictionary) -> void:
 			})
 			return
 
-	# Compile the script
+	# Compile the script — capture errors via Logger if available
+	if _has_logger:
+		_clear_script_errors()
 	var script := GDScript.new()
 	script.source_code = "@tool\nextends Node\n\n" + code
 	var err: int = script.reload()
 	if err != OK:
-		send_response(peer_id, id, {
+		var resp := {
 			"status": "COMPILE_ERROR",
 			"result": "",
-			"error": "Compilation failed (error %d). Check GDScript syntax." % err,
-		})
+			"error": "Compilation failed (error %d: %s). Check GDScript syntax." % [err, error_string(err)],
+		}
+		if _has_logger:
+			var captured := _get_script_errors()
+			if not captured.is_empty():
+				var details: Array = []
+				for entry in captured:
+					details.append({"line": entry.get("line", 0), "message": entry.get("message", "")})
+				resp["error_detail"] = details
+		send_response(peer_id, id, resp)
 		return
 
 	# Instantiate and execute
diff --git a/godot-addon/addons/godotiq/plugin.cfg b/godot-addon/addons/godotiq/plugin.cfg
index beef210..3159887 100644
--- a/godot-addon/addons/godotiq/plugin.cfg
+++ b/godot-addon/addons/godotiq/plugin.cfg
@@ -2,6 +2,6 @@
 
 name="GodotIQ"
 description="AI-assisted Godot development via MCP bridge"
-author="GodotIQ"
-version="2.0.0"
+author="Nash Farruggio"
+version="0.1.0"
 script="godotiq_plugin.gd"
diff --git a/pyproject.toml b/pyproject.toml
index 41fad04..bf3d93a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,12 +4,12 @@ build-backend = "hatchling.build"
 
 [project]
 name = "godotiq"
-version = "0.1.0"
+version = "0.1.3"
 description = "The definitive MCP for AI-assisted Godot development"
 readme = "README.md"
 license = "MIT"
 requires-python = ">=3.10"
-dependencies = ["mcp>=1.0.0", "websockets>=14.0"]
+dependencies = ["mcp>=1.0.0", "websockets>=14.0", "httpx>=0.27"]
 authors = [
     { name = "Salvatore Farruggio", email = "coolvolve.info@gmail.com" },
 ]
diff --git a/src/godotiq/parsers/tscn_parser.py b/src/godotiq/parsers/tscn_parser.py
index ab0c8e5..7a75bd3 100644
--- a/src/godotiq/parsers/tscn_parser.py
+++ b/src/godotiq/parsers/tscn_parser.py
@@ -75,6 +75,7 @@ class TscnNode:
     # Scene instancing
     instance: str | None = None
     unique_id: int | None = None
+    is_instance_override: bool = False
     # Groups and script
     groups: list[str] = field(default_factory=list)
     script: str | None = None
@@ -513,6 +514,12 @@ def _build_tree(scene: TscnScene) -> None:
             full_path = f"{root_name}/{node.parent}/{node.name}"
         path_map[full_path] = node
 
+    # Collect names of instance nodes (nodes with instance=ExtResource(...))
+    instance_names: set[str] = set()
+    for node in scene.nodes:
+        if node.instance is not None:
+            instance_names.add(node.name)
+
     # Link children to parents
     for node in scene.nodes:
         if node is root:
@@ -526,7 +533,19 @@ def _build_tree(scene: TscnScene) -> None:
         if parent_node is not None:
             parent_node.children.append(node)
         else:
-            logger.warning(
-                "Parent not found for node '%s' (parent path: '%s')",
-                node.name, node.parent,
-            )
+            # Check if this is a property override on an instanced scene.
+            # In .tscn files, nodes inside instanced scenes appear with parent
+            # paths relative to the instance root (e.g. parent="Geometry" when
+            # the instance node "Map" contains a child "Geometry"). The first
+            # segment of the parent path (or node.parent itself for direct
+            # children) corresponds to the instance node name.
+            first_segment = node.parent.split("/")[0] if node.parent != "." else ""
+            instance_parent = path_map.get(f"{root_name}/{first_segment}")
+            if first_segment in instance_names and instance_parent is not None:
+                node.is_instance_override = True
+                instance_parent.children.append(node)
+            else:
+                logger.warning(
+                    "Parent not found for node '%s' (parent path: '%s')",
+                    node.name, node.parent,
+                )
diff --git a/src/godotiq/server.py b/src/godotiq/server.py
index 3c60721..c0b8b7b 100644
--- a/src/godotiq/server.py
+++ b/src/godotiq/server.py
@@ -71,6 +71,45 @@ async def godot_development_prompt() -> str:
     return (PROMPT_DIR / "godot_development.md").read_text(encoding="utf-8")
 
 
+_cached_update_version: str | None = None
+_update_checked: bool = False
+
+
+def _is_newer_version(remote: str, local: str) -> bool:
+    """Return True if remote version is strictly newer than local."""
+    r = [int(x) for x in remote.split(".")]
+    l = [int(x) for x in local.split(".")]  # noqa: E741
+    max_len = max(len(r), len(l))
+    for i in range(max_len):
+        rv = r[i] if i < len(r) else 0
+        lv = l[i] if i < len(l) else 0
+        if rv > lv:
+            return True
+        if rv < lv:
+            return False
+    return False
+
+
+async def _check_pypi_update() -> str | None:
+    """Check PyPI for a newer version. Returns version string or None."""
+    global _cached_update_version, _update_checked
+    if _update_checked:
+        return _cached_update_version
+    _update_checked = True
+    try:
+        import httpx
+
+        async with httpx.AsyncClient(timeout=10.0) as client:
+            resp = await client.get("https://pypi.org/pypi/godotiq/json")
+            if resp.status_code == 200:
+                remote = resp.json()["info"]["version"]
+                if _is_newer_version(remote, __version__):
+                    _cached_update_version = remote
+    except Exception:
+        pass
+    return _cached_update_version
+
+
 @mcp.tool()
 async def godotiq_ping(detail: str = "normal") -> dict:
     """Health check tool. Returns server status and version.
@@ -78,7 +117,17 @@ async def godotiq_ping(detail: str = "normal") -> dict:
     Args:
         detail: Output verbosity — "brief" (summary), "normal" (default), or "full" (everything).
     """
-    return {"status": "ok", "version": __version__}
+    from godotiq.license import is_pro
+
+    result = {
+        "status": "ok",
+        "version": __version__,
+        "license": "pro" if is_pro() else "community",
+    }
+    update = await _check_pypi_update()
+    if update:
+        result["update_available"] = update
+    return result
 
 
 # Tool imports — each module registers tools on the mcp instance
diff --git a/src/godotiq/tools/animation/__init__.py b/src/godotiq/tools/animation/__init__.py
index f341565..46ffcc2 100644
--- a/src/godotiq/tools/animation/__init__.py
+++ b/src/godotiq/tools/animation/__init__.py
@@ -12,6 +12,7 @@ from collections import defaultdict
 
 from mcp.server.fastmcp import Context
 
+from godotiq.license import gate_pro
 from godotiq.parsers.tscn_parser import TscnScene
 from godotiq.session import GodotIQSession
 
@@ -586,7 +587,7 @@ def _build_animation_info(
         },
     }
 
-    return result
+    return apply_output_limit(result, detail, list_keys=["animation_players"])
 
 
 def _filter_animations_by_detail(animations: list[dict], detail: str) -> list[dict]:
@@ -677,6 +678,7 @@ def _determine_sources(summary: dict) -> list[str]:
 # MCP Tool: godotiq_animation_info
 # ---------------------------------------------------------------------------
 
+from godotiq.tools.output_limit import apply_output_limit
 from godotiq.server import mcp  # noqa: E402
 
 
@@ -716,12 +718,25 @@ _SEVERITY_ORDER = {"critical": 0, "warning": 1, "info": 2}
 _MAX_ISSUES = 50
 _LOOP_SUSPECT_KEYWORDS = {"idle", "walk", "run", "swim", "fly", "breathe"}
 _LOOP_SUSPECT_MAX_LENGTH = 3.0
+_RESET_MAX_LENGTH = 0.01
+
+
+def _is_reset_animation(anim: dict) -> bool:
+    """Return True if *anim* is a Godot auto-generated RESET animation."""
+    name: str = anim.get("name", "")
+    length: float = float(anim.get("length", 1.0))
+    if name == "RESET":
+        return True
+    if length <= _RESET_MAX_LENGTH:
+        return True
+    return False
 
 
 def _build_animation_audit(
     session: GodotIQSession,
     scope: str = "all",
     scene_filter: str | None = None,
+    detail: str = "normal",
 ) -> dict:
     """Find animation problems across the project or in a specific scene.
 
@@ -824,7 +839,7 @@ def _build_animation_audit(
         has_references = bool(code_anims) or bool(trees)
         if has_references:
             for anim in animations:
-                if anim["name"] not in all_referenced:
+                if anim["name"] not in all_referenced and not _is_reset_animation(anim):
                     issues.append({
                         "type": "orphan_animation",
                         "severity": "warning",
@@ -969,6 +984,19 @@ def _build_animation_audit(
         if sev in by_severity:
             by_severity[sev] += 1
 
+    detail = detail.lower() if detail else "normal"
+
+    if detail == "brief":
+        result = {
+            "scope": scope,
+            "scenes_scanned": scenes_scanned,
+            "animated_scenes": animated_scenes,
+            "total_animations": total_animations,
+            "total_issues": len(issues),
+            "by_severity": by_severity,
+        }
+        return apply_output_limit(result, detail)
+
     result: dict = {
         "scope": scope,
         "scenes_scanned": scenes_scanned,
@@ -981,7 +1009,7 @@ def _build_animation_audit(
     if truncated:
         result["truncated"] = True
 
-    return result
+    return apply_output_limit(result, detail, list_keys=["issues"])
 
 
 # ---------------------------------------------------------------------------
@@ -1001,6 +1029,7 @@ def _build_animation_audit(
 async def godotiq_animation_audit(
     scope: str = "all",
     scene: str | None = None,
+    detail: str = "normal",
     ctx: Context = None,
 ) -> dict:
     """Find animation problems: broken tracks, missing transitions, wrong loop settings, unreferenced animations. Run before shipping to catch animation bugs.
@@ -1008,8 +1037,9 @@ async def godotiq_animation_audit(
     Args:
         scope: "all" (scan entire project) or "scene" (single scene).
         scene: Path to .tscn file when scope="scene".
+        detail: "brief" (severity counts only), "normal" (+ issue list), "full".
     """
     session = ctx.request_context.lifespan_context["session"]
     if session is None:
         return {"error": "No Godot project loaded"}
-    return _build_animation_audit(session, scope, scene)
+    return gate_pro("godotiq_animation_audit", _build_animation_audit(session, scope, scene, detail=detail))
diff --git a/src/godotiq/tools/assets/__init__.py b/src/godotiq/tools/assets/__init__.py
index 0573483..f78eb26 100644
--- a/src/godotiq/tools/assets/__init__.py
+++ b/src/godotiq/tools/assets/__init__.py
@@ -8,6 +8,7 @@ from pathlib import PurePosixPath
 
 from mcp.server.fastmcp import Context
 
+from godotiq.license import gate_pro
 from godotiq.server import mcp
 from godotiq.session import GodotIQSession
 from godotiq.tools.output_limit import apply_output_limit
@@ -252,7 +253,7 @@ async def godotiq_asset_registry(
     session = ctx.request_context.lifespan_context["session"]
     if session is None:
         return {"error": "No Godot project loaded"}
-    return _build_asset_registry(session, category, check_usage, detail=detail, path_filter=path_filter, max_results=max_results)
+    return gate_pro("godotiq_asset_registry", _build_asset_registry(session, category, check_usage, detail=detail, path_filter=path_filter, max_results=max_results))
 
 
 # ---------------------------------------------------------------------------
@@ -658,4 +659,4 @@ async def godotiq_suggest_scale(
     session = ctx.request_context.lifespan_context["session"]
     if session is None:
         return {"error": "No Godot project loaded"}
-    return _build_suggest_scale(session, scene, model, near_node, intended_use, detail=detail)
+    return gate_pro("godotiq_suggest_scale", _build_suggest_scale(session, scene, model, near_node, intended_use, detail=detail))
diff --git a/src/godotiq/tools/bridge/exec_code.py b/src/godotiq/tools/bridge/exec_code.py
index 09f5734..f34c9ba 100644
--- a/src/godotiq/tools/bridge/exec_code.py
+++ b/src/godotiq/tools/bridge/exec_code.py
@@ -59,12 +59,35 @@ async def exec_code(
     bridge = await get_bridge()
 
     if context == "editor":
-        return await bridge.request("exec_editor", params={
+        result = await bridge.request("exec_editor", params={
             "code": code,
             "timeout_ms": timeout_ms,
         }, timeout=10.0)
     else:
-        return await bridge.request("exec", params={
+        result = await bridge.request("exec", params={
             "code": code,
             "timeout_ms": timeout_ms,
         }, timeout=10.0)
+
+    return _format_error_detail(result)
+
+
+def _format_error_detail(result: dict) -> dict:
+    """Format structured error_detail from bridge COMPILE_ERROR responses.
+
+    If the bridge returns an ``error_detail`` list of
+    ``{"line": int, "message": str}`` entries, join them into a
+    human-readable string.  Empty or missing lists are removed so
+    callers only see the key when there is real data.
+    """
+    raw = result.get("error_detail")
+    if not isinstance(raw, list) or not raw:
+        result.pop("error_detail", None)
+        return result
+
+    parts = [f"line {e['line']}: {e['message']}" for e in raw if "line" in e and "message" in e]
+    if parts:
+        result["error_detail"] = "; ".join(parts)
+    else:
+        result.pop("error_detail", None)
+    return result
diff --git a/src/godotiq/tools/code/__init__.py b/src/godotiq/tools/code/__init__.py
index 6f0b22d..38dc79d 100644
--- a/src/godotiq/tools/code/__init__.py
+++ b/src/godotiq/tools/code/__init__.py
@@ -9,6 +9,7 @@ import re
 
 from mcp.server.fastmcp import Context
 
+from godotiq.license import gate_pro
 from godotiq.parsers.gd_parser import GdScript
 from godotiq.server import mcp
 from godotiq.session import GodotIQSession
@@ -303,7 +304,7 @@ async def godotiq_dependency_graph(
     session = ctx.request_context.lifespan_context["session"]
     if session is None:
         return {"error": "No Godot project loaded"}
-    return _build_dependency_graph(session, file, depth, detail=detail)
+    return gate_pro("godotiq_dependency_graph", _build_dependency_graph(session, file, depth, detail=detail))
 
 
 # ---------------------------------------------------------------------------
@@ -500,7 +501,7 @@ async def godotiq_signal_map(
     session = ctx.request_context.lifespan_context["session"]
     if session is None:
         return {"error": "No Godot project loaded"}
-    return _build_signal_map(session, scope, find, detail=detail)
+    return gate_pro("godotiq_signal_map", _build_signal_map(session, scope, find, detail=detail))
 
 
 # ---------------------------------------------------------------------------
@@ -730,7 +731,7 @@ async def godotiq_impact_check(
     session = ctx.request_context.lifespan_context["session"]
     if session is None:
         return {"error": "No Godot project loaded"}
-    return _build_impact_check(session, file, action, target, change_description=change_description, detail=detail)
+    return gate_pro("godotiq_impact_check", _build_impact_check(session, file, action, target, change_description=change_description, detail=detail))
 
 
 # ---------------------------------------------------------------------------
@@ -919,4 +920,4 @@ async def godotiq_validate(
     session = ctx.request_context.lifespan_context["session"]
     if session is None:
         return {"error": "No Godot project loaded"}
-    return _build_validate(session, target, detail=detail)
+    return gate_pro("godotiq_validate", _build_validate(session, target, detail=detail))
diff --git a/src/godotiq/tools/flow/__init__.py b/src/godotiq/tools/flow/__init__.py
index be76bd7..d876481 100644
--- a/src/godotiq/tools/flow/__init__.py
+++ b/src/godotiq/tools/flow/__init__.py
@@ -8,9 +8,11 @@ import re
 
 from mcp.server.fastmcp import Context
 
+from godotiq.license import gate_pro
 from godotiq.parsers.gd_parser import GdFunction, GdScript
 from godotiq.server import mcp
 from godotiq.session import GodotIQSession
+from godotiq.tools.output_limit import apply_output_limit
 
 # Guard condition patterns (the "if" part only — return is checked on next line)
 _FAIL_IF_NOT_RE = re.compile(r"if\s+not\s+(\w+)\s*:")
@@ -164,6 +166,38 @@ def _detect_failure_points(
     return failures
 
 
+def _find_local_calls(
+    script: GdScript,
+    func: GdFunction,
+    file_path: str,
+    project_root: Path,
+    file_lines_cache: dict[str, list[str]],
+) -> list[str]:
+    """Find calls to other functions defined in the same script."""
+    all_func_names = {f.name for f in script.functions}
+    lines = _get_file_lines(file_path, project_root, file_lines_cache)
+    if not lines:
+        return []
+
+    start = func.line - 1  # 0-indexed
+    end_line = func.end_line if func.end_line >= func.line else func.line
+    end = min(end_line, len(lines))
+
+    call_pattern = re.compile(r"(?:self\.)?(\w+)\s*\(")
+    found: set[str] = set()
+
+    for i in range(start, end):
+        stripped = lines[i].strip()
+        if stripped.startswith("#"):
+            continue
+        for match in call_pattern.finditer(lines[i]):
+            called_name = match.group(1)
+            if called_name in all_func_names and called_name != func.name:
+                found.add(called_name)
+
+    return list(found)
+
+
 def _trace_from_function(
     file_path: str,
     function_name: str,
@@ -269,6 +303,28 @@ def _trace_from_function(
                 all_steps.extend(sub_steps)
                 all_failures.extend(sub_failures)
 
+    # Recurse on local (intra-file) function calls
+    local_calls = _find_local_calls(
+        script, func, file_path, project_root, file_lines_cache,
+    )
+    if local_calls:
+        step.calls.extend(f"self.{name}" for name in local_calls)
+        # Rebuild action description with local calls included
+        parts_desc = []
+        if step.emits:
+            parts_desc.append("Emits: " + ", ".join(step.emits))
+        if step.calls:
+            parts_desc.append("Calls: " + ", ".join(step.calls))
+        step.action = "; ".join(parts_desc) if parts_desc else step.action
+
+        for called_func_name in local_calls:
+            sub_steps, sub_failures = _trace_from_function(
+                file_path, called_func_name, scripts, listeners, autoloads,
+                visited, depth + 1, max_depth, file_lines_cache, project_root,
+            )
+            all_steps.extend(sub_steps)
+            all_failures.extend(sub_failures)
+
     return all_steps, all_failures
 
 
@@ -322,13 +378,15 @@ async def _build_trace_flow(
             "total_steps": len(all_steps),
         }
 
-    return {
+    result = {
         "flow": flow_dicts,
         "failure_points": failure_dicts,
         "files_involved": files_involved,
         "total_steps": len(all_steps),
     }
 
+    return apply_output_limit(result, detail, list_keys=["flow", "failure_points"])
+
 
 @mcp.tool(
     name="godotiq_trace_flow",
@@ -355,4 +413,4 @@ async def godotiq_trace_flow(
     session = ctx.request_context.lifespan_context["session"]
     if session is None:
         return {"error": "No Godot project loaded"}
-    return await _build_trace_flow(session, trigger, depth, detail)
+    return gate_pro("godotiq_trace_flow", await _build_trace_flow(session, trigger, depth, detail))
diff --git a/src/godotiq/tools/memory/__init__.py b/src/godotiq/tools/memory/__init__.py
index 97a7af9..d2369e5 100644
--- a/src/godotiq/tools/memory/__init__.py
+++ b/src/godotiq/tools/memory/__init__.py
@@ -11,8 +11,10 @@ from mcp.server.fastmcp import Context
 
 from godotiq.parsers.gd_parser import GdScript
 from godotiq.parsers.tscn_parser import TscnScene
+from godotiq.license import gate_pro
 from godotiq.server import mcp
 from godotiq.session import GodotIQSession
+from godotiq.tools.output_limit import apply_output_limit
 
 
 def _read_main_scene(session: GodotIQSession) -> str:
@@ -260,7 +262,7 @@ def _build_project_summary(session: GodotIQSession, detail: str = "normal") -> d
     if detail == "full":
         result.update(_build_full(session))
 
-    return result
+    return apply_output_limit(result, detail)
 
 
 def _build_gd_context(session: GodotIQSession, res_path: str, gd: GdScript) -> dict:
@@ -464,7 +466,7 @@ def _build_file_context(
                 "is_autoload": full["is_autoload"],
                 "public_api": full["public_api"],
             }
-        return full
+        return apply_output_limit(full, detail)
 
     # Try as scene
     scene = session.get_scene(res_path)
@@ -478,7 +480,7 @@ def _build_file_context(
                 "root_type": full["root_type"],
                 "scripts_used": full["scripts_used"],
             }
-        return full
+        return apply_output_limit(full, detail)
 
     return {
         "error": "Unsupported file type. Only .gd and .tscn files are supported.",
@@ -507,7 +509,7 @@ async def godotiq_project_summary(
     session = ctx.request_context.lifespan_context["session"]
     if session is None:
         return {"error": "No Godot project loaded. Set GODOTIQ_PROJECT_ROOT or run from project directory."}
-    return _build_project_summary(session, detail)
+    return gate_pro("godotiq_project_summary", _build_project_summary(session, detail))
 
 
 @mcp.tool(
@@ -533,4 +535,4 @@ async def godotiq_file_context(
     session = ctx.request_context.lifespan_context["session"]
     if session is None:
         return {"error": "No Godot project loaded"}
-    return _build_file_context(session, file, detail=detail)
+    return gate_pro("godotiq_file_context", _build_file_context(session, file, detail=detail))
diff --git a/src/godotiq/tools/spatial/__init__.py b/src/godotiq/tools/spatial/__init__.py
index 0fd7d88..1fbe1c0 100644
--- a/src/godotiq/tools/spatial/__init__.py
+++ b/src/godotiq/tools/spatial/__init__.py
@@ -9,6 +9,7 @@ import math
 
 from mcp.server.fastmcp import Context
 
+from godotiq.license import gate_pro
 from godotiq.server import mcp
 from godotiq.session import GodotIQSession
 from godotiq.parsers.scene_resolver import (
@@ -401,7 +402,7 @@ async def godotiq_scene_map(
     session = ctx.request_context.lifespan_context["session"]
     if session is None:
         return {"error": "No Godot project loaded"}
-    return _build_scene_map(session, scene, focus, radius, include, detail)
+    return gate_pro("godotiq_scene_map", _build_scene_map(session, scene, focus, radius, include, detail))
 
 
 # ---------------------------------------------------------------------------
@@ -767,7 +768,7 @@ async def godotiq_spatial_audit(
     session = ctx.request_context.lifespan_context["session"]
     if session is None:
         return {"error": "No Godot project loaded"}
-    return _build_spatial_audit(session, scene, checks, min_severity=min_severity, detail=detail)
+    return gate_pro("godotiq_spatial_audit", _build_spatial_audit(session, scene, checks, min_severity=min_severity, detail=detail))
 
 
 # ---------------------------------------------------------------------------
@@ -864,4 +865,4 @@ async def godotiq_placement(
         result.pop("excluded_zones", None)
         result.pop("scale_reasoning", None)
 
-    return apply_output_limit(result, detail, list_keys=["suggestions", "excluded_zones"])
+    return gate_pro("godotiq_placement", apply_output_limit(result, detail, list_keys=["suggestions", "excluded_zones"]))
diff --git a/tests/test_bridge/test_exec_code.py b/tests/test_bridge/test_exec_code.py
index e019987..02291d7 100644
--- a/tests/test_bridge/test_exec_code.py
+++ b/tests/test_bridge/test_exec_code.py
@@ -84,3 +84,86 @@ class TestExecCodeTool:
             params = call_args[1]["params"]
             assert params["code"] == "func run():\n\treturn 1"
             assert params["timeout_ms"] == 7000
+
+
+class TestExecErrorDetail:
+    """Tests for error_detail extraction from COMPILE_ERROR responses (Section 05)."""
+
+    @pytest.mark.asyncio
+    async def test_compile_error_with_error_detail_from_bridge(self):
+        """When bridge returns error_detail list, Python side formats it."""
+        mock_bridge = AsyncMock()
+        mock_bridge.request = AsyncMock(return_value={
+            "status": "COMPILE_ERROR",
+            "result": "",
+            "error": "Compilation failed (error 43: Parse Error)",
+            "error_detail": [
+                {"line": 5, "message": "Expected end of statement after expression"},
+            ],
+        })
+        with patch("godotiq.tools.bridge.exec_code.get_bridge", return_value=mock_bridge):
+            result = await exec_code(code="func run():\n\tvar x = :")
+            assert result["status"] == "COMPILE_ERROR"
+            assert "error_detail" in result
+            assert "line 5" in result["error_detail"]
+            assert "Expected end of statement" in result["error_detail"]
+
+    @pytest.mark.asyncio
+    async def test_compile_error_multiple_errors(self):
+        """Multiple error entries are joined into a single string."""
+        mock_bridge = AsyncMock()
+        mock_bridge.request = AsyncMock(return_value={
+            "status": "COMPILE_ERROR",
+            "result": "",
+            "error": "Compilation failed (error 43: Parse Error)",
+            "error_detail": [
+                {"line": 3, "message": "Unexpected token"},
+                {"line": 7, "message": "Invalid assignment"},
+            ],
+        })
+        with patch("godotiq.tools.bridge.exec_code.get_bridge", return_value=mock_bridge):
+            result = await exec_code(code="func run():\n\tvar x = :")
+            assert "line 3" in result["error_detail"]
+            assert "line 7" in result["error_detail"]
+
+    @pytest.mark.asyncio
+    async def test_compile_error_no_error_detail(self):
+        """When bridge returns no error_detail, response has no error_detail key."""
+        mock_bridge = AsyncMock()
+        mock_bridge.request = AsyncMock(return_value={
+            "status": "COMPILE_ERROR",
+            "result": "",
+            "error": "Compilation failed (error 43)",
+        })
+        with patch("godotiq.tools.bridge.exec_code.get_bridge", return_value=mock_bridge):
+            result = await exec_code(code="func run():\n\tvar x = :")
+            assert result["status"] == "COMPILE_ERROR"
+            assert "error_detail" not in result
+
+    @pytest.mark.asyncio
+    async def test_compile_error_empty_error_detail(self):
+        """Empty error_detail list means no error_detail in response."""
+        mock_bridge = AsyncMock()
+        mock_bridge.request = AsyncMock(return_value={
+            "status": "COMPILE_ERROR",
+            "result": "",
+            "error": "Compilation failed (error 43)",
+            "error_detail": [],
+        })
+        with patch("godotiq.tools.bridge.exec_code.get_bridge", return_value=mock_bridge):
+            result = await exec_code(code="func run():\n\tvar x = :")
+            assert "error_detail" not in result
+
+    @pytest.mark.asyncio
+    async def test_ok_response_passes_through(self):
+        """OK responses are not modified by error_detail logic."""
+        mock_bridge = AsyncMock()
+        mock_bridge.request = AsyncMock(return_value={
+            "status": "OK",
+            "result": "42",
+            "error": "",
+        })
+        with patch("godotiq.tools.bridge.exec_code.get_bridge", return_value=mock_bridge):
+            result = await exec_code(code="func run():\n\treturn 42")
+            assert result["status"] == "OK"
+            assert "error_detail" not in result
diff --git a/tests/test_scaffolding_int.py b/tests/test_scaffolding_int.py
index 7faec83..703d8f5 100644
--- a/tests/test_scaffolding_int.py
+++ b/tests/test_scaffolding_int.py
@@ -135,7 +135,7 @@ class TestMcpServerPingToolRegistered:
         assert len(result) == 1
         payload = json.loads(result[0].text)
         from godotiq import __version__
-        assert payload == {"status": "ok", "version": __version__}
+        assert payload == {"status": "ok", "version": __version__, "license": "community"}
 
         # main() exists and is callable
         from godotiq.server import main
diff --git a/tests/test_server.py b/tests/test_server.py
index 79bf476..243223f 100644
--- a/tests/test_server.py
+++ b/tests/test_server.py
@@ -31,10 +31,17 @@ class TestPingTool:
 
     @pytest.mark.asyncio
     async def test_ping_returns_expected(self) -> None:
-        result = await mcp.call_tool("godotiq_ping", {})
+        # Reset update check cache so the test is deterministic
+        import godotiq.server as _srv
+        _srv._update_checked = False
+        _srv._cached_update_version = None
+        # Mock the PyPI check to avoid network calls
+        from unittest.mock import AsyncMock, patch
+        with patch.object(_srv, "_check_pypi_update", new_callable=AsyncMock, return_value=None):
+            result = await mcp.call_tool("godotiq_ping", {})
         payload = json.loads(result[0].text)
         from godotiq import __version__
-        assert payload == {"status": "ok", "version": __version__}
+        assert payload == {"status": "ok", "version": __version__, "license": "community"}
 
 
 class TestParserStubs:
diff --git a/tests/test_tools/test_asset_registry.py b/tests/test_tools/test_asset_registry.py
index 467b40d..f1d3684 100644
--- a/tests/test_tools/test_asset_registry.py
+++ b/tests/test_tools/test_asset_registry.py
@@ -43,7 +43,7 @@ class TestAssetRegistryCategoryFilter:
     # ROI: 80 | Business Value: 8 | Frequency: 7
     def test_registry_category_filter(self, session: GodotIQSession) -> None:
         """category='models' only returns model assets."""
-        result = _build_asset_registry(session, category="models")
+        result = _build_asset_registry(session, category="models", detail="full")
         # SKELETON: all returned assets should be model type
         assert "assets" in result
         for asset in result["assets"]:
@@ -111,7 +111,7 @@ class TestAssetRegistryByDirectory:
     # ROI: 65 | Business Value: 6 | Frequency: 5
     def test_registry_by_directory(self, session: GodotIQSession) -> None:
         """Groups assets by directory."""
-        result = _build_asset_registry(session)
+        result = _build_asset_registry(session, detail="full")
         # SKELETON: by_directory maps directory paths to asset counts or lists
         assert "by_directory" in result
         assert isinstance(result["by_directory"], dict)
@@ -148,7 +148,7 @@ class TestAssetRegistryRecursiveUsage:
             # orphan_sub.tscn is not in scene_instances values anywhere
             # (it's never instanced by another scene)
 
-            result = _build_asset_registry(session)
+            result = _build_asset_registry(session, detail="full")
             orphan_assets = [
                 a for a in result["assets"]
                 if "orphan_only" in a["path"]
@@ -182,7 +182,7 @@ class TestAssetRegistryMissingFile:
                 "size_bytes": 0,
                 "used_by": [],
             }
-            result = _build_asset_registry(session)
+            result = _build_asset_registry(session, detail="full")
             # SKELETON: the missing file should appear in the registry
             assert "assets" in result
             gone_assets = [
diff --git a/tests/test_tools/test_trace_flow.py b/tests/test_tools/test_trace_flow.py
index b04aca0..072cb22 100644
--- a/tests/test_tools/test_trace_flow.py
+++ b/tests/test_tools/test_trace_flow.py
@@ -15,6 +15,7 @@ from godotiq.tools.flow import (
     _build_trace_flow,
     _detect_failure_points,
     _find_containing_function,
+    _find_local_calls,
     _find_trigger,
     _trace_from_function,
 )
@@ -432,6 +433,97 @@ class TestBuildTraceFlow:
         assert len(result["failure_points"]) >= 1
 
 
+class TestLocalCallTracking:
+    """Tests for intra-file (local) function call tracking."""
+
+    def _make_trace_args(self, session):
+        scripts = session._gd_scripts
+        listeners = _build_signal_listeners(scripts)
+        autoloads = session._index.autoloads
+        project_root = session._index.project_root
+        return scripts, listeners, autoloads, project_root
+
+    def test_follows_local_function(self, trace_flow_session):
+        """accept_item() calls _process_item() in same file -> trace includes _process_item."""
+        scripts, listeners, autoloads, root = self._make_trace_args(trace_flow_session)
+        script = scripts["res://chained_manager.gd"]
+        func = next(f for f in script.functions if f.name == "accept_item")
+        local = _find_local_calls(script, func, "res://chained_manager.gd", root, {})
+        assert "_process_item" in local
+
+    def test_local_call_recursive_chain(self, trace_flow_session):
+        """accept_item -> _process_item -> _create_task: all three appear in trace."""
+        scripts, listeners, autoloads, root = self._make_trace_args(trace_flow_session)
+        visited = set()
+        steps, _ = _trace_from_function(
+            "res://chained_manager.gd", "accept_item", scripts, listeners, autoloads,
+            visited, 0, 10, {}, root,
+        )
+        func_names = [s.function for s in steps]
+        assert "accept_item" in func_names
+        assert "_process_item" in func_names
+        assert "_create_task" in func_names
+
+    def test_local_plus_signal(self, trace_flow_session):
+        """Chain reaches _create_task which emits task_completed -> both local calls and emission."""
+        scripts, listeners, autoloads, root = self._make_trace_args(trace_flow_session)
+        visited = set()
+        steps, _ = _trace_from_function(
+            "res://chained_manager.gd", "accept_item", scripts, listeners, autoloads,
+            visited, 0, 10, {}, root,
+        )
+        # _create_task should emit task_completed
+        create_step = next(s for s in steps if s.function == "_create_task")
+        assert "task_completed" in create_step.emits
+
+    def test_ignores_builtin_calls(self, trace_flow_session):
+        """Built-in calls like print() are not in the script's function list, so not followed."""
+        script = GdScript(
+            functions=[
+                GdFunction(name="do_work", line=1, end_line=3),
+            ]
+        )
+        # Simulate source with print() call
+        cache = {"res://test_builtin.gd": [
+            "func do_work():",
+            "\tprint('hello')",
+            "\tpush_error('fail')",
+        ]}
+        root = trace_flow_session._index.project_root
+        local = _find_local_calls(script, script.functions[0], "res://test_builtin.gd", root, cache)
+        assert "print" not in local
+        assert "push_error" not in local
+
+    def test_cycle_in_local_calls(self, trace_flow_session):
+        """A() -> B() -> A() doesn't infinite loop thanks to visited set."""
+        script = GdScript(
+            functions=[
+                GdFunction(name="func_a", line=1, end_line=2),
+                GdFunction(name="func_b", line=3, end_line=4),
+            ]
+        )
+        cache = {"res://cycle.gd": [
+            "func func_a():",
+            "\tfunc_b()",
+            "func func_b():",
+            "\tfunc_a()",
+        ]}
+        scripts = {"res://cycle.gd": script}
+        listeners = _build_signal_listeners(scripts)
+        root = trace_flow_session._index.project_root
+        visited = set()
+        steps, _ = _trace_from_function(
+            "res://cycle.gd", "func_a", scripts, listeners, {},
+            visited, 0, 10, cache, root,
+        )
+        func_names = [s.function for s in steps]
+        assert "func_a" in func_names
+        assert "func_b" in func_names
+        # Each appears exactly once (cycle prevented)
+        assert func_names.count("func_a") == 1
+        assert func_names.count("func_b") == 1
+
+
 class TestServerRegistration:
     """Verify the tool is registered on the MCP server."""
 
diff --git a/tests/test_version.py b/tests/test_version.py
index 16b1157..972e506 100644
--- a/tests/test_version.py
+++ b/tests/test_version.py
@@ -22,7 +22,7 @@ class TestVersionAttribute:
     def test_version_matches_expected(self):
         """__version__ should equal the version in pyproject.toml (currently 0.1.0)."""
         from godotiq import __version__
-        assert __version__ == "0.1.0"
+        assert __version__ == "0.1.1"
 
     def test_version_looks_like_semver_or_dev(self):
         """__version__ should be a semver string or the dev fallback."""
diff --git a/uv.lock b/uv.lock
index 106ca3c..0d56f4d 100644
--- a/uv.lock
+++ b/uv.lock
@@ -43,6 +43,22 @@ wheels = [
     { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" },
 ]
 
+[[package]]
+name = "build"
+version = "1.4.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "colorama", marker = "os_name == 'nt'" },
+    { name = "importlib-metadata", marker = "python_full_version < '3.10.2'" },
+    { name = "packaging" },
+    { name = "pyproject-hooks" },
+    { name = "tomli", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/42/18/94eaffda7b329535d91f00fe605ab1f1e5cd68b2074d03f255c7d250687d/build-1.4.0.tar.gz", hash = "sha256:f1b91b925aa322be454f8330c6fb48b465da993d1e7e7e6fa35027ec49f3c936", size = 50054, upload-time = "2026-01-08T16:41:47.696Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl", hash = "sha256:6a07c1b8eb6f2b311b96fcbdbce5dab5fe637ffda0fd83c9cac622e927501596", size = 24141, upload-time = "2026-01-08T16:41:46.453Z" },
+]
+
 [[package]]
 name = "certifi"
 version = "2026.2.25"
@@ -229,21 +245,25 @@ wheels = [
 
 [[package]]
 name = "godotiq"
-version = "0.1.0"
+version = "0.1.3"
 source = { editable = "." }
 dependencies = [
+    { name = "httpx" },
     { name = "mcp" },
     { name = "websockets" },
 ]
 
 [package.optional-dependencies]
 dev = [
+    { name = "build" },
     { name = "pytest" },
     { name = "pytest-asyncio" },
 ]
 
 [package.metadata]
 requires-dist = [
+    { name = "build", marker = "extra == 'dev'" },
+    { name = "httpx", specifier = ">=0.27" },
     { name = "mcp", specifier = ">=1.0.0" },
     { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0" },
     { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.21" },
@@ -306,6 +326,18 @@ wheels = [
     { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
 ]
 
+[[package]]
+name = "importlib-metadata"
+version = "8.7.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "zipp" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" },
+]
+
 [[package]]
 name = "iniconfig"
 version = "2.3.0"
@@ -564,6 +596,15 @@ crypto = [
     { name = "cryptography" },
 ]
 
+[[package]]
+name = "pyproject-hooks"
+version = "1.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" },
+]
+
 [[package]]
 name = "pytest"
 version = "9.0.2"
@@ -954,3 +995,12 @@ wheels = [
     { url = "https://files.pythonhosted.org/packages/9a/3f/f70e03f40ffc9a30d817eef7da1be72ee4956ba8d7255c399a01b135902a/websockets-16.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a653aea902e0324b52f1613332ddf50b00c06fdaf7e92624fbf8c77c78fa5767", size = 178735, upload-time = "2026-01-10T09:23:42.259Z" },
     { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" },
 ]
+
+[[package]]
+name = "zipp"
+version = "3.23.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" },
+]
