The regex-based validator worked for everything tested, but had a class of structural blindspot: it didn't actually know what a token was, so it accepted `SELECT 1; SELECT 2` (no forbidden keyword in either statement) and relied entirely on the keyword scan catching write verbs. With sqlparse we get: - Explicit multi-statement detection via `len(sqlparse.parse(query))` — `SELECT 1; SELECT 2` is now refused with a clear "Multiple statements detected" message. - Proper string/comment boundary handling — `'log: DROP detected'` is one Literal.String token; the DROP inside it never reaches the forbidden-keyword scan. `inserted_at` is one Name token; INSERT isn't matched as a substring. - Same conservative behavior for keywords-as-identifiers (sqlparse is a lexer, not a parser, so `SELECT delete FROM device` is still refused — CUCM's data dictionary doesn't use SQL keywords as column names anyway). Hamilton review CRITICAL #1 preserved: the cleaned query returned to the caller is still byte-for-byte the input (modulo trailing ; and outer whitespace). sqlparse is consulted for analysis only. Tests: +6 sqlparse-specific cases in TestSqlparseSpecific covering multi-statement, comment-disguised injection, keyword-substring identifiers, and CTE walks. 2 existing tests broadened from match="DROP" to match="DROP|Multiple" — same query refused, the diagnosis just got more accurate (multi-statement caught earlier than forbidden-keyword scan). 36/36 validator tests pass.
95 lines
3.2 KiB
TOML
95 lines
3.2 KiB
TOML
[project]
|
|
name = "mcaxl"
|
|
version = "2026.04.27.1"
|
|
description = "Read-only MCP server for Cisco Unified Communications Manager (CUCM) — AXL SOAP API + RisPort70 registration state — purpose-built for LLM-driven dial-plan and configuration auditing."
|
|
authors = [{name = "Ryan Malloy", email = "ryan@supported.systems"}]
|
|
readme = "README.md"
|
|
license = {text = "MIT"}
|
|
requires-python = ">=3.11"
|
|
keywords = [
|
|
"mcp", "cisco", "cucm", "axl", "risport",
|
|
"voip", "sip", "audit", "telephony",
|
|
]
|
|
classifiers = [
|
|
"Development Status :: 4 - Beta",
|
|
"Environment :: Console",
|
|
"Intended Audience :: System Administrators",
|
|
"Intended Audience :: Telecommunications Industry",
|
|
"License :: OSI Approved :: MIT License",
|
|
"Operating System :: OS Independent",
|
|
"Programming Language :: Python :: 3",
|
|
"Programming Language :: Python :: 3.11",
|
|
"Programming Language :: Python :: 3.12",
|
|
"Programming Language :: Python :: 3.13",
|
|
"Topic :: Communications :: Telephony",
|
|
"Topic :: System :: Networking :: Monitoring",
|
|
]
|
|
|
|
dependencies = [
|
|
"fastmcp>=3.2",
|
|
"zeep>=4.3",
|
|
"platformdirs>=4.9",
|
|
"numpy>=1.26",
|
|
"python-dotenv>=1.0",
|
|
# SQL tokenizer for the executeSQLQuery validator. Using a real lexer
|
|
# (instead of regex keyword scanning) gives us proper string/comment
|
|
# boundary handling and explicit multi-statement detection. See
|
|
# src/mcaxl/sql_validator.py for the read-only enforcement layer.
|
|
"sqlparse>=0.5",
|
|
]
|
|
|
|
[project.optional-dependencies]
|
|
test = [
|
|
"pytest>=8.0",
|
|
"pytest-asyncio>=0.24",
|
|
]
|
|
|
|
[project.scripts]
|
|
mcaxl = "mcaxl.server:main"
|
|
|
|
[project.urls]
|
|
Homepage = "https://git.supported.systems/mcp/mcaxl"
|
|
Source = "https://git.supported.systems/mcp/mcaxl"
|
|
Issues = "https://git.supported.systems/mcp/mcaxl/issues"
|
|
Changelog = "https://git.supported.systems/mcp/mcaxl/src/branch/main/CHANGELOG.md"
|
|
|
|
[build-system]
|
|
requires = ["hatchling"]
|
|
build-backend = "hatchling.build"
|
|
|
|
[tool.hatch.build.targets.wheel]
|
|
packages = ["src/mcaxl"]
|
|
|
|
[tool.hatch.build.targets.sdist]
|
|
# Keep the published source distribution focused on what's needed to
|
|
# build / install / run. Excluded files exist for local development only.
|
|
#
|
|
# IMPORTANT: this list is the last line of defense for PII leakage.
|
|
# `tests/` contains real cluster fixtures; `.mcp.json` contains a
|
|
# local filesystem path; `audits/` contains cluster-specific findings.
|
|
# Pre-publish workflow: extract the sdist to /tmp and grep for any
|
|
# site-specific tokens (your org name, internal IP ranges, hostnames)
|
|
# across the full unpacked tree — MUST return empty before publish.
|
|
exclude = [
|
|
"CLAUDE.md", # operator-private project context for Claude Code
|
|
".env", # never ship credentials
|
|
".env.local",
|
|
".mcp.json", # contains local filesystem path; dev-only artifact
|
|
"axlsqltoolkit.zip", # Cisco-licensed; do not redistribute
|
|
"audits/", # cluster-specific audit reports
|
|
"docs/", # Astro docs site — node_modules, build artifacts, dev container scaffolding
|
|
"tests/", # tests live in source repo, not the sdist
|
|
".pytest_cache/",
|
|
".ruff_cache/",
|
|
"dist/",
|
|
"build/",
|
|
]
|
|
|
|
[tool.ruff]
|
|
line-length = 100
|
|
target-version = "py311"
|
|
|
|
[tool.pytest.ini_options]
|
|
testpaths = ["tests"]
|
|
asyncio_mode = "auto"
|