From 5adee3354636eba790b24703f2a69f4cc2d2bdb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Murat=20=C3=96ZDEM=C4=B0R?= Date: Mon, 5 Oct 2026 17:47:47 +0300 Subject: [PATCH] GAIA-431: require countryCode in KKTC-aware city listing tools and ADMINISTRATIVE boundaries The backend now serves city catalogs per country, so the list_cities tools for forecast and geo alarms take a required countryCode (TUR or CTR) and call /v1/alarms/{forecasts|geometries}/{countryCode}/cities instead of the removed country-less path, which the MCP could no longer reach. The boundary schema of both register and update tools now rejects ADMINISTRATIVE boundaries without a countryCode of TUR or CTR, mirroring the server-side validation so the client gets a clear error before the request is sent, since city and district ids are only unique within a country. Tool descriptions also explain that KKTC neighbourhoods are sub-districts. --- src/tools/forecast-alarms.ts | 25 ++++++++++++++++++------- src/tools/geo-alarms.ts | 29 ++++++++++++++++++++--------- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/tools/forecast-alarms.ts b/src/tools/forecast-alarms.ts index d5bf851..1bc040c 100644 --- a/src/tools/forecast-alarms.ts +++ b/src/tools/forecast-alarms.ts @@ -6,7 +6,7 @@ const webhookDescription = const boundaryDescription = "Boundary definition. One of:\n" + - "- Administrative: { type: 'ADMINISTRATIVE', cityId: 6, districtId: 557 }\n" + + "- Administrative: { type: 'ADMINISTRATIVE', countryCode: 'TUR', cityId: 6, districtId: 557 } (countryCode is required: TUR for Türkiye, CTR for Northern Cyprus; city and district IDs come from forecast_alarm_list_cities / forecast_alarm_list_districts for that country)\n" + "- Point: { type: 'POINT', point: { lat: 39.93, lng: 32.85 } }"; export const forecastAlarmTools = [ @@ -167,11 +167,17 @@ export const forecastAlarmTools = [ }, { name: "forecast_alarm_list_cities", - description: "List all cities in Türkiye available for forecast alarm boundaries.", + description: "List all cities of a country available for forecast alarm boundaries. Supported countries: TUR (Türkiye) and CTR (Northern Cyprus).", inputSchema: { type: "object" as const, - properties: {}, - required: [], + properties: { + countryCode: { + type: "string", + enum: ["TUR", "CTR"], + description: "ISO 3166 alpha-3 country code: TUR (Türkiye) or CTR (Northern Cyprus)", + }, + }, + required: ["countryCode"], }, }, { @@ -222,7 +228,10 @@ const WebhookSchema = z.object({ const ForecastBoundarySchema = z.object({ type: z.enum(["ADMINISTRATIVE", "POINT"]), -}).passthrough(); +}).passthrough().refine( + (boundary) => boundary.type !== "ADMINISTRATIVE" || boundary.countryCode === "TUR" || boundary.countryCode === "CTR", + { message: "countryCode (TUR or CTR) is required for ADMINISTRATIVE boundaries", path: ["countryCode"] } +); const ForecastRegisterSchema = z.object({ recipientId: z.string(), @@ -327,8 +336,10 @@ export async function handleForecastAlarmTool( case "forecast_alarm_list": return apiPost("/v1/alarms/forecasts/page", ListSchema.parse(args)); - case "forecast_alarm_list_cities": - return apiGet("/v1/alarms/forecasts/cities"); + case "forecast_alarm_list_cities": { + const countryCode = z.enum(["TUR", "CTR"]).parse(args.countryCode); + return apiGet(`/v1/alarms/forecasts/${countryCode}/cities`); + } case "forecast_alarm_get_city": { const cityId = z.number().parse(args.cityId); diff --git a/src/tools/geo-alarms.ts b/src/tools/geo-alarms.ts index c5da926..90915a5 100644 --- a/src/tools/geo-alarms.ts +++ b/src/tools/geo-alarms.ts @@ -6,7 +6,7 @@ const webhookDescription = const boundaryDescription = "Boundary definition. One of:\n" + - "- Administrative: { type: 'ADMINISTRATIVE', cityId: 6, districtId: 557, neighborhoodId: 55766 }\n" + + "- Administrative: { type: 'ADMINISTRATIVE', countryCode: 'TUR', cityId: 6, districtId: 557, neighborhoodId: 55766 } (countryCode is required: TUR for Türkiye, CTR for Northern Cyprus; IDs come from the catalog tools for that country. For CTR the neighborhood level is the sub-district)\n" + "- Polygon: { type: 'POLYGON', polygon: { exterior: [{lat, lng}, ...] } }\n" + "- H3 Index: { type: 'H3INDEX', h3Address: '8928308280fffff' }"; @@ -133,11 +133,17 @@ export const geoAlarmTools = [ }, { name: "geo_alarm_list_cities", - description: "List all cities in Türkiye available for geo alarm boundaries.", + description: "List all cities of a country available for geo alarm boundaries. Supported countries: TUR (Türkiye) and CTR (Northern Cyprus).", inputSchema: { type: "object" as const, - properties: {}, - required: [], + properties: { + countryCode: { + type: "string", + enum: ["TUR", "CTR"], + description: "ISO 3166 alpha-3 country code: TUR (Türkiye) or CTR (Northern Cyprus)", + }, + }, + required: ["countryCode"], }, }, { @@ -175,7 +181,7 @@ export const geoAlarmTools = [ }, { name: "geo_alarm_list_neighborhoods", - description: "List all neighborhoods for a given district.", + description: "List all neighborhoods for a given district. For CTR (Northern Cyprus) neighborhoods are the sub-district level.", inputSchema: { type: "object" as const, properties: { @@ -186,7 +192,7 @@ export const geoAlarmTools = [ }, { name: "geo_alarm_get_neighborhood", - description: "Get boundary details for a specific neighborhood by its ID.", + description: "Get boundary details for a specific neighborhood by its ID. For CTR (Northern Cyprus) neighborhoods are the sub-district level.", inputSchema: { type: "object" as const, properties: { @@ -210,7 +216,10 @@ const WebhookSchema = z.object({ const GeoBoundarySchema = z.object({ type: z.enum(["ADMINISTRATIVE", "POLYGON", "H3INDEX"]), -}).passthrough(); +}).passthrough().refine( + (boundary) => boundary.type !== "ADMINISTRATIVE" || boundary.countryCode === "TUR" || boundary.countryCode === "CTR", + { message: "countryCode (TUR or CTR) is required for ADMINISTRATIVE boundaries", path: ["countryCode"] } +); const GeoRegisterSchema = z.object({ recipientId: z.string(), @@ -302,8 +311,10 @@ export async function handleGeoAlarmTool( case "geo_alarm_list": return apiPost("/v1/alarms/geometries/page", ListSchema.parse(args)); - case "geo_alarm_list_cities": - return apiGet("/v1/alarms/geometries/cities"); + case "geo_alarm_list_cities": { + const countryCode = z.enum(["TUR", "CTR"]).parse(args.countryCode); + return apiGet(`/v1/alarms/geometries/${countryCode}/cities`); + } case "geo_alarm_get_city": { const cityId = z.number().parse(args.cityId);