mcp-server/README.md
Murat ÖZDEMİR 8b151ae4c4 Adds comprehensive HTTP logging and token persistence
Introduces robust HTTP request and tool invocation logging with sensitive data sanitization and configurable file rotation. This improves debuggability and operational oversight.

Implements token state persistence to disk, allowing the server to maintain authenticated sessions across restarts.

Adds `_with_webhook` variants for alarm registration tools, enabling explicit webhook configuration. Corrects spelling inconsistencies in geographical alarm tools (e.g., 'neighbourhood' to 'neighborhood').

Includes a new build and packaging script.
2026-03-25 15:22:59 +03:00

720 lines
25 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 🌩️ iklim.co MCP Server
[Model Context Protocol (MCP)](https://modelcontextprotocol.io) server for the iklim.co Weather API. AI asistanların (Claude, OpenClaw vb.) iklim.co'nun hava durumu, yıldırım, fırtına, yağış ve alarm API'lerine doğal dil ile erişmesini sağlar.
## 📋 İçindekiler
- [🌐 Genel Bakış](#-genel-bakış)
- [⚙️ Gereksinimler](#-gereksinimler)
- [🚀 Kurulum](#-kurulum)
- [🔧 Ortam Değişkenleri](#-ortam-değişkenleri)
- [▶️ Build ve Çalıştırma](#-build-ve-çalıştırma)
- [🔌 MCP Client Konfigürasyonu](#-mcp-client-konfigürasyonu)
- [Claude CLI (.mcp.json)](#claude-cli-mcpjson)
- [OpenClaw](#openclaw)
- [Diğer MCP İstemcileri](#diğer-mcp-i̇stemcileri)
- [🛠️ Araç Kataloğu](#-araç-kataloğu)
- [⚡ Yıldırım / Lightning](#-yıldırım--lightning)
- [🌪️ Fırtına / Thunderstorm](#-fırtına--thunderstorm)
- [🌧️ Yağış / Precipitation](#-yağış--precipitation)
- [🌤️ Hava Tahmini / Forecast](#-hava-tahmini--forecast)
- [👤 Auth & Kullanıcı](#-auth--kullanıcı)
- [🏢 Hesap / Account](#-hesap--account)
- [📍 Nokta Alarmları / Point Alarms](#-nokta-alarmları--point-alarms)
- [🗺️ Coğrafi Alarmlar / Geo Alarms](#-coğrafi-alarmlar--geo-alarms)
- [📅 Tahmin Alarmları / Forecast Alarms](#-tahmin-alarmları--forecast-alarms)
- [🏗️ Mimari](#-mimari)
- [🔐 Kimlik Doğrulama ve Güvenlik](#-kimlik-doğrulama-ve-güvenlik)
- [Dahili Auth Akışı](#dahili-auth-akışı)
- [JWT Token Yaşam Döngüsü](#jwt-token-yaşam-döngüsü)
- [HTTP İstek Header'ları](#http-i̇stek-headerları)
- [HMAC-SHA256 İmza Hesabı](#hmac-sha256-i̇mza-hesabı)
- [Auth ile Normal İstekler Arasındaki Fark](#auth-ile-normal-i̇stekler-arasındaki-fark)
- [Güvenlik Önerileri](#güvenlik-önerileri)
- [💻 Geliştirici Notları](#-geliştirici-notları)
---
## 🌐 Genel Bakış
Bu MCP server, iklim.co REST API'sinin tüm yeteneklerini **57 araç** (tool) olarak sunar. Araçlar 9 kategoriye ayrılmıştır:
| Kategori | Araç Sayısı | Kapsam |
|----------|:-----------:|--------|
| ⚡ Lightning | 2 | Yıldırım çarpma verileri |
| 🌪️ Thunderstorm | 3 | Fırtına hücresi takibi |
| 🌧️ Precipitation | 2 | Radar yağış verileri |
| 🌤️ Forecast | 3 | Saatlik / günlük / anlık tahmin |
| 👤 Auth & User | 11 | Kimlik doğrulama, kullanıcı yönetimi |
| 🏢 Account | 8 | Hesap ve abonelik yönetimi |
| 📍 Point Alarms | 6 | Nokta tabanlı uyarı abonelikleri |
| 🗺️ Geo Alarms | 12 | Coğrafi sınır bazlı uyarılar + il/ilçe/mahalle kataloğu |
| 📅 Forecast Alarms | 10 | Eşik bazlı tahmin uyarıları + il/ilçe kataloğu |
| **Toplam** | **57** | |
---
## ⚙️ Gereksinimler
- **Node.js** >= 18 (ES2022 desteği gerekli)
- **npm** >= 9
- iklim.co API erişim bilgileri (HMAC secret, kullanıcı adı ve şifre)
---
## 🚀 Kurulum
```bash
cd mcp-server
npm install
npm run build
```
---
## 🔧 Ortam Değişkenleri
Server başlamadan önce aşağıdaki değişkenlerin tanımlı olması gerekir. Geliştirme ortamında `.env` dosyası oluşturabilirsiniz (projenin `.gitignore` dosyasına ekli):
```bash
# .env
IKLIM_ENV=test # prod | test | local (IKLIM_BASE_URL yoksa kullanılır)
IKLIM_BASE_URL= # Opsiyonel. Tanımlıysa IKLIM_ENV'i override eder
IKLIM_HMAC_SECRET=<secret> # Zorunlu. İstek imzalama için HMAC-SHA256 anahtarı
IKLIM_USERNAME=<email> # Zorunlu. API kullanıcı e-postası
IKLIM_PASSWORD=<password> # Zorunlu. API kullanıcı şifresi
IKLIM_TOKEN_STORE_PATH=/home/murat/iklim-mcp-server/token-state.bin # Opsiyonel. Access/refresh token binary dosyası
IKLIM_HTTP_LOG_PATH= # Opsiyonel. API istek log dosyası (örn: /var/log/iklim-mcp/http.log)
IKLIM_HTTP_LOG_MAX_BYTES=5242880 # Opsiyonel. Rotate eşiği (byte), default: 5MB
IKLIM_HTTP_LOG_MAX_FILES=5 # Opsiyonel. Tutulacak rotated dosya sayısı (0 = geçmiş dosya tutma)
IKLIM_HTTP_LOG_REQUEST_BODY_MAX_BYTES=16384 # Opsiyonel. Request body log limiti (byte)
IKLIM_HTTP_LOG_RESPONSE_BODY_MAX_BYTES=16384 # Opsiyonel. Response body log limiti (byte)
```
**🌍 Ortama göre base URL:**
| `IKLIM_ENV` | URL |
|-------------|-----|
| `prod` | `https://api.iklim.co` |
| `test` | `https://api-test.iklim.co` |
| `local` | `http://localhost:8080` |
**📝 API istek logları (rotate):**
- `IKLIM_HTTP_LOG_PATH` tanımlıysa MCP server yaptığı tüm API çağrıları için tek satır JSON log yazar.
- Her satırda istek header'ları da (`requestHeaders`) bulunur; hassas alanlar maskelenir (`Authorization`, `X-Signature`, `X-Idempotency-Key`, `X-Nonce`).
- Request body (`requestBody`) loglanır; JSON ise hassas alanlar maskelenir (örn. `password`, `token`) ve değer `IKLIM_HTTP_LOG_REQUEST_BODY_MAX_BYTES` sınırında kırpılır.
- Response header'ları (`responseHeaders`) loglanır; hassas alanlar maskelenir (`Set-Cookie`/`Cookie` dahil).
- Response body (`responseBody`) loglanır; JSON ise hassas alanlar maskelenir (`password`, `secret`, `token` vb.) ve değer `IKLIM_HTTP_LOG_RESPONSE_BODY_MAX_BYTES` sınırında kırpılır.
- Log dosyası `IKLIM_HTTP_LOG_MAX_BYTES` değerini aşınca rotate olur:
- `http.log``http.log.1`
- eski `http.log.1``http.log.2` ... `http.log.<N>`
- `IKLIM_HTTP_LOG_MAX_FILES` kadar geçmiş dosya tutulur.
---
## ▶️ Build ve Çalıştırma
```bash
# TypeScript'i derle (dist/ klasörünü oluşturur)
npm run build
# Derlenmiş server'ı başlat
npm start
# Geliştirme modunda çalıştır (derleme gerekmez, ts-node kullanır)
npm run dev
```
Başarılı başlatmada çıktı:
```
iklim.co MCP server running
```
> Server **stdio** transportu üzerinden iletişim kurar — doğrudan terminal ile çalıştırmak yerine bir MCP istemcisi tarafından yönetilmesi beklenir.
---
## 🔌 MCP Client Konfigürasyonu
### Claude CLI (.mcp.json)
Projenin kök dizinindeki `.mcp.json` dosyası Claude CLI tarafından otomatik olarak yüklenir:
```json
{
"mcpServers": {
"iklim": {
"command": "node",
"args": ["/tam/yol/mcp-server/dist/index.js"],
"env": {
"IKLIM_ENV": "test",
"IKLIM_HMAC_SECRET": "<secret>",
"IKLIM_USERNAME": "<email>",
"IKLIM_PASSWORD": "<password>"
}
}
}
}
```
Global olarak tanımlamak için `~/.claude/settings.json` içine aynı `mcpServers` bloğunu ekleyin.
### OpenClaw
`openclaw mcp set` komutu `env` parametresini desteklemez. Tüm alanlar tek satır JSON olarak geçirilmeli, `env` de dahil:
```bash
openclaw mcp set iklim '{"type":"stdio","command":"node","args":["/tam/yol/mcp-server/dist/index.js"],"env":{"IKLIM_ENV":"test","IKLIM_HMAC_SECRET":"<secret>","IKLIM_USERNAME":"<email>","IKLIM_PASSWORD":"<password>"}}'
```
Veya `~/.openclaw/openclaw.json` içinde `mcp` bölümüne doğrudan ekleyin (okunabilir format):
```json
{
"mcp": {
"iklim": {
"type": "stdio",
"command": "node",
"args": ["/tam/yol/mcp-server/dist/index.js"],
"env": {
"IKLIM_ENV": "test",
"IKLIM_HMAC_SECRET": "<secret>",
"IKLIM_USERNAME": "<email>",
"IKLIM_PASSWORD": "<password>"
}
}
}
}
```
### Diğer MCP İstemcileri
MCP stdio standardını destekleyen her istemci kullanılabilir. Gerekli bilgiler:
- **transport**: `stdio`
- **command**: `node`
- **args**: `["<dist/index.js tam yolu>"]`
- **env**: Yukarıdaki dört değişken
---
## 🛠️ Araç Kataloğu
### ⚡ Yıldırım / Lightning
#### `get_lightnings_within`
Belirli bir merkez noktası ve yarıçap içindeki yıldırım çarpmalarını sorgular.
| Parametre | Tip | Açıklama |
|-----------|-----|----------|
| `latitude` | number | Merkez enlem (-90 / 90) |
| `longitude` | number | Merkez boylam (-180 / 180) |
| `radius` | number | Yarıçap, metre (0 50.000) |
| `backwardInterval` | number | Geriye dönük süre, saniye (60 2.592.000 / 30 gün) |
| `endTimeEpoch` | number | Sorgu bitiş zamanı, epoch ms |
| `pageNumber` | number? | Sayfa numarası (default: 0) |
| `pageSize` | number? | Sayfa boyutu, max 100 (default: 10) |
#### `get_lightnings_page`
Zaman aralığına göre yıldırım verilerini sayfalı olarak getirir.
| Parametre | Tip | Açıklama |
|-----------|-----|----------|
| `backwardInterval` | number | Geriye dönük süre, saniye (60 432.000 / 5 gün) |
| `endTimeEpoch` | number | Sorgu bitiş zamanı, epoch ms |
| `pageNumber` | number? | Sayfa numarası |
| `pageSize` | number? | Sayfa boyutu, max 100 |
---
### 🌪️ Fırtına / Thunderstorm
#### `get_thunderstorms_within`
Yarıçap içindeki fırtına hücrelerini sorgular.
| Parametre | Tip | Açıklama |
|-----------|-----|----------|
| `latitude` | number | Merkez enlem |
| `longitude` | number | Merkez boylam |
| `radius` | number | Yarıçap, metre (0 50.000) |
| `backwardInterval` | number | Geriye dönük süre, saniye (60 2.592.000) |
| `endTimeEpoch` | number | Bitiş zamanı, epoch ms |
| `intersectsWith` | string? | `THREAT_POLYGON` veya `CELL_POLYGON` |
| `pageNumber` | number? | |
| `pageSize` | number? | |
#### `get_thunderstorms_page`
Zaman aralığına göre fırtına verilerini sayfalı getirir.
| Parametre | Tip | Açıklama |
|-----------|-----|----------|
| `backwardInterval` | number | Geriye dönük süre, saniye (60 432.000) |
| `endTimeEpoch` | number | Bitiş zamanı, epoch ms |
| `pageNumber` | number? | |
| `pageSize` | number? | |
#### `get_thunderstorm_details`
Belirli bir fırtına olayının geçmiş detaylarını getirir.
| Parametre | Tip | Açıklama |
|-----------|-----|----------|
| `eventId` | string | Örn: `EVT20240413001` |
| `pageNumber` | number? | |
| `pageSize` | number? | |
---
### 🌧️ Yağış / Precipitation
#### `get_precipitations_within`
Dairesel alan içindeki radar yağış verilerini sorgular.
| Parametre | Tip | Açıklama |
|-----------|-----|----------|
| `latitude` | number | Merkez enlem |
| `longitude` | number | Merkez boylam |
| `radius` | number | Yarıçap, metre (0 50.000) |
| `backwardInterval` | number | Geriye dönük süre, saniye (60 2.592.000) |
| `endTimeEpoch` | number | Bitiş zamanı, epoch ms |
| `intensityThreshold` | string? | Min. yoğunluk: `DRIZZLE` < `LIGHT` < `MODERATE` < `HEAVY` < `VERY_HEAVY` < `EXTREME` |
| `pageNumber` | number? | |
| `pageSize` | number? | |
#### `get_precipitations_page`
Zaman aralığına göre yağış verilerini sayfalı getirir. `intensityThreshold` **zorunludur**.
---
### 🌤️ Hava Tahmini / Forecast
#### `get_hourly_forecast`
Saatlik hava durumu tahminlerini getirir (114 gün).
| Parametre | Tip | Açıklama |
|-----------|-----|----------|
| `latitude` | number | Enlem |
| `longitude` | number | Boylam |
| `forecastDays` | number? | 114, default 7 |
| `metrics` | string[]? | İstenilen metrik listesi (bkz. aşağısı) |
| `startTime` | string? | ISO 8601 başlangıç zamanı |
| `solarPanelTiltForRadiation` | number? | Panel eğim açısı (radyasyon metriği için) |
| `solarPanelAzimuthForRadiation` | number? | Panel azimut açısı |
<details>
<summary>📊 Desteklenen metrikler (53 adet)</summary>
`WEATHER_ICON`, `TEMPERATURE`, `APPARENT_TEMPERATURE`, `DEW_POINT_TEMPERATURE`, `HUMIDITY`, `CLOUD_COVER`, `CLOUD_COVER_LOW`, `CLOUD_COVER_MID`, `CLOUD_COVER_HIGH`, `WIND_SPEED`, `WIND_GUST`, `WIND_DIRECTION`, `WIND_SPEED_AT_100M`, `WIND_DIRECTION_AT_100M`, `PRECIPITATION`, `RAIN`, `SHOWERS`, `SNOWFALL`, `SNOW_DEPTH`, `PRECIPITATION_PROBABILITY`, `WEATHER_CODE`, `PRESSURE_MSL`, `SURFACE_PRESSURE`, `VISIBILITY`, `EVAPOTRANSPIRATION`, `ET0_FAO_EVAPOTRANSPIRATION`, `VAPOUR_PRESSURE_DEFICIT`, `CAPE`, `LIFTED_INDEX`, `CONVECTIVE_INHIBITION`, `SUNSHINE_DURATION`, `SHORTWAVE_RADIATION`, `DIRECT_RADIATION`, `DIFFUSE_RADIATION`, `DIRECT_NORMAL_IRRADIANCE`, `GLOBAL_TILTED_IRRADIANCE`, `TERRESTRIAL_RADIATION`, `SHORTWAVE_RADIATION_INSTANT`, `DIRECT_RADIATION_INSTANT`, `DIFFUSE_RADIATION_INSTANT`, `DIRECT_NORMAL_IRRADIANCE_INSTANT`, `GLOBAL_TILTED_IRRADIANCE_INSTANT`, `TERRESTRIAL_RADIATION_INSTANT`, `SOIL_TEMPERATURE_0CM`, `SOIL_TEMPERATURE_6CM`, `SOIL_TEMPERATURE_18CM`, `SOIL_TEMPERATURE_54CM`, `SOIL_MOISTURE_0_TO_1CM`, `SOIL_MOISTURE_1_TO_3CM`, `SOIL_MOISTURE_3_TO_9CM`, `SOIL_MOISTURE_9_TO_27CM`, `SOIL_MOISTURE_27_TO_81CM`, `IS_DAY`
</details>
#### `get_daily_forecast`
Günlük agregat tahminleri getirir. Parametreler `get_hourly_forecast` ile aynıdır (`solarPanel*` parametreleri hariç).
#### `get_current_weather`
Konum için anlık (en güncel) hava verilerini getirir.
| Parametre | Tip | Açıklama |
|-----------|-----|----------|
| `latitude` | number | Enlem |
| `longitude` | number | Boylam |
| `metrics` | string[]? | Yukarıdaki metrik listesinden seçim |
---
### 👤 Auth & Kullanıcı
#### `auth_register`
Yeni kullanıcı kaydı oluşturur.
| Parametre | Tip |
|-----------|-----|
| `username` | string (e-posta) |
| `password` | string |
| `firstName` | string |
| `lastName` | string |
| `midName` | string? |
| `locale` | string? |
| `timezone` | string? |
#### `auth_logout`
Geçerli JWT token'ı geçersiz kılar.
#### `user_get_me`
Oturum açmış kullanıcının profilini getirir.
#### `user_get`
`userId` ile kullanıcı detayını getirir.
#### `user_create`
_(Admin)_ Yeni kullanıcı oluşturur. `roles` ve `status` zorunludur.
#### `user_update`
_(Admin)_ `userId` ile kullanıcı alanlarını günceller.
#### `user_list`
Kullanıcı listesini sayfalı getirir. `roles`, `status`, `pageNumber`, `pageSize` ile filtrelenir.
#### `user_unblock`
Bloke edilmiş kullanıcıyı açar.
#### `user_change_password`
`oldPassword` ve `newPassword` ile şifre değiştirir.
#### `user_password_reset_request`
Şifre sıfırlama e-postası gönderir. `userName` ve `passwordResetPageLink` gerekir.
#### `user_password_reset`
Sıfırlama token'ı ile şifre günceller. `userId`, `token`, `newPassword`, `loginPageLink` gerekir.
---
### 🏢 Hesap / Account
#### `account_get`
`userId` ile hesap detaylarını getirir.
#### `account_create`
Yeni hesap oluşturur.
| Parametre | Tip | Değerler |
|-----------|-----|----------|
| `type` | string | `INDIVIDUAL` \| `ORGANIZATION` |
| `subscriptionPlan` | string | `NONE` \| `TRIAL` \| `BASIC_MONTHLY` \| `BASIC_YEARLY` \| `PREMIUM_MONTHLY` \| `PREMIUM_YEARLY` \| `CUSTOM` |
| `mobilePhoneNumber` | string? | |
| `location` | string? | |
| `company` | string? | |
| `industry` | string? | |
| `profilePictureUrl` | string? | |
#### `account_update`
`accountId` ile hesap alanlarını günceller.
#### `account_activation_request`
Aktivasyon e-postası gönderir.
#### `account_activate`
E-posta doğrulama token'ı ile hesabı aktive eder.
#### `account_phone_activation_request`
SMS doğrulama kodu gönderir.
#### `account_activate_phone`
SMS token'ı ile telefonu doğrular.
#### `account_update_subscription`
Abonelik planını değiştirir.
---
### 📍 Nokta Alarmları / Point Alarms
Belirli bir GPS koordinatı ve yarıçap etrafındaki olaylar için uyarı abonelikleri.
#### `point_alarm_register`
Yeni nokta alarmı oluşturur.
| Parametre | Tip | Açıklama |
|-----------|-----|----------|
| `recipientId` | string | Uyarı alıcısı ID |
| `latitude` | number | Merkez enlem |
| `longitude` | number | Merkez boylam |
| `radius` | number | Yarıçap, metre (0 50.000) |
| `lightningFilter` | object? | Yıldırım filtresi |
| `thunderstormFilter` | object? | Fırtına filtresi |
| `precipitationFilter` | object? | Yağış filtresi |
| `webhook` | object? | Webhook konfigürasyonu |
#### `point_alarm_update`
Mevcut kaydı günceller.
#### `point_alarm_delete`
Alarm kaydını siler.
#### `point_alarm_get_by_id`
Tekil alarm detayını getirir.
#### `point_alarm_get_by_recipient`
Alıcıya ait tüm alarmları listeler.
#### `point_alarm_list`
Sayfalı alarm listesi. `recipientIds` ile filtreleme yapılabilir.
---
### 🗺️ Coğrafi Alarmlar / Geo Alarms
İdari sınır, poligon veya H3 adresi bazlı uyarı abonelikleri.
#### `geo_alarm_register`
Yeni coğrafi alarm oluşturur. Üç sınır tipi desteklenir:
```json
// İdari sınır
{ "type": "ADMINISTRATIVE", "cityId": 6, "districtId": 60 }
// Poligon
{ "type": "POLYGON", "polygon": { "exterior": [{"lat": 39.9, "lng": 32.8}, ...] } }
// H3 hücre indeksi
{ "type": "H3INDEX", "h3Address": "8f2830828052d25" }
```
#### `geo_alarm_update` / `geo_alarm_delete` / `geo_alarm_get_by_id` / `geo_alarm_get_by_recipient` / `geo_alarm_list`
Nokta alarmlarıyla aynı imza.
#### 🏙️ Konum Kataloğu
| Araç | Açıklama |
|------|----------|
| `geo_alarm_list_cities` | Tüm illeri listeler |
| `geo_alarm_get_city` | `cityId` ile il detayı |
| `geo_alarm_list_districts` | `cityId` ile ilçeleri listeler |
| `geo_alarm_get_district` | `districtId` ile ilçe detayı |
| `geo_alarm_list_neighborhoods` | `districtId` ile mahalleleri listeler |
| `geo_alarm_get_neighborhood` | `neighborhoodId` ile mahalle detayı |
---
### 📅 Tahmin Alarmları / Forecast Alarms
Eşik aşıldığında sabah (04:00 UTC) veya akşam (16:00 UTC) uyarı gönderir.
#### `forecast_alarm_register`
Yeni tahmin alarmı oluşturur.
**Sınır tipleri:**
```json
{ "type": "ADMINISTRATIVE", "cityId": 6, "districtId": 60 }
{ "type": "POINT", "latitude": 39.92, "longitude": 32.85 }
```
**⚠️ Eşik parametreleri:**
| Parametre | Değerler |
|-----------|----------|
| `precipitationThreshold` | mm cinsinden sayısal değer |
| `snowFallThreshold` | `LIGHT` \| `MODERATE` \| `HEAVY` |
| `windGustThreshold` | `STRONG_WIND` \| `STORM` \| `SEVERE_STORM` \| `HURRICANE` |
| `hotTemperatureThreshold` | `HOT_SNAP` \| `HEAVY_HOT_SNAP` \| `EXTREME_HOT_SNAP` |
| `coldTemperatureThreshold` | `COLD_SNAP` \| `HEAVY_COLD_SNAP` \| `EXTREME_COLD_SNAP` |
**📬 Teslimat:**
| Parametre | Açıklama |
|-----------|----------|
| `forecastDays` | 17, kaç günlük tahmin |
| `forecastAlarmDelivery` | `MORNING` (04:00 UTC) \| `EVENING` (16:00 UTC) |
#### `forecast_alarm_update` / `forecast_alarm_delete` / `forecast_alarm_get_by_id` / `forecast_alarm_get_by_recipient` / `forecast_alarm_list`
Nokta alarmlarıyla aynı imza.
#### 🏙️ Konum Kataloğu
| Araç | Açıklama |
|------|----------|
| `forecast_alarm_list_cities` | Tüm illeri listeler |
| `forecast_alarm_get_city` | `cityId` ile il detayı |
| `forecast_alarm_list_districts` | `cityId` ile ilçeleri listeler |
| `forecast_alarm_get_district` | `districtId` ile ilçe detayı |
---
## 🏗️ Mimari
```
src/
├── index.ts # MCP server başlatma, tool routing
├── config.ts # Ortam değişkenleri
├── auth.ts # JWT token yönetimi (otomatik yenileme)
├── client.ts # HTTP API istemcisi (HMAC imzalama)
├── security.ts # HMAC-SHA256, nonce, idempotency key
└── tools/
├── lightnings.ts
├── thunderstorms.ts
├── precipitations.ts
├── forecasts.ts
├── auth.ts
├── accounts.ts
├── point-alarms.ts
├── geo-alarms.ts
└── forecast-alarms.ts
```
**İstek akışı:**
```
MCP İstemci
index.ts (CallToolRequestSchema)
tools/<kategori>.ts ← Zod validasyonu
client.ts (apiGet / apiPost / apiPatch / apiDelete)
│ ├── auth.ts → geçerli JWT token al (gerekirse otomatik yenile)
│ └── security.ts → HMAC-SHA256 imzası üret
iklim.co REST API
```
---
## 🔐 Kimlik Doğrulama ve Güvenlik
API ile iletişim iki katmanlı güvenlik mekanizması üzerine kuruludur: **JWT tabanlı kimlik doğrulama** ve **HMAC-SHA256 istek imzalama**. Her ikisi de her istekte birlikte kullanılır.
### Dahili Auth Akışı
Server ilk araç çağrısında otomatik olarak login olur; bu işlem dışarıdan tetiklenmez, tamamen içseldir.
```
İlk araç çağrısı
getValidAccessToken() ← auth.ts
├─ tokenState yok → login()
│ POST /v1/auth/login
│ { username, password }
│ ← { accessToken, refreshToken }
│ JWT payload decode → expiry hesapla
│ tokenState'e kaydet
├─ accessToken süresi dolmak üzere (< 30 sn kaldı) → refresh()
│ POST /v1/auth/refresh
│ { refreshToken }
│ ← { accessToken, refreshToken }
│ tokenState güncelle
└─ accessToken geçerli → doğrudan döndür
```
> ⚠️ **Önemli:** `login` ve `refresh` endpoint'leri `Authorization: Bearer` header'ı **içermez** — bu istekler yalnızca HMAC imzasıyla doğrulanır (bkz. aşağısı).
### JWT Token Yaşam Döngüsü
Token state bellekte (`tokenState`) tutulur ve her araç çağrısından önce kontrol edilir:
```
tokenState = {
accessToken: string // API isteklerinde kullanılan JWT
refreshToken: string // accessToken yenileme için
accessTokenExpiresAt: number // epoch ms (JWT payload'dan decode edilir)
refreshTokenExpiresAt: number // epoch ms
}
```
Karar ağacı (`EXPIRY_BUFFER_MS = 30.000 ms`):
```
now < accessTokenExpiresAt - 30s → mevcut token'ı kullan
now < refreshTokenExpiresAt - 30s → refresh token ile yenile
aksi hâlde → yeniden login ol
```
30 saniyelik tampon, istek transit süresinde token'ın geçersiz kalması riskini ortadan kaldırır.
### HTTP İstek Header'ları
Her API isteğine (`login` ve `refresh` dahil) aşağıdaki header'lar eklenir:
| Header | Değer | Açıklama |
|--------|-------|----------|
| `Content-Type` | `application/json` | Sabit |
| `Authorization` | `Bearer <accessToken>` | Yalnızca normal API isteklerinde; login/refresh'te **yoktur** |
| `X-Signature` | hex string | HMAC-SHA256 imzası (bkz. aşağısı) |
| `X-Timestamp` | `Date.now()` string | Unix epoch, milisaniye |
| `X-Nonce` | UUID v4 | Her istekte tekil, replay saldırısı önleme |
| `X-Idempotency-Key` | UUID v4 | `POST`, `PUT`, `PATCH` ve `DELETE` isteklerinde |
### HMAC-SHA256 İmza Hesabı
`X-Signature` değeri şu dört bileşenin `|` ile birleştirilmesinden elde edilen string'in HMAC-SHA256'sıdır:
```
imzalanacak_veri = "METHOD|PATH_WITH_QUERY|TIMESTAMP|BODY"
X-Signature = HMAC-SHA256(imzalanacak_veri, IKLIM_HMAC_SECRET) → hex
```
**Bileşenler:**
| Bileşen | Açıklama | Örnek |
|---------|----------|-------|
| `METHOD` | HTTP metodu, büyük harf | `POST` |
| `PATH_WITH_QUERY` | Sorgu parametreleri dahil path | `/v1/lightnings/within` veya `/v1/users?page=0` |
| `TIMESTAMP` | `X-Timestamp` ile aynı değer | `1774349677000` |
| `BODY` | JSON body string; body yoksa boş string `""` | `{"username":"..."}` |
**Örnek hesaplama (GET isteği):**
```
METHOD = "GET"
PATH = "/v1/users?pageNumber=0&pageSize=10"
TIMESTAMP = "1774349677000"
BODY = "" ← GET isteğinde body yok
imzalanacak = "GET|/v1/users?pageNumber=0&pageSize=10|1774349677000|"
X-Signature = HMAC-SHA256(imzalanacak, secret) → "a3f9c2..."
```
**Örnek hesaplama (POST isteği):**
```
METHOD = "POST"
PATH = "/v1/lightnings/within"
TIMESTAMP = "1774349677000"
BODY = '{"center":{"lat":39.87,"lng":32.74},"radius":50000,...}'
imzalanacak = "POST|/v1/lightnings/within|1774349677000|{\"center\":...}"
X-Signature = HMAC-SHA256(imzalanacak, secret) → "7be41d..."
```
> Kaynak: [`src/security.ts`](src/security.ts) — `buildSignature()` fonksiyonu
### Auth ile Normal İstekler Arasındaki Fark
| | `POST /v1/auth/login` | `POST /v1/auth/refresh` | Normal API İstekleri |
|---|---|---|---|
| `Authorization` | ❌ | ❌ | ✅ `Bearer <token>` |
| `X-Signature` | ✅ | ✅ | ✅ |
| `X-Timestamp` | ✅ | ✅ | ✅ |
| `X-Nonce` | ✅ | ✅ | ✅ |
| `X-Idempotency-Key` | ✅ | ✅ | ✅ (POST/PUT/PATCH/DELETE) |
### Güvenlik Önerileri
- 🔒 `IKLIM_HMAC_SECRET` ve `IKLIM_PASSWORD` değerlerini kaynak koda veya git geçmişine eklemeyin
- 🏭 Üretim ortamında `.env` dosyası yerine sistem ortam değişkenlerini veya secret manager kullanın
- 🌍 Her ortam (prod/test/local) için ayrı kimlik bilgileri kullanın
- 🔄 HMAC secret'ı düzenli olarak rotate edin
---
## 💻 Geliştirici Notları
** Yeni araç eklemek**
1. `src/tools/` altında ilgili dosyaya yeni tool tanımı ve handler ekle
2. `src/index.ts` içinde `toolHandlerMap`'e ve `allTools` dizisine kaydet
3. `npm run build` ile derle
**✅ Zod şemaları**
Tüm araç girdileri Zod ile çalışma zamanında doğrulanır. Her araç `schema.parse(args)` çağrısından geçer; geçersiz girdi anlamlı bir hata mesajıyla geri döner.
**🔧 TypeScript derleme hedefi**
`tsconfig.json``target: ES2022`, `module: Node16`
**📦 Bağımlılıklar**
| Paket | Versiyon | Kullanım |
|-------|----------|----------|
| `@modelcontextprotocol/sdk` | ^1.0.0 | MCP altyapısı |
| `zod` | ^3.23.8 | Girdi validasyonu |
| `typescript` | ^5.5.0 | Derleme |
| `ts-node` | ^10.9.2 | Geliştirme modu |