Rename project from kicad-mcp to mckicad

Rename source directory kicad_mcp/ → mckicad/, update all imports,
pyproject.toml metadata, documentation references, Makefile targets,
and .gitignore paths. All 195 tests pass.
This commit is contained in:
Ryan Malloy 2026-02-13 00:53:59 -07:00
parent b99d1bd2b9
commit 687e14bd11
85 changed files with 474 additions and 540 deletions

2
.gitignore vendored
View File

@ -47,7 +47,7 @@ logs/
.DS_Store
# MCP specific
~/.kicad_mcp/drc_history/
~/.mckicad/drc_history/
# UV and modern Python tooling
uv.lock

View File

@ -9,8 +9,8 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
- `make run` - Start the KiCad MCP server (`uv run python main.py`)
- `make test` - Run all tests with pytest
- `make test <file>` - Run specific test file
- `make lint` - Run linting with ruff and mypy (`uv run ruff check kicad_mcp/ tests/` + `uv run mypy kicad_mcp/`)
- `make format` - Format code with ruff (`uv run ruff format kicad_mcp/ tests/`)
- `make lint` - Run linting with ruff and mypy (`uv run ruff check mckicad/ tests/` + `uv run mypy mckicad/`)
- `make format` - Format code with ruff (`uv run ruff format mckicad/ tests/`)
- `make build` - Build package with uv
- `make clean` - Clean build artifacts
@ -48,19 +48,19 @@ This project implements a Model Context Protocol (MCP) server for KiCad electron
### Key Modules
#### Core Server (`kicad_mcp/server.py`)
#### Core Server (`mckicad/server.py`)
- FastMCP server initialization with lifespan management
- Registers all resources, tools, and prompts
- Signal handling for graceful shutdown
- Cleanup handlers for temporary directories
#### Configuration (`kicad_mcp/config.py`)
#### Configuration (`mckicad/config.py`)
- Platform-specific KiCad paths (macOS/Windows/Linux)
- Environment variable handling (`KICAD_SEARCH_PATHS`, `KICAD_USER_DIR`)
- Component library mappings and default footprints
- Timeout and display constants
#### Context Management (`kicad_mcp/context.py`)
#### Context Management (`mckicad/context.py`)
- Lifespan context with KiCad module availability detection
- Shared cache across requests
- Application state management
@ -79,7 +79,7 @@ This project implements a Model Context Protocol (MCP) server for KiCad electron
### Project Structure
```
kicad_mcp/
mckicad/
├── resources/ # MCP resources (data providers)
├── tools/ # MCP tools (action performers)
├── prompts/ # MCP prompt templates
@ -94,7 +94,7 @@ kicad_mcp/
### Adding New Features
1. Identify component type (resource/tool/prompt)
2. Add implementation to appropriate module in `kicad_mcp/`
2. Add implementation to appropriate module in `mckicad/`
3. Register in `server.py` create_server() function
4. Use lifespan context for shared state and caching
5. Include progress reporting for long operations
@ -137,7 +137,7 @@ This project implements groundbreaking real-time PCB automation through two adva
**Key Usage Pattern:**
```python
from kicad_mcp.utils.ipc_client import kicad_ipc_session
from mckicad.utils.ipc_client import kicad_ipc_session
with kicad_ipc_session(board_path) as client:
footprints = client.get_footprints()
@ -216,12 +216,12 @@ Key environment variables in `.env`:
The package declares a script entry point in `pyproject.toml`:
```toml
[project.scripts]
kicad-mcp = "kicad_mcp.server:main"
mckicad = "mckicad.server:main"
```
This enables running via `uvx kicad-mcp` after installation.
This enables running via `uvx mckicad` after installation.
### Logging
- All logs written to `kicad-mcp.log` in project root
- All logs written to `mckicad.log` in project root
- Log file overwritten on each server start
- PID included in log messages for process tracking
- Use `logging` module, never `print()` statements
@ -231,7 +231,7 @@ This enables running via `uvx kicad-mcp` after installation.
### FastMCP Tool Registration
All tools follow this registration pattern in `server.py`:
```python
from kicad_mcp.tools.example_tools import register_example_tools
from mckicad.tools.example_tools import register_example_tools
def create_server() -> FastMCP:
mcp = FastMCP("KiCad", lifespan=lifespan_factory)
@ -259,7 +259,7 @@ Each tool module should:
### Progress Reporting
For long operations, use progress constants:
```python
from kicad_mcp.config import PROGRESS_CONSTANTS
from mckicad.config import PROGRESS_CONSTANTS
progress_callback(PROGRESS_CONSTANTS["start"])
# ... do work ...
@ -328,13 +328,13 @@ LOG_LEVEL=DEBUG
### Test IPC Connection
```python
from kicad_mcp.utils.ipc_client import check_kicad_availability
from mckicad.utils.ipc_client import check_kicad_availability
print(check_kicad_availability())
```
### Test FreeRouting Setup
```python
from kicad_mcp.utils.freerouting_engine import check_routing_prerequisites
from mckicad.utils.freerouting_engine import check_routing_prerequisites
print(check_routing_prerequisites())
```

View File

@ -25,11 +25,11 @@ test:
@:
lint:
uv run ruff check kicad_mcp/ tests/
uv run mypy kicad_mcp/
uv run ruff check mckicad/ tests/
uv run mypy mckicad/
format:
uv run ruff format kicad_mcp/ tests/
uv run ruff format mckicad/ tests/
clean:
rm -rf dist/

View File

@ -117,8 +117,8 @@ We've integrated cutting-edge technologies to create something truly revolutiona
```bash
# Clone and setup
git clone https://github.com/your-org/kicad-mcp.git
cd kicad-mcp
git clone https://github.com/your-org/mckicad.git
cd mckicad
make install
# Configure environment
@ -132,8 +132,8 @@ cp .env.example .env
{
"mcpServers": {
"kicad": {
"command": "/path/to/kicad-mcp/.venv/bin/python",
"args": ["/path/to/kicad-mcp/main.py"]
"command": "/path/to/mckicad/.venv/bin/python",
"args": ["/path/to/mckicad/main.py"]
}
}
}
@ -456,8 +456,8 @@ make run
- ❓ **[FAQ](docs/faq.md)** - Common questions answered
### **Community**
- 💬 **[Discussions](https://github.com/your-org/kicad-mcp/discussions)** - Share ideas and get help
- 🐛 **[Issues](https://github.com/your-org/kicad-mcp/issues)** - Report bugs and request features
- 💬 **[Discussions](https://github.com/your-org/mckicad/discussions)** - Share ideas and get help
- 🐛 **[Issues](https://github.com/your-org/mckicad/issues)** - Report bugs and request features
- 🔧 **[Contributing Guide](CONTRIBUTING.md)** - Join the development
### **Support**
@ -499,7 +499,7 @@ This project is open source under the **MIT License** - see the [LICENSE](LICENS
## 🚀 Ready to Transform Your Design Workflow?
**[Get Started Now](https://github.com/your-org/kicad-mcp)** • **[Join the Community](https://discord.gg/your-invite)** • **[Read the Docs](docs/)**
**[Get Started Now](https://github.com/your-org/mckicad)** • **[Join the Community](https://discord.gg/your-invite)** • **[Read the Docs](docs/)**
---

View File

@ -256,6 +256,6 @@ And it's available today, through the power of human-AI collaboration.
**About This Collaboration:**
This revolutionary EDA automation platform was built through an intensive human-AI collaboration between Ryan Malloy and Claude Sonnet 4 using Claude Code. The project demonstrates the incredible potential when human creativity and AI technical implementation work together to solve complex engineering challenges.
**GitHub Repository**: [KiCad MCP Server](https://github.com/user/kicad-mcp)
**GitHub Repository**: [KiCad MCP Server](https://github.com/user/mckicad)
**Performance Metrics**: 100% test success rate, sub-millisecond response times
**Impact**: Democratizes professional PCB design through conversational AI

View File

@ -8,7 +8,7 @@ The KiCad MCP Server can be configured in multiple ways:
1. **Environment Variables**: Set directly when running the server
2. **.env File**: Create a `.env` file in the project root (recommended)
3. **Code Modifications**: Edit configuration constants in `kicad_mcp/config.py`
3. **Code Modifications**: Edit configuration constants in `mckicad/config.py`
## Core Configuration Options
@ -97,9 +97,9 @@ To configure Claude Desktop to use the KiCad MCP Server:
{
"mcpServers": {
"kicad": {
"command": "/ABSOLUTE/PATH/TO/YOUR/PROJECT/kicad-mcp/venv/bin/python",
"command": "/ABSOLUTE/PATH/TO/YOUR/PROJECT/mckicad/venv/bin/python",
"args": [
"/ABSOLUTE/PATH/TO/YOUR/PROJECT/kicad-mcp/main.py"
"/ABSOLUTE/PATH/TO/YOUR/PROJECT/mckicad/main.py"
]
}
}
@ -111,9 +111,9 @@ To configure Claude Desktop to use the KiCad MCP Server:
{
"mcpServers": {
"kicad": {
"command": "C:\\Path\\To\\Your\\Project\\kicad-mcp\\venv\\Scripts\\python.exe",
"command": "C:\\Path\\To\\Your\\Project\\mckicad\\venv\\Scripts\\python.exe",
"args": [
"C:\\Path\\To\\Your\\Project\\kicad-mcp\\main.py"
"C:\\Path\\To\\Your\\Project\\mckicad\\main.py"
]
}
}
@ -128,9 +128,9 @@ You can also set environment variables directly in the client configuration:
{
"mcpServers": {
"kicad": {
"command": "/ABSOLUTE/PATH/TO/YOUR/PROJECT/kicad-mcp/venv/bin/python",
"command": "/ABSOLUTE/PATH/TO/YOUR/PROJECT/mckicad/venv/bin/python",
"args": [
"/ABSOLUTE/PATH/TO/YOUR/PROJECT/kicad-mcp/main.py"
"/ABSOLUTE/PATH/TO/YOUR/PROJECT/mckicad/main.py"
],
"env": {
"KICAD_SEARCH_PATHS": "/custom/path1,/custom/path2",
@ -145,7 +145,7 @@ You can also set environment variables directly in the client configuration:
### Custom KiCad Extensions
If you need to modify the recognized KiCad file extensions, you can edit `kicad_mcp/config.py`:
If you need to modify the recognized KiCad file extensions, you can edit `mckicad/config.py`:
```python
# File extensions
@ -161,14 +161,14 @@ KICAD_EXTENSIONS = {
The server stores DRC history to track changes over time. By default, history is stored in:
- macOS/Linux: `~/.kicad_mcp/drc_history/`
- Windows: `%APPDATA%\kicad_mcp\drc_history\`
- macOS/Linux: `~/.mckicad/drc_history/`
- Windows: `%APPDATA%\mckicad\drc_history\`
You can modify this in `kicad_mcp/utils/drc_history.py` if needed.
You can modify this in `mckicad/utils/drc_history.py` if needed.
### Python Path for KiCad Modules
The server attempts to locate and add KiCad's Python modules to the Python path automatically. If this fails, you can modify the search paths in `kicad_mcp/utils/python_path.py`.
The server attempts to locate and add KiCad's Python modules to the Python path automatically. If this fails, you can modify the search paths in `mckicad/utils/python_path.py`.
## Platform-Specific Configuration

View File

@ -29,9 +29,9 @@ This guide provides detailed information for developers who want to modify or ex
The KiCad MCP Server follows a modular architecture:
```
kicad-mcp/
mckicad/
├── main.py # Entry point
├── kicad_mcp/ # Main package
├── mckicad/ # Main package
│ ├── __init__.py
│ ├── server.py # Server creation and setup
│ ├── config.py # Configuration settings
@ -69,7 +69,7 @@ kicad-mcp/
Resources provide read-only data to the LLM. To add a new resource:
1. Add your function to an existing resource file or create a new one in `kicad_mcp/resources/`:
1. Add your function to an existing resource file or create a new one in `mckicad/resources/`:
```python
from mcp.server.fastmcp import FastMCP
@ -91,10 +91,10 @@ def register_my_resources(mcp: FastMCP) -> None:
return f"Formatted data about {parameter}"
```
2. Register your resources in `kicad_mcp/server.py`:
2. Register your resources in `mckicad/server.py`:
```python
from kicad_mcp.resources.my_resources import register_my_resources
from mckicad.resources.my_resources import register_my_resources
def create_server() -> FastMCP:
# ...
@ -106,7 +106,7 @@ def create_server() -> FastMCP:
Tools are functions that perform actions or computations. To add a new tool:
1. Add your function to an existing tool file or create a new one in `kicad_mcp/tools/`:
1. Add your function to an existing tool file or create a new one in `mckicad/tools/`:
```python
from typing import Dict, Any
@ -143,10 +143,10 @@ def register_my_tools(mcp: FastMCP) -> None:
}
```
2. Register your tools in `kicad_mcp/server.py`:
2. Register your tools in `mckicad/server.py`:
```python
from kicad_mcp.tools.my_tools import register_my_tools
from mckicad.tools.my_tools import register_my_tools
def create_server() -> FastMCP:
# ...
@ -158,7 +158,7 @@ def create_server() -> FastMCP:
Prompts are reusable templates for common interactions. To add a new prompt:
1. Add your function to an existing prompt file or create a new one in `kicad_mcp/prompts/`:
1. Add your function to an existing prompt file or create a new one in `mckicad/prompts/`:
```python
from mcp.server.fastmcp import FastMCP
@ -185,10 +185,10 @@ def register_my_prompts(mcp: FastMCP) -> None:
return prompt
```
2. Register your prompts in `kicad_mcp/server.py`:
2. Register your prompts in `mckicad/server.py`:
```python
from kicad_mcp.prompts.my_prompts import register_my_prompts
from mckicad.prompts.my_prompts import register_my_prompts
def create_server() -> FastMCP:
# ...
@ -201,7 +201,7 @@ def create_server() -> FastMCP:
The KiCad MCP Server uses a typed lifespan context to share data across requests:
```python
from kicad_mcp.context import KiCadAppContext
from mckicad.context import KiCadAppContext
@mcp.tool()
def my_tool(parameter: str, ctx: Context) -> Dict[str, Any]:

View File

