[project]
name = "igloo_mcp"
version = "0.4.1"
description = "Igloo MCP - Snowflake MCP Server for agentic native workflows"
readme = "README.md"
authors = [
{ name = "Evan Kim", email = "ekcopersonal@gmail.com" }
]
requires-python = ">=3.12,<3.14"
keywords = ["snowflake", "mcp", "ai", "data", "analytics", "model-context-protocol"]
dependencies = [
"rich>=13.0.0",
"pyyaml>=6.0.0",
"snowflake-cli>=2.0.0",
"sqlglot>=27.16.3",
"pyjwt>=2.9.0",
"pyvis>=0.3.2",
"networkx>=3.0",
"websockets>=15.0.1",
"mcp>=1.0.0",
"fastmcp>=2.8.1",
"snowflake-labs-mcp>=1.3.3",
"pydantic>=2.7.0",
"jinja2>=3.1.0",
"markdown>=3.5,<4.0",
]
# MCP dependencies are included by default - no separate installation needed
[project.scripts]
# Primary entry point: MCP server for AI assistant integration
igloo_mcp = "igloo_mcp.mcp_server:main"
# Administrative entry point: CLI tools for power users and system administrators
igloo = "igloo_mcp.cli:main"
[project.urls]
Homepage = "https://github.com/Evan-Kim2028/igloo-mcp"
Repository = "https://github.com/Evan-Kim2028/igloo-mcp"
Documentation = "https://github.com/Evan-Kim2028/igloo-mcp#readme"
[build-system]
requires = ["uv_build>=0.8.15,<0.9.0"]
build-backend = "uv_build"
[tool.uv]
dev-dependencies = [
"pytest>=7.0.0",
"pytest-asyncio>=0.21.0",
"pytest-mock>=3.10.0",
"pytest-xdist>=3.5.0",
"ruff>=0.6.0",
"mypy>=1.0.0",
"pre-commit>=3.0.0",
"types-pyyaml>=6.0.12.20250822",
"inline-snapshot>=0.10.1",
"dirty-equals>=0.7.0",
"pytest-examples>=0.0.11",
"types-networkx>=3.2.0.20240731",
"pytest-cov>=7.0.0",
"trio>=0.26.0",
"hypothesis>=6.90.0",
# Phase 2: Import linting
"import-linter>=2.0",
"types-markdown>=3.10.0.20251106",
]
[tool.ruff]
line-length = 120
target-version = "py312"
[tool.ruff.lint]
# Phase 1: Baseline + Security + Code Quality
select = [
"E", # pycodestyle errors
"F", # Pyflakes
"I", # isort
# Phase 1: Security
"S", # flake8-bandit (security)
"UP", # pyupgrade (modernize Python syntax)
"SIM", # flake8-simplify
"RUF", # Ruff-specific rules
"B", # flake8-bugbear (bug detection)
"C4", # flake8-comprehensions
"PT", # flake8-pytest-style
# Phase 2: Import organization
"TID", # flake8-tidy-imports (ban relative imports)
# v0.3.7: Exception handling
"BLE", # flake8-blind-except (catch silent failures)
]
ignore = [
"S101", # assert statements allowed in tests
"S603", # subprocess without shell validation (manually validated)
"S607", # partial executable path (manually validated)
"B008", # function call in argument defaults (FastMCP pattern)
]
[tool.ruff.lint.per-file-ignores]
# Test files can use additional patterns
"tests/**" = ["S101", "S105", "S106", "S108", "S301", "S608", "PT011", "PT012", "PT017", "B017"]
# Best-effort operations that should not fail main workflow
# These catch Exception for resilience but log warnings
"src/igloo_mcp/cache/query_result_cache.py" = ["BLE001", "S110"] # Cache failures shouldn't break queries
"src/igloo_mcp/logging/query_history.py" = ["BLE001", "S110"] # History logging is best-effort
"src/igloo_mcp/cli.py" = ["BLE001"] # CLI should show user-friendly errors, not crash
"src/igloo_mcp/living_reports/storage.py" = ["BLE001", "S110"] # File I/O failures logged but don't crash
"src/igloo_mcp/error_handling.py" = ["BLE001"] # Error handling itself must be resilient
"src/igloo_mcp/living_reports/service.py" = ["BLE001", "S110"] # Service layer has best-effort operations
"src/igloo_mcp/catalog/catalog_service.py" = ["BLE001"] # Catalog operations are best-effort
"src/igloo_mcp/dependency/dependency_service.py" = ["BLE001"] # Dependency graph is best-effort
"src/igloo_mcp/living_reports/renderers/html_standalone.py" = ["BLE001"] # Chart embedding is best-effort
"src/igloo_mcp/living_reports/index.py" = ["BLE001", "S110"] # Index operations are best-effort (already fixed #115 with logging)
"src/igloo_mcp/mcp/tools/base.py" = ["BLE001"] # Tool error handling must be resilient
"src/igloo_mcp/mcp/tools/build_catalog.py" = ["BLE001", "S110"] # Catalog building is best-effort
"src/igloo_mcp/mcp/tools/evolve_report.py" = ["BLE001"] # Report evolution has best-effort validations
"src/igloo_mcp/mcp/tools/execute_query.py" = ["S110", "B904", "SIM105", "BLE001"] # Query execution has resilient error paths # BLE001: lines 1024,1535 are legitimate broad catches
"src/igloo_mcp/sql_validation.py" = ["BLE001", "S110"] # SQL parsing failures are expected fallback behavior
"src/igloo_mcp/mcp/tools/get_catalog_summary.py" = ["BLE001", "S110"] # Catalog operations are best-effort
"src/igloo_mcp/mcp/tools/health.py" = ["BLE001"] # Health checks are best-effort diagnostics
"src/igloo_mcp/mcp/tools/render_report.py" = ["BLE001", "S110"] # Report rendering is best-effort
"src/igloo_mcp/mcp/tools/search_catalog.py" = ["BLE001", "S110"] # Catalog search is best-effort
"src/igloo_mcp/mcp/tools/search_citations.py" = ["BLE001"] # Citation search is best-effort
"src/igloo_mcp/mcp/validation_helpers.py" = ["BLE001"] # Validation helpers have fallback behavior
"src/igloo_mcp/mcp_health.py" = ["BLE001"] # Health monitoring is best-effort
"src/igloo_mcp/mcp_resources.py" = ["BLE001"] # Resource loading is best-effort
"src/igloo_mcp/mcp_server.py" = ["BLE001"] # Server lifecycle has resilient error handling
# Phase 2: Ban relative parent imports (allow same-package imports)
[tool.ruff.lint.flake8-tidy-imports]
ban-relative-imports = "parents"
[tool.ruff.format]
# Like Black, use double quotes for strings
quote-style = "double"
# Like Black, indent with spaces, rather than tabs
indent-style = "space"
# Like Black, respect magic trailing commas
skip-magic-trailing-comma = false
# Like Black, automatically detect the appropriate line ending
line-ending = "auto"
[tool.pytest.ini_options]
# Pytest configuration
minversion = "7.0"
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
# Markers for test categorization
markers = [
"requires_snowflake: marks tests requiring live Snowflake connection (skipped unless --snowflake)",
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
"integration: marks integration tests (cross-component)",
"system: marks system tests (end-to-end workflows)",
"unit: marks unit tests (fast, isolated)",
"performance: marks performance and scale tests",
# Phase 3.6: Integration test categories
"integration_catalog: marks catalog integration tests (build + search workflow)",
"integration_reports: marks living reports integration tests (create + evolve + render)",
"integration_query: marks query execution integration tests (execute + cache + history)",
# Phase 3.6: System test categories
"system_discovery: marks data discovery system tests (test → catalog → query)",
"system_reporting: marks reporting system tests (create → evolve → search → render)",
# Phase 3.6: Test quality markers
"regression: marks regression tests for specific bug fixes",
"property_based: marks property-based tests (Hypothesis)",
]
# Default options (can override with CLI flags)
addopts = [
"-ra", # Show summary of all test outcomes
"--strict-markers", # Raise error on unknown markers
"--strict-config", # Raise error on config issues
"--showlocals", # Show local variables in tracebacks
]
# Asyncio configuration
asyncio_mode = "auto"
[tool.coverage.run]
# Coverage configuration
source = ["src/igloo_mcp"]
branch = true
parallel = true
omit = [
"*/tests/*",
"*/test_*.py",
"*/__pycache__/*",
"*/site-packages/*",
]
[tool.coverage.report]
# Coverage reporting configuration
precision = 2
show_missing = true
skip_covered = false
fail_under = 80 # Phase 3.6: Require 80% minimum coverage
exclude_lines = [
"pragma: no cover",
"def __repr__",
"raise AssertionError",
"raise NotImplementedError",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
"class .*\\bProtocol\\):",
"@(abc\\.)?abstractmethod",
]
[tool.coverage.html]
directory = "htmlcov"
[tool.coverage.json]
output = "coverage.json"
# Phase 2: Import-linter - Enforce layer architecture
[tool.importlinter]
root_packages = ["igloo_mcp"]
# Layer architecture contract commented out due to shared descendants
# We'll enforce via forbidden contracts instead
# [[tool.importlinter.contracts]]
# name = "Layer architecture"
# type = "layers"
# layers = [...]
[[tool.importlinter.contracts]]
name = "No circular dependencies between living_reports and mcp"
type = "forbidden"
source_modules = ["igloo_mcp.living_reports"]
forbidden_modules = ["igloo_mcp.mcp.tools"]
[[tool.importlinter.contracts]]
name = "Service layer isolation"
type = "independence"
modules = [
"igloo_mcp.service_layer.catalog_service",
"igloo_mcp.service_layer.query_service",
"igloo_mcp.service_layer.dependency_service",
]