From 5a26dce075757128829b66e367e0a930831c775d Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Tue, 17 Feb 2026 18:18:31 -0700 Subject: [PATCH] Rename packages for PyPI: winegard-birdcage + mcbirdcage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Core library published as winegard-birdcage (import stays `birdcage`). MCP server renamed birdcage-mcp → mcbirdcage, matching mcserial pattern. Both packages live on PyPI — `uvx mcbirdcage` works out of the box. --- CLAUDE.md | 24 ++++-- mcp/pyproject.toml | 23 +++-- mcp/src/birdcage_mcp/__init__.py | 1 - mcp/src/mcbirdcage/__init__.py | 1 + .../{birdcage_mcp => mcbirdcage}/prompts.py | 0 .../{birdcage_mcp => mcbirdcage}/resources.py | 0 .../{birdcage_mcp => mcbirdcage}/server.py | 8 +- mcp/src/{birdcage_mcp => mcbirdcage}/state.py | 0 .../tools/__init__.py | 0 .../tools/connection.py | 2 +- .../tools/console.py | 2 +- .../tools/movement.py | 2 +- .../tools/satellite.py | 2 +- .../tools/signal.py | 2 +- .../tools/system.py | 2 +- mcp/tests/conftest.py | 8 +- mcp/uv.lock | 84 +++++++++---------- pyproject.toml | 15 +++- tui/pyproject.toml | 4 +- tui/uv.lock | 34 ++++---- 20 files changed, 122 insertions(+), 92 deletions(-) delete mode 100644 mcp/src/birdcage_mcp/__init__.py create mode 100644 mcp/src/mcbirdcage/__init__.py rename mcp/src/{birdcage_mcp => mcbirdcage}/prompts.py (100%) rename mcp/src/{birdcage_mcp => mcbirdcage}/resources.py (100%) rename mcp/src/{birdcage_mcp => mcbirdcage}/server.py (88%) rename mcp/src/{birdcage_mcp => mcbirdcage}/state.py (100%) rename mcp/src/{birdcage_mcp => mcbirdcage}/tools/__init__.py (100%) rename mcp/src/{birdcage_mcp => mcbirdcage}/tools/connection.py (95%) rename mcp/src/{birdcage_mcp => mcbirdcage}/tools/console.py (94%) rename mcp/src/{birdcage_mcp => mcbirdcage}/tools/movement.py (96%) rename mcp/src/{birdcage_mcp => mcbirdcage}/tools/satellite.py (95%) rename mcp/src/{birdcage_mcp => mcbirdcage}/tools/signal.py (96%) rename mcp/src/{birdcage_mcp => mcbirdcage}/tools/system.py (96%) diff --git a/CLAUDE.md b/CLAUDE.md index af07d0b..f304c16 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -772,9 +772,9 @@ directly via widget API for deterministic screenshots. | Property | Value | |----------|-------| -| Package | `birdcage-mcp` | -| Source | `mcp/src/birdcage_mcp/` | -| Entry point | `birdcage_mcp.server:main` | +| PyPI | [`mcbirdcage`](https://pypi.org/project/mcbirdcage/) | +| Source | `mcp/src/mcbirdcage/` | +| Entry point | `mcbirdcage.server:main` | | Tools | 35 (connection, movement, signal, system, satellite, console) | | Resources | 5 (`birdcage://config`, `position`, `firmware`, `motor-dynamics`, `el-limits`) | | Prompts | 3 (`setup_wizard`, `satellite_tracking_guide`, `rf_sweep_guide`) | @@ -783,17 +783,25 @@ directly via widget API for deterministic screenshots. ### Running ```bash -# Demo mode (no hardware) -BIRDCAGE_DEMO=1 uv run --directory mcp birdcage-mcp +# Published package (from PyPI) — no clone needed +uvx mcbirdcage # demo mode auto-detected +BIRDCAGE_DEMO=1 uvx mcbirdcage # explicit demo mode + +# Local development (from repo) +BIRDCAGE_DEMO=1 uv run --directory mcp mcbirdcage # Hardware mode -BIRDCAGE_PORT=/dev/ttyUSB2 uv run --directory mcp birdcage-mcp +BIRDCAGE_PORT=/dev/ttyUSB2 uv run --directory mcp mcbirdcage ``` ### Adding to Claude Code ```bash -claude mcp add birdcage-mcp -- env BIRDCAGE_DEMO=1 uv run --directory mcp birdcage-mcp +# Published package (recommended) +claude mcp add mcbirdcage -- uvx mcbirdcage + +# Local development +claude mcp add mcbirdcage -- env BIRDCAGE_DEMO=1 uv run --directory mcp mcbirdcage ``` ### Environment Variables @@ -809,5 +817,5 @@ claude mcp add birdcage-mcp -- env BIRDCAGE_DEMO=1 uv run --directory mcp birdca ```bash cd mcp && uv run pytest tests/ # 49 tests via FastMCP run_server_async -uv run ruff check src/ # Lint +uv run ruff check mcp/src/ # Lint ``` diff --git a/mcp/pyproject.toml b/mcp/pyproject.toml index ef2f14f..774b7c2 100644 --- a/mcp/pyproject.toml +++ b/mcp/pyproject.toml @@ -3,22 +3,33 @@ requires = ["hatchling"] build-backend = "hatchling.build" [project] -name = "birdcage-mcp" +name = "mcbirdcage" version = "2026.02.17" -description = "FastMCP server for Winegard satellite dish control" +description = "MCP server for Winegard satellite dish control via serial" license = "MIT" requires-python = ">=3.11" authors = [{name = "Ryan Malloy", email = "ryan@supported.systems"}] +keywords = ["mcp", "satellite", "antenna", "winegard", "amateur-radio"] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Science/Research", + "Topic :: Scientific/Engineering", + "Topic :: Communications :: Ham Radio", +] dependencies = [ - "birdcage", + "winegard-birdcage", "fastmcp>=2.0", ] +[project.urls] +Repository = "https://git.supported.systems/warehack.ing/birdcage" +Documentation = "https://birdcage.warehack.ing" + [project.scripts] -birdcage-mcp = "birdcage_mcp.server:main" +mcbirdcage = "mcbirdcage.server:main" [tool.uv.sources] -birdcage = { path = "..", editable = true } +winegard-birdcage = { path = "..", editable = true } [tool.ruff] target-version = "py311" @@ -28,7 +39,7 @@ src = ["src"] select = ["E", "F", "I", "UP", "B", "SIM"] [tool.hatch.build.targets.wheel] -packages = ["src/birdcage_mcp"] +packages = ["src/mcbirdcage"] [dependency-groups] dev = ["pytest>=9.0.2", "pytest-asyncio>=1.3.0"] diff --git a/mcp/src/birdcage_mcp/__init__.py b/mcp/src/birdcage_mcp/__init__.py deleted file mode 100644 index 5d81f8d..0000000 --- a/mcp/src/birdcage_mcp/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""birdcage-mcp: FastMCP server for Winegard satellite dish control.""" diff --git a/mcp/src/mcbirdcage/__init__.py b/mcp/src/mcbirdcage/__init__.py new file mode 100644 index 0000000..aa62dc6 --- /dev/null +++ b/mcp/src/mcbirdcage/__init__.py @@ -0,0 +1 @@ +"""mcbirdcage: MCP server for Winegard satellite dish control via serial.""" diff --git a/mcp/src/birdcage_mcp/prompts.py b/mcp/src/mcbirdcage/prompts.py similarity index 100% rename from mcp/src/birdcage_mcp/prompts.py rename to mcp/src/mcbirdcage/prompts.py diff --git a/mcp/src/birdcage_mcp/resources.py b/mcp/src/mcbirdcage/resources.py similarity index 100% rename from mcp/src/birdcage_mcp/resources.py rename to mcp/src/mcbirdcage/resources.py diff --git a/mcp/src/birdcage_mcp/server.py b/mcp/src/mcbirdcage/server.py similarity index 88% rename from mcp/src/birdcage_mcp/server.py rename to mcp/src/mcbirdcage/server.py index 200da89..df8c426 100644 --- a/mcp/src/birdcage_mcp/server.py +++ b/mcp/src/mcbirdcage/server.py @@ -7,7 +7,7 @@ from contextlib import asynccontextmanager from fastmcp import FastMCP -from birdcage_mcp.state import BirdcageState +from mcbirdcage.state import BirdcageState logger = logging.getLogger(__name__) @@ -67,8 +67,8 @@ mcp = FastMCP( ) # Register tool modules -from birdcage_mcp import prompts, resources # noqa: E402, F401 -from birdcage_mcp.tools import ( # noqa: E402 +from mcbirdcage import prompts, resources # noqa: E402, F401 +from mcbirdcage.tools import ( # noqa: E402 connection, console, movement, @@ -88,5 +88,5 @@ prompts.register(mcp) def main(): - print("birdcage-mcp v2026.02.17", file=sys.stderr) + print("mcbirdcage v2026.02.17", file=sys.stderr) mcp.run() diff --git a/mcp/src/birdcage_mcp/state.py b/mcp/src/mcbirdcage/state.py similarity index 100% rename from mcp/src/birdcage_mcp/state.py rename to mcp/src/mcbirdcage/state.py diff --git a/mcp/src/birdcage_mcp/tools/__init__.py b/mcp/src/mcbirdcage/tools/__init__.py similarity index 100% rename from mcp/src/birdcage_mcp/tools/__init__.py rename to mcp/src/mcbirdcage/tools/__init__.py diff --git a/mcp/src/birdcage_mcp/tools/connection.py b/mcp/src/mcbirdcage/tools/connection.py similarity index 95% rename from mcp/src/birdcage_mcp/tools/connection.py rename to mcp/src/mcbirdcage/tools/connection.py index f0200e8..13c73b9 100644 --- a/mcp/src/birdcage_mcp/tools/connection.py +++ b/mcp/src/mcbirdcage/tools/connection.py @@ -2,7 +2,7 @@ from fastmcp import Context -from birdcage_mcp.state import BirdcageState +from mcbirdcage.state import BirdcageState def register(mcp): diff --git a/mcp/src/birdcage_mcp/tools/console.py b/mcp/src/mcbirdcage/tools/console.py similarity index 94% rename from mcp/src/birdcage_mcp/tools/console.py rename to mcp/src/mcbirdcage/tools/console.py index 18fb7f7..df27d0b 100644 --- a/mcp/src/birdcage_mcp/tools/console.py +++ b/mcp/src/mcbirdcage/tools/console.py @@ -2,7 +2,7 @@ from fastmcp import Context -from birdcage_mcp.state import BirdcageState +from mcbirdcage.state import BirdcageState # Commands that are dangerous at the root menu level. _BLOCKED_ROOT_COMMANDS = {"q"} diff --git a/mcp/src/birdcage_mcp/tools/movement.py b/mcp/src/mcbirdcage/tools/movement.py similarity index 96% rename from mcp/src/birdcage_mcp/tools/movement.py rename to mcp/src/mcbirdcage/tools/movement.py index a4e7368..d703c8d 100644 --- a/mcp/src/birdcage_mcp/tools/movement.py +++ b/mcp/src/mcbirdcage/tools/movement.py @@ -2,7 +2,7 @@ from fastmcp import Context -from birdcage_mcp.state import BirdcageState +from mcbirdcage.state import BirdcageState def _require_device(state: BirdcageState): diff --git a/mcp/src/birdcage_mcp/tools/satellite.py b/mcp/src/mcbirdcage/tools/satellite.py similarity index 95% rename from mcp/src/birdcage_mcp/tools/satellite.py rename to mcp/src/mcbirdcage/tools/satellite.py index 783e6d5..515f428 100644 --- a/mcp/src/birdcage_mcp/tools/satellite.py +++ b/mcp/src/mcbirdcage/tools/satellite.py @@ -4,7 +4,7 @@ from dataclasses import asdict from fastmcp import Context -from birdcage_mcp.state import BirdcageState +from mcbirdcage.state import BirdcageState def _require_craft(state: BirdcageState): diff --git a/mcp/src/birdcage_mcp/tools/signal.py b/mcp/src/mcbirdcage/tools/signal.py similarity index 96% rename from mcp/src/birdcage_mcp/tools/signal.py rename to mcp/src/mcbirdcage/tools/signal.py index a96f89d..a860785 100644 --- a/mcp/src/birdcage_mcp/tools/signal.py +++ b/mcp/src/mcbirdcage/tools/signal.py @@ -2,7 +2,7 @@ from fastmcp import Context -from birdcage_mcp.state import BirdcageState +from mcbirdcage.state import BirdcageState def _require_device(state: BirdcageState): diff --git a/mcp/src/birdcage_mcp/tools/system.py b/mcp/src/mcbirdcage/tools/system.py similarity index 96% rename from mcp/src/birdcage_mcp/tools/system.py rename to mcp/src/mcbirdcage/tools/system.py index 7142770..39b58af 100644 --- a/mcp/src/birdcage_mcp/tools/system.py +++ b/mcp/src/mcbirdcage/tools/system.py @@ -2,7 +2,7 @@ from fastmcp import Context -from birdcage_mcp.state import BirdcageState +from mcbirdcage.state import BirdcageState def _require_device(state: BirdcageState): diff --git a/mcp/tests/conftest.py b/mcp/tests/conftest.py index e4654ff..f53a59d 100644 --- a/mcp/tests/conftest.py +++ b/mcp/tests/conftest.py @@ -1,4 +1,4 @@ -"""Shared fixtures for birdcage-mcp tests. +"""Shared fixtures for mcbirdcage tests. Uses FastMCP's run_server_async to spin up a real MCP server backed by DemoDevice + DemoCraftClient. No serial hardware, no subprocesses. @@ -13,7 +13,7 @@ from fastmcp import Client, FastMCP from fastmcp.client.transports import StreamableHttpTransport from fastmcp.utilities.tests import run_server_async -from birdcage_mcp.state import BirdcageState +from mcbirdcage.state import BirdcageState @asynccontextmanager @@ -36,8 +36,8 @@ async def _test_lifespan(server: FastMCP): def _build_server() -> FastMCP: """Build a FastMCP server with all tools registered.""" - from birdcage_mcp import prompts, resources - from birdcage_mcp.tools import ( + from mcbirdcage import prompts, resources + from mcbirdcage.tools import ( connection, console, movement, diff --git a/mcp/uv.lock b/mcp/uv.lock index bb2a326..b91250a 100644 --- a/mcp/uv.lock +++ b/mcp/uv.lock @@ -81,48 +81,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl", hash = "sha256:d16c9bbc61ea14637596c5f6fbff2ee99cbe3573e46a716401734ef50c3060c2", size = 1333658, upload-time = "2025-12-13T06:50:28.266Z" }, ] -[[package]] -name = "birdcage" -version = "2026.2.12.1" -source = { editable = "../" } -dependencies = [ - { name = "click" }, - { name = "pyserial" }, -] - -[package.metadata] -requires-dist = [ - { name = "click", specifier = ">=8.0" }, - { name = "pyserial", specifier = ">=3.5" }, -] - -[[package]] -name = "birdcage-mcp" -version = "2026.2.17" -source = { editable = "." } -dependencies = [ - { name = "birdcage" }, - { name = "fastmcp" }, -] - -[package.dev-dependencies] -dev = [ - { name = "pytest" }, - { name = "pytest-asyncio" }, -] - -[package.metadata] -requires-dist = [ - { name = "birdcage", editable = "../" }, - { name = "fastmcp", specifier = ">=2.0" }, -] - -[package.metadata.requires-dev] -dev = [ - { name = "pytest", specifier = ">=9.0.2" }, - { name = "pytest-asyncio", specifier = ">=1.3.0" }, -] - [[package]] name = "cachetools" version = "7.0.1" @@ -774,6 +732,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, ] +[[package]] +name = "mcbirdcage" +version = "2026.2.17" +source = { editable = "." } +dependencies = [ + { name = "fastmcp" }, + { name = "winegard-birdcage" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, + { name = "pytest-asyncio" }, +] + +[package.metadata] +requires-dist = [ + { name = "fastmcp", specifier = ">=2.0" }, + { name = "winegard-birdcage", editable = "../" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=9.0.2" }, + { name = "pytest-asyncio", specifier = ">=1.3.0" }, +] + [[package]] name = "mcp" version = "1.26.0" @@ -1668,6 +1653,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, ] +[[package]] +name = "winegard-birdcage" +version = "2026.2.17" +source = { editable = "../" } +dependencies = [ + { name = "click" }, + { name = "pyserial" }, +] + +[package.metadata] +requires-dist = [ + { name = "click", specifier = ">=8.0" }, + { name = "pyserial", specifier = ">=3.5" }, +] + [[package]] name = "zipp" version = "3.23.0" diff --git a/pyproject.toml b/pyproject.toml index be32ad1..6210b6b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,17 +3,28 @@ requires = ["hatchling"] build-backend = "hatchling.build" [project] -name = "birdcage" -version = "2026.02.12.1" +name = "winegard-birdcage" +version = "2026.02.17" description = "Winegard satellite dish control for amateur radio sky tracking" license = "MIT" requires-python = ">=3.11" authors = [{name = "Ryan Malloy", email = "ryan@supported.systems"}] +keywords = ["satellite", "antenna", "winegard", "amateur-radio", "ham"] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Science/Research", + "Topic :: Scientific/Engineering", + "Topic :: Communications :: Ham Radio", +] dependencies = [ "pyserial>=3.5", "click>=8.0", ] +[project.urls] +Repository = "https://git.supported.systems/warehack.ing/birdcage" +Documentation = "https://birdcage.warehack.ing" + [project.scripts] birdcage = "birdcage.cli:main" console-probe = "console_probe.cli:main" diff --git a/tui/pyproject.toml b/tui/pyproject.toml index fdd9ea7..c775b1c 100644 --- a/tui/pyproject.toml +++ b/tui/pyproject.toml @@ -10,7 +10,7 @@ license = "MIT" requires-python = ">=3.11" authors = [{name = "Ryan Malloy", email = "ryan@supported.systems"}] dependencies = [ - "birdcage", + "winegard-birdcage", "textual>=1.0.0", ] @@ -21,7 +21,7 @@ camera = ["Pillow>=10.0", "astropy>=6.0"] birdcage-tui = "birdcage_tui.app:main" [tool.uv.sources] -birdcage = { path = "..", editable = true } +winegard-birdcage = { path = "..", editable = true } [tool.ruff] target-version = "py311" diff --git a/tui/uv.lock b/tui/uv.lock index 07782cb..43882ae 100644 --- a/tui/uv.lock +++ b/tui/uv.lock @@ -34,28 +34,13 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/49/94/dcdac330d2c26776956555610ef584147584acc6b060e451aa65dd9142d9/astropy_iers_data-0.2026.2.16.0.48.25-py3-none-any.whl", hash = "sha256:180d1c3f59d18aa616345560799c2d88ec6e5164b8c45c746380acf892946136", size = 1982562, upload-time = "2026-02-16T00:49:09.602Z" }, ] -[[package]] -name = "birdcage" -version = "2026.2.12.1" -source = { editable = "../" } -dependencies = [ - { name = "click" }, - { name = "pyserial" }, -] - -[package.metadata] -requires-dist = [ - { name = "click", specifier = ">=8.0" }, - { name = "pyserial", specifier = ">=3.5" }, -] - [[package]] name = "birdcage-tui" version = "2026.2.13" source = { editable = "." } dependencies = [ - { name = "birdcage" }, { name = "textual" }, + { name = "winegard-birdcage" }, ] [package.optional-dependencies] @@ -73,9 +58,9 @@ dev = [ [package.metadata] requires-dist = [ { name = "astropy", marker = "extra == 'camera'", specifier = ">=6.0" }, - { name = "birdcage", editable = "../" }, { name = "pillow", marker = "extra == 'camera'", specifier = ">=10.0" }, { name = "textual", specifier = ">=1.0.0" }, + { name = "winegard-birdcage", editable = "../" }, ] provides-extras = ["camera"] @@ -525,3 +510,18 @@ sdist = { url = "https://files.pythonhosted.org/packages/91/7a/146a99696aee0609e wheels = [ { url = "https://files.pythonhosted.org/packages/37/87/1f677586e8ac487e29672e4b17455758fce261de06a0d086167bb760361a/uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5", size = 6229, upload-time = "2024-02-09T16:52:00.371Z" }, ] + +[[package]] +name = "winegard-birdcage" +version = "2026.2.17" +source = { editable = "../" } +dependencies = [ + { name = "click" }, + { name = "pyserial" }, +] + +[package.metadata] +requires-dist = [ + { name = "click", specifier = ">=8.0" }, + { name = "pyserial", specifier = ">=3.5" }, +]