[project] name = "postfader-fl-studio-mcp" version = "10.0.0" description = "AI copilot for FL Studio project control, mix analysis, plug-ins, composition, and arrangement" readme = "README.md" license = "Apache-2.0" license-files = ["LICENSE", "NOTICE"] requires-python = ">=3.10,<3.15" keywords = ["fl-studio", "mcp", "model-context-protocol", "midi", "music-production", "mixing", "composition"] classifiers = [ "Development Status :: 4 - Beta", "Environment :: MacOS X", "Intended Audience :: Developers", "Intended Audience :: End Users/Desktop", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows :: Windows 11", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Topic :: Multimedia :: Sound/Audio", ] dependencies = [ "anyio>=4.5,<5", "mcp>=2.0.0,<2.1", "jsonschema>=4.20,<5", "numpy>=1.24,<3", # SciPy 1.15's PROPACK wheel fails to load on recent macOS dyld; the # fixed newer releases require Python >=3.11. Keep 3.10 installs working. "scipy>=1.10,<1.15; sys_platform == 'darwin' and python_version < '3.11'", "scipy>=1.10,<2; sys_platform != 'darwin' or python_version >= '3.11'", "soundfile>=0.12,<1", "pyloudnorm>=0.1.1,<1", "python-rtmidi>=1.5,<2", "pydantic>=2.12,<3", ] [project.scripts] fl-studio-mcp = "fl_studio_mcp.mcp_server:main" postfader = "fl_studio_mcp.cli:main" postfader-install-bridge = "fl_studio_mcp.bridge_install:main" postfader-doctor = "fl_studio_mcp.diagnostics:main" postfader-plugin-report = "fl_studio_mcp.plugin_report:main" postfader-plugin-atlas = "fl_studio_mcp.plugin_atlas.cli:main" postfader-setup = "fl_studio_mcp.setup_wizard:main" [project.optional-dependencies] test = [ "tomli>=2; python_version < '3.11'", ] dev = [ "coverage>=7.6,<8", "fl-studio-api-stubs>=37,<38", "pyright>=1.1.390,<2", "tomli>=2; python_version < '3.11'", "ruff>=0.9,<1", ] [project.urls] Homepage = "https://github.com/synopsys0/postfader-fl-studio-mcp" Repository = "https://github.com/synopsys0/postfader-fl-studio-mcp" Issues = "https://github.com/synopsys0/postfader-fl-studio-mcp/issues" Documentation = "https://github.com/synopsys0/postfader-fl-studio-mcp#readme" [build-system] # 77 is the first release to understand PEP 639 `license`/`license-files`. # Building with anything older silently drops the license metadata. requires = ["setuptools>=77"] build-backend = "setuptools.build_meta" [tool.setuptools] include-package-data = false packages = [ "fl_studio_mcp", "fl_studio_mcp._bridge", "fl_studio_mcp.creation_pipeline", "fl_studio_mcp.creation_review", "fl_studio_mcp.plugin_atlas", "fl_studio_mcp.plugin_atlas_data", "fl_studio_mcp.sound_selection", "fl_studio_mcp.sound_selection.data", ] [tool.setuptools.package-data] # Atlas knowledge is deliberately data, not Python constants. Keep every # machine-readable shard in the installed wheel; the loader resolves the # manifest's relative paths through importlib.resources. "fl_studio_mcp.plugin_atlas_data" = ["*.json", "**/*.json"] "fl_studio_mcp.sound_selection.data" = ["*.json", "**/*.json"] # The bridge directory intentionally has no __init__.py and no runtime module # imports it: the controller body is executable only inside FL Studio. List the # namespace explicitly so setuptools treats its .py source as an intentional # package member instead of deprecated, implicitly included package data. [tool.ruff] # Ruff is a lint/import/error gate only for now. Formatting is intentionally # not enabled: the existing project uses stable hand-formatted source and a # formatter-wide rewrite would obscure safety-relevant review. target-version = "py310" extend-exclude = ["tests", "fl_studio_mcp/_bridge"] [tool.ruff.lint] # Keep the first CI baseline deliberately small: pycodestyle's syntax/error # subsets, pyflakes, and import sorting catch actionable defects without # turning existing style into an unrelated repository-wide diff. select = ["E4", "E7", "E9", "F", "I"] [tool.ruff.lint.isort] # Existing imports intentionally preserve the project's case-sensitive public # contract names (for example, SCHEMA_VERSION before ContractModel). case-sensitive = true # Keep the established two-line visual separation before top-level constants # and declarations; this is import layout, not a formatter-wide rewrite. lines-after-imports = 2 [tool.ruff.lint.per-file-ignores] # These files predate the import gate and intentionally use the repository's # one-line separation before a comment or declaration. Keep the exception # file-scoped so new modules still receive I001 coverage. "fl_studio_mcp/audio.py" = ["I001"] "fl_studio_mcp/bridge_client.py" = ["I001"] "fl_studio_mcp/bridge_install.py" = ["I001"] "fl_studio_mcp/creative.py" = ["I001"] "fl_studio_mcp/mcp_server.py" = ["I001"] "fl_studio_mcp/mixing.py" = ["I001"] "fl_studio_mcp/music_analysis.py" = ["I001"] "fl_studio_mcp/performance.py" = ["I001"] "fl_studio_mcp/readonly_inspector.py" = ["I001"] "fl_studio_mcp/track_b_contracts.py" = ["I001"] "fl_studio_mcp/verified_writer.py" = ["I001"] "fl_studio_mcp/workflows.py" = ["I001"] "scripts/build_mcpb.py" = ["I001"] "scripts/build_release_bundles.py" = ["I001"] "scripts/doctor.py" = ["I001"] "scripts/generate_mcp_config.py" = ["I001"] "scripts/inspect_readonly.py" = ["I001"] "scripts/live_note_acceptance.py" = ["I001"] "scripts/live_read_acceptance.py" = ["I001"] "scripts/live_write_acceptance.py" = ["I001"] "scripts/plugin_report.py" = ["I001"] "scripts/validate_selection_readonly.py" = ["I001"] "scripts/validate_writes.py" = ["I001"] "scripts/verify_distribution.py" = ["I001"] [tool.coverage.run] # The safe suite runs each test in a fresh child. The wrapper combines the # parallel data files after all children exit. Source coverage intentionally # measures the Python package only; test harnesses and the FL-only controller # are not product-runtime coverage targets. branch = true parallel = true source = ["fl_studio_mcp"] omit = ["*/_bridge/device_UniversalBridge.py"] [tool.coverage.report] exclude_also = [ "if TYPE_CHECKING:", "if __name__ == .__main__.:", "pragma: no cover", ] precision = 1