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.
Potentially problematic release.
This version of data-aggregator-mcp might be problematic. Click here for more details.
- package/README.md +336 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +333 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/exchange.d.ts +15 -0
- package/dist/tools/exchange.d.ts.map +1 -0
- package/dist/tools/exchange.js +195 -0
- package/dist/tools/exchange.js.map +1 -0
- package/dist/tools/news.d.ts +20 -0
- package/dist/tools/news.d.ts.map +1 -0
- package/dist/tools/news.js +175 -0
- package/dist/tools/news.js.map +1 -0
- package/dist/tools/public-data.d.ts +24 -0
- package/dist/tools/public-data.d.ts.map +1 -0
- package/dist/tools/public-data.js +262 -0
- package/dist/tools/public-data.js.map +1 -0
- package/dist/tools/scraper.d.ts +19 -0
- package/dist/tools/scraper.d.ts.map +1 -0
- package/dist/tools/scraper.js +185 -0
- package/dist/tools/scraper.js.map +1 -0
- package/dist/tools/stocks.d.ts +14 -0
- package/dist/tools/stocks.d.ts.map +1 -0
- package/dist/tools/stocks.js +172 -0
- package/dist/tools/stocks.js.map +1 -0
- package/dist/tools/weather.d.ts +15 -0
- package/dist/tools/weather.d.ts.map +1 -0
- package/dist/tools/weather.js +172 -0
- package/dist/tools/weather.js.map +1 -0
- package/dist/types.d.ts +160 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/cache.d.ts +45 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +88 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/http.d.ts +39 -0
- package/dist/utils/http.d.ts.map +1 -0
- package/dist/utils/http.js +103 -0
- package/dist/utils/http.js.map +1 -0
- package/dist/utils/rate-limiter.d.ts +34 -0
- package/dist/utils/rate-limiter.d.ts.map +1 -0
- package/dist/utils/rate-limiter.js +95 -0
- package/dist/utils/rate-limiter.js.map +1 -0
- package/package.json +42 -0
- package/src/index.ts +461 -0
- package/src/tools/exchange.ts +241 -0
- package/src/tools/news.ts +238 -0
- package/src/tools/public-data.ts +325 -0
- package/src/tools/scraper.ts +217 -0
- package/src/tools/stocks.ts +205 -0
- package/src/tools/weather.ts +216 -0
- package/src/types.ts +184 -0
- package/src/utils/cache.ts +103 -0
- package/src/utils/http.ts +156 -0
- package/src/utils/rate-limiter.ts +114 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory cache with per-key TTL support.
|
|
3
|
+
*
|
|
4
|
+
* TTL defaults (in milliseconds):
|
|
5
|
+
* - Market data: 5 minutes (300_000)
|
|
6
|
+
* - Exchange rates: 30 minutes (1_800_000)
|
|
7
|
+
* - Weather: 1 hour (3_600_000)
|
|
8
|
+
* - News: 15 minutes (900_000)
|
|
9
|
+
* - Web scraping: 10 minutes (600_000)
|
|
10
|
+
* - Public data: 1 hour (3_600_000)
|
|
11
|
+
*/
|
|
12
|
+
export const TTL = {
|
|
13
|
+
MARKET: 5 * 60 * 1000,
|
|
14
|
+
EXCHANGE: 30 * 60 * 1000,
|
|
15
|
+
WEATHER: 60 * 60 * 1000,
|
|
16
|
+
NEWS: 15 * 60 * 1000,
|
|
17
|
+
SCRAPE: 10 * 60 * 1000,
|
|
18
|
+
PUBLIC: 60 * 60 * 1000,
|
|
19
|
+
};
|
|
20
|
+
export class Cache {
|
|
21
|
+
store = new Map();
|
|
22
|
+
cleanupInterval;
|
|
23
|
+
constructor(cleanupIntervalMs = 60_000) {
|
|
24
|
+
// Periodic sweep to evict expired entries
|
|
25
|
+
this.cleanupInterval = setInterval(() => this.evictExpired(), cleanupIntervalMs);
|
|
26
|
+
// Allow the process to exit even if the interval is still running
|
|
27
|
+
if (this.cleanupInterval.unref) {
|
|
28
|
+
this.cleanupInterval.unref();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Retrieve a cached value, or undefined if missing / expired. */
|
|
32
|
+
get(key) {
|
|
33
|
+
const entry = this.store.get(key);
|
|
34
|
+
if (!entry)
|
|
35
|
+
return undefined;
|
|
36
|
+
if (Date.now() > entry.expiresAt) {
|
|
37
|
+
this.store.delete(key);
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
return entry.data;
|
|
41
|
+
}
|
|
42
|
+
/** Store a value with a TTL (milliseconds). */
|
|
43
|
+
set(key, data, ttlMs) {
|
|
44
|
+
this.store.set(key, {
|
|
45
|
+
data,
|
|
46
|
+
expiresAt: Date.now() + ttlMs,
|
|
47
|
+
createdAt: Date.now(),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/** Check whether a non-expired entry exists. */
|
|
51
|
+
has(key) {
|
|
52
|
+
return this.get(key) !== undefined;
|
|
53
|
+
}
|
|
54
|
+
/** Remove a single key. */
|
|
55
|
+
delete(key) {
|
|
56
|
+
this.store.delete(key);
|
|
57
|
+
}
|
|
58
|
+
/** Remove all entries. */
|
|
59
|
+
clear() {
|
|
60
|
+
this.store.clear();
|
|
61
|
+
}
|
|
62
|
+
/** Number of entries (including possibly-expired ones until next sweep). */
|
|
63
|
+
get size() {
|
|
64
|
+
return this.store.size;
|
|
65
|
+
}
|
|
66
|
+
/** Build a deterministic cache key from parts. */
|
|
67
|
+
static key(...parts) {
|
|
68
|
+
return parts
|
|
69
|
+
.map((p) => (p === undefined || p === null ? "_" : String(p).toLowerCase()))
|
|
70
|
+
.join(":");
|
|
71
|
+
}
|
|
72
|
+
/** Remove all expired entries. */
|
|
73
|
+
evictExpired() {
|
|
74
|
+
const now = Date.now();
|
|
75
|
+
for (const [key, entry] of this.store) {
|
|
76
|
+
if (now > entry.expiresAt) {
|
|
77
|
+
this.store.delete(key);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** Tear down the cleanup interval (for graceful shutdown). */
|
|
82
|
+
destroy() {
|
|
83
|
+
clearInterval(this.cleanupInterval);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** Singleton cache instance shared across the server. */
|
|
87
|
+
export const cache = new Cache();
|
|
88
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/utils/cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared HTTP client with:
|
|
3
|
+
* - Automatic retries with exponential back-off
|
|
4
|
+
* - User-Agent rotation (for scraping)
|
|
5
|
+
* - Timeout support
|
|
6
|
+
* - JSON convenience helpers
|
|
7
|
+
*/
|
|
8
|
+
export interface HttpOptions {
|
|
9
|
+
/** Request timeout in milliseconds (default: 15 000). */
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
/** Maximum number of attempts (default: 3). */
|
|
12
|
+
maxRetries?: number;
|
|
13
|
+
/** Additional headers to merge in. */
|
|
14
|
+
headers?: Record<string, string>;
|
|
15
|
+
/** Use a rotating browser-like User-Agent (default: false). */
|
|
16
|
+
rotateUserAgent?: boolean;
|
|
17
|
+
/** HTTP method (default: GET). */
|
|
18
|
+
method?: string;
|
|
19
|
+
/** Request body (for POST/PUT). */
|
|
20
|
+
body?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Fetch with retries, timeouts, and optional UA rotation.
|
|
24
|
+
* Returns the raw `Response` object on success.
|
|
25
|
+
*/
|
|
26
|
+
export declare function httpFetch(url: string, opts?: HttpOptions): Promise<Response>;
|
|
27
|
+
/**
|
|
28
|
+
* Convenience: fetch and parse JSON.
|
|
29
|
+
*/
|
|
30
|
+
export declare function httpGetJson<T = unknown>(url: string, opts?: HttpOptions): Promise<T>;
|
|
31
|
+
/**
|
|
32
|
+
* Convenience: fetch and return text.
|
|
33
|
+
*/
|
|
34
|
+
export declare function httpGetText(url: string, opts?: HttpOptions): Promise<string>;
|
|
35
|
+
/**
|
|
36
|
+
* Format an error into a user-friendly string for tool results.
|
|
37
|
+
*/
|
|
38
|
+
export declare function formatError(err: unknown): string;
|
|
39
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/utils/http.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAUH,MAAM,WAAW,WAAW;IAC1B,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+CAA+C;IAC/C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,+DAA+D;IAC/D,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,kCAAkC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mCAAmC;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAMD;;;GAGG;AACH,wBAAsB,SAAS,CAC7B,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,QAAQ,CAAC,CAuDnB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,CAAC,GAAG,OAAO,EAC3C,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,CAAC,CAAC,CAcZ;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,MAAM,CAAC,CAWjB;AAQD;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAMhD"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared HTTP client with:
|
|
3
|
+
* - Automatic retries with exponential back-off
|
|
4
|
+
* - User-Agent rotation (for scraping)
|
|
5
|
+
* - Timeout support
|
|
6
|
+
* - JSON convenience helpers
|
|
7
|
+
*/
|
|
8
|
+
const USER_AGENTS = [
|
|
9
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
|
|
10
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15",
|
|
11
|
+
"Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0",
|
|
12
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0",
|
|
13
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
|
|
14
|
+
];
|
|
15
|
+
function pickUserAgent() {
|
|
16
|
+
return USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Fetch with retries, timeouts, and optional UA rotation.
|
|
20
|
+
* Returns the raw `Response` object on success.
|
|
21
|
+
*/
|
|
22
|
+
export async function httpFetch(url, opts = {}) {
|
|
23
|
+
const { timeoutMs = 15_000, maxRetries = 3, headers = {}, rotateUserAgent = false, method = "GET", body, } = opts;
|
|
24
|
+
let lastError;
|
|
25
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
26
|
+
const controller = new AbortController();
|
|
27
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
28
|
+
try {
|
|
29
|
+
const finalHeaders = {
|
|
30
|
+
Accept: "application/json, text/html, */*",
|
|
31
|
+
...headers,
|
|
32
|
+
};
|
|
33
|
+
if (rotateUserAgent) {
|
|
34
|
+
finalHeaders["User-Agent"] = pickUserAgent();
|
|
35
|
+
}
|
|
36
|
+
const response = await fetch(url, {
|
|
37
|
+
method,
|
|
38
|
+
headers: finalHeaders,
|
|
39
|
+
body,
|
|
40
|
+
signal: controller.signal,
|
|
41
|
+
});
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
// Retry on server errors (5xx), not on client errors (4xx)
|
|
44
|
+
if (response.status >= 500 && attempt < maxRetries) {
|
|
45
|
+
lastError = new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
46
|
+
await backoff(attempt);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
return response;
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
clearTimeout(timer);
|
|
53
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
54
|
+
if (attempt < maxRetries) {
|
|
55
|
+
await backoff(attempt);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
throw lastError ?? new Error(`Failed to fetch ${url}`);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Convenience: fetch and parse JSON.
|
|
64
|
+
*/
|
|
65
|
+
export async function httpGetJson(url, opts = {}) {
|
|
66
|
+
const resp = await httpFetch(url, {
|
|
67
|
+
...opts,
|
|
68
|
+
headers: { Accept: "application/json", ...opts.headers },
|
|
69
|
+
});
|
|
70
|
+
if (!resp.ok) {
|
|
71
|
+
const body = await resp.text().catch(() => "");
|
|
72
|
+
throw new Error(`HTTP ${resp.status} from ${url}: ${body.slice(0, 300)}`);
|
|
73
|
+
}
|
|
74
|
+
return resp.json();
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Convenience: fetch and return text.
|
|
78
|
+
*/
|
|
79
|
+
export async function httpGetText(url, opts = {}) {
|
|
80
|
+
const resp = await httpFetch(url, opts);
|
|
81
|
+
if (!resp.ok) {
|
|
82
|
+
const body = await resp.text().catch(() => "");
|
|
83
|
+
throw new Error(`HTTP ${resp.status} from ${url}: ${body.slice(0, 300)}`);
|
|
84
|
+
}
|
|
85
|
+
return resp.text();
|
|
86
|
+
}
|
|
87
|
+
/** Exponential back-off: 500ms, 1s, 2s, ... */
|
|
88
|
+
async function backoff(attempt) {
|
|
89
|
+
const ms = Math.min(500 * 2 ** (attempt - 1), 5_000);
|
|
90
|
+
await new Promise((r) => setTimeout(r, ms));
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Format an error into a user-friendly string for tool results.
|
|
94
|
+
*/
|
|
95
|
+
export function formatError(err) {
|
|
96
|
+
if (err instanceof Error) {
|
|
97
|
+
if (err.name === "AbortError")
|
|
98
|
+
return "Request timed out. The API may be slow or unreachable.";
|
|
99
|
+
return err.message;
|
|
100
|
+
}
|
|
101
|
+
return String(err);
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/utils/http.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sliding-window rate limiter keyed by source name.
|
|
3
|
+
*
|
|
4
|
+
* Default limits per source (requests / window):
|
|
5
|
+
* - CoinGecko: 30 req / 60 s
|
|
6
|
+
* - Open-Meteo: 60 req / 60 s
|
|
7
|
+
* - ExchangeRate: 60 req / 60 s
|
|
8
|
+
* - NewsAPI: 50 req / 60 s (free tier is tight)
|
|
9
|
+
* - Google News RSS: 60 req / 60 s
|
|
10
|
+
* - Wikipedia: 100 req / 60 s
|
|
11
|
+
* - Generic HTTP: 120 req / 60 s
|
|
12
|
+
*/
|
|
13
|
+
export declare class RateLimiter {
|
|
14
|
+
private windows;
|
|
15
|
+
/** Register or update limits for a source. */
|
|
16
|
+
configure(source: string, maxRequests: number, windowMs: number): void;
|
|
17
|
+
/**
|
|
18
|
+
* Try to acquire a permit for `source`.
|
|
19
|
+
* Returns `true` if the request is allowed, `false` if rate-limited.
|
|
20
|
+
*/
|
|
21
|
+
tryAcquire(source: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Acquire a permit, waiting if necessary. Throws after `timeoutMs`.
|
|
24
|
+
*/
|
|
25
|
+
acquire(source: string, timeoutMs?: number): Promise<void>;
|
|
26
|
+
/** How many requests remain in the current window. */
|
|
27
|
+
remaining(source: string): number;
|
|
28
|
+
/** Milliseconds until the next permit becomes available (0 if available now). */
|
|
29
|
+
retryAfterMs(source: string): number;
|
|
30
|
+
private getOrCreate;
|
|
31
|
+
}
|
|
32
|
+
/** Singleton rate limiter shared across the server. */
|
|
33
|
+
export declare const rateLimiter: RateLimiter;
|
|
34
|
+
//# sourceMappingURL=rate-limiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/utils/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAoBH,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAA6B;IAE5C,8CAA8C;IAC9C,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAUtE;;;OAGG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAenC;;OAEG;IACG,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAchE,sDAAsD;IACtD,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAOjC,iFAAiF;IACjF,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAWpC,OAAO,CAAC,WAAW;CASpB;AAED,uDAAuD;AACvD,eAAO,MAAM,WAAW,aAAoB,CAAC"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sliding-window rate limiter keyed by source name.
|
|
3
|
+
*
|
|
4
|
+
* Default limits per source (requests / window):
|
|
5
|
+
* - CoinGecko: 30 req / 60 s
|
|
6
|
+
* - Open-Meteo: 60 req / 60 s
|
|
7
|
+
* - ExchangeRate: 60 req / 60 s
|
|
8
|
+
* - NewsAPI: 50 req / 60 s (free tier is tight)
|
|
9
|
+
* - Google News RSS: 60 req / 60 s
|
|
10
|
+
* - Wikipedia: 100 req / 60 s
|
|
11
|
+
* - Generic HTTP: 120 req / 60 s
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_LIMITS = {
|
|
14
|
+
coingecko: { maxRequests: 30, windowMs: 60_000 },
|
|
15
|
+
"open-meteo": { maxRequests: 60, windowMs: 60_000 },
|
|
16
|
+
exchangerate: { maxRequests: 60, windowMs: 60_000 },
|
|
17
|
+
newsapi: { maxRequests: 50, windowMs: 60_000 },
|
|
18
|
+
googlenews: { maxRequests: 60, windowMs: 60_000 },
|
|
19
|
+
wikipedia: { maxRequests: 100, windowMs: 60_000 },
|
|
20
|
+
generic: { maxRequests: 120, windowMs: 60_000 },
|
|
21
|
+
dns: { maxRequests: 120, windowMs: 60_000 },
|
|
22
|
+
ipgeo: { maxRequests: 45, windowMs: 60_000 },
|
|
23
|
+
};
|
|
24
|
+
export class RateLimiter {
|
|
25
|
+
windows = new Map();
|
|
26
|
+
/** Register or update limits for a source. */
|
|
27
|
+
configure(source, maxRequests, windowMs) {
|
|
28
|
+
const existing = this.windows.get(source);
|
|
29
|
+
if (existing) {
|
|
30
|
+
existing.maxRequests = maxRequests;
|
|
31
|
+
existing.windowMs = windowMs;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
this.windows.set(source, { timestamps: [], maxRequests, windowMs });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Try to acquire a permit for `source`.
|
|
39
|
+
* Returns `true` if the request is allowed, `false` if rate-limited.
|
|
40
|
+
*/
|
|
41
|
+
tryAcquire(source) {
|
|
42
|
+
const win = this.getOrCreate(source);
|
|
43
|
+
const now = Date.now();
|
|
44
|
+
// Slide the window: drop timestamps older than the window
|
|
45
|
+
win.timestamps = win.timestamps.filter((t) => now - t < win.windowMs);
|
|
46
|
+
if (win.timestamps.length >= win.maxRequests) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
win.timestamps.push(now);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Acquire a permit, waiting if necessary. Throws after `timeoutMs`.
|
|
54
|
+
*/
|
|
55
|
+
async acquire(source, timeoutMs = 10_000) {
|
|
56
|
+
const deadline = Date.now() + timeoutMs;
|
|
57
|
+
while (!this.tryAcquire(source)) {
|
|
58
|
+
if (Date.now() >= deadline) {
|
|
59
|
+
throw new Error(`Rate limit exceeded for "${source}". Try again in a few seconds.`);
|
|
60
|
+
}
|
|
61
|
+
// Wait a short interval before retrying
|
|
62
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** How many requests remain in the current window. */
|
|
66
|
+
remaining(source) {
|
|
67
|
+
const win = this.getOrCreate(source);
|
|
68
|
+
const now = Date.now();
|
|
69
|
+
const active = win.timestamps.filter((t) => now - t < win.windowMs).length;
|
|
70
|
+
return Math.max(0, win.maxRequests - active);
|
|
71
|
+
}
|
|
72
|
+
/** Milliseconds until the next permit becomes available (0 if available now). */
|
|
73
|
+
retryAfterMs(source) {
|
|
74
|
+
const win = this.getOrCreate(source);
|
|
75
|
+
if (win.timestamps.length < win.maxRequests)
|
|
76
|
+
return 0;
|
|
77
|
+
const now = Date.now();
|
|
78
|
+
const oldest = win.timestamps.find((t) => now - t < win.windowMs);
|
|
79
|
+
if (!oldest)
|
|
80
|
+
return 0;
|
|
81
|
+
return Math.max(0, oldest + win.windowMs - now);
|
|
82
|
+
}
|
|
83
|
+
getOrCreate(source) {
|
|
84
|
+
let win = this.windows.get(source);
|
|
85
|
+
if (!win) {
|
|
86
|
+
const defaults = DEFAULT_LIMITS[source] ?? DEFAULT_LIMITS.generic;
|
|
87
|
+
win = { timestamps: [], ...defaults };
|
|
88
|
+
this.windows.set(source, win);
|
|
89
|
+
}
|
|
90
|
+
return win;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Singleton rate limiter shared across the server. */
|
|
94
|
+
export const rateLimiter = new RateLimiter();
|
|
95
|
+
//# sourceMappingURL=rate-limiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../src/utils/rate-limiter.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "data-aggregator-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A unified MCP server replacing 5-10 separate data servers. Stocks, weather, news, web scraping, exchange rates, and public data APIs in one package.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"data-aggregator-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"start:sse": "node dist/index.js --sse",
|
|
14
|
+
"dev": "tsc --watch",
|
|
15
|
+
"prepublishOnly": "npm run build"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"mcp",
|
|
19
|
+
"model-context-protocol",
|
|
20
|
+
"data-aggregator",
|
|
21
|
+
"stocks",
|
|
22
|
+
"weather",
|
|
23
|
+
"news",
|
|
24
|
+
"web-scraping",
|
|
25
|
+
"exchange-rates",
|
|
26
|
+
"claude",
|
|
27
|
+
"ai-tools"
|
|
28
|
+
],
|
|
29
|
+
"author": "",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
33
|
+
"zod": "^3.24.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^22.0.0",
|
|
37
|
+
"typescript": "^5.7.0"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18.0.0"
|
|
41
|
+
}
|
|
42
|
+
}
|