Payload-safe paging for hierarchy/components + safer asset search + docs (#490)
* Fix test teardown to avoid dropping MCP bridge CodexConfigHelperTests was calling MCPServiceLocator.Reset() in TearDown, which disposes the active bridge/transport during MCP-driven test runs. Replace with restoring only the mutated service (IPlatformService). * Avoid leaking PlatformService in CodexConfigHelperTests Capture the original IPlatformService before this fixture runs and restore it in TearDown. This preserves the MCP connection safety fix (no MCPServiceLocator.Reset()) while avoiding global state leakage to subsequent tests. * Fix SO MCP tooling: validate folder roots, normalize paths, expand tests; remove vestigial SO tools * Remove UnityMCPTests stress artifacts and ignore Assets/Temp * Ignore UnityMCPTests Assets/Temp only * Clarify array_resize fallback logic comments * Refactor: simplify action set and reuse slash sanitization * Enhance: preserve GUID on overwrite & support Vector/Color types in ScriptableObject tools * Fix: ensure asset name matches filename to suppress Unity warnings * Fix: resolve Unity warnings by ensuring asset name match and removing redundant import * Refactor: Validate assetName, strict object parsing for vectors, remove broken SO logic from ManageAsset * Hardening: reject Windows drive paths; clarify supported asset types * Delete FixscriptableobjecPlan.md * Paginate get_hierarchy and get_components to prevent large payload crashes * dev: add uvx dev-mode refresh + safer HTTP stop; fix server typing eval * Payload-safe paging defaults + docs; harden asset search; stabilize Codex tests * chore: align uvx args + coercion helpers; tighten safety guidance * chore: minor cleanup + stabilize EditMode SO testsmain
parent
28f60b42b0
commit
35165e11b3
|
|
@ -423,7 +423,9 @@ namespace MCPForUnity.Editor.Clients
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var (uvxPath, gitUrl, packageName) = AssetPathUtility.GetUvxCommandParts();
|
var (uvxPath, gitUrl, packageName) = AssetPathUtility.GetUvxCommandParts();
|
||||||
args = $"mcp add --transport stdio UnityMCP -- \"{uvxPath}\" --from \"{gitUrl}\" {packageName}";
|
bool devForceRefresh = GetDevModeForceRefresh();
|
||||||
|
string devFlags = devForceRefresh ? "--no-cache --refresh " : string.Empty;
|
||||||
|
args = $"mcp add --transport stdio UnityMCP -- \"{uvxPath}\" {devFlags}--from \"{gitUrl}\" {packageName}";
|
||||||
}
|
}
|
||||||
|
|
||||||
string projectDir = Path.GetDirectoryName(Application.dataPath);
|
string projectDir = Path.GetDirectoryName(Application.dataPath);
|
||||||
|
|
@ -537,14 +539,23 @@ namespace MCPForUnity.Editor.Clients
|
||||||
}
|
}
|
||||||
|
|
||||||
string gitUrl = AssetPathUtility.GetMcpServerGitUrl();
|
string gitUrl = AssetPathUtility.GetMcpServerGitUrl();
|
||||||
|
bool devForceRefresh = GetDevModeForceRefresh();
|
||||||
|
string devFlags = devForceRefresh ? "--no-cache --refresh " : string.Empty;
|
||||||
|
|
||||||
return "# Register the MCP server with Claude Code:\n" +
|
return "# Register the MCP server with Claude Code:\n" +
|
||||||
$"claude mcp add --transport stdio UnityMCP -- \"{uvxPath}\" --from \"{gitUrl}\" mcp-for-unity\n\n" +
|
$"claude mcp add --transport stdio UnityMCP -- \"{uvxPath}\" {devFlags}--from \"{gitUrl}\" mcp-for-unity\n\n" +
|
||||||
"# Unregister the MCP server:\n" +
|
"# Unregister the MCP server:\n" +
|
||||||
"claude mcp remove UnityMCP\n\n" +
|
"claude mcp remove UnityMCP\n\n" +
|
||||||
"# List registered servers:\n" +
|
"# List registered servers:\n" +
|
||||||
"claude mcp list # Only works when claude is run in the project's directory";
|
"claude mcp list # Only works when claude is run in the project's directory";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool GetDevModeForceRefresh()
|
||||||
|
{
|
||||||
|
try { return EditorPrefs.GetBool(EditorPrefKeys.DevModeForceServerRefresh, false); }
|
||||||
|
catch { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
public override IList<string> GetInstallationSteps() => new List<string>
|
public override IList<string> GetInstallationSteps() => new List<string>
|
||||||
{
|
{
|
||||||
"Ensure Claude CLI is installed",
|
"Ensure Claude CLI is installed",
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ namespace MCPForUnity.Editor.Constants
|
||||||
internal const string SessionId = "MCPForUnity.SessionId";
|
internal const string SessionId = "MCPForUnity.SessionId";
|
||||||
internal const string WebSocketUrlOverride = "MCPForUnity.WebSocketUrl";
|
internal const string WebSocketUrlOverride = "MCPForUnity.WebSocketUrl";
|
||||||
internal const string GitUrlOverride = "MCPForUnity.GitUrlOverride";
|
internal const string GitUrlOverride = "MCPForUnity.GitUrlOverride";
|
||||||
|
internal const string DevModeForceServerRefresh = "MCPForUnity.DevModeForceServerRefresh";
|
||||||
|
|
||||||
internal const string PackageDeploySourcePath = "MCPForUnity.PackageDeploy.SourcePath";
|
internal const string PackageDeploySourcePath = "MCPForUnity.PackageDeploy.SourcePath";
|
||||||
internal const string PackageDeployLastBackupPath = "MCPForUnity.PackageDeploy.LastBackupPath";
|
internal const string PackageDeployLastBackupPath = "MCPForUnity.PackageDeploy.LastBackupPath";
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,11 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using MCPForUnity.Editor.Constants;
|
||||||
using MCPForUnity.Editor.Services;
|
using MCPForUnity.Editor.Services;
|
||||||
using MCPForUnity.External.Tommy;
|
using MCPForUnity.External.Tommy;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace MCPForUnity.Editor.Helpers
|
namespace MCPForUnity.Editor.Helpers
|
||||||
{
|
{
|
||||||
|
|
@ -15,6 +17,26 @@ namespace MCPForUnity.Editor.Helpers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class CodexConfigHelper
|
public static class CodexConfigHelper
|
||||||
{
|
{
|
||||||
|
private static bool GetDevModeForceRefresh()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return EditorPrefs.GetBool(EditorPrefKeys.DevModeForceServerRefresh, false);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddDevModeArgs(TomlArray args, bool devForceRefresh)
|
||||||
|
{
|
||||||
|
if (args == null) return;
|
||||||
|
if (!devForceRefresh) return;
|
||||||
|
args.Add(new TomlString { Value = "--no-cache" });
|
||||||
|
args.Add(new TomlString { Value = "--refresh" });
|
||||||
|
}
|
||||||
|
|
||||||
public static string BuildCodexServerBlock(string uvPath)
|
public static string BuildCodexServerBlock(string uvPath)
|
||||||
{
|
{
|
||||||
var table = new TomlTable();
|
var table = new TomlTable();
|
||||||
|
|
@ -37,9 +59,12 @@ namespace MCPForUnity.Editor.Helpers
|
||||||
{
|
{
|
||||||
// Stdio mode: Use command and args
|
// Stdio mode: Use command and args
|
||||||
var (uvxPath, fromUrl, packageName) = AssetPathUtility.GetUvxCommandParts();
|
var (uvxPath, fromUrl, packageName) = AssetPathUtility.GetUvxCommandParts();
|
||||||
|
bool devForceRefresh = GetDevModeForceRefresh();
|
||||||
|
|
||||||
unityMCP["command"] = uvxPath;
|
unityMCP["command"] = uvxPath;
|
||||||
|
|
||||||
var args = new TomlArray();
|
var args = new TomlArray();
|
||||||
|
AddDevModeArgs(args, devForceRefresh);
|
||||||
if (!string.IsNullOrEmpty(fromUrl))
|
if (!string.IsNullOrEmpty(fromUrl))
|
||||||
{
|
{
|
||||||
args.Add(new TomlString { Value = "--from" });
|
args.Add(new TomlString { Value = "--from" });
|
||||||
|
|
@ -184,9 +209,12 @@ namespace MCPForUnity.Editor.Helpers
|
||||||
{
|
{
|
||||||
// Stdio mode: Use command and args
|
// Stdio mode: Use command and args
|
||||||
var (uvxPath, fromUrl, packageName) = AssetPathUtility.GetUvxCommandParts();
|
var (uvxPath, fromUrl, packageName) = AssetPathUtility.GetUvxCommandParts();
|
||||||
|
bool devForceRefresh = GetDevModeForceRefresh();
|
||||||
|
|
||||||
unityMCP["command"] = new TomlString { Value = uvxPath };
|
unityMCP["command"] = new TomlString { Value = uvxPath };
|
||||||
|
|
||||||
var argsArray = new TomlArray();
|
var argsArray = new TomlArray();
|
||||||
|
AddDevModeArgs(argsArray, devForceRefresh);
|
||||||
if (!string.IsNullOrEmpty(fromUrl))
|
if (!string.IsNullOrEmpty(fromUrl))
|
||||||
{
|
{
|
||||||
argsArray.Add(new TomlString { Value = "--from" });
|
argsArray.Add(new TomlString { Value = "--from" });
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,10 @@ namespace MCPForUnity.Editor.Helpers
|
||||||
// Stdio mode: Use uvx command
|
// Stdio mode: Use uvx command
|
||||||
var (uvxPath, fromUrl, packageName) = AssetPathUtility.GetUvxCommandParts();
|
var (uvxPath, fromUrl, packageName) = AssetPathUtility.GetUvxCommandParts();
|
||||||
|
|
||||||
var toolArgs = BuildUvxArgs(fromUrl, packageName);
|
bool devForceRefresh = false;
|
||||||
|
try { devForceRefresh = EditorPrefs.GetBool(EditorPrefKeys.DevModeForceServerRefresh, false); } catch { }
|
||||||
|
|
||||||
|
var toolArgs = BuildUvxArgs(fromUrl, packageName, devForceRefresh);
|
||||||
|
|
||||||
if (ShouldUseWindowsCmdShim(client))
|
if (ShouldUseWindowsCmdShim(client))
|
||||||
{
|
{
|
||||||
|
|
@ -149,15 +152,23 @@ namespace MCPForUnity.Editor.Helpers
|
||||||
return created;
|
return created;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IList<string> BuildUvxArgs(string fromUrl, string packageName)
|
private static IList<string> BuildUvxArgs(string fromUrl, string packageName, bool devForceRefresh)
|
||||||
{
|
{
|
||||||
var args = new List<string> { packageName };
|
// Dev mode: force a fresh install/resolution (avoids stale cached builds while iterating).
|
||||||
|
// `--no-cache` is the key flag; `--refresh` ensures metadata is revalidated.
|
||||||
|
// Keep ordering consistent with other uvx builders: dev flags first, then --from <url>, then package name.
|
||||||
|
var args = new List<string>();
|
||||||
|
if (devForceRefresh)
|
||||||
|
{
|
||||||
|
args.Add("--no-cache");
|
||||||
|
args.Add("--refresh");
|
||||||
|
}
|
||||||
if (!string.IsNullOrEmpty(fromUrl))
|
if (!string.IsNullOrEmpty(fromUrl))
|
||||||
{
|
{
|
||||||
args.Insert(0, fromUrl);
|
args.Add("--from");
|
||||||
args.Insert(0, "--from");
|
args.Add(fromUrl);
|
||||||
}
|
}
|
||||||
|
args.Add(packageName);
|
||||||
|
|
||||||
args.Add("--transport");
|
args.Add("--transport");
|
||||||
args.Add("stdio");
|
args.Add("stdio");
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Collections.Generic;
|
||||||
using MCPForUnity.Editor.Constants;
|
using MCPForUnity.Editor.Constants;
|
||||||
using MCPForUnity.Editor.Helpers;
|
using MCPForUnity.Editor.Helpers;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
|
|
@ -171,15 +172,7 @@ namespace MCPForUnity.Editor.Services
|
||||||
// First, try to stop any existing server
|
// First, try to stop any existing server
|
||||||
StopLocalHttpServer();
|
StopLocalHttpServer();
|
||||||
|
|
||||||
// Clear the cache to ensure we get a fresh version
|
// Note: Dev mode cache-busting is handled by `uvx --no-cache --refresh` in the generated command.
|
||||||
try
|
|
||||||
{
|
|
||||||
ClearUvxCache();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
McpLog.Warn($"Failed to clear cache before starting server: {ex.Message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (EditorUtility.DisplayDialog(
|
if (EditorUtility.DisplayDialog(
|
||||||
"Start Local HTTP Server",
|
"Start Local HTTP Server",
|
||||||
|
|
@ -237,20 +230,47 @@ namespace MCPForUnity.Editor.Services
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
McpLog.Info($"Attempting to stop any process listening on local port {port}. This will terminate the owning process even if it is not the MCP server.");
|
// Guardrails:
|
||||||
|
// - Never terminate the Unity Editor process.
|
||||||
|
// - Only terminate processes that look like the MCP server (uv/uvx/python running mcp-for-unity).
|
||||||
|
// This prevents accidental termination of unrelated services (including Unity itself).
|
||||||
|
int unityPid = GetCurrentProcessIdSafe();
|
||||||
|
|
||||||
int pid = GetProcessIdForPort(port);
|
var pids = GetListeningProcessIdsForPort(port);
|
||||||
if (pid > 0)
|
if (pids.Count == 0)
|
||||||
{
|
|
||||||
KillProcess(pid);
|
|
||||||
McpLog.Info($"Stopped local HTTP server on port {port} (PID: {pid})");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
McpLog.Info($"No process found listening on port {port}");
|
McpLog.Info($"No process found listening on port {port}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool stoppedAny = false;
|
||||||
|
foreach (var pid in pids)
|
||||||
|
{
|
||||||
|
if (pid <= 0) continue;
|
||||||
|
if (unityPid > 0 && pid == unityPid)
|
||||||
|
{
|
||||||
|
McpLog.Warn($"Refusing to stop port {port}: owning PID appears to be the Unity Editor process (PID {pid}).");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!LooksLikeMcpServerProcess(pid))
|
||||||
|
{
|
||||||
|
McpLog.Warn($"Refusing to stop port {port}: owning PID {pid} does not look like mcp-for-unity (uvx/uv/python).");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TerminateProcess(pid))
|
||||||
|
{
|
||||||
|
McpLog.Info($"Stopped local HTTP server on port {port} (PID: {pid})");
|
||||||
|
stoppedAny = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
McpLog.Warn($"Failed to stop process PID {pid} on port {port}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stoppedAny;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -259,8 +279,9 @@ namespace MCPForUnity.Editor.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetProcessIdForPort(int port)
|
private List<int> GetListeningProcessIdsForPort(int port)
|
||||||
{
|
{
|
||||||
|
var results = new List<int>();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string stdout, stderr;
|
string stdout, stderr;
|
||||||
|
|
@ -280,7 +301,7 @@ namespace MCPForUnity.Editor.Services
|
||||||
var parts = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
var parts = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
if (parts.Length > 0 && int.TryParse(parts[parts.Length - 1], out int pid))
|
if (parts.Length > 0 && int.TryParse(parts[parts.Length - 1], out int pid))
|
||||||
{
|
{
|
||||||
return pid;
|
results.Add(pid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -288,12 +309,13 @@ namespace MCPForUnity.Editor.Services
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// lsof -i :<port> -t
|
// lsof: only return LISTENers (avoids capturing random clients)
|
||||||
// Use /usr/sbin/lsof directly as it might not be in PATH for Unity
|
// Use /usr/sbin/lsof directly as it might not be in PATH for Unity
|
||||||
string lsofPath = "/usr/sbin/lsof";
|
string lsofPath = "/usr/sbin/lsof";
|
||||||
if (!System.IO.File.Exists(lsofPath)) lsofPath = "lsof"; // Fallback
|
if (!System.IO.File.Exists(lsofPath)) lsofPath = "lsof"; // Fallback
|
||||||
|
|
||||||
success = ExecPath.TryRun(lsofPath, $"-i :{port} -t", Application.dataPath, out stdout, out stderr);
|
// -nP: avoid DNS/service name lookups; faster and less error-prone
|
||||||
|
success = ExecPath.TryRun(lsofPath, $"-nP -iTCP:{port} -sTCP:LISTEN -t", Application.dataPath, out stdout, out stderr);
|
||||||
if (success && !string.IsNullOrWhiteSpace(stdout))
|
if (success && !string.IsNullOrWhiteSpace(stdout))
|
||||||
{
|
{
|
||||||
var pidStrings = stdout.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
var pidStrings = stdout.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
@ -301,12 +323,7 @@ namespace MCPForUnity.Editor.Services
|
||||||
{
|
{
|
||||||
if (int.TryParse(pidString.Trim(), out int pid))
|
if (int.TryParse(pidString.Trim(), out int pid))
|
||||||
{
|
{
|
||||||
if (pidStrings.Length > 1)
|
results.Add(pid);
|
||||||
{
|
|
||||||
McpLog.Debug($"Multiple processes found on port {port}; attempting to stop PID {pid} returned by lsof -t.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return pid;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -316,26 +333,96 @@ namespace MCPForUnity.Editor.Services
|
||||||
{
|
{
|
||||||
McpLog.Warn($"Error checking port {port}: {ex.Message}");
|
McpLog.Warn($"Error checking port {port}: {ex.Message}");
|
||||||
}
|
}
|
||||||
return -1;
|
return results.Distinct().ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void KillProcess(int pid)
|
private static int GetCurrentProcessIdSafe()
|
||||||
|
{
|
||||||
|
try { return System.Diagnostics.Process.GetCurrentProcess().Id; }
|
||||||
|
catch { return -1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool LooksLikeMcpServerProcess(int pid)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Windows best-effort: tasklist /FI "PID eq X"
|
||||||
|
if (Application.platform == RuntimePlatform.WindowsEditor)
|
||||||
|
{
|
||||||
|
if (ExecPath.TryRun("cmd.exe", $"/c tasklist /FI \"PID eq {pid}\"", Application.dataPath, out var stdout, out var stderr, 5000))
|
||||||
|
{
|
||||||
|
string combined = (stdout ?? string.Empty) + "\n" + (stderr ?? string.Empty);
|
||||||
|
combined = combined.ToLowerInvariant();
|
||||||
|
// Common process names: python.exe, uv.exe, uvx.exe
|
||||||
|
return combined.Contains("python") || combined.Contains("uvx") || combined.Contains("uv.exe") || combined.Contains("uvx.exe");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// macOS/Linux: ps -p pid -o comm= -o args=
|
||||||
|
if (ExecPath.TryRun("ps", $"-p {pid} -o comm= -o args=", Application.dataPath, out var psOut, out var psErr, 5000))
|
||||||
|
{
|
||||||
|
string s = (psOut ?? string.Empty).Trim().ToLowerInvariant();
|
||||||
|
if (string.IsNullOrEmpty(s))
|
||||||
|
{
|
||||||
|
s = (psErr ?? string.Empty).Trim().ToLowerInvariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicitly never kill Unity / Unity Hub processes
|
||||||
|
if (s.Contains("unity") || s.Contains("unityhub") || s.Contains("unity hub"))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Positive indicators
|
||||||
|
bool mentionsUvx = s.Contains("uvx") || s.Contains(" uvx ");
|
||||||
|
bool mentionsUv = s.Contains("uv ") || s.Contains("/uv");
|
||||||
|
bool mentionsPython = s.Contains("python");
|
||||||
|
bool mentionsMcp = s.Contains("mcp-for-unity") || s.Contains("mcp_for_unity") || s.Contains("mcp for unity");
|
||||||
|
bool mentionsTransport = s.Contains("--transport") && s.Contains("http");
|
||||||
|
|
||||||
|
// Accept if it looks like uv/uvx/python launching our server package/entrypoint
|
||||||
|
if ((mentionsUvx || mentionsUv || mentionsPython) && (mentionsMcp || mentionsTransport))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TerminateProcess(int pid)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string stdout, stderr;
|
string stdout, stderr;
|
||||||
if (Application.platform == RuntimePlatform.WindowsEditor)
|
if (Application.platform == RuntimePlatform.WindowsEditor)
|
||||||
{
|
{
|
||||||
ExecPath.TryRun("taskkill", $"/F /PID {pid}", Application.dataPath, out stdout, out stderr);
|
// taskkill without /F first; fall back to /F if needed.
|
||||||
|
bool ok = ExecPath.TryRun("taskkill", $"/PID {pid}", Application.dataPath, out stdout, out stderr);
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
ok = ExecPath.TryRun("taskkill", $"/F /PID {pid}", Application.dataPath, out stdout, out stderr);
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ExecPath.TryRun("kill", $"-9 {pid}", Application.dataPath, out stdout, out stderr);
|
// Try a graceful termination first, then escalate.
|
||||||
|
bool ok = ExecPath.TryRun("kill", $"-15 {pid}", Application.dataPath, out stdout, out stderr);
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
ok = ExecPath.TryRun("kill", $"-9 {pid}", Application.dataPath, out stdout, out stderr);
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
McpLog.Error($"Error killing process {pid}: {ex.Message}");
|
McpLog.Error($"Error killing process {pid}: {ex.Message}");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -368,9 +455,13 @@ namespace MCPForUnity.Editor.Services
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool devForceRefresh = false;
|
||||||
|
try { devForceRefresh = EditorPrefs.GetBool(EditorPrefKeys.DevModeForceServerRefresh, false); } catch { }
|
||||||
|
|
||||||
|
string devFlags = devForceRefresh ? "--no-cache --refresh " : string.Empty;
|
||||||
string args = string.IsNullOrEmpty(fromUrl)
|
string args = string.IsNullOrEmpty(fromUrl)
|
||||||
? $"{packageName} --transport http --http-url {httpUrl}"
|
? $"{devFlags}{packageName} --transport http --http-url {httpUrl}"
|
||||||
: $"--from {fromUrl} {packageName} --transport http --http-url {httpUrl}";
|
: $"{devFlags}--from {fromUrl} {packageName} --transport http --http-url {httpUrl}";
|
||||||
|
|
||||||
command = $"{uvxPath} {args}";
|
command = $"{uvxPath} {args}";
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -180,8 +180,44 @@ namespace MCPForUnity.Editor.Tools
|
||||||
return new ErrorResponse(
|
return new ErrorResponse(
|
||||||
"'target' parameter required for get_components."
|
"'target' parameter required for get_components."
|
||||||
);
|
);
|
||||||
// Pass the includeNonPublicSerialized flag here
|
// Paging + safety: return metadata by default; deep fields are opt-in.
|
||||||
return GetComponentsFromTarget(getCompTarget, searchMethod, includeNonPublicSerialized);
|
int CoerceInt(JToken t, int @default)
|
||||||
|
{
|
||||||
|
if (t == null || t.Type == JTokenType.Null) return @default;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (t.Type == JTokenType.Integer) return t.Value<int>();
|
||||||
|
var s = t.ToString().Trim();
|
||||||
|
if (s.Length == 0) return @default;
|
||||||
|
if (int.TryParse(s, out var i)) return i;
|
||||||
|
if (double.TryParse(s, out var d)) return (int)d;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
return @default;
|
||||||
|
}
|
||||||
|
bool CoerceBool(JToken t, bool @default)
|
||||||
|
{
|
||||||
|
if (t == null || t.Type == JTokenType.Null) return @default;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (t.Type == JTokenType.Boolean) return t.Value<bool>();
|
||||||
|
var s = t.ToString().Trim();
|
||||||
|
if (s.Length == 0) return @default;
|
||||||
|
if (bool.TryParse(s, out var b)) return b;
|
||||||
|
if (s == "1") return true;
|
||||||
|
if (s == "0") return false;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
return @default;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pageSize = CoerceInt(@params["pageSize"] ?? @params["page_size"], 25);
|
||||||
|
int cursor = CoerceInt(@params["cursor"], 0);
|
||||||
|
int maxComponents = CoerceInt(@params["maxComponents"] ?? @params["max_components"], 50);
|
||||||
|
bool includeProperties = CoerceBool(@params["includeProperties"] ?? @params["include_properties"], false);
|
||||||
|
|
||||||
|
// Pass the includeNonPublicSerialized flag through, but only used if includeProperties is true.
|
||||||
|
return GetComponentsFromTarget(getCompTarget, searchMethod, includeNonPublicSerialized, pageSize, cursor, maxComponents, includeProperties);
|
||||||
case "get_component":
|
case "get_component":
|
||||||
string getSingleCompTarget = targetToken?.ToString();
|
string getSingleCompTarget = targetToken?.ToString();
|
||||||
if (getSingleCompTarget == null)
|
if (getSingleCompTarget == null)
|
||||||
|
|
@ -1191,7 +1227,15 @@ namespace MCPForUnity.Editor.Tools
|
||||||
return new SuccessResponse($"Found {results.Count} GameObject(s).", results);
|
return new SuccessResponse($"Found {results.Count} GameObject(s).", results);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static object GetComponentsFromTarget(string target, string searchMethod, bool includeNonPublicSerialized = true)
|
private static object GetComponentsFromTarget(
|
||||||
|
string target,
|
||||||
|
string searchMethod,
|
||||||
|
bool includeNonPublicSerialized = true,
|
||||||
|
int pageSize = 25,
|
||||||
|
int cursor = 0,
|
||||||
|
int maxComponents = 50,
|
||||||
|
bool includeProperties = false
|
||||||
|
)
|
||||||
{
|
{
|
||||||
GameObject targetGo = FindObjectInternal(target, searchMethod);
|
GameObject targetGo = FindObjectInternal(target, searchMethod);
|
||||||
if (targetGo == null)
|
if (targetGo == null)
|
||||||
|
|
@ -1203,57 +1247,90 @@ namespace MCPForUnity.Editor.Tools
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// --- Get components, immediately copy to list, and null original array ---
|
int resolvedPageSize = Mathf.Clamp(pageSize, 1, 200);
|
||||||
Component[] originalComponents = targetGo.GetComponents<Component>();
|
int resolvedCursor = Mathf.Max(0, cursor);
|
||||||
List<Component> componentsToIterate = new List<Component>(originalComponents ?? Array.Empty<Component>()); // Copy immediately, handle null case
|
int resolvedMaxComponents = Mathf.Clamp(maxComponents, 1, 500);
|
||||||
int componentCount = componentsToIterate.Count;
|
int effectiveTake = Mathf.Min(resolvedPageSize, resolvedMaxComponents);
|
||||||
originalComponents = null; // Null the original reference
|
|
||||||
// Debug.Log($"[GetComponentsFromTarget] Found {componentCount} components on {targetGo.name}. Copied to list, nulled original. Starting REVERSE for loop...");
|
|
||||||
// --- End Copy and Null ---
|
|
||||||
|
|
||||||
var componentData = new List<object>();
|
// Build a stable list once; pagination is applied to this list.
|
||||||
|
var all = targetGo.GetComponents<Component>();
|
||||||
for (int i = componentCount - 1; i >= 0; i--) // Iterate backwards over the COPY
|
var components = new List<Component>(all?.Length ?? 0);
|
||||||
|
if (all != null)
|
||||||
{
|
{
|
||||||
Component c = componentsToIterate[i]; // Use the copy
|
for (int i = 0; i < all.Length; i++)
|
||||||
if (c == null)
|
|
||||||
{
|
{
|
||||||
// Debug.LogWarning($"[GetComponentsFromTarget REVERSE for] Encountered a null component at index {i} on {targetGo.name}. Skipping.");
|
if (all[i] != null) components.Add(all[i]);
|
||||||
continue; // Safety check
|
|
||||||
}
|
}
|
||||||
// Debug.Log($"[GetComponentsFromTarget REVERSE for] Processing component: {c.GetType()?.FullName ?? "null"} (ID: {c.GetInstanceID()}) at index {i} on {targetGo.name}");
|
}
|
||||||
|
|
||||||
|
int total = components.Count;
|
||||||
|
if (resolvedCursor > total) resolvedCursor = total;
|
||||||
|
int end = Mathf.Min(total, resolvedCursor + effectiveTake);
|
||||||
|
|
||||||
|
var items = new List<object>(Mathf.Max(0, end - resolvedCursor));
|
||||||
|
|
||||||
|
// If caller explicitly asked for properties, we still enforce a conservative payload budget.
|
||||||
|
const int maxPayloadChars = 250_000; // ~250KB assuming 1 char ~= 1 byte ASCII-ish
|
||||||
|
int payloadChars = 0;
|
||||||
|
|
||||||
|
for (int i = resolvedCursor; i < end; i++)
|
||||||
|
{
|
||||||
|
var c = components[i];
|
||||||
|
if (c == null) continue;
|
||||||
|
|
||||||
|
if (!includeProperties)
|
||||||
|
{
|
||||||
|
items.Add(BuildComponentMetadata(c));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var data = Helpers.GameObjectSerializer.GetComponentData(c, includeNonPublicSerialized);
|
var data = Helpers.GameObjectSerializer.GetComponentData(c, includeNonPublicSerialized);
|
||||||
if (data != null) // Ensure GetComponentData didn't return null
|
if (data == null) continue;
|
||||||
|
|
||||||
|
// Rough cap to keep responses from exploding even when includeProperties is true.
|
||||||
|
var token = JToken.FromObject(data);
|
||||||
|
int addChars = token.ToString(Newtonsoft.Json.Formatting.None).Length;
|
||||||
|
if (payloadChars + addChars > maxPayloadChars && items.Count > 0)
|
||||||
{
|
{
|
||||||
componentData.Insert(0, data); // Insert at beginning to maintain original order in final list
|
// Stop early; next_cursor will allow fetching more (or caller can use get_component).
|
||||||
|
end = i;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
// else
|
payloadChars += addChars;
|
||||||
// {
|
items.Add(token);
|
||||||
// Debug.LogWarning($"[GetComponentsFromTarget REVERSE for] GetComponentData returned null for component {c.GetType().FullName} (ID: {c.GetInstanceID()}) on {targetGo.name}. Skipping addition.");
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.LogError($"[GetComponentsFromTarget REVERSE for] Error processing component {c.GetType().FullName} (ID: {c.GetInstanceID()}) on {targetGo.name}: {ex.Message}\n{ex.StackTrace}");
|
// Avoid throwing; mark the component as failed.
|
||||||
// Optionally add placeholder data or just skip
|
items.Add(
|
||||||
componentData.Insert(0, new JObject( // Insert error marker at beginning
|
new JObject(
|
||||||
new JProperty("typeName", c.GetType().FullName + " (Serialization Error)"),
|
new JProperty("typeName", c.GetType().FullName + " (Serialization Error)"),
|
||||||
new JProperty("instanceID", c.GetInstanceID()),
|
new JProperty("instanceID", c.GetInstanceID()),
|
||||||
new JProperty("error", ex.Message)
|
new JProperty("error", ex.Message)
|
||||||
));
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Debug.Log($"[GetComponentsFromTarget] Finished REVERSE for loop.");
|
|
||||||
|
|
||||||
// Cleanup the list we created
|
bool truncated = end < total;
|
||||||
componentsToIterate.Clear();
|
string nextCursor = truncated ? end.ToString() : null;
|
||||||
componentsToIterate = null;
|
|
||||||
|
var payload = new
|
||||||
|
{
|
||||||
|
cursor = resolvedCursor,
|
||||||
|
pageSize = effectiveTake,
|
||||||
|
next_cursor = nextCursor,
|
||||||
|
truncated = truncated,
|
||||||
|
total = total,
|
||||||
|
includeProperties = includeProperties,
|
||||||
|
items = items,
|
||||||
|
};
|
||||||
|
|
||||||
return new SuccessResponse(
|
return new SuccessResponse(
|
||||||
$"Retrieved {componentData.Count} components from '{targetGo.name}'.",
|
$"Retrieved components page from '{targetGo.name}'.",
|
||||||
componentData // List was built in original order
|
payload
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|
@ -1264,6 +1341,21 @@ namespace MCPForUnity.Editor.Tools
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static object BuildComponentMetadata(Component c)
|
||||||
|
{
|
||||||
|
if (c == null) return null;
|
||||||
|
var d = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "typeName", c.GetType().FullName },
|
||||||
|
{ "instanceID", c.GetInstanceID() },
|
||||||
|
};
|
||||||
|
if (c is Behaviour b)
|
||||||
|
{
|
||||||
|
d["enabled"] = b.enabled;
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
private static object GetSingleComponentFromTarget(string target, string searchMethod, string componentName, bool includeNonPublicSerialized = true)
|
private static object GetSingleComponentFromTarget(string target, string searchMethod, string componentName, bool includeNonPublicSerialized = true)
|
||||||
{
|
{
|
||||||
GameObject targetGo = FindObjectInternal(target, searchMethod);
|
GameObject targetGo = FindObjectInternal(target, searchMethod);
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,15 @@ namespace MCPForUnity.Editor.Tools
|
||||||
public int? buildIndex { get; set; }
|
public int? buildIndex { get; set; }
|
||||||
public string fileName { get; set; } = string.Empty;
|
public string fileName { get; set; } = string.Empty;
|
||||||
public int? superSize { get; set; }
|
public int? superSize { get; set; }
|
||||||
|
|
||||||
|
// get_hierarchy paging + safety (summary-first)
|
||||||
|
public JToken parent { get; set; }
|
||||||
|
public int? pageSize { get; set; }
|
||||||
|
public int? cursor { get; set; }
|
||||||
|
public int? maxNodes { get; set; }
|
||||||
|
public int? maxDepth { get; set; }
|
||||||
|
public int? maxChildrenPerNode { get; set; }
|
||||||
|
public bool? includeTransform { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SceneCommand ToSceneCommand(JObject p)
|
private static SceneCommand ToSceneCommand(JObject p)
|
||||||
|
|
@ -40,6 +49,21 @@ namespace MCPForUnity.Editor.Tools
|
||||||
if (double.TryParse(s, out var d)) return (int)d;
|
if (double.TryParse(s, out var d)) return (int)d;
|
||||||
return t.Type == JTokenType.Integer ? t.Value<int>() : (int?)null;
|
return t.Type == JTokenType.Integer ? t.Value<int>() : (int?)null;
|
||||||
}
|
}
|
||||||
|
bool? BB(JToken t)
|
||||||
|
{
|
||||||
|
if (t == null || t.Type == JTokenType.Null) return null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (t.Type == JTokenType.Boolean) return t.Value<bool>();
|
||||||
|
var s = t.ToString().Trim();
|
||||||
|
if (s.Length == 0) return null;
|
||||||
|
if (bool.TryParse(s, out var b)) return b;
|
||||||
|
if (s == "1") return true;
|
||||||
|
if (s == "0") return false;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return new SceneCommand
|
return new SceneCommand
|
||||||
{
|
{
|
||||||
action = (p["action"]?.ToString() ?? string.Empty).Trim().ToLowerInvariant(),
|
action = (p["action"]?.ToString() ?? string.Empty).Trim().ToLowerInvariant(),
|
||||||
|
|
@ -47,7 +71,16 @@ namespace MCPForUnity.Editor.Tools
|
||||||
path = p["path"]?.ToString() ?? string.Empty,
|
path = p["path"]?.ToString() ?? string.Empty,
|
||||||
buildIndex = BI(p["buildIndex"] ?? p["build_index"]),
|
buildIndex = BI(p["buildIndex"] ?? p["build_index"]),
|
||||||
fileName = (p["fileName"] ?? p["filename"])?.ToString() ?? string.Empty,
|
fileName = (p["fileName"] ?? p["filename"])?.ToString() ?? string.Empty,
|
||||||
superSize = BI(p["superSize"] ?? p["super_size"] ?? p["supersize"])
|
superSize = BI(p["superSize"] ?? p["super_size"] ?? p["supersize"]),
|
||||||
|
|
||||||
|
// get_hierarchy paging + safety
|
||||||
|
parent = p["parent"],
|
||||||
|
pageSize = BI(p["pageSize"] ?? p["page_size"]),
|
||||||
|
cursor = BI(p["cursor"]),
|
||||||
|
maxNodes = BI(p["maxNodes"] ?? p["max_nodes"]),
|
||||||
|
maxDepth = BI(p["maxDepth"] ?? p["max_depth"]),
|
||||||
|
maxChildrenPerNode = BI(p["maxChildrenPerNode"] ?? p["max_children_per_node"]),
|
||||||
|
includeTransform = BB(p["includeTransform"] ?? p["include_transform"]),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,7 +170,7 @@ namespace MCPForUnity.Editor.Tools
|
||||||
return SaveScene(fullPath, relativePath);
|
return SaveScene(fullPath, relativePath);
|
||||||
case "get_hierarchy":
|
case "get_hierarchy":
|
||||||
try { McpLog.Info("[ManageScene] get_hierarchy: entering", always: false); } catch { }
|
try { McpLog.Info("[ManageScene] get_hierarchy: entering", always: false); } catch { }
|
||||||
var gh = GetSceneHierarchy();
|
var gh = GetSceneHierarchyPaged(cmd);
|
||||||
try { McpLog.Info("[ManageScene] get_hierarchy: exiting", always: false); } catch { }
|
try { McpLog.Info("[ManageScene] get_hierarchy: exiting", always: false); } catch { }
|
||||||
return gh;
|
return gh;
|
||||||
case "get_active":
|
case "get_active":
|
||||||
|
|
@ -452,7 +485,7 @@ namespace MCPForUnity.Editor.Tools
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static object GetSceneHierarchy()
|
private static object GetSceneHierarchyPaged(SceneCommand cmd)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -466,15 +499,71 @@ namespace MCPForUnity.Editor.Tools
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
try { McpLog.Info("[ManageScene] get_hierarchy: fetching root objects", always: false); } catch { }
|
// Defaults tuned for safety; callers can override but we clamp to sane maxes.
|
||||||
GameObject[] rootObjects = activeScene.GetRootGameObjects();
|
// NOTE: pageSize is "items per page", not "number of pages".
|
||||||
try { McpLog.Info($"[ManageScene] get_hierarchy: rootCount={rootObjects?.Length ?? 0}", always: false); } catch { }
|
// Keep this conservative to reduce peak response sizes when callers omit page_size.
|
||||||
var hierarchy = rootObjects.Select(go => GetGameObjectDataRecursive(go)).ToList();
|
int resolvedPageSize = Mathf.Clamp(cmd.pageSize ?? 50, 1, 500);
|
||||||
|
int resolvedCursor = Mathf.Max(0, cmd.cursor ?? 0);
|
||||||
|
int resolvedMaxNodes = Mathf.Clamp(cmd.maxNodes ?? 1000, 1, 5000);
|
||||||
|
int effectiveTake = Mathf.Min(resolvedPageSize, resolvedMaxNodes);
|
||||||
|
int resolvedMaxChildrenPerNode = Mathf.Clamp(cmd.maxChildrenPerNode ?? 200, 0, 2000);
|
||||||
|
bool includeTransform = cmd.includeTransform ?? false;
|
||||||
|
|
||||||
var resp = new SuccessResponse(
|
// NOTE: maxDepth is accepted for forward-compatibility, but current paging mode
|
||||||
$"Retrieved hierarchy for scene '{activeScene.name}'.",
|
// returns a single level (roots or direct children). This keeps payloads bounded.
|
||||||
hierarchy
|
|
||||||
);
|
List<GameObject> nodes;
|
||||||
|
string scope;
|
||||||
|
|
||||||
|
GameObject parentGo = ResolveGameObject(cmd.parent, activeScene);
|
||||||
|
if (cmd.parent == null || cmd.parent.Type == JTokenType.Null)
|
||||||
|
{
|
||||||
|
try { McpLog.Info("[ManageScene] get_hierarchy: listing root objects (paged summary)", always: false); } catch { }
|
||||||
|
nodes = activeScene.GetRootGameObjects().Where(go => go != null).ToList();
|
||||||
|
scope = "roots";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (parentGo == null)
|
||||||
|
{
|
||||||
|
return new ErrorResponse($"Parent GameObject ('{cmd.parent}') not found.");
|
||||||
|
}
|
||||||
|
try { McpLog.Info($"[ManageScene] get_hierarchy: listing children of '{parentGo.name}' (paged summary)", always: false); } catch { }
|
||||||
|
nodes = new List<GameObject>(parentGo.transform.childCount);
|
||||||
|
foreach (Transform child in parentGo.transform)
|
||||||
|
{
|
||||||
|
if (child != null) nodes.Add(child.gameObject);
|
||||||
|
}
|
||||||
|
scope = "children";
|
||||||
|
}
|
||||||
|
|
||||||
|
int total = nodes.Count;
|
||||||
|
if (resolvedCursor > total) resolvedCursor = total;
|
||||||
|
int end = Mathf.Min(total, resolvedCursor + effectiveTake);
|
||||||
|
|
||||||
|
var items = new List<object>(Mathf.Max(0, end - resolvedCursor));
|
||||||
|
for (int i = resolvedCursor; i < end; i++)
|
||||||
|
{
|
||||||
|
var go = nodes[i];
|
||||||
|
if (go == null) continue;
|
||||||
|
items.Add(BuildGameObjectSummary(go, includeTransform, resolvedMaxChildrenPerNode));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool truncated = end < total;
|
||||||
|
string nextCursor = truncated ? end.ToString() : null;
|
||||||
|
|
||||||
|
var payload = new
|
||||||
|
{
|
||||||
|
scope = scope,
|
||||||
|
cursor = resolvedCursor,
|
||||||
|
pageSize = effectiveTake,
|
||||||
|
next_cursor = nextCursor,
|
||||||
|
truncated = truncated,
|
||||||
|
total = total,
|
||||||
|
items = items,
|
||||||
|
};
|
||||||
|
|
||||||
|
var resp = new SuccessResponse($"Retrieved hierarchy page for scene '{activeScene.name}'.", payload);
|
||||||
try { McpLog.Info("[ManageScene] get_hierarchy: success", always: false); } catch { }
|
try { McpLog.Info("[ManageScene] get_hierarchy: success", always: false); } catch { }
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
@ -485,6 +574,111 @@ namespace MCPForUnity.Editor.Tools
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static GameObject ResolveGameObject(JToken targetToken, Scene activeScene)
|
||||||
|
{
|
||||||
|
if (targetToken == null || targetToken.Type == JTokenType.Null) return null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (targetToken.Type == JTokenType.Integer || int.TryParse(targetToken.ToString(), out _))
|
||||||
|
{
|
||||||
|
if (int.TryParse(targetToken.ToString(), out int id))
|
||||||
|
{
|
||||||
|
var obj = EditorUtility.InstanceIDToObject(id);
|
||||||
|
if (obj is GameObject go) return go;
|
||||||
|
if (obj is Component c) return c.gameObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
string s = targetToken.ToString();
|
||||||
|
if (string.IsNullOrEmpty(s)) return null;
|
||||||
|
|
||||||
|
// Path-based find (e.g., "Root/Child/GrandChild")
|
||||||
|
if (s.Contains("/"))
|
||||||
|
{
|
||||||
|
try { return GameObject.Find(s); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name-based find (first match, includes inactive)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var all = activeScene.GetRootGameObjects();
|
||||||
|
foreach (var root in all)
|
||||||
|
{
|
||||||
|
if (root == null) continue;
|
||||||
|
if (root.name == s) return root;
|
||||||
|
var trs = root.GetComponentsInChildren<Transform>(includeInactive: true);
|
||||||
|
foreach (var t in trs)
|
||||||
|
{
|
||||||
|
if (t != null && t.gameObject != null && t.gameObject.name == s) return t.gameObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object BuildGameObjectSummary(GameObject go, bool includeTransform, int maxChildrenPerNode)
|
||||||
|
{
|
||||||
|
if (go == null) return null;
|
||||||
|
|
||||||
|
int childCount = 0;
|
||||||
|
try { childCount = go.transform != null ? go.transform.childCount : 0; } catch { }
|
||||||
|
bool childrenTruncated = childCount > 0; // We do not inline children in summary mode.
|
||||||
|
|
||||||
|
var d = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "name", go.name },
|
||||||
|
{ "instanceID", go.GetInstanceID() },
|
||||||
|
{ "activeSelf", go.activeSelf },
|
||||||
|
{ "activeInHierarchy", go.activeInHierarchy },
|
||||||
|
{ "tag", go.tag },
|
||||||
|
{ "layer", go.layer },
|
||||||
|
{ "isStatic", go.isStatic },
|
||||||
|
{ "path", GetGameObjectPath(go) },
|
||||||
|
{ "childCount", childCount },
|
||||||
|
{ "childrenTruncated", childrenTruncated },
|
||||||
|
{ "childrenCursor", childCount > 0 ? "0" : null },
|
||||||
|
{ "childrenPageSizeDefault", maxChildrenPerNode },
|
||||||
|
};
|
||||||
|
|
||||||
|
if (includeTransform && go.transform != null)
|
||||||
|
{
|
||||||
|
var t = go.transform;
|
||||||
|
d["transform"] = new
|
||||||
|
{
|
||||||
|
position = new[] { t.localPosition.x, t.localPosition.y, t.localPosition.z },
|
||||||
|
rotation = new[] { t.localRotation.eulerAngles.x, t.localRotation.eulerAngles.y, t.localRotation.eulerAngles.z },
|
||||||
|
scale = new[] { t.localScale.x, t.localScale.y, t.localScale.z },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetGameObjectPath(GameObject go)
|
||||||
|
{
|
||||||
|
if (go == null) return string.Empty;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var names = new Stack<string>();
|
||||||
|
Transform t = go.transform;
|
||||||
|
while (t != null)
|
||||||
|
{
|
||||||
|
names.Push(t.name);
|
||||||
|
t = t.parent;
|
||||||
|
}
|
||||||
|
return string.Join("/", names);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return go.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Recursively builds a data representation of a GameObject and its children.
|
/// Recursively builds a data representation of a GameObject and its children.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ namespace MCPForUnity.Editor.Windows.Components.Settings
|
||||||
private TextField gitUrlOverride;
|
private TextField gitUrlOverride;
|
||||||
private Button browseGitUrlButton;
|
private Button browseGitUrlButton;
|
||||||
private Button clearGitUrlButton;
|
private Button clearGitUrlButton;
|
||||||
|
private Toggle devModeForceRefreshToggle;
|
||||||
private TextField deploySourcePath;
|
private TextField deploySourcePath;
|
||||||
private Button browseDeploySourceButton;
|
private Button browseDeploySourceButton;
|
||||||
private Button clearDeploySourceButton;
|
private Button clearDeploySourceButton;
|
||||||
|
|
@ -79,6 +80,7 @@ namespace MCPForUnity.Editor.Windows.Components.Settings
|
||||||
gitUrlOverride = Root.Q<TextField>("git-url-override");
|
gitUrlOverride = Root.Q<TextField>("git-url-override");
|
||||||
browseGitUrlButton = Root.Q<Button>("browse-git-url-button");
|
browseGitUrlButton = Root.Q<Button>("browse-git-url-button");
|
||||||
clearGitUrlButton = Root.Q<Button>("clear-git-url-button");
|
clearGitUrlButton = Root.Q<Button>("clear-git-url-button");
|
||||||
|
devModeForceRefreshToggle = Root.Q<Toggle>("dev-mode-force-refresh-toggle");
|
||||||
deploySourcePath = Root.Q<TextField>("deploy-source-path");
|
deploySourcePath = Root.Q<TextField>("deploy-source-path");
|
||||||
browseDeploySourceButton = Root.Q<Button>("browse-deploy-source-button");
|
browseDeploySourceButton = Root.Q<Button>("browse-deploy-source-button");
|
||||||
clearDeploySourceButton = Root.Q<Button>("clear-deploy-source-button");
|
clearDeploySourceButton = Root.Q<Button>("clear-deploy-source-button");
|
||||||
|
|
@ -105,6 +107,7 @@ namespace MCPForUnity.Editor.Windows.Components.Settings
|
||||||
|
|
||||||
advancedSettingsFoldout.value = false;
|
advancedSettingsFoldout.value = false;
|
||||||
gitUrlOverride.value = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, "");
|
gitUrlOverride.value = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, "");
|
||||||
|
devModeForceRefreshToggle.value = EditorPrefs.GetBool(EditorPrefKeys.DevModeForceServerRefresh, false);
|
||||||
|
|
||||||
UpdateDeploymentSection();
|
UpdateDeploymentSection();
|
||||||
}
|
}
|
||||||
|
|
@ -151,6 +154,12 @@ namespace MCPForUnity.Editor.Windows.Components.Settings
|
||||||
OnHttpServerCommandUpdateRequested?.Invoke();
|
OnHttpServerCommandUpdateRequested?.Invoke();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
devModeForceRefreshToggle.RegisterValueChangedCallback(evt =>
|
||||||
|
{
|
||||||
|
EditorPrefs.SetBool(EditorPrefKeys.DevModeForceServerRefresh, evt.newValue);
|
||||||
|
OnHttpServerCommandUpdateRequested?.Invoke();
|
||||||
|
});
|
||||||
|
|
||||||
deploySourcePath.RegisterValueChangedCallback(evt =>
|
deploySourcePath.RegisterValueChangedCallback(evt =>
|
||||||
{
|
{
|
||||||
string path = evt.newValue?.Trim();
|
string path = evt.newValue?.Trim();
|
||||||
|
|
@ -205,6 +214,7 @@ namespace MCPForUnity.Editor.Windows.Components.Settings
|
||||||
}
|
}
|
||||||
|
|
||||||
gitUrlOverride.value = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, "");
|
gitUrlOverride.value = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, "");
|
||||||
|
devModeForceRefreshToggle.value = EditorPrefs.GetBool(EditorPrefKeys.DevModeForceServerRefresh, false);
|
||||||
UpdateDeploymentSection();
|
UpdateDeploymentSection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,13 @@
|
||||||
<ui:Label text="• Local: /Users/you/Projects/unity-mcp/Server" class="help-text" />
|
<ui:Label text="• Local: /Users/you/Projects/unity-mcp/Server" class="help-text" />
|
||||||
<ui:Label text="• Remote: git+https://github.com/CoplayDev/unity-mcp@v6.3.0#subdirectory=Server" class="help-text" />
|
<ui:Label text="• Remote: git+https://github.com/CoplayDev/unity-mcp@v6.3.0#subdirectory=Server" class="help-text" />
|
||||||
|
|
||||||
|
<ui:Label text="Dev Mode:" class="advanced-label" style="margin-top: 10px;" />
|
||||||
|
<ui:Label text="When enabled, generated uvx commands add '--no-cache --refresh' before launching (slower startup, but avoids stale cached builds while iterating on the Server)." class="help-text" />
|
||||||
|
<ui:VisualElement class="setting-row">
|
||||||
|
<ui:Label text="Force fresh server install:" class="setting-label" />
|
||||||
|
<ui:Toggle name="dev-mode-force-refresh-toggle" class="setting-toggle" />
|
||||||
|
</ui:VisualElement>
|
||||||
|
|
||||||
<ui:Label text="Local Package Deployment:" class="advanced-label" style="margin-top: 12px;" />
|
<ui:Label text="Local Package Deployment:" class="advanced-label" style="margin-top: 12px;" />
|
||||||
<ui:Label text="Copy a MCPForUnity folder into this project's package location." class="help-text" />
|
<ui:Label text="Copy a MCPForUnity folder into this project's package location." class="help-text" />
|
||||||
<ui:VisualElement class="override-row">
|
<ui:VisualElement class="override-row">
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ keywords = ["mcp", "unity", "ai", "model context protocol", "gamedev", "unity3d"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"httpx>=0.27.2",
|
"httpx>=0.27.2",
|
||||||
"fastmcp>=2.13.0,<2.13.2",
|
"fastmcp==2.14.1",
|
||||||
"mcp>=1.16.0",
|
"mcp>=1.16.0",
|
||||||
"pydantic>=2.12.5",
|
"pydantic>=2.12.5",
|
||||||
"tomli>=2.3.0",
|
"tomli>=2.3.0",
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,32 @@ import time
|
||||||
from typing import AsyncIterator, Any
|
from typing import AsyncIterator, Any
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
# Workaround for environments where tool signature evaluation runs with a globals
|
||||||
|
# dict that does not include common `typing` names (e.g. when annotations are strings
|
||||||
|
# and evaluated via `eval()` during schema generation).
|
||||||
|
# Making these names available in builtins avoids `NameError: Annotated/Literal/... is not defined`.
|
||||||
|
try: # pragma: no cover - startup safety guard
|
||||||
|
import builtins
|
||||||
|
import typing as _typing
|
||||||
|
|
||||||
|
_typing_names = (
|
||||||
|
"Annotated",
|
||||||
|
"Literal",
|
||||||
|
"Any",
|
||||||
|
"Union",
|
||||||
|
"Optional",
|
||||||
|
"Dict",
|
||||||
|
"List",
|
||||||
|
"Tuple",
|
||||||
|
"Set",
|
||||||
|
"FrozenSet",
|
||||||
|
)
|
||||||
|
for _name in _typing_names:
|
||||||
|
if not hasattr(builtins, _name) and hasattr(_typing, _name):
|
||||||
|
setattr(builtins, _name, getattr(_typing, _name)) # type: ignore[attr-defined]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
from fastmcp import FastMCP
|
from fastmcp import FastMCP
|
||||||
from logging.handlers import RotatingFileHandler
|
from logging.handlers import RotatingFileHandler
|
||||||
from starlette.requests import Request
|
from starlette.requests import Request
|
||||||
|
|
@ -242,6 +268,18 @@ Console Monitoring:
|
||||||
Menu Items:
|
Menu Items:
|
||||||
- Use `execute_menu_item` when you have read the menu items resource
|
- Use `execute_menu_item` when you have read the menu items resource
|
||||||
- This lets you interact with Unity's menu system and third-party tools
|
- This lets you interact with Unity's menu system and third-party tools
|
||||||
|
|
||||||
|
Payload sizing & paging (important):
|
||||||
|
- Many Unity queries can return very large JSON. Prefer **paged + summary-first** calls.
|
||||||
|
- `manage_scene(action="get_hierarchy")`:
|
||||||
|
- Use `page_size` + `cursor` and follow `next_cursor` until null.
|
||||||
|
- `page_size` is **items per page**; recommended starting point: **50**.
|
||||||
|
- `manage_gameobject(action="get_components")`:
|
||||||
|
- Start with `include_properties=false` (metadata-only) and small `page_size` (e.g. **10-25**).
|
||||||
|
- Only request `include_properties=true` when needed; keep `page_size` small (e.g. **3-10**) to bound payloads.
|
||||||
|
- `manage_asset(action="search")`:
|
||||||
|
- Use paging (`page_size`, `page_number`) and keep `page_size` modest (e.g. **25-50**) to avoid token-heavy responses.
|
||||||
|
- Keep `generate_preview=false` unless you explicitly need thumbnails (previews may include large base64 payloads).
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from core.telemetry import get_package_version
|
||||||
|
|
||||||
from fastmcp import Context
|
from fastmcp import Context
|
||||||
from services.registry import mcp_for_unity_tool
|
from services.registry import mcp_for_unity_tool
|
||||||
|
|
@ -50,6 +54,11 @@ def debug_request_context(ctx: Context) -> dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"data": {
|
"data": {
|
||||||
|
"server": {
|
||||||
|
"version": get_package_version(),
|
||||||
|
"cwd": os.getcwd(),
|
||||||
|
"argv": list(sys.argv),
|
||||||
|
},
|
||||||
"request_context": {
|
"request_context": {
|
||||||
"client_id": rc_client_id,
|
"client_id": rc_client_id,
|
||||||
"session_id": rc_session_id,
|
"session_id": rc_session_id,
|
||||||
|
|
|
||||||
|
|
@ -9,18 +9,22 @@ from typing import Annotated, Any, Literal
|
||||||
from fastmcp import Context
|
from fastmcp import Context
|
||||||
from services.registry import mcp_for_unity_tool
|
from services.registry import mcp_for_unity_tool
|
||||||
from services.tools import get_unity_instance_from_context
|
from services.tools import get_unity_instance_from_context
|
||||||
from services.tools.utils import parse_json_payload
|
from services.tools.utils import parse_json_payload, coerce_int
|
||||||
from transport.unity_transport import send_with_unity_instance
|
from transport.unity_transport import send_with_unity_instance
|
||||||
from transport.legacy.unity_connection import async_send_command_with_retry
|
from transport.legacy.unity_connection import async_send_command_with_retry
|
||||||
|
|
||||||
|
|
||||||
@mcp_for_unity_tool(
|
@mcp_for_unity_tool(
|
||||||
description="Performs asset operations (import, create, modify, delete, etc.) in Unity."
|
description=(
|
||||||
|
"Performs asset operations (import, create, modify, delete, etc.) in Unity.\n\n"
|
||||||
|
"Tip (payload safety): for `action=\"search\"`, prefer paging (`page_size`, `page_number`) and keep "
|
||||||
|
"`generate_preview=false` (previews can add large base64 blobs)."
|
||||||
|
)
|
||||||
)
|
)
|
||||||
async def manage_asset(
|
async def manage_asset(
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
action: Annotated[Literal["import", "create", "modify", "delete", "duplicate", "move", "rename", "search", "get_info", "create_folder", "get_components"], "Perform CRUD operations on assets."],
|
action: Annotated[Literal["import", "create", "modify", "delete", "duplicate", "move", "rename", "search", "get_info", "create_folder", "get_components"], "Perform CRUD operations on assets."],
|
||||||
path: Annotated[str, "Asset path (e.g., 'Materials/MyMaterial.mat') or search scope."],
|
path: Annotated[str, "Asset path (e.g., 'Materials/MyMaterial.mat') or search scope (e.g., 'Assets')."],
|
||||||
asset_type: Annotated[str,
|
asset_type: Annotated[str,
|
||||||
"Asset type (e.g., 'Material', 'Folder') - required for 'create'. Note: For ScriptableObjects, use manage_scriptable_object."] | None = None,
|
"Asset type (e.g., 'Material', 'Folder') - required for 'create'. Note: For ScriptableObjects, use manage_scriptable_object."] | None = None,
|
||||||
properties: Annotated[dict[str, Any] | str,
|
properties: Annotated[dict[str, Any] | str,
|
||||||
|
|
@ -28,16 +32,18 @@ async def manage_asset(
|
||||||
destination: Annotated[str,
|
destination: Annotated[str,
|
||||||
"Target path for 'duplicate'/'move'."] | None = None,
|
"Target path for 'duplicate'/'move'."] | None = None,
|
||||||
generate_preview: Annotated[bool,
|
generate_preview: Annotated[bool,
|
||||||
"Generate a preview/thumbnail for the asset when supported."] = False,
|
"Generate a preview/thumbnail for the asset when supported. "
|
||||||
|
"Warning: previews may include large base64 payloads; keep false unless needed."] = False,
|
||||||
search_pattern: Annotated[str,
|
search_pattern: Annotated[str,
|
||||||
"Search pattern (e.g., '*.prefab')."] | None = None,
|
"Search pattern (e.g., '*.prefab' or AssetDatabase filters like 't:MonoScript'). "
|
||||||
|
"Recommended: put queries like 't:MonoScript' here and set path='Assets'."] | None = None,
|
||||||
filter_type: Annotated[str, "Filter type for search"] | None = None,
|
filter_type: Annotated[str, "Filter type for search"] | None = None,
|
||||||
filter_date_after: Annotated[str,
|
filter_date_after: Annotated[str,
|
||||||
"Date after which to filter"] | None = None,
|
"Date after which to filter"] | None = None,
|
||||||
page_size: Annotated[int | float | str,
|
page_size: Annotated[int | float | str,
|
||||||
"Page size for pagination"] | None = None,
|
"Page size for pagination. Recommended: 25 (smaller for LLM-friendly responses)."] | None = None,
|
||||||
page_number: Annotated[int | float | str,
|
page_number: Annotated[int | float | str,
|
||||||
"Page number for pagination"] | None = None,
|
"Page number for pagination (1-based)."] | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
unity_instance = get_unity_instance_from_context(ctx)
|
unity_instance = get_unity_instance_from_context(ctx)
|
||||||
|
|
||||||
|
|
@ -83,24 +89,32 @@ async def manage_asset(
|
||||||
await ctx.error(parse_error)
|
await ctx.error(parse_error)
|
||||||
return {"success": False, "message": parse_error}
|
return {"success": False, "message": parse_error}
|
||||||
|
|
||||||
# Coerce numeric inputs defensively
|
page_size = coerce_int(page_size)
|
||||||
def _coerce_int(value, default=None):
|
page_number = coerce_int(page_number)
|
||||||
if value is None:
|
|
||||||
return default
|
|
||||||
try:
|
|
||||||
if isinstance(value, bool):
|
|
||||||
return default
|
|
||||||
if isinstance(value, int):
|
|
||||||
return int(value)
|
|
||||||
s = str(value).strip()
|
|
||||||
if s.lower() in ("", "none", "null"):
|
|
||||||
return default
|
|
||||||
return int(float(s))
|
|
||||||
except Exception:
|
|
||||||
return default
|
|
||||||
|
|
||||||
page_size = _coerce_int(page_size)
|
# --- Payload-safe normalization for common LLM mistakes (search) ---
|
||||||
page_number = _coerce_int(page_number)
|
# Unity's C# handler treats `path` as a folder scope. If a model mistakenly puts a query like
|
||||||
|
# "t:MonoScript" into `path`, Unity will consider it an invalid folder and fall back to searching
|
||||||
|
# the entire project, which is token-heavy. Normalize such cases into search_pattern + Assets scope.
|
||||||
|
action_l = (action or "").lower()
|
||||||
|
if action_l == "search":
|
||||||
|
try:
|
||||||
|
raw_path = (path or "").strip()
|
||||||
|
except (AttributeError, TypeError):
|
||||||
|
# Handle case where path is not a string despite type annotation
|
||||||
|
raw_path = ""
|
||||||
|
|
||||||
|
# If the caller put an AssetDatabase query into `path`, treat it as `search_pattern`.
|
||||||
|
if (not search_pattern) and raw_path.startswith("t:"):
|
||||||
|
search_pattern = raw_path
|
||||||
|
path = "Assets"
|
||||||
|
await ctx.info("manage_asset(search): normalized query from `path` into `search_pattern` and set path='Assets'")
|
||||||
|
|
||||||
|
# If the caller used `asset_type` to mean a search filter, map it to filter_type.
|
||||||
|
# (In Unity, filterType becomes `t:<filterType>`.)
|
||||||
|
if (not filter_type) and asset_type and isinstance(asset_type, str):
|
||||||
|
filter_type = asset_type
|
||||||
|
await ctx.info("manage_asset(search): mapped `asset_type` into `filter_type` for safer server-side filtering")
|
||||||
|
|
||||||
# Prepare parameters for the C# handler
|
# Prepare parameters for the C# handler
|
||||||
params_dict = {
|
params_dict = {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from services.registry import mcp_for_unity_tool
|
||||||
from services.tools import get_unity_instance_from_context
|
from services.tools import get_unity_instance_from_context
|
||||||
from transport.unity_transport import send_with_unity_instance
|
from transport.unity_transport import send_with_unity_instance
|
||||||
from transport.legacy.unity_connection import async_send_command_with_retry
|
from transport.legacy.unity_connection import async_send_command_with_retry
|
||||||
from services.tools.utils import coerce_bool, parse_json_payload
|
from services.tools.utils import coerce_bool, parse_json_payload, coerce_int
|
||||||
|
|
||||||
|
|
||||||
@mcp_for_unity_tool(
|
@mcp_for_unity_tool(
|
||||||
|
|
@ -68,6 +68,11 @@ async def manage_gameobject(
|
||||||
# Controls whether serialization of private [SerializeField] fields is included
|
# Controls whether serialization of private [SerializeField] fields is included
|
||||||
includeNonPublicSerialized: Annotated[bool | str,
|
includeNonPublicSerialized: Annotated[bool | str,
|
||||||
"Controls whether serialization of private [SerializeField] fields is included (accepts true/false or 'true'/'false')"] | None = None,
|
"Controls whether serialization of private [SerializeField] fields is included (accepts true/false or 'true'/'false')"] | None = None,
|
||||||
|
# --- Paging/safety for get_components ---
|
||||||
|
page_size: Annotated[int | str, "Page size for get_components paging."] | None = None,
|
||||||
|
cursor: Annotated[int | str, "Opaque cursor for get_components paging (offset)."] | None = None,
|
||||||
|
max_components: Annotated[int | str, "Hard cap on returned components per request (safety)."] | None = None,
|
||||||
|
include_properties: Annotated[bool | str, "If true, include serialized component properties (bounded)."] | None = None,
|
||||||
# --- Parameters for 'duplicate' ---
|
# --- Parameters for 'duplicate' ---
|
||||||
new_name: Annotated[str,
|
new_name: Annotated[str,
|
||||||
"New name for the duplicated object (default: SourceName_Copy)"] | None = None,
|
"New name for the duplicated object (default: SourceName_Copy)"] | None = None,
|
||||||
|
|
@ -134,7 +139,12 @@ async def manage_gameobject(
|
||||||
search_in_children = coerce_bool(search_in_children)
|
search_in_children = coerce_bool(search_in_children)
|
||||||
search_inactive = coerce_bool(search_inactive)
|
search_inactive = coerce_bool(search_inactive)
|
||||||
includeNonPublicSerialized = coerce_bool(includeNonPublicSerialized)
|
includeNonPublicSerialized = coerce_bool(includeNonPublicSerialized)
|
||||||
|
include_properties = coerce_bool(include_properties)
|
||||||
world_space = coerce_bool(world_space, default=True)
|
world_space = coerce_bool(world_space, default=True)
|
||||||
|
# If coercion fails, omit these fields (None) rather than preserving invalid input.
|
||||||
|
page_size = coerce_int(page_size, default=None)
|
||||||
|
cursor = coerce_int(cursor, default=None)
|
||||||
|
max_components = coerce_int(max_components, default=None)
|
||||||
|
|
||||||
# Coerce 'component_properties' from JSON string to dict for client compatibility
|
# Coerce 'component_properties' from JSON string to dict for client compatibility
|
||||||
component_properties = parse_json_payload(component_properties)
|
component_properties = parse_json_payload(component_properties)
|
||||||
|
|
@ -194,6 +204,10 @@ async def manage_gameobject(
|
||||||
"searchInactive": search_inactive,
|
"searchInactive": search_inactive,
|
||||||
"componentName": component_name,
|
"componentName": component_name,
|
||||||
"includeNonPublicSerialized": includeNonPublicSerialized,
|
"includeNonPublicSerialized": includeNonPublicSerialized,
|
||||||
|
"pageSize": page_size,
|
||||||
|
"cursor": cursor,
|
||||||
|
"maxComponents": max_components,
|
||||||
|
"includeProperties": include_properties,
|
||||||
# Parameters for 'duplicate'
|
# Parameters for 'duplicate'
|
||||||
"new_name": new_name,
|
"new_name": new_name,
|
||||||
"offset": offset,
|
"offset": offset,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ from typing import Annotated, Literal, Any
|
||||||
from fastmcp import Context
|
from fastmcp import Context
|
||||||
from services.registry import mcp_for_unity_tool
|
from services.registry import mcp_for_unity_tool
|
||||||
from services.tools import get_unity_instance_from_context
|
from services.tools import get_unity_instance_from_context
|
||||||
|
from services.tools.utils import coerce_int, coerce_bool
|
||||||
from transport.unity_transport import send_with_unity_instance
|
from transport.unity_transport import send_with_unity_instance
|
||||||
from transport.legacy.unity_connection import async_send_command_with_retry
|
from transport.legacy.unity_connection import async_send_command_with_retry
|
||||||
|
|
||||||
|
|
@ -27,29 +28,27 @@ async def manage_scene(
|
||||||
"Unity build index (quote as string, e.g., '0')."] | None = None,
|
"Unity build index (quote as string, e.g., '0')."] | None = None,
|
||||||
screenshot_file_name: Annotated[str, "Screenshot file name (optional). Defaults to timestamp when omitted."] | None = None,
|
screenshot_file_name: Annotated[str, "Screenshot file name (optional). Defaults to timestamp when omitted."] | None = None,
|
||||||
screenshot_super_size: Annotated[int | str, "Screenshot supersize multiplier (integer ≥1). Optional." ] | None = None,
|
screenshot_super_size: Annotated[int | str, "Screenshot supersize multiplier (integer ≥1). Optional." ] | None = None,
|
||||||
|
# --- get_hierarchy paging/safety ---
|
||||||
|
parent: Annotated[str | int, "Optional parent GameObject reference (name/path/instanceID) to list direct children."] | None = None,
|
||||||
|
page_size: Annotated[int | str, "Page size for get_hierarchy paging."] | None = None,
|
||||||
|
cursor: Annotated[int | str, "Opaque cursor for paging (offset)."] | None = None,
|
||||||
|
max_nodes: Annotated[int | str, "Hard cap on returned nodes per request (safety)."] | None = None,
|
||||||
|
max_depth: Annotated[int | str, "Accepted for forward-compatibility; current paging returns a single level."] | None = None,
|
||||||
|
max_children_per_node: Annotated[int | str, "Child paging hint (safety)."] | None = None,
|
||||||
|
include_transform: Annotated[bool | str, "If true, include local transform in node summaries."] | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
# Get active instance from session state
|
# Get active instance from session state
|
||||||
# Removed session_state import
|
# Removed session_state import
|
||||||
unity_instance = get_unity_instance_from_context(ctx)
|
unity_instance = get_unity_instance_from_context(ctx)
|
||||||
try:
|
try:
|
||||||
# Coerce numeric inputs defensively
|
coerced_build_index = coerce_int(build_index, default=None)
|
||||||
def _coerce_int(value, default=None):
|
coerced_super_size = coerce_int(screenshot_super_size, default=None)
|
||||||
if value is None:
|
coerced_page_size = coerce_int(page_size, default=None)
|
||||||
return default
|
coerced_cursor = coerce_int(cursor, default=None)
|
||||||
try:
|
coerced_max_nodes = coerce_int(max_nodes, default=None)
|
||||||
if isinstance(value, bool):
|
coerced_max_depth = coerce_int(max_depth, default=None)
|
||||||
return default
|
coerced_max_children_per_node = coerce_int(max_children_per_node, default=None)
|
||||||
if isinstance(value, int):
|
coerced_include_transform = coerce_bool(include_transform, default=None)
|
||||||
return int(value)
|
|
||||||
s = str(value).strip()
|
|
||||||
if s.lower() in ("", "none", "null"):
|
|
||||||
return default
|
|
||||||
return int(float(s))
|
|
||||||
except Exception:
|
|
||||||
return default
|
|
||||||
|
|
||||||
coerced_build_index = _coerce_int(build_index, default=None)
|
|
||||||
coerced_super_size = _coerce_int(screenshot_super_size, default=None)
|
|
||||||
|
|
||||||
params: dict[str, Any] = {"action": action}
|
params: dict[str, Any] = {"action": action}
|
||||||
if name:
|
if name:
|
||||||
|
|
@ -62,6 +61,22 @@ async def manage_scene(
|
||||||
params["fileName"] = screenshot_file_name
|
params["fileName"] = screenshot_file_name
|
||||||
if coerced_super_size is not None:
|
if coerced_super_size is not None:
|
||||||
params["superSize"] = coerced_super_size
|
params["superSize"] = coerced_super_size
|
||||||
|
|
||||||
|
# get_hierarchy paging/safety params (optional)
|
||||||
|
if parent is not None:
|
||||||
|
params["parent"] = parent
|
||||||
|
if coerced_page_size is not None:
|
||||||
|
params["pageSize"] = coerced_page_size
|
||||||
|
if coerced_cursor is not None:
|
||||||
|
params["cursor"] = coerced_cursor
|
||||||
|
if coerced_max_nodes is not None:
|
||||||
|
params["maxNodes"] = coerced_max_nodes
|
||||||
|
if coerced_max_depth is not None:
|
||||||
|
params["maxDepth"] = coerced_max_depth
|
||||||
|
if coerced_max_children_per_node is not None:
|
||||||
|
params["maxChildrenPerNode"] = coerced_max_children_per_node
|
||||||
|
if coerced_include_transform is not None:
|
||||||
|
params["includeTransform"] = coerced_include_transform
|
||||||
|
|
||||||
# Use centralized retry helper with instance routing
|
# Use centralized retry helper with instance routing
|
||||||
response = await send_with_unity_instance(async_send_command_with_retry, unity_instance, "manage_scene", params)
|
response = await send_with_unity_instance(async_send_command_with_retry, unity_instance, "manage_scene", params)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ from typing import Annotated, Any, Literal
|
||||||
from fastmcp import Context
|
from fastmcp import Context
|
||||||
from services.registry import mcp_for_unity_tool
|
from services.registry import mcp_for_unity_tool
|
||||||
from services.tools import get_unity_instance_from_context
|
from services.tools import get_unity_instance_from_context
|
||||||
|
from services.tools.utils import coerce_int, coerce_bool
|
||||||
from transport.unity_transport import send_with_unity_instance
|
from transport.unity_transport import send_with_unity_instance
|
||||||
from transport.legacy.unity_connection import async_send_command_with_retry
|
from transport.legacy.unity_connection import async_send_command_with_retry
|
||||||
|
|
||||||
|
|
@ -38,42 +39,14 @@ async def read_console(
|
||||||
format = format if format is not None else 'detailed'
|
format = format if format is not None else 'detailed'
|
||||||
# Coerce booleans defensively (strings like 'true'/'false')
|
# Coerce booleans defensively (strings like 'true'/'false')
|
||||||
|
|
||||||
def _coerce_bool(value, default=None):
|
include_stacktrace = coerce_bool(include_stacktrace, default=True)
|
||||||
if value is None:
|
|
||||||
return default
|
|
||||||
if isinstance(value, bool):
|
|
||||||
return value
|
|
||||||
if isinstance(value, str):
|
|
||||||
v = value.strip().lower()
|
|
||||||
if v in ("true", "1", "yes", "on"):
|
|
||||||
return True
|
|
||||||
if v in ("false", "0", "no", "off"):
|
|
||||||
return False
|
|
||||||
return bool(value)
|
|
||||||
|
|
||||||
include_stacktrace = _coerce_bool(include_stacktrace, True)
|
|
||||||
|
|
||||||
# Normalize action if it's a string
|
# Normalize action if it's a string
|
||||||
if isinstance(action, str):
|
if isinstance(action, str):
|
||||||
action = action.lower()
|
action = action.lower()
|
||||||
|
|
||||||
# Coerce count defensively (string/float -> int)
|
# Coerce count defensively (string/float -> int)
|
||||||
def _coerce_int(value, default=None):
|
count = coerce_int(count)
|
||||||
if value is None:
|
|
||||||
return default
|
|
||||||
try:
|
|
||||||
if isinstance(value, bool):
|
|
||||||
return default
|
|
||||||
if isinstance(value, int):
|
|
||||||
return int(value)
|
|
||||||
s = str(value).strip()
|
|
||||||
if s.lower() in ("", "none", "null"):
|
|
||||||
return default
|
|
||||||
return int(float(s))
|
|
||||||
except Exception:
|
|
||||||
return default
|
|
||||||
|
|
||||||
count = _coerce_int(count)
|
|
||||||
|
|
||||||
# Prepare parameters for the C# handler
|
# Prepare parameters for the C# handler
|
||||||
params_dict = {
|
params_dict = {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ from pydantic import BaseModel, Field
|
||||||
from models import MCPResponse
|
from models import MCPResponse
|
||||||
from services.registry import mcp_for_unity_tool
|
from services.registry import mcp_for_unity_tool
|
||||||
from services.tools import get_unity_instance_from_context
|
from services.tools import get_unity_instance_from_context
|
||||||
|
from services.tools.utils import coerce_int
|
||||||
from transport.unity_transport import send_with_unity_instance
|
from transport.unity_transport import send_with_unity_instance
|
||||||
from transport.legacy.unity_connection import async_send_command_with_retry
|
from transport.legacy.unity_connection import async_send_command_with_retry
|
||||||
|
|
||||||
|
|
@ -54,22 +55,6 @@ async def run_tests(
|
||||||
) -> RunTestsResponse:
|
) -> RunTestsResponse:
|
||||||
unity_instance = get_unity_instance_from_context(ctx)
|
unity_instance = get_unity_instance_from_context(ctx)
|
||||||
|
|
||||||
# Coerce timeout defensively (string/float -> int)
|
|
||||||
def _coerce_int(value, default=None):
|
|
||||||
if value is None:
|
|
||||||
return default
|
|
||||||
try:
|
|
||||||
if isinstance(value, bool):
|
|
||||||
return default
|
|
||||||
if isinstance(value, int):
|
|
||||||
return int(value)
|
|
||||||
s = str(value).strip()
|
|
||||||
if s.lower() in ("", "none", "null"):
|
|
||||||
return default
|
|
||||||
return int(float(s))
|
|
||||||
except Exception:
|
|
||||||
return default
|
|
||||||
|
|
||||||
# Coerce string or list to list of strings
|
# Coerce string or list to list of strings
|
||||||
def _coerce_string_list(value) -> list[str] | None:
|
def _coerce_string_list(value) -> list[str] | None:
|
||||||
if value is None:
|
if value is None:
|
||||||
|
|
@ -82,7 +67,7 @@ async def run_tests(
|
||||||
return None
|
return None
|
||||||
|
|
||||||
params: dict[str, Any] = {"mode": mode}
|
params: dict[str, Any] = {"mode": mode}
|
||||||
ts = _coerce_int(timeout_seconds)
|
ts = coerce_int(timeout_seconds)
|
||||||
if ts is not None:
|
if ts is not None:
|
||||||
params["timeoutSeconds"] = ts
|
params["timeoutSeconds"] = ts
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,3 +58,20 @@ def parse_json_payload(value: Any) -> Any:
|
||||||
except (json.JSONDecodeError, ValueError):
|
except (json.JSONDecodeError, ValueError):
|
||||||
# If parsing fails, assume it was meant to be a literal string
|
# If parsing fails, assume it was meant to be a literal string
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def coerce_int(value: Any, default: int | None = None) -> int | None:
|
||||||
|
"""Attempt to coerce a loosely-typed value to an integer."""
|
||||||
|
if value is None:
|
||||||
|
return default
|
||||||
|
try:
|
||||||
|
if isinstance(value, bool):
|
||||||
|
return default
|
||||||
|
if isinstance(value, int):
|
||||||
|
return value
|
||||||
|
s = str(value).strip()
|
||||||
|
if s.lower() in ("", "none", "null"):
|
||||||
|
return default
|
||||||
|
return int(float(s))
|
||||||
|
except Exception:
|
||||||
|
return default
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def test_debug_request_context_includes_server_diagnostics(monkeypatch):
|
||||||
|
# Import inside test so stubs in conftest are applied.
|
||||||
|
import services.tools.debug_request_context as mod
|
||||||
|
|
||||||
|
class DummyCtx:
|
||||||
|
# minimal surface for debug_request_context
|
||||||
|
request_context = None
|
||||||
|
session_id = None
|
||||||
|
client_id = None
|
||||||
|
|
||||||
|
def get_state(self, _k):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Ensure get_package_version is stable for assertion
|
||||||
|
monkeypatch.setattr(mod, "get_package_version", lambda: "9.9.9-test")
|
||||||
|
|
||||||
|
res = mod.debug_request_context(DummyCtx())
|
||||||
|
assert res.get("success") is True
|
||||||
|
data = res.get("data") or {}
|
||||||
|
server = data.get("server") or {}
|
||||||
|
assert server.get("version") == "9.9.9-test"
|
||||||
|
assert "cwd" in server
|
||||||
|
assert "argv" in server
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -32,5 +32,41 @@ async def test_manage_gameobject_boolean_and_tag_mapping(monkeypatch):
|
||||||
assert "data" in resp
|
assert "data" in resp
|
||||||
# ensure tag mapped to searchTerm and booleans passed through; C# side coerces true/false already
|
# ensure tag mapped to searchTerm and booleans passed through; C# side coerces true/false already
|
||||||
assert captured["params"]["searchTerm"] == "Player"
|
assert captured["params"]["searchTerm"] == "Player"
|
||||||
assert captured["params"]["findAll"] == "true" or captured["params"]["findAll"] is True
|
assert captured["params"]["findAll"] is True
|
||||||
assert captured["params"]["searchInactive"] in ("0", False, 0)
|
assert captured["params"]["searchInactive"] is False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_manage_gameobject_get_components_paging_params_pass_through(monkeypatch):
|
||||||
|
captured = {}
|
||||||
|
|
||||||
|
async def fake_send(cmd, params, **kwargs):
|
||||||
|
captured["params"] = params
|
||||||
|
return {"success": True, "data": {}}
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
manage_go_mod,
|
||||||
|
"async_send_command_with_retry",
|
||||||
|
fake_send,
|
||||||
|
)
|
||||||
|
|
||||||
|
resp = await manage_go_mod.manage_gameobject(
|
||||||
|
ctx=DummyContext(),
|
||||||
|
action="get_components",
|
||||||
|
target="Player",
|
||||||
|
search_method="by_name",
|
||||||
|
page_size="25",
|
||||||
|
cursor="50",
|
||||||
|
max_components="100",
|
||||||
|
include_properties="true",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert resp.get("success") is True
|
||||||
|
p = captured["params"]
|
||||||
|
assert p["action"] == "get_components"
|
||||||
|
assert p["target"] == "Player"
|
||||||
|
assert p["searchMethod"] == "by_name"
|
||||||
|
assert p["pageSize"] == 25
|
||||||
|
assert p["cursor"] == 50
|
||||||
|
assert p["maxComponents"] == 100
|
||||||
|
assert p["includeProperties"] is True
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from .test_helpers import DummyContext
|
||||||
|
import services.tools.manage_scene as manage_scene_mod
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_manage_scene_get_hierarchy_paging_params_pass_through(monkeypatch):
|
||||||
|
captured = {}
|
||||||
|
|
||||||
|
async def fake_send(cmd, params, **kwargs):
|
||||||
|
captured["params"] = params
|
||||||
|
return {"success": True, "data": {}}
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
manage_scene_mod,
|
||||||
|
"async_send_command_with_retry",
|
||||||
|
fake_send,
|
||||||
|
)
|
||||||
|
|
||||||
|
resp = await manage_scene_mod.manage_scene(
|
||||||
|
ctx=DummyContext(),
|
||||||
|
action="get_hierarchy",
|
||||||
|
parent="Player",
|
||||||
|
page_size="10",
|
||||||
|
cursor="20",
|
||||||
|
max_nodes="1000",
|
||||||
|
max_depth="6",
|
||||||
|
max_children_per_node="200",
|
||||||
|
include_transform="true",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert resp.get("success") is True
|
||||||
|
p = captured["params"]
|
||||||
|
assert p["action"] == "get_hierarchy"
|
||||||
|
assert p["parent"] == "Player"
|
||||||
|
assert p["pageSize"] in (10, "10")
|
||||||
|
assert p["cursor"] in (20, "20")
|
||||||
|
assert p["maxNodes"] in (1000, "1000")
|
||||||
|
assert p["maxDepth"] in (6, "6")
|
||||||
|
assert p["maxChildrenPerNode"] in (200, "200")
|
||||||
|
assert p["includeTransform"] in (True, "true")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
# pyright: reportMissingImports=false
|
||||||
|
|
||||||
|
|
||||||
|
def test_manage_scene_signature_includes_paging_params():
|
||||||
|
import services.tools.manage_scene as mod
|
||||||
|
|
||||||
|
sig = inspect.signature(mod.manage_scene)
|
||||||
|
names = list(sig.parameters.keys())
|
||||||
|
|
||||||
|
# get_hierarchy paging/safety params
|
||||||
|
assert "parent" in names
|
||||||
|
assert "page_size" in names
|
||||||
|
assert "cursor" in names
|
||||||
|
assert "max_nodes" in names
|
||||||
|
assert "max_depth" in names
|
||||||
|
assert "max_children_per_node" in names
|
||||||
|
assert "include_transform" in names
|
||||||
|
|
||||||
|
|
||||||
|
def test_manage_gameobject_signature_includes_paging_params():
|
||||||
|
import services.tools.manage_gameobject as mod
|
||||||
|
|
||||||
|
sig = inspect.signature(mod.manage_gameobject)
|
||||||
|
names = list(sig.parameters.keys())
|
||||||
|
|
||||||
|
assert "page_size" in names
|
||||||
|
assert "cursor" in names
|
||||||
|
assert "max_components" in names
|
||||||
|
assert "include_properties" in names
|
||||||
|
|
||||||
|
|
||||||
371
Server/uv.lock
371
Server/uv.lock
|
|
@ -35,6 +35,15 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" },
|
{ url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-timeout"
|
||||||
|
version = "5.0.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "attrs"
|
name = "attrs"
|
||||||
version = "25.4.0"
|
version = "25.4.0"
|
||||||
|
|
@ -284,6 +293,15 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" },
|
{ url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cloudpickle"
|
||||||
|
version = "3.1.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorama"
|
name = "colorama"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
|
|
@ -436,6 +454,25 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" },
|
{ url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fakeredis"
|
||||||
|
version = "2.33.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "redis" },
|
||||||
|
{ name = "sortedcontainers" },
|
||||||
|
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/5f/f9/57464119936414d60697fcbd32f38909bb5688b616ae13de6e98384433e0/fakeredis-2.33.0.tar.gz", hash = "sha256:d7bc9a69d21df108a6451bbffee23b3eba432c21a654afc7ff2d295428ec5770", size = 175187, upload-time = "2025-12-16T19:45:52.269Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6e/78/a850fed8aeef96d4a99043c90b818b2ed5419cd5b24a4049fd7cfb9f1471/fakeredis-2.33.0-py3-none-any.whl", hash = "sha256:de535f3f9ccde1c56672ab2fdd6a8efbc4f2619fc2f1acc87b8737177d71c965", size = 119605, upload-time = "2025-12-16T19:45:51.08Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.optional-dependencies]
|
||||||
|
lua = [
|
||||||
|
{ name = "lupa" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastapi"
|
name = "fastapi"
|
||||||
version = "0.121.2"
|
version = "0.121.2"
|
||||||
|
|
@ -453,7 +490,7 @@ wheels = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastmcp"
|
name = "fastmcp"
|
||||||
version = "2.13.0.2"
|
version = "2.14.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "authlib" },
|
{ name = "authlib" },
|
||||||
|
|
@ -466,14 +503,16 @@ dependencies = [
|
||||||
{ name = "platformdirs" },
|
{ name = "platformdirs" },
|
||||||
{ name = "py-key-value-aio", extra = ["disk", "keyring", "memory"] },
|
{ name = "py-key-value-aio", extra = ["disk", "keyring", "memory"] },
|
||||||
{ name = "pydantic", extra = ["email"] },
|
{ name = "pydantic", extra = ["email"] },
|
||||||
|
{ name = "pydocket" },
|
||||||
{ name = "pyperclip" },
|
{ name = "pyperclip" },
|
||||||
{ name = "python-dotenv" },
|
{ name = "python-dotenv" },
|
||||||
{ name = "rich" },
|
{ name = "rich" },
|
||||||
|
{ name = "uvicorn" },
|
||||||
{ name = "websockets" },
|
{ name = "websockets" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/1b/74/584a152bcd174c99ddf3cfdd7e86ec4a6c696fb190a907c2a2ec9056bda2/fastmcp-2.13.0.2.tar.gz", hash = "sha256:d35386561b6f3cde195ba2b5892dc89b8919a721e6b39b98e7a16f9a7c0b8e8b", size = 7762083, upload-time = "2025-10-28T13:56:21.702Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/9e/50/d38e4371bdc34e709f4731b1e882cb7bc50e51c1a224859d4cd381b3a79b/fastmcp-2.14.1.tar.gz", hash = "sha256:132725cbf77b68fa3c3d165eff0cfa47e40c1479457419e6a2cfda65bd84c8d6", size = 8263331, upload-time = "2025-12-15T02:26:27.102Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/bd/c6/95eacd687cfab64fec13bfb64e6c6e7da13d01ecd4cb7d7e991858a08119/fastmcp-2.13.0.2-py3-none-any.whl", hash = "sha256:eb381eb073a101aabbc0ac44b05e23fef0cd1619344b7703115c825c8755fa1c", size = 367511, upload-time = "2025-10-28T13:56:18.83Z" },
|
{ url = "https://files.pythonhosted.org/packages/1d/82/72401d09dc27c27fdf72ad6c2fe331e553e3c3646e01b5ff16473191033d/fastmcp-2.14.1-py3-none-any.whl", hash = "sha256:fb3e365cc1d52573ab89caeba9944dd4b056149097be169bce428e011f0a57e5", size = 412176, upload-time = "2025-12-15T02:26:25.356Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -657,6 +696,80 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" },
|
{ url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lupa"
|
||||||
|
version = "2.6"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b8/1c/191c3e6ec6502e3dbe25a53e27f69a5daeac3e56de1f73c0138224171ead/lupa-2.6.tar.gz", hash = "sha256:9a770a6e89576be3447668d7ced312cd6fd41d3c13c2462c9dc2c2ab570e45d9", size = 7240282, upload-time = "2025-10-24T07:20:29.738Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a1/15/713cab5d0dfa4858f83b99b3e0329072df33dc14fc3ebbaa017e0f9755c4/lupa-2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6b3dabda836317e63c5ad052826e156610f356a04b3003dfa0dbe66b5d54d671", size = 954828, upload-time = "2025-10-24T07:17:15.726Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2e/71/704740cbc6e587dd6cc8dabf2f04820ac6a671784e57cc3c29db795476db/lupa-2.6-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8726d1c123bbe9fbb974ce29825e94121824e66003038ff4532c14cc2ed0c51c", size = 1919259, upload-time = "2025-10-24T07:17:18.586Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/eb/18/f248341c423c5d48837e35584c6c3eb4acab7e722b6057d7b3e28e42dae8/lupa-2.6-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:f4e159e7d814171199b246f9235ca8961f6461ea8c1165ab428afa13c9289a94", size = 984998, upload-time = "2025-10-24T07:17:20.428Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/44/1e/8a4bd471e018aad76bcb9455d298c2c96d82eced20f2ae8fcec8cd800948/lupa-2.6-cp310-cp310-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:202160e80dbfddfb79316692a563d843b767e0f6787bbd1c455f9d54052efa6c", size = 1174871, upload-time = "2025-10-24T07:17:22.755Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2a/5c/3a3f23fd6a91b0986eea1ceaf82ad3f9b958fe3515a9981fb9c4eb046c8b/lupa-2.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5deede7c5b36ab64f869dae4831720428b67955b0bb186c8349cf6ea121c852b", size = 1057471, upload-time = "2025-10-24T07:17:24.908Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/45/ac/01be1fed778fb0c8f46ee8cbe344e4d782f6806fac12717f08af87aa4355/lupa-2.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86f04901f920bbf7c0cac56807dc9597e42347123e6f1f3ca920f15f54188ce5", size = 2100592, upload-time = "2025-10-24T07:17:27.089Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3f/6c/1a05bb873e30830f8574e10cd0b4cdbc72e9dbad2a09e25810b5e3b1f75d/lupa-2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6deef8f851d6afb965c84849aa5b8c38856942df54597a811ce0369ced678610", size = 1081396, upload-time = "2025-10-24T07:17:29.064Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a2/c2/a19dd80d6dc98b39bbf8135b8198e38aa7ca3360b720eac68d1d7e9286b5/lupa-2.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:21f2b5549681c2a13b1170a26159d30875d367d28f0247b81ca347222c755038", size = 1192007, upload-time = "2025-10-24T07:17:31.362Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4f/43/e1b297225c827f55752e46fdbfb021c8982081b0f24490e42776ea69ae3b/lupa-2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:66eea57630eab5e6f49fdc5d7811c0a2a41f2011be4ea56a087ea76112011eb7", size = 2196661, upload-time = "2025-10-24T07:17:33.484Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2e/8f/2272d429a7fa9dc8dbd6e9c5c9073a03af6007eb22a4c78829fec6a34b80/lupa-2.6-cp310-cp310-win32.whl", hash = "sha256:60a403de8cab262a4fe813085dd77010effa6e2eb1886db2181df803140533b1", size = 1412738, upload-time = "2025-10-24T07:17:35.11Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/35/2a/1708911271dd49ad87b4b373b5a4b0e0a0516d3d2af7b76355946c7ee171/lupa-2.6-cp310-cp310-win_amd64.whl", hash = "sha256:e4656a39d93dfa947cf3db56dc16c7916cb0cc8024acd3a952071263f675df64", size = 1656898, upload-time = "2025-10-24T07:17:36.949Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ca/29/1f66907c1ebf1881735afa695e646762c674f00738ebf66d795d59fc0665/lupa-2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6d988c0f9331b9f2a5a55186701a25444ab10a1432a1021ee58011499ecbbdd5", size = 962875, upload-time = "2025-10-24T07:17:39.107Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/67/4a748604be360eb9c1c215f6a0da921cd1a2b44b2c5951aae6fb83019d3a/lupa-2.6-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:ebe1bbf48259382c72a6fe363dea61a0fd6fe19eab95e2ae881e20f3654587bf", size = 1935390, upload-time = "2025-10-24T07:17:41.427Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ac/0c/8ef9ee933a350428b7bdb8335a37ef170ab0bb008bbf9ca8f4f4310116b6/lupa-2.6-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:a8fcee258487cf77cdd41560046843bb38c2e18989cd19671dd1e2596f798306", size = 992193, upload-time = "2025-10-24T07:17:43.231Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/65/46/e6c7facebdb438db8a65ed247e56908818389c1a5abbf6a36aab14f1057d/lupa-2.6-cp311-cp311-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:561a8e3be800827884e767a694727ed8482d066e0d6edfcbf423b05e63b05535", size = 1165844, upload-time = "2025-10-24T07:17:45.437Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1c/26/9f1154c6c95f175ccbf96aa96c8f569c87f64f463b32473e839137601a8b/lupa-2.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af880a62d47991cae78b8e9905c008cbfdc4a3a9723a66310c2634fc7644578c", size = 1048069, upload-time = "2025-10-24T07:17:47.181Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/68/67/2cc52ab73d6af81612b2ea24c870d3fa398443af8e2875e5befe142398b1/lupa-2.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:80b22923aa4023c86c0097b235615f89d469a0c4eee0489699c494d3367c4c85", size = 2079079, upload-time = "2025-10-24T07:17:49.755Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2e/dc/f843f09bbf325f6e5ee61730cf6c3409fc78c010d968c7c78acba3019ca7/lupa-2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:153d2cc6b643f7efb9cfc0c6bb55ec784d5bac1a3660cfc5b958a7b8f38f4a75", size = 1071428, upload-time = "2025-10-24T07:17:51.991Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2e/60/37533a8d85bf004697449acb97ecdacea851acad28f2ad3803662487dd2a/lupa-2.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3fa8777e16f3ded50b72967dc17e23f5a08e4f1e2c9456aff2ebdb57f5b2869f", size = 1181756, upload-time = "2025-10-24T07:17:53.752Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e4/f2/cf29b20dbb4927b6a3d27c339ac5d73e74306ecc28c8e2c900b2794142ba/lupa-2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8dbdcbe818c02a2f56f5ab5ce2de374dab03e84b25266cfbaef237829bc09b3f", size = 2175687, upload-time = "2025-10-24T07:17:56.228Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/7c/050e02f80c7131b63db1474bff511e63c545b5a8636a24cbef3fc4da20b6/lupa-2.6-cp311-cp311-win32.whl", hash = "sha256:defaf188fde8f7a1e5ce3a5e6d945e533b8b8d547c11e43b96c9b7fe527f56dc", size = 1412592, upload-time = "2025-10-24T07:17:59.062Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6f/9a/6f2af98aa5d771cea661f66c8eb8f53772ec1ab1dfbce24126cfcd189436/lupa-2.6-cp311-cp311-win_amd64.whl", hash = "sha256:9505ae600b5c14f3e17e70f87f88d333717f60411faca1ddc6f3e61dce85fa9e", size = 1669194, upload-time = "2025-10-24T07:18:01.647Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/86/ce243390535c39d53ea17ccf0240815e6e457e413e40428a658ea4ee4b8d/lupa-2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:47ce718817ef1cc0c40d87c3d5ae56a800d61af00fbc0fad1ca9be12df2f3b56", size = 951707, upload-time = "2025-10-24T07:18:03.884Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/86/85/cedea5e6cbeb54396fdcc55f6b741696f3f036d23cfaf986d50d680446da/lupa-2.6-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:7aba985b15b101495aa4b07112cdc08baa0c545390d560ad5cfde2e9e34f4d58", size = 1916703, upload-time = "2025-10-24T07:18:05.6Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/24/be/3d6b5f9a8588c01a4d88129284c726017b2089f3a3fd3ba8bd977292fea0/lupa-2.6-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:b766f62f95b2739f2248977d29b0722e589dcf4f0ccfa827ccbd29f0148bd2e5", size = 985152, upload-time = "2025-10-24T07:18:08.561Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/eb/23/9f9a05beee5d5dce9deca4cb07c91c40a90541fc0a8e09db4ee670da550f/lupa-2.6-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:00a934c23331f94cb51760097ebfab14b005d55a6b30a2b480e3c53dd2fa290d", size = 1159599, upload-time = "2025-10-24T07:18:10.346Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/40/4e/e7c0583083db9d7f1fd023800a9767d8e4391e8330d56c2373d890ac971b/lupa-2.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21de9f38bd475303e34a042b7081aabdf50bd9bafd36ce4faea2f90fd9f15c31", size = 1038686, upload-time = "2025-10-24T07:18:12.112Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1c/9f/5a4f7d959d4feba5e203ff0c31889e74d1ca3153122be4a46dca7d92bf7c/lupa-2.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf3bda96d3fc41237e964a69c23647d50d4e28421111360274d4799832c560e9", size = 2071956, upload-time = "2025-10-24T07:18:14.572Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/92/34/2f4f13ca65d01169b1720176aedc4af17bc19ee834598c7292db232cb6dc/lupa-2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a76ead245da54801a81053794aa3975f213221f6542d14ec4b859ee2e7e0323", size = 1057199, upload-time = "2025-10-24T07:18:16.379Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/35/2a/5f7d2eebec6993b0dcd428e0184ad71afb06a45ba13e717f6501bfed1da3/lupa-2.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8dd0861741caa20886ddbda0a121d8e52fb9b5bb153d82fa9bba796962bf30e8", size = 1173693, upload-time = "2025-10-24T07:18:18.153Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e4/29/089b4d2f8e34417349af3904bb40bec40b65c8731f45e3fd8d497ca573e5/lupa-2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:239e63948b0b23023f81d9a19a395e768ed3da6a299f84e7963b8f813f6e3f9c", size = 2164394, upload-time = "2025-10-24T07:18:20.403Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f3/1b/79c17b23c921f81468a111cad843b076a17ef4b684c4a8dff32a7969c3f0/lupa-2.6-cp312-cp312-win32.whl", hash = "sha256:325894e1099499e7a6f9c351147661a2011887603c71086d36fe0f964d52d1ce", size = 1420647, upload-time = "2025-10-24T07:18:23.368Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b8/15/5121e68aad3584e26e1425a5c9a79cd898f8a152292059e128c206ee817c/lupa-2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c735a1ce8ee60edb0fe71d665f1e6b7c55c6021f1d340eb8c865952c602cd36f", size = 1688529, upload-time = "2025-10-24T07:18:25.523Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/28/1d/21176b682ca5469001199d8b95fa1737e29957a3d185186e7a8b55345f2e/lupa-2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:663a6e58a0f60e7d212017d6678639ac8df0119bc13c2145029dcba084391310", size = 947232, upload-time = "2025-10-24T07:18:27.878Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/4c/d327befb684660ca13cf79cd1f1d604331808f9f1b6fb6bf57832f8edf80/lupa-2.6-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:d1f5afda5c20b1f3217a80e9bc1b77037f8a6eb11612fd3ada19065303c8f380", size = 1908625, upload-time = "2025-10-24T07:18:29.944Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/66/8e/ad22b0a19454dfd08662237a84c792d6d420d36b061f239e084f29d1a4f3/lupa-2.6-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:26f2b3c085fe76e9119e48c1013c1cccdc1f51585d456858290475aa38e7089e", size = 981057, upload-time = "2025-10-24T07:18:31.553Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/48/74859073ab276bd0566c719f9ca0108b0cfc1956ca0d68678d117d47d155/lupa-2.6-cp313-cp313-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:60d2f902c7b96fb8ab98493dcff315e7bb4d0b44dc9dd76eb37de575025d5685", size = 1156227, upload-time = "2025-10-24T07:18:33.981Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/09/6c/0e9ded061916877253c2266074060eb71ed99fb21d73c8c114a76725bce2/lupa-2.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a02d25dee3a3250967c36590128d9220ae02f2eda166a24279da0b481519cbff", size = 1035752, upload-time = "2025-10-24T07:18:36.32Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dd/ef/f8c32e454ef9f3fe909f6c7d57a39f950996c37a3deb7b391fec7903dab7/lupa-2.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6eae1ee16b886b8914ff292dbefbf2f48abfbdee94b33a88d1d5475e02423203", size = 2069009, upload-time = "2025-10-24T07:18:38.072Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/53/dc/15b80c226a5225815a890ee1c11f07968e0aba7a852df41e8ae6fe285063/lupa-2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0edd5073a4ee74ab36f74fe61450148e6044f3952b8d21248581f3c5d1a58be", size = 1056301, upload-time = "2025-10-24T07:18:40.165Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/31/14/2086c1425c985acfb30997a67e90c39457122df41324d3c179d6ee2292c6/lupa-2.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0c53ee9f22a8a17e7d4266ad48e86f43771951797042dd51d1494aaa4f5f3f0a", size = 1170673, upload-time = "2025-10-24T07:18:42.426Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/10/e5/b216c054cf86576c0191bf9a9f05de6f7e8e07164897d95eea0078dca9b2/lupa-2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:de7c0f157a9064a400d828789191a96da7f4ce889969a588b87ec80de9b14772", size = 2162227, upload-time = "2025-10-24T07:18:46.112Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/59/2f/33ecb5bedf4f3bc297ceacb7f016ff951331d352f58e7e791589609ea306/lupa-2.6-cp313-cp313-win32.whl", hash = "sha256:ee9523941ae0a87b5b703417720c5d78f72d2f5bc23883a2ea80a949a3ed9e75", size = 1419558, upload-time = "2025-10-24T07:18:48.371Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f9/b4/55e885834c847ea610e111d87b9ed4768f0afdaeebc00cd46810f25029f6/lupa-2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b1335a5835b0a25ebdbc75cf0bda195e54d133e4d994877ef025e218c2e59db9", size = 1683424, upload-time = "2025-10-24T07:18:50.976Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/66/9d/d9427394e54d22a35d1139ef12e845fd700d4872a67a34db32516170b746/lupa-2.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dcb6d0a3264873e1653bc188499f48c1fb4b41a779e315eba45256cfe7bc33c1", size = 953818, upload-time = "2025-10-24T07:18:53.378Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/10/41/27bbe81953fb2f9ecfced5d9c99f85b37964cfaf6aa8453bb11283983721/lupa-2.6-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:a37e01f2128f8c36106726cb9d360bac087d58c54b4522b033cc5691c584db18", size = 1915850, upload-time = "2025-10-24T07:18:55.259Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a3/98/f9ff60db84a75ba8725506bbf448fb085bc77868a021998ed2a66d920568/lupa-2.6-cp314-cp314-macosx_11_0_x86_64.whl", hash = "sha256:458bd7e9ff3c150b245b0fcfbb9bd2593d1152ea7f0a7b91c1d185846da033fe", size = 982344, upload-time = "2025-10-24T07:18:57.05Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/41/f7/f39e0f1c055c3b887d86b404aaf0ca197b5edfd235a8b81b45b25bac7fc3/lupa-2.6-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:052ee82cac5206a02df77119c325339acbc09f5ce66967f66a2e12a0f3211cad", size = 1156543, upload-time = "2025-10-24T07:18:59.251Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9e/9c/59e6cffa0d672d662ae17bd7ac8ecd2c89c9449dee499e3eb13ca9cd10d9/lupa-2.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96594eca3c87dd07938009e95e591e43d554c1dbd0385be03c100367141db5a8", size = 1047974, upload-time = "2025-10-24T07:19:01.449Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/23/c6/a04e9cef7c052717fcb28fb63b3824802488f688391895b618e39be0f684/lupa-2.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8faddd9d198688c8884091173a088a8e920ecc96cda2ffed576a23574c4b3f6", size = 2073458, upload-time = "2025-10-24T07:19:03.369Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/10/824173d10f38b51fc77785228f01411b6ca28826ce27404c7c912e0e442c/lupa-2.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:daebb3a6b58095c917e76ba727ab37b27477fb926957c825205fbda431552134", size = 1067683, upload-time = "2025-10-24T07:19:06.2Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b6/dc/9692fbcf3c924d9c4ece2d8d2f724451ac2e09af0bd2a782db1cef34e799/lupa-2.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f3154e68972befe0f81564e37d8142b5d5d79931a18309226a04ec92487d4ea3", size = 1171892, upload-time = "2025-10-24T07:19:08.544Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/84/ff/e318b628d4643c278c96ab3ddea07fc36b075a57383c837f5b11e537ba9d/lupa-2.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e4dadf77b9fedc0bfa53417cc28dc2278a26d4cbd95c29f8927ad4d8fe0a7ef9", size = 2166641, upload-time = "2025-10-24T07:19:10.485Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/12/f7/a6f9ec2806cf2d50826980cdb4b3cffc7691dc6f95e13cc728846d5cb793/lupa-2.6-cp314-cp314-win32.whl", hash = "sha256:cb34169c6fa3bab3e8ac58ca21b8a7102f6a94b6a5d08d3636312f3f02fafd8f", size = 1456857, upload-time = "2025-10-24T07:19:37.989Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c5/de/df71896f25bdc18360fdfa3b802cd7d57d7fede41a0e9724a4625b412c85/lupa-2.6-cp314-cp314-win_amd64.whl", hash = "sha256:b74f944fe46c421e25d0f8692aef1e842192f6f7f68034201382ac440ef9ea67", size = 1731191, upload-time = "2025-10-24T07:19:40.281Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/47/3c/a1f23b01c54669465f5f4c4083107d496fbe6fb45998771420e9aadcf145/lupa-2.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0e21b716408a21ab65723f8841cf7f2f37a844b7a965eeabb785e27fca4099cf", size = 999343, upload-time = "2025-10-24T07:19:12.519Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c5/6d/501994291cb640bfa2ccf7f554be4e6914afa21c4026bd01bff9ca8aac57/lupa-2.6-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:589db872a141bfff828340079bbdf3e9a31f2689f4ca0d88f97d9e8c2eae6142", size = 2000730, upload-time = "2025-10-24T07:19:14.869Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/53/a5/457ffb4f3f20469956c2d4c4842a7675e884efc895b2f23d126d23e126cc/lupa-2.6-cp314-cp314t-macosx_11_0_x86_64.whl", hash = "sha256:cd852a91a4a9d4dcbb9a58100f820a75a425703ec3e3f049055f60b8533b7953", size = 1021553, upload-time = "2025-10-24T07:19:17.123Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/51/6b/36bb5a5d0960f2a5c7c700e0819abb76fd9bf9c1d8a66e5106416d6e9b14/lupa-2.6-cp314-cp314t-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:0334753be028358922415ca97a64a3048e4ed155413fc4eaf87dd0a7e2752983", size = 1133275, upload-time = "2025-10-24T07:19:20.51Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/19/86/202ff4429f663013f37d2229f6176ca9f83678a50257d70f61a0a97281bf/lupa-2.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:661d895cd38c87658a34780fac54a690ec036ead743e41b74c3fb81a9e65a6aa", size = 1038441, upload-time = "2025-10-24T07:19:22.509Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a7/42/d8125f8e420714e5b52e9c08d88b5329dfb02dcca731b4f21faaee6cc5b5/lupa-2.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aa58454ccc13878cc177c62529a2056be734da16369e451987ff92784994ca7", size = 2058324, upload-time = "2025-10-24T07:19:24.979Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2b/2c/47bf8b84059876e877a339717ddb595a4a7b0e8740bacae78ba527562e1c/lupa-2.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1425017264e470c98022bba8cff5bd46d054a827f5df6b80274f9cc71dafd24f", size = 1060250, upload-time = "2025-10-24T07:19:27.262Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c2/06/d88add2b6406ca1bdec99d11a429222837ca6d03bea42ca75afa169a78cb/lupa-2.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:224af0532d216e3105f0a127410f12320f7c5f1aa0300bdf9646b8d9afb0048c", size = 1151126, upload-time = "2025-10-24T07:19:29.522Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/a0/89e6a024c3b4485b89ef86881c9d55e097e7cb0bdb74efb746f2fa6a9a76/lupa-2.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9abb98d5a8fd27c8285302e82199f0e56e463066f88f619d6594a450bf269d80", size = 2153693, upload-time = "2025-10-24T07:19:31.379Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b6/36/a0f007dc58fc1bbf51fb85dcc82fcb1f21b8c4261361de7dab0e3d8521ef/lupa-2.6-cp314-cp314t-win32.whl", hash = "sha256:1849efeba7a8f6fb8aa2c13790bee988fd242ae404bd459509640eeea3d1e291", size = 1590104, upload-time = "2025-10-24T07:19:33.514Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7d/5e/db903ce9cf82c48d6b91bf6d63ae4c8d0d17958939a4e04ba6b9f38b8643/lupa-2.6-cp314-cp314t-win_amd64.whl", hash = "sha256:fc1498d1a4fc028bc521c26d0fad4ca00ed63b952e32fb95949bda76a04bad52", size = 1913818, upload-time = "2025-10-24T07:19:36.039Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "markdown-it-py"
|
name = "markdown-it-py"
|
||||||
version = "3.0.0"
|
version = "3.0.0"
|
||||||
|
|
@ -671,7 +784,7 @@ wheels = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mcp"
|
name = "mcp"
|
||||||
version = "1.20.0"
|
version = "1.25.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "anyio" },
|
{ name = "anyio" },
|
||||||
|
|
@ -685,16 +798,18 @@ dependencies = [
|
||||||
{ name = "pywin32", marker = "sys_platform == 'win32'" },
|
{ name = "pywin32", marker = "sys_platform == 'win32'" },
|
||||||
{ name = "sse-starlette" },
|
{ name = "sse-starlette" },
|
||||||
{ name = "starlette" },
|
{ name = "starlette" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
{ name = "typing-inspection" },
|
||||||
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
|
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/f8/22/fae38092e6c2995c03232635028510d77e7decff31b4ae79dfa0ba99c635/mcp-1.20.0.tar.gz", hash = "sha256:9ccc09eaadbfbcbbdab1c9723cfe2e0d1d9e324d7d3ce7e332ef90b09ed35177", size = 451377, upload-time = "2025-10-30T22:14:53.421Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/d5/2d/649d80a0ecf6a1f82632ca44bec21c0461a9d9fc8934d38cb5b319f2db5e/mcp-1.25.0.tar.gz", hash = "sha256:56310361ebf0364e2d438e5b45f7668cbb124e158bb358333cd06e49e83a6802", size = 605387, upload-time = "2025-12-19T10:19:56.985Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/df/00/76fc92f4892d47fecb37131d0e95ea69259f077d84c68f6793a0d96cfe80/mcp-1.20.0-py3-none-any.whl", hash = "sha256:d0dc06f93653f7432ff89f694721c87f79876b6f93741bf628ad1e48f7ac5e5d", size = 173136, upload-time = "2025-10-30T22:14:51.078Z" },
|
{ url = "https://files.pythonhosted.org/packages/e2/fc/6dc7659c2ae5ddf280477011f4213a74f806862856b796ef08f028e664bf/mcp-1.25.0-py3-none-any.whl", hash = "sha256:b37c38144a666add0862614cc79ec276e97d72aa8ca26d622818d4e278b9721a", size = 233076, upload-time = "2025-12-19T10:19:55.416Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mcpforunityserver"
|
name = "mcpforunityserver"
|
||||||
version = "8.2.3"
|
version = "8.3.0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "fastapi" },
|
{ name = "fastapi" },
|
||||||
|
|
@ -715,7 +830,7 @@ dev = [
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "fastapi", specifier = ">=0.104.0" },
|
{ name = "fastapi", specifier = ">=0.104.0" },
|
||||||
{ name = "fastmcp", specifier = ">=2.13.0,<2.13.2" },
|
{ name = "fastmcp", specifier = "==2.14.1" },
|
||||||
{ name = "httpx", specifier = ">=0.27.2" },
|
{ name = "httpx", specifier = ">=0.27.2" },
|
||||||
{ name = "mcp", specifier = ">=1.16.0" },
|
{ name = "mcp", specifier = ">=1.16.0" },
|
||||||
{ name = "pydantic", specifier = ">=2.12.5" },
|
{ name = "pydantic", specifier = ">=2.12.5" },
|
||||||
|
|
@ -756,6 +871,75 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/12/cf/03675d8bd8ecbf4445504d8071adab19f5f993676795708e36402ab38263/openapi_pydantic-0.5.1-py3-none-any.whl", hash = "sha256:a3a09ef4586f5bd760a8df7f43028b60cafb6d9f61de2acba9574766255ab146", size = 96381, upload-time = "2025-01-08T19:29:25.275Z" },
|
{ url = "https://files.pythonhosted.org/packages/12/cf/03675d8bd8ecbf4445504d8071adab19f5f993676795708e36402ab38263/openapi_pydantic-0.5.1-py3-none-any.whl", hash = "sha256:a3a09ef4586f5bd760a8df7f43028b60cafb6d9f61de2acba9574766255ab146", size = 96381, upload-time = "2025-01-08T19:29:25.275Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opentelemetry-api"
|
||||||
|
version = "1.39.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "importlib-metadata" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/97/b9/3161be15bb8e3ad01be8be5a968a9237c3027c5be504362ff800fca3e442/opentelemetry_api-1.39.1.tar.gz", hash = "sha256:fbde8c80e1b937a2c61f20347e91c0c18a1940cecf012d62e65a7caf08967c9c", size = 65767, upload-time = "2025-12-11T13:32:39.182Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cf/df/d3f1ddf4bb4cb50ed9b1139cc7b1c54c34a1e7ce8fd1b9a37c0d1551a6bd/opentelemetry_api-1.39.1-py3-none-any.whl", hash = "sha256:2edd8463432a7f8443edce90972169b195e7d6a05500cd29e6d13898187c9950", size = 66356, upload-time = "2025-12-11T13:32:17.304Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opentelemetry-exporter-prometheus"
|
||||||
|
version = "0.60b1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "opentelemetry-api" },
|
||||||
|
{ name = "opentelemetry-sdk" },
|
||||||
|
{ name = "prometheus-client" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/14/39/7dafa6fff210737267bed35a8855b6ac7399b9e582b8cf1f25f842517012/opentelemetry_exporter_prometheus-0.60b1.tar.gz", hash = "sha256:a4011b46906323f71724649d301b4dc188aaa068852e814f4df38cc76eac616b", size = 14976, upload-time = "2025-12-11T13:32:42.944Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9b/0d/4be6bf5477a3eb3d917d2f17d3c0b6720cd6cb97898444a61d43cc983f5c/opentelemetry_exporter_prometheus-0.60b1-py3-none-any.whl", hash = "sha256:49f59178de4f4590e3cef0b8b95cf6e071aae70e1f060566df5546fad773b8fd", size = 13019, upload-time = "2025-12-11T13:32:23.974Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opentelemetry-instrumentation"
|
||||||
|
version = "0.60b1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "opentelemetry-api" },
|
||||||
|
{ name = "opentelemetry-semantic-conventions" },
|
||||||
|
{ name = "packaging" },
|
||||||
|
{ name = "wrapt" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/41/0f/7e6b713ac117c1f5e4e3300748af699b9902a2e5e34c9cf443dde25a01fa/opentelemetry_instrumentation-0.60b1.tar.gz", hash = "sha256:57ddc7974c6eb35865af0426d1a17132b88b2ed8586897fee187fd5b8944bd6a", size = 31706, upload-time = "2025-12-11T13:36:42.515Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/77/d2/6788e83c5c86a2690101681aeef27eeb2a6bf22df52d3f263a22cee20915/opentelemetry_instrumentation-0.60b1-py3-none-any.whl", hash = "sha256:04480db952b48fb1ed0073f822f0ee26012b7be7c3eac1a3793122737c78632d", size = 33096, upload-time = "2025-12-11T13:35:33.067Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opentelemetry-sdk"
|
||||||
|
version = "1.39.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "opentelemetry-api" },
|
||||||
|
{ name = "opentelemetry-semantic-conventions" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/eb/fb/c76080c9ba07e1e8235d24cdcc4d125ef7aa3edf23eb4e497c2e50889adc/opentelemetry_sdk-1.39.1.tar.gz", hash = "sha256:cf4d4563caf7bff906c9f7967e2be22d0d6b349b908be0d90fb21c8e9c995cc6", size = 171460, upload-time = "2025-12-11T13:32:49.369Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/98/e91cf858f203d86f4eccdf763dcf01cf03f1dae80c3750f7e635bfa206b6/opentelemetry_sdk-1.39.1-py3-none-any.whl", hash = "sha256:4d5482c478513ecb0a5d938dcc61394e647066e0cc2676bee9f3af3f3f45f01c", size = 132565, upload-time = "2025-12-11T13:32:35.069Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opentelemetry-semantic-conventions"
|
||||||
|
version = "0.60b1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "opentelemetry-api" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/91/df/553f93ed38bf22f4b999d9be9c185adb558982214f33eae539d3b5cd0858/opentelemetry_semantic_conventions-0.60b1.tar.gz", hash = "sha256:87c228b5a0669b748c76d76df6c364c369c28f1c465e50f661e39737e84bc953", size = 137935, upload-time = "2025-12-11T13:32:50.487Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7a/5e/5958555e09635d09b75de3c4f8b9cae7335ca545d77392ffe7331534c402/opentelemetry_semantic_conventions-0.60b1-py3-none-any.whl", hash = "sha256:9fa8c8b0c110da289809292b0591220d3a7b53c1526a23021e977d68597893fb", size = 219982, upload-time = "2025-12-11T13:32:36.955Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "packaging"
|
name = "packaging"
|
||||||
version = "25.0"
|
version = "25.0"
|
||||||
|
|
@ -801,17 +985,26 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prometheus-client"
|
||||||
|
version = "0.23.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/23/53/3edb5d68ecf6b38fcbcc1ad28391117d2a322d9a1a3eff04bfdb184d8c3b/prometheus_client-0.23.1.tar.gz", hash = "sha256:6ae8f9081eaaaf153a2e959d2e6c4f4fb57b12ef76c8c7980202f1e57b48b2ce", size = 80481, upload-time = "2025-09-18T20:47:25.043Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl", hash = "sha256:dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99", size = 61145, upload-time = "2025-09-18T20:47:23.875Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "py-key-value-aio"
|
name = "py-key-value-aio"
|
||||||
version = "0.2.8"
|
version = "0.3.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "beartype" },
|
{ name = "beartype" },
|
||||||
{ name = "py-key-value-shared" },
|
{ name = "py-key-value-shared" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/ca/35/65310a4818acec0f87a46e5565e341c5a96fc062a9a03495ad28828ff4d7/py_key_value_aio-0.2.8.tar.gz", hash = "sha256:c0cfbb0bd4e962a3fa1a9fa6db9ba9df812899bd9312fa6368aaea7b26008b36", size = 32853, upload-time = "2025-10-24T13:31:04.688Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/93/ce/3136b771dddf5ac905cc193b461eb67967cf3979688c6696e1f2cdcde7ea/py_key_value_aio-0.3.0.tar.gz", hash = "sha256:858e852fcf6d696d231266da66042d3355a7f9871650415feef9fca7a6cd4155", size = 50801, upload-time = "2025-11-17T16:50:04.711Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/cd/5a/e56747d87a97ad2aff0f3700d77f186f0704c90c2da03bfed9e113dae284/py_key_value_aio-0.2.8-py3-none-any.whl", hash = "sha256:561565547ce8162128fd2bd0b9d70ce04a5f4586da8500cce79a54dfac78c46a", size = 69200, upload-time = "2025-10-24T13:31:03.81Z" },
|
{ url = "https://files.pythonhosted.org/packages/99/10/72f6f213b8f0bce36eff21fda0a13271834e9eeff7f9609b01afdc253c79/py_key_value_aio-0.3.0-py3-none-any.whl", hash = "sha256:1c781915766078bfd608daa769fefb97e65d1d73746a3dfb640460e322071b64", size = 96342, upload-time = "2025-11-17T16:50:03.801Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.optional-dependencies]
|
[package.optional-dependencies]
|
||||||
|
|
@ -825,18 +1018,21 @@ keyring = [
|
||||||
memory = [
|
memory = [
|
||||||
{ name = "cachetools" },
|
{ name = "cachetools" },
|
||||||
]
|
]
|
||||||
|
redis = [
|
||||||
|
{ name = "redis" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "py-key-value-shared"
|
name = "py-key-value-shared"
|
||||||
version = "0.2.8"
|
version = "0.3.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "beartype" },
|
{ name = "beartype" },
|
||||||
{ name = "typing-extensions" },
|
{ name = "typing-extensions" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/26/79/05a1f9280cfa0709479319cbfd2b1c5beb23d5034624f548c83fb65b0b61/py_key_value_shared-0.2.8.tar.gz", hash = "sha256:703b4d3c61af124f0d528ba85995c3c8d78f8bd3d2b217377bd3278598070cc1", size = 8216, upload-time = "2025-10-24T13:31:03.601Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/7b/e4/1971dfc4620a3a15b4579fe99e024f5edd6e0967a71154771a059daff4db/py_key_value_shared-0.3.0.tar.gz", hash = "sha256:8fdd786cf96c3e900102945f92aa1473138ebe960ef49da1c833790160c28a4b", size = 11666, upload-time = "2025-11-17T16:50:06.849Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/84/7a/1726ceaa3343874f322dd83c9ec376ad81f533df8422b8b1e1233a59f8ce/py_key_value_shared-0.2.8-py3-none-any.whl", hash = "sha256:aff1bbfd46d065b2d67897d298642e80e5349eae588c6d11b48452b46b8d46ba", size = 14586, upload-time = "2025-10-24T13:31:02.838Z" },
|
{ url = "https://files.pythonhosted.org/packages/51/e4/b8b0a03ece72f47dce2307d36e1c34725b7223d209fc679315ffe6a4e2c3/py_key_value_shared-0.3.0-py3-none-any.whl", hash = "sha256:5b0efba7ebca08bb158b1e93afc2f07d30b8f40c2fc12ce24a4c0d84f42f9298", size = 19560, upload-time = "2025-11-17T16:50:05.954Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -999,6 +1195,30 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/0b/53/a64f03044927dc47aafe029c42a5b7aabc38dfb813475e0e1bf71c4a59d0/pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c", size = 30839, upload-time = "2025-02-27T10:10:30.711Z" },
|
{ url = "https://files.pythonhosted.org/packages/0b/53/a64f03044927dc47aafe029c42a5b7aabc38dfb813475e0e1bf71c4a59d0/pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c", size = 30839, upload-time = "2025-02-27T10:10:30.711Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydocket"
|
||||||
|
version = "0.16.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "cloudpickle" },
|
||||||
|
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
||||||
|
{ name = "fakeredis", extra = ["lua"] },
|
||||||
|
{ name = "opentelemetry-api" },
|
||||||
|
{ name = "opentelemetry-exporter-prometheus" },
|
||||||
|
{ name = "opentelemetry-instrumentation" },
|
||||||
|
{ name = "prometheus-client" },
|
||||||
|
{ name = "py-key-value-aio", extra = ["memory", "redis"] },
|
||||||
|
{ name = "python-json-logger" },
|
||||||
|
{ name = "redis" },
|
||||||
|
{ name = "rich" },
|
||||||
|
{ name = "typer" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/e0/c5/61dcfce4d50b66a3f09743294d37fab598b81bb0975054b7f732da9243ec/pydocket-0.16.3.tar.gz", hash = "sha256:78e9da576de09e9f3f410d2471ef1c679b7741ddd21b586c97a13872b69bd265", size = 297080, upload-time = "2025-12-23T23:37:33.32Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2c/94/93b7f5981aa04f922e0d9ce7326a4587866ec7e39f7c180ffcf408e66ee8/pydocket-0.16.3-py3-none-any.whl", hash = "sha256:e2b50925356e7cd535286255195458ac7bba15f25293356651b36d223db5dd7c", size = 67087, upload-time = "2025-12-23T23:37:31.829Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pygments"
|
name = "pygments"
|
||||||
version = "2.19.1"
|
version = "2.19.1"
|
||||||
|
|
@ -1072,6 +1292,15 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" },
|
{ url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-json-logger"
|
||||||
|
version = "4.0.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/29/bf/eca6a3d43db1dae7070f70e160ab20b807627ba953663ba07928cdd3dc58/python_json_logger-4.0.0.tar.gz", hash = "sha256:f58e68eb46e1faed27e0f574a55a0455eecd7b8a5b88b85a784519ba3cff047f", size = 17683, upload-time = "2025-10-06T04:15:18.984Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl", hash = "sha256:af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2", size = 15548, upload-time = "2025-10-06T04:15:17.553Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-multipart"
|
name = "python-multipart"
|
||||||
version = "0.0.20"
|
version = "0.0.20"
|
||||||
|
|
@ -1176,6 +1405,18 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
|
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redis"
|
||||||
|
version = "7.1.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "async-timeout", marker = "python_full_version < '3.11.3'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/43/c8/983d5c6579a411d8a99bc5823cc5712768859b5ce2c8afe1a65b37832c81/redis-7.1.0.tar.gz", hash = "sha256:b1cc3cfa5a2cb9c2ab3ba700864fb0ad75617b41f01352ce5779dabf6d5f9c3c", size = 4796669, upload-time = "2025-11-19T15:54:39.961Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/f0/8956f8a86b20d7bb9d6ac0187cf4cd54d8065bc9a1a09eb8011d4d326596/redis-7.1.0-py3-none-any.whl", hash = "sha256:23c52b208f92b56103e17c5d06bdc1a6c2c0b3106583985a76a18f83b265de2b", size = 354159, upload-time = "2025-11-19T15:54:38.064Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "referencing"
|
name = "referencing"
|
||||||
version = "0.36.2"
|
version = "0.36.2"
|
||||||
|
|
@ -1380,6 +1621,15 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" },
|
{ url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shellingham"
|
||||||
|
version = "1.5.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sniffio"
|
name = "sniffio"
|
||||||
version = "1.3.1"
|
version = "1.3.1"
|
||||||
|
|
@ -1389,6 +1639,15 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
|
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sortedcontainers"
|
||||||
|
version = "2.4.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sse-starlette"
|
name = "sse-starlette"
|
||||||
version = "2.2.1"
|
version = "2.2.1"
|
||||||
|
|
@ -1463,6 +1722,21 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" },
|
{ url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typer"
|
||||||
|
version = "0.21.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "click" },
|
||||||
|
{ name = "rich" },
|
||||||
|
{ name = "shellingham" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/85/30/ff9ede605e3bd086b4dd842499814e128500621f7951ca1e5ce84bbf61b1/typer-0.21.0.tar.gz", hash = "sha256:c87c0d2b6eee3b49c5c64649ec92425492c14488096dfbc8a0c2799b2f6f9c53", size = 106781, upload-time = "2025-12-25T09:54:53.651Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/e4/5ebc1899d31d2b1601b32d21cfb4bba022ae6fce323d365f0448031b1660/typer-0.21.0-py3-none-any.whl", hash = "sha256:c79c01ca6b30af9fd48284058a7056ba0d3bf5cf10d0ff3d0c5b11b68c258ac6", size = 47109, upload-time = "2025-12-25T09:54:51.918Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typing-extensions"
|
name = "typing-extensions"
|
||||||
version = "4.15.0"
|
version = "4.15.0"
|
||||||
|
|
@ -1566,6 +1840,75 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" },
|
{ url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wrapt"
|
||||||
|
version = "1.17.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3f/23/bb82321b86411eb51e5a5db3fb8f8032fd30bd7c2d74bfe936136b2fa1d6/wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04", size = 53482, upload-time = "2025-08-12T05:51:44.467Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/45/69/f3c47642b79485a30a59c63f6d739ed779fb4cc8323205d047d741d55220/wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2", size = 38676, upload-time = "2025-08-12T05:51:32.636Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/71/e7e7f5670c1eafd9e990438e69d8fb46fa91a50785332e06b560c869454f/wrapt-1.17.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd341868a4b6714a5962c1af0bd44f7c404ef78720c7de4892901e540417111c", size = 38957, upload-time = "2025-08-12T05:51:54.655Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/de/17/9f8f86755c191d6779d7ddead1a53c7a8aa18bccb7cea8e7e72dfa6a8a09/wrapt-1.17.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f9b2601381be482f70e5d1051a5965c25fb3625455a2bf520b5a077b22afb775", size = 81975, upload-time = "2025-08-12T05:52:30.109Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f2/15/dd576273491f9f43dd09fce517f6c2ce6eb4fe21681726068db0d0467096/wrapt-1.17.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:343e44b2a8e60e06a7e0d29c1671a0d9951f59174f3709962b5143f60a2a98bd", size = 83149, upload-time = "2025-08-12T05:52:09.316Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/c4/5eb4ce0d4814521fee7aa806264bf7a114e748ad05110441cd5b8a5c744b/wrapt-1.17.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:33486899acd2d7d3066156b03465b949da3fd41a5da6e394ec49d271baefcf05", size = 82209, upload-time = "2025-08-12T05:52:10.331Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/31/4b/819e9e0eb5c8dc86f60dfc42aa4e2c0d6c3db8732bce93cc752e604bb5f5/wrapt-1.17.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e6f40a8aa5a92f150bdb3e1c44b7e98fb7113955b2e5394122fa5532fec4b418", size = 81551, upload-time = "2025-08-12T05:52:31.137Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f8/83/ed6baf89ba3a56694700139698cf703aac9f0f9eb03dab92f57551bd5385/wrapt-1.17.3-cp310-cp310-win32.whl", hash = "sha256:a36692b8491d30a8c75f1dfee65bef119d6f39ea84ee04d9f9311f83c5ad9390", size = 36464, upload-time = "2025-08-12T05:53:01.204Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2f/90/ee61d36862340ad7e9d15a02529df6b948676b9a5829fd5e16640156627d/wrapt-1.17.3-cp310-cp310-win_amd64.whl", hash = "sha256:afd964fd43b10c12213574db492cb8f73b2f0826c8df07a68288f8f19af2ebe6", size = 38748, upload-time = "2025-08-12T05:53:00.209Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bd/c3/cefe0bd330d389c9983ced15d326f45373f4073c9f4a8c2f99b50bfea329/wrapt-1.17.3-cp310-cp310-win_arm64.whl", hash = "sha256:af338aa93554be859173c39c85243970dc6a289fa907402289eeae7543e1ae18", size = 36810, upload-time = "2025-08-12T05:52:51.906Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/52/db/00e2a219213856074a213503fdac0511203dceefff26e1daa15250cc01a0/wrapt-1.17.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:273a736c4645e63ac582c60a56b0acb529ef07f78e08dc6bfadf6a46b19c0da7", size = 53482, upload-time = "2025-08-12T05:51:45.79Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5e/30/ca3c4a5eba478408572096fe9ce36e6e915994dd26a4e9e98b4f729c06d9/wrapt-1.17.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5531d911795e3f935a9c23eb1c8c03c211661a5060aab167065896bbf62a5f85", size = 38674, upload-time = "2025-08-12T05:51:34.629Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/31/25/3e8cc2c46b5329c5957cec959cb76a10718e1a513309c31399a4dad07eb3/wrapt-1.17.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0610b46293c59a3adbae3dee552b648b984176f8562ee0dba099a56cfbe4df1f", size = 38959, upload-time = "2025-08-12T05:51:56.074Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5d/8f/a32a99fc03e4b37e31b57cb9cefc65050ea08147a8ce12f288616b05ef54/wrapt-1.17.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311", size = 82376, upload-time = "2025-08-12T05:52:32.134Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/31/57/4930cb8d9d70d59c27ee1332a318c20291749b4fba31f113c2f8ac49a72e/wrapt-1.17.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cccf4f81371f257440c88faed6b74f1053eef90807b77e31ca057b2db74edb1", size = 83604, upload-time = "2025-08-12T05:52:11.663Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a8/f3/1afd48de81d63dd66e01b263a6fbb86e1b5053b419b9b33d13e1f6d0f7d0/wrapt-1.17.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8a210b158a34164de8bb68b0e7780041a903d7b00c87e906fb69928bf7890d5", size = 82782, upload-time = "2025-08-12T05:52:12.626Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1e/d7/4ad5327612173b144998232f98a85bb24b60c352afb73bc48e3e0d2bdc4e/wrapt-1.17.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:79573c24a46ce11aab457b472efd8d125e5a51da2d1d24387666cd85f54c05b2", size = 82076, upload-time = "2025-08-12T05:52:33.168Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/59/e0adfc831674a65694f18ea6dc821f9fcb9ec82c2ce7e3d73a88ba2e8718/wrapt-1.17.3-cp311-cp311-win32.whl", hash = "sha256:c31eebe420a9a5d2887b13000b043ff6ca27c452a9a22fa71f35f118e8d4bf89", size = 36457, upload-time = "2025-08-12T05:53:03.936Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/83/88/16b7231ba49861b6f75fc309b11012ede4d6b0a9c90969d9e0db8d991aeb/wrapt-1.17.3-cp311-cp311-win_amd64.whl", hash = "sha256:0b1831115c97f0663cb77aa27d381237e73ad4f721391a9bfb2fe8bc25fa6e77", size = 38745, upload-time = "2025-08-12T05:53:02.885Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/1e/c4d4f3398ec073012c51d1c8d87f715f56765444e1a4b11e5180577b7e6e/wrapt-1.17.3-cp311-cp311-win_arm64.whl", hash = "sha256:5a7b3c1ee8265eb4c8f1b7d29943f195c00673f5ab60c192eba2d4a7eae5f46a", size = 36806, upload-time = "2025-08-12T05:52:53.368Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/41/cad1aba93e752f1f9268c77270da3c469883d56e2798e7df6240dcb2287b/wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0", size = 53998, upload-time = "2025-08-12T05:51:47.138Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/60/f8/096a7cc13097a1869fe44efe68dace40d2a16ecb853141394047f0780b96/wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba", size = 39020, upload-time = "2025-08-12T05:51:35.906Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/33/df/bdf864b8997aab4febb96a9ae5c124f700a5abd9b5e13d2a3214ec4be705/wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd", size = 39098, upload-time = "2025-08-12T05:51:57.474Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828", size = 88036, upload-time = "2025-08-12T05:52:34.784Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ca/38/2e1785df03b3d72d34fc6252d91d9d12dc27a5c89caef3335a1bbb8908ca/wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9", size = 88156, upload-time = "2025-08-12T05:52:13.599Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/8b/48cdb60fe0603e34e05cffda0b2a4adab81fd43718e11111a4b0100fd7c1/wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396", size = 87102, upload-time = "2025-08-12T05:52:14.56Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3c/51/d81abca783b58f40a154f1b2c56db1d2d9e0d04fa2d4224e357529f57a57/wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc", size = 87732, upload-time = "2025-08-12T05:52:36.165Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9e/b1/43b286ca1392a006d5336412d41663eeef1ad57485f3e52c767376ba7e5a/wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe", size = 36705, upload-time = "2025-08-12T05:53:07.123Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/28/de/49493f962bd3c586ab4b88066e967aa2e0703d6ef2c43aa28cb83bf7b507/wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c", size = 38877, upload-time = "2025-08-12T05:53:05.436Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f1/48/0f7102fe9cb1e8a5a77f80d4f0956d62d97034bbe88d33e94699f99d181d/wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6", size = 36885, upload-time = "2025-08-12T05:52:54.367Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132, upload-time = "2025-08-12T05:51:49.864Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091, upload-time = "2025-08-12T05:51:38.935Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172, upload-time = "2025-08-12T05:51:59.365Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163, upload-time = "2025-08-12T05:52:40.965Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963, upload-time = "2025-08-12T05:52:20.326Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945, upload-time = "2025-08-12T05:52:21.581Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857, upload-time = "2025-08-12T05:52:43.043Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178, upload-time = "2025-08-12T05:53:12.605Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310, upload-time = "2025-08-12T05:53:11.106Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266, upload-time = "2025-08-12T05:52:56.531Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544, upload-time = "2025-08-12T05:51:51.109Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283, upload-time = "2025-08-12T05:51:39.912Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366, upload-time = "2025-08-12T05:52:00.693Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571, upload-time = "2025-08-12T05:52:44.521Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094, upload-time = "2025-08-12T05:52:22.618Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659, upload-time = "2025-08-12T05:52:24.057Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946, upload-time = "2025-08-12T05:52:45.976Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717, upload-time = "2025-08-12T05:53:15.214Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334, upload-time = "2025-08-12T05:53:14.178Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zipp"
|
name = "zipp"
|
||||||
version = "3.23.0"
|
version = "3.23.0"
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ namespace MCPForUnityTests.Editor.Helpers
|
||||||
private string _originalGitOverride;
|
private string _originalGitOverride;
|
||||||
private bool _hadHttpTransport;
|
private bool _hadHttpTransport;
|
||||||
private bool _originalHttpTransport;
|
private bool _originalHttpTransport;
|
||||||
|
private bool _hadDevForceRefresh;
|
||||||
|
private bool _originalDevForceRefresh;
|
||||||
private IPlatformService _originalPlatformService;
|
private IPlatformService _originalPlatformService;
|
||||||
|
|
||||||
[OneTimeSetUp]
|
[OneTimeSetUp]
|
||||||
|
|
@ -41,6 +43,8 @@ namespace MCPForUnityTests.Editor.Helpers
|
||||||
_originalGitOverride = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, string.Empty);
|
_originalGitOverride = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, string.Empty);
|
||||||
_hadHttpTransport = EditorPrefs.HasKey(EditorPrefKeys.UseHttpTransport);
|
_hadHttpTransport = EditorPrefs.HasKey(EditorPrefKeys.UseHttpTransport);
|
||||||
_originalHttpTransport = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true);
|
_originalHttpTransport = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true);
|
||||||
|
_hadDevForceRefresh = EditorPrefs.HasKey(EditorPrefKeys.DevModeForceServerRefresh);
|
||||||
|
_originalDevForceRefresh = EditorPrefs.GetBool(EditorPrefKeys.DevModeForceServerRefresh, false);
|
||||||
_originalPlatformService = MCPServiceLocator.Platform;
|
_originalPlatformService = MCPServiceLocator.Platform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,6 +55,9 @@ namespace MCPForUnityTests.Editor.Helpers
|
||||||
EditorPrefs.DeleteKey(EditorPrefKeys.GitUrlOverride);
|
EditorPrefs.DeleteKey(EditorPrefKeys.GitUrlOverride);
|
||||||
// Default to stdio mode for existing tests unless specified otherwise
|
// Default to stdio mode for existing tests unless specified otherwise
|
||||||
EditorPrefs.SetBool(EditorPrefKeys.UseHttpTransport, false);
|
EditorPrefs.SetBool(EditorPrefKeys.UseHttpTransport, false);
|
||||||
|
// Ensure deterministic uvx args ordering for these tests regardless of editor settings
|
||||||
|
// (dev-mode inserts --no-cache/--refresh, which changes the first args).
|
||||||
|
EditorPrefs.SetBool(EditorPrefKeys.DevModeForceServerRefresh, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TearDown]
|
[TearDown]
|
||||||
|
|
@ -66,6 +73,10 @@ namespace MCPForUnityTests.Editor.Helpers
|
||||||
{
|
{
|
||||||
MCPServiceLocator.Register<IPlatformService>(_originalPlatformService);
|
MCPServiceLocator.Register<IPlatformService>(_originalPlatformService);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MCPServiceLocator.Register<IPlatformService>(new PlatformService());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[OneTimeTearDown]
|
[OneTimeTearDown]
|
||||||
|
|
@ -88,6 +99,15 @@ namespace MCPForUnityTests.Editor.Helpers
|
||||||
{
|
{
|
||||||
EditorPrefs.DeleteKey(EditorPrefKeys.UseHttpTransport);
|
EditorPrefs.DeleteKey(EditorPrefKeys.UseHttpTransport);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_hadDevForceRefresh)
|
||||||
|
{
|
||||||
|
EditorPrefs.SetBool(EditorPrefKeys.DevModeForceServerRefresh, _originalDevForceRefresh);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EditorPrefs.DeleteKey(EditorPrefKeys.DevModeForceServerRefresh);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.TestTools;
|
using UnityEngine.TestTools;
|
||||||
|
|
@ -68,6 +69,81 @@ namespace MCPForUnityTests.Editor.Tools
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetComponents_ReturnsPagedMetadataByDefault()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
testGameObject.AddComponent<Rigidbody>();
|
||||||
|
testGameObject.AddComponent<BoxCollider>();
|
||||||
|
|
||||||
|
var p = new JObject
|
||||||
|
{
|
||||||
|
["action"] = "get_components",
|
||||||
|
["target"] = testGameObject.name,
|
||||||
|
["searchMethod"] = "by_name",
|
||||||
|
["pageSize"] = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var raw = ManageGameObject.HandleCommand(p);
|
||||||
|
var result = raw as JObject ?? JObject.FromObject(raw);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsTrue(result.Value<bool>("success"), result.ToString());
|
||||||
|
var data = result["data"] as JObject;
|
||||||
|
Assert.IsNotNull(data, "Expected data payload object.");
|
||||||
|
Assert.AreEqual(false, data.Value<bool>("includeProperties"));
|
||||||
|
|
||||||
|
var items = data["items"] as JArray;
|
||||||
|
Assert.IsNotNull(items, "Expected items array.");
|
||||||
|
Assert.AreEqual(2, items.Count, "Expected exactly pageSize items.");
|
||||||
|
|
||||||
|
var first = items[0] as JObject;
|
||||||
|
Assert.IsNotNull(first, "Expected item to be an object.");
|
||||||
|
Assert.IsNotNull(first["typeName"]);
|
||||||
|
Assert.IsNotNull(first["instanceID"]);
|
||||||
|
Assert.IsNull(first["properties"], "Metadata response should not include heavy serialized properties by default.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetComponents_CanIncludePropertiesButStillPages()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
testGameObject.AddComponent<Rigidbody>();
|
||||||
|
testGameObject.AddComponent<BoxCollider>();
|
||||||
|
|
||||||
|
var p = new JObject
|
||||||
|
{
|
||||||
|
["action"] = "get_components",
|
||||||
|
["target"] = testGameObject.name,
|
||||||
|
["searchMethod"] = "by_name",
|
||||||
|
["pageSize"] = 2,
|
||||||
|
["includeProperties"] = true
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var raw = ManageGameObject.HandleCommand(p);
|
||||||
|
var result = raw as JObject ?? JObject.FromObject(raw);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsTrue(result.Value<bool>("success"), result.ToString());
|
||||||
|
var data = result["data"] as JObject;
|
||||||
|
Assert.IsNotNull(data);
|
||||||
|
Assert.AreEqual(true, data.Value<bool>("includeProperties"));
|
||||||
|
|
||||||
|
var items = data["items"] as JArray;
|
||||||
|
Assert.IsNotNull(items);
|
||||||
|
Assert.IsTrue(items.Count > 0);
|
||||||
|
|
||||||
|
var first = items[0] as JObject;
|
||||||
|
Assert.IsNotNull(first);
|
||||||
|
Assert.IsNotNull(first["typeName"]);
|
||||||
|
Assert.IsNotNull(first["instanceID"]);
|
||||||
|
|
||||||
|
// Heuristic: property-including payload should have more than just typeName/instanceID.
|
||||||
|
Assert.Greater(first.Properties().Count(), 2, "Expected richer component payload when includeProperties=true.");
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void ComponentResolver_Integration_WorksWithRealComponents()
|
public void ComponentResolver_Integration_WorksWithRealComponents()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
using NUnit.Framework;
|
||||||
|
using UnityEngine;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using MCPForUnity.Editor.Tools;
|
||||||
|
|
||||||
|
namespace MCPForUnityTests.Editor.Tools
|
||||||
|
{
|
||||||
|
public class ManageSceneHierarchyPagingTests
|
||||||
|
{
|
||||||
|
private GameObject _root;
|
||||||
|
private readonly System.Collections.Generic.List<GameObject> _created = new System.Collections.Generic.List<GameObject>();
|
||||||
|
|
||||||
|
[TearDown]
|
||||||
|
public void TearDown()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _created.Count; i++)
|
||||||
|
{
|
||||||
|
if (_created[i] != null) Object.DestroyImmediate(_created[i]);
|
||||||
|
}
|
||||||
|
_created.Clear();
|
||||||
|
|
||||||
|
if (_root != null)
|
||||||
|
{
|
||||||
|
Object.DestroyImmediate(_root);
|
||||||
|
_root = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetHierarchy_PaginatesRoots_AndSupportsChildrenPaging()
|
||||||
|
{
|
||||||
|
// Arrange: create many roots so paging must occur.
|
||||||
|
// Note: Keep counts modest to avoid slowing EditMode tests.
|
||||||
|
const int rootCount = 40;
|
||||||
|
for (int i = 0; i < rootCount; i++)
|
||||||
|
{
|
||||||
|
_created.Add(new GameObject($"HS_Root_{i:D2}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
_root = new GameObject("HS_Parent");
|
||||||
|
for (int i = 0; i < 15; i++)
|
||||||
|
{
|
||||||
|
var child = new GameObject($"HS_Child_{i:D2}");
|
||||||
|
child.transform.SetParent(_root.transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act: request a small page to force truncation
|
||||||
|
var p1 = new JObject
|
||||||
|
{
|
||||||
|
["action"] = "get_hierarchy",
|
||||||
|
["pageSize"] = 10,
|
||||||
|
};
|
||||||
|
var raw1 = ManageScene.HandleCommand(p1);
|
||||||
|
var res1 = raw1 as JObject ?? JObject.FromObject(raw1);
|
||||||
|
|
||||||
|
// Assert: envelope success + payload shape
|
||||||
|
Assert.IsTrue(res1.Value<bool>("success"), res1.ToString());
|
||||||
|
var data1 = res1["data"] as JObject;
|
||||||
|
Assert.IsNotNull(data1, "Expected data payload to be an object.");
|
||||||
|
Assert.AreEqual("roots", data1.Value<string>("scope"));
|
||||||
|
Assert.AreEqual(true, data1.Value<bool>("truncated"), "Expected truncation when pageSize < root count.");
|
||||||
|
Assert.IsNotNull(data1["next_cursor"], "Expected next_cursor when truncated.");
|
||||||
|
|
||||||
|
var items1 = data1["items"] as JArray;
|
||||||
|
Assert.IsNotNull(items1, "Expected items array.");
|
||||||
|
Assert.AreEqual(10, items1.Count, "Expected exactly pageSize items returned.");
|
||||||
|
|
||||||
|
// Act: fetch next page of roots using next_cursor
|
||||||
|
var cursor = data1.Value<string>("next_cursor");
|
||||||
|
var p2 = new JObject
|
||||||
|
{
|
||||||
|
["action"] = "get_hierarchy",
|
||||||
|
["pageSize"] = 10,
|
||||||
|
["cursor"] = cursor,
|
||||||
|
};
|
||||||
|
var raw2 = ManageScene.HandleCommand(p2);
|
||||||
|
var res2 = raw2 as JObject ?? JObject.FromObject(raw2);
|
||||||
|
Assert.IsTrue(res2.Value<bool>("success"), res2.ToString());
|
||||||
|
var data2 = res2["data"] as JObject;
|
||||||
|
Assert.IsNotNull(data2);
|
||||||
|
var items2 = data2["items"] as JArray;
|
||||||
|
Assert.IsNotNull(items2);
|
||||||
|
Assert.AreEqual(10, items2.Count);
|
||||||
|
|
||||||
|
// Act: page children of a specific parent via 'parent' param (instance ID)
|
||||||
|
var pChildren = new JObject
|
||||||
|
{
|
||||||
|
["action"] = "get_hierarchy",
|
||||||
|
["parent"] = _root.GetInstanceID(),
|
||||||
|
["pageSize"] = 7,
|
||||||
|
};
|
||||||
|
var rawChildren = ManageScene.HandleCommand(pChildren);
|
||||||
|
var resChildren = rawChildren as JObject ?? JObject.FromObject(rawChildren);
|
||||||
|
Assert.IsTrue(resChildren.Value<bool>("success"), resChildren.ToString());
|
||||||
|
var dataChildren = resChildren["data"] as JObject;
|
||||||
|
Assert.IsNotNull(dataChildren);
|
||||||
|
Assert.AreEqual("children", dataChildren.Value<string>("scope"));
|
||||||
|
Assert.AreEqual(true, dataChildren.Value<bool>("truncated"));
|
||||||
|
Assert.IsNotNull(dataChildren["next_cursor"]);
|
||||||
|
var childItems = dataChildren["items"] as JArray;
|
||||||
|
Assert.IsNotNull(childItems);
|
||||||
|
Assert.AreEqual(7, childItems.Count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 95a76a6de453c48abaee108f9029cb65
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -3,6 +3,7 @@ using Newtonsoft.Json.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using System.Threading;
|
||||||
using MCPForUnity.Editor.Helpers;
|
using MCPForUnity.Editor.Helpers;
|
||||||
using MCPForUnity.Editor.Tools;
|
using MCPForUnity.Editor.Tools;
|
||||||
using MCPForUnityTests.Editor.Tools.Fixtures;
|
using MCPForUnityTests.Editor.Tools.Fixtures;
|
||||||
|
|
@ -22,6 +23,7 @@ namespace MCPForUnityTests.Editor.Tools
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp()
|
public void SetUp()
|
||||||
{
|
{
|
||||||
|
WaitForUnityReady();
|
||||||
EnsureFolder("Assets/Temp");
|
EnsureFolder("Assets/Temp");
|
||||||
// Start from a clean slate every time (prevents intermittent setup failures).
|
// Start from a clean slate every time (prevents intermittent setup failures).
|
||||||
if (AssetDatabase.IsValidFolder(TempRoot))
|
if (AssetDatabase.IsValidFolder(TempRoot))
|
||||||
|
|
@ -45,6 +47,7 @@ namespace MCPForUnityTests.Editor.Tools
|
||||||
AssetDatabase.CreateAsset(new Material(shader), _matBPath);
|
AssetDatabase.CreateAsset(new Material(shader), _matBPath);
|
||||||
AssetDatabase.SaveAssets();
|
AssetDatabase.SaveAssets();
|
||||||
AssetDatabase.Refresh();
|
AssetDatabase.Refresh();
|
||||||
|
WaitForUnityReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
[TearDown]
|
[TearDown]
|
||||||
|
|
@ -304,6 +307,22 @@ namespace MCPForUnityTests.Editor.Tools
|
||||||
{
|
{
|
||||||
return result as JObject ?? JObject.FromObject(result);
|
return result as JObject ?? JObject.FromObject(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void WaitForUnityReady(double timeoutSeconds = 30.0)
|
||||||
|
{
|
||||||
|
// Some EditMode tests trigger script compilation/domain reload. Tools like ManageScriptableObject
|
||||||
|
// intentionally return "compiling_or_reloading" during these windows. Wait until Unity is stable
|
||||||
|
// to make tests deterministic.
|
||||||
|
double start = EditorApplication.timeSinceStartup;
|
||||||
|
while (EditorApplication.isCompiling || EditorApplication.isUpdating)
|
||||||
|
{
|
||||||
|
if (EditorApplication.timeSinceStartup - start > timeoutSeconds)
|
||||||
|
{
|
||||||
|
Assert.Fail($"Timed out waiting for Unity to finish compiling/updating (>{timeoutSeconds:0.0}s).");
|
||||||
|
}
|
||||||
|
Thread.Sleep(50);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,7 @@ Use the MCP for Unity Editor window (Window > MCP for Unity) and open **Advanced
|
||||||
|
|
||||||
- **UV/UVX Path Override**: Point the UI to a specific `uv`/`uvx` executable (e.g., from a custom install) when PATH resolution is wrong. Clear to fall back to auto-discovery.
|
- **UV/UVX Path Override**: Point the UI to a specific `uv`/`uvx` executable (e.g., from a custom install) when PATH resolution is wrong. Clear to fall back to auto-discovery.
|
||||||
- **Server Source Override**: Set a local folder or git URL for the Python server (`uvx --from <url> mcp-for-unity`). Clear to use the packaged default.
|
- **Server Source Override**: Set a local folder or git URL for the Python server (`uvx --from <url> mcp-for-unity`). Clear to use the packaged default.
|
||||||
|
- **Dev Mode (Force fresh server install)**: When enabled, generated `uvx` commands add `--no-cache --refresh` before launching. This is slower, but avoids accidentally running a stale cached build while iterating on `Server/`.
|
||||||
- **Local Package Deployment**: Pick a local `MCPForUnity` folder (must contain `Editor/` and `Runtime/`) and click **Deploy to Project** to copy it over the currently installed package path (from `Packages/manifest.json` / Package Manager). A timestamped backup is stored under `Library/MCPForUnityDeployBackups`, and **Restore Last Backup** reverts the last deploy.
|
- **Local Package Deployment**: Pick a local `MCPForUnity` folder (must contain `Editor/` and `Runtime/`) and click **Deploy to Project** to copy it over the currently installed package path (from `Packages/manifest.json` / Package Manager). A timestamped backup is stored under `Library/MCPForUnityDeployBackups`, and **Restore Last Backup** reverts the last deploy.
|
||||||
|
|
||||||
Tips:
|
Tips:
|
||||||
|
|
@ -168,6 +169,41 @@ To find it reliably:
|
||||||
|
|
||||||
Note: In recent builds, the Python server sources are also bundled inside the package under `Server`. This is handy for local testing or pointing MCP clients directly at the packaged server.
|
Note: In recent builds, the Python server sources are also bundled inside the package under `Server`. This is handy for local testing or pointing MCP clients directly at the packaged server.
|
||||||
|
|
||||||
|
## Payload sizing & paging defaults (recommended)
|
||||||
|
|
||||||
|
Some Unity tool calls can return *very large* JSON payloads (deep hierarchies, components with full serialized properties). To keep MCP responses bounded and avoid Unity freezes/crashes, prefer **paged + summary-first** reads and fetch full properties only when needed.
|
||||||
|
|
||||||
|
### `manage_scene(action="get_hierarchy")`
|
||||||
|
|
||||||
|
- **Default behavior**: returns a **paged summary** of either root GameObjects (no `parent`) or direct children (`parent` specified). It does **not** inline full recursive subtrees.
|
||||||
|
- **Paging params**:
|
||||||
|
- **`page_size`**: defaults to **50**, clamped to **1..500**
|
||||||
|
- **`cursor`**: defaults to **0**
|
||||||
|
- **`next_cursor`**: returned as a **string** when more results remain; `null` when complete
|
||||||
|
- **Other safety knobs**:
|
||||||
|
- **`max_nodes`**: defaults to **1000**, clamped to **1..5000**
|
||||||
|
- **`include_transform`**: defaults to **false**
|
||||||
|
|
||||||
|
### `manage_gameobject(action="get_components")`
|
||||||
|
|
||||||
|
- **Default behavior**: returns **paged component metadata** only (`typeName`, `instanceID`).
|
||||||
|
- **Paging params**:
|
||||||
|
- **`page_size`**: defaults to **25**, clamped to **1..200**
|
||||||
|
- **`cursor`**: defaults to **0**
|
||||||
|
- **`max_components`**: defaults to **50**, clamped to **1..500**
|
||||||
|
- **`next_cursor`**: returned as a **string** when more results remain; `null` when complete
|
||||||
|
- **Properties-on-demand**:
|
||||||
|
- **`include_properties`** defaults to **false**
|
||||||
|
- When `include_properties=true`, the implementation enforces a conservative response-size budget (roughly **~250KB** of JSON text) and may return fewer than `page_size` items; use `next_cursor` to continue.
|
||||||
|
|
||||||
|
### Practical defaults (what we recommend in prompts/tests)
|
||||||
|
|
||||||
|
- **Hierarchy roots**: start with `page_size=50` and follow `next_cursor` (usually 1–2 calls for big scenes).
|
||||||
|
- **Children**: page by `parent` with `page_size=10..50` (depending on expected breadth).
|
||||||
|
- **Components**:
|
||||||
|
- Start with `include_properties=false` and `page_size=10..25`
|
||||||
|
- When you need full properties, keep `include_properties=true` with a **small** `page_size` (e.g. **3..10**) to bound peak payload sizes.
|
||||||
|
|
||||||
## MCP Bridge Stress Test
|
## MCP Bridge Stress Test
|
||||||
|
|
||||||
An on-demand stress utility exercises the MCP bridge with multiple concurrent clients while triggering real script reloads via immediate script edits (no menu calls required).
|
An on-demand stress utility exercises the MCP bridge with multiple concurrent clients while triggering real script reloads via immediate script edits (no menu calls required).
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue