mapbox-mcp-runtime-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Mapbox MCP Runtime Patterns

Mapbox MCP 运行时集成模式

This skill provides patterns for integrating the Mapbox MCP Server into AI applications for production use with geospatial capabilities.
本技能提供了将Mapbox MCP Server集成到具备地理空间能力的生产级AI应用中的模式。

What is Mapbox MCP Server?

什么是Mapbox MCP Server?

The Mapbox MCP Server is a Model Context Protocol (MCP) server that provides AI agents with geospatial tools:
Offline Tools (Turf.js):
  • Distance, bearing, midpoint calculations
  • Point-in-polygon tests
  • Area, buffer, centroid operations
  • Bounding box, geometry simplification
  • No API calls, instant results
Mapbox API Tools:
  • Directions and routing
  • Reverse geocoding
  • POI category search
  • Isochrones (reachability)
  • Travel time matrices
  • Static map images
  • GPS trace map matching
  • Multi-stop route optimization
Utility Tools:
  • Server version info
  • POI category list
Key benefit: Give your AI application geospatial superpowers without manually integrating multiple APIs.
Mapbox MCP Server是一款基于Model Context Protocol(MCP)的服务器,为AI代理提供地理空间工具:
离线工具(Turf.js):
  • 距离、方位角、中点计算
  • 点-in-多边形检测
  • 面积、缓冲区、质心运算
  • 边界框、几何简化
  • 无需API调用,结果即时返回
Mapbox API工具:
  • 路线规划与导航
  • 逆地理编码
  • POI分类搜索
  • 等时线(可达范围)
  • 出行时间矩阵
  • 静态地图图片
  • GPS轨迹地图匹配
  • 多站点路线优化
实用工具:
  • 服务器版本信息
  • POI分类列表
核心优势: 无需手动集成多个API,即可为AI应用赋予地理空间超能力。

Understanding Tool Categories

理解工具分类

Before integrating, understand the key distinctions between tools to help your LLM choose correctly:
在集成前,需明确不同工具的关键区别,帮助LLM正确选择工具:

Distance: "As the Crow Flies" vs "Along Roads"

距离:"直线距离" vs "道路距离"

Straight-line distance (offline, instant):
  • Tools:
    distance_tool
    ,
    bearing_tool
    ,
    midpoint_tool
  • Use for: Proximity checks, "how far away is X?", comparing distances
  • Example: "Is this restaurant within 2 miles?" →
    distance_tool
Route distance (API, traffic-aware):
  • Tools:
    directions_tool
    ,
    matrix_tool
  • Use for: Navigation, drive time, "how long to drive?"
  • Example: "How long to drive there?" →
    directions_tool
直线距离(离线,即时):
  • 工具:
    distance_tool
    bearing_tool
    midpoint_tool
  • 适用场景:邻近性检查、"X到Y有多远?"、距离对比
  • 示例:"这家餐厅是否在2英里范围内?" → 使用
    distance_tool
道路距离(API,支持交通实时数据):
  • 工具:
    directions_tool
    matrix_tool
  • 适用场景:导航、驾驶时长、"开车到那里需要多久?"
  • 示例:"开车到那里需要多久?" → 使用
    directions_tool

Search: Type vs Specific Place

搜索:分类搜索 vs 特定地点搜索

Category/type search:
  • Tool:
    category_search_tool
  • Use for: "Find coffee shops", "restaurants nearby", browsing by type
  • Example: "What hotels are near me?" →
    category_search_tool
Specific place/address:
  • Tool:
    search_and_geocode_tool
    ,
    reverse_geocode_tool
  • Use for: Named places, street addresses, landmarks
  • Example: "Find 123 Main Street" →
    search_and_geocode_tool
分类/类型搜索
  • 工具:
    category_search_tool
  • 适用场景:"找咖啡店"、"附近的餐厅"、按类型浏览地点
  • 示例:"我附近有哪些酒店?" → 使用
    category_search_tool
特定地点/地址搜索
  • 工具:
    search_and_geocode_tool
    reverse_geocode_tool
  • 适用场景:命名地点、街道地址、地标
  • 示例:"找到123 Main Street" → 使用
    search_and_geocode_tool

Travel Time: Area vs Route

出行时间:可达范围 vs 特定路线

