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

```
├── .azdignore
├── .env.example
├── .github
│   └── copilot-instructions.md
├── .gitignore
├── .nvmrc
├── .vscode
│   └── tasks.json
├── azure.yaml
├── infra
│   ├── abbreviations.json
│   ├── core
│   │   └── host
│   │       ├── appservice.bicep
│   │       └── appserviceplan.bicep
│   ├── main.bicep
│   └── main.parameters.json
├── mcp-config.json
├── package-lock.json
├── package.json
├── public
│   └── test.html
├── README.md
├── server.js
├── test-client.js
└── web.config
```

# Files

--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------

```
22

```

--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------

```
# Example environment variables for the MCP Server
# Copy this file to .env and update the values as needed

# Server Configuration
PORT=8000
NODE_ENV=development

# CORS Configuration (optional - defaults to allow all origins in development)
# ALLOWED_ORIGINS=http://localhost:3000,https://yourdomain.com

# Logging Level (optional - defaults to 'info')
# LOG_LEVEL=debug

```

--------------------------------------------------------------------------------
/.azdignore:
--------------------------------------------------------------------------------

```
# Azure Developer CLI ignore file
# Files and directories to ignore during deployment

# Development files
.env
.env.local
.env.development
.env.test
.env.production

# Node.js
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*

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

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

# nyc test coverage
.nyc_output

# Dependency directories
node_modules/
jspm_packages/

# Optional npm cache directory
.npm

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
.env.test

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

# VS Code
.vscode/
.history/

# Git
.git/
.gitignore

# Azure Dev CLI
.azure/

# Development and testing
test-client.js
public/test.html
start_server.ps1
start_server.bat

# Documentation (optional - remove if you want to include docs)
README.md
DEPLOY.md

# Windows
Thumbs.db
ehthumbs.db
Desktop.ini
$RECYCLE.BIN/

# macOS
.DS_Store
.AppleDouble
.LSOverride
Icon
._*

# Linux
*~

```

--------------------------------------------------------------------------------
/.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

# 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.*

# Azure deployment
.azure/

```

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

```markdown
# Node.js MCP Weather Server with Azure Deployment

A Model Context Protocol (MCP) server built with Express.js and Node.js that provides weather information using the National Weather Service API. Ready for deployment to Azure App Service with Azure Developer CLI (azd).

## 🌟 Features

- **Express.js Framework**: Fast, unopinionated web framework for Node.js
- **MCP Protocol Compliance**: Full support for JSON-RPC 2.0 MCP protocol
- **HTTP Transport**: HTTP-based communication for web connectivity
- **Weather Tools**:
  - `get_alerts`: Get weather alerts for any US state
  - `get_forecast`: Get detailed weather forecast for any location
- **Azure Ready**: Pre-configured for Azure App Service deployment
- **Web Test Interface**: Built-in HTML interface for testing
- **National Weather Service API**: Real-time weather data from official US government source

## 💻 Local Development

### Prerequisites
- **Node.js 22+** (or Node.js 18+)
- **npm** (Node Package Manager)

### Setup & Run

1. **Clone and install dependencies**:
   ```bash
   git clone <your-repo-url>
   cd remote-mcp-webapp-node
   npm install
   ```

2. **Start the development server**:
   ```bash
   npm run dev
   ```

3. **Access the server**:
   - Server: http://localhost:8000
   - Health Check: http://localhost:8000/health
   - Test Interface: http://localhost:8000/test

## 🔌 Connect to the Local MCP Server

### Using VS Code - Copilot Agent Mode

1. **Add MCP Server** from command palette and add the URL to your running server's HTTP endpoint:
   ```
   http://localhost:8000
   ```
2. **List MCP Servers** from command palette and start the server
3. In Copilot chat agent mode, enter a prompt to trigger the tool:
   ```
   What's the weather forecast for San Francisco?
   ```
4. When prompted to run the tool, consent by clicking **Continue**

### Using MCP Inspector

1. In a **new terminal window**, install and run MCP Inspector:
   ```bash
   npx @modelcontextprotocol/inspector
   ```
