data-aggregator-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +336 -0
  2. package/dist/index.d.ts +14 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +333 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/tools/exchange.d.ts +15 -0
  7. package/dist/tools/exchange.d.ts.map +1 -0
  8. package/dist/tools/exchange.js +195 -0
  9. package/dist/tools/exchange.js.map +1 -0
  10. package/dist/tools/news.d.ts +20 -0
  11. package/dist/tools/news.d.ts.map +1 -0
  12. package/dist/tools/news.js +175 -0
  13. package/dist/tools/news.js.map +1 -0
  14. package/dist/tools/public-data.d.ts +24 -0
  15. package/dist/tools/public-data.d.ts.map +1 -0
  16. package/dist/tools/public-data.js +262 -0
  17. package/dist/tools/public-data.js.map +1 -0
  18. package/dist/tools/scraper.d.ts +19 -0
  19. package/dist/tools/scraper.d.ts.map +1 -0
  20. package/dist/tools/scraper.js +185 -0
  21. package/dist/tools/scraper.js.map +1 -0
  22. package/dist/tools/stocks.d.ts +14 -0
  23. package/dist/tools/stocks.d.ts.map +1 -0
  24. package/dist/tools/stocks.js +172 -0
  25. package/dist/tools/stocks.js.map +1 -0
  26. package/dist/tools/weather.d.ts +15 -0
  27. package/dist/tools/weather.d.ts.map +1 -0
  28. package/dist/tools/weather.js +172 -0
  29. package/dist/tools/weather.js.map +1 -0
  30. package/dist/types.d.ts +160 -0
  31. package/dist/types.d.ts.map +1 -0
  32. package/dist/types.js +3 -0
  33. package/dist/types.js.map +1 -0
  34. package/dist/utils/cache.d.ts +45 -0
  35. package/dist/utils/cache.d.ts.map +1 -0
  36. package/dist/utils/cache.js +88 -0
  37. package/dist/utils/cache.js.map +1 -0
  38. package/dist/utils/http.d.ts +39 -0
  39. package/dist/utils/http.d.ts.map +1 -0
  40. package/dist/utils/http.js +103 -0
  41. package/dist/utils/http.js.map +1 -0
  42. package/dist/utils/rate-limiter.d.ts +34 -0
  43. package/dist/utils/rate-limiter.d.ts.map +1 -0
  44. package/dist/utils/rate-limiter.js +95 -0
  45. package/dist/utils/rate-limiter.js.map +1 -0
  46. package/package.json +42 -0
  47. package/src/index.ts +461 -0
  48. package/src/tools/exchange.ts +241 -0
  49. package/src/tools/news.ts +238 -0
  50. package/src/tools/public-data.ts +325 -0
  51. package/src/tools/scraper.ts +217 -0
  52. package/src/tools/stocks.ts +205 -0
  53. package/src/tools/weather.ts +216 -0
  54. package/src/types.ts +184 -0
  55. package/src/utils/cache.ts +103 -0
  56. package/src/utils/http.ts +156 -0
  57. package/src/utils/rate-limiter.ts +114 -0
  58. package/tsconfig.json +19 -0