Reachable area (what's within reach):
  • Tool:
    isochrone_tool
  • Returns: GeoJSON polygon of everywhere reachable
  • Example: "What can I reach in 15 minutes?" →
    isochrone_tool
Specific route (how to get there):
  • Tool:
    directions_tool
  • Returns: Turn-by-turn directions to one destination
  • Example: "How do I get to the airport?" →
    directions_tool
可达范围(在限定时间内可到达的区域):
  • 工具:
    isochrone_tool
  • 返回结果:表示所有可达区域的GeoJSON多边形
  • 示例:"15分钟内我可以到达哪些地方?" → 使用
    isochrone_tool
特定路线(如何到达目的地):
  • 工具:
    directions_tool
  • 返回结果:前往单个目的地的逐向导航指引
  • 示例:"我怎么去机场?" → 使用
    directions_tool

Cost & Performance

成本与性能

Offline tools (free, instant):
  • No API calls, no token usage
  • Use whenever real-time data not needed
  • Examples:
    distance_tool
    ,
    point_in_polygon_tool
    ,
    area_tool
API tools (requires token, counts against usage):
  • Real-time traffic, live POI data, current conditions
  • Use when accuracy and freshness matter
  • Examples:
    directions_tool
    ,
    category_search_tool
    ,
    isochrone_tool
Best practice: Prefer offline tools when possible, use API tools when you need real-time data or routing.
离线工具(免费,即时):
  • 无需API调用,不消耗令牌
  • 无需实时数据时优先使用
  • 示例:
    distance_tool
    point_in_polygon_tool
    area_tool
API工具(需要令牌,计入使用量):
  • 提供实时交通、实时POI数据、当前路况
  • 对准确性和数据新鲜度有要求时使用
  • 示例:
    directions_tool
    category_search_tool
    isochrone_tool
最佳实践:尽可能优先使用离线工具,仅在需要实时数据或路线规划时使用API工具。

Installation & Setup

安装与设置

Option 1: Hosted Server (Recommended)

选项1:托管服务器(推荐)

Easiest integration - Use Mapbox's hosted MCP server at:
https://mcp.mapbox.com/mcp
No installation required. Simply pass your Mapbox access token in the
Authorization
header.
Benefits:
  • No server management
  • Always up-to-date
  • Production-ready
  • Lower latency (Mapbox infrastructure)
Authentication:
Use token-based authentication (standard for programmatic access):
Authorization: Bearer your_mapbox_token
Note: The hosted server also supports OAuth, but that's primarily for interactive flows (coding assistants, not production apps).
最简单的集成方式 - 使用Mapbox提供的托管MCP服务器:
https://mcp.mapbox.com/mcp
无需安装。只需在
Authorization
请求头中传入你的Mapbox访问令牌即可。
优势:
  • 无需服务器管理
  • 始终保持最新版本
  • 生产环境就绪
  • 更低延迟(基于Mapbox基础设施)
认证方式:
使用基于令牌的认证(程序化访问的标准方式):
Authorization: Bearer your_mapbox_token
注意: 托管服务器也支持OAuth,但主要用于交互式流程(如代码助手,而非生产应用)。

Option 2: Self-Hosted

选项2:自托管

For custom deployments or development:
bash
npm install @mapbox/mcp-server
Or use directly via npx:
bash
npx @mapbox/mcp-server
Environment setup:
bash
export MAPBOX_ACCESS_TOKEN="your_token_here"
适用于自定义部署或开发场景:
bash
npm install @mapbox/mcp-server
或直接通过npx使用:
bash
npx @mapbox/mcp-server
环境设置:
bash
export MAPBOX_ACCESS_TOKEN="your_token_here"

Integration Patterns

集成模式

Python Frameworks

Python框架

Pattern 1: Pydantic AI Integration

模式1:Pydantic AI集成

Use case: Building AI agents with type-safe tools in Python
适用场景: 使用Python构建具备类型安全工具的AI代理

Using Hosted Server (Recommended)

使用托管服务器(推荐)

Common mistake: When using pydantic-ai with OpenAI, the correct import is
from pydantic_ai.models.openai import OpenAIChatModel
. Do NOT use
OpenAIModel
— that class does not exist in pydantic-ai and will throw an ImportError at runtime.
python
from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIChatModel
import requests
import json
import os

class MapboxMCP:
    """Mapbox MCP via hosted server."""

    def __init__(self, token: str = None):
        self.url = 'https://mcp.mapbox.com/mcp'
        self.headers = {'Content-Type': 'application/json'}

        # Use token from environment or parameter
        token = token or os.getenv('MAPBOX_ACCESS_TOKEN')
        if token:
            self.headers['Authorization'] = f'Bearer {token}'

    def call_tool(self, tool_name: str, params: dict) -> dict:
        """Call MCP tool via HTTPS."""
        request = {
            'jsonrpc': '2.0',
            'id': 1,
            'method': 'tools/call',
            'params': {
                'name': tool_name,
                'arguments': params
            }
        }

        response = requests.post(
            self.url,
            headers=self.headers,
            json=request
        )
        response.raise_for_status()
        data = response.json()

        if 'error' in data:
            raise RuntimeError(f"MCP error: {data['error']['message']}")

        return data['result']['content'][0]['text']
常见错误: 当在pydantic-ai中与OpenAI一起使用时,正确的导入方式是
from pydantic_ai.models.openai import OpenAIChatModel
。请勿使用
OpenAIModel
——该类在pydantic-ai中不存在,会在运行时抛出ImportError。
python
from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIChatModel
import requests
import json
import os

class MapboxMCP:
    """Mapbox MCP via hosted server."""

    def __init__(self, token: str = None):
        self.url = 'https://mcp.mapbox.com/mcp'
        self.headers = {'Content-Type': 'application/json'}

        # Use token from environment or parameter
        token = token or os.getenv('MAPBOX_ACCESS_TOKEN')
        if token:
            self.headers['Authorization'] = f'Bearer {token}'

    def call_tool(self, tool_name: str, params: dict) -> dict:
        """Call MCP tool via HTTPS."""
        request = {
            'jsonrpc': '2.0',
            'id': 1,
            'method': 'tools/call',
            'params': {
                'name': tool_name,
                'arguments': params
            }
        }

        response = requests.post(
            self.url,
            headers=self.headers,
            json=request
        )
        response.raise_for_status()
        data = response.json()

        if 'error' in data:
            raise RuntimeError(f"MCP error: {data['error']['message']}")

        return data['result']['content'][0]['text']

Create agent with Mapbox tools

Create agent with Mapbox tools

Pass token directly or set MAPBOX_ACCESS_TOKEN env var

Pass token directly or set MAPBOX_ACCESS_TOKEN env var

mapbox = MapboxMCP(token='your_token')
agent = Agent( model=OpenAIChatModel('gateway/openai:gpt-5.2'), tools=[ lambda from_loc, to_loc: mapbox.call_tool( 'directions_tool', {'coordinates': [from_loc, to_loc], 'routing_profile': 'mapbox/driving-traffic'} ), lambda address: mapbox.call_tool( 'reverse_geocode_tool', {'coordinates': {'longitude': address[0], 'latitude': address[1]}} ) ] )
mapbox = MapboxMCP(token='your_token')
agent = Agent( model=OpenAIChatModel('gateway/openai:gpt-5.2'), tools=[ lambda from_loc, to_loc: mapbox.call_tool( 'directions_tool', {'coordinates': [from_loc, to_loc], 'routing_profile': 'mapbox/driving-traffic'} ), lambda address: mapbox.call_tool( 'reverse_geocode_tool', {'coordinates': {'longitude': address[0], 'latitude': address[1]}} ) ] )

Use agent

Use agent

result = agent.run_sync( "What's the driving time from Boston to NYC?" )
undefined
result = agent.run_sync( "What's the driving time from Boston to NYC?" )
undefined

Using Self-Hosted Server

使用自托管服务器

python
import subprocess

class MapboxMCPLocal:
    def __init__(self, token: str):
        self.token = token
        self.mcp_process = subprocess.Popen(
            ['npx', '@mapbox/mcp-server'],
            env={'MAPBOX_ACCESS_TOKEN': token},
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE
        )

    def call_tool(self, tool_name: str, params: dict) -> dict:
        # ... similar to hosted but via subprocess
        pass
Benefits:
  • Type-safe tool definitions
  • Seamless MCP integration
  • Python-native development
python
import subprocess

class MapboxMCPLocal:
    def __init__(self, token: str):
        self.token = token
        self.mcp_process = subprocess.Popen(
            ['npx', '@mapbox/mcp-server'],
            env={'MAPBOX_ACCESS_TOKEN': token},
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE
        )

    def call_tool(self, tool_name: str, params: dict) -> dict:
        # ... similar to hosted but via subprocess
        pass
优势:
  • 类型安全的工具定义
  • 无缝的MCP集成
  • 原生Python开发体验

Pattern 2: CrewAI Integration

模式2:CrewAI集成

Use case: Multi-agent orchestration with geospatial capabilities
CrewAI enables building autonomous agent crews with specialized roles. Integration with Mapbox MCP adds geospatial intelligence to your crew.
python
from crewai import Agent, Task, Crew
from crewai.tools import BaseTool
import requests
import os
from typing import Type
from pydantic import BaseModel, Field

class MapboxMCP:
    """Mapbox MCP connector."""

    def __init__(self, token: str = None):
        self.url = 'https://mcp.mapbox.com/mcp'
        token = token or os.getenv('MAPBOX_ACCESS_TOKEN')
        self.headers = {
            'Content-Type': 'application/json',
            'Authorization': f'Bearer {token}'
        }

    def call_tool(self, tool_name: str, params: dict) -> str:
        request = {
            'jsonrpc': '2.0',
            'id': 1,
            'method': 'tools/call',
            'params': {'name': tool_name, 'arguments': params}
        }
        response = requests.post(self.url, headers=self.headers, json=request)
        response.raise_for_status()
        data = response.json()

        if 'error' in data:
            raise RuntimeError(f"MCP error: {data['error']['message']}")

        return data['result']['content'][0]['text']
适用场景: 具备地理空间能力的多代理编排
CrewAI支持构建具备特定角色的自主代理团队。与Mapbox MCP的集成可为你的代理团队增添地理空间智能。
python
from crewai import Agent, Task, Crew
from crewai.tools import BaseTool
import requests
import os
from typing import Type
from pydantic import BaseModel, Field

class MapboxMCP:
    """Mapbox MCP connector."""

    def __init__(self, token: str = None):
        self.url = 'https://mcp.mapbox.com/mcp'
        token = token or os.getenv('MAPBOX_ACCESS_TOKEN')
        self.headers = {
            'Content-Type': 'application/json',
            'Authorization': f'Bearer {token}'
        }

    def call_tool(self, tool_name: str, params: dict) -> str:
        request = {
            'jsonrpc': '2.0',
            'id': 1,
            'method': 'tools/call',
            'params': {'name': tool_name, 'arguments': params}
        }
        response = requests.post(self.url, headers=self.headers, json=request)
        response.raise_for_status()
        data = response.json()

        if 'error' in data:
            raise RuntimeError(f"MCP error: {data['error']['message']}")

        return data['result']['content'][0]['text']

Create Mapbox tools for CrewAI

Create Mapbox tools for CrewAI

class DirectionsTool(BaseTool): name: str = "directions_tool" description: str = "Get driving directions between two locations"
class InputSchema(BaseModel):
    origin: list = Field(description="Origin [lng, lat]")
    destination: list = Field(description="Destination [lng, lat]")

args_schema: Type[BaseModel] = InputSchema

def __init__(self):
    super().__init__()
    self.mcp = MapboxMCP()

def _run(self, origin: list, destination: list) -> str:
    result = self.mcp.call_tool('directions_tool', {
        'coordinates': [
            {'longitude': origin[0], 'latitude': origin[1]},
            {'longitude': destination[0], 'latitude': destination[1]}
        ],
        'routing_profile': 'mapbox/driving-traffic'
    })
    return f"Directions: {result}"
class GeocodeTool(BaseTool): name: str = "reverse_geocode_tool" description: str = "Convert coordinates to human-readable address"
class InputSchema(BaseModel):
    coordinates: list = Field(description="Coordinates [lng, lat]")

args_schema: Type[BaseModel] = InputSchema

def __init__(self):
    super().__init__()
    self.mcp = MapboxMCP()

def _run(self, coordinates: list) -> str:
    result = self.mcp.call_tool('reverse_geocode_tool', {
        'coordinates': {'longitude': coordinates[0], 'latitude': coordinates[1]}
    })
    return result
class SearchPOITool(BaseTool): name: str = "search_poi" description: str = "Find points of interest by category near a location"
class InputSchema(BaseModel):
    category: str = Field(description="POI category (restaurant, hotel, etc.)")
    location: list = Field(description="Search center [lng, lat]")

args_schema: Type[BaseModel] = InputSchema

def __init__(self):
    super().__init__()
    self.mcp = MapboxMCP()

def _run(self, category: str, location: list) -> str:
    result = self.mcp.call_tool('category_search_tool', {
        'category': category,
        'proximity': {'longitude': location[0], 'latitude': location[1]}
    })
    return result
class DirectionsTool(BaseTool): name: str = "directions_tool" description: str = "Get driving directions between two locations"
class InputSchema(BaseModel):
    origin: list = Field(description="Origin [lng, lat]")
    destination: list = Field(description="Destination [lng, lat]")

args_schema: Type[BaseModel] = InputSchema

def __init__(self):
    super().__init__()
    self.mcp = MapboxMCP()

def _run(self, origin: list, destination: list) -> str:
    result = self.mcp.call_tool('directions_tool', {
        'coordinates': [
            {'longitude': origin[0], 'latitude': origin[1]},
            {'longitude': destination[0], 'latitude': destination[1]}
        ],
        'routing_profile': 'mapbox/driving-traffic'
    })
    return f"Directions: {result}"
class GeocodeTool(BaseTool): name: str = "reverse_geocode_tool" description: str = "Convert coordinates to human-readable address"
class InputSchema(BaseModel):
    coordinates: list = Field(description="Coordinates [lng, lat]")

args_schema: Type[BaseModel] = InputSchema

def __init__(self):
    super().__init__()
    self.mcp = MapboxMCP()

def _run(self, coordinates: list) -> str:
    result = self.mcp.call_tool('reverse_geocode_tool', {
        'coordinates': {'longitude': coordinates[0], 'latitude': coordinates[1]}
    })
    return result
class SearchPOITool(BaseTool): name: str = "search_poi" description: str = "Find points of interest by category near a location"
class InputSchema(BaseModel):
    category: str = Field(description="POI category (restaurant, hotel, etc.)")
    location: list = Field(description="Search center [lng, lat]")

args_schema: Type[BaseModel] = InputSchema

def __init__(self):
    super().__init__()
    self.mcp = MapboxMCP()

def _run(self, category: str, location: list) -> str:
    result = self.mcp.call_tool('category_search_tool', {
        'category': category,
        'proximity': {'longitude': location[0], 'latitude': location[1]}
    })
    return result

Create specialized agents with geospatial tools

Create specialized agents with geospatial tools

location_analyst = Agent( role='Location Analyst', goal='Analyze geographic locations and provide insights', backstory="""Expert in geographic analysis and location intelligence.
Use search_poi for finding types of places (restaurants, hotels).
Use reverse_geocode_tool for converting coordinates to addresses.""",
tools=[GeocodeTool(), SearchPOITool()],
verbose=True
)
route_planner = Agent( role='Route Planner', goal='Plan optimal routes and provide travel time estimates', backstory="""Experienced logistics coordinator specializing in route optimization.
Use directions_tool for route distance along roads with traffic.
Always use when traffic-aware travel time is needed.""",
tools=[DirectionsTool()],
verbose=True
)
location_analyst = Agent( role='Location Analyst', goal='Analyze geographic locations and provide insights', backstory="""Expert in geographic analysis and location intelligence.
Use search_poi for finding types of places (restaurants, hotels).
Use reverse_geocode_tool for converting coordinates to addresses.""",
tools=[GeocodeTool(), SearchPOITool()],
verbose=True
)
route_planner = Agent( role='Route Planner', goal='Plan optimal routes and provide travel time estimates', backstory="""Experienced logistics coordinator specializing in route optimization.
Use directions_tool for route distance along roads with traffic.
Always use when traffic-aware travel time is needed.""",
tools=[DirectionsTool()],
verbose=True
)

Create tasks

Create tasks

find_restaurants_task = Task( description=""" Find the top 5 restaurants near coordinates [-73.9857, 40.7484] (Times Square). Provide their names and approximate distances. """, agent=location_analyst, expected_output="List of 5 restaurants with distances" )
plan_route_task = Task( description=""" Plan a route from [-74.0060, 40.7128] (downtown NYC) to [-73.9857, 40.7484] (Times Square). Provide driving time considering current traffic. """, agent=route_planner, expected_output="Route with estimated driving time" )
find_restaurants_task = Task( description=""" Find the top 5 restaurants near coordinates [-73.9857, 40.7484] (Times Square). Provide their names and approximate distances. """, agent=location_analyst, expected_output="List of 5 restaurants with distances" )
plan_route_task = Task( description=""" Plan a route from [-74.0060, 40.7128] (downtown NYC) to [-73.9857, 40.7484] (Times Square). Provide driving time considering current traffic. """, agent=route_planner, expected_output="Route with estimated driving time" )

Create and run crew

Create and run crew

crew = Crew( agents=[location_analyst, route_planner], tasks=[find_restaurants_task, plan_route_task], verbose=True )
result = crew.kickoff() print(result)

**Real-world example - Restaurant finder crew:**

```python
crew = Crew( agents=[location_analyst, route_planner], tasks=[find_restaurants_task, plan_route_task], verbose=True )
result = crew.kickoff() print(result)

**真实场景示例 - 餐厅搜索代理团队:**

```python

Define crew for restaurant recommendation system

Define crew for restaurant recommendation system

class RestaurantCrew: def init(self): self.mcp = MapboxMCP()
    # Location specialist agent
    self.location_agent = Agent(
        role='Location Specialist',
        goal='Find and analyze restaurant locations',
        tools=[SearchPOITool(), GeocodeTool()],
        backstory='Expert in finding the best dining locations'
    )

    # Logistics agent
    self.logistics_agent = Agent(
        role='Logistics Coordinator',
        goal='Calculate travel times and optimal routes',
        tools=[DirectionsTool()],
        backstory='Specialist in urban navigation and time optimization'
    )

def find_restaurants_with_commute(self, user_location: list, max_minutes: int):
    # Task 1: Find nearby restaurants
    search_task = Task(
        description=f"Find restaurants near {user_location}",
        agent=self.location_agent,
        expected_output="List of restaurants with coordinates"
    )

    # Task 2: Calculate travel times
    route_task = Task(
        description=f"Calculate travel time to each restaurant from {user_location}",
        agent=self.logistics_agent,
        expected_output="Travel times to each restaurant",
        context=[search_task]  # Depends on search results
    )

    crew = Crew(
        agents=[self.location_agent, self.logistics_agent],
        tasks=[search_task, route_task],
        verbose=True
    )

    return crew.kickoff()
class RestaurantCrew: def init(self): self.mcp = MapboxMCP()
    # Location specialist agent
    self.location_agent = Agent(
        role='Location Specialist',
        goal='Find and analyze restaurant locations',
        tools=[SearchPOITool(), GeocodeTool()],
        backstory='Expert in finding the best dining locations'
    )

    # Logistics agent
    self.logistics_agent = Agent(
        role='Logistics Coordinator',
        goal='Calculate travel times and optimal routes',
        tools=[DirectionsTool()],
        backstory='Specialist in urban navigation and time optimization'
    )

def find_restaurants_with_commute(self, user_location: list, max_minutes: int):
    # Task 1: Find nearby restaurants
    search_task = Task(
        description=f"Find restaurants near {user_location}",
        agent=self.location_agent,
        expected_output="List of restaurants with coordinates"
    )

    # Task 2: Calculate travel times
    route_task = Task(
        description=f"Calculate travel time to each restaurant from {user_location}",
        agent=self.logistics_agent,
        expected_output="Travel times to each restaurant",
        context=[search_task]  # Depends on search results
    )

    crew = Crew(
        agents=[self.location_agent, self.logistics_agent],
        tasks=[search_task, route_task],
        verbose=True
    )

    return crew.kickoff()

Usage

Usage

restaurant_crew = RestaurantCrew() results = restaurant_crew.find_restaurants_with_commute( user_location=[-73.9857, 40.7484], max_minutes=15 )

**Benefits:**

- Multi-agent orchestration with geospatial tools
- Task dependencies and context passing
- Role-based agent specialization
- Autonomous crew execution
restaurant_crew = RestaurantCrew() results = restaurant_crew.find_restaurants_with_commute( user_location=[-73.9857, 40.7484], max_minutes=15 )

**优势:**

- 具备地理空间工具的多代理编排
- 任务依赖与上下文传递
- 基于角色的代理专业化
- 自主代理团队执行

Pattern 3: Smolagents Integration

模式3:Smolagents集成

Use case: Lightweight agents with geospatial capabilities (Hugging Face)
Smolagents is Hugging Face's simple, efficient agent framework. Perfect for deploying geospatial agents with minimal overhead.
python
from smolagents import CodeAgent, Tool, HfApiModel
import requests
import os

class MapboxMCP:
    """Mapbox MCP connector."""

    def __init__(self, token: str = None):
        self.url = 'https://mcp.mapbox.com/mcp'
        token = token or os.getenv('MAPBOX_ACCESS_TOKEN')
        self.headers = {
            'Content-Type': 'application/json',
            'Authorization': f'Bearer {token}'
        }

    def call_tool(self, tool_name: str, params: dict) -> str:
        request = {
            'jsonrpc': '2.0',
            'id': 1,
            'method': 'tools/call',
            'params': {'name': tool_name, 'arguments': params}
        }
        response = requests.post(self.url, headers=self.headers, json=request)
        result = response.json()['result']
        return result['content'][0]['text']
适用场景: 具备地理空间能力的轻量级代理(Hugging Face)
Smolagents是Hugging Face推出的简单高效的代理框架。非常适合以最小开销部署地理空间代理。
python
from smolagents import CodeAgent, Tool, HfApiModel
import requests
import os

class MapboxMCP:
    """Mapbox MCP connector."""

    def __init__(self, token: str = None):
        self.url = 'https://mcp.mapbox.com/mcp'
        token = token or os.getenv('MAPBOX_ACCESS_TOKEN')
        self.headers = {
            'Content-Type': 'application/json',
            'Authorization': f'Bearer {token}'
        }

    def call_tool(self, tool_name: str, params: dict) -> str:
        request = {
            'jsonrpc': '2.0',
            'id': 1,
            'method': 'tools/call',
            'params': {'name': tool_name, 'arguments': params}
        }
        response = requests.post(self.url, headers=self.headers, json=request)
        result = response.json()['result']
        return result['content'][0]['text']

Create Mapbox tools for Smolagents

Create Mapbox tools for Smolagents

class DirectionsTool(Tool): name = "directions_tool" description = """ Get driving directions between two locations.
Args:
    origin: Origin coordinates as [longitude, latitude]
    destination: Destination coordinates as [longitude, latitude]

Returns:
    Directions with distance and travel time
"""

def __init__(self):
    super().__init__()
    self.mcp = MapboxMCP()

def forward(self, origin: list, destination: list) -> str:
    return self.mcp.call_tool('directions_tool', {
        'coordinates': [
            {'longitude': origin[0], 'latitude': origin[1]},
            {'longitude': destination[0], 'latitude': destination[1]}
        ],
        'routing_profile': 'mapbox/driving-traffic'
    })
class CalculateDistanceTool(Tool): name = "distance_tool" description = """ Calculate distance between two points (offline, instant).
Args:
    from_coords: Start coordinates [longitude, latitude]
    to_coords: End coordinates [longitude, latitude]
    units: 'miles' or 'kilometers'

Returns:
    Distance as a number
"""

def __init__(self):
    super().__init__()
    self.mcp = MapboxMCP()

def forward(self, from_coords: list, to_coords: list, units: str = 'miles') -> str:
    return self.mcp.call_tool('distance_tool', {
        'from': {'longitude': from_coords[0], 'latitude': from_coords[1]},
        'to': {'longitude': to_coords[0], 'latitude': to_coords[1]},
        'units': units
    })
class SearchPOITool(Tool): name = "search_poi" description = """ Search for points of interest by category.
Args:
    category: POI category (restaurant, hotel, gas_station, etc.)
    location: Search center [longitude, latitude]

Returns:
    List of nearby POIs with names and coordinates
"""

def __init__(self):
    super().__init__()
    self.mcp = MapboxMCP()

def forward(self, category: str, location: list) -> str:
    return self.mcp.call_tool('category_search_tool', {
        'category': category,
        'proximity': {'longitude': location[0], 'latitude': location[1]}
    })
class IsochroneTool(Tool): name = "isochrone_tool" description = """ Calculate reachable area within time limit (isochrone).
Args:
    location: Center point [longitude, latitude]
    minutes: Time limit in minutes
    profile: 'mapbox/driving', 'mapbox/walking', or 'mapbox/cycling'

Returns:
    GeoJSON polygon of reachable area
"""

def __init__(self):
    super().__init__()
    self.mcp = MapboxMCP()

def forward(self, location: list, minutes: int, profile: str = 'mapbox/driving') -> str:
    return self.mcp.call_tool('isochrone_tool', {
        'coordinates': {'longitude': location[0], 'latitude': location[1]},
        'contours_minutes': [minutes],
        'profile': profile
    })
class DirectionsTool(Tool): name = "directions_tool" description = """ Get driving directions between two locations.
Args:
    origin: Origin coordinates as [longitude, latitude]
    destination: Destination coordinates as [longitude, latitude]

Returns:
    Directions with distance and travel time
"""

def __init__(self):
    super().__init__()
    self.mcp = MapboxMCP()

def forward(self, origin: list, destination: list) -> str:
    return self.mcp.call_tool('directions_tool', {
        'coordinates': [
            {'longitude': origin[0], 'latitude': origin[1]},
            {'longitude': destination[0], 'latitude': destination[1]}
        ],
        'routing_profile': 'mapbox/driving-traffic'
    })
class CalculateDistanceTool(Tool): name = "distance_tool" description = """ Calculate distance between two points (offline, instant).
Args:
    from_coords: Start coordinates [longitude, latitude]
    to_coords: End coordinates [longitude, latitude]
    units: 'miles' or 'kilometers'

Returns:
    Distance as a number
"""

def __init__(self):
    super().__init__()
    self.mcp = MapboxMCP()

def forward(self, from_coords: list, to_coords: list, units: str = 'miles') -> str:
    return self.mcp.call_tool('distance_tool', {
        'from': {'longitude': from_coords[0], 'latitude': from_coords[1]},
        'to': {'longitude': to_coords[0], 'latitude': to_coords[1]},
        'units': units
    })
class SearchPOITool(Tool): name = "search_poi" description = """ Search for points of interest by category.
Args:
    category: POI category (restaurant, hotel, gas_station, etc.)
    location: Search center [longitude, latitude]

Returns:
    List of nearby POIs with names and coordinates
"""

def __init__(self):
    super().__init__()
    self.mcp = MapboxMCP()

def forward(self, category: str, location: list) -> str:
    return self.mcp.call_tool('category_search_tool', {
        'category': category,
        'proximity': {'longitude': location[0], 'latitude': location[1]}
    })
class IsochroneTool(Tool): name = "isochrone_tool" description = """ Calculate reachable area within time limit (isochrone).
Args:
    location: Center point [longitude, latitude]
    minutes: Time limit in minutes
    profile: 'mapbox/driving', 'mapbox/walking', or 'mapbox/cycling'

Returns:
    GeoJSON polygon of reachable area
"""

def __init__(self):
    super().__init__()
    self.mcp = MapboxMCP()

def forward(self, location: list, minutes: int, profile: str = 'mapbox/driving') -> str:
    return self.mcp.call_tool('isochrone_tool', {
        'coordinates': {'longitude': location[0], 'latitude': location[1]},
        'contours_minutes': [minutes],
        'profile': profile
    })

Create agent with Mapbox tools

Create agent with Mapbox tools

model = HfApiModel()
agent = CodeAgent( tools=[ DirectionsTool(), CalculateDistanceTool(), SearchPOITool(), IsochroneTool() ], model=model )
model = HfApiModel()
agent = CodeAgent( tools=[ DirectionsTool(), CalculateDistanceTool(), SearchPOITool(), IsochroneTool() ], model=model )

Use agent

Use agent

result = agent.run( "Find restaurants within 10 minutes walking from Times Square NYC " "(coordinates: -73.9857, 40.7484). Calculate distances to each." )
print(result)

**Real-world example - Property search agent:**

```python
class PropertySearchAgent:
    def __init__(self):
        self.mcp = MapboxMCP()

        # Create specialized tools
        tools = [
            IsochroneTool(),
            SearchPOITool(),
            CalculateDistanceTool()
        ]

        self.agent = CodeAgent(
            tools=tools,
            model=HfApiModel()
        )

    def find_properties_near_work(
        self,
        work_location: list,
        max_commute_minutes: int,
        property_locations: list[dict]
    ):
        """Find properties within commute time of work."""

        prompt = f"""
        I need to find properties within {max_commute_minutes} minutes
        driving of my work at {work_location}.

        Property locations to check:
        {property_locations}

        For each property:
        1. Calculate if it's within the commute time
        2. Find nearby amenities (grocery stores, restaurants)
        3. Calculate distances to key locations

        Return a ranked list of properties with commute time and nearby amenities.
        """

        return self.agent.run(prompt)
result = agent.run( "Find restaurants within 10 minutes walking from Times Square NYC " "(coordinates: -73.9857, 40.7484). Calculate distances to each." )
print(result)

**真实场景示例 - 房产搜索代理:**

```python
class PropertySearchAgent:
    def __init__(self):
        self.mcp = MapboxMCP()

        # Create specialized tools
        tools = [
            IsochroneTool(),
            SearchPOITool(),
            CalculateDistanceTool()
        ]

        self.agent = CodeAgent(
            tools=tools,
            model=HfApiModel()
        )

    def find_properties_near_work(
        self,
        work_location: list,
        max_commute_minutes: int,
        property_locations: list[dict]
    ):
        """Find properties within commute time of work."""

        prompt = f"""
        I need to find properties within {max_commute_minutes} minutes
        driving of my work at {work_location}.

        Property locations to check:
        {property_locations}

        For each property:
        1. Calculate if it's within the commute time
        2. Find nearby amenities (grocery stores, restaurants)
        3. Calculate distances to key locations

        Return a ranked list of properties with commute time and nearby amenities.
        """

        return self.agent.run(prompt)

Usage

Usage

property_agent = PropertySearchAgent()
properties = [ {'id': 1, 'address': '123 Main St', 'coords': [-122.4194, 37.7749]}, {'id': 2, 'address': '456 Oak Ave', 'coords': [-122.4094, 37.7849]}, ]
results = property_agent.find_properties_near_work( work_location=[-122.4, 37.79], # Downtown SF max_commute_minutes=30, property_locations=properties )

**Benefits:**

- Lightweight and efficient
- Simple tool definition
- Code-based agent execution
- Great for production deployment
property_agent = PropertySearchAgent()
properties = [ {'id': 1, 'address': '123 Main St', 'coords': [-122.4194, 37.7749]}, {'id': 2, 'address': '456 Oak Ave', 'coords': [-122.4094, 37.7849]}, ]
results = property_agent.find_properties_near_work( work_location=[-122.4, 37.79], # Downtown SF max_commute_minutes=30, property_locations=properties )

**优势:**

- 轻量级且高效
- 简单的工具定义
- 基于代码的代理执行
- 非常适合生产部署

JavaScript/TypeScript Frameworks

JavaScript/TypeScript框架

Pattern 4: Mastra Integration

模式4:Mastra集成

Use case: Building multi-agent systems with geospatial workflows
typescript
import { Mastra } from '@mastra/core';

class MapboxMCP {
  private url = 'https://mcp.mapbox.com/mcp';
  private headers: Record<string, string>;

  constructor(token?: string) {
    const mapboxToken = token || process.env.MAPBOX_ACCESS_TOKEN;
    this.headers = {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${mapboxToken}`
    };
  }

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

    const response = await fetch(this.url, {
      method: 'POST',
      headers: this.headers,
      body: JSON.stringify(request)
    });

    const data = await response.json();
    return JSON.parse(data.result.content[0].text);
  }
}

// Create Mastra agent with Mapbox tools
import { Agent } from '@mastra/core/agent';
import { createTool } from '@mastra/core/tools';
import { z } from 'zod';

const mcp = new MapboxMCP();

// Create Mapbox tools
const searchPOITool = createTool({
  id: 'search-poi',
  description: 'Find places of a specific category near a location',
  inputSchema: z.object({
    category: z.string(),
    location: z.array(z.number()).length(2)
  }),
  execute: async ({ category, location }) => {
    return await mcp.callTool('category_search_tool', {
      category,
      proximity: { longitude: location[0], latitude: location[1] }
    });
  }
});

const getDirectionsTool = createTool({
  id: 'get-directions',
  description: 'Get driving directions with traffic',
  inputSchema: z.object({
    origin: z.array(z.number()).length(2),
    destination: z.array(z.number()).length(2)
  }),
  execute: async ({ origin, destination }) => {
    return await mcp.callTool('directions_tool', {
      coordinates: [
        { longitude: origin[0], latitude: origin[1] },
        { longitude: destination[0], latitude: destination[1] }
      ],
      routing_profile: 'mapbox/driving-traffic'
    });
  }
});

// Create location agent
const locationAgent = new Agent({
  id: 'location-agent',
  name: 'Location Intelligence Agent',
  instructions: 'You help users find places and plan routes with geospatial tools.',
  model: 'openai/gpt-5.2',
  tools: {
    searchPOITool,
    getDirectionsTool
  }
});

// Use agent
const result = await locationAgent.generate([
  { role: 'user', content: 'Find restaurants near Times Square NYC (-73.9857, 40.7484)' }
]);
Benefits:
  • Multi-step geospatial workflows
  • Agent orchestration
  • State management
适用场景: 构建具备地理空间工作流的多代理系统
typescript
import { Mastra } from '@mastra/core';

class MapboxMCP {
  private url = 'https://mcp.mapbox.com/mcp';
  private headers: Record<string, string>;

  constructor(token?: string) {
    const mapboxToken = token || process.env.MAPBOX_ACCESS_TOKEN;
    this.headers = {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${mapboxToken}`
    };
  }

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

    const response = await fetch(this.url, {
      method: 'POST',
      headers: this.headers,
      body: JSON.stringify(request)
    });

    const data = await response.json();
    return JSON.parse(data.result.content[0].text);
  }
}

// Create Mastra agent with Mapbox tools
import { Agent } from '@mastra/core/agent';
import { createTool } from '@mastra/core/tools';
import { z } from 'zod';

const mcp = new MapboxMCP();

// Create Mapbox tools
const searchPOITool = createTool({
  id: 'search-poi',
  description: 'Find places of a specific category near a location',
  inputSchema: z.object({
    category: z.string(),
    location: z.array(z.number()).length(2)
  }),
  execute: async ({ category, location }) => {
    return await mcp.callTool('category_search_tool', {
      category,
      proximity: { longitude: location[0], latitude: location[1] }
    });
  }
});

const getDirectionsTool = createTool({
  id: 'get-directions',
  description: 'Get driving directions with traffic',
  inputSchema: z.object({
    origin: z.array(z.number()).length(2),
    destination: z.array(z.number()).length(2)
  }),
  execute: async ({ origin, destination }) => {
    return await mcp.callTool('directions_tool', {
      coordinates: [
        { longitude: origin[0], latitude: origin[1] },
        { longitude: destination[0], latitude: destination[1] }
      ],
      routing_profile: 'mapbox/driving-traffic'
    });
  }
});

// Create location agent
const locationAgent = new Agent({
  id: 'location-agent',
  name: 'Location Intelligence Agent',
  instructions: 'You help users find places and plan routes with geospatial tools.',
  model: 'openai/gpt-5.2',
  tools: {
    searchPOITool,
    getDirectionsTool
  }
});

// Use agent
const result = await locationAgent.generate([
  { role: 'user', content: 'Find restaurants near Times Square NYC (-73.9857, 40.7484)' }
]);
优势:
  • 多步骤地理空间工作流
  • 代理编排
  • 状态管理

Pattern 5: LangChain Integration

模式5:LangChain集成

Use case: Building conversational AI with geospatial tools
typescript
import { ChatOpenAI } from '@langchain/openai';
import { AgentExecutor, createToolCallingAgent } from 'langchain/agents';
import { DynamicStructuredTool } from '@langchain/core/tools';
import { ChatPromptTemplate, MessagesPlaceholder } from '@langchain/core/prompts';
import { z } from 'zod';

// MCP Server wrapper for hosted server
class MapboxMCP {
  private url = 'https://mcp.mapbox.com/mcp';
  private headers: Record<string, string>;

  constructor(token?: string) {
    const mapboxToken = token || process.env.MAPBOX_ACCESS_TOKEN;
    this.headers = {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${mapboxToken}`
    };
  }

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

    const response = await fetch(this.url, {
      method: 'POST',
      headers: this.headers,
      body: JSON.stringify(request)
    });

    const data = await response.json();
    return data.result.content[0].text;
  }
}

// Create LangChain tools from MCP
const mcp = new MapboxMCP();

const tools = [
  new DynamicStructuredTool({
    name: 'directions_tool',
    description:
      'Get turn-by-turn driving directions with traffic-aware route distance along roads. Use when you need the actual driving route or traffic-aware duration.',
    schema: z.object({
      origin: z.tuple([z.number(), z.number()]).describe('Origin [longitude, latitude]'),
      destination: z.tuple([z.number(), z.number()]).describe('Destination [longitude, latitude]')
    }) as any,
    func: async ({ origin, destination }: any) => {
      return await mcp.callTool('directions_tool', {
        coordinates: [
          { longitude: origin[0], latitude: origin[1] },
          { longitude: destination[0], latitude: destination[1] }
        ],
        routing_profile: 'mapbox/driving-traffic'
      });
    }
  }),

  new DynamicStructuredTool({
    name: 'category_search_tool',
    description:
      'Find ALL places of a specific category type near a location. Use when user wants to browse places by type (restaurants, hotels, coffee, etc.).',
    schema: z.object({
      category: z.string().describe('POI category: restaurant, hotel, coffee, etc.'),
      location: z.tuple([z.number(), z.number()]).describe('Search center [longitude, latitude]')
    }) as any,
    func: async ({ category, location }: any) => {
      return await mcp.callTool('category_search_tool', {
        category,
        proximity: { longitude: location[0], latitude: location[1] }
      });
    }
  }),

  new DynamicStructuredTool({
    name: 'isochrone_tool',
    description:
      'Calculate the AREA reachable within a time limit from a starting point. Use for "What can I reach in X minutes?" questions.',
    schema: z.object({
      location: z.tuple([z.number(), z.number()]).describe('Center point [longitude, latitude]'),
      minutes: z.number().describe('Time limit in minutes'),
      profile: z.enum(['mapbox/driving', 'mapbox/walking', 'mapbox/cycling']).optional()
    }) as any,
    func: async ({ location, minutes, profile }: any) => {
      return await mcp.callTool('isochrone_tool', {
        coordinates: { longitude: location[0], latitude: location[1] },
        contours_minutes: [minutes],
        profile: profile || 'mapbox/walking'
      });
    }
  }),

  new DynamicStructuredTool({
    name: 'distance_tool',
    description: 'Calculate straight-line distance between two points (offline, free)',
    schema: z.object({
      from: z.tuple([z.number(), z.number()]).describe('Start [longitude, latitude]'),
      to: z.tuple([z.number(), z.number()]).describe('End [longitude, latitude]'),
      units: z.enum(['miles', 'kilometers']).optional()
    }) as any,
    func: async ({ from, to, units }: any) => {
      return await mcp.callTool('distance_tool', {
        from: { longitude: from[0], latitude: from[1] },
        to: { longitude: to[0], latitude: to[1] },
        units: units || 'miles'
      });
    }
  })
];

// Create agent
const llm = new ChatOpenAI({ model: 'gpt-5.2', temperature: 0 });
const prompt = ChatPromptTemplate.fromMessages([
  ['system', 'You are a location intelligence assistant.'],
  ['human', '{input}'],
  new MessagesPlaceholder('agent_scratchpad')
]);
// @ts-ignore - Zod tuple schemas cause deep type recursion
const agent = await createToolCallingAgent({ llm, tools, prompt });
const executor = new AgentExecutor({ agent, tools, verbose: true });

// Use agent
const result = await executor.invoke({
  input: 'Find coffee shops within 10 minutes walking from Union Square, NYC'
});
Benefits:
  • Conversational interface
  • Tool chaining
  • Memory and context management
TypeScript Type Considerations:
When using
DynamicStructuredTool
with Zod schemas (especially
z.tuple()
), TypeScript may encounter deep type recursion errors. This is a known limitation with complex Zod generic types. The minimal fix is to add
as any
type assertions:
typescript
const tool = new DynamicStructuredTool({
  name: 'my_tool',
  schema: z.object({
    coords: z.tuple([z.number(), z.number()])
  }) as any, // ← Add 'as any' to prevent type recursion
  func: async ({ coords }: any) => {
    // ← Type parameters as 'any'
    // Implementation
  }
});

// For JSON responses from external APIs
const data = (await response.json()) as any;

// For createOpenAIFunctionsAgent with complex tool types
// @ts-ignore - Zod tuple schemas cause deep type recursion
const agent = await createOpenAIFunctionsAgent({ llm, tools, prompt });
This doesn't affect runtime validation (Zod still validates at runtime) - it only helps TypeScript's type checker avoid infinite recursion during compilation.
适用场景: 构建具备地理空间工具的对话式AI
typescript
import { ChatOpenAI } from '@langchain/openai';
import { AgentExecutor, createToolCallingAgent } from 'langchain/agents';
import { DynamicStructuredTool } from '@langchain/core/tools';
import { ChatPromptTemplate, MessagesPlaceholder } from '@langchain/core/prompts';
import { z } from 'zod';

// MCP Server wrapper for hosted server
class MapboxMCP {
  private url = 'https://mcp.mapbox.com/mcp';
  private headers: Record<string, string>;

  constructor(token?: string) {
    const mapboxToken = token || process.env.MAPBOX_ACCESS_TOKEN;
    this.headers = {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${mapboxToken}`
    };
  }

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

    const response = await fetch(this.url, {
      method: 'POST',
      headers: this.headers,
      body: JSON.stringify(request)
    });

    const data = await response.json();
    return data.result.content[0].text;
  }
}

// Create LangChain tools from MCP
const mcp = new MapboxMCP();

const tools = [
  new DynamicStructuredTool({
    name: 'directions_tool',
    description:
      'Get turn-by-turn driving directions with traffic-aware route distance along roads. Use when you need the actual driving route or traffic-aware duration.',
    schema: z.object({
      origin: z.tuple([z.number(), z.number()]).describe('Origin [longitude, latitude]'),
      destination: z.tuple([z.number(), z.number()]).describe('Destination [longitude, latitude]')
    }) as any,
    func: async ({ origin, destination }: any) => {
      return await mcp.callTool('directions_tool', {
        coordinates: [
          { longitude: origin[0], latitude: origin[1] },
          { longitude: destination[0], latitude: destination[1] }
        ],
        routing_profile: 'mapbox/driving-traffic'
      });
    }
  }),

  new DynamicStructuredTool({
    name: 'category_search_tool',
    description:
      'Find ALL places of a specific category type near a location. Use when user wants to browse places by type (restaurants, hotels, coffee, etc.).',
    schema: z.object({
      category: z.string().describe('POI category: restaurant, hotel, coffee, etc.'),
      location: z.tuple([z.number(), z.number()]).describe('Search center [longitude, latitude]')
    }) as any,
    func: async ({ category, location }: any) => {
      return await mcp.callTool('category_search_tool', {
        category,
        proximity: { longitude: location[0], latitude: location[1] }
      });
    }
  }),

  new DynamicStructuredTool({
    name: 'isochrone_tool',
    description:
      'Calculate the AREA reachable within a time limit from a starting point. Use for "What can I reach in X minutes?" questions.',
    schema: z.object({
      location: z.tuple([z.number(), z.number()]).describe('Center point [longitude, latitude]'),
      minutes: z.number().describe('Time limit in minutes'),
      profile: z.enum(['mapbox/driving', 'mapbox/walking', 'mapbox/cycling']).optional()
    }) as any,
    func: async ({ location, minutes, profile }: any) => {
      return await mcp.callTool('isochrone_tool', {
        coordinates: { longitude: location[0], latitude: location[1] },
        contours_minutes: [minutes],
        profile: profile || 'mapbox/walking'
      });
    }
  }),

  new DynamicStructuredTool({
    name: 'distance_tool',
    description: 'Calculate straight-line distance between two points (offline, free)',
    schema: z.object({
      from: z.tuple([z.number(), z.number()]).describe('Start [longitude, latitude]'),
      to: z.tuple([z.number(), z.number()]).describe('End [longitude, latitude]'),
      units: z.enum(['miles', 'kilometers']).optional()
    }) as any,
    func: async ({ from, to, units }: any) => {
      return await mcp.callTool('distance_tool', {
        from: { longitude: from[0], latitude: from[1] },
        to: { longitude: to[0], latitude: to[1] },
        units: units || 'miles'
      });
    }
  })
];

// Create agent
const llm = new ChatOpenAI({ model: 'gpt-5.2', temperature: 0 });
const prompt = ChatPromptTemplate.fromMessages([
  ['system', 'You are a location intelligence assistant.'],
  ['human', '{input}'],
  new MessagesPlaceholder('agent_scratchpad')
]);
// @ts-ignore - Zod tuple schemas cause deep type recursion
const agent = await createToolCallingAgent({ llm, tools, prompt });
const executor = new AgentExecutor({ agent, tools, verbose: true });

// Use agent
const result = await executor.invoke({
  input: 'Find coffee shops within 10 minutes walking from Union Square, NYC'
});
优势:
  • 对话式交互界面
  • 工具链调用
  • 记忆与上下文管理
TypeScript类型注意事项:
当将
DynamicStructuredTool
与Zod模式(尤其是
z.tuple()
)一起使用时,TypeScript可能会遇到深层类型递归错误。这是复杂Zod泛型类型的已知限制。最简单的解决方法是添加
as any
类型断言:
typescript
const tool = new DynamicStructuredTool({
  name: 'my_tool',
  schema: z.object({
    coords: z.tuple([z.number(), z.number()])
  }) as any, // ← Add 'as any' to prevent type recursion
  func: async ({ coords }: any) => {
    // ← Type parameters as 'any'
    // Implementation
  }
});

// For JSON responses from external APIs
const data = (await response.json()) as any;

// For createOpenAIFunctionsAgent with complex tool types
// @ts-ignore - Zod tuple schemas cause deep type recursion
const agent = await createOpenAIFunctionsAgent({ llm, tools, prompt });
这不会影响运行时验证(Zod仍会在运行时进行验证)——它只是帮助TypeScript的类型检查器在编译时避免无限递归。

Pattern 6: Custom Agent Integration

模式6:自定义代理集成

Use case: Building domain-specific AI applications (Zillow-style, TripAdvisor-style)
typescript
interface MCPTool {
  name: string;
  description: string;
  inputSchema: any;
}

class CustomMapboxAgent {
  private url = 'https://mcp.mapbox.com/mcp';
  private headers: Record<string, string>;
  private tools: Map<string, MCPTool> = new Map();

  constructor(token?: string) {
    const mapboxToken = token || process.env.MAPBOX_ACCESS_TOKEN;
    this.headers = {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${mapboxToken}`
    };
  }

  async initialize() {
    // Discover available tools from MCP server
    await this.discoverTools();
  }

  private async discoverTools() {
    const request = {
      jsonrpc: '2.0',
      id: 1,
      method: 'tools/list'
    };

    const response = await this.sendMCPRequest(request);
    response.result.tools.forEach((tool: MCPTool) => {
      this.tools.set(tool.name, tool);
    });
  }

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

    const response = await this.sendMCPRequest(request);
    return response.result.content[0].text;
  }

  private async sendMCPRequest(request: any): Promise<any> {
    const response = await fetch(this.url, {
      method: 'POST',
      headers: this.headers,
      body: JSON.stringify(request)
    });

    const data = await response.json();

    if (data.error) {
      throw new Error(data.error.message);
    }

    return data;
  }

  // Domain-specific methods
  async findPropertiesWithCommute(
    homeLocation: [number, number],
    workLocation: [number, number],
    maxCommuteMinutes: number
  ) {
    // Get isochrone from work location
    const isochrone = await this.callTool('isochrone_tool', {
      coordinates: { longitude: workLocation[0], latitude: workLocation[1] },
      contours_minutes: [maxCommuteMinutes],
      profile: 'mapbox/driving-traffic'
    });

    // Check if home is within isochrone
    const isInRange = await this.callTool('point_in_polygon_tool', {
      point: { longitude: homeLocation[0], latitude: homeLocation[1] },
      polygon: JSON.parse(isochrone).features[0].geometry
    });

    return JSON.parse(isInRange);
  }

  async findRestaurantsNearby(location: [number, number], radiusMiles: number) {
    // Search restaurants
    const results = await this.callTool('category_search_tool', {
      category: 'restaurant',
      proximity: { longitude: location[0], latitude: location[1] }
    });

    // Filter by distance
    const restaurants = JSON.parse(results);
    const filtered = [];

    for (const restaurant of restaurants) {
      const distance = await this.callTool('distance_tool', {
        from: { longitude: location[0], latitude: location[1] },
        to: { longitude: restaurant.coordinates[0], latitude: restaurant.coordinates[1] },
        units: 'miles'
      });

      if (parseFloat(distance) <= radiusMiles) {
        filtered.push({
          ...restaurant,
          distance: parseFloat(distance)
        });
      }
    }

    return filtered.sort((a, b) => a.distance - b.distance);
  }
}

// Usage in Zillow-style app
const agent = new CustomMapboxAgent();
await agent.initialize();

const properties = await agent.findPropertiesWithCommute(
  [-122.4194, 37.7749], // Home in SF
  [-122.4, 37.79], // Work downtown
  30 // Max 30min commute
);

// Usage in TripAdvisor-style app
const restaurants = await agent.findRestaurantsNearby(
  [-73.9857, 40.7484], // Times Square
  0.5 // Within 0.5 miles
);
Benefits:
  • Full control over agent behavior
  • Domain-specific abstractions
  • Custom error handling
适用场景: 构建特定领域的AI应用(如Zillow、TripAdvisor风格)
typescript
interface MCPTool {
  name: string;
  description: string;
  inputSchema: any;
}

class CustomMapboxAgent {
  private url = 'https://mcp.mapbox.com/mcp';
  private headers: Record<string, string>;
  private tools: Map<string, MCPTool> = new Map();

  constructor(token?: string) {
    const mapboxToken = token || process.env.MAPBOX_ACCESS_TOKEN;
    this.headers = {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${mapboxToken}`
    };
  }

  async initialize() {
    // Discover available tools from MCP server
    await this.discoverTools();
  }

  private async discoverTools() {
    const request = {
      jsonrpc: '2.0',
      id: 1,
      method: 'tools/list'
    };

    const response = await this.sendMCPRequest(request);
    response.result.tools.forEach((tool: MCPTool) => {
      this.tools.set(tool.name, tool);
    });
  }

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

    const response = await this.sendMCPRequest(request);
    return response.result.content[0].text;
  }

  private async sendMCPRequest(request: any): Promise<any> {
    const response = await fetch(this.url, {
      method: 'POST',
      headers: this.headers,
      body: JSON.stringify(request)
    });

    const data = await response.json();

    if (data.error) {
      throw new Error(data.error.message);
    }

    return data;
  }

  // Domain-specific methods
  async findPropertiesWithCommute(
    homeLocation: [number, number],
    workLocation: [number, number],
    maxCommuteMinutes: number
  ) {
    // Get isochrone from work location
    const isochrone = await this.callTool('isochrone_tool', {
      coordinates: { longitude: workLocation[0], latitude: workLocation[1] },
      contours_minutes: [maxCommuteMinutes],
      profile: 'mapbox/driving-traffic'
    });

    // Check if home is within isochrone
    const isInRange = await this.callTool('point_in_polygon_tool', {
      point: { longitude: homeLocation[0], latitude: homeLocation[1] },
      polygon: JSON.parse(isochrone).features[0].geometry
    });

    return JSON.parse(isInRange);
  }

  async findRestaurantsNearby(location: [number, number], radiusMiles: number) {
    // Search restaurants
    const results = await this.callTool('category_search_tool', {
      category: 'restaurant',
      proximity: { longitude: location[0], latitude: location[1] }
    });

    // Filter by distance
    const restaurants = JSON.parse(results);
    const filtered = [];

    for (const restaurant of restaurants) {
      const distance = await this.callTool('distance_tool', {
        from: { longitude: location[0], latitude: location[1] },
        to: { longitude: restaurant.coordinates[0], latitude: restaurant.coordinates[1] },
        units: 'miles'
      });

      if (parseFloat(distance) <= radiusMiles) {
        filtered.push({
          ...restaurant,
          distance: parseFloat(distance)
        });
      }
    }

    return filtered.sort((a, b) => a.distance - b.distance);
  }
}

