@cenogram/mcp-server 0.1.2 → 0.1.4
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/dist/api-client.d.ts +11 -0
- package/dist/api-client.js +15 -0
- package/dist/formatters.js +2 -0
- package/dist/index.js +8 -3
- package/dist/mappings.d.ts +7 -0
- package/dist/mappings.js +72 -0
- package/dist/tools.js +52 -6
- package/package.json +19 -5
package/dist/api-client.d.ts
CHANGED
|
@@ -49,6 +49,7 @@ export interface Transaction {
|
|
|
49
49
|
parcel_area: number | null;
|
|
50
50
|
unit_function: number | null;
|
|
51
51
|
parcel_id: string | null;
|
|
52
|
+
parcel_number: string | null;
|
|
52
53
|
county_name: string | null;
|
|
53
54
|
voivodeship_name: string | null;
|
|
54
55
|
centroid: {
|
|
@@ -103,6 +104,9 @@ export interface TransactionParams {
|
|
|
103
104
|
parcelId?: string;
|
|
104
105
|
propertyType?: number;
|
|
105
106
|
marketType?: number;
|
|
107
|
+
unitFunction?: number;
|
|
108
|
+
buildingType?: number;
|
|
109
|
+
mpzpDesignation?: string;
|
|
106
110
|
minPrice?: number;
|
|
107
111
|
maxPrice?: number;
|
|
108
112
|
dateFrom?: string;
|
|
@@ -138,6 +142,9 @@ export interface SpatialSearchParams {
|
|
|
138
142
|
};
|
|
139
143
|
propertyType?: number;
|
|
140
144
|
marketType?: number;
|
|
145
|
+
unitFunction?: number;
|
|
146
|
+
buildingType?: number;
|
|
147
|
+
mpzpDesignation?: string;
|
|
141
148
|
minPrice?: number;
|
|
142
149
|
maxPrice?: number;
|
|
143
150
|
dateFrom?: string;
|
|
@@ -163,6 +170,7 @@ export interface SpatialFeatureProperties {
|
|
|
163
170
|
city: string | null;
|
|
164
171
|
district: string | null;
|
|
165
172
|
parcel_area: number | null;
|
|
173
|
+
parcel_number: string | null;
|
|
166
174
|
}
|
|
167
175
|
export interface SpatialFeature {
|
|
168
176
|
type: "Feature";
|
|
@@ -192,6 +200,9 @@ export interface CompareParams {
|
|
|
192
200
|
districts: string;
|
|
193
201
|
propertyType?: number;
|
|
194
202
|
marketType?: number;
|
|
203
|
+
unitFunction?: number;
|
|
204
|
+
buildingType?: number;
|
|
205
|
+
mpzpDesignation?: string;
|
|
195
206
|
minPrice?: number;
|
|
196
207
|
maxPrice?: number;
|
|
197
208
|
dateFrom?: string;
|
package/dist/api-client.js
CHANGED
|
@@ -95,6 +95,9 @@ export function getTransactions(p, apiKey) {
|
|
|
95
95
|
parcelId: p.parcelId,
|
|
96
96
|
propertyType: p.propertyType,
|
|
97
97
|
marketType: p.marketType,
|
|
98
|
+
unitFunction: p.unitFunction,
|
|
99
|
+
buildingType: p.buildingType,
|
|
100
|
+
mpzpDesignation: p.mpzpDesignation,
|
|
98
101
|
minPrice: p.minPrice,
|
|
99
102
|
maxPrice: p.maxPrice,
|
|
100
103
|
dateFrom: p.dateFrom,
|
|
@@ -114,6 +117,9 @@ export function getTransactionsSummary(p, apiKey) {
|
|
|
114
117
|
street: p.street,
|
|
115
118
|
propertyType: p.propertyType,
|
|
116
119
|
marketType: p.marketType,
|
|
120
|
+
unitFunction: p.unitFunction,
|
|
121
|
+
buildingType: p.buildingType,
|
|
122
|
+
mpzpDesignation: p.mpzpDesignation,
|
|
117
123
|
minPrice: p.minPrice,
|
|
118
124
|
maxPrice: p.maxPrice,
|
|
119
125
|
dateFrom: p.dateFrom,
|
|
@@ -141,6 +147,12 @@ export function searchByPolygon(p, apiKey) {
|
|
|
141
147
|
body.propertyType = p.propertyType;
|
|
142
148
|
if (p.marketType != null)
|
|
143
149
|
body.marketType = p.marketType;
|
|
150
|
+
if (p.unitFunction != null)
|
|
151
|
+
body.unitFunction = p.unitFunction;
|
|
152
|
+
if (p.buildingType != null)
|
|
153
|
+
body.buildingType = p.buildingType;
|
|
154
|
+
if (p.mpzpDesignation)
|
|
155
|
+
body.mpzpDesignation = p.mpzpDesignation;
|
|
144
156
|
if (p.minPrice != null)
|
|
145
157
|
body.minPrice = p.minPrice;
|
|
146
158
|
if (p.maxPrice != null)
|
|
@@ -166,6 +178,9 @@ export function compareLocations(p, apiKey) {
|
|
|
166
178
|
districts: p.districts,
|
|
167
179
|
propertyType: p.propertyType,
|
|
168
180
|
marketType: p.marketType,
|
|
181
|
+
unitFunction: p.unitFunction,
|
|
182
|
+
buildingType: p.buildingType,
|
|
183
|
+
mpzpDesignation: p.mpzpDesignation,
|
|
169
184
|
minPrice: p.minPrice,
|
|
170
185
|
maxPrice: p.maxPrice,
|
|
171
186
|
dateFrom: p.dateFrom,
|
package/dist/formatters.js
CHANGED
|
@@ -50,6 +50,8 @@ function formatTransactionCore(f) {
|
|
|
50
50
|
parts.push(price.join(" | "));
|
|
51
51
|
// Extra details
|
|
52
52
|
const extra = [];
|
|
53
|
+
if (f.parcel_number)
|
|
54
|
+
extra.push(`Plot no: ${f.parcel_number}`);
|
|
53
55
|
if (f.rooms != null)
|
|
54
56
|
extra.push(`Rooms: ${f.rooms}`);
|
|
55
57
|
if (f.floor != null)
|
package/dist/index.js
CHANGED
|
@@ -20,8 +20,8 @@ export function createMcpServer(apiKey) {
|
|
|
20
20
|
"",
|
|
21
21
|
"CRITICAL - District names (ALWAYS verify first):",
|
|
22
22
|
"- NEVER guess district names. Call list_locations(search=\"city\") first.",
|
|
23
|
-
"- Warsaw: use
|
|
24
|
-
"- Kraków
|
|
23
|
+
"- Warsaw: 'Warszawa' auto-includes all 18 districts. Or use specific: Mokotów, Wola, Śródmieście",
|
|
24
|
+
"- Kraków/Łódź: 'Kraków'/'Łódź' auto-include all sub-districts. Or use specific: Kraków-Podgórze, etc.",
|
|
25
25
|
"- Most cities (Gdańsk, Gdynia, Sopot, Poznań): just the city name, no sub-districts",
|
|
26
26
|
"",
|
|
27
27
|
"Workflows:",
|
|
@@ -49,7 +49,7 @@ async function main() {
|
|
|
49
49
|
const { createServer } = await import("node:http");
|
|
50
50
|
const { StreamableHTTPServerTransport } = await import("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
51
51
|
const port = parseInt(process.env.MCP_PORT || "3002", 10);
|
|
52
|
-
|
|
52
|
+
const handleHttpRequest = async (req, res) => {
|
|
53
53
|
try {
|
|
54
54
|
const pathname = req.url?.split("?")[0];
|
|
55
55
|
if (pathname === "/mcp") {
|
|
@@ -82,6 +82,11 @@ async function main() {
|
|
|
82
82
|
res.writeHead(500, { "Content-Type": "application/json" }).end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32603, message: "Internal server error" }, id: null }));
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
+
};
|
|
86
|
+
createServer((req, res) => {
|
|
87
|
+
handleHttpRequest(req, res).catch((err) => {
|
|
88
|
+
process.stderr.write(`Unhandled HTTP error: ${String(err)}\n`);
|
|
89
|
+
});
|
|
85
90
|
}).listen(port, "0.0.0.0", () => {
|
|
86
91
|
process.stderr.write(`MCP HTTP server on http://0.0.0.0:${port}/mcp\n`);
|
|
87
92
|
});
|
package/dist/mappings.d.ts
CHANGED
|
@@ -2,7 +2,14 @@ export declare const PROPERTY_TYPES: Record<number, string>;
|
|
|
2
2
|
export declare const MARKET_TYPES: Record<number, string>;
|
|
3
3
|
export declare function mapPropertyType(value: string | undefined): number | undefined;
|
|
4
4
|
export declare function mapMarketType(value: string | undefined): number | undefined;
|
|
5
|
+
export declare const UNIT_FUNCTIONS: Record<number, string>;
|
|
6
|
+
export declare function mapUnitFunction(value: string | undefined): number | undefined;
|
|
7
|
+
export declare const BUILDING_TYPES: Record<number, string>;
|
|
8
|
+
export declare function mapBuildingType(value: string | undefined): number | undefined;
|
|
5
9
|
/** Convert lat/lng/radius to bbox [minLng, minLat, maxLng, maxLat] (lng-first!) */
|
|
6
10
|
export declare function radiusKmToBbox(lat: number, lng: number, radiusKm: number): [number, number, number, number];
|
|
7
11
|
/** Filter districts by location name (case-insensitive includes match) */
|
|
8
12
|
export declare function filterByLocation(location: string, districts: string[]): string[];
|
|
13
|
+
export declare const CITY_SUBDISTRICTS: ReadonlyMap<string, readonly string[]>;
|
|
14
|
+
/** Returns sub-districts for known multi-district cities, or [district] for everything else. */
|
|
15
|
+
export declare function expandDistrict(district: string): string[];
|
package/dist/mappings.js
CHANGED
|
@@ -29,6 +29,58 @@ export function mapMarketType(value) {
|
|
|
29
29
|
return undefined;
|
|
30
30
|
return MARKET_TYPE_MAP[value];
|
|
31
31
|
}
|
|
32
|
+
// ── Unit function enum maps ─────────────────────────────────────────
|
|
33
|
+
export const UNIT_FUNCTIONS = {
|
|
34
|
+
1: "Residential (Mieszkalna)",
|
|
35
|
+
2: "Commercial (Handlowo-usługowa)",
|
|
36
|
+
3: "Office (Biurowa)",
|
|
37
|
+
4: "Production (Produkcyjna)",
|
|
38
|
+
5: "Garage (Garaż)",
|
|
39
|
+
6: "Other (Inne)",
|
|
40
|
+
};
|
|
41
|
+
const UNIT_FUNCTION_MAP = {
|
|
42
|
+
residential: 1,
|
|
43
|
+
commercial: 2,
|
|
44
|
+
office: 3,
|
|
45
|
+
production: 4,
|
|
46
|
+
garage: 5,
|
|
47
|
+
other: 6,
|
|
48
|
+
};
|
|
49
|
+
export function mapUnitFunction(value) {
|
|
50
|
+
if (!value)
|
|
51
|
+
return undefined;
|
|
52
|
+
return UNIT_FUNCTION_MAP[value];
|
|
53
|
+
}
|
|
54
|
+
// ── Building type enum maps ─────────────────────────────────────────
|
|
55
|
+
export const BUILDING_TYPES = {
|
|
56
|
+
110: "Residential (Mieszkalny)",
|
|
57
|
+
121: "Commercial (Handlowo-usługowy)",
|
|
58
|
+
122: "Industrial (Przemysłowy)",
|
|
59
|
+
123: "Transport (Transportu i łączności)",
|
|
60
|
+
124: "Office (Biurowy)",
|
|
61
|
+
125: "Warehouse (Zbiorniki/Silosy/Magazyny)",
|
|
62
|
+
126: "Education/Sports (Oświaty i sportu)",
|
|
63
|
+
127: "Farm/Utility (Gospodarczy)",
|
|
64
|
+
128: "Hospital (Szpitale)",
|
|
65
|
+
129: "Other non-residential (Pozostałe niemieszkalne)",
|
|
66
|
+
};
|
|
67
|
+
const BUILDING_TYPE_MAP = {
|
|
68
|
+
residential: 110,
|
|
69
|
+
commercial: 121,
|
|
70
|
+
industrial: 122,
|
|
71
|
+
transport: 123,
|
|
72
|
+
office: 124,
|
|
73
|
+
warehouse: 125,
|
|
74
|
+
education_sports: 126,
|
|
75
|
+
farm_utility: 127,
|
|
76
|
+
hospital: 128,
|
|
77
|
+
other_nonresidential: 129,
|
|
78
|
+
};
|
|
79
|
+
export function mapBuildingType(value) {
|
|
80
|
+
if (!value)
|
|
81
|
+
return undefined;
|
|
82
|
+
return BUILDING_TYPE_MAP[value];
|
|
83
|
+
}
|
|
32
84
|
// ── Bbox conversion ─────────────────────────────────────────────────
|
|
33
85
|
/** Convert lat/lng/radius to bbox [minLng, minLat, maxLng, maxLat] (lng-first!) */
|
|
34
86
|
export function radiusKmToBbox(lat, lng, radiusKm) {
|
|
@@ -47,3 +99,23 @@ export function filterByLocation(location, districts) {
|
|
|
47
99
|
const lower = location.toLowerCase();
|
|
48
100
|
return districts.filter((d) => d.toLowerCase().includes(lower));
|
|
49
101
|
}
|
|
102
|
+
// ── City → sub-district expansion ───────────────────────────────────
|
|
103
|
+
// Keep in sync with api/src/helpers.ts CITY_SUBDISTRICTS (ADR-003).
|
|
104
|
+
// Cities: Warszawa (19), Kraków (5), Łódź (6).
|
|
105
|
+
export const CITY_SUBDISTRICTS = new Map([
|
|
106
|
+
["Warszawa", [
|
|
107
|
+
"Warszawa", "Bemowo", "Białołęka", "Bielany", "Mokotów", "Ochota",
|
|
108
|
+
"Praga-Południe", "Praga-Północ", "Rembertów", "Śródmieście", "Targówek",
|
|
109
|
+
"Ursus", "Ursynów", "Wawer", "Wesoła", "Wilanów", "Włochy", "Wola", "Żoliborz",
|
|
110
|
+
]],
|
|
111
|
+
["Kraków", [
|
|
112
|
+
"Kraków", "Kraków-Krowodrza", "Kraków-Nowa Huta", "Kraków-Podgórze", "Kraków-Śródmieście",
|
|
113
|
+
]],
|
|
114
|
+
["Łódź", [
|
|
115
|
+
"Łódź", "Łódź-Bałuty", "Łódź-Górna", "Łódź-Polesie", "Łódź-Śródmieście", "Łódź-Widzew",
|
|
116
|
+
]],
|
|
117
|
+
]);
|
|
118
|
+
/** Returns sub-districts for known multi-district cities, or [district] for everything else. */
|
|
119
|
+
export function expandDistrict(district) {
|
|
120
|
+
return CITY_SUBDISTRICTS.get(district)?.slice() ?? [district];
|
|
121
|
+
}
|
package/dist/tools.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { getStats, getTransactions, getPricePerM2, getDistricts, getPriceHistogram, getTransactionsSummary, searchParcels, searchByPolygon, compareLocations, } from "./api-client.js";
|
|
3
3
|
import { formatTransactionList, formatMarketOverview, formatPriceStats, formatHistogram, formatParcelResults, formatSpatialResults, formatCompareResults, } from "./formatters.js";
|
|
4
|
-
import { mapPropertyType, mapMarketType, radiusKmToBbox, filterByLocation, } from "./mappings.js";
|
|
4
|
+
import { mapPropertyType, mapMarketType, mapUnitFunction, mapBuildingType, radiusKmToBbox, filterByLocation, expandDistrict, CITY_SUBDISTRICTS, } from "./mappings.js";
|
|
5
5
|
// ── Helpers ─────────────────────────────────────────────────────────
|
|
6
6
|
function textResponse(text) {
|
|
7
7
|
return { content: [{ type: "text", text }] };
|
|
@@ -32,11 +32,17 @@ export function registerTools(server, apiKey) {
|
|
|
32
32
|
Returns transaction details: address, date, price, area, price/m², property type.
|
|
33
33
|
Use list_locations first to find valid location names.
|
|
34
34
|
Example: search for apartments in Mokotów sold in 2024 above 500,000 PLN.`, {
|
|
35
|
-
location: z.string().optional().describe("Location name - city (e.g. 'Kraków', 'Gdańsk') or district (e.g. 'Mokotów', '
|
|
35
|
+
location: z.string().optional().describe("Location name - city (e.g. 'Warszawa', 'Kraków', 'Gdańsk') or district (e.g. 'Mokotów', 'Kraków-Podgórze'). 'Warszawa', 'Kraków', 'Łódź' auto-expand to all sub-districts. Use list_locations to find valid names."),
|
|
36
36
|
propertyType: z.enum(["land", "building", "developed_land", "unit"]).optional()
|
|
37
37
|
.describe("Property type filter"),
|
|
38
38
|
marketType: z.enum(["primary", "secondary"]).optional()
|
|
39
39
|
.describe("Market type: primary (developer) or secondary (resale)"),
|
|
40
|
+
unitFunction: z.enum(["residential", "commercial", "office", "production", "garage", "other"]).optional()
|
|
41
|
+
.describe("Unit/apartment function filter"),
|
|
42
|
+
buildingType: z.enum(["residential", "commercial", "industrial", "transport", "office", "warehouse", "education_sports", "farm_utility", "hospital", "other_nonresidential"]).optional()
|
|
43
|
+
.describe("Building type filter (PKOB classification)"),
|
|
44
|
+
mpzpDesignation: z.string().optional()
|
|
45
|
+
.describe("MPZP zoning designation filter (exact match, e.g. 'budownictwoMieszkanioweWielorodzinne', 'terenObiektowProdukcyjnychSkladowIMagazynow')"),
|
|
40
46
|
minPrice: z.number().optional().describe("Minimum price in PLN"),
|
|
41
47
|
maxPrice: z.number().optional().describe("Maximum price in PLN"),
|
|
42
48
|
dateFrom: z.string().optional().describe("Start date (YYYY-MM-DD)"),
|
|
@@ -60,6 +66,9 @@ Example: search for apartments in Mokotów sold in 2024 above 500,000 PLN.`, {
|
|
|
60
66
|
district: params.location,
|
|
61
67
|
propertyType: mapPropertyType(params.propertyType),
|
|
62
68
|
marketType: mapMarketType(params.marketType),
|
|
69
|
+
unitFunction: mapUnitFunction(params.unitFunction),
|
|
70
|
+
buildingType: mapBuildingType(params.buildingType),
|
|
71
|
+
mpzpDesignation: params.mpzpDesignation,
|
|
63
72
|
minPrice: params.minPrice,
|
|
64
73
|
maxPrice: params.maxPrice,
|
|
65
74
|
dateFrom: params.dateFrom,
|
|
@@ -83,14 +92,20 @@ Example: search for apartments in Mokotów sold in 2024 above 500,000 PLN.`, {
|
|
|
83
92
|
// ── Tool 2: get_price_statistics ────────────────────────────────────
|
|
84
93
|
server.tool("get_price_statistics", `Get price per m² statistics by location for residential apartments in Poland.
|
|
85
94
|
Note: only covers residential units (lokale mieszkalne). For other property types, use search_transactions.
|
|
86
|
-
|
|
87
|
-
location: z.string().optional().describe("Filter by location name
|
|
95
|
+
'Warszawa'/'Kraków'/'Łódź' auto-expand to all sub-districts (Warszawa=19, Kraków=5, Łódź=6). Other names use partial match.`, {
|
|
96
|
+
location: z.string().optional().describe("Filter by location name. 'Warszawa'/'Kraków'/'Łódź' auto-expand to all sub-districts. Other names use case-insensitive partial match (e.g. 'Wrocł' matches 'Wrocław'). Omit for all Poland."),
|
|
88
97
|
}, { readOnlyHint: true }, async (params) => withErrorHandling(async () => {
|
|
89
98
|
requireApiKey(apiKey);
|
|
90
99
|
const { data: allRows, creditInfo } = await getPricePerM2(apiKey);
|
|
91
100
|
let rows = allRows;
|
|
92
101
|
if (params.location) {
|
|
93
|
-
|
|
102
|
+
if (CITY_SUBDISTRICTS.has(params.location)) {
|
|
103
|
+
const allowed = new Set(expandDistrict(params.location));
|
|
104
|
+
rows = rows.filter((r) => allowed.has(r.district));
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
rows = rows.filter((r) => filterByLocation(params.location, [r.district]).length > 0);
|
|
108
|
+
}
|
|
94
109
|
}
|
|
95
110
|
return textResponse(formatPriceStats(rows, params.location) + formatCreditFooter(creditInfo));
|
|
96
111
|
}));
|
|
@@ -109,7 +124,8 @@ Useful for understanding the overall market price structure in Poland.`, {
|
|
|
109
124
|
// ── Tool 4: search_by_area ──────────────────────────────────────────
|
|
110
125
|
server.tool("search_by_area", `Search real estate transactions within a geographic radius.
|
|
111
126
|
Provide latitude/longitude coordinates and a radius in km.
|
|
112
|
-
Example: find apartment sales within 2km of Warsaw's Palace of Culture (lat 52.2317, lng 21.0060)
|
|
127
|
+
Example: find apartment sales within 2km of Warsaw's Palace of Culture (lat 52.2317, lng 21.0060).
|
|
128
|
+
Area filters (minArea/maxArea) work for all propertyType values via COALESCE(usable_area_m2, parcel_area).`, {
|
|
113
129
|
latitude: z.number().min(49).max(55)
|
|
114
130
|
.describe("Latitude (Poland range: 49-55)"),
|
|
115
131
|
longitude: z.number().min(14).max(25)
|
|
@@ -120,8 +136,16 @@ Example: find apartment sales within 2km of Warsaw's Palace of Culture (lat 52.2
|
|
|
120
136
|
.describe("Property type filter"),
|
|
121
137
|
marketType: z.enum(["primary", "secondary"]).optional()
|
|
122
138
|
.describe("Market type filter"),
|
|
139
|
+
unitFunction: z.enum(["residential", "commercial", "office", "production", "garage", "other"]).optional()
|
|
140
|
+
.describe("Unit/apartment function filter"),
|
|
141
|
+
buildingType: z.enum(["residential", "commercial", "industrial", "transport", "office", "warehouse", "education_sports", "farm_utility", "hospital", "other_nonresidential"]).optional()
|
|
142
|
+
.describe("Building type filter (PKOB classification)"),
|
|
123
143
|
minPrice: z.number().optional().describe("Minimum price in PLN"),
|
|
124
144
|
maxPrice: z.number().optional().describe("Maximum price in PLN"),
|
|
145
|
+
minArea: z.number().optional()
|
|
146
|
+
.describe("Minimum area in m² (usable_area_m2 for units, parcel_area for land)"),
|
|
147
|
+
maxArea: z.number().optional()
|
|
148
|
+
.describe("Maximum area in m²"),
|
|
125
149
|
dateFrom: z.string().optional().describe("Start date (YYYY-MM-DD)"),
|
|
126
150
|
dateTo: z.string().optional().describe("End date (YYYY-MM-DD)"),
|
|
127
151
|
limit: z.number().min(1).max(50).default(20)
|
|
@@ -133,8 +157,12 @@ Example: find apartment sales within 2km of Warsaw's Palace of Culture (lat 52.2
|
|
|
133
157
|
bbox: bbox.join(","),
|
|
134
158
|
propertyType: mapPropertyType(params.propertyType),
|
|
135
159
|
marketType: mapMarketType(params.marketType),
|
|
160
|
+
unitFunction: mapUnitFunction(params.unitFunction),
|
|
161
|
+
buildingType: mapBuildingType(params.buildingType),
|
|
136
162
|
minPrice: params.minPrice,
|
|
137
163
|
maxPrice: params.maxPrice,
|
|
164
|
+
minArea: params.minArea,
|
|
165
|
+
maxArea: params.maxArea,
|
|
138
166
|
dateFrom: params.dateFrom,
|
|
139
167
|
dateTo: params.dateTo,
|
|
140
168
|
limit: params.limit,
|
|
@@ -213,6 +241,12 @@ Example: {"type":"Polygon","coordinates":[[[21.0,52.2],[21.01,52.2],[21.01,52.21
|
|
|
213
241
|
.describe("Property type filter"),
|
|
214
242
|
marketType: z.enum(["primary", "secondary"]).optional()
|
|
215
243
|
.describe("Market type filter"),
|
|
244
|
+
unitFunction: z.enum(["residential", "commercial", "office", "production", "garage", "other"]).optional()
|
|
245
|
+
.describe("Unit/apartment function filter"),
|
|
246
|
+
buildingType: z.enum(["residential", "commercial", "industrial", "transport", "office", "warehouse", "education_sports", "farm_utility", "hospital", "other_nonresidential"]).optional()
|
|
247
|
+
.describe("Building type filter (PKOB classification)"),
|
|
248
|
+
mpzpDesignation: z.string().optional()
|
|
249
|
+
.describe("MPZP zoning designation filter (exact match)"),
|
|
216
250
|
minPrice: z.number().optional().describe("Minimum price in PLN"),
|
|
217
251
|
maxPrice: z.number().optional().describe("Maximum price in PLN"),
|
|
218
252
|
dateFrom: z.string().optional().describe("Start date (YYYY-MM-DD)"),
|
|
@@ -229,6 +263,9 @@ Example: {"type":"Polygon","coordinates":[[[21.0,52.2],[21.01,52.2],[21.01,52.21
|
|
|
229
263
|
polygon: params.polygon,
|
|
230
264
|
propertyType: mapPropertyType(params.propertyType),
|
|
231
265
|
marketType: mapMarketType(params.marketType),
|
|
266
|
+
unitFunction: mapUnitFunction(params.unitFunction),
|
|
267
|
+
buildingType: mapBuildingType(params.buildingType),
|
|
268
|
+
mpzpDesignation: params.mpzpDesignation,
|
|
232
269
|
minPrice: params.minPrice,
|
|
233
270
|
maxPrice: params.maxPrice,
|
|
234
271
|
dateFrom: params.dateFrom,
|
|
@@ -252,6 +289,12 @@ Example: compare Mokotów, Wola, Ursynów for apartments.`, {
|
|
|
252
289
|
.describe("Property type filter (recommended - API requires at least one filter)"),
|
|
253
290
|
marketType: z.enum(["primary", "secondary"]).optional()
|
|
254
291
|
.describe("Market type filter"),
|
|
292
|
+
unitFunction: z.enum(["residential", "commercial", "office", "production", "garage", "other"]).optional()
|
|
293
|
+
.describe("Unit/apartment function filter"),
|
|
294
|
+
buildingType: z.enum(["residential", "commercial", "industrial", "transport", "office", "warehouse", "education_sports", "farm_utility", "hospital", "other_nonresidential"]).optional()
|
|
295
|
+
.describe("Building type filter (PKOB classification)"),
|
|
296
|
+
mpzpDesignation: z.string().optional()
|
|
297
|
+
.describe("MPZP zoning designation prefix filter (e.g. 'terenRolniczy', 'budownictwoMieszkanioweJednorodzinne', 'budownictwoMieszkanioweWielorodzinne')"),
|
|
255
298
|
minPrice: z.number().optional().describe("Minimum price in PLN"),
|
|
256
299
|
maxPrice: z.number().optional().describe("Maximum price in PLN"),
|
|
257
300
|
dateFrom: z.string().optional().describe("Start date (YYYY-MM-DD)"),
|
|
@@ -265,6 +308,9 @@ Example: compare Mokotów, Wola, Ursynów for apartments.`, {
|
|
|
265
308
|
districts: params.districts,
|
|
266
309
|
propertyType: mapPropertyType(params.propertyType),
|
|
267
310
|
marketType: mapMarketType(params.marketType),
|
|
311
|
+
unitFunction: mapUnitFunction(params.unitFunction),
|
|
312
|
+
buildingType: mapBuildingType(params.buildingType),
|
|
313
|
+
mpzpDesignation: params.mpzpDesignation,
|
|
268
314
|
minPrice: params.minPrice,
|
|
269
315
|
maxPrice: params.maxPrice,
|
|
270
316
|
dateFrom: params.dateFrom,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cenogram/mcp-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "MCP Server for Polish real estate transaction data (7M+ transactions from RCN)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,23 +10,37 @@
|
|
|
10
10
|
"dist"
|
|
11
11
|
],
|
|
12
12
|
"scripts": {
|
|
13
|
-
"build": "tsc",
|
|
13
|
+
"build": "tsc -p tsconfig.build.json",
|
|
14
14
|
"dev": "tsx src/index.ts",
|
|
15
15
|
"test": "vitest run",
|
|
16
|
-
"
|
|
16
|
+
"lint": "eslint .",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"check": "tsc --noEmit && eslint .",
|
|
19
|
+
"prepublishOnly": "npm run lint && npm run test && npm run build"
|
|
17
20
|
},
|
|
18
21
|
"dependencies": {
|
|
19
22
|
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
20
23
|
"zod": "^3.24.0"
|
|
21
24
|
},
|
|
22
25
|
"devDependencies": {
|
|
26
|
+
"@eslint/js": "^9.13.0",
|
|
23
27
|
"@types/node": "^22.0.0",
|
|
28
|
+
"eslint": "^9.13.0",
|
|
24
29
|
"tsx": "^4.19.0",
|
|
25
30
|
"typescript": "^5.6.0",
|
|
26
|
-
"
|
|
31
|
+
"typescript-eslint": "^8.11.0",
|
|
32
|
+
"vitest": "^4.1.4"
|
|
27
33
|
},
|
|
28
34
|
"mcpName": "pl.cenogram/mcp-server",
|
|
29
|
-
"keywords": [
|
|
35
|
+
"keywords": [
|
|
36
|
+
"mcp",
|
|
37
|
+
"real-estate",
|
|
38
|
+
"poland",
|
|
39
|
+
"rcn",
|
|
40
|
+
"nieruchomosci",
|
|
41
|
+
"property",
|
|
42
|
+
"transactions"
|
|
43
|
+
],
|
|
30
44
|
"license": "MIT",
|
|
31
45
|
"publishConfig": {
|
|
32
46
|
"access": "public"
|