├── .env.example ├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── config.ts ├── index.ts ├── test.ts ├── types.ts └── types │ ├── global.d.ts │ └── modelcontextprotocol-sdk.d.ts └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | # Flightradar24 API Configuration 2 | FR24_API_KEY=your-api-key-here 3 | # Flightradar24 API URL, shouldn't need to change this 4 | FR24_API_URL=https://fr24api.flightradar24.com 5 | # Server Configuration 6 | PORT=3000 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | dist/ 3 | node_modules/ 4 | build/ 5 | *.log 6 | .env 7 | .env.local 8 | .env.*.local 9 | 10 | # Keep example file 11 | !.env.example -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 sunsetcoder 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flightradar24 MCP Server 🛩️ 2 | 3 | A Claude Desktop MCP server that helps you track flights in real-time using Flightradar24 data. Perfect for aviation enthusiasts, travel planners, or anyone curious about flights overhead! 4 | 5 | ## What Can This Do? ✨ 6 | 7 | - 🔍 Track any flight in real-time 8 | - ⏰ Get arrival and departure times for specific flights 9 | - 🌉 View the status of flights at an airport 10 | - 🚨 Monitor emergency flights 11 | 12 | Anthropic Claude MCP Hackathon - FlightRadar24 MCP server 13 | 14 | ## Setup Guide 🚀 15 | 16 | ### 1. Prerequisites 17 | - [Claude Desktop](https://claude.ai/desktop) installed on your computer 18 | - A Flightradar24 API key (get one from [Flightradar24's website](https://www.flightradar24.com/premium))* 19 | 20 | ### 2. Installation 21 | 22 | 1. Clone this repository somewhere on your computer: 23 | ```bash 24 | git clone https://github.com/sunsetcoder/flightradar24-mcp-server.git 25 | ``` 26 | 27 | 2. Install dependencies & build the project: 28 | ```bash 29 | cd flightradar24-mcp-server 30 | npm install 31 | npm run build 32 | ``` 33 | 34 | ### 3. Integration with Claude Desktop 35 | 36 | 1. Open your Claude Desktop configuration file: 37 | ``` 38 | # On Mac: 39 | ~/Library/Application Support/Claude/claude_desktop_config.json 40 | 41 | # On Windows: 42 | %APPDATA%/Claude/claude_desktop_config.json 43 | ``` 44 | 45 | 2. Add the following to the `mcpServers` object in your config: 46 | ```json 47 | { 48 | "mcpServers": { 49 | "flightradar24-server": { 50 | "command": "node", 51 | "args": [ 52 | "/Users///flightradar24-mcp-server/dist/index.js" 53 | ], 54 | "env": { 55 | "FR24_API_KEY": "your_api_key_here", 56 | "FR24_API_URL": "https://fr24api.flightradar24.com" 57 | } 58 | } 59 | } 60 | } 61 | ``` 62 | 63 | 3. Important Steps: 64 | - Replace `/FULL/PATH/TO/flightradar24-mcp-server` with the actual full path to where you cloned the repository 65 | - Add your Flightradar24 API key in the `env` section 66 | - Make sure to use forward slashes (`/`) in the path, even on Windows 67 | 68 | 4. Restart Claude Desktop for the changes to take effect 69 | 70 | ## Environment Setup 71 | 72 | 1. Copy `.env.example` to `.env`: 73 | ```bash 74 | cp .env.example .env 75 | ``` 76 | 77 | 2. Update the `.env` file with your actual Flightradar24 API key: 78 | ```env 79 | FR24_API_KEY=your_actual_api_key_here 80 | ``` 81 | 82 | Note: Never commit your actual API key to version control. The `.env` file is ignored by git for security reasons. 83 | 84 | ## Let's Try It Out! 🎮 85 | 86 | Once the server is configured, you can ask Claude questions like: 87 | 88 | 1. "What's the ETA for United Airlines flight UA123?" 89 | 2. "Show me all flights currently at SFO" 90 | 3. "Are there any emergency flights in the area?" 91 | 4. "Show me all international flights arriving at SFO in the next 2 hours" 92 | 5. "How many commercial flights are currently over the Pacific Ocean?" 93 | 6. "Identify any flights that have declared an emergency in the California region" 94 | 95 | Example conversation with Claude: 96 | ``` 97 | You: What's the status of flight UA123? 98 | Claude: Let me check that for you... 99 | [Claude will use the MCP server to fetch real-time flight information] 100 | ``` 101 | 102 | ## Common Questions & Troubleshooting 🤔 103 | 104 | ### "Claude can't connect to the server" 105 | - Check if the path in `claude_desktop_config.json` is correct 106 | - Make sure you're using the full absolute path 107 | - Verify your API key is correct 108 | - Try restarting Claude Desktop 109 | 110 | ### "The server isn't responding" 111 | - Make sure your Flightradar24 API key is valid 112 | - Check if the API URL is correct 113 | - Look for any error messages in server logs 114 | 115 | ### FlightRadar API Access 116 | - Note: Using Flightradar24's API requires a [subscription](https://fr24api.flightradar24.com/subscriptions-and-credits) 117 | 118 | ## Need More Help? 🆘 119 | 120 | 1. Make sure Claude Desktop is properly installed 121 | 2. Verify your Flightradar24 API key is active 122 | 3. Check the path in your configuration file is correct 123 | 4. Look for error messages in MCP server logs 124 | 125 | ## License 📄 126 | 127 | MIT 128 | 129 | --- 130 | 131 | Made with ❤️ for aviation enthusiasts 132 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flightradar24-mcp-server", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "flightradar24-mcp-server", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@modelcontextprotocol/sdk": "latest", 13 | "axios": "^1.6.0", 14 | "dotenv": "^16.4.7", 15 | "zod": "^3.22.4" 16 | }, 17 | "devDependencies": { 18 | "@types/express": "^4.17.21", 19 | "@types/node": "^20.10.4", 20 | "tsc-watch": "^6.0.4", 21 | "typescript": "^5.3.3" 22 | } 23 | }, 24 | "node_modules/@modelcontextprotocol/sdk": { 25 | "version": "1.0.3", 26 | "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.0.3.tgz", 27 | "integrity": "sha512-2as3cX/VJ0YBHGmdv3GFyTpoM8q2gqE98zh3Vf1NwnsSY0h3mvoO07MUzfygCKkWsFjcZm4otIiqD6Xh7kiSBQ==", 28 | "dependencies": { 29 | "content-type": "^1.0.5", 30 | "raw-body": "^3.0.0", 31 | "zod": "^3.23.8" 32 | } 33 | }, 34 | "node_modules/@types/body-parser": { 35 | "version": "1.19.5", 36 | "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", 37 | "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", 38 | "dev": true, 39 | "dependencies": { 40 | "@types/connect": "*", 41 | "@types/node": "*" 42 | } 43 | }, 44 | "node_modules/@types/connect": { 45 | "version": "3.4.38", 46 | "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", 47 | "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", 48 | "dev": true, 49 | "dependencies": { 50 | "@types/node": "*" 51 | } 52 | }, 53 | "node_modules/@types/express": { 54 | "version": "4.17.21", 55 | "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", 56 | "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", 57 | "dev": true, 58 | "dependencies": { 59 | "@types/body-parser": "*", 60 | "@types/express-serve-static-core": "^4.17.33", 61 | "@types/qs": "*", 62 | "@types/serve-static": "*" 63 | } 64 | }, 65 | "node_modules/@types/express-serve-static-core": { 66 | "version": "4.19.6", 67 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", 68 | "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", 69 | "dev": true, 70 | "dependencies": { 71 | "@types/node": "*", 72 | "@types/qs": "*", 73 | "@types/range-parser": "*", 74 | "@types/send": "*" 75 | } 76 | }, 77 | "node_modules/@types/http-errors": { 78 | "version": "2.0.4", 79 | "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", 80 | "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", 81 | "dev": true 82 | }, 83 | "node_modules/@types/mime": { 84 | "version": "1.3.5", 85 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", 86 | "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", 87 | "dev": true 88 | }, 89 | "node_modules/@types/node": { 90 | "version": "20.17.9", 91 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.9.tgz", 92 | "integrity": "sha512-0JOXkRyLanfGPE2QRCwgxhzlBAvaRdCNMcvbd7jFfpmD4eEXll7LRwy5ymJmyeZqk7Nh7eD2LeUyQ68BbndmXw==", 93 | "dev": true, 94 | "license": "MIT", 95 | "dependencies": { 96 | "undici-types": "~6.19.2" 97 | } 98 | }, 99 | "node_modules/@types/qs": { 100 | "version": "6.9.17", 101 | "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", 102 | "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", 103 | "dev": true 104 | }, 105 | "node_modules/@types/range-parser": { 106 | "version": "1.2.7", 107 | "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", 108 | "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", 109 | "dev": true 110 | }, 111 | "node_modules/@types/send": { 112 | "version": "0.17.4", 113 | "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", 114 | "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", 115 | "dev": true, 116 | "dependencies": { 117 | "@types/mime": "^1", 118 | "@types/node": "*" 119 | } 120 | }, 121 | "node_modules/@types/serve-static": { 122 | "version": "1.15.7", 123 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", 124 | "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", 125 | "dev": true, 126 | "dependencies": { 127 | "@types/http-errors": "*", 128 | "@types/node": "*", 129 | "@types/send": "*" 130 | } 131 | }, 132 | "node_modules/asynckit": { 133 | "version": "0.4.0", 134 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 135 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", 136 | "license": "MIT" 137 | }, 138 | "node_modules/axios": { 139 | "version": "1.7.9", 140 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", 141 | "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", 142 | "license": "MIT", 143 | "dependencies": { 144 | "follow-redirects": "^1.15.6", 145 | "form-data": "^4.0.0", 146 | "proxy-from-env": "^1.1.0" 147 | } 148 | }, 149 | "node_modules/bytes": { 150 | "version": "3.1.2", 151 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 152 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 153 | "license": "MIT", 154 | "engines": { 155 | "node": ">= 0.8" 156 | } 157 | }, 158 | "node_modules/combined-stream": { 159 | "version": "1.0.8", 160 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 161 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 162 | "license": "MIT", 163 | "dependencies": { 164 | "delayed-stream": "~1.0.0" 165 | }, 166 | "engines": { 167 | "node": ">= 0.8" 168 | } 169 | }, 170 | "node_modules/content-type": { 171 | "version": "1.0.5", 172 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 173 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 174 | "license": "MIT", 175 | "engines": { 176 | "node": ">= 0.6" 177 | } 178 | }, 179 | "node_modules/cross-spawn": { 180 | "version": "7.0.6", 181 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 182 | "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 183 | "dev": true, 184 | "dependencies": { 185 | "path-key": "^3.1.0", 186 | "shebang-command": "^2.0.0", 187 | "which": "^2.0.1" 188 | }, 189 | "engines": { 190 | "node": ">= 8" 191 | } 192 | }, 193 | "node_modules/delayed-stream": { 194 | "version": "1.0.0", 195 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 196 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 197 | "license": "MIT", 198 | "engines": { 199 | "node": ">=0.4.0" 200 | } 201 | }, 202 | "node_modules/depd": { 203 | "version": "2.0.0", 204 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 205 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 206 | "license": "MIT", 207 | "engines": { 208 | "node": ">= 0.8" 209 | } 210 | }, 211 | "node_modules/dotenv": { 212 | "version": "16.4.7", 213 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", 214 | "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", 215 | "engines": { 216 | "node": ">=12" 217 | }, 218 | "funding": { 219 | "url": "https://dotenvx.com" 220 | } 221 | }, 222 | "node_modules/duplexer": { 223 | "version": "0.1.2", 224 | "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", 225 | "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", 226 | "dev": true 227 | }, 228 | "node_modules/event-stream": { 229 | "version": "3.3.4", 230 | "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", 231 | "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", 232 | "dev": true, 233 | "dependencies": { 234 | "duplexer": "~0.1.1", 235 | "from": "~0", 236 | "map-stream": "~0.1.0", 237 | "pause-stream": "0.0.11", 238 | "split": "0.3", 239 | "stream-combiner": "~0.0.4", 240 | "through": "~2.3.1" 241 | } 242 | }, 243 | "node_modules/follow-redirects": { 244 | "version": "1.15.9", 245 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", 246 | "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", 247 | "funding": [ 248 | { 249 | "type": "individual", 250 | "url": "https://github.com/sponsors/RubenVerborgh" 251 | } 252 | ], 253 | "license": "MIT", 254 | "engines": { 255 | "node": ">=4.0" 256 | }, 257 | "peerDependenciesMeta": { 258 | "debug": { 259 | "optional": true 260 | } 261 | } 262 | }, 263 | "node_modules/form-data": { 264 | "version": "4.0.1", 265 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", 266 | "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", 267 | "license": "MIT", 268 | "dependencies": { 269 | "asynckit": "^0.4.0", 270 | "combined-stream": "^1.0.8", 271 | "mime-types": "^2.1.12" 272 | }, 273 | "engines": { 274 | "node": ">= 6" 275 | } 276 | }, 277 | "node_modules/from": { 278 | "version": "0.1.7", 279 | "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", 280 | "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", 281 | "dev": true 282 | }, 283 | "node_modules/http-errors": { 284 | "version": "2.0.0", 285 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 286 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 287 | "license": "MIT", 288 | "dependencies": { 289 | "depd": "2.0.0", 290 | "inherits": "2.0.4", 291 | "setprototypeof": "1.2.0", 292 | "statuses": "2.0.1", 293 | "toidentifier": "1.0.1" 294 | }, 295 | "engines": { 296 | "node": ">= 0.8" 297 | } 298 | }, 299 | "node_modules/iconv-lite": { 300 | "version": "0.6.3", 301 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 302 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 303 | "dependencies": { 304 | "safer-buffer": ">= 2.1.2 < 3.0.0" 305 | }, 306 | "engines": { 307 | "node": ">=0.10.0" 308 | } 309 | }, 310 | "node_modules/inherits": { 311 | "version": "2.0.4", 312 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 313 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 314 | "license": "ISC" 315 | }, 316 | "node_modules/isexe": { 317 | "version": "2.0.0", 318 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 319 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 320 | "dev": true 321 | }, 322 | "node_modules/map-stream": { 323 | "version": "0.1.0", 324 | "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", 325 | "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", 326 | "dev": true 327 | }, 328 | "node_modules/mime-db": { 329 | "version": "1.52.0", 330 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 331 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 332 | "license": "MIT", 333 | "engines": { 334 | "node": ">= 0.6" 335 | } 336 | }, 337 | "node_modules/mime-types": { 338 | "version": "2.1.35", 339 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 340 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 341 | "license": "MIT", 342 | "dependencies": { 343 | "mime-db": "1.52.0" 344 | }, 345 | "engines": { 346 | "node": ">= 0.6" 347 | } 348 | }, 349 | "node_modules/node-cleanup": { 350 | "version": "2.1.2", 351 | "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", 352 | "integrity": "sha512-qN8v/s2PAJwGUtr1/hYTpNKlD6Y9rc4p8KSmJXyGdYGZsDGKXrGThikLFP9OCHFeLeEpQzPwiAtdIvBLqm//Hw==", 353 | "dev": true 354 | }, 355 | "node_modules/path-key": { 356 | "version": "3.1.1", 357 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 358 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 359 | "dev": true, 360 | "engines": { 361 | "node": ">=8" 362 | } 363 | }, 364 | "node_modules/pause-stream": { 365 | "version": "0.0.11", 366 | "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", 367 | "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", 368 | "dev": true, 369 | "dependencies": { 370 | "through": "~2.3" 371 | } 372 | }, 373 | "node_modules/proxy-from-env": { 374 | "version": "1.1.0", 375 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 376 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", 377 | "license": "MIT" 378 | }, 379 | "node_modules/ps-tree": { 380 | "version": "1.2.0", 381 | "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", 382 | "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", 383 | "dev": true, 384 | "dependencies": { 385 | "event-stream": "=3.3.4" 386 | }, 387 | "bin": { 388 | "ps-tree": "bin/ps-tree.js" 389 | }, 390 | "engines": { 391 | "node": ">= 0.10" 392 | } 393 | }, 394 | "node_modules/raw-body": { 395 | "version": "3.0.0", 396 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", 397 | "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", 398 | "dependencies": { 399 | "bytes": "3.1.2", 400 | "http-errors": "2.0.0", 401 | "iconv-lite": "0.6.3", 402 | "unpipe": "1.0.0" 403 | }, 404 | "engines": { 405 | "node": ">= 0.8" 406 | } 407 | }, 408 | "node_modules/safer-buffer": { 409 | "version": "2.1.2", 410 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 411 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 412 | "license": "MIT" 413 | }, 414 | "node_modules/setprototypeof": { 415 | "version": "1.2.0", 416 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 417 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 418 | "license": "ISC" 419 | }, 420 | "node_modules/shebang-command": { 421 | "version": "2.0.0", 422 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 423 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 424 | "dev": true, 425 | "dependencies": { 426 | "shebang-regex": "^3.0.0" 427 | }, 428 | "engines": { 429 | "node": ">=8" 430 | } 431 | }, 432 | "node_modules/shebang-regex": { 433 | "version": "3.0.0", 434 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 435 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 436 | "dev": true, 437 | "engines": { 438 | "node": ">=8" 439 | } 440 | }, 441 | "node_modules/split": { 442 | "version": "0.3.3", 443 | "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", 444 | "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", 445 | "dev": true, 446 | "dependencies": { 447 | "through": "2" 448 | }, 449 | "engines": { 450 | "node": "*" 451 | } 452 | }, 453 | "node_modules/statuses": { 454 | "version": "2.0.1", 455 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 456 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 457 | "license": "MIT", 458 | "engines": { 459 | "node": ">= 0.8" 460 | } 461 | }, 462 | "node_modules/stream-combiner": { 463 | "version": "0.0.4", 464 | "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", 465 | "integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==", 466 | "dev": true, 467 | "dependencies": { 468 | "duplexer": "~0.1.1" 469 | } 470 | }, 471 | "node_modules/string-argv": { 472 | "version": "0.3.2", 473 | "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", 474 | "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", 475 | "dev": true, 476 | "engines": { 477 | "node": ">=0.6.19" 478 | } 479 | }, 480 | "node_modules/through": { 481 | "version": "2.3.8", 482 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 483 | "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", 484 | "dev": true 485 | }, 486 | "node_modules/toidentifier": { 487 | "version": "1.0.1", 488 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 489 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 490 | "license": "MIT", 491 | "engines": { 492 | "node": ">=0.6" 493 | } 494 | }, 495 | "node_modules/tsc-watch": { 496 | "version": "6.2.1", 497 | "resolved": "https://registry.npmjs.org/tsc-watch/-/tsc-watch-6.2.1.tgz", 498 | "integrity": "sha512-GLwdz5Dy9K3sVm3RzgkLcyDpl5cvU9HEcE1A3gf5rqEwlUe7gDLxNCgcuNEw3zoKOiegMo3LnbF1t6HLqxhrSA==", 499 | "dev": true, 500 | "dependencies": { 501 | "cross-spawn": "^7.0.3", 502 | "node-cleanup": "^2.1.2", 503 | "ps-tree": "^1.2.0", 504 | "string-argv": "^0.3.1" 505 | }, 506 | "bin": { 507 | "tsc-watch": "dist/lib/tsc-watch.js" 508 | }, 509 | "engines": { 510 | "node": ">=12.12.0" 511 | }, 512 | "peerDependencies": { 513 | "typescript": "*" 514 | } 515 | }, 516 | "node_modules/typescript": { 517 | "version": "5.7.2", 518 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", 519 | "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", 520 | "dev": true, 521 | "license": "Apache-2.0", 522 | "bin": { 523 | "tsc": "bin/tsc", 524 | "tsserver": "bin/tsserver" 525 | }, 526 | "engines": { 527 | "node": ">=14.17" 528 | } 529 | }, 530 | "node_modules/undici-types": { 531 | "version": "6.19.8", 532 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", 533 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", 534 | "dev": true, 535 | "license": "MIT" 536 | }, 537 | "node_modules/unpipe": { 538 | "version": "1.0.0", 539 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 540 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 541 | "license": "MIT", 542 | "engines": { 543 | "node": ">= 0.8" 544 | } 545 | }, 546 | "node_modules/which": { 547 | "version": "2.0.2", 548 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 549 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 550 | "dev": true, 551 | "dependencies": { 552 | "isexe": "^2.0.0" 553 | }, 554 | "bin": { 555 | "node-which": "bin/node-which" 556 | }, 557 | "engines": { 558 | "node": ">= 8" 559 | } 560 | }, 561 | "node_modules/zod": { 562 | "version": "3.24.1", 563 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", 564 | "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", 565 | "funding": { 566 | "url": "https://github.com/sponsors/colinhacks" 567 | } 568 | } 569 | } 570 | } 571 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flightradar24-mcp-server", 3 | "version": "1.0.0", 4 | "description": "Model Context Protocol server for FlightRadar24 API", 5 | "main": "build/index.js", 6 | "type": "module", 7 | "scripts": { 8 | "build": "tsc", 9 | "start": "node dist/index.js", 10 | "dev": "ts-node src/index.ts", 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "keywords": [ 14 | "flightradar24", 15 | "api", 16 | "mcp", 17 | "flight-tracking" 18 | ], 19 | "author": "", 20 | "license": "MIT", 21 | "dependencies": { 22 | "@modelcontextprotocol/sdk": "latest", 23 | "axios": "^1.6.0", 24 | "dotenv": "^16.4.7", 25 | "zod": "^3.22.4" 26 | }, 27 | "devDependencies": { 28 | "@types/express": "^4.17.21", 29 | "@types/node": "^20.10.4", 30 | "tsc-watch": "^6.0.4", 31 | "typescript": "^5.3.3" 32 | } 33 | } -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod' 2 | import dotenv from 'dotenv' 3 | 4 | // Load environment variables 5 | dotenv.config() 6 | 7 | /** 8 | * Environment variable schema validation 9 | */ 10 | const envSchema = z.object({ 11 | FR24_API_KEY: z.string({ 12 | required_error: "FR24_API_KEY is required in environment variables", 13 | invalid_type_error: "FR24_API_KEY must be a string" 14 | }).min(1, { message: "FR24_API_KEY cannot be empty" }), 15 | FR24_API_URL: z.string({ 16 | required_error: "FR24_API_URL is required in environment variables", 17 | invalid_type_error: "FR24_API_URL must be a string" 18 | }).url({ message: "FR24_API_URL must be a valid URL" }) 19 | }) 20 | 21 | /** 22 | * Validate and extract environment variables 23 | * @throws {Error} If validation fails 24 | */ 25 | export const env = envSchema.parse(process.env) -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js'; 3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 4 | import { 5 | ListToolsRequestSchema, 6 | CallToolRequestSchema, 7 | ErrorCode, 8 | McpError, 9 | ListToolsRequest, 10 | CallToolRequest 11 | } from '@modelcontextprotocol/sdk/types.js'; 12 | import axios from 'axios'; 13 | import { 14 | FlightTrackingParams, 15 | FlightPositionsResponse, 16 | FlightETAResponse, 17 | isValidFlightTrackingParams 18 | } from './types.js'; 19 | import { env } from './config.js'; 20 | 21 | // API client configuration 22 | const fr24Client = axios.create({ 23 | baseURL: env.FR24_API_URL, 24 | headers: { 25 | 'Accept': 'application/json', 26 | 'Accept-Version': 'v1', 27 | 'Authorization': `Bearer ${env.FR24_API_KEY}` 28 | } 29 | }); 30 | 31 | /** 32 | * MCP Server implementation for Flightradar24 API 33 | * Provides tools for tracking flights and monitoring air traffic 34 | */ 35 | class FlightTrackingServer { 36 | private server: Server; 37 | 38 | constructor() { 39 | this.server = new Server( 40 | { name: 'flightradar24-server', version: '1.0.0' }, 41 | { 42 | capabilities: { 43 | tools: {}, 44 | jsonrpc: true // Add this to enable JSON-RPC support 45 | } 46 | } 47 | ); 48 | 49 | this.setupHandlers(); 50 | this.setupErrorHandling(); 51 | } 52 | 53 | private setupErrorHandling(): void { 54 | this.server.onerror = (error) => { 55 | console.error('[MCP Error]', error); 56 | }; 57 | 58 | process.on('SIGINT', async () => { 59 | await this.server.close(); 60 | process.exit(0); 61 | }); 62 | } 63 | 64 | private setupHandlers(): void { 65 | this.setupToolHandlers(); 66 | } 67 | 68 | /** 69 | * Configure available tools and their handlers 70 | */ 71 | private setupToolHandlers(): void { 72 | this.server.setRequestHandler(ListToolsRequestSchema, async (request: ListToolsRequest) => ({ 73 | tools: [ 74 | { 75 | name: 'get_flight_positions', 76 | description: 'Get real-time flight positions with various filtering options', 77 | inputSchema: { 78 | type: 'object', 79 | properties: { 80 | airports: { type: 'string', description: 'Comma-separated list of airport ICAO codes' }, 81 | bounds: { type: 'string', description: 'Geographical bounds (lat1,lon1,lat2,lon2)' }, 82 | categories: { type: 'string', description: 'Aircraft categories (P,C,J)' }, 83 | limit: { type: 'number', description: 'Maximum number of results' } 84 | } 85 | } 86 | }, 87 | { 88 | name: 'get_flight_eta', 89 | description: 'Get estimated arrival time for a specific flight', 90 | inputSchema: { 91 | type: 'object', 92 | properties: { 93 | flightNumber: { 94 | type: 'string', 95 | description: 'Flight number (e.g., UA123)', 96 | pattern: '^[A-Z0-9]{2,8}$' 97 | } 98 | }, 99 | required: ['flightNumber'] 100 | } 101 | } 102 | ] 103 | })); 104 | 105 | this.server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => { 106 | try { 107 | const toolName = request.params.name; 108 | const toolArgs = request.params.arguments; 109 | 110 | switch (toolName) { 111 | case 'get_flight_positions': { 112 | const params: FlightTrackingParams = { 113 | ...toolArgs, 114 | limit: toolArgs?.limit ? parseInt(toolArgs.limit as string) : undefined 115 | }; 116 | 117 | if (!isValidFlightTrackingParams(params)) { 118 | throw new McpError( 119 | ErrorCode.InvalidParams, 120 | 'Invalid or missing query parameters. At least one valid parameter is required.' 121 | ); 122 | } 123 | 124 | const response = await fr24Client.get( 125 | '/api/live/flight-positions/light', 126 | { params } 127 | ); 128 | 129 | return { 130 | content: [{ 131 | type: "text", 132 | text: JSON.stringify(response.data, null, 2) 133 | }] 134 | }; 135 | } 136 | 137 | case 'get_flight_eta': { 138 | const { flightNumber } = toolArgs || {}; 139 | const flightNumberStr = String(flightNumber || ''); 140 | 141 | if (!flightNumberStr || !/^[A-Z0-9]{2,8}$/.test(flightNumberStr)) { 142 | throw new McpError( 143 | ErrorCode.InvalidParams, 144 | 'Invalid flight number format' 145 | ); 146 | } 147 | 148 | const response = await fr24Client.get( 149 | '/api/flights/detail', 150 | { 151 | params: { 152 | flight: flightNumberStr, 153 | format: 'eta' 154 | } 155 | } 156 | ); 157 | 158 | return { 159 | content: [{ 160 | type: "text", 161 | text: JSON.stringify(response.data, null, 2) 162 | }] 163 | }; 164 | } 165 | 166 | default: 167 | throw new McpError( 168 | ErrorCode.MethodNotFound, 169 | `Unknown tool: ${toolName}` 170 | ); 171 | } 172 | } catch (error) { 173 | if (axios.isAxiosError(error)) { 174 | return { 175 | isError: true, 176 | content: [{ 177 | type: "text", 178 | text: `Flightradar24 API error: ${error.response?.data?.message || error.message || 'Unknown error'}` 179 | }] 180 | }; 181 | } 182 | throw error; 183 | } 184 | }); 185 | } 186 | 187 | /** 188 | * Start the MCP server using stdio transport 189 | */ 190 | async run(): Promise { 191 | const transport = new StdioServerTransport(); 192 | await this.server.connect(transport); 193 | } 194 | } 195 | 196 | // Create and start the server 197 | const server = new FlightTrackingServer(); 198 | server.run().catch(console.error); 199 | //console.log(env.FR24_API_KEY) 200 | //console.log(env.FR24_API_URL) -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // Test requests 2 | const requests = [ 3 | // List tools request 4 | { 5 | jsonrpc: "2.0", 6 | id: "1", 7 | method: "list_tools", 8 | params: {} 9 | }, 10 | 11 | // Test flight positions request 12 | { 13 | jsonrpc: "2.0", 14 | id: "2", 15 | method: "call_tool", 16 | params: { 17 | name: "get_flight_positions", 18 | arguments: { 19 | airports: "KJFK,KLAX", 20 | limit: "5" 21 | } 22 | } 23 | }, 24 | 25 | // Test flight ETA request 26 | { 27 | jsonrpc: "2.0", 28 | id: "3", 29 | method: "call_tool", 30 | params: { 31 | name: "get_flight_eta", 32 | arguments: { 33 | flightNumber: "UA123" 34 | } 35 | } 36 | } 37 | ]; 38 | 39 | // Print each request with a delay 40 | requests.forEach((request, index) => { 41 | console.log(JSON.stringify(request)); 42 | }); -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export interface Coordinate { 2 | lat: number; 3 | lng: number; 4 | } 5 | 6 | export interface ApiResponse { 7 | contents: { 8 | uri: string; 9 | mimeType: string; 10 | text: string; 11 | }[]; 12 | } 13 | 14 | /** 15 | * Flightradar24 API Types 16 | */ 17 | 18 | export interface FlightPosition { 19 | fr24_id: string; 20 | hex: string; 21 | callsign: string; 22 | lat: number; 23 | lon: number; 24 | track: number; 25 | alt: number; 26 | gspeed: number; 27 | vspeed: number; 28 | squawk: string; 29 | timestamp: string; 30 | source: 'ADSB' | 'MLAT' | 'ESTIMATED'; 31 | } 32 | 33 | export interface FlightPositionsResponse { 34 | data: FlightPosition[]; 35 | } 36 | 37 | export interface FlightETA { 38 | flight_number: string; 39 | departure: { 40 | airport: string; 41 | scheduled: string; 42 | actual: string; 43 | delay?: number; 44 | }; 45 | arrival: { 46 | airport: string; 47 | scheduled: string; 48 | estimated: string; 49 | delay?: number; 50 | }; 51 | status: 'scheduled' | 'active' | 'landed' | 'cancelled'; 52 | } 53 | 54 | export interface FlightETAResponse { 55 | data: FlightETA; 56 | } 57 | 58 | export interface FlightTrackingParams { 59 | bounds?: string; 60 | flights?: string; 61 | callsigns?: string; 62 | registrations?: string; 63 | painted_as?: string; 64 | operating_as?: string; 65 | airports?: string; 66 | routes?: string; 67 | aircraft?: string; 68 | altitude_ranges?: string; 69 | squawks?: string; 70 | categories?: string; 71 | data_sources?: string; 72 | airspaces?: string; 73 | limit?: number; 74 | } 75 | 76 | /** 77 | * Validates flight tracking parameters 78 | */ 79 | export function isValidFlightTrackingParams(params: unknown): params is FlightTrackingParams { 80 | if (!params || typeof params !== 'object') { 81 | return false; 82 | } 83 | 84 | const typedParams = params as Record; 85 | 86 | // Check that at least one valid parameter is provided 87 | const validParams = [ 88 | 'bounds', 'flights', 'callsigns', 'registrations', 'painted_as', 89 | 'operating_as', 'airports', 'routes', 'aircraft', 'altitude_ranges', 90 | 'squawks', 'categories', 'data_sources', 'airspaces', 'limit' 91 | ]; 92 | 93 | const hasValidParam = validParams.some(param => { 94 | const value = typedParams[param]; 95 | if (param === 'limit') { 96 | return typeof value === 'number' && value > 0; 97 | } 98 | return typeof value === 'string' && value.length > 0; 99 | }); 100 | 101 | return hasValidParam; 102 | } 103 | 104 | export enum FlightCategory { 105 | PASSENGER = 'P', 106 | CARGO = 'C', 107 | MILITARY_AND_GOVERNMENT = 'M', 108 | BUSINESS_JETS = 'J', 109 | GENERAL_AVIATION = 'T', 110 | HELICOPTERS = 'H', 111 | LIGHTER_THAN_AIR = 'B', 112 | GLIDERS = 'G', 113 | DRONES = 'D', 114 | GROUND_VEHICLES = 'V', 115 | OTHER = 'O', 116 | NON_CATEGORIZED = 'N' 117 | } 118 | 119 | export interface ApiError { 120 | code: string; 121 | message: string; 122 | details?: any; 123 | } -------------------------------------------------------------------------------- /src/types/global.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace NodeJS { 2 | interface ProcessEnv { 3 | FR24_API_KEY: string; 4 | FR24_API_URL: string; 5 | } 6 | } -------------------------------------------------------------------------------- /src/types/modelcontextprotocol-sdk.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@modelcontextprotocol/sdk' { 2 | export class MCPServer { 3 | constructor(config: { apiKey: string }); 4 | } 5 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "resolveJsonModule": true 13 | }, 14 | "include": [ 15 | "src/**/*" 16 | ], 17 | "exclude": [ 18 | "node_modules" 19 | ] 20 | } --------------------------------------------------------------------------------