Rename packages for PyPI: winegard-birdcage + mcbirdcage
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.
This commit is contained in:
parent
b98a5482fa
commit
5a26dce075
24
CLAUDE.md
24
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
|
||||
```
|
||||
|
||||
@ -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"]
|
||||
|
||||
@ -1 +0,0 @@
|
||||
"""birdcage-mcp: FastMCP server for Winegard satellite dish control."""
|
||||
1
mcp/src/mcbirdcage/__init__.py
Normal file
1
mcp/src/mcbirdcage/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""mcbirdcage: MCP server for Winegard satellite dish control via serial."""
|
||||
@ -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()
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
from fastmcp import Context
|
||||
|
||||
from birdcage_mcp.state import BirdcageState
|
||||
from mcbirdcage.state import BirdcageState
|
||||
|
||||
|
||||
def register(mcp):
|
||||
@ -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"}
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
from fastmcp import Context
|
||||
|
||||
from birdcage_mcp.state import BirdcageState
|
||||
from mcbirdcage.state import BirdcageState
|
||||
|
||||
|
||||
def _require_device(state: BirdcageState):
|
||||
@ -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):
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
from fastmcp import Context
|
||||
|
||||
from birdcage_mcp.state import BirdcageState
|
||||
from mcbirdcage.state import BirdcageState
|
||||
|
||||
|
||||
def _require_device(state: BirdcageState):
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
from fastmcp import Context
|
||||
|
||||
from birdcage_mcp.state import BirdcageState
|
||||
from mcbirdcage.state import BirdcageState
|
||||
|
||||
|
||||
def _require_device(state: BirdcageState):
|
||||
@ -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,
|
||||
|
||||
84
mcp/uv.lock
generated
84
mcp/uv.lock
generated
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
34
tui/uv.lock
generated
34
tui/uv.lock
generated
@ -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" },
|
||||
]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user