Command Palette

Search for a command to run...

UnylyUnyly
Весь каталог

Surf

БесплатноНе проверен

MCP server and Python library for Surfline's public forecast API

GitHubEmbed

Описание

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 POORPOOR_TO_FAIRFAIRFAIR_TO_GOODGOODGOOD_TO_EPICEPIC, 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.

from github.com/j-suchon/surf-mcp

Установить Surf в Claude Desktop, Claude Code, Cursor

Рекомендуется · одна команда, все IDE
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

Compare Surf with

Не уверен что выбрать?

Найди свой стек за 60 секунд

Автор?

Embed-бейдж для README

Похожее

Все в категории development