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 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.
- Oct 28, 2026Anthropic Turns Claude Code Into a Platform: Claude Mods Flip the Agent Harness
- Oct 28, 2026Security engineer builds an MCP server in 7 days that spooks an AI agent with vulnerabilities
- Oct 28, 2026Bayer Launches Multi-Agent Engine on AgentCore: 80% of Questions Handled Without Engineers
- Oct 28, 2026Liquid AI releases LFM2.5-24B-A2B: an agent model with 2B active parameters
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
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.
The AI world, distilled — once a week
Seven stories that actually mattered, hand-picked. No noise, no reposts, no press releases.
Done! Check your inbox for a confirmation.