// Usage in Zillow-style app
const agent = new CustomMapboxAgent();
await agent.initialize();

const properties = await agent.findPropertiesWithCommute(
  [-122.4194, 37.7749], // Home in SF
  [-122.4, 37.79], // Work downtown
  30 // Max 30min commute
);

// Usage in TripAdvisor-style app
const restaurants = await agent.findRestaurantsNearby(
  [-73.9857, 40.7484], // Times Square
  0.5 // Within 0.5 miles
);
优势:
  • 完全控制代理行为
  • 特定领域的抽象
  • 自定义错误处理

Architecture Patterns

架构模式

Pattern: MCP as Service Layer

模式:MCP作为服务层

┌─────────────────────────────────────┐
│         Your Application            │
│  (Next.js, Express, FastAPI, etc.)  │
└────────────────┬────────────────────┘
┌─────────────────────────────────────┐
│        AI Agent Layer               │
│   (pydantic-ai, mastra, custom)     │
└────────────────┬────────────────────┘
┌─────────────────────────────────────┐
│     Mapbox MCP Server               │
│  (Geospatial tools abstraction)     │
└────────────────┬────────────────────┘
          ┌──────┴──────┐
          ▼             ▼
    ┌─────────┐   ┌──────────┐
    │ Turf.js │   │ Mapbox   │
    │ (Local) │   │   APIs   │
    └─────────┘   └──────────┘
