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

```
├── .gitignore
├── Dockerfile
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── smithery.yaml
├── src
│   ├── config.ts
│   ├── constants.ts
│   ├── formatters.ts
│   ├── handlers
│   │   ├── findParks.ts
│   │   ├── getAlerts.ts
│   │   ├── getCampgrounds.ts
│   │   ├── getEvents.ts
│   │   ├── getParkDetails.ts
│   │   └── getVisitorCenters.ts
│   ├── index.ts
│   ├── schemas.ts
│   ├── server.ts
│   └── utils
│       └── npsApiClient.ts
└── tsconfig.json
```

# Files

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

```
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# vuepress v2.x temp and cache directory
.temp
.cache

# vitepress build output
**/.vitepress/dist

# vitepress cache directory
**/.vitepress/cache

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

# dev logs
user_stories.md

# Build Directory
build/
```

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

```markdown
# National Parks MCP Server
[![smithery badge](https://smithery.ai/badge/@KyrieTangSheng/mcp-server-nationalparks)](https://smithery.ai/server/@KyrieTangSheng/mcp-server-nationalparks)
[![Verified on MseeP](https://mseep.ai/badge.svg)](https://mseep.ai/app/8c07fa61-fd4b-4662-8356-908408e45e44)

MCP Server for the National Park Service (NPS) API, providing real-time information about U.S. National Parks, including park details, alerts, and activities.

## Tools

1. `findParks`
   - Search for national parks based on various criteria
   - Inputs:
     - `stateCode` (optional string): Filter parks by state code (e.g., "CA" for California). Multiple states can be comma-separated (e.g., "CA,OR,WA")
     - `q` (optional string): Search term to filter parks by name or description
     - `limit` (optional number): Maximum number of parks to return (default: 10, max: 50)
     - `start` (optional number): Start position for results (useful for pagination)
     - `activities` (optional string): Filter by available activities (e.g., "hiking,camping")
   - Returns: Matching parks with detailed information

2. `getParkDetails`
   - Get comprehensive information about a specific national park
   - Inputs:
     - `parkCode` (string): The park code of the national park (e.g., "yose" for Yosemite, "grca" for Grand Canyon)
   - Returns: Detailed park information including descriptions, hours, fees, contacts, and activities

3. `getAlerts`
   - Get current alerts for national parks including closures, hazards, and important information
   - Inputs:
     - `parkCode` (optional string): Filter alerts by park code (e.g., "yose" for Yosemite). Multiple parks can be comma-separated (e.g., "yose,grca")
     - `limit` (optional number): Maximum number of alerts to return (default: 10, max: 50)
     - `start` (optional number): Start position for results (useful for pagination)
     - `q` (optional string): Search term to filter alerts by title or description
   - Returns: Current alerts organized by park

4. `getVisitorCenters`
   - Get information about visitor centers and their operating hours
   - Inputs:
     - `parkCode` (optional string): Filter visitor centers by park code (e.g., "yose" for Yosemite). Multiple parks can be comma-separated (e.g., "yose,grca")
     - `limit` (optional number): Maximum number of visitor centers to return (default: 10, max: 50)
     - `start` (optional number): Start position for results (useful for pagination)
     - `q` (optional string): Search term to filter visitor centers by name or description
   - Returns: Visitor center information including location, hours, and contact details

5. `getCampgrounds`
   - Get information about available campgrounds and their amenities
   - Inputs:
     - `parkCode` (optional string): Filter campgrounds by park code (e.g., "yose" for Yosemite). Multiple parks can be comma-separated (e.g., "yose,grca")
     - `limit` (optional number): Maximum number of campgrounds to return (default: 10, max: 50)
     - `start` (optional number): Start position for results (useful for pagination)
     - `q` (optional string): Search term to filter campgrounds by name or description
   - Returns: Campground information including amenities, fees, and reservation details

6. `getEvents`
   - Find upcoming events at parks
   - Inputs:
     - `parkCode` (optional string): Filter events by park code (e.g., "yose" for Yosemite). Multiple parks can be comma-separated (e.g., "yose,grca")
     - `limit` (optional number): Maximum number of events to return (default: 10, max: 50)
     - `start` (optional number): Start position for results (useful for pagination)
     - `dateStart` (optional string): Start date for filtering events (format: YYYY-MM-DD)
     - `dateEnd` (optional string): End date for filtering events (format: YYYY-MM-DD)
     - `q` (optional string): Search term to filter events by title or description
   - Returns: Event information including dates, times, and descriptions

## Setup

### Installing via Smithery

To install mcp-server-nationalparks for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@KyrieTangSheng/mcp-server-nationalparks):

```bash
npx -y @smithery/cli install @KyrieTangSheng/mcp-server-nationalparks --client claude
```

### NPS API Key
1. Get a free API key from the [National Park Service Developer Portal](https://www.nps.gov/subjects/developer/get-started.htm)
2. Store this key securely as it will be used to authenticate requests

### Usage with Claude Desktop

To use this server with Claude Desktop, add the following to your `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "nationalparks": {
      "command": "npx",
      "args": ["-y", "mcp-server-nationalparks"],
      "env": {
        "NPS_API_KEY": "YOUR_NPS_API_KEY"
      }
    }
  }
}
```
## Example Usage

### Finding Parks in a State
```
Tell me about national parks in Colorado.
```

### Getting Details About a Specific Park
```
What's the entrance fee for Yellowstone National Park?
```

### Checking for Alerts or Closures
```
Are there any closures or alerts at Yosemite right now?
```

### Finding Visitor Centers
```
What visitor centers are available at Grand Canyon National Park?
```

### Looking for Campgrounds
```
Are there any campgrounds with electrical hookups in Zion National Park?
```

### Finding Upcoming Events
```
What events are happening at Acadia National Park next weekend?
```

### Planning a Trip Based on Activities
```
Which national parks in Utah have good hiking trails?
```

## License

This MCP server is licensed under the MIT License. See the LICENSE file for details.


## Appendix: Popular National Parks and their codes

| Park Name | Park Code |
|-----------|-----------|
| Yosemite | yose |
| Grand Canyon | grca |
| Yellowstone | yell |
| Zion | zion |
| Great Smoky Mountains | grsm |
| Acadia | acad |
| Olympic | olym |
| Rocky Mountain | romo |
| Joshua Tree | jotr |
| Sequoia & Kings Canyon | seki |

For a complete list, visit the [NPS website](https://www.nps.gov/findapark/index.htm).

```

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

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