2. CTRL+click the URL displayed by the app (e.g. http://localhost:5173/#resources)
3. Set the transport type to `HTTP`
4. Set the URL to your running server's HTTP endpoint and **Connect**:
   ```
   http://localhost:8000
   ```
5. **List Tools**, click on a tool, and **Run Tool**

## 🚀 Quick Deploy to Azure

### Prerequisites
- [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli)
- [Azure Developer CLI (azd)](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd)
- Active Azure subscription

### Deploy in 3 Commands

```bash
# 1. Login to Azure
azd auth login

# 2. Initialize the project
azd init

# 3. Deploy to Azure
azd up
```

After deployment, your MCP server will be available at:
- **Health Check**: `https://<your-app>.azurewebsites.net/health`
- **MCP Capabilities**: `https://<your-app>.azurewebsites.net/mcp/capabilities`
- **Test Interface**: `https://<your-app>.azurewebsites.net/test`

## 🔌 Connect to the Remote MCP Server

Follow the same guidance as above, but use your App Service URL instead.

## 🧪 Testing

Visit `/test` endpoint for an interactive testing interface.

## 🌦️ Data Source

This server uses the **National Weather Service (NWS) API**:
- Real-time weather alerts and warnings
- Detailed weather forecasts
- Official US government weather data  
- No API key required
- High reliability and accuracy
```

--------------------------------------------------------------------------------
/azure.yaml:
--------------------------------------------------------------------------------

```yaml
name: remote-mcp-webapp-node
metadata:
  template: [email protected]
services:
  web:
    project: .
    language: js
    host: appservice

```

--------------------------------------------------------------------------------
/infra/main.parameters.json:
--------------------------------------------------------------------------------

```json
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "environmentName": {
      "value": "${AZURE_ENV_NAME}"
    },
    "location": {
      "value": "${AZURE_LOCATION}"
    },
    "principalId": {
      "value": "${AZURE_PRINCIPAL_ID}"
    }
  }
}

```

--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------

```json
{
	"version": "2.0.0",
	"tasks": [
		{
			"label": "Start MCP Weather Server",
			"type": "shell",
			"command": "npm",
			"args": [
				"run",
				"dev"
			],
			"group": "build",
			"isBackground": true,
			"problemMatcher": [],
			"presentation": {
				"echo": true,
				"reveal": "always",
				"focus": false,
				"panel": "shared"
			}
		}
	]
}
```

--------------------------------------------------------------------------------
/mcp-config.json:
--------------------------------------------------------------------------------

```json
{
  "mcpServers": {
    "weather-mcp-server-local": {
      "transport": {
        "type": "http",
        "url": "http://localhost:8000/mcp/stream"
      },
      "name": "Weather MCP Server (Local - Node.js)",
      "description": "MCP Server with weather forecast and alerts tools running locally on Node.js"
    },
    "weather-mcp-server-azure": {
      "transport": {
        "type": "http",
        "url": "https://<APP-SERVICE-NAME>.azurewebsites.net/mcp/stream"
      },
      "name": "Weather MCP Server (Azure - Node.js)",
      "description": "MCP Server with weather forecast and alerts tools hosted on Azure with Node.js"
    }
  }
}

```

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

```json
{
  "name": "remote-mcp-webapp-node",
  "version": "1.0.0",
  "main": "server.js",
  "description": "A Model Context Protocol (MCP) server built with Express.js that provides weather information using the National Weather Service API",
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js",
    "test": "node test-client.js",
    "test:azure": "node test-client.js --url=https://<APP-SERVICE-NAME>.azurewebsites.net",
    "build": "echo 'No build step required for this Node.js app'"
  },
  "keywords": [
    "mcp",
    "model-context-protocol", 
    "weather",
    "api",
    "express",
    "nodejs",
    "jsonrpc"
  ],
  "author": "",
  "license": "MIT",
  "engines": {
    "node": ">=22.0.0"
  },
  "dependencies": {
    "axios": "^1.9.0",
    "cors": "^2.8.5",
    "dotenv": "^16.5.0",
    "express": "^5.1.0",
    "helmet": "^8.1.0",
    "uuid": "^11.1.0"
  },
  "devDependencies": {
    "nodemon": "^3.1.10"
  }
}

```

--------------------------------------------------------------------------------
/web.config:
--------------------------------------------------------------------------------

```
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <handlers>
      <add name="iisnode" path="server.js" verb="*" modules="iisnode"/>
    </handlers>
    <rewrite>
      <rules>
        <rule name="NodeInspector" patternSyntax="ECMAScript" stopProcessing="true">
          <match url="^server.js\/debug[\/]?" />
        </rule>
        <rule name="StaticContent">
          <action type="Rewrite" url="public{REQUEST_URI}"/>
        </rule>
        <rule name="DynamicContent">
          <conditions>
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="True"/>
          </conditions>
          <action type="Rewrite" url="server.js"/>
        </rule>
      </rules>
    </rewrite>
    <security>
      <requestFiltering>
        <hiddenSegments>
          <remove segment="bin"/>
        </hiddenSegments>
      </requestFiltering>
    </security>
    <httpErrors existingResponse="PassThrough" />
    <iisnode watchedFiles="web.config;*.js"/>
  </system.webServer>
