Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,12 @@ export const CategorySearchInputSchema = z.object({
.default('formatted_text')
.describe(
'Output format: "json_string" returns raw GeoJSON data as a JSON string that can be parsed; "formatted_text" returns human-readable text with place names, addresses, and coordinates. Both return as text content but json_string contains parseable JSON data while formatted_text is for display.'
),
compact: z
.boolean()
.optional()
.default(true)
.describe(
'When true (default), returns simplified GeoJSON with only essential fields (name, address, coordinates, categories, brand). When false, returns full verbose Mapbox API response with all metadata. Only applies to structured content output.'
)
});
69 changes: 66 additions & 3 deletions src/tools/category-search-tool/CategorySearchTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,62 @@ export class CategorySearchTool extends MapboxApiBasedTool<
return results.join('\n\n');
}

/**
* Simplify GeoJSON response to only essential fields
* Removes verbose nested context, attribution, and metadata while keeping valid GeoJSON structure
*/
private compactGeoJsonResponse(
geoJsonResponse: MapboxFeatureCollection
): Record<string, unknown> {
return {
type: 'FeatureCollection',
features: geoJsonResponse.features.map((feature) => {
const props = feature.properties || {};
const context = (props.context || {}) as Record<string, any>;
const geom = feature.geometry;

// Extract coordinates safely
let coordinates: [number, number] | undefined;
if (geom && geom.type === 'Point' && 'coordinates' in geom) {
coordinates = geom.coordinates as [number, number];
}

return {
type: 'Feature',
geometry: geom
? {
type: geom.type,
coordinates:
'coordinates' in geom ? geom.coordinates : undefined
}
: null,
properties: {
name: props.name,
full_address: props.full_address,
place_formatted: props.place_formatted,
feature_type: props.feature_type,
coordinates: coordinates
? {
longitude: coordinates[0],
latitude: coordinates[1]
}
: undefined,
poi_category: props.poi_category,
brand: props.brand,
maki: props.maki,
address: context.address?.name,
street: context.street?.name,
postcode: context.postcode?.name,
place: context.place?.name,
district: context.district?.name,
region: context.region?.name,
country: context.country?.name
}
};
})
};
}

protected async execute(
input: z.infer<typeof CategorySearchInputSchema>,
accessToken: string
Expand Down Expand Up @@ -193,16 +249,23 @@ export class CategorySearchTool extends MapboxApiBasedTool<
data = rawData as MapboxFeatureCollection;
}

// Determine which structured content to return
const structuredContent = input.compact
? this.compactGeoJsonResponse(data)
: (data as unknown as Record<string, unknown>);

if (input.format === 'json_string') {
return {
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
structuredContent: data as unknown as Record<string, unknown>,
content: [
{ type: 'text', text: JSON.stringify(structuredContent, null, 2) }
],
structuredContent,
isError: false
};
} else {
return {
content: [{ type: 'text', text: this.formatGeoJsonToText(data) }],
structuredContent: data as unknown as Record<string, unknown>,
structuredContent,
isError: false
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,12 @@ export const ReverseGeocodeInputSchema = z.object({
.default('formatted_text')
.describe(
'Output format: "json_string" returns raw GeoJSON data as a JSON string that can be parsed; "formatted_text" returns human-readable text with place names, addresses, and coordinates. Both return as text content but json_string contains parseable JSON data while formatted_text is for display.'
),
compact: z
.boolean()
.optional()
.default(true)
.describe(
'When true (default), returns simplified GeoJSON with only essential fields (name, address, coordinates, location hierarchy). When false, returns full verbose Mapbox API response with all metadata. Only applies to structured content output.'
)
});
65 changes: 62 additions & 3 deletions src/tools/reverse-geocode-tool/ReverseGeocodeTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,58 @@ export class ReverseGeocodeTool extends MapboxApiBasedTool<
return results.join('\n\n');
}

/**
* Simplify GeoJSON response to only essential fields
* Removes verbose nested context and attribution while keeping valid GeoJSON structure
*/
private compactGeoJsonResponse(
geoJsonResponse: MapboxFeatureCollection
): Record<string, unknown> {
return {
type: 'FeatureCollection',
features: geoJsonResponse.features.map((feature) => {
const props = feature.properties || {};
const context = (props.context || {}) as Record<string, any>;
const geom = feature.geometry;

// Extract coordinates safely
let coordinates: [number, number] | undefined;
if (geom && geom.type === 'Point' && 'coordinates' in geom) {
coordinates = geom.coordinates as [number, number];
}

return {
type: 'Feature',
geometry: geom
? {
type: geom.type,
coordinates:
'coordinates' in geom ? geom.coordinates : undefined
}
: null,
properties: {
name: props.name,
full_address: props.full_address,
place_formatted: props.place_formatted,
feature_type: props.feature_type,
coordinates: coordinates
? {
longitude: coordinates[0],
latitude: coordinates[1]
}
: undefined,
address: context.address?.name,
postcode: context.postcode?.name,
place: context.place?.name,
district: context.district?.name,
region: context.region?.name,
country: context.country?.name
}
};
})
};
}

protected async execute(
input: z.infer<typeof ReverseGeocodeInputSchema>,
accessToken: string
Expand Down Expand Up @@ -193,16 +245,23 @@ export class ReverseGeocodeTool extends MapboxApiBasedTool<
};
}

// Determine which structured content to return
const structuredContent = input.compact
? this.compactGeoJsonResponse(data)
: (data as unknown as Record<string, unknown>);

if (input.format === 'json_string') {
return {
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
structuredContent: data as unknown as Record<string, unknown>,
content: [
{ type: 'text', text: JSON.stringify(structuredContent, null, 2) }
],
structuredContent,
isError: false
};
} else {
return {
content: [{ type: 'text', text: this.formatGeoJsonToText(data) }],
structuredContent: data as unknown as Record<string, unknown>,
structuredContent,
isError: false
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,12 @@ export const SearchAndGeocodeInputSchema = z.object({
longitude: z.number().min(-180).max(180),
latitude: z.number().min(-90).max(90)
})
.optional(),
compact: z
.boolean()
.optional()
.default(true)
.describe(
'When true (default), returns simplified GeoJSON with only essential fields (name, address, coordinates, categories, brand). When false, returns full verbose Mapbox API response with all metadata. Only applies to structured content output.'
)
});
Loading