2GIS's MCP Server: How to Stop LLMs From Making Up Addresses and Coordinates

2GIS has launched an MCP server that gives AI agents direct access to geo-services through the Model Context Protocol — the open standard for connecting external tools to models. At first glance the task looks simple: a model without tools invents addresses, so give it access to a database and the problem is solved. In practice it turned out differently: the model starts lying in a new way, one that's much harder to spot and catch. The fix is architecture, not persuasion. 2GIS engineers split the server into a core framework (shared logic, error handling, HTTP pools, logging) and thin tool adapters. That way each new tool adds a single file, with no copy-pasted error handling. The key is a strict contract: a JSON schema for parameters and a clear description for the model, rather than trying to "convince" it through a system prompt. • **Six tools**: place and organization search, forward geocoding, reverse geocoding, route building, isochrones, static maps • **Error separation**: business errors (no access, invalid key) are returned to the model as a tool result, while network failures surface as protocol-level failures • **A typed contract**: Pydantic models for input/output, JSON Schema for parameters, descriptions for sensible invocation — the model only sees what it needs

AI-processed from Habr AI; edited by Hamidun News
2GIS's MCP Server: How to Stop LLMs From Making Up Addresses and Coordinates
Source: Habr AI. Collage: Hamidun News.
Listen to article
Article Fact Sheet & Key Figures
ParameterDetails
Primary sourceHabr AI ↗
Editor-in-Chief
Six toolsplace and organization search, forward geocoding, reverse geocoding, route building, isochrones, static maps
Error separationbusiness errors (no access, invalid key) are returned to the model as a tool result, while network failures surface as protocol-level failures
A typed contractPydantic models for input/output, JSON Schema for parameters, descriptions for sensible invocation — the model only sees what it needs

2GIS has released an MCP server (mcp-2gis) — a tool that connects AI agents directly to the geo-services of the 2GIS API platform. It currently ships with six tools: place and organization search, forward geocoding, reverse geocoding, route building, isochrones, and static maps.

The problem the server solves is a classic one for LLMs: without connected tools, a model asked about a route or the nearest organization answers confidently — and completely makes it up. The obvious next step seems simple: give the model access to real geo-data. In practice, it turned out that access alone isn't enough: once it has tools, the model starts lying differently. It may call a tool incorrectly, pass invalid parameters, or misinterpret the result. And that's much harder to notice, because it sounds far more convincing.

Architecture: a core framework plus thin adapters

The key decision was a scalable architecture. Instead of making every tool fully self-contained with its own error handling, logging, and HTTP pools, 2GIS split the system into layers:

1. FastMCP middleware — authentication, allowlisting, metrics, timeouts 2. tools/<domain>.py — the MCP schema, Pydantic validation of input/output, result mapping 3. tools/gis.py — lifespan dependencies and the shared error-handling boundary 4. integrations/<domain>_api.py — the URLs, parameters, and JSON interaction specific to each 2GIS API 5. integrations/gis_client.py — a shared httpx pool for all requests, API key injection, and conversion of HTTP errors

Each layer is responsible only for its own part. The integration module receives a URL and an API interaction schema, but never sees headers, environment variables, or the MCP layer itself. A tool doesn't create HTTP pools or read environment variables. This separation cuts down on duplication and lets new tools or MCP servers be added without copy-pasting tons of code.

How the calling side works

A single helper function, `run_gis_tool`, assembles all the dependencies, opens a span for the tool call, and translates errors into two distinct contracts. This implements a critical requirement: a business error (an invalid key, lack of access) must reach the agent as a tool result with a machine-readable code, not as a protocol failure. Otherwise the model doesn't understand what to do and simply calls the tool again.

A network failure or timeout, by contrast, must remain an MCP-level error — there's no point reasoning about it, only retrying the request.

Contract instead of persuasion

Adding a new tool requires:

• Pydantic input/output models with types and validation • A JSON Schema — the parameter contract (what's required, what's optional, which values are allowed) • A tool description — the text the model reads when deciding whether to call the tool and with what arguments • Unit tests and in-process integration tests

The description in MCP isn't a comment — it's part of the product. With a poorly written description, the model may either fail to realize the tool is needed or call it incorrectly. JSON Schema rejects unknown values before the request is even made, not after.

Bottom line

This approach solves two deep problems at the intersection of LLMs and geo-APIs: first, that a model makes up addresses at all; second, that it makes them up differently even with tools in hand. The first is solved by giving it tools; the second by a strict typed contract, validation, and a sensible separation between protocol-level and business-level errors.

Story

Key Questions Answered

What are the key details on Architecture: a core framework plus thin adapters?

The key decision was a scalable architecture. Instead of making every tool fully self-contained with its own error handling, logging, and HTTP pools, 2GIS split the system into layers: 1. FastMCP middleware — authentication, allowlisting, metrics, timeouts 2. tools/<domain>.py — the MCP schema, Pydantic validation of input/output, result mapping 3. tools/gis.py — lifespan dependencies and the shared error-handling boundary 4. integrations/<domain>_api.py — the URLs, parameters, and JSON interaction specific to each 2GIS API 5. integrations/gis_client.py — a shared httpx poo

How the calling side works?

A single helper function, `run_gis_tool`, assembles all the dependencies, opens a span for the tool call, and translates errors into two distinct contracts. This implements a critical requirement: a business error (an invalid key, lack of access) must reach the agent as a tool result with a machine-readable code, not as a protocol failure. Otherwise the model doesn't understand what to do and simply calls the tool again. A network failure or timeout, by contrast, must remain an MCP-level error — there's no point reasoning about it, only retrying the request.

What are the key details on Contract instead of persuasion?

Adding a new tool requires: Pydantic input/output models with types and validation A JSON Schema — the parameter contract (what's required, what's optional, which values are allowed) A tool description — the text the model reads when deciding whether to call the tool and with what arguments Unit tests and in-process integration tests The description in MCP isn't a comment — it's part of the product. With a poorly written description, the model may either fail to realize the tool is needed or call it incorrectly. JSON Schema rejects unknown values before the request is even

ZK
Zhemal Khamidun — CPO at Alpina Digital, product manager of AlpinaGPT. Building AI products since 2019. Hamidun News is a pet project that grew into a production aggregator with 6,000+ readers.
✦ Hands-On AI Engineering · Hamidun Academy

Build & Ship Production AI Agents Without Boilerplate Code

Start your free 72-hour trial at Hamidun Academy (academy.hamidun.com) — interactive lessons on Claude Code & Cursor, live Tech Radar, and production agent templates.

Start 72h Free Trial →Vibe-Coding Intensive
What do you think?
Loading comments…