Benefits:
  • Clean separation of concerns
  • Easy to swap MCP server versions
  • Centralized geospatial logic
┌─────────────────────────────────────┐
│         Your Application            │
│  (Next.js, Express, FastAPI, etc.)  │
└────────────────┬────────────────────┘
┌─────────────────────────────────────┐
│        AI Agent Layer               │
│   (pydantic-ai, mastra, custom)     │
└────────────────┬────────────────────┘
┌─────────────────────────────────────┐
│     Mapbox MCP Server               │
│  (Geospatial tools abstraction)     │
└────────────────┬────────────────────┘
          ┌──────┴──────┐
          ▼             ▼
    ┌─────────┐   ┌──────────┐
    │ Turf.js │   │ Mapbox   │
    │ (Local) │   │   APIs   │
    └─────────┘   └──────────┘
优势:
  • 清晰的关注点分离
  • 轻松切换MCP服务器版本
  • 集中化的地理空间逻辑

Pattern: Hybrid Approach

模式:混合方式

You can use MCP for AI agent features while using direct Mapbox APIs for other parts of your app.
typescript
class GeospatialService {
  constructor(
    private mcpServer: MapboxMCPServer, // For AI features
    private mapboxSdk: MapboxSDK // For direct app features
  ) {}

  // AI Agent Feature: Natural language search
  async aiSearchNearby(userQuery: string): Promise<string> {
    // Let AI agent use MCP tools to interpret query and find places
    // Returns natural language response
    return await this.agent.execute(userQuery, [
      this.mcpServer.tools.category_search_tool,
      this.mcpServer.tools.directions_tool
    ]);
  }

