Rename package from noaa-tides to mcnoaa-tides

Distribution name, import package, entry point script, MCP config,
and all internal references updated. Git tracks the directory rename
so file history is preserved.
This commit is contained in:
Ryan Malloy 2026-02-22 16:53:56 -07:00
parent 66ec2ba9e7
commit 9f6d7bb4ac
21 changed files with 91 additions and 91 deletions

View File

@ -1,8 +1,8 @@
{ {
"mcpServers": { "mcpServers": {
"noaa-tides": { "mcnoaa-tides": {
"command": "uv", "command": "uv",
"args": ["run", "--directory", "/home/rpm/claude/mat/noaa-tides", "noaa-tides"] "args": ["run", "--directory", "/home/rpm/claude/mat/noaa-tides", "mcnoaa-tides"]
} }
} }
} }

View File

@ -1,4 +1,4 @@
# noaa-tides # mcnoaa-tides
MCP server for [NOAA CO-OPS Tides and Currents](https://tidesandcurrents.noaa.gov/). Exposes tide predictions, observed water levels, and meteorological data from ~301 U.S. coastal stations as tools, resources, and prompts via [FastMCP 3.0](https://gofastmcp.com/). MCP server for [NOAA CO-OPS Tides and Currents](https://tidesandcurrents.noaa.gov/). Exposes tide predictions, observed water levels, and meteorological data from ~301 U.S. coastal stations as tools, resources, and prompts via [FastMCP 3.0](https://gofastmcp.com/).
@ -8,16 +8,16 @@ Built for marine planning — fishing trips, boating, crabbing, safety checks.
```bash ```bash
# Run directly (no install needed) # Run directly (no install needed)
uvx noaa-tides uvx mcnoaa-tides
# Or add to Claude Code # Or add to Claude Code
claude mcp add noaa-tides -- uvx noaa-tides claude mcp add mcnoaa-tides -- uvx mcnoaa-tides
# With visualization support (charts) # With visualization support (charts)
uv pip install noaa-tides[viz] uv pip install mcnoaa-tides[viz]
# Local development # Local development
uv run noaa-tides uv run mcnoaa-tides
``` ```
## Tools ## Tools
@ -192,7 +192,7 @@ Parameters:
- `include_observed` — overlay actual readings (default true) - `include_observed` — overlay actual readings (default true)
- `format``"png"` (inline image) or `"html"` (interactive file) - `format``"png"` (inline image) or `"html"` (interactive file)
Requires `noaa-tides[viz]` — install with `uv pip install noaa-tides[viz]`. Requires `mcnoaa-tides[viz]` — install with `uv pip install mcnoaa-tides[viz]`.
### `visualize_conditions` — Multi-panel conditions dashboard ### `visualize_conditions` — Multi-panel conditions dashboard
@ -208,7 +208,7 @@ Parameters:
- `hours` — data window (default 24) - `hours` — data window (default 24)
- `format``"png"` (inline image) or `"html"` (interactive file) - `format``"png"` (inline image) or `"html"` (interactive file)
Requires `noaa-tides[viz]`. Requires `mcnoaa-tides[viz]`.
## Resources ## Resources
@ -253,7 +253,7 @@ marine_safety_check(station_id="9447130")
## Development ## Development
```bash ```bash
git clone <repo-url> && cd noaa-tides git clone <repo-url> && cd mcnoaa-tides
uv sync --dev uv sync --dev
# Run tests (mock client, no network) # Run tests (mock client, no network)
@ -263,12 +263,12 @@ uv run pytest tests/ -v
uv run ruff check src/ uv run ruff check src/
# Start server locally # Start server locally
uv run noaa-tides uv run mcnoaa-tides
# Headless test with Claude # Headless test with Claude
claude -p "Search for tide stations in Rhode Island" \ claude -p "Search for tide stations in Rhode Island" \
--mcp-config .mcp.json \ --mcp-config .mcp.json \
--allowedTools "mcp__noaa-tides__*" --allowedTools "mcp__mcnoaa-tides__*"
``` ```
## Data source ## Data source

View File

@ -1,5 +1,5 @@
[project] [project]
name = "noaa-tides" name = "mcnoaa-tides"
version = "2026.02.21" version = "2026.02.21"
description = "FastMCP server for NOAA CO-OPS Tides and Currents API" description = "FastMCP server for NOAA CO-OPS Tides and Currents API"
authors = [{ name = "Ryan Malloy", email = "ryan@supported.systems" }] authors = [{ name = "Ryan Malloy", email = "ryan@supported.systems" }]
@ -12,14 +12,14 @@ license = "MIT"
viz = ["matplotlib>=3.8", "plotly>=5.18"] viz = ["matplotlib>=3.8", "plotly>=5.18"]
[project.scripts] [project.scripts]
noaa-tides = "noaa_tides.server:main" mcnoaa-tides = "mcnoaa_tides.server:main"
[build-system] [build-system]
requires = ["hatchling"] requires = ["hatchling"]
build-backend = "hatchling.build" build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel] [tool.hatch.build.targets.wheel]
packages = ["src/noaa_tides"] packages = ["src/mcnoaa_tides"]
[tool.ruff] [tool.ruff]
target-version = "py312" target-version = "py312"
@ -32,4 +32,4 @@ select = ["E", "F", "I", "W"]
asyncio_mode = "auto" asyncio_mode = "auto"
[dependency-groups] [dependency-groups]
dev = ["pytest>=8", "pytest-asyncio>=0.24", "ruff>=0.9", "noaa-tides[viz]"] dev = ["pytest>=8", "pytest-asyncio>=0.24", "ruff>=0.9", "mcnoaa-tides[viz]"]

