-
-
Notifications
You must be signed in to change notification settings - Fork 1
feat(examples): Add weather app example with Open-Meteo API integration #81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
d5aa2ac
feat(examples): Add weather app example with Open-Meteo API integration
claude 2c392a2
style(weather-app): Format HTML and CSS files with prettier
claude 8bb21ff
fix(examples): Address code review feedback for weather-app
claude 19a3a04
fix(weather-app): Update protocol from "mcp" to "openai" in server co…
gabe4coding File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| node_modules/ | ||
| dist/ | ||
| public/ | ||
| *.log | ||
| .env | ||
| .env.local |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| # Weather App Example | ||
|
|
||
| An MCP application demonstrating weather tools built with @mcp-apps-kit. Uses the [Open-Meteo API](https://open-meteo.com/) for real weather data (free, no API key required) with mock data fallback. | ||
|
|
||
| ## Features | ||
|
|
||
| - **getCurrentWeather**: Get current weather conditions for any location | ||
| - **getForecast**: Get up to 16-day weather forecast | ||
| - **getWeatherAlerts**: Get weather alerts and warnings (mock data) | ||
|
|
||
| ## Tools | ||
|
|
||
| | Tool | Description | | ||
| | ------------------- | ------------------------------------------------------------------------- | | ||
| | `getCurrentWeather` | Returns current temperature, humidity, wind, and conditions | | ||
| | `getForecast` | Returns daily forecast with high/low temps, precipitation, sunrise/sunset | | ||
| | `getWeatherAlerts` | Returns active weather warnings, watches, and advisories | | ||
|
|
||
| ## Development | ||
|
|
||
| ```bash | ||
| # Install dependencies (from monorepo root) | ||
| pnpm install | ||
|
|
||
| # Run development server | ||
| pnpm -C examples/weather-app dev | ||
| ``` | ||
|
|
||
| ## Build | ||
|
|
||
| ```bash | ||
| pnpm -C examples/weather-app build | ||
| ``` | ||
|
|
||
| ## Testing | ||
|
|
||
| ```bash | ||
| # Run tests | ||
| pnpm -C examples/weather-app test | ||
|
|
||
| # Run tests in watch mode | ||
| pnpm -C examples/weather-app test:watch | ||
| ``` | ||
|
|
||
| ### Mock Mode | ||
|
|
||
| Set `USE_MOCK_WEATHER=true` to use mock data instead of the real API: | ||
|
|
||
| ```bash | ||
| USE_MOCK_WEATHER=true pnpm -C examples/weather-app dev | ||
| ``` | ||
|
|
||
| ## Connecting to an MCP Apps Host | ||
|
|
||
| Configure your MCP Apps-compatible host to connect to the server: | ||
|
|
||
| **HTTP mode (default):** | ||
|
|
||
| - Endpoint: `http://localhost:3005/mcp` | ||
|
|
||
| **Stdio mode (for hosts that support it):** | ||
|
|
||
| ```bash | ||
| npx tsx examples/weather-app/server/index.ts | ||
| ``` | ||
|
|
||
| ## Project Structure | ||
|
|
||
| ``` | ||
| weather-app/ | ||
| ├── server/ | ||
| │ ├── index.ts # App entry point with tool definitions | ||
| │ └── services/ | ||
| │ └── weatherService.ts # Open-Meteo API integration | ||
| ├── ui/ | ||
| │ ├── src/ | ||
| │ │ ├── App.tsx # React UI components | ||
| │ │ └── styles.css # Styling | ||
| │ └── vite.config.ts | ||
| └── tests/ | ||
| ├── integration/ | ||
| │ └── server.test.ts # Integration tests | ||
| └── unit/ | ||
| └── weatherService.test.ts # Unit tests | ||
| ``` | ||
|
|
||
| ## API Reference | ||
|
|
||
| ### Open-Meteo | ||
|
|
||
| This example uses the [Open-Meteo API](https://open-meteo.com/), a free and open-source weather API that doesn't require authentication. | ||
|
|
||
| - **Geocoding**: Converts city names to coordinates | ||
| - **Weather**: Current conditions and forecasts | ||
| - **Limits**: No rate limits for reasonable usage | ||
|
|
||
| ## License | ||
|
|
||
| MIT |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| { | ||
| "name": "@mcp-apps-kit/example-weather-app", | ||
| "version": "0.1.0", | ||
| "private": true, | ||
| "description": "Weather app example demonstrating Open-Meteo API integration with @mcp-apps-kit/core", | ||
| "type": "module", | ||
| "scripts": { | ||
| "dev": "concurrently \"pnpm dev:server\" \"pnpm dev:ui\"", | ||
| "dev:server": "tsx watch server/index.ts", | ||
| "dev:ui": "vite build --watch --config ui/vite.config.ts", | ||
| "start": "tsx server/index.ts", | ||
| "build": "pnpm build:ui && tsc", | ||
| "build:ui": "vite build --config ui/vite.config.ts", | ||
| "typecheck": "tsc --noEmit", | ||
| "test": "vitest run", | ||
| "test:watch": "vitest" | ||
| }, | ||
| "dependencies": { | ||
| "@mcp-apps-kit/core": "workspace:*", | ||
| "@mcp-apps-kit/ui": "workspace:*", | ||
| "@mcp-apps-kit/ui-react": "workspace:*", | ||
| "react": "^19.2.3", | ||
| "react-dom": "^19.2.3", | ||
| "zod": "^4.0.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@mcp-apps-kit/testing": "workspace:*", | ||
| "@testing-library/dom": "^10.4.1", | ||
| "@testing-library/jest-dom": "^6.9.1", | ||
| "@testing-library/react": "^16.3.1", | ||
| "@types/node": "^25.0.3", | ||
| "@types/react": "^19.2.7", | ||
| "@types/react-dom": "^19.2.3", | ||
| "@vitejs/plugin-react": "^5.1.2", | ||
| "concurrently": "^9.2.1", | ||
| "jsdom": "^27.3.0", | ||
| "tsx": "^4.0.0", | ||
| "typescript": "^5.0.0", | ||
| "vite": "^7.3.0", | ||
| "vite-plugin-singlefile": "^2.3.0", | ||
| "vitest": "^4.0.16" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,204 @@ | ||
| /** | ||
| * Weather App - MCP Server | ||
| * | ||
| * Demonstrates weather-related tools using Open-Meteo API (free, no API key required) | ||
| * with mock data fallback for testing and offline usage. | ||
| */ | ||
|
|
||
| import { createApp, defineTool, defineUI, type ClientToolsFromCore } from "@mcp-apps-kit/core"; | ||
| import { z } from "zod"; | ||
| import { | ||
| WeatherService, | ||
| type CurrentWeather, | ||
| type WeatherForecast, | ||
| type WeatherAlertsResponse, | ||
| } from "./services/weatherService.js"; | ||
|
|
||
| // Initialize weather service (uses real API by default, falls back to mock) | ||
| const weatherService = new WeatherService({ | ||
| useMock: process.env.USE_MOCK_WEATHER === "true", | ||
| }); | ||
|
|
||
| // Define UIs for weather widgets | ||
| const currentWeatherUI = defineUI({ | ||
| name: "Current Weather Widget", | ||
| description: "Displays current weather conditions", | ||
| html: "./ui/dist/index.html", | ||
| prefersBorder: true, | ||
| }); | ||
|
|
||
| const forecastUI = defineUI({ | ||
| name: "Weather Forecast Widget", | ||
| description: "Displays weather forecast", | ||
| html: "./ui/dist/index.html", | ||
| prefersBorder: true, | ||
| }); | ||
|
|
||
| const alertsUI = defineUI({ | ||
| name: "Weather Alerts Widget", | ||
| description: "Displays weather alerts and warnings", | ||
| html: "./ui/dist/index.html", | ||
| prefersBorder: true, | ||
| }); | ||
|
|
||
| // Schema definitions | ||
| const currentWeatherOutputSchema = z.object({ | ||
| location: z.object({ | ||
| name: z.string(), | ||
| latitude: z.number(), | ||
| longitude: z.number(), | ||
| country: z.string().optional(), | ||
| timezone: z.string().optional(), | ||
| }), | ||
| temperature: z.number().describe("Temperature in Celsius"), | ||
| feelsLike: z.number().describe("Feels like temperature in Celsius"), | ||
| humidity: z.number().describe("Relative humidity percentage"), | ||
| windSpeed: z.number().describe("Wind speed in km/h"), | ||
| windDirection: z.number().describe("Wind direction in degrees"), | ||
| weatherCode: z.number(), | ||
| description: z.string(), | ||
| icon: z.string(), | ||
| isDay: z.boolean(), | ||
| timestamp: z.string(), | ||
| }); | ||
|
|
||
| const dailyForecastSchema = z.object({ | ||
| date: z.string(), | ||
| temperatureMax: z.number(), | ||
| temperatureMin: z.number(), | ||
| weatherCode: z.number(), | ||
| description: z.string(), | ||
| icon: z.string(), | ||
| precipitationProbability: z.number(), | ||
| windSpeedMax: z.number(), | ||
| sunrise: z.string(), | ||
| sunset: z.string(), | ||
| }); | ||
|
|
||
| const forecastOutputSchema = z.object({ | ||
| location: z.object({ | ||
| name: z.string(), | ||
| latitude: z.number(), | ||
| longitude: z.number(), | ||
| country: z.string().optional(), | ||
| timezone: z.string().optional(), | ||
| }), | ||
| daily: z.array(dailyForecastSchema), | ||
| generatedAt: z.string(), | ||
| }); | ||
|
|
||
| const alertSchema = z.object({ | ||
| id: z.string(), | ||
| type: z.enum(["warning", "watch", "advisory"]), | ||
| severity: z.enum(["minor", "moderate", "severe", "extreme"]), | ||
| headline: z.string(), | ||
| description: z.string(), | ||
| startTime: z.string(), | ||
| endTime: z.string(), | ||
| }); | ||
|
|
||
| const alertsOutputSchema = z.object({ | ||
| location: z.object({ | ||
| name: z.string(), | ||
| latitude: z.number(), | ||
| longitude: z.number(), | ||
| country: z.string().optional(), | ||
| timezone: z.string().optional(), | ||
| }), | ||
| alerts: z.array(alertSchema), | ||
| lastChecked: z.string(), | ||
| }); | ||
|
|
||
| // Create the MCP App | ||
| const app = createApp({ | ||
| name: "weather-app", | ||
| version: "0.1.0", | ||
|
|
||
| config: { | ||
| protocol: "openai", | ||
| }, | ||
|
|
||
| tools: { | ||
| getCurrentWeather: defineTool({ | ||
| title: "Get Current Weather", | ||
| description: | ||
| "Get the current weather conditions for a specified location. Returns temperature, humidity, wind, and conditions.", | ||
| input: z.object({ | ||
| location: z | ||
| .string() | ||
| .describe( | ||
| "City name, address, or location to get weather for (e.g., 'New York', 'Tokyo', 'London, UK')" | ||
| ), | ||
| }), | ||
| output: currentWeatherOutputSchema, | ||
| visibility: "both", | ||
| annotations: { | ||
| readOnlyHint: true, | ||
| }, | ||
| handler: async ({ location }): Promise<CurrentWeather> => { | ||
| return await weatherService.getCurrentWeather(location); | ||
| }, | ||
| ui: currentWeatherUI, | ||
| }), | ||
|
|
||
| getForecast: defineTool({ | ||
| title: "Get Weather Forecast", | ||
| description: | ||
| "Get a multi-day weather forecast for a specified location. Returns daily forecasts with temperatures, precipitation, and conditions.", | ||
| input: z.object({ | ||
| location: z.string().describe("City name, address, or location to get forecast for"), | ||
| days: z | ||
| .number() | ||
| .min(1) | ||
| .max(16) | ||
| .default(7) | ||
| .describe("Number of days to forecast (1-16, default: 7)"), | ||
| }), | ||
| output: forecastOutputSchema, | ||
| visibility: "both", | ||
| annotations: { | ||
| readOnlyHint: true, | ||
| }, | ||
| handler: async ({ location, days }): Promise<WeatherForecast> => { | ||
| return await weatherService.getForecast(location, days); | ||
| }, | ||
| ui: forecastUI, | ||
| }), | ||
|
|
||
| getWeatherAlerts: defineTool({ | ||
| title: "Get Weather Alerts", | ||
| description: | ||
| "Get active weather alerts and warnings for a specified location. Returns any watches, warnings, or advisories in effect.", | ||
| input: z.object({ | ||
| location: z.string().describe("City name, address, or location to check for alerts"), | ||
| }), | ||
| output: alertsOutputSchema, | ||
| visibility: "both", | ||
| annotations: { | ||
| readOnlyHint: true, | ||
| }, | ||
| handler: async ({ location }): Promise<WeatherAlertsResponse> => { | ||
| return await weatherService.getAlerts(location); | ||
| }, | ||
| ui: alertsUI, | ||
| }), | ||
| }, | ||
| }); | ||
|
|
||
| // Export types for UI components | ||
| export type AppToolsDef = { | ||
| getCurrentWeather: typeof app.tools.getCurrentWeather; | ||
| getForecast: typeof app.tools.getForecast; | ||
| getWeatherAlerts: typeof app.tools.getWeatherAlerts; | ||
| }; | ||
| export type AppTools = ClientToolsFromCore<AppToolsDef>; | ||
|
|
||
| // Start server (skip in test environment) | ||
| if (process.env.NODE_ENV !== "test") { | ||
| const port = parseInt(process.env.PORT || "3005", 10); | ||
| await app.start({ port }); | ||
| console.log(`Weather App MCP server running on http://localhost:${port}`); | ||
| } | ||
|
|
||
| // Export app for testing | ||
| export { app }; | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate PORT environment variable to handle invalid values.
The
parseIntcall can returnNaNifPORTcontains a non-numeric string. While unlikely in practice, this could cause confusing startup errors.🛡️ Suggested PORT validation
📝 Committable suggestion
🤖 Prompt for AI Agents