# mycal

**Category:** 💻 Developer Tools  
**Repository:** https://github.com/Junhui20/malaysia-calendar-api  
**Views:** 0  
**Installs:** 0  
**Upvotes:** 0  
**Directory Page:** https://allmcps.com/mcp/mycal

## Description
Malaysia public holidays, school terms, business days & a leave optimizer (13 tools).

## Claude Desktop Quick Installation
Heuristic fallback — verify the package name and runner against the repository README before running it. Uses `npx` (confidence: low):

```json
"mcpServers": {
  "mycal": {
    "command": "npx",
    "args": ["-y","mycal"]
  }
}
```

## Documentation & README

# Malaysia Calendar API

Malaysia's most complete calendar API — public holidays, school calendar, exam schedules, and MCP server for AI tools.

[![npm: mycal-core](https://img.shields.io/npm/v/@catlabtech/mycal-core?label=mycal-core)](https://www.npmjs.com/package/@catlabtech/mycal-core)
[![npm: mycal-sdk](https://img.shields.io/npm/v/@catlabtech/mycal-sdk?label=mycal-sdk)](https://www.npmjs.com/package/@catlabtech/mycal-sdk)
[![npm: mycal-mcp-server](https://img.shields.io/npm/v/@catlabtech/mycal-mcp-server?label=mycal-mcp-server)](https://www.npmjs.com/package/@catlabtech/mycal-mcp-server)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
[![Deploys on Cloudflare](https://img.shields.io/badge/deploys%20on-Cloudflare-F38020?logo=cloudflare&logoColor=white)](https://mycal-web.pages.dev)

**Data source**: Official government gazette (JPM BKPP), JAKIM, KPM, MPM. Not scraped from third-party websites.

## Links

- **Website + demo**: https://mycal-web.pages.dev
- **Developer docs**: https://mycal-web.pages.dev/docs
- **API base URL**: https://mycal-api.huijun00100101.workers.dev/v1
- **GitHub**: https://github.com/Junhui20/malaysia-calendar-api

## Packages

Published to npm under the `@catlabtech` scope:

| Package | Purpose | Install |
|---------|---------|---------|
| [`@catlabtech/mycal-core`](https://www.npmjs.com/package/@catlabtech/mycal-core) | Shared types, Zod schemas, and calendar/business-day logic | `npm install @catlabtech/mycal-core` |
| [`@catlabtech/mycal-sdk`](https://www.npmjs.com/package/@catlabtech/mycal-sdk) | TypeScript client SDK for the REST API | `npm install @catlabtech/mycal-sdk` |
| [`@catlabtech/mycal-mcp-server`](https://www.npmjs.com/package/@catlabtech/mycal-mcp-server) | MCP server exposing 13 calendar tools for AI agents | `npm install @catlabtech/mycal-mcp-server` |

## Features

- **49 public holidays** for 2026 from official gazette (Warta Kerajaan) — federal + state-specific
- **16 states + 3 Federal Territories** with aliases (KL, JB, Penang, etc.)
- **Weekend-aware** — Kedah/Kelantan/Terengganu use Fri-Sat (Kumpulan A), all others use Sat-Sun (Kumpulan B), with Johor's historical switch tracked
- **Cuti ganti** (replacement holiday) auto-calculation per state weekend config
- **Business day calculator** — per-state, holiday-aware
- **School calendar** — terms, holidays, KPM cuti perayaan (Lampiran A/B/C)
- **Exam schedules** — SPM, STPM, MUET, PT3
- **iCal subscription feeds** — per-state `.ics` feeds
- **MCP Server** for AI agents — 13 tools for Claude, ChatGPT, and other assistants
- **TypeScript SDK** (`@catlabtech/mycal-sdk`) with typed responses
- **OpenAPI 3.1 spec** + interactive docs
- **Trilingual** — Bahasa Melayu, English, Chinese names (三语支持)

## Quick Start

```bash
# Clone and install
git clone https://github.com/Junhui20/malaysia-calendar-api.git
cd malaysia-calendar-api
pnpm install

# Build shared packages
pnpm --filter @catlabtech/mycal-core build
pnpm --filter @catlabtech/mycal-sdk build

# Run API locally (http://localhost:8787)
cd packages/api && npx wrangler dev

# Run web site locally (http://localhost:4321)
pnpm --filter @mycal/web dev

# Validate data
pnpm validate

# Run tests
pnpm test
```

## API Examples

Base URL: `https://mycal-api.huijun00100101.workers.dev/v1`

### List holidays

```bash
# All holidays for Selangor in 2026
curl "https://mycal-api.huijun00100101.workers.dev/v1/holidays?year=2026&state=selangor"

# Islamic holidays only
curl "https://mycal-api.huijun00100101.workers.dev/v1/holidays?year=2026&type=islamic"

# March holidays for KL
curl "https://mycal-api.huijun00100101.workers.dev/v1/holidays?year=2026&state=KL&month=3"
```

### Check a date

```bash
# Is March 21 a holiday/weekend/working day?
curl "https://mycal-api.huijun00100101.workers.dev/v1/holidays/check?date=2026-03-21&state=KL"
```

Response:
```json
{
  "data": {
    "date": "2026-03-21",
    "dayOfWeek": "Saturday",
    "isHoliday": true,
    "isWeekend": true,
    "isWorkingDay": false,
    "isSchoolDay": false,
    "holidays": [
      {
        "id": "2026-hari-raya-aidilfitri-1",
        "name": { "ms": "Hari Raya Aidilfitri", "en": "Eid al-Fitr", "zh": "开斋节" },
        "type": "islamic",
        "status": "confirmed"
      }
    ]
  }
}
```

### Business days

```bash
# Count working days in March for Selangor
curl "https://mycal-api.huijun00100101.workers.dev/v1/business-days?start=2026-03-01&end=2026-03-31&state=selangor"

# Add 10 business days to a date
curl "https://mycal-api.huijun00100101.workers.dev/v1/business-days/add?date=2026-03-01&days=10&state=selangor"
```

### School calendar

```bash
# Is this a school day?
curl "https://mycal-api.huijun00100101.workers.dev/v1/school/is-school-day?date=2026-03-21&state=selangor"

# School holidays for Kumpulan B
curl "https://mycal-api.huijun00100101.workers.dev/v1/school/holidays?year=2026&group=B"

# Exam schedule
curl "https://mycal-api.huijun00100101.workers.dev/v1/school/exams?year=2026&type=spm"
```

### Next holiday

```bash
# Next holiday for Penang
curl "https://mycal-api.huijun00100101.workers.dev/v1/holidays/next?state=penang"
```

### State resolution

```bash
# Resolve alias
curl "https://mycal-api.huijun00100101.workers.dev/v1/states/resolve?q=kl"
# -> { "data": { "canonical": "kuala-lumpur", "group": "B" } }
```

## Full API Reference

| Endpoint | Description |
|----------|-------------|
| `GET /v1/holidays` | List holidays (filter by year, state, type, status, month) |
| `GET /v1/holidays/check` | Is this date a holiday/weekend/working day/school day? |
| `GET /v1/holidays/today` | Today's holiday status |
| `GET /v1/holidays/next` | Next upcoming holiday |
| `GET /v1/holidays/between` | Holidays in date range |
| `GET /v1/holidays/leave-optimizer` | Suggest annual-leave days to spend for the longest consecutive break |
| `GET /v1/business-days` | Count business days between dates |
| `GET /v1/business-days/add` | Add N business days to a date |
| `GET /v1/states` | All 16 states + 3 FTs with weekend config |
| `GET /v1/states/resolve` | Resolve alias (KL, penang, jb) to canonical code |
| `GET /v1/school/terms` | School term dates + day counts |
| `GET /v1/school/holidays` | School holidays + KPM cuti perayaan |
| `GET /v1/school/exams` | SPM, STPM, MUET, PT3 schedule |
| `GET /v1/school/is-school-day` | Is this a school day? |
| `GET /v1/feed/ical/:state` | iCal subscription feed |
| `GET /v1/feed/csv/:state` | CSV/spreadsheet export of holidays |
| `GET /v1/data/*` | Raw, cacheable open-data snapshots — `manifest`, `states`, `holidays/:year`, `all` (no API key required) |
| `GET /v1/prayer-times/:zone` | JAKIM prayer times for a zone (MYT-formatted); `GET /v1/prayer-times/zones` lists the zone directory |
| `POST /v1/keys` | Issue an API key (admin-gated for now; `GET /v1/keys/whoami` checks the caller's tier) |

See the full [OpenAPI 3.1 spec](./openapi.yaml) for request/response schemas.

## SDK Usage

```typescript
import { MyCalClient } from "@catlabtech/mycal-sdk";

const cal = new MyCalClient();

// Check if a date is a working day
const result = await cal.check("2026-03-21", "selangor");
console.log(result.isWorkingDay); // false

// List holidays
const holidays = await cal.holidays({ year: 2026, state: "KL" });

// Business days
const workDays = await cal.businessDays("2026-03-01", "2026-03-31", "selangor");
console.log(workDays.businessDays); // 22

// School calendar
const terms = await cal.school.terms({ year: 2026, group: "B" });
const exams = await cal.school.exams({ year: 2026, type: "spm" });
const isSchool = await cal.school.isSchoolDay("2026-03-21", "selangor");
```

## MCP Server

Connect the Malaysia Calendar API to Claude, ChatGPT, or any MCP-compatible AI assistant.

### Setup with Claude Desktop / Claude Code

Add to your MCP configuration:

```json
{
  "mcpServers": {
    "malaysia-calendar": {
      "command": "npx",
      "args": ["@catlabtech/mycal-mcp-server"]
    }
  }
}
```

### Available Tools (13)

| Tool | Description |
|------|-------------|
| `get_malaysia_holidays` | Get public holidays (filter by year, state, type) |
| `check_malaysia_holiday` | Check if a date is a holiday or working day |
| `next_malaysia_holiday` | Find the next upcoming holiday |
| `malaysia_business_days` | Count working days between two dates |
| `malaysia_long_weekends` | Find long weekends (3+ days) |
| `malaysia_leave_optimizer` | Find the most efficient leave days to take to maximise consecutive days off |
| `list_malaysia_states` | List all states with weekend config |
| `resolve_malaysia_state` | Resolve alias (KL, JB) to canonical code |
| `malaysia_holiday_changes` | Recent data changes |
| `malaysia_school_terms` | School term dates and day counts |
| `malaysia_school_holidays` | School holidays (cuti penggal, cuti perayaan) |
| `malaysia_exams` | SPM, STPM, MUET, PT3 exam schedule |
| `malaysia_is_school_day` | Check if a date is a school day |

## State Codes

| Code | Aliases | Group | Weekend |
|------|---------|-------|---------|
| `johor` | jhr, jb | B | Sat-Sun (was Fri-Sat 2014-2024) |
| `kedah` | kd, kdh | A | Fri-Sat |
| `kelantan` | kel, kb | A | Fri-Sat |
| `terengganu` | trg, kt | A | Fri-Sat |
| `perak` | prk, ipoh | B | Sat-Sun |
| `pulau-pinang` | penang, pg | B | Sat-Sun |
| `selangor` | sel, sgr | B | Sat-Sun |
| `negeri-sembilan` | ns, n9 | B | Sat-Sun |
| `melaka` | mlk, malacca | B | Sat-Sun |
| `pahang` | phg, kuantan | B | Sat-Sun |
| `perlis` | pls, kangar | B | Sat-Sun |
| `sabah` | sbh, kk | B | Sat-Sun |
| `sarawak` | swk, kuching | B | Sat-Sun |
| `kuala-lumpur` | kl | B | Sat-Sun |
| `wp-putrajaya` | putrajaya, pjy | B | Sat-Sun |
| `wp-labuan` | labuan, lbn | B | Sat-Sun |

State aliases are case-insensitive. Use `GET /v1/states/resolve?q=kl` to resolve any alias to the canonical code.

## Project Structure

```
malaysia-calendar-api/
├── data/                        # JSON data files (source of truth / 数据源)
│   ├── holidays/
│   │   ├── 2024.json            # Holiday data per year
│   │   ├── 2025.json
│   │   └── 2026.json
│   ├── school/
│   │   ├── terms-2026.json      # School terms (Kumpulan A + B)
│   │   ├── holidays-2026.json   # School holidays + KPM cuti perayaan
│   │   └── exams-2026.json      # SPM, STPM, MUET, PT3 schedules
│   ├── states.json              # 16 states + 3 FT, aliases, weekend history
│   └── known-fixed-holidays.json
├── packages/
│   ├── core/                    # Shared business logic (types, schemas, utils)
│   │   └── src/
│   │       ├── types.ts         # Holiday, State, SchoolTerm, Exam interfaces
│   │       ├── schemas.ts       # Zod validation schemas
│   │       ├── filter.ts        # Query filtering logic
│   │       ├── replacement.ts   # Cuti ganti calculation
│   │       ├── state-resolver.ts
│   │       ├── business-days.ts
│   │       └── school.ts        # School term/holiday/exam logic
│   ├── api/                     # Hono API on Cloudflare Workers
│   ├── mcp-server/              # MCP Server (13 tools)
│   ├── sdk/                     # TypeScript client SDK (@catlabtech/mycal-sdk)
│   └── web/                     # Astro + Starlight — marketing site, demos, docs
├── scripts/
│   ├── validate-data.ts         # 5-layer data validation pipeline
│   └── sync-to-kv.ts           # JSON -> Cloudflare KV denormalization
├── openapi.yaml                 # OpenAPI 3.1 spec (spec-first)
├── pnpm-workspace.yaml
└── turbo.json
```

## Data Sources

All data is sourced from official Malaysian government publications:

| Source | Data | URL |
|--------|------|-----|
| **JPM BKPP** | Federal Gazette / Warta Kerajaan (public holidays) | kabinet.gov.my |
| **JAKIM** | Takwim Hijri-Miladi (Islamic calendar) | e-solat.gov.my |
| **KPM** | Kalendar Akademik / school calendar (Lampiran A/B/C) | moe.gov.my |
| **MPM** | STPM & MUET exam schedules | mpm.edu.my |
| **State Portals** | State-specific holidays (16 states) | *.gov.my |

Holiday data includes gazette references (e.g., `P.U.(B) 305/2025`) for traceability.

## Deploy

Two parts deploy independently:

### API → Cloudflare Workers

```bash
pnpm --filter @catlabtech/mycal-core build
cd packages/api && npx wrangler deploy
```

### Website → Cloudflare Pages

```bash
pnpm --filter @catlabtech/mycal-core build
pnpm --filter @catlabtech/mycal-sdk build
pnpm --filter @mycal/web build

# Direct upload via wrangler (requires CLOUDFLARE_API_TOKEN + CLOUDFLARE_ACCOUNT_ID)
cd packages/web
npx wrangler pages deploy dist --project-name=mycal-web
```

First-time Pages setup:
1. Create a Pages project named `mycal-web` in the Cloudflare dashboard.
2. Either connect the GitHub repo for automatic builds, or rely on the GitHub Actions workflow (`.github/workflows/deploy.yml`) to push via `wrangler pages deploy`.
3. Required GitHub secrets: `CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_ACCOUNT_ID`.

### CI/CD

GitHub Actions handles:
1. **PR gate** — Zod schema validation + cross-source checks on every PR
2. **Deploy** — On merge to `main`: build and deploy API to Workers + Web to Pages in parallel
3. **Daily scrape** — Government portal monitoring for updates
4. **Rukyah monitor** — Islamic date confirmation tracking

## Contributing

See [CONTRIBUTING.md](./CONTRIBUTING.md) for how to:
- Report a missing holiday or cuti peristiwa
- Fix data errors
- Add new features

## License

MIT

