news-weather-mcp
Version:
a cool news and weather MCP server
121 lines (108 loc) • 3.63 kB
JavaScript
import fetch from 'node-fetch'
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { z } from 'zod'
const NEWS_KEY = `94d4de1b09714b469a9c58b692c876cc` // have my free key lol
const weatherCodes = {
0: 'Clear',
1: 'Mostly Clear',
2: 'Partly Cloudy',
3: 'Cloudy',
45: 'Fog',
48: 'Freezing Fog',
51: 'Light Drizzle',
53: 'Drizzle',
55: 'Heavy Drizzle',
56: 'Light Freezing Drizzle',
57: 'Freezing Drizzle',
61: 'Light Rain',
63: 'Rain',
65: 'Heavy Rain',
66: 'Light Freezing Rain',
67: 'Freezing Rain',
71: 'Light Snow',
73: 'Snow',
75: 'Heavy Snow',
77: 'Snow Grains',
80: 'Light Rain Shower',
81: 'Rain Shower',
82: 'Heavy Rain Shower',
85: 'Light Snow Shower',
86: 'Heavy Snow Shower',
95: 'Thunderstorm',
96: 'Thunderstorm with Hail',
99: 'Heavy Hailstorm'
}
async function fetchNews(country = 'us') {
const res = await fetch(
`https://newsapi.org/v2/top-headlines?country=${country}&apiKey=${NEWS_KEY}&pageSize=5` // for basic shows 5 pages news
)
if (!res.ok) throw new Error(`NewsAPI error ${res.status}`)
const json = await res.json()
return json.articles.slice(0, 5).map(a => ({
title: a.title,
url: a.url,
source: a.source.name,
published: a.publishedAt
}))
}
async function geocodeCity(city) {
const res = await fetch(
`https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city)}&count=1`
)
if (!res.ok) throw new Error(`Geocoding error: ${res.status}`)
const j = await res.json()
if (!j.results?.length) throw new Error(`City not found: ${city}`)
return j.results[0]
}
async function fetchWeather(lat, lon) {
const res = await fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}¤t_weather=true`
)
if (!res.ok) throw new Error(`Weather API error: ${res.status}`)
return res.json()
}
async function main() {
const server = new McpServer({ name: 'news-weather-mcp', version: '1.2.1' })
server.registerTool('GetNews', {
title: 'Get World News',
description: 'Top 5 headlines for a country (ISO code)',
inputSchema: { country: z.string().length(2).default('us') }
}, async ({ country }) => {
const articles = await fetchNews(country)
const summaryText = articles
.map((a, i) => `${i + 1}. ${a.title} (${a.source})`)
.join('\n')
return {
content: [{ type: 'text', text: `Top news for ${country.toUpperCase()}:\n` + summaryText }],
structuredContent: { articles }
}
})
server.registerTool('GetWeather', {
title: 'Get Weather by City',
description: 'Current weather by city name',
inputSchema: { city: z.string() }
}, async ({ city }) => {
const geo = await geocodeCity(city)
const j = await fetchWeather(geo.latitude, geo.longitude)
const cw = j.current_weather
const description = weatherCodes[cw.weathercode] || 'Unknown'
const summary = `Weather in ${geo.name}: ${description}, ${cw.temperature}°C, wind ${cw.windspeed} m/s`
return {
content: [{ type: 'text', text: summary }],
structuredContent: {
city: geo.name,
temperature: cw.temperature,
windspeed: cw.windspeed,
description,
time: cw.time
}
}
})
const transport = new StdioServerTransport()
await server.connect(transport)
}
main().catch(err => {
console.error(err)
process.exit(1)
})