  // Direct App Feature: Display route on map
  async getRouteGeometry(origin: Point, dest: Point): Promise<LineString> {
    // Direct API call for map rendering - returns GeoJSON
    const result = await this.mapboxSdk.directions.getDirections({
      waypoints: [origin, dest],
      geometries: 'geojson'
    });
    return result.routes[0].geometry;
  }

  // Offline Feature: Distance calculations (always use MCP/Turf.js)
  async calculateDistance(from: Point, to: Point): Promise<number> {
    // No API cost, instant
    return await this.mcpServer.callTool('distance_tool', {
      from,
      to,
      units: 'miles'
    });
  }
}
Architecture Decision Guide:
Use CaseUse ThisWhy
AI agent natural language featuresMCP ServerSimplified tool interface, AI-friendly responses
Map rendering, direct UI controlsMapbox SDKMore control, better performance
Distance/area calculationsMCP Server (offline tools)Free, instant, no API calls
Custom map stylingMapbox SDKFine-grained style control
Conversational geospatial queriesMCP ServerAI agent can chain tools
你可以将MCP用于AI代理功能,同时在应用的其他部分直接使用Mapbox API。
typescript
class GeospatialService {
  constructor(
    private mcpServer: MapboxMCPServer, // For AI features
    private mapboxSdk: MapboxSDK // For direct app features
  ) {}

