Toolkit MCP Server
by cyanheads
Verified
- src
- tools
import { GeoLocation, RateLimitInfo } from '../types.js';
import { Cache, RateLimiter } from '../utils/cache.js';
const geoCache = new Cache<GeoLocation>(5 * 60 * 1000); // 5 minute cache
const rateLimiter = new RateLimiter(45, 60000); // 45 requests per minute
async function fetchGeoData(query: string): Promise<{ data: GeoLocation; rateLimit: RateLimitInfo }> {
const fields = [
'status',
'message',
'country',
'countryCode',
'region',
'regionName',
'city',
'zip',
'lat',
'lon',
'timezone',
'offset',
'isp',
'org',
'as',
'query'
].join(',');
const url = `http://ip-api.com/json/${encodeURIComponent(query)}?fields=${fields}`;
const response = await fetch(url);
const data = await response.json() as GeoLocation;
const remaining = Number(response.headers.get('X-Rl') ?? '0');
const ttl = Number(response.headers.get('X-Ttl') ?? '0');
return {
data,
rateLimit: { remaining, ttl }
};
}
export const geoTools = {
geolocate: {
name: 'geolocate',
description: 'Get geolocation information for an IP address or domain',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'IP address or domain to lookup'
}
},
required: ['query']
},
handler: async ({ query }: { query: string }) => {
// Check cache first
const cached = geoCache.get(query);
if (cached) {
return {
content: [{
type: 'text',
text: JSON.stringify({
data: cached,
source: 'cache'
}, null, 2)
}]
};
}
// Check rate limit
if (!rateLimiter.canMakeRequest()) {
const timeToReset = rateLimiter.getTimeToReset();
throw new Error(`Rate limit exceeded. Please try again in ${Math.ceil(timeToReset / 1000)} seconds.`);
}
try {
const { data, rateLimit } = await fetchGeoData(query);
// Update rate limiter based on response headers
rateLimiter.incrementRequests();
// Cache successful responses
if (data.status === 'success') {
geoCache.set(query, data);
}
return {
content: [{
type: 'text',
text: JSON.stringify({
data,
rateLimit,
source: 'api'
}, null, 2)
}]
};
} catch (error) {
throw new Error(`Geolocation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
},
clearGeoCache: {
name: 'clearGeoCache',
description: 'Clear the geolocation cache',
inputSchema: {
type: 'object',
properties: {}
},
handler: async () => {
geoCache.clear();
return {
content: [{
type: 'text',
text: JSON.stringify({
message: 'Geolocation cache cleared'
}, null, 2)
}]
};
}
}
};