View File

@ -1,6 +1,6 @@
from importlib.metadata import PackageNotFoundError, version from importlib.metadata import PackageNotFoundError, version
try: try:
__version__ = version("noaa-tides") __version__ = version("mcnoaa-tides")
except PackageNotFoundError: except PackageNotFoundError:
__version__ = "0.0.0-dev" __version__ = "0.0.0-dev"

View File

@ -1,6 +1,6 @@
"""Chart rendering for NOAA tide and conditions data. """Chart rendering for NOAA tide and conditions data.
Optional dependency requires noaa-tides[viz] to be installed. Optional dependency requires mcnoaa-tides[viz] to be installed.
""" """
# Marine color palette — shared across all chart renderers # Marine color palette — shared across all chart renderers
@ -21,7 +21,7 @@ def check_deps(format: str) -> None:
except ImportError: except ImportError:
raise ValueError( raise ValueError(
"PNG charts require matplotlib. Install with: " "PNG charts require matplotlib. Install with: "
"uv pip install noaa-tides[viz]" "uv pip install mcnoaa-tides[viz]"
) )
elif format == "html": elif format == "html":
try: try:
@ -29,5 +29,5 @@ def check_deps(format: str) -> None:
except ImportError: except ImportError:
raise ValueError( raise ValueError(
"HTML charts require plotly. Install with: " "HTML charts require plotly. Install with: "
"uv pip install noaa-tides[viz]" "uv pip install mcnoaa-tides[viz]"
) )

View File

