#
tokens: 13866/50000 16/16 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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:
--------------------------------------------------------------------------------

```
1 | 22
2 | 
```

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

```
 1 | # Example environment variables for the MCP Server
 2 | # Copy this file to .env and update the values as needed
 3 | 
 4 | # Server Configuration
 5 | PORT=8000
 6 | NODE_ENV=development
 7 | 
 8 | # CORS Configuration (optional - defaults to allow all origins in development)
 9 | # ALLOWED_ORIGINS=http://localhost:3000,https://yourdomain.com
10 | 
11 | # Logging Level (optional - defaults to 'info')
12 | # LOG_LEVEL=debug
13 | 
```

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

```
 1 | # Azure Developer CLI ignore file
 2 | # Files and directories to ignore during deployment
 3 | 
 4 | # Development files
 5 | .env
 6 | .env.local
 7 | .env.development
 8 | .env.test
 9 | .env.production
10 | 
11 | # Node.js
12 | node_modules/
13 | npm-debug.log*
14 | yarn-debug.log*
15 | yarn-error.log*
16 | 
17 | # Runtime data
18 | pids
19 | *.pid
20 | *.seed
21 | *.pid.lock
22 | 
23 | # Coverage directory used by tools like istanbul
24 | coverage/
25 | *.lcov
26 | 
27 | # nyc test coverage
28 | .nyc_output
29 | 
30 | # Dependency directories
31 | node_modules/
32 | jspm_packages/
33 | 
34 | # Optional npm cache directory
35 | .npm
36 | 
37 | # Optional REPL history
38 | .node_repl_history
39 | 
40 | # Output of 'npm pack'
41 | *.tgz
42 | 
43 | # Yarn Integrity file
44 | .yarn-integrity
45 | 
46 | # dotenv environment variables file
47 | .env
48 | .env.test
49 | 
50 | # parcel-bundler cache (https://parceljs.org/)
51 | .cache
52 | .parcel-cache
53 | 
54 | # VS Code
55 | .vscode/
56 | .history/
57 | 
58 | # Git
59 | .git/
60 | .gitignore
61 | 
62 | # Azure Dev CLI
63 | .azure/
64 | 
65 | # Development and testing
66 | test-client.js
67 | public/test.html
68 | start_server.ps1
69 | start_server.bat
70 | 
71 | # Documentation (optional - remove if you want to include docs)
72 | README.md
73 | DEPLOY.md
74 | 
75 | # Windows
76 | Thumbs.db
77 | ehthumbs.db
78 | Desktop.ini
79 | $RECYCLE.BIN/
80 | 
81 | # macOS
82 | .DS_Store
83 | .AppleDouble
84 | .LSOverride
85 | Icon
86 | ._*
87 | 
88 | # Linux
89 | *~
90 | 
```

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

```
  1 | # Logs
  2 | logs
  3 | *.log
  4 | npm-debug.log*
  5 | yarn-debug.log*
  6 | yarn-error.log*
  7 | lerna-debug.log*
  8 | .pnpm-debug.log*
  9 | 
 10 | # Diagnostic reports (https://nodejs.org/api/report.html)
 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
 12 | 
 13 | # Runtime data
 14 | pids
 15 | *.pid
 16 | *.seed
 17 | *.pid.lock
 18 | 
 19 | # Directory for instrumented libs generated by jscoverage/JSCover
 20 | lib-cov
 21 | 
 22 | # Coverage directory used by tools like istanbul
 23 | coverage
 24 | *.lcov
 25 | 
 26 | # nyc test coverage
 27 | .nyc_output
 28 | 
 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
 30 | .grunt
 31 | 
 32 | # Bower dependency directory (https://bower.io/)
 33 | bower_components
 34 | 
 35 | # node-waf configuration
 36 | .lock-wscript
 37 | 
 38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
 39 | build/Release
 40 | 
 41 | # Dependency directories
 42 | node_modules/
 43 | jspm_packages/
 44 | 
 45 | # Snowpack dependency directory (https://snowpack.dev/)
 46 | web_modules/
 47 | 
 48 | # TypeScript cache
 49 | *.tsbuildinfo
 50 | 
 51 | # Optional npm cache directory
 52 | .npm
 53 | 
 54 | # Optional eslint cache
 55 | .eslintcache
 56 | 
 57 | # Optional stylelint cache
 58 | .stylelintcache
 59 | 
 60 | # Microbundle cache
 61 | .rpt2_cache/
 62 | .rts2_cache_cjs/
 63 | .rts2_cache_es/
 64 | .rts2_cache_umd/
 65 | 
 66 | # Optional REPL history
 67 | .node_repl_history
 68 | 
 69 | # Output of 'npm pack'
 70 | *.tgz
 71 | 
 72 | # Yarn Integrity file
 73 | .yarn-integrity
 74 | 
 75 | # dotenv environment variable files
 76 | .env
 77 | .env.development.local
 78 | .env.test.local
 79 | .env.production.local
 80 | .env.local
 81 | 
 82 | # parcel-bundler cache (https://parceljs.org/)
 83 | .cache
 84 | .parcel-cache
 85 | 
 86 | # Next.js build output
 87 | .next
 88 | out
 89 | 
 90 | # Nuxt.js build / generate output
 91 | .nuxt
 92 | dist
 93 | 
 94 | # Gatsby files
 95 | .cache/
 96 | # Comment in the public line in if your project uses Gatsby and not Next.js
 97 | # https://nextjs.org/blog/next-9-1#public-directory-support
 98 | # public
 99 | 
100 | # vuepress build output
101 | .vuepress/dist
102 | 
103 | # vuepress v2.x temp and cache directory
104 | .temp
105 | .cache
106 | 
107 | # Docusaurus cache and generated files
108 | .docusaurus
109 | 
110 | # Serverless directories
111 | .serverless/
112 | 
113 | # FuseBox cache
114 | .fusebox/
115 | 
116 | # DynamoDB Local files
117 | .dynamodb/
118 | 
119 | # TernJS port file
120 | .tern-port
121 | 
122 | # Stores VSCode versions used for testing VSCode extensions
123 | .vscode-test
124 | 
125 | # yarn v2
126 | .yarn/cache
127 | .yarn/unplugged
128 | .yarn/build-state.yml
129 | .yarn/install-state.gz
130 | .pnp.*
131 | 
132 | # Azure deployment
133 | .azure/
134 | 
```

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

