← Files MapboxARCHIVED FILE

skills/mapbox-mcp-runtime-patterns/AGENTS.md

12.6 KB · Sep 30, 2026 · 23:11 UTC

↓ Download file

# Mapbox MCP Runtime Patterns

Quick reference for integrating Mapbox MCP Server into AI applications for production use.

## What is MCP Server?

Runtime server providing geospatial tools to AI agents via Model Context Protocol.

**Repo:** <https://github.com/mapbox/mcp-server>

## Tools Available

| Category              | Tools                                                                                                                                                                                            | Cost            |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------- |
| **Offline (Turf.js)** | `distance_tool`, `bearing_tool`, `midpoint_tool`, `point_in_polygon_tool`, `area_tool`, `buffer_tool`, `centroid_tool`, `bbox_tool`, `simplify_tool`                                             | Free, instant   |
| **Mapbox APIs**       | `directions_tool`, `search_and_geocode_tool`, `reverse_geocode_tool`, `category_search_tool`, `isochrone_tool`, `matrix_tool`, `static_map_image_tool`, `map_matching_tool`, `optimization_tool` | API costs apply |
| **Utility**           | `version_tool`, `category_list_tool`                                                                                                                                                             | Free            |

## Coordinate Formats

All tools use `{longitude, latitude}` object format — **not** arrays.

**Object format** `{longitude: lng, latitude: lat}`:

- `directions_tool` - `coordinates` array of objects
- `isochrone_tool` - `coordinates` parameter
- `reverse_geocode_tool` - `coordinates` parameter
- `category_search_tool` - `proximity` parameter
- `distance_tool` - `from`/`to` parameters
- `bearing_tool` - `from`/`to` parameters
- `midpoint_tool` - `from`/`to` parameters
- `point_in_polygon_tool` - `point` parameter

**Exception — GeoJSON geometry** (arrays only):

- `buffer_tool` - `geometry` parameter uses `[longitude, latitude]` arrays (GeoJSON format)
- `point_in_polygon_tool` - `polygon` rings use `[longitude, latitude]` arrays

**Note:** All coordinates use `longitude` before `latitude` order.

## Installation

### Hosted (Recommended)

Use Mapbox's hosted server - no installation needed:

```
https://mcp.mapbox.com/mcp
```

Connect with your token in the `Authorization: Bearer <token>` header.

**Note:** Hosted server supports OAuth for interactive flows (coding assistants), but use token auth for programmatic runtime access.

### Self-Hosted

```bash
npm install @mapbox/mcp-server
# Or: npx @mapbox/mcp-server

export MAPBOX_ACCESS_TOKEN="your_token"
```

## Framework Integration

### Pydantic AI

```python
from pydantic_ai import Agent
import subprocess

# Start MCP server
mcp = subprocess.Popen(['npx', '@mapbox/mcp-server'],
                       env={'MAPBOX_ACCESS_TOKEN': token})

agent = Agent(
    model='gateway/openai:gpt-5.2',
    tools=[
        lambda from_loc, to_loc: call_mcp('directions_tool', {
            'origin': from_loc,
            'destination': to_loc
        })
    ]
)
```

### Mastra

```typescript
import { spawn } from 'child_process';

const mcp = spawn('npx', ['@mapbox/mcp-server'], {
  env: { MAPBOX_ACCESS_TOKEN: process.env.MAPBOX_ACCESS_TOKEN }
});

const mastra = new Mastra({
  workflows: {
    findRestaurants: {
      steps: [
        { tool: 'mapbox.category_search_tool', input: {...} },
        { tool: 'mapbox.matrix_tool', input: {...} }
      ]
    }
  }
});
```

### LangChain

```typescript
import { DynamicTool } from '@langchain/core/tools';

const tools = [
  new DynamicTool({
    name: 'directions_tool',
    description: 'Get driving directions',
    func: async (input) => {
      const { origin, destination } = JSON.parse(input);
      return await callMCP('directions_tool', { origin, destination });
    }
  })
];
```

### Custom Agent

