cenogram-mcp-server
OfficialServer Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| MCP_PORT | No | HTTP server port (HTTP mode only) | 3002 |
| MCP_TRANSPORT | No | Set to 'http' for Streamable HTTP mode | stdio |
| CENOGRAM_API_KEY | Yes | API key from cenogram.pl/api (required for stdio mode) | |
| CENOGRAM_API_URL | No | API base URL | https://cenogram.pl |
| CENOGRAM_CLIENT_ID | No | Persistent client identifier |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": true
} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| search_transactionsA | Find individual Polish real-estate transactions from the national RCN registry (8M+ notarial deeds, not asking prices) by place, property type, price, area, date or street. Returns address, date, price, area, price/m² and property type. Resolve the place first: list_locations(search=...), then pass its TERYT code as teryt= (exact match). Use location= only for a name that result flags as an RCN district (rcn_district) - other names silently return zero rows. Neighborhoods (osiedla) are not TERYT districts: use search_by_area. Data notes: marketType is NULL for ~55% of records (filtering by it drops them); ~1.7% have no transaction_date. Permalink to a result: https://cenogram.pl/ceny-transakcyjne?src=mcpstdio#v=1&lat=&lng=&z=16&tx= ( from the "id:" line, / from "Location: °N, °E"; omit &tx= for the area only). Inline [...] notes flag computed values (summed or converted parcel area, inferred property type) and approximated streets; all else is as recorded in the deed. |
| get_price_statisticsA | Price per m² statistics for residential apartments (lokale mieszkalne) by location; for other property types use search_transactions. Default window: last 24 months (same as the search_transactions median); period="all" for full history. The response states the period used. 'Warszawa'/'Kraków'/'Łódź' auto-expand to all sub-districts; other names use partial match. Based on notarial-deed prices, not asking prices; coverage varies by county (some have data gaps of 5+ years). |
| get_price_distributionA | National histogram (all Poland) of transactions per price range - use it for the overall price structure of the market. No location filter (location/teryt are ignored); for one place use get_price_statistics or search_transactions. |
| search_by_areaA | Find transactions within a radius of a point - the tool for neighborhoods/osiedla (not TERYT districts) and street-level searches. Returns TRANSACTIONS (deeds and prices), despite the name; for the land plots themselves in an area use list_parcels_in_area. Radius: 0.3-0.5 km for a street, 0.5-1 km for a neighborhood, 2-5 km for a city area; the area is a bounding box around the radius, so corners extend beyond radiusKm. Take coordinates from the user, never invent them. minArea/maxArea work for every propertyType. Costs 3 API tokens (1 with summaryOnly). Permalink to a result: https://cenogram.pl/ceny-transakcyjne?src=mcpstdio#v=1&lat=&lng=&z=16&tx= ( from the "id:" line, / from "Location: °N, °E"; omit &tx= for the area only). |
| get_market_overviewA | Start here for the shape of the whole Polish transaction database (not one place; for a place use get_price_statistics or search_transactions): total count, date range, breakdown by property and market type, top locations, price statistics. marketType is unknown for ~55% of records, transaction_date missing for ~1.7%. |
| list_locationsA | Resolve a place name to TERYT codes, or browse the TERYT hierarchy - the first step before search_transactions (pass the returned code as teryt=).
|
| search_parcelsA | Autocomplete land parcels by cadastral ID PREFIX only, never by area: for the parcels in a place or shape use list_parcels_in_area; to turn a coordinate or 'locality + number' into one parcel use resolve_parcel (a street address: list_parcels_in_area). Returns matching parcels with district, area and GPS coordinates (e.g. prefix '146518_8.01'). Coverage: the cadastral register we hold is near-complete but not live, so an empty result more likely means a recent change or a mistyped prefix than that no such parcel exists - never report it as "this parcel does not exist" (resolve_parcel with the FULL id can still confirm and add one). Free: costs no API tokens. |
| resolve_parcelA | Resolve a land parcel to its cadastral identity using exactly ONE of:
|
| get_parcel_reportA | The whole dossier for one land parcel in one call - prefer it over the per-layer parcel tools. Contains the parcel core (location, area, land use, plan designation); thirteen layers (flood, heritage, landslide, subsurface, nuisance surroundings, transit, general-plan zoning, buildings, building activity, agricultural eligibility, land-use and soil classification, nature, road access); the parcel's transactions (newest first, up to 20); local price context (county and gmina median zł/m², last 12 months); municipal context (demographics, infrastructure signals); and a parcel-shape section (outline geometry only). For a coordinate or 'locality + number' use resolve_parcel first; for a street address use list_parcels_in_area. Every section has its own state: covered; covered_no_data (checked, nothing found; billed); not_covered (outside our data; refunded); not_computed (did not finish in time; refunded - the report can be partial). Context sections use full / low_sample / suppressed / no_data (shape: full / no_data). Long lists are capped with a count of the rest. Reading it: the nearby designation signal comes from RCN deeds of neighbouring land, not from the planning register - it describes the neighbourhood, not this parcel; confirm with the local plan (MPZP) or a zoning decision. Neither it nor the shape section (geometry only) says whether a parcel can be built on or divided. Construction age is an ESTIMATE with an interval (permit records start in 2016), often "not established". Costs 45 API tokens, billed by outcome: an unresolvable parcel is refunded in full; a resolved parcel with no layer data pays only the 1-token core floor; with at least one covered layer, in full. Requests are rate-limited per API key per minute, so parallel agents sharing one key share the same limit. On a "Too many requests" response, wait the given seconds and retry sequentially. |
| get_parcel_land_classA | The official land-use and soil-quality classification of one land parcel and what it implies for taking the land out of agricultural use. Use it only for that question; for anything else about the parcel (prices, flood, zoning, buildings, permits, surroundings, transport) call get_parcel_report (one flat-price call, includes this classification). Returns the land-use categories and soil grades entered for the parcel, whether any grade is in the protected I-III range, whether the parcel is inside a city boundary (changes the rule), and a re-designation note with the date the legal state was verified. They come back as SETS: the source records no area for any of them, so the answer can never say which category prevails or give a share. Answers only where the county publishes the classification; many counties, including several large cities, do not. States: covered; covered_no_data (published, this parcel has none; billed); not_covered (county does not publish it, or we do not hold the parcel; refunded); not_computed (did not finish in time; refunded, retry). Costs 4 API tokens, refunded on not_covered and not_computed. Not legal advice, and never a statement that a parcel can or cannot be built on. |
| list_parcels_in_areaA | List cadastral parcels in an area - the land plots themselves, NOT transactions (for deeds and prices use search_by_area or search_by_polygon). For an id prefix use search_parcels; for a coordinate or 'locality + number' use resolve_parcel (a street address goes here, via street= and buildingNumber=); for everything about one parcel use get_parcel_report. Name the area (at least one): teryt (codes from list_locations); location (county, city or gmina NAME; a name shared by several units is an error listing the candidates, never a guess); bbox; lat + lng + radiusKm (a circle; not together with a bbox); polygon (on its own). Two answer shapes, priced differently: the default LIGHT list (parcel id, district, centre point, held street address; 2 API tokens; cursor paging) and OUTLINES, with includeGeometry=true (needs a bbox) or a polygon (GeoJSON polygon per parcel; 5 API tokens; max 100 parcels, 50 by default; no paging). An outline answer marked TRUNCATED is an arbitrary subset (not the first, nearest or largest): never report it as the area's parcel list, ask for a smaller area. Light-list filters (not on outline calls) need a narrow scope - a bbox, a circle, a location name, or a teryt of at least 4 digits (county) - and do not count as naming the area. minArea / maxArea (m²) = the registered parcel surface.
Coverage has two gaps. (1) The cadastral data we hold is near-complete but not live (measured per county; fresh splits, merges or renumbering may be missing), for EVERY entrance: a short list or no truncation marker never means you have every parcel. Each answer has a corpus_coverage block (figures for teryt/location, null for bbox/circle/polygon). (2) bbox, circle and polygon miss the few held parcels whose outline is in an unreadable coordinate system; teryt/location list those with no location. A returned parcel is real; never read an empty or short answer as proof the land is not there. Limits: an area over 500 km², a radius over 12.6 km or a polygon over 500 vertices is refused. |
| search_by_polygonA | Find transactions inside a GeoJSON Polygon - for precise neighborhood/osiedla boundaries; for a quick look start with search_by_area (its results can help estimate coordinates). Returns TRANSACTIONS (deeds and prices) with coordinates, despite the name; for the land plots inside a drawn shape pass the same polygon to list_parcels_in_area. Costs 5 API tokens. Permalink to a result: https://cenogram.pl/ceny-transakcyjne?src=mcpstdio#v=1&lat=&lng=&z=16&tx= ( from the "id:" line, / from "Location: °N, °E"; omit &tx= for the area only). |
| compare_locationsA | Compare 2-5 districts side-by-side: median price/m², average area and transaction counts. Matches names only: call list_locations(search=...) first and use names it flags as RCN districts (rcn_district) - other names (most TERYT unit names) silently return no data. Needs at least one filter besides districts (e.g. propertyType). |
| get_demographicsA | Local statistics for a Polish location from GUS BDL (Poland's Central Statistical Office): ~50 indicators across 11 categories (population, economy, housing, planning, infrastructure, environment, safety, education, prices) plus a few derived metrics. Address by location (city, county or gmina name; a city or county resolves to county level) OR teryt (codes from list_locations). Neighborhoods/osiedla are NOT addressable. Returns the requested level PLUS all parent levels. Optional year, or yearFrom+yearTo for a time series, and category. Cost: 1 token. |
| get_infrastructure_signalsA | Signals that a Polish municipality is about to build infrastructure, from three public sources: tenders in the national procurement bulletin (rolling 12 months), membership in an agglomeration of the national urban waste-water programme, and the municipality's planned capital expenditure from its multi-year forecast. Address by location (city/county name → every municipality in the county) OR teryt (codes from list_locations; wins when both are given). State these limits when reporting: the bulletin has only contracts BELOW EU thresholds (from 2021), so the largest investments are invisible; a tender is attributed to the contracting authority's SEAT, not the works location, so the counters include municipal authorities only while the notice list shows every authority with a flag. Absence of tenders is NOT evidence that a municipality is not investing. Cost: 1 token. |
| estimate_valueA | [Beta] Estimate an apartment's market value from comparable registered transaction prices near a point. An orientation estimate, NOT a certified appraisal (operat szacunkowy): it ignores condition, finish standard and floor. Address by lat + lng OR parcelId (the parcel centroid is used) - exactly one. area (usable m², 10–250) is REQUIRED. Optional: rooms (1–10) and market (primary/secondary) narrow the comparables; includeComps (default true) echoes them. Returns the point estimate, a likely and a wide range, a confidence band, the comparable count and an as_of date; as_of lags by county, so estimates are NOT comparable across cities with different as_of. Apartments only; for a building plot or farmland use estimate_land_value. Too few comparables → no estimate. Costs 5 API tokens, refunded when no estimate is produced. Aggregates exclude fractional shares and non-market deeds. |
| estimate_land_valueA | [Beta] Estimate an undeveloped plot's market value from comparable registered transaction prices near a point. An orientation estimate, NOT a certified appraisal (operat szacunkowy). Address by lat + lng OR parcelId (the parcel centroid is used) - exactly one. area (m², 100–1000000) is REQUIRED with lat/lng; with parcelId it defaults to the cadastre area. landType: plot (building plot, default) or agricultural (farmland) - the caller chooses. Optional mpzpDesignation narrows the comparables; includeComps (default true) echoes them. Returns the estimate per m² and per hectare, a likely and a wide range, a confidence band, the comparable count and an as_of date (as_of lags by county: estimates are NOT comparable across areas with different as_of). Comparables are chosen by the designation recorded in the transaction register (not a local zoning plan), so this does not assert the plot is buildable. Undeveloped land only. Too few comparables, or an area that cannot be determined → no estimate. Costs 5 API tokens, refunded when no estimate is produced. It can be switched off for maintenance (a plain "temporarily unavailable" reply). Aggregates exclude fractional shares and non-market deeds. |
| get_building_breakdownA | Building-by-building breakdown for one transaction: footprint area, storeys and estimated total floor area (footprint × storeys) of each building on the property. Use it after a search whose result has building data (searches return per-transaction building SUMS inline) - e.g. a developed-land deed covering several buildings. Each building carries a construction-age ESTIMATE with an interval, derived from building-permit records: never a registry construction date, and the records only start in 2016, so "construction year not established" is the usual honest answer. Cost: 4 tokens. Returns nothing for a transaction with no buildings. |
| get_transaction_floodA | Parcel-by-parcel flood-hazard breakdown for one transaction: per linked plot in a mapped flood zone - the worst hazard category (high/medium/low, ~1-in-10-year to ~1-in-500-year), hazard type (river/coastal/infrastructure), the share of the plot inside the zone and the per-scenario list with return periods. Use it after a search whose result shows flood_risk (search_transactions and search_by_area surface it inline; search_by_polygon does not). For a parcel with no transaction, get_parcel_report lists the same scenarios. Land in no mapped zone returns nothing - absence of a zone is never asserted as "safe". Cost: 4 tokens (refunded when there is no flood data). |
| get_transaction_heritageA | Parcel-by-parcel heritage-listing breakdown for one transaction: per linked plot with a detected listing - the status (listed = a protected monument on/at the plot; zone = within a protected urban layout or the designated surroundings of a monument), the share of the plot inside the protected area (when measurable) and the entries (category, name, function, period, entry date). Use it after a search whose result shows a heritage listing (search_transactions and search_by_area surface heritage_status inline; search_by_polygon does not). For a parcel with no transaction, get_parcel_report lists the same entries. No detected listing returns nothing - absence of a detection is never "not listed". Indicative data: the regional heritage conservator makes the binding determination. Cost: 4 tokens (refunded when there is no heritage data). |
| get_transaction_landslideA | Parcel-by-parcel landslide-hazard breakdown for one transaction, from official landslide-hazard maps (1:10,000): per linked plot intersecting a mapped hazard area - the worst category ('landslide' = a mapped landslide area, 'threatened' = an area threatened by mass movements), the share of the plot inside the zones, and the per-zone list (with source_version_date, the source-record version date, not an observation date). At this scale an intersection means overlap with a mapped area, not that the parcel itself is a landslide. Use it after a search whose result shows landslide_risk (inline in search_transactions and search_by_area, not search_by_polygon). For a parcel with no transaction, get_parcel_report gives the share and zone kinds. Land in no mapped zone returns nothing - absence of data is never an assertion of safety. Cost: 4 tokens (refunded when there is no landslide data). |
| get_transaction_subsurfaceA | Use it to see whether one transaction's land overlaps a mining terrain or a major groundwater reservoir: per linked plot overlapping either - the mining-terrain status ('active' | 'former') with mineral class ('subsidence' = extraction with surface deformation, the actual mining-damage risk; 'surface' = open-pit, mostly local impact; 'fluid' = borehole extraction; 'other'), the reservoir status ('documented' | 'undocumented'), the share of the plot inside each, and per-object details. A mining terrain is a legally defined zone of anticipated mining influence with an approximate mapped location: an intersection is an advisory signal to verify with the competent mining-supervision authority, not a legal determination. A reservoir's extent alone imposes NO restriction; one would come only from a protection zone, which is not published here. For a parcel with no transaction, get_parcel_report lists the same objects. Land overlapping neither layer returns nothing - absence of data is never an assertion of safety. Cost: 4 tokens (refunded when there is no subsurface data). |
| get_transaction_roadsA | Plot-by-plot road-access evidence for one transaction, to judge how its land sits relative to the road network: per linked plot, the distance in meters to the nearest public road (500 m), the nearest road of any kind (500 m) and the nearest motorway/expressway/dual-carriageway (3 km; a traffic-nuisance proxy, not access) - plus, for the nearest public road, the estimated distance to its carriageway EDGE (null when the source has no width), its management category and functional class, and whether it runs at ground level (false = viaduct or tunnel). Each plot carries access_indicator (likely / uncertain / unlikely) and its rule version. access_indicator is GEOMETRIC EVIDENCE from carriageway centrelines in reference road data. It does NOT determine legal access or say anything about easements and rights of way (land register, not published here) - a lead to verify, never a conclusion. A null distance means no such road within the radius in the reference data - NEVER a guarantee none exists; assessed=false = not evaluated yet. For a parcel with no transaction, get_parcel_report has a one-line road-access summary. Cost: 4 tokens (refunded when there is no informative data). |
| get_transaction_surroundingsA | Plot-by-plot surroundings profile for one transaction, for due diligence on nearby nuisances: per linked plot, the distance in meters to the nearest cemetery (1 km radius), landfill (3 km), sewage treatment plant (2 km), industrial/storage area (1 km), large industrial plant (3 km), intensive livestock farm (3 km), high-voltage overhead power line (1 km) and extra-high-voltage overhead power line (1 km). Distances are approximate, from the plot boundary; 0 = the plot touches or overlaps. Only overhead high- and extra-high-voltage lines are covered (medium and low voltage are ubiquitous). For a parcel with no transaction, get_parcel_report lists the same distances. A null distance means no such object within the radius in the reference data - NEVER a guarantee none exists; assessed=false = not evaluated yet. Cost: 4 tokens (refunded when there is no informative data). |
| get_transaction_transitA | Use it to check public-transport access to one transaction's land: for each linked plot, the nearest stop distance by mode (rail/metro within 3000 m, tram 1500 m, bus 1000 m) from open GTFS data, plus the nearest stop's name per mode present. No stop within its cap in any mode returns nothing - absence of a row is never "no transit access" (open feeds cover cities and national rail, not every rural area). Cost: 4 tokens (refunded when there is no transit data). |
| get_transaction_permitsA | Building-permit history for one transaction's parcels, from the national registry of building permits and works notifications (records since 2016): per case - kind (permit / notification), works type, object category, authority, date, address, volume. Use it after a search to screen what was built or approved on the land. Match is by the parcel's current identifier, so splits/merges break the link. Permits are held once a decision is issued (its outcome, granted or refused, is not held); notifications only where accepted without objection; pending cases not at all. For a parcel with no transaction, get_parcel_report lists the cases in its building-activity section. For what is being built around a point, use get_permits_in_area. Three coverage states: covered (at least one registered case), covered_no_data (the transaction exists, its parcels have no case), not_covered (no transaction with this id). An empty result never confirms that nothing was planned. Cost: 4 tokens (refunded when there is no record or no such transaction). |
| get_permits_in_areaA | List the building permits and works notifications registered since 2016 within a radius of a point - issued decisions only, matched through the parcel geometry we hold, which is near-complete but not the whole cadastre - to see what is being built nearby. Returns cases newest first, NOT nearest first (a permit and a notification for one investment are two cases), plus an aggregate by year and statutory object category over ALL matched cases (do not add it up across pages) with the permit/notification split and building volume. truncated = more cases than the limit: pass the returned cursor back as cursor with the same filters. Examples: apartment blocks → categories=[XIII]; drop utility-network noise (XXVI can be most of a list) → excludeCategories=[XXVI]; utility works as a sign that land is being serviced for development → categories=[XXVI]. For "how many / how much" pass aggregateOnly=true (same price). The list is ordered by date: for what is closest use a smaller radiusKm (e.g. 0.3) instead of paging; radiusKm is 0.1–2 (default 2), for a wider area make several calls for neighbouring centres. Reading it: decision outcomes (granted or refused) are not held; notifications only where accepted without objection; pending cases not at all. Matching uses the parcel's current identifier and geometry, so cases filed before a split or merge may be missed, parcels without geometry are not searched and a parcel number with a sheet segment may be matched less exactly. The answer states how many parcels we hold in the circle (0 = no data for the area) and the date of the last refresh: loaded periodically, not live. A volume marked suspect stays in the sums. A short or empty list is never a statement that nothing is being built. A query over the time limit returns an error asking for a smaller radius. Coordinates: for a parcel take them from resolve_parcel (or search_parcels); for a street use list_parcels_in_area(location, street) and the centre point of a returned row; otherwise from the user - never guess them. Cost: 5 API tokens per call, also for an empty list; each page is a separate call. It can be switched off for maintenance (a plain "temporarily unavailable" reply). |
| get_transaction_planningA | General-plan (plan ogólny, POG) zoning for one transaction's land: per linked plot, the zones covering it - symbol, name, share of the plot, the building parameters the plan sets (max height, max development intensity, max built-up coverage, min biologically active area) - plus overlay areas (infill development area / obszar uzupełnienia zabudowy, central development area). Use it for feasibility and permitted-use questions on a plot. Coverage is THREE-STATE: 'covered' returns zone data; 'covered_no_data' = the municipality has an adopted general plan but no zone data covers these plots yet; 'not_covered' = no published general-plan data for the municipality yet - NEVER a claim that it has no plan (general plans are still being adopted). Cost: 4 tokens (refunded for covered_no_data and not_covered). |
| get_transaction_farmlandA | Parcel-by-parcel agricultural land-eligibility breakdown for one transaction, from official nationwide eligibility data (updated weekly): per linked parcel with a matched eligible area - the area in m², its share of the parcel (when the measured area is known) and the number of source features; also how many linked parcels carry a match and the snapshot date. Useful for due diligence on land actually eligible/maintained as agricultural, beyond the paper classification. A parcel with no match returns nothing - absence is NEVER a statement that the property is non-agricultural (small plots not actively farmed are absent, the layer has its own update cadence, older transactions can reference renumbered parcels). Cost: 4 tokens (refunded when there is no eligible area). |
| get_transaction_natureA | Parcel-by-parcel nature breakdown for one transaction: per linked plot with a nature signal - the nearest forest within 2 km (forest_distance_m, 0 = the plot overlaps forest, with the overlap share) and any overlapping protected areas: the sharpest form (protection_rank 1 = national park, 2 = nature reserve, 3 = Natura 2000, 4 = landscape park, 5 = protected landscape, 6 = other), building_restriction ('statutory_ban' = a ban following from the Nature Protection Act for parks and reserves, 'conditional' = depends on the establishing act), the share of the plot under protection and names. Search results carry no nature signal: call this directly on a transaction id when forest proximity or protected-area build limits matter. For a parcel with no transaction, get_parcel_report names the protected areas. It describes the SOURCE of a restriction, not a permitting outcome; not legal advice. An empty result is NEVER a statement that building is allowed (local plans and permit decisions are not covered). A buffer zone around a park or reserve is reported as 'buffer_zone' at rank 6, never as a statutory ban. An empty result says which kind it is: plots checked with no signal, or no nature data held yet - the second is never a finding of no forest or protected area. Cost: 4 tokens (refunded on any empty result). |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
TDQS
Scored across 30 tools
Descriptions actively steer callers between overlapping families (search_transactions vs search_by_area vs search_by_polygon; get_parcel_report vs per-layer get_transaction_* tools), so most boundaries are clear. However, with 30 tools and several near-neighbors (get_transaction_permits vs get_permits_in_area, estimate_value vs estimate_land_value), an agent still needs to read carefully to avoid misselection.
Nearly all tools use a predictable snake_case verb_noun pattern (search_transactions, get_price_statistics, get_transaction_flood, list_locations). The main deviations are the search_by_area/search_by_polygon forms and mixed estimate_* naming, but these remain readable and not chaotic.
The domain is broad, but 30 tools exceeds the recommended range and includes many per-layer transaction detail tools that largely duplicate what get_parcel_report returns. Several could be consolidated into a parameterized transaction-detail or parcel-report call.
The read-only surface is extensive: transaction search by place/radius/polygon, price statistics, parcel resolution and reporting, environmental layers, permits, planning, nature, farmland, valuation, demographics and infrastructure. A generic get_transaction(id) endpoint is notable by its absence, but the layer-specific and aggregate report tools cover most workflows without major dead ends.