</configuration>

```

--------------------------------------------------------------------------------
/.github/copilot-instructions.md:
--------------------------------------------------------------------------------

```markdown
<!-- Use this file to provide workspace-specific custom instructions to Copilot. For more details, visit https://code.visualstudio.com/docs/copilot/copilot-customization#_use-a-githubcopilotinstructionsmd-file -->

# MCP Server Development Guidelines

This is an MCP (Model Context Protocol) server project built with Express.js and Node.js.

## Key Context

- This project implements the MCP protocol using JSON-RPC 2.0
- The server provides weather-related tools using the National Weather Service API
- Main endpoints follow MCP specification for tools, resources, and capabilities
- Use Express.js patterns and Node.js best practices
- Weather data comes from official US government APIs (no API key required)

## Code Style Guidelines

- Use modern JavaScript (ES6+) features where appropriate
- Follow Express.js conventions for routing and middleware
- Implement proper error handling with try-catch blocks
- Use consistent JSON-RPC 2.0 response formats
- Include comprehensive logging for debugging
- Maintain backward compatibility with the MCP protocol

## Architecture Notes

- `server.js` contains the main Express application and MCP protocol implementation
- Weather tools are implemented as async functions that call NWS APIs
- HTTP transport is used for MCP Inspector connectivity
- Web test interface provides manual testing capabilities

## Testing

- Use the built-in test client (`test-client.js`) for protocol testing
- Web interface (`/test`) for manual verification
- Ensure all MCP methods return proper JSON-RPC 2.0 responses

You can find more info and examples at https://modelcontextprotocol.io/llms-full.txt

```

--------------------------------------------------------------------------------
/test-client.js:
--------------------------------------------------------------------------------

```javascript
const axios = require('axios');

class MCPTestClient {
    constructor(baseUrl = 'http://localhost:8000') {
        this.baseUrl = baseUrl;
        this.mcpEndpoint = `${baseUrl}/mcp/stream`;
    }

    async makeRequest(method, params = {}, id = null) {
        const request = {
            jsonrpc: '2.0',
            method,
            params,
            id: id || Math.floor(Math.random() * 1000)
        };

        try {
            console.log(`\n🔄 Sending request: ${method}`);
            console.log('Request:', JSON.stringify(request, null, 2));
            
            const response = await axios.post(this.mcpEndpoint, request);
            
            console.log('✅ Response received:');
            console.log(JSON.stringify(response.data, null, 2));
            
            return response.data;
        } catch (error) {
            console.error('❌ Error:', error.message);
            if (error.response) {
                console.error('Response data:', error.response.data);
            }
            throw error;
        }
    }

    async testInitialize() {
        console.log('\n=== Testing Initialize ===');
        return await this.makeRequest('initialize', {});
    }

    async testListTools() {
        console.log('\n=== Testing List Tools ===');
        return await this.makeRequest('tools/list');
    }

    async testGetAlerts(state = 'CA') {
        console.log('\n=== Testing Weather Alerts ===');
        return await this.makeRequest('tools/call', {
            name: 'get_alerts',
            arguments: { state }
        });
    }

    async testGetForecast(latitude = 37.7749, longitude = -122.4194) {
        console.log('\n=== Testing Weather Forecast ===');
        return await this.makeRequest('tools/call', {
            name: 'get_forecast',
            arguments: { latitude, longitude }
        });
    }

    async testListResources() {
        console.log('\n=== Testing List Resources ===');
        return await this.makeRequest('resources/list');
    }

    async testReadResource(uri = 'mcp://server/sample') {
        console.log('\n=== Testing Read Resource ===');
        return await this.makeRequest('resources/read', { uri });
    }

    async checkHealth() {
        console.log('\n=== Checking Server Health ===');
        try {
            const response = await axios.get(`${this.baseUrl}/health`);
            console.log('✅ Health check passed:');
            console.log(JSON.stringify(response.data, null, 2));
            return response.data;
        } catch (error) {
            console.error('❌ Health check failed:', error.message);
            throw error;
        }
    }

    async runAllTests() {
        console.log('🚀 Starting MCP Server Tests');
        console.log('================================');

        try {
            // Check server health first
            await this.checkHealth();

            // Test MCP protocol methods
            await this.testInitialize();
            await this.testListTools();
            await this.testListResources();
            await this.testReadResource();
            
            // Test weather tools
            await this.testGetAlerts('CA');
            await this.testGetForecast(37.7749, -122.4194); // San Francisco
            
            console.log('\n🎉 All tests completed successfully!');
        } catch (error) {
            console.error('\n💥 Test suite failed:', error.message);
            process.exit(1);
        }
    }
}

