<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Yoel Castaño</title>
    <description>The latest articles on DEV Community by Yoel Castaño (@yoel_castao_d5cdcbe81850).</description>
    <link>https://dev.to/yoel_castao_d5cdcbe81850</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4189086%2Fa2b6af6b-448c-4daf-befe-b13001c96595.jpg</url>
      <title>DEV Community: Yoel Castaño</title>
      <link>https://dev.to/yoel_castao_d5cdcbe81850</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yoel_castao_d5cdcbe81850"/>
    <language>en</language>
    <item>
      <title>Querying Germany’s historical street catalogue with MCP</title>
      <dc:creator>Yoel Castaño</dc:creator>
      <pubDate>Fri, 16 Oct 2026 19:22:40 +0000</pubDate>
      <link>https://dev.to/yoel_castao_d5cdcbe81850/querying-germanys-historical-street-catalogue-with-mcp-48m5</link>
      <guid>https://dev.to/yoel_castao_d5cdcbe81850/querying-germanys-historical-street-catalogue-with-mcp-48m5</guid>
      <description>&lt;p&gt;A national street search can return two records with the same name and coordinates. If an assistant turns those into two independent streets, its answer is already misleading before it uses any nearby-service counts.&lt;/p&gt;

&lt;p&gt;Callejear Germany now exposes a public, read-only MCP endpoint for exploring its historical catalogue. This walkthrough uses live responses checked on 16 October 2026 and shows how to keep the record identity and uncertainty intact. Disclosure: this is a maintainer publication, prepared with autonomous AI assistance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect and discover the scope
&lt;/h2&gt;

&lt;p&gt;Add a remote MCP server in a client that supports Streamable HTTP:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Endpoint: &lt;code&gt;https://callejear.de/api/mcp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Authentication: none&lt;/li&gt;
&lt;li&gt;Service name returned by initialization: &lt;code&gt;callejear-germany&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The client handles MCP initialization and tool discovery. The JSON examples below are parameters for the MCP &lt;code&gt;tools/call&lt;/code&gt; method, not standalone REST request bodies.&lt;/p&gt;

&lt;p&gt;Start with coverage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"callejear_get_coverage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:{}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The checked response reports &lt;strong&gt;593,062 historical street records&lt;/strong&gt;, &lt;strong&gt;10,650 municipality records&lt;/strong&gt; and all &lt;strong&gt;16 federal states&lt;/strong&gt;. These are counts in the extract, not a claim that it is a complete or current official register. The export timestamp is not the date of the underlying observations.&lt;/p&gt;

&lt;p&gt;The five tools are coverage discovery, municipality search, street search, street context and comparison. The &lt;a href="https://callejear.de/mcp/" rel="noopener noreferrer"&gt;connection documentation&lt;/a&gt; describes their sources and limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resolve the municipality before the street
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"callejear_search_municipalities"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Berlin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returned Berlin, Berlingen and Berlingerode. The exact Berlin match had state code &lt;code&gt;DE-BE&lt;/code&gt; and municipality ID &lt;code&gt;110000000000&lt;/code&gt;. A text search can include other names containing the search term, so select the municipality explicitly rather than passing the first result through blindly.&lt;/p&gt;

&lt;p&gt;Then search inside that municipality:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"callejear_search_streets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"city_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"110000000000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Unter den Linden"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the interesting part of the response, shortened to identity fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"de:legacy:65eee30dd2de"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"U Unter den Linden"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"catalog_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"point"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;52.517009&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;13.388838&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"identity_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"legacy_not_independently_verified"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"de:legacy:b92a3b00da92"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"U Unter den Linden"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"catalog_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"point"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;52.517009&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;13.388838&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"identity_status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"legacy_not_independently_verified"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both records have the same displayed name and stored point. A distinct database ID does not prove a distinct real-world street. The service preserves the historical records and reports that their identities have not been independently verified. It also omits an ambiguous catalogue link instead of inventing a slug.&lt;/p&gt;

&lt;p&gt;A client should retain that distinction in its display. For this example, “two historical records matched” is supported; “two different streets matched” is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read context without converting observations into claims about today
&lt;/h2&gt;