@ -3,7 +3,7 @@
import io import io
from datetime import datetime from datetime import datetime
from noaa_tides.charts import BG_COLOR, CORAL, GRID_COLOR, OCEAN_BLUE, SAND, SLATE, TEAL from mcnoaa_tides.charts import BG_COLOR, CORAL, GRID_COLOR, OCEAN_BLUE, SAND, SLATE, TEAL
def _parse_time_series(data: list[dict], value_key: str = "v") -> tuple[list, list]: def _parse_time_series(data: list[dict], value_key: str = "v") -> tuple[list, list]:
@ -105,7 +105,7 @@ def render_conditions_png(snapshot: dict, station_name: str = "") -> bytes:
if has_predictions: if has_predictions:
preds = snapshot["predictions"].get("predictions", []) preds = snapshot["predictions"].get("predictions", [])
if preds: if preds:
from noaa_tides.charts.tides import _parse_predictions from mcnoaa_tides.charts.tides import _parse_predictions
p_times, p_values, markers = _parse_predictions(preds) p_times, p_values, markers = _parse_predictions(preds)
ax.plot( ax.plot(
@ -122,7 +122,7 @@ def render_conditions_png(snapshot: dict, station_name: str = "") -> bytes:
if has_water_level: if has_water_level:
obs_data = snapshot["water_level"].get("data", []) obs_data = snapshot["water_level"].get("data", [])
if obs_data: if obs_data:
from noaa_tides.charts.tides import _parse_observed from mcnoaa_tides.charts.tides import _parse_observed
o_times, o_values = _parse_observed(obs_data) o_times, o_values = _parse_observed(obs_data)
if o_times: if o_times:
@ -255,7 +255,7 @@ def render_conditions_html(snapshot: dict, station_name: str = "") -> str:
if has_predictions: if has_predictions:
preds = snapshot["predictions"].get("predictions", []) preds = snapshot["predictions"].get("predictions", [])
if preds: if preds:
from noaa_tides.charts.tides import _parse_predictions from mcnoaa_tides.charts.tides import _parse_predictions
p_times, p_values, markers = _parse_predictions(preds) p_times, p_values, markers = _parse_predictions(preds)
fig.add_trace( fig.add_trace(
@ -290,7 +290,7 @@ def render_conditions_html(snapshot: dict, station_name: str = "") -> str:
if has_water_level: if has_water_level:
obs_data = snapshot["water_level"].get("data", []) obs_data = snapshot["water_level"].get("data", [])
if obs_data: if obs_data:
from noaa_tides.charts.tides import _parse_observed from mcnoaa_tides.charts.tides import _parse_observed
o_times, o_values = _parse_observed(obs_data) o_times, o_values = _parse_observed(obs_data)
if o_times: if o_times:

View File

@ -3,7 +3,7 @@
import io import io
from datetime import datetime from datetime import datetime
from noaa_tides.charts import BG_COLOR, CORAL, GRID_COLOR, OCEAN_BLUE, SAND, SLATE, TEAL from mcnoaa_tides.charts import BG_COLOR, CORAL, GRID_COLOR, OCEAN_BLUE, SAND, SLATE, TEAL
def _parse_predictions(predictions: list[dict]) -> tuple[list[datetime], list[float], list[dict]]: def _parse_predictions(predictions: list[dict]) -> tuple[list[datetime], list[float], list[dict]]:

View File

@ -7,7 +7,7 @@ import time
import httpx import httpx
from noaa_tides.models import Station from mcnoaa_tides.models import Station
DATA_URL = "https://api.tidesandcurrents.noaa.gov/api/prod/datagetter" DATA_URL = "https://api.tidesandcurrents.noaa.gov/api/prod/datagetter"
META_URL = "https://api.tidesandcurrents.noaa.gov/mdapi/prod/webapi" META_URL = "https://api.tidesandcurrents.noaa.gov/mdapi/prod/webapi"
@ -139,7 +139,7 @@ class NOAAClient:
"units": units, "units": units,
"time_zone": time_zone, "time_zone": time_zone,
"format": "json", "format": "json",
"application": "noaa-tides-mcp", "application": "mcnoaa-tides-mcp",
} }
if begin_date: if begin_date:
params["begin_date"] = begin_date params["begin_date"] = begin_date

View File

@ -4,7 +4,7 @@ import json
from fastmcp import Context, FastMCP from fastmcp import Context, FastMCP
from noaa_tides.client import NOAAClient from mcnoaa_tides.client import NOAAClient
def register(mcp: FastMCP) -> None: def register(mcp: FastMCP) -> None:

View File

@ -5,9 +5,9 @@ from contextlib import asynccontextmanager
from fastmcp import FastMCP from fastmcp import FastMCP
from noaa_tides import __version__, prompts, resources from mcnoaa_tides import __version__, prompts, resources
from noaa_tides.client import NOAAClient from mcnoaa_tides.client import NOAAClient
from noaa_tides.tools import charts, conditions, meteorological, stations, tides from mcnoaa_tides.tools import charts, conditions, meteorological, stations, tides
@asynccontextmanager @asynccontextmanager
@ -34,7 +34,7 @@ async def lifespan(server: FastMCP):
mcp = FastMCP( mcp = FastMCP(
"noaa-tides", "mcnoaa-tides",
instructions=( instructions=(
"NOAA Tides & Currents data server. " "NOAA Tides & Currents data server. "
"Provides tide predictions, observed water levels, and meteorological data " "Provides tide predictions, observed water levels, and meteorological data "
@ -57,5 +57,5 @@ prompts.register(mcp)
def main(): def main():
print(f"noaa-tides v{__version__}", file=sys.stderr) print(f"mcnoaa-tides v{__version__}", file=sys.stderr)
mcp.run() mcp.run()

View File

@ -8,8 +8,8 @@ from typing import Literal
from fastmcp import Context, FastMCP from fastmcp import Context, FastMCP
from fastmcp.utilities.types import Image from fastmcp.utilities.types import Image
from noaa_tides.charts import check_deps from mcnoaa_tides.charts import check_deps
from noaa_tides.client import NOAAClient from mcnoaa_tides.client import NOAAClient
def register(mcp: FastMCP) -> None: def register(mcp: FastMCP) -> None:
@ -30,7 +30,7 @@ def register(mcp: FastMCP) -> None:
PNG format returns an inline image. HTML format saves an interactive PNG format returns an inline image. HTML format saves an interactive
chart to artifacts/charts/ and returns the file path. chart to artifacts/charts/ and returns the file path.
Requires noaa-tides[viz] to be installed. Requires mcnoaa-tides[viz] to be installed.
""" """
check_deps(format) check_deps(format)
noaa: NOAAClient = ctx.lifespan_context["noaa_client"] noaa: NOAAClient = ctx.lifespan_context["noaa_client"]
@ -81,12 +81,12 @@ def register(mcp: FastMCP) -> None:
pass pass
if format == "png": if format == "png":
from noaa_tides.charts.tides import render_tide_chart_png from mcnoaa_tides.charts.tides import render_tide_chart_png
png_bytes = render_tide_chart_png(predictions, observed, station_name) png_bytes = render_tide_chart_png(predictions, observed, station_name)
return Image(data=png_bytes, format="image/png") return Image(data=png_bytes, format="image/png")
else: else:
from noaa_tides.charts.tides import render_tide_chart_html from mcnoaa_tides.charts.tides import render_tide_chart_html
html = render_tide_chart_html(predictions, observed, station_name) html = render_tide_chart_html(predictions, observed, station_name)
path = _save_html(html, station_id, "tides") path = _save_html(html, station_id, "tides")
@ -112,7 +112,7 @@ def register(mcp: FastMCP) -> None:
PNG format returns an inline image. HTML format saves an interactive PNG format returns an inline image. HTML format saves an interactive
chart to artifacts/charts/ and returns the file path. chart to artifacts/charts/ and returns the file path.
Requires noaa-tides[viz] to be installed. Requires mcnoaa-tides[viz] to be installed.
""" """
check_deps(format) check_deps(format)
noaa: NOAAClient = ctx.lifespan_context["noaa_client"] noaa: NOAAClient = ctx.lifespan_context["noaa_client"]
@ -159,12 +159,12 @@ def register(mcp: FastMCP) -> None:
pass pass
if format == "png": if format == "png":
from noaa_tides.charts.conditions import render_conditions_png from mcnoaa_tides.charts.conditions import render_conditions_png
png_bytes = render_conditions_png(snapshot, station_name) png_bytes = render_conditions_png(snapshot, station_name)
return Image(data=png_bytes, format="image/png") return Image(data=png_bytes, format="image/png")
else: else:
from noaa_tides.charts.conditions import render_conditions_html from mcnoaa_tides.charts.conditions import render_conditions_html
html = render_conditions_html(snapshot, station_name) html = render_conditions_html(snapshot, station_name)
path = _save_html(html, station_id, "conditions") path = _save_html(html, station_id, "conditions")

View File

@ -5,7 +5,7 @@ from datetime import datetime, timezone
from fastmcp import Context, FastMCP from fastmcp import Context, FastMCP
from noaa_tides.client import NOAAClient from mcnoaa_tides.client import NOAAClient
def register(mcp: FastMCP) -> None: def register(mcp: FastMCP) -> None:

View File

@ -4,7 +4,7 @@ from typing import Literal
from fastmcp import Context, FastMCP from fastmcp import Context, FastMCP
from noaa_tides.client import NOAAClient from mcnoaa_tides.client import NOAAClient
MetProduct = Literal[ MetProduct = Literal[
"air_temperature", "air_temperature",

View File

@ -2,7 +2,7 @@
from fastmcp import Context, FastMCP from fastmcp import Context, FastMCP
from noaa_tides.client import NOAAClient from mcnoaa_tides.client import NOAAClient
def register(mcp: FastMCP) -> None: def register(mcp: FastMCP) -> None:

View File

@ -2,7 +2,7 @@
from fastmcp import Context, FastMCP from fastmcp import Context, FastMCP
from noaa_tides.client import NOAAClient from mcnoaa_tides.client import NOAAClient
def register(mcp: FastMCP) -> None: def register(mcp: FastMCP) -> None:

View File

@ -8,9 +8,9 @@ from fastmcp import Client, FastMCP
from fastmcp.client.transports import StreamableHttpTransport from fastmcp.client.transports import StreamableHttpTransport
from fastmcp.utilities.tests import run_server_async from fastmcp.utilities.tests import run_server_async
from noaa_tides import prompts, resources from mcnoaa_tides import prompts, resources
from noaa_tides.client import NOAAClient from mcnoaa_tides.client import NOAAClient
from noaa_tides.tools import charts, conditions, meteorological, stations, tides from mcnoaa_tides.tools import charts, conditions, meteorological, stations, tides
# Realistic station fixtures # Realistic station fixtures
MOCK_STATIONS_RAW = [ MOCK_STATIONS_RAW = [
@ -98,7 +98,7 @@ MOCK_METADATA = {
def _build_mock_client() -> NOAAClient: def _build_mock_client() -> NOAAClient:
"""Build a NOAAClient with mocked HTTP but real search/nearest logic.""" """Build a NOAAClient with mocked HTTP but real search/nearest logic."""
from noaa_tides.models import Station from mcnoaa_tides.models import Station
client = NOAAClient() client = NOAAClient()
client._stations = [Station(**s) for s in MOCK_STATIONS_RAW] client._stations = [Station(**s) for s in MOCK_STATIONS_RAW]
@ -135,7 +135,7 @@ async def _test_lifespan(server: FastMCP):
def _build_test_server() -> FastMCP: def _build_test_server() -> FastMCP:
mcp = FastMCP("noaa-tides-test", lifespan=_test_lifespan) mcp = FastMCP("mcnoaa-tides-test", lifespan=_test_lifespan)
stations.register(mcp) stations.register(mcp)
tides.register(mcp) tides.register(mcp)
meteorological.register(mcp) meteorological.register(mcp)

View File

@ -3,8 +3,8 @@
import pytest import pytest
from fastmcp import Client from fastmcp import Client
from noaa_tides.charts.conditions import render_conditions_html, render_conditions_png from mcnoaa_tides.charts.conditions import render_conditions_html, render_conditions_png
from noaa_tides.charts.tides import ( from mcnoaa_tides.charts.tides import (
_parse_observed, _parse_observed,
_parse_predictions, _parse_predictions,
render_tide_chart_html, render_tide_chart_html,

76
uv.lock generated
View File

@ -767,6 +767,44 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560, upload-time = "2025-12-10T22:56:38.008Z" }, { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560, upload-time = "2025-12-10T22:56:38.008Z" },
] ]
[[package]]
name = "mcnoaa-tides"
version = "2026.2.21"
source = { editable = "." }
dependencies = [
{ name = "fastmcp" },
]
[package.optional-dependencies]
viz = [
{ name = "matplotlib" },
{ name = "plotly" },
]
[package.dev-dependencies]
dev = [
{ name = "mcnoaa-tides", extra = ["viz"] },
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "ruff" },
]
[package.metadata]
requires-dist = [
{ name = "fastmcp", specifier = ">=3.0.1" },
{ name = "matplotlib", marker = "extra == 'viz'", specifier = ">=3.8" },
{ name = "plotly", marker = "extra == 'viz'", specifier = ">=5.18" },
]
provides-extras = ["viz"]
[package.metadata.requires-dev]
dev = [
{ name = "mcnoaa-tides", extras = ["viz"] },
{ name = "pytest", specifier = ">=8" },
{ name = "pytest-asyncio", specifier = ">=0.24" },
{ name = "ruff", specifier = ">=0.9" },
]
[[package]] [[package]]
name = "mcp" name = "mcp"
version = "1.26.0" version = "1.26.0"
@ -819,44 +857,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/03/cc/7cb74758e6df95e0c4e1253f203b6dd7f348bf2f29cf89e9210a2416d535/narwhals-2.16.0-py3-none-any.whl", hash = "sha256:846f1fd7093ac69d63526e50732033e86c30ea0026a44d9b23991010c7d1485d", size = 443951, upload-time = "2026-02-02T10:30:58.635Z" }, { url = "https://files.pythonhosted.org/packages/03/cc/7cb74758e6df95e0c4e1253f203b6dd7f348bf2f29cf89e9210a2416d535/narwhals-2.16.0-py3-none-any.whl", hash = "sha256:846f1fd7093ac69d63526e50732033e86c30ea0026a44d9b23991010c7d1485d", size = 443951, upload-time = "2026-02-02T10:30:58.635Z" },
] ]
[[package]]
name = "noaa-tides"
version = "2026.2.21"
source = { editable = "." }
dependencies = [
{ name = "fastmcp" },
]
[package.optional-dependencies]
viz = [
{ name = "matplotlib" },
{ name = "plotly" },
]
[package.dev-dependencies]
dev = [
{ name = "noaa-tides", extra = ["viz"] },
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "ruff" },
]
[package.metadata]
requires-dist = [
{ name = "fastmcp", specifier = ">=3.0.1" },
{ name = "matplotlib", marker = "extra == 'viz'", specifier = ">=3.8" },
{ name = "plotly", marker = "extra == 'viz'", specifier = ">=5.18" },
]
provides-extras = ["viz"]
[package.metadata.requires-dev]
dev = [
{ name = "noaa-tides", extras = ["viz"] },
{ name = "pytest", specifier = ">=8" },
{ name = "pytest-asyncio", specifier = ">=0.24" },
{ name = "ruff", specifier = ">=0.9" },
]
[[package]] [[package]]
name = "numpy" name = "numpy"
version = "2.4.2" version = "2.4.2"