```markdown
  1 | # Node.js MCP Weather Server with Azure Deployment
  2 | 
  3 | 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).
  4 | 
  5 | ## 🌟 Features
  6 | 
  7 | - **Express.js Framework**: Fast, unopinionated web framework for Node.js
  8 | - **MCP Protocol Compliance**: Full support for JSON-RPC 2.0 MCP protocol
  9 | - **HTTP Transport**: HTTP-based communication for web connectivity
 10 | - **Weather Tools**:
 11 |   - `get_alerts`: Get weather alerts for any US state
 12 |   - `get_forecast`: Get detailed weather forecast for any location
 13 | - **Azure Ready**: Pre-configured for Azure App Service deployment
 14 | - **Web Test Interface**: Built-in HTML interface for testing
 15 | - **National Weather Service API**: Real-time weather data from official US government source
 16 | 
 17 | ## 💻 Local Development
 18 | 
 19 | ### Prerequisites
 20 | - **Node.js 22+** (or Node.js 18+)
 21 | - **npm** (Node Package Manager)
 22 | 
 23 | ### Setup & Run
 24 | 
 25 | 1. **Clone and install dependencies**:
 26 |    ```bash
 27 |    git clone <your-repo-url>
 28 |    cd remote-mcp-webapp-node
 29 |    npm install
 30 |    ```
 31 | 
 32 | 2. **Start the development server**:
 33 |    ```bash
 34 |    npm run dev
 35 |    ```
 36 | 
 37 | 3. **Access the server**:
 38 |    - Server: http://localhost:8000
 39 |    - Health Check: http://localhost:8000/health
 40 |    - Test Interface: http://localhost:8000/test
 41 | 
 42 | ## 🔌 Connect to the Local MCP Server
 43 | 
 44 | ### Using VS Code - Copilot Agent Mode
 45 | 
 46 | 1. **Add MCP Server** from command palette and add the URL to your running server's HTTP endpoint:
 47 |    ```
 48 |    http://localhost:8000
 49 |    ```
 50 | 2. **List MCP Servers** from command palette and start the server
 51 | 3. In Copilot chat agent mode, enter a prompt to trigger the tool:
 52 |    ```
 53 |    What's the weather forecast for San Francisco?
 54 |    ```
 55 | 4. When prompted to run the tool, consent by clicking **Continue**
 56 | 
 57 | ### Using MCP Inspector
 58 | 
 59 | 1. In a **new terminal window**, install and run MCP Inspector:
 60 |    ```bash
 61 |    npx @modelcontextprotocol/inspector
 62 |    ```
 63 | 2. CTRL+click the URL displayed by the app (e.g. http://localhost:5173/#resources)
 64 | 3. Set the transport type to `HTTP`
 65 | 4. Set the URL to your running server's HTTP endpoint and **Connect**:
 66 |    ```
 67 |    http://localhost:8000
 68 |    ```
 69 | 5. **List Tools**, click on a tool, and **Run Tool**
 70 | 
 71 | ## 🚀 Quick Deploy to Azure
 72 | 
 73 | ### Prerequisites
 74 | - [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli)
 75 | - [Azure Developer CLI (azd)](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd)
 76 | - Active Azure subscription
 77 | 
 78 | ### Deploy in 3 Commands
 79 | 
 80 | ```bash
 81 | # 1. Login to Azure
 82 | azd auth login
 83 | 
 84 | # 2. Initialize the project
 85 | azd init
 86 | 
 87 | # 3. Deploy to Azure
 88 | azd up
 89 | ```
 90 | 
 91 | After deployment, your MCP server will be available at:
 92 | - **Health Check**: `https://<your-app>.azurewebsites.net/health`
 93 | - **MCP Capabilities**: `https://<your-app>.azurewebsites.net/mcp/capabilities`
 94 | - **Test Interface**: `https://<your-app>.azurewebsites.net/test`
 95 | 
 96 | ## 🔌 Connect to the Remote MCP Server
 97 | 
 98 | Follow the same guidance as above, but use your App Service URL instead.
 99 | 
100 | ## 🧪 Testing
101 | 
102 | Visit `/test` endpoint for an interactive testing interface.
103 | 
104 | ## 🌦️ Data Source
105 | 
106 | This server uses the **National Weather Service (NWS) API**:
107 | - Real-time weather alerts and warnings
108 | - Detailed weather forecasts
109 | - Official US government weather data  
110 | - No API key required
111 | - High reliability and accuracy
```

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

```yaml
1 | name: remote-mcp-webapp-node
2 | metadata:
3 |   template: [email protected]
4 | services:
5 |   web:
6 |     project: .
7 |     language: js
8 |     host: appservice
9 | 
```

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

```json
 1 | {
 2 |   "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
 3 |   "contentVersion": "1.0.0.0",
 4 |   "parameters": {
 5 |     "environmentName": {
 6 |       "value": "${AZURE_ENV_NAME}"
 7 |     },
 8 |     "location": {
 9 |       "value": "${AZURE_LOCATION}"
10 |     },
11 |     "principalId": {
12 |       "value": "${AZURE_PRINCIPAL_ID}"
13 |     }
14 |   }
15 | }
16 | 
```

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

```json
 1 | {
 2 | 	"version": "2.0.0",
 3 | 	"tasks": [
 4 | 		{
 5 | 			"label": "Start MCP Weather Server",
 6 | 			"type": "shell",
 7 | 			"command": "npm",
 8 | 			"args": [
 9 | 				"run",
10 | 				"dev"
11 | 			],
12 | 			"group": "build",
13 | 			"isBackground": true,
14 | 			"problemMatcher": [],
15 | 			"presentation": {
16 | 				"echo": true,
17 | 				"reveal": "always",
18 | 				"focus": false,
19 | 				"panel": "shared"
20 | 			}
21 | 		}
22 | 	]
23 | }
```

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

```json
 1 | {
 2 |   "mcpServers": {
 3 |     "weather-mcp-server-local": {
 4 |       "transport": {
 5 |         "type": "http",
 6 |         "url": "http://localhost:8000/mcp/stream"
 7 |       },
 8 |       "name": "Weather MCP Server (Local - Node.js)",
 9 |       "description": "MCP Server with weather forecast and alerts tools running locally on Node.js"
10 |     },
11 |     "weather-mcp-server-azure": {
12 |       "transport": {
13 |         "type": "http",
14 |         "url": "https://<APP-SERVICE-NAME>.azurewebsites.net/mcp/stream"
15 |       },
16 |       "name": "Weather MCP Server (Azure - Node.js)",
17 |       "description": "MCP Server with weather forecast and alerts tools hosted on Azure with Node.js"
18 |     }
19 |   }
20 | }
21 | 
```

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

```json
 1 | {
 2 |   "name": "remote-mcp-webapp-node",
 3 |   "version": "1.0.0",
 4 |   "main": "server.js",
 5 |   "description": "A Model Context Protocol (MCP) server built with Express.js that provides weather information using the National Weather Service API",
 6 |   "scripts": {
 7 |     "start": "node server.js",
 8 |     "dev": "nodemon server.js",
 9 |     "test": "node test-client.js",
10 |     "test:azure": "node test-client.js --url=https://<APP-SERVICE-NAME>.azurewebsites.net",
11 |     "build": "echo 'No build step required for this Node.js app'"
12 |   },
13 |   "keywords": [
14 |     "mcp",
15 |     "model-context-protocol", 
16 |     "weather",
17 |     "api",
18 |     "express",
19 |     "nodejs",
20 |     "jsonrpc"
21 |   ],
22 |   "author": "",
23 |   "license": "MIT",
24 |   "engines": {
25 |     "node": ">=22.0.0"
26 |   },
27 |   "dependencies": {
28 |     "axios": "^1.9.0",
29 |     "cors": "^2.8.5",
30 |     "dotenv": "^16.5.0",
31 |     "express": "^5.1.0",
32 |     "helmet": "^8.1.0",
33 |     "uuid": "^11.1.0"
34 |   },
35 |   "devDependencies": {
36 |     "nodemon": "^3.1.10"
37 |   }
38 | }
39 | 
```

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

```
 1 | <?xml version="1.0" encoding="utf-8"?>
 2 | <configuration>
 3 |   <system.webServer>
 4 |     <handlers>
 5 |       <add name="iisnode" path="server.js" verb="*" modules="iisnode"/>
 6 |     </handlers>
 7 |     <rewrite>
 8 |       <rules>
 9 |         <rule name="NodeInspector" patternSyntax="ECMAScript" stopProcessing="true">