  // AI Agent Feature: Natural language search
  async aiSearchNearby(userQuery: string): Promise<string> {
    // Let AI agent use MCP tools to interpret query and find places
    // Returns natural language response
    return await this.agent.execute(userQuery, [
      this.mcpServer.tools.category_search_tool,
      this.mcpServer.tools.directions_tool
    ]);
  }

  // Direct App Feature: Display route on map
  async getRouteGeometry(origin: Point, dest: Point): Promise<LineString> {
    // Direct API call for map rendering - returns GeoJSON
    const result = await this.mapboxSdk.directions.getDirections({
      waypoints: [origin, dest],
      geometries: 'geojson'
    });
    return result.routes[0].geometry;
  }

  // Offline Feature: Distance calculations (always use MCP/Turf.js)
  async calculateDistance(from: Point, to: Point): Promise<number> {
    // No API cost, instant
    return await this.mcpServer.callTool('distance_tool', {
      from,
      to,
      units: 'miles'
    });
  }
}
架构决策指南:
适用场景推荐使用方式原因
AI代理自然语言功能MCP Server简化的工具界面,AI友好的响应格式
地图渲染、直接UI控制Mapbox SDK更高的控制度,更好的性能
距离/面积计算MCP Server(离线工具)免费、即时、无需API调用
自定义地图样式Mapbox SDK精细化的样式控制
对话式地理空间查询MCP ServerAI代理可实现工具链调用

Use Cases by Application Type

按应用类型划分的适用场景

Real Estate App (Zillow-style)

房地产应用(Zillow风格)

typescript
// Find properties with good commute
async findPropertiesByCommute(
  searchArea: Polygon,
  workLocation: Point,
  maxCommuteMinutes: number
) {
  // 1. Get isochrone from work
  const reachableArea = await mcp.callTool('isochrone_tool', {
    coordinates: { longitude: workLocation[0], latitude: workLocation[1] },
    contours_minutes: [maxCommuteMinutes],
    profile: 'mapbox/driving'
  });

  // 2. Check each property
  const propertiesInRange = [];
  for (const property of properties) {
    const inRange = await mcp.callTool('point_in_polygon_tool', {
      point: { longitude: property.location[0], latitude: property.location[1] },
      polygon: reachableArea
    });

    if (inRange) {
      // 3. Get exact commute time
      const directions = await mcp.callTool('directions_tool', {
        coordinates: [property.location, workLocation],
        routing_profile: 'mapbox/driving-traffic'
      });

      propertiesInRange.push({
        ...property,
        commuteTime: directions.duration / 60
      });
    }
  }

  return propertiesInRange;
}
typescript
// Find properties with good commute
async findPropertiesByCommute(
  searchArea: Polygon,
  workLocation: Point,
  maxCommuteMinutes: number
) {
  // 1. Get isochrone from work
  const reachableArea = await mcp.callTool('isochrone_tool', {
    coordinates: { longitude: workLocation[0], latitude: workLocation[1] },
    contours_minutes: [maxCommuteMinutes],
    profile: 'mapbox/driving'
  });

  // 2. Check each property
  const propertiesInRange = [];
  for (const property of properties) {
    const inRange = await mcp.callTool('point_in_polygon_tool', {
      point: { longitude: property.location[0], latitude: property.location[1] },
      polygon: reachableArea
    });

    if (inRange) {
      // 3. Get exact commute time
      const directions = await mcp.callTool('directions_tool', {
        coordinates: [property.location, workLocation],
        routing_profile: 'mapbox/driving-traffic'
      });

      propertiesInRange.push({
        ...property,
        commuteTime: directions.duration / 60
      });
    }
  }

  return propertiesInRange;
}

Food Delivery App (DoorDash-style)

外卖配送应用(DoorDash风格)

