Skip to main content
Glama
README.md
# Triplus MCP Server

A comprehensive MCP (Model Context Protocol) server for travel-related information, providing real-time exchange rates, travel safety alerts, and carry-on prohibited items search.

- 한국어: [README-ko.md](README-ko.md)

## Features

### 🌍 Exchange Rates
- Real-time currency exchange rates via Frankfurter API
- Support for 30+ currencies
- Historical data and time series queries

### ⚠️ Travel Safety Alerts
- Korean government travel warnings and alerts
- Country-specific safety information
- Real-time updates from data.go.kr

### ✈️ Prohibited Items Search
- Searchable database of carry-on prohibited items
- Korean and English search support
- Category filtering
- Based on Korea Transportation Safety Authority data (504 items)

### 🏢 Airport Congestion
- Real-time airport congestion information
- Support for 6 major Korean airports
- Wait time estimates for Incheon Airport
- Zone-by-zone congestion levels
- **NEW**: Includes timezone information for each airport

### 🌐 Timezone Information
- Get current time and timezone info for any location
- Convert times between timezones
- Support for 25+ countries and major cities
- Airport code to timezone mapping
- World clock for multiple locations
- DST (Daylight Saving Time) support

### 🏛️ Embassy & Consulate Information
- Korean embassy and consulate locations worldwide
- Contact information (phone, emergency hotline, toll-free)
- Address and GPS coordinates
- Search by country name (Korean) or ISO country code
- Real-time updates from Korean Ministry of Foreign Affairs

### 🛂 Entrance Visa Requirements
- Country-by-country entrance visa requirements (visa needed / visa-free conditions)
- Supports general, official, and diplomatic passports (when provided)
- Search by country name (Korean) or ISO country code
- Real-time updates from Korean Ministry of Foreign Affairs

### 📅 Public Holidays
- Public holiday lookup by country and year/date
- Optional subdivision (state/province) support
- Observed/alternate holiday toggle
- Powered by vacanza/holidays

### Weather
- Hourly weather observations via BrightSky API
- Query by latitude, longitude, and date (YYYY-MM-DD)

### ✈️ Flight Search (Korean Airports)
- **Korean Regional Airports (KAC)**: Real-time flight status for GMP, PUS, TAE, CJJ, CJU
- **Incheon Airport (IIAC)**: Detailed departure/arrival info for ICN including gate, terminal, baggage carousel
- Domestic and international flights
- Flight status, delays, and estimated times
- Simple date-based search (YYYYMMDD format)

### Trip Briefing (NEW)
- One-shot trip briefing (timezone, alarms, rates, congestion, packing checks)
- Airport arrival time recommendation (uses congestion when available)

## Available Tools

| Tool | Description | Data Source | Notes |
|------|-------------|-------------|-------|
| `get_exchange_rates` | Fetch exchange rates (latest, historical, or time series) | Frankfurter API | |
| `list_exchange_rate_currencies` | List all supported currency codes | Frankfurter API | |
| `get_travel_alarm` | Get travel safety alerts by country | data.go.kr (MOFA Travel Alarm 0404) | API key required |
| **`get_embassy_list`** | Get Korean embassy/consulate information by country | data.go.kr (MOFA Embassy/Consulate) | API key required |
| **`get_entrance_visa_requirements`** | Get entrance visa requirements by country | data.go.kr (MOFA Visa Requirements) | API key required |
| **`get_holidays`** | Get public holidays by country and year/date | Holiday API (vacanza/holidays) | |
| `search_prohibited_items` | Search prohibited carry-on items | Local dataset (KOTSA via data.go.kr file) | Dataset required |
| `get_airport_congestion` | Get real-time airport congestion data (now includes timezone info) | data.go.kr (IIAC) + api.odcloud.kr (KAC) | API key required |
| `list_supported_airports` | List all airports supported for congestion queries | Internal list (Korean airports) | |
| **`get_timezone_info`** | Get timezone information for any location | Luxon + internal mapping | |
| **`convert_timezone`** | Convert datetime between timezones | Luxon + internal mapping | |
| **`get_world_clock`** | Get current time for multiple locations | Luxon + internal mapping | |
| **`get_weather`** | Fetch hourly weather observations via BrightSky API (lat/lon/date) | BrightSky API (DWD data) | |
| **`check_packing`** | Check a list of items against carry-on prohibited items dataset | Local dataset (KOTSA) | Dataset required |
| **`recommend_airport_arrival_time`** | Recommend when to arrive/leave for the airport (uses congestion when available) | Internal logic + airport congestion APIs (data.go.kr + api.odcloud.kr) | |
| **`generate_trip_briefing`** | One-shot trip briefing (timezone, alarms, rates, congestion, packing) | Luxon + Frankfurter + data.go.kr + airport congestion APIs + prohibited items dataset | |
| **`search_kac_flights`** | Search flights at Korean regional airports (GMP, PUS, TAE, CJJ, CJU) | data.go.kr (KAC) | API key required |
| **`search_icn_flights`** | Search flights at Incheon International Airport (ICN) | data.go.kr (IIAC) | API key required |
| **`diagnose_setup`** | Diagnose API keys and local dataset availability | Local checks | |