// Run tests if this file is executed directly
if (require.main === module) {
    const client = new MCPTestClient();
    
    // Parse command line arguments
    const args = process.argv.slice(2);
    const serverUrl = args.find(arg => arg.startsWith('--url='))?.split('=')[1] || 'http://localhost:8000';
    
    if (serverUrl !== 'http://localhost:8000') {
        client.baseUrl = serverUrl;
        client.mcpEndpoint = `${serverUrl}/mcp/stream`;
        console.log(`🌐 Testing server at: ${serverUrl}`);
    }
    
    client.runAllTests();
}

module.exports = MCPTestClient;

```

--------------------------------------------------------------------------------
/infra/abbreviations.json:
--------------------------------------------------------------------------------

```json
{
  "analysisServicesServers": "as",
  "apiManagementService": "apim-",
  "appConfigurationConfigurationStores": "appcs-",
  "appManagedEnvironments": "cae-",
  "appContainerApps": "ca-",
  "authorizationPolicyDefinitions": "policy-",
  "automationAutomationAccounts": "aa-",
  "blueprintBlueprints": "bp-",
  "blueprintBlueprintsArtifacts": "bpa-",
  "cacheRedis": "redis-",
  "cdnProfiles": "cdnp-",
  "cdnProfilesEndpoints": "cdne-",
  "cognitiveServicesAccounts": "cog-",
  "cognitiveServicesFormRecognizer": "cog-fr-",
  "cognitiveServicesTextAnalytics": "cog-ta-",
  "computeAvailabilitySets": "avail-",
  "computeCloudServices": "cld-",
  "computeDiskEncryptionSets": "des-",
  "computeDisks": "disk-",
  "computeDisksOs": "osdisk-",
  "computeGalleries": "gal-",
  "computeSnapshots": "snap-",
  "computeVirtualMachines": "vm-",
  "computeVirtualMachineScaleSets": "vmss-",
  "containerInstanceContainerGroups": "ci-",
  "containerRegistryRegistries": "cr-",
  "containerServiceManagedClusters": "aks-",
  "databricksWorkspaces": "dbw-",
  "dataFactoryFactories": "adf-",
  "dataLakeAnalyticsAccounts": "dla-",
  "dataLakeStoreAccounts": "dls-",
  "dataMigrationServices": "dms-",
  "dBforMySQLServers": "mysql-",
  "dBforPostgreSQLServers": "psql-",
  "devicesIotHubs": "iot-",
  "devicesProvisioningServices": "provs-",
  "devicesProvisioningServicesCertificates": "pcert-",
  "documentDBDatabaseAccounts": "cosmos-",
  "eventGridDomains": "evgd-",
  "eventGridDomainsTopic": "evgt-",
  "eventGridEventSubscriptions": "evgs-",
  "eventHubNamespaces": "evhns-",
  "eventHubNamespacesEventHubs": "evh-",
  "hdInsightClustersHadoop": "hadoop-",
  "hdInsightClustersHbase": "hbase-",
  "hdInsightClustersKafka": "kafka-",
  "hdInsightClustersMl": "mls-",
  "hdInsightClustersSpark": "spark-",
  "hdInsightClustersStorm": "storm-",
  "hybridComputeMachines": "arcs-",
  "insightsActionGroups": "ag-",
  "insightsComponents": "appi-",
  "keyVaultVaults": "kv-",
  "kubernetesConnectedClusters": "arck-",
  "kustoClusters": "dec-",
  "loadTesting": "lt-",
  "logicIntegrationAccounts": "ia-",
  "logicWorkflows": "logic-",
  "machineLearningServicesWorkspaces": "mlw-",
  "managedIdentityUserAssignedIdentities": "id-",
  "managementManagementGroups": "mg-",
  "migrateAssessmentProjects": "migr-",
  "networkApplicationGateways": "agw-",
  "networkApplicationSecurityGroups": "asg-",
  "networkAzureFirewalls": "afw-",
  "networkBastionHosts": "bas-",
  "networkConnections": "con-",
  "networkDnsZones": "dnsz-",
  "networkExpressRouteCircuits": "erc-",
  "networkFirewallPolicies": "afwp-",
  "networkFirewallPoliciesWebApplication": "waf-",
  "networkFirewallPoliciesRuleGroups": "wafrg-",
  "networkFrontDoors": "fd-",
  "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-",
  "networkLoadBalancersExternal": "lbe-",
  "networkLoadBalancersInternal": "lbi-",
  "networkLoadBalancersInboundNatRules": "rule-",
  "networkLocalNetworkGateways": "lgw-",
  "networkNatGateways": "ng-",
  "networkNetworkInterfaces": "nic-",
  "networkNetworkSecurityGroups": "nsg-",
  "networkNetworkSecurityGroupsSecurityRules": "nsgsr-",
  "networkNetworkWatchers": "nw-",
  "networkPrivateDnsZones": "pdnsz-",
  "networkPrivateLinkServices": "pl-",
  "networkPublicIPAddresses": "pip-",
  "networkPublicIPPrefixes": "ippre-",
  "networkRouteFilters": "rf-",
  "networkRouteTables": "rt-",
  "networkRouteTablesRoutes": "udr-",
  "networkTrafficManagerProfiles": "traf-",
  "networkVirtualNetworkGateways": "vgw-",
  "networkVirtualNetworks": "vnet-",
  "networkVirtualNetworksSubnets": "snet-",
  "networkVirtualNetworksVirtualNetworkPeerings": "peer-",
  "networkVirtualWans": "vwan-",
  "networkVpnGateways": "vpng-",
  "networkVpnGatewaysVpnConnections": "vcn-",
  "networkVpnGatewaysVpnSites": "vst-",
  "notificationHubsNamespaces": "ntfns-",
  "notificationHubsNamespacesNotificationHubs": "ntf-",
  "operationalInsightsWorkspaces": "log-",
  "portalDashboards": "dash-",
  "powerBIDedicatedCapacities": "pbi-",
  "purviewAccounts": "pview-",
  "recoveryServicesVaults": "rsv-",
  "resourcesResourceGroups": "rg-",
  "searchSearchServices": "srch-",
  "serviceBusNamespaces": "sb-",
  "serviceBusNamespacesQueues": "sbq-",
  "serviceBusNamespacesTopics": "sbt-",
  "serviceEndPointPolicies": "se-",
  "serviceFabricClusters": "sf-",
  "signalRServiceSignalR": "sigr-",
  "sqlManagedInstances": "sqlmi-",
  "sqlServers": "sql-",
  "sqlServersDataWarehouse": "sqldw-",
  "sqlServersDatabase": "sqldb-",
  "sqlServersFirewallRules": "sqlfw-",
  "storageStorageAccounts": "st",
  "storageStorageAccountsVm": "stvm",
  "streamAnalyticsCluster": "asa-",
  "synapseWorkspaces": "syn-",
  "timeSeriesInsightsEnvironments": "tsi-",
  "webServerFarms": "plan-",
  "webSitesAppService": "app-",
  "webSitesAppServiceEnvironment": "ase-",
  "webSitesFunctions": "func-"
}

