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>
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。