## Response Format

All tools return a JSON envelope with data sources:

```json
{
  "sources": [
    {
      "name": "Source name",
      "url": "https://source.example",
      "note": "Optional notes"
    }
  ],
  "data": {}
}
```

- `sources`: Providers and inputs used to build the response.
- `data`: Tool-specific payload. For XML returnType tools, `data` may be a string.

## Quick Demo

If you're unsure whether keys/datasets are ready, run `diagnose_setup` first.

Example: one-shot trip briefing
```json
{
  "destinationLocation": "Tokyo",
  "destinationCountryIso2": "JP",
  "originAirportCode": "GMP",
  "departureTime": "2025-12-20T09:00:00+09:00",
  "transitMinutes": 60,
  "packingItems": ["Lighter", "Scissors"]
}
```

## Setup

```bash
npm install
npm run build

# Download prohibited items dataset (required for search_prohibited_items)
npm run update:prohibited-items
```

## HTTP(S) Transport (Streamable HTTP)

Triplus can run over HTTP using the MCP Streamable HTTP transport. For HTTPS, terminate TLS at a reverse proxy
(nginx/Apache) and proxy to this server.

### Run (HTTP)

```bash
npm run build
npm run start:http
# or: node dist/src/http.js
```

Defaults:
- MCP endpoint: `/mcp`
- Health check: `/healthz`
- Bind: `127.0.0.1:3000`

Environment variables:
- `MCP_HTTP_HOST` (default: `127.0.0.1`)
- `MCP_HTTP_PORT` (default: `3000`, falls back to `PORT`)
- `MCP_HTTP_PATH` (default: `/mcp`)
- `MCP_HTTP_STATEFUL` (default: `false`, set to `true` to enable session IDs)
- `MCP_HTTP_ENABLE_JSON_RESPONSE` (default: `false`, set to `true` to return JSON instead of SSE)

### Reverse Proxy (nginx)

Example (serve MCP at `https://example.com/mcp`):

```nginx
location /mcp {
  proxy_pass http://127.0.0.1:3000/mcp;
  proxy_http_version 1.1;
  proxy_set_header Host $host;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto $scheme;

  # Important for SSE streaming responses
  proxy_buffering off;
  proxy_read_timeout 3600;
}
```

### Reverse Proxy (Apache)

Recommended: configure in your VirtualHost with `mod_proxy` + `mod_proxy_http`:

```apache
ProxyPreserveHost On
ProxyPass /mcp http://127.0.0.1:3000/mcp flushpackets=on retry=0
ProxyPassReverse /mcp http://127.0.0.1:3000/mcp

# Important for SSE streaming responses
ProxyTimeout 3600
```

If you must use `.htaccess`, see `.htaccess` in this repo (requires `mod_rewrite` and proxy support).

## Claude Desktop Integration

### Configuration

Add to your `claude_desktop_config.json`:

**Windows**:
```json
{
  "mcpServers": {
    "triplus": {
      "command": "node",
      "args": [
        "C:\\Users\\YourName\\path\\to\\Triplus\\dist\\index.js"
      ]
    }
  }
}
```

**macOS/Linux**:
```json
{
  "mcpServers": {
    "triplus": {
      "command": "node",
      "args": [
        "/absolute/path/to/Triplus/dist/index.js"
      ]
    }
  }
}
```

### Config File Location

- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
- **Linux**: `~/.config/Claude/claude_desktop_config.json`

**Note**: The server uses absolute paths internally, so it works correctly regardless of the working directory.

## Environment Variables

Create a `.env` file in the project root (see `.env.example`). The server loads it automatically:

```env
# Required for get_travel_alarm, get_embassy_list, get_entrance_visa_requirements,
# get_airport_congestion (ICN), search_kac_flights, and search_icn_flights
DATA_GO_KR_SERVICE_KEY=your_api_key_here

# Required for get_airport_congestion (PUS, TAE, CJJ, GMP, CJU)
OPENCLOUD_SERVICE_KEY=your_opencloud_api_key_here

# Optional: Holiday API base URL (defaults to https://holiday.rasca99.dev)
HOLIDAY_API_BASE_URL=https://holiday.rasca99.dev

# Optional: Override dataset paths
# PROHIBITED_ITEMS_DATA_PATH=custom/path/to/data.json
# PROHIBITED_ITEMS_RAW_CSV_PATH=custom/path/to/raw.csv
# PROHIBITED_ITEMS_KEEP_RAW_CSV=true
```

### Get API Keys

**IMPORTANT**: Each API requires **separate application** and approval.

#### DATA_GO_KR_SERVICE_KEY

