@cenogram/mcp-server 0.1.1 → 0.1.2
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/index.js +0 -4
- package/dist/tools.js +14 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -55,10 +55,6 @@ async function main() {
|
|
|
55
55
|
if (pathname === "/mcp") {
|
|
56
56
|
const auth = req.headers.authorization;
|
|
57
57
|
const apiKeyFromHeader = auth?.startsWith("Bearer ") ? auth.slice(7).trim() : undefined;
|
|
58
|
-
if (!apiKeyFromHeader) {
|
|
59
|
-
res.writeHead(401, { "Content-Type": "application/json" }).end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32001, message: "Authorization: Bearer <api-key> required" }, id: null }));
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
58
|
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
63
59
|
const mcpServer = createMcpServer(apiKeyFromHeader);
|
|
64
60
|
try {
|
package/dist/tools.js
CHANGED
|
@@ -11,6 +11,11 @@ function formatCreditFooter(creditInfo) {
|
|
|
11
11
|
return "";
|
|
12
12
|
return `\n---\nTokeny API: ${creditInfo.balance} pozostało (koszt zapytania: ${creditInfo.cost})`;
|
|
13
13
|
}
|
|
14
|
+
function requireApiKey(apiKey) {
|
|
15
|
+
if (!apiKey) {
|
|
16
|
+
throw new Error("Authorization: Bearer <api-key> required. Get your free API key at https://cenogram.pl/api");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
14
19
|
async function withErrorHandling(fn) {
|
|
15
20
|
try {
|
|
16
21
|
return await fn();
|
|
@@ -50,6 +55,7 @@ Example: search for apartments in Mokotów sold in 2024 above 500,000 PLN.`, {
|
|
|
50
55
|
page: z.number().min(1).default(1).optional()
|
|
51
56
|
.describe("Page number for pagination (default: 1)"),
|
|
52
57
|
}, { readOnlyHint: true }, async (params) => withErrorHandling(async () => {
|
|
58
|
+
requireApiKey(apiKey);
|
|
53
59
|
const txParams = {
|
|
54
60
|
district: params.location,
|
|
55
61
|
propertyType: mapPropertyType(params.propertyType),
|
|
@@ -80,6 +86,7 @@ Note: only covers residential units (lokale mieszkalne). For other property type
|
|
|
80
86
|
For Warsaw: use district names (Mokotów, Wola) - 'Warszawa' won't match any results.`, {
|
|
81
87
|
location: z.string().optional().describe("Filter by location name (case-insensitive partial match). E.g. 'Kraków' matches 'Kraków-Podgórze', 'Kraków-Śródmieście', etc. Omit for all Poland."),
|
|
82
88
|
}, { readOnlyHint: true }, async (params) => withErrorHandling(async () => {
|
|
89
|
+
requireApiKey(apiKey);
|
|
83
90
|
const { data: allRows, creditInfo } = await getPricePerM2(apiKey);
|
|
84
91
|
let rows = allRows;
|
|
85
92
|
if (params.location) {
|
|
@@ -95,6 +102,7 @@ Useful for understanding the overall market price structure in Poland.`, {
|
|
|
95
102
|
maxPrice: z.number().default(3_000_000)
|
|
96
103
|
.describe("Maximum price to include (default 3,000,000 PLN)"),
|
|
97
104
|
}, { readOnlyHint: true }, async (params) => withErrorHandling(async () => {
|
|
105
|
+
requireApiKey(apiKey);
|
|
98
106
|
const { data: bins, creditInfo } = await getPriceHistogram(params.bins, params.maxPrice, apiKey);
|
|
99
107
|
return textResponse(formatHistogram(bins) + formatCreditFooter(creditInfo));
|
|
100
108
|
}));
|
|
@@ -119,6 +127,7 @@ Example: find apartment sales within 2km of Warsaw's Palace of Culture (lat 52.2
|
|
|
119
127
|
limit: z.number().min(1).max(50).default(20)
|
|
120
128
|
.describe("Number of results (1-50, default 20)"),
|
|
121
129
|
}, { readOnlyHint: true }, async (params) => withErrorHandling(async () => {
|
|
130
|
+
requireApiKey(apiKey);
|
|
122
131
|
const bbox = radiusKmToBbox(params.latitude, params.longitude, params.radiusKm);
|
|
123
132
|
const txParams = {
|
|
124
133
|
bbox: bbox.join(","),
|
|
@@ -141,6 +150,7 @@ Example: find apartment sales within 2km of Warsaw's Palace of Culture (lat 52.2
|
|
|
141
150
|
// ── Tool 5: get_market_overview ─────────────────────────────────────
|
|
142
151
|
server.tool("get_market_overview", `Get a comprehensive overview of the Polish real estate transaction database.
|
|
143
152
|
Returns: total transaction count, date range, breakdown by property type and market type, top locations, price statistics.`, {}, { readOnlyHint: true }, async () => withErrorHandling(async () => {
|
|
153
|
+
requireApiKey(apiKey);
|
|
144
154
|
const { data: stats, creditInfo } = await getStats(apiKey);
|
|
145
155
|
return textResponse(formatMarketOverview(stats) + formatCreditFooter(creditInfo));
|
|
146
156
|
}));
|
|
@@ -152,6 +162,7 @@ For Kraków: returns sub-districts (Kraków-Podgórze, Kraków-Śródmieście, e
|
|
|
152
162
|
Use the search parameter to filter by name.`, {
|
|
153
163
|
search: z.string().optional().describe("Filter locations by name (case-insensitive partial match, e.g. 'Krak' for Kraków districts)"),
|
|
154
164
|
}, { readOnlyHint: true }, async (params) => withErrorHandling(async () => {
|
|
165
|
+
requireApiKey(apiKey);
|
|
155
166
|
const { data: allDistricts, creditInfo } = await getDistricts(apiKey);
|
|
156
167
|
let districts = allDistricts;
|
|
157
168
|
if (params.search) {
|
|
@@ -183,6 +194,7 @@ Example: search for parcels starting with '146518_8.01'.`, {
|
|
|
183
194
|
limit: z.number().min(1).max(10).default(10).optional()
|
|
184
195
|
.describe("Max results (1-10, default 10)"),
|
|
185
196
|
}, { readOnlyHint: true }, async (params) => withErrorHandling(async () => {
|
|
197
|
+
requireApiKey(apiKey);
|
|
186
198
|
const { data, creditInfo } = await searchParcels(params.q, params.limit, apiKey);
|
|
187
199
|
return textResponse(formatParcelResults(data, params.q) + formatCreditFooter(creditInfo));
|
|
188
200
|
}));
|
|
@@ -212,6 +224,7 @@ Example: {"type":"Polygon","coordinates":[[[21.0,52.2],[21.01,52.2],[21.01,52.21
|
|
|
212
224
|
limit: z.number().min(1).max(5000).default(100).optional()
|
|
213
225
|
.describe("Max results (1-5000, default 100). MCP displays up to 50 transactions."),
|
|
214
226
|
}, { readOnlyHint: true }, async (params) => withErrorHandling(async () => {
|
|
227
|
+
requireApiKey(apiKey);
|
|
215
228
|
const { data, creditInfo } = await searchByPolygon({
|
|
216
229
|
polygon: params.polygon,
|
|
217
230
|
propertyType: mapPropertyType(params.propertyType),
|
|
@@ -247,6 +260,7 @@ Example: compare Mokotów, Wola, Ursynów for apartments.`, {
|
|
|
247
260
|
maxArea: z.number().optional().describe("Maximum area in m²"),
|
|
248
261
|
street: z.string().optional().describe("Street name filter"),
|
|
249
262
|
}, { readOnlyHint: true }, async (params) => withErrorHandling(async () => {
|
|
263
|
+
requireApiKey(apiKey);
|
|
250
264
|
const { data, creditInfo } = await compareLocations({
|
|
251
265
|
districts: params.districts,
|
|
252
266
|
propertyType: mapPropertyType(params.propertyType),
|