2025-09-10 09:45:09 +08:00
|
|
|
import sys
|
|
|
|
|
import pathlib
|
|
|
|
|
import importlib.util
|
2025-10-22 01:42:55 +08:00
|
|
|
import os
|
2025-09-10 09:45:09 +08:00
|
|
|
import types
|
|
|
|
|
import threading
|
|
|
|
|
import time
|
|
|
|
|
import queue as q
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
2025-10-04 08:23:28 +08:00
|
|
|
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
|
2025-09-10 09:45:09 +08:00
|
|
|
sys.path.insert(0, str(SRC))
|
|
|
|
|
|
|
|
|
|
# Stub mcp.server.fastmcp to satisfy imports without the full dependency
|
|
|
|
|
mcp_pkg = types.ModuleType("mcp")
|
|
|
|
|
server_pkg = types.ModuleType("mcp.server")
|
|
|
|
|
fastmcp_pkg = types.ModuleType("mcp.server.fastmcp")
|
|
|
|
|
|
2025-10-01 04:25:33 +08:00
|
|
|
|
2025-09-10 09:45:09 +08:00
|
|
|
class _Dummy:
|
|
|
|
|
pass
|
|
|
|
|
|
2025-10-01 04:25:33 +08:00
|
|
|
|
2025-09-10 09:45:09 +08:00
|
|
|
fastmcp_pkg.FastMCP = _Dummy
|
|
|
|
|
fastmcp_pkg.Context = _Dummy
|
|
|
|
|
server_pkg.fastmcp = fastmcp_pkg
|
|
|
|
|
mcp_pkg.server = server_pkg
|
|
|
|
|
sys.modules.setdefault("mcp", mcp_pkg)
|
|
|
|
|
sys.modules.setdefault("mcp.server", server_pkg)
|
|
|
|
|
sys.modules.setdefault("mcp.server.fastmcp", fastmcp_pkg)
|
|
|
|
|
|
2025-10-22 01:42:55 +08:00
|
|
|
# Ensure telemetry module has get_package_version stub before importing
|
|
|
|
|
telemetry_stub = types.ModuleType("telemetry")
|
|
|
|
|
telemetry_stub.get_package_version = lambda: "0.0.0"
|
|
|
|
|
sys.modules.setdefault("telemetry", telemetry_stub)
|
|
|
|
|
|
2025-09-10 09:45:09 +08:00
|
|
|
|
|
|
|
|
def _load_module(path: pathlib.Path, name: str):
|
|
|
|
|
spec = importlib.util.spec_from_file_location(name, path)
|
Harden MCP tool parameter handling to eliminate “invalid param” errors (#339)
* feat: migrate to FastMCP 2.0 (v2.12.5)
- Update pyproject.toml to use fastmcp>=2.12.5 instead of mcp[cli]
- Replace all imports from mcp.server.fastmcp to fastmcp
- Maintain MCP protocol compliance with mcp>=1.16.0
- All 15 files updated with new import statements
- Server and tools registration working with FastMCP 2.0
* chore: bump MCP for Unity to 6.2.2 and widen numeric tool params (asset search/read_resource/run_tests) for better LLM compatibility
* chore: bump installed server_version.txt to 6.2.2 so Unity installer logs correct version
* fix(parameters): apply parameter hardening to read_console, manage_editor, and manage_gameobject
- read_console: accept int|str for count parameter with coercion
- manage_editor: accept bool|str for wait_for_completion with coercion
- manage_gameobject: accept bool|str for all boolean parameters with coercion
- All tools now handle string parameters gracefully and convert to proper types internally
* chore(deps): drop fastmcp, use mcp>=1.18.0; update imports to mcp.server.fastmcp
* chore(deps): re-add fastmcp>=2.12.5, relax mcp to >=1.16.0
Adds fastmcp as explicit dependency for FastMCP 2.0 migration.
Relaxes mcp version constraint to support broader compatibility.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* test: remove obsolete mcp stubs for FastMCP 2.0 compatibility
Removes stub mcp modules from test files that were conflicting with
the real mcp and fastmcp packages now installed as dependencies.
Adds tests/__init__.py to make tests a proper Python package.
This fixes test collection errors after migrating to FastMCP 2.0.
Test results: 40 passed, 7 xpassed, 5 skipped, 1 failed (pre-existing)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: complete FastMCP 2.0 migration with correct import paths
Updates all remaining files to use `from fastmcp import` instead of
the old `from mcp.server.fastmcp import` path.
Changes:
- server.py: Update FastMCP import
- tools/__init__.py: Update FastMCP import
- resources/__init__.py: Update FastMCP import
- tools/manage_script.py, read_console.py, resource_tools.py: Update imports
- test stubs: Update to stub `fastmcp` instead of `mcp.server.fastmcp`
Addresses PR review feedback about incomplete migration.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: harden parameter type handling and resolve PR feedback
Parameter Type Improvements:
- Broaden count in read_console.py to accept int | str
- Broaden build_index in manage_scene.py to accept int | str
- Harden vector parsing in manage_gameobject.py with NaN/Inf checks
- Add whitespace-delimited vector support (e.g., "1 2 3")
- Narrow exception handling from Exception to (ValueError, TypeError)
Test Improvements:
- Harden _load_module in test files with spec/loader validation
- Fix test_manage_gameobject_boolean_and_tag_mapping by mapping tag→search_term
Bug Fixes:
- Fix syntax error in manage_shader.py (remove stray 'x')
Version: Bump to 6.2.3
All tests pass: 41 passed, 5 skipped, 7 xpassed
---------
Co-authored-by: Claude <noreply@anthropic.com>
2025-10-23 09:42:46 +08:00
|
|
|
if spec is None or spec.loader is None:
|
|
|
|
|
raise ImportError(f"Cannot load module {name} from {path}")
|
2025-09-10 09:45:09 +08:00
|
|
|
mod = importlib.util.module_from_spec(spec)
|
|
|
|
|
spec.loader.exec_module(mod)
|
|
|
|
|
return mod
|
|
|
|
|
|
|
|
|
|
|
2025-10-22 01:42:55 +08:00
|
|
|
# Load real telemetry on top of stub (it will reuse stubbed helpers)
|
|
|
|
|
# Note: CWD change required because telemetry.py calls get_package_version()
|
|
|
|
|
# at module load time, which reads pyproject.toml using a relative path.
|
|
|
|
|
# This is fragile but necessary given current telemetry module design.
|
|
|
|
|
_prev_cwd = os.getcwd()
|
|
|
|
|
os.chdir(str(SRC))
|
|
|
|
|
try:
|
|
|
|
|
telemetry = _load_module(SRC / "telemetry.py", "telemetry_mod")
|
|
|
|
|
finally:
|
|
|
|
|
os.chdir(_prev_cwd)
|
2025-09-10 09:45:09 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_telemetry_queue_backpressure_and_single_worker(monkeypatch, caplog):
|
|
|
|
|
caplog.set_level("DEBUG")
|
|
|
|
|
|
|
|
|
|
collector = telemetry.TelemetryCollector()
|
|
|
|
|
# Force-enable telemetry regardless of env settings from conftest
|
|
|
|
|
collector.config.enabled = True
|
|
|
|
|
|
2025-09-11 00:24:09 +08:00
|
|
|
# Wake existing worker once so it observes the new queue on the next loop
|
|
|
|
|
collector.record(telemetry.RecordType.TOOL_EXECUTION, {"i": -1})
|
2025-09-10 09:45:09 +08:00
|
|
|
# Replace queue with tiny one to trigger backpressure quickly
|
|
|
|
|
small_q = q.Queue(maxsize=2)
|
|
|
|
|
collector._queue = small_q
|
2025-09-11 00:24:09 +08:00
|
|
|
# Give the worker a moment to switch queues
|
|
|
|
|
time.sleep(0.02)
|
2025-09-10 09:45:09 +08:00
|
|
|
|
|
|
|
|
# Make sends slow to build backlog and exercise worker
|
|
|
|
|
def slow_send(self, rec):
|
|
|
|
|
time.sleep(0.05)
|
|
|
|
|
|
|
|
|
|
collector._send_telemetry = types.MethodType(slow_send, collector)
|
|
|
|
|
|
|
|
|
|
# Fire many events quickly; record() should not block even when queue fills
|
|
|
|
|
start = time.perf_counter()
|
|
|
|
|
for i in range(50):
|
|
|
|
|
collector.record(telemetry.RecordType.TOOL_EXECUTION, {"i": i})
|
|
|
|
|
elapsed_ms = (time.perf_counter() - start) * 1000.0
|
|
|
|
|
|
|
|
|
|
# Should be fast despite backpressure (non-blocking enqueue or drop)
|
2025-10-22 01:42:55 +08:00
|
|
|
# Timeout relaxed to 200ms to handle thread scheduling variance in CI/local environments
|
|
|
|
|
assert elapsed_ms < 200.0, f"Took {elapsed_ms:.1f}ms (expected <200ms)"
|
2025-09-10 09:45:09 +08:00
|
|
|
|
|
|
|
|
# Allow worker to process some
|
|
|
|
|
time.sleep(0.3)
|
|
|
|
|
|
|
|
|
|
# Verify drops were logged (queue full backpressure)
|
2025-10-01 04:25:33 +08:00
|
|
|
dropped_logs = [
|
|
|
|
|
m for m in caplog.messages if "Telemetry queue full; dropping" in m]
|
2025-09-10 09:45:09 +08:00
|
|
|
assert len(dropped_logs) >= 1
|
|
|
|
|
|
|
|
|
|
# Ensure only one worker thread exists and is alive
|
|
|
|
|
assert collector._worker.is_alive()
|
2025-10-01 04:25:33 +08:00
|
|
|
worker_threads = [
|
|
|
|
|
t for t in threading.enumerate() if t is collector._worker]
|
2025-09-10 09:45:09 +08:00
|
|
|
assert len(worker_threads) == 1
|