package/README.md ADDED
@@ -0,0 +1,336 @@
1
+ # Data Aggregator MCP Server
2
+
3
+ A unified MCP (Model Context Protocol) server that replaces 5-10 separate data servers with one installation. Instead of juggling separate servers for financial data, weather, news, web scraping, and more, install **one server** that handles all your data needs.
4
+
5
+ **Solves "MCP server sprawl."**
6
+
7
+ ## Quick Start
8
+
9
+ ### Install and run with npx
10
+
11
+ ```bash
12
+ npx data-aggregator-mcp
13
+ ```
14
+
15
+ ### Or clone and build
16
+
17
+ ```bash
18
+ git clone <repo-url>
19
+ cd data-aggregator
20
+ npm install
21
+ npm run build
22
+ npm start
23
+ ```
24
+
25
+ ### Claude Desktop Configuration
26
+
27
+ Add to your `claude_desktop_config.json`:
28
+
29
+ ```json
30
+ {
31
+ "mcpServers": {
32
+ "data-aggregator": {
33
+ "command": "node",
34
+ "args": ["/path/to/data-aggregator/dist/index.js"],
35
+ "env": {
36
+ "NEWS_API_KEY": "your-optional-newsapi-key",
37
+ "ALPHA_VANTAGE_KEY": "your-optional-alpha-vantage-key"
38
+ }
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ ### HTTP/SSE Transport (Remote Deployment)
45
+
46
+ ```bash
47
+ # Start with HTTP transport on port 3000 (default)
48
+ npm run start:sse
49
+
50
+ # Or specify a custom port
51
+ node dist/index.js --sse --port=8080
52
+ ```
53
+
54
+ ## Tools
55
+
56
+ ### 1. `query_stocks` - Market Data
57
+
58
+ Fetch real-time stock and cryptocurrency prices.
59
+
60
+ **Sources:** Yahoo Finance (stocks), CoinGecko (crypto) -- both free, no API key required. Optional Alpha Vantage support with `ALPHA_VANTAGE_KEY` env variable.
61
+
62
+ | Parameter | Type | Required | Description |
63
+ |-----------|------|----------|-------------|
64
+ | `symbol` | string | Yes | Ticker symbol (e.g., `AAPL`, `BTC`, `ETH`) |
65
+ | `type` | `"stock"` \| `"crypto"` \| `"auto"` | No | Asset type detection (default: `auto`) |
66
+
67
+ **Example requests:**
68
+ ```
69
+ "Get me the current price of AAPL"
70
+ "What's Bitcoin trading at?"
71
+ "Fetch SOL crypto price"
72
+ ```
73
+
74
+ **Example response:**
75
+ ```json
76
+ {
77
+ "symbol": "AAPL",
78
+ "price": 198.52,
79
+ "currency": "USD",
80
+ "change": 2.34,
81
+ "changePercent": 1.19,
82
+ "volume": 54321000,
83
+ "high": 199.10,
84
+ "low": 196.80,
85
+ "source": "Yahoo Finance"
86
+ }
87
+ ```
88
+
89
+ ---
90
+
91
+ ### 2. `fetch_webpage` - Web Scraping
92
+
93
+ Extract structured data from any web page.
94
+
95
+ | Parameter | Type | Required | Description |
96
+ |-----------|------|----------|-------------|
97
+ | `url` | string (URL) | Yes | Page to scrape |
98
+ | `selector` | string | No | CSS selector (`#id`, `.class`, or tag name) |
99
+
100
+ **Example requests:**
101
+ ```
102
+ "Scrape the main content from https://example.com"
103
+ "Get the article text from this URL using selector .post-content"
104
+ ```
105
+
106
+ **Returns:** Title, meta description, headings, main content (text only), and links.
107
+
108
+ ---
109
+
110
+ ### 3. `search_news` - News Aggregation
111
+
112
+ Search and aggregate news from multiple sources.
113
+
114
+ **Sources:** Google News RSS (free, no key) or NewsAPI.org (optional key via `NEWS_API_KEY` env).
115
+
116
+ | Parameter | Type | Required | Description |
117
+ |-----------|------|----------|-------------|
118
+ | `query` | string | No | Search keyword |
119
+ | `category` | string | No | `business`, `technology`, `science`, etc. (NewsAPI only) |
120
+ | `source` | string | No | Source ID (e.g., `bbc-news`) |
121
+ | `language` | string | No | Language code (default: `en`) |
122
+ | `from` | string | No | Start date `YYYY-MM-DD` (NewsAPI only) |
123
+ | `to` | string | No | End date `YYYY-MM-DD` (NewsAPI only) |
124
+ | `maxResults` | number | No | 1-100 (default: 10) |
125
+
126
+ **Example requests:**
127
+ ```
128
+ "Search news about artificial intelligence"
129
+ "Get top technology news"
130
+ "Find news about climate change from the last week"
131
+ ```
132
+
133
+ ---
134
+
135
+ ### 4. `query_weather` - Weather Data
136
+
137
+ Current conditions and 7-day forecasts.
138
+
139
+ **Source:** Open-Meteo API (100% free, no API key required).
140
+
141
+ | Parameter | Type | Required | Description |
142
+ |-----------|------|----------|-------------|
143
+ | `city` | string | No* | City name (e.g., `"London"`) |
144
+ | `latitude` | number | No* | Latitude (-90 to 90) |
145
+ | `longitude` | number | No* | Longitude (-180 to 180) |
146
+ | `units` | `"celsius"` \| `"fahrenheit"` | No | Temperature units (default: celsius) |
147
+
148
+ *Provide either `city` or both `latitude`/`longitude`.
149
+
150
+ **Example requests:**
151
+ ```
152
+ "What's the weather in Tokyo?"
153
+ "Get the 7-day forecast for New York in Fahrenheit"
154
+ "Weather at coordinates 51.5, -0.1"
155
+ ```
156
+
157
+ **Example response:**
158
+ ```json
159
+ {
160
+ "location": "London, England, United Kingdom",
161
+ "current": {
162
+ "temperature": 15.2,
163
+ "feelsLike": 13.8,
164
+ "humidity": 72,
165
+ "windSpeed": 12.5,
166
+ "weatherDescription": "Partly cloudy"
167
+ },
168
+ "forecast": [
169
+ {
170
+ "date": "2026-03-31",
171
+ "temperatureMax": 17.1,
172
+ "temperatureMin": 8.3,
173
+ "precipitationProbability": 20,
174
+ "weatherDescription": "Mainly clear"
175
+ }
176
+ ]
177
+ }
178
+ ```
179
+
180
+ ---
181
+
182
+ ### 5. `query_exchange_rates` - Currency Exchange
183
+
184
+ Real-time and historical exchange rates with conversion.
185
+
186
+ **Sources:** Frankfurter/ECB (fiat, free), CoinGecko (crypto, free).
187
+
188
+ | Parameter | Type | Required | Description |
189
+ |-----------|------|----------|-------------|
190
+ | `base` | string | Yes | Base currency (`USD`, `EUR`, `BTC`, etc.) |
191
+ | `target` | string | Yes | Target currency |
192
+ | `amount` | number | No | Amount to convert (default: 1) |
193
+ | `date` | string | No | Historical date `YYYY-MM-DD` (fiat only) |
194
+
195
+ **Supported flows:** Fiat-to-fiat, fiat-to-crypto, crypto-to-fiat, crypto-to-crypto.
196
+
197
+ **Example requests:**
198
+ ```
199
+ "Convert 100 USD to EUR"
200
+ "What's the BTC to USD rate?"
201
+ "Historical EUR/GBP rate on 2025-01-15"
202
+ ```
203
+
204
+ ---
205
+
206
+ ### 6. `query_public_data` - Multi-Purpose API
207
+
208
+ Access various public data APIs through sub-commands.
209
+
210
+ | Parameter | Type | Required | Description |
211
+ |-----------|------|----------|-------------|
212
+ | `command` | string | Yes | Sub-command (see below) |
213
+
214
+ **Sub-commands:**
215
+
216
+ #### `wikipedia` - Article Summaries
217
+ | Parameter | Description |
218
+ |-----------|-------------|
219
+ | `query` | Search term (e.g., `"Theory of relativity"`) |
220
+ | `language` | Wiki language code (default: `en`) |
221
+
222
+ #### `ip_geolocation` - IP Location Lookup
223
+ | Parameter | Description |
224
+ |-----------|-------------|
225
+ | `ip` | IP address (omit for your own IP) |
226
+
227
+ #### `dns_lookup` - DNS Records
228
+ | Parameter | Description |
229
+ |-----------|-------------|
230
+ | `domain` | Domain name (e.g., `"example.com"`) |
231
+ | `recordType` | `A`, `AAAA`, `MX`, `TXT`, `NS`, `CNAME`, `SOA` (default: `A`) |
232
+
233
+ #### `expand_url` - URL Expander
234
+ | Parameter | Description |
235
+ |-----------|-------------|
236
+ | `url` | Shortened URL to follow to its destination |
237
+
238
+ **Example requests:**
239
+ ```
240
+ "Look up the Wikipedia article on quantum computing"
241
+ "What's the geolocation of IP 8.8.8.8?"
242
+ "Get MX records for gmail.com"
243
+ "Expand this shortened URL: https://bit.ly/xyz"
244
+ ```
245
+
246
+ ## Environment Variables
247
+
248
+ | Variable | Required | Description |
249
+ |----------|----------|-------------|
250
+ | `NEWS_API_KEY` | No | NewsAPI.org key for enhanced news search. Falls back to Google News RSS without it. |
251
+ | `ALPHA_VANTAGE_KEY` | No | Alpha Vantage key for stock data fallback. Yahoo Finance is used by default. |
252
+
253
+ ## Architecture
254
+
255
+ ### Caching
256
+
257
+ Built-in in-memory cache with per-category TTLs:
258
+
259
+ | Data Type | Cache Duration |
260
+ |-----------|---------------|
261
+ | Market data (stocks/crypto) | 5 minutes |
262
+ | Exchange rates | 30 minutes |
263
+ | News articles | 15 minutes |
264
+ | Weather | 1 hour |
265
+ | Web scraping | 10 minutes |
266
+ | Public data (Wikipedia, DNS, etc.) | 1 hour |
267
+
268
+ ### Rate Limiting
269
+
270
+ Per-source sliding-window rate limiters prevent API abuse:
271
+
272
+ | Source | Limit |
273
+ |--------|-------|
274
+ | CoinGecko | 30 req/min |
275
+ | Open-Meteo | 60 req/min |
276
+ | Frankfurter (exchange) | 60 req/min |
277
+ | NewsAPI | 50 req/min |
278
+ | Google News RSS | 60 req/min |
279
+ | Wikipedia | 100 req/min |
280
+ | Generic HTTP | 120 req/min |
281
+
282
+ ### Error Handling
283
+
284
+ - Automatic retries with exponential back-off (up to 3 attempts)
285
+ - Graceful degradation when APIs are unavailable
286
+ - User-friendly error messages
287
+ - Fallback sources (Yahoo Finance -> Alpha Vantage, NewsAPI -> Google News RSS)
288
+
289
+ ## Pricing
290
+
291
+ ### Free Tier
292
+ - **100 queries/day** across all tools
293
+ - No credit card required
294
+ - All 6 tools included
295
+
296
+ ### Pay-Per-Query
297
+ - **$0.01 - $0.05 per query** depending on data source
298
+ - No monthly commitment
299
+ - Volume discounts available
300
+
301
+ ### Pro Plan - $16/month
302
+ - **Unlimited queries**
303
+ - Priority rate limits
304
+ - Email support
305
+ - Via [MCPize](https://mcpize.com)
306
+
307
+ ### Premium Plan - $32/month
308
+ - Everything in Pro
309
+ - Dedicated rate limit pools
310
+ - Custom API key management
311
+ - Priority support
312
+ - Historical data access
313
+ - Via [MCPize](https://mcpize.com)
314
+
315
+ ## Development
316
+
317
+ ```bash
318
+ # Install dependencies
319
+ npm install
320
+
321
+ # Build TypeScript
322
+ npm run build
323
+
324
+ # Watch mode for development
325
+ npm run dev
326
+
327
+ # Run the server (stdio)
328
+ npm start
329
+
330
+ # Run the server (HTTP/SSE)
331
+ npm run start:sse
332
+ ```
333
+
334
+ ## License
335
+
336
+ MIT
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Data Aggregator MCP Server
4
+ *
5
+ * A unified data server that replaces 5-10 separate MCP servers.
6
+ * Provides stocks, weather, news, web scraping, exchange rates,
7
+ * and public data queries through a single installation.
8
+ *
9
+ * Transports:
10
+ * - stdio (default): for Claude Desktop, CLI tools
11
+ * - HTTP/SSE (--sse or --http flag): for remote deployments
12
+ */
13
+ export {};
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;GAUG"}
package/dist/index.js ADDED
@@ -0,0 +1,333 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Data Aggregator MCP Server
4
+ *
5
+ * A unified data server that replaces 5-10 separate MCP servers.
6
+ * Provides stocks, weather, news, web scraping, exchange rates,
7
+ * and public data queries through a single installation.
8
+ *
9
+ * Transports:
10
+ * - stdio (default): for Claude Desktop, CLI tools
11
+ * - HTTP/SSE (--sse or --http flag): for remote deployments
12
+ */
13
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
15
+ import { z } from "zod";
16
+ import { queryStocks } from "./tools/stocks.js";
17
+ import { fetchWebpage } from "./tools/scraper.js";
18
+ import { searchNews } from "./tools/news.js";
19
+ import { queryWeather } from "./tools/weather.js";
20
+ import { queryExchangeRates } from "./tools/exchange.js";
21
+ import { queryPublicData } from "./tools/public-data.js";
22
+ import { cache } from "./utils/cache.js";
23
+ // ─── Server Setup ──────────────────────────────────────────────────────────
24
+ const server = new McpServer({
25
+ name: "data-aggregator",
26
+ version: "1.0.0",
27
+ }, {
28
+ capabilities: {
29
+ logging: {},
30
+ },
31
+ });
32
+ // ─── Tool Registration ────────────────────────────────────────────────────
33
+ // 1. query_stocks
34
+ server.tool("query_stocks", "Fetch real-time stock and cryptocurrency market data. Supports major stocks (AAPL, MSFT, GOOGL) and crypto (BTC, ETH, SOL). Returns price, daily change, volume, and market cap. Data from Yahoo Finance (stocks) and CoinGecko (crypto).", {
35
+ symbol: z
36
+ .string()
37
+ .describe("Ticker symbol (e.g., AAPL, MSFT, GOOGL for stocks; BTC, ETH, SOL for crypto)"),
38
+ type: z
39
+ .enum(["stock", "crypto", "auto"])
40
+ .optional()
41
+ .default("auto")
42
+ .describe('Asset type. "auto" detects based on symbol. Use "stock" or "crypto" to force.'),
43
+ }, async (args) => queryStocks(args));
44
+ // 2. fetch_webpage
45
+ server.tool("fetch_webpage", "Scrape and extract structured data from any web page. Returns title, meta description, headings, main text content, and links. Optionally extract specific content using a CSS selector (#id, .class, or tag name).", {
46
+ url: z.string().url().describe("The URL to scrape"),
47
+ selector: z
48
+ .string()
49
+ .optional()
50
+ .describe('Optional CSS-like selector to extract specific content (e.g., "#main", ".article-body", "article")'),
51
+ }, async (args) => fetchWebpage(args));
52
+ // 3. search_news
53
+ server.tool("search_news", "Search and aggregate news articles from multiple sources. Uses NewsAPI.org (if API key configured) or Google News RSS (free, no key). Filter by keyword, category, source, date range, and language.", {
54
+ query: z
55
+ .string()
56
+ .optional()
57
+ .describe("Search keyword or phrase (e.g., 'artificial intelligence', 'climate change')"),
58
+ category: z
59
+ .enum([
60
+ "business",
61
+ "entertainment",
62
+ "general",
63
+ "health",
64
+ "science",
65
+ "sports",
66
+ "technology",
67
+ ])
68
+ .optional()
69
+ .describe("News category (only with NewsAPI key, ignored with Google News RSS)"),
70
+ source: z
71
+ .string()
72
+ .optional()
73
+ .describe('Specific news source ID (e.g., "bbc-news", "cnn", "the-verge")'),
74
+ language: z
75
+ .string()
76
+ .optional()
77
+ .default("en")
78
+ .describe("Language code (default: en)"),
79
+ from: z
80
+ .string()
81
+ .optional()
82
+ .describe("Start date for articles (YYYY-MM-DD format, NewsAPI only)"),
83
+ to: z
84
+ .string()
85
+ .optional()
86
+ .describe("End date for articles (YYYY-MM-DD format, NewsAPI only)"),
87
+ maxResults: z
88
+ .number()
89
+ .int()
90
+ .min(1)
91
+ .max(100)
92
+ .optional()
93
+ .default(10)
94
+ .describe("Maximum number of articles to return (1-100, default: 10)"),
95
+ }, async (args) => searchNews(args));
96
+ // 4. query_weather
97
+ server.tool("query_weather", "Get current weather conditions and 7-day forecast. Uses Open-Meteo API (free, no API key required). Accepts city name or coordinates. Returns temperature, humidity, wind, precipitation, and daily forecasts.", {
98
+ city: z
99
+ .string()
100
+ .optional()
101
+ .describe('City name (e.g., "London", "New York", "Tokyo")'),
102
+ latitude: z
103
+ .number()
104
+ .min(-90)
105
+ .max(90)
106
+ .optional()
107
+ .describe("Latitude (-90 to 90). Use with longitude as alternative to city name."),
108
+ longitude: z
109
+ .number()
110
+ .min(-180)
111
+ .max(180)
112
+ .optional()
113
+ .describe("Longitude (-180 to 180). Use with latitude as alternative to city name."),
114
+ units: z
115
+ .enum(["celsius", "fahrenheit"])
116
+ .optional()
117
+ .default("celsius")
118
+ .describe("Temperature units (default: celsius)"),
119
+ }, async (args) => queryWeather(args));
120
+ // 5. query_exchange_rates
121
+ server.tool("query_exchange_rates", "Get currency exchange rates and perform conversions. Supports all major fiat currencies (USD, EUR, GBP, JPY, etc.) and popular cryptocurrencies (BTC, ETH, SOL, etc.). Can fetch historical rates by date.", {
122
+ base: z
123
+ .string()
124
+ .describe('Base currency code (e.g., "USD", "EUR", "BTC")'),
125
+ target: z
126
+ .string()
127
+ .describe('Target currency code (e.g., "GBP", "JPY", "ETH")'),
128
+ amount: z
129
+ .number()
130
+ .positive()
131
+ .optional()
132
+ .default(1)
133
+ .describe("Amount to convert (default: 1)"),
134
+ date: z
135
+ .string()
136
+ .optional()
137
+ .describe("Historical date in YYYY-MM-DD format (fiat only). Omit for current rates."),
138
+ }, async (args) => queryExchangeRates(args));
139
+ // 6. query_public_data
140
+ server.tool("query_public_data", "Access miscellaneous public data APIs: Wikipedia summaries, IP geolocation, DNS lookups, and URL expansion. Select the sub-command and provide the relevant parameters.", {
141
+ command: z
142
+ .enum(["wikipedia", "ip_geolocation", "dns_lookup", "expand_url"])
143
+ .describe("Sub-command to run: wikipedia (article summary), ip_geolocation (IP location), dns_lookup (DNS records), expand_url (follow redirects)"),
144
+ query: z
145
+ .string()
146
+ .optional()
147
+ .describe('Search term for Wikipedia (e.g., "Theory of relativity")'),
148
+ ip: z
149
+ .string()
150
+ .optional()
151
+ .describe('IP address for geolocation (e.g., "8.8.8.8"). Omit to look up your own IP.'),
152
+ domain: z
153
+ .string()
154
+ .optional()
155
+ .describe('Domain for DNS lookup (e.g., "example.com")'),
156
+ recordType: z
157
+ .enum(["A", "AAAA", "MX", "TXT", "NS", "CNAME", "SOA"])
158
+ .optional()
159
+ .default("A")
160
+ .describe("DNS record type (default: A)"),
161
+ url: z
162
+ .string()
163
+ .optional()
164
+ .describe('Shortened URL to expand (e.g., "https://bit.ly/xyz")'),
165
+ language: z
166
+ .string()
167
+ .optional()
168
+ .default("en")
169
+ .describe("Language code for Wikipedia (default: en)"),
170
+ }, async (args) => queryPublicData(args));
171
+ // ─── Transport Selection & Startup ─────────────────────────────────────────
172
+ async function main() {
173
+ const args = process.argv.slice(2);
174
+ const useHttp = args.includes("--sse") || args.includes("--http");
175
+ if (useHttp) {
176
+ // HTTP / SSE transport for remote deployments
177
+ const portArg = args.find((a) => a.startsWith("--port="));
178
+ const port = portArg ? parseInt(portArg.split("=")[1], 10) : 3000;
179
+ try {
180
+ const { StreamableHTTPServerTransport } = await import("@modelcontextprotocol/sdk/server/streamableHttp.js");
181
+ const { createServer } = await import("node:http");
182
+ const { randomUUID } = await import("node:crypto");
183
+ const transports = {};
184
+ const httpServer = createServer(async (req, res) => {
185
+ // CORS headers
186
+ res.setHeader("Access-Control-Allow-Origin", "*");
187
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
188
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, mcp-session-id");
189
+ if (req.method === "OPTIONS") {
190
+ res.writeHead(204);
191
+ res.end();
192
+ return;
193
+ }
194
+ // Health check endpoint
195
+ if (req.url === "/health") {
196
+ res.writeHead(200, { "Content-Type": "application/json" });
197
+ res.end(JSON.stringify({ status: "ok", server: "data-aggregator", version: "1.0.0" }));
198
+ return;
199
+ }
200
+ if (req.url !== "/mcp") {
201
+ res.writeHead(404);
202
+ res.end("Not found");
203
+ return;
204
+ }
205
+ const sessionId = req.headers["mcp-session-id"];
206
+ // Parse body for POST requests
207
+ let body = undefined;
208
+ if (req.method === "POST") {
209
+ const chunks = [];
210
+ for await (const chunk of req) {
211
+ chunks.push(chunk);
212
+ }
213
+ try {
214
+ body = JSON.parse(Buffer.concat(chunks).toString());
215
+ }
216
+ catch {
217
+ res.writeHead(400, { "Content-Type": "application/json" });
218
+ res.end(JSON.stringify({ error: "Invalid JSON" }));
219
+ return;
220
+ }
221
+ }
222
+ if (req.method === "GET" && sessionId && transports[sessionId]) {
223
+ await transports[sessionId].handleRequest(req, res);
224
+ return;
225
+ }
226
+ if (req.method === "POST" && sessionId && transports[sessionId]) {
227
+ await transports[sessionId].handleRequest(req, res, body);
228
+ return;
229
+ }
230
+ if (req.method === "POST" && !sessionId) {
231
+ // New session
232
+ const transport = new StreamableHTTPServerTransport({
233
+ sessionIdGenerator: () => randomUUID(),
234
+ onsessioninitialized: (id) => {
235
+ transports[id] = transport;
236
+ },
237
+ });
238
+ transport.onclose = () => {
239
+ if (transport.sessionId) {
240
+ delete transports[transport.sessionId];
241
+ }
242
+ };
243
+ const sessionServer = new McpServer({ name: "data-aggregator", version: "1.0.0" }, { capabilities: { logging: {} } });
244
+ // Re-register tools for this session's server
245
+ registerTools(sessionServer);
246
+ await sessionServer.connect(transport);
247
+ await transport.handleRequest(req, res, body);
248
+ return;
249
+ }
250
+ res.writeHead(400, { "Content-Type": "application/json" });
251
+ res.end(JSON.stringify({
252
+ jsonrpc: "2.0",
253
+ error: { code: -32000, message: "Invalid request or session" },
254
+ id: null,
255
+ }));
256
+ });
257
+ httpServer.listen(port, "0.0.0.0", () => {
258
+ console.error(`Data Aggregator MCP server (HTTP) listening on http://0.0.0.0:${port}/mcp`);
259
+ console.error(`Health check: http://0.0.0.0:${port}/health`);
260
+ });
261
+ }
262
+ catch (err) {
263
+ console.error("Failed to start HTTP transport. Make sure @modelcontextprotocol/node is installed.", err);
264
+ process.exit(1);
265
+ }
266
+ }
267
+ else {
268
+ // stdio transport (default) for Claude Desktop, CLI
269
+ const transport = new StdioServerTransport();
270
+ await server.connect(transport);
271
+ console.error("Data Aggregator MCP server started (stdio transport)");
272
+ }
273
+ }
274
+ /**
275
+ * Register all tools on a given McpServer instance.
276
+ * Used for per-session servers in HTTP mode.
277
+ */
278
+ function registerTools(s) {
279
+ s.tool("query_stocks", "Fetch real-time stock and cryptocurrency market data.", {
280
+ symbol: z.string().describe("Ticker symbol (e.g., AAPL, BTC)"),
281
+ type: z.enum(["stock", "crypto", "auto"]).optional().default("auto")
282
+ .describe('Asset type. "auto" detects based on symbol.'),
283
+ }, async (args) => queryStocks(args));
284
+ s.tool("fetch_webpage", "Scrape and extract structured data from any web page.", {
285
+ url: z.string().url().describe("The URL to scrape"),
286
+ selector: z.string().optional()
287
+ .describe("Optional CSS-like selector to extract specific content"),
288
+ }, async (args) => fetchWebpage(args));
289
+ s.tool("search_news", "Search and aggregate news articles from multiple sources.", {
290
+ query: z.string().optional().describe("Search keyword or phrase"),
291
+ category: z.enum(["business", "entertainment", "general", "health", "science", "sports", "technology"]).optional(),
292
+ source: z.string().optional().describe("Specific news source ID"),
293
+ language: z.string().optional().default("en"),
294
+ from: z.string().optional().describe("Start date (YYYY-MM-DD)"),
295
+ to: z.string().optional().describe("End date (YYYY-MM-DD)"),
296
+ maxResults: z.number().int().min(1).max(100).optional().default(10),
297
+ }, async (args) => searchNews(args));
298
+ s.tool("query_weather", "Get current weather conditions and 7-day forecast via Open-Meteo.", {
299
+ city: z.string().optional().describe("City name"),
300
+ latitude: z.number().min(-90).max(90).optional(),
301
+ longitude: z.number().min(-180).max(180).optional(),
302
+ units: z.enum(["celsius", "fahrenheit"]).optional().default("celsius"),
303
+ }, async (args) => queryWeather(args));
304
+ s.tool("query_exchange_rates", "Get currency exchange rates and perform conversions.", {
305
+ base: z.string().describe("Base currency code"),
306
+ target: z.string().describe("Target currency code"),
307
+ amount: z.number().positive().optional().default(1),
308
+ date: z.string().optional().describe("Historical date (YYYY-MM-DD)"),
309
+ }, async (args) => queryExchangeRates(args));
310
+ s.tool("query_public_data", "Access Wikipedia, IP geolocation, DNS lookup, and URL expansion.", {
311
+ command: z.enum(["wikipedia", "ip_geolocation", "dns_lookup", "expand_url"]),
312
+ query: z.string().optional(),
313
+ ip: z.string().optional(),
314
+ domain: z.string().optional(),
315
+ recordType: z.enum(["A", "AAAA", "MX", "TXT", "NS", "CNAME", "SOA"]).optional().default("A"),
316
+ url: z.string().optional(),
317
+ language: z.string().optional().default("en"),
318
+ }, async (args) => queryPublicData(args));
319
+ }
320
+ // ─── Graceful shutdown ─────────────────────────────────────────────────────
321
+ function shutdown() {
322
+ console.error("Shutting down Data Aggregator MCP server...");
323
+ cache.destroy();
324
+ process.exit(0);
325
+ }
326
+ process.on("SIGINT", shutdown);
327
+ process.on("SIGTERM", shutdown);
328
+ // ─── Start ─────────────────────────────────────────────────────────────────
329
+ main().catch((err) => {
330
+ console.error("Fatal error starting server:", err);
331
+ process.exit(1);
332
+ });
333
+ //# sourceMappingURL=index.js.map