#
tokens: 26021/50000 32/32 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .gitignore
├── CLAUDE.md
├── Dockerfile
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── smithery.yaml
├── src
│   ├── index.ts
│   ├── interfaces
│   │   ├── moon.ts
│   │   ├── noaa.ts
│   │   ├── parameters.ts
│   │   └── sun.ts
│   ├── schemas
│   │   ├── common.ts
│   │   └── dpapi.ts
│   ├── server
│   │   ├── config.ts
│   │   └── mcp-server.ts
│   ├── services
│   │   ├── dpapi-service.ts
│   │   ├── moon-phase-service.ts
│   │   ├── noaa-parameters-service.ts
│   │   ├── noaa-service.ts
│   │   └── sun-service.ts
│   ├── tools
│   │   ├── derived-product-tools.ts
│   │   ├── index.ts
│   │   ├── moon-tools.ts
│   │   ├── parameter-tools.ts
│   │   ├── station-tools.ts
│   │   ├── sun-tools.ts
│   │   └── water-tools.ts
│   └── types
│       ├── moon.ts
│       ├── sun.ts
│       └── suncalc.d.ts
├── test-dpapi.js
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# dependencies
/node_modules
/.pnp
.pnp.js

# production
/dist
/build

# misc
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# IDE files
.idea/
.vscode/
*.swp
*.swo 



.cursor

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# 🌊 NOAA Tides & Currents MCP Server

<div align="center">