```typescript
class MapboxAgent {
  private mcpProcess: ChildProcess;

  async initialize() {
    this.mcpProcess = spawn('npx', ['@mapbox/mcp-server'], {
      env: { MAPBOX_ACCESS_TOKEN: process.env.MAPBOX_ACCESS_TOKEN }
    });
  }

  async callTool(name: string, params: any): Promise<any> {
    const request = {
      jsonrpc: '2.0',
      id: Date.now(),
      method: 'tools/call',
      params: { name, arguments: params }
    };

    this.mcpProcess.stdin.write(JSON.stringify(request) + '\n');

    return new Promise((resolve) => {
      this.mcpProcess.stdout.once('data', (data) => {
        const response = JSON.parse(data.toString());
        resolve(response.result);
      });
    });
  }
}
```

## Common Use Cases

### Real Estate (Zillow-style)

```typescript
// Find properties with good commute
async findByCommute(home: Point, work: Point, maxMinutes: number) {
  // 1. Get reachable area from work
  const isochrone = await mcp.call('isochrone_tool', {
    coordinates: work,
    contours_minutes: [maxMinutes]
  });

  // 2. Check if home is within range
  const inRange = await mcp.call('point_in_polygon_tool', {
    point: home,
    polygon: isochrone
  });

  // 3. Get exact commute time
  if (inRange) {
    const route = await mcp.call('directions_tool', {
      coordinates: [
        { longitude: home[0], latitude: home[1] },
        { longitude: work[0], latitude: work[1] }
      ],
      routing_profile: 'mapbox/driving-traffic'
    });
    return { commuteMinutes: route.duration / 60 };
  }
}
```

### Food Delivery (DoorDash-style)

```typescript
// Check delivery availability
async canDeliver(restaurant: Point, address: Point, maxTime: number) {
  // 1. Calculate delivery zone
  const zone = await mcp.call('isochrone_tool', {
    coordinates: restaurant,
    contours_minutes: [maxTime],
    profile: 'mapbox/driving'
  });

  // 2. Check if address is in zone
  const canDeliver = await mcp.call('point_in_polygon_tool', {
    point: address,
    polygon: zone
  });

  // 3. Get delivery time with traffic
  if (canDeliver) {
    const route = await mcp.call('directions_tool', {
      coordinates: [
        { longitude: restaurant[0], latitude: restaurant[1] },
        { longitude: address[0], latitude: address[1] }
      ],
      routing_profile: 'mapbox/driving-traffic'
    });
    return { eta: route.duration / 60, distance: route.distance };
  }
}
```

### Travel Planning (TripAdvisor-style)

```typescript
// Find nearby attractions with travel times
async findAttractions(hotel: Point, category: string) {
  // 1. Search nearby
  const places = await mcp.call('category_search_tool', {
    category,
    proximity: hotel
  });

  // 2. Calculate distances (offline, free)
  const withDistances = await Promise.all(
    places.map(async (place) => ({
      ...place,
      distance: await mcp.call('distance_tool', {
        from: hotel,
        to: place.coordinates,
        units: 'miles'
      })
    }))
  );

  // 3. Get travel times (batch API call)
  const matrix = await mcp.call('matrix_tool', {
    origins: [hotel],
    destinations: places.map(p => p.coordinates),
    profile: 'mapbox/walking'
  });

  return withDistances.map((place, i) => ({
    ...place,
    walkingMinutes: matrix.durations[0][i] / 60
  }));
}
```

## Architecture Pattern

```
Application Layer
      ↓
AI Agent Layer (pydantic-ai, mastra, custom)
      ↓
MCP Server (geospatial tools)
      ↓
   ↙     ↘
Turf.js   Mapbox APIs
(free)    (API costs)
```

## Tool Selection Strategy

| Need                    | Use                                  | Reason                |
| ----------------------- | ------------------------------------ | --------------------- |
| Distance calculation    | distance_tool (offline)              | Free, instant         |
| Point in polygon        | point_in_polygon_tool (offline)      | Free, instant         |
| Bounding box            | bbox_tool (offline)                  | Free, instant         |
| Simplify geometry       | simplify_tool (offline)              | Free, instant         |
| Directions with traffic | directions_tool (API)                | Real-time data        |
| Geocoding               | reverse_geocode_tool (API)           | Requires database     |
| Isochrones              | isochrone_tool (API)                 | Complex calculation   |
| Multi-stop optimization | optimization_tool (API)              | Complex calculation   |
| GPS trace matching      | map_matching_tool (API)              | Requires routing data |
| Bearing/midpoint        | bearing_tool/midpoint_tool (offline) | Free, instant         |
| POI categories          | category_list_tool (utility)         | Metadata lookup       |

## Performance Optimization

### Caching