10 |           <match url="^server.js\/debug[\/]?" />
11 |         </rule>
12 |         <rule name="StaticContent">
13 |           <action type="Rewrite" url="public{REQUEST_URI}"/>
14 |         </rule>
15 |         <rule name="DynamicContent">
16 |           <conditions>
17 |             <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="True"/>
18 |           </conditions>
19 |           <action type="Rewrite" url="server.js"/>
20 |         </rule>
21 |       </rules>
22 |     </rewrite>
23 |     <security>
24 |       <requestFiltering>
25 |         <hiddenSegments>
26 |           <remove segment="bin"/>
27 |         </hiddenSegments>
28 |       </requestFiltering>
29 |     </security>
30 |     <httpErrors existingResponse="PassThrough" />
31 |     <iisnode watchedFiles="web.config;*.js"/>
32 |   </system.webServer>
33 | </configuration>
34 | 
```

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

```markdown
 1 | <!-- 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 -->
 2 | 
 3 | # MCP Server Development Guidelines
 4 | 
 5 | This is an MCP (Model Context Protocol) server project built with Express.js and Node.js.
 6 | 
 7 | ## Key Context
 8 | 
 9 | - This project implements the MCP protocol using JSON-RPC 2.0
10 | - The server provides weather-related tools using the National Weather Service API
11 | - Main endpoints follow MCP specification for tools, resources, and capabilities
12 | - Use Express.js patterns and Node.js best practices
13 | - Weather data comes from official US government APIs (no API key required)
14 | 
15 | ## Code Style Guidelines
16 | 
17 | - Use modern JavaScript (ES6+) features where appropriate
18 | - Follow Express.js conventions for routing and middleware
19 | - Implement proper error handling with try-catch blocks
20 | - Use consistent JSON-RPC 2.0 response formats
21 | - Include comprehensive logging for debugging
22 | - Maintain backward compatibility with the MCP protocol
23 | 
24 | ## Architecture Notes
25 | 
26 | - `server.js` contains the main Express application and MCP protocol implementation
27 | - Weather tools are implemented as async functions that call NWS APIs
28 | - HTTP transport is used for MCP Inspector connectivity
29 | - Web test interface provides manual testing capabilities
30 | 
31 | ## Testing
32 | 
33 | - Use the built-in test client (`test-client.js`) for protocol testing
34 | - Web interface (`/test`) for manual verification
35 | - Ensure all MCP methods return proper JSON-RPC 2.0 responses
36 | 
37 | You can find more info and examples at https://modelcontextprotocol.io/llms-full.txt
38 | 
```

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

```javascript
  1 | const axios = require('axios');
  2 | 
  3 | class MCPTestClient {
  4 |     constructor(baseUrl = 'http://localhost:8000') {
  5 |         this.baseUrl = baseUrl;
  6 |         this.mcpEndpoint = `${baseUrl}/mcp/stream`;
  7 |     }
  8 | 
  9 |     async makeRequest(method, params = {}, id = null) {
 10 |         const request = {
 11 |             jsonrpc: '2.0',
 12 |             method,
 13 |             params,
 14 |             id: id || Math.floor(Math.random() * 1000)
 15 |         };
 16 | 
 17 |         try {
 18 |             console.log(`\n🔄 Sending request: ${method}`);
 19 |             console.log('Request:', JSON.stringify(request, null, 2));
 20 |             
 21 |             const response = await axios.post(this.mcpEndpoint, request);
 22 |             
 23 |             console.log('✅ Response received:');
 24 |             console.log(JSON.stringify(response.data, null, 2));
 25 |             
 26 |             return response.data;
 27 |         } catch (error) {
 28 |             console.error('❌ Error:', error.message);
 29 |             if (error.response) {
 30 |                 console.error('Response data:', error.response.data);
 31 |             }
 32 |             throw error;
 33 |         }
 34 |     }
 35 | 
 36 |     async testInitialize() {
 37 |         console.log('\n=== Testing Initialize ===');
 38 |         return await this.makeRequest('initialize', {});
 39 |     }
 40 | 
 41 |     async testListTools() {
 42 |         console.log('\n=== Testing List Tools ===');
 43 |         return await this.makeRequest('tools/list');
 44 |     }
 45 | 
 46 |     async testGetAlerts(state = 'CA') {
 47 |         console.log('\n=== Testing Weather Alerts ===');
 48 |         return await this.makeRequest('tools/call', {
 49 |             name: 'get_alerts',
 50 |             arguments: { state }
 51 |         });
 52 |     }
 53 | 
 54 |     async testGetForecast(latitude = 37.7749, longitude = -122.4194) {
 55 |         console.log('\n=== Testing Weather Forecast ===');
 56 |         return await this.makeRequest('tools/call', {
 57 |             name: 'get_forecast',
 58 |             arguments: { latitude, longitude }
 59 |         });
 60 |     }
 61 | 
 62 |     async testListResources() {
 63 |         console.log('\n=== Testing List Resources ===');
 64 |         return await this.makeRequest('resources/list');
 65 |     }
 66 | 
 67 |     async testReadResource(uri = 'mcp://server/sample') {
 68 |         console.log('\n=== Testing Read Resource ===');
 69 |         return await this.makeRequest('resources/read', { uri });
 70 |     }
 71 | 
 72 |     async checkHealth() {
 73 |         console.log('\n=== Checking Server Health ===');
 74 |         try {
 75 |             const response = await axios.get(`${this.baseUrl}/health`);
 76 |             console.log('✅ Health check passed:');
 77 |             console.log(JSON.stringify(response.data, null, 2));
 78 |             return response.data;
 79 |         } catch (error) {
 80 |             console.error('❌ Health check failed:', error.message);
 81 |             throw error;
 82 |         }
 83 |     }
 84 | 
 85 |     async runAllTests() {
 86 |         console.log('🚀 Starting MCP Server Tests');
 87 |         console.log('================================');
 88 | 
 89 |         try {
 90 |             // Check server health first
 91 |             await this.checkHealth();
 92 | 
 93 |             // Test MCP protocol methods
 94 |             await this.testInitialize();
 95 |             await this.testListTools();
 96 |             await this.testListResources();
 97 |             await this.testReadResource();
 98 |             
 99 |             // Test weather tools
100 |             await this.testGetAlerts('CA');
101 |             await this.testGetForecast(37.7749, -122.4194); // San Francisco
102 |             
103 |             console.log('\n🎉 All tests completed successfully!');
104 |         } catch (error) {
105 |             console.error('\n💥 Test suite failed:', error.message);
106 |             process.exit(1);
107 |         }
108 |     }
109 | }
110 | 
111 | // Run tests if this file is executed directly
112 | if (require.main === module) {
113 |     const client = new MCPTestClient();
114 |     
115 |     // Parse command line arguments
116 |     const args = process.argv.slice(2);
117 |     const serverUrl = args.find(arg => arg.startsWith('--url='))?.split('=')[1] || 'http://localhost:8000';
118 |     
119 |     if (serverUrl !== 'http://localhost:8000') {
120 |         client.baseUrl = serverUrl;
121 |         client.mcpEndpoint = `${serverUrl}/mcp/stream`;
122 |         console.log(`🌐 Testing server at: ${serverUrl}`);
123 |     }
124 |     
125 |     client.runAllTests();
126 | }
127 | 
128 | module.exports = MCPTestClient;
129 | 
```

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

```json
  1 | {
  2 |   "analysisServicesServers": "as",
  3 |   "apiManagementService": "apim-",
  4 |   "appConfigurationConfigurationStores": "appcs-",
  5 |   "appManagedEnvironments": "cae-",
  6 |   "appContainerApps": "ca-",
  7 |   "authorizationPolicyDefinitions": "policy-",
  8 |   "automationAutomationAccounts": "aa-",
  9 |   "blueprintBlueprints": "bp-",
 10 |   "blueprintBlueprintsArtifacts": "bpa-",
 11 |   "cacheRedis": "redis-",
 12 |   "cdnProfiles": "cdnp-",
 13 |   "cdnProfilesEndpoints": "cdne-",
 14 |   "cognitiveServicesAccounts": "cog-",
 15 |   "cognitiveServicesFormRecognizer": "cog-fr-",
 16 |   "cognitiveServicesTextAnalytics": "cog-ta-",
 17 |   "computeAvailabilitySets": "avail-",
 18 |   "computeCloudServices": "cld-",
 19 |   "computeDiskEncryptionSets": "des-",
 20 |   "computeDisks": "disk-",
 21 |   "computeDisksOs": "osdisk-",
 22 |   "computeGalleries": "gal-",
 23 |   "computeSnapshots": "snap-",
 24 |   "computeVirtualMachines": "vm-",
 25 |   "computeVirtualMachineScaleSets": "vmss-",
 26 |   "containerInstanceContainerGroups": "ci-",
 27 |   "containerRegistryRegistries": "cr-",
 28 |   "containerServiceManagedClusters": "aks-",
 29 |   "databricksWorkspaces": "dbw-",
 30 |   "dataFactoryFactories": "adf-",
 31 |   "dataLakeAnalyticsAccounts": "dla-",
 32 |   "dataLakeStoreAccounts": "dls-",
 33 |   "dataMigrationServices": "dms-",
 34 |   "dBforMySQLServers": "mysql-",
 35 |   "dBforPostgreSQLServers": "psql-",
 36 |   "devicesIotHubs": "iot-",
 37 |   "devicesProvisioningServices": "provs-",
 38 |   "devicesProvisioningServicesCertificates": "pcert-",
 39 |   "documentDBDatabaseAccounts": "cosmos-",
 40 |   "eventGridDomains": "evgd-",
 41 |   "eventGridDomainsTopic": "evgt-",
 42 |   "eventGridEventSubscriptions": "evgs-",
 43 |   "eventHubNamespaces": "evhns-",
 44 |   "eventHubNamespacesEventHubs": "evh-",
 45 |   "hdInsightClustersHadoop": "hadoop-",
 46 |   "hdInsightClustersHbase": "hbase-",
 47 |   "hdInsightClustersKafka": "kafka-",
 48 |   "hdInsightClustersMl": "mls-",
 49 |   "hdInsightClustersSpark": "spark-",
 50 |   "hdInsightClustersStorm": "storm-",
 51 |   "hybridComputeMachines": "arcs-",
 52 |   "insightsActionGroups": "ag-",
 53 |   "insightsComponents": "appi-",
 54 |   "keyVaultVaults": "kv-",
 55 |   "kubernetesConnectedClusters": "arck-",
 56 |   "kustoClusters": "dec-",
 57 |   "loadTesting": "lt-",
 58 |   "logicIntegrationAccounts": "ia-",
 59 |   "logicWorkflows": "logic-",
 60 |   "machineLearningServicesWorkspaces": "mlw-",
 61 |   "managedIdentityUserAssignedIdentities": "id-",
 62 |   "managementManagementGroups": "mg-",
 63 |   "migrateAssessmentProjects": "migr-",
 64 |   "networkApplicationGateways": "agw-",
 65 |   "networkApplicationSecurityGroups": "asg-",
 66 |   "networkAzureFirewalls": "afw-",
 67 |   "networkBastionHosts": "bas-",
 68 |   "networkConnections": "con-",
 69 |   "networkDnsZones": "dnsz-",
 70 |   "networkExpressRouteCircuits": "erc-",
 71 |   "networkFirewallPolicies": "afwp-",
 72 |   "networkFirewallPoliciesWebApplication": "waf-",
 73 |   "networkFirewallPoliciesRuleGroups": "wafrg-",
 74 |   "networkFrontDoors": "fd-",
 75 |   "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-",
 76 |   "networkLoadBalancersExternal": "lbe-",
 77 |   "networkLoadBalancersInternal": "lbi-",
 78 |   "networkLoadBalancersInboundNatRules": "rule-",
 79 |   "networkLocalNetworkGateways": "lgw-",
 80 |   "networkNatGateways": "ng-",
 81 |   "networkNetworkInterfaces": "nic-",
 82 |   "networkNetworkSecurityGroups": "nsg-",
 83 |   "networkNetworkSecurityGroupsSecurityRules": "nsgsr-",
 84 |   "networkNetworkWatchers": "nw-",
 85 |   "networkPrivateDnsZones": "pdnsz-",
 86 |   "networkPrivateLinkServices": "pl-",
 87 |   "networkPublicIPAddresses": "pip-",
 88 |   "networkPublicIPPrefixes": "ippre-",
 89 |   "networkRouteFilters": "rf-",
 90 |   "networkRouteTables": "rt-",
 91 |   "networkRouteTablesRoutes": "udr-",
 92 |   "networkTrafficManagerProfiles": "traf-",
 93 |   "networkVirtualNetworkGateways": "vgw-",
 94 |   "networkVirtualNetworks": "vnet-",
 95 |   "networkVirtualNetworksSubnets": "snet-",
 96 |   "networkVirtualNetworksVirtualNetworkPeerings": "peer-",
 97 |   "networkVirtualWans": "vwan-",
 98 |   "networkVpnGateways": "vpng-",
 99 |   "networkVpnGatewaysVpnConnections": "vcn-",
100 |   "networkVpnGatewaysVpnSites": "vst-",
101 |   "notificationHubsNamespaces": "ntfns-",
102 |   "notificationHubsNamespacesNotificationHubs": "ntf-",
103 |   "operationalInsightsWorkspaces": "log-",
104 |   "portalDashboards": "dash-",
105 |   "powerBIDedicatedCapacities": "pbi-",
106 |   "purviewAccounts": "pview-",
107 |   "recoveryServicesVaults": "rsv-",
108 |   "resourcesResourceGroups": "rg-",
109 |   "searchSearchServices": "srch-",
110 |   "serviceBusNamespaces": "sb-",
111 |   "serviceBusNamespacesQueues": "sbq-",
112 |   "serviceBusNamespacesTopics": "sbt-",
113 |   "serviceEndPointPolicies": "se-",
114 |   "serviceFabricClusters": "sf-",
115 |   "signalRServiceSignalR": "sigr-",
116 |   "sqlManagedInstances": "sqlmi-",
117 |   "sqlServers": "sql-",
118 |   "sqlServersDataWarehouse": "sqldw-",
119 |   "sqlServersDatabase": "sqldb-",
120 |   "sqlServersFirewallRules": "sqlfw-",
121 |   "storageStorageAccounts": "st",
122 |   "storageStorageAccountsVm": "stvm",
123 |   "streamAnalyticsCluster": "asa-",
124 |   "synapseWorkspaces": "syn-",
125 |   "timeSeriesInsightsEnvironments": "tsi-",
126 |   "webServerFarms": "plan-",
127 |   "webSitesAppService": "app-",
128 |   "webSitesAppServiceEnvironment": "ase-",
129 |   "webSitesFunctions": "func-"
130 | }
131 | 
```

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

```javascript
  1 | const express = require('express');
  2 | const cors = require('cors');
  3 | const helmet = require('helmet');
  4 | const axios = require('axios');
  5 | const path = require('path');
  6 | const fs = require('fs');
  7 | const { v4: uuidv4 } = require('uuid');
  8 | 
  9 | const app = express();
 10 | const PORT = process.env.PORT || 8000;
 11 | 
 12 | // Middleware
 13 | app.use(helmet());
 14 | app.use(cors());
 15 | app.use(express.json());
 16 | app.use(express.static('public'));
 17 | 
 18 | // MCP Server Implementation
 19 | class MCPServer {
 20 |     constructor() {
 21 |         this.tools = [
 22 |             {
 23 |                 name: 'get_alerts',
 24 |                 description: 'Get weather alerts for a US state.',
 25 |                 inputSchema: {
 26 |                     type: 'object',
 27 |                     properties: {
 28 |                         state: {
 29 |                             type: 'string',
 30 |                             description: 'Two-letter US state code (e.g. CA, NY)'
 31 |                         }
 32 |                     },
 33 |                     required: ['state']
 34 |                 }
 35 |             },
 36 |             {
 37 |                 name: 'get_forecast',
 38 |                 description: 'Get weather forecast for a location.',
 39 |                 inputSchema: {
 40 |                     type: 'object',
 41 |                     properties: {
 42 |                         latitude: {
 43 |                             type: 'number',
 44 |                             description: 'Latitude of the location'
 45 |                         },
 46 |                         longitude: {
 47 |                             type: 'number',
 48 |                             description: 'Longitude of the location'
 49 |                         }
 50 |                     },
 51 |                     required: ['latitude', 'longitude']
 52 |                 }
 53 |             }
 54 |         ];
 55 |         
 56 |         this.resources = [
 57 |             {
 58 |                 uri: 'mcp://server/sample',
 59 |                 name: 'Sample Resource',
 60 |                 description: 'Sample resource for demonstration',
 61 |                 mimeType: 'text/plain'
 62 |             }
 63 |         ];
 64 |     }
 65 | 
 66 |     async handleRequest(request) {
 67 |         const { jsonrpc, method, params, id } = request;
 68 |         
 69 |         if (jsonrpc !== '2.0') {
 70 |             return this.createErrorResponse(id, -32600, 'Invalid Request');
 71 |         }
 72 | 
 73 |         try {
 74 |             switch (method) {
 75 |                 case 'initialize':
 76 |                     return this.handleInitialize(id, params);
 77 |                 case 'tools/list':
 78 |                     return this.handleToolsList(id);
 79 |                 case 'tools/call':
 80 |                     return await this.handleToolsCall(id, params);
 81 |                 case 'resources/list':
 82 |                     return this.handleResourcesList(id);
 83 |                 case 'resources/read':
 84 |                     return this.handleResourcesRead(id, params);
 85 |                 default:
 86 |                     return this.createErrorResponse(id, -32601, 'Method not found');
 87 |             }
 88 |         } catch (error) {
 89 |             console.error('Error handling request:', error);
 90 |             return this.createErrorResponse(id, -32603, 'Internal error');
 91 |         }
 92 |     }
 93 | 
 94 |     handleInitialize(id, params) {
 95 |         return {
 96 |             jsonrpc: '2.0',
 97 |             id,
 98 |             result: {
 99 |                 protocolVersion: '2024-11-05',
100 |                 capabilities: {
101 |                     tools: {},
102 |                     resources: {}
103 |                 },
104 |                 serverInfo: {
105 |                     name: 'Weather MCP Server (Node.js)',
106 |                     version: '1.0.0'
107 |                 }
108 |             }
109 |         };
110 |     }
111 | 
112 |     handleToolsList(id) {
113 |         return {
114 |             jsonrpc: '2.0',
115 |             id,
116 |             result: {
117 |                 tools: this.tools
118 |             }
119 |         };
120 |     }
121 | 
122 |     async handleToolsCall(id, params) {
123 |         const { name, arguments: args } = params;
124 |         
125 |         try {
126 |             let result;
127 |             switch (name) {
128 |                 case 'get_alerts':
129 |                     result = await this.getAlerts(args.state);
130 |                     break;
131 |                 case 'get_forecast':
132 |                     result = await this.getForecast(args.latitude, args.longitude);
133 |                     break;
134 |                 default:
135 |                     return this.createErrorResponse(id, -32602, 'Invalid tool name');
136 |             }
137 |             
138 |             return {
139 |                 jsonrpc: '2.0',
140 |                 id,
141 |                 result: {
142 |                     content: [
143 |                         {
144 |                             type: 'text',
145 |                             text: result
146 |                         }
147 |                     ]
148 |                 }
149 |             };
150 |         } catch (error) {
151 |             return this.createErrorResponse(id, -32603, `Tool execution error: ${error.message}`);
152 |         }
153 |     }
154 | 
155 |     handleResourcesList(id) {
156 |         return {
157 |             jsonrpc: '2.0',
158 |             id,
159 |             result: {
160 |                 resources: this.resources
161 |             }
162 |         };
163 |     }
164 | 
165 |     handleResourcesRead(id, params) {
166 |         const { uri } = params;
167 |         
168 |         if (uri === 'mcp://server/sample') {
169 |             return {
170 |                 jsonrpc: '2.0',
171 |                 id,
172 |                 result: {
173 |                     contents: [
174 |                         {
175 |                             uri,
176 |                             mimeType: 'text/plain',
177 |                             text: 'This is a sample resource from the MCP server.'
178 |                         }
179 |                     ]
180 |                 }
181 |             };
182 |         }
183 |         
184 |         return this.createErrorResponse(id, -32602, 'Resource not found');
185 |     }
186 | 
187 |     async getAlerts(state) {
188 |         try {
189 |             const response = await axios.get(`https://api.weather.gov/alerts/active?area=${state.toUpperCase()}`);
190 |             const alerts = response.data.features;
191 |             
192 |             if (alerts.length === 0) {
193 |                 return `No active weather alerts for ${state.toUpperCase()}.`;
194 |             }
195 |             
196 |             let alertsText = `Active weather alerts for ${state.toUpperCase()}:\n\n`;
197 |             alerts.forEach((alert, index) => {
198 |                 const properties = alert.properties;
199 |                 alertsText += `${index + 1}. ${properties.headline}\n`;
200 |                 alertsText += `   Severity: ${properties.severity}\n`;
201 |                 alertsText += `   Urgency: ${properties.urgency}\n`;
202 |                 alertsText += `   Event: ${properties.event}\n`;
203 |                 alertsText += `   Description: ${properties.description}\n`;
204 |                 if (properties.instruction) {
205 |                     alertsText += `   Instructions: ${properties.instruction}\n`;
206 |                 }
207 |                 alertsText += '\n';
208 |             });
209 |             
210 |             return alertsText;
211 |         } catch (error) {
212 |             throw new Error(`Failed to fetch weather alerts: ${error.message}`);
213 |         }
214 |     }
215 | 
216 |     async getForecast(latitude, longitude) {
217 |         try {
218 |             // First, get the grid point
219 |             const pointResponse = await axios.get(`https://api.weather.gov/points/${latitude},${longitude}`);
220 |             const forecastUrl = pointResponse.data.properties.forecast;
221 |             
222 |             // Then get the forecast
223 |             const forecastResponse = await axios.get(forecastUrl);
224 |             const periods = forecastResponse.data.properties.periods;
225 |             
226 |             let forecastText = `Weather forecast for ${latitude}, ${longitude}:\n\n`;
227 |             periods.slice(0, 10).forEach((period, index) => {
228 |                 forecastText += `${period.name}:\n`;
229 |                 forecastText += `  Temperature: ${period.temperature}°${period.temperatureUnit}\n`;
230 |                 forecastText += `  Wind: ${period.windSpeed} ${period.windDirection}\n`;
231 |                 forecastText += `  Forecast: ${period.detailedForecast}\n\n`;
232 |             });
233 |             
234 |             return forecastText;
235 |         } catch (error) {
236 |             throw new Error(`Failed to fetch weather forecast: ${error.message}`);
237 |         }
238 |     }
239 | 
240 |     createErrorResponse(id, code, message) {
241 |         return {
242 |             jsonrpc: '2.0',
243 |             id,
244 |             error: {
245 |                 code,
246 |                 message
247 |             }
248 |         };
249 |     }
250 | }
251 | 
252 | const mcpServer = new MCPServer();
253 | 
254 | // Routes
255 | app.get('/health', (req, res) => {
256 |     res.json({ status: 'healthy', timestamp: new Date().toISOString() });
257 | });
258 | 
259 | app.get('/mcp/capabilities', (req, res) => {
260 |     res.json({
261 |         protocolVersion: '2024-11-05',
262 |         capabilities: {
263 |             tools: {},
264 |             resources: {}
265 |         },
266 |         serverInfo: {
267 |             name: 'Weather MCP Server (Node.js)',
268 |             version: '1.0.0'
269 |         }
270 |     });
271 | });
272 | 
273 | // Main MCP endpoint with streamable HTTP
274 | app.post('/mcp/stream', async (req, res) => {
275 |     try {
276 |         const response = await mcpServer.handleRequest(req.body);
277 |         res.json(response);
278 |     } catch (error) {
279 |         console.error('MCP Stream Error:', error);
280 |         res.status(500).json({
281 |             jsonrpc: '2.0',
282 |             id: req.body.id || null,
283 |             error: {
284 |                 code: -32603,
285 |                 message: 'Internal error'
286 |             }
287 |         });
288 |     }
289 | });
290 | 
291 | // Legacy MCP endpoint
292 | app.post('/mcp', async (req, res) => {
293 |     try {
294 |         const response = await mcpServer.handleRequest(req.body);
295 |         res.json(response);
296 |     } catch (error) {
297 |         console.error('MCP Error:', error);
298 |         res.status(500).json({
299 |             jsonrpc: '2.0',
300 |             id: req.body.id || null,
301 |             error: {
302 |                 code: -32603,
303 |                 message: 'Internal error'
304 |             }
305 |         });
306 |     }
307 | });
308 | 
309 | // Web test interface
310 | app.get('/test', (req, res) => {
311 |     res.sendFile(path.join(__dirname, 'public', 'test.html'));
312 | });
313 | 
314 | // Start server
315 | app.listen(PORT, '0.0.0.0', () => {
316 |     console.log(`MCP Weather Server running on http://localhost:${PORT}`);
317 |     console.log(`Health check: http://localhost:${PORT}/health`);
318 |     console.log(`MCP Endpoint: http://localhost:${PORT}/mcp/stream`);
319 |     console.log(`Web Test Interface: http://localhost:${PORT}/test`);
320 |     console.log(`API Documentation: http://localhost:${PORT}/mcp/capabilities`);
321 | });
322 | 
```

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

```html
  1 | <!DOCTYPE html>
  2 | <html lang="en">
  3 | <head>
  4 |     <meta charset="UTF-8">
  5 |     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6 |     <title>MCP Weather Server - Test Interface</title>
  7 |     <style>
  8 |         body {
  9 |             font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
 10 |             max-width: 1200px;
 11 |             margin: 0 auto;
 12 |             padding: 20px;
 13 |             background-color: #f5f5f5;
 14 |         }
 15 |         .container {
 16 |             background: white;
 17 |             border-radius: 8px;
 18 |             padding: 30px;
 19 |             box-shadow: 0 2px 10px rgba(0,0,0,0.1);
 20 |         }
 21 |         h1 {
 22 |             color: #2563eb;
 23 |             margin-bottom: 10px;
 24 |         }
 25 |         .subtitle {
 26 |             color: #6b7280;
 27 |             margin-bottom: 30px;
 28 |         }
 29 |         .section {
 30 |             margin-bottom: 30px;
 31 |             padding: 20px;
 32 |             border: 1px solid #e5e7eb;
 33 |             border-radius: 6px;
 34 |             background-color: #f9fafb;
 35 |         }
 36 |         .section h3 {
 37 |             margin-top: 0;
 38 |             color: #374151;
 39 |         }
 40 |         .form-group {
 41 |             margin-bottom: 15px;
 42 |         }
 43 |         label {
 44 |             display: block;
 45 |             margin-bottom: 5px;
 46 |             font-weight: 500;
 47 |             color: #374151;
 48 |         }
 49 |         input, select, textarea {
 50 |             width: 100%;
 51 |             padding: 8px 12px;
 52 |             border: 1px solid #d1d5db;
 53 |             border-radius: 4px;
 54 |             font-size: 14px;
 55 |             box-sizing: border-box;
 56 |         }
 57 |         button {
 58 |             background-color: #2563eb;
 59 |             color: white;
 60 |             padding: 10px 20px;
 61 |             border: none;
 62 |             border-radius: 4px;
 63 |             cursor: pointer;
 64 |             font-size: 14px;
 65 |             margin-right: 10px;
 66 |             margin-bottom: 10px;
 67 |         }
 68 |         button:hover {
 69 |             background-color: #1d4ed8;
 70 |         }
 71 |         .response {
 72 |             margin-top: 20px;
 73 |             padding: 15px;
 74 |             background-color: #f3f4f6;
 75 |             border-radius: 4px;
 76 |             border-left: 4px solid #2563eb;
 77 |             white-space: pre-wrap;
 78 |             font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
 79 |             font-size: 13px;
 80 |             line-height: 1.4;
 81 |         }
 82 |         .error {
 83 |             border-left-color: #ef4444;
 84 |             background-color: #fef2f2;
 85 |             color: #991b1b;
 86 |         }
 87 |         .success {
 88 |             border-left-color: #10b981;
 89 |             background-color: #f0fdf4;
 90 |             color: #065f46;
 91 |         }
 92 |         .json-input {
 93 |             height: 120px;
 94 |             font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
 95 |             font-size: 13px;
 96 |         }
 97 |     </style>
 98 | </head>
 99 | <body>