```

--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------

```javascript
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const axios = require('axios');
const path = require('path');
const fs = require('fs');
const { v4: uuidv4 } = require('uuid');

const app = express();
const PORT = process.env.PORT || 8000;

// Middleware
app.use(helmet());
app.use(cors());
app.use(express.json());
app.use(express.static('public'));

// MCP Server Implementation
class MCPServer {
    constructor() {
        this.tools = [
            {
                name: 'get_alerts',
                description: 'Get weather alerts for a US state.',
                inputSchema: {
                    type: 'object',
                    properties: {
                        state: {
                            type: 'string',
                            description: 'Two-letter US state code (e.g. CA, NY)'
                        }
                    },
                    required: ['state']
                }
            },
            {
                name: 'get_forecast',
                description: 'Get weather forecast for a location.',
                inputSchema: {
                    type: 'object',
                    properties: {
                        latitude: {
                            type: 'number',
                            description: 'Latitude of the location'
                        },
                        longitude: {
                            type: 'number',
                            description: 'Longitude of the location'
                        }
                    },
                    required: ['latitude', 'longitude']
                }
            }
        ];
        
        this.resources = [
            {
                uri: 'mcp://server/sample',
                name: 'Sample Resource',
                description: 'Sample resource for demonstration',
                mimeType: 'text/plain'
            }
        ];
    }