typescript
// Check if restaurant can deliver to address
async canDeliver(
  restaurantLocation: Point,
  deliveryAddress: Point,
  maxDeliveryTime: number
) {
  // 1. Calculate delivery zone
  const deliveryZone = await mcp.callTool('isochrone_tool', {
    coordinates: restaurantLocation,
    contours_minutes: [maxDeliveryTime],
    profile: 'mapbox/driving'
  });

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

  if (!canDeliver) return false;

  // 3. Get accurate delivery time
  const route = await mcp.callTool('directions_tool', {
    coordinates: [restaurantLocation, deliveryAddress],
    routing_profile: 'mapbox/driving-traffic'
  });

  return {
    canDeliver: true,
    estimatedTime: route.duration / 60,
    distance: route.distance
  };
}
typescript
// Check if restaurant can deliver to address
async canDeliver(
  restaurantLocation: Point,
  deliveryAddress: Point,
  maxDeliveryTime: number
) {
  // 1. Calculate delivery zone
  const deliveryZone = await mcp.callTool('isochrone_tool', {
    coordinates: restaurantLocation,
    contours_minutes: [maxDeliveryTime],
    profile: 'mapbox/driving'
  });

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

  if (!canDeliver) return false;

  // 3. Get accurate delivery time
  const route = await mcp.callTool('directions_tool', {
    coordinates: [restaurantLocation, deliveryAddress],
    routing_profile: 'mapbox/driving-traffic'
  });

  return {
    canDeliver: true,
    estimatedTime: route.duration / 60,
    distance: route.distance
  };
}

Travel Planning App (TripAdvisor-style)

旅行规划应用(TripAdvisor风格)

typescript
// Build day itinerary with travel times
async buildItinerary(
  hotel: Point,
  attractions: Array<{name: string, location: Point}>
) {
  // 1. Calculate distances from hotel
  const attractionsWithDistance = await Promise.all(
    attractions.map(async (attr) => ({
      ...attr,
      distance: await mcp.callTool('distance_tool', {
        from: hotel,
        to: attr.location,
        units: 'miles'
      })
    }))
  );

  // 2. Get travel time matrix
  const matrix = await mcp.callTool('matrix_tool', {
    origins: [hotel],
    destinations: attractions.map(a => a.location),
    profile: 'mapbox/walking'
  });

  // 3. Sort by walking time
  return attractionsWithDistance
    .map((attr, idx) => ({
      ...attr,
      walkingTime: matrix.durations[0][idx] / 60
    }))
    .sort((a, b) => a.walkingTime - b.walkingTime);
}
typescript
// Build day itinerary with travel times
async buildItinerary(
  hotel: Point,
  attractions: Array<{name: string, location: Point}>
) {
  // 1. Calculate distances from hotel
  const attractionsWithDistance = await Promise.all(
    attractions.map(async (attr) => ({
      ...attr,
      distance: await mcp.callTool('distance_tool', {
        from: hotel,
        to: attr.location,
        units: 'miles'
      })
    }))
  );

  // 2. Get travel time matrix
  const matrix = await mcp.callTool('matrix_tool', {
    origins: [hotel],
    destinations: attractions.map(a => a.location),
    profile: 'mapbox/walking'
  });

  // 3. Sort by walking time
  return attractionsWithDistance
    .map((attr, idx) => ({
      ...attr,
      walkingTime: matrix.durations[0][idx] / 60
    }))
    .sort((a, b) => a.walkingTime - b.walkingTime);
}

Performance Optimization

性能优化

Caching Strategy

缓存策略

typescript
class CachedMapboxMCP {
  private cache = new Map<string, { result: any; timestamp: number }>();
  private cacheTTL = 3600000; // 1 hour

  async callTool(name: string, params: any): Promise<any> {
    // Cache offline tools indefinitely (deterministic)
    const offlineTools = ['distance_tool', 'point_in_polygon_tool', 'bearing_tool'];
    const ttl = offlineTools.includes(name) ? Infinity : this.cacheTTL;

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

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

    // Call MCP
    const result = await this.mcpServer.callTool(name, params);

    // Store in cache
    this.cache.set(cacheKey, {
      result,
      timestamp: Date.now()
    });

    return result;
  }
}
typescript
class CachedMapboxMCP {
  private cache = new Map<string, { result: any; timestamp: number }>();
  private cacheTTL = 3600000; // 1 hour

  async callTool(name: string, params: any): Promise<any> {
    // Cache offline tools indefinitely (deterministic)
    const offlineTools = ['distance_tool', 'point_in_polygon_tool', 'bearing_tool'];
    const ttl = offlineTools.includes(name) ? Infinity : this.cacheTTL;

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

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

    // Call MCP
    const result = await this.mcpServer.callTool(name, params);

    // Store in cache
    this.cache.set(cacheKey, {
      result,
      timestamp: Date.now()
    });

    return result;
  }
}

Batch Operations

批量操作

typescript
// ❌ Bad: Sequential calls
for (const location of locations) {
  const distance = await mcp.callTool('distance_tool', {
    from: userLocation,
    to: location
  });
}

// ✅ Good: Parallel batch
const distances = await Promise.all(
  locations.map((location) =>
    mcp.callTool('distance_tool', {
      from: userLocation,
      to: location
    })
  )
);

// ✅ Better: Use matrix tool
const matrix = await mcp.callTool('matrix_tool', {
  origins: [userLocation],
  destinations: locations
});
typescript
// ❌ Bad: Sequential calls
for (const location of locations) {
  const distance = await mcp.callTool('distance_tool', {
    from: userLocation,
    to: location
  });
}

// ✅ Good: Parallel batch
const distances = await Promise.all(
  locations.map((location) =>
    mcp.callTool('distance_tool', {
      from: userLocation,
      to: location
    })
  )
);

// ✅ Better: Use matrix tool
const matrix = await mcp.callTool('matrix_tool', {
  origins: [userLocation],
  destinations: locations
});

Writing Effective Tool Descriptions

编写有效的工具描述

Clear, specific tool descriptions are critical for helping LLMs select the right tools. Poor descriptions lead to incorrect tool calls, wasted API requests, and user frustration.
清晰、具体的工具描述对于帮助LLM正确选择工具至关重要。模糊的描述会导致错误的工具调用、浪费API请求以及用户不满。

Common Confusion Points

常见混淆点

Problem: "How far is it from A to B?" - Could trigger either
directions_tool
OR
distance_tool
typescript
// ❌ Ambiguous descriptions
{
  name: 'directions_tool',
  description: 'Get directions between two locations'  // Could mean distance
}
{
  name: 'distance_tool',
  description: 'Calculate distance between two points'  // Unclear what kind
}

// ✅ Clear, specific descriptions
{
  name: 'directions_tool',
  description: 'Get turn-by-turn driving directions with traffic-aware route distance and travel time. Use when you need the actual route, navigation instructions, or driving duration. Returns route geometry, distance along roads, and time estimate.'
}
{
  name: 'distance_tool',
  description: 'Calculate straight-line (great-circle) distance between two points. Use for quick "as the crow flies" distance checks, proximity comparisons, or when routing is not needed. Works offline, instant, no API cost.'
}
Problem: "Find coffee shops nearby" - Could trigger
category_search_tool
OR
search_and_geocode_tool
typescript
// ❌ Ambiguous
{
  name: 'search_poi',
  description: 'Search for places'
}

// ✅ Clear when to use each
{
  name: 'category_search_tool',
  description: 'Find ALL places of a specific type/category (e.g., "all coffee shops", "restaurants", "gas stations") near a location. Use for browsing or discovering places by category. Returns multiple results.'
}
{
  name: 'search_and_geocode_tool',
  description: 'Search for a SPECIFIC named place or address (e.g., "Starbucks on Main St", "123 Market St"). Use when the user provides a business name, street address, or landmark. Returns best match.'
}
Problem: "Where can I go in 15 minutes?" - Could trigger
isochrone_tool
OR
directions_tool
typescript
// ❌ Confusing
{
  name: 'isochrone_tool',
  description: 'Calculate travel time area'
}

// ✅ Clear distinction
{
  name: 'isochrone_tool',
  description: 'Calculate the AREA reachable within a time limit from a starting point. Returns a GeoJSON polygon showing everywhere you can reach. Use for: "What can I reach in X minutes?", service area analysis, catchment zones, delivery zones.'
}
{
  name: 'directions_tool',
  description: 'Get route from point A to specific point B. Returns turn-by-turn directions to ONE destination. Use for: "How do I get to X?", "Route from A to B", navigation to a known destination.'
}
问题:"从A到B有多远?" - 可能触发
directions_tool
distance_tool
typescript
// ❌ Ambiguous descriptions
{
  name: 'directions_tool',
  description: 'Get directions between two locations'  // Could mean distance
}
{
  name: 'distance_tool',
  description: 'Calculate distance between two points'  // Unclear what kind
}

// ✅ Clear, specific descriptions
{
  name: 'directions_tool',
  description: 'Get turn-by-turn driving directions with traffic-aware route distance and travel time. Use when you need the actual route, navigation instructions, or driving duration. Returns route geometry, distance along roads, and time estimate.'
}
{
  name: 'distance_tool',
  description: 'Calculate straight-line (great-circle) distance between two points. Use for quick "as the crow flies" distance checks, proximity comparisons, or when routing is not needed. Works offline, instant, no API cost.'
}
问题:"找附近的咖啡店" - 可能触发
category_search_tool
search_and_geocode_tool
typescript
// ❌ Ambiguous
{
  name: 'search_poi',
  description: 'Search for places'
}

// ✅ Clear when to use each
{
  name: 'category_search_tool',
  description: 'Find ALL places of a specific type/category (e.g., "all coffee shops", "restaurants", "gas stations") near a location. Use for browsing or discovering places by category. Returns multiple results.'
}
{
  name: 'search_and_geocode_tool',
  description: 'Search for a SPECIFIC named place or address (e.g., "Starbucks on Main St", "123 Market St"). Use when the user provides a business name, street address, or landmark. Returns best match.'
}
问题:"15分钟内我可以到达哪些地方?" - 可能触发
isochrone_tool
directions_tool
typescript
// ❌ Confusing
{
  name: 'isochrone_tool',
  description: 'Calculate travel time area'
}

// ✅ Clear distinction
{
  name: 'isochrone_tool',
  description: 'Calculate the AREA reachable within a time limit from a starting point. Returns a GeoJSON polygon showing everywhere you can reach. Use for: "What can I reach in X minutes?", service area analysis, catchment zones, delivery zones.'
}
{
  name: 'directions_tool',
  description: 'Get route from point A to specific point B. Returns turn-by-turn directions to ONE destination. Use for: "How do I get to X?", "Route from A to B", navigation to a known destination.'
}

Best Practices for Tool Descriptions

工具描述最佳实践

  1. Start with the primary use case in simple terms
  2. Explain WHEN to use this tool vs alternatives
  3. Include key distinguishing details: Does it use traffic? Is it offline? Does it cost API calls?
  4. Give concrete examples of questions that should trigger this tool
  5. Mention what it returns so LLMs know if it fits the user's need