[![npm version](https://img.shields.io/npm/v/@ryancardin/noaa-tides-currents-mcp-server?style=for-the-badge&logo=npm&color=blue)](https://www.npmjs.com/package/@ryancardin/noaa-tides-currents-mcp-server)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge)](https://opensource.org/licenses/MIT)
[![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
[![MCP](https://img.shields.io/badge/MCP-Model_Context_Protocol-green?style=for-the-badge)](https://modelcontextprotocol.io/)

[![smithery badge](https://smithery.ai/badge/@RyanCardin15/noaa-tidesandcurrents-mcp)](https://smithery.ai/server/@RyanCardin15/noaa-tidesandcurrents-mcp)

**🚀 Lightning-fast access to NOAA's oceanic and atmospheric data through MCP**

*Your one-stop solution for tides, currents, weather, astronomy, and climate data*

[📦 Quick Start](#-quick-start) • [🛠️ Tools](#️-available-tools) • [📖 Examples](#-usage-examples) • [🏗️ Advanced](#️-advanced-usage)

</div>

---

## ✨ What Makes This Awesome

🌊 **25+ Specialized Tools** - From basic tide data to advanced climate projections  
⚡ **Lightning Fast** - Built on FastMCP for optimal performance  
🎯 **Zero Config** - Works out of the box with Claude Desktop  
🌍 **Comprehensive Data** - Water levels, currents, weather, moon phases, sun data  
📊 **Climate Research Ready** - Sea level trends, flooding projections, extreme events  
🚀 **NPX Ready** - Install and run with a single command  

---

## 🚀 Quick Start

### ⚡ NPX Installation (Recommended)

```bash
# Install and run immediately - no setup required!
npx @ryancardin/noaa-tides-currents-mcp-server

# Or use the shorter alias
npx noaa-mcp
```

#### 🔌 Transport Modes

**STDIO Mode (Default - MCP Protocol)**
```bash
# Standard MCP server for Claude Desktop integration
npx @ryancardin/noaa-tides-currents-mcp-server

# Or use the shorter alias
npx noaa-mcp
```

**HTTP Streamable Mode (Web Integration)**
```bash
# Start HTTP server on default port 3000
npx @ryancardin/noaa-tides-currents-mcp-server --http

# Specify custom port
npx @ryancardin/noaa-tides-currents-mcp-server --http --port 8080

# Using shorter alias
npx noaa-mcp --http --port 8080

# Access via Server-Sent Events
curl http://localhost:3000/sse
```

### 🎯 Claude Desktop Integration

Install directly to Claude Desktop via [Smithery](https://smithery.ai/server/@RyanCardin15/tidesandcurrents):

```bash
npx -y @smithery/cli install @RyanCardin15/tidesandcurrents --client claude
```

### 🔧 Manual Development Setup

```bash
# Clone and build
git clone https://github.com/RyanCardin15/NOAA-Tides-And-Currents-MCP.git
cd NOAA-Tides-And-Currents-MCP
npm install && npm run build

# Start the server
npm start

# Test with FastMCP
npx fastmcp dev dist/index.js
```

---

## 🛠️ Available Tools

<details>
<summary><strong>🌊 Water Data Tools (6 tools)</strong></summary>

### Water Levels & Tides
- **`get_water_levels`** - Real-time and historical water level data
- **`get_tide_predictions`** - High/low tide predictions and continuous data
- **`get_currents`** - Real-time and historical current measurements  
- **`get_current_predictions`** - Current speed and direction forecasts
- **`get_meteorological_data`** - Wind, air temp, water temp, pressure, etc.

### Station Information
- **`get_stations`** - Search and list monitoring stations
- **`get_station_details`** - Detailed station metadata and capabilities

</details>

<details>
<summary><strong>🔬 Climate & Research Tools (9 tools)</strong></summary>

### Sea Level Analysis
- **`get_sea_level_trends`** - Long-term sea level rise trends and rates
- **`get_extreme_water_levels`** - Statistical analysis of extreme events

### High Tide Flooding Analysis
- **`get_high_tide_flooding_daily`** - Daily flood event counts
- **`get_high_tide_flooding_monthly`** - Monthly flooding patterns
- **`get_high_tide_flooding_seasonal`** - Seasonal flood analysis
- **`get_high_tide_flooding_annual`** - Yearly flooding trends
- **`get_high_tide_flooding_projections`** - Future flood risk scenarios
- **`get_high_tide_flooding_likelihoods`** - Daily flood probability

### Historical Extremes
- **`get_top_ten_water_levels`** - Highest/lowest water levels on record

</details>

<details>
<summary><strong>🌙 Astronomy Tools (7 tools)</strong></summary>

### Moon Phase Calculations
- **`get_moon_phase`** - Current moon phase and illumination
- **`get_moon_phases_range`** - Moon phases over date ranges
- **`get_next_moon_phase`** - Find next new/full/quarter moons

### Solar Calculations  
- **`get_sun_times`** - Sunrise, sunset, dawn, dusk times
- **`get_sun_times_range`** - Solar times over date ranges
- **`get_sun_position`** - Real-time sun azimuth and elevation
- **`get_next_sun_event`** - Next sunrise, sunset, or solar noon

</details>

<details>
<summary><strong>⚙️ Configuration Tools (1 tool)</strong></summary>

### API Parameters
- **`get_parameter_definitions`** - Valid values for all API parameters

</details>

---

## 📖 Usage Examples

### 🌊 Get Current Tide Conditions

```bash
# Get latest water levels for Boston Harbor
get_water_levels station="8443970" date="latest"

# Get today's tide predictions for Miami
get_tide_predictions station="8723214" begin_date="today" end_date="today" interval="hilo"
```

### 🌀 Hurricane Preparedness 

```bash
# Get extreme water level statistics for storm planning
get_extreme_water_levels station="8518750" units="english"

# Check flooding likelihood for tomorrow
get_high_tide_flooding_likelihoods station="8518750" date="2024-12-16" threshold="minor"
```

### 🔬 Climate Research

```bash
# Analyze 30-year sea level trends
get_sea_level_trends station="8518750" affiliation="US"

# Get high tide flooding projections for 2050s under intermediate sea level rise
get_high_tide_flooding_projections station="8518750" scenario="intermediate" decade="2050s"
```

### 🌙 Astronomy & Navigation

```bash
# Get tonight's moon phase for navigation
get_moon_phase date="2024-12-15" latitude="42.3601" longitude="-71.0589"

# Calculate sunrise/sunset for sailing
get_sun_times date="2024-12-15" latitude="25.7617" longitude="-80.1918" timezone="America/New_York"
```

### 🎣 Fishing & Recreation

```bash
# Best fishing times with current predictions
get_current_predictions station="ACT0446" date="today" interval="MAX_SLACK"

# Wind and weather conditions
get_meteorological_data station="8443970" product="wind" date="today"
```

---

## 🏗️ Advanced Usage

### 🔧 Development & Testing

```bash
# Run in development mode (stdio)
npm run dev

# Development with HTTP transport
npm run dev:http

# Production builds with different transports
npm start                    # STDIO mode (default)
npm run start:http          # HTTP on port 3000
npm run start:http:3001     # HTTP on port 3001
npm run start:http:8080     # HTTP on port 8080

# Inspect server capabilities
npx fastmcp inspect dist/index.js
```

### 🌐 HTTP Stream Integration

When running in HTTP mode, the server provides Server-Sent Events (SSE) at `/sse`:

```bash
# Start HTTP server
npx @ryancardin/noaa-tides-currents-mcp-server --http --port 3000

# Test the endpoint
curl -N http://localhost:3000/sse

# Or integrate with web applications
fetch('http://localhost:3000/sse')
  .then(response => response.body.getReader())
  .then(reader => {
    // Handle streaming MCP responses
  });
```

**Use Cases for HTTP Mode:**
- 🌐 **Web Applications** - Integrate with React, Vue, Angular apps
- 📱 **Mobile Apps** - REST-like access from mobile applications  
- 🔗 **API Gateways** - Proxy through load balancers or API gateways
- 🧪 **Testing** - Easy curl-based testing and debugging

### 📊 Data Formats & Export

All tools support multiple output formats:
- **JSON** (default) - Perfect for programmatic use
- **XML** - Legacy system integration  
- **CSV** - Direct spreadsheet import

### 🌍 Global Station Coverage

- **13,000+ stations** worldwide
- **Real-time data** from NOAA's CO-OPS network
- **Historical records** dating back decades
- **Global tide predictions** and current forecasts

---

## 🚦 API Endpoints

This server integrates with three NOAA APIs:

| API | Purpose | Base URL |
|-----|---------|----------|
| **Data API** | Real-time observations & predictions | `api.tidesandcurrents.noaa.gov/api/prod/` |
| **Metadata API** | Station information & capabilities | `api.tidesandcurrents.noaa.gov/mdapi/prod/` |
| **Derived Products API** | Climate analysis & research data | `api.tidesandcurrents.noaa.gov/dpapi/prod/` |

---

## 🛠️ Technical Details

### Architecture
- **🚀 FastMCP Framework** - High-performance MCP server
- **📝 TypeScript** - Full type safety and IntelliSense
- **🔧 Zod Validation** - Runtime parameter validation
- **⚡ Axios HTTP Client** - Reliable API communication
- **🌙 SunCalc Integration** - Precise astronomical calculations

### Transport Options
- **📡 STDIO Transport** - Standard MCP protocol for desktop clients
- **🌐 HTTP Stream Transport** - Server-Sent Events for web integration
- **🔄 Dual Mode Support** - Switch between transports via command-line flags

### System Requirements
- **Node.js** 18+ 
- **NPM** 8+
- **MCP Client** (Claude Desktop, etc.)

### Package Size
- **📦 Bundled**: 43.9 KB
- **📂 Installed**: 286.2 KB
- **⚡ Load Time**: <100ms

---

## 🐛 Troubleshooting

<details>
<summary><strong>Common Issues & Solutions</strong></summary>

### Server Won't Start
```bash
# Check Node.js version
node --version  # Should be 18+

# Rebuild TypeScript
npm run build
```

### API Errors
- **Invalid Station ID**: Use `get_stations` to find valid stations
- **Date Format Issues**: Use YYYYMMDD or MM/DD/YYYY formats
- **Rate Limiting**: NOAA APIs have usage limits - space out requests

### MCP Connection Issues
- Ensure Claude Desktop MCP settings are configured correctly
- Check that the server binary has execute permissions: `chmod +x dist/index.js`

</details>

---

## 📈 Roadmap

- [ ] 🌊 **Real-time Alerts** - Webhook support for tide/weather alerts
- [ ] 📱 **Mobile SDK** - React Native integration
- [ ] 🗺️ **GIS Integration** - Shapefile and KML export
- [ ] 🤖 **AI Insights** - Automated pattern recognition
- [ ] ⚡ **GraphQL API** - Modern query interface
- [ ] 🌐 **Multi-language** - I18n support

---

## 🤝 Contributing

We love contributions! Here's how to get started:

1. **🍴 Fork** the repository
2. **🌿 Branch** for your feature (`git checkout -b amazing-feature`)
3. **💻 Code** your improvements
4. **✅ Test** with `npm test`
5. **📤 Submit** a pull request

### Development Commands
```bash
npm run build    # Build TypeScript
npm run dev      # Development mode  
npm run test     # Run test suite
npm run format   # Format with Prettier
```

---

## 📄 License

**MIT License** - see [LICENSE](LICENSE) file for details.

Built with ❤️ by [Ryan Cardin](https://github.com/RyanCardin15)

---

## 🔗 Links & Resources

- **📦 NPM Package**: [@ryancardin/noaa-tides-currents-mcp-server](https://www.npmjs.com/package/@ryancardin/noaa-tides-currents-mcp-server)
- **🏪 Smithery**: [Auto-install for Claude Desktop](https://smithery.ai/server/@RyanCardin15/noaa-tidesandcurrents-mcp)  
- **🌊 NOAA CO-OPS**: [Official NOAA Data Portal](https://tidesandcurrents.noaa.gov/)
- **🤖 MCP Protocol**: [Model Context Protocol Docs](https://modelcontextprotocol.io/)
- **⚡ FastMCP**: [FastMCP Framework](https://github.com/jlowin/fastmcp)

<div align="center">

**⭐ Star this repo if it helped you!**

Made possible by NOAA's commitment to open oceanic data 🌊

</div>
```

--------------------------------------------------------------------------------
/CLAUDE.md:
--------------------------------------------------------------------------------

```markdown
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Development Commands

### Building and Running
- `npm run build` - Build TypeScript to JavaScript in `dist/` directory
- `npm start` - Start the MCP server (requires build first)
- `npm run dev` - Run in development mode with ts-node-esm
- `npm run format` - Format code with Prettier
- `npm test` - Run tests with Vitest

### Testing and Development
- `npx fastmcp dev dist/index.js` - Test server with fastmcp CLI
- `npx fastmcp inspect dist/index.js` - Inspect server capabilities

## Architecture Overview

This is a FastMCP server that provides tools for accessing NOAA Tides and Currents data, moon phase information, and sun position calculations.

### Core Structure
- **Entry Point**: `src/index.ts` - Creates server, registers tools, and starts stdio transport
- **Server Configuration**: `src/server/config.ts` - FastMCP server setup with fixed stdio transport
- **Tool Registration**: `src/tools/index.ts` - Centralized tool registration hub

### Service Layer
The application follows a service-oriented architecture:
- `NoaaService` - Handles all NOAA API interactions (data and metadata APIs)
- `MoonPhaseService` - Calculates moon phases and lunar information
- `SunService` - Calculates sun position, rise/set times using suncalc library
- `NoaaParametersService` - Provides parameter definitions for NOAA API

### Tool Categories
Tools are organized by functional area:
- **Water Tools** (`water-tools.ts`) - Water levels, tide predictions, currents
- **Station Tools** (`station-tools.ts`) - Station metadata and information
- **Moon Tools** (`moon-tools.ts`) - Moon phase calculations
- **Sun Tools** (`sun-tools.ts`) - Sun position and event calculations
- **Parameter Tools** (`parameter-tools.ts`) - API parameter definitions

### Data Validation
- Uses Zod schemas for parameter validation in `src/schemas/common.ts`
- Common validation patterns for dates, stations, units, formats, etc.
- Refined validation for date parameter combinations

### API Integration
- **NOAA Data API**: `https://api.tidesandcurrents.noaa.gov/api/prod/datagetter`
- **NOAA Metadata API**: `https://api.tidesandcurrents.noaa.gov/mdapi/prod/webapi`
- Uses axios for HTTP requests with proper error handling

### Key Dependencies
- `fastmcp` - MCP server framework
- `axios` - HTTP client for NOAA API calls
- `suncalc` - Sun position and timing calculations
- `zod` - Schema validation
- TypeScript with ES modules and Node16 module resolution

## Important Implementation Notes

### MCP Server Configuration
The server uses stdio transport exclusively - do not modify to use other transports as this is designed for MCP client integration.

### Error Handling
NOAA API errors are caught and re-thrown with structured error messages including status codes and response data.

### Date Handling
The codebase supports multiple date formats and has specific validation logic for date parameter combinations (date vs begin_date/end_date).

### Tool Organization
Each tool category has its own registration function that accepts the server instance and relevant service(s). This modular approach makes it easy to add new tools or modify existing ones.
```

--------------------------------------------------------------------------------
/test-dpapi.js:
--------------------------------------------------------------------------------

```javascript

```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
FROM node:lts-alpine
WORKDIR /app

# Install dependencies
COPY package*.json ./
RUN npm install --ignore-scripts

# Copy source code and build the project
COPY . .
RUN npm run build

# Start the server in stdio mode
CMD [ "npm", "start" ]

```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "esModuleInterop": true,
    "strict": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "**/*.test.ts"]
} 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node
import { FastMCP } from 'fastmcp';
import { createServer, startServer } from './server/config.js';
import { registerAllTools } from './tools/index.js';

// Create and configure the MCP server
const server = createServer();

// Register all tools
const services = registerAllTools(server);

// Start the server
startServer(server); 
```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml

startCommand:
  type: stdio
  configSchema:
    # Minimal JSON Schema for configuration
    type: object
    properties: {}
  exampleConfig: {}
  commandFunction:
    # Simple command to start the MCP server on stdio without additional configuration
    |-
    () => ({
      command: 'node',
      args: ['dist/index.js']
    })

```

--------------------------------------------------------------------------------
/src/types/moon.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Moon phase names and their approximate ranges
 */
export enum MoonPhaseName {
  NEW_MOON = 'New Moon',
  WAXING_CRESCENT = 'Waxing Crescent',
  FIRST_QUARTER = 'First Quarter',
  WAXING_GIBBOUS = 'Waxing Gibbous',
  FULL_MOON = 'Full Moon',
  WANING_GIBBOUS = 'Waning Gibbous',
  LAST_QUARTER = 'Last Quarter',
  WANING_CRESCENT = 'Waning Crescent'
}

/**
 * Moon phase information
 */
export interface MoonPhaseInfo {
  date: string;
  phase: number;
  phaseName: MoonPhaseName;
  illumination: number;
  age: number;
  distance: number;
  diameter: number;
  isWaxing: boolean;
} 
```

--------------------------------------------------------------------------------
/src/interfaces/parameters.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';

// Parameter type schema
export const ParameterSchema = z.object({
  parameter: z.string().optional().describe('Parameter type to get information about (time_zones, datums, units, tide_intervals, current_intervals, velocity_types, products, station_types, date_formats, output_formats). If not provided, returns information about all parameter types.')
});

export type GetParametersParams = z.infer<typeof ParameterSchema>;

// Interface for parameter info responses
export interface ParameterInfo {
  id: string;
  description: string;
}

export interface DateFormatInfo {
  format: string;
  description: string;
  example: string;
} 
```

--------------------------------------------------------------------------------
/src/server/config.ts:
--------------------------------------------------------------------------------

```typescript
import { FastMCP } from 'fastmcp';

/**
 * Create and configure the FastMCP server
 */
export function createServer() {
  // Create the MCP server with fixed configuration
  const server = new FastMCP({
    name: 'LocalTides',
    version: '1.0.0'
  });

  return server;
}

/**
 * Start the FastMCP server with configurable transport
 */
export function startServer(server: FastMCP) {
  // Check command line arguments for transport type
  const args = process.argv.slice(2);
  const httpIndex = args.indexOf('--http');
  const portIndex = args.indexOf('--port');
  
  if (httpIndex !== -1) {
    // HTTP transport mode
    const port = portIndex !== -1 && args[portIndex + 1] ? parseInt(args[portIndex + 1]) : 3000;
    console.log(`Starting NOAA MCP server on HTTP port ${port}`);
    server.start({ 
      transportType: 'httpStream',
      httpStream: {
        endpoint: '/sse',
        port: port
      }
    });
  } else {
    // Default stdio transport
    console.log('Starting NOAA MCP server with stdio transport');
    server.start({ 
      transportType: 'stdio'
    });
  }
} 
```

--------------------------------------------------------------------------------
/src/types/sun.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Sun event types
 */
export enum SunEventType {
  SUNRISE = 'sunrise',
  SUNSET = 'sunset',
  DAWN = 'dawn',
  DUSK = 'dusk',
  SOLAR_NOON = 'solarNoon',
  NIGHT_START = 'night',
  NIGHT_END = 'nightEnd',
  GOLDEN_HOUR_START = 'goldenHourStart',
  GOLDEN_HOUR_END = 'goldenHourEnd',
  NAUTICAL_DAWN = 'nauticalDawn',
  NAUTICAL_DUSK = 'nauticalDusk',
  ASTRONOMICAL_DAWN = 'astronomicalDawn',
  ASTRONOMICAL_DUSK = 'astronomicalDusk'
}

/**
 * Sun times information
 */
export interface SunTimesInfo {
  date: string;
  sunrise: string | null;
  sunset: string | null;
  solarNoon: string | null;
  dawn: string | null;
  dusk: string | null;
  nightStart: string | null;
  nightEnd: string | null;
  goldenHourStart: string | null;
  goldenHourEnd: string | null;
  nauticalDawn: string | null;
  nauticalDusk: string | null;
  astronomicalDawn: string | null;
  astronomicalDusk: string | null;
  dayLength: number; // in minutes
}

/**
 * Sun position information
 */
export interface SunPositionInfo {
  date: string;
  time: string;
  azimuth: number;
  altitude: number;
  declination: number;
  rightAscension: number;
} 
```

--------------------------------------------------------------------------------
/src/tools/index.ts:
--------------------------------------------------------------------------------

```typescript
import { FastMCP } from 'fastmcp';
import { NoaaService } from '../services/noaa-service.js';
import { MoonPhaseService } from '../services/moon-phase-service.js';
import { SunService } from '../services/sun-service.js';
import { NoaaParametersService } from '../services/noaa-parameters-service.js';
import { DpapiService } from '../services/dpapi-service.js';
import { registerWaterTools } from './water-tools.js';
import { registerStationTools } from './station-tools.js';
import { registerMoonTools } from './moon-tools.js';
import { registerSunTools } from './sun-tools.js';
import { registerParameterTools } from './parameter-tools.js';
import { registerDerivedProductTools } from './derived-product-tools.js';

/**
 * Register all tools with the MCP server
 */
export function registerAllTools(server: FastMCP) {
  // Create service instances
  const noaaService = new NoaaService();
  const moonPhaseService = new MoonPhaseService();
  const sunService = new SunService();
  const parametersService = new NoaaParametersService();
  const dpapiService = new DpapiService();
  
  // Register tools by category
  registerWaterTools(server, noaaService);
  registerStationTools(server, noaaService);
  registerMoonTools(server, moonPhaseService);
  registerSunTools(server, sunService);
  registerParameterTools(server, parametersService);
  registerDerivedProductTools(server, dpapiService);
  
  return {
    noaaService,
    moonPhaseService,
    sunService,
    parametersService,
    dpapiService
  };
} 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "@ryancardin/noaa-tides-currents-mcp-server",
  "version": "1.0.0",
  "description": "MCP Server that interfaces with NOAA Tides and Currents API using FastMCP",
  "main": "dist/index.js",
  "bin": {
    "noaa-mcp": "dist/index.js"
  },
  "type": "module",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "start:http": "node dist/index.js --http --port 3000",
    "start:http:3001": "node dist/index.js --http --port 3001",
    "start:http:8080": "node dist/index.js --http --port 8080",
    "dev": "ts-node-esm src/index.ts",
    "dev:http": "ts-node-esm src/index.ts --http --port 3000",
    "test": "vitest run",
    "format": "prettier --write .",
    "inspector": "npx @modelcontextprotocol/inspector ./dist/index.js"
  },
  "keywords": [
    "noaa",
    "tides",
    "currents",
    "mcp",
    "fastmcp",
    "api"
  ],
  "author": "Ryan Cardin",
  "repository": {
    "type": "git",
    "url": "https://github.com/RyanCardin15/NOAA-Tides-And-Currents-MCP.git"
  },
  "bugs": {
    "url": "https://github.com/RyanCardin15/NOAA-Tides-And-Currents-MCP/issues"
  },
  "homepage": "https://github.com/RyanCardin15/NOAA-Tides-And-Currents-MCP#readme",
  "publishConfig": {
    "access": "public"
  },
  "license": "MIT",
  "dependencies": {
    "axios": "^1.6.2",
    "fastmcp": "^1.16.3",
    "suncalc": "^1.9.0",
    "zod": "^3.22.4"
  },
  "devDependencies": {
    "@types/node": "^20.10.0",
    "prettier": "^3.0.3",
    "ts-node": "^10.9.1",
    "typescript": "^5.3.2",
    "vitest": "^3.2.4"
  }
}

```

--------------------------------------------------------------------------------
/src/schemas/common.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';

// Common parameter schemas
export const StationSchema = z.string().min(1).describe('Station ID');
export const DateSchema = z.string().optional().describe('Date to retrieve data for ("today", "latest", "recent", or specific date)');
export const BeginDateSchema = z.string().optional().describe('Start date (YYYYMMDD or MM/DD/YYYY)');
export const EndDateSchema = z.string().optional().describe('End date (YYYYMMDD or MM/DD/YYYY)');
export const RangeSchema = z.number().optional().describe('Number of hours to retrieve data for');
export const DatumSchema = z.string().optional().describe('Datum to use (MLLW, MSL, etc.)');
export const UnitsSchema = z.enum(['english', 'metric']).optional().describe('Units to use ("english" or "metric")');
export const TimeZoneSchema = z.enum(['gmt', 'lst', 'lst_ldt']).optional().describe('Time zone (gmt, lst, lst_ldt)');
export const FormatSchema = z.enum(['json', 'xml', 'csv']).optional().describe('Output format (json, xml, csv)');
export const BinSchema = z.number().optional().describe('Bin number');
export const IntervalSchema = z.string().optional().describe('Interval (hilo, hl, h, or a number for minutes)');

// Schema refinement function for date parameters
export const refineDateParams = (data: any) => 
  (data.date || (data.begin_date && data.end_date) || 
   (data.begin_date && data.range) || (data.end_date && data.range) || 
   data.range);

export const dateRefinementMessage = 
  "You must provide either 'date', 'begin_date' and 'end_date', 'begin_date' and 'range', 'end_date' and 'range', or just 'range'"; 
```

--------------------------------------------------------------------------------
/src/tools/station-tools.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';
import { FastMCP } from 'fastmcp';
import { NoaaService } from '../services/noaa-service.js';
import { 
  StationSchema, 
  UnitsSchema, 
  FormatSchema
} from '../schemas/common.js';

/**
 * Register station-related tools with the MCP server
 */
export function registerStationTools(server: FastMCP, noaaService: NoaaService) {
  // Add stations tool
  server.addTool({
    name: 'get_stations',
    description: 'Get list of stations',
    parameters: z.object({
      type: z.string().optional().describe('Station type (waterlevels, currents, etc.)'),
      format: z.enum(['json', 'xml']).optional().describe('Output format (json, xml)'),
      units: UnitsSchema,
    }),
    execute: async (params) => {
      try {
        const result = await noaaService.getStations(params);
        return JSON.stringify(result);
      } catch (error) {
        if (error instanceof Error) {
          throw new Error(`Failed to get stations: ${error.message}`);
        }
        throw new Error('Failed to get stations');
      }
    }
  });

  // Add station details tool
  server.addTool({
    name: 'get_station_details',
    description: 'Get detailed information about a station',
    parameters: z.object({
      station: StationSchema,
      format: z.enum(['json', 'xml']).optional().describe('Output format (json, xml)'),
      units: UnitsSchema,
    }),
    execute: async (params) => {
      try {
        const result = await noaaService.getStationDetails(params);
        return JSON.stringify(result);
      } catch (error) {
        if (error instanceof Error) {
          throw new Error(`Failed to get station details: ${error.message}`);
        }
        throw new Error('Failed to get station details');
      }
    }
  });
} 
```

--------------------------------------------------------------------------------
/src/interfaces/moon.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';
import { MoonPhaseName } from '../types/moon.js';

/**
 * Parameters for getting moon phase
 */
export const MoonPhaseParamsSchema = z.object({
  date: z.string().optional().describe('Date to get moon phase for (YYYY-MM-DD format). Defaults to current date.'),
  latitude: z.number().min(-90).max(90).optional().describe('Latitude for location-specific calculations'),
  longitude: z.number().min(-180).max(180).optional().describe('Longitude for location-specific calculations'),
  format: z.enum(['json', 'text']).optional().describe('Output format (json or text)')
});

export type MoonPhaseParams = z.infer<typeof MoonPhaseParamsSchema>;

/**
 * Parameters for getting moon phases for a date range
 */
export const MoonPhasesRangeParamsSchema = z.object({
  start_date: z.string().describe('Start date (YYYY-MM-DD format)'),
  end_date: z.string().describe('End date (YYYY-MM-DD format)'),
  latitude: z.number().min(-90).max(90).optional().describe('Latitude for location-specific calculations'),
  longitude: z.number().min(-180).max(180).optional().describe('Longitude for location-specific calculations'),
  format: z.enum(['json', 'text']).optional().describe('Output format (json or text)')
});

export type MoonPhasesRangeParams = z.infer<typeof MoonPhasesRangeParamsSchema>;

/**
 * Parameters for getting next moon phase
 */
export const NextMoonPhaseParamsSchema = z.object({
  phase: z.enum([
    MoonPhaseName.NEW_MOON,
    MoonPhaseName.FIRST_QUARTER,
    MoonPhaseName.FULL_MOON,
    MoonPhaseName.LAST_QUARTER
  ]).describe('Moon phase to find'),
  date: z.string().optional().describe('Starting date (YYYY-MM-DD format). Defaults to current date.'),
  count: z.number().positive().optional().describe('Number of occurrences to return. Defaults to 1.'),
  format: z.enum(['json', 'text']).optional().describe('Output format (json or text)')
});

export type NextMoonPhaseParams = z.infer<typeof NextMoonPhaseParamsSchema>; 
```

--------------------------------------------------------------------------------
/src/tools/moon-tools.ts:
--------------------------------------------------------------------------------

```typescript
import { FastMCP } from 'fastmcp';
import { MoonPhaseService } from '../services/moon-phase-service.js';
import { 
  MoonPhaseParamsSchema, 
  MoonPhasesRangeParamsSchema, 
  NextMoonPhaseParamsSchema 
} from '../interfaces/moon.js';

/**
 * Register moon-related tools with the MCP server
 */
export function registerMoonTools(server: FastMCP, moonPhaseService: MoonPhaseService) {
  // Add moon phase tool
  server.addTool({
    name: 'get_moon_phase',
    description: 'Get moon phase information for a specific date',
    parameters: MoonPhaseParamsSchema,
    execute: async (params) => {
      try {
        const result = moonPhaseService.getMoonPhase(params);
        if (params.format === 'text') {
          return `Moon phase for ${result.date}: ${result.phaseName} (${(result.illumination * 100).toFixed(1)}% illuminated)`;
        }
        return JSON.stringify(result);
      } catch (error) {
        if (error instanceof Error) {
          throw new Error(`Failed to get moon phase: ${error.message}`);
        }
        throw new Error('Failed to get moon phase');
      }
    }
  });

  // Add moon phases range tool
  server.addTool({
    name: 'get_moon_phases_range',
    description: 'Get moon phase information for a date range',
    parameters: MoonPhasesRangeParamsSchema,
    execute: async (params) => {
      try {
        const results = moonPhaseService.getMoonPhasesRange(params);
        if (params.format === 'text') {
          return results.map(result => 
            `${result.date}: ${result.phaseName} (${(result.illumination * 100).toFixed(1)}% illuminated)`
          ).join('\n');
        }
        return JSON.stringify(results);
      } catch (error) {
        if (error instanceof Error) {
          throw new Error(`Failed to get moon phases: ${error.message}`);
        }
        throw new Error('Failed to get moon phases');
      }
    }
  });

  // Add next moon phase tool
  server.addTool({
    name: 'get_next_moon_phase',
    description: 'Get the next occurrence(s) of a specific moon phase',
    parameters: NextMoonPhaseParamsSchema,
    execute: async (params) => {
      try {
        const results = moonPhaseService.getNextMoonPhase(params);
        if (params.format === 'text') {
          return results.map(result => 
            `Next ${result.phase}: ${result.date}`
          ).join('\n');
        }
        return JSON.stringify(results);
      } catch (error) {
        if (error instanceof Error) {
          throw new Error(`Failed to get next moon phase: ${error.message}`);
        }
        throw new Error('Failed to get next moon phase');
      }
    }
  });
} 
```

--------------------------------------------------------------------------------
/src/interfaces/sun.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';
import { SunEventType } from '../types/sun.js';

/**
 * Parameters for getting sun times
 */
export const SunTimesParamsSchema = z.object({
  date: z.string().optional().describe('Date to get sun times for (YYYY-MM-DD format). Defaults to current date.'),
  latitude: z.number().min(-90).max(90).describe('Latitude for location-specific calculations'),
  longitude: z.number().min(-180).max(180).describe('Longitude for location-specific calculations'),
  format: z.enum(['json', 'text']).optional().describe('Output format (json or text)'),
  timezone: z.string().optional().describe('Timezone for the results. Defaults to UTC.')
});

export type SunTimesParams = z.infer<typeof SunTimesParamsSchema>;

/**
 * Parameters for getting sun times for a date range
 */
export const SunTimesRangeParamsSchema = z.object({
  start_date: z.string().describe('Start date (YYYY-MM-DD format)'),
  end_date: z.string().describe('End date (YYYY-MM-DD format)'),
  latitude: z.number().min(-90).max(90).describe('Latitude for location-specific calculations'),
  longitude: z.number().min(-180).max(180).describe('Longitude for location-specific calculations'),
  format: z.enum(['json', 'text']).optional().describe('Output format (json or text)'),
  timezone: z.string().optional().describe('Timezone for the results. Defaults to UTC.')
});

export type SunTimesRangeParams = z.infer<typeof SunTimesRangeParamsSchema>;

/**
 * Parameters for getting sun position
 */
export const SunPositionParamsSchema = z.object({
  date: z.string().optional().describe('Date to get sun position for (YYYY-MM-DD format). Defaults to current date.'),
  time: z.string().optional().describe('Time to get sun position for (HH:MM:SS format). Defaults to current time.'),
  latitude: z.number().min(-90).max(90).describe('Latitude for location-specific calculations'),
  longitude: z.number().min(-180).max(180).describe('Longitude for location-specific calculations'),
  format: z.enum(['json', 'text']).optional().describe('Output format (json or text)')
});

export type SunPositionParams = z.infer<typeof SunPositionParamsSchema>;

/**
 * Parameters for finding the next sun event
 */
export const NextSunEventParamsSchema = z.object({
  event: z.nativeEnum(SunEventType).describe('Sun event to find'),
  date: z.string().optional().describe('Starting date (YYYY-MM-DD format). Defaults to current date.'),
  latitude: z.number().min(-90).max(90).describe('Latitude for location-specific calculations'),
  longitude: z.number().min(-180).max(180).describe('Longitude for location-specific calculations'),
  count: z.number().positive().optional().describe('Number of occurrences to return. Defaults to 1.'),
  format: z.enum(['json', 'text']).optional().describe('Output format (json or text)'),
  timezone: z.string().optional().describe('Timezone for the results. Defaults to UTC.')
});

export type NextSunEventParams = z.infer<typeof NextSunEventParamsSchema>; 
```

--------------------------------------------------------------------------------
/src/tools/parameter-tools.ts:
--------------------------------------------------------------------------------

```typescript
import { FastMCP } from 'fastmcp';
import { ParameterSchema } from '../interfaces/parameters.js';
import { NoaaParametersService } from '../services/noaa-parameters-service.js';

/**
 * Register parameter tools with the MCP server
 */
export function registerParameterTools(server: FastMCP, parametersService: NoaaParametersService) {
  /**
   * Add parameter definitions tool
   */
  server.addTool({
    name: 'get_parameter_definitions',
    description: 'Get information about valid parameter values for NOAA API requests',
    parameters: ParameterSchema,
    execute: async (params) => {
      try {
        const { parameter } = params;

        // If no parameter specified, return all parameter types and their descriptions
        if (!parameter) {
          return JSON.stringify({
            time_zones: parametersService.getTimeZones(),
            datums: parametersService.getDatums(),
            units: parametersService.getUnits(),
            tide_intervals: parametersService.getTidePredictionIntervals(),
            current_intervals: parametersService.getCurrentPredictionIntervals(),
            velocity_types: parametersService.getVelocityTypes(),
            products: parametersService.getMeteorologicalProducts(),
            station_types: parametersService.getStationTypes(),
            date_formats: parametersService.getDateFormats(),
            output_formats: parametersService.getOutputFormats()
          });
        }

        // Return specific parameter information based on the parameter type
        let result;
        switch (parameter) {
          case 'time_zones':
            result = parametersService.getTimeZones();
            break;
          case 'datums':
            result = parametersService.getDatums();
            break;
          case 'units':
            result = parametersService.getUnits();
            break;
          case 'tide_intervals':
            result = parametersService.getTidePredictionIntervals();
            break;
          case 'current_intervals':
            result = parametersService.getCurrentPredictionIntervals();
            break;
          case 'velocity_types':
            result = parametersService.getVelocityTypes();
            break;
          case 'products':
            result = parametersService.getMeteorologicalProducts();
            break;
          case 'station_types':
            result = parametersService.getStationTypes();
            break;
          case 'date_formats':
            result = parametersService.getDateFormats();
            break;
          case 'output_formats':
            result = parametersService.getOutputFormats();
            break;
          default:
            throw new Error(`Unknown parameter type: ${parameter}`);
        }

        return JSON.stringify(result);
      } catch (error) {
        if (error instanceof Error) {
          throw new Error(`Failed to get parameter definitions: ${error.message}`);
        }
        throw new Error('Failed to get parameter definitions');
      }
    }
  });
} 
```

--------------------------------------------------------------------------------
/src/server/mcp-server.ts:
--------------------------------------------------------------------------------

```typescript
import { NoaaService } from '../services/noaa-service.js';
import { ZodSchema } from 'zod';
import {
  GetWaterLevelsSchema,
  GetTidePredictionsSchema,
  GetCurrentsSchema,
  GetCurrentPredictionsSchema,
  GetMeteorologicalDataSchema,
  GetStationsSchema,
  GetStationDetailsSchema
} from '../interfaces/noaa.js';

// MCP Tool type
interface MCPTool<TParams = any, TResult = any> {
  name: string;
  description: string;
  inputSchema: ZodSchema<TParams>;
  handler: (params: TParams) => Promise<TResult>;
}

// Class for the MCP server
export class McpServer {
  private tools: MCPTool[];
  private noaaService: NoaaService;

  constructor(noaaService: NoaaService) {
    this.noaaService = noaaService;
    this.tools = this.initializeTools();
  }

  // Initialize the tools
  private initializeTools(): MCPTool[] {
    // Water Levels tool
    const getWaterLevels: MCPTool = {
      name: "get_water_levels",
      description: "Get water level data for a station",
      inputSchema: GetWaterLevelsSchema,
      handler: async (params) => {
        return this.noaaService.getWaterLevels(params);
      }
    };

    // Tide Predictions tool
    const getTidePredictions: MCPTool = {
      name: "get_tide_predictions",
      description: "Get tide prediction data",
      inputSchema: GetTidePredictionsSchema,
      handler: async (params) => {
        return this.noaaService.getTidePredictions(params);
      }
    };

    // Currents tool
    const getCurrents: MCPTool = {
      name: "get_currents",
      description: "Get currents data for a station",
      inputSchema: GetCurrentsSchema,
      handler: async (params) => {
        return this.noaaService.getCurrents(params);
      }
    };

    // Current Predictions tool
    const getCurrentPredictions: MCPTool = {
      name: "get_current_predictions",
      description: "Get current predictions",
      inputSchema: GetCurrentPredictionsSchema,
      handler: async (params) => {
        return this.noaaService.getCurrentPredictions(params);
      }
    };

    // Meteorological Data tool
    const getMeteorologicalData: MCPTool = {
      name: "get_meteorological_data",
      description: "Get meteorological data",
      inputSchema: GetMeteorologicalDataSchema,
      handler: async (params) => {
        return this.noaaService.getMeteorologicalData(params);
      }
    };

    // Stations tool
    const getStations: MCPTool = {
      name: "get_stations",
      description: "Get list of stations",
      inputSchema: GetStationsSchema,
      handler: async (params) => {
        return this.noaaService.getStations(params);
      }
    };

    // Station Details tool
    const getStationDetails: MCPTool = {
      name: "get_station_details",
      description: "Get detailed information about a station",
      inputSchema: GetStationDetailsSchema,
      handler: async (params) => {
        return this.noaaService.getStationDetails(params);
      }
    };

    return [
      getWaterLevels,
      getTidePredictions,
      getCurrents,
      getCurrentPredictions,
      getMeteorologicalData,
      getStations,
      getStationDetails
    ];
  }

  // Method to get all tools
  getTools(): { name: string, description: string }[] {
    return this.tools.map(tool => ({
      name: tool.name,
      description: tool.description
    }));
  }

  // Method to handle tool execution
  async executeTool(toolName: string, params: any): Promise<any> {
    const tool = this.tools.find(t => t.name === toolName);
    if (!tool) {
      throw new Error(`Tool '${toolName}' not found`);
    }

    // Validate the parameters against the schema
    const validatedParams = tool.inputSchema.parse(params);
    
    // Execute the tool handler
    return tool.handler(validatedParams);
  }
} 
```

--------------------------------------------------------------------------------
/src/types/suncalc.d.ts:
--------------------------------------------------------------------------------

```typescript
declare module 'suncalc' {
  export interface MoonIllumination {
    /** fraction of moon's visible disk that is illuminated */
    fraction: number;
    /** moon phase (0.0-1.0) */
    phase: number;
    /** midpoint angle in radians of the illuminated limb of the moon reckoned eastward from the north point of the disk */
    angle: number;
  }

  export interface MoonPosition {
    /** moon azimuth in radians */
    azimuth: number;
    /** moon altitude above the horizon in radians */
    altitude: number;
    /** distance to moon in kilometers */
    distance: number;
    /** parallactic angle of the moon in radians */
    parallacticAngle: number;
  }

  export interface SunPosition {
    /** sun azimuth in radians (direction along the horizon, measured from south to west) */
    azimuth: number;
    /** sun altitude above the horizon in radians */
    altitude: number;
  }

  export interface SunTimes {
    /** sunrise (top edge of the sun appears on the horizon) */
    sunrise: Date;
    /** sunrise ends (bottom edge of the sun touches the horizon) */
    sunriseEnd: Date;
    /** morning golden hour (soft light, best time for photography) starts */
    goldenHourEnd: Date;
    /** solar noon (sun is in the highest position) */
    solarNoon: Date;
    /** evening golden hour starts */
    goldenHour: Date;
    /** sunset starts (bottom edge of the sun touches the horizon) */
    sunsetStart: Date;
    /** sunset (sun disappears below the horizon, evening civil twilight starts) */
    sunset: Date;
    /** dusk (evening nautical twilight starts) */
    dusk: Date;
    /** nautical dusk (evening astronomical twilight starts) */
    nauticalDusk: Date;
    /** astronomical dusk (evening astronomical twilight starts) */
    astronomicalDusk: Date;
    /** night starts (dark enough for astronomical observations) */
    night: Date;
    /** nadir (darkest moment of the night, sun is in the lowest position) */
    nadir: Date;
    /** night ends (morning astronomical twilight starts) */
    nightEnd: Date;
    /** astronomical dawn (morning astronomical twilight starts) */
    astronomicalDawn: Date;
    /** nautical dawn (morning nautical twilight starts) */
    nauticalDawn: Date;
    /** dawn (morning nautical twilight ends, morning civil twilight starts) */
    dawn: Date;
  }

  export interface MoonTimes {
    /** moonrise time as Date */
    rise: Date | null;
    /** moonset time as Date */
    set: Date | null;
    /** true if the moon never rises/sets and is always above the horizon during the day */
    alwaysUp: boolean;
    /** true if the moon is always below the horizon */
    alwaysDown: boolean;
  }

  /**
   * Calculates sun position for a given date and latitude/longitude
   */
  export function getPosition(date: Date | number, lat: number, lng: number): SunPosition;

  /**
   * Calculates sun times for a given date, latitude/longitude, and, optionally, the observer height (in meters) relative to the horizon
   */
  export function getTimes(date: Date | number, lat: number, lng: number, height?: number): SunTimes;

  /**
   * Returns an object with the following properties:
   * altitude: moon altitude above the horizon in radians
   * azimuth: moon azimuth in radians
   * distance: distance to moon in kilometers
   * parallacticAngle: parallactic angle of the moon in radians
   */
  export function getMoonPosition(date: Date | number, lat: number, lng: number): MoonPosition;

  /**
   * Returns an object with the following properties:
   * fraction: illuminated fraction of the moon; varies from 0.0 (new moon) to 1.0 (full moon)
   * phase: moon phase; varies from 0.0 to 1.0, described below
   * angle: midpoint angle in radians of the illuminated limb of the moon reckoned eastward from the north point of the disk
   */
  export function getMoonIllumination(date: Date | number): MoonIllumination;

  /**
   * Returns an object with the following properties:
   * rise: moonrise time as Date
   * set: moonset time as Date
   * alwaysUp: true if the moon never rises/sets and is always above the horizon during the day
   * alwaysDown: true if the moon is always below the horizon
   */
  export function getMoonTimes(date: Date | number, lat: number, lng: number, inUTC?: boolean): MoonTimes;
} 
```

--------------------------------------------------------------------------------
/src/schemas/dpapi.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';
import { StationSchema, UnitsSchema, FormatSchema } from './common.js';

// DPAPI-specific parameter schemas
export const AffiliationSchema = z.enum(['Global', 'US']).optional().describe('Station affiliation (Global or US)');
export const ScenarioSchema = z.enum(['all', 'low', 'intermediate-low', 'intermediate', 'intermediate-high', 'high', 'extreme']).optional().describe('Sea level rise scenario');
export const SeasonSchema = z.enum(['DJF', 'MAM', 'JJA', 'SON']).optional().describe('Season months (DJF-Winter, MAM-Spring, JJA-Summer, SON-Fall)');
export const DpapiDatumSchema = z.enum(['STND', 'MLLW', 'MHHW', 'GT', 'MSL', 'MLW', 'MHW']).optional().describe('Datum reference for DPAPI');
export const ThresholdSchema = z.enum(['minor', 'moderate', 'major']).optional().describe('Flood threshold level');
export const DecadeSchema = z.string().optional().describe('Decade for projections (e.g., "2050")');
export const YearSchema = z.string().optional().describe('Year for analysis (YYYY format)');
export const YearRangeSchema = z.string().optional().describe('Year range (YYYY-YYYY format)');

// Sea Level Trends schema
export const SeaLevelTrendsSchema = z.object({
  station: StationSchema,
  affil: AffiliationSchema,
  format: FormatSchema
}).describe('Get sea level trends for a station');

// Extreme Water Levels schema
export const ExtremeWaterLevelsSchema = z.object({
  station: StationSchema,
  units: UnitsSchema,
  format: FormatSchema
}).describe('Get extreme water levels for a station');

// High Tide Flooding Daily schema
export const HighTideFloodingDailySchema = z.object({
  station: StationSchema,
  format: FormatSchema,
  datum: DpapiDatumSchema,
  threshold: ThresholdSchema,
  begin_date: z.string().optional().describe('Start date (YYYYMMDD format)'),
  end_date: z.string().optional().describe('End date (YYYYMMDD format)'),
  year: YearSchema
}).describe('Get high tide flooding daily count data');

// High Tide Flooding Monthly schema
export const HighTideFloodingMonthlySchema = z.object({
  station: StationSchema,
  format: FormatSchema,
  datum: DpapiDatumSchema,
  threshold: ThresholdSchema,
  begin_date: z.string().optional().describe('Start date (YYYYMMDD format)'),
  end_date: z.string().optional().describe('End date (YYYYMMDD format)'),
  year: YearSchema
}).describe('Get high tide flooding monthly count data');

// High Tide Flooding Seasonal schema
export const HighTideFloodingSeasonalSchema = z.object({
  station: StationSchema,
  format: FormatSchema,
  datum: DpapiDatumSchema,
  threshold: ThresholdSchema,
  season_months: SeasonSchema,
  begin_date: z.string().optional().describe('Start date (YYYYMMDD format)'),
  end_date: z.string().optional().describe('End date (YYYYMMDD format)'),
  year: YearSchema
}).describe('Get high tide flooding seasonal count data');

// High Tide Flooding Annual schema
export const HighTideFloodingAnnualSchema = z.object({
  station: StationSchema,
  format: FormatSchema,
  datum: DpapiDatumSchema,
  threshold: ThresholdSchema,
  begin_date: z.string().optional().describe('Start date (YYYYMMDD format)'),
  end_date: z.string().optional().describe('End date (YYYYMMDD format)'),
  year_range: YearRangeSchema
}).describe('Get high tide flooding annual count data');

// High Tide Flooding Projections schema
export const HighTideFloodingProjectionsSchema = z.object({
  station: StationSchema,
  format: FormatSchema,
  scenario: ScenarioSchema,
  datum: DpapiDatumSchema,
  threshold: ThresholdSchema,
  decade: DecadeSchema
}).describe('Get high tide flooding decadal projections');

// High Tide Flooding Likelihoods schema
export const HighTideFloodingLikelihoodsSchema = z.object({
  station: StationSchema,
  format: FormatSchema,
  datum: DpapiDatumSchema,
  threshold: ThresholdSchema,
  date: z.string().optional().describe('Specific date (YYYYMMDD format)')
}).describe('Get high tide flooding daily likelihoods');

// Top Ten Water Levels schema
export const TopTenWaterLevelsSchema = z.object({
  station: StationSchema,
  format: FormatSchema,
  datum: DpapiDatumSchema,
  units: UnitsSchema,
  analysis_type: z.enum(['highest', 'lowest']).optional().describe('Analysis type (highest or lowest)')
}).describe('Get top ten water levels for a station');
```

--------------------------------------------------------------------------------
/src/tools/sun-tools.ts:
--------------------------------------------------------------------------------

```typescript
import { FastMCP } from 'fastmcp';
import { SunService } from '../services/sun-service.js';
import { 
  SunTimesParamsSchema, 
  SunTimesRangeParamsSchema, 
  SunPositionParamsSchema, 
  NextSunEventParamsSchema 
} from '../interfaces/sun.js';

/**
 * Register sun-related tools with the MCP server
 */
export function registerSunTools(server: FastMCP, sunService: SunService) {
  // Add sun times tool
  server.addTool({
    name: 'get_sun_times',
    description: 'Get sun rise/set and other sun event times for a specific date and location',
    parameters: SunTimesParamsSchema,
    execute: async (params) => {
      try {
        const result = sunService.getSunTimes(params);
        if (params.format === 'text') {
          let text = `Sun times for ${result.date} at latitude ${params.latitude}, longitude ${params.longitude}:\n`;
          text += `Sunrise: ${result.sunrise || 'N/A'}\n`;
          text += `Sunset: ${result.sunset || 'N/A'}\n`;
          text += `Day length: ${Math.floor(result.dayLength / 60)}h ${Math.round(result.dayLength % 60)}m\n`;
          text += `Solar noon: ${result.solarNoon || 'N/A'}\n`;
          text += `Dawn: ${result.dawn || 'N/A'}\n`;
          text += `Dusk: ${result.dusk || 'N/A'}\n`;
          return text;
        }
        return JSON.stringify(result);
      } catch (error) {
        if (error instanceof Error) {
          throw new Error(`Failed to get sun times: ${error.message}`);
        }
        throw new Error('Failed to get sun times');
      }
    }
  });

  // Add sun times range tool
  server.addTool({
    name: 'get_sun_times_range',
    description: 'Get sun rise/set and other sun event times for a date range and location',
    parameters: SunTimesRangeParamsSchema,
    execute: async (params) => {
      try {
        const results = sunService.getSunTimesRange(params);
        if (params.format === 'text') {
          let text = `Sun times from ${params.start_date} to ${params.end_date} at latitude ${params.latitude}, longitude ${params.longitude}:\n\n`;
          
          results.forEach(result => {
            text += `Date: ${result.date}\n`;
            text += `Sunrise: ${result.sunrise || 'N/A'}\n`;
            text += `Sunset: ${result.sunset || 'N/A'}\n`;
            text += `Day length: ${Math.floor(result.dayLength / 60)}h ${Math.round(result.dayLength % 60)}m\n\n`;
          });
          
          return text;
        }
        return JSON.stringify(results);
      } catch (error) {
        if (error instanceof Error) {
          throw new Error(`Failed to get sun times range: ${error.message}`);
        }
        throw new Error('Failed to get sun times range');
      }
    }
  });

  // Add sun position tool
  server.addTool({
    name: 'get_sun_position',
    description: 'Get sun position information for a specific date, time, and location',
    parameters: SunPositionParamsSchema,
    execute: async (params) => {
      try {
        const result = sunService.getSunPosition(params);
        if (params.format === 'text') {
          let text = `Sun position for ${result.date} ${result.time} at latitude ${params.latitude}, longitude ${params.longitude}:\n`;
          text += `Azimuth: ${result.azimuth.toFixed(2)}°\n`;
          text += `Altitude: ${result.altitude.toFixed(2)}°\n`;
          text += `Declination: ${result.declination.toFixed(2)}°\n`;
          text += `Right Ascension: ${result.rightAscension.toFixed(2)}h\n`;
          return text;
        }
        return JSON.stringify(result);
      } catch (error) {
        if (error instanceof Error) {
          throw new Error(`Failed to get sun position: ${error.message}`);
        }
        throw new Error('Failed to get sun position');
      }
    }
  });

  // Add next sun event tool
  server.addTool({
    name: 'get_next_sun_event',
    description: 'Get the next occurrence(s) of a specific sun event',
    parameters: NextSunEventParamsSchema,
    execute: async (params) => {
      try {
        const results = sunService.getNextSunEvent(params);
        if (params.format === 'text') {
          return results.map(result => 
            `Next ${result.event}: ${result.date} at ${result.time}`
          ).join('\n');
        }
        return JSON.stringify(results);
      } catch (error) {
        if (error instanceof Error) {
          throw new Error(`Failed to get next sun event: ${error.message}`);
        }
        throw new Error('Failed to get next sun event');
      }
    }
  });
} 
```

--------------------------------------------------------------------------------
/src/services/noaa-service.ts:
--------------------------------------------------------------------------------

```typescript
import axios from 'axios';

// Base URLs for the different NOAA APIs
const DATA_API_BASE_URL = 'https://api.tidesandcurrents.noaa.gov/api/prod/datagetter';
const METADATA_API_BASE_URL = 'https://api.tidesandcurrents.noaa.gov/mdapi/prod/webapi';

/**
 * Service for interacting with NOAA Tides and Currents APIs
 */
export class NoaaService {

  /**
   * Build parameters for the API request
   * @param params Parameters for the request
   * @returns URL-encoded parameters string
   */
  private buildParams(params: Record<string, any>): string {
    // Remove undefined and null values
    const filteredParams = Object.entries(params)
      .filter(([_, value]) => value !== undefined && value !== null)
      .reduce((acc, [key, value]) => {
        acc[key] = value;
        return acc;
      }, {} as Record<string, any>);

    // Convert to URL parameters
    return new URLSearchParams(filteredParams as Record<string, string>).toString();
  }

  /**
   * Make a request to the Data API
   * @param params Parameters for the request
   * @returns Response data
   */
  async fetchDataApi(params: Record<string, any>): Promise<any> {
    try {
      const queryParams = this.buildParams(params);
      const url = `${DATA_API_BASE_URL}?${queryParams}`;
      
      const response = await axios.get(url);
      return response.data;
    } catch (error) {
      if (axios.isAxiosError(error) && error.response) {
        throw new Error(`NOAA API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`);
      }
      throw error;
    }
  }

  /**
   * Make a request to the Metadata API
   * @param endpoint Endpoint path
   * @param params Parameters for the request
   * @returns Response data
   */
  async fetchMetadataApi(endpoint: string, params: Record<string, any> = {}): Promise<any> {
    try {
      const queryParams = this.buildParams(params);
      const url = `${METADATA_API_BASE_URL}${endpoint}${queryParams ? '?' + queryParams : ''}`;
      
      const response = await axios.get(url);
      return response.data;
    } catch (error) {
      if (axios.isAxiosError(error) && error.response) {
        throw new Error(`NOAA API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`);
      }
      throw error;
    }
  }

  /**
   * Get water level data
   */
  async getWaterLevels(params: Record<string, any>): Promise<any> {
    return this.fetchDataApi({
      ...params,
      product: 'water_level'
    });
  }

  /**
   * Get tide predictions
   */
  async getTidePredictions(params: Record<string, any>): Promise<any> {
    return this.fetchDataApi({
      ...params,
      product: 'predictions'
    });
  }

  /**
   * Get currents data
   */
  async getCurrents(params: Record<string, any>): Promise<any> {
    return this.fetchDataApi({
      ...params,
      product: 'currents'
    });
  }

  /**
   * Get current predictions
   */
  async getCurrentPredictions(params: Record<string, any>): Promise<any> {
    return this.fetchDataApi({
      ...params,
      product: 'currents_predictions'
    });
  }

  /**
   * Get meteorological data (air_temperature, wind, etc.)
   */
  async getMeteorologicalData(params: Record<string, any>): Promise<any> {
    const { product, ...rest } = params;
    return this.fetchDataApi({
      ...rest,
      product
    });
  }

  /**
   * Get list of stations
   */
  async getStations(params: Record<string, any>): Promise<any> {
    const { 
      type, 
      name, 
      lat_min, 
      lat_max, 
      lon_min, 
      lon_max, 
      state, 
      limit, 
      offset, 
      sort_by, 
      sort_order, 
      ...rest 
    } = params;
    
    const endpoint = '/stations.' + (rest.format || 'json');
    
    // Build query parameters with all the filters
    const queryParams: Record<string, any> = { ...rest };
    
    // Add filters only if they are defined
    if (type) queryParams.type = type;
    if (name) queryParams.name = name;
    if (lat_min !== undefined) queryParams.lat_min = lat_min;
    if (lat_max !== undefined) queryParams.lat_max = lat_max;
    if (lon_min !== undefined) queryParams.lon_min = lon_min;
    if (lon_max !== undefined) queryParams.lon_max = lon_max;
    if (state) queryParams.state = state;
    if (limit !== undefined) queryParams.limit = limit;
    if (offset !== undefined) queryParams.offset = offset;
    if (sort_by) queryParams.sort_by = sort_by;
    if (sort_order) queryParams.sort_order = sort_order;
    
    return this.fetchMetadataApi(endpoint, queryParams);
  }

  /**
   * Get station details
   */
  async getStationDetails(params: Record<string, any>): Promise<any> {
    const { station, ...rest } = params;
    const endpoint = `/stations/${station}/details.` + (rest.format || 'json');
    
    return this.fetchMetadataApi(endpoint, rest);
  }
} 
```

--------------------------------------------------------------------------------
/src/services/noaa-parameters-service.ts:
--------------------------------------------------------------------------------

```typescript
import axios from 'axios';

/**
 * Provides information about valid NOAA Tides and Currents API parameters
 */
export class NoaaParametersService {
  /**
   * Get valid time zone values
   */
  getTimeZones(): { id: string, description: string }[] {
    return [
      { id: 'gmt', description: 'Greenwich Mean Time' },
      { id: 'lst', description: 'Local Standard Time' },
      { id: 'lst_ldt', description: 'Local Standard/Local Daylight Time' }
    ];
  }

  /**
   * Get valid datum values
   */
  getDatums(): { id: string, description: string }[] {
    return [
      { id: 'MHHW', description: 'Mean Higher High Water' },
      { id: 'MHW', description: 'Mean High Water' },
      { id: 'MTL', description: 'Mean Tide Level' },
      { id: 'MSL', description: 'Mean Sea Level' },
      { id: 'MLW', description: 'Mean Low Water' },
      { id: 'MLLW', description: 'Mean Lower Low Water' },
      { id: 'NAVD', description: 'North American Vertical Datum' },
      { id: 'STND', description: 'Station Datum' }
    ];
  }

  /**
   * Get valid units
   */
  getUnits(): { id: string, description: string }[] {
    return [
      { id: 'english', description: 'English units (feet, mph, etc.)' },
      { id: 'metric', description: 'Metric units (meters, kph, etc.)' }
    ];
  }

  /**
   * Get valid intervals for tide predictions
   */
  getTidePredictionIntervals(): { id: string, description: string }[] {
    return [
      { id: 'hilo', description: 'High/low tide predictions only' },
      { id: 'h', description: 'Hourly predictions' },
      { id: '6', description: '6-minute predictions' },
      { id: '30', description: '30-minute predictions' },
      { id: '60', description: '60-minute predictions' }
    ];
  }

  /**
   * Get valid intervals for current predictions
   */
  getCurrentPredictionIntervals(): { id: string, description: string }[] {
    return [
      { id: 'MAX_SLACK', description: 'Maximum flood/ebb and slack predictions only' },
      { id: '6', description: '6-minute predictions' },
      { id: '30', description: '30-minute predictions' },
      { id: '60', description: '60-minute predictions' }
    ];
  }

  /**
   * Get valid velocity types for current predictions
   */
  getVelocityTypes(): { id: string, description: string }[] {
    return [
      { id: 'default', description: 'Default velocity reporting (flood/ebb direction)' },
      { id: 'speed_dir', description: 'Speed and direction format' }
    ];
  }

  /**
   * Get valid meteorological products
   */
  getMeteorologicalProducts(): { id: string, description: string }[] {
    return [
      { id: 'air_temperature', description: 'Air temperature' },
      { id: 'water_temperature', description: 'Water temperature' },
      { id: 'wind', description: 'Wind speed and direction' },
      { id: 'air_pressure', description: 'Barometric pressure' },
      { id: 'air_gap', description: 'Air gap (distance between bridge and water surface)' },
      { id: 'conductivity', description: 'Conductivity' },
      { id: 'visibility', description: 'Visibility' },
      { id: 'humidity', description: 'Relative humidity' },
      { id: 'salinity', description: 'Salinity' },
      { id: 'hourly_height', description: 'Verified hourly height water level' },
      { id: 'high_low', description: 'Verified high/low water level' },
      { id: 'daily_mean', description: 'Verified daily mean water level' },
      { id: 'monthly_mean', description: 'Verified monthly mean water level' },
      { id: 'one_minute_water_level', description: 'One-minute water level data' },
      { id: 'datums', description: 'Datums' }
    ];
  }

  /**
   * Get valid station types
   */
  getStationTypes(): { id: string, description: string }[] {
    return [
      { id: 'waterlevels', description: 'Water level stations' },
      { id: 'currentpredictions', description: 'Current prediction stations' },
      { id: 'currents', description: 'Current observation stations' },
      { id: 'tidepredictions', description: 'Tide prediction stations' },
      { id: 'weather', description: 'Weather stations' },
      { id: 'ports', description: 'Physical Oceanographic Real-Time System (PORTS) stations' }
    ];
  }

  /**
   * Get valid date formats
   */
  getDateFormats(): { format: string, description: string, example: string }[] {
    return [
      { format: 'YYYYMMDD', description: 'Year, month, day without separators', example: '20230401' },
      { format: 'MM/DD/YYYY', description: 'Month/day/year with separators', example: '04/01/2023' },
      { format: 'today', description: 'Current date', example: 'today' },
      { format: 'latest', description: 'Latest available data', example: 'latest' },
      { format: 'recent', description: 'Most recent data', example: 'recent' }
    ];
  }

  /**
   * Get valid output formats
   */
  getOutputFormats(): { id: string, description: string }[] {
    return [
      { id: 'json', description: 'JSON format' },
      { id: 'xml', description: 'XML format' },
      { id: 'csv', description: 'CSV format (not available for all endpoints)' }
    ];
  }
} 
```

--------------------------------------------------------------------------------
/src/tools/water-tools.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';
import { FastMCP } from 'fastmcp';
import { NoaaService } from '../services/noaa-service.js';
import { 
  StationSchema, 
  DateSchema, 
  BeginDateSchema, 
  EndDateSchema, 
  RangeSchema,
  DatumSchema, 
  UnitsSchema, 
  TimeZoneSchema, 
  FormatSchema,
  BinSchema,
  IntervalSchema,
  refineDateParams,
  dateRefinementMessage
} from '../schemas/common.js';

/**
 * Register water-related tools with the MCP server
 */
export function registerWaterTools(server: FastMCP, noaaService: NoaaService) {
  // Add water levels tool
  server.addTool({
    name: 'get_water_levels',
    description: 'Get water level data for a station',
    parameters: z.object({
      station: StationSchema,
      date: DateSchema,
      begin_date: BeginDateSchema,
      end_date: EndDateSchema,
      range: RangeSchema,
      datum: DatumSchema,
      units: UnitsSchema,
      time_zone: TimeZoneSchema,
      format: FormatSchema,
    }).refine(refineDateParams, { message: dateRefinementMessage }),
    execute: async (params) => {
      try {
        const result = await noaaService.getWaterLevels(params);
        return JSON.stringify(result);
      } catch (error) {
        if (error instanceof Error) {
          throw new Error(`Failed to get water levels: ${error.message}`);
        }
        throw new Error('Failed to get water levels');
      }
    }
  });

  // Add tide predictions tool
  server.addTool({
    name: 'get_tide_predictions',
    description: 'Get tide prediction data',
    parameters: z.object({
      station: StationSchema,
      begin_date: BeginDateSchema,
      end_date: EndDateSchema,
      date: DateSchema,
      range: RangeSchema,
      datum: DatumSchema,
      units: UnitsSchema,
      time_zone: TimeZoneSchema,
      interval: IntervalSchema,
      format: FormatSchema,
    }).refine(refineDateParams, { message: dateRefinementMessage }),
    execute: async (params) => {
      try {
        const result = await noaaService.getTidePredictions(params);
        return JSON.stringify(result);
      } catch (error) {
        if (error instanceof Error) {
          throw new Error(`Failed to get tide predictions: ${error.message}`);
        }
        throw new Error('Failed to get tide predictions');
      }
    }
  });

  // Add currents tool
  server.addTool({
    name: 'get_currents',
    description: 'Get currents data for a station',
    parameters: z.object({
      station: StationSchema,
      date: DateSchema,
      begin_date: BeginDateSchema,
      end_date: EndDateSchema,
      range: RangeSchema,
      bin: BinSchema,
      units: UnitsSchema,
      time_zone: TimeZoneSchema,
      format: FormatSchema,
    }).refine(refineDateParams, { message: dateRefinementMessage }),
    execute: async (params) => {
      try {
        const result = await noaaService.getCurrents(params);
        return JSON.stringify(result);
      } catch (error) {
        if (error instanceof Error) {
          throw new Error(`Failed to get currents: ${error.message}`);
        }
        throw new Error('Failed to get currents');
      }
    }
  });

  // Add current predictions tool
  server.addTool({
    name: 'get_current_predictions',
    description: 'Get current predictions',
    parameters: z.object({
      station: StationSchema,
      date: DateSchema,
      begin_date: BeginDateSchema,
      end_date: EndDateSchema,
      range: RangeSchema,
      bin: BinSchema,
      interval: z.string().optional().describe('Interval (MAX_SLACK or a number for minutes)'),
      vel_type: z.enum(['speed_dir', 'default']).optional().describe('Velocity type (speed_dir or default)'),
      units: UnitsSchema,
      time_zone: TimeZoneSchema,
      format: FormatSchema,
    }).refine(refineDateParams, { message: dateRefinementMessage }),
    execute: async (params) => {
      try {
        const result = await noaaService.getCurrentPredictions(params);
        return JSON.stringify(result);
      } catch (error) {
        if (error instanceof Error) {
          throw new Error(`Failed to get current predictions: ${error.message}`);
        }
        throw new Error('Failed to get current predictions');
      }
    }
  });

  // Add meteorological data tool
  server.addTool({
    name: 'get_meteorological_data',
    description: 'Get meteorological data',
    parameters: z.object({
      station: StationSchema,
      product: z.string().min(1).describe('Product (air_temperature, wind, etc.)'),
      date: DateSchema,
      begin_date: BeginDateSchema,
      end_date: EndDateSchema,
      range: RangeSchema,
      units: UnitsSchema,
      time_zone: TimeZoneSchema,
      format: FormatSchema,
    }).refine(refineDateParams, { message: dateRefinementMessage }),
    execute: async (params) => {
      try {
        const result = await noaaService.getMeteorologicalData(params);
        return JSON.stringify(result);
      } catch (error) {
        if (error instanceof Error) {
          throw new Error(`Failed to get meteorological data: ${error.message}`);
        }
        throw new Error('Failed to get meteorological data');
      }
    }
  });
} 
```

--------------------------------------------------------------------------------
/src/services/dpapi-service.ts:
--------------------------------------------------------------------------------

```typescript
import axios from 'axios';

// Base URL for the NOAA Derived Product API
const DPAPI_BASE_URL = 'https://api.tidesandcurrents.noaa.gov/dpapi/prod';

/**
 * Service for interacting with NOAA Derived Product API (DPAPI)
 */
export class DpapiService {

  /**
   * Build parameters for the API request
   * @param params Parameters for the request
   * @returns URL-encoded parameters string
   */
  private buildParams(params: Record<string, any>): string {
    // Remove undefined and null values
    const filteredParams = Object.entries(params)
      .filter(([_, value]) => value !== undefined && value !== null)
      .reduce((acc, [key, value]) => {
        acc[key] = value;
        return acc;
      }, {} as Record<string, any>);

    // Convert to URL parameters
    return new URLSearchParams(filteredParams as Record<string, string>).toString();
  }

  /**
   * Make a request to the DPAPI
   * @param endpoint Endpoint path
   * @param params Parameters for the request
   * @returns Response data
   */
  async fetchDpapi(endpoint: string, params: Record<string, any> = {}): Promise<any> {
    try {
      const queryParams = this.buildParams(params);
      const url = `${DPAPI_BASE_URL}${endpoint}${queryParams ? '?' + queryParams : ''}`;
      
      const response = await axios.get(url);
      return response.data;
    } catch (error) {
      if (axios.isAxiosError(error) && error.response) {
        throw new Error(`NOAA DPAPI Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`);
      }
      throw error;
    }
  }

  /**
   * Get sea level trends for a station
   * @param params Parameters including station ID and affiliation
   * @returns Sea level trend data
   */
  async getSeaLevelTrends(params: Record<string, any>): Promise<any> {
    const { station, affil = 'Global', format = 'json', ...rest } = params;
    
    return this.fetchDpapi('/sltrends', {
      station,
      affil,
      format,
      ...rest
    });
  }

  /**
   * Get extreme water levels for a station
   * @param params Parameters including station ID and units
   * @returns Extreme water level data
   */
  async getExtremeWaterLevels(params: Record<string, any>): Promise<any> {
    const { station, units = 'english', format = 'json', ...rest } = params;
    
    return this.fetchDpapi('/ewl', {
      station,
      units,
      format,
      ...rest
    });
  }

  /**
   * Get high tide flooding daily count data
   * @param params Parameters including station ID, date range, and thresholds
   * @returns Daily flood count data
   */
  async getHighTideFloodingDaily(params: Record<string, any>): Promise<any> {
    const { station, format = 'json', ...rest } = params;
    
    return this.fetchDpapi('/htf/daily', {
      station,
      format,
      ...rest
    });
  }

  /**
   * Get high tide flooding monthly count data
   * @param params Parameters including station ID, date range, and thresholds
   * @returns Monthly flood count data
   */
  async getHighTideFloodingMonthly(params: Record<string, any>): Promise<any> {
    const { station, format = 'json', ...rest } = params;
    
    return this.fetchDpapi('/htf/monthly', {
      station,
      format,
      ...rest
    });
  }

  /**
   * Get high tide flooding seasonal count data
   * @param params Parameters including station ID, seasons, and thresholds
   * @returns Seasonal flood count data
   */
  async getHighTideFloodingSeasonal(params: Record<string, any>): Promise<any> {
    const { station, format = 'json', ...rest } = params;
    
    return this.fetchDpapi('/htf/seasonal', {
      station,
      format,
      ...rest
    });
  }

  /**
   * Get high tide flooding annual count data
   * @param params Parameters including station ID, year range, and thresholds
   * @returns Annual flood count data
   */
  async getHighTideFloodingAnnual(params: Record<string, any>): Promise<any> {
    const { station, format = 'json', ...rest } = params;
    
    return this.fetchDpapi('/htf/annual', {
      station,
      format,
      ...rest
    });
  }

  /**
   * Get high tide flooding decadal projections
   * @param params Parameters including station ID, scenario, and decade
   * @returns Decadal projection data
   */
  async getHighTideFloodingProjections(params: Record<string, any>): Promise<any> {
    const { station, scenario = 'all', format = 'json', ...rest } = params;
    
    return this.fetchDpapi('/htf/projections', {
      station,
      scenario,
      format,
      ...rest
    });
  }

  /**
   * Get high tide flooding daily likelihoods
   * @param params Parameters including station ID and date
   * @returns Daily likelihood data
   */
  async getHighTideFloodingLikelihoods(params: Record<string, any>): Promise<any> {
    const { station, format = 'json', ...rest } = params;
    
    return this.fetchDpapi('/htf/likelihoods', {
      station,
      format,
      ...rest
    });
  }

  /**
   * Get top ten water levels for a station
   * @param params Parameters including station ID and analysis type
   * @returns Top ten water level data
   */
  async getTopTenWaterLevels(params: Record<string, any>): Promise<any> {
    const { station, format = 'json', ...rest } = params;
    
    return this.fetchDpapi('/topten', {
      station,
      format,
      ...rest
    });
  }
}
```

--------------------------------------------------------------------------------
/src/services/moon-phase-service.ts:
--------------------------------------------------------------------------------

```typescript
import SunCalc from 'suncalc';
import { MoonPhaseParams, MoonPhasesRangeParams, NextMoonPhaseParams } from '../interfaces/moon.js';
import { MoonPhaseName, MoonPhaseInfo } from '../types/moon.js';

/**
 * Service for moon phase calculations
 */
export class MoonPhaseService {
  /**
   * Get the moon phase for a specific date
   * @param params Parameters for the request
   * @returns Moon phase information
   */
  getMoonPhase(params: MoonPhaseParams): MoonPhaseInfo {
    const date = params.date ? new Date(params.date) : new Date();
    
    // Get moon illumination data
    const illuminationData = SunCalc.getMoonIllumination(date);
    
    // Get moon position data (requires location)
    const latitude = params.latitude ?? 0;
    const longitude = params.longitude ?? 0;
    const positionData = SunCalc.getMoonPosition(date, latitude, longitude);
    
    // Calculate moon phase name
    const phaseName = this.getMoonPhaseName(illuminationData.phase);
    
    // Calculate if the moon is waxing (increasing illumination)
    const isWaxing = illuminationData.phase < 0.5;
    
    // Calculate approximate moon age (0-29.53 days)
    const lunarMonth = 29.53; // days
    const age = illuminationData.phase * lunarMonth;
    
    // Calculate apparent diameter (in degrees)
    const diameter = 0.5181 * (384400 / positionData.distance);
    
    return {
      date: date.toISOString().split('T')[0],
      phase: illuminationData.phase,
      phaseName,
      illumination: illuminationData.fraction,
      age,
      distance: positionData.distance,
      diameter,
      isWaxing
    };
  }

  /**
   * Get moon phases for a date range
   * @param params Parameters for the request
   * @returns Array of moon phase information
   */
  getMoonPhasesRange(params: MoonPhasesRangeParams): MoonPhaseInfo[] {
    const startDate = new Date(params.start_date);
    const endDate = new Date(params.end_date);
    
    if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
      throw new Error('Invalid date format. Please use YYYY-MM-DD format.');
    }
    
    if (startDate > endDate) {
      throw new Error('Start date must be before end date.');
    }
    
    const result: MoonPhaseInfo[] = [];
    const currentDate = new Date(startDate);
    
    while (currentDate <= endDate) {
      result.push(this.getMoonPhase({
        date: currentDate.toISOString().split('T')[0],
        latitude: params.latitude,
        longitude: params.longitude
      }));
      
      // Move to next day
      currentDate.setDate(currentDate.getDate() + 1);
    }
    
    return result;
  }

  /**
   * Get the next occurrence(s) of a specific moon phase
   * @param params Parameters for the request
   * @returns Array of dates for the next occurrences of the specified phase
   */
  getNextMoonPhase(params: NextMoonPhaseParams): { date: string, phase: string }[] {
    const startDate = params.date ? new Date(params.date) : new Date();
    const count = params.count || 1;
    const targetPhase = params.phase;
    
    // Map phase names to their approximate values
    const phaseValues: Record<string, number> = {
      [MoonPhaseName.NEW_MOON]: 0,
      [MoonPhaseName.FIRST_QUARTER]: 0.25,
      [MoonPhaseName.FULL_MOON]: 0.5,
      [MoonPhaseName.LAST_QUARTER]: 0.75
    };
    
    const targetPhaseValue = phaseValues[targetPhase];
    const results: { date: string, phase: string }[] = [];
    let currentDate = new Date(startDate);
    
    // Find the next occurrences
    while (results.length < count) {
      // Check every day (could be optimized with better algorithms)
      const illuminationData = SunCalc.getMoonIllumination(currentDate);
      const prevDate = new Date(currentDate);
      prevDate.setDate(prevDate.getDate() - 1);
      const prevIlluminationData = SunCalc.getMoonIllumination(prevDate);
      
      // Check if we've passed the target phase between yesterday and today
      const prevDiff = Math.abs(prevIlluminationData.phase - targetPhaseValue);
      const currentDiff = Math.abs(illuminationData.phase - targetPhaseValue);
      
      // If we're getting closer to the target phase and then further away, we've passed it
      // Or if we're very close to the target phase (within 0.01)
      if ((prevDiff > currentDiff && currentDiff < 0.01) || currentDiff < 0.005) {
        results.push({
          date: currentDate.toISOString().split('T')[0],
          phase: targetPhase
        });
      }
      
      // Move to next day
      currentDate.setDate(currentDate.getDate() + 1);
      
      // Safety check to prevent infinite loops
      if (results.length === 0 && currentDate.getTime() - startDate.getTime() > 366 * 24 * 60 * 60 * 1000) {
        throw new Error('Could not find the specified moon phase within a year.');
      }
    }
    
    return results;
  }

  /**
   * Get the moon phase name based on the phase value (0-1)
   * @param phase Phase value (0-1)
   * @returns Moon phase name
   */
  private getMoonPhaseName(phase: number): MoonPhaseName {
    // Normalize phase to 0-1 range
    const normalizedPhase = phase < 0 ? phase + 1 : phase > 1 ? phase - 1 : phase;
    
    // Determine moon phase based on the value
    if (normalizedPhase < 0.0625 || normalizedPhase >= 0.9375) {
      return MoonPhaseName.NEW_MOON;
    } else if (normalizedPhase < 0.1875) {
      return MoonPhaseName.WAXING_CRESCENT;
    } else if (normalizedPhase < 0.3125) {
      return MoonPhaseName.FIRST_QUARTER;
    } else if (normalizedPhase < 0.4375) {
      return MoonPhaseName.WAXING_GIBBOUS;
    } else if (normalizedPhase < 0.5625) {
      return MoonPhaseName.FULL_MOON;
    } else if (normalizedPhase < 0.6875) {
      return MoonPhaseName.WANING_GIBBOUS;
    } else if (normalizedPhase < 0.8125) {
      return MoonPhaseName.LAST_QUARTER;
    } else {
      return MoonPhaseName.WANING_CRESCENT;
    }
  }
} 
```

--------------------------------------------------------------------------------
/src/interfaces/noaa.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';

// Common parameter schemas
const StationSchema = z.string().min(1).describe('Station ID');
const DateSchema = z.string().optional().describe('Date to retrieve data for ("today", "latest", "recent", or specific date)');
const BeginDateSchema = z.string().optional().describe('Start date (YYYYMMDD or MM/DD/YYYY)');
const EndDateSchema = z.string().optional().describe('End date (YYYYMMDD or MM/DD/YYYY)');
const RangeSchema = z.number().optional().describe('Number of hours to retrieve data for');
const DatumSchema = z.string().optional().describe('Datum to use (MLLW, MSL, etc.)');
const UnitsSchema = z.enum(['english', 'metric']).optional().describe('Units to use ("english" or "metric")');
const TimeZoneSchema = z.enum(['gmt', 'lst', 'lst_ldt']).optional().describe('Time zone (gmt, lst, lst_ldt)');
const FormatSchema = z.enum(['json', 'xml', 'csv']).optional().describe('Output format (json, xml, csv)');
const BinSchema = z.number().optional().describe('Bin number');
const IntervalSchema = z.string().optional().describe('Interval (hilo, hl, h, or a number for minutes)');

// Water Level Schema
export const GetWaterLevelsSchema = z.object({
  station: StationSchema,
  date: DateSchema,
  begin_date: BeginDateSchema,
  end_date: EndDateSchema,
  range: RangeSchema,
  datum: DatumSchema,
  units: UnitsSchema,
  time_zone: TimeZoneSchema,
  format: FormatSchema,
}).refine(
  data => (data.date || (data.begin_date && data.end_date) || (data.begin_date && data.range) || (data.end_date && data.range) || data.range),
  { message: "You must provide either 'date', 'begin_date' and 'end_date', 'begin_date' and 'range', 'end_date' and 'range', or just 'range'" }
);

// Tide Predictions Schema
export const GetTidePredictionsSchema = z.object({
  station: StationSchema,
  begin_date: BeginDateSchema,
  end_date: EndDateSchema,
  date: DateSchema,
  range: RangeSchema,
  datum: DatumSchema,
  units: UnitsSchema,
  time_zone: TimeZoneSchema,
  interval: IntervalSchema,
  format: FormatSchema,
}).refine(
  data => (data.date || (data.begin_date && data.end_date) || (data.begin_date && data.range) || (data.end_date && data.range) || data.range),
  { message: "You must provide either 'date', 'begin_date' and 'end_date', 'begin_date' and 'range', 'end_date' and 'range', or just 'range'" }
);

// Currents Schema
export const GetCurrentsSchema = z.object({
  station: StationSchema,
  date: DateSchema,
  begin_date: BeginDateSchema,
  end_date: EndDateSchema,
  range: RangeSchema,
  bin: BinSchema,
  units: UnitsSchema,
  time_zone: TimeZoneSchema,
  format: FormatSchema,
}).refine(
  data => (data.date || (data.begin_date && data.end_date) || (data.begin_date && data.range) || (data.end_date && data.range) || data.range),
  { message: "You must provide either 'date', 'begin_date' and 'end_date', 'begin_date' and 'range', 'end_date' and 'range', or just 'range'" }
);

// Current Predictions Schema
export const GetCurrentPredictionsSchema = z.object({
  station: StationSchema,
  date: DateSchema,
  begin_date: BeginDateSchema,
  end_date: EndDateSchema,
  range: RangeSchema,
  bin: BinSchema,
  interval: z.string().optional().describe('Interval (MAX_SLACK or a number for minutes)'),
  vel_type: z.enum(['speed_dir', 'default']).optional().describe('Velocity type (speed_dir or default)'),
  units: UnitsSchema,
  time_zone: TimeZoneSchema,
  format: FormatSchema,
}).refine(
  data => (data.date || (data.begin_date && data.end_date) || (data.begin_date && data.range) || (data.end_date && data.range) || data.range),
  { message: "You must provide either 'date', 'begin_date' and 'end_date', 'begin_date' and 'range', 'end_date' and 'range', or just 'range'" }
);

// Meteorological Data Schema
export const GetMeteorologicalDataSchema = z.object({
  station: StationSchema,
  product: z.string().min(1).describe('Product (air_temperature, wind, etc.)'),
  date: DateSchema,
  begin_date: BeginDateSchema,
  end_date: EndDateSchema,
  range: RangeSchema,
  units: UnitsSchema,
  time_zone: TimeZoneSchema,
  format: FormatSchema,
}).refine(
  data => (data.date || (data.begin_date && data.end_date) || (data.begin_date && data.range) || (data.end_date && data.range) || data.range),
  { message: "You must provide either 'date', 'begin_date' and 'end_date', 'begin_date' and 'range', 'end_date' and 'range', or just 'range'" }
);

// Station List Schema
export const GetStationsSchema = z.object({
  type: z.string().optional().describe('Station type (waterlevels, currents, etc.)'),
  units: UnitsSchema,
  format: z.enum(['json', 'xml']).optional().describe('Output format (json, xml)'),
  name: z.string().optional().describe('Filter stations by name (partial match)'),
  lat_min: z.number().optional().describe('Minimum latitude boundary'),
  lat_max: z.number().optional().describe('Maximum latitude boundary'),
  lon_min: z.number().optional().describe('Minimum longitude boundary'),
  lon_max: z.number().optional().describe('Maximum longitude boundary'),
  state: z.string().optional().describe('Filter stations by state code (e.g., CA, NY)'),
  limit: z.number().optional().describe('Maximum number of stations to return'),
  offset: z.number().optional().describe('Number of stations to skip for pagination'),
  sort_by: z.enum(['name', 'id', 'state']).optional().describe('Field to sort results by'),
  sort_order: z.enum(['asc', 'desc']).optional().describe('Sort order (ascending or descending)'),
});

// Station Details Schema
export const GetStationDetailsSchema = z.object({
  station: StationSchema,
  units: UnitsSchema,
  format: z.enum(['json', 'xml']).optional().describe('Output format (json, xml)'),
});

// Define exported types
export type GetWaterLevelsParams = z.infer<typeof GetWaterLevelsSchema>;
export type GetTidePredictionsParams = z.infer<typeof GetTidePredictionsSchema>;
export type GetCurrentsParams = z.infer<typeof GetCurrentsSchema>;
export type GetCurrentPredictionsParams = z.infer<typeof GetCurrentPredictionsSchema>;
export type GetMeteorologicalDataParams = z.infer<typeof GetMeteorologicalDataSchema>;
export type GetStationsParams = z.infer<typeof GetStationsSchema>;
export type GetStationDetailsParams = z.infer<typeof GetStationDetailsSchema>; 
```

--------------------------------------------------------------------------------
/src/tools/derived-product-tools.ts:
--------------------------------------------------------------------------------

```typescript
import { FastMCP } from 'fastmcp';
import { DpapiService } from '../services/dpapi-service.js';
import { 
  SeaLevelTrendsSchema,
  ExtremeWaterLevelsSchema,
  HighTideFloodingDailySchema,
  HighTideFloodingMonthlySchema,
  HighTideFloodingSeasonalSchema,
  HighTideFloodingAnnualSchema,
  HighTideFloodingProjectionsSchema,
  HighTideFloodingLikelihoodsSchema,
  TopTenWaterLevelsSchema
} from '../schemas/dpapi.js';

/**
 * Register derived product tools with the MCP server
 */
export function registerDerivedProductTools(server: FastMCP, dpapiService: DpapiService) {
  // Sea Level Trends tool
  server.addTool({
    name: 'get_sea_level_trends',
    description: 'Get sea level trends and error margins for a station',
    parameters: SeaLevelTrendsSchema,
    execute: async (params) => {
      try {
        const result = await dpapiService.getSeaLevelTrends(params);
        return JSON.stringify(result);
      } catch (error) {
        if (error instanceof Error) {
          throw new Error(`Failed to get sea level trends: ${error.message}`);
        }
        throw new Error('Failed to get sea level trends');
      }
    }
  });

  // Extreme Water Levels tool
  server.addTool({
    name: 'get_extreme_water_levels',
    description: 'Get extreme water levels and exceedance probabilities for a station',
    parameters: ExtremeWaterLevelsSchema,
    execute: async (params) => {
      try {
        const result = await dpapiService.getExtremeWaterLevels(params);
        return JSON.stringify(result);
      } catch (error) {
        if (error instanceof Error) {
          throw new Error(`Failed to get extreme water levels: ${error.message}`);
        }
        throw new Error('Failed to get extreme water levels');
      }
    }
  });

  // High Tide Flooding Daily tool
  server.addTool({
    name: 'get_high_tide_flooding_daily',
    description: 'Get high tide flooding daily count data for a station',
    parameters: HighTideFloodingDailySchema,
    execute: async (params) => {
      try {
        const result = await dpapiService.getHighTideFloodingDaily(params);
        return JSON.stringify(result);
      } catch (error) {
        if (error instanceof Error) {
          throw new Error(`Failed to get high tide flooding daily data: ${error.message}`);
        }
        throw new Error('Failed to get high tide flooding daily data');
      }
    }
  });

  // High Tide Flooding Monthly tool
  server.addTool({
    name: 'get_high_tide_flooding_monthly',
    description: 'Get high tide flooding monthly count data for a station',
    parameters: HighTideFloodingMonthlySchema,
    execute: async (params) => {
      try {
        const result = await dpapiService.getHighTideFloodingMonthly(params);
        return JSON.stringify(result);
      } catch (error) {
        if (error instanceof Error) {
          throw new Error(`Failed to get high tide flooding monthly data: ${error.message}`);
        }
        throw new Error('Failed to get high tide flooding monthly data');
      }
    }
  });

  // High Tide Flooding Seasonal tool
  server.addTool({
    name: 'get_high_tide_flooding_seasonal',
    description: 'Get high tide flooding seasonal count data for a station',
    parameters: HighTideFloodingSeasonalSchema,
    execute: async (params) => {
      try {
        const result = await dpapiService.getHighTideFloodingSeasonal(params);
        return JSON.stringify(result);
      } catch (error) {
        if (error instanceof Error) {
          throw new Error(`Failed to get high tide flooding seasonal data: ${error.message}`);
        }
        throw new Error('Failed to get high tide flooding seasonal data');
      }
    }
  });

  // High Tide Flooding Annual tool
  server.addTool({
    name: 'get_high_tide_flooding_annual',
    description: 'Get high tide flooding annual count data for a station',
    parameters: HighTideFloodingAnnualSchema,
    execute: async (params) => {
      try {
        const result = await dpapiService.getHighTideFloodingAnnual(params);
        return JSON.stringify(result);
      } catch (error) {
        if (error instanceof Error) {
          throw new Error(`Failed to get high tide flooding annual data: ${error.message}`);
        }
        throw new Error('Failed to get high tide flooding annual data');
      }
    }
  });

  // High Tide Flooding Projections tool
  server.addTool({
    name: 'get_high_tide_flooding_projections',
    description: 'Get high tide flooding decadal projections for sea level rise scenarios',
    parameters: HighTideFloodingProjectionsSchema,
    execute: async (params) => {
      try {
        const result = await dpapiService.getHighTideFloodingProjections(params);
        return JSON.stringify(result);
      } catch (error) {
        if (error instanceof Error) {
          throw new Error(`Failed to get high tide flooding projections: ${error.message}`);
        }
        throw new Error('Failed to get high tide flooding projections');
      }
    }
  });

  // High Tide Flooding Likelihoods tool
  server.addTool({
    name: 'get_high_tide_flooding_likelihoods',
    description: 'Get high tide flooding daily likelihoods for a station',
    parameters: HighTideFloodingLikelihoodsSchema,
    execute: async (params) => {
      try {
        const result = await dpapiService.getHighTideFloodingLikelihoods(params);
        return JSON.stringify(result);
      } catch (error) {
        if (error instanceof Error) {
          throw new Error(`Failed to get high tide flooding likelihoods: ${error.message}`);
        }
        throw new Error('Failed to get high tide flooding likelihoods');
      }
    }
  });

  // Top Ten Water Levels tool
  server.addTool({
    name: 'get_top_ten_water_levels',
    description: 'Get top ten highest or lowest water levels for a station',
    parameters: TopTenWaterLevelsSchema,
    execute: async (params) => {
      try {
        const result = await dpapiService.getTopTenWaterLevels(params);
        return JSON.stringify(result);
      } catch (error) {
        if (error instanceof Error) {
          throw new Error(`Failed to get top ten water levels: ${error.message}`);
        }
        throw new Error('Failed to get top ten water levels');
      }
    }
  });
}
```

--------------------------------------------------------------------------------
/src/services/sun-service.ts:
--------------------------------------------------------------------------------

```typescript
import SunCalc from 'suncalc';
import { SunTimesParams, SunTimesRangeParams, SunPositionParams, NextSunEventParams } from '../interfaces/sun.js';
import { SunTimesInfo, SunPositionInfo, SunEventType } from '../types/sun.js';

/**
 * Service for sun calculations
 */
export class SunService {
  /**
   * Get sun times for a specific date and location
   * @param params Parameters for the request
   * @returns Sun times information
   */
  getSunTimes(params: SunTimesParams): SunTimesInfo {
    const date = params.date ? new Date(params.date) : new Date();
    const { latitude, longitude } = params;
    
    // Get sun times data
    const sunTimes = SunCalc.getTimes(date, latitude, longitude);
    
    // Format times or return null if not available
    const formatTime = (time: Date | null): string | null => {
      if (!time || isNaN(time.getTime())) return null;
      
      if (params.timezone) {
        try {
          return time.toLocaleTimeString('en-US', { timeZone: params.timezone });
        } catch (error) {
          // If timezone is invalid, fall back to ISO string
          console.warn(`Invalid timezone: ${params.timezone}. Using UTC.`);
        }
      }
      
      return time.toISOString();
    };
    
    // Calculate day length in minutes
    const sunrise = sunTimes.sunrise;
    const sunset = sunTimes.sunset;
    let dayLength = 0;
    
    if (sunrise && sunset && !isNaN(sunrise.getTime()) && !isNaN(sunset.getTime())) {
      dayLength = (sunset.getTime() - sunrise.getTime()) / (60 * 1000);
    }
    
    return {
      date: date.toISOString().split('T')[0],
      sunrise: formatTime(sunTimes.sunrise),
      sunset: formatTime(sunTimes.sunset),
      solarNoon: formatTime(sunTimes.solarNoon),
      dawn: formatTime(sunTimes.dawn),
      dusk: formatTime(sunTimes.dusk),
      nightStart: formatTime(sunTimes.night),
      nightEnd: formatTime(sunTimes.nightEnd),
      goldenHourStart: formatTime(sunTimes.goldenHour),
      goldenHourEnd: formatTime(sunTimes.goldenHourEnd),
      nauticalDawn: formatTime(sunTimes.nauticalDawn),
      nauticalDusk: formatTime(sunTimes.nauticalDusk),
      astronomicalDawn: formatTime(sunTimes.astronomicalDawn),
      astronomicalDusk: formatTime(sunTimes.astronomicalDusk),
      dayLength
    };
  }

  /**
   * Get sun times for a date range
   * @param params Parameters for the request
   * @returns Array of sun times information
   */
  getSunTimesRange(params: SunTimesRangeParams): SunTimesInfo[] {
    const startDate = new Date(params.start_date);
    const endDate = new Date(params.end_date);
    
    if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
      throw new Error('Invalid date format. Please use YYYY-MM-DD format.');
    }
    
    if (startDate > endDate) {
      throw new Error('Start date must be before end date.');
    }
    
    const result: SunTimesInfo[] = [];
    const currentDate = new Date(startDate);
    
    while (currentDate <= endDate) {
      result.push(this.getSunTimes({
        date: currentDate.toISOString().split('T')[0],
        latitude: params.latitude,
        longitude: params.longitude,
        timezone: params.timezone
      }));
      
      // Move to next day
      currentDate.setDate(currentDate.getDate() + 1);
    }
    
    return result;
  }

  /**
   * Get sun position for a specific date, time, and location
   * @param params Parameters for the request
   * @returns Sun position information
   */
  getSunPosition(params: SunPositionParams): SunPositionInfo {
    const date = params.date ? new Date(params.date) : new Date();
    const time = params.time;
    const { latitude, longitude } = params;
    
    // Set the time if provided
    if (time) {
      const [hours, minutes, seconds] = time.split(':').map(Number);
      
      if (!isNaN(hours) && !isNaN(minutes) && (!seconds || !isNaN(seconds))) {
        date.setHours(hours, minutes, seconds || 0, 0);
      } else {
        throw new Error('Invalid time format. Please use HH:MM:SS format.');
      }
    }
    
    // Get sun position data
    const position = SunCalc.getPosition(date, latitude, longitude);
    
    // Calculate right ascension and declination (approximate values)
    // Note: These are approximate calculations and may not be precise
    const equatorialCoords = this.calculateEquatorialCoordinates(date, position.azimuth, position.altitude, latitude, longitude);
    
    return {
      date: date.toISOString().split('T')[0],
      time: date.toISOString().split('T')[1].split('.')[0],
      azimuth: position.azimuth * (180 / Math.PI),
      altitude: position.altitude * (180 / Math.PI),
      declination: equatorialCoords.declination,
      rightAscension: equatorialCoords.rightAscension
    };
  }

  /**
   * Get the next occurrence(s) of a specific sun event
   * @param params Parameters for the request
   * @returns Array of dates for the next occurrences of the specified event
   */
  getNextSunEvent(params: NextSunEventParams): { date: string, time: string, event: string }[] {
    const startDate = params.date ? new Date(params.date) : new Date();
    const count = params.count !== undefined ? params.count : 1;
    const { latitude, longitude } = params;
    const timezone = params.timezone !== undefined ? params.timezone : 'UTC';
    
    const results: { date: string, time: string, event: string }[] = [];
    let currentDate = new Date(startDate);
    
    // Find the next occurrences
    while (results.length < count) {
      const sunTimes = SunCalc.getTimes(currentDate, latitude, longitude);
      const eventTime = sunTimes[params.event as keyof typeof sunTimes];
      
      if (eventTime && !isNaN(eventTime.getTime()) && eventTime > startDate) {
        let formattedTime: string;
        
        try {
          formattedTime = eventTime.toLocaleTimeString('en-US', { timeZone: timezone });
        } catch (error) {
          // If timezone is invalid, fall back to ISO string
          console.warn(`Invalid timezone: ${timezone}. Using UTC.`);
          formattedTime = eventTime.toISOString().split('T')[1].split('.')[0];
        }
        
        results.push({
          date: eventTime.toISOString().split('T')[0],
          time: formattedTime,
          event: params.event as string
        });
        
        // Move to next day to find the next occurrence
        currentDate.setDate(currentDate.getDate() + 1);
      } else {
        // Event not found for this day, try next day
        currentDate.setDate(currentDate.getDate() + 1);
      }
      
      // Safety check to prevent infinite loops
      if (results.length === 0 && currentDate.getTime() - startDate.getTime() > 366 * 24 * 60 * 60 * 1000) {
        throw new Error('Could not find the specified sun event within a year.');
      }
    }
    
    return results;
  }

  /**
   * Calculate approximate equatorial coordinates (right ascension and declination)
   * from horizontal coordinates (azimuth and altitude)
   * Note: This is a simplified calculation and may not be precise
   * @param date Date of observation
   * @param azimuth Azimuth in radians
   * @param altitude Altitude in radians
   * @param latitude Observer's latitude
   * @param longitude Observer's longitude
   * @returns Approximate equatorial coordinates
   */
  private calculateEquatorialCoordinates(date: Date, azimuth: number, altitude: number, latitude: number, longitude: number): { rightAscension: number, declination: number } {
    // Convert degrees to radians
    const lat = latitude * (Math.PI / 180);
    
    // Calculate hour angle and declination
    const sinDec = Math.sin(altitude) * Math.sin(lat) + Math.cos(altitude) * Math.cos(lat) * Math.cos(azimuth);
    const declination = Math.asin(sinDec) * (180 / Math.PI);
    
    const cosH = (Math.sin(altitude) - Math.sin(lat) * sinDec) / (Math.cos(lat) * Math.cos(declination * (Math.PI / 180)));
    const hourAngle = Math.acos(Math.max(-1, Math.min(1, cosH)));
    
    // Adjust hour angle based on azimuth
    const adjustedHourAngle = (azimuth > 0 && azimuth < Math.PI) ? (2 * Math.PI - hourAngle) : hourAngle;
    
    // Calculate right ascension
    const localSiderealTime = this.calculateLocalSiderealTime(date, longitude);
    let rightAscension = (localSiderealTime - adjustedHourAngle) * (12 / Math.PI);
    
    // Normalize right ascension to 0-24 hours
    rightAscension = rightAscension % 24;
    if (rightAscension < 0) rightAscension += 24;
    
    return { rightAscension, declination };
  }

  /**
   * Calculate approximate local sidereal time
   * @param date Date of observation
   * @param longitude Observer's longitude
   * @returns Local sidereal time in radians
   */
  private calculateLocalSiderealTime(date: Date, longitude: number): number {
    // Calculate days since J2000.0
    const jd = this.calculateJulianDay(date);
    const d = jd - 2451545.0;
    
    // Calculate Greenwich Mean Sidereal Time
    const gmst = (18.697374558 + 24.06570982441908 * d) % 24;
    
    // Convert longitude to hours and calculate local sidereal time
    const longitudeHours = longitude / 15;
    let lst = gmst + longitudeHours;
    
    // Normalize to 0-24 hours
    lst = lst % 24;
    if (lst < 0) lst += 24;
    
    // Convert to radians
    return lst * (Math.PI / 12);
  }

  /**
   * Calculate Julian day from date
   * @param date Date to convert
   * @returns Julian day
   */
  private calculateJulianDay(date: Date): number {
    const y = date.getFullYear();
    const m = date.getMonth() + 1;
    const d = date.getDate();
    
    // Calculate Julian day
    const jd = 367 * y - Math.floor(7 * (y + Math.floor((m + 9) / 12)) / 4) -
      Math.floor(3 * (Math.floor((y + (m - 9) / 7) / 100) + 1) / 4) +
      Math.floor(275 * m / 9) + d + 1721028.5;
    
    // Add time of day
    const hours = date.getUTCHours();
    const minutes = date.getUTCMinutes();
    const seconds = date.getUTCSeconds();
    const milliseconds = date.getUTCMilliseconds();
    
    return jd + (hours + minutes / 60 + seconds / 3600 + milliseconds / 3600000) / 24;
  }
} 
```