    async handleRequest(request) {
        const { jsonrpc, method, params, id } = request;
        
        if (jsonrpc !== '2.0') {
            return this.createErrorResponse(id, -32600, 'Invalid Request');
        }

        try {
            switch (method) {
                case 'initialize':
                    return this.handleInitialize(id, params);
                case 'tools/list':
                    return this.handleToolsList(id);
                case 'tools/call':
                    return await this.handleToolsCall(id, params);
                case 'resources/list':
                    return this.handleResourcesList(id);
                case 'resources/read':
                    return this.handleResourcesRead(id, params);
                default:
                    return this.createErrorResponse(id, -32601, 'Method not found');
            }
        } catch (error) {
            console.error('Error handling request:', error);
            return this.createErrorResponse(id, -32603, 'Internal error');
        }
    }

    handleInitialize(id, params) {
        return {
            jsonrpc: '2.0',
            id,
            result: {
                protocolVersion: '2024-11-05',
                capabilities: {
                    tools: {},
                    resources: {}
                },
                serverInfo: {
                    name: 'Weather MCP Server (Node.js)',
                    version: '1.0.0'
                }
            }
        };
    }

    handleToolsList(id) {
        return {
            jsonrpc: '2.0',
            id,
            result: {
                tools: this.tools
            }
        };
    }

    async handleToolsCall(id, params) {
        const { name, arguments: args } = params;
        
        try {
            let result;
            switch (name) {
                case 'get_alerts':
                    result = await this.getAlerts(args.state);
                    break;
                case 'get_forecast':
                    result = await this.getForecast(args.latitude, args.longitude);
                    break;
                default:
                    return this.createErrorResponse(id, -32602, 'Invalid tool name');
            }
            
            return {
                jsonrpc: '2.0',
                id,
                result: {
                    content: [
                        {
                            type: 'text',
                            text: result
                        }
                    ]
                }
            };
        } catch (error) {
            return this.createErrorResponse(id, -32603, `Tool execution error: ${error.message}`);
        }
    }

    handleResourcesList(id) {
        return {
            jsonrpc: '2.0',
            id,
            result: {
                resources: this.resources
            }
        };
    }

    handleResourcesRead(id, params) {
        const { uri } = params;
        
        if (uri === 'mcp://server/sample') {
            return {
                jsonrpc: '2.0',
                id,
                result: {
                    contents: [
                        {
                            uri,
                            mimeType: 'text/plain',
                            text: 'This is a sample resource from the MCP server.'
                        }
                    ]
                }
            };
        }
        
        return this.createErrorResponse(id, -32602, 'Resource not found');
    }

    async getAlerts(state) {
        try {
            const response = await axios.get(`https://api.weather.gov/alerts/active?area=${state.toUpperCase()}`);
            const alerts = response.data.features;
            
            if (alerts.length === 0) {
                return `No active weather alerts for ${state.toUpperCase()}.`;
            }
            
            let alertsText = `Active weather alerts for ${state.toUpperCase()}:\n\n`;
            alerts.forEach((alert, index) => {
                const properties = alert.properties;
                alertsText += `${index + 1}. ${properties.headline}\n`;
                alertsText += `   Severity: ${properties.severity}\n`;
                alertsText += `   Urgency: ${properties.urgency}\n`;
                alertsText += `   Event: ${properties.event}\n`;
                alertsText += `   Description: ${properties.description}\n`;
                if (properties.instruction) {
                    alertsText += `   Instructions: ${properties.instruction}\n`;
                }
                alertsText += '\n';
            });
            
            return alertsText;
        } catch (error) {
            throw new Error(`Failed to fetch weather alerts: ${error.message}`);
        }
    }

    async getForecast(latitude, longitude) {
        try {
            // First, get the grid point
            const pointResponse = await axios.get(`https://api.weather.gov/points/${latitude},${longitude}`);
            const forecastUrl = pointResponse.data.properties.forecast;
            
            // Then get the forecast
            const forecastResponse = await axios.get(forecastUrl);
            const periods = forecastResponse.data.properties.periods;
            
            let forecastText = `Weather forecast for ${latitude}, ${longitude}:\n\n`;
            periods.slice(0, 10).forEach((period, index) => {
                forecastText += `${period.name}:\n`;
                forecastText += `  Temperature: ${period.temperature}°${period.temperatureUnit}\n`;
                forecastText += `  Wind: ${period.windSpeed} ${period.windDirection}\n`;
                forecastText += `  Forecast: ${period.detailedForecast}\n\n`;
            });
            
            return forecastText;
        } catch (error) {
            throw new Error(`Failed to fetch weather forecast: ${error.message}`);
        }
    }

    createErrorResponse(id, code, message) {
        return {
            jsonrpc: '2.0',
            id,
            error: {
                code,
                message
            }
        };
    }
}

const mcpServer = new MCPServer();

