Skip to main content
Glama
stadiamaps

Stadia Maps Location API MCP Server

bulk-geocode

Convert multiple addresses to geographic coordinates and contextual data in a single request for efficient batch processing.

Instructions

Perform multiple address geocoding operations in a single request. Returns results as a JSON list, showing only the first result for each. Using this to geocode POIs is strongly discouraged, as many places with the same name exist; only use this for addresses. Returned geographic information includes coordinates, bounding box, local context (what country, city, etc. is it in).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
itemsYesArray of geocoding items to process in bulk.

Implementation Reference

  • src/index.ts:63-78 (registration)
    Registers the 'bulk-geocode' MCP tool, providing name, description, Zod input schema for bulk items, and links to the bulkGeocode handler.
    server.tool(
      "bulk-geocode",
      `Perform multiple address geocoding operations in a single request. Returns results as a JSON list, showing only the first result for each. Using this to geocode POIs is strongly discouraged, as many places with the same name exist; only use this for addresses. ${commonGeocodingDescription}`,
      {
        items: z
          .array(
            z.object({
              query: geocodingUnstructuredQuery,
              ...geocodingCommonSchema.shape,
            }),
          )
          .min(1)
          .describe("Array of geocoding items to process in bulk."),
      },
      bulkGeocode,
    );
  • The main handler function for the bulk-geocode tool. Constructs bulk requests from input items and calls the Stadia Maps bulk geocoding API, then formats results using bulkGeocodingToolResult.
    export async function bulkGeocode({
      items,
    }: BulkUnstructuredGeocodeParams): Promise<CallToolResult> {
      if (!items || items.length === 0) {
        return {
          content: [
            {
              type: "text",
              text: "No geocoding items provided.",
            },
          ],
        };
      }
    
      // All items are valid by type definition (query is required)
      const bulkRequests = items.map((item) => {
        const request: BulkRequest = {
          endpoint: BulkRequestEndpointEnum.V1Search,
        };
    
        // Unstructured query
        request.query = {
          text: item.query,
          focusPointLat: item.focusPoint?.lat,
          focusPointLon: item.focusPoint?.lon,
          boundaryCountry: item.countryFilter,
          lang: item.lang,
        };
    
        return request;
      });
    
      return handleToolError(
        async () => {
          const responses = await geocodeApi.searchBulk({
            bulkRequest: bulkRequests,
          });
          return bulkGeocodingToolResult(responses, []);
        },
        {
          contextMessage: "Error performing bulk unstructured geocoding",
          enableLogging: true,
        },
      );
    }
  • Zod schema for common geocoding parameters (countryFilter, lang, focusPoint, layer), used in the input schema for each item in bulk-geocode.
    export const geocodingCommonSchema = z.object({
      countryFilter: countryFilterSchema,
      lang: languageSchema,
      focusPoint: focusPointSchema,
      layer: z
        .enum(["address", "poi", "coarse", "country", "region", "locality"])
        .describe(
          "The layer to search in. Coarse searches for areas such as neighborhoods, cities, states, and countries, AND a specific layer is not available. Address searches for street addresses. Country is what you expect. Localities are what we would colloquially refer to as a 'city', town, or village. Region is for first-level subdivisions within countries like states, provinces, or whatever they are called locally. POI searches for points of interest including restaurants, parks, shops, and museums. Defaults to all layers if not specified.",
        )
        .optional(),
    });
  • Zod schema for the unstructured query string parameter, used in each item of the bulk-geocode input.
    export const geocodingUnstructuredQuery = z
      .string()
      .describe(
        "The address or place name to search for. Use local formatting and order when possible. When searching for a POI name (e.g. 'Starbucks'), you will get better results with a focus point and filters. Avoid spelling out precise locations (e.g. 'Starbucks, Downtown Greenville'); this is acceptable for large areas though (e.g. 'Paris, France' is OK, as is 'Louvre, Paris'). Make multiple queries or use general knowledge when necessary to identify the correct non-textual filters.",
      );
  • Helper function to process bulk geocoding API responses into a structured JSON result with successful results and metadata on failures.
    function bulkGeocodingToolResult(
      responses: Array<BulkSearchResponse>,
      invalidItems: Array<{ item: BulkGeocodeItemType; error: string }> = [],
    ): CallToolResult {
      // Filter out any failed requests and extract the features
      const successfulResults = responses
        .filter(
          (response) =>
            response.status === 200 &&
            response.response?.features &&
            response.response.features.length > 0,
        )
        .flatMap((response) => {
          if (!response.response) return [];
    
          return response.response.features
            .map((feature) => {
              return {
                label: feature.properties?.label,
                geometry: feature.geometry,
                matchType: feature.properties?.matchType,
                addendum: feature.properties?.addendum,
                bbox: feature.bbox,
              };
              // Slice to keep only the first element in the array;
              // we request a few more to enable dedupe, but only keep the first one.
            })
            .slice(0, 1);
        });
    
      // Get failed responses
      const failedResponses = responses
        .filter(
          (response) =>
            response.status !== 200 || !response.response?.features?.length,
        )
        .map((response) => ({
          status: response.status,
          message: response.msg || "No results found",
        }));
    
      // Combine all results
      const result = {
        results: successfulResults,
        metadata: {
          totalRequests: responses.length + invalidItems.length,
          successfulRequests: successfulResults.length,
          failedRequests: failedResponses.length,
          invalidItems:
            invalidItems.length > 0
              ? invalidItems.map((i) => ({
                  item: i.item,
                  error: i.error,
                }))
              : undefined,
        },
      };
    
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(result),
          },
        ],
      };
    }
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden and does well by disclosing key behavioral traits: it's a batch operation, returns only first result per query, includes what geographic information is returned (coordinates, bounding box, local context), and warns about limitations with POIs. It doesn't mention rate limits, authentication needs, or error handling, which keeps it from a perfect score.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is efficiently structured with three sentences that each earn their place: first states the core functionality, second provides critical usage guidance, third details return values. No wasted words, and the most important information (bulk geocoding for addresses only) is front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with no annotations and no output schema, the description does well by explaining the batch nature, return format, and content of results. It could be more complete by mentioning error handling for invalid addresses or whether all items must succeed for the batch to complete, but it covers the essential context given the complexity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description doesn't add any parameter-specific information beyond what's already documented in the schema. It focuses on overall tool behavior rather than explaining individual parameters like 'items', 'countryFilter', or 'layer'.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the specific action ('perform multiple address geocoding operations in a single request') and distinguishes it from the sibling 'geocode' tool by emphasizing bulk processing. It explicitly identifies the resource being operated on (addresses) and the output format (JSON list).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit guidance on when to use this tool ('only use this for addresses') versus when not to ('strongly discouraged' for POIs), and explains why (many places with same name exist). It also implies an alternative approach for POIs (using general knowledge or multiple queries), though it doesn't name specific sibling tools as alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/stadiamaps/stadiamaps-mcp-server-ts'

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