Move Get commands to editor resources + Run Python tests every update (#368)
* Add a function to reload the domain Closes #357 * feat: restructure server instructions into workflow-focused format - Reorganized instructions from flat bullet list into categorized workflow sections - Emphasized critical script management workflow with numbered steps - Improved readability and scannability for AI agents using the MCP server It doesn't make sense to repeat the fucnction tools, they're already parsed * docs: reorder tool list alphabetically in README + add reload_domain tool * feat: add Unity editor state and project info resources - Implemented resources for querying active tool, editor state, prefab stage, selection, and open windows - Added project configuration resources for layers and project metadata - Organized new resources into Editor and Project namespaces for better structure * feat: clarify script management workflow in system prompt - Expanded guidance to include scripts created by any tool, not just manage_script - Added "etc" to tools examples for better clarity * refactor: remove reload_domain tool and update script management workflow - Removed reload_domain tool as Unity automatically recompiles scripts when modified - Updated script management instructions to rely on editor_state polling and console checking instead of manual domain reload - Simplified workflow by removing unnecessary manual recompilation step * Change name of menu items resource as the LLM seems it * refactor: reorganize tests into src/tests/integration directory - Moved all test files from root tests/ to MCPForUnity/UnityMcpServer~/src/tests/integration/ for better organization - Added conftest.py with telemetry and dependency stubs to simplify test setup - Removed redundant path manipulation and module loading code from individual test files * feat: expand Unity test workflow triggers - Run tests on all branches instead of only main - Add pull request trigger to catch issues before merge - Maintain path filtering to run only when relevant files change * chore: add GitHub Actions workflow for Python tests - Configured automated testing on push and pull requests using pytest - Set up uv for dependency management and Python 3.10 environment - Added test results artifact upload for debugging failed runs * refactor: update import path for fastmcp Context * docs: update development setup instructions to use uv - Changed installation commands from pip to uv pip for better dependency management - Updated test running instructions to use uv run pytest - Added examples for running integration and unit tests separately * Formatting [skip ci] * refactor: optimize CI workflow with path filters and dependency installation - Added path filters to only trigger tests when Python source or workflow files change - Split dependency installation into sync and dev install steps for better clarity - Fixed YAML indentation for improved readability * Update .github/workflows/python-tests.yml Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix: standardize test mode values to match Unity's naming convention - Changed default mode from "edit" to "EditMode" in C# code - Updated Python tool to use "EditMode" and "PlayMode" instead of lowercase variants * refactor: convert test imports to relative imports - Changed absolute imports to relative imports in integration tests for better package structure - Removed test packages from pyproject.toml package list * refactor: use Field with default_factory for mutable default in TagsResponse * refactor: remove duplicate PrefabStageUtility call * Update this as well [skip ci] * Update MCPForUnity/UnityMcpServer~/src/tests/integration/test_script_tools.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * chore: remove pull_request triggers from test workflows [skip ci] It's already covered by pushes * refactor: update resource function return types to include MCPResponse union * refactor: remove manual domain reload tool - Removed reload_domain tool as Unity handles script recompilation automatically - Updated documentation to reflect automatic compilation workflow - Simplified script management workflow instructions in server description * refactor: add context support to resource handlers - Updated all resource handlers to accept Context parameter for Unity instance routing - Replaced direct async_send_command_with_retry calls with async_send_with_unity_instance wrapper - Added imports for get_unity_instance_from_context and async_send_with_unity_instance helpers * fix: correct grammar in menu items documentation * docs: update README with expanded tools and resources documentation - Added new tools: manage_prefabs, create_script, delete_script, get_sha - Added new resources: editor state, windows, project info, layers, and tags - Clarified manage_script as compatibility router with recommendation to use newer edit tools - Fixed run_test to run_tests for consistency * refactor: convert unity_instances function to async [skip ci] - Changed function signature from synchronous to async - Added await keywords to ctx.info() and ctx.error() calls to properly handle async context methods --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>main
parent
f667582505
commit
2649d9c379
|
|
@ -0,0 +1,45 @@
|
||||||
|
name: Python Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["**"]
|
||||||
|
paths:
|
||||||
|
- MCPForUnity/UnityMcpServer~/src/**
|
||||||
|
- .github/workflows/python-tests.yml
|
||||||
|
workflow_dispatch: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Run Python Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install uv
|
||||||
|
uses: astral-sh/setup-uv@v4
|
||||||
|
with:
|
||||||
|
version: "latest"
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
run: uv python install 3.10
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
cd MCPForUnity/UnityMcpServer~/src
|
||||||
|
uv sync
|
||||||
|
uv pip install -e ".[dev]"
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
cd MCPForUnity/UnityMcpServer~/src
|
||||||
|
uv run pytest tests/ -v --tb=short
|
||||||
|
|
||||||
|
- name: Upload test results
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: pytest-results
|
||||||
|
path: |
|
||||||
|
MCPForUnity/UnityMcpServer~/src/.pytest_cache/
|
||||||
|
MCPForUnity/UnityMcpServer~/src/tests/
|
||||||
|
|
@ -3,7 +3,7 @@ name: Unity Tests
|
||||||
on:
|
on:
|
||||||
workflow_dispatch: {}
|
workflow_dispatch: {}
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: ["**"]
|
||||||
paths:
|
paths:
|
||||||
- TestProjects/UnityMCPTests/**
|
- TestProjects/UnityMCPTests/**
|
||||||
- MCPForUnity/Editor/**
|
- MCPForUnity/Editor/**
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: bacdb2f03a45d448888245e6ac9cca1b
|
guid: 266967ec2e1df44209bf46ec6037d61d
|
||||||
folderAsset: yes
|
folderAsset: yes
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
using System;
|
||||||
|
using MCPForUnity.Editor.Helpers;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using UnityEditor;
|
||||||
|
|
||||||
|
namespace MCPForUnity.Editor.Resources.Editor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides information about the currently active editor tool.
|
||||||
|
/// </summary>
|
||||||
|
[McpForUnityResource("get_active_tool")]
|
||||||
|
public static class ActiveTool
|
||||||
|
{
|
||||||
|
public static object HandleCommand(JObject @params)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Tool currentTool = UnityEditor.Tools.current;
|
||||||
|
string toolName = currentTool.ToString();
|
||||||
|
bool customToolActive = UnityEditor.Tools.current == Tool.Custom;
|
||||||
|
string activeToolName = customToolActive ? EditorTools.GetActiveToolName() : toolName;
|
||||||
|
|
||||||
|
var toolInfo = new
|
||||||
|
{
|
||||||
|
activeTool = activeToolName,
|
||||||
|
isCustom = customToolActive,
|
||||||
|
pivotMode = UnityEditor.Tools.pivotMode.ToString(),
|
||||||
|
pivotRotation = UnityEditor.Tools.pivotRotation.ToString(),
|
||||||
|
handleRotation = new
|
||||||
|
{
|
||||||
|
x = UnityEditor.Tools.handleRotation.eulerAngles.x,
|
||||||
|
y = UnityEditor.Tools.handleRotation.eulerAngles.y,
|
||||||
|
z = UnityEditor.Tools.handleRotation.eulerAngles.z
|
||||||
|
},
|
||||||
|
handlePosition = new
|
||||||
|
{
|
||||||
|
x = UnityEditor.Tools.handlePosition.x,
|
||||||
|
y = UnityEditor.Tools.handlePosition.y,
|
||||||
|
z = UnityEditor.Tools.handlePosition.z
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Response.Success("Retrieved active tool information.", toolInfo);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return Response.Error($"Error getting active tool: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper class for custom tool names
|
||||||
|
internal static class EditorTools
|
||||||
|
{
|
||||||
|
public static string GetActiveToolName()
|
||||||
|
{
|
||||||
|
if (UnityEditor.Tools.current == Tool.Custom)
|
||||||
|
{
|
||||||
|
return "Unknown Custom Tool";
|
||||||
|
}
|
||||||
|
return UnityEditor.Tools.current.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6e78b6227ab7742a8a4f679ee6a8a212
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
using System;
|
||||||
|
using MCPForUnity.Editor.Helpers;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.SceneManagement;
|
||||||
|
|
||||||
|
namespace MCPForUnity.Editor.Resources.Editor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides dynamic editor state information that changes frequently.
|
||||||
|
/// </summary>
|
||||||
|
[McpForUnityResource("get_editor_state")]
|
||||||
|
public static class EditorState
|
||||||
|
{
|
||||||
|
public static object HandleCommand(JObject @params)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var activeScene = EditorSceneManager.GetActiveScene();
|
||||||
|
var state = new
|
||||||
|
{
|
||||||
|
isPlaying = EditorApplication.isPlaying,
|
||||||
|
isPaused = EditorApplication.isPaused,
|
||||||
|
isCompiling = EditorApplication.isCompiling,
|
||||||
|
isUpdating = EditorApplication.isUpdating,
|
||||||
|
timeSinceStartup = EditorApplication.timeSinceStartup,
|
||||||
|
activeSceneName = activeScene.name ?? "",
|
||||||
|
selectionCount = UnityEditor.Selection.count,
|
||||||
|
activeObjectName = UnityEditor.Selection.activeObject?.name
|
||||||
|
};
|
||||||
|
|
||||||
|
return Response.Success("Retrieved editor state.", state);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return Response.Error($"Error getting editor state: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f7c6df54e014c44fdb0cd3f65a479e37
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
using System;
|
||||||
|
using MCPForUnity.Editor.Helpers;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using UnityEditor.SceneManagement;
|
||||||
|
|
||||||
|
namespace MCPForUnity.Editor.Resources.Editor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides information about the current prefab editing context.
|
||||||
|
/// </summary>
|
||||||
|
[McpForUnityResource("get_prefab_stage")]
|
||||||
|
public static class PrefabStage
|
||||||
|
{
|
||||||
|
public static object HandleCommand(JObject @params)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var stage = PrefabStageUtility.GetCurrentPrefabStage();
|
||||||
|
|
||||||
|
if (stage == null)
|
||||||
|
{
|
||||||
|
return Response.Success("No prefab stage is currently open.", new { isOpen = false });
|
||||||
|
}
|
||||||
|
|
||||||
|
var stageInfo = new
|
||||||
|
{
|
||||||
|
isOpen = true,
|
||||||
|
assetPath = stage.assetPath,
|
||||||
|
prefabRootName = stage.prefabContentsRoot?.name,
|
||||||
|
mode = stage.mode.ToString(),
|
||||||
|
isDirty = stage.scene.isDirty
|
||||||
|
};
|
||||||
|
|
||||||
|
return Response.Success("Prefab stage info retrieved.", stageInfo);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return Response.Error($"Error getting prefab stage info: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7a30b083e68bd4ae3b3d1ce5a45a9414
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using MCPForUnity.Editor.Helpers;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using UnityEditor;
|
||||||
|
|
||||||
|
namespace MCPForUnity.Editor.Resources.Editor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides detailed information about the current editor selection.
|
||||||
|
/// </summary>
|
||||||
|
[McpForUnityResource("get_selection")]
|
||||||
|
public static class Selection
|
||||||
|
{
|
||||||
|
public static object HandleCommand(JObject @params)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var selectionInfo = new
|
||||||
|
{
|
||||||
|
activeObject = UnityEditor.Selection.activeObject?.name,
|
||||||
|
activeGameObject = UnityEditor.Selection.activeGameObject?.name,
|
||||||
|
activeTransform = UnityEditor.Selection.activeTransform?.name,
|
||||||
|
activeInstanceID = UnityEditor.Selection.activeInstanceID,
|
||||||
|
count = UnityEditor.Selection.count,
|
||||||
|
objects = UnityEditor.Selection.objects
|
||||||
|
.Select(obj => new
|
||||||
|
{
|
||||||
|
name = obj?.name,
|
||||||
|
type = obj?.GetType().FullName,
|
||||||
|
instanceID = obj?.GetInstanceID()
|
||||||
|
})
|
||||||
|
.ToList(),
|
||||||
|
gameObjects = UnityEditor.Selection.gameObjects
|
||||||
|
.Select(go => new
|
||||||
|
{
|
||||||
|
name = go?.name,
|
||||||
|
instanceID = go?.GetInstanceID()
|
||||||
|
})
|
||||||
|
.ToList(),
|
||||||
|
assetGUIDs = UnityEditor.Selection.assetGUIDs
|
||||||
|
};
|
||||||
|
|
||||||
|
return Response.Success("Retrieved current selection details.", selectionInfo);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return Response.Error($"Error getting selection: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c7ea869623e094599a70be086ab4fc0e
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using MCPForUnity.Editor.Helpers;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace MCPForUnity.Editor.Resources.Editor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides list of all open editor windows.
|
||||||
|
/// </summary>
|
||||||
|
[McpForUnityResource("get_windows")]
|
||||||
|
public static class Windows
|
||||||
|
{
|
||||||
|
public static object HandleCommand(JObject @params)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
EditorWindow[] allWindows = UnityEngine.Resources.FindObjectsOfTypeAll<EditorWindow>();
|
||||||
|
var openWindows = new List<object>();
|
||||||
|
|
||||||
|
foreach (EditorWindow window in allWindows)
|
||||||
|
{
|
||||||
|
if (window == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
openWindows.Add(new
|
||||||
|
{
|
||||||
|
title = window.titleContent.text,
|
||||||
|
typeName = window.GetType().FullName,
|
||||||
|
isFocused = EditorWindow.focusedWindow == window,
|
||||||
|
position = new
|
||||||
|
{
|
||||||
|
x = window.position.x,
|
||||||
|
y = window.position.y,
|
||||||
|
width = window.position.width,
|
||||||
|
height = window.position.height
|
||||||
|
},
|
||||||
|
instanceID = window.GetInstanceID()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"Could not get info for window {window.GetType().Name}: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.Success("Retrieved list of open editor windows.", openWindows);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return Response.Error($"Error getting editor windows: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 58a341e64bea440b29deaf859aaea552
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 538489f13d7914c4eba9a67e29001b43
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using MCPForUnity.Editor.Helpers;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace MCPForUnity.Editor.Resources.Project
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides dictionary of layer indices to layer names.
|
||||||
|
/// </summary>
|
||||||
|
[McpForUnityResource("get_layers")]
|
||||||
|
public static class Layers
|
||||||
|
{
|
||||||
|
private const int TotalLayerCount = 32;
|
||||||
|
|
||||||
|
public static object HandleCommand(JObject @params)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var layers = new Dictionary<int, string>();
|
||||||
|
for (int i = 0; i < TotalLayerCount; i++)
|
||||||
|
{
|
||||||
|
string layerName = LayerMask.LayerToName(i);
|
||||||
|
if (!string.IsNullOrEmpty(layerName))
|
||||||
|
{
|
||||||
|
layers.Add(i, layerName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.Success("Retrieved current named layers.", layers);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return Response.Error($"Failed to retrieve layers: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 959ee428299454ac19a636275208ca00
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using MCPForUnity.Editor.Helpers;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace MCPForUnity.Editor.Resources.Project
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides static project configuration information.
|
||||||
|
/// </summary>
|
||||||
|
[McpForUnityResource("get_project_info")]
|
||||||
|
public static class ProjectInfo
|
||||||
|
{
|
||||||
|
public static object HandleCommand(JObject @params)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string assetsPath = Application.dataPath.Replace('\\', '/');
|
||||||
|
string projectRoot = Directory.GetParent(assetsPath)?.FullName.Replace('\\', '/');
|
||||||
|
string projectName = Path.GetFileName(projectRoot);
|
||||||
|
|
||||||
|
var info = new
|
||||||
|
{
|
||||||
|
projectRoot = projectRoot ?? "",
|
||||||
|
projectName = projectName ?? "",
|
||||||
|
unityVersion = Application.unityVersion,
|
||||||
|
platform = EditorUserBuildSettings.activeBuildTarget.ToString(),
|
||||||
|
assetsPath = assetsPath
|
||||||
|
};
|
||||||
|
|
||||||
|
return Response.Success("Retrieved project info.", info);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return Response.Error($"Error getting project info: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 81b03415fcf93466e9ed667d19b58d43
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
using System;
|
||||||
|
using MCPForUnity.Editor.Helpers;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using UnityEditorInternal;
|
||||||
|
|
||||||
|
namespace MCPForUnity.Editor.Resources.Project
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides list of all tags in the project.
|
||||||
|
/// </summary>
|
||||||
|
[McpForUnityResource("get_tags")]
|
||||||
|
public static class Tags
|
||||||
|
{
|
||||||
|
public static object HandleCommand(JObject @params)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string[] tags = InternalEditorUtility.tags;
|
||||||
|
return Response.Success("Retrieved current tags.", tags);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return Response.Error($"Failed to retrieve tags: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2179ac5d98f264d1681e7d5c0d0ed341
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -87,19 +87,19 @@ namespace MCPForUnity.Editor.Resources.Tests
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modeStr.Equals("edit", StringComparison.OrdinalIgnoreCase))
|
if (modeStr.Equals("EditMode", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
mode = TestMode.EditMode;
|
mode = TestMode.EditMode;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modeStr.Equals("play", StringComparison.OrdinalIgnoreCase))
|
if (modeStr.Equals("PlayMode", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
mode = TestMode.PlayMode;
|
mode = TestMode.PlayMode;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
error = $"Unknown test mode: '{modeStr}'. Use 'edit' or 'play'";
|
error = $"Unknown test mode: '{modeStr}'. Use 'EditMode' or 'PlayMode'";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,14 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.IO;
|
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditorInternal; // Required for tag management
|
using UnityEditorInternal; // Required for tag management
|
||||||
using UnityEditor.SceneManagement;
|
|
||||||
using UnityEngine;
|
|
||||||
using MCPForUnity.Editor.Helpers;
|
using MCPForUnity.Editor.Helpers;
|
||||||
|
|
||||||
namespace MCPForUnity.Editor.Tools
|
namespace MCPForUnity.Editor.Tools
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles operations related to controlling and querying the Unity Editor state,
|
/// Handles editor control actions including play mode control, tool selection,
|
||||||
/// including managing Tags and Layers.
|
/// and tag/layer management. For reading editor state, use MCP resources instead.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[McpForUnityTool("manage_editor")]
|
[McpForUnityTool("manage_editor")]
|
||||||
public static class ManageEditor
|
public static class ManageEditor
|
||||||
|
|
@ -89,19 +84,7 @@ namespace MCPForUnity.Editor.Tools
|
||||||
return Response.Error($"Error stopping play mode: {e.Message}");
|
return Response.Error($"Error stopping play mode: {e.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Editor State/Info
|
// Tool Control
|
||||||
case "get_state":
|
|
||||||
return GetEditorState();
|
|
||||||
case "get_project_root":
|
|
||||||
return GetProjectRoot();
|
|
||||||
case "get_windows":
|
|
||||||
return GetEditorWindows();
|
|
||||||
case "get_active_tool":
|
|
||||||
return GetActiveTool();
|
|
||||||
case "get_selection":
|
|
||||||
return GetSelection();
|
|
||||||
case "get_prefab_stage":
|
|
||||||
return GetPrefabStageInfo();
|
|
||||||
case "set_active_tool":
|
case "set_active_tool":
|
||||||
string toolName = @params["toolName"]?.ToString();
|
string toolName = @params["toolName"]?.ToString();
|
||||||
if (string.IsNullOrEmpty(toolName))
|
if (string.IsNullOrEmpty(toolName))
|
||||||
|
|
@ -117,9 +100,6 @@ namespace MCPForUnity.Editor.Tools
|
||||||
if (string.IsNullOrEmpty(tagName))
|
if (string.IsNullOrEmpty(tagName))
|
||||||
return Response.Error("'tagName' parameter required for remove_tag.");
|
return Response.Error("'tagName' parameter required for remove_tag.");
|
||||||
return RemoveTag(tagName);
|
return RemoveTag(tagName);
|
||||||
case "get_tags":
|
|
||||||
return GetTags(); // Helper to list current tags
|
|
||||||
|
|
||||||
// Layer Management
|
// Layer Management
|
||||||
case "add_layer":
|
case "add_layer":
|
||||||
if (string.IsNullOrEmpty(layerName))
|
if (string.IsNullOrEmpty(layerName))
|
||||||
|
|
@ -129,9 +109,6 @@ namespace MCPForUnity.Editor.Tools
|
||||||
if (string.IsNullOrEmpty(layerName))
|
if (string.IsNullOrEmpty(layerName))
|
||||||
return Response.Error("'layerName' parameter required for remove_layer.");
|
return Response.Error("'layerName' parameter required for remove_layer.");
|
||||||
return RemoveLayer(layerName);
|
return RemoveLayer(layerName);
|
||||||
case "get_layers":
|
|
||||||
return GetLayers(); // Helper to list current layers
|
|
||||||
|
|
||||||
// --- Settings (Example) ---
|
// --- Settings (Example) ---
|
||||||
// case "set_resolution":
|
// case "set_resolution":
|
||||||
// int? width = @params["width"]?.ToObject<int?>();
|
// int? width = @params["width"]?.ToObject<int?>();
|
||||||
|
|
@ -144,167 +121,12 @@ namespace MCPForUnity.Editor.Tools
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return Response.Error(
|
return Response.Error(
|
||||||
$"Unknown action: '{action}'. Supported actions include play, pause, stop, get_state, get_project_root, get_windows, get_active_tool, get_selection, get_prefab_stage, set_active_tool, add_tag, remove_tag, get_tags, add_layer, remove_layer, get_layers."
|
$"Unknown action: '{action}'. Supported actions: play, pause, stop, set_active_tool, add_tag, remove_tag, add_layer, remove_layer. Use MCP resources for reading editor state, project info, tags, layers, selection, windows, prefab stage, and active tool."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Editor State/Info Methods ---
|
// --- Tool Control Methods ---
|
||||||
private static object GetEditorState()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var state = new
|
|
||||||
{
|
|
||||||
isPlaying = EditorApplication.isPlaying,
|
|
||||||
isPaused = EditorApplication.isPaused,
|
|
||||||
isCompiling = EditorApplication.isCompiling,
|
|
||||||
isUpdating = EditorApplication.isUpdating,
|
|
||||||
applicationPath = EditorApplication.applicationPath,
|
|
||||||
applicationContentsPath = EditorApplication.applicationContentsPath,
|
|
||||||
timeSinceStartup = EditorApplication.timeSinceStartup,
|
|
||||||
};
|
|
||||||
return Response.Success("Retrieved editor state.", state);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
return Response.Error($"Error getting editor state: {e.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static object GetProjectRoot()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Application.dataPath points to <Project>/Assets
|
|
||||||
string assetsPath = Application.dataPath.Replace('\\', '/');
|
|
||||||
string projectRoot = Directory.GetParent(assetsPath)?.FullName.Replace('\\', '/');
|
|
||||||
if (string.IsNullOrEmpty(projectRoot))
|
|
||||||
{
|
|
||||||
return Response.Error("Could not determine project root from Application.dataPath");
|
|
||||||
}
|
|
||||||
return Response.Success("Project root resolved.", new { projectRoot });
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
return Response.Error($"Error getting project root: {e.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static object GetEditorWindows()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Get all types deriving from EditorWindow
|
|
||||||
var windowTypes = AppDomain
|
|
||||||
.CurrentDomain.GetAssemblies()
|
|
||||||
.SelectMany(assembly => assembly.GetTypes())
|
|
||||||
.Where(type => type.IsSubclassOf(typeof(EditorWindow)))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var openWindows = new List<object>();
|
|
||||||
|
|
||||||
// Find currently open instances
|
|
||||||
// Resources.FindObjectsOfTypeAll seems more reliable than GetWindow for finding *all* open windows
|
|
||||||
EditorWindow[] allWindows = UnityEngine.Resources.FindObjectsOfTypeAll<EditorWindow>();
|
|
||||||
|
|
||||||
foreach (EditorWindow window in allWindows)
|
|
||||||
{
|
|
||||||
if (window == null)
|
|
||||||
continue; // Skip potentially destroyed windows
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
openWindows.Add(
|
|
||||||
new
|
|
||||||
{
|
|
||||||
title = window.titleContent.text,
|
|
||||||
typeName = window.GetType().FullName,
|
|
||||||
isFocused = EditorWindow.focusedWindow == window,
|
|
||||||
position = new
|
|
||||||
{
|
|
||||||
x = window.position.x,
|
|
||||||
y = window.position.y,
|
|
||||||
width = window.position.width,
|
|
||||||
height = window.position.height,
|
|
||||||
},
|
|
||||||
instanceID = window.GetInstanceID(),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.LogWarning(
|
|
||||||
$"Could not get info for window {window.GetType().Name}: {ex.Message}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Response.Success("Retrieved list of open editor windows.", openWindows);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
return Response.Error($"Error getting editor windows: {e.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static object GetPrefabStageInfo()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
PrefabStage stage = PrefabStageUtility.GetCurrentPrefabStage();
|
|
||||||
if (stage == null)
|
|
||||||
{
|
|
||||||
return Response.Success
|
|
||||||
("No prefab stage is currently open.", new { isOpen = false });
|
|
||||||
}
|
|
||||||
|
|
||||||
return Response.Success(
|
|
||||||
"Prefab stage info retrieved.",
|
|
||||||
new
|
|
||||||
{
|
|
||||||
isOpen = true,
|
|
||||||
assetPath = stage.assetPath,
|
|
||||||
prefabRootName = stage.prefabContentsRoot != null ? stage.prefabContentsRoot.name : null,
|
|
||||||
mode = stage.mode.ToString(),
|
|
||||||
isDirty = stage.scene.isDirty
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
return Response.Error($"Error getting prefab stage info: {e.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static object GetActiveTool()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Tool currentTool = UnityEditor.Tools.current;
|
|
||||||
string toolName = currentTool.ToString(); // Enum to string
|
|
||||||
bool customToolActive = UnityEditor.Tools.current == Tool.Custom; // Check if a custom tool is active
|
|
||||||
string activeToolName = customToolActive
|
|
||||||
? EditorTools.GetActiveToolName()
|
|
||||||
: toolName; // Get custom name if needed
|
|
||||||
|
|
||||||
var toolInfo = new
|
|
||||||
{
|
|
||||||
activeTool = activeToolName,
|
|
||||||
isCustom = customToolActive,
|
|
||||||
pivotMode = UnityEditor.Tools.pivotMode.ToString(),
|
|
||||||
pivotRotation = UnityEditor.Tools.pivotRotation.ToString(),
|
|
||||||
handleRotation = UnityEditor.Tools.handleRotation.eulerAngles, // Euler for simplicity
|
|
||||||
handlePosition = UnityEditor.Tools.handlePosition,
|
|
||||||
};
|
|
||||||
|
|
||||||
return Response.Success("Retrieved active tool information.", toolInfo);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
return Response.Error($"Error getting active tool: {e.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static object SetActiveTool(string toolName)
|
private static object SetActiveTool(string toolName)
|
||||||
{
|
{
|
||||||
|
|
@ -341,43 +163,6 @@ namespace MCPForUnity.Editor.Tools
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static object GetSelection()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var selectionInfo = new
|
|
||||||
{
|
|
||||||
activeObject = Selection.activeObject?.name,
|
|
||||||
activeGameObject = Selection.activeGameObject?.name,
|
|
||||||
activeTransform = Selection.activeTransform?.name,
|
|
||||||
activeInstanceID = Selection.activeInstanceID,
|
|
||||||
count = Selection.count,
|
|
||||||
objects = Selection
|
|
||||||
.objects.Select(obj => new
|
|
||||||
{
|
|
||||||
name = obj?.name,
|
|
||||||
type = obj?.GetType().FullName,
|
|
||||||
instanceID = obj?.GetInstanceID(),
|
|
||||||
})
|
|
||||||
.ToList(),
|
|
||||||
gameObjects = Selection
|
|
||||||
.gameObjects.Select(go => new
|
|
||||||
{
|
|
||||||
name = go?.name,
|
|
||||||
instanceID = go?.GetInstanceID(),
|
|
||||||
})
|
|
||||||
.ToList(),
|
|
||||||
assetGUIDs = Selection.assetGUIDs, // GUIDs for selected assets in Project view
|
|
||||||
};
|
|
||||||
|
|
||||||
return Response.Success("Retrieved current selection details.", selectionInfo);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
return Response.Error($"Error getting selection: {e.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Tag Management Methods ---
|
// --- Tag Management Methods ---
|
||||||
|
|
||||||
private static object AddTag(string tagName)
|
private static object AddTag(string tagName)
|
||||||
|
|
@ -386,7 +171,7 @@ namespace MCPForUnity.Editor.Tools
|
||||||
return Response.Error("Tag name cannot be empty or whitespace.");
|
return Response.Error("Tag name cannot be empty or whitespace.");
|
||||||
|
|
||||||
// Check if tag already exists
|
// Check if tag already exists
|
||||||
if (InternalEditorUtility.tags.Contains(tagName))
|
if (System.Linq.Enumerable.Contains(InternalEditorUtility.tags, tagName))
|
||||||
{
|
{
|
||||||
return Response.Error($"Tag '{tagName}' already exists.");
|
return Response.Error($"Tag '{tagName}' already exists.");
|
||||||
}
|
}
|
||||||
|
|
@ -413,7 +198,7 @@ namespace MCPForUnity.Editor.Tools
|
||||||
return Response.Error("Cannot remove the built-in 'Untagged' tag.");
|
return Response.Error("Cannot remove the built-in 'Untagged' tag.");
|
||||||
|
|
||||||
// Check if tag exists before attempting removal
|
// Check if tag exists before attempting removal
|
||||||
if (!InternalEditorUtility.tags.Contains(tagName))
|
if (!System.Linq.Enumerable.Contains(InternalEditorUtility.tags, tagName))
|
||||||
{
|
{
|
||||||
return Response.Error($"Tag '{tagName}' does not exist.");
|
return Response.Error($"Tag '{tagName}' does not exist.");
|
||||||
}
|
}
|
||||||
|
|
@ -433,19 +218,6 @@ namespace MCPForUnity.Editor.Tools
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static object GetTags()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string[] tags = InternalEditorUtility.tags;
|
|
||||||
return Response.Success("Retrieved current tags.", tags);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
return Response.Error($"Failed to retrieve tags: {e.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Layer Management Methods ---
|
// --- Layer Management Methods ---
|
||||||
|
|
||||||
private static object AddLayer(string layerName)
|
private static object AddLayer(string layerName)
|
||||||
|
|
@ -569,27 +341,6 @@ namespace MCPForUnity.Editor.Tools
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static object GetLayers()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var layers = new Dictionary<int, string>();
|
|
||||||
for (int i = 0; i < TotalLayerCount; i++)
|
|
||||||
{
|
|
||||||
string layerName = LayerMask.LayerToName(i);
|
|
||||||
if (!string.IsNullOrEmpty(layerName)) // Only include layers that have names
|
|
||||||
{
|
|
||||||
layers.Add(i, layerName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Response.Success("Retrieved current named layers.", layers);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
return Response.Error($"Failed to retrieve layers: {e.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Helper Methods ---
|
// --- Helper Methods ---
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -605,7 +356,7 @@ namespace MCPForUnity.Editor.Tools
|
||||||
);
|
);
|
||||||
if (tagManagerAssets == null || tagManagerAssets.Length == 0)
|
if (tagManagerAssets == null || tagManagerAssets.Length == 0)
|
||||||
{
|
{
|
||||||
Debug.LogError("[ManageEditor] TagManager.asset not found in ProjectSettings.");
|
McpLog.Error("[ManageEditor] TagManager.asset not found in ProjectSettings.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// The first object in the asset file should be the TagManager
|
// The first object in the asset file should be the TagManager
|
||||||
|
|
@ -613,7 +364,7 @@ namespace MCPForUnity.Editor.Tools
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Debug.LogError($"[ManageEditor] Error accessing TagManager.asset: {e.Message}");
|
McpLog.Error($"[ManageEditor] Error accessing TagManager.asset: {e.Message}");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -624,22 +375,4 @@ namespace MCPForUnity.Editor.Tools
|
||||||
private static object SetQualityLevel(JToken qualityLevelToken) { ... }
|
private static object SetQualityLevel(JToken qualityLevelToken) { ... }
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper class to get custom tool names (remains the same)
|
|
||||||
internal static class EditorTools
|
|
||||||
{
|
|
||||||
public static string GetActiveToolName()
|
|
||||||
{
|
|
||||||
// This is a placeholder. Real implementation depends on how custom tools
|
|
||||||
// are registered and tracked in the specific Unity project setup.
|
|
||||||
// It might involve checking static variables, calling methods on specific tool managers, etc.
|
|
||||||
if (UnityEditor.Tools.current == Tool.Custom)
|
|
||||||
{
|
|
||||||
// Example: Check a known custom tool manager
|
|
||||||
// if (MyCustomToolManager.IsActive) return MyCustomToolManager.ActiveToolName;
|
|
||||||
return "Unknown Custom Tool";
|
|
||||||
}
|
|
||||||
return UnityEditor.Tools.current.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ namespace MCPForUnity.Editor.Tools
|
||||||
string modeStr = @params?["mode"]?.ToString();
|
string modeStr = @params?["mode"]?.ToString();
|
||||||
if (string.IsNullOrWhiteSpace(modeStr))
|
if (string.IsNullOrWhiteSpace(modeStr))
|
||||||
{
|
{
|
||||||
modeStr = "edit";
|
modeStr = "EditMode";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ModeParser.TryParse(modeStr, out var parsedMode, out var parseError))
|
if (!ModeParser.TryParse(modeStr, out var parsedMode, out var parseError))
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ py-modules = [
|
||||||
"server",
|
"server",
|
||||||
"telemetry",
|
"telemetry",
|
||||||
"telemetry_decorator",
|
"telemetry_decorator",
|
||||||
"unity_connection"
|
"unity_connection",
|
||||||
|
"unity_instance_middleware"
|
||||||
]
|
]
|
||||||
packages = ["tools", "resources", "registry"]
|
packages = ["tools", "resources", "registry"]
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from fastmcp import Context
|
||||||
|
|
||||||
|
from models import MCPResponse
|
||||||
|
from registry import mcp_for_unity_resource
|
||||||
|
from tools import get_unity_instance_from_context, async_send_with_unity_instance
|
||||||
|
from unity_connection import async_send_command_with_retry
|
||||||
|
|
||||||
|
|
||||||
|
class Vector3(BaseModel):
|
||||||
|
"""3D vector."""
|
||||||
|
x: float = 0.0
|
||||||
|
y: float = 0.0
|
||||||
|
z: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
|
class ActiveToolData(BaseModel):
|
||||||
|
"""Active tool data fields."""
|
||||||
|
activeTool: str = ""
|
||||||
|
isCustom: bool = False
|
||||||
|
pivotMode: str = ""
|
||||||
|
pivotRotation: str = ""
|
||||||
|
handleRotation: Vector3 = Vector3()
|
||||||
|
handlePosition: Vector3 = Vector3()
|
||||||
|
|
||||||
|
|
||||||
|
class ActiveToolResponse(MCPResponse):
|
||||||
|
"""Information about the currently active editor tool."""
|
||||||
|
data: ActiveToolData = ActiveToolData()
|
||||||
|
|
||||||
|
|
||||||
|
@mcp_for_unity_resource(
|
||||||
|
uri="unity://editor/active-tool",
|
||||||
|
name="editor_active_tool",
|
||||||
|
description="Currently active editor tool (Move, Rotate, Scale, etc.) and transform handle settings."
|
||||||
|
)
|
||||||
|
async def get_active_tool(ctx: Context) -> ActiveToolResponse | MCPResponse:
|
||||||
|
"""Get active editor tool information."""
|
||||||
|
unity_instance = get_unity_instance_from_context(ctx)
|
||||||
|
response = await async_send_with_unity_instance(
|
||||||
|
async_send_command_with_retry,
|
||||||
|
unity_instance,
|
||||||
|
"get_active_tool",
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
return ActiveToolResponse(**response) if isinstance(response, dict) else response
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from fastmcp import Context
|
||||||
|
|
||||||
|
from models import MCPResponse
|
||||||
|
from registry import mcp_for_unity_resource
|
||||||
|
from tools import get_unity_instance_from_context, async_send_with_unity_instance
|
||||||
|
from unity_connection import async_send_command_with_retry
|
||||||
|
|
||||||
|
|
||||||
|
class EditorStateData(BaseModel):
|
||||||
|
"""Editor state data fields."""
|
||||||
|
isPlaying: bool = False
|
||||||
|
isPaused: bool = False
|
||||||
|
isCompiling: bool = False
|
||||||
|
isUpdating: bool = False
|
||||||
|
timeSinceStartup: float = 0.0
|
||||||
|
activeSceneName: str = ""
|
||||||
|
selectionCount: int = 0
|
||||||
|
activeObjectName: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class EditorStateResponse(MCPResponse):
|
||||||
|
"""Dynamic editor state information that changes frequently."""
|
||||||
|
data: EditorStateData = EditorStateData()
|
||||||
|
|
||||||
|
|
||||||
|
@mcp_for_unity_resource(
|
||||||
|
uri="unity://editor/state",
|
||||||
|
name="editor_state",
|
||||||
|
description="Current editor runtime state including play mode, compilation status, active scene, and selection summary. Refresh frequently for up-to-date information."
|
||||||
|
)
|
||||||
|
async def get_editor_state(ctx: Context) -> EditorStateResponse | MCPResponse:
|
||||||
|
"""Get current editor runtime state."""
|
||||||
|
unity_instance = get_unity_instance_from_context(ctx)
|
||||||
|
response = await async_send_with_unity_instance(
|
||||||
|
async_send_command_with_retry,
|
||||||
|
unity_instance,
|
||||||
|
"get_editor_state",
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
return EditorStateResponse(**response) if isinstance(response, dict) else response
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
from fastmcp import Context
|
||||||
|
|
||||||
|
from models import MCPResponse
|
||||||
|
from registry import mcp_for_unity_resource
|
||||||
|
from tools import get_unity_instance_from_context, async_send_with_unity_instance
|
||||||
|
from unity_connection import async_send_command_with_retry
|
||||||
|
|
||||||
|
|
||||||
|
class LayersResponse(MCPResponse):
|
||||||
|
"""Dictionary of layer indices to layer names."""
|
||||||
|
data: dict[int, str] = {}
|
||||||
|
|
||||||
|
|
||||||
|
@mcp_for_unity_resource(
|
||||||
|
uri="unity://project/layers",
|
||||||
|
name="project_layers",
|
||||||
|
description="All layers defined in the project's TagManager with their indices (0-31). Read this before using add_layer or remove_layer tools."
|
||||||
|
)
|
||||||
|
async def get_layers(ctx: Context) -> LayersResponse | MCPResponse:
|
||||||
|
"""Get all project layers with their indices."""
|
||||||
|
unity_instance = get_unity_instance_from_context(ctx)
|
||||||
|
response = await async_send_with_unity_instance(
|
||||||
|
async_send_command_with_retry,
|
||||||
|
unity_instance,
|
||||||
|
"get_layers",
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
return LayersResponse(**response) if isinstance(response, dict) else response
|
||||||
|
|
@ -12,10 +12,10 @@ class GetMenuItemsResponse(MCPResponse):
|
||||||
|
|
||||||
@mcp_for_unity_resource(
|
@mcp_for_unity_resource(
|
||||||
uri="mcpforunity://menu-items",
|
uri="mcpforunity://menu-items",
|
||||||
name="get_menu_items",
|
name="menu_items",
|
||||||
description="Provides a list of all menu items."
|
description="Provides a list of all menu items."
|
||||||
)
|
)
|
||||||
async def get_menu_items(ctx: Context) -> GetMenuItemsResponse:
|
async def get_menu_items(ctx: Context) -> GetMenuItemsResponse | MCPResponse:
|
||||||
"""Provides a list of all menu items.
|
"""Provides a list of all menu items.
|
||||||
"""
|
"""
|
||||||
unity_instance = get_unity_instance_from_context(ctx)
|
unity_instance = get_unity_instance_from_context(ctx)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from fastmcp import Context
|
||||||
|
|
||||||
|
from models import MCPResponse
|
||||||
|
from registry import mcp_for_unity_resource
|
||||||
|
from tools import get_unity_instance_from_context, async_send_with_unity_instance
|
||||||
|
from unity_connection import async_send_command_with_retry
|
||||||
|
|
||||||
|
|
||||||
|
class PrefabStageData(BaseModel):
|
||||||
|
"""Prefab stage data fields."""
|
||||||
|
isOpen: bool = False
|
||||||
|
assetPath: str | None = None
|
||||||
|
prefabRootName: str | None = None
|
||||||
|
mode: str | None = None
|
||||||
|
isDirty: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
class PrefabStageResponse(MCPResponse):
|
||||||
|
"""Information about the current prefab editing context."""
|
||||||
|
data: PrefabStageData = PrefabStageData()
|
||||||
|
|
||||||
|
|
||||||
|
@mcp_for_unity_resource(
|
||||||
|
uri="unity://editor/prefab-stage",
|
||||||
|
name="editor_prefab_stage",
|
||||||
|
description="Current prefab editing context if a prefab is open in isolation mode. Returns isOpen=false if no prefab is being edited."
|
||||||
|
)
|
||||||
|
async def get_prefab_stage(ctx: Context) -> PrefabStageResponse | MCPResponse:
|
||||||
|
"""Get current prefab stage information."""
|
||||||
|
unity_instance = get_unity_instance_from_context(ctx)
|
||||||
|
response = await async_send_with_unity_instance(
|
||||||
|
async_send_command_with_retry,
|
||||||
|
unity_instance,
|
||||||
|
"get_prefab_stage",
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
return PrefabStageResponse(**response) if isinstance(response, dict) else response
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from fastmcp import Context
|
||||||
|
|
||||||
|
from models import MCPResponse
|
||||||
|
from registry import mcp_for_unity_resource
|
||||||
|
from tools import get_unity_instance_from_context, async_send_with_unity_instance
|
||||||
|
from unity_connection import async_send_command_with_retry
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectInfoData(BaseModel):
|
||||||
|
"""Project info data fields."""
|
||||||
|
projectRoot: str = ""
|
||||||
|
projectName: str = ""
|
||||||
|
unityVersion: str = ""
|
||||||
|
platform: str = ""
|
||||||
|
assetsPath: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectInfoResponse(MCPResponse):
|
||||||
|
"""Static project configuration information."""
|
||||||
|
data: ProjectInfoData = ProjectInfoData()
|
||||||
|
|
||||||
|
|
||||||
|
@mcp_for_unity_resource(
|
||||||
|
uri="unity://project/info",
|
||||||
|
name="project_info",
|
||||||
|
description="Static project information including root path, Unity version, and platform. This data rarely changes."
|
||||||
|
)
|
||||||
|
async def get_project_info(ctx: Context) -> ProjectInfoResponse | MCPResponse:
|
||||||
|
"""Get static project configuration information."""
|
||||||
|
unity_instance = get_unity_instance_from_context(ctx)
|
||||||
|
response = await async_send_with_unity_instance(
|
||||||
|
async_send_command_with_retry,
|
||||||
|
unity_instance,
|
||||||
|
"get_project_info",
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
return ProjectInfoResponse(**response) if isinstance(response, dict) else response
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from fastmcp import Context
|
||||||
|
|
||||||
|
from models import MCPResponse
|
||||||
|
from registry import mcp_for_unity_resource
|
||||||
|
from tools import get_unity_instance_from_context, async_send_with_unity_instance
|
||||||
|
from unity_connection import async_send_command_with_retry
|
||||||
|
|
||||||
|
|
||||||
|
class SelectionObjectInfo(BaseModel):
|
||||||
|
"""Information about a selected object."""
|
||||||
|
name: str | None = None
|
||||||
|
type: str | None = None
|
||||||
|
instanceID: int | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class SelectionGameObjectInfo(BaseModel):
|
||||||
|
"""Information about a selected GameObject."""
|
||||||
|
name: str | None = None
|
||||||
|
instanceID: int | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class SelectionData(BaseModel):
|
||||||
|
"""Selection data fields."""
|
||||||
|
activeObject: str | None = None
|
||||||
|
activeGameObject: str | None = None
|
||||||
|
activeTransform: str | None = None
|
||||||
|
activeInstanceID: int = 0
|
||||||
|
count: int = 0
|
||||||
|
objects: list[SelectionObjectInfo] = []
|
||||||
|
gameObjects: list[SelectionGameObjectInfo] = []
|
||||||
|
assetGUIDs: list[str] = []
|
||||||
|
|
||||||
|
|
||||||
|
class SelectionResponse(MCPResponse):
|
||||||
|
"""Detailed information about the current editor selection."""
|
||||||
|
data: SelectionData = SelectionData()
|
||||||
|
|
||||||
|
|
||||||
|
@mcp_for_unity_resource(
|
||||||
|
uri="unity://editor/selection",
|
||||||
|
name="editor_selection",
|
||||||
|
description="Detailed information about currently selected objects in the editor, including GameObjects, assets, and their properties."
|
||||||
|
)
|
||||||
|
async def get_selection(ctx: Context) -> SelectionResponse | MCPResponse:
|
||||||
|
"""Get detailed editor selection information."""
|
||||||
|
unity_instance = get_unity_instance_from_context(ctx)
|
||||||
|
response = await async_send_with_unity_instance(
|
||||||
|
async_send_command_with_retry,
|
||||||
|
unity_instance,
|
||||||
|
"get_selection",
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
return SelectionResponse(**response) if isinstance(response, dict) else response
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
from pydantic import Field
|
||||||
|
from fastmcp import Context
|
||||||
|
|
||||||
|
from models import MCPResponse
|
||||||
|
from registry import mcp_for_unity_resource
|
||||||
|
from tools import get_unity_instance_from_context, async_send_with_unity_instance
|
||||||
|
from unity_connection import async_send_command_with_retry
|
||||||
|
|
||||||
|
|
||||||
|
class TagsResponse(MCPResponse):
|
||||||
|
"""List of all tags in the project."""
|
||||||
|
data: list[str] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
@mcp_for_unity_resource(
|
||||||
|
uri="unity://project/tags",
|
||||||
|
name="project_tags",
|
||||||
|
description="All tags defined in the project's TagManager. Read this before using add_tag or remove_tag tools."
|
||||||
|
)
|
||||||
|
async def get_tags(ctx: Context) -> TagsResponse | MCPResponse:
|
||||||
|
"""Get all project tags."""
|
||||||
|
unity_instance = get_unity_instance_from_context(ctx)
|
||||||
|
response = await async_send_with_unity_instance(
|
||||||
|
async_send_command_with_retry,
|
||||||
|
unity_instance,
|
||||||
|
"get_tags",
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
return TagsResponse(**response) if isinstance(response, dict) else response
|
||||||
|
|
@ -21,7 +21,7 @@ class GetTestsResponse(MCPResponse):
|
||||||
|
|
||||||
|
|
||||||
@mcp_for_unity_resource(uri="mcpforunity://tests", name="get_tests", description="Provides a list of all tests.")
|
@mcp_for_unity_resource(uri="mcpforunity://tests", name="get_tests", description="Provides a list of all tests.")
|
||||||
async def get_tests(ctx: Context) -> GetTestsResponse:
|
async def get_tests(ctx: Context) -> GetTestsResponse | MCPResponse:
|
||||||
"""Provides a list of all tests.
|
"""Provides a list of all tests.
|
||||||
"""
|
"""
|
||||||
unity_instance = get_unity_instance_from_context(ctx)
|
unity_instance = get_unity_instance_from_context(ctx)
|
||||||
|
|
@ -38,7 +38,7 @@ async def get_tests(ctx: Context) -> GetTestsResponse:
|
||||||
async def get_tests_for_mode(
|
async def get_tests_for_mode(
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
mode: Annotated[Literal["EditMode", "PlayMode"], Field(description="The mode to filter tests by.")],
|
mode: Annotated[Literal["EditMode", "PlayMode"], Field(description="The mode to filter tests by.")],
|
||||||
) -> GetTestsResponse:
|
) -> GetTestsResponse | MCPResponse:
|
||||||
"""Provides a list of tests for a specific mode.
|
"""Provides a list of tests for a specific mode.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ from unity_connection import get_unity_connection_pool
|
||||||
name="unity_instances",
|
name="unity_instances",
|
||||||
description="Lists all running Unity Editor instances with their details."
|
description="Lists all running Unity Editor instances with their details."
|
||||||
)
|
)
|
||||||
def unity_instances(ctx: Context) -> dict[str, Any]:
|
async def unity_instances(ctx: Context) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
List all available Unity Editor instances.
|
List all available Unity Editor instances.
|
||||||
|
|
||||||
|
|
@ -30,7 +30,7 @@ def unity_instances(ctx: Context) -> dict[str, Any]:
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary containing list of instances and metadata
|
Dictionary containing list of instances and metadata
|
||||||
"""
|
"""
|
||||||
ctx.info("Listing Unity instances")
|
await ctx.info("Listing Unity instances")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
pool = get_unity_connection_pool()
|
pool = get_unity_connection_pool()
|
||||||
|
|
@ -58,7 +58,7 @@ def unity_instances(ctx: Context) -> dict[str, Any]:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
ctx.error(f"Error listing Unity instances: {e}")
|
await ctx.error(f"Error listing Unity instances: {e}")
|
||||||
return {
|
return {
|
||||||
"success": False,
|
"success": False,
|
||||||
"error": f"Failed to list Unity instances: {str(e)}",
|
"error": f"Failed to list Unity instances: {str(e)}",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from fastmcp import Context
|
||||||
|
|
||||||
|
from models import MCPResponse
|
||||||
|
from registry import mcp_for_unity_resource
|
||||||
|
from tools import get_unity_instance_from_context, async_send_with_unity_instance
|
||||||
|
from unity_connection import async_send_command_with_retry
|
||||||
|
|
||||||
|
|
||||||
|
class WindowPosition(BaseModel):
|
||||||
|
"""Window position and size."""
|
||||||
|
x: float = 0.0
|
||||||
|
y: float = 0.0
|
||||||
|
width: float = 0.0
|
||||||
|
height: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
|
class WindowInfo(BaseModel):
|
||||||
|
"""Information about an editor window."""
|
||||||
|
title: str = ""
|
||||||
|
typeName: str = ""
|
||||||
|
isFocused: bool = False
|
||||||
|
position: WindowPosition = WindowPosition()
|
||||||
|
instanceID: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
class WindowsResponse(MCPResponse):
|
||||||
|
"""List of all open editor windows."""
|
||||||
|
data: list[WindowInfo] = []
|
||||||
|
|
||||||
|
|
||||||
|
@mcp_for_unity_resource(
|
||||||
|
uri="unity://editor/windows",
|
||||||
|
name="editor_windows",
|
||||||
|
description="All currently open editor windows with their titles, types, positions, and focus state."
|
||||||
|
)
|
||||||
|
async def get_windows(ctx: Context) -> WindowsResponse | MCPResponse:
|
||||||
|
"""Get all open editor windows."""
|
||||||
|
unity_instance = get_unity_instance_from_context(ctx)
|
||||||
|
response = await async_send_with_unity_instance(
|
||||||
|
async_send_command_with_retry,
|
||||||
|
unity_instance,
|
||||||
|
"get_windows",
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
return WindowsResponse(**response) if isinstance(response, dict) else response
|
||||||
|
|
@ -172,23 +172,36 @@ mcp = FastMCP(
|
||||||
name="mcp-for-unity-server",
|
name="mcp-for-unity-server",
|
||||||
lifespan=server_lifespan,
|
lifespan=server_lifespan,
|
||||||
instructions="""
|
instructions="""
|
||||||
This server provides tools to interact with the Unity Game Engine Editor.\n\n
|
This server provides tools to interact with the Unity Game Engine Editor.
|
||||||
Available tools:\n
|
|
||||||
- `manage_editor`: Controls editor state and queries info.\n
|
|
||||||
- `execute_menu_item`: Executes, lists and checks for the existence of Unity Editor menu items.\n
|
|
||||||
- `read_console`: Reads or clears Unity console messages, with filtering options.\n
|
|
||||||
- `manage_scene`: Manages scenes.\n
|
|
||||||
- `manage_gameobject`: Manages GameObjects in the scene.\n
|
|
||||||
- `manage_script`: Manages C# script files.\n
|
|
||||||
- `manage_asset`: Manages prefabs and assets.\n
|
|
||||||
- `manage_shader`: Manages shaders.\n\n
|
|
||||||
- Tips:\n
|
|
||||||
- Create prefabs for reusable GameObjects.\n
|
|
||||||
- Always include a camera and main light in your scenes.\n
|
|
||||||
- Unless specified otherwise, paths are relative to the project's `Assets/` folder.\n
|
|
||||||
- After creating or modifying scripts with `manage_script`, allow Unity to recompile; use `read_console` to check for compile errors.\n
|
|
||||||
- Use `execute_menu_item` for interacting with Unity systems and third party tools like a user would.\n
|
|
||||||
|
|
||||||
|
Important Workflows:
|
||||||
|
|
||||||
|
Resources vs Tools:
|
||||||
|
- Use RESOURCES to read editor state (editor_state, project_info, project_tags, tests, etc)
|
||||||
|
- Use TOOLS to perform actions and mutations (manage_editor for play mode control, tag/layer management, etc)
|
||||||
|
- Always check related resources before modifying the engine state with tools
|
||||||
|
|
||||||
|
Script Management:
|
||||||
|
- After creating or modifying scripts (by your own tools or the `manage_script` tool) use `read_console` to check for compilation errors before proceeding
|
||||||
|
- Only after successful compilation can new components/types be used
|
||||||
|
- You can poll the `editor_state` resource's `isCompiling` field to check if the domain reload is complete
|
||||||
|
|
||||||
|
Scene Setup:
|
||||||
|
- Always include a Camera and main Light (Directional Light) in new scenes
|
||||||
|
- Create prefabs with `manage_asset` for reusable GameObjects
|
||||||
|
- Use `manage_scene` to load, save, and query scene information
|
||||||
|
|
||||||
|
Path Conventions:
|
||||||
|
- Unless specified otherwise, all paths are relative to the project's `Assets/` folder
|
||||||
|
- Use forward slashes (/) in paths for cross-platform compatibility
|
||||||
|
|
||||||
|
Console Monitoring:
|
||||||
|
- Check `read_console` regularly to catch errors, warnings, and compilation status
|
||||||
|
- Filter by log type (Error, Warning, Log) to focus on specific issues
|
||||||
|
|
||||||
|
Menu Items:
|
||||||
|
- 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
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import types
|
||||||
|
|
||||||
|
# Ensure telemetry is disabled during test collection and execution to avoid
|
||||||
|
# any background network or thread startup that could slow or block pytest.
|
||||||
|
os.environ.setdefault("DISABLE_TELEMETRY", "true")
|
||||||
|
os.environ.setdefault("UNITY_MCP_DISABLE_TELEMETRY", "true")
|
||||||
|
os.environ.setdefault("MCP_DISABLE_TELEMETRY", "true")
|
||||||
|
|
||||||
|
# NOTE: These tests are integration tests for the MCP server Python code.
|
||||||
|
# They test tools, resources, and utilities without requiring Unity to be running.
|
||||||
|
# Tests can now import directly from the parent package since they're inside src/
|
||||||
|
# To run: cd MCPForUnity/UnityMcpServer~/src && uv run pytest tests/integration/ -v
|
||||||
|
|
||||||
|
# Stub telemetry modules to avoid file I/O during import of tools package
|
||||||
|
telemetry = types.ModuleType("telemetry")
|
||||||
|
def _noop(*args, **kwargs):
|
||||||
|
pass
|
||||||
|
class MilestoneType:
|
||||||
|
pass
|
||||||
|
telemetry.record_resource_usage = _noop
|
||||||
|
telemetry.record_tool_usage = _noop
|
||||||
|
telemetry.record_milestone = _noop
|
||||||
|
telemetry.MilestoneType = MilestoneType
|
||||||
|
telemetry.get_package_version = lambda: "0.0.0"
|
||||||
|
sys.modules.setdefault("telemetry", telemetry)
|
||||||
|
|
||||||
|
telemetry_decorator = types.ModuleType("telemetry_decorator")
|
||||||
|
def telemetry_tool(*dargs, **dkwargs):
|
||||||
|
def _wrap(fn):
|
||||||
|
return fn
|
||||||
|
return _wrap
|
||||||
|
telemetry_decorator.telemetry_tool = telemetry_tool
|
||||||
|
sys.modules.setdefault("telemetry_decorator", telemetry_decorator)
|
||||||
|
|
||||||
|
# Stub fastmcp module (not mcp.server.fastmcp)
|
||||||
|
fastmcp = types.ModuleType("fastmcp")
|
||||||
|
|
||||||
|
class _DummyFastMCP:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class _DummyContext:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class _DummyMiddleware:
|
||||||
|
"""Base middleware class stub."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class _DummyMiddlewareContext:
|
||||||
|
"""Middleware context stub."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
fastmcp.FastMCP = _DummyFastMCP
|
||||||
|
fastmcp.Context = _DummyContext
|
||||||
|
sys.modules.setdefault("fastmcp", fastmcp)
|
||||||
|
|
||||||
|
# Stub fastmcp.server.middleware submodule
|
||||||
|
fastmcp_server = types.ModuleType("fastmcp.server")
|
||||||
|
fastmcp_server_middleware = types.ModuleType("fastmcp.server.middleware")
|
||||||
|
fastmcp_server_middleware.Middleware = _DummyMiddleware
|
||||||
|
fastmcp_server_middleware.MiddlewareContext = _DummyMiddlewareContext
|
||||||
|
fastmcp.server = fastmcp_server
|
||||||
|
fastmcp_server.middleware = fastmcp_server_middleware
|
||||||
|
sys.modules.setdefault("fastmcp.server", fastmcp_server)
|
||||||
|
sys.modules.setdefault("fastmcp.server.middleware", fastmcp_server_middleware)
|
||||||
|
|
@ -1,23 +1,4 @@
|
||||||
import sys
|
from .test_helpers import DummyContext
|
||||||
import pathlib
|
|
||||||
import importlib.util
|
|
||||||
|
|
||||||
|
|
||||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
|
||||||
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
|
|
||||||
sys.path.insert(0, str(SRC))
|
|
||||||
|
|
||||||
|
|
||||||
def _load(path: pathlib.Path, name: str):
|
|
||||||
spec = importlib.util.spec_from_file_location(name, path)
|
|
||||||
mod = importlib.util.module_from_spec(spec)
|
|
||||||
spec.loader.exec_module(mod)
|
|
||||||
return mod
|
|
||||||
|
|
||||||
|
|
||||||
manage_script = _load(SRC / "tools" / "manage_script.py", "manage_script_mod2")
|
|
||||||
manage_script_edits = _load(
|
|
||||||
SRC / "tools" / "script_apply_edits.py", "script_apply_edits_mod2")
|
|
||||||
|
|
||||||
|
|
||||||
class DummyMCP:
|
class DummyMCP:
|
||||||
|
|
@ -28,9 +9,6 @@ class DummyMCP:
|
||||||
return deco
|
return deco
|
||||||
|
|
||||||
|
|
||||||
from tests.test_helpers import DummyContext
|
|
||||||
|
|
||||||
|
|
||||||
def setup_tools():
|
def setup_tools():
|
||||||
mcp = DummyMCP()
|
mcp = DummyMCP()
|
||||||
# Import the tools module to trigger decorator registration
|
# Import the tools module to trigger decorator registration
|
||||||
|
|
@ -1,53 +1,4 @@
|
||||||
import sys
|
from .test_helpers import DummyContext
|
||||||
import pathlib
|
|
||||||
import importlib.util
|
|
||||||
import types
|
|
||||||
|
|
||||||
from tests.test_helpers import DummyContext
|
|
||||||
|
|
||||||
|
|
||||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
|
||||||
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
|
|
||||||
sys.path.insert(0, str(SRC))
|
|
||||||
|
|
||||||
# Stub telemetry modules to avoid file I/O during import of tools package
|
|
||||||
telemetry = types.ModuleType("telemetry")
|
|
||||||
def _noop(*args, **kwargs):
|
|
||||||
pass
|
|
||||||
class MilestoneType:
|
|
||||||
pass
|
|
||||||
telemetry.record_resource_usage = _noop
|
|
||||||
telemetry.record_tool_usage = _noop
|
|
||||||
telemetry.record_milestone = _noop
|
|
||||||
telemetry.MilestoneType = MilestoneType
|
|
||||||
telemetry.get_package_version = lambda: "0.0.0"
|
|
||||||
sys.modules.setdefault("telemetry", telemetry)
|
|
||||||
|
|
||||||
telemetry_decorator = types.ModuleType("telemetry_decorator")
|
|
||||||
def telemetry_tool(*dargs, **dkwargs):
|
|
||||||
def _wrap(fn):
|
|
||||||
return fn
|
|
||||||
return _wrap
|
|
||||||
telemetry_decorator.telemetry_tool = telemetry_tool
|
|
||||||
sys.modules.setdefault("telemetry_decorator", telemetry_decorator)
|
|
||||||
|
|
||||||
# stub mcp.server.fastmcp
|
|
||||||
mcp_pkg = types.ModuleType("mcp")
|
|
||||||
server_pkg = types.ModuleType("mcp.server")
|
|
||||||
fastmcp_pkg = types.ModuleType("mcp.server.fastmcp")
|
|
||||||
|
|
||||||
|
|
||||||
class _Dummy:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
fastmcp_pkg.FastMCP = _Dummy
|
|
||||||
fastmcp_pkg.Context = _Dummy
|
|
||||||
server_pkg.fastmcp = fastmcp_pkg
|
|
||||||
mcp_pkg.server = server_pkg
|
|
||||||
sys.modules.setdefault("mcp", mcp_pkg)
|
|
||||||
sys.modules.setdefault("mcp.server", server_pkg)
|
|
||||||
sys.modules.setdefault("mcp.server.fastmcp", fastmcp_pkg)
|
|
||||||
|
|
||||||
|
|
||||||
class DummyMCP:
|
class DummyMCP:
|
||||||
|
|
@ -1,36 +1,7 @@
|
||||||
import sys
|
|
||||||
import pathlib
|
|
||||||
import importlib.util
|
|
||||||
import types
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from tests.test_helpers import DummyContext
|
from .test_helpers import DummyContext
|
||||||
|
|
||||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
|
||||||
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
|
|
||||||
sys.path.insert(0, str(SRC))
|
|
||||||
|
|
||||||
# Stub telemetry modules to avoid file I/O during import of tools package
|
|
||||||
telemetry = types.ModuleType("telemetry")
|
|
||||||
def _noop(*args, **kwargs):
|
|
||||||
pass
|
|
||||||
class MilestoneType:
|
|
||||||
pass
|
|
||||||
telemetry.record_resource_usage = _noop
|
|
||||||
telemetry.record_tool_usage = _noop
|
|
||||||
telemetry.record_milestone = _noop
|
|
||||||
telemetry.MilestoneType = MilestoneType
|
|
||||||
telemetry.get_package_version = lambda: "0.0.0"
|
|
||||||
sys.modules.setdefault("telemetry", telemetry)
|
|
||||||
|
|
||||||
telemetry_decorator = types.ModuleType("telemetry_decorator")
|
|
||||||
def telemetry_tool(*dargs, **dkwargs):
|
|
||||||
def _wrap(fn):
|
|
||||||
return fn
|
|
||||||
return _wrap
|
|
||||||
telemetry_decorator.telemetry_tool = telemetry_tool
|
|
||||||
sys.modules.setdefault("telemetry_decorator", telemetry_decorator)
|
|
||||||
|
|
||||||
|
|
||||||
class DummyMCP:
|
class DummyMCP:
|
||||||
|
|
@ -1,24 +1,4 @@
|
||||||
import sys
|
from .test_helpers import DummyContext
|
||||||
import pathlib
|
|
||||||
import importlib.util
|
|
||||||
|
|
||||||
|
|
||||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
|
||||||
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
|
|
||||||
sys.path.insert(0, str(SRC))
|
|
||||||
|
|
||||||
|
|
||||||
def _load_module(path: pathlib.Path, name: str):
|
|
||||||
spec = importlib.util.spec_from_file_location(name, path)
|
|
||||||
if spec is None or spec.loader is None:
|
|
||||||
raise ImportError(f"Cannot load module {name} from {path}")
|
|
||||||
mod = importlib.util.module_from_spec(spec)
|
|
||||||
spec.loader.exec_module(mod)
|
|
||||||
return mod
|
|
||||||
|
|
||||||
|
|
||||||
manage_script = _load_module(
|
|
||||||
SRC / "tools" / "manage_script.py", "manage_script_mod")
|
|
||||||
|
|
||||||
|
|
||||||
class DummyMCP:
|
class DummyMCP:
|
||||||
|
|
@ -32,9 +12,6 @@ class DummyMCP:
|
||||||
return deco
|
return deco
|
||||||
|
|
||||||
|
|
||||||
from tests.test_helpers import DummyContext
|
|
||||||
|
|
||||||
|
|
||||||
def setup_tools():
|
def setup_tools():
|
||||||
mcp = DummyMCP()
|
mcp = DummyMCP()
|
||||||
# Import the tools module to trigger decorator registration
|
# Import the tools module to trigger decorator registration
|
||||||
|
|
@ -2,25 +2,9 @@
|
||||||
Test the improved anchor matching logic.
|
Test the improved anchor matching logic.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import re
|
||||||
import pathlib
|
|
||||||
import importlib.util
|
|
||||||
|
|
||||||
# add server src to path and load modules
|
import tools.script_apply_edits as script_apply_edits_module
|
||||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
|
||||||
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
|
|
||||||
sys.path.insert(0, str(SRC))
|
|
||||||
|
|
||||||
|
|
||||||
def load_module(path, name):
|
|
||||||
spec = importlib.util.spec_from_file_location(name, path)
|
|
||||||
module = importlib.util.module_from_spec(spec)
|
|
||||||
spec.loader.exec_module(module)
|
|
||||||
return module
|
|
||||||
|
|
||||||
|
|
||||||
script_apply_edits_module = load_module(
|
|
||||||
SRC / "tools" / "script_apply_edits.py", "script_apply_edits_module")
|
|
||||||
|
|
||||||
|
|
||||||
def test_improved_anchor_matching():
|
def test_improved_anchor_matching():
|
||||||
|
|
@ -41,8 +25,6 @@ public class TestClass : MonoBehaviour
|
||||||
}
|
}
|
||||||
}'''
|
}'''
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
# Test the problematic anchor pattern
|
# Test the problematic anchor pattern
|
||||||
anchor_pattern = r"\s*}\s*$"
|
anchor_pattern = r"\s*}\s*$"
|
||||||
flags = re.MULTILINE
|
flags = re.MULTILINE
|
||||||
|
|
@ -10,18 +10,10 @@ DESIGN: Single source of truth via middleware state:
|
||||||
- get_unity_instance_from_context() reads from ctx.get_state()
|
- get_unity_instance_from_context() reads from ctx.get_state()
|
||||||
- All tools (GameObject, Script, Asset, etc.) use get_unity_instance_from_context()
|
- All tools (GameObject, Script, Asset, etc.) use get_unity_instance_from_context()
|
||||||
"""
|
"""
|
||||||
import sys
|
|
||||||
import pathlib
|
|
||||||
import pytest
|
import pytest
|
||||||
from unittest.mock import AsyncMock, Mock, MagicMock, patch
|
from unittest.mock import AsyncMock, Mock, MagicMock, patch
|
||||||
from fastmcp import Context
|
from fastmcp import Context
|
||||||
|
|
||||||
# Add Server source to path
|
|
||||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
|
||||||
SRC = ROOT / "Server"
|
|
||||||
if str(SRC) not in sys.path:
|
|
||||||
sys.path.insert(0, str(SRC))
|
|
||||||
|
|
||||||
from unity_instance_middleware import UnityInstanceMiddleware
|
from unity_instance_middleware import UnityInstanceMiddleware
|
||||||
from tools import get_unity_instance_from_context
|
from tools import get_unity_instance_from_context
|
||||||
|
|
||||||
|
|
@ -1,11 +1,4 @@
|
||||||
import sys
|
from .test_helpers import DummyContext
|
||||||
import pathlib
|
|
||||||
from tests.test_helpers import DummyContext
|
|
||||||
|
|
||||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
|
||||||
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
|
|
||||||
if str(SRC) not in sys.path:
|
|
||||||
sys.path.insert(0, str(SRC))
|
|
||||||
|
|
||||||
|
|
||||||
def test_manage_gameobject_uses_session_state(monkeypatch):
|
def test_manage_gameobject_uses_session_state(monkeypatch):
|
||||||
|
|
@ -4,7 +4,7 @@ Tests for JSON string parameter parsing in manage_asset tool.
|
||||||
import pytest
|
import pytest
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from tests.test_helpers import DummyContext
|
from .test_helpers import DummyContext
|
||||||
from tools.manage_asset import manage_asset
|
from tools.manage_asset import manage_asset
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
from .test_helpers import DummyContext
|
||||||
|
import tools.manage_asset as manage_asset_mod
|
||||||
|
|
||||||
|
|
||||||
|
def test_manage_asset_pagination_coercion(monkeypatch):
|
||||||
|
captured = {}
|
||||||
|
|
||||||
|
async def fake_async_send(cmd, params, **kwargs):
|
||||||
|
captured["params"] = params
|
||||||
|
return {"success": True, "data": {}}
|
||||||
|
|
||||||
|
monkeypatch.setattr(manage_asset_mod, "async_send_command_with_retry", fake_async_send)
|
||||||
|
|
||||||
|
result = asyncio.run(
|
||||||
|
manage_asset_mod.manage_asset(
|
||||||
|
ctx=DummyContext(),
|
||||||
|
action="search",
|
||||||
|
path="Assets",
|
||||||
|
page_size="50",
|
||||||
|
page_number="2",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result == {"success": True, "data": {}}
|
||||||
|
assert captured["params"]["pageSize"] == 50
|
||||||
|
assert captured["params"]["pageNumber"] == 2
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
from .test_helpers import DummyContext
|
||||||
|
import tools.manage_gameobject as manage_go_mod
|
||||||
|
|
||||||
|
|
||||||
|
def test_manage_gameobject_boolean_and_tag_mapping(monkeypatch):
|
||||||
|
captured = {}
|
||||||
|
|
||||||
|
def fake_send(cmd, params):
|
||||||
|
captured["params"] = params
|
||||||
|
return {"success": True, "data": {}}
|
||||||
|
|
||||||
|
monkeypatch.setattr(manage_go_mod, "send_command_with_retry", fake_send)
|
||||||
|
|
||||||
|
# find by tag: allow tag to map to searchTerm
|
||||||
|
resp = manage_go_mod.manage_gameobject(
|
||||||
|
ctx=DummyContext(),
|
||||||
|
action="find",
|
||||||
|
search_method="by_tag",
|
||||||
|
tag="Player",
|
||||||
|
find_all="true",
|
||||||
|
search_inactive="0",
|
||||||
|
)
|
||||||
|
# Loosen equality: wrapper may include a diagnostic message
|
||||||
|
assert resp.get("success") is True
|
||||||
|
assert "data" in resp
|
||||||
|
# ensure tag mapped to searchTerm and booleans passed through; C# side coerces true/false already
|
||||||
|
assert captured["params"]["searchTerm"] == "Player"
|
||||||
|
assert captured["params"]["findAll"] == "true" or captured["params"]["findAll"] is True
|
||||||
|
assert captured["params"]["searchInactive"] in ("0", False, 0)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,40 +1,6 @@
|
||||||
# import triggers registration elsewhere; no direct use here
|
|
||||||
import sys
|
|
||||||
import types
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from .test_helpers import DummyContext
|
||||||
# Locate server src dynamically to avoid hardcoded layout assumptions (same as other tests)
|
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
|
||||||
candidates = [
|
|
||||||
ROOT / "MCPForUnity" / "UnityMcpServer~" / "src",
|
|
||||||
ROOT / "UnityMcpServer~" / "src",
|
|
||||||
]
|
|
||||||
SRC = next((p for p in candidates if p.exists()), None)
|
|
||||||
if SRC is None:
|
|
||||||
searched = "\n".join(str(p) for p in candidates)
|
|
||||||
pytest.skip(
|
|
||||||
"MCP for Unity server source not found. Tried:\n" + searched,
|
|
||||||
allow_module_level=True,
|
|
||||||
)
|
|
||||||
sys.path.insert(0, str(SRC))
|
|
||||||
|
|
||||||
# Stub fastmcp to avoid real MCP deps
|
|
||||||
fastmcp_pkg = types.ModuleType("fastmcp")
|
|
||||||
|
|
||||||
|
|
||||||
class _Dummy:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
fastmcp_pkg.FastMCP = _Dummy
|
|
||||||
fastmcp_pkg.Context = _Dummy
|
|
||||||
sys.modules.setdefault("fastmcp", fastmcp_pkg)
|
|
||||||
|
|
||||||
|
|
||||||
# Import target module after path injection
|
|
||||||
|
|
||||||
|
|
||||||
class DummyMCP:
|
class DummyMCP:
|
||||||
|
|
@ -48,12 +14,6 @@ class DummyMCP:
|
||||||
return _decorator
|
return _decorator
|
||||||
|
|
||||||
|
|
||||||
# (removed unused DummyCtx)
|
|
||||||
|
|
||||||
|
|
||||||
from tests.test_helpers import DummyContext
|
|
||||||
|
|
||||||
|
|
||||||
def _register_tools():
|
def _register_tools():
|
||||||
mcp = DummyMCP()
|
mcp = DummyMCP()
|
||||||
# Import the tools module to trigger decorator registration
|
# Import the tools module to trigger decorator registration
|
||||||
|
|
@ -1,23 +1,4 @@
|
||||||
import sys
|
from .test_helpers import DummyContext
|
||||||
import pathlib
|
|
||||||
import importlib.util
|
|
||||||
|
|
||||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
|
||||||
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
|
|
||||||
sys.path.insert(0, str(SRC))
|
|
||||||
|
|
||||||
|
|
||||||
def _load_module(path: pathlib.Path, name: str):
|
|
||||||
spec = importlib.util.spec_from_file_location(name, path)
|
|
||||||
if spec is None or spec.loader is None:
|
|
||||||
raise ImportError(f"Cannot load module {name} from {path}")
|
|
||||||
mod = importlib.util.module_from_spec(spec)
|
|
||||||
spec.loader.exec_module(mod)
|
|
||||||
return mod
|
|
||||||
|
|
||||||
|
|
||||||
read_console_mod = _load_module(
|
|
||||||
SRC / "tools" / "read_console.py", "read_console_mod")
|
|
||||||
|
|
||||||
|
|
||||||
class DummyMCP:
|
class DummyMCP:
|
||||||
|
|
@ -31,9 +12,6 @@ class DummyMCP:
|
||||||
return deco
|
return deco
|
||||||
|
|
||||||
|
|
||||||
from tests.test_helpers import DummyContext
|
|
||||||
|
|
||||||
|
|
||||||
def setup_tools():
|
def setup_tools():
|
||||||
mcp = DummyMCP()
|
mcp = DummyMCP()
|
||||||
# Import the tools module to trigger decorator registration
|
# Import the tools module to trigger decorator registration
|
||||||
|
|
@ -1,30 +1,7 @@
|
||||||
import sys
|
|
||||||
import pathlib
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import types
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
from .test_helpers import DummyContext
|
||||||
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
|
|
||||||
sys.path.insert(0, str(SRC))
|
|
||||||
|
|
||||||
# Stub mcp.server.fastmcp to satisfy imports without full package
|
|
||||||
mcp_pkg = types.ModuleType("mcp")
|
|
||||||
server_pkg = types.ModuleType("mcp.server")
|
|
||||||
fastmcp_pkg = types.ModuleType("mcp.server.fastmcp")
|
|
||||||
|
|
||||||
|
|
||||||
class _Dummy:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
fastmcp_pkg.FastMCP = _Dummy
|
|
||||||
fastmcp_pkg.Context = _Dummy
|
|
||||||
server_pkg.fastmcp = fastmcp_pkg
|
|
||||||
mcp_pkg.server = server_pkg
|
|
||||||
sys.modules.setdefault("mcp", mcp_pkg)
|
|
||||||
sys.modules.setdefault("mcp.server", server_pkg)
|
|
||||||
sys.modules.setdefault("mcp.server.fastmcp", fastmcp_pkg)
|
|
||||||
|
|
||||||
|
|
||||||
class DummyMCP:
|
class DummyMCP:
|
||||||
|
|
@ -38,9 +15,6 @@ class DummyMCP:
|
||||||
return deco
|
return deco
|
||||||
|
|
||||||
|
|
||||||
from tests.test_helpers import DummyContext
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def resource_tools():
|
def resource_tools():
|
||||||
mcp = DummyMCP()
|
mcp = DummyMCP()
|
||||||
|
|
@ -1,33 +1,6 @@
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
import pytest
|
import pytest
|
||||||
import types
|
|
||||||
|
|
||||||
# locate server src dynamically to avoid hardcoded layout assumptions
|
from .test_helpers import DummyContext
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
|
||||||
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
|
|
||||||
sys.path.insert(0, str(SRC))
|
|
||||||
|
|
||||||
# Stub telemetry modules to avoid file I/O during import of tools package
|
|
||||||
telemetry = types.ModuleType("telemetry")
|
|
||||||
def _noop(*args, **kwargs):
|
|
||||||
pass
|
|
||||||
class MilestoneType: # minimal placeholder
|
|
||||||
pass
|
|
||||||
telemetry.record_resource_usage = _noop
|
|
||||||
telemetry.record_tool_usage = _noop
|
|
||||||
telemetry.record_milestone = _noop
|
|
||||||
telemetry.MilestoneType = MilestoneType
|
|
||||||
telemetry.get_package_version = lambda: "0.0.0"
|
|
||||||
sys.modules.setdefault("telemetry", telemetry)
|
|
||||||
|
|
||||||
telemetry_decorator = types.ModuleType("telemetry_decorator")
|
|
||||||
def telemetry_tool(*_args, **_kwargs):
|
|
||||||
def _wrap(fn):
|
|
||||||
return fn
|
|
||||||
return _wrap
|
|
||||||
telemetry_decorator.telemetry_tool = telemetry_tool
|
|
||||||
sys.modules.setdefault("telemetry_decorator", telemetry_decorator)
|
|
||||||
|
|
||||||
|
|
||||||
class DummyMCP:
|
class DummyMCP:
|
||||||
|
|
@ -41,9 +14,6 @@ class DummyMCP:
|
||||||
return deco
|
return deco
|
||||||
|
|
||||||
|
|
||||||
from tests.test_helpers import DummyContext
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def resource_tools():
|
def resource_tools():
|
||||||
mcp = DummyMCP()
|
mcp = DummyMCP()
|
||||||
|
|
@ -1,26 +1,7 @@
|
||||||
import sys
|
|
||||||
import pathlib
|
|
||||||
import importlib.util
|
|
||||||
import pytest
|
import pytest
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
# add server src to path and load modules without triggering package imports
|
from .test_helpers import DummyContext
|
||||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
|
||||||
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
|
|
||||||
sys.path.insert(0, str(SRC))
|
|
||||||
|
|
||||||
|
|
||||||
def load_module(path, name):
|
|
||||||
spec = importlib.util.spec_from_file_location(name, path)
|
|
||||||
module = importlib.util.module_from_spec(spec)
|
|
||||||
spec.loader.exec_module(module)
|
|
||||||
return module
|
|
||||||
|
|
||||||
|
|
||||||
manage_script_module = load_module(
|
|
||||||
SRC / "tools" / "manage_script.py", "manage_script_module")
|
|
||||||
manage_asset_module = load_module(
|
|
||||||
SRC / "tools" / "manage_asset.py", "manage_asset_module")
|
|
||||||
|
|
||||||
|
|
||||||
class DummyMCP:
|
class DummyMCP:
|
||||||
|
|
@ -34,9 +15,6 @@ class DummyMCP:
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
from tests.test_helpers import DummyContext
|
|
||||||
|
|
||||||
|
|
||||||
def setup_manage_script():
|
def setup_manage_script():
|
||||||
mcp = DummyMCP()
|
mcp = DummyMCP()
|
||||||
# Import the tools module to trigger decorator registration
|
# Import the tools module to trigger decorator registration
|
||||||
|
|
@ -7,14 +7,7 @@ def test_endpoint_rejects_non_http(tmp_path, monkeypatch):
|
||||||
monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path))
|
monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path))
|
||||||
monkeypatch.setenv("UNITY_MCP_TELEMETRY_ENDPOINT", "file:///etc/passwd")
|
monkeypatch.setenv("UNITY_MCP_TELEMETRY_ENDPOINT", "file:///etc/passwd")
|
||||||
|
|
||||||
# Import the telemetry module from the correct path
|
# Import the telemetry module
|
||||||
import sys
|
|
||||||
import pathlib
|
|
||||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
|
||||||
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
|
|
||||||
sys.path.insert(0, str(SRC))
|
|
||||||
|
|
||||||
monkeypatch.chdir(str(SRC))
|
|
||||||
telemetry = importlib.import_module("telemetry")
|
telemetry = importlib.import_module("telemetry")
|
||||||
importlib.reload(telemetry)
|
importlib.reload(telemetry)
|
||||||
|
|
||||||
|
|
@ -29,18 +22,10 @@ def test_config_preferred_then_env_override(tmp_path, monkeypatch):
|
||||||
monkeypatch.delenv("UNITY_MCP_TELEMETRY_ENDPOINT", raising=False)
|
monkeypatch.delenv("UNITY_MCP_TELEMETRY_ENDPOINT", raising=False)
|
||||||
|
|
||||||
# Patch config.telemetry_endpoint via import mocking
|
# Patch config.telemetry_endpoint via import mocking
|
||||||
import importlib
|
|
||||||
import sys
|
|
||||||
import pathlib
|
|
||||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
|
||||||
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
|
|
||||||
sys.path.insert(0, str(SRC))
|
|
||||||
|
|
||||||
cfg_mod = importlib.import_module("config")
|
cfg_mod = importlib.import_module("config")
|
||||||
old_endpoint = cfg_mod.config.telemetry_endpoint
|
old_endpoint = cfg_mod.config.telemetry_endpoint
|
||||||
cfg_mod.config.telemetry_endpoint = "https://example.com/telemetry"
|
cfg_mod.config.telemetry_endpoint = "https://example.com/telemetry"
|
||||||
try:
|
try:
|
||||||
monkeypatch.chdir(str(SRC))
|
|
||||||
telemetry = importlib.import_module("telemetry")
|
telemetry = importlib.import_module("telemetry")
|
||||||
importlib.reload(telemetry)
|
importlib.reload(telemetry)
|
||||||
tc = telemetry.TelemetryCollector()
|
tc = telemetry.TelemetryCollector()
|
||||||
|
|
@ -50,7 +35,6 @@ def test_config_preferred_then_env_override(tmp_path, monkeypatch):
|
||||||
# Env should override config
|
# Env should override config
|
||||||
monkeypatch.setenv("UNITY_MCP_TELEMETRY_ENDPOINT",
|
monkeypatch.setenv("UNITY_MCP_TELEMETRY_ENDPOINT",
|
||||||
"https://override.example/ep")
|
"https://override.example/ep")
|
||||||
monkeypatch.chdir(str(SRC))
|
|
||||||
importlib.reload(telemetry)
|
importlib.reload(telemetry)
|
||||||
tc2 = telemetry.TelemetryCollector()
|
tc2 = telemetry.TelemetryCollector()
|
||||||
assert tc2.config.endpoint == "https://override.example/ep"
|
assert tc2.config.endpoint == "https://override.example/ep"
|
||||||
|
|
@ -61,14 +45,7 @@ def test_config_preferred_then_env_override(tmp_path, monkeypatch):
|
||||||
def test_uuid_preserved_on_malformed_milestones(tmp_path, monkeypatch):
|
def test_uuid_preserved_on_malformed_milestones(tmp_path, monkeypatch):
|
||||||
monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path))
|
monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path))
|
||||||
|
|
||||||
# Import the telemetry module from the correct path
|
# Import the telemetry module
|
||||||
import sys
|
|
||||||
import pathlib
|
|
||||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
|
||||||
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
|
|
||||||
sys.path.insert(0, str(SRC))
|
|
||||||
|
|
||||||
monkeypatch.chdir(str(SRC))
|
|
||||||
telemetry = importlib.import_module("telemetry")
|
telemetry = importlib.import_module("telemetry")
|
||||||
importlib.reload(telemetry)
|
importlib.reload(telemetry)
|
||||||
|
|
||||||
|
|
@ -1,60 +1,9 @@
|
||||||
import sys
|
|
||||||
import pathlib
|
|
||||||
import importlib.util
|
|
||||||
import os
|
|
||||||
import types
|
import types
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import queue as q
|
import queue as q
|
||||||
|
|
||||||
|
import telemetry
|
||||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
|
||||||
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
|
|
||||||
sys.path.insert(0, str(SRC))
|
|
||||||
|
|
||||||
# Stub mcp.server.fastmcp to satisfy imports without the full dependency
|
|
||||||
mcp_pkg = types.ModuleType("mcp")
|
|
||||||
server_pkg = types.ModuleType("mcp.server")
|
|
||||||
fastmcp_pkg = types.ModuleType("mcp.server.fastmcp")
|
|
||||||
|
|
||||||
|
|
||||||
class _Dummy:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
fastmcp_pkg.FastMCP = _Dummy
|
|
||||||
fastmcp_pkg.Context = _Dummy
|
|
||||||
server_pkg.fastmcp = fastmcp_pkg
|
|
||||||
mcp_pkg.server = server_pkg
|
|
||||||
sys.modules.setdefault("mcp", mcp_pkg)
|
|
||||||
sys.modules.setdefault("mcp.server", server_pkg)
|
|
||||||
sys.modules.setdefault("mcp.server.fastmcp", fastmcp_pkg)
|
|
||||||
|
|
||||||
# Ensure telemetry module has get_package_version stub before importing
|
|
||||||
telemetry_stub = types.ModuleType("telemetry")
|
|
||||||
telemetry_stub.get_package_version = lambda: "0.0.0"
|
|
||||||
sys.modules.setdefault("telemetry", telemetry_stub)
|
|
||||||
|
|
||||||
|
|
||||||
def _load_module(path: pathlib.Path, name: str):
|
|
||||||
spec = importlib.util.spec_from_file_location(name, path)
|
|
||||||
if spec is None or spec.loader is None:
|
|
||||||
raise ImportError(f"Cannot load module {name} from {path}")
|
|
||||||
mod = importlib.util.module_from_spec(spec)
|
|
||||||
spec.loader.exec_module(mod)
|
|
||||||
return mod
|
|
||||||
|
|
||||||
|
|
||||||
# Load real telemetry on top of stub (it will reuse stubbed helpers)
|
|
||||||
# Note: CWD change required because telemetry.py calls get_package_version()
|
|
||||||
# at module load time, which reads pyproject.toml using a relative path.
|
|
||||||
# This is fragile but necessary given current telemetry module design.
|
|
||||||
_prev_cwd = os.getcwd()
|
|
||||||
os.chdir(str(SRC))
|
|
||||||
try:
|
|
||||||
telemetry = _load_module(SRC / "telemetry.py", "telemetry_mod")
|
|
||||||
finally:
|
|
||||||
os.chdir(_prev_cwd)
|
|
||||||
|
|
||||||
|
|
||||||
def test_telemetry_queue_backpressure_and_single_worker(monkeypatch, caplog):
|
def test_telemetry_queue_backpressure_and_single_worker(monkeypatch, caplog):
|
||||||
|
|
@ -6,10 +6,7 @@ def _get_decorator_module():
|
||||||
import sys
|
import sys
|
||||||
import pathlib
|
import pathlib
|
||||||
import types
|
import types
|
||||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
# Tests can now import directly from parent package
|
||||||
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
|
|
||||||
if str(SRC) not in sys.path:
|
|
||||||
sys.path.insert(0, str(SRC))
|
|
||||||
# Remove any previously stubbed module to force real import
|
# Remove any previously stubbed module to force real import
|
||||||
sys.modules.pop("telemetry_decorator", None)
|
sys.modules.pop("telemetry_decorator", None)
|
||||||
# Preload a minimal telemetry stub to satisfy telemetry_decorator imports
|
# Preload a minimal telemetry stub to satisfy telemetry_decorator imports
|
||||||
|
|
@ -23,7 +23,7 @@ if SRC is None:
|
||||||
"MCP for Unity server source not found. Tried:\n" + searched,
|
"MCP for Unity server source not found. Tried:\n" + searched,
|
||||||
allow_module_level=True,
|
allow_module_level=True,
|
||||||
)
|
)
|
||||||
sys.path.insert(0, str(SRC))
|
# Tests can now import directly from parent package
|
||||||
|
|
||||||
|
|
||||||
def start_dummy_server(greeting: bytes, respond_ping: bool = False):
|
def start_dummy_server(greeting: bytes, respond_ping: bool = False):
|
||||||
|
|
@ -1,23 +1,4 @@
|
||||||
import sys
|
from .test_helpers import DummyContext
|
||||||
import pathlib
|
|
||||||
import importlib.util
|
|
||||||
|
|
||||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
|
||||||
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
|
|
||||||
sys.path.insert(0, str(SRC))
|
|
||||||
|
|
||||||
|
|
||||||
def _load_module(path: pathlib.Path, name: str):
|
|
||||||
spec = importlib.util.spec_from_file_location(name, path)
|
|
||||||
if spec is None or spec.loader is None:
|
|
||||||
raise ImportError(f"Cannot load module {name} from {path}")
|
|
||||||
mod = importlib.util.module_from_spec(spec)
|
|
||||||
spec.loader.exec_module(mod)
|
|
||||||
return mod
|
|
||||||
|
|
||||||
|
|
||||||
manage_script = _load_module(
|
|
||||||
SRC / "tools" / "manage_script.py", "manage_script_mod")
|
|
||||||
|
|
||||||
|
|
||||||
class DummyMCP:
|
class DummyMCP:
|
||||||
|
|
@ -31,9 +12,6 @@ class DummyMCP:
|
||||||
return deco
|
return deco
|
||||||
|
|
||||||
|
|
||||||
from tests.test_helpers import DummyContext
|
|
||||||
|
|
||||||
|
|
||||||
def setup_tools():
|
def setup_tools():
|
||||||
mcp = DummyMCP()
|
mcp = DummyMCP()
|
||||||
# Import the tools module to trigger decorator registration
|
# Import the tools module to trigger decorator registration
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
[pytest]
|
||||||
|
python_files = test_*.py
|
||||||
|
python_classes = Test*
|
||||||
|
python_functions = test_*
|
||||||
|
|
||||||
|
markers =
|
||||||
|
integration: Integration tests that test multiple components together
|
||||||
|
unit: Unit tests for individual functions or classes
|
||||||
|
|
@ -44,11 +44,11 @@ class RunTestsResponse(MCPResponse):
|
||||||
)
|
)
|
||||||
async def run_tests(
|
async def run_tests(
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
mode: Annotated[Literal["edit", "play"], "Unity test mode to run"] = "edit",
|
mode: Annotated[Literal["EditMode", "PlayMode"], Field(
|
||||||
timeout_seconds: Annotated[int | str | None, "Optional timeout in seconds for the Unity test run (string, e.g. '30')"] = None,
|
description="Unity test mode to run")] = "EditMode",
|
||||||
) -> dict[str, Any]:
|
timeout_seconds: Annotated[int | str, Field(
|
||||||
# Get active instance from session state
|
description="Optional timeout in seconds for the Unity test run (string, e.g. '30')")] | None = None,
|
||||||
# Removed session_state import
|
) -> RunTestsResponse:
|
||||||
unity_instance = get_unity_instance_from_context(ctx)
|
unity_instance = get_unity_instance_from_context(ctx)
|
||||||
|
|
||||||
# Coerce timeout defensively (string/float -> int)
|
# Coerce timeout defensively (string/float -> int)
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,33 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" },
|
{ url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "backports-tarfile"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/86/72/cd9b395f25e290e633655a100af28cb253e4393396264a98bd5f5951d50f/backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991", size = 86406, upload-time = "2024-05-28T17:01:54.731Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b9/fa/123043af240e49752f1c4bd24da5053b6bd00cad78c2be53c0d1e8b975bc/backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34", size = 30181, upload-time = "2024-05-28T17:01:53.112Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "beartype"
|
||||||
|
version = "0.22.5"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a6/09/9003e5662691056e0e8b2e6f57c799e71875fac0be0e785d8cb11557cd2a/beartype-0.22.5.tar.gz", hash = "sha256:516a9096cc77103c96153474fa35c3ebcd9d36bd2ec8d0e3a43307ced0fa6341", size = 1586256, upload-time = "2025-11-01T05:49:20.771Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f7/f6/073d19f7b571c08327fbba3f8e011578da67ab62a11f98911274ff80653f/beartype-0.22.5-py3-none-any.whl", hash = "sha256:d9743dd7cd6d193696eaa1e025f8a70fb09761c154675679ff236e61952dfba0", size = 1321700, upload-time = "2025-11-01T05:49:18.436Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cachetools"
|
||||||
|
version = "6.2.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/cc/7e/b975b5814bd36faf009faebe22c1072a1fa1168db34d285ef0ba071ad78c/cachetools-6.2.1.tar.gz", hash = "sha256:3f391e4bd8f8bf0931169baf7456cc822705f4e2a31f840d218f445b9a854201", size = 31325, upload-time = "2025-10-12T14:55:30.139Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/96/c5/1e741d26306c42e2bf6ab740b2202872727e0f606033c9dd713f8b93f5a8/cachetools-6.2.1-py3-none-any.whl", hash = "sha256:09868944b6dde876dfd44e1d47e18484541eaf12f26f29b7af91b26cc892d701", size = 11280, upload-time = "2025-10-12T14:55:28.382Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "certifi"
|
name = "certifi"
|
||||||
version = "2025.1.31"
|
version = "2025.1.31"
|
||||||
|
|
@ -339,6 +366,15 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/44/0e/0a22e076944600aeb06f40b7e03bbd762a42d56d43a2f5f4ab954aed9005/cyclopts-4.0.0-py3-none-any.whl", hash = "sha256:e64801a2c86b681f08323fd50110444ee961236a0bae402a66d2cc3feda33da7", size = 178837, upload-time = "2025-10-20T18:33:00.191Z" },
|
{ url = "https://files.pythonhosted.org/packages/44/0e/0a22e076944600aeb06f40b7e03bbd762a42d56d43a2f5f4ab954aed9005/cyclopts-4.0.0-py3-none-any.whl", hash = "sha256:e64801a2c86b681f08323fd50110444ee961236a0bae402a66d2cc3feda33da7", size = 178837, upload-time = "2025-10-20T18:33:00.191Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "diskcache"
|
||||||
|
version = "5.6.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/3f/21/1c1ffc1a039ddcc459db43cc108658f32c57d271d7289a2794e401d0fdb6/diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc", size = 67916, upload-time = "2023-08-31T06:12:00.316Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19", size = 45550, upload-time = "2023-08-31T06:11:58.822Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dnspython"
|
name = "dnspython"
|
||||||
version = "2.8.0"
|
version = "2.8.0"
|
||||||
|
|
@ -393,24 +429,27 @@ wheels = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastmcp"
|
name = "fastmcp"
|
||||||
version = "2.12.5"
|
version = "2.13.0.2"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "authlib" },
|
{ name = "authlib" },
|
||||||
{ name = "cyclopts" },
|
{ name = "cyclopts" },
|
||||||
{ name = "exceptiongroup" },
|
{ name = "exceptiongroup" },
|
||||||
{ name = "httpx" },
|
{ name = "httpx" },
|
||||||
|
{ name = "jsonschema-path" },
|
||||||
{ name = "mcp" },
|
{ name = "mcp" },
|
||||||
{ name = "openapi-core" },
|
|
||||||
{ name = "openapi-pydantic" },
|
{ name = "openapi-pydantic" },
|
||||||
|
{ name = "platformdirs" },
|
||||||
|
{ name = "py-key-value-aio", extra = ["disk", "keyring", "memory"] },
|
||||||
{ name = "pydantic", extra = ["email"] },
|
{ name = "pydantic", extra = ["email"] },
|
||||||
{ name = "pyperclip" },
|
{ name = "pyperclip" },
|
||||||
{ name = "python-dotenv" },
|
{ name = "python-dotenv" },
|
||||||
{ name = "rich" },
|
{ name = "rich" },
|
||||||
|
{ name = "websockets" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/00/a6/e3b46cd3e228635e0064c2648788b6f66a53bf0d0ddbf5fb44cca951f908/fastmcp-2.12.5.tar.gz", hash = "sha256:2dfd02e255705a4afe43d26caddbc864563036e233dbc6870f389ee523b39a6a", size = 7190263, upload-time = "2025-10-17T13:24:58.896Z" }
|
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" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/d8/c1/9fb98c9649e15ea8cc691b4b09558b61dafb3dc0345f7322f8c4a8991ade/fastmcp-2.12.5-py3-none-any.whl", hash = "sha256:b1e542f9b83dbae7cecfdc9c73b062f77074785abda9f2306799116121344133", size = 329099, upload-time = "2025-10-17T13:24:57.518Z" },
|
{ 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" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -468,6 +507,18 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
|
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "importlib-metadata"
|
||||||
|
version = "8.7.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "zipp" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iniconfig"
|
name = "iniconfig"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
|
|
@ -478,12 +529,48 @@ wheels = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "isodate"
|
name = "jaraco-classes"
|
||||||
version = "0.7.2"
|
version = "3.4.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" }
|
dependencies = [
|
||||||
|
{ name = "more-itertools" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" },
|
{ url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777, upload-time = "2024-03-31T07:27:34.792Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jaraco-context"
|
||||||
|
version = "6.0.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "backports-tarfile", marker = "python_full_version < '3.12'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/df/ad/f3777b81bf0b6e7bc7514a1656d3e637b2e8e15fab2ce3235730b3e7a4e6/jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3", size = 13912, upload-time = "2024-08-20T03:39:27.358Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ff/db/0c52c4cf5e4bd9f5d7135ec7669a3a767af21b3a308e1ed3674881e52b62/jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4", size = 6825, upload-time = "2024-08-20T03:39:25.966Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jaraco-functools"
|
||||||
|
version = "4.3.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "more-itertools" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f7/ed/1aa2d585304ec07262e1a83a9889880701079dde796ac7b1d1826f40c63d/jaraco_functools-4.3.0.tar.gz", hash = "sha256:cfd13ad0dd2c47a3600b439ef72d8615d482cedcff1632930d6f28924d92f294", size = 19755, upload-time = "2025-08-18T20:05:09.91Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/09/726f168acad366b11e420df31bf1c702a54d373a83f968d94141a8c3fde0/jaraco_functools-4.3.0-py3-none-any.whl", hash = "sha256:227ff8ed6f7b8f62c56deff101545fa7543cf2c8e7b82a7c2116e672f29c26e8", size = 10408, upload-time = "2025-08-18T20:05:08.69Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jeepney"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758, upload-time = "2025-02-27T18:51:01.684Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -529,48 +616,21 @@ wheels = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy-object-proxy"
|
name = "keyring"
|
||||||
version = "1.12.0"
|
version = "25.6.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/08/a2/69df9c6ba6d316cfd81fe2381e464db3e6de5db45f8c43c6a23504abf8cb/lazy_object_proxy-1.12.0.tar.gz", hash = "sha256:1f5a462d92fd0cfb82f1fab28b51bfb209fabbe6aabf7f0d51472c0c124c0c61", size = 43681, upload-time = "2025-08-22T13:50:06.783Z" }
|
dependencies = [
|
||||||
|
{ name = "importlib-metadata", marker = "python_full_version < '3.12'" },
|
||||||
|
{ name = "jaraco-classes" },
|
||||||
|
{ name = "jaraco-context" },
|
||||||
|
{ name = "jaraco-functools" },
|
||||||
|
{ name = "jeepney", marker = "sys_platform == 'linux'" },
|
||||||
|
{ name = "pywin32-ctypes", marker = "sys_platform == 'win32'" },
|
||||||
|
{ name = "secretstorage", marker = "sys_platform == 'linux'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/70/09/d904a6e96f76ff214be59e7aa6ef7190008f52a0ab6689760a98de0bf37d/keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66", size = 62750, upload-time = "2024-12-25T15:26:45.782Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/d6/2b/d5e8915038acbd6c6a9fcb8aaf923dc184222405d3710285a1fec6e262bc/lazy_object_proxy-1.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61d5e3310a4aa5792c2b599a7a78ccf8687292c8eb09cf187cca8f09cf6a7519", size = 26658, upload-time = "2025-08-22T13:42:23.373Z" },
|
{ url = "https://files.pythonhosted.org/packages/d3/32/da7f44bcb1105d3e88a0b74ebdca50c59121d2ddf71c9e34ba47df7f3a56/keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd", size = 39085, upload-time = "2024-12-25T15:26:44.377Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/da/8f/91fc00eeea46ee88b9df67f7c5388e60993341d2a406243d620b2fdfde57/lazy_object_proxy-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1ca33565f698ac1aece152a10f432415d1a2aa9a42dfe23e5ba2bc255ab91f6", size = 68412, upload-time = "2025-08-22T13:42:24.727Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/07/d2/b7189a0e095caedfea4d42e6b6949d2685c354263bdf18e19b21ca9b3cd6/lazy_object_proxy-1.12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d01c7819a410f7c255b20799b65d36b414379a30c6f1684c7bd7eb6777338c1b", size = 67559, upload-time = "2025-08-22T13:42:25.875Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a3/ad/b013840cc43971582ff1ceaf784d35d3a579650eb6cc348e5e6ed7e34d28/lazy_object_proxy-1.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:029d2b355076710505c9545aef5ab3f750d89779310e26ddf2b7b23f6ea03cd8", size = 66651, upload-time = "2025-08-22T13:42:27.427Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7e/6f/b7368d301c15612fcc4cd00412b5d6ba55548bde09bdae71930e1a81f2ab/lazy_object_proxy-1.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc6e3614eca88b1c8a625fc0a47d0d745e7c3255b21dac0e30b3037c5e3deeb8", size = 66901, upload-time = "2025-08-22T13:42:28.585Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/61/1b/c6b1865445576b2fc5fa0fbcfce1c05fee77d8979fd1aa653dd0f179aefc/lazy_object_proxy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:be5fe974e39ceb0d6c9db0663c0464669cf866b2851c73971409b9566e880eab", size = 26536, upload-time = "2025-08-22T13:42:29.636Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/01/b3/4684b1e128a87821e485f5a901b179790e6b5bc02f89b7ee19c23be36ef3/lazy_object_proxy-1.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1cf69cd1a6c7fe2dbcc3edaa017cf010f4192e53796538cc7d5e1fedbfa4bcff", size = 26656, upload-time = "2025-08-22T13:42:30.605Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3a/03/1bdc21d9a6df9ff72d70b2ff17d8609321bea4b0d3cffd2cea92fb2ef738/lazy_object_proxy-1.12.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:efff4375a8c52f55a145dc8487a2108c2140f0bec4151ab4e1843e52eb9987ad", size = 68832, upload-time = "2025-08-22T13:42:31.675Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3d/4b/5788e5e8bd01d19af71e50077ab020bc5cce67e935066cd65e1215a09ff9/lazy_object_proxy-1.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1192e8c2f1031a6ff453ee40213afa01ba765b3dc861302cd91dbdb2e2660b00", size = 69148, upload-time = "2025-08-22T13:42:32.876Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/79/0e/090bf070f7a0de44c61659cb7f74c2fe02309a77ca8c4b43adfe0b695f66/lazy_object_proxy-1.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3605b632e82a1cbc32a1e5034278a64db555b3496e0795723ee697006b980508", size = 67800, upload-time = "2025-08-22T13:42:34.054Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/cf/d2/b320325adbb2d119156f7c506a5fbfa37fcab15c26d13cf789a90a6de04e/lazy_object_proxy-1.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a61095f5d9d1a743e1e20ec6d6db6c2ca511961777257ebd9b288951b23b44fa", size = 68085, upload-time = "2025-08-22T13:42:35.197Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/6a/48/4b718c937004bf71cd82af3713874656bcb8d0cc78600bf33bb9619adc6c/lazy_object_proxy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:997b1d6e10ecc6fb6fe0f2c959791ae59599f41da61d652f6c903d1ee58b7370", size = 26535, upload-time = "2025-08-22T13:42:36.521Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0d/1b/b5f5bd6bda26f1e15cd3232b223892e4498e34ec70a7f4f11c401ac969f1/lazy_object_proxy-1.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ee0d6027b760a11cc18281e702c0309dd92da458a74b4c15025d7fc490deede", size = 26746, upload-time = "2025-08-22T13:42:37.572Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/55/64/314889b618075c2bfc19293ffa9153ce880ac6153aacfd0a52fcabf21a66/lazy_object_proxy-1.12.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4ab2c584e3cc8be0dfca422e05ad30a9abe3555ce63e9ab7a559f62f8dbc6ff9", size = 71457, upload-time = "2025-08-22T13:42:38.743Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/11/53/857fc2827fc1e13fbdfc0ba2629a7d2579645a06192d5461809540b78913/lazy_object_proxy-1.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:14e348185adbd03ec17d051e169ec45686dcd840a3779c9d4c10aabe2ca6e1c0", size = 71036, upload-time = "2025-08-22T13:42:40.184Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2b/24/e581ffed864cd33c1b445b5763d617448ebb880f48675fc9de0471a95cbc/lazy_object_proxy-1.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c4fcbe74fb85df8ba7825fa05eddca764138da752904b378f0ae5ab33a36c308", size = 69329, upload-time = "2025-08-22T13:42:41.311Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/78/be/15f8f5a0b0b2e668e756a152257d26370132c97f2f1943329b08f057eff0/lazy_object_proxy-1.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:563d2ec8e4d4b68ee7848c5ab4d6057a6d703cb7963b342968bb8758dda33a23", size = 70690, upload-time = "2025-08-22T13:42:42.51Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5d/aa/f02be9bbfb270e13ee608c2b28b8771f20a5f64356c6d9317b20043c6129/lazy_object_proxy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:53c7fd99eb156bbb82cbc5d5188891d8fdd805ba6c1e3b92b90092da2a837073", size = 26563, upload-time = "2025-08-22T13:42:43.685Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f4/26/b74c791008841f8ad896c7f293415136c66cc27e7c7577de4ee68040c110/lazy_object_proxy-1.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:86fd61cb2ba249b9f436d789d1356deae69ad3231dc3c0f17293ac535162672e", size = 26745, upload-time = "2025-08-22T13:42:44.982Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9b/52/641870d309e5d1fb1ea7d462a818ca727e43bfa431d8c34b173eb090348c/lazy_object_proxy-1.12.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:81d1852fb30fab81696f93db1b1e55a5d1ff7940838191062f5f56987d5fcc3e", size = 71537, upload-time = "2025-08-22T13:42:46.141Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/47/b6/919118e99d51c5e76e8bf5a27df406884921c0acf2c7b8a3b38d847ab3e9/lazy_object_proxy-1.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be9045646d83f6c2664c1330904b245ae2371b5c57a3195e4028aedc9f999655", size = 71141, upload-time = "2025-08-22T13:42:47.375Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e5/47/1d20e626567b41de085cf4d4fb3661a56c159feaa73c825917b3b4d4f806/lazy_object_proxy-1.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:67f07ab742f1adfb3966c40f630baaa7902be4222a17941f3d85fd1dae5565ff", size = 69449, upload-time = "2025-08-22T13:42:48.49Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/58/8d/25c20ff1a1a8426d9af2d0b6f29f6388005fc8cd10d6ee71f48bff86fdd0/lazy_object_proxy-1.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:75ba769017b944fcacbf6a80c18b2761a1795b03f8899acdad1f1c39db4409be", size = 70744, upload-time = "2025-08-22T13:42:49.608Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c0/67/8ec9abe15c4f8a4bcc6e65160a2c667240d025cbb6591b879bea55625263/lazy_object_proxy-1.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:7b22c2bbfb155706b928ac4d74c1a63ac8552a55ba7fff4445155523ea4067e1", size = 26568, upload-time = "2025-08-22T13:42:57.719Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/23/12/cd2235463f3469fd6c62d41d92b7f120e8134f76e52421413a0ad16d493e/lazy_object_proxy-1.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4a79b909aa16bde8ae606f06e6bbc9d3219d2e57fb3e0076e17879072b742c65", size = 27391, upload-time = "2025-08-22T13:42:50.62Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/60/9e/f1c53e39bbebad2e8609c67d0830cc275f694d0ea23d78e8f6db526c12d3/lazy_object_proxy-1.12.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:338ab2f132276203e404951205fe80c3fd59429b3a724e7b662b2eb539bb1be9", size = 80552, upload-time = "2025-08-22T13:42:51.731Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4c/b6/6c513693448dcb317d9d8c91d91f47addc09553613379e504435b4cc8b3e/lazy_object_proxy-1.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c40b3c9faee2e32bfce0df4ae63f4e73529766893258eca78548bac801c8f66", size = 82857, upload-time = "2025-08-22T13:42:53.225Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/12/1c/d9c4aaa4c75da11eb7c22c43d7c90a53b4fca0e27784a5ab207768debea7/lazy_object_proxy-1.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:717484c309df78cedf48396e420fa57fc8a2b1f06ea889df7248fdd156e58847", size = 80833, upload-time = "2025-08-22T13:42:54.391Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0b/ae/29117275aac7d7d78ae4f5a4787f36ff33262499d486ac0bf3e0b97889f6/lazy_object_proxy-1.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b7ea5ea1ffe15059eb44bcbcb258f97bcb40e139b88152c40d07b1a1dfc9ac", size = 79516, upload-time = "2025-08-22T13:42:55.812Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/19/40/b4e48b2c38c69392ae702ae7afa7b6551e0ca5d38263198b7c79de8b3bdf/lazy_object_proxy-1.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:08c465fb5cd23527512f9bd7b4c7ba6cec33e28aad36fbbe46bf7b858f9f3f7f", size = 27656, upload-time = "2025-08-22T13:42:56.793Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ef/3a/277857b51ae419a1574557c0b12e0d06bf327b758ba94cafc664cb1e2f66/lazy_object_proxy-1.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c9defba70ab943f1df98a656247966d7729da2fe9c2d5d85346464bf320820a3", size = 26582, upload-time = "2025-08-22T13:49:49.366Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1a/b6/c5e0fa43535bb9c87880e0ba037cdb1c50e01850b0831e80eb4f4762f270/lazy_object_proxy-1.12.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6763941dbf97eea6b90f5b06eb4da9418cc088fce0e3883f5816090f9afcde4a", size = 71059, upload-time = "2025-08-22T13:49:50.488Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/06/8a/7dcad19c685963c652624702f1a968ff10220b16bfcc442257038216bf55/lazy_object_proxy-1.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fdc70d81235fc586b9e3d1aeef7d1553259b62ecaae9db2167a5d2550dcc391a", size = 71034, upload-time = "2025-08-22T13:49:54.224Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/12/ac/34cbfb433a10e28c7fd830f91c5a348462ba748413cbb950c7f259e67aa7/lazy_object_proxy-1.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0a83c6f7a6b2bfc11ef3ed67f8cbe99f8ff500b05655d8e7df9aab993a6abc95", size = 69529, upload-time = "2025-08-22T13:49:55.29Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/6f/6a/11ad7e349307c3ca4c0175db7a77d60ce42a41c60bcb11800aabd6a8acb8/lazy_object_proxy-1.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:256262384ebd2a77b023ad02fbcc9326282bcfd16484d5531154b02bc304f4c5", size = 70391, upload-time = "2025-08-22T13:49:56.35Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/59/97/9b410ed8fbc6e79c1ee8b13f8777a80137d4bc189caf2c6202358e66192c/lazy_object_proxy-1.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:7601ec171c7e8584f8ff3f4e440aa2eebf93e854f04639263875b8c2971f819f", size = 26988, upload-time = "2025-08-22T13:49:57.302Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/41/a0/b91504515c1f9a299fc157967ffbd2f0321bce0516a3d5b89f6f4cad0355/lazy_object_proxy-1.12.0-pp39.pp310.pp311.graalpy311-none-any.whl", hash = "sha256:c3b2e0af1f7f77c4263759c4824316ce458fabe0fceadcd24ef8ca08b2d1e402", size = 15072, upload-time = "2025-08-22T13:50:05.498Z" },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -585,94 +645,9 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" },
|
{ url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "markupsafe"
|
|
||||||
version = "3.0.3"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mcp"
|
name = "mcp"
|
||||||
version = "1.16.0"
|
version = "1.20.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "anyio" },
|
{ name = "anyio" },
|
||||||
|
|
@ -681,15 +656,16 @@ dependencies = [
|
||||||
{ name = "jsonschema" },
|
{ name = "jsonschema" },
|
||||||
{ name = "pydantic" },
|
{ name = "pydantic" },
|
||||||
{ name = "pydantic-settings" },
|
{ name = "pydantic-settings" },
|
||||||
|
{ name = "pyjwt", extra = ["crypto"] },
|
||||||
{ name = "python-multipart" },
|
{ name = "python-multipart" },
|
||||||
{ name = "pywin32", marker = "sys_platform == 'win32'" },
|
{ name = "pywin32", marker = "sys_platform == 'win32'" },
|
||||||
{ name = "sse-starlette" },
|
{ name = "sse-starlette" },
|
||||||
{ name = "starlette" },
|
{ name = "starlette" },
|
||||||
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
|
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/3d/a1/b1f328da3b153683d2ec34f849b4b6eac2790fb240e3aef06ff2fab3df9d/mcp-1.16.0.tar.gz", hash = "sha256:39b8ca25460c578ee2cdad33feeea122694cfdf73eef58bee76c42f6ef0589df", size = 472918, upload-time = "2025-10-02T16:58:20.631Z" }
|
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" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/c9/0e/7cebc88e17daf94ebe28c95633af595ccb2864dc2ee7abd75542d98495cc/mcp-1.16.0-py3-none-any.whl", hash = "sha256:ec917be9a5d31b09ba331e1768aa576e0af45470d657a0319996a20a57d7d633", size = 167266, upload-time = "2025-10-02T16:58:19.039Z" },
|
{ 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" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -712,7 +688,7 @@ dev = [
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "fastmcp", specifier = ">=2.12.5" },
|
{ name = "fastmcp", specifier = ">=2.13.0" },
|
||||||
{ 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.0" },
|
{ name = "pydantic", specifier = ">=2.12.0" },
|
||||||
|
|
@ -740,26 +716,6 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" },
|
{ url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openapi-core"
|
|
||||||
version = "0.19.5"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "isodate" },
|
|
||||||
{ name = "jsonschema" },
|
|
||||||
{ name = "jsonschema-path" },
|
|
||||||
{ name = "more-itertools" },
|
|
||||||
{ name = "openapi-schema-validator" },
|
|
||||||
{ name = "openapi-spec-validator" },
|
|
||||||
{ name = "parse" },
|
|
||||||
{ name = "typing-extensions" },
|
|
||||||
{ name = "werkzeug" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/b1/35/1acaa5f2fcc6e54eded34a2ec74b479439c4e469fc4e8d0e803fda0234db/openapi_core-0.19.5.tar.gz", hash = "sha256:421e753da56c391704454e66afe4803a290108590ac8fa6f4a4487f4ec11f2d3", size = 103264, upload-time = "2025-03-20T20:17:28.193Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/27/6f/83ead0e2e30a90445ee4fc0135f43741aebc30cca5b43f20968b603e30b6/openapi_core-0.19.5-py3-none-any.whl", hash = "sha256:ef7210e83a59394f46ce282639d8d26ad6fc8094aa904c9c16eb1bac8908911f", size = 106595, upload-time = "2025-03-20T20:17:26.77Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openapi-pydantic"
|
name = "openapi-pydantic"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
|
@ -772,35 +728,6 @@ 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 = "openapi-schema-validator"
|
|
||||||
version = "0.6.3"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "jsonschema" },
|
|
||||||
{ name = "jsonschema-specifications" },
|
|
||||||
{ name = "rfc3339-validator" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/8b/f3/5507ad3325169347cd8ced61c232ff3df70e2b250c49f0fe140edb4973c6/openapi_schema_validator-0.6.3.tar.gz", hash = "sha256:f37bace4fc2a5d96692f4f8b31dc0f8d7400fd04f3a937798eaf880d425de6ee", size = 11550, upload-time = "2025-01-10T18:08:22.268Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/21/c6/ad0fba32775ae749016829dace42ed80f4407b171da41313d1a3a5f102e4/openapi_schema_validator-0.6.3-py3-none-any.whl", hash = "sha256:f3b9870f4e556b5a62a1c39da72a6b4b16f3ad9c73dc80084b1b11e74ba148a3", size = 8755, upload-time = "2025-01-10T18:08:19.758Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openapi-spec-validator"
|
|
||||||
version = "0.7.2"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "jsonschema" },
|
|
||||||
{ name = "jsonschema-path" },
|
|
||||||
{ name = "lazy-object-proxy" },
|
|
||||||
{ name = "openapi-schema-validator" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/82/af/fe2d7618d6eae6fb3a82766a44ed87cd8d6d82b4564ed1c7cfb0f6378e91/openapi_spec_validator-0.7.2.tar.gz", hash = "sha256:cc029309b5c5dbc7859df0372d55e9d1ff43e96d678b9ba087f7c56fc586f734", size = 36855, upload-time = "2025-06-07T14:48:56.299Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/27/dd/b3fd642260cb17532f66cc1e8250f3507d1e580483e209dc1e9d13bd980d/openapi_spec_validator-0.7.2-py3-none-any.whl", hash = "sha256:4bbdc0894ec85f1d1bea1d6d9c8b2c3c8d7ccaa13577ef40da9c006c9fd0eb60", size = 39713, upload-time = "2025-06-07T14:48:54.077Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "packaging"
|
name = "packaging"
|
||||||
version = "25.0"
|
version = "25.0"
|
||||||
|
|
@ -810,15 +737,6 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
|
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parse"
|
|
||||||
version = "1.20.2"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/4f/78/d9b09ba24bb36ef8b83b71be547e118d46214735b6dfb39e4bfde0e9b9dd/parse-1.20.2.tar.gz", hash = "sha256:b41d604d16503c79d81af5165155c0b20f6c8d6c559efa66b4b695c3e5a0a0ce", size = 29391, upload-time = "2024-06-11T04:41:57.34Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d0/31/ba45bf0b2aa7898d81cbbfac0e88c267befb59ad91a19e36e1bc5578ddb1/parse-1.20.2-py2.py3-none-any.whl", hash = "sha256:967095588cb802add9177d0c0b6133b5ba33b1ea9007ca800e526f42a85af558", size = 20126, upload-time = "2024-06-11T04:41:55.057Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pathable"
|
name = "pathable"
|
||||||
version = "0.4.4"
|
version = "0.4.4"
|
||||||
|
|
@ -828,6 +746,24 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/7d/eb/b6260b31b1a96386c0a880edebe26f89669098acea8e0318bff6adb378fd/pathable-0.4.4-py3-none-any.whl", hash = "sha256:5ae9e94793b6ef5a4cbe0a7ce9dbbefc1eec38df253763fd0aeeacf2762dbbc2", size = 9592, upload-time = "2025-01-10T18:43:11.88Z" },
|
{ url = "https://files.pythonhosted.org/packages/7d/eb/b6260b31b1a96386c0a880edebe26f89669098acea8e0318bff6adb378fd/pathable-0.4.4-py3-none-any.whl", hash = "sha256:5ae9e94793b6ef5a4cbe0a7ce9dbbefc1eec38df253763fd0aeeacf2762dbbc2", size = 9592, upload-time = "2025-01-10T18:43:11.88Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pathvalidate"
|
||||||
|
version = "3.3.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/fa/2a/52a8da6fe965dea6192eb716b357558e103aea0a1e9a8352ad575a8406ca/pathvalidate-3.3.1.tar.gz", hash = "sha256:b18c07212bfead624345bb8e1d6141cdcf15a39736994ea0b94035ad2b1ba177", size = 63262, upload-time = "2025-06-15T09:07:20.736Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/70/875f4a23bfc4731703a5835487d0d2fb999031bd415e7d17c0ae615c18b7/pathvalidate-3.3.1-py3-none-any.whl", hash = "sha256:5263baab691f8e1af96092fa5137ee17df5bdfbd6cff1fcac4d6ef4bc2e1735f", size = 24305, upload-time = "2025-06-15T09:07:19.117Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "platformdirs"
|
||||||
|
version = "4.5.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pluggy"
|
name = "pluggy"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
|
|
@ -837,6 +773,44 @@ 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 = "py-key-value-aio"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "beartype" },
|
||||||
|
{ 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" }
|
||||||
|
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" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.optional-dependencies]
|
||||||
|
disk = [
|
||||||
|
{ name = "diskcache" },
|
||||||
|
{ name = "pathvalidate" },
|
||||||
|
]
|
||||||
|
keyring = [
|
||||||
|
{ name = "keyring" },
|
||||||
|
]
|
||||||
|
memory = [
|
||||||
|
{ name = "cachetools" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "py-key-value-shared"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "beartype" },
|
||||||
|
{ 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" }
|
||||||
|
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" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pycparser"
|
name = "pycparser"
|
||||||
version = "2.23"
|
version = "2.23"
|
||||||
|
|
@ -998,6 +972,20 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" },
|
{ url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyjwt"
|
||||||
|
version = "2.10.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.optional-dependencies]
|
||||||
|
crypto = [
|
||||||
|
{ name = "cryptography" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyperclip"
|
name = "pyperclip"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
|
|
@ -1079,6 +1067,15 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" },
|
{ url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pywin32-ctypes"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyyaml"
|
name = "pyyaml"
|
||||||
version = "6.0.3"
|
version = "6.0.3"
|
||||||
|
|
@ -1172,18 +1169,6 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
|
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rfc3339-validator"
|
|
||||||
version = "0.1.4"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "six" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rich"
|
name = "rich"
|
||||||
version = "13.9.4"
|
version = "13.9.4"
|
||||||
|
|
@ -1347,12 +1332,16 @@ wheels = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "six"
|
name = "secretstorage"
|
||||||
version = "1.17.0"
|
version = "3.4.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
|
dependencies = [
|
||||||
|
{ name = "cryptography" },
|
||||||
|
{ name = "jeepney" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/31/9f/11ef35cf1027c1339552ea7bfe6aaa74a8516d8b5caf6e7d338daf54fd80/secretstorage-3.4.0.tar.gz", hash = "sha256:c46e216d6815aff8a8a18706a2fbfd8d53fcbb0dce99301881687a1b0289ef7c", size = 19748, upload-time = "2025-09-09T16:42:13.859Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
|
{ url = "https://files.pythonhosted.org/packages/91/ff/2e2eed29e02c14a5cb6c57f09b2d5b40e65d6cc71f45b52e0be295ccbc2f/secretstorage-3.4.0-py3-none-any.whl", hash = "sha256:0e3b6265c2c63509fb7415717607e4b2c9ab767b7f344a57473b779ca13bd02e", size = 15272, upload-time = "2025-09-09T16:42:12.744Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1483,13 +1472,69 @@ wheels = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "werkzeug"
|
name = "websockets"
|
||||||
version = "3.1.1"
|
version = "15.0.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" }
|
||||||
{ name = "markupsafe" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/32/af/d4502dc713b4ccea7175d764718d5183caf8d0867a4f0190d5d4a45cea49/werkzeug-3.1.1.tar.gz", hash = "sha256:8cd39dfbdfc1e051965f156163e2974e52c210f130810e9ad36858f0fd3edad4", size = 806453, upload-time = "2024-11-01T16:40:45.462Z" }
|
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/ee/ea/c67e1dee1ba208ed22c06d1d547ae5e293374bfc43e0eb0ef5e262b68561/werkzeug-3.1.1-py3-none-any.whl", hash = "sha256:a71124d1ef06008baafa3d266c02f56e1836a5984afd6dd6c9230669d60d9fb5", size = 224371, upload-time = "2024-11-01T16:40:43.994Z" },
|
{ url = "https://files.pythonhosted.org/packages/1e/da/6462a9f510c0c49837bbc9345aca92d767a56c1fb2939e1579df1e1cdcf7/websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b", size = 175423, upload-time = "2025-03-05T20:01:35.363Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1c/9f/9d11c1a4eb046a9e106483b9ff69bce7ac880443f00e5ce64261b47b07e7/websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205", size = 173080, upload-time = "2025-03-05T20:01:37.304Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d5/4f/b462242432d93ea45f297b6179c7333dd0402b855a912a04e7fc61c0d71f/websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a", size = 173329, upload-time = "2025-03-05T20:01:39.668Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6e/0c/6afa1f4644d7ed50284ac59cc70ef8abd44ccf7d45850d989ea7310538d0/websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e", size = 182312, upload-time = "2025-03-05T20:01:41.815Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dd/d4/ffc8bd1350b229ca7a4db2a3e1c482cf87cea1baccd0ef3e72bc720caeec/websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf", size = 181319, upload-time = "2025-03-05T20:01:43.967Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/97/3a/5323a6bb94917af13bbb34009fac01e55c51dfde354f63692bf2533ffbc2/websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb", size = 181631, upload-time = "2025-03-05T20:01:46.104Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a6/cc/1aeb0f7cee59ef065724041bb7ed667b6ab1eeffe5141696cccec2687b66/websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d", size = 182016, upload-time = "2025-03-05T20:01:47.603Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/79/f9/c86f8f7af208e4161a7f7e02774e9d0a81c632ae76db2ff22549e1718a51/websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9", size = 181426, upload-time = "2025-03-05T20:01:48.949Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/b9/828b0bc6753db905b91df6ae477c0b14a141090df64fb17f8a9d7e3516cf/websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c", size = 181360, upload-time = "2025-03-05T20:01:50.938Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/fb/250f5533ec468ba6327055b7d98b9df056fb1ce623b8b6aaafb30b55d02e/websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256", size = 176388, upload-time = "2025-03-05T20:01:52.213Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1c/46/aca7082012768bb98e5608f01658ff3ac8437e563eca41cf068bd5849a5e/websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41", size = 176830, upload-time = "2025-03-05T20:01:53.922Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/02/9e/d40f779fa16f74d3468357197af8d6ad07e7c5a27ea1ca74ceb38986f77a/websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3", size = 173109, upload-time = "2025-03-05T20:03:17.769Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bc/cd/5b887b8585a593073fd92f7c23ecd3985cd2c3175025a91b0d69b0551372/websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1", size = 173343, upload-time = "2025-03-05T20:03:19.094Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fe/ae/d34f7556890341e900a95acf4886833646306269f899d58ad62f588bf410/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475", size = 174599, upload-time = "2025-03-05T20:03:21.1Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/71/e6/5fd43993a87db364ec60fc1d608273a1a465c0caba69176dd160e197ce42/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9", size = 174207, upload-time = "2025-03-05T20:03:23.221Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2b/fb/c492d6daa5ec067c2988ac80c61359ace5c4c674c532985ac5a123436cec/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04", size = 174155, upload-time = "2025-03-05T20:03:25.321Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/68/a1/dcb68430b1d00b698ae7a7e0194433bce4f07ded185f0ee5fb21e2a2e91e/websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122", size = 176884, upload-time = "2025-03-05T20:03:27.934Z" },
|
||||||
|
{ 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 = "zipp"
|
||||||
|
version = "3.23.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" },
|
||||||
]
|
]
|
||||||
|
|
|
||||||
37
README-zh.md
37
README-zh.md
|
|
@ -38,17 +38,42 @@ MCP for Unity 作为桥梁,允许 AI 助手(如 Claude、Cursor)通过本
|
||||||
|
|
||||||
您的大语言模型可以使用以下功能:
|
您的大语言模型可以使用以下功能:
|
||||||
|
|
||||||
* `read_console`: 获取控制台消息或清除控制台。
|
* `execute_menu_item`: 执行 Unity 编辑器菜单项(例如,"File/Save Project")。
|
||||||
* `manage_script`: 管理 C# 脚本(创建、读取、更新、删除)。
|
|
||||||
* `manage_editor`: 控制和查询编辑器的状态和设置。
|
|
||||||
* `manage_scene`: 管理场景(加载、保存、创建、获取层次结构等)。
|
|
||||||
* `manage_asset`: 执行资源操作(导入、创建、修改、删除等)。
|
* `manage_asset`: 执行资源操作(导入、创建、修改、删除等)。
|
||||||
* `manage_shader`: 执行着色器 CRUD 操作(创建、读取、修改、删除)。
|
* `manage_editor`: 控制和查询编辑器的状态和设置。
|
||||||
* `manage_gameobject`: 管理游戏对象:创建、修改、删除、查找和组件操作。
|
* `manage_gameobject`: 管理游戏对象:创建、修改、删除、查找和组件操作。
|
||||||
* `execute_menu_item`: 执行 Unity 编辑器菜单项(例如,执行"File/Save Project")。
|
* `manage_prefabs`: 执行预制件操作(创建、修改、删除等)。
|
||||||
|
* `manage_scene`: 管理场景(加载、保存、创建、获取层次结构等)。
|
||||||
|
* `manage_script`: 传统脚本操作的兼容性路由器(创建、读取、删除)。建议使用 `apply_text_edits` 或 `script_apply_edits` 进行编辑。
|
||||||
|
* `manage_shader`: 执行着色器 CRUD 操作(创建、读取、修改、删除)。
|
||||||
|
* `read_console`: 获取控制台消息或清除控制台。
|
||||||
|
* `run_tests`: 在 Unity 编辑器中运行测试。
|
||||||
|
* `set_active_instance`: 将后续工具调用路由到特定的 Unity 实例(当运行多个实例时)。
|
||||||
* `apply_text_edits`: 具有前置条件哈希和原子多编辑批次的精确文本编辑。
|
* `apply_text_edits`: 具有前置条件哈希和原子多编辑批次的精确文本编辑。
|
||||||
* `script_apply_edits`: 结构化 C# 方法/类编辑(插入/替换/删除),具有更安全的边界。
|
* `script_apply_edits`: 结构化 C# 方法/类编辑(插入/替换/删除),具有更安全的边界。
|
||||||
* `validate_script`: 快速验证(基本/标准)以在写入前后捕获语法/结构问题。
|
* `validate_script`: 快速验证(基本/标准)以在写入前后捕获语法/结构问题。
|
||||||
|
* `create_script`: 在给定的项目路径创建新的 C# 脚本。
|
||||||
|
* `delete_script`: 通过 URI 或 Assets 相对路径删除 C# 脚本。
|
||||||
|
* `get_sha`: 获取 Unity C# 脚本的 SHA256 和基本元数据,而不返回文件内容。
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary><strong> 可用资源 </strong></summary>
|
||||||
|
|
||||||
|
您的大语言模型可以检索以下资源:
|
||||||
|
|
||||||
|
* `unity_instances`: 列出所有正在运行的 Unity 编辑器实例及其详细信息(名称、路径、端口、状态)。
|
||||||
|
* `menu_items`: 检索 Unity 编辑器中所有可用的菜单项。
|
||||||
|
* `tests`: 检索 Unity 编辑器中所有可用的测试。可以选择特定类型的测试(例如,"EditMode"、"PlayMode")。
|
||||||
|
* `editor_active_tool`: 当前活动的编辑器工具(移动、旋转、缩放等)和变换手柄设置。
|
||||||
|
* `editor_prefab_stage`: 如果预制件在隔离模式下打开,则为当前预制件编辑上下文。
|
||||||
|
* `editor_selection`: 有关编辑器中当前选定对象的详细信息。
|
||||||
|
* `editor_state`: 当前编辑器运行时状态,包括播放模式、编译状态、活动场景和选择摘要。
|
||||||
|
* `editor_windows`: 所有当前打开的编辑器窗口及其标题、类型、位置和焦点状态。
|
||||||
|
* `project_info`: 静态项目信息,包括根路径、Unity 版本和平台。
|
||||||
|
* `project_layers`: 项目 TagManager 中定义的所有层及其索引(0-31)。
|
||||||
|
* `project_tags`: 项目 TagManager 中定义的所有标签。
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
30
README.md
30
README.md
|
|
@ -40,19 +40,23 @@ MCP for Unity acts as a bridge, allowing AI assistants (like Claude, Cursor) to
|
||||||
|
|
||||||
Your LLM can use functions like:
|
Your LLM can use functions like:
|
||||||
|
|
||||||
* `read_console`: Gets messages from or clears the console.
|
|
||||||
* `manage_script`: Manages C# scripts (create, read, update, delete).
|
|
||||||
* `manage_editor`: Controls and queries the editor's state and settings.
|
|
||||||
* `manage_scene`: Manages scenes (load, save, create, get hierarchy, etc.).
|
|
||||||
* `manage_asset`: Performs asset operations (import, create, modify, delete, etc.).
|
|
||||||
* `manage_shader`: Performs shader CRUD operations (create, read, modify, delete).
|
|
||||||
* `manage_gameobject`: Manages GameObjects: create, modify, delete, find, and component operations.
|
|
||||||
* `execute_menu_item`: Executes Unity Editor menu items (e.g., "File/Save Project").
|
* `execute_menu_item`: Executes Unity Editor menu items (e.g., "File/Save Project").
|
||||||
|
* `manage_asset`: Performs asset operations (import, create, modify, delete, etc.).
|
||||||
|
* `manage_editor`: Controls and queries the editor's state and settings.
|
||||||
|
* `manage_gameobject`: Manages GameObjects: create, modify, delete, find, and component operations.
|
||||||
|
* `manage_prefabs`: Performs prefab operations (create, modify, delete, etc.).
|
||||||
|
* `manage_scene`: Manages scenes (load, save, create, get hierarchy, etc.).
|
||||||
|
* `manage_script`: Compatibility router for legacy script operations (create, read, delete). Prefer `apply_text_edits` or `script_apply_edits` for edits.
|
||||||
|
* `manage_shader`: Performs shader CRUD operations (create, read, modify, delete).
|
||||||
|
* `read_console`: Gets messages from or clears the console.
|
||||||
|
* `run_tests`: Runs tests in the Unity Editor.
|
||||||
|
* `set_active_instance`: Routes subsequent tool calls to a specific Unity instance (when multiple are running).
|
||||||
* `apply_text_edits`: Precise text edits with precondition hashes and atomic multi-edit batches.
|
* `apply_text_edits`: Precise text edits with precondition hashes and atomic multi-edit batches.
|
||||||
* `script_apply_edits`: Structured C# method/class edits (insert/replace/delete) with safer boundaries.
|
* `script_apply_edits`: Structured C# method/class edits (insert/replace/delete) with safer boundaries.
|
||||||
* `validate_script`: Fast validation (basic/standard) to catch syntax/structure issues before/after writes.
|
* `validate_script`: Fast validation (basic/standard) to catch syntax/structure issues before/after writes.
|
||||||
* `run_test`: Runs a tests in the Unity Editor.
|
* `create_script`: Create a new C# script at the given project path.
|
||||||
* `set_active_instance`: Routes subsequent tool calls to a specific Unity instance (when multiple are running).
|
* `delete_script`: Delete a C# script by URI or Assets-relative path.
|
||||||
|
* `get_sha`: Get SHA256 and basic metadata for a Unity C# script without returning file contents.
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -64,6 +68,14 @@ MCP for Unity acts as a bridge, allowing AI assistants (like Claude, Cursor) to
|
||||||
* `unity_instances`: Lists all running Unity Editor instances with their details (name, path, port, status).
|
* `unity_instances`: Lists all running Unity Editor instances with their details (name, path, port, status).
|
||||||
* `menu_items`: Retrieves all available menu items in the Unity Editor.
|
* `menu_items`: Retrieves all available menu items in the Unity Editor.
|
||||||
* `tests`: Retrieves all available tests in the Unity Editor. Can select tests of a specific type (e.g., "EditMode", "PlayMode").
|
* `tests`: Retrieves all available tests in the Unity Editor. Can select tests of a specific type (e.g., "EditMode", "PlayMode").
|
||||||
|
* `editor_active_tool`: Currently active editor tool (Move, Rotate, Scale, etc.) and transform handle settings.
|
||||||
|
* `editor_prefab_stage`: Current prefab editing context if a prefab is open in isolation mode.
|
||||||
|
* `editor_selection`: Detailed information about currently selected objects in the editor.
|
||||||
|
* `editor_state`: Current editor runtime state including play mode, compilation status, active scene, and selection summary.
|
||||||
|
* `editor_windows`: All currently open editor windows with their titles, types, positions, and focus state.
|
||||||
|
* `project_info`: Static project information including root path, Unity version, and platform.
|
||||||
|
* `project_layers`: All layers defined in the project's TagManager with their indices (0-31).
|
||||||
|
* `project_tags`: All tags defined in the project's TagManager.
|
||||||
</details>
|
</details>
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ from unity_connection import get_unity_connection_pool
|
||||||
name="unity_instances",
|
name="unity_instances",
|
||||||
description="Lists all running Unity Editor instances with their details."
|
description="Lists all running Unity Editor instances with their details."
|
||||||
)
|
)
|
||||||
def unity_instances(ctx: Context) -> dict[str, Any]:
|
async def unity_instances(ctx: Context) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
List all available Unity Editor instances.
|
List all available Unity Editor instances.
|
||||||
|
|
||||||
|
|
@ -30,7 +30,7 @@ def unity_instances(ctx: Context) -> dict[str, Any]:
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary containing list of instances and metadata
|
Dictionary containing list of instances and metadata
|
||||||
"""
|
"""
|
||||||
ctx.info("Listing Unity instances")
|
await ctx.info("Listing Unity instances")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
pool = get_unity_connection_pool()
|
pool = get_unity_connection_pool()
|
||||||
|
|
@ -58,7 +58,7 @@ def unity_instances(ctx: Context) -> dict[str, Any]:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
ctx.error(f"Error listing Unity instances: {e}")
|
await ctx.error(f"Error listing Unity instances: {e}")
|
||||||
return {
|
return {
|
||||||
"success": False,
|
"success": False,
|
||||||
"error": f"Failed to list Unity instances: {str(e)}",
|
"error": f"Failed to list Unity instances: {str(e)}",
|
||||||
|
|
|
||||||
|
|
@ -108,12 +108,14 @@ async def server_lifespan(server: FastMCP) -> AsyncIterator[Dict[str, Any]]:
|
||||||
instances = _unity_connection_pool.discover_all_instances()
|
instances = _unity_connection_pool.discover_all_instances()
|
||||||
|
|
||||||
if instances:
|
if instances:
|
||||||
logger.info(f"Discovered {len(instances)} Unity instance(s): {[i.id for i in instances]}")
|
logger.info(
|
||||||
|
f"Discovered {len(instances)} Unity instance(s): {[i.id for i in instances]}")
|
||||||
|
|
||||||
# Try to connect to default instance
|
# Try to connect to default instance
|
||||||
try:
|
try:
|
||||||
_unity_connection_pool.get_connection()
|
_unity_connection_pool.get_connection()
|
||||||
logger.info("Connected to default Unity instance on startup")
|
logger.info(
|
||||||
|
"Connected to default Unity instance on startup")
|
||||||
|
|
||||||
# Record successful Unity connection (deferred)
|
# Record successful Unity connection (deferred)
|
||||||
import threading as _t
|
import threading as _t
|
||||||
|
|
@ -126,7 +128,8 @@ async def server_lifespan(server: FastMCP) -> AsyncIterator[Dict[str, Any]]:
|
||||||
}
|
}
|
||||||
)).start()
|
)).start()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("Could not connect to default Unity instance: %s", e)
|
logger.warning(
|
||||||
|
"Could not connect to default Unity instance: %s", e)
|
||||||
else:
|
else:
|
||||||
logger.warning("No Unity instances found on startup")
|
logger.warning("No Unity instances found on startup")
|
||||||
|
|
||||||
|
|
@ -172,23 +175,31 @@ mcp = FastMCP(
|
||||||
name="mcp-for-unity-server",
|
name="mcp-for-unity-server",
|
||||||
lifespan=server_lifespan,
|
lifespan=server_lifespan,
|
||||||
instructions="""
|
instructions="""
|
||||||
This server provides tools to interact with the Unity Game Engine Editor.\n\n
|
This server provides tools to interact with the Unity Game Engine Editor.
|
||||||
Available tools:\n
|
|
||||||
- `manage_editor`: Controls editor state and queries info.\n
|
|
||||||
- `execute_menu_item`: Executes, lists and checks for the existence of Unity Editor menu items.\n
|
|
||||||
- `read_console`: Reads or clears Unity console messages, with filtering options.\n
|
|
||||||
- `manage_scene`: Manages scenes.\n
|
|
||||||
- `manage_gameobject`: Manages GameObjects in the scene.\n
|
|
||||||
- `manage_script`: Manages C# script files.\n
|
|
||||||
- `manage_asset`: Manages prefabs and assets.\n
|
|
||||||
- `manage_shader`: Manages shaders.\n\n
|
|
||||||
- Tips:\n
|
|
||||||
- Create prefabs for reusable GameObjects.\n
|
|
||||||
- Always include a camera and main light in your scenes.\n
|
|
||||||
- Unless specified otherwise, paths are relative to the project's `Assets/` folder.\n
|
|
||||||
- After creating or modifying scripts with `manage_script`, allow Unity to recompile; use `read_console` to check for compile errors.\n
|
|
||||||
- Use `execute_menu_item` for interacting with Unity systems and third party tools like a user would.\n
|
|
||||||
|
|
||||||
|
Important Workflows:
|
||||||
|
|
||||||
|
Script Management:
|
||||||
|
1. After creating or modifying scripts with `manage_script`
|
||||||
|
2. Use `read_console` to check for compilation errors before proceeding
|
||||||
|
3. Only after successful compilation can new components/types be used
|
||||||
|
|
||||||
|
Scene Setup:
|
||||||
|
- Always include a Camera and main Light (Directional Light) in new scenes
|
||||||
|
- Create prefabs with `manage_asset` for reusable GameObjects
|
||||||
|
- Use `manage_scene` to load, save, and query scene information
|
||||||
|
|
||||||
|
Path Conventions:
|
||||||
|
- Unless specified otherwise, all paths are relative to the project's `Assets/` folder
|
||||||
|
- Use forward slashes (/) in paths for cross-platform compatibility
|
||||||
|
|
||||||
|
Console Monitoring:
|
||||||
|
- Check `read_console` regularly to catch errors, warnings, and compilation status
|
||||||
|
- Filter by log type (Error, Warning, Log) to focus on specific issues
|
||||||
|
|
||||||
|
Menu Items:
|
||||||
|
- 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
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -237,7 +248,8 @@ Examples:
|
||||||
# Set environment variable if --default-instance is provided
|
# Set environment variable if --default-instance is provided
|
||||||
if args.default_instance:
|
if args.default_instance:
|
||||||
os.environ["UNITY_MCP_DEFAULT_INSTANCE"] = args.default_instance
|
os.environ["UNITY_MCP_DEFAULT_INSTANCE"] = args.default_instance
|
||||||
logger.info(f"Using default Unity instance from command-line: {args.default_instance}")
|
logger.info(
|
||||||
|
f"Using default Unity instance from command-line: {args.default_instance}")
|
||||||
|
|
||||||
mcp.run(transport='stdio')
|
mcp.run(transport='stdio')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,11 +44,11 @@ class RunTestsResponse(MCPResponse):
|
||||||
)
|
)
|
||||||
async def run_tests(
|
async def run_tests(
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
mode: Annotated[Literal["edit", "play"], "Unity test mode to run"] = "edit",
|
mode: Annotated[Literal["EditMode", "PlayMode"], Field(
|
||||||
timeout_seconds: Annotated[int | str | None, "Optional timeout in seconds for the Unity test run (string, e.g. '30')"] = None,
|
description="Unity test mode to run")] = "EditMode",
|
||||||
) -> dict[str, Any]:
|
timeout_seconds: Annotated[int | str, Field(
|
||||||
# Get active instance from session state
|
description="Optional timeout in seconds for the Unity test run (string, e.g. '30')")] | None = None,
|
||||||
# Removed session_state import
|
) -> RunTestsResponse:
|
||||||
unity_instance = get_unity_instance_from_context(ctx)
|
unity_instance = get_unity_instance_from_context(ctx)
|
||||||
|
|
||||||
# Coerce timeout defensively (string/float -> int)
|
# Coerce timeout defensively (string/float -> int)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.TestTools;
|
using UnityEngine.TestTools;
|
||||||
using System.Collections;
|
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using MCPForUnity.Editor.Tools;
|
using MCPForUnity.Editor.Tools;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using MCPForUnity.Editor.Tools;
|
using MCPForUnity.Editor.Tools;
|
||||||
using static MCPForUnity.Editor.Tools.ManageGameObject;
|
|
||||||
|
|
||||||
namespace MCPForUnityTests.Editor.Tools
|
namespace MCPForUnityTests.Editor.Tools
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using MCPForUnity.Editor.Tools;
|
using MCPForUnity.Editor.Tools;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ using System;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using MCPForUnity.Editor.Tools;
|
using MCPForUnity.Editor.Tools;
|
||||||
using static MCPForUnity.Editor.Tools.ManageGameObject;
|
|
||||||
|
|
||||||
namespace MCPForUnityTests.Editor.Tools
|
namespace MCPForUnityTests.Editor.Tools
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEditor;
|
|
||||||
using UnityEngine.TestTools;
|
using UnityEngine.TestTools;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using MCPForUnity.Editor.Tools;
|
using MCPForUnity.Editor.Tools;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ using UnityEditor;
|
||||||
using UnityEditor.SceneManagement;
|
using UnityEditor.SceneManagement;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using MCPForUnity.Editor.Tools.Prefabs;
|
using MCPForUnity.Editor.Tools.Prefabs;
|
||||||
using MCPForUnity.Editor.Tools;
|
|
||||||
|
|
||||||
namespace MCPForUnityTests.Editor.Tools
|
namespace MCPForUnityTests.Editor.Tools
|
||||||
{
|
{
|
||||||
|
|
@ -53,11 +52,11 @@ namespace MCPForUnityTests.Editor.Tools
|
||||||
|
|
||||||
Assert.IsTrue(openResult.Value<bool>("success"), "open_stage should succeed for a valid prefab.");
|
Assert.IsTrue(openResult.Value<bool>("success"), "open_stage should succeed for a valid prefab.");
|
||||||
|
|
||||||
PrefabStage stage = PrefabStageUtility.GetCurrentPrefabStage();
|
UnityEditor.SceneManagement.PrefabStage stage = PrefabStageUtility.GetCurrentPrefabStage();
|
||||||
Assert.IsNotNull(stage, "Prefab stage should be open after open_stage.");
|
Assert.IsNotNull(stage, "Prefab stage should be open after open_stage.");
|
||||||
Assert.AreEqual(prefabPath, stage.assetPath, "Opened stage should match prefab path.");
|
Assert.AreEqual(prefabPath, stage.assetPath, "Opened stage should match prefab path.");
|
||||||
|
|
||||||
var stageInfo = ToJObject(ManageEditor.HandleCommand(new JObject { ["action"] = "get_prefab_stage" }));
|
var stageInfo = ToJObject(MCPForUnity.Editor.Resources.Editor.PrefabStage.HandleCommand(new JObject()));
|
||||||
Assert.IsTrue(stageInfo.Value<bool>("success"), "get_prefab_stage should succeed when stage is open.");
|
Assert.IsTrue(stageInfo.Value<bool>("success"), "get_prefab_stage should succeed when stage is open.");
|
||||||
|
|
||||||
var data = stageInfo["data"] as JObject;
|
var data = stageInfo["data"] as JObject;
|
||||||
|
|
@ -125,7 +124,7 @@ namespace MCPForUnityTests.Editor.Tools
|
||||||
["prefabPath"] = prefabPath
|
["prefabPath"] = prefabPath
|
||||||
});
|
});
|
||||||
|
|
||||||
PrefabStage stage = PrefabStageUtility.GetCurrentPrefabStage();
|
UnityEditor.SceneManagement.PrefabStage stage = PrefabStageUtility.GetCurrentPrefabStage();
|
||||||
Assert.IsNotNull(stage, "Stage should be open before modifying.");
|
Assert.IsNotNull(stage, "Stage should be open before modifying.");
|
||||||
|
|
||||||
stage.prefabContentsRoot.transform.localScale = new Vector3(2f, 2f, 2f);
|
stage.prefabContentsRoot.transform.localScale = new Vector3(2f, 2f, 2f);
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEditor;
|
|
||||||
using UnityEngine.TestTools;
|
|
||||||
using MCPForUnity.Editor.Helpers;
|
using MCPForUnity.Editor.Helpers;
|
||||||
|
|
||||||
namespace MCPForUnityTests.Editor.Tools
|
namespace MCPForUnityTests.Editor.Tools
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ Create a Python file **anywhere in your Unity project**. For example, `Assets/Ed
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from typing import Annotated, Any
|
from typing import Annotated, Any
|
||||||
from mcp.server.fastmcp import Context
|
from fastmcp import Context
|
||||||
from registry import mcp_for_unity_tool
|
from registry import mcp_for_unity_tool
|
||||||
from unity_connection import send_command_with_retry
|
from unity_connection import send_command_with_retry
|
||||||
|
|
||||||
|
|
@ -127,7 +127,7 @@ Here's a complete example showing how to create a screenshot capture tool.
|
||||||
```python
|
```python
|
||||||
from typing import Annotated, Any
|
from typing import Annotated, Any
|
||||||
|
|
||||||
from mcp.server.fastmcp import Context
|
from fastmcp import Context
|
||||||
|
|
||||||
from registry import mcp_for_unity_tool
|
from registry import mcp_for_unity_tool
|
||||||
from unity_connection import send_command_with_retry
|
from unity_connection import send_command_with_retry
|
||||||
|
|
|
||||||
|
|
@ -9,32 +9,44 @@ Welcome to the MCP for Unity development environment! This directory contains to
|
||||||
|
|
||||||
### Installing Development Dependencies
|
### Installing Development Dependencies
|
||||||
|
|
||||||
To contribute or run tests, you need to install the development dependencies:
|
To contribute or run tests, you need to install the development dependencies using `uv`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Navigate to the server source directory
|
# Navigate to the server source directory
|
||||||
cd MCPForUnity/UnityMcpServer~/src
|
cd MCPForUnity/UnityMcpServer~/src
|
||||||
|
|
||||||
# Install the package in editable mode with dev dependencies
|
# Install the package in editable mode with dev dependencies
|
||||||
pip install -e .[dev]
|
uv pip install -e ".[dev]"
|
||||||
```
|
```
|
||||||
|
|
||||||
This installs:
|
This installs:
|
||||||
|
|
||||||
- **Runtime dependencies**: `httpx`, `mcp`, `pydantic`, `tomli`
|
- **Runtime dependencies**: `httpx`, `fastmcp`, `mcp`, `pydantic`, `tomli`
|
||||||
- **Development dependencies**: `pytest`, `pytest-anyio`
|
- **Development dependencies**: `pytest`, `pytest-asyncio`
|
||||||
|
|
||||||
### Running Tests
|
### Running Tests
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# From the repo root
|
# From the server source directory
|
||||||
pytest tests/ -v
|
cd MCPForUnity/UnityMcpServer~/src
|
||||||
|
uv run pytest tests/ -v
|
||||||
```
|
```
|
||||||
|
|
||||||
Or if you prefer using Python module syntax:
|
Or from the repo root:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m pytest tests/ -v
|
# Using uv from the server directory
|
||||||
|
cd MCPForUnity/UnityMcpServer~/src && uv run pytest tests/ -v
|
||||||
|
```
|
||||||
|
|
||||||
|
To run only integration tests:
|
||||||
|
```bash
|
||||||
|
uv run pytest tests/ -v -m integration
|
||||||
|
```
|
||||||
|
|
||||||
|
To run only unit tests:
|
||||||
|
```bash
|
||||||
|
uv run pytest tests/ -v -m unit
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🚀 Available Development Features
|
## 🚀 Available Development Features
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
[pytest]
|
|
||||||
testpaths = tests
|
|
||||||
norecursedirs = UnityMcpBridge MCPForUnity
|
|
||||||
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
import os
|
|
||||||
|
|
||||||
# Ensure telemetry is disabled during test collection and execution to avoid
|
|
||||||
# any background network or thread startup that could slow or block pytest.
|
|
||||||
os.environ.setdefault("DISABLE_TELEMETRY", "true")
|
|
||||||
os.environ.setdefault("UNITY_MCP_DISABLE_TELEMETRY", "true")
|
|
||||||
os.environ.setdefault("MCP_DISABLE_TELEMETRY", "true")
|
|
||||||
|
|
||||||
# Avoid collecting tests under the two 'src' package folders to prevent
|
|
||||||
# duplicate-package import conflicts (two different 'src' packages).
|
|
||||||
collect_ignore = [
|
|
||||||
"UnityMcpBridge/UnityMcpServer~/src",
|
|
||||||
"MCPForUnity/UnityMcpServer~/src",
|
|
||||||
]
|
|
||||||
collect_ignore_glob = [
|
|
||||||
"UnityMcpBridge/UnityMcpServer~/src/*",
|
|
||||||
"MCPForUnity/UnityMcpServer~/src/*",
|
|
||||||
]
|
|
||||||
|
|
||||||
def pytest_ignore_collect(path):
|
|
||||||
p = str(path)
|
|
||||||
norm = p.replace("\\", "/")
|
|
||||||
return (
|
|
||||||
"/UnityMcpBridge/UnityMcpServer~/src/" in norm
|
|
||||||
or "/MCPForUnity/UnityMcpServer~/src/" in norm
|
|
||||||
or norm.endswith("UnityMcpBridge/UnityMcpServer~/src")
|
|
||||||
or norm.endswith("MCPForUnity/UnityMcpServer~/src")
|
|
||||||
)
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
import sys
|
|
||||||
import pathlib
|
|
||||||
import importlib.util
|
|
||||||
import types
|
|
||||||
import asyncio
|
|
||||||
import os
|
|
||||||
|
|
||||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
|
||||||
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
|
|
||||||
sys.path.insert(0, str(SRC))
|
|
||||||
|
|
||||||
|
|
||||||
def _load_module(path: pathlib.Path, name: str):
|
|
||||||
spec = importlib.util.spec_from_file_location(name, path)
|
|
||||||
if spec is None or spec.loader is None:
|
|
||||||
raise ImportError(f"Cannot load module {name} from {path}")
|
|
||||||
mod = importlib.util.module_from_spec(spec)
|
|
||||||
spec.loader.exec_module(mod)
|
|
||||||
return mod
|
|
||||||
|
|
||||||
|
|
||||||
# Stub fastmcp to avoid real MCP deps
|
|
||||||
fastmcp_pkg = types.ModuleType("fastmcp")
|
|
||||||
|
|
||||||
|
|
||||||
class _Dummy:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
fastmcp_pkg.FastMCP = _Dummy
|
|
||||||
fastmcp_pkg.Context = _Dummy
|
|
||||||
sys.modules.setdefault("fastmcp", fastmcp_pkg)
|
|
||||||
|
|
||||||
|
|
||||||
from tests.test_helpers import DummyContext
|
|
||||||
|
|
||||||
|
|
||||||
def test_manage_asset_pagination_coercion(monkeypatch):
|
|
||||||
# Import with SRC as CWD to satisfy telemetry import side effects
|
|
||||||
_prev = os.getcwd()
|
|
||||||
os.chdir(str(SRC))
|
|
||||||
try:
|
|
||||||
manage_asset_mod = _load_module(SRC / "tools" / "manage_asset.py", "manage_asset_mod")
|
|
||||||
finally:
|
|
||||||
os.chdir(_prev)
|
|
||||||
|
|
||||||
captured = {}
|
|
||||||
|
|
||||||
async def fake_async_send(cmd, params, **kwargs):
|
|
||||||
captured["params"] = params
|
|
||||||
return {"success": True, "data": {}}
|
|
||||||
|
|
||||||
monkeypatch.setattr(manage_asset_mod, "async_send_command_with_retry", fake_async_send)
|
|
||||||
|
|
||||||
result = asyncio.run(
|
|
||||||
manage_asset_mod.manage_asset(
|
|
||||||
ctx=DummyContext(),
|
|
||||||
action="search",
|
|
||||||
path="Assets",
|
|
||||||
page_size="50",
|
|
||||||
page_number="2",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result == {"success": True, "data": {}}
|
|
||||||
assert captured["params"]["pageSize"] == 50
|
|
||||||
assert captured["params"]["pageNumber"] == 2
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
import sys
|
|
||||||
import pathlib
|
|
||||||
import importlib.util
|
|
||||||
import types
|
|
||||||
import os
|
|
||||||
|
|
||||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
|
||||||
SRC = ROOT / "MCPForUnity" / "UnityMcpServer~" / "src"
|
|
||||||
sys.path.insert(0, str(SRC))
|
|
||||||
|
|
||||||
|
|
||||||
def _load_module(path: pathlib.Path, name: str):
|
|
||||||
spec = importlib.util.spec_from_file_location(name, path)
|
|
||||||
if spec is None or spec.loader is None:
|
|
||||||
raise ImportError(f"Cannot load module {name} from {path}")
|
|
||||||
mod = importlib.util.module_from_spec(spec)
|
|
||||||
spec.loader.exec_module(mod)
|
|
||||||
return mod
|
|
||||||
|
|
||||||
|
|
||||||
# Stub fastmcp to avoid real MCP deps
|
|
||||||
fastmcp_pkg = types.ModuleType("fastmcp")
|
|
||||||
|
|
||||||
|
|
||||||
class _Dummy:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
fastmcp_pkg.FastMCP = _Dummy
|
|
||||||
fastmcp_pkg.Context = _Dummy
|
|
||||||
sys.modules.setdefault("fastmcp", fastmcp_pkg)
|
|
||||||
|
|
||||||
|
|
||||||
from tests.test_helpers import DummyContext
|
|
||||||
|
|
||||||
|
|
||||||
def test_manage_gameobject_boolean_and_tag_mapping(monkeypatch):
|
|
||||||
# Import with SRC as CWD to satisfy telemetry import side effects
|
|
||||||
_prev = os.getcwd()
|
|
||||||
os.chdir(str(SRC))
|
|
||||||
try:
|
|
||||||
manage_go_mod = _load_module(SRC / "tools" / "manage_gameobject.py", "manage_go_mod")
|
|
||||||
finally:
|
|
||||||
os.chdir(_prev)
|
|
||||||
|
|
||||||
captured = {}
|
|
||||||
|
|
||||||
def fake_send(cmd, params):
|
|
||||||
captured["params"] = params
|
|
||||||
return {"success": True, "data": {}}
|
|
||||||
|
|
||||||
monkeypatch.setattr(manage_go_mod, "send_command_with_retry", fake_send)
|
|
||||||
|
|
||||||
# find by tag: allow tag to map to searchTerm
|
|
||||||
resp = manage_go_mod.manage_gameobject(
|
|
||||||
ctx=DummyContext(),
|
|
||||||
action="find",
|
|
||||||
search_method="by_tag",
|
|
||||||
tag="Player",
|
|
||||||
find_all="true",
|
|
||||||
search_inactive="0",
|
|
||||||
)
|
|
||||||
# Loosen equality: wrapper may include a diagnostic message
|
|
||||||
assert resp.get("success") is True
|
|
||||||
assert "data" in resp
|
|
||||||
# ensure tag mapped to searchTerm and booleans passed through; C# side coerces true/false already
|
|
||||||
assert captured["params"]["searchTerm"] == "Player"
|
|
||||||
assert captured["params"]["findAll"] == "true" or captured["params"]["findAll"] is True
|
|
||||||
assert captured["params"]["searchInactive"] in ("0", False, 0)
|
|
||||||
|
|
||||||
|
|
||||||
Loading…
Reference in New Issue