Skip to content

Commit 2f63f77

Browse files
Fix schema validation: add missing country property (#81)
* [reverse-geocode] Add compact parameter to reduce response verbosity Adds optional 'compact' parameter (default: true) to reverse_geocode_tool that significantly reduces response size while maintaining valid GeoJSON structure and all useful information. Changes: - Added 'compact' boolean parameter to input schema (default: true) - Implemented compactGeoJsonResponse() that: - Removes verbose 'attribution' field - Flattens nested 'context' object to simple properties - Removes internal mapbox_ids and metadata - Keeps all useful location data (name, address, coordinates, hierarchy) - Updated execute() to use compact format for structuredContent - Both json_string and formatted_text formats use compact data Benefits: - ~90% reduction in response size (100+ lines → ~20 lines) - Valid GeoJSON maintained (type: FeatureCollection + features: []) - All useful information preserved (name, address, coordinates, hierarchy) - Easier for AI agents to parse (flatter structure) - Backward compatible (set compact: false for full response) Example compact output: { "type": "FeatureCollection", "features": [{ "type": "Feature", "geometry": { "type": "Point", "coordinates": [-76.998, 36.003] }, "properties": { "name": "620 Mardre Road", "full_address": "620 Mardre Road, Windsor, NC 27983, US", "feature_type": "address", "coordinates": { "longitude": -76.998, "latitude": 36.003 }, "address": "620 Mardre Road", "postcode": "27983", "place": "Windsor", "district": "Bertie County", "region": "North Carolina", "country": "United States" } }] } Tests: - Added test for compact format (default behavior) - Updated existing test to use compact: false - All 393 tests passing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <[email protected]> * [tools] Add compact parameter to search_and_geocode_tool Adds compact parameter (default: true) to search_and_geocode_tool to reduce response size by ~90% while maintaining valid GeoJSON structure. - Removes attribution, external_ids, mapbox_id, metadata - Flattens nested context object into direct properties - Keeps essential fields: name, address, coordinates, poi_category, brand, maki - Maintains valid GeoJSON structure for use in geojson.io and other tools - Backward compatible with compact: false for full verbose response 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <[email protected]> * [tools] Add compact parameter to category_search_tool Adds compact parameter (default: true) to category_search_tool to reduce response size by ~90% while maintaining valid GeoJSON structure. - Removes attribution, external_ids, mapbox_id, metadata - Flattens nested context object into direct properties - Keeps essential fields: name, address, coordinates, poi_category, brand, maki - Maintains valid GeoJSON structure for use in geojson.io and other tools - Backward compatible with compact: false for full verbose response 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <[email protected]> * Fix schema validation: add missing country property Mapbox Search Box API returns 'country' as a top-level property in feature.properties, but the output schema didn't include it. This causes validation warning when using the MCP server: ✗ Validation Error: data.features[0].properties should NOT have additional properties Changes: 1. Added country: z.string().optional() to SearchBoxFeaturePropertiesSchema 2. Added .passthrough() to allow future API additions without breaking The API returns both: - properties.country: "France" (top-level, for convenience) - properties.context.country.name: "France" (nested, full context) Now schema accepts both structures without validation warnings. Testing: ✓ All 31 tests pass ✓ Validated against real API responses --------- Co-authored-by: Claude Sonnet 4.5 <[email protected]>
1 parent f3f439b commit 2f63f77

File tree

1 file changed

+107
-101
lines changed

1 file changed

+107
-101
lines changed

src/tools/search-and-geocode-tool/SearchAndGeocodeTool.output.schema.ts

Lines changed: 107 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -4,113 +4,119 @@
44
import { z } from 'zod';
55

66
// Search Box API feature properties schema
7-
const SearchBoxFeaturePropertiesSchema = z.object({
8-
// Basic identification
9-
mapbox_id: z.string().optional(),
10-
feature_type: z.string().optional(),
11-
name: z.string().optional(),
12-
name_preferred: z.string().optional(),
7+
const SearchBoxFeaturePropertiesSchema = z
8+
.object({
9+
// Basic identification
10+
mapbox_id: z.string().optional(),
11+
feature_type: z.string().optional(),
12+
name: z.string().optional(),
13+
name_preferred: z.string().optional(),
1314

14-
// Address components
15-
full_address: z.string().optional(),
16-
place_formatted: z.string().optional(),
17-
address_number: z.string().optional(),
18-
street_name: z.string().optional(),
15+
// Address components
16+
full_address: z.string().optional(),
17+
place_formatted: z.string().optional(),
18+
address_number: z.string().optional(),
19+
street_name: z.string().optional(),
1920

20-
// Administrative areas
21-
context: z
22-
.object({
23-
country: z
24-
.object({
25-
name: z.string().optional(),
26-
country_code: z.string().optional(),
27-
country_code_alpha_3: z.string().optional()
28-
})
29-
.optional(),
30-
region: z
31-
.object({
32-
name: z.string().optional(),
33-
region_code: z.string().optional(),
34-
region_code_full: z.string().optional()
35-
})
36-
.optional(),
37-
postcode: z
38-
.object({
39-
name: z.string().optional()
40-
})
41-
.optional(),
42-
district: z
43-
.object({
44-
name: z.string().optional()
45-
})
46-
.optional(),
47-
place: z
48-
.object({
49-
name: z.string().optional()
50-
})
51-
.optional(),
52-
locality: z
53-
.object({
54-
name: z.string().optional()
55-
})
56-
.optional(),
57-
neighborhood: z
58-
.object({
59-
name: z.string().optional()
60-
})
61-
.optional(),
62-
street: z
63-
.object({
64-
name: z.string().optional()
65-
})
66-
.optional(),
67-
address: z
68-
.object({
69-
address_number: z.string().optional(),
70-
street_name: z.string().optional()
71-
})
72-
.optional()
73-
})
74-
.optional(),
75-
76-
// Coordinates and bounds
77-
coordinates: z
78-
.object({
79-
longitude: z.number(),
80-
latitude: z.number(),
81-
accuracy: z.string().optional(),
82-
routable_points: z
83-
.array(
84-
z.object({
85-
name: z.string(),
86-
latitude: z.number(),
87-
longitude: z.number()
21+
// Administrative areas
22+
context: z
23+
.object({
24+
country: z
25+
.object({
26+
name: z.string().optional(),
27+
country_code: z.string().optional(),
28+
country_code_alpha_3: z.string().optional()
29+
})
30+
.optional(),
31+
region: z
32+
.object({
33+
name: z.string().optional(),
34+
region_code: z.string().optional(),
35+
region_code_full: z.string().optional()
36+
})
37+
.optional(),
38+
postcode: z
39+
.object({
40+
name: z.string().optional()
41+
})
42+
.optional(),
43+
district: z
44+
.object({
45+
name: z.string().optional()
46+
})
47+
.optional(),
48+
place: z
49+
.object({
50+
name: z.string().optional()
51+
})
52+
.optional(),
53+
locality: z
54+
.object({
55+
name: z.string().optional()
8856
})
89-
)
90-
.optional()
91-
})
92-
.optional(),
93-
bbox: z.array(z.number()).length(4).optional(),
57+
.optional(),
58+
neighborhood: z
59+
.object({
60+
name: z.string().optional()
61+
})
62+
.optional(),
63+
street: z
64+
.object({
65+
name: z.string().optional()
66+
})
67+
.optional(),
68+
address: z
69+
.object({
70+
address_number: z.string().optional(),
71+
street_name: z.string().optional()
72+
})
73+
.optional()
74+
})
75+
.optional(),
9476

95-
// POI specific fields
96-
poi_category: z.array(z.string()).optional(),
97-
poi_category_ids: z.array(z.string()).optional(),
98-
brand: z.array(z.string()).optional(),
99-
brand_id: z.union([z.string(), z.array(z.string())]).optional(),
100-
external_ids: z.record(z.string()).optional(),
77+
// Coordinates and bounds
78+
coordinates: z
79+
.object({
80+
longitude: z.number(),
81+
latitude: z.number(),
82+
accuracy: z.string().optional(),
83+
routable_points: z
84+
.array(
85+
z.object({
86+
name: z.string(),
87+
latitude: z.number(),
88+
longitude: z.number()
89+
})
90+
)
91+
.optional()
92+
})
93+
.optional(),
94+
bbox: z.array(z.number()).length(4).optional(),
10195

102-
// Additional metadata
103-
maki: z.string().optional(),
104-
operational_status: z.string().optional(),
96+
// POI specific fields
97+
poi_category: z.array(z.string()).optional(),
98+
poi_category_ids: z.array(z.string()).optional(),
99+
brand: z.array(z.string()).optional(),
100+
brand_id: z.union([z.string(), z.array(z.string())]).optional(),
101+
external_ids: z.record(z.string()).optional(),
105102

106-
// ETA information (when requested)
107-
eta: z
108-
.object({
109-
duration: z.number().optional(),
110-
distance: z.number().optional()
111-
})
112-
.optional()
113-
});
103+
// Additional metadata
104+
maki: z.string().optional(),
105+
operational_status: z.string().optional(),
106+
107+
// ETA information (when requested)
108+
eta: z
109+
.object({
110+
duration: z.number().optional(),
111+
distance: z.number().optional()
112+
})
113+
.optional(),
114+
115+
// Top-level country field (in addition to context.country)
116+
// Mapbox Search Box API sometimes returns country at the top level
117+
country: z.string().optional()
118+
})
119+
.passthrough(); // Allow additional properties the API may add in the future
114120

115121
// GeoJSON geometry schema
116122
const GeometrySchema = z.object({

0 commit comments

Comments
 (0)