100 |     <div class="container">
101 |         <h1>🌤️ MCP Weather Server Test Interface</h1>
102 |         <p class="subtitle">Test the Model Context Protocol (MCP) server with weather tools</p>
103 | 
104 |         <div class="section">
105 |             <h3>🔧 Server Status</h3>
106 |             <button onclick="checkHealth()">Check Health</button>
107 |             <button onclick="getCapabilities()">Get Capabilities</button>
108 |             <button onclick="listTools()">List Tools</button>
109 |             <div id="statusResponse" class="response" style="display: none;"></div>
110 |         </div>
111 | 
112 |         <div class="section">
113 |             <h3>🚨 Weather Alerts</h3>
114 |             <div class="form-group">
115 |                 <label for="stateSelect">Select State:</label>
116 |                 <select id="stateSelect">
117 |                     <option value="CA">California (CA)</option>
118 |                     <option value="NY">New York (NY)</option>
119 |                     <option value="TX">Texas (TX)</option>
120 |                     <option value="FL">Florida (FL)</option>
121 |                     <option value="WA">Washington (WA)</option>
122 |                     <option value="IL">Illinois (IL)</option>
123 |                     <option value="PA">Pennsylvania (PA)</option>
124 |                     <option value="OH">Ohio (OH)</option>
125 |                     <option value="GA">Georgia (GA)</option>
126 |                     <option value="NC">North Carolina (NC)</option>
127 |                 </select>
128 |             </div>
129 |             <button onclick="getAlerts()">Get Weather Alerts</button>
130 |             <div id="alertsResponse" class="response" style="display: none;"></div>
131 |         </div>
132 | 
133 |         <div class="section">
134 |             <h3>🌡️ Weather Forecast</h3>
135 |             <div class="form-group">
136 |                 <label for="latitude">Latitude:</label>
137 |                 <input type="number" id="latitude" value="37.7749" step="any" placeholder="e.g., 37.7749">
138 |             </div>
139 |             <div class="form-group">
140 |                 <label for="longitude">Longitude:</label>
141 |                 <input type="number" id="longitude" value="-122.4194" step="any" placeholder="e.g., -122.4194">
142 |             </div>
143 |             <button onclick="getForecast()">Get Weather Forecast</button>
144 |             <div id="forecastResponse" class="response" style="display: none;"></div>
145 |         </div>
146 | 
147 |         <div class="section">
148 |             <h3>🔍 Custom MCP Request</h3>
149 |             <div class="form-group">
150 |                 <label for="customRequest">JSON-RPC Request:</label>
151 |                 <textarea id="customRequest" class="json-input" placeholder='{"jsonrpc": "2.0", "method": "tools/list", "id": 1}'></textarea>
152 |             </div>
153 |             <button onclick="sendCustomRequest()">Send Request</button>
154 |             <div id="customResponse" class="response" style="display: none;"></div>
155 |         </div>
156 |     </div>
157 | 
158 |     <script>
159 |         async function makeRequest(url, data) {
160 |             try {
161 |                 const response = await fetch(url, {
162 |                     method: 'POST',
163 |                     headers: {
164 |                         'Content-Type': 'application/json',
165 |                     },
166 |                     body: JSON.stringify(data)
167 |                 });
168 |                 return await response.json();
169 |             } catch (error) {
170 |                 throw new Error(`Network error: ${error.message}`);
171 |             }
172 |         }
173 | 
174 |         async function makeGetRequest(url) {
175 |             try {
176 |                 const response = await fetch(url);
177 |                 return await response.json();
178 |             } catch (error) {
179 |                 throw new Error(`Network error: ${error.message}`);
180 |             }
181 |         }
182 | 
183 |         function showResponse(elementId, data, isError = false) {
184 |             const element = document.getElementById(elementId);
185 |             element.style.display = 'block';
186 |             element.className = `response ${isError ? 'error' : 'success'}`;
187 |             element.textContent = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
188 |         }
189 | 
190 |         async function checkHealth() {
191 |             try {
192 |                 const data = await makeGetRequest('/health');
193 |                 showResponse('statusResponse', data);
194 |             } catch (error) {
195 |                 showResponse('statusResponse', error.message, true);
196 |             }
197 |         }
198 | 
199 |         async function getCapabilities() {
200 |             try {
201 |                 const data = await makeGetRequest('/mcp/capabilities');
202 |                 showResponse('statusResponse', data);
203 |             } catch (error) {
204 |                 showResponse('statusResponse', error.message, true);
205 |             }
206 |         }
207 | 
208 |         async function listTools() {
209 |             try {
210 |                 const request = {
211 |                     jsonrpc: "2.0",
212 |                     method: "tools/list",
213 |                     id: 1
214 |                 };
215 |                 const data = await makeRequest('/mcp/stream', request);
216 |                 showResponse('statusResponse', data);
217 |             } catch (error) {
218 |                 showResponse('statusResponse', error.message, true);
219 |             }
220 |         }
221 | 
222 |         async function getAlerts() {
223 |             try {
224 |                 const state = document.getElementById('stateSelect').value;
225 |                 const request = {
226 |                     jsonrpc: "2.0",
227 |                     method: "tools/call",
228 |                     params: {
229 |                         name: "get_alerts",
230 |                         arguments: { state: state }
231 |                     },
232 |                     id: 2
233 |                 };
234 |                 const data = await makeRequest('/mcp/stream', request);
235 |                 showResponse('alertsResponse', data);
236 |             } catch (error) {
237 |                 showResponse('alertsResponse', error.message, true);
238 |             }
239 |         }
240 | 
241 |         async function getForecast() {
242 |             try {
243 |                 const latitude = parseFloat(document.getElementById('latitude').value);
244 |                 const longitude = parseFloat(document.getElementById('longitude').value);
245 |                 
246 |                 if (isNaN(latitude) || isNaN(longitude)) {
247 |                     throw new Error('Please enter valid latitude and longitude values');
248 |                 }
249 |                 
250 |                 const request = {
251 |                     jsonrpc: "2.0",
252 |                     method: "tools/call",
253 |                     params: {
254 |                         name: "get_forecast",
255 |                         arguments: { latitude: latitude, longitude: longitude }
256 |                     },
257 |                     id: 3
258 |                 };
259 |                 const data = await makeRequest('/mcp/stream', request);
260 |                 showResponse('forecastResponse', data);
261 |             } catch (error) {
262 |                 showResponse('forecastResponse', error.message, true);
263 |             }
264 |         }
265 | 
266 |         async function sendCustomRequest() {
267 |             try {
268 |                 const requestText = document.getElementById('customRequest').value;
269 |                 if (!requestText.trim()) {
270 |                     throw new Error('Please enter a JSON-RPC request');
271 |                 }
272 |                 
273 |                 const request = JSON.parse(requestText);
274 |                 const data = await makeRequest('/mcp/stream', request);
275 |                 showResponse('customResponse', data);
276 |             } catch (error) {
277 |                 showResponse('customResponse', error.message, true);
278 |             }
279 |         }
280 | 
281 |         // Load initial status on page load
282 |         window.onload = function() {
283 |             checkHealth();
284 |         };
285 |     </script>
286 | </body>
287 | </html>
288 | 
```