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.
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.
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
- Node.js 22+ (download)
- npm or pnpm
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 worldwideproviders.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
- Fork the repo
- Create a feature branch (
git checkout -b feature/add-bali-spots) - Make your changes
- Run tests (
npm test) and type check (npm run typecheck) - 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
ForecastProviderfor 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
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
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.
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.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
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.
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.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
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.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.