```typescript
class CachedMCP {
  private cache = new Map();
  private offlineTools = ['distance_tool', 'point_in_polygon_tool'];

  async callTool(name: string, params: any) {
    // Cache offline tools forever (deterministic)
    const ttl = this.offlineTools.includes(name) ? Infinity : 3600000;

    const key = JSON.stringify({ name, params });
    const cached = this.cache.get(key);

    if (cached && Date.now() - cached.timestamp < ttl) {
      return cached.result;
    }

    const result = await this.mcp.callTool(name, params);
    this.cache.set(key, { result, timestamp: Date.now() });
    return result;
  }
}
```

### Batching

```typescript
// ❌ Bad: Sequential calls
for (const location of locations) {
  await mcp.call('distance_tool', { from: user, to: location });
}

// ✅ Good: Parallel
await Promise.all(locations.map((loc) => mcp.call('distance_tool', { from: user, to: loc })));

// ✅ Better: Use matrix tool
await mcp.call('matrix_tool', {
  origins: [user],
  destinations: locations
});
```

## Error Handling

```typescript
class RobustMCP {
  async callWithRetry(name: string, params: any, retries = 3) {
    for (let i = 0; i < retries; i++) {
      try {
        return await this.mcp.callTool(name, params);
      } catch (error) {
        if (error.code === 'RATE_LIMIT') {
          await this.sleep(Math.pow(2, i) * 1000); // Exponential backoff
          continue;
        }
        throw error; // Non-retryable
      }
    }
  }
}
```

## Security

```typescript
// ✅ Good: Environment variables
const token = process.env.MAPBOX_ACCESS_TOKEN;

// ✅ Good: Scoped tokens (minimal permissions)
// directions:read, geocoding:read only

// ✅ Good: Rate limiting
class RateLimitedMCP {
  private requestsPerMinute = 300;
  private requestCount = 0;

  async callTool(name: string, params: any) {
    if (this.requestCount >= this.requestsPerMinute) {
      await this.waitForNextMinute();
    }
    this.requestCount++;
    return await this.mcp.callTool(name, params);
  }
}
```

## Testing

```typescript
// Mock MCP for tests
class MockMCP {
  async callTool(name: string, params: any) {
    const mocks = {
      distance_tool: () => '2.5',
      directions_tool: () => ({ duration: 1200, distance: 5000 }),
      point_in_polygon_tool: () => true
    };
    return mocks[name]?.();
  }
}

// Use in tests
const agent = new MapboxAgent(new MockMCP());
```

## When to Use

| Use MCP ✅                  | Use Direct API ❌     |
| --------------------------- | --------------------- |
| AI agent interactions       | Simple operations     |
| Complex workflows           | Performance-critical  |
| Offline calculations        | Client-side rendering |
| Multi-step geospatial logic | Map display           |
| Prototyping                 | Production maps       |

## Cost Optimization

```typescript
// Prefer offline tools (free)
const freeOps = [
  'distance_tool',
  'point_in_polygon_tool',
  'bearing_tool',
  'area_tool',
  'centroid_tool',
  'bbox_tool',
  'simplify_tool',
  'midpoint_tool',
  'buffer_tool'
];

// Use API tools only when necessary
const apiOps = [
  'directions_tool', // Need traffic data
  'reverse_geocode_tool', // Need address database
  'isochrone_tool', // Complex calculation
  'category_search_tool', // Need POI database
  'matrix_tool', // Travel time matrix
  'static_map_image_tool', // Static map generation
  'map_matching_tool', // GPS trace matching
  'optimization_tool' // Route optimization
];

// Utility tools
const utilityOps = [
  'version_tool', // Server version info
  'category_list_tool' // Available POI categories
];

function chooseTool(operation: string, needsRealtime: boolean) {
  if (needsRealtime) return apiOps[operation];
  return freeOps.includes(operation) ? operation : apiOps[operation];
}
```

## Resources

- [MCP Server](https://github.com/mapbox/mcp-server)
- [MCP Protocol](https://modelcontextprotocol.io)
- [Pydantic AI](https://ai.pydantic.dev/)
- [Mastra](https://mastra.ai/)
- [LangChain](https://docs.langchain.com/oss/javascript/langchain/overview/)
- [Mapbox APIs](https://docs.mapbox.com/api/)

SHA-256: 81b301aa7a81b8181b8a8e45e07864a012d35bd2901a69bbe2f9765bf0c67706