WORKDIR /app

# Copy package files and install dependencies
COPY package*.json ./

RUN npm install --ignore-scripts

# Copy all files
COPY . .

# Build the project
RUN npm run build

CMD [ "node", "build/index.js" ]

```

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

```json
{
    "compilerOptions": {
      "target": "ES2022",
      "module": "Node16",
      "moduleResolution": "Node16",
      "outDir": "./build",
      "rootDir": "./src",
      "strict": true,
      "esModuleInterop": true,
      "skipLibCheck": true,
      "forceConsistentCasingInFileNames": true
    },
    "include": ["src/**/*"],
    "exclude": ["node_modules"]
  }
```

--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------

```typescript
// List of valid state codes for validation
export const STATE_CODES = [
  'AL', 'AK', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DE', 'FL', 'GA',
  'HI', 'ID', 'IL', 'IN', 'IA', 'KS', 'KY', 'LA', 'ME', 'MD',
  'MA', 'MI', 'MN', 'MS', 'MO', 'MT', 'NE', 'NV', 'NH', 'NJ',
  'NM', 'NY', 'NC', 'ND', 'OH', 'OK', 'OR', 'PA', 'RI', 'SC',
  'SD', 'TN', 'TX', 'UT', 'VT', 'VA', 'WA', 'WV', 'WI', 'WY',
  'DC', 'AS', 'GU', 'MP', 'PR', 'VI', 'UM'
];

// Version information
export const VERSION = '1.0.0'; 
```

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

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

startCommand:
  type: stdio
  configSchema:
    # JSON Schema defining the configuration options for the MCP.
    type: object
    required:
      - npsApiKey
    properties:
      npsApiKey:
        type: string
        description: API key for the National Park Service
  commandFunction:
    # A JS function that produces the CLI command based on the given config to start the MCP on stdio.
    |-
    (config) => ({ command: 'node', args: ['build/index.js'], env: { NPS_API_KEY: config.npsApiKey } })
  exampleConfig:
    npsApiKey: YOUR_NPS_API_KEY

```

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

```typescript
#!/usr/bin/env node
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import dotenv from 'dotenv';
import { createServer } from './server.js';

// Load environment variables
dotenv.config();

// Check for API key
if (!process.env.NPS_API_KEY) {
  console.warn('Warning: NPS_API_KEY is not set in environment variables.');
  console.warn('Get your API key at: https://www.nps.gov/subjects/developer/get-started.htm');
}

// Start the server
async function runServer() {
  const server = createServer();
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("National Parks MCP Server running on stdio");
}

runServer().catch((error) => {
  console.error("Fatal error in main():", error);
  process.exit(1);
});
```

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

```typescript
/**
 * Configuration for the NPS MCP Server
 */

import dotenv from 'dotenv';
import path from 'path';

// Load environment variables from .env file
dotenv.config({ path: path.resolve(__dirname, '../.env') });

export const config = {
  // NPS API Configuration
  npsApiKey: process.env.NPS_API_KEY || '',
  
  // Server Configuration
  serverName: 'mcp-server-nationalparks',
  serverVersion: '1.0.0',
  serverDescription: 'MCP server providing real-time data about U.S. national parks',
  
  // Logging Configuration
  logLevel: process.env.LOG_LEVEL || 'info',
};

// Validate required configuration
if (!config.npsApiKey) {
  console.warn('Warning: NPS_API_KEY is not set in environment variables. The server will not function correctly without an API key.');
  console.warn('Get your API key at: https://www.nps.gov/subjects/developer/get-started.htm');
}

export default config;
```

--------------------------------------------------------------------------------
/src/handlers/getParkDetails.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';
import { GetParkDetailsSchema } from '../schemas.js';
import { npsApiClient } from '../utils/npsApiClient.js';
import { formatParkDetails } from '../formatters.js';

export async function getParkDetailsHandler(args: z.infer<typeof GetParkDetailsSchema>) {
  const response = await npsApiClient.getParkByCode(args.parkCode);
  
  // Check if park was found
  if (!response.data || response.data.length === 0) {
    return {
      content: [{ 
        type: "text", 
        text: JSON.stringify({
          error: 'Park not found',
          message: `No park found with park code: ${args.parkCode}`
        }, null, 2)
      }]
    };
  }
  
  // Format the response for better readability by the AI
  const parkDetails = formatParkDetails(response.data[0]);
  
  return {
    content: [{ 
      type: "text", 
      text: JSON.stringify(parkDetails, null, 2)
    }]
  };
} 
```

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

```json
{
  "name": "mcp-server-nationalparks",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",
  "bin": {
    "mcp-server-nationalparks": "./build/index.js"
  },
  "scripts": {
    "build": "tsc && chmod 755 build/index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "files": [
    "build"
  ],
  "repository": {
    "type": "git",
    "url": "git+https://github.com/KyrieTangSheng/mcp-server-nationalparks.git"
  },
  "keywords": ["mcp", "claude", "national-parks", "api", "anthropic"],
  "author": "Tang Sheng",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/KyrieTangSheng/mcp-server-nationalparks/issues"
  },
  "homepage": "https://github.com/KyrieTangSheng/mcp-server-nationalparks#readme",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.7.0",
    "axios": "^1.8.4",
    "dotenv": "^16.4.7",
    "zod": "^3.24.2"
  },
  "devDependencies": {
    "@types/node": "^22.13.10",
    "ts-node": "^10.9.2",
    "typescript": "^5.8.2"
  }
}

