[project] name = "medium-mcp" version = "3.1.0" description = "MCP server for Medium article scraping with multi-tier extraction" readme = "README.md" license = { text = "MIT" } requires-python = ">=3.11" authors = [{ name = "T0X1N" }] keywords = ["mcp", "medium", "scraper", "ai", "llm"] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", ] dependencies = [ "fastmcp>=0.2.0", "playwright>=1.40.0", "beautifulsoup4>=4.12.0", "httpx>=0.25.0", "aiohttp>=3.9.0", "lxml>=5.0.0", "html5lib>=1.1", "markdownify>=0.11.0", "fake-useragent>=1.4.0", "curl-cffi>=0.6.0", "groq>=0.4.0", "google-generativeai>=0.3.0", "openai>=1.0.0", "elevenlabs>=1.0.0", "gradio>=4.0.0", "python-dotenv>=1.0.0", "structlog>=23.0.0", "pydantic>=2.0.0", "pydantic-settings>=2.0.0", "sqlite-utils>=3.35.0", ] [project.optional-dependencies] dev = [ "pytest>=7.4.0", "pytest-asyncio>=0.23.0", "pytest-cov>=4.1.0", "pytest-xdist>=3.5.0", "ruff>=0.8.0", "mypy>=1.13.0", "pre-commit>=3.6.0", "pip-audit>=2.7.0", "bandit>=1.7.0", "types-beautifulsoup4>=4.12.0", ] [project.urls] Homepage = "https://github.com/N1KH1LT0X1N/Medium-Agent" Repository = "https://github.com/N1KH1LT0X1N/Medium-Agent" [build-system] requires = ["hatchling"] build-backend = "hatchling.build" # ============================================================================ # RUFF - Linting & Formatting # ============================================================================ [tool.ruff] line-length = 100 target-version = "py311" exclude = [ ".git", ".venv", "__pycache__", "*.egg-info", ".skills", "tests/fixtures", ] [tool.ruff.lint] select = [ "E", # pycodestyle errors "F", # pyflakes "I", # isort "N", # pep8-naming "W", # pycodestyle warnings "B", # flake8-bugbear "C4", # flake8-comprehensions "UP", # pyupgrade "ASYNC", # flake8-async "S", # flake8-bandit (security) "T20", # flake8-print ] ignore = [ "E501", # Line length (handled by formatter) "S101", # assert usage (fine in tests) "B008", # function call in default arg (Pydantic) ] [tool.ruff.lint.isort] known-first-party = ["src", "mcp", "ui"] force-single-line = false lines-after-imports = 2 [tool.ruff.format] quote-style = "double" indent-style = "space" skip-magic-trailing-comma = false line-ending = "auto" # ============================================================================ # MYPY - Type Checking # ============================================================================ [tool.mypy] python_version = "3.11" strict = true warn_return_any = true warn_unused_ignores = true disallow_untyped_defs = true disallow_incomplete_defs = true check_untyped_defs = true disallow_untyped_decorators = false # Allow Gradio decorators no_implicit_optional = true warn_redundant_casts = true warn_unused_configs = true show_error_codes = true plugins = ["pydantic.mypy"] [[tool.mypy.overrides]] module = "tests.*" disallow_untyped_defs = false disallow_incomplete_defs = false [[tool.mypy.overrides]] module = [ "playwright.*", "elevenlabs.*", "fake_useragent.*", "curl_cffi.*", "gradio.*", "groq.*", "google.generativeai.*", "sqlite_utils.*", "markdownify.*", ] ignore_missing_imports = true # ============================================================================ # PYTEST - Testing # ============================================================================ [tool.pytest.ini_options] asyncio_mode = "auto" testpaths = ["tests"] python_files = ["test_*.py"] python_functions = ["test_*"] addopts = [ "-v", "--tb=short", "--cov=src", "--cov-report=term-missing", "--cov-report=xml", "-x", # Stop on first failure ] filterwarnings = [ "ignore::DeprecationWarning", "ignore::PendingDeprecationWarning", ] markers = [ "slow: marks tests as slow (deselect with '-m \"not slow\"')", "integration: marks integration tests", "e2e: marks end-to-end tests", ] # ============================================================================ # COVERAGE # ============================================================================ [tool.coverage.run] branch = true source = ["src"] omit = [ "tests/*", "*/__pycache__/*", "*/site-packages/*", ] [tool.coverage.report] exclude_lines = [ "pragma: no cover", "def __repr__", "raise NotImplementedError", "if TYPE_CHECKING:", "if __name__ == .__main__.:", ] fail_under = 60 show_missing = true # ============================================================================ # BANDIT - Security # ============================================================================ [tool.bandit] exclude_dirs = ["tests", ".venv", ".skills"] skips = ["B101"] # assert usage