Surf
БесплатноНе проверенMCP server and Python library for Surfline's public forecast API
Описание
MCP server and Python library for Surfline's public forecast API
README
Surfline forecast data, exposed as an MCP tool
Hourly surf data points inclduing swell, wind, tides, ratings, and forecaster blurbs. All merged, normalized, and ready for an LLM to actually reason about to give accurate surf predictions
Works with Claude Desktop, Claude Code, the Anthropic Agent SDK, the MCP Python SDK, or any other MCP client. You can also skip the protocol entirely and use it as a regular Python lib
Current version: v0.1.0 (changelog)
Why bother
Surfline shows a colored bar rating that goes POOR → POOR_TO_FAIR → FAIR → FAIR_TO_GOOD → GOOD → GOOD_TO_EPIC → EPIC, plus a paragraph of forecaster stuff. Useful summary, but often misreads individual spots. Also one label trying to compress swell, wind, tide, and conditions into a single word is a fool's errand. The actual measurements behind the bar are in Surfline's public API. Most tools throw them out on the way to your screen but
surf-mcp keeps everything:
- Multi-component swell sorted by height per hour: primary, secondary, tertiary (not labeling ground vs wind swell yet)
- Cardinal directions (16-point compass)
- Tide push windows. Every (low → next high) interval w/ duration in hours
- Surfline's headline, observation paragraph, and rating ladder left intact for visibility
Built for anyone who wants to play with the actual data. Pick spots based on the science, not on whether FAIR_TO_GOOD happened to land on a Saturday. Some local knowledge of spots is needed and can be configured as a skill on top of this to access easily via chats and saved as a config.
Install
For MCP use, skip ahead. uvx handles install on first launch, no venv to manage (see wiring below)
For Python lib use, or hacking on the source:
pip install git+https://github.com/j-suchon/surf-mcp
Or local:
git clone https://github.com/j-suchon/surf-mcp
cd surf-mcp
pip install -e .
You get a surf-mcp console script. It speaks stdio MCP transport
Wire into Claude Desktop
Easiest path: uvx pulls + caches straight from GitHub on first launch. No local install of anything except uv itself (brew install uv on Mac, takes ~10 seconds).
Edit ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"surf": {
"command": "uvx",
"args": ["--from", "git+https://github.com/j-suchon/surf-mcp", "surf-mcp"]
}
}
}
Restart Claude Desktop. Done and ready. First call takes ~5s (clone + build), after that it's instant from cache
If you've got a local install instead, point at the venv binary:
{
"mcpServers": {
"surf": {
"command": "/absolute/path/to/your/.venv/bin/surf-mcp",
"args": []
}
}
}
Can also wire into Claude Code
uvx:
claude mcp add surf -- uvx --from git+https://github.com/j-suchon/surf-mcp surf-mcp
Or local install:
claude mcp add surf -- /absolute/path/to/.venv/bin/surf-mcp
Tools
search_spots(query, limit=8)
Find spots by name. Returns ranked candidates with breadcrumb regions so Claude (or you) can disambiguate when common beach names collide
| Param | Type | Default | Description |
|---|---|---|---|
query |
str | (required) | Spot name or partial name |
limit |
int | 8 |
Max results to return |
Sunset Beach returns 5 hits across LA County, North Shore Oahu, Florida, Dubai, Cape Town. breadcrumbs is the disambiguator:
{
"query": "Sunset Beach",
"count": 5,
"results": [
{
"id": "584204214e65fad6a7709b9e",
"name": "Sunset Beach",
"breadcrumbs": ["United States", "California", "Los Angeles County"],
"lat": 34.0387,
"lon": -118.5514,
"score": 78.36,
"has_cam": true
},
{
"id": "5842041f4e65fad6a770888d",
"name": "Sunset Beach",
"breadcrumbs": ["United States", "Hawaii", "Honolulu County", "O‘ahu"],
"lat": 21.679,
"lon": -158.042,
"score": 6.83,
"has_cam": true
}
// ...3 more
]
}
Pass the chosen id to get_full_forecast or lookup_spot. Results are Elasticsearch-scored; the top hit is usually correct but not always for ambiguous names, so always check breadcrumbs.
get_full_forecast(spot_id, days=5)
Five Surfline endpoints in parallel (conditions, rating, wave, wind, tides), joined by local hour. Returns one payload with daily prose plus per-hour science
| Param | Type | Default | Description |
|---|---|---|---|
spot_id |
str | (required) | 24-character Surfline spot ID, or any URL containing one |
days |
int | 5 |
Forecast horizon (1 to 16) |
Returns (Sunset Point, 2026-05-05 dawn, truncated to one hour and two tide extrema for readability):
{
"spot_id": "5842041f4e65fad6a7708914",
"days_requested": 5,
"fetched_at_utc": "2026-05-06T04:59:15+00:00",
"daily": [
{
"date": "2026-05-05",
"day_to_watch": false,
"headline": "Up a bit from Monday off a blend of swells. Wind is manageable but not great in the AM, builds PM.",
"observation": "Dawn to 10am: Modest SSW swell peaks and NW swell mix is up slightly from Monday. The blend offers occasional beachbreak corners as the tide fills back in after a drained low early. The points are the best option for shape during the low tide. ...",
"hourly": [
{
"timestamp": 1777986000,
"local_iso": "2026-05-05T06:00:00-07:00",
"local_day": "2026-05-05",
"local_hour": 6,
"utc_offset": -7,
"surf_min_ft": 1.0,
"surf_max_ft": 2.0,
"surf_human": "Knee to thigh",
"swells": [
{ "height_ft": 1.81, "period_s": 13.0, "direction_deg": 202.5, "cardinal": "SSW" },
{ "height_ft": 1.24, "period_s": 14.0, "direction_deg": 251.5, "cardinal": "WSW" },
{ "height_ft": 0.63, "period_s": 16.0, "direction_deg": 214.4, "cardinal": "SW" }
],
"wind": {
"speed_kts": 3.9,
"gust_kts": 5.4,
"direction_deg": 263.2,
"cardinal": "W"
},
"rating": { "key": "FAIR", "value": 3.0 }
}
],
"tide_hourly": [{ "timestamp": 1777964400, "utc_offset": -7, "height_ft": 5.04, "type": "NORMAL" }],
"tide_extrema": [
{ "timestamp": 1777988478, "type": "LOW", "height_ft": -0.03 },
{ "timestamp": 1778013810, "type": "HIGH", "height_ft": 2.89 }
],
"tide_pushes": [
{
"low_ts": 1777988478, "low_height_ft": -0.03,
"high_ts": 1778013810, "high_height_ft": 2.89,
"duration_hours": 7.0
}
]
}
]
}
swells[] is sorted by height_ft descending. swells[0] is your primary. Surfline returns a long tail of minor components, often reported 0ft. We drop those for noise
lookup_spot(text)
Paste a Surfline URL or a 24-hex spot ID. Get back the spot's name, region, and lat/lon.
// input: "https://www.surfline.com/surf-report/5842041f4e65fad6a7708914"
{
"id": "5842041f4e65fad6a7708914",
"name": "Sunset Point",
"region": null,
"lat": 34.038053387863464,
"lon": -118.5532
}
region is often null for individual spots. The taxonomy endpoint exposes the regions on the full payload but Surfline's structure is inconsistent enough that we don't try to flatten it.
Returns {"error": "..."} if there's no valid ID in the input so watch that
compare_spots(spot_ids, days=5)
Many spots, one call. Per-spot errors don't break the batch. Always one entry back per ID you passed in.
{
"days": 5,
"spots": [
{ "spot_id": "5842041f4e65fad6a7708914", "forecast": { /* Sunset Point, same shape as get_full_forecast */ } },
{ "spot_id": "5ff8a259222b7ff78169c757", "forecast": { /* Will Rogers Beach */ } },
{ "spot_id": "590927576a2e4300134fbed8", "error": "Surfline HTTP 404 ..." }
]
}
Use it as a Python library
The same basic functions back the MCP, no protocol layer required:
from surf_mcp import (
fetch_full_forecast,
lookup_spot_metadata,
search_spots_by_name,
extract_spot_id,
)
# Search by name when you don't have the ID
hits = search_spots_by_name("Sunset Beach", limit=3)
for h in hits:
print(h["id"], h["name"], "/".join(h["breadcrumbs"]))
# Already have a URL or ID? Skip the search
spot_id = extract_spot_id(
"https://www.surfline.com/surf-report/5842041f4e65fad6a7708914"
)
forecast = fetch_full_forecast(spot_id, days=5)
day0 = forecast["daily"][0]
print(f"{day0['date']}: {len(day0['hourly'])} hourly entries")
meta = lookup_spot_metadata(spot_id)
print(meta["name"])
Useful for non-MCP consumers: web apps, cron jobs, notebooks, whatever else your vibe-coding heart desires
Finding spot IDs
Easiest: ask Claude to use search_spots with the name. You never have to know an ID, nor should you. Names get resolved by Surfline's site search
If you already have a Surfline URL, the 24-hex ID is the last path segment, easy to pick out
https://www.surfline.com/surf-report/sunset-beach/584204214e65fad6a7709b9e
└─────────── ID ───────────┘
Pass the full URL or just the bare ID to any tool that takes one. extract_spot_id digs the 24-hex out of either.
Rate limits
This package has none. Surfline's public endpoints do, server-side. If you build something that fans this out to many spots per minute, add your own caching or back off. Don't be the reason these endpoints go behind auth
Surfline endpoints
All public, all unauthenticated, no API key needed, just raw surf data
| Endpoint | Provides |
|---|---|
services.surfline.com/kbyg/spots/forecasts/conditions |
Daily prose: headline + observation |
services.surfline.com/kbyg/spots/forecasts/rating |
Surfline's own rating values per timestamp |
services.surfline.com/kbyg/spots/forecasts/wave |
Surf height ranges + multi-component swell |
services.surfline.com/kbyg/spots/forecasts/wind |
Wind speed, gust, direction |
services.surfline.com/kbyg/spots/forecasts/tides |
Hourly tide heights + LOW/HIGH extrema |
services.surfline.com/taxonomy |
Spot metadata (name, region, lat/lon) |
services.surfline.com/search/site |
Spot search by name (Elasticsearch-scored hits) |
Not affiliated with Surfline at all. Like not even a little bit. Don't ship a high-volume product on top of this without their permission, dudes
Layout
surf-mcp/
├── surf_mcp/
│ ├── __init__.py public Python API
│ ├── __main__.py `python -m surf_mcp` entry
│ ├── server.py MCP tool definitions (FastMCP)
│ └── surfline_client.py Surfline endpoint fetchers + merging logic
├── pyproject.toml
├── README.md
└── LICENSE
server.py is a thin shell. The merging and unit-normalization lives in surfline_client.py, so the underlying functions can run standalone without touching MCP.
What's next
v0.2: HTTP transport + a Dockerfile to self-host as a remote MCP. Mobile clients need it: phones can't spawn local subprocesses, so stdio is desktop-only. Goal is a cheap container anyone can deploy on free-tier GCP / Fly / Railway and register with their Claude account once. Same tools, just over the network instead of stdin/stdout
Credits
The original idea and the first MCP wrapper around Surfline's conditions and rating endpoints came from ivoytov/surfline-mcp. Thanks to that project for proving the concept.
This repo expands the surface to all five forecast endpoints (wave, wind, tides added to the original two), reshapes the output into a per-hour merged payload, and adds search_spots, lookup_spot, and compare_spots. None of that would have started without the upstream work done by others before me so quick shoutout
License
MIT.
Установить Surf в Claude Desktop, Claude Code, Cursor
unyly install surfСтавит в Claude Desktop, Claude Code, Cursor и VS Code — сам разбирается с npx, uvx и сборкой из исходников.
Впервые? Поставь CLI: curl -fsSL https://unyly.org/install | sh
Или настроить вручную
Выполни в терминале:
claude mcp add surf -- uvx --from git+https://github.com/j-suchon/surf-mcp surf-mcpПошаговые гайды: как установить Surf
FAQ
Surf MCP бесплатный?
Да, Surf MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для Surf?
Нет, Surf работает без API-ключей и переменных окружения.
Surf — hosted или self-hosted?
Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.
Как установить Surf в Claude Desktop, Claude Code или Cursor?
Открой Surf на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
GitHub
PRs, issues, code search, CI status
автор: GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
автор: mcpdotdirectAmap Maps Mcp Server
MCP server for using the AMap Maps API
автор: duxiaohuiSupabase
Database, auth and storage
автор: SupabaseEverything
Reference / test server with prompts, resources, and tools.
Git
Tools to read, search, and manipulate Git repositories.
Sequential Thinking
Dynamic and reflective problem-solving through thought sequences.
Time
Time and timezone conversion capabilities.
Compare Surf with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории development