```

--------------------------------------------------------------------------------
/src/handlers/getAlerts.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';
import { GetAlertsSchema } from '../schemas.js';
import { npsApiClient } from '../utils/npsApiClient.js';
import { formatAlertData } from '../formatters.js';

export async function getAlertsHandler(args: z.infer<typeof GetAlertsSchema>) {
  // Set default limit if not provided or if it exceeds maximum
  const limit = args.limit ? Math.min(args.limit, 50) : 10;
  
  // Format the request parameters
  const requestParams = {
    limit,
    ...args
  };
  
  const response = await npsApiClient.getAlerts(requestParams);
  
  // Format the response for better readability by the AI
  const formattedAlerts = formatAlertData(response.data);
  
  // Group alerts by park code for better organization
  const alertsByPark: { [key: string]: any[] } = {};
  formattedAlerts.forEach(alert => {
    if (!alertsByPark[alert.parkCode]) {
      alertsByPark[alert.parkCode] = [];
    }
    alertsByPark[alert.parkCode].push(alert);
  });
  
  const result = {
    total: parseInt(response.total),
    limit: parseInt(response.limit),
    start: parseInt(response.start),
    alerts: formattedAlerts,
    alertsByPark
  };
  
  return {
    content: [{ 
      type: "text", 
      text: JSON.stringify(result, null, 2)
    }]
  };
} 
```

--------------------------------------------------------------------------------
/src/handlers/getEvents.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';
import { GetEventsSchema } from '../schemas.js';
import { npsApiClient } from '../utils/npsApiClient.js';
import { formatEventData } from '../formatters.js';

export async function getEventsHandler(args: z.infer<typeof GetEventsSchema>) {
  // Set default limit if not provided or if it exceeds maximum
  const limit = args.limit ? Math.min(args.limit, 50) : 10;
  
  // Format the request parameters
  const requestParams = {
    limit,
    ...args
  };
  
  const response = await npsApiClient.getEvents(requestParams);
  
  // Format the response for better readability by the AI
  const formattedEvents = formatEventData(response.data);
  
  // Group events by park code for better organization
  const eventsByPark: { [key: string]: any[] } = {};
  formattedEvents.forEach(event => {
    if (!eventsByPark[event.parkCode]) {
      eventsByPark[event.parkCode] = [];
    }
    eventsByPark[event.parkCode].push(event);
  });
  
  const result = {
    total: parseInt(response.total),
    limit: parseInt(response.limit),
    start: parseInt(response.start),
    events: formattedEvents,
    eventsByPark: eventsByPark
  };
  
  return {
    content: [{ 
      type: "text", 
      text: JSON.stringify(result, null, 2)
    }]
  };
} 
```

--------------------------------------------------------------------------------
/src/handlers/getVisitorCenters.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';
import { GetVisitorCentersSchema } from '../schemas.js';
import { npsApiClient } from '../utils/npsApiClient.js';
import { formatVisitorCenterData } from '../formatters.js';

export async function getVisitorCentersHandler(args: z.infer<typeof GetVisitorCentersSchema>) {
  // Set default limit if not provided or if it exceeds maximum
  const limit = args.limit ? Math.min(args.limit, 50) : 10;
  
  // Format the request parameters
  const requestParams = {
    limit,
    ...args
  };
  
  const response = await npsApiClient.getVisitorCenters(requestParams);
  
  // Format the response for better readability by the AI
  const formattedCenters = formatVisitorCenterData(response.data);
  
  // Group visitor centers by park code for better organization
  const centersByPark: { [key: string]: any[] } = {};
  formattedCenters.forEach(center => {
    if (!centersByPark[center.parkCode]) {
      centersByPark[center.parkCode] = [];
    }
    centersByPark[center.parkCode].push(center);
  });
  
  const result = {
    total: parseInt(response.total),
    limit: parseInt(response.limit),
    start: parseInt(response.start),
    visitorCenters: formattedCenters,
    visitorCentersByPark: centersByPark
  };
  
  return {
    content: [{ 
      type: "text", 
      text: JSON.stringify(result, null, 2)
    }]
  };
} 
```

--------------------------------------------------------------------------------
/src/handlers/getCampgrounds.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';
import { GetCampgroundsSchema } from '../schemas.js';
import { npsApiClient } from '../utils/npsApiClient.js';
import { formatCampgroundData } from '../formatters.js';

export async function getCampgroundsHandler(args: z.infer<typeof GetCampgroundsSchema>) {
  // Set default limit if not provided or if it exceeds maximum
  const limit = args.limit ? Math.min(args.limit, 50) : 10;
  
  // Format the request parameters
  const requestParams = {
    limit,
    ...args
  };
  
  const response = await npsApiClient.getCampgrounds(requestParams);
  
  // Format the response for better readability by the AI
  const formattedCampgrounds = formatCampgroundData(response.data);
  
  // Group campgrounds by park code for better organization
  const campgroundsByPark: { [key: string]: any[] } = {};
  formattedCampgrounds.forEach(campground => {
    if (!campgroundsByPark[campground.parkCode]) {
      campgroundsByPark[campground.parkCode] = [];
    }
    campgroundsByPark[campground.parkCode].push(campground);
  });
  
  const result = {
    total: parseInt(response.total),
    limit: parseInt(response.limit),
    start: parseInt(response.start),
    campgrounds: formattedCampgrounds,
    campgroundsByPark: campgroundsByPark
  };
  
  return {
    content: [{ 
      type: "text", 
      text: JSON.stringify(result, null, 2)
    }]
  };
} 
```

--------------------------------------------------------------------------------
/src/handlers/findParks.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';
import { FindParksSchema } from '../schemas.js';
import { npsApiClient } from '../utils/npsApiClient.js';
import { formatParkData } from '../formatters.js';
import { STATE_CODES } from '../constants.js';

export async function findParksHandler(args: z.infer<typeof FindParksSchema>) {
  // Validate state codes if provided
  if (args.stateCode) {
    const providedStates = args.stateCode.split(',').map(s => s.trim().toUpperCase());
    const invalidStates = providedStates.filter(state => !STATE_CODES.includes(state));
    
    if (invalidStates.length > 0) {
      return {
        content: [{ 
          type: "text", 
          text: JSON.stringify({
            error: `Invalid state code(s): ${invalidStates.join(', ')}`,
            validStateCodes: STATE_CODES
          })
        }]
      };
    }
  }
  
  // Set default limit if not provided or if it exceeds maximum
  const limit = args.limit ? Math.min(args.limit, 50) : 10;
  
  // Format the request parameters
  const requestParams = {
    limit,
    ...args
  };
  
  const response = await npsApiClient.getParks(requestParams);
  
  // Format the response for better readability by the AI
  const formattedParks = formatParkData(response.data);
  
  const result = {
    total: parseInt(response.total),
    limit: parseInt(response.limit),
    start: parseInt(response.start),
    parks: formattedParks
  };
  
  return {
    content: [{ 
      type: "text", 
      text: JSON.stringify(result, null, 2)
    }]
  };
} 
```

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

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

