31eb45c3f8
Foundation for multi-user operation (Phase 1): - crypto.py: AES-256-GCM encrypt/decrypt for the per-user Intervals API key, key from INTERVALS_ENC_KEY (base64 32 bytes). Random nonce per message. - db/models.py + db/session.py: SQLAlchemy 2.0 async User model (keyed on the Authentik sub; api_key stored encrypted; enabled = admin-approval gate) and a lazy async engine/sessionmaker from DATABASE_URL. - store.py: async CRUD. New users created disabled; login never flips enabled; get_active_credentials returns decrypted creds only for an enabled user that has them. - credentials.py: resolve_caller_credentials() maps get_access_token().subject to that user's stored creds, falling back to env config only when unauthenticated (stdio/local). - Tests (SQLite in-memory, no infra): crypto round-trip/tamper, store gating, resolver paths. Suite 199 passing at 90.4% (gate holds). Deps: sqlalchemy[asyncio], asyncpg, alembic (+ aiosqlite for tests). Pin py3.12. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
127 lines
3.2 KiB
TOML
127 lines
3.2 KiB
TOML
[build-system]
|
|
requires = ["hatchling"]
|
|
build-backend = "hatchling.build"
|
|
|
|
[project]
|
|
name = "intervalsicu-mcp"
|
|
version = "0.1.0"
|
|
description = "A Model Context Protocol server for Intervals.icu (FastMCP, native OAuth)"
|
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
requires-python = ">=3.12"
|
|
license = { text = "GPL-3.0-only" }
|
|
authors = [{ name = "Marc Vilanova", email = "barker-riddle.8z@icloud.com" }]
|
|
dependencies = [
|
|
"mcp[cli]>=1.28.1",
|
|
"httpx>=0.25.0",
|
|
"python-dotenv>=1.0.0",
|
|
"pyjwt[crypto]>=2.8.0",
|
|
"sqlalchemy[asyncio]>=2.0.30",
|
|
"asyncpg>=0.29",
|
|
"alembic>=1.13",
|
|
]
|
|
classifiers = [
|
|
"Development Status :: 3 - Alpha",
|
|
"Intended Audience :: Developers",
|
|
"Programming Language :: Python :: 3",
|
|
"Programming Language :: Python :: 3.12",
|
|
"Topic :: Software Development :: Libraries",
|
|
]
|
|
keywords = ["intervals", "cycling", "running", "mcp", "ai"]
|
|
|
|
[project.urls]
|
|
"Homepage" = "https://git.farh.net/farhoodlabs/intervalsicu-mcp"
|
|
"Bug Tracker" = "https://git.farh.net/farhoodlabs/intervalsicu-mcp/issues"
|
|
"Upstream" = "https://github.com/mvilanova/intervals-mcp-server"
|
|
|
|
[project.optional-dependencies]
|
|
dev = ["pytest>=8.3.5", "mypy>=1.0.0", "ruff>=0.1.0", "pytest-asyncio>=0.21", "pre-commit", "hatch", "pytest-mock==3.12.0", "pytest-cov>=5.0", "cryptography>=42.0", "aiosqlite>=0.20"]
|
|
|
|
[tool.hatch.build]
|
|
include = ["server.py", "utils/*.py", "README.md", ".env.example"]
|
|
|
|
[tool.hatch.build.targets.wheel]
|
|
packages = ["src/intervals_mcp_server"]
|
|
|
|
[tool.hatch.envs.default.scripts]
|
|
test = "pytest -q"
|
|
|
|
|
|
|
|
[tool.ruff]
|
|
# Exclude a variety of commonly ignored directories.
|
|
exclude = [
|
|
".bzr",
|
|
".direnv",
|
|
".eggs",
|
|
".git",
|
|
".hg",
|
|
".mypy_cache",
|
|
".nox",
|
|
".pants.d",
|
|
".ruff_cache",
|
|
".svn",
|
|
".tox",
|
|
".venv",
|
|
"__pypackages__",
|
|
"_build",
|
|
"buck-out",
|
|
"build",
|
|
"dist",
|
|
"node_modules",
|
|
"venv",
|
|
]
|
|
line-length = 100
|
|
target-version = "py311"
|
|
|
|
[tool.ruff.lint]
|
|
# Select specific errors and warnings
|
|
select = [
|
|
"E", # pycodestyle errors
|
|
"W", # pycodestyle warnings
|
|
"F", # pyflakes
|
|
# "I", # isort
|
|
"C", # flake8-comprehensions
|
|
"B", # flake8-bugbear
|
|
]
|
|
|
|
# Ignore specific errors and warnings
|
|
ignore = [
|
|
"E501", # line too long, handled by black
|
|
"B008", # do not perform function calls in argument defaults
|
|
"C901", # complexity
|
|
"G004", # Ignore f-string in logging
|
|
]
|
|
|
|
# Allow autofix for all enabled rules (when `--fix`) is provided.
|
|
fixable = ["A", "B", "C", "D", "E", "F"]
|
|
|
|
# No unfixable rules.
|
|
unfixable = []
|
|
|
|
# Allow unused variables when underscore-prefixed.
|
|
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
|
|
|
isort.known-third-party = ["starlette"]
|
|
|
|
# Unlike Flake8, default to a complexity level of 10.
|
|
mccabe.max-complexity = 10
|
|
|
|
[tool.pylint.design]
|
|
max-args = 8
|
|
max-positional-arguments = 8
|
|
|
|
[tool.typos]
|
|
default.check-filename = true
|
|
default.check-file = true
|
|
default.unicode = true
|
|
default.locale = "en-us"
|
|
|
|
[tool.pytest.ini_options]
|
|
addopts = "-q --cov=intervals_mcp_server --cov-report=term-missing --cov-fail-under=90"
|
|
testpaths = ["tests"]
|
|
python_files = "test_*.py"
|
|
asyncio_default_fixture_loop_scope = "function"
|
|
|
|
[tool.uv]
|
|
index-url = "https://pypi.org/simple"
|