# Timezone MCP Server

A dual-interface timezone server supporting both **MCP (Model Context Protocol)** for LLM discovery and **REST API** for direct HTTP access. Get available regions, cities, and current time in any timezone with ISO 8601 formatted timestamps.

## Features

- **🤖 MCP Protocol Support** - LLMs can auto-discover and use timezone tools via JSON-RPC
- **🌐 REST API** - Traditional HTTP endpoints with Swagger/OpenAPI documentation
- **🔐 OAuth2 Authentication** - Secure access with user allowlisting and client credentials
- **⏰ Timezone Operations** - Get regions, cities, and current time in any timezone
- **📅 ISO 8601 Timestamps** - Local datetime with offset, UTC datetime, and timezone offset
- **🧪 100% Test Coverage** - Comprehensive unit and e2e tests (Vitest)
- **🔧 NestJS & TypeScript** - Modern, type-safe development
- **📚 Dual Documentation** - Swagger UI for humans, MCP schema for LLMs
- **🎨 Code Quality** - ESLint, Prettier, and automated formatting

## Getting Started

### Prerequisites

- Node.js 22.20.0+ (LTS recommended)
- pnpm 10.13.1 (managed via Corepack)

> **📦 Package Manager:** This project uses **pnpm** exclusively via **Corepack** (built into Node.js). The exact pnpm version is enforced by the `packageManager` field in package.json. After installing Node.js, run `corepack enable` to activate pnpm support.
>
> **🔧 Version Management:** This project uses **fnm** (Fast Node Manager) or **nvm** to manage Node.js versions. The `.node-version` file will automatically switch to the correct Node.js version when you `cd` into the project directory (if you have fnm/nvm shell integration enabled).

### Installation

```bash
pnpm install
```

### Authentication Setup

This server requires OAuth2 authentication for all endpoints except `/health`. You need to configure authentication before running the server.

#### Step 1: Configure Environment Variables

Copy the example environment file and edit it:

```bash
cp example.env .env
```

Edit `.env` and configure:

1. **JWT Settings** (required):

   ```env
   JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
   JWT_EXPIRES_IN=3600
   ```

2. **OAuth2 Provider** (choose one - Google, GitHub, or any OAuth2-compliant provider):

   For **Google**:

   ```env
   OAUTH2_AUTHORIZATION_URL=https://accounts.google.com/o/oauth2/v2/auth
   OAUTH2_TOKEN_URL=https://oauth2.googleapis.com/token
   OAUTH2_USER_INFO_URL=https://www.googleapis.com/oauth2/v2/userinfo
   OAUTH2_CLIENT_ID=your-google-client-id.apps.googleusercontent.com
   OAUTH2_CLIENT_SECRET=your-google-client-secret
   OAUTH2_CALLBACK_URL=http://localhost:3000/auth/callback
   OAUTH2_SCOPE=openid email profile
   ```

   For **GitHub**:

   ```env
   OAUTH2_AUTHORIZATION_URL=https://github.com/login/oauth/authorize
   OAUTH2_TOKEN_URL=https://github.com/login/oauth/access_token
   OAUTH2_USER_INFO_URL=https://api.github.com/user
   OAUTH2_CLIENT_ID=your-github-client-id
   OAUTH2_CLIENT_SECRET=your-github-client-secret
   OAUTH2_CALLBACK_URL=http://localhost:3000/auth/callback
   OAUTH2_SCOPE=user:email
   ```

3. **User Allowlist** (required):

   ```env
   ALLOWED_EMAILS=user1@example.com,user2@example.com,admin@company.com
   ```

#### Step 2: Set Up OAuth2 Provider

Create OAuth2 credentials with your provider:

