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
访问服务器

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>

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选