&lt;p&gt;Use an ID returned by the search:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"callejear_get_street_context"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"street_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"de:legacy:65eee30dd2de"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The checked response contained 6 supermarket, 4 pharmacy, 18 bakery and 59 cafe observations. Every metric was marked &lt;code&gt;partial&lt;/code&gt;, used the unit &lt;code&gt;observed_osm_records&lt;/code&gt;, and had an unknown source update date.&lt;/p&gt;

&lt;p&gt;Those numbers refer to a &lt;strong&gt;500-metre straight-line radius around a stored historical catalogue centroid&lt;/strong&gt;. They are not a count of currently open, independently verified businesses along the whole street. Possible duplicate observations and unknown freshness limit what a client can infer.&lt;/p&gt;

&lt;p&gt;The schema makes that visible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"services.supermarkets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"observed_osm_records"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"partial"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"radius_m"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"distance_method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"historical_haversine"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reference_geometry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"historical_catalog_centroid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_updated_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"computed_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Missing or unverified categories remain null. Do not turn null into zero in a chart or a sentence. Likewise, a fresh API response timestamp does not make its underlying data fresh.&lt;/p&gt;

&lt;p&gt;A useful assistant prompt is: “Find the Berlin municipality, search the historical catalogue for Unter den Linden, and explain the identity and source-date limitations before showing any service observations.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare records without ranking places to live
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;callejear_compare_streets&lt;/code&gt; accepts two to five returned street IDs and puts their context side by side. It does not provide a quality ranking. Comparing the two records above as if they were independent neighbourhood measurements would create a false distinction.&lt;/p&gt;

&lt;p&gt;The service does not expose property prices, safety scores, demographic or political profiles, or housing recommendations. For client developers, the useful pattern is to preserve the response’s sources, limitations and unavailable dimensions through to the final answer.&lt;/p&gt;

&lt;p&gt;Keep requests targeted. The public service allows 30 tool calls per minute and 100 per day per origin, with a shared daily budget of 1,000. Searches return at most 20 results per page. It is a small query interface, not a bulk export API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources and reuse
&lt;/h2&gt;

&lt;p&gt;Source attribution includes © OpenStreetMap contributors and © GeoBasis-DE / BKG. The &lt;a href="https://callejear.de/mcp/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; links the derived data extract and explains the ODbL terms and source attribution. Neither organisation endorses this independent service.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/Habanacasta88/callejear-de-mcp" rel="noopener noreferrer"&gt;public connection repository&lt;/a&gt; contains metadata and documentation, not the hosted implementation’s source. It is also registered as &lt;code&gt;io.github.Habanacasta88/callejear-de&lt;/code&gt; in the official MCP Registry.&lt;/p&gt;

&lt;p&gt;The main integration lesson is to treat a returned ID as a catalogue-record identifier and keep uncertainty attached to it. That makes the tool useful for exploration without asking the model to manufacture certainty the data does not contain.&lt;/p&gt;

</description>
      <category>mcp</category>
    </item>
    <item>
      <title>Building a read-only city-data MCP: street identity, missing data and honest comparisons</title>
      <dc:creator>Yoel Castaño</dc:creator>
      <pubDate>Fri, 16 Oct 2026 17:14:35 +0000</pubDate>
      <link>https://dev.to/yoel_castao_d5cdcbe81850/building-a-read-only-city-data-mcp-street-identity-missing-data-and-honest-comparisons-fcd</link>
      <guid>https://dev.to/yoel_castao_d5cdcbe81850/building-a-read-only-city-data-mcp-street-identity-missing-data-and-honest-comparisons-fcd</guid>
      <description>&lt;p&gt;I work on Callejear, an independent project for exploring municipal data in Spain. We recently exposed a read-only MCP service for Madrid. The useful engineering lesson was that making data callable is only part of the job: the response contract must also prevent an assistant from drawing conclusions the data cannot support.&lt;/p&gt;

&lt;p&gt;Here is a small integration example, followed by the decisions behind the interface. This article was prepared with AI assistance and checked against the project's implementation and documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Discover coverage before answering questions
&lt;/h2&gt;

&lt;p&gt;The service covers the municipality of Madrid, INE code &lt;code&gt;28079&lt;/code&gt;, rather than the whole Madrid region. Its current catalogue contains 9,400 official street codes from the municipal source dated 2026-10-13. Of these, 9,151 have a validated reference point; 249 remain searchable with missing location and metrics.&lt;/p&gt;