@ -120,7 +120,7 @@ The pattern recognition system is designed to be extensible. If you find that ce
### Adding New Component Patterns
The pattern recognition is primarily based on regular expression matching of component values and library IDs. The patterns are defined in the `kicad_mcp/utils/pattern_recognition.py` file.
The pattern recognition is primarily based on regular expression matching of component values and library IDs. The patterns are defined in the `mckicad/utils/pattern_recognition.py` file.
For example, to add support for a new microcontroller family, you could update the `mcu_patterns` dictionary in the `identify_microcontrollers()` function:
@ -139,7 +139,7 @@ Similarly, you can add patterns for new sensors, power supply ICs, or other comp
### Adding New Circuit Recognition Functions
For entirely new types of circuits, you can add new recognition functions in the `kicad_mcp/utils/pattern_recognition.py` file, following the pattern of existing functions.
For entirely new types of circuits, you can add new recognition functions in the `mckicad/utils/pattern_recognition.py` file, following the pattern of existing functions.
For example, you might add:
@ -150,7 +150,7 @@ def identify_motor_drivers(components: Dict[str, Any], nets: Dict[str, Any]) ->
...
```
Then, update the `identify_circuit_patterns()` function in `kicad_mcp/tools/pattern_tools.py` to call your new function and include its results.
Then, update the `identify_circuit_patterns()` function in `mckicad/tools/pattern_tools.py` to call your new function and include its results.
### Contributing Your Extensions
@ -237,7 +237,7 @@ The pattern recognition system relies on a community-driven database of componen
If you work with components that aren't being recognized:
1. Check the current patterns in `kicad_mcp/utils/pattern_recognition.py`
1. Check the current patterns in `mckicad/utils/pattern_recognition.py`
2. Add your own patterns for components you use
3. Submit a pull request to share with the community

View File

@ -63,9 +63,9 @@ This guide helps you troubleshoot common issues with the KiCad MCP Server.
{
"mcpServers": {
"kicad": {
"command": "/ABSOLUTE/PATH/TO/YOUR/PROJECT/kicad-mcp/venv/bin/python",
"command": "/ABSOLUTE/PATH/TO/YOUR/PROJECT/mckicad/venv/bin/python",
"args": [
"/ABSOLUTE/PATH/TO/YOUR/PROJECT/kicad-mcp/main.py"
"/ABSOLUTE/PATH/TO/YOUR/PROJECT/mckicad/main.py"
]
}
}

10
main.py
View File

@ -8,12 +8,12 @@ import sys
import logging # Import logging module
# Must import config BEFORE env potentially overrides it via os.environ
from kicad_mcp.config import KICAD_USER_DIR, ADDITIONAL_SEARCH_PATHS
from kicad_mcp.server import main as server_main
from kicad_mcp.utils.env import load_dotenv
from mckicad.config import KICAD_USER_DIR, ADDITIONAL_SEARCH_PATHS
from mckicad.server import main as server_main
from mckicad.utils.env import load_dotenv
# --- Setup Logging ---
log_file = os.path.join(os.path.dirname(__file__), 'kicad-mcp.log')
log_file = os.path.join(os.path.dirname(__file__), 'mckicad.log')
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - [PID:%(process)d] - %(message)s',
@ -49,7 +49,7 @@ logging.info(f"os.environ['KICAD_SEARCH_PATHS'] after load_dotenv: {effective_se
# Re-log the values imported from config.py to see if they reflect os.environ changes
# (This depends on config.py using os.getenv internally AFTER load_dotenv runs)
try:
from kicad_mcp import config
from mckicad import config
import importlib
importlib.reload(config) # Attempt to force re-reading config
logging.info(f"Effective KICAD_USER_DIR from config.py after reload: {config.KICAD_USER_DIR}")

View File

@ -9,8 +9,8 @@ from fastmcp import FastMCP
import pandas as pd
# Import the helper functions from bom_tools.py to avoid code duplication
from kicad_mcp.tools.bom_tools import analyze_bom_data, parse_bom_file
from kicad_mcp.utils.file_utils import get_project_files
from mckicad.tools.bom_tools import analyze_bom_data, parse_bom_file
from mckicad.utils.file_utils import get_project_files
def register_bom_resources(mcp: FastMCP) -> None:

View File

@ -6,9 +6,9 @@ import os
from fastmcp import FastMCP
from kicad_mcp.tools.drc_impl.cli_drc import run_drc_via_cli
from kicad_mcp.utils.drc_history import get_drc_history
from kicad_mcp.utils.file_utils import get_project_files
from mckicad.tools.drc_impl.cli_drc import run_drc_via_cli
from mckicad.utils.drc_history import get_drc_history
from mckicad.utils.file_utils import get_project_files
def register_drc_resources(mcp: FastMCP) -> None:

View File

@ -6,8 +6,8 @@ import os
from fastmcp import FastMCP
from kicad_mcp.utils.file_utils import get_project_files
from kicad_mcp.utils.netlist_parser import analyze_netlist, extract_netlist
from mckicad.utils.file_utils import get_project_files
from mckicad.utils.netlist_parser import analyze_netlist, extract_netlist
def register_netlist_resources(mcp: FastMCP) -> None:

View File

@ -6,9 +6,9 @@ import os
from fastmcp import FastMCP
from kicad_mcp.utils.file_utils import get_project_files
from kicad_mcp.utils.netlist_parser import extract_netlist
from kicad_mcp.utils.pattern_recognition import (
from mckicad.utils.file_utils import get_project_files
from mckicad.utils.netlist_parser import extract_netlist
from mckicad.utils.pattern_recognition import (
identify_amplifiers,
identify_digital_interfaces,
identify_filters,

View File

@ -6,7 +6,7 @@ import os
from fastmcp import FastMCP
from kicad_mcp.utils.file_utils import get_project_files, load_project_json
from mckicad.utils.file_utils import get_project_files, load_project_json
def register_project_resources(mcp: FastMCP) -> None:

View File

@ -12,37 +12,37 @@ import signal
from fastmcp import FastMCP
# Import context management
from kicad_mcp.context import kicad_lifespan
from kicad_mcp.prompts.bom_prompts import register_bom_prompts
from kicad_mcp.prompts.drc_prompt import register_drc_prompts
from kicad_mcp.prompts.pattern_prompts import register_pattern_prompts
from mckicad.context import kicad_lifespan
from mckicad.prompts.bom_prompts import register_bom_prompts
from mckicad.prompts.drc_prompt import register_drc_prompts
from mckicad.prompts.pattern_prompts import register_pattern_prompts
# Import prompt handlers
from kicad_mcp.prompts.templates import register_prompts
from kicad_mcp.resources.bom_resources import register_bom_resources
from kicad_mcp.resources.drc_resources import register_drc_resources
from kicad_mcp.resources.files import register_file_resources
from kicad_mcp.resources.netlist_resources import register_netlist_resources
from kicad_mcp.resources.pattern_resources import register_pattern_resources
from mckicad.prompts.templates import register_prompts
from mckicad.resources.bom_resources import register_bom_resources
from mckicad.resources.drc_resources import register_drc_resources
from mckicad.resources.files import register_file_resources
from mckicad.resources.netlist_resources import register_netlist_resources
from mckicad.resources.pattern_resources import register_pattern_resources
# Import resource handlers
from kicad_mcp.resources.projects import register_project_resources
from kicad_mcp.tools.advanced_drc_tools import register_advanced_drc_tools
from kicad_mcp.tools.ai_tools import register_ai_tools
from kicad_mcp.tools.analysis_tools import register_analysis_tools
from kicad_mcp.tools.bom_tools import register_bom_tools
from kicad_mcp.tools.drc_tools import register_drc_tools
from kicad_mcp.tools.export_tools import register_export_tools
from kicad_mcp.tools.layer_tools import register_layer_tools
from kicad_mcp.tools.model3d_tools import register_model3d_tools
from kicad_mcp.tools.netlist_tools import register_netlist_tools
from kicad_mcp.tools.pattern_tools import register_pattern_tools
from kicad_mcp.tools.project_automation import register_project_automation_tools
from mckicad.resources.projects import register_project_resources
from mckicad.tools.advanced_drc_tools import register_advanced_drc_tools
from mckicad.tools.ai_tools import register_ai_tools
from mckicad.tools.analysis_tools import register_analysis_tools
from mckicad.tools.bom_tools import register_bom_tools
from mckicad.tools.drc_tools import register_drc_tools
from mckicad.tools.export_tools import register_export_tools
from mckicad.tools.layer_tools import register_layer_tools
from mckicad.tools.model3d_tools import register_model3d_tools
from mckicad.tools.netlist_tools import register_netlist_tools
from mckicad.tools.pattern_tools import register_pattern_tools
from mckicad.tools.project_automation import register_project_automation_tools
# Import tool handlers
from kicad_mcp.tools.project_tools import register_project_tools
from kicad_mcp.tools.routing_tools import register_routing_tools
from kicad_mcp.tools.symbol_tools import register_symbol_tools
from mckicad.tools.project_tools import register_project_tools
from mckicad.tools.routing_tools import register_routing_tools
from mckicad.tools.symbol_tools import register_symbol_tools
# Track cleanup handlers
cleanup_handlers = []
@ -196,7 +196,7 @@ def create_server() -> FastMCP:
"""Clean up any temporary directories created by the server."""
import shutil
from kicad_mcp.utils.temp_dir_manager import get_temp_dirs
from mckicad.utils.temp_dir_manager import get_temp_dirs
temp_dirs = get_temp_dirs()
logging.info(f"Cleaning up {len(temp_dirs)} temporary directories")

View File

@ -9,8 +9,8 @@ from typing import Any
from fastmcp import FastMCP
from kicad_mcp.utils.advanced_drc import RuleSeverity, RuleType, create_drc_manager
from kicad_mcp.utils.path_validator import validate_kicad_file
from mckicad.utils.advanced_drc import RuleSeverity, RuleType, create_drc_manager
from mckicad.utils.path_validator import validate_kicad_file
def register_advanced_drc_tools(mcp: FastMCP) -> None:

View File

@ -9,10 +9,10 @@ from typing import Any
from fastmcp import FastMCP
from kicad_mcp.utils.component_utils import ComponentType, get_component_type
from kicad_mcp.utils.file_utils import get_project_files
from kicad_mcp.utils.netlist_parser import parse_netlist_file
from kicad_mcp.utils.pattern_recognition import analyze_circuit_patterns
from mckicad.utils.component_utils import ComponentType, get_component_type
from mckicad.utils.file_utils import get_project_files
from mckicad.utils.netlist_parser import parse_netlist_file
from mckicad.utils.pattern_recognition import analyze_circuit_patterns
def register_ai_tools(mcp: FastMCP) -> None:

View File

@ -9,8 +9,8 @@ from typing import Any
from fastmcp import FastMCP
from kicad_mcp.utils.file_utils import get_project_files
from kicad_mcp.utils.ipc_client import check_kicad_availability, kicad_ipc_session
from mckicad.utils.file_utils import get_project_files
from mckicad.utils.ipc_client import check_kicad_availability, kicad_ipc_session
def register_analysis_tools(mcp: FastMCP) -> None:

View File

@ -10,7 +10,7 @@ from typing import Any
from fastmcp import FastMCP
import pandas as pd
from kicad_mcp.utils.file_utils import get_project_files
from mckicad.utils.file_utils import get_project_files
def register_bom_tools(mcp: FastMCP) -> None:
@ -644,7 +644,7 @@ async def export_bom_with_cli(
# Define the command based on operating system
if system == "Darwin": # macOS
from kicad_mcp.config import KICAD_APP_PATH
from mckicad.config import KICAD_APP_PATH
# Path to KiCad command-line tools on macOS
kicad_cli = os.path.join(KICAD_APP_PATH, "Contents/MacOS/kicad-cli")
@ -660,7 +660,7 @@ async def export_bom_with_cli(
cmd = [kicad_cli, "sch", "export", "bom", "--output", output_file, schematic_file]
elif system == "Windows":
from kicad_mcp.config import KICAD_APP_PATH
from mckicad.config import KICAD_APP_PATH
# Path to KiCad command-line tools on Windows
kicad_cli = os.path.join(KICAD_APP_PATH, "bin", "kicad-cli.exe")

View File

@ -10,7 +10,7 @@ from typing import Any
from mcp.server.fastmcp import Context
from kicad_mcp.config import system
from mckicad.config import system
async def run_drc_via_cli(pcb_file: str) -> dict[str, Any]:

View File

@ -10,9 +10,9 @@ from typing import Any
from fastmcp import FastMCP
# Import implementations
from kicad_mcp.tools.drc_impl.cli_drc import run_drc_via_cli
from kicad_mcp.utils.drc_history import compare_with_previous, get_drc_history, save_drc_result
from kicad_mcp.utils.file_utils import get_project_files
from mckicad.tools.drc_impl.cli_drc import run_drc_via_cli
from mckicad.utils.drc_history import compare_with_previous, get_drc_history, save_drc_result
from mckicad.utils.file_utils import get_project_files
def register_drc_tools(mcp: FastMCP) -> None:
@ -96,7 +96,7 @@ def register_drc_tools(mcp: FastMCP) -> None:
print("Using kicad-cli for DRC")
# Use synchronous DRC check
try:
from kicad_mcp.tools.drc_impl.cli_drc import run_drc_via_cli_sync
from mckicad.tools.drc_impl.cli_drc import run_drc_via_cli_sync
drc_results = run_drc_via_cli_sync(pcb_file)
except ImportError:
# Fallback - call the async version but handle it differently

View File

@ -10,8 +10,8 @@ import subprocess
from fastmcp import FastMCP
from fastmcp.utilities.types import Image
from kicad_mcp.config import KICAD_APP_PATH, system
from kicad_mcp.utils.file_utils import get_project_files
from mckicad.config import KICAD_APP_PATH, system
from mckicad.utils.file_utils import get_project_files
def register_export_tools(mcp: FastMCP) -> None:

View File

@ -9,8 +9,8 @@ from typing import Any
from fastmcp import FastMCP
from kicad_mcp.utils.layer_stackup import create_stackup_analyzer
from kicad_mcp.utils.path_validator import validate_kicad_file
from mckicad.utils.layer_stackup import create_stackup_analyzer
from mckicad.utils.path_validator import validate_kicad_file
def register_layer_tools(mcp: FastMCP) -> None:

View File

@ -10,12 +10,12 @@ from typing import Any
from fastmcp import FastMCP
from kicad_mcp.utils.model3d_analyzer import (
from mckicad.utils.model3d_analyzer import (
Model3DAnalyzer,
analyze_pcb_3d_models,
get_mechanical_constraints,
)
from kicad_mcp.utils.path_validator import validate_kicad_file
from mckicad.utils.path_validator import validate_kicad_file
def register_model3d_tools(mcp: FastMCP) -> None:

View File

@ -7,8 +7,8 @@ from typing import Any
from fastmcp import FastMCP
from kicad_mcp.utils.file_utils import get_project_files
from kicad_mcp.utils.netlist_parser import analyze_netlist, extract_netlist
from mckicad.utils.file_utils import get_project_files
from mckicad.utils.netlist_parser import analyze_netlist, extract_netlist
def register_netlist_tools(mcp: FastMCP) -> None:

View File

@ -7,9 +7,9 @@ from typing import Any
from fastmcp import FastMCP
from kicad_mcp.utils.file_utils import get_project_files
from kicad_mcp.utils.netlist_parser import analyze_netlist, extract_netlist
from kicad_mcp.utils.pattern_recognition import (
from mckicad.utils.file_utils import get_project_files
from mckicad.utils.netlist_parser import analyze_netlist, extract_netlist
from mckicad.utils.pattern_recognition import (
identify_amplifiers,
identify_digital_interfaces,
identify_filters,

View File

@ -13,9 +13,9 @@ from typing import Any
from fastmcp import FastMCP
from kicad_mcp.utils.file_utils import get_project_files
from kicad_mcp.utils.freerouting_engine import FreeRoutingEngine
from kicad_mcp.utils.ipc_client import check_kicad_availability
from mckicad.utils.file_utils import get_project_files
from mckicad.utils.freerouting_engine import FreeRoutingEngine
from mckicad.utils.ipc_client import check_kicad_availability
logger = logging.getLogger(__name__)

View File

@ -8,8 +8,8 @@ from typing import Any
from fastmcp import FastMCP
from kicad_mcp.utils.file_utils import get_project_files, load_project_json
from kicad_mcp.utils.kicad_utils import find_kicad_projects, open_kicad_project
from mckicad.utils.file_utils import get_project_files, load_project_json
from mckicad.utils.kicad_utils import find_kicad_projects, open_kicad_project
# Get PID for logging
# _PID = os.getpid()

View File

@ -10,9 +10,9 @@ from typing import Any
from fastmcp import FastMCP
from kicad_mcp.utils.file_utils import get_project_files
from kicad_mcp.utils.freerouting_engine import FreeRoutingEngine, check_routing_prerequisites
from kicad_mcp.utils.ipc_client import (
from mckicad.utils.file_utils import get_project_files
from mckicad.utils.freerouting_engine import FreeRoutingEngine, check_routing_prerequisites
from mckicad.utils.ipc_client import (
check_kicad_availability,
kicad_ipc_session,
)

View File

@ -10,7 +10,7 @@ from typing import Any
from fastmcp import FastMCP
from kicad_mcp.utils.symbol_library import create_symbol_analyzer
from mckicad.utils.symbol_library import create_symbol_analyzer
def register_symbol_tools(mcp: FastMCP) -> None:

View File

@ -11,8 +11,8 @@ from typing import Any
from fastmcp import Context, FastMCP
from kicad_mcp.utils.boundary_validator import BoundaryValidator
from kicad_mcp.utils.file_utils import get_project_files
from mckicad.utils.boundary_validator import BoundaryValidator
from mckicad.utils.file_utils import get_project_files
async def validate_project_boundaries(project_path: str = None) -> dict[str, Any]:

View File

@ -10,8 +10,8 @@ from enum import Enum
import json
from typing import Any
from kicad_mcp.utils.component_layout import ComponentLayoutManager, SchematicBounds
from kicad_mcp.utils.coordinate_converter import CoordinateConverter, validate_position
from mckicad.utils.component_layout import ComponentLayoutManager, SchematicBounds
from mckicad.utils.coordinate_converter import CoordinateConverter, validate_position
class ValidationSeverity(Enum):

View File

@ -15,11 +15,11 @@ from typing import Any
if platform.system() == "Windows":
# Windows: Use APPDATA or LocalAppData
DRC_HISTORY_DIR = os.path.join(
os.environ.get("APPDATA", os.path.expanduser("~")), "kicad_mcp", "drc_history"
os.environ.get("APPDATA", os.path.expanduser("~")), "mckicad", "drc_history"
)
else:
# macOS/Linux: Use ~/.kicad_mcp/drc_history
DRC_HISTORY_DIR = os.path.expanduser("~/.kicad_mcp/drc_history")
# macOS/Linux: Use ~/.mckicad/drc_history
DRC_HISTORY_DIR = os.path.expanduser("~/.mckicad/drc_history")
def ensure_history_dir() -> None:

View File

@ -6,7 +6,7 @@ import json
import os
from typing import Any
from kicad_mcp.utils.kicad_utils import get_project_name_from_path
from mckicad.utils.kicad_utils import get_project_name_from_path
def get_project_files(project_path: str) -> dict[str, str]:
@ -18,7 +18,7 @@ def get_project_files(project_path: str) -> dict[str, str]:
Returns:
Dictionary mapping file types to file paths
"""
from kicad_mcp.config import DATA_EXTENSIONS, KICAD_EXTENSIONS
from mckicad.config import DATA_EXTENSIONS, KICAD_EXTENSIONS
project_dir = os.path.dirname(project_path)
project_name = get_project_name_from_path(project_path)

View File

@ -19,7 +19,7 @@ from typing import Any
from kipy.board_types import BoardLayer
from kicad_mcp.utils.ipc_client import kicad_ipc_session
from mckicad.utils.ipc_client import kicad_ipc_session
logger = logging.getLogger(__name__)
@ -650,7 +650,7 @@ def check_routing_prerequisites() -> dict[str, Any]:
# Check KiCad IPC API
try:
from kicad_mcp.utils.ipc_client import check_kicad_availability
from mckicad.utils.ipc_client import check_kicad_availability
kicad_status = check_kicad_availability()
status["components"]["kicad_ipc"] = kicad_status
except Exception as e:

View File

@ -6,7 +6,7 @@ import os
import shutil
import subprocess
from kicad_mcp.config import system
from mckicad.config import system
def check_for_cli_api() -> bool:

View File

@ -8,7 +8,7 @@ import subprocess
import sys # Add sys import
from typing import Any
from kicad_mcp.config import (
from mckicad.config import (
ADDITIONAL_SEARCH_PATHS,
KICAD_APP_PATH,
KICAD_EXTENSIONS,

View File

@ -8,7 +8,7 @@ and ensure file operations are restricted to safe directories.
import os
import pathlib
from kicad_mcp.config import KICAD_EXTENSIONS
from mckicad.config import KICAD_EXTENSIONS
class PathValidationError(Exception):

View File

@ -5,7 +5,7 @@ Circuit pattern recognition functions for KiCad schematics.
import re
from typing import Any
from kicad_mcp.utils.component_utils import (
from mckicad.utils.component_utils import (
extract_frequency_from_value,
extract_voltage_from_regulator,
)
@ -1039,7 +1039,7 @@ def analyze_circuit_patterns(schematic_file: str) -> dict[str, Any]:
Dictionary of identified patterns
"""
try:
from kicad_mcp.utils.netlist_parser import parse_netlist_file
from mckicad.utils.netlist_parser import parse_netlist_file
# Parse netlist to get components and nets
netlist_data = parse_netlist_file(schematic_file)

View File

@ -189,7 +189,7 @@ class SecureSubprocessRunner:
raise SecureSubprocessError(f"Command failed: {e}") from e
def create_temp_file(
self, suffix: str = "", prefix: str = "kicad_mcp_", content: str | None = None
self, suffix: str = "", prefix: str = "mckicad_", content: str | None = None
) -> str:
"""
Create a temporary file within validated directories.
@ -288,7 +288,7 @@ async def run_kicad_command_async(
def create_temp_file(
suffix: str = "", prefix: str = "kicad_mcp_", content: str | None = None
suffix: str = "", prefix: str = "mckicad_", content: str | None = None
) -> str:
"""Convenience function to create temporary file."""
return get_subprocess_runner().create_temp_file(suffix, prefix, content)

View File

@ -3,7 +3,7 @@ requires = ["hatchling>=1.28.0"]
build-backend = "hatchling.build"
[project]
name = "kicad-mcp"
name = "mckicad"
version = "0.1.0"
description = "Model Context Protocol (MCP) server for KiCad electronic design automation (EDA) files"
readme = "README.md"
@ -50,12 +50,12 @@ dependencies = [
]
[project.urls]
"Homepage" = "https://github.com/lamaalrajih/kicad-mcp"
"Bug Tracker" = "https://github.com/lamaalrajih/kicad-mcp/issues"
"Documentation" = "https://github.com/lamaalrajih/kicad-mcp#readme"
"Homepage" = "https://github.com/lamaalrajih/mckicad"
"Bug Tracker" = "https://github.com/lamaalrajih/mckicad/issues"
"Documentation" = "https://github.com/lamaalrajih/mckicad#readme"
[project.scripts]
kicad-mcp = "kicad_mcp.server:main"
mckicad = "mckicad.server:main"
[dependency-groups]
dev = [
@ -119,12 +119,12 @@ unfixable = [
"D103", # Missing docstring in public function
"SLF001", # Private member accessed
]
"kicad_mcp/config.py" = [
"mckicad/config.py" = [
"E501", # Long lines in config are ok
]
[tool.ruff.lint.isort]
known-first-party = ["kicad_mcp"]
known-first-party = ["mckicad"]
force-sort-within-sections = true
[tool.ruff.format]
@ -164,7 +164,7 @@ minversion = "7.0"
addopts = [
"--strict-markers",
"--strict-config",
"--cov=kicad_mcp",
"--cov=mckicad",
"--cov-report=term-missing",
"--cov-report=html:htmlcov",
"--cov-report=xml",
@ -191,11 +191,11 @@ filterwarnings = [
]
[tool.coverage.run]
source = ["kicad_mcp"]
source = ["mckicad"]
branch = true
omit = [
"tests/*",
"kicad_mcp/__init__.py",
"mckicad/__init__.py",
"*/migrations/*",
"*/venv/*",
"*/.venv/*",
@ -227,8 +227,8 @@ skips = ["*_test.py", "*/test_*.py"]
[tool.setuptools.packages.find]
where = ["."]
include = ["kicad_mcp*"]
include = ["mckicad*"]
exclude = ["tests*", "docs*"]
[tool.setuptools.package-data]
"kicad_mcp" = ["prompts/*.txt", "resources/**/*.json"]
"mckicad" = ["prompts/*.txt", "resources/**/*.json"]

View File

@ -36,15 +36,15 @@ def main():
exit_code = 0
# Run linting
exit_code |= run_command(["uv", "run", "ruff", "check", "kicad_mcp/", "tests/"], "Lint check")
exit_code |= run_command(["uv", "run", "ruff", "check", "mckicad/", "tests/"], "Lint check")
# Run formatting check
exit_code |= run_command(
["uv", "run", "ruff", "format", "--check", "kicad_mcp/", "tests/"], "Format check"
["uv", "run", "ruff", "format", "--check", "mckicad/", "tests/"], "Format check"
)
# Run type checking
exit_code |= run_command(["uv", "run", "mypy", "kicad_mcp/"], "Type check")
exit_code |= run_command(["uv", "run", "mypy", "mckicad/"], "Type check")
# Run tests
exit_code |= run_command(["uv", "run", "python", "-m", "pytest", "tests/", "-v"], "Unit tests")

View File

@ -8,13 +8,13 @@ import sys
import time
from pathlib import Path
# Add the kicad_mcp module to path
# Add the mckicad module to path
sys.path.insert(0, str(Path(__file__).parent))
from kicad_mcp.utils.ipc_client import KiCadIPCClient, check_kicad_availability
from kicad_mcp.utils.file_utils import get_project_files
from kicad_mcp.utils.netlist_parser import extract_netlist, analyze_netlist
from kicad_mcp.tools.validation_tools import validate_project_boundaries
from mckicad.utils.ipc_client import KiCadIPCClient, check_kicad_availability
from mckicad.utils.file_utils import get_project_files
from mckicad.utils.netlist_parser import extract_netlist, analyze_netlist
from mckicad.tools.validation_tools import validate_project_boundaries
# Our new demo project
PROJECT_PATH = "/home/rpm/claude/Demo_PCB_Project/Smart_Sensor_Board.kicad_pro"

View File

@ -10,13 +10,13 @@ from basic project analysis to real-time board manipulation and routing.
import sys
from pathlib import Path
# Add the kicad_mcp module to path
# Add the mckicad module to path
sys.path.insert(0, str(Path(__file__).parent))
from kicad_mcp.utils.ipc_client import KiCadIPCClient
from kicad_mcp.utils.freerouting_engine import check_routing_prerequisites
from kicad_mcp.utils.file_utils import get_project_files
from kicad_mcp.utils.netlist_parser import extract_netlist, analyze_netlist
from mckicad.utils.ipc_client import KiCadIPCClient
from mckicad.utils.freerouting_engine import check_routing_prerequisites
from mckicad.utils.file_utils import get_project_files
from mckicad.utils.netlist_parser import extract_netlist, analyze_netlist
# Test project path
PROJECT_PATH = "/home/rpm/claude/MLX90640-Thermal-Camera/PCB/Thermal_Camera.kicad_pro"

View File

@ -9,13 +9,13 @@ This demonstrates the complete, fully-functional EDA automation platform.
import sys
from pathlib import Path
# Add the kicad_mcp module to path
# Add the mckicad module to path
sys.path.insert(0, str(Path(__file__).parent))
from kicad_mcp.utils.ipc_client import KiCadIPCClient
from kicad_mcp.utils.freerouting_engine import check_routing_prerequisites
from kicad_mcp.utils.file_utils import get_project_files
from kicad_mcp.utils.netlist_parser import extract_netlist, analyze_netlist
from mckicad.utils.ipc_client import KiCadIPCClient
from mckicad.utils.freerouting_engine import check_routing_prerequisites
from mckicad.utils.file_utils import get_project_files
from mckicad.utils.netlist_parser import extract_netlist, analyze_netlist
# Test project path
PROJECT_PATH = "/home/rpm/claude/MLX90640-Thermal-Camera/PCB/Thermal_Camera.kicad_pro"

View File

@ -12,14 +12,14 @@ import time
from pathlib import Path
from datetime import datetime
# Add the kicad_mcp module to path
# Add the mckicad module to path
sys.path.insert(0, str(Path(__file__).parent))
from kicad_mcp.utils.ipc_client import KiCadIPCClient
from kicad_mcp.utils.freerouting_engine import check_routing_prerequisites
from kicad_mcp.utils.file_utils import get_project_files
from kicad_mcp.utils.netlist_parser import extract_netlist, analyze_netlist
from kicad_mcp.server import create_server
from mckicad.utils.ipc_client import KiCadIPCClient
from mckicad.utils.freerouting_engine import check_routing_prerequisites
from mckicad.utils.file_utils import get_project_files
from mckicad.utils.netlist_parser import extract_netlist, analyze_netlist
from mckicad.server import create_server
# Test project
PROJECT_PATH = "/home/rpm/claude/MLX90640-Thermal-Camera/PCB/Thermal_Camera.kicad_pro"

View File

@ -10,11 +10,11 @@ import os
import tempfile
import subprocess
# Add the kicad_mcp module to path
# Add the mckicad module to path
sys.path.insert(0, str(Path(__file__).parent))
from kicad_mcp.utils.ipc_client import KiCadIPCClient
from kicad_mcp.utils.freerouting_engine import FreeRoutingEngine, check_routing_prerequisites
from mckicad.utils.ipc_client import KiCadIPCClient
from mckicad.utils.freerouting_engine import FreeRoutingEngine, check_routing_prerequisites
def test_routing_prerequisites():
"""Test routing prerequisites and components."""

View File

@ -10,7 +10,7 @@ import os
import tempfile
import subprocess
# Add the kicad_mcp module to path
# Add the mckicad module to path
sys.path.insert(0, str(Path(__file__).parent))
PROJECT_PATH = "/home/rpm/claude/MLX90640-Thermal-Camera/PCB/Thermal_Camera.kicad_pro"

View File

@ -12,14 +12,14 @@ import logging
import sys
from pathlib import Path
# Add the kicad_mcp module to path
# Add the mckicad module to path
sys.path.insert(0, str(Path(__file__).parent))
from kicad_mcp.utils.freerouting_engine import check_routing_prerequisites
from kicad_mcp.utils.ipc_client import check_kicad_availability
from kicad_mcp.tools.analysis_tools import register_analysis_tools
from kicad_mcp.tools.routing_tools import register_routing_tools
from kicad_mcp.tools.ai_tools import register_ai_tools
from mckicad.utils.freerouting_engine import check_routing_prerequisites
from mckicad.utils.ipc_client import check_kicad_availability
from mckicad.tools.analysis_tools import register_analysis_tools
from mckicad.tools.routing_tools import register_routing_tools
from mckicad.tools.ai_tools import register_ai_tools
# Set up logging
logging.basicConfig(level=logging.INFO)
@ -88,7 +88,7 @@ def test_project_validation():
logger.info("Testing project validation...")
try:
from kicad_mcp.utils.file_utils import get_project_files
from mckicad.utils.file_utils import get_project_files
if not Path(PROJECT_PATH).exists():
logger.error(f"Test project not found: {PROJECT_PATH}")

View File

@ -8,11 +8,11 @@ import sys
import json
from pathlib import Path
# Add the kicad_mcp module to path
# Add the mckicad module to path
sys.path.insert(0, str(Path(__file__).parent))
# Import our server and tools
from kicad_mcp.server import create_server
from mckicad.server import create_server
def test_server_initialization():
"""Test MCP server initialization and tool registration."""
@ -40,13 +40,13 @@ def test_tool_registration():
try:
# Import and test tool registration functions
from kicad_mcp.tools.analysis_tools import register_analysis_tools
from kicad_mcp.tools.project_tools import register_project_tools
from kicad_mcp.tools.drc_tools import register_drc_tools
from kicad_mcp.tools.bom_tools import register_bom_tools
from kicad_mcp.tools.netlist_tools import register_netlist_tools
from kicad_mcp.tools.pattern_tools import register_pattern_tools
from kicad_mcp.tools.export_tools import register_export_tools
from mckicad.tools.analysis_tools import register_analysis_tools
from mckicad.tools.project_tools import register_project_tools
from mckicad.tools.drc_tools import register_drc_tools
from mckicad.tools.bom_tools import register_bom_tools
from mckicad.tools.netlist_tools import register_netlist_tools
from mckicad.tools.pattern_tools import register_pattern_tools
from mckicad.tools.export_tools import register_export_tools
# Test that registration functions exist
registration_functions = [
@ -80,11 +80,11 @@ def test_resource_registration():
try:
# Import resource registration functions
from kicad_mcp.resources.projects import register_project_resources
from kicad_mcp.resources.files import register_file_resources
from kicad_mcp.resources.drc_resources import register_drc_resources
from kicad_mcp.resources.bom_resources import register_bom_resources
from kicad_mcp.resources.netlist_resources import register_netlist_resources
from mckicad.resources.projects import register_project_resources
from mckicad.resources.files import register_file_resources
from mckicad.resources.drc_resources import register_drc_resources
from mckicad.resources.bom_resources import register_bom_resources
from mckicad.resources.netlist_resources import register_netlist_resources
resource_functions = [
("project_resources", register_project_resources),
@ -115,10 +115,10 @@ def test_prompt_registration():
try:
# Import prompt registration functions
from kicad_mcp.prompts.templates import register_prompts
from kicad_mcp.prompts.drc_prompt import register_drc_prompts
from kicad_mcp.prompts.bom_prompts import register_bom_prompts
from kicad_mcp.prompts.pattern_prompts import register_pattern_prompts
from mckicad.prompts.templates import register_prompts
from mckicad.prompts.drc_prompt import register_drc_prompts
from mckicad.prompts.bom_prompts import register_bom_prompts
from mckicad.prompts.pattern_prompts import register_pattern_prompts
prompt_functions = [
("templates", register_prompts),
@ -148,10 +148,10 @@ def test_core_functionality():
try:
# Test key utility imports
from kicad_mcp.utils.file_utils import get_project_files
from kicad_mcp.utils.ipc_client import KiCadIPCClient, check_kicad_availability
from kicad_mcp.utils.freerouting_engine import check_routing_prerequisites
from kicad_mcp.utils.netlist_parser import extract_netlist
from mckicad.utils.file_utils import get_project_files
from mckicad.utils.ipc_client import KiCadIPCClient, check_kicad_availability
from mckicad.utils.freerouting_engine import check_routing_prerequisites
from mckicad.utils.netlist_parser import extract_netlist
print(f"📦 Core Utilities Available:")
print(f" ✅ file_utils: Project file management")
@ -189,9 +189,9 @@ def test_server_completeness():
try:
# Check that the main server creation works
from kicad_mcp.server import create_server
from kicad_mcp.config import KICAD_CLI_TIMEOUT
from kicad_mcp.context import KiCadAppContext
from mckicad.server import create_server
from mckicad.config import KICAD_CLI_TIMEOUT
from mckicad.context import KiCadAppContext
print(f"📊 Server Components:")
print(f" ✅ create_server(): Main entry point")
@ -199,7 +199,7 @@ def test_server_completeness():
print(f" ✅ Context management: {KiCadAppContext.__name__}")
# Verify key constants and configurations
from kicad_mcp import config
from mckicad import config
config_items = [
'KICAD_CLI_TIMEOUT', 'DEFAULT_KICAD_PATHS',

View File

@ -1,5 +1,5 @@
"""
Tests for the kicad_mcp.config module.
Tests for the mckicad.config module.
"""
import os
import platform
@ -11,7 +11,7 @@ class TestConfigModule:
def test_system_detection(self):
"""Test that system is properly detected."""
from kicad_mcp.config import system
from mckicad.config import system
assert system in ['Darwin', 'Windows', 'Linux'] or isinstance(system, str)
assert system == platform.system()
@ -22,10 +22,10 @@ class TestConfigModule:
# Need to reload the config module after patching
import importlib
import kicad_mcp.config
importlib.reload(kicad_mcp.config)
import mckicad.config
importlib.reload(mckicad.config)
from kicad_mcp.config import KICAD_APP_PATH, KICAD_PYTHON_BASE, KICAD_USER_DIR
from mckicad.config import KICAD_APP_PATH, KICAD_PYTHON_BASE, KICAD_USER_DIR
assert os.path.expanduser("~/Documents/KiCad") == KICAD_USER_DIR
assert KICAD_APP_PATH == "/Applications/KiCad/KiCad.app"
@ -36,10 +36,10 @@ class TestConfigModule:
with patch('platform.system', return_value='Windows'):
import importlib
import kicad_mcp.config
importlib.reload(kicad_mcp.config)
import mckicad.config
importlib.reload(mckicad.config)
from kicad_mcp.config import KICAD_APP_PATH, KICAD_PYTHON_BASE, KICAD_USER_DIR
from mckicad.config import KICAD_APP_PATH, KICAD_PYTHON_BASE, KICAD_USER_DIR
assert os.path.expanduser("~/Documents/KiCad") == KICAD_USER_DIR
assert KICAD_APP_PATH == r"C:\Program Files\KiCad"
@ -50,10 +50,10 @@ class TestConfigModule:
with patch('platform.system', return_value='Linux'):
import importlib
import kicad_mcp.config
importlib.reload(kicad_mcp.config)
import mckicad.config
importlib.reload(mckicad.config)
from kicad_mcp.config import KICAD_APP_PATH, KICAD_PYTHON_BASE, KICAD_USER_DIR
from mckicad.config import KICAD_APP_PATH, KICAD_PYTHON_BASE, KICAD_USER_DIR
assert os.path.expanduser("~/KiCad") == KICAD_USER_DIR
assert KICAD_APP_PATH == "/usr/share/kicad"
@ -64,17 +64,17 @@ class TestConfigModule:
with patch('platform.system', return_value='FreeBSD'):
import importlib
import kicad_mcp.config
importlib.reload(kicad_mcp.config)
import mckicad.config
importlib.reload(mckicad.config)
from kicad_mcp.config import KICAD_APP_PATH, KICAD_USER_DIR
from mckicad.config import KICAD_APP_PATH, KICAD_USER_DIR
assert os.path.expanduser("~/Documents/KiCad") == KICAD_USER_DIR
assert KICAD_APP_PATH == "/Applications/KiCad/KiCad.app"
def test_kicad_extensions(self):
"""Test KiCad file extension mappings."""
from kicad_mcp.config import KICAD_EXTENSIONS
from mckicad.config import KICAD_EXTENSIONS
expected_keys = ["project", "pcb", "schematic", "design_rules",
"worksheet", "footprint", "netlist", "kibot_config"]
@ -86,7 +86,7 @@ class TestConfigModule:
def test_data_extensions(self):
"""Test data file extensions list."""
from kicad_mcp.config import DATA_EXTENSIONS
from mckicad.config import DATA_EXTENSIONS
assert isinstance(DATA_EXTENSIONS, list)
assert len(DATA_EXTENSIONS) > 0
@ -97,7 +97,7 @@ class TestConfigModule:
def test_circuit_defaults(self):
"""Test circuit default parameters."""
from kicad_mcp.config import CIRCUIT_DEFAULTS
from mckicad.config import CIRCUIT_DEFAULTS
required_keys = ["grid_spacing", "component_spacing", "wire_width",
"text_size", "pin_length"]
@ -112,7 +112,7 @@ class TestConfigModule:
def test_common_libraries_structure(self):
"""Test common libraries configuration structure."""
from kicad_mcp.config import COMMON_LIBRARIES
from mckicad.config import COMMON_LIBRARIES
expected_categories = ["basic", "power", "connectors"]
@ -128,7 +128,7 @@ class TestConfigModule:
def test_default_footprints_structure(self):
"""Test default footprints configuration structure."""
from kicad_mcp.config import DEFAULT_FOOTPRINTS
from mckicad.config import DEFAULT_FOOTPRINTS
# Test that at least some common components are present
common_components = ["R", "C", "LED", "D"]
@ -145,7 +145,7 @@ class TestConfigModule:
def test_timeout_constants(self):
"""Test timeout constants are reasonable values."""
from kicad_mcp.config import TIMEOUT_CONSTANTS
from mckicad.config import TIMEOUT_CONSTANTS
required_keys = ["kicad_cli_version_check", "kicad_cli_export",
"application_open", "subprocess_default"]
@ -158,7 +158,7 @@ class TestConfigModule:
def test_progress_constants(self):
"""Test progress constants are valid percentages."""
from kicad_mcp.config import PROGRESS_CONSTANTS
from mckicad.config import PROGRESS_CONSTANTS
required_keys = ["start", "detection", "setup", "processing",
"finishing", "validation", "complete"]
@ -171,7 +171,7 @@ class TestConfigModule:
def test_display_constants(self):
"""Test display constants are reasonable values."""
from kicad_mcp.config import DISPLAY_CONSTANTS
from mckicad.config import DISPLAY_CONSTANTS
assert "bom_preview_limit" in DISPLAY_CONSTANTS
limit = DISPLAY_CONSTANTS["bom_preview_limit"]
@ -183,11 +183,11 @@ class TestConfigModule:
with patch.dict(os.environ, {"KICAD_SEARCH_PATHS": ""}):
import importlib
import kicad_mcp.config
importlib.reload(kicad_mcp.config)
import mckicad.config
importlib.reload(mckicad.config)
# Should still have default locations if they exist
from kicad_mcp.config import ADDITIONAL_SEARCH_PATHS
from mckicad.config import ADDITIONAL_SEARCH_PATHS
assert isinstance(ADDITIONAL_SEARCH_PATHS, list)
def test_nonexistent_search_paths_ignored(self):
@ -196,10 +196,10 @@ class TestConfigModule:
patch('os.path.exists', return_value=False):
import importlib
import kicad_mcp.config
importlib.reload(kicad_mcp.config)
import mckicad.config
importlib.reload(mckicad.config)
from kicad_mcp.config import ADDITIONAL_SEARCH_PATHS
from mckicad.config import ADDITIONAL_SEARCH_PATHS
# Should not contain the nonexistent paths
assert "/nonexistent/path1" not in ADDITIONAL_SEARCH_PATHS
@ -213,10 +213,10 @@ class TestConfigModule:
import importlib
import kicad_mcp.config
importlib.reload(kicad_mcp.config)
import mckicad.config
importlib.reload(mckicad.config)
from kicad_mcp.config import ADDITIONAL_SEARCH_PATHS
from mckicad.config import ADDITIONAL_SEARCH_PATHS
# Should contain expanded paths
assert "/home/user/test_path1" in ADDITIONAL_SEARCH_PATHS
@ -224,7 +224,7 @@ class TestConfigModule:
def test_default_project_locations_expanded(self):
"""Test that default project locations are properly expanded."""
from kicad_mcp.config import DEFAULT_PROJECT_LOCATIONS
from mckicad.config import DEFAULT_PROJECT_LOCATIONS
assert isinstance(DEFAULT_PROJECT_LOCATIONS, list)
assert len(DEFAULT_PROJECT_LOCATIONS) > 0

View File

@ -1,11 +1,11 @@
"""
Tests for the kicad_mcp.context module.
Tests for the mckicad.context module.
"""
from unittest.mock import Mock, patch
import pytest
from kicad_mcp.context import KiCadAppContext, kicad_lifespan
from mckicad.context import KiCadAppContext, kicad_lifespan
class TestKiCadAppContext:
@ -62,7 +62,7 @@ class TestKiCadLifespan:
@pytest.mark.asyncio
async def test_lifespan_basic_flow(self, mock_server):
"""Test basic lifespan flow with successful initialization and cleanup."""
with patch('kicad_mcp.context.logging') as mock_logging:
with patch('mckicad.context.logging') as mock_logging:
async with kicad_lifespan(mock_server, kicad_modules_available=True) as context:
# Check context is properly initialized
assert isinstance(context, KiCadAppContext)
@ -103,7 +103,7 @@ class TestKiCadLifespan:
@pytest.mark.asyncio
async def test_lifespan_cache_cleanup(self, mock_server):
"""Test that cache is properly cleared on shutdown."""
with patch('kicad_mcp.context.logging') as mock_logging:
with patch('mckicad.context.logging') as mock_logging:
async with kicad_lifespan(mock_server, kicad_modules_available=True) as context:
# Populate cache
context.cache["test1"] = "value1"
@ -116,7 +116,7 @@ class TestKiCadLifespan:
@pytest.mark.asyncio
async def test_lifespan_exception_handling(self, mock_server):
"""Test that cleanup happens even if an exception occurs."""
with patch('kicad_mcp.context.logging') as mock_logging:
with patch('mckicad.context.logging') as mock_logging:
with pytest.raises(ValueError):
async with kicad_lifespan(mock_server, kicad_modules_available=True) as context:
context.cache["test"] = "value"
@ -130,8 +130,8 @@ class TestKiCadLifespan:
@pytest.mark.skip(reason="Mock setup complexity - temp dir cleanup not critical")
async def test_lifespan_temp_dir_cleanup(self, mock_server):
"""Test temporary directory cleanup functionality."""
with patch('kicad_mcp.context.logging') as mock_logging, \
patch('kicad_mcp.context.shutil') as mock_shutil:
with patch('mckicad.context.logging') as mock_logging, \
patch('mckicad.context.shutil') as mock_shutil:
async with kicad_lifespan(mock_server, kicad_modules_available=True) as context:
# The current implementation has an empty created_temp_dirs list
@ -145,8 +145,8 @@ class TestKiCadLifespan:
async def test_lifespan_temp_dir_cleanup_error_handling(self, mock_server):
"""Test error handling in temp directory cleanup."""
# Mock the created_temp_dirs to have some directories for testing
with patch('kicad_mcp.context.logging') as mock_logging, \
patch('kicad_mcp.context.shutil') as mock_shutil:
with patch('mckicad.context.logging') as mock_logging, \
patch('mckicad.context.shutil') as mock_shutil:
# Patch the created_temp_dirs list in the function scope
original_lifespan = kicad_lifespan
@ -183,7 +183,7 @@ class TestKiCadLifespan:
@pytest.mark.asyncio
async def test_lifespan_logging_messages(self, mock_server):
"""Test specific logging messages are called correctly."""
with patch('kicad_mcp.context.logging') as mock_logging:
with patch('mckicad.context.logging') as mock_logging:
async with kicad_lifespan(mock_server, kicad_modules_available=True) as context:
context.cache["test"] = "data"
@ -203,7 +203,7 @@ class TestKiCadLifespan:
@pytest.mark.asyncio
async def test_lifespan_empty_cache_no_cleanup_log(self, mock_server):
"""Test that empty cache doesn't log cleanup message."""
with patch('kicad_mcp.context.logging') as mock_logging:
with patch('mckicad.context.logging') as mock_logging:
async with kicad_lifespan(mock_server, kicad_modules_available=False) as context:
# Don't add anything to cache
pass

View File

@ -1,5 +1,5 @@
"""
Tests for the kicad_mcp.server module.
Tests for the mckicad.server module.
"""
import logging
import signal
@ -7,7 +7,7 @@ from unittest.mock import Mock, call, patch
import pytest
from kicad_mcp.server import (
from mckicad.server import (
add_cleanup_handler,
create_server,
main,
@ -23,7 +23,7 @@ class TestCleanupHandlers:
def setup_method(self):
"""Reset cleanup handlers before each test."""
from kicad_mcp.server import cleanup_handlers
from mckicad.server import cleanup_handlers
cleanup_handlers.clear()
def test_add_cleanup_handler(self):
@ -33,7 +33,7 @@ class TestCleanupHandlers:
add_cleanup_handler(dummy_handler)
from kicad_mcp.server import cleanup_handlers
from mckicad.server import cleanup_handlers
assert dummy_handler in cleanup_handlers
def test_add_multiple_cleanup_handlers(self):
@ -47,12 +47,12 @@ class TestCleanupHandlers:
add_cleanup_handler(handler1)
add_cleanup_handler(handler2)
from kicad_mcp.server import cleanup_handlers
from mckicad.server import cleanup_handlers
assert handler1 in cleanup_handlers
assert handler2 in cleanup_handlers
assert len(cleanup_handlers) == 2
@patch('kicad_mcp.server.logging')
@patch('mckicad.server.logging')
def test_run_cleanup_handlers_success(self, mock_logging):
"""Test successful execution of cleanup handlers."""
handler1 = Mock()
@ -69,7 +69,7 @@ class TestCleanupHandlers:
handler2.assert_called_once()
mock_logging.info.assert_any_call("Running cleanup handlers...")
@patch('kicad_mcp.server.logging')
@patch('mckicad.server.logging')
@pytest.mark.skip(reason="Mock handler execution complexity - exception handling works in practice")
def test_run_cleanup_handlers_with_exception(self, mock_logging):
"""Test cleanup handlers with exceptions."""
@ -91,7 +91,7 @@ class TestCleanupHandlers:
# Should still log success for working handler
mock_logging.info.assert_any_call("Cleanup handler working_handler completed successfully")
@patch('kicad_mcp.server.logging')
@patch('mckicad.server.logging')
@pytest.mark.skip(reason="Global state management complexity - double execution prevention works")
def test_run_cleanup_handlers_prevents_double_execution(self, mock_logging):
"""Test that cleanup handlers don't run twice."""
@ -113,17 +113,17 @@ class TestServerShutdown:
def setup_method(self):
"""Reset server instance before each test."""
import kicad_mcp.server
kicad_mcp.server._server_instance = None
import mckicad.server
mckicad.server._server_instance = None
@patch('kicad_mcp.server.logging')
@patch('mckicad.server.logging')
def test_shutdown_server_with_instance(self, mock_logging):
"""Test shutting down server when instance exists."""
import kicad_mcp.server
import mckicad.server
# Set up mock server instance
mock_server = Mock()
kicad_mcp.server._server_instance = mock_server
mckicad.server._server_instance = mock_server
shutdown_server()
@ -131,9 +131,9 @@ class TestServerShutdown:
mock_logging.info.assert_any_call("KiCad MCP server shutdown complete")
# Server instance should be cleared
assert kicad_mcp.server._server_instance is None
assert mckicad.server._server_instance is None
@patch('kicad_mcp.server.logging')
@patch('mckicad.server.logging')
def test_shutdown_server_no_instance(self, mock_logging):
"""Test shutting down server when no instance exists."""
shutdown_server()
@ -145,8 +145,8 @@ class TestServerShutdown:
class TestSignalHandlers:
"""Test signal handler registration."""
@patch('kicad_mcp.server.signal.signal')
@patch('kicad_mcp.server.logging')
@patch('mckicad.server.signal.signal')
@patch('mckicad.server.logging')
def test_register_signal_handlers_success(self, mock_logging, mock_signal):
"""Test successful signal handler registration."""
mock_server = Mock()
@ -163,8 +163,8 @@ class TestSignalHandlers:
mock_logging.info.assert_any_call("Registered handler for signal 2") # SIGINT
mock_logging.info.assert_any_call("Registered handler for signal 15") # SIGTERM
@patch('kicad_mcp.server.signal.signal')
@patch('kicad_mcp.server.logging')
@patch('mckicad.server.signal.signal')
@patch('mckicad.server.logging')
def test_register_signal_handlers_failure(self, mock_logging, mock_signal):
"""Test signal handler registration failure."""
mock_server = Mock()
@ -175,15 +175,15 @@ class TestSignalHandlers:
# Should log errors for failed registrations
mock_logging.error.assert_called()
@patch('kicad_mcp.server.run_cleanup_handlers')
@patch('kicad_mcp.server.shutdown_server')
@patch('kicad_mcp.server.os._exit')
@patch('kicad_mcp.server.logging')
@patch('mckicad.server.run_cleanup_handlers')
@patch('mckicad.server.shutdown_server')
@patch('mckicad.server.os._exit')
@patch('mckicad.server.logging')
def test_signal_handler_execution(self, mock_logging, mock_exit, mock_shutdown, mock_cleanup):
"""Test that signal handler executes cleanup and shutdown."""
mock_server = Mock()
with patch('kicad_mcp.server.signal.signal') as mock_signal:
with patch('mckicad.server.signal.signal') as mock_signal:
register_signal_handlers(mock_server)
# Get the registered handler function
@ -202,11 +202,11 @@ class TestSignalHandlers:
class TestCreateServer:
"""Test server creation and configuration."""
@patch('kicad_mcp.server.logging')
@patch('kicad_mcp.server.FastMCP')
@patch('kicad_mcp.server.register_signal_handlers')
@patch('kicad_mcp.server.atexit.register')
@patch('kicad_mcp.server.add_cleanup_handler')
@patch('mckicad.server.logging')
@patch('mckicad.server.FastMCP')
@patch('mckicad.server.register_signal_handlers')
@patch('mckicad.server.atexit.register')
@patch('mckicad.server.add_cleanup_handler')
def test_create_server_basic(self, mock_add_cleanup, mock_atexit, mock_register_signals, mock_fastmcp, mock_logging):
"""Test basic server creation."""
mock_server_instance = Mock()
@ -227,16 +227,16 @@ class TestCreateServer:
assert server == mock_server_instance
@patch('kicad_mcp.server.logging')
@patch('kicad_mcp.server.FastMCP')
@patch('mckicad.server.logging')
@patch('mckicad.server.FastMCP')
def test_create_server_logging(self, mock_fastmcp, mock_logging):
"""Test server creation logging."""
mock_server_instance = Mock()
mock_fastmcp.return_value = mock_server_instance
with patch('kicad_mcp.server.register_signal_handlers'), \
patch('kicad_mcp.server.atexit.register'), \
patch('kicad_mcp.server.add_cleanup_handler'):
with patch('mckicad.server.register_signal_handlers'), \
patch('mckicad.server.atexit.register'), \
patch('mckicad.server.add_cleanup_handler'):
create_server()
@ -254,9 +254,9 @@ class TestCreateServer:
for expected_call in expected_log_calls:
mock_logging.info.assert_any_call(expected_call)
@patch('kicad_mcp.server.get_temp_dirs')
@patch('kicad_mcp.server.os.path.exists')
@patch('kicad_mcp.server.logging')
@patch('mckicad.server.get_temp_dirs')
@patch('mckicad.server.os.path.exists')
@patch('mckicad.server.logging')
@pytest.mark.skip(reason="Complex mock setup for temp dir cleanup - functionality works in practice")
def test_temp_directory_cleanup_handler(self, mock_logging, mock_exists, mock_get_temp_dirs):
"""Test that temp directory cleanup handler works correctly."""
@ -264,11 +264,11 @@ class TestCreateServer:
mock_get_temp_dirs.return_value = ["/tmp/test1", "/tmp/test2"]
mock_exists.return_value = True
with patch('kicad_mcp.server.FastMCP'), \
patch('kicad_mcp.server.register_signal_handlers'), \
patch('kicad_mcp.server.atexit.register'), \
patch('kicad_mcp.server.add_cleanup_handler') as mock_add_cleanup, \
patch('kicad_mcp.server.shutil.rmtree') as mock_rmtree:
with patch('mckicad.server.FastMCP'), \
patch('mckicad.server.register_signal_handlers'), \
patch('mckicad.server.atexit.register'), \
patch('mckicad.server.add_cleanup_handler') as mock_add_cleanup, \
patch('mckicad.server.shutil.rmtree') as mock_rmtree:
create_server()
@ -291,7 +291,7 @@ class TestCreateServer:
class TestSetupLogging:
"""Test logging configuration."""
@patch('kicad_mcp.server.logging.basicConfig')
@patch('mckicad.server.logging.basicConfig')
def test_setup_logging(self, mock_basic_config):
"""Test logging setup configuration."""
setup_logging()
@ -308,9 +308,9 @@ class TestSetupLogging:
class TestMain:
"""Test main server entry point."""
@patch('kicad_mcp.server.setup_logging')
@patch('kicad_mcp.server.create_server')
@patch('kicad_mcp.server.logging')
@patch('mckicad.server.setup_logging')
@patch('mckicad.server.create_server')
@patch('mckicad.server.logging')
def test_main_successful_run(self, mock_logging, mock_create_server, mock_setup_logging):
"""Test successful main execution."""
mock_server = Mock()
@ -325,9 +325,9 @@ class TestMain:
mock_logging.info.assert_any_call("Starting KiCad MCP server...")
mock_logging.info.assert_any_call("Server shutdown complete")
@patch('kicad_mcp.server.setup_logging')
@patch('kicad_mcp.server.create_server')
@patch('kicad_mcp.server.logging')
@patch('mckicad.server.setup_logging')
@patch('mckicad.server.create_server')
@patch('mckicad.server.logging')
def test_main_keyboard_interrupt(self, mock_logging, mock_create_server, mock_setup_logging):
"""Test main with keyboard interrupt."""
mock_server = Mock()
@ -339,9 +339,9 @@ class TestMain:
mock_logging.info.assert_any_call("Server interrupted by user")
mock_logging.info.assert_any_call("Server shutdown complete")
@patch('kicad_mcp.server.setup_logging')
@patch('kicad_mcp.server.create_server')
@patch('kicad_mcp.server.logging')
@patch('mckicad.server.setup_logging')
@patch('mckicad.server.create_server')
@patch('mckicad.server.logging')
def test_main_exception(self, mock_logging, mock_create_server, mock_setup_logging):
"""Test main with general exception."""
mock_server = Mock()
@ -353,15 +353,15 @@ class TestMain:
mock_logging.error.assert_any_call("Server error: Server error")
mock_logging.info.assert_any_call("Server shutdown complete")
@patch('kicad_mcp.server.setup_logging')
@patch('kicad_mcp.server.create_server')
@patch('mckicad.server.setup_logging')
@patch('mckicad.server.create_server')
def test_main_cleanup_always_runs(self, mock_create_server, mock_setup_logging):
"""Test that cleanup always runs even with exceptions."""
mock_server = Mock()
mock_server.run.side_effect = Exception("Test exception")
mock_create_server.return_value = mock_server
with patch('kicad_mcp.server.logging') as mock_logging:
with patch('mckicad.server.logging') as mock_logging:
main()
# Verify finally block executed

View File

@ -1,9 +1,9 @@
"""
Tests for the kicad_mcp.utils.component_utils module.
Tests for the mckicad.utils.component_utils module.
"""
import pytest
from kicad_mcp.utils.component_utils import (
from mckicad.utils.component_utils import (
extract_capacitance_value,
extract_frequency_from_value,
extract_inductance_value,

View File

@ -1,18 +1,18 @@
"""
Tests for the kicad_mcp.utils.file_utils module.
Tests for the mckicad.utils.file_utils module.
"""
import json
import os
import tempfile
from unittest.mock import mock_open, patch
from kicad_mcp.utils.file_utils import get_project_files, load_project_json
from mckicad.utils.file_utils import get_project_files, load_project_json
class TestGetProjectFiles:
"""Test get_project_files function."""
@patch('kicad_mcp.utils.file_utils.get_project_name_from_path')
@patch('mckicad.utils.file_utils.get_project_name_from_path')
@patch('os.path.dirname')
@patch('os.path.exists')
@patch('os.listdir')
@ -31,7 +31,7 @@ class TestGetProjectFiles:
assert "bom" in result
assert result["bom"] == "/test/project/myproject-bom.csv"
@patch('kicad_mcp.utils.file_utils.get_project_name_from_path')
@patch('mckicad.utils.file_utils.get_project_name_from_path')
@patch('os.path.dirname')
@patch('os.path.exists')
@patch('os.listdir')
@ -55,7 +55,7 @@ class TestGetProjectFiles:
if file_type in result:
assert result[file_type].startswith("/test/project/test_project")
@patch('kicad_mcp.utils.file_utils.get_project_name_from_path')
@patch('mckicad.utils.file_utils.get_project_name_from_path')
@patch('os.path.dirname')
@patch('os.path.exists')
@patch('os.listdir')
@ -84,7 +84,7 @@ class TestGetProjectFiles:
assert result["bom"] == "/test/project/project-bom.csv"
assert result["positions"] == "/test/project/project_positions.pos"
@patch('kicad_mcp.utils.file_utils.get_project_name_from_path')
@patch('mckicad.utils.file_utils.get_project_name_from_path')
@patch('os.path.dirname')
@patch('os.path.exists')
@patch('os.listdir')
@ -102,7 +102,7 @@ class TestGetProjectFiles:
# Should not crash and return basic result
assert len(result) >= 1
@patch('kicad_mcp.utils.file_utils.get_project_name_from_path')
@patch('mckicad.utils.file_utils.get_project_name_from_path')
@patch('os.path.dirname')
@patch('os.path.exists')
@patch('os.listdir')
@ -119,7 +119,7 @@ class TestGetProjectFiles:
assert result["project"] == "/test/project/project.kicad_pro"
assert len(result) == 1
@patch('kicad_mcp.utils.file_utils.get_project_name_from_path')
@patch('mckicad.utils.file_utils.get_project_name_from_path')
@patch('os.path.dirname')
@patch('os.path.exists')
@patch('os.listdir')
@ -307,7 +307,7 @@ class TestIntegration:
assert json_data == project_data
assert json_data["board"]["thickness"] == 1.6
@patch('kicad_mcp.utils.file_utils.get_project_name_from_path')
@patch('mckicad.utils.file_utils.get_project_name_from_path')
def test_project_name_integration(self, mock_get_name):
"""Test integration with get_project_name_from_path function."""
mock_get_name.return_value = "custom_name"

View File

@ -1,5 +1,5 @@
"""
Tests for the kicad_mcp.utils.kicad_cli module.
Tests for the mckicad.utils.kicad_cli module.
"""
import platform
import subprocess
@ -7,7 +7,7 @@ from unittest.mock import Mock, patch
import pytest
from kicad_mcp.utils.kicad_cli import (
from mckicad.utils.kicad_cli import (
KiCadCLIError,
KiCadCLIManager,
find_kicad_cli,
@ -44,8 +44,8 @@ class TestKiCadCLIManager:
assert manager._cache_validated is False
assert manager._system == platform.system()
@patch('kicad_mcp.utils.kicad_cli.KiCadCLIManager._detect_cli_path')
@patch('kicad_mcp.utils.kicad_cli.KiCadCLIManager._validate_cli_path')
@patch('mckicad.utils.kicad_cli.KiCadCLIManager._detect_cli_path')
@patch('mckicad.utils.kicad_cli.KiCadCLIManager._validate_cli_path')
def test_find_kicad_cli_success(self, mock_validate, mock_detect):
"""Test successful CLI detection."""
mock_detect.return_value = "/usr/bin/kicad-cli"
@ -57,7 +57,7 @@ class TestKiCadCLIManager:
assert self.manager._cached_cli_path == "/usr/bin/kicad-cli"
assert self.manager._cache_validated is True
@patch('kicad_mcp.utils.kicad_cli.KiCadCLIManager._detect_cli_path')
@patch('mckicad.utils.kicad_cli.KiCadCLIManager._detect_cli_path')
def test_find_kicad_cli_not_found(self, mock_detect):
"""Test CLI detection failure."""
mock_detect.return_value = None
@ -68,8 +68,8 @@ class TestKiCadCLIManager:
assert self.manager._cached_cli_path is None
assert self.manager._cache_validated is False
@patch('kicad_mcp.utils.kicad_cli.KiCadCLIManager._detect_cli_path')
@patch('kicad_mcp.utils.kicad_cli.KiCadCLIManager._validate_cli_path')
@patch('mckicad.utils.kicad_cli.KiCadCLIManager._detect_cli_path')
@patch('mckicad.utils.kicad_cli.KiCadCLIManager._validate_cli_path')
def test_find_kicad_cli_validation_failure(self, mock_validate, mock_detect):
"""Test CLI detection with validation failure."""
mock_detect.return_value = "/usr/bin/kicad-cli"
@ -86,7 +86,7 @@ class TestKiCadCLIManager:
self.manager._cached_cli_path = "/cached/path"
self.manager._cache_validated = True
with patch('kicad_mcp.utils.kicad_cli.KiCadCLIManager._detect_cli_path') as mock_detect:
with patch('mckicad.utils.kicad_cli.KiCadCLIManager._detect_cli_path') as mock_detect:
result = self.manager.find_kicad_cli()
assert result == "/cached/path"
@ -97,8 +97,8 @@ class TestKiCadCLIManager:
self.manager._cached_cli_path = "/cached/path"
self.manager._cache_validated = True
with patch('kicad_mcp.utils.kicad_cli.KiCadCLIManager._detect_cli_path') as mock_detect, \
patch('kicad_mcp.utils.kicad_cli.KiCadCLIManager._validate_cli_path') as mock_validate:
with patch('mckicad.utils.kicad_cli.KiCadCLIManager._detect_cli_path') as mock_detect, \
patch('mckicad.utils.kicad_cli.KiCadCLIManager._validate_cli_path') as mock_validate:
mock_detect.return_value = "/new/path"
mock_validate.return_value = True
@ -108,7 +108,7 @@ class TestKiCadCLIManager:
assert result == "/new/path"
mock_detect.assert_called_once()
@patch('kicad_mcp.utils.kicad_cli.KiCadCLIManager.find_kicad_cli')
@patch('mckicad.utils.kicad_cli.KiCadCLIManager.find_kicad_cli')
def test_get_cli_path_success(self, mock_find):
"""Test successful CLI path retrieval."""
mock_find.return_value = "/usr/bin/kicad-cli"
@ -117,7 +117,7 @@ class TestKiCadCLIManager:
assert result == "/usr/bin/kicad-cli"
@patch('kicad_mcp.utils.kicad_cli.KiCadCLIManager.find_kicad_cli')
@patch('mckicad.utils.kicad_cli.KiCadCLIManager.find_kicad_cli')
def test_get_cli_path_not_required(self, mock_find):
"""Test CLI path retrieval when not required."""
mock_find.return_value = None
@ -126,7 +126,7 @@ class TestKiCadCLIManager:
assert result is None
@patch('kicad_mcp.utils.kicad_cli.KiCadCLIManager.find_kicad_cli')
@patch('mckicad.utils.kicad_cli.KiCadCLIManager.find_kicad_cli')
def test_get_cli_path_required_raises(self, mock_find):
"""Test that exception is raised when CLI required but not found."""
mock_find.return_value = None
@ -136,22 +136,22 @@ class TestKiCadCLIManager:
assert "KiCad CLI not found" in str(exc_info.value)
@patch('kicad_mcp.utils.kicad_cli.KiCadCLIManager.find_kicad_cli')
@patch('mckicad.utils.kicad_cli.KiCadCLIManager.find_kicad_cli')
def test_is_available_true(self, mock_find):
"""Test is_available returns True when CLI found."""
mock_find.return_value = "/usr/bin/kicad-cli"
assert self.manager.is_available() is True
@patch('kicad_mcp.utils.kicad_cli.KiCadCLIManager.find_kicad_cli')
@patch('mckicad.utils.kicad_cli.KiCadCLIManager.find_kicad_cli')
def test_is_available_false(self, mock_find):
"""Test is_available returns False when CLI not found."""
mock_find.return_value = None
assert self.manager.is_available() is False
@patch('kicad_mcp.utils.kicad_cli.subprocess.run')
@patch('kicad_mcp.utils.kicad_cli.KiCadCLIManager.find_kicad_cli')
@patch('mckicad.utils.kicad_cli.subprocess.run')
@patch('mckicad.utils.kicad_cli.KiCadCLIManager.find_kicad_cli')
def test_get_version_success(self, mock_find, mock_run):
"""Test successful version retrieval."""
mock_find.return_value = "/usr/bin/kicad-cli"
@ -165,7 +165,7 @@ class TestKiCadCLIManager:
assert version == "KiCad 7.0.0"
mock_run.assert_called_once()
@patch('kicad_mcp.utils.kicad_cli.KiCadCLIManager.find_kicad_cli')
@patch('mckicad.utils.kicad_cli.KiCadCLIManager.find_kicad_cli')
def test_get_version_cli_not_found(self, mock_find):
"""Test version retrieval when CLI not found."""
mock_find.return_value = None
@ -174,8 +174,8 @@ class TestKiCadCLIManager:
assert version is None
@patch('kicad_mcp.utils.kicad_cli.subprocess.run')
@patch('kicad_mcp.utils.kicad_cli.KiCadCLIManager.find_kicad_cli')
@patch('mckicad.utils.kicad_cli.subprocess.run')
@patch('mckicad.utils.kicad_cli.KiCadCLIManager.find_kicad_cli')
def test_get_version_subprocess_error(self, mock_find, mock_run):
"""Test version retrieval with subprocess error."""
mock_find.return_value = "/usr/bin/kicad-cli"
@ -185,9 +185,9 @@ class TestKiCadCLIManager:
assert version is None
@patch('kicad_mcp.utils.kicad_cli.os.environ.get')
@patch('kicad_mcp.utils.kicad_cli.os.path.isfile')
@patch('kicad_mcp.utils.kicad_cli.os.access')
@patch('mckicad.utils.kicad_cli.os.environ.get')
@patch('mckicad.utils.kicad_cli.os.path.isfile')
@patch('mckicad.utils.kicad_cli.os.access')
def test_detect_cli_path_environment_variable(self, mock_access, mock_isfile, mock_env_get):
"""Test CLI detection from environment variable."""
mock_env_get.return_value = "/custom/kicad-cli"
@ -198,8 +198,8 @@ class TestKiCadCLIManager:
assert result == "/custom/kicad-cli"
@patch('kicad_mcp.utils.kicad_cli.os.environ.get')
@patch('kicad_mcp.utils.kicad_cli.shutil.which')
@patch('mckicad.utils.kicad_cli.os.environ.get')
@patch('mckicad.utils.kicad_cli.shutil.which')
def test_detect_cli_path_system_path(self, mock_which, mock_env_get):
"""Test CLI detection from system PATH."""
mock_env_get.return_value = None
@ -209,10 +209,10 @@ class TestKiCadCLIManager:
assert result == "/usr/bin/kicad-cli"
@patch('kicad_mcp.utils.kicad_cli.os.environ.get')
@patch('kicad_mcp.utils.kicad_cli.shutil.which')
@patch('kicad_mcp.utils.kicad_cli.os.path.isfile')
@patch('kicad_mcp.utils.kicad_cli.os.access')
@patch('mckicad.utils.kicad_cli.os.environ.get')
@patch('mckicad.utils.kicad_cli.shutil.which')
@patch('mckicad.utils.kicad_cli.os.path.isfile')
@patch('mckicad.utils.kicad_cli.os.access')
def test_detect_cli_path_common_locations(self, mock_access, mock_isfile, mock_which, mock_env_get):
"""Test CLI detection from common installation paths."""
mock_env_get.return_value = None
@ -265,7 +265,7 @@ class TestKiCadCLIManager:
assert "/usr/bin/kicad-cli" in paths
assert "/snap/kicad/current/usr/bin/kicad-cli" in paths
@patch('kicad_mcp.utils.kicad_cli.subprocess.run')
@patch('mckicad.utils.kicad_cli.subprocess.run')
def test_validate_cli_path_success(self, mock_run):
"""Test successful CLI validation."""
mock_result = Mock()
@ -276,7 +276,7 @@ class TestKiCadCLIManager:
assert result is True
@patch('kicad_mcp.utils.kicad_cli.subprocess.run')
@patch('mckicad.utils.kicad_cli.subprocess.run')
def test_validate_cli_path_failure(self, mock_run):
"""Test CLI validation failure."""
mock_result = Mock()
@ -287,7 +287,7 @@ class TestKiCadCLIManager:
assert result is False
@patch('kicad_mcp.utils.kicad_cli.subprocess.run')
@patch('mckicad.utils.kicad_cli.subprocess.run')
def test_validate_cli_path_exception(self, mock_run):
"""Test CLI validation with exception."""
mock_run.side_effect = subprocess.SubprocessError("Test error")
@ -302,8 +302,8 @@ class TestGlobalFunctions:
def setup_method(self):
"""Reset global manager before each test."""
import kicad_mcp.utils.kicad_cli
kicad_mcp.utils.kicad_cli._cli_manager = None
import mckicad.utils.kicad_cli
mckicad.utils.kicad_cli._cli_manager = None
def test_get_cli_manager_singleton(self):
"""Test that get_cli_manager returns singleton instance."""
@ -313,7 +313,7 @@ class TestGlobalFunctions:
assert manager1 is manager2
assert isinstance(manager1, KiCadCLIManager)
@patch('kicad_mcp.utils.kicad_cli.get_cli_manager')
@patch('mckicad.utils.kicad_cli.get_cli_manager')
def test_find_kicad_cli_convenience(self, mock_get_manager):
"""Test find_kicad_cli convenience function."""
mock_manager = Mock()
@ -325,7 +325,7 @@ class TestGlobalFunctions:
assert result == "/usr/bin/kicad-cli"
mock_manager.find_kicad_cli.assert_called_once_with(True)
@patch('kicad_mcp.utils.kicad_cli.get_cli_manager')
@patch('mckicad.utils.kicad_cli.get_cli_manager')
def test_get_kicad_cli_path_convenience(self, mock_get_manager):
"""Test get_kicad_cli_path convenience function."""
mock_manager = Mock()
@ -337,7 +337,7 @@ class TestGlobalFunctions:
assert result == "/usr/bin/kicad-cli"
mock_manager.get_cli_path.assert_called_once_with(False)
@patch('kicad_mcp.utils.kicad_cli.get_cli_manager')
@patch('mckicad.utils.kicad_cli.get_cli_manager')
def test_is_kicad_cli_available_convenience(self, mock_get_manager):
"""Test is_kicad_cli_available convenience function."""
mock_manager = Mock()
@ -349,7 +349,7 @@ class TestGlobalFunctions:
assert result is True
mock_manager.is_available.assert_called_once()
@patch('kicad_mcp.utils.kicad_cli.get_cli_manager')
@patch('mckicad.utils.kicad_cli.get_cli_manager')
def test_get_kicad_version_convenience(self, mock_get_manager):
"""Test get_kicad_version convenience function."""
mock_manager = Mock()
@ -374,8 +374,8 @@ class TestIntegration:
assert not manager._cache_validated
# Simulate finding CLI
with patch('kicad_mcp.utils.kicad_cli.KiCadCLIManager._detect_cli_path') as mock_detect, \
patch('kicad_mcp.utils.kicad_cli.KiCadCLIManager._validate_cli_path') as mock_validate:
with patch('mckicad.utils.kicad_cli.KiCadCLIManager._detect_cli_path') as mock_detect, \
patch('mckicad.utils.kicad_cli.KiCadCLIManager._validate_cli_path') as mock_validate:
mock_detect.return_value = "/test/kicad-cli"
mock_validate.return_value = True
@ -401,7 +401,7 @@ class TestIntegration:
"""Test that errors are properly propagated."""
manager = KiCadCLIManager()
with patch('kicad_mcp.utils.kicad_cli.KiCadCLIManager.find_kicad_cli') as mock_find:
with patch('mckicad.utils.kicad_cli.KiCadCLIManager.find_kicad_cli') as mock_find:
mock_find.return_value = None
# Should not raise when required=False

View File

@ -7,7 +7,7 @@ import tempfile
import pytest
from kicad_mcp.utils.path_validator import (
from mckicad.utils.path_validator import (
PathValidationError,
PathValidator,
validate_directory,
@ -200,7 +200,7 @@ class TestConvenienceFunctions:
"""Test validate_path convenience function."""
with tempfile.TemporaryDirectory() as temp_dir:
# Add temp_dir to default validator
from kicad_mcp.utils.path_validator import get_default_validator
from mckicad.utils.path_validator import get_default_validator
get_default_validator().add_trusted_root(temp_dir)
@ -215,7 +215,7 @@ class TestConvenienceFunctions:
"""Test validate_kicad_file convenience function."""
with tempfile.TemporaryDirectory() as temp_dir:
# Add temp_dir to default validator
from kicad_mcp.utils.path_validator import get_default_validator
from mckicad.utils.path_validator import get_default_validator
get_default_validator().add_trusted_root(temp_dir)
@ -230,7 +230,7 @@ class TestConvenienceFunctions:
"""Test validate_directory convenience function."""
with tempfile.TemporaryDirectory() as temp_dir:
# Add temp_dir to default validator
from kicad_mcp.utils.path_validator import get_default_validator
from mckicad.utils.path_validator import get_default_validator
get_default_validator().add_trusted_root(temp_dir)

View File

@ -9,9 +9,9 @@ from unittest.mock import MagicMock, patch
import pytest
from kicad_mcp.utils.kicad_cli import KiCadCLIError
from kicad_mcp.utils.path_validator import PathValidationError, PathValidator
from kicad_mcp.utils.secure_subprocess import (
from mckicad.utils.kicad_cli import KiCadCLIError
from mckicad.utils.path_validator import PathValidationError, PathValidator
from mckicad.utils.secure_subprocess import (
SecureSubprocessError,
SecureSubprocessRunner,
create_temp_file,
@ -22,7 +22,7 @@ from kicad_mcp.utils.secure_subprocess import (
def _kicad_cli_available():
"""Check if KiCad CLI is available."""
try:
from kicad_mcp.utils.kicad_cli import get_kicad_cli_path
from mckicad.utils.kicad_cli import get_kicad_cli_path
get_kicad_cli_path()
return True
@ -45,7 +45,7 @@ class TestSecureSubprocessRunner:
runner = SecureSubprocessRunner(path_validator=validator)
assert runner.path_validator is validator
@patch("kicad_mcp.utils.secure_subprocess.get_kicad_cli_path")
@patch("mckicad.utils.secure_subprocess.get_kicad_cli_path")
@patch.object(SecureSubprocessRunner, "_run_subprocess")
def test_run_kicad_command_success(self, mock_run_subprocess, mock_get_cli):
"""Test successful KiCad command execution."""
@ -76,7 +76,7 @@ class TestSecureSubprocessRunner:
assert result is mock_result
mock_run_subprocess.assert_called_once()
@patch("kicad_mcp.utils.secure_subprocess.get_kicad_cli_path")
@patch("mckicad.utils.secure_subprocess.get_kicad_cli_path")
def test_run_kicad_command_cli_not_found(self, mock_get_cli):
"""Test KiCad command when CLI not found."""
mock_get_cli.side_effect = KiCadCLIError("CLI not found")
@ -113,7 +113,7 @@ class TestSecureSubprocessRunner:
output_files=["/etc/output.svg"],
)
@patch("kicad_mcp.utils.secure_subprocess.get_kicad_cli_path")
@patch("mckicad.utils.secure_subprocess.get_kicad_cli_path")
@patch.object(SecureSubprocessRunner, "_run_subprocess")
def test_run_kicad_command_with_working_dir(self, mock_run_subprocess, mock_get_cli):
"""Test KiCad command with working directory."""
@ -132,7 +132,7 @@ class TestSecureSubprocessRunner:
call_args = mock_run_subprocess.call_args
assert call_args[1]["working_dir"] == os.path.realpath(temp_dir)
@patch("kicad_mcp.utils.secure_subprocess.get_kicad_cli_path")
@patch("mckicad.utils.secure_subprocess.get_kicad_cli_path")
@patch.object(SecureSubprocessRunner, "_run_subprocess")
def test_run_kicad_command_subprocess_error(self, mock_run_subprocess, mock_get_cli):
"""Test KiCad command with subprocess error."""
@ -145,7 +145,7 @@ class TestSecureSubprocessRunner:
runner.run_kicad_command(["--version"])
@pytest.mark.asyncio
@patch("kicad_mcp.utils.secure_subprocess.get_kicad_cli_path")
@patch("mckicad.utils.secure_subprocess.get_kicad_cli_path")
@patch.object(SecureSubprocessRunner, "run_kicad_command")
async def test_run_kicad_command_async(self, mock_run_command, mock_get_cli):
"""Test async KiCad command execution."""

236
uv.lock generated
View File

@ -565,39 +565,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b4/39/61981f3c5d123f6ceff73479e3ace1de81d90af95632e2fe3d094b0ac8a9/filelock-3.21.1-py3-none-any.whl", hash = "sha256:f3aa1887cdf3514b13a379b5835ff35327d7aa24a96db4453b97531c00476760", size = 21472, upload-time = "2026-02-12T22:29:27.518Z" },
]
[[package]]
name = "greenlet"
version = "3.2.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c9/92/bb85bd6e80148a4d2e0c59f7c0c2891029f8fd510183afc7d8d2feeed9b6/greenlet-3.2.3.tar.gz", hash = "sha256:8b0dd8ae4c0d6f5e54ee55ba935eeb3d735a9b58a8a1e5b5cbab64e01a39f365", size = 185752, upload-time = "2025-06-05T16:16:09.955Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f3/94/ad0d435f7c48debe960c53b8f60fb41c2026b1d0fa4a99a1cb17c3461e09/greenlet-3.2.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:25ad29caed5783d4bd7a85c9251c651696164622494c00802a139c00d639242d", size = 271992, upload-time = "2025-06-05T16:11:23.467Z" },
{ url = "https://files.pythonhosted.org/packages/93/5d/7c27cf4d003d6e77749d299c7c8f5fd50b4f251647b5c2e97e1f20da0ab5/greenlet-3.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88cd97bf37fe24a6710ec6a3a7799f3f81d9cd33317dcf565ff9950c83f55e0b", size = 638820, upload-time = "2025-06-05T16:38:52.882Z" },
{ url = "https://files.pythonhosted.org/packages/c6/7e/807e1e9be07a125bb4c169144937910bf59b9d2f6d931578e57f0bce0ae2/greenlet-3.2.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:baeedccca94880d2f5666b4fa16fc20ef50ba1ee353ee2d7092b383a243b0b0d", size = 653046, upload-time = "2025-06-05T16:41:36.343Z" },
{ url = "https://files.pythonhosted.org/packages/9d/ab/158c1a4ea1068bdbc78dba5a3de57e4c7aeb4e7fa034320ea94c688bfb61/greenlet-3.2.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:be52af4b6292baecfa0f397f3edb3c6092ce071b499dd6fe292c9ac9f2c8f264", size = 647701, upload-time = "2025-06-05T16:48:19.604Z" },
{ url = "https://files.pythonhosted.org/packages/cc/0d/93729068259b550d6a0288da4ff72b86ed05626eaf1eb7c0d3466a2571de/greenlet-3.2.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0cc73378150b8b78b0c9fe2ce56e166695e67478550769536a6742dca3651688", size = 649747, upload-time = "2025-06-05T16:13:04.628Z" },
{ url = "https://files.pythonhosted.org/packages/f6/f6/c82ac1851c60851302d8581680573245c8fc300253fc1ff741ae74a6c24d/greenlet-3.2.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:706d016a03e78df129f68c4c9b4c4f963f7d73534e48a24f5f5a7101ed13dbbb", size = 605461, upload-time = "2025-06-05T16:12:50.792Z" },
{ url = "https://files.pythonhosted.org/packages/98/82/d022cf25ca39cf1200650fc58c52af32c90f80479c25d1cbf57980ec3065/greenlet-3.2.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:419e60f80709510c343c57b4bb5a339d8767bf9aef9b8ce43f4f143240f88b7c", size = 1121190, upload-time = "2025-06-05T16:36:48.59Z" },
{ url = "https://files.pythonhosted.org/packages/f5/e1/25297f70717abe8104c20ecf7af0a5b82d2f5a980eb1ac79f65654799f9f/greenlet-3.2.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:93d48533fade144203816783373f27a97e4193177ebaaf0fc396db19e5d61163", size = 1149055, upload-time = "2025-06-05T16:12:40.457Z" },
{ url = "https://files.pythonhosted.org/packages/1f/8f/8f9e56c5e82eb2c26e8cde787962e66494312dc8cb261c460e1f3a9c88bc/greenlet-3.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:7454d37c740bb27bdeddfc3f358f26956a07d5220818ceb467a483197d84f849", size = 297817, upload-time = "2025-06-05T16:29:49.244Z" },
{ url = "https://files.pythonhosted.org/packages/b1/cf/f5c0b23309070ae93de75c90d29300751a5aacefc0a3ed1b1d8edb28f08b/greenlet-3.2.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:500b8689aa9dd1ab26872a34084503aeddefcb438e2e7317b89b11eaea1901ad", size = 270732, upload-time = "2025-06-05T16:10:08.26Z" },
{ url = "https://files.pythonhosted.org/packages/48/ae/91a957ba60482d3fecf9be49bc3948f341d706b52ddb9d83a70d42abd498/greenlet-3.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a07d3472c2a93117af3b0136f246b2833fdc0b542d4a9799ae5f41c28323faef", size = 639033, upload-time = "2025-06-05T16:38:53.983Z" },
{ url = "https://files.pythonhosted.org/packages/6f/df/20ffa66dd5a7a7beffa6451bdb7400d66251374ab40b99981478c69a67a8/greenlet-3.2.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8704b3768d2f51150626962f4b9a9e4a17d2e37c8a8d9867bbd9fa4eb938d3b3", size = 652999, upload-time = "2025-06-05T16:41:37.89Z" },
{ url = "https://files.pythonhosted.org/packages/51/b4/ebb2c8cb41e521f1d72bf0465f2f9a2fd803f674a88db228887e6847077e/greenlet-3.2.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5035d77a27b7c62db6cf41cf786cfe2242644a7a337a0e155c80960598baab95", size = 647368, upload-time = "2025-06-05T16:48:21.467Z" },
{ url = "https://files.pythonhosted.org/packages/8e/6a/1e1b5aa10dced4ae876a322155705257748108b7fd2e4fae3f2a091fe81a/greenlet-3.2.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2d8aa5423cd4a396792f6d4580f88bdc6efcb9205891c9d40d20f6e670992efb", size = 650037, upload-time = "2025-06-05T16:13:06.402Z" },
{ url = "https://files.pythonhosted.org/packages/26/f2/ad51331a157c7015c675702e2d5230c243695c788f8f75feba1af32b3617/greenlet-3.2.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c724620a101f8170065d7dded3f962a2aea7a7dae133a009cada42847e04a7b", size = 608402, upload-time = "2025-06-05T16:12:51.91Z" },
{ url = "https://files.pythonhosted.org/packages/26/bc/862bd2083e6b3aff23300900a956f4ea9a4059de337f5c8734346b9b34fc/greenlet-3.2.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:873abe55f134c48e1f2a6f53f7d1419192a3d1a4e873bace00499a4e45ea6af0", size = 1119577, upload-time = "2025-06-05T16:36:49.787Z" },
{ url = "https://files.pythonhosted.org/packages/86/94/1fc0cc068cfde885170e01de40a619b00eaa8f2916bf3541744730ffb4c3/greenlet-3.2.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:024571bbce5f2c1cfff08bf3fbaa43bbc7444f580ae13b0099e95d0e6e67ed36", size = 1147121, upload-time = "2025-06-05T16:12:42.527Z" },
{ url = "https://files.pythonhosted.org/packages/27/1a/199f9587e8cb08a0658f9c30f3799244307614148ffe8b1e3aa22f324dea/greenlet-3.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5195fb1e75e592dd04ce79881c8a22becdfa3e6f500e7feb059b1e6fdd54d3e3", size = 297603, upload-time = "2025-06-05T16:20:12.651Z" },
{ url = "https://files.pythonhosted.org/packages/d8/ca/accd7aa5280eb92b70ed9e8f7fd79dc50a2c21d8c73b9a0856f5b564e222/greenlet-3.2.3-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:3d04332dddb10b4a211b68111dabaee2e1a073663d117dc10247b5b1642bac86", size = 271479, upload-time = "2025-06-05T16:10:47.525Z" },
{ url = "https://files.pythonhosted.org/packages/55/71/01ed9895d9eb49223280ecc98a557585edfa56b3d0e965b9fa9f7f06b6d9/greenlet-3.2.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8186162dffde068a465deab08fc72c767196895c39db26ab1c17c0b77a6d8b97", size = 683952, upload-time = "2025-06-05T16:38:55.125Z" },
{ url = "https://files.pythonhosted.org/packages/ea/61/638c4bdf460c3c678a0a1ef4c200f347dff80719597e53b5edb2fb27ab54/greenlet-3.2.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f4bfbaa6096b1b7a200024784217defedf46a07c2eee1a498e94a1b5f8ec5728", size = 696917, upload-time = "2025-06-05T16:41:38.959Z" },
{ url = "https://files.pythonhosted.org/packages/22/cc/0bd1a7eb759d1f3e3cc2d1bc0f0b487ad3cc9f34d74da4b80f226fde4ec3/greenlet-3.2.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:ed6cfa9200484d234d8394c70f5492f144b20d4533f69262d530a1a082f6ee9a", size = 692443, upload-time = "2025-06-05T16:48:23.113Z" },
{ url = "https://files.pythonhosted.org/packages/67/10/b2a4b63d3f08362662e89c103f7fe28894a51ae0bc890fabf37d1d780e52/greenlet-3.2.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:02b0df6f63cd15012bed5401b47829cfd2e97052dc89da3cfaf2c779124eb892", size = 692995, upload-time = "2025-06-05T16:13:07.972Z" },
{ url = "https://files.pythonhosted.org/packages/5a/c6/ad82f148a4e3ce9564056453a71529732baf5448ad53fc323e37efe34f66/greenlet-3.2.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86c2d68e87107c1792e2e8d5399acec2487a4e993ab76c792408e59394d52141", size = 655320, upload-time = "2025-06-05T16:12:53.453Z" },
{ url = "https://files.pythonhosted.org/packages/5c/4f/aab73ecaa6b3086a4c89863d94cf26fa84cbff63f52ce9bc4342b3087a06/greenlet-3.2.3-cp314-cp314-win_amd64.whl", hash = "sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a", size = 301236, upload-time = "2025-06-05T16:15:20.111Z" },
]
[[package]]
name = "h11"
version = "0.16.0"
@ -823,93 +790,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" },
]
[[package]]
name = "kicad-mcp"
version = "0.1.0"
source = { editable = "." }
dependencies = [
{ name = "defusedxml" },
{ name = "fastmcp" },
{ name = "kicad-python" },
{ name = "mcp", extra = ["cli"] },
{ name = "pandas" },
{ name = "pyyaml" },
{ name = "requests" },
]
[package.dev-dependencies]
dev = [
{ name = "bandit" },
{ name = "mypy" },
{ name = "pre-commit" },
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "pytest-cov" },
{ name = "pytest-mock" },
{ name = "pytest-xdist" },
{ name = "ruff" },
]
docs = [
{ name = "myst-parser" },
{ name = "sphinx" },
{ name = "sphinx-rtd-theme" },
]
performance = [
{ name = "memory-profiler" },
{ name = "py-spy" },
]
security = [
{ name = "bandit" },
{ name = "safety" },
]
visualization = [
{ name = "cairosvg" },
{ name = "pillow" },
{ name = "playwright" },
]
[package.metadata]
requires-dist = [
{ name = "defusedxml", specifier = ">=0.7.1" },
{ name = "fastmcp", specifier = ">=2.14.5" },
{ name = "kicad-python", specifier = ">=0.5.0" },
{ name = "mcp", extras = ["cli"], specifier = ">=1.26.0" },
{ name = "pandas", specifier = ">=2.3.3" },
{ name = "pyyaml", specifier = ">=6.0.3" },
{ name = "requests", specifier = ">=2.32.5" },
]
[package.metadata.requires-dev]
dev = [
{ name = "bandit", specifier = ">=1.9.3" },
{ name = "mypy", specifier = ">=1.19.1" },
{ name = "pre-commit", specifier = ">=4.5.1" },
{ name = "pytest", specifier = ">=8.4.2" },
{ name = "pytest-asyncio", specifier = ">=1.3.0" },
{ name = "pytest-cov", specifier = ">=7.0.0" },
{ name = "pytest-mock", specifier = ">=3.15.1" },
{ name = "pytest-xdist", specifier = ">=3.8.0" },
{ name = "ruff", specifier = ">=0.15.1" },
]
docs = [
{ name = "myst-parser", specifier = ">=5.0.0" },
{ name = "sphinx", specifier = ">=9.1.0" },
{ name = "sphinx-rtd-theme", specifier = ">=3.1.0" },
]
performance = [
{ name = "memory-profiler", specifier = ">=0.61.0" },
{ name = "py-spy", specifier = ">=0.3.0" },
]
security = [
{ name = "bandit", specifier = ">=1.9.3" },
{ name = "safety", specifier = ">=3.7.0" },
]
visualization = [
{ name = "cairosvg", specifier = ">=2.8.2" },
{ name = "pillow", specifier = ">=12.1.1" },
{ name = "playwright", specifier = ">=1.58.0" },
]
[[package]]
name = "kicad-python"
version = "0.5.0"
@ -1095,6 +975,91 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d6/26/6cc45d156f44dbe1d5696d9e54042e4dcaf7b946c0b86df6a97d29706f32/marshmallow-4.0.0-py3-none-any.whl", hash = "sha256:e7b0528337e9990fd64950f8a6b3a1baabed09ad17a0dfb844d701151f92d203", size = 48420, upload-time = "2025-04-17T02:25:53.375Z" },
]
[[package]]
name = "mckicad"
version = "0.1.0"
source = { editable = "." }
dependencies = [
{ name = "defusedxml" },
{ name = "fastmcp" },
{ name = "kicad-python" },
{ name = "mcp", extra = ["cli"] },
{ name = "pandas" },
{ name = "pyyaml" },
{ name = "requests" },
]
[package.dev-dependencies]
dev = [
{ name = "bandit" },
{ name = "mypy" },
{ name = "pre-commit" },
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "pytest-cov" },
{ name = "pytest-mock" },
{ name = "pytest-xdist" },
{ name = "ruff" },
]
docs = [
{ name = "myst-parser" },
{ name = "sphinx" },
{ name = "sphinx-rtd-theme" },
]
performance = [
{ name = "memory-profiler" },
{ name = "py-spy" },
]
security = [
{ name = "bandit" },
{ name = "safety" },
]
visualization = [
{ name = "cairosvg" },
{ name = "pillow" },
]
[package.metadata]
requires-dist = [
{ name = "defusedxml", specifier = ">=0.7.1" },
{ name = "fastmcp", specifier = ">=2.14.5" },
{ name = "kicad-python", specifier = ">=0.5.0" },
{ name = "mcp", extras = ["cli"], specifier = ">=1.26.0" },
{ name = "pandas", specifier = ">=2.3.3" },
{ name = "pyyaml", specifier = ">=6.0.3" },
{ name = "requests", specifier = ">=2.32.5" },
]
[package.metadata.requires-dev]
dev = [
{ name = "bandit", specifier = ">=1.9.3" },
{ name = "mypy", specifier = ">=1.19.1" },
{ name = "pre-commit", specifier = ">=4.5.1" },
{ name = "pytest", specifier = ">=8.4.2" },
{ name = "pytest-asyncio", specifier = ">=1.3.0" },
{ name = "pytest-cov", specifier = ">=7.0.0" },
{ name = "pytest-mock", specifier = ">=3.15.1" },
{ name = "pytest-xdist", specifier = ">=3.8.0" },
{ name = "ruff", specifier = ">=0.15.1" },
]
docs = [
{ name = "myst-parser", specifier = ">=5.0.0" },
{ name = "sphinx", specifier = ">=9.1.0" },
{ name = "sphinx-rtd-theme", specifier = ">=3.1.0" },
]
performance = [
{ name = "memory-profiler", specifier = ">=0.61.0" },
{ name = "py-spy", specifier = ">=0.3.0" },
]
security = [
{ name = "bandit", specifier = ">=1.9.3" },
{ name = "safety", specifier = ">=3.7.0" },
]
visualization = [
{ name = "cairosvg", specifier = ">=2.8.2" },
{ name = "pillow", specifier = ">=12.1.1" },
]
[[package]]
name = "mcp"
version = "1.26.0"
@ -1567,25 +1532,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" },
]
[[package]]
name = "playwright"
version = "1.58.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "greenlet" },
{ name = "pyee" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/f8/c9/9c6061d5703267f1baae6a4647bfd1862e386fbfdb97d889f6f6ae9e3f64/playwright-1.58.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:96e3204aac292ee639edbfdef6298b4be2ea0a55a16b7068df91adac077cc606", size = 42251098, upload-time = "2026-01-30T15:09:24.028Z" },
{ url = "https://files.pythonhosted.org/packages/e0/40/59d34a756e02f8c670f0fee987d46f7ee53d05447d43cd114ca015cb168c/playwright-1.58.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:70c763694739d28df71ed578b9c8202bb83e8fe8fb9268c04dd13afe36301f71", size = 41039625, upload-time = "2026-01-30T15:09:27.558Z" },
{ url = "https://files.pythonhosted.org/packages/e1/ee/3ce6209c9c74a650aac9028c621f357a34ea5cd4d950700f8e2c4b7fe2c4/playwright-1.58.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:185e0132578733d02802dfddfbbc35f42be23a45ff49ccae5081f25952238117", size = 42251098, upload-time = "2026-01-30T15:09:30.461Z" },
{ url = "https://files.pythonhosted.org/packages/f1/af/009958cbf23fac551a940d34e3206e6c7eed2b8c940d0c3afd1feb0b0589/playwright-1.58.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:c95568ba1eda83812598c1dc9be60b4406dffd60b149bc1536180ad108723d6b", size = 46235268, upload-time = "2026-01-30T15:09:33.787Z" },
{ url = "https://files.pythonhosted.org/packages/d9/a6/0e66ad04b6d3440dae73efb39540c5685c5fc95b17c8b29340b62abbd952/playwright-1.58.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f9999948f1ab541d98812de25e3a8c410776aa516d948807140aff797b4bffa", size = 45964214, upload-time = "2026-01-30T15:09:36.751Z" },
{ url = "https://files.pythonhosted.org/packages/0e/4b/236e60ab9f6d62ed0fd32150d61f1f494cefbf02304c0061e78ed80c1c32/playwright-1.58.0-py3-none-win32.whl", hash = "sha256:1e03be090e75a0fabbdaeab65ce17c308c425d879fa48bb1d7986f96bfad0b99", size = 36815998, upload-time = "2026-01-30T15:09:39.627Z" },
{ url = "https://files.pythonhosted.org/packages/41/f8/5ec599c5e59d2f2f336a05b4f318e733077cd5044f24adb6f86900c3e6a7/playwright-1.58.0-py3-none-win_amd64.whl", hash = "sha256:a2bf639d0ce33b3ba38de777e08697b0d8f3dc07ab6802e4ac53fb65e3907af8", size = 36816005, upload-time = "2026-01-30T15:09:42.449Z" },
{ url = "https://files.pythonhosted.org/packages/c8/c4/cc0229fea55c87d6c9c67fe44a21e2cd28d1d558a5478ed4d617e9fb0c93/playwright-1.58.0-py3-none-win_arm64.whl", hash = "sha256:32ffe5c303901a13a0ecab91d1c3f74baf73b84f4bedbb6b935f5bc11cc98e1b", size = 33085919, upload-time = "2026-01-30T15:09:45.71Z" },
]
[[package]]
name = "pluggy"
version = "1.6.0"
@ -1812,18 +1758,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c9/c7/68f2553819965326f968375f02597d49efe71b309ba9d8fef539aeb51c48/pydocket-0.17.7-py3-none-any.whl", hash = "sha256:d1e0921ac02026c4a0140fc72a3848545f3e91e6e74c6e32c588489017c130b2", size = 94608, upload-time = "2026-02-11T21:01:30.111Z" },
]
[[package]]
name = "pyee"
version = "13.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/95/03/1fd98d5841cd7964a27d729ccf2199602fe05eb7a405c1462eb7277945ed/pyee-13.0.0.tar.gz", hash = "sha256:b391e3c5a434d1f5118a25615001dbc8f669cf410ab67d04c4d4e07c55481c37", size = 31250, upload-time = "2025-03-17T18:53:15.955Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9b/4d/b9add7c84060d4c1906abe9a7e5359f2a60f7a9a4f67268b2766673427d8/pyee-13.0.0-py3-none-any.whl", hash = "sha256:48195a3cddb3b1515ce0695ed76036b5ccc2ef3a9f963ff9f77aec0139845498", size = 15730, upload-time = "2025-03-17T18:53:14.532Z" },
]
[[package]]
name = "pygments"
version = "2.19.2"