Skip to main content
Glama
jonfreeland

MongoDB MCP Server

by jonfreeland

geo_query

Execute geospatial queries on MongoDB collections to find locations near points, within polygons or circles, and calculate distances between coordinates.

Instructions

Execute geospatial queries on a MongoDB collection.

Supports:

  • Finding points near a location

  • Finding documents within a polygon, circle, or box

  • Calculating distances between points

  • GeoJSON and legacy coordinate pair formats

Requirements:

  • Collection must have a geospatial index (2dsphere recommended)

  • Coordinates should follow MongoDB conventions (longitude first, then latitude)

Examples:

  1. Find locations near a point (2 miles radius): use_mcp_tool with server_name: "mongodb", tool_name: "geo_query", arguments: { "collection": "restaurants", "operation": "near", "point": [-73.9667, 40.78], "maxDistance": 3218.69, // 2 miles in meters "distanceField": "distance" }

  2. Find locations within a polygon: use_mcp_tool with server_name: "mongodb", tool_name: "geo_query", arguments: { "collection": "properties", "operation": "geoWithin", "geometry": { "type": "Polygon", "coordinates": [ [[-73.958, 40.8], [-73.94, 40.79], [-73.95, 40.76], [-73.97, 40.76], [-73.958, 40.8]] ] } }

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
databaseNoDatabase name (optional if default database is configured)
collectionYesCollection name
operationYesGeospatial operation to perform
locationFieldNoField containing geospatial data (default: "location")
pointNoPoint coordinates [longitude, latitude] for near/nearSphere queries
maxDistanceNoMaximum distance in meters for near/nearSphere queries
minDistanceNoMinimum distance in meters for near/nearSphere queries
geometryNoGeoJSON geometry for geoWithin/geoIntersects queries
distanceFieldNoField to store calculated distances (for near/nearSphere queries)
sphericalNoCalculate distances on a sphere (Earth) rather than flat plane
limitNoMaximum number of results to return
additionalFilterNoAdditional MongoDB query criteria to combine with geospatial query

Implementation Reference

  • src/index.ts:843-944 (registration)
    Registration of the 'geo_query' tool including its detailed description and complete input schema definition in the ListToolsRequestSchema handler.
            {
              name: 'geo_query',
              description: `Execute geospatial queries on a MongoDB collection.
    
    Supports:
    - Finding points near a location
    - Finding documents within a polygon, circle, or box
    - Calculating distances between points
    - GeoJSON and legacy coordinate pair formats
    
    Requirements:
    - Collection must have a geospatial index (2dsphere recommended)
    - Coordinates should follow MongoDB conventions (longitude first, then latitude)
    
    Examples:
    1. Find locations near a point (2 miles radius):
    use_mcp_tool with
      server_name: "mongodb",
      tool_name: "geo_query",
      arguments: {
        "collection": "restaurants",
        "operation": "near",
        "point": [-73.9667, 40.78],
        "maxDistance": 3218.69,  // 2 miles in meters
        "distanceField": "distance"
      }
    
    2. Find locations within a polygon:
    use_mcp_tool with
      server_name: "mongodb",
      tool_name: "geo_query",
      arguments: {
        "collection": "properties",
        "operation": "geoWithin",
        "geometry": {
          "type": "Polygon",
          "coordinates": [
            [[-73.958, 40.8], [-73.94, 40.79], [-73.95, 40.76], [-73.97, 40.76], [-73.958, 40.8]]
          ]
        }
      }`,
              inputSchema: {
                type: 'object',
                properties: {
                  database: {
                    type: 'string',
                    description: 'Database name (optional if default database is configured)',
                  },
                  collection: {
                    type: 'string',
                    description: 'Collection name',
                  },
                  operation: {
                    type: 'string',
                    description: 'Geospatial operation to perform',
                    enum: ['near', 'geoWithin', 'geoIntersects', 'nearSphere'],
                  },
                  locationField: {
                    type: 'string',
                    description: 'Field containing geospatial data (default: "location")',
                  },
                  point: {
                    type: 'array',
                    description: 'Point coordinates [longitude, latitude] for near/nearSphere queries',
                    items: {
                      type: 'number',
                    },
                  },
                  maxDistance: {
                    type: 'number',
                    description: 'Maximum distance in meters for near/nearSphere queries',
                  },
                  minDistance: {
                    type: 'number',
                    description: 'Minimum distance in meters for near/nearSphere queries',
                  },
                  geometry: {
                    type: 'object',
                    description: 'GeoJSON geometry for geoWithin/geoIntersects queries',
                  },
                  distanceField: {
                    type: 'string',
                    description: 'Field to store calculated distances (for near/nearSphere queries)',
                  },
                  spherical: {
                    type: 'boolean',
                    description: 'Calculate distances on a sphere (Earth) rather than flat plane',
                  },
                  limit: {
                    type: 'number',
                    description: 'Maximum number of results to return',
                    minimum: 1,
                    maximum: 1000,
                  },
                  additionalFilter: {
                    type: 'object',
                    description: 'Additional MongoDB query criteria to combine with geospatial query',
                  },
                },
                required: ['collection', 'operation'],
              },
            },
  • Input schema definition for the 'geo_query' tool, specifying parameters for various geospatial operations.
    inputSchema: {
      type: 'object',
      properties: {
        database: {
          type: 'string',
          description: 'Database name (optional if default database is configured)',
        },
        collection: {
          type: 'string',
          description: 'Collection name',
        },
        operation: {
          type: 'string',
          description: 'Geospatial operation to perform',
          enum: ['near', 'geoWithin', 'geoIntersects', 'nearSphere'],
        },
        locationField: {
          type: 'string',
          description: 'Field containing geospatial data (default: "location")',
        },
        point: {
          type: 'array',
          description: 'Point coordinates [longitude, latitude] for near/nearSphere queries',
          items: {
            type: 'number',
          },
        },
        maxDistance: {
          type: 'number',
          description: 'Maximum distance in meters for near/nearSphere queries',
        },
        minDistance: {
          type: 'number',
          description: 'Minimum distance in meters for near/nearSphere queries',
        },
        geometry: {
          type: 'object',
          description: 'GeoJSON geometry for geoWithin/geoIntersects queries',
        },
        distanceField: {
          type: 'string',
          description: 'Field to store calculated distances (for near/nearSphere queries)',
        },
        spherical: {
          type: 'boolean',
          description: 'Calculate distances on a sphere (Earth) rather than flat plane',
        },
        limit: {
          type: 'number',
          description: 'Maximum number of results to return',
          minimum: 1,
          maximum: 1000,
        },
        additionalFilter: {
          type: 'object',
          description: 'Additional MongoDB query criteria to combine with geospatial query',
        },
      },
      required: ['collection', 'operation'],
    },
  • Implementation of the 'geo_query' tool handler within the CallToolRequestSchema switch statement. Handles near/nearSphere using $geoNear aggregation and geoWithin/geoIntersects using find with GeoJSON geometry. Includes input validation, geospatial index checks, and visualization hints.
    case 'geo_query': {
      const { 
        database, 
        collection, 
        operation, 
        locationField = 'location',
        point, 
        maxDistance, 
        minDistance, 
        geometry, 
        distanceField,
        spherical = true, 
        limit = 100, 
        additionalFilter = {} 
      } = request.params.arguments as {
        database?: string;
        collection: string;
        operation: 'near' | 'geoWithin' | 'geoIntersects' | 'nearSphere';
        locationField?: string;
        point?: number[];
        maxDistance?: number;
        minDistance?: number;
        geometry?: any;
        distanceField?: string;
        spherical?: boolean;
        limit?: number;
        additionalFilter?: object;
      };
      
      const dbName = database || this.defaultDatabase;
      if (!dbName) {
        throw new McpError(
          ErrorCode.InvalidRequest,
          'Database name is required when no default database is configured'
        );
      }
    
      const db = client.db(dbName);
      
      switch (operation) {
        case 'near':
        case 'nearSphere': {
          if (!Array.isArray(point) || point.length !== 2) {
            throw new McpError(
              ErrorCode.InvalidRequest,
              'Point coordinates [longitude, latitude] are required for near/nearSphere queries'
            );
          }
          
          // Validate coordinates
          const [longitude, latitude] = point;
          if (longitude < -180 || longitude > 180) {
            throw new McpError(
              ErrorCode.InvalidRequest,
              'Invalid longitude: must be between -180 and 180'
            );
          }
          if (latitude < -90 || latitude > 90) {
            throw new McpError(
              ErrorCode.InvalidRequest,
              'Invalid latitude: must be between -90 and 90'
            );
          }
          
          const geoNearOptions: any = {
            near: { type: 'Point', coordinates: point },
            distanceField: distanceField || 'distance',
            spherical: operation === 'nearSphere' || spherical,
            query: additionalFilter
          };
          
          if (maxDistance !== undefined) geoNearOptions.maxDistance = maxDistance;
          if (minDistance !== undefined) geoNearOptions.minDistance = minDistance;
          if (limit) geoNearOptions.limit = limit;
          
          try {
            // Use aggregation for geoNear
            const results = await db.collection(collection).aggregate([
              { $geoNear: geoNearOptions }
            ]).toArray();
            
            const vizHint = this.generateVisualizationHint(results);
            
            return {
              content: [
                {
                  type: 'text',
                  text: JSON.stringify(results, null, 2) + 
                        (vizHint ? `\n\nVisualization Hint:\n${vizHint}` : ''),
                },
              ],
            };
          } catch (error) {
            // Check if error is due to missing geospatial index
            if (error instanceof Error && 
                (error.message.includes('2dsphere') || error.message.includes('geo'))) {
              throw new McpError(
                ErrorCode.InvalidRequest,
                `Geospatial index required. Create one using: db.${collection}.createIndex({ "${locationField}": "2dsphere" })`
              );
            }
            throw error;
          }
        }
        
        case 'geoWithin':
        case 'geoIntersects': {
          if (!geometry || !geometry.type || !geometry.coordinates) {
            throw new McpError(
              ErrorCode.InvalidRequest,
              'Valid GeoJSON geometry is required for geoWithin/geoIntersects queries'
            );
          }
          
          const operator = operation === 'geoWithin' ? '$geoWithin' : '$geoIntersects';
          
          const geoQuery = {
            [locationField]: {
              [operator]: {
                $geometry: geometry
              }
            },
            ...additionalFilter
          };
          
          try {
            const results = await db.collection(collection)
              .find(geoQuery)
              .limit(limit)
              .toArray();
            
            const vizHint = this.generateVisualizationHint(results);
            
            return {
              content: [
                {
                  type: 'text',
                  text: JSON.stringify(results, null, 2) + 
                        (vizHint ? `\n\nVisualization Hint:\n${vizHint}` : ''),
                },
              ],
            };
          } catch (error) {
            // Check if error is due to missing geospatial index
            if (error instanceof Error && 
                (error.message.includes('2dsphere') || error.message.includes('geo'))) {
              throw new McpError(
                ErrorCode.InvalidRequest,
                `Geospatial index required. Create one using: db.${collection}.createIndex({ "${locationField}": "2dsphere" })`
              );
            }
            throw error;
          }
        }
        
        default:
          throw new McpError(
            ErrorCode.InvalidRequest,
            `Unsupported geospatial operation: ${operation}`
          );
      }
    }
  • Supporting helper method generateVisualizationHint used by geo_query (and others) that detects geospatial data and suggests map-based visualizations.
    private generateVisualizationHint(data: any[]): string {
      if (!Array.isArray(data) || data.length === 0) return '';
    
      // Check if the data looks like time series
      const hasDateFields = Object.keys(data[0]).some(key => 
        data[0][key] instanceof Date || 
        (typeof data[0][key] === 'string' && !isNaN(Date.parse(data[0][key])))
      );
    
      // Check if the data has numeric fields
      const numericFields = Object.keys(data[0]).filter(key => 
        typeof data[0][key] === 'number'
      );
    
      // Check if the data has categorical fields
      const categoricalFields = Object.keys(data[0]).filter(key => 
        typeof data[0][key] === 'string' && 
        data.every(item => typeof item[key] === 'string')
      );
    
      // Check if the data has geospatial fields
      const hasGeoData = Object.keys(data[0]).some(key => {
        const value = data[0][key];
        return value && typeof value === 'object' && 
          (('type' in value && value.type === 'Point' && 'coordinates' in value) ||
           (Array.isArray(value) && value.length === 2 && 
            typeof value[0] === 'number' && typeof value[1] === 'number'));
      });
    
      let hints = [];
    
      if (hasDateFields && numericFields.length > 0) {
        hints.push('Time Series Visualization:\n- Consider line charts for temporal trends\n- Time-based heat maps for density patterns\n- Area charts for cumulative values over time');
      }
    
      if (categoricalFields.length > 0 && numericFields.length > 0) {
        hints.push('Categorical Analysis:\n- Bar charts for comparing categories\n- Box plots for distribution analysis\n- Heat maps for category correlations\n- Treemaps for hierarchical data');
      }
    
      if (numericFields.length >= 2) {
        hints.push('Numerical Analysis:\n- Scatter plots for correlation analysis\n- Bubble charts if three numeric dimensions\n- Correlation matrices for multiple variables\n- Histograms for distribution analysis');
      }
    
      if (hasGeoData) {
        hints.push('Geospatial Visualization:\n- Map overlays for location data\n- Choropleth maps for regional analysis\n- Heat maps for density visualization\n- Cluster maps for point concentration');
      }
    
      if (data.length > 1000) {
        hints.push('Large Dataset Considerations:\n- Consider sampling for initial visualization\n- Use aggregation for summary views\n- Implement pagination or infinite scroll\n- Consider server-side rendering');
      }
    
      return hints.join('\n\n');
    }

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/jonfreeland/mongodb-mcp'

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