Register at [data.go.kr](https://www.data.go.kr) and request access to:

1. **여행경보서비스0404** (Travel Alarm Service)
   - For `get_travel_alarm` tool
   - Search: "여행경보서비스0404"
   - Click "활용신청" (Request Usage)

2. **인천국제공항공사_출국장 혼잡도 조회** (Incheon Airport Departure Congestion)
   - For `get_airport_congestion` with ICN airport
   - Search: "인천국제공항공사_출국장 혼잡도 조회"
   - Click "활용신청" (Request Usage)

3. **외교부_국가·지역별 재외공관 정보** (Embassy/Consulate)
   - For `get_embassy_list`
   - Search: "외교부_국가·지역별 재외공관 정보"
   - API endpoint: https://apis.data.go.kr/1262000/EmbassyService2/getEmbassyList2
   - Click "활용신청" (Request Usage)

4. **외교부_국가·지역별 입국허가요건** (Entrance Visa Requirements)
   - For `get_entrance_visa_requirements`
   - Search: "외교부_국가·지역별 입국허가요건"
   - Click "활용신청" (Request Usage)

5. **인천국제공항공사_항공기 운항 현황 상세 조회** (IIAC Flight Status)
   - For `search_icn_flights`
   - API endpoint: http://apis.data.go.kr/B551177/statusOfAllFltDeOdp
   - Click "활용신청" (Request Usage)

After approval (usually 1-2 hours), copy the **일반 인증키(Encoding)** from your account page.

#### OPENCLOUD_SERVICE_KEY

Register at [data.go.kr](https://www.data.go.kr) Open API section for:

1. **공항 혼잡도 관리(김해,대구,청주공항) v2**
   - For PUS, TAE, CJJ airports

2. **공항 혼잡도 관리**
   - For GMP, CJU airports

**Troubleshooting 403/401 errors**:
- Check that your API application is **approved** (not pending)
- Use the correct **일반 인증키(Encoding)** key
- Disable IP restrictions or register your IP
- See [Airport Congestion README](travel/airport-congestion/README.md) for details

**Note**: data.go.kr typically issues an account-level **일반 인증키(Encoding)**, so you can often reuse the same key for both `DATA_GO_KR_SERVICE_KEY` and `OPENCLOUD_SERVICE_KEY` (API usage approval is still required per service).

## Prohibited Items Dataset

The prohibited items search requires a local dataset that must be downloaded first:

```bash
# Download and process the dataset (run this once after setup)
npm run update:prohibited-items
```

This will:
- Download the latest data from Korea Transportation Safety Authority
- Parse and process 504 prohibited items
- Save to `datasets/prohibited-items/data/prohibited-items.json`
- Handle Korean (EUC-KR) encoding automatically

**Note**: You must run this command before using `search_prohibited_items` tool.

## Usage Examples

### Exchange Rates

Ask Claude:
```
"What's the current exchange rate from USD to KRW?"
"Show me EUR to JPY exchange rates for the last week"
"What currencies are supported for exchange rates?"
```

### Travel Alerts

Ask Claude (requires API key):
```
"Are there any travel warnings for Russia?"
"Show me travel alerts for Japan"
"What's the current travel advisory for Thailand?"
```

### Embassy & Consulate

Ask Claude (requires API key):
```
"Show me the Korean embassy info for Japan"
"What is the emergency phone number for the Korean consulate in the US?"
```
Notes:
- EmbassyService2 supports filtering by `countryName` or `countryIso2` (either one is enough).

### Entrance Visa Requirements

Ask Claude (requires API key):
```
"Do Koreans need a visa for Japan?"
"Show me entrance visa requirements for Vietnam (general passport)"
```

### Public Holidays

Ask Claude:
```
"Show me public holidays in Korea for 2026"
"List Japan holidays in October 2026"
"What are California (US-CA) holidays for 2026?"
```

### Prohibited Items

Ask Claude:
```
"Can I bring a power bank on a plane?"
"Is a lighter allowed in carry-on luggage?"
"Search for battery-related prohibited items"
```

### Airport Congestion

Ask Claude:
```
"What's the current congestion at Incheon Airport?"
"Show me Gimpo airport congestion levels"
"Is Jeju airport crowded right now?"
"Which gates at Incheon have the shortest wait times?"
```

### Timezone Information

Ask Claude:
```
"What time is it in Seoul right now?"
"Convert 3 PM Seoul time to New York time"
"What's the time difference between Tokyo and London?"
"Show me the current time in Seoul, Tokyo, New York, and Paris"
"What timezone is Incheon Airport in?"
"List all supported timezone locations"
```

### Weather

Ask Claude:
```
"Show me the hourly weather for 35.6895, 139.6917 on 2025-12-20"
```

### Flight Search

Ask Claude:
```
"Show me flights at Gimpo Airport (GMP) today"
"Find Korean Air flights at Incheon Airport on December 23, 2025"
"Search for departures from Jeju Airport (CJU) on 20251223"
"What flights are arriving at Incheon (ICN) from Tokyo in the next 2 hours?"
"Show me all departures and arrivals at Gimhae/Busan Airport (PUS) today"
```

Example KAC search (regional airports):
```json
{
  "airport": "GMP",
  "date": "20251223",
  "type": "departure"
}
```

Example ICN search (Incheon):
```json
{
  "date": "20251223",
  "type": "both",
  "startTime": "0600",
  "endTime": "2300"
}
```

Example search results for "보조배터리" (power bank):
```json
{
  "sources": [
    {
      "name": "한국교통안전공단_금지물품",
      "url": "https://www.data.go.kr/cmm/cmm/fileDownload.do?atchFileId=FILE_000000003160455&fileDetailSn=1&dataNm=%ED%95%9C%EA%B5%AD%EA%B5%90%ED%86%B5%EC%95%88%EC%A0%84%EA%B3%B5%EB%8B%A8_%EA%B8%88%EC%A7%80%EB%AC%BC%ED%92%88_20211231"
    }
  ],
  "data": {
    "source": {
      "name": "한국교통안전공단_금지물품",
      "downloadedAt": "2025-12-20T09:20:43.513Z"
    },
    "count": 2,
    "items": [
      {
        "id": 479,
        "koreanName": "보조배터리",
        "englishName": "battery",
        "category": "전자제품류",
        "searchCount": 148662
      },
      {
        "id": 433,
        "koreanName": "케이블충전기(보조배터리 제외)",
        "englishName": "Charger cable(except power banks)",
        "category": "전자제품류",
        "searchCount": 1239
      }
    ]
  }
}
```

Example airport congestion response for Incheon (ICN):
```json
{
  "sources": [
    {
      "name": "data.go.kr (IIAC departure congestion)",
      "url": "https://www.data.go.kr/data/15148225/openapi.do"
    }
  ],
  "data": [
    {
      "airportCode": "ICN",
      "airportName": "인천국제공항",
      "terminal": "Terminal 1",
      "zones": [
        {
          "zoneId": "DG1_E",
          "zoneName": "출국장 1 동쪽",
          "congestionLevel": 2,
          "congestionText": "보통",
          "waitTime": 22,
          "waitingPeople": 150
        },
        {
          "zoneId": "DG1_W",
          "zoneName": "출국장 1 서쪽",
          "congestionLevel": 1,
          "congestionText": "원활",
          "waitTime": 9,
          "waitingPeople": 48
        }
      ],
      "overallCongestion": {
        "level": 2,
        "text": "보통"
      },
      "timestamp": "2025-12-20T10:50:01.000Z",
      "operatingTime": "05:00~22:00",
      "source": "incheon",
      "timezone": {
        "timezone": "Asia/Seoul",
        "localTime": "2025-12-20T19:50:01.000+09:00",
        "utcOffset": 9,
        "utcOffsetString": "+09:00"
      }
    }
  ]
}
```

Example airport congestion response for Gimpo (GMP):
```json
{
  "sources": [
    {
      "name": "api.odcloud.kr (KAC airport congestion)",
      "url": "https://api.odcloud.kr/api/getAPRTPsgrCongestion_v2/v1/aprtPsgrCongestionV2"
    }
  ],
  "data": [
    {
      "airportCode": "GMP",
      "airportName": "김포국제공항",
      "zones": [
        {
          "zoneId": "A",
          "zoneName": "1구간",
          "congestionLevel": 1,
          "congestionText": "원활"
        },
        {
          "zoneId": "B",
          "zoneName": "2구간",
          "congestionLevel": 2,
          "congestionText": "보통"
        },
        {
          "zoneId": "C",
          "zoneName": "3구간",
          "congestionLevel": 1,
          "congestionText": "원활"
        }
      ],
      "overallCongestion": {
        "level": 1,
        "text": "원활"
      },
      "timestamp": "2025-12-20T10:50:15.000Z",
      "source": "opencloud_v1"
    }
  ]
}
```

**Supported Airports**:
- **ICN** - Incheon International Airport (인천국제공항)
- **PUS** - Gimhae International Airport / Busan (김해국제공항)
- **TAE** - Daegu International Airport (대구국제공항)
- **CJJ** - Cheongju International Airport (청주국제공항)
- **GMP** - Gimpo International Airport (김포국제공항)
- **CJU** - Jeju International Airport (제주국제공항)

**Congestion Levels**:
- **1 (원활)**: Smooth - Wait time < 20 minutes
- **2 (보통)**: Normal - Wait time 20-30 minutes
- **3 (혼잡)**: Crowded - Wait time 30-40 minutes
- **4 (매우혼잡)**: Very Crowded - Wait time > 40 minutes

Example timezone info response for Seoul:
```json
{
  "sources": [
    {
      "name": "Luxon + Triplus timezone mapping"
    }
  ],
  "data": {
    "timezone": "Asia/Seoul",
    "localTime": "2025-12-20T19:45:30.123+09:00",
    "utcOffset": 9,
    "utcOffsetString": "+09:00",
    "isDST": false,
    "abbreviation": "KST",
    "countryCode": "KR",
    "cityName": "Seoul"
  }
}
```

Example timezone conversion response:
```json
{
  "sources": [
    {
      "name": "Luxon + Triplus timezone mapping"
    }
  ],
  "data": {
    "originalTime": "2025-12-20T15:00:00.000+09:00",
    "originalTimezone": "Asia/Seoul",
    "convertedTime": "2025-12-20T01:00:00.000-05:00",
    "targetTimezone": "America/New_York",
    "timeDifference": -14
  }
}
```

**Supported Location Identifiers**:
- **Country names**: "South Korea", "Japan", "United States"
- **Country codes**: "KR", "JP", "US"
- **City names**: "Seoul", "Tokyo", "New York", "London"
- **Airport codes**: "ICN", "NRT", "JFK", "LHR"
- **IANA timezones**: "Asia/Seoul", "America/New_York"

**Supported Countries** (25+):
- Asia: South Korea, Japan, China, Thailand, Singapore, Vietnam, Malaysia, Indonesia, Philippines, India, UAE
- Europe: UK, France, Germany, Italy, Spain, Switzerland
- Americas: USA, Canada, Mexico, Brazil
- Oceania: Australia, New Zealand

## Technical Details

### Architecture
- **Transport**: STDIO (Standard Input/Output)
- **Runtime**: Node.js with ES Modules
- **Protocol**: MCP (Model Context Protocol)
- **Data Sources**:
  - Frankfurter API (exchange rates)
  - BrightSky API (weather)
  - Holiday API (vacanza/holidays)
  - data.go.kr API (travel alarms, embassy/consulate, entrance visa requirements, Incheon airport congestion, Korean airport flights)
  - Korea Airports Corporation API via data.go.kr (regional airport flights: GMP, PUS, TAE, CJJ, CJU)
  - Incheon International Airport Corporation API via data.go.kr (ICN flight details)
  - api.odcloud.kr API (domestic airport congestion)
  - Korea Transportation Safety Authority (prohibited items)
  - Luxon library (timezone information and conversions)

### Path Resolution
The server uses `__dirname`-based absolute paths instead of `process.cwd()`, ensuring correct file resolution even when launched from different working directories (common with Claude Desktop).

### Encoding Handling
Automatically detects and handles Korean character encoding (EUC-KR/UTF-8) when downloading government datasets.

## Troubleshooting

### "Missing required CSV header: 번호" Error

**Cause**: The prohibited items dataset hasn't been downloaded yet.

**Solution**:
```bash
npm run update:prohibited-items
```

### "Dataset file not found" Error

**Cause**: Dataset file is missing or server can't find it.

**Solutions**:
1. Run `npm run update:prohibited-items` to download the dataset
2. Verify `datasets/prohibited-items/data/prohibited-items.json` exists
3. Rebuild the project: `npm run build`

### Travel Alarm API Errors

**Cause**: Missing or invalid API key.

**Solution**:
1. Get API key from [data.go.kr](https://www.data.go.kr)
2. Add to `.env`: `DATA_GO_KR_SERVICE_KEY=your_key`
3. Restart Claude Desktop to reload the MCP server

## Development

```bash
# Install dependencies
npm install

# Build TypeScript
npm run build

# Update prohibited items dataset
npm run update:prohibited-items

# Run the server directly (for testing)
node dist/index.js
```

## Feature Documentation

- [Data sources / quotas / licenses](docs/DATA_SOURCES.md)
- [API cache metadata guide](docs/CACHE_METADATA.md)
- [Exchange Rates](travel/exchange-rates/README.md)
- [Travel Alarms](travel/travel-alarm/README.md)
- [Embassy / Consulate](travel/embassy/README.md)
- [Entrance Visa Requirements](travel/entrance-visa/README.md)
- [Airport Congestion](travel/airport-congestion/README.md)
- [Timezone](travel/timezone/README.md)
- [Holidays](travel/holidays/README.md)
- [Weather](travel/weather/README.md)
- [Flight Search - Korean Airports (KAC & IIAC)](travel/korean-airports/) - Regional airports and Incheon
- [Trip Assistant](travel/trip-assistant/README.md)
- [Prohibited Items Dataset](datasets/prohibited-items/README.md)

## License

This project is licensed under the MIT License. See [LICENSE](LICENSE).

Individual data sources have their own licenses/terms of use—see their respective documentation.
See also: [docs/DATA_SOURCES.md](docs/DATA_SOURCES.md)

## Contributing

Issues and pull requests are welcome!