// Routes
app.get('/health', (req, res) => {
    res.json({ status: 'healthy', timestamp: new Date().toISOString() });
});

app.get('/mcp/capabilities', (req, res) => {
    res.json({
        protocolVersion: '2024-11-05',
        capabilities: {
            tools: {},
            resources: {}
        },
        serverInfo: {
            name: 'Weather MCP Server (Node.js)',
            version: '1.0.0'
        }
    });
});

// Main MCP endpoint with streamable HTTP
app.post('/mcp/stream', async (req, res) => {
    try {
        const response = await mcpServer.handleRequest(req.body);
        res.json(response);
    } catch (error) {
        console.error('MCP Stream Error:', error);
        res.status(500).json({
            jsonrpc: '2.0',
            id: req.body.id || null,
            error: {
                code: -32603,
                message: 'Internal error'
            }
        });
    }
});

// Legacy MCP endpoint
app.post('/mcp', async (req, res) => {
    try {
        const response = await mcpServer.handleRequest(req.body);
        res.json(response);
    } catch (error) {
        console.error('MCP Error:', error);
        res.status(500).json({
            jsonrpc: '2.0',
            id: req.body.id || null,
            error: {
                code: -32603,
                message: 'Internal error'
            }
        });
    }
});

// Web test interface
app.get('/test', (req, res) => {
    res.sendFile(path.join(__dirname, 'public', 'test.html'));
});

// Start server
app.listen(PORT, '0.0.0.0', () => {
    console.log(`MCP Weather Server running on http://localhost:${PORT}`);
    console.log(`Health check: http://localhost:${PORT}/health`);
    console.log(`MCP Endpoint: http://localhost:${PORT}/mcp/stream`);
    console.log(`Web Test Interface: http://localhost:${PORT}/test`);
    console.log(`API Documentation: http://localhost:${PORT}/mcp/capabilities`);
});

