MCP Surf Forecast

MCP Surf Forecast

An MCP server that provides real-time surf intelligence, including wave quality scoring, beach discovery, session planning, and forecast analysis across 12+ global data sources. It turns AI assistants into surf-savvy copilots for finding the perfect wave.

Category
Visit Server

README

<div align="center">

๐Ÿ„ MCP Surf Forecast

The AI-powered surf companion that knows every break on the planet.

An MCP server that gives LLMs real-time surf intelligence โ€” wave quality scoring, beach discovery, session planning, and forecast analysis across 12+ global data sources.

Node.js 22+ FastMCP TS License: MIT CI SonarCloud

Quick Start ยท Features ยท Tools ยท Prompts ยท Configuration ยท Contributing

</div>


๐ŸŒŠ What is this?

MCP Surf Forecast turns any AI assistant into a surf-savvy copilot. It's a Model Context Protocol server that exposes surf data, smart tools, and planning prompts โ€” so Claude, Cursor, ChatGPT, or any MCP client can help you find the perfect wave.

You: "What's the best spot near Lisbon this morning?"

AI: Using wave_quality_index and classify_best_spot_nearby...

๐Ÿ† #1 Coxos (Ericeira) โ€” WQI 8.2/10 "Very Good"
   6ft+ sets, 14s period, light NE offshore
   Best window: 07:00โ€“10:30 (dropping tide)

๐Ÿฅˆ #2 Supertubos (Peniche) โ€” WQI 7.8/10 "Very Good"
   Hollow barrels, overhead, advanced surfers only

๐Ÿฅ‰ #3 Carcavelos โ€” WQI 6.1/10 "Good"
   Fun peaks, 3-4ft, great for all levels

โœจ Features

๐ŸŽฏ Wave Quality Index (0โ€“10)

A proprietary scoring algorithm that condenses swell, wind, tide, consistency, and direction into one number. No more cross-referencing 5 tabs โ€” just ask "how good is it?"

Score Label You should...
8โ€“10 Very Good / Epic Drop everything and go
6โ€“7 Good Great session guaranteed
4โ€“5 Fair Fun if you're not picky
2โ€“3 Poor Maybe do yoga instead
0โ€“1 Flat Definitely do yoga

๐ŸŒ Global Coverage

Data aggregated from 12+ providers across every major surf region:

Region Providers
๐ŸŒ Global Surfline, Surf-Forecast, Magicseaweed
๐Ÿ‡ช๐Ÿ‡บ Europe MeoSurf, Windguru
๐Ÿ‡ง๐Ÿ‡ท Brazil Waves.com.br
๐Ÿ‡ฆ๐Ÿ‡บ Australia Coastalwatch, Swellnet
๐Ÿ‡บ๐Ÿ‡ธ Hawaii Surfnewsnetwork
๐Ÿ‡ฎ๐Ÿ‡ฉ Indonesia Baliwaves
๐Ÿ‡ฟ๐Ÿ‡ฆ Africa Wavescape

๐Ÿง  Smart Classification

Not just data โ€” intelligence. The server tells you where to go based on your skill level, location, and current conditions. Beginners get safe recommendations; pros get the heavy spots.


๐Ÿ›  Tools

Tool What it does
wave_quality_index Score any beach 0โ€“10 with full breakdown
classify_best_spot_nearby Ranked spots by quality within your radius
classify_spot_for_skill Go / Caution / Avoid for your level
find_beaches_in_radius Discover spots near GPS coordinates
find_beaches_near_city Same, but from a city name
filter_beaches Multi-criteria search (region, wave type, skill)
best_sessions_today Optimal windows considering tide + wind
compare_beaches Side-by-side forecast comparison
check_swell_alert Upcoming swell notifications
wetsuit_recommendation Gear advice based on water temp + wind chill
surf_conditions_full Complete conditions in one call

Tool Sequence Diagrams

wave_quality_index

sequenceDiagram
    participant Client as MCP Client
    participant Tool as wave_quality_index
    participant Repo as BeachRepository
    participant Forecast as ForecastProvider
    participant WQI as WQI Engine

    Client->>Tool: { beachId, timestamp? }
    Tool->>Repo: getById(beachId)
    Repo-->>Tool: Beach (bestSwellDir, bestWindDir, bestTide)
    Tool->>Forecast: getForecast(beachId)
    Forecast-->>Tool: ForecastEntry[]
    Tool->>WQI: calculate(forecast, beachProfile)
    Note over WQI: swellScore ร— 0.30<br/>windScore ร— 0.25<br/>consistency ร— 0.15<br/>tideAlignment ร— 0.15<br/>directionMatch ร— 0.15
    WQI-->>Tool: { wqi: 7.8, label, breakdown }
    Tool-->>Client: WQI result + summary