// Find Parks Schema
export const FindParksSchema = z.object({
  stateCode: z.string().optional().describe('Filter parks by state code (e.g., "CA" for California, "NY" for New York). Multiple states can be comma-separated (e.g., "CA,OR,WA")'),
  q: z.string().optional().describe('Search term to filter parks by name or description'),
  limit: z.number().optional().describe('Maximum number of parks to return (default: 10, max: 50)'),
  start: z.number().optional().describe('Start position for results (useful for pagination)'),
  activities: z.string().optional().describe('Filter by available activities (e.g., "hiking,camping")')
});

// Get Park Details Schema
export const GetParkDetailsSchema = z.object({
  parkCode: z.string().describe('The park code of the national park (e.g., "yose" for Yosemite, "grca" for Grand Canyon)')
});

// Get Alerts Schema
export const GetAlertsSchema = z.object({
  parkCode: z.string().optional().describe('Filter alerts by park code (e.g., "yose" for Yosemite). Multiple parks can be comma-separated (e.g., "yose,grca").'),
  limit: z.number().optional().describe('Maximum number of alerts to return (default: 10, max: 50)'),
  start: z.number().optional().describe('Start position for results (useful for pagination)'),
  q: z.string().optional().describe('Search term to filter alerts by title or description')
});

// Get Visitor Centers Schema
export const GetVisitorCentersSchema = z.object({
  parkCode: z.string().optional().describe('Filter visitor centers by park code (e.g., "yose" for Yosemite). Multiple parks can be comma-separated (e.g., "yose,grca").'),
  limit: z.number().optional().describe('Maximum number of visitor centers to return (default: 10, max: 50)'),
  start: z.number().optional().describe('Start position for results (useful for pagination)'),
  q: z.string().optional().describe('Search term to filter visitor centers by name or description')
});

// Get Campgrounds Schema
export const GetCampgroundsSchema = z.object({
  parkCode: z.string().optional().describe('Filter campgrounds by park code (e.g., "yose" for Yosemite). Multiple parks can be comma-separated (e.g., "yose,grca").'),
  limit: z.number().optional().describe('Maximum number of campgrounds to return (default: 10, max: 50)'),
  start: z.number().optional().describe('Start position for results (useful for pagination)'),
  q: z.string().optional().describe('Search term to filter campgrounds by name or description')
});

// Get Events Schema
export const GetEventsSchema = z.object({
  parkCode: z.string().optional().describe('Filter events by park code (e.g., "yose" for Yosemite). Multiple parks can be comma-separated (e.g., "yose,grca").'),
  limit: z.number().optional().describe('Maximum number of events to return (default: 10, max: 50)'),
  start: z.number().optional().describe('Start position for results (useful for pagination)'),
  dateStart: z.string().optional().describe('Start date for filtering events (format: YYYY-MM-DD)'),
  dateEnd: z.string().optional().describe('End date for filtering events (format: YYYY-MM-DD)'),
  q: z.string().optional().describe('Search term to filter events by title or description')
}); 
```

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

```typescript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';

import { VERSION } from './constants.js';
import { 
  FindParksSchema, 
  GetParkDetailsSchema, 
  GetAlertsSchema,
  GetVisitorCentersSchema,
  GetCampgroundsSchema,
  GetEventsSchema
} from './schemas.js';
import { findParksHandler } from './handlers/findParks.js';
import { getParkDetailsHandler } from './handlers/getParkDetails.js';
import { getAlertsHandler } from './handlers/getAlerts.js';
import { getVisitorCentersHandler } from './handlers/getVisitorCenters.js';
import { getCampgroundsHandler } from './handlers/getCampgrounds.js';
import { getEventsHandler } from './handlers/getEvents.js';