```

--------------------------------------------------------------------------------
/public/test.html:
--------------------------------------------------------------------------------

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MCP Weather Server - Test Interface</title>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
            background-color: #f5f5f5;
        }
        .container {
            background: white;
            border-radius: 8px;
            padding: 30px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        h1 {
            color: #2563eb;
            margin-bottom: 10px;
        }
        .subtitle {
            color: #6b7280;
            margin-bottom: 30px;
        }
        .section {
            margin-bottom: 30px;
            padding: 20px;
            border: 1px solid #e5e7eb;
            border-radius: 6px;
            background-color: #f9fafb;
        }
        .section h3 {
            margin-top: 0;
            color: #374151;
        }
        .form-group {
            margin-bottom: 15px;
        }
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: 500;
            color: #374151;
        }
        input, select, textarea {
            width: 100%;
            padding: 8px 12px;
            border: 1px solid #d1d5db;
            border-radius: 4px;
            font-size: 14px;
            box-sizing: border-box;
        }
        button {
            background-color: #2563eb;
            color: white;
            padding: 10px 20px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
            margin-right: 10px;
            margin-bottom: 10px;
        }
        button:hover {
            background-color: #1d4ed8;
        }
        .response {
            margin-top: 20px;
            padding: 15px;
            background-color: #f3f4f6;
            border-radius: 4px;
            border-left: 4px solid #2563eb;
            white-space: pre-wrap;
            font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
            font-size: 13px;
            line-height: 1.4;
        }
        .error {
            border-left-color: #ef4444;
            background-color: #fef2f2;
            color: #991b1b;
        }
        .success {
            border-left-color: #10b981;
            background-color: #f0fdf4;
            color: #065f46;
        }
        .json-input {
            height: 120px;
            font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
            font-size: 13px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>🌤️ MCP Weather Server Test Interface</h1>
        <p class="subtitle">Test the Model Context Protocol (MCP) server with weather tools</p>

        <div class="section">
            <h3>🔧 Server Status</h3>
            <button onclick="checkHealth()">Check Health</button>
            <button onclick="getCapabilities()">Get Capabilities</button>
            <button onclick="listTools()">List Tools</button>
            <div id="statusResponse" class="response" style="display: none;"></div>
        </div>

        <div class="section">
            <h3>🚨 Weather Alerts</h3>
            <div class="form-group">
                <label for="stateSelect">Select State:</label>
                <select id="stateSelect">
                    <option value="CA">California (CA)</option>
                    <option value="NY">New York (NY)</option>
                    <option value="TX">Texas (TX)</option>
                    <option value="FL">Florida (FL)</option>
                    <option value="WA">Washington (WA)</option>
                    <option value="IL">Illinois (IL)</option>
                    <option value="PA">Pennsylvania (PA)</option>
                    <option value="OH">Ohio (OH)</option>
                    <option value="GA">Georgia (GA)</option>
                    <option value="NC">North Carolina (NC)</option>
                </select>
            </div>
            <button onclick="getAlerts()">Get Weather Alerts</button>
            <div id="alertsResponse" class="response" style="display: none;"></div>
        </div>

        <div class="section">
            <h3>🌡️ Weather Forecast</h3>
            <div class="form-group">
                <label for="latitude">Latitude:</label>
                <input type="number" id="latitude" value="37.7749" step="any" placeholder="e.g., 37.7749">
            </div>
            <div class="form-group">
                <label for="longitude">Longitude:</label>
                <input type="number" id="longitude" value="-122.4194" step="any" placeholder="e.g., -122.4194">
            </div>
            <button onclick="getForecast()">Get Weather Forecast</button>
            <div id="forecastResponse" class="response" style="display: none;"></div>
        </div>

        <div class="section">
            <h3>🔍 Custom MCP Request</h3>
            <div class="form-group">
                <label for="customRequest">JSON-RPC Request:</label>
                <textarea id="customRequest" class="json-input" placeholder='{"jsonrpc": "2.0", "method": "tools/list", "id": 1}'></textarea>
            </div>
            <button onclick="sendCustomRequest()">Send Request</button>
            <div id="customResponse" class="response" style="display: none;"></div>
        </div>
    </div>

    <script>
        async function makeRequest(url, data) {
            try {
                const response = await fetch(url, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify(data)
                });
                return await response.json();
            } catch (error) {
                throw new Error(`Network error: ${error.message}`);
            }
        }

        async function makeGetRequest(url) {
            try {
                const response = await fetch(url);
                return await response.json();
            } catch (error) {
                throw new Error(`Network error: ${error.message}`);
            }
        }

        function showResponse(elementId, data, isError = false) {
            const element = document.getElementById(elementId);
            element.style.display = 'block';
            element.className = `response ${isError ? 'error' : 'success'}`;
            element.textContent = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
        }

        async function checkHealth() {
            try {
                const data = await makeGetRequest('/health');
                showResponse('statusResponse', data);
            } catch (error) {
                showResponse('statusResponse', error.message, true);
            }
        }

        async function getCapabilities() {
            try {
                const data = await makeGetRequest('/mcp/capabilities');
                showResponse('statusResponse', data);
            } catch (error) {
                showResponse('statusResponse', error.message, true);
            }
        }

        async function listTools() {
            try {
                const request = {
                    jsonrpc: "2.0",
                    method: "tools/list",
                    id: 1
                };
                const data = await makeRequest('/mcp/stream', request);
                showResponse('statusResponse', data);
            } catch (error) {
                showResponse('statusResponse', error.message, true);
            }
        }

        async function getAlerts() {
            try {
                const state = document.getElementById('stateSelect').value;
                const request = {
                    jsonrpc: "2.0",
                    method: "tools/call",
                    params: {
                        name: "get_alerts",
                        arguments: { state: state }
                    },
                    id: 2
                };
                const data = await makeRequest('/mcp/stream', request);
                showResponse('alertsResponse', data);
            } catch (error) {
                showResponse('alertsResponse', error.message, true);
            }
        }

        async function getForecast() {
            try {
                const latitude = parseFloat(document.getElementById('latitude').value);
                const longitude = parseFloat(document.getElementById('longitude').value);
                
                if (isNaN(latitude) || isNaN(longitude)) {
                    throw new Error('Please enter valid latitude and longitude values');
                }
                
                const request = {
                    jsonrpc: "2.0",
                    method: "tools/call",
                    params: {
                        name: "get_forecast",
                        arguments: { latitude: latitude, longitude: longitude }
                    },
                    id: 3
                };
                const data = await makeRequest('/mcp/stream', request);
                showResponse('forecastResponse', data);
            } catch (error) {
                showResponse('forecastResponse', error.message, true);
            }
        }

        async function sendCustomRequest() {
            try {
                const requestText = document.getElementById('customRequest').value;
                if (!requestText.trim()) {
                    throw new Error('Please enter a JSON-RPC request');
                }
                
                const request = JSON.parse(requestText);
                const data = await makeRequest('/mcp/stream', request);
                showResponse('customResponse', data);
            } catch (error) {
                showResponse('customResponse', error.message, true);
            }
        }

        // Load initial status on page load
        window.onload = function() {
            checkHealth();
        };
    </script>
</body>
</html>

```