[build-system] requires = ["hatchling"] build-backend = "hatchling.build" [project] name = "swiss-procurement-mcp" version = "0.18.5" description = "MCP server for the simap.ch Swiss public procurement API — all cantons, read-only" readme = "README.md" license = "MIT" requires-python = ">=3.10" authors = [{ name = "malkreide" }] keywords = ["mcp", "model-context-protocol", "llm", "swiss-open-data", "procurement", "simap"] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", ] dependencies = [ # Bounded to the 2.x line. The upper bound stays on principle: `mcp` 2.0.0 # removed `mcp.server.fastmcp` outright with no deprecation shim, and an # unbounded requirement turned every CI run red the day it was published. # A major version is a decision to take on purpose, not one a resolver # should make on a random Tuesday. "mcp>=2.0.0,<3", "httpx>=0.27", "pydantic>=2.7", # OBS-003: structured logging with contextvars-bound correlation ids. "structlog>=24.1,<27", # SDK-004: the HTTP transports build their own Starlette app so CORS can be # attached. Both arrive transitively via `mcp`, but this module imports them # directly, and an undeclared import is one upstream dependency change away # from an ImportError at startup. "starlette>=0.37,<2", "uvicorn>=0.30", ] [project.optional-dependencies] dev = ["pytest>=8.0", "pytest-asyncio>=0.23", "respx>=0.21", "ruff==0.16.5"] [project.scripts] swiss-procurement-mcp = "swiss_procurement_mcp.__main__:main" [project.urls] Homepage = "https://github.com/malkreide/swiss-procurement-mcp" Repository = "https://github.com/malkreide/swiss-procurement-mcp" Changelog = "https://github.com/malkreide/swiss-procurement-mcp/blob/main/CHANGELOG.md" Issues = "https://github.com/malkreide/swiss-procurement-mcp/issues" [tool.hatch.build.targets.wheel] packages = ["src/swiss_procurement_mcp"] [tool.pytest.ini_options] asyncio_mode = "auto" testpaths = ["tests"] markers = ["live: hits the real simap.ch API (excluded from CI)"] [tool.ruff] line-length = 100 # Target the interpreter versions the project supports. target-version = "py310" [tool.ruff.lint] # Explicit rule set so a `ruff` upgrade in CI can't silently change what is # enforced. A config `select` fully replaces ruff's built-in defaults, so the # enforced set is exactly what is listed here regardless of the ruff version: # E4/E7/E9 — pycodestyle errors (line length E501 is left to the formatter) # F — Pyflakes # I — import sorting (isort) # UP — pyupgrade (modern syntax, e.g. `X | None`, collections.abc) # B — flake8-bugbear # C4 — flake8-comprehensions # SIM — flake8-simplify # RUF — ruff-specific rules select = ["E4", "E7", "E9", "F", "I", "UP", "B", "C4", "SIM", "RUF"] # The user-facing strings are German and use intentional typography — em/en # dashes (—, –), arrows (→) and the like. The ambiguous-unicode rules would # flag those as look-alikes, so they are disabled for this codebase. ignore = ["RUF001", "RUF002", "RUF003"]