// Create and configure the server
export function createServer() {
  const server = new Server(
    {
      name: "nationalparks-mcp-server",
      version: VERSION,
    },
    {
      capabilities: {
        tools: {},
      },
    }
  );

  // Register tool definitions
  server.setRequestHandler(ListToolsRequestSchema, async () => {
    return {
      tools: [
        {
          name: "findParks",
          description: "Search for national parks based on state, name, activities, or other criteria",
          inputSchema: zodToJsonSchema(FindParksSchema),
        },
        {
          name: "getParkDetails",
          description: "Get detailed information about a specific national park",
          inputSchema: zodToJsonSchema(GetParkDetailsSchema),
        },
        {
          name: "getAlerts",
          description: "Get current alerts for national parks including closures, hazards, and important information",
          inputSchema: zodToJsonSchema(GetAlertsSchema),
        },
        {
          name: "getVisitorCenters",
          description: "Get information about visitor centers and their operating hours",
          inputSchema: zodToJsonSchema(GetVisitorCentersSchema),
        },
        {
          name: "getCampgrounds",
          description: "Get information about available campgrounds and their amenities",
          inputSchema: zodToJsonSchema(GetCampgroundsSchema),
        },
        {
          name: "getEvents",
          description: "Find upcoming events at parks",
          inputSchema: zodToJsonSchema(GetEventsSchema),
        },
      ],
    };
  });

  // Handle tool executions
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
    try {
      if (!request.params.arguments) {
        throw new Error("Arguments are required");
      }

      switch (request.params.name) {
        case "findParks": {
          const args = FindParksSchema.parse(request.params.arguments);
          return await findParksHandler(args);
        }

        case "getParkDetails": {
          const args = GetParkDetailsSchema.parse(request.params.arguments);
          return await getParkDetailsHandler(args);
        }

        case "getAlerts": {
          const args = GetAlertsSchema.parse(request.params.arguments);
          return await getAlertsHandler(args);
        }

        case "getVisitorCenters": {
          const args = GetVisitorCentersSchema.parse(request.params.arguments);
          return await getVisitorCentersHandler(args);
        }

        case "getCampgrounds": {
          const args = GetCampgroundsSchema.parse(request.params.arguments);
          return await getCampgroundsHandler(args);
        }

        case "getEvents": {
          const args = GetEventsSchema.parse(request.params.arguments);
          return await getEventsHandler(args);
        }

        default:
          throw new Error(`Unknown tool: ${request.params.name}`);
      }
    } catch (error) {
      if (error instanceof z.ZodError) {
        return {
          content: [{ 
            type: "text", 
            text: JSON.stringify({
              error: 'Validation error',
              details: error.errors
            }, null, 2)
          }]
        };
      }
      
      console.error('Error executing tool:', error);
      return {
        content: [{ 
          type: "text", 
          text: JSON.stringify({
            error: 'Server error',
            message: error instanceof Error ? error.message : 'Unknown error'
          }, null, 2)
        }]
      };
    }
  });

  return server;
} 
```

--------------------------------------------------------------------------------
/src/utils/npsApiClient.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * NPS API Client
 * 
 * A client for interacting with the National Park Service API.
 * https://www.nps.gov/subjects/developer/api-documentation.htm
 */

import axios, { AxiosInstance } from 'axios';
import dotenv from 'dotenv';

// Load environment variables
dotenv.config();

// Define types for API responses
export interface NPSResponse<T> {
  total: string;
  limit: string;
  start: string;
  data: T[];
}

export interface ParkData {
  id: string;
  url: string;
  fullName: string;
  parkCode: string;
  description: string;
  latitude: string;
  longitude: string;
  latLong: string;
  activities: Array<{ id: string; name: string }>;
  topics: Array<{ id: string; name: string }>;
  states: string;
  contacts: {
    phoneNumbers: Array<{ phoneNumber: string; description: string; extension: string; type: string }>;
    emailAddresses: Array<{ description: string; emailAddress: string }>;
  };
  entranceFees: Array<{ cost: string; description: string; title: string }>;
  entrancePasses: Array<{ cost: string; description: string; title: string }>;
  fees: any[];
  directionsInfo: string;
  directionsUrl: string;
  operatingHours: Array<{
    exceptions: any[];
    description: string;
    standardHours: {
      sunday: string;
      monday: string;
      tuesday: string;
      wednesday: string;
      thursday: string;
      friday: string;
      saturday: string;
    };
    name: string;
  }>;
  addresses: Array<{
    postalCode: string;
    city: string;
    stateCode: string;
    line1: string;
    line2: string;
    line3: string;
    type: string;
  }>;
  images: Array<{
    credit: string;
    title: string;
    altText: string;
    caption: string;
    url: string;
  }>;
  weatherInfo: string;
  name: string;
  designation: string;
}

export interface AlertData {
  id: string;
  url: string;
  title: string;
  parkCode: string;
  description: string;
  category: string;
  lastIndexedDate: string;
}

// Define parameter types for the API methods
export interface ParkQueryParams {
  parkCode?: string;
  stateCode?: string;
  limit?: number;
  start?: number;
  q?: string;
  fields?: string;
}

export interface AlertQueryParams {
  parkCode?: string;
  limit?: number;
  start?: number;
  q?: string;
}

export interface VisitorCenterData {
  id: string;
  url: string;
  name: string;
  parkCode: string;
  description: string;
  latitude: string;
  longitude: string;
  latLong: string;
  directionsInfo: string;
  directionsUrl: string;
  addresses: Array<{
    postalCode: string;
    city: string;
    stateCode: string;
    line1: string;
    line2: string;
    line3: string;
    type: string;
  }>;
  operatingHours: Array<{
    exceptions: any[];
    description: string;
    standardHours: {
      sunday: string;
      monday: string;
      tuesday: string;
      wednesday: string;
      thursday: string;
      friday: string;
      saturday: string;
    };
    name: string;
  }>;
  contacts: {
    phoneNumbers: Array<{ phoneNumber: string; description: string; extension: string; type: string }>;
    emailAddresses: Array<{ description: string; emailAddress: string }>;
  };
}

export interface CampgroundData {
  id: string;
  url: string;
  name: string;
  parkCode: string;
  description: string;
  latitude: string;
  longitude: string;
  latLong: string;
  audioDescription: string;
  isPassportStampLocation: boolean;
  passportStampLocationDescription: string;
  passportStampImages: any[];
  geometryPoiId: string;
  reservationInfo: string;
  reservationUrl: string;
  regulationsurl: string;
  regulationsOverview: string;
  amenities: {
    trashRecyclingCollection: boolean;
    toilets: string[];
    internetConnectivity: boolean;
    showers: string[];
    cellPhoneReception: boolean;
    laundry: boolean;
    amphitheater: boolean;
    dumpStation: boolean;
    campStore: boolean;
    staffOrVolunteerHostOnsite: boolean;
    potableWater: string[];
    iceAvailableForSale: boolean;
    firewoodForSale: boolean;
    foodStorageLockers: boolean;
  };
  contacts: {
    phoneNumbers: Array<{ phoneNumber: string; description: string; extension: string; type: string }>;
    emailAddresses: Array<{ description: string; emailAddress: string }>;
  };
  fees: Array<{
    cost: string;
    description: string;
    title: string;
  }>;
  directionsOverview: string;
  directionsUrl: string;
  operatingHours: Array<{
    exceptions: any[];
    description: string;
    standardHours: {
      sunday: string;
      monday: string;
      tuesday: string;
      wednesday: string;
      thursday: string;
      friday: string;
      saturday: string;
    };
    name: string;
  }>;
  addresses: Array<{
    postalCode: string;
    city: string;
    stateCode: string;
    line1: string;
    line2: string;
    line3: string;
    type: string;
  }>;
  weatherOverview: string;
  numberOfSitesReservable: string;
  numberOfSitesFirstComeFirstServe: string;
  campsites: {
    totalSites: string;
    group: string;
    horse: string;
    tentOnly: string;
    electricalHookups: string;
    rvOnly: string;
    walkBoatTo: string;
    other: string;
  };
  accessibility: {
    wheelchairAccess: string;
    internetInfo: string;
    cellPhoneInfo: string;
    fireStovePolicy: string;
    rvAllowed: boolean;
    rvInfo: string;
    rvMaxLength: string;
    additionalInfo: string;
    trailerMaxLength: string;
    adaInfo: string;
    trailerAllowed: boolean;
    accessRoads: string[];
    classifications: string[];
  };
}

export interface EventData {
  id: string;
  url: string;
  title: string;
  parkFullName: string;
  description: string;
  latitude: string;
  longitude: string;
  category: string;
  subcategory: string;
  location: string;
  tags: string[];
  recurrenceDateStart: string;
  recurrenceDateEnd: string;
  times: Array<{
    timeStart: string;
    timeEnd: string;
    sunriseTimeStart: boolean;
    sunsetTimeEnd: boolean;
  }>;
  dates: string[];
  dateStart: string;
  dateEnd: string;
  regresurl: string;
  contactEmailAddress: string;
  contactTelephoneNumber: string;
  feeInfo: string;
  isRecurring: boolean;
  isAllDay: boolean;
  siteCode: string;
  parkCode: string;
  organizationName: string;
  types: string[];
  createDate: string;
  lastUpdated: string;
  infoURL: string;
  portalName: string;
}

export interface VisitorCenterQueryParams {
  parkCode?: string;
  limit?: number;
  start?: number;
  q?: string;
}

export interface CampgroundQueryParams {
  parkCode?: string;
  limit?: number;
  start?: number;
  q?: string;
}

export interface EventQueryParams {
  parkCode?: string;
  limit?: number;
  start?: number;
  q?: string;
  dateStart?: string;
  dateEnd?: string;
}

/**
 * NPS API Client class
 */
class NPSApiClient {
  private api: AxiosInstance;
  private baseUrl: string = 'https://developer.nps.gov/api/v1';
  private apiKey: string;

  constructor() {
    this.apiKey = process.env.NPS_API_KEY || '';
    
    if (!this.apiKey) {
      console.warn('Warning: NPS_API_KEY is not set in environment variables.');
      console.warn('Get your API key at: https://www.nps.gov/subjects/developer/get-started.htm');
    }
    
    // Create axios instance for NPS API
    this.api = axios.create({
      baseURL: this.baseUrl,
      headers: {
        'X-Api-Key': this.apiKey,
      },
    });

    // Add response interceptor for error handling
    this.api.interceptors.response.use(
      (response) => response,
      (error) => {
        if (error.response) {
          // Check for rate limiting
          if (error.response.status === 429) {
            console.error('Rate limit exceeded for NPS API. Please try again later.');
          }
          
          // Log the error details
          console.error('NPS API Error:', {
            status: error.response.status,
            statusText: error.response.statusText,
            data: error.response.data,
          });
        } else if (error.request) {
          console.error('No response received from NPS API:', error.request);
        } else {
          console.error('Error setting up NPS API request:', error.message);
        }
        
        return Promise.reject(error);
      }
    );
  }

  /**
   * Fetch parks data from the NPS API
   * @param params Query parameters
   * @returns Promise with parks data
   */
  async getParks(params: ParkQueryParams = {}): Promise<NPSResponse<ParkData>> {
    try {
      const response = await this.api.get('/parks', { params });
      return response.data;
    } catch (error) {
      console.error('Error fetching parks data:', error);
      throw error;
    }
  }

  /**
   * Fetch a specific park by its parkCode
   * @param parkCode The park code (e.g., 'yose' for Yosemite)
   * @returns Promise with the park data
   */
  async getParkByCode(parkCode: string): Promise<NPSResponse<ParkData>> {
    try {
      const response = await this.api.get('/parks', { 
        params: { 
          parkCode,
          limit: 1
        } 
      });
      return response.data;
    } catch (error) {
      console.error(`Error fetching park with code ${parkCode}:`, error);
      throw error;
    }
  }

  /**
   * Fetch alerts from the NPS API
   * @param params Query parameters
   * @returns Promise with alerts data
   */
  async getAlerts(params: AlertQueryParams = {}): Promise<NPSResponse<AlertData>> {
    try {
      const response = await this.api.get('/alerts', { params });
      return response.data;
    } catch (error) {
      console.error('Error fetching alerts data:', error);
      throw error;
    }
  }

  /**
   * Fetch alerts for a specific park
   * @param parkCode The park code (e.g., 'yose' for Yosemite)
   * @returns Promise with the park's alerts
   */
  async getAlertsByParkCode(parkCode: string): Promise<NPSResponse<AlertData>> {
    try {
      const response = await this.api.get('/alerts', { 
        params: { 
          parkCode 
        } 
      });
      return response.data;
    } catch (error) {
      console.error(`Error fetching alerts for park ${parkCode}:`, error);
      throw error;
    }
  }

  /**
   * Fetch visitor centers from the NPS API
   * @param params Query parameters
   * @returns Promise with visitor centers data
   */
  async getVisitorCenters(params: VisitorCenterQueryParams = {}): Promise<NPSResponse<VisitorCenterData>> {
    try {
      const response = await this.api.get('/visitorcenters', { params });
      return response.data;
    } catch (error) {
      console.error('Error fetching visitor centers data:', error);
      throw error;
    }
  }

  /**
   * Fetch campgrounds from the NPS API
   * @param params Query parameters
   * @returns Promise with campgrounds data
   */
  async getCampgrounds(params: CampgroundQueryParams = {}): Promise<NPSResponse<CampgroundData>> {
    try {
      const response = await this.api.get('/campgrounds', { params });
      return response.data;
    } catch (error) {
      console.error('Error fetching campgrounds data:', error);
      throw error;
    }
  }

  /**
   * Fetch events from the NPS API
   * @param params Query parameters
   * @returns Promise with events data
   */
  async getEvents(params: EventQueryParams = {}): Promise<NPSResponse<EventData>> {
    try {
      const response = await this.api.get('/events', { params });
      return response.data;
    } catch (error) {
      console.error('Error fetching events data:', error);
      throw error;
    }
  }
}

// Export a singleton instance
export const npsApiClient = new NPSApiClient();
```

