@cenogram/mcp-server 0.5.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -8
- package/dist/api-client.d.ts +279 -1
- package/dist/api-client.js +117 -4
- package/dist/error-messages.js +1 -1
- package/dist/formatters.d.ts +14 -1
- package/dist/formatters.js +647 -9
- package/dist/index.d.ts +1 -0
- package/dist/index.js +129 -108
- package/dist/tools.js +336 -47
- package/package.json +8 -2
package/dist/api-client.js
CHANGED
|
@@ -26,7 +26,8 @@ function extractCreditInfo(res) {
|
|
|
26
26
|
const cost = parseInt(res.headers.get("X-Credits-Cost") ?? "", 10);
|
|
27
27
|
if (isNaN(balance) || isNaN(cost))
|
|
28
28
|
return null;
|
|
29
|
-
|
|
29
|
+
const refunded = parseInt(res.headers.get("X-Credits-Refunded") ?? "", 10);
|
|
30
|
+
return isNaN(refunded) ? { balance, cost } : { balance, cost, refunded };
|
|
30
31
|
}
|
|
31
32
|
export const OAUTH_CTX_PREFIX = "\x01";
|
|
32
33
|
export function encodeOAuthCtx(userId, grantId) {
|
|
@@ -94,15 +95,56 @@ export function formatRetryAfter(seconds) {
|
|
|
94
95
|
return unit(Math.ceil(seconds / 3600), "hour");
|
|
95
96
|
return unit(Math.ceil(seconds / 86400), "day");
|
|
96
97
|
}
|
|
98
|
+
export class ApiHttpError extends Error {
|
|
99
|
+
status;
|
|
100
|
+
code;
|
|
101
|
+
authMode;
|
|
102
|
+
retryAfterSeconds;
|
|
103
|
+
constructor(message, opts) {
|
|
104
|
+
super(message);
|
|
105
|
+
this.name = "ApiHttpError";
|
|
106
|
+
this.status = opts.status;
|
|
107
|
+
this.code = opts.code;
|
|
108
|
+
this.authMode = opts.authMode;
|
|
109
|
+
this.retryAfterSeconds = opts.retryAfterSeconds;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export function isExpectedApiError(error) {
|
|
113
|
+
if (!(error instanceof ApiHttpError))
|
|
114
|
+
return false;
|
|
115
|
+
const { status, authMode, code } = error;
|
|
116
|
+
if (status >= 500)
|
|
117
|
+
return false;
|
|
118
|
+
switch (status) {
|
|
119
|
+
case 400:
|
|
120
|
+
case 402:
|
|
121
|
+
case 404:
|
|
122
|
+
case 409:
|
|
123
|
+
case 410:
|
|
124
|
+
case 422:
|
|
125
|
+
case 429:
|
|
126
|
+
return true;
|
|
127
|
+
case 401:
|
|
128
|
+
return authMode !== "oauth";
|
|
129
|
+
case 403:
|
|
130
|
+
return code === "email_not_verified";
|
|
131
|
+
default:
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
97
135
|
async function handleErrorResponse(res, apiKey) {
|
|
136
|
+
const mode = getAuthMode(apiKey ?? process.env.CENOGRAM_API_KEY);
|
|
98
137
|
if (res.status === 429) {
|
|
99
138
|
const seconds = parseRetryAfterSeconds(res.headers?.get?.("Retry-After"));
|
|
100
139
|
const wait = seconds !== null ? ` Retry in ${formatRetryAfter(seconds)}.` : " Retry shortly.";
|
|
101
|
-
throw new
|
|
140
|
+
throw new ApiHttpError(`Too many requests - this is a rate limit, not an exhausted allowance.${wait}`, { status: 429, authMode: mode, retryAfterSeconds: seconds ?? undefined });
|
|
102
141
|
}
|
|
103
142
|
const body = (await res.json().catch(() => ({})));
|
|
104
|
-
|
|
105
|
-
|
|
143
|
+
throw new ApiHttpError(authErrorMessage(res.status, mode, body), {
|
|
144
|
+
status: res.status,
|
|
145
|
+
code: typeof body.error === "string" ? body.error : undefined,
|
|
146
|
+
authMode: mode,
|
|
147
|
+
});
|
|
106
148
|
}
|
|
107
149
|
function toQueryParams(obj) {
|
|
108
150
|
const params = {};
|
|
@@ -175,6 +217,10 @@ export function getTransactions(p, apiKey) {
|
|
|
175
217
|
floodRisk: p.floodRisk,
|
|
176
218
|
heritageStatus: p.heritageStatus,
|
|
177
219
|
landslideRisk: p.landslideRisk,
|
|
220
|
+
landUse: p.landUse,
|
|
221
|
+
buildingStoreys: p.buildingStoreys,
|
|
222
|
+
minFootprintArea: p.minFootprintArea,
|
|
223
|
+
maxFootprintArea: p.maxFootprintArea,
|
|
178
224
|
minPrice: p.minPrice,
|
|
179
225
|
maxPrice: p.maxPrice,
|
|
180
226
|
dateFrom: p.dateFrom,
|
|
@@ -207,6 +253,10 @@ export function getTransactionsSummary(p, apiKey) {
|
|
|
207
253
|
floodRisk: p.floodRisk,
|
|
208
254
|
heritageStatus: p.heritageStatus,
|
|
209
255
|
landslideRisk: p.landslideRisk,
|
|
256
|
+
landUse: p.landUse,
|
|
257
|
+
buildingStoreys: p.buildingStoreys,
|
|
258
|
+
minFootprintArea: p.minFootprintArea,
|
|
259
|
+
maxFootprintArea: p.maxFootprintArea,
|
|
210
260
|
minPrice: p.minPrice,
|
|
211
261
|
maxPrice: p.maxPrice,
|
|
212
262
|
dateFrom: p.dateFrom,
|
|
@@ -247,6 +297,17 @@ export function getPriceSpreadLocations(params, apiKey) {
|
|
|
247
297
|
search: params.search,
|
|
248
298
|
}), apiKey);
|
|
249
299
|
}
|
|
300
|
+
export function getFloodRisk(params, apiKey) {
|
|
301
|
+
return fetchApi("/api/v1/flood-risk", toQueryParams({
|
|
302
|
+
location: params.location,
|
|
303
|
+
teryt: params.teryt,
|
|
304
|
+
}), apiKey);
|
|
305
|
+
}
|
|
306
|
+
export function getFloodRiskLocations(params, apiKey) {
|
|
307
|
+
return fetchApi("/api/v1/flood-risk/locations", toQueryParams({
|
|
308
|
+
search: params.search,
|
|
309
|
+
}), apiKey);
|
|
310
|
+
}
|
|
250
311
|
export function getValuation(params, apiKey) {
|
|
251
312
|
return fetchApi("/api/v1/valuations", toQueryParams({
|
|
252
313
|
lat: params.lat,
|
|
@@ -261,12 +322,43 @@ export function getValuation(params, apiKey) {
|
|
|
261
322
|
export function getLocations(parent, apiKey) {
|
|
262
323
|
return fetchApi("/api/v1/locations", parent ? { parent } : undefined, apiKey);
|
|
263
324
|
}
|
|
325
|
+
export function searchLocations(q, apiKey) {
|
|
326
|
+
return fetchApi("/api/v1/locations", { search: q }, apiKey);
|
|
327
|
+
}
|
|
264
328
|
export function getPriceHistogram(bins = 20, max = 3_000_000, apiKey) {
|
|
265
329
|
return fetchApi("/api/v1/stats/price-histogram", toQueryParams({ bins, max }), apiKey);
|
|
266
330
|
}
|
|
267
331
|
export function searchParcels(q, limit, apiKey) {
|
|
268
332
|
return fetchApi("/api/v1/parcels/search", toQueryParams({ q, limit }), apiKey);
|
|
269
333
|
}
|
|
334
|
+
export function listParcels(p, apiKey) {
|
|
335
|
+
return fetchApi("/api/v1/parcels", toQueryParams({
|
|
336
|
+
location: p.location,
|
|
337
|
+
teryt: p.teryt,
|
|
338
|
+
bbox: p.bbox,
|
|
339
|
+
lat: p.lat,
|
|
340
|
+
lng: p.lng,
|
|
341
|
+
radiusKm: p.radiusKm,
|
|
342
|
+
minArea: p.minArea,
|
|
343
|
+
maxArea: p.maxArea,
|
|
344
|
+
street: p.street,
|
|
345
|
+
buildingNumber: p.buildingNumber,
|
|
346
|
+
limit: p.limit,
|
|
347
|
+
cursor: p.cursor,
|
|
348
|
+
}), apiKey);
|
|
349
|
+
}
|
|
350
|
+
export function getStreets(scope, apiKey) {
|
|
351
|
+
return fetchApi("/api/v1/streets", toQueryParams({ ...scope }), apiKey);
|
|
352
|
+
}
|
|
353
|
+
export function getParcelsMap(bbox, limit, apiKey) {
|
|
354
|
+
return fetchApi("/api/v1/parcels/map", toQueryParams({ bbox, limit }), apiKey);
|
|
355
|
+
}
|
|
356
|
+
export function searchParcelsByPolygon(polygon, limit, apiKey) {
|
|
357
|
+
const body = { polygon };
|
|
358
|
+
if (limit != null)
|
|
359
|
+
body.limit = limit;
|
|
360
|
+
return fetchApiPost("/api/v1/parcels/spatial", body, apiKey);
|
|
361
|
+
}
|
|
270
362
|
export function resolveParcel(params, apiKey) {
|
|
271
363
|
return fetchApi("/api/v1/parcels/resolve", toQueryParams({
|
|
272
364
|
q: params.q,
|
|
@@ -279,6 +371,10 @@ export function getParcelReport(parcelKey, apiKey) {
|
|
|
279
371
|
const urlKey = parcelKey.trim().replace(/\//g, "-");
|
|
280
372
|
return fetchApi(`/api/v1/parcels/${encodeURIComponent(urlKey)}/report`, undefined, apiKey);
|
|
281
373
|
}
|
|
374
|
+
export function getParcelLandClass(parcelKey, apiKey) {
|
|
375
|
+
const urlKey = parcelKey.trim().replace(/\//g, "-");
|
|
376
|
+
return fetchApi(`/api/v1/parcels/${encodeURIComponent(urlKey)}/land-class`, undefined, apiKey);
|
|
377
|
+
}
|
|
282
378
|
export function searchByPolygon(p, apiKey) {
|
|
283
379
|
const body = { polygon: p.polygon };
|
|
284
380
|
if (p.propertyType != null)
|
|
@@ -315,6 +411,14 @@ export function searchByPolygon(p, apiKey) {
|
|
|
315
411
|
body.rooms = p.rooms;
|
|
316
412
|
if (p.floor)
|
|
317
413
|
body.floor = p.floor;
|
|
414
|
+
if (p.landUse)
|
|
415
|
+
body.landUse = p.landUse;
|
|
416
|
+
if (p.buildingStoreys)
|
|
417
|
+
body.buildingStoreys = p.buildingStoreys;
|
|
418
|
+
if (p.minFootprintArea != null)
|
|
419
|
+
body.minFootprintArea = p.minFootprintArea;
|
|
420
|
+
if (p.maxFootprintArea != null)
|
|
421
|
+
body.maxFootprintArea = p.maxFootprintArea;
|
|
318
422
|
if (p.limit != null)
|
|
319
423
|
body.limit = p.limit;
|
|
320
424
|
return fetchApiPost("/api/v1/transactions/spatial", body, apiKey);
|
|
@@ -353,9 +457,18 @@ export function getTransactionHeritage(transactionId, apiKey) {
|
|
|
353
457
|
export function getTransactionLandslide(transactionId, apiKey) {
|
|
354
458
|
return fetchApi(`/api/v1/transactions/${transactionId}/landslide`, undefined, apiKey);
|
|
355
459
|
}
|
|
460
|
+
export function getTransactionNature(transactionId, apiKey) {
|
|
461
|
+
return fetchApi(`/api/v1/transactions/${transactionId}/nature`, undefined, apiKey);
|
|
462
|
+
}
|
|
463
|
+
export function getTransactionSubsurface(transactionId, apiKey) {
|
|
464
|
+
return fetchApi(`/api/v1/transactions/${transactionId}/subsurface`, undefined, apiKey);
|
|
465
|
+
}
|
|
356
466
|
export function getTransactionSurroundings(transactionId, apiKey) {
|
|
357
467
|
return fetchApi(`/api/v1/transactions/${transactionId}/surroundings`, undefined, apiKey);
|
|
358
468
|
}
|
|
469
|
+
export function getTransactionRoads(transactionId, apiKey) {
|
|
470
|
+
return fetchApi(`/api/v1/transactions/${transactionId}/roads`, undefined, apiKey);
|
|
471
|
+
}
|
|
359
472
|
export function getTransactionTransit(transactionId, apiKey) {
|
|
360
473
|
return fetchApi(`/api/v1/transactions/${transactionId}/transit`, undefined, apiKey);
|
|
361
474
|
}
|
package/dist/error-messages.js
CHANGED
|
@@ -41,7 +41,7 @@ export function authErrorMessage(status, mode, body = {}) {
|
|
|
41
41
|
return "API key rejected. Check https://cenogram.pl/ustawienia#api-keys if it's still active.";
|
|
42
42
|
case 402: {
|
|
43
43
|
const explanation = specificField(body.message);
|
|
44
|
-
if (
|
|
44
|
+
if (explanation) {
|
|
45
45
|
const upgrade = specificField(body.upgrade);
|
|
46
46
|
return upgrade && !explanation.includes(upgrade) ? `${sentence(explanation)} (${upgrade})` : explanation;
|
|
47
47
|
}
|
package/dist/formatters.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Transaction, TransactionsResponse, TransactionsSummary, StatsResponse, PricePerM2Row, HistogramBin, ParcelSearchResponse, ParcelResolveResponse, SpatialSearchResponse, CompareResponse, LocationItem, RentalYieldResponse, RentalYieldLocationsResponse, PriceSpreadResponse, PriceSpreadLocationsResponse, ValuationResponse, BuildingBreakdownResponse, FloodBreakdownResponse, HeritageBreakdownResponse, LandslideBreakdownResponse, SurroundingsResponse, TransitBreakdownResponse, PermitsResponse, PlanningResponse, FarmlandResponse, DemographicsResponse, InfrastructureSignalsResponse, ParcelReportResponse } from "./api-client.js";
|
|
1
|
+
import type { Transaction, TransactionsResponse, TransactionsSummary, StatsResponse, PricePerM2Row, HistogramBin, ParcelSearchResponse, ParcelResolveResponse, ParcelListResponse, StreetListResponse, ParcelFeatureCollection, CorpusCoverage, SpatialSearchResponse, CompareResponse, LocationItem, LocationSearchItem, RentalYieldResponse, RentalYieldLocationsResponse, PriceSpreadResponse, PriceSpreadLocationsResponse, FloodRiskResponse, FloodRiskLocationsResponse, ValuationResponse, BuildingBreakdownResponse, BuildingAgeEstimate, FloodBreakdownResponse, HeritageBreakdownResponse, LandslideBreakdownResponse, NatureBreakdownResponse, SubsurfaceBreakdownResponse, SurroundingsResponse, RoadsBreakdownResponse, TransitBreakdownResponse, PermitsResponse, PlanningResponse, FarmlandResponse, DemographicsResponse, InfrastructureSignalsResponse, ParcelReportResponse, ParcelLandClassResponse } from "./api-client.js";
|
|
2
2
|
export declare const MARKET_CAVEAT = "Note: median/average prices are market-based \u2014 fractional ownership shares and non-market deeds (public tenders, foreclosures, privileged/subsidized sales) are excluded from price aggregates. Transaction counts and coverage stay complete.";
|
|
3
3
|
export declare function formatPLN(value: number | null | undefined): string;
|
|
4
4
|
export declare function formatArea(m2: number | null | undefined): string;
|
|
@@ -9,18 +9,27 @@ export declare function formatMarketOverview(stats: StatsResponse): string;
|
|
|
9
9
|
export declare function formatPriceStats(rows: PricePerM2Row[], location?: string): string;
|
|
10
10
|
export declare function formatHistogram(bins: HistogramBin[]): string;
|
|
11
11
|
export declare function formatParcelResults(res: ParcelSearchResponse, query: string): string;
|
|
12
|
+
export declare function formatCorpusCoverage(cov: CorpusCoverage | null | undefined): string;
|
|
13
|
+
export declare function formatParcelList(res: ParcelListResponse, scope: string, creditsRefunded?: boolean): string;
|
|
14
|
+
export declare function formatStreetList(res: StreetListResponse, q: string, scope: string): string;
|
|
15
|
+
export declare function formatParcelFeatures(res: ParcelFeatureCollection, scope: string): string;
|
|
12
16
|
export declare function formatParcelResolve(res: ParcelResolveResponse): string;
|
|
13
17
|
export declare function formatSpatialResults(res: SpatialSearchResponse): string;
|
|
18
|
+
export declare function formatBuildingAge(age: BuildingAgeEstimate | undefined): string;
|
|
14
19
|
export declare function formatBuildingBreakdown(res: BuildingBreakdownResponse): string;
|
|
15
20
|
export declare function formatFloodBreakdown(res: FloodBreakdownResponse): string;
|
|
16
21
|
export declare function formatHeritageBreakdown(res: HeritageBreakdownResponse): string;
|
|
17
22
|
export declare function formatLandslideBreakdown(res: LandslideBreakdownResponse): string;
|
|
23
|
+
export declare function formatNatureBreakdown(res: NatureBreakdownResponse): string;
|
|
24
|
+
export declare function formatSubsurfaceBreakdown(res: SubsurfaceBreakdownResponse): string;
|
|
18
25
|
export declare function formatSurroundings(res: SurroundingsResponse): string;
|
|
26
|
+
export declare function formatRoads(res: RoadsBreakdownResponse): string;
|
|
19
27
|
export declare function formatTransitBreakdown(res: TransitBreakdownResponse): string;
|
|
20
28
|
export declare function formatPermitsBreakdown(res: PermitsResponse): string;
|
|
21
29
|
export declare function formatPlanningBreakdown(res: PlanningResponse): string;
|
|
22
30
|
export declare function formatFarmland(res: FarmlandResponse): string;
|
|
23
31
|
export declare function formatLocationHierarchy(items: LocationItem[], parent?: string): string;
|
|
32
|
+
export declare function formatLocationSearch(items: LocationSearchItem[], rcnOnly: string[], query: string): string;
|
|
24
33
|
export declare function formatCompareResults(res: CompareResponse): string;
|
|
25
34
|
export declare function formatDemographics(r: DemographicsResponse): string;
|
|
26
35
|
export declare function formatInfrastructureSignals(r: InfrastructureSignalsResponse): string;
|
|
@@ -28,5 +37,9 @@ export declare function formatRentalYield(r: RentalYieldResponse): string;
|
|
|
28
37
|
export declare function formatRentalYieldLocations(r: RentalYieldLocationsResponse): string;
|
|
29
38
|
export declare function formatPriceSpread(r: PriceSpreadResponse): string;
|
|
30
39
|
export declare function formatPriceSpreadLocations(r: PriceSpreadLocationsResponse): string;
|
|
40
|
+
export declare function formatFloodRisk(r: FloodRiskResponse): string;
|
|
41
|
+
export declare function formatFloodRiskLocations(r: FloodRiskLocationsResponse): string;
|
|
31
42
|
export declare function formatValuation(r: ValuationResponse): string;
|
|
43
|
+
export declare const REPORT_LAYER_ORDER: Array<[string, string]>;
|
|
32
44
|
export declare function formatParcelReport(res: ParcelReportResponse): string;
|
|
45
|
+
export declare function formatParcelLandClass(res: ParcelLandClassResponse): string;
|