typescript
// ✅ Complete example
const searchPOITool = new DynamicStructuredTool({
  name: 'category_search_tool',
  description: `Find places by category type (restaurants, hotels, coffee shops, gas stations, etc.) near a location.

  Use when the user wants to:
  - Browse places of a certain type: "coffee shops nearby", "find restaurants"
  - Discover options: "what hotels are in this area?"
  - Search by industry/amenity, not by specific name

  Returns: List of matching places with names, addresses, and coordinates.

  DO NOT use for:
  - Specific named places (use search_and_geocode_tool instead)
  - Addresses (use search_and_geocode_tool or reverse_geocode_tool)`
  // ... schema and implementation
});
  1. 以简单明了的主要适用场景开头
  2. 说明何时使用此工具,而非其他工具
  3. 包含关键区分细节:是否使用交通数据?是否离线?是否消耗API调用?
  4. 给出具体示例:哪些问题应该触发此工具
  5. 提及返回内容,让LLM了解它是否符合用户需求
typescript
// ✅ Complete example
const searchPOITool = new DynamicStructuredTool({
  name: 'category_search_tool',
  description: `Find places by category type (restaurants, hotels, coffee shops, gas stations, etc.) near a location.

  Use when the user wants to:
  - Browse places of a certain type: "coffee shops nearby", "find restaurants"
  - Discover options: "what hotels are in this area?"
  - Search by industry/amenity, not by specific name

  Returns: List of matching places with names, addresses, and coordinates.

  DO NOT use for:
  - Specific named places (use search_and_geocode_tool instead)
  - Addresses (use search_and_geocode_tool or reverse_geocode_tool)`
  // ... schema and implementation
});

System Prompt Guidance

系统提示指引

Add tool selection guidance to your agent's system prompt:
typescript
const systemPrompt = `You are a location intelligence assistant.

TOOL SELECTION RULES:
- Use distance_tool for straight-line distance ("as the crow flies")
- Use directions_tool for route distance along roads with traffic
- Use category_search_tool for finding types of places ("coffee shops")
- Use search_and_geocode_tool for specific addresses or named places ("123 Main St", "Starbucks downtown")
- Use isochrone_tool for "what can I reach in X minutes" questions
- Use offline tools (distance_tool, point_in_polygon_tool) when real-time data is not needed

When in doubt, prefer:
1. Offline tools over API calls (faster, free)
2. Specific tools over general ones
3. Asking for clarification over guessing`;
在代理的系统提示中添加工具选择指引:
typescript
const systemPrompt = `You are a location intelligence assistant.

TOOL SELECTION RULES:
- Use distance_tool for straight-line distance ("as the crow flies")
- Use directions_tool for route distance along roads with traffic
- Use category_search_tool for finding types of places ("coffee shops")
- Use search_and_geocode_tool for specific addresses or named places ("123 Main St", "Starbucks downtown")
- Use isochrone_tool for "what can I reach in X minutes" questions
- Use offline tools (distance_tool, point_in_polygon_tool) when real-time data is not needed

When in doubt, prefer:
1. Offline tools over API calls (faster, free)
2. Specific tools over general ones
3. Asking for clarification over guessing`;

Tool Selection

工具选择

typescript
// Use offline tools when possible (faster, free)
const localOps = {
  distance: 'distance_tool', // Turf.js
  pointInPolygon: 'point_in_polygon_tool', // Turf.js
  bearing: 'bearing_tool', // Turf.js
  area: 'area_tool' // Turf.js
};

// Use API tools when necessary (requires token, slower)
const apiOps = {
  directions: 'directions_tool', // Mapbox API
  geocoding: 'reverse_geocode_tool', // Mapbox API
  isochrone: 'isochrone_tool', // Mapbox API
  search: 'category_search_tool' // Mapbox API
};

// Choose based on requirements
function chooseTool(operation: string, needsRealtime: boolean) {
  if (needsRealtime) {
    return apiOps[operation]; // Traffic, live data
  }
  return localOps[operation] || apiOps[operation];
}
typescript
// Use offline tools when possible (faster, free)
const localOps = {
  distance: 'distance_tool', // Turf.js
  pointInPolygon: 'point_in_polygon_tool', // Turf.js
  bearing: 'bearing_tool', // Turf.js
  area: 'area_tool' // Turf.js
};

// Use API tools when necessary (requires token, slower)
const apiOps = {
  directions: 'directions_tool', // Mapbox API
  geocoding: 'reverse_geocode_tool', // Mapbox API
  isochrone: 'isochrone_tool', // Mapbox API
  search: 'category_search_tool' // Mapbox API
};

// Choose based on requirements
function chooseTool(operation: string, needsRealtime: boolean) {
  if (needsRealtime) {
    return apiOps[operation]; // Traffic, live data
  }
  return localOps[operation] || apiOps[operation];
}

Error Handling

错误处理

typescript
class RobustMapboxMCP {
  async callToolWithRetry(name: string, params: any, maxRetries: number = 3): Promise<any> {
    for (let i = 0; i < maxRetries; i++) {
      try {
        return await this.mcpServer.callTool(name, params);
      } catch (error) {
        if (error.code === 'RATE_LIMIT') {
          // Exponential backoff
          await this.sleep(Math.pow(2, i) * 1000);
          continue;
        }

        if (error.code === 'INVALID_TOKEN') {
          // Non-retryable error
          throw error;
        }

        if (i === maxRetries - 1) {
          throw error;
        }
      }
    }
  }

  async callToolWithFallback(primaryTool: string, fallbackTool: string, params: any): Promise<any> {
    try {
      return await this.callTool(primaryTool, params);
    } catch (error) {
      console.warn(`Primary tool ${primaryTool} failed, using fallback`);
      return await this.callTool(fallbackTool, params);
    }
  }
}
typescript
class RobustMapboxMCP {
  async callToolWithRetry(name: string, params: any, maxRetries: number = 3): Promise<any> {
    for (let i = 0; i < maxRetries; i++) {
      try {
        return await this.mcpServer.callTool(name, params);
      } catch (error) {
        if (error.code === 'RATE_LIMIT') {
          // Exponential backoff
          await this.sleep(Math.pow(2, i) * 1000);
          continue;
        }

        if (error.code === 'INVALID_TOKEN') {
          // Non-retryable error
          throw error;
        }

        if (i === maxRetries - 1) {
          throw error;
        }
      }
    }
  }

  async callToolWithFallback(primaryTool: string, fallbackTool: string, params: any): Promise<any> {
    try {
      return await this.callTool(primaryTool, params);
    } catch (error) {
      console.warn(`Primary tool ${primaryTool} failed, using fallback`);
      return await this.callTool(fallbackTool, params);
    }
  }
}

Security Best Practices

安全最佳实践

Token Management

令牌管理

typescript
// ✅ Good: Use environment variables
const mcp = new MapboxMCP({
  token: process.env.MAPBOX_ACCESS_TOKEN
});

// ❌ Bad: Hardcode tokens
const mcp = new MapboxMCP({
  token: 'pk.ey...' // Never do this!
});

// ✅ Good: Use scoped tokens
// Create token with minimal scopes:
// - directions:read
// - geocoding:read
// - No write permissions
typescript
// ✅ Good: Use environment variables
const mcp = new MapboxMCP({
  token: process.env.MAPBOX_ACCESS_TOKEN
});

// ❌ Bad: Hardcode tokens
const mcp = new MapboxMCP({
  token: 'pk.ey...' // Never do this!
});

// ✅ Good: Use scoped tokens
// Create token with minimal scopes:
// - directions:read
// - geocoding:read
// - No write permissions

Rate Limiting

速率限制

typescript
class RateLimitedMCP {
  private requestQueue: Array<() => Promise<any>> = [];
  private requestsPerMinute = 300;
  private currentMinute = Math.floor(Date.now() / 60000);
  private requestCount = 0;

  async callTool(name: string, params: any): Promise<any> {
    // Check rate limit
    const minute = Math.floor(Date.now() / 60000);
    if (minute !== this.currentMinute) {
      this.currentMinute = minute;
      this.requestCount = 0;
    }

    if (this.requestCount >= this.requestsPerMinute) {
      // Wait until next minute
      const waitMs = (this.currentMinute + 1) * 60000 - Date.now();
      await this.sleep(waitMs);
    }

    this.requestCount++;
    return await this.mcpServer.callTool(name, params);
  }
}
typescript
class RateLimitedMCP {
  private requestQueue: Array<() => Promise<any>> = [];
  private requestsPerMinute = 300;
  private currentMinute = Math.floor(Date.now() / 60000);
  private requestCount = 0;

  async callTool(name: string, params: any): Promise<any> {
    // Check rate limit
    const minute = Math.floor(Date.now() / 60000);
    if (minute !== this.currentMinute) {
      this.currentMinute = minute;
      this.requestCount = 0;
    }

    if (this.requestCount >= this.requestsPerMinute) {
      // Wait until next minute
      const waitMs = (this.currentMinute + 1) * 60000 - Date.now();
      await this.sleep(waitMs);
    }

    this.requestCount++;
    return await this.mcpServer.callTool(name, params);
  }
}

Testing

测试

typescript
// Mock MCP server for testing
class MockMapboxMCP {
  async callTool(name: string, params: any): Promise<any> {
    const mocks = {
      distance_tool: () => '2.5',
      directions_tool: () => JSON.stringify({
        duration: 1200,
        distance: 5000,
        geometry: {...}
      }),
      point_in_polygon_tool: () => 'true'
    };

    return mocks[name]?.() || '{}';
  }
}

// Use in tests
describe('Property search', () => {
  it('finds properties within commute time', async () => {
    const agent = new CustomMapboxAgent(new MockMapboxMCP());
    const results = await agent.findPropertiesWithCommute(
      [-122.4, 37.7],
      [-122.41, 37.78],
      30
    );

    expect(results).toHaveLength(5);
  });
});
typescript
// Mock MCP server for testing
class MockMapboxMCP {
  async callTool(name: string, params: any): Promise<any> {
    const mocks = {
      distance_tool: () => '2.5',
      directions_tool: () => JSON.stringify({
        duration: 1200,
        distance: 5000,
        geometry: {...}
      }),
      point_in_polygon_tool: () => 'true'
    };

    return mocks[name]?.() || '{}';
  }
}

// Use in tests
describe('Property search', () => {
  it('finds properties within commute time', async () => {
    const agent = new CustomMapboxAgent(new MockMapboxMCP());
    const results = await agent.findPropertiesWithCommute(
      [-122.4, 37.7],
      [-122.41, 37.78],
      30
    );

    expect(results).toHaveLength(5);
  });
});

Resources

资源

When to Use This Skill

何时使用本技能

Invoke this skill when:
  • Integrating Mapbox MCP Server into AI applications
  • Building AI agents with geospatial capabilities
  • Architecting Zillow/TripAdvisor/DoorDash-style apps with AI
  • Choosing between MCP, direct APIs, or SDKs
  • Optimizing geospatial operations in production
  • Implementing error handling for geospatial AI features
  • Testing AI applications with geospatial tools
在以下场景中调用本技能:
  • 将Mapbox MCP Server集成到AI应用中
  • 构建具备地理空间能力的AI代理
  • 构建Zillow/TripAdvisor/DoorDash风格的AI应用
  • 在MCP、直接API或SDK之间进行选择
  • 优化生产环境中的地理空间操作
  • 为地理空间AI功能实现错误处理
  • 测试具备地理空间工具的AI应用