--------------------------------------------------------------------------------
/src/formatters.ts:
--------------------------------------------------------------------------------

```typescript
import { ParkData, AlertData, VisitorCenterData, CampgroundData, EventData } from './utils/npsApiClient.js';

/**
 * Format the park data into a more readable format for LLMs
 */
export function formatParkData(parkData: ParkData[]) {
  return parkData.map(park => ({
    name: park.fullName,
    code: park.parkCode,
    description: park.description,
    states: park.states.split(',').map(code => code.trim()),
    url: park.url,
    designation: park.designation,
    activities: park.activities.map(activity => activity.name),
    weatherInfo: park.weatherInfo,
    location: {
      latitude: park.latitude,
      longitude: park.longitude
    },
    entranceFees: park.entranceFees.map(fee => ({
      cost: fee.cost,
      description: fee.description,
      title: fee.title
    })),
    operatingHours: park.operatingHours.map(hours => ({
      name: hours.name,
      description: hours.description,
      standardHours: hours.standardHours
    })),
    contacts: {
      phoneNumbers: park.contacts.phoneNumbers.map(phone => ({
        type: phone.type,
        number: phone.phoneNumber,
        description: phone.description
      })),
      emailAddresses: park.contacts.emailAddresses.map(email => ({
        address: email.emailAddress,
        description: email.description
      }))
    },
    images: park.images.map(image => ({
      url: image.url,
      title: image.title,
      altText: image.altText,
      caption: image.caption,
      credit: image.credit
    }))
  }));
}

/**
 * Format park details for a single park
 */
export function formatParkDetails(park: ParkData) {
  // Determine the best address to use as the primary address
  const physicalAddress = park.addresses.find(addr => addr.type === 'Physical') || park.addresses[0];
  
  // Format operating hours in a more readable way
  const formattedHours = park.operatingHours.map(hours => {
    const { standardHours } = hours;
    const formattedStandardHours = Object.entries(standardHours)
      .map(([day, hours]) => {
        // Convert day to proper case (e.g., 'monday' to 'Monday')
        const properDay = day.charAt(0).toUpperCase() + day.slice(1);
        return `${properDay}: ${hours || 'Closed'}`;
      });
      
    return {
      name: hours.name,
      description: hours.description,
      standardHours: formattedStandardHours
    };
  });

  return {
    name: park.fullName,
    code: park.parkCode,
    url: park.url,
    description: park.description,
    designation: park.designation,
    states: park.states.split(',').map(code => code.trim()),
    weatherInfo: park.weatherInfo,
    directionsInfo: park.directionsInfo,
    directionsUrl: park.directionsUrl,
    location: {
      latitude: park.latitude,
      longitude: park.longitude,
      address: physicalAddress ? {
        line1: physicalAddress.line1,
        line2: physicalAddress.line2,
        city: physicalAddress.city,
        stateCode: physicalAddress.stateCode,
        postalCode: physicalAddress.postalCode
      } : undefined
    },
    contacts: {
      phoneNumbers: park.contacts.phoneNumbers.map(phone => ({
        type: phone.type,
        number: phone.phoneNumber,
        extension: phone.extension,
        description: phone.description
      })),
      emailAddresses: park.contacts.emailAddresses.map(email => ({
        address: email.emailAddress,
        description: email.description
      }))
    },
    entranceFees: park.entranceFees.map(fee => ({
      title: fee.title,
      cost: `$${fee.cost}`,
      description: fee.description
    })),
    entrancePasses: park.entrancePasses.map(pass => ({
      title: pass.title,
      cost: `$${pass.cost}`,
      description: pass.description
    })),
    operatingHours: formattedHours,
    topics: park.topics.map(topic => topic.name),
    activities: park.activities.map(activity => activity.name),
    images: park.images.map(image => ({
      url: image.url,
      title: image.title,
      altText: image.altText,
      caption: image.caption,
      credit: image.credit
    }))
  };
}

/**
 * Format the alert data into a more readable format for LLMs
 */
export function formatAlertData(alertData: AlertData[]) {
  return alertData.map(alert => {
    // Get the date part from the lastIndexedDate (which is in ISO format)
    const lastUpdated = alert.lastIndexedDate ? new Date(alert.lastIndexedDate).toLocaleDateString() : 'Unknown';
    
    // Categorize the alert type
    let alertType = alert.category;
    if (alertType === 'Information') {
      alertType = 'Information (non-emergency)';
    } else if (alertType === 'Caution') {
      alertType = 'Caution (potential hazard)';
    } else if (alertType === 'Danger') {
      alertType = 'Danger (significant hazard)';
    } else if (alertType === 'Park Closure') {
      alertType = 'Park Closure (area inaccessible)';
    }
    
    return {
      title: alert.title,
      description: alert.description,
      parkCode: alert.parkCode,
      type: alertType,
      url: alert.url,
      lastUpdated
    };
  });
}

/**
 * Format visitor center data for better readability
 */
export function formatVisitorCenterData(visitorCenterData: VisitorCenterData[]) {
  return visitorCenterData.map(center => {
    // Find physical address if available
    const physicalAddress = center.addresses.find(addr => addr.type === 'Physical') || center.addresses[0];
    
    // Format operating hours
    const formattedHours = center.operatingHours.map(hours => {
      const { standardHours } = hours;
      const formattedStandardHours = Object.entries(standardHours)
        .map(([day, hours]) => {
          // Convert day to proper case (e.g., 'monday' to 'Monday')
          const properDay = day.charAt(0).toUpperCase() + day.slice(1);
          return `${properDay}: ${hours || 'Closed'}`;
        });
        
      return {
        name: hours.name,
        description: hours.description,
        standardHours: formattedStandardHours
      };
    });

    return {
      name: center.name,
      parkCode: center.parkCode,
      description: center.description,
      url: center.url,
      directionsInfo: center.directionsInfo,
      directionsUrl: center.directionsUrl,
      location: {
        latitude: center.latitude,
        longitude: center.longitude,
        address: physicalAddress ? {
          line1: physicalAddress.line1,
          line2: physicalAddress.line2,
          city: physicalAddress.city,
          stateCode: physicalAddress.stateCode,
          postalCode: physicalAddress.postalCode
        } : undefined
      },
      operatingHours: formattedHours,
      contacts: {
        phoneNumbers: center.contacts.phoneNumbers.map(phone => ({
          type: phone.type,
          number: phone.phoneNumber,
          extension: phone.extension,
          description: phone.description
        })),
        emailAddresses: center.contacts.emailAddresses.map(email => ({
          address: email.emailAddress,
          description: email.description
        }))
      }
    };
  });
}

/**
 * Format campground data for better readability
 */
export function formatCampgroundData(campgroundData: CampgroundData[]) {
  return campgroundData.map(campground => {
    // Find physical address if available
    const physicalAddress = campground.addresses.find(addr => addr.type === 'Physical') || campground.addresses[0];
    
    // Format operating hours
    const formattedHours = campground.operatingHours.map(hours => {
      const { standardHours } = hours;
      const formattedStandardHours = Object.entries(standardHours)
        .map(([day, hours]) => {
          const properDay = day.charAt(0).toUpperCase() + day.slice(1);
          return `${properDay}: ${hours || 'Closed'}`;
        });
        
      return {
        name: hours.name,
        description: hours.description,
        standardHours: formattedStandardHours
      };
    });

    // Format amenities for better readability
    const amenities = [];
    if (campground.amenities) {
      if (campground.amenities.trashRecyclingCollection) amenities.push('Trash/Recycling Collection');
      if (campground.amenities.toilets && campground.amenities.toilets.length > 0) 
        amenities.push(`Toilets (${campground.amenities.toilets.join(', ')})`);
      if (campground.amenities.internetConnectivity) amenities.push('Internet Connectivity');
      if (campground.amenities.showers && campground.amenities.showers.length > 0) 
        amenities.push(`Showers (${campground.amenities.showers.join(', ')})`);
      if (campground.amenities.cellPhoneReception) amenities.push('Cell Phone Reception');
      if (campground.amenities.laundry) amenities.push('Laundry');
      if (campground.amenities.amphitheater) amenities.push('Amphitheater');
      if (campground.amenities.dumpStation) amenities.push('Dump Station');
      if (campground.amenities.campStore) amenities.push('Camp Store');
      if (campground.amenities.staffOrVolunteerHostOnsite) amenities.push('Staff/Volunteer Host Onsite');
      if (campground.amenities.potableWater && campground.amenities.potableWater.length > 0) 
        amenities.push(`Potable Water (${campground.amenities.potableWater.join(', ')})`);
      if (campground.amenities.iceAvailableForSale) amenities.push('Ice Available For Sale');
      if (campground.amenities.firewoodForSale) amenities.push('Firewood For Sale');
      if (campground.amenities.foodStorageLockers) amenities.push('Food Storage Lockers');
    }

    return {
      name: campground.name,
      parkCode: campground.parkCode,
      description: campground.description,
      url: campground.url,
      reservationInfo: campground.reservationInfo,
      reservationUrl: campground.reservationUrl,
      regulations: campground.regulationsOverview,
      regulationsUrl: campground.regulationsurl,
      weatherOverview: campground.weatherOverview,
      location: {
        latitude: campground.latitude,
        longitude: campground.longitude,
        address: physicalAddress ? {
          line1: physicalAddress.line1,
          line2: physicalAddress.line2,
          city: physicalAddress.city,
          stateCode: physicalAddress.stateCode,
          postalCode: physicalAddress.postalCode
        } : undefined
      },
      operatingHours: formattedHours,
      fees: campground.fees.map(fee => ({
        title: fee.title,
        cost: `$${fee.cost}`,
        description: fee.description
      })),
      totalSites: campground.campsites?.totalSites || '0',
      sitesReservable: campground.numberOfSitesReservable || '0',
      sitesFirstComeFirstServe: campground.numberOfSitesFirstComeFirstServe || '0',
      campsiteTypes: {
        group: campground.campsites?.group || '0',
        horse: campground.campsites?.horse || '0',
        tentOnly: campground.campsites?.tentOnly || '0',
        electricalHookups: campground.campsites?.electricalHookups || '0',
        rvOnly: campground.campsites?.rvOnly || '0',
        walkBoatTo: campground.campsites?.walkBoatTo || '0',
        other: campground.campsites?.other || '0'
      },
      amenities: amenities,
      accessibility: {
        wheelchairAccess: campground.accessibility?.wheelchairAccess,
        rvAllowed: campground.accessibility?.rvAllowed,
        rvMaxLength: campground.accessibility?.rvMaxLength,
        trailerAllowed: campground.accessibility?.trailerAllowed,
        trailerMaxLength: campground.accessibility?.trailerMaxLength,
        accessRoads: campground.accessibility?.accessRoads,
        adaInfo: campground.accessibility?.adaInfo
      },
      contacts: {
        phoneNumbers: campground.contacts.phoneNumbers.map(phone => ({
          type: phone.type,
          number: phone.phoneNumber,
          extension: phone.extension,
          description: phone.description
        })),
        emailAddresses: campground.contacts.emailAddresses.map(email => ({
          address: email.emailAddress,
          description: email.description
        }))
      }
    };
  });
}

/**
 * Format event data for better readability
 */
export function formatEventData(eventData: EventData[]) {
  return eventData.map(event => {
    // Format dates and times
    const formattedDates = event.dates ? event.dates.join(', ') : '';
    
    // Format times
    const formattedTimes = event.times.map(time => {
      let timeString = '';
      if (time.timeStart) {
        timeString += time.sunriseTimeStart ? 'Sunrise' : time.timeStart;
      }
      if (time.timeEnd) {
        timeString += ' to ';
        timeString += time.sunsetTimeEnd ? 'Sunset' : time.timeEnd;
      }
      return timeString || 'All day';
    }).join(', ');

    return {
      title: event.title,
      parkCode: event.parkCode,
      parkName: event.parkFullName,
      description: event.description,
      category: event.category,
      subcategory: event.subcategory,
      tags: event.tags,
      location: event.location,
      coordinates: {
        latitude: event.latitude,
        longitude: event.longitude
      },
      dateTime: {
        dates: formattedDates,
        times: formattedTimes,
        dateStart: event.dateStart,
        dateEnd: event.dateEnd,
        isAllDay: event.isAllDay,
        isRecurring: event.isRecurring,
        recurrenceDateStart: event.recurrenceDateStart,
        recurrenceDateEnd: event.recurrenceDateEnd
      },
      feeInfo: event.feeInfo,
      contactInfo: {
        email: event.contactEmailAddress,
        phone: event.contactTelephoneNumber
      },
      infoUrl: event.infoURL || event.url,
      lastUpdated: event.lastUpdated
    };
  });
} 
```