Skip to main content
Glama
ReverseGeocodeTool.ts5.89 kB
import { z } from 'zod'; import { MapboxApiBasedTool } from '../MapboxApiBasedTool.js'; const ReverseGeocodeInputSchema = z.object({ longitude: z .number() .min(-180) .max(180) .describe('Longitude coordinate to reverse geocode'), latitude: z .number() .min(-90) .max(90) .describe('Latitude coordinate to reverse geocode'), permanent: z .boolean() .optional() .default(false) .describe('Whether results can be stored permanently'), country: z .array(z.string().length(2)) .optional() .describe('Array of ISO 3166 alpha 2 country codes to limit results'), language: z .string() .optional() .describe( 'IETF language tag for the response (e.g., "en", "es", "fr", "de", "ja")' ), limit: z .number() .min(1) .max(5) .optional() .default(1) .describe( 'Maximum number of results (1-5). Use 1 for best results. If you need more than 1 result, you must specify exactly one type in the types parameter.' ), types: z .array( z.enum([ 'country', 'region', 'postcode', 'district', 'place', 'locality', 'neighborhood', 'address' ]) ) .optional() .describe('Array of feature types to filter results'), worldview: z .enum(['us', 'cn', 'jp', 'in']) .optional() .default('us') .describe('Returns features from a specific regional perspective'), format: z .enum(['json_string', 'formatted_text']) .optional() .default('formatted_text') .describe( 'Output format: "json_string" returns raw GeoJSON data as a JSON string that can be parsed; "formatted_text" returns human-readable text with place names, addresses, and coordinates. Both return as text content but json_string contains parseable JSON data while formatted_text is for display.' ) }); export class ReverseGeocodeTool extends MapboxApiBasedTool< typeof ReverseGeocodeInputSchema > { name = 'reverse_geocode_tool'; description = 'Find addresses, cities, towns, neighborhoods, postcodes, districts, regions, and countries around a specified geographic coordinate pair. Converts geographic coordinates (longitude, latitude) into human-readable addresses or place names. Use limit=1 for best results. This tool cannot reverse geocode businesses, landmarks, historic sites, and other points of interest that are not of the types mentioned. Supports both JSON and text output formats.'; constructor() { super({ inputSchema: ReverseGeocodeInputSchema }); } private formatGeoJsonToText(geoJsonResponse: any): string { if ( !geoJsonResponse || !geoJsonResponse.features || geoJsonResponse.features.length === 0 ) { return 'No results found.'; } const results = geoJsonResponse.features.map( (feature: any, index: number) => { const props = feature.properties || {}; const geom = feature.geometry || {}; let result = `${index + 1}. `; // Place name result += `${props.name}`; if (props.name_preferred) { result += ` (${props.name_preferred})`; } // Full address if (props.full_address) { result += `\n Address: ${props.full_address}`; } else if (props.place_formatted) { result += `\n Address: ${props.place_formatted}`; } // Geographic coordinates if (geom.coordinates && Array.isArray(geom.coordinates)) { const [lng, lat] = geom.coordinates; result += `\n Coordinates: ${lat}, ${lng}`; } // Feature type if (props.feature_type) { result += `\n Type: ${props.feature_type}`; } return result; } ); return results.join('\n\n'); } protected async execute( input: z.infer<typeof ReverseGeocodeInputSchema>, accessToken: string ): Promise<{ type: 'text'; text: string }> { // When limit > 1, must specify exactly one type if ( input.limit && input.limit > 1 && (!input.types || input.types.length !== 1) ) { throw new Error( 'When limit > 1 for reverse geocoding, you must specify exactly one type in the types parameter (e.g., types: ["address"]). Consider using limit: 1 instead for best results.' ); } const url = new URL( `${MapboxApiBasedTool.MAPBOX_API_ENDPOINT}search/geocode/v6/reverse` ); // Required parameters url.searchParams.append('longitude', input.longitude.toString()); url.searchParams.append('latitude', input.latitude.toString()); url.searchParams.append('access_token', accessToken); // Optional parameters url.searchParams.append('permanent', input.permanent.toString()); url.searchParams.append('limit', input.limit.toString()); url.searchParams.append('worldview', input.worldview); if (input.country && input.country.length > 0) { url.searchParams.append('country', input.country.join(',')); } if (input.language) { url.searchParams.append('language', input.language); } if (input.types && input.types.length > 0) { url.searchParams.append('types', input.types.join(',')); } const response = await fetch(url.toString()); if (!response.ok) { throw new Error( `Failed to reverse geocode: ${response.status} ${response.statusText}` ); } const data = (await response.json()) as any; // Check if the response has features if (!data || !data.features || data.features.length === 0) { return { type: 'text', text: 'No results found.' }; } if (input.format === 'json_string') { return { type: 'text', text: JSON.stringify(data, null, 2) }; } else { return { type: 'text', text: this.formatGeoJsonToText(data) }; } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Waldzell-Agentics/mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server