&lt;p&gt;That distinction matters: complete coverage of a street catalogue does not imply complete coverage of the services near every street.&lt;/p&gt;

&lt;p&gt;The first tool call should therefore be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"callejear_get_coverage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"city_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"28079"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Search names, then use returned identifiers
&lt;/h2&gt;

&lt;p&gt;A street name is a search term, not a reliable database key. Names have variants, and source records can disagree. The catalogue preserves four official-code/name conflicts explicitly instead of silently choosing a name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"callejear_search_streets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"city_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"28079"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Alcalá"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the identifier returned by the search when calling &lt;code&gt;callejear_get_street_context&lt;/code&gt;. Do not construct an ID from a guessed slug. Search is paginated, with at most 20 results per page; cursors belong to a specific search and expire after 15 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. A minimal JavaScript client
&lt;/h2&gt;

&lt;p&gt;The project's documented client example uses Node 22.22.x and the pinned MCP client package below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-exact&lt;/span&gt; @modelcontextprotocol/client@2.3.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save this as &lt;code&gt;consulta.mjs&lt;/code&gt; and run &lt;code&gt;node consulta.mjs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;StreamableHTTPClientTransport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@modelcontextprotocol/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;madrid-data-example&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1.0.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StreamableHTTPClientTransport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://callejear.com/api/mcp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;callejear_get_coverage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;city_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;28079&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;callejear_search_streets&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;city_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;28079&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Alcalá&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;callTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tool returned an error:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;structuredContent&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an anonymous Streamable HTTP endpoint. The service performs reads only and does not require credentials for other services.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Preserve the difference between null and zero
&lt;/h2&gt;

&lt;p&gt;The four available dimensions count observed records for pharmacies, schools, supermarkets and libraries within 500 metres of a representative point, using Haversine distance.&lt;/p&gt;

&lt;p&gt;They are partial historical observations. They do not measure an entire street, guarantee that a business is still open, or represent a complete inventory of unique establishments.&lt;/p&gt;

&lt;p&gt;Consequently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;null&lt;/code&gt; means unavailable, not zero.&lt;/li&gt;
&lt;li&gt;Zero means no matching records in the extract and search area, not proof that no service exists.&lt;/li&gt;
&lt;li&gt;A higher count is not a score for safety, school quality or suitability for living.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;callejear_compare_streets&lt;/code&gt; accepts two to five distinct street IDs. A client should preserve the definitions, radius, sources and limitations when presenting a comparison, rather than turning a count into an unsupported recommendation.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Treat payload size and tool errors as part of the contract
&lt;/h2&gt;

&lt;p&gt;Responses are capped at 32 KiB. For larger comparisons, the full result is in &lt;code&gt;structuredContent&lt;/code&gt;, while the text content may be a readable summary. A client that always parses &lt;code&gt;content[0].text&lt;/code&gt; as JSON will break this contract.&lt;/p&gt;

&lt;p&gt;Likewise, HTTP success does not guarantee tool success: an exhausted tool quota returns an MCP error result over HTTP 200. Check &lt;code&gt;isError&lt;/code&gt;. HTTP throttling separately uses 429.&lt;/p&gt;

&lt;p&gt;The public pilot has modest limits: 30 tool calls per minute and 100 per UTC day per origin, with a shared daily budget of 1,000. Cached responses also count. Use targeted queries rather than attempting a bulk crawl through the tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources and implementation notes
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://callejear.com/mcp/" rel="noopener noreferrer"&gt;Callejear MCP documentation&lt;/a&gt; includes the connection details, method, data downloads and limitations. The official registry identifier is &lt;code&gt;com.callejear/madrid&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The street catalogue comes from the &lt;a href="https://datos.madrid.es/dataset/213605-0-callejero-oficial-madrid" rel="noopener noreferrer"&gt;Ayuntamiento de Madrid's official street dataset&lt;/a&gt;, with municipal attribution and CC BY 4.0 terms. The OSM-derived extract retains &lt;a href="https://www.openstreetmap.org/copyright" rel="noopener noreferrer"&gt;OpenStreetMap attribution and ODbL terms&lt;/a&gt;. These are data licences, not a claim about a software licence. Neither source endorses this independent service.&lt;/p&gt;

&lt;p&gt;For similar city-data tools, I would make coverage, missingness and provenance explicit in the first version of the response schema. Those details determine which answers a client can responsibly produce.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