classify_best_spot_nearby

sequenceDiagram
    participant Client as MCP Client
    participant Tool as classify_best_spot_nearby
    participant Repo as BeachRepository
    participant WQI as WQI Engine

    Client->>Tool: { lat, lon, radiusKm, topN, skillLevel?, minWqi }
    Tool->>Repo: findInRadius({ lat, lon }, radiusKm)
    Repo-->>Tool: Beach[] with distances
    opt skillLevel provided
        Tool->>Tool: filter by skillLevel
    end
    loop For each candidate beach
        Tool->>WQI: calculate(beachId)
        WQI-->>Tool: { wqi, label, bestWindow }
    end
    Tool->>Tool: filter wqi >= minWqi
    Tool->>Tool: sort by WQI descending
    Tool->>Tool: take topN
    Tool-->>Client: Ranked list with WQI + highlights

classify_spot_for_skill

sequenceDiagram
    participant Client as MCP Client
    participant Tool as classify_spot_for_skill
    participant Repo as BeachRepository
    participant WQI as WQI Engine

    Client->>Tool: { beachId, skillLevel }
    Tool->>Repo: getById(beachId)
    Repo-->>Tool: Beach (waveType, skillLevel[])
    Tool->>WQI: calculate(beachId)
    WQI-->>Tool: { wqi, breakdown, forecast }
    Tool->>Tool: evaluate(waveSize, power, currents vs skillLevel)
    alt Safe for level
        Tool-->>Client: { recommendation: "go", reasons }
    else Marginal
        Tool->>Repo: findAlternative(nearby, beginner-friendly)
        Tool-->>Client: { recommendation: "caution", reasons, alternative }
    else Dangerous
        Tool->>Repo: findAlternative(nearby, safer)
        Tool-->>Client: { recommendation: "avoid", reasons, alternative }
    end

find_beaches_in_radius

sequenceDiagram
    participant Client as MCP Client
    participant Tool as find_beaches_in_radius
    participant Repo as BeachRepository
    participant Geo as Haversine

    Client->>Tool: { lat, lon, radiusKm, limit }
    Tool->>Repo: getAll()
    Repo-->>Tool: Beach[]
    loop For each beach
        Tool->>Geo: haversineDistance(input, beach)
        Geo-->>Tool: distanceKm
    end
    Tool->>Tool: filter distance <= radiusKm
    Tool->>Tool: sort by distance ASC
    Tool->>Tool: take limit
    Tool-->>Client: Beach[] with distanceKm

find_beaches_near_city

sequenceDiagram
    participant Client as MCP Client
    participant Tool as find_beaches_near_city
    participant Geocode as Nominatim API
    participant Radius as find_beaches_in_radius

    Client->>Tool: { city, country?, radiusKm, limit }
    Tool->>Geocode: geocodeCity(city, country)
    Geocode-->>Tool: { lat, lon }
    alt Geocoding failed
        Tool-->>Client: Error: "Could not geocode city"
    else Success
        Tool->>Radius: findInRadius(lat, lon, radiusKm, limit)
        Radius-->>Tool: Beach[] with distances
        Tool-->>Client: Beach[] with distances
    end

filter_beaches

sequenceDiagram
    participant Client as MCP Client
    participant Tool as filter_beaches
    participant Repo as BeachRepository
    participant WQI as WQI Engine

    Client->>Tool: { region?, country?, waveType?, skillLevel?, minWqi, maxResults }
    Tool->>Repo: filter({ region, country, waveType, skillLevel })
    Repo-->>Tool: Beach[] matching criteria
    opt minWqi > 0
        loop For each beach
            Tool->>WQI: calculate(beachId)
            WQI-->>Tool: { wqi }
        end
        Tool->>Tool: filter wqi >= minWqi
    end
    Tool->>Tool: take maxResults
    Tool-->>Client: Filtered Beach[]

best_sessions_today