- **Google**: [Google Cloud Console](https://console.cloud.google.com/apis/credentials)
- **GitHub**: [GitHub Developer Settings](https://github.com/settings/developers)

Set the **Authorized redirect URI** to: `http://localhost:3000/auth/callback`

#### Step 3: Authentication Flow

1. Visit `http://localhost:3000/auth/login`
2. Redirected to OAuth2 provider (Google/GitHub/etc.)
3. Sign in and authorize
4. Redirected back with JWT token displayed on screen
5. Copy the token and use it in API requests:
   ```bash
   curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:3000/timezones/regions
   ```

### Running the Server

This project provides **three interfaces** that can run independently:

#### Option 1: MCP Server via stdio (for local LLM clients)

```bash
# Development mode with hot reload
pnpm mcp:dev

# Production mode
pnpm build
pnpm mcp:start
```

Uses **stdio transport** - launches as a subprocess. Perfect for Claude Desktop config file.

#### Option 2: MCP Server via HTTP (for remote LLM clients)

```bash
# Development mode with hot reload
pnpm mcp:http:dev

# Production mode
pnpm build
pnpm mcp:http
```

Uses **Streamable HTTP over HTTP** - accessible at `http://localhost:3001/mcp`

Perfect for Claude Desktop's "Add Custom Connector" UI! 🎯

#### Option 3: REST API Server (for HTTP clients)

```bash
# Development mode with hot reload
pnpm dev

# Production mode
pnpm build
pnpm start:prod

# Open Swagger UI in browser
pnpm swagger
```

The REST API will start on [http://localhost:3000](http://localhost:3000)

Interactive Swagger/OpenAPI documentation is available at [http://localhost:3000/api](http://localhost:3000/api)

### Available Endpoints

**Authentication:**

- `GET /auth/login` - Initiate OAuth2 login (redirects to provider)
- `GET /auth/callback` - OAuth2 callback (returns JWT token)

**Health:**

- `GET /health` - Health check endpoint (public, no authentication required)
  ```json
  {
    "status": "ok",
    "timestamp": "2025-10-17T18:30:45.123Z"
  }
  ```

**Timezone API:** (requires authentication - include `Authorization: Bearer TOKEN` header)

- `GET /timezones/regions` - Get all available timezone regions

  ```json
  {
    "regions": [
      "Africa",
      "America",
      "Antarctica",
      "Asia",
      "Atlantic",
      "Australia",
      "Europe",
      "Indian",
      "Pacific"
    ],
    "count": 15
  }
  ```

- `GET /timezones/regions/:region/cities` - Get all cities in a specific region

  ```json
  {
    "region": "America",
    "cities": ["New_York", "Los_Angeles", "Chicago", "Denver", "Phoenix"],
    "count": 150
  }
  ```

- `GET /timezones/:region/:city` - Get current time in a specific timezone
  ```json
  {
    "timezone": "America/New_York",
    "datetime_local": "2025-10-17T13:47:23-04:00",
    "datetime_utc": "2025-10-17T17:47:23.345Z",
    "timezone_offset": "-04:00",
    "timestamp": 1760723243345
  }
  ```

## MCP Protocol Integration

### What is MCP?

[Model Context Protocol (MCP)](https://modelcontextprotocol.io) is a standardized protocol that allows LLMs to discover and use tools automatically. Instead of manually telling an LLM about your API endpoints, MCP-enabled clients like Claude Desktop can:

1. **Auto-discover** available tools via `tools/list`
2. **Inspect schemas** to understand input parameters
3. **Execute tools** via JSON-RPC `tools/call` requests

### Testing with MCP Inspector (Quickest)

The MCP Inspector is a web UI for testing your MCP server without needing Claude Desktop:

```bash
pnpm install
pnpm build
pnpm mcp:inspector
```

This opens `http://localhost:5173` where you can:

- See all 3 tools listed
- Inspect tool schemas and parameters
- Execute tools and see JSON responses
- Debug without installing anything else

### Setting Up with Claude Desktop

#### Method 1: Local stdio Connection (Recommended)

1. Build the project:

   ```bash
   pnpm install
   pnpm build
   ```

2. Edit Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):

   ```json
   {
     "mcpServers": {
       "timezone": {
         "command": "node",
         "args": ["/absolute/path/to/demo-mcp/dist/main.js"]
       }
     }
   }
   ```

3. Restart Claude Desktop

4. Claude can now auto-discover and use the timezone tools!

### Available MCP Tools

| Tool                | Parameters                          | Description                            |
| ------------------- | ----------------------------------- | -------------------------------------- |
| `get_regions`       | None                                | Get list of all timezone regions       |
| `get_cities`        | `region: string`                    | Get cities in a specific region        |
| `get_timezone_info` | `region: string`<br/>`city: string` | Get current time with ISO 8601 formats |

### Architecture

```
┌───────────────────────────────────────────────────┐
│              Timezone MCP Server                  │
├───────────────────────────────────────────────────┤
│                                                   │
│  REST API        MCP (HTTP/SSE)      MCP (stdio)  │
│  Port 3000       Port 3001           subprocess   │
│      ↓                ↓                   ↓       │
│  ┌────────┐     ┌─────────┐         ┌─────────┐   │
│  │NestJS  │     │   MCP   │         │   MCP   │   │
│  │ HTTP   │     │  HTTP   │         │  stdio  │   │
│  └───┬────┘     └────┬────┘         └────┬────┘   │
│      │               │                   │        │
│      └───────────────┴───────────────────┘        │
│                      ↓                            │
│              TimezoneService                      │
│          (Shared Business Logic)                  │
└───────────────────────────────────────────────────┘
```

All three interfaces use the same `TimezoneService`, ensuring consistent behavior across REST API, remote MCP, and local MCP connections.

## Testing

```bash
# Run all tests (includes MCP stdio e2e tests)
pnpm test

# Run tests in watch mode
pnpm test:watch

# Run tests with coverage
pnpm test:cov

# Run tests with UI
pnpm test:ui

# Run e2e tests only
pnpm test:e2e
```

**Test Coverage**: 103/109 tests passing (94.5%). The HTTP MCP transport has comprehensive manual testing via MCP Inspector but automated e2e tests are pending due to SSE timing complexity.

**Manual Testing HTTP MCP Server:**

```bash
# Start HTTP MCP server
pnpm mcp:http:dev

# Test with MCP Inspector
pnpm mcp:inspector

# Test with Claude Desktop
# Settings > Connectors > Add Custom Connector
# URL: http://localhost:3001/mcp
```

## Code Quality

```bash
# Lint code
pnpm lint

# Fix lint issues
pnpm lint:fix

# Format code
pnpm format

# Check formatting
pnpm format:check
```

## Documentation

Additional guides and documentation are available in the [`docs/`](docs/) directory:

- **[Local Testing Guide](docs/LOCAL_TESTING.md)** - Step-by-step instructions for testing with MCP Inspector
- **[Deployment Guide](docs/DEPLOYMENT.md)** - Deploy to Google Cloud Run
- **[Deployment Success Guide](docs/DEPLOYMENT_SUCCESS.md)** - Post-deployment verification

## Scripts Reference

### Server Scripts

- `pnpm dev` - Start REST API server in development mode
- `pnpm start` - Start REST API server
- `pnpm start:prod` - Start REST API server in production mode
- `pnpm mcp:dev` - Start MCP server (stdio) in development mode
- `pnpm mcp:start` - Start MCP server (stdio) in production mode
- `pnpm mcp:http:dev` - Start MCP server (HTTP/SSE) in development mode
- `pnpm mcp:http` - Start MCP server (HTTP/SSE) in production mode
- `pnpm mcp:inspector` - Start MCP Inspector (debug/test MCP tools in browser)
- `pnpm swagger` - Open Swagger UI in default browser

### Build & Test

- `pnpm build` - Build the project (compiles TypeScript to dist/)
- `pnpm test` - Run all tests (84 tests including MCP e2e)
- `pnpm test:watch` - Run tests in watch mode
- `pnpm test:cov` - Run tests with coverage report (100% coverage)
- `pnpm test:ui` - Run tests with Vitest UI
- `pnpm test:e2e` - Run end-to-end tests only

### Code Quality

- `pnpm lint` - Lint code with ESLint
- `pnpm lint:fix` - Fix linting issues automatically
- `pnpm format` - Format code with Prettier
- `pnpm format:check` - Check if code is properly formatted
- `deps:check` - Check if dependencies can be upgraded (minor/patch only)
- `deps:update` - Upgrade dependencies (minor/patch only)
