@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 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),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cenogram/mcp-server",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "MCP Server for Polish real estate transaction data (7M+ transactions from RCN)",
5
5
  "type": "module",
6
6
  "bin": {