sequenceDiagram
    participant Client as MCP Client
    participant Tool as best_sessions_today
    participant Repo as BeachRepository
    participant Forecast as ForecastProvider
    participant WQI as WQI Engine

    Client->>Tool: { beachId }
    Tool->>Repo: getById(beachId)
    Repo-->>Tool: Beach (bestTide, bestWindDir)
    Tool->>Forecast: getForecast(beachId, days=1)
    Forecast-->>Tool: ForecastEntry[] (hourly)
    loop For each hour today (sunrise โ†’ sunset)
        Tool->>WQI: calculate(beachId, hour)
        WQI-->>Tool: { wqi, tideState, windDirection }
    end
    Tool->>Tool: find peak WQI windows
    Tool->>Tool: group consecutive good hours
    Tool-->>Client: SessionWindow[] { startTime, endTime, wqi, confidence }

compare_beaches

sequenceDiagram
    participant Client as MCP Client
    participant Tool as compare_beaches
    participant Repo as BeachRepository
    participant WQI as WQI Engine
    participant Forecast as ForecastProvider

    Client->>Tool: { beachIds[], date? }
    loop For each beachId
        Tool->>Repo: getById(beachId)
        Repo-->>Tool: Beach profile
        Tool->>WQI: calculate(beachId, date)
        WQI-->>Tool: { wqi, breakdown }
        Tool->>Forecast: getForecast(beachId)
        Forecast-->>Tool: Conditions
    end
    Tool->>Tool: build side-by-side comparison
    Tool-->>Client: Comparison[] { beach, wqi, swell, wind, tide, bestWindow }

check_swell_alert

sequenceDiagram
    participant Client as MCP Client
    participant Tool as check_swell_alert
    participant Forecast as ForecastProvider

    Client->>Tool: { beachId, minSwellHeightM, daysAhead }
    Tool->>Forecast: getForecast(beachId, daysAhead)
    Forecast-->>Tool: ForecastEntry[] (multi-day)
    Tool->>Tool: filter swellHeightM >= minSwellHeightM
    alt Swell found
        Tool-->>Client: Matching windows[] { timestamp, height, period, direction }
    else No swell
        Tool-->>Client: { alert: false, message: "No significant swell in next N days" }
    end

wetsuit_recommendation

sequenceDiagram
    participant Client as MCP Client
    participant Tool as wetsuit_recommendation
    participant Forecast as ForecastProvider
    participant Calc as Wetsuit Calculator

    Client->>Tool: { beachId, timestamp?, sessionDurationMin }
    Tool->>Forecast: getForecast(beachId)
    Forecast-->>Tool: { waterTempC, windSpeedKts, airTempC }
    Tool->>Calc: recommendWetsuit(waterTemp, windSpeed, airTemp, duration)
    Note over Calc: Wind chill adjustment<br/>Long session adjustment<br/>Thickness lookup table
    Calc-->>Tool: { type, thickness, boots, gloves, hood }
    Tool-->>Client: Full recommendation + UV advice

surf_conditions_full

sequenceDiagram
    participant Client as MCP Client
    participant Tool as surf_conditions_full
    participant Repo as BeachRepository
    participant Forecast as ForecastProvider
    participant WQI as WQI Engine
    participant Wetsuit as Wetsuit Calculator

    Client->>Tool: { beachId, timestamp? }
    Tool->>Repo: getById(beachId)
    Repo-->>Tool: Beach profile
    Tool->>Forecast: getForecast(beachId)
    Forecast-->>Tool: Full forecast (marine + weather + ocean)
    Tool->>WQI: calculate(beachId, timestamp)
    WQI-->>Tool: { wqi, label, breakdown }
    Tool->>Wetsuit: recommendWetsuit(waterTemp, wind, airTemp)
    Wetsuit-->>Tool: Gear recommendation
    Tool->>Tool: assess safety (lightning, fog, currents)
    Tool-->>Client: Complete response { marine, wind, weather, sun, ocean, gear, safety, wqi }

๐Ÿ’ฌ Prompts

Pre-built conversation starters that turn your AI into a surf expert:

Prompt Use case
plan_surf_trip Multi-day itinerary with forecasts
daily_surf_report Morning conditions check
analyze_spot Deep dive on a specific break
beginner_spot_finder Safe spots for learners

๐Ÿš€ Quick Start

Prerequisites

Install

git clone https://github.com/YOUR_ORG/mcp-surf-forecast.git
cd mcp-surf-forecast
npm install

Run the server

# stdio transport (for Claude Desktop, Cursor, etc.)
npm start

# HTTP transport (for network clients, debugging)
npm run start:http
# โ†’ Server running at http://localhost:3000

# Interactive Inspector UI (development)
npm run dev

Connect your AI client

Claude Desktop โ€” add to your mcp.json:

{
  "mcpServers": {
    "surf-forecast": {
      "command": "npx",
      "args": ["fastmcp", "run", "/path/to/mcp-surf-forecast/src/server.ts"]
    }
  }
}

Cursor / HTTP clients:

{
  "mcpServers": {
    "surf-forecast": {
      "url": "http://localhost:3000"
    }
  }
}

๐Ÿงช Testing

Run tests locally

# Run all tests
npm test

# Run tests in watch mode (re-runs on file change)
npm run test:watch

# Run tests with coverage report
npm run test:coverage

# Type check (no emit)
npm run typecheck

Test structure

tests/
โ”œโ”€โ”€ unit/          # Pure logic: WQI engine, geo math, schemas
โ”œโ”€โ”€ integration/   # Tools + resources with mock data
โ””โ”€โ”€ e2e/           # Full MCP server via client connection

Verify MCP compliance

# List all registered resources, tools, and prompts
npm run inspect

# Call a specific tool from terminal
npx fastmcp call wave_quality_index --file src/server.ts beachId=supertubos-peniche

# Open the visual Inspector UI
npm run dev

๐Ÿ” MCP Inspector

The FastMCP Inspector is a visual development tool that lets you browse, test, and debug all your MCP components interactively โ€” without needing an AI client connected.

Launch the Inspector

npm run dev

This opens a browser-based UI with hot reload. Every time you save a file, the Inspector reloads your server automatically.

What you can do in the Inspector

Feature How
๐Ÿ“‹ Browse all tools, resources & prompts Listed in the sidebar with schemas
๐Ÿงช Call any tool with custom params Fill in the form, hit "Run", see the response
๐Ÿ“– Read any resource by URI Type surf://beaches/supertubos-peniche and see the data
๐Ÿ’ฌ Render prompts with arguments Preview exactly what the LLM receives
โŒ Test error handling Pass invalid params and verify error messages
โฑ๏ธ Check response times Each call shows execution duration

Inspector from the terminal (headless)

If you prefer CLI over UI:

# List everything the server exposes
npx fastmcp inspect --file src/server.ts

# Output:
# Tools (9):
#   - wave_quality_index
#   - classify_best_spot_nearby
#   - classify_spot_for_skill
#   - find_beaches_in_radius
#   - find_beaches_near_city
#   - filter_beaches
#   - best_sessions_today
#   - compare_beaches
#   - check_swell_alert
#
# Resources (6):
#   - surf://providers
#   - surf://providers/{providerId}
#   - surf://beaches
#   - surf://beaches/{beachId}
#   - surf://forecast/{beachId}
#   - surf://cams/{beachId}
#
# Prompts (4):
#   - plan_surf_trip
#   - daily_surf_report
#   - analyze_spot
#   - beginner_spot_finder

Call tools directly from terminal

# Score a beach
npx fastmcp call wave_quality_index --file src/server.ts beachId=supertubos-peniche

# Find beaches near coordinates
npx fastmcp call find_beaches_in_radius --file src/server.ts lat=38.7 lon=-9.14 radiusKm=50

# Classify for a beginner
npx fastmcp call classify_spot_for_skill --file src/server.ts beachId=carcavelos skillLevel=beginner

# Check upcoming swell
npx fastmcp call check_swell_alert --file src/server.ts beachId=pipeline-oahu minSwellHeightM=3 daysAhead=5

Run server locally for HTTP clients

# Start on port 3000 (default)
npm run start:http

# Or specify a custom port
PORT=8080 npx fastmcp run src/server.ts --transport http --port 8080

Then test with curl:

# Health check (server is up)
curl http://localhost:3000

# Or connect a FastMCP client
npx fastmcp call wave_quality_index --url http://localhost:3000 beachId=coxos-ericeira

โš™๏ธ Configuration

Environment Variables

Variable Required Default Description
PORT No 3000 HTTP transport port
TRANSPORT No stdio Transport type: stdio or http
LOG_LEVEL No info Logging level: debug, info, warn, error
NOMINATIM_USER_AGENT No mcp-surf-forecast User-Agent for geocoding requests
SONAR_TOKEN CI only โ€” SonarCloud authentication token
SEMGREP_APP_TOKEN CI only โ€” Semgrep security scanning token

sonar-project.properties

sonar.projectKey=mcp-surf-forecast
sonar.organization=<your-github-org>
sonar.sources=src
sonar.tests=tests
sonar.javascript.lcov.reportPaths=coverage/lcov.info
sonar.exclusions=src/data/**,dist/**

Data Configuration

The beach database and provider registry live in src/data/:

  • beaches.json โ€” Static database of ~50-100 surf spots worldwide
  • providers.json โ€” Metadata for all data source providers

To add a new beach, just append to beaches.json:

{
  "id": "your-spot-name",
  "name": "Your Spot",
  "region": "Europe",
  "country": "Portugal",
  "lat": 39.36,
  "lon": -9.37,
  "waveType": ["beach-break"],
  "skillLevel": ["intermediate"],
  "bestTide": "mid",
  "bestSwellDir": "NW",
  "bestWindDir": "E",
  "providers": ["surfline", "windguru"],
  "camUrl": null
}

No code changes required โ€” the server picks it up automatically.

WQI Weights

The Wave Quality Index scoring weights are defined in src/tools/wqi.ts:

const WQI_WEIGHTS = {
  swellScore:     0.30,   // Wave height + period
  windScore:      0.25,   // Offshore vs onshore
  consistency:    0.15,   // Set frequency
  tideAlignment:  0.15,   // Current vs ideal tide
  directionMatch: 0.15,   // Swell direction vs spot's sweet spot
}

๐Ÿ“ Project Structure

src/
โ”œโ”€โ”€ server.ts          # Composition root โ€” wires everything together
โ”œโ”€โ”€ resources/         # MCP resources (read-only data endpoints)
โ”œโ”€โ”€ tools/             # MCP tools (computation & actions)
โ”œโ”€โ”€ prompts/           # MCP prompts (LLM interaction templates)
โ”œโ”€โ”€ models/            # Domain entities & Zod schemas
โ”œโ”€โ”€ providers/         # Data access layer (static JSON for MVP)
โ”œโ”€โ”€ data/              # Beach & provider JSON databases
โ””โ”€โ”€ utils/             # Geo math, geocoding, helpers

Full architecture details in docs/01-architecture.md


๐Ÿ— Built With

Technology Why
๐Ÿ”Œ FastMCP TypeScript The standard MCP framework
๐Ÿ›ก๏ธ Zod Runtime schema validation
๐ŸŒ Haversine Geographic distance calculations
๐Ÿงช Vitest Lightning-fast testing
๐Ÿ“Š SonarCloud Code quality & security

๐Ÿค Contributing

We welcome contributions! Whether it's adding new beaches, improving the WQI algorithm, or integrating real-time data sources.

How to contribute

  1. Fork the repo
  2. Create a feature branch (git checkout -b feature/add-bali-spots)
  3. Make your changes
  4. Run tests (npm test) and type check (npm run typecheck)
  5. Open a PR

Easy first contributions

  • ๐Ÿ–๏ธ Add beaches โ€” Drop entries into src/data/beaches.json (no code needed!)
  • ๐ŸŒŠ Tune WQI โ€” Improve scoring accuracy for specific wave types
  • ๐ŸŒ Add providers โ€” Implement a new ForecastProvider for a data source
  • ๐Ÿ“ Improve prompts โ€” Better LLM guidance for surf planning

Development workflow

npm run dev          # Inspector UI with hot reload
npm test             # Run test suite
npm run test:watch   # Tests re-run on save
npm run inspect      # Validate all MCP components

๐Ÿ“‹ Roadmap

  • [x] Static beach database (50+ spots worldwide)
  • [x] Wave Quality Index (0โ€“10 scoring)
  • [x] Geo-based search & classification
  • [x] Parameterized prompts for trip planning
  • [ ] Real-time Surfline API integration
  • [ ] Live cam snapshot analysis
  • [ ] Historical data & trend analysis
  • [ ] User favorites & personalized alerts
  • [ ] Multi-language support
  • [ ] Mobile companion app

๐Ÿ“„ Documentation

Full technical documentation lives in docs/:

Document Topic
Architecture System design & SOLID principles
Data Models Beach, Forecast, WQI schemas
Resources MCP data endpoints
Tools Computation & classification
WQI Spec Scoring algorithm deep-dive
Prompts LLM interaction templates
Testing CI, SonarCloud, test strategy
Project Setup Dev environment & config

๐Ÿ“œ License

MIT โ€” see LICENSE for details.


<div align="center">

Built for surfers, by surfers. ๐Ÿค™

Stop checking 5 different apps. Let AI find your next session.

โญ Star this repo ยท ๐Ÿ› Report Bug ยท ๐Ÿ’ก Request Feature

</div>

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured