Skip to main content
Glama

sunat-mcp

Servidor MCP para validar y consultar RUCs peruanos contra el Padrón Reducido oficial de SUNAT, indexado localmente.

Los RUCs que consultas no salen de tu máquina.

sunat-mcp resolviendo consultas reales

El GIF no es una recreación: demo/record.py levanta el servidor por stdio con el cliente oficial de MCP, llama a las herramientas y anima las respuestas que realmente devuelve sobre el índice local. Si el índice no existe, aborta en vez de inventar datos.

El problema

Todo estudio contable peruano valida RUCs a diario: verificar que una factura tenga un RUC bien formado, confirmar la razón social de un proveedor, revisar si un contribuyente está activo y habido antes de aceptar un comprobante.

Las soluciones que existen tienen un problema u otro:

  • Consultar la web de SUNAT a mano — lento, imposible de automatizar.

  • Scrapear la web de SUNAT — frágil (se rompe con cada cambio de HTML) y de legalidad discutible.

  • APIs de terceros — requieren llave, tienen límite de consultas y, sobre todo, le envías a un tercero los RUCs de tus clientes. Para un contador con deber de reserva, eso no es un detalle menor.

Este proyecto toma el camino que casi nadie toma: SUNAT publica el padrón completo como datos abiertos. Se descarga una vez, se indexa, y se consulta local.

Related MCP server: mcp-egrul

Cómo funciona

Padrón Reducido oficial (ZIP, ~372 MiB)
        │  scripts/ingest.py — descarga, verifica SHA-256, recorre en streaming
        ▼
data/padron.sqlite3 — índice local, consulta por clave primaria
        │
        ▼
sunat_mcp/server.py — 4 herramientas MCP  →  Claude Code / Claude Desktop

La ingesta nunca carga el archivo completo en memoria: lee el .txt de 1.56 GB línea por línea directamente desde el ZIP.

Herramientas

Herramienta

Qué devuelve

Requiere índice

Red

validar_ruc

Estructura, prefijo y dígito verificador (módulo 11 SUNAT)

No

No

consultar_ruc

Razón social, estado, condición de domicilio, ubigeo, dirección

No

buscar_razon_social

Contribuyentes cuyo nombre coincide

No

estado_padron

Fuente, SHA-256, fecha de SUNAT, fecha de ingesta, nº de registros

No

validar_ruc funciona sin haber construido el índice: es aritmética pura.

Instalación

Requiere Python 3.10+.

pip install mcp-sunat

O sin instalar nada, directo desde PyPI:

uvx mcp-sunat

El paquete se publica como mcp-sunat; el módulo que se importa es sunat_mcp y el repositorio se llama sunat-mcp. Es la misma distinción que entre pillow y PIL.

Para trabajar sobre el código o construir el índice:

git clone https://github.com/r2nochi/sunat-mcp
cd sunat-mcp
python -m venv .venv
.\.venv\Scripts\pip.exe install -e ".[dev,ingest]"

Construir el índice

.\.venv\Scripts\python.exe scripts\ingest.py

Descarga el ZIP oficial, verifica su checksum y construye el índice.

Ten en cuenta: descarga ~372 MiB y el índice resultante ocupa varios GB en disco. Es una operación de una sola vez; para actualizar, vuelve a correrlo.

Para probar sin bajar todo el padrón:

.\.venv\Scripts\python.exe scripts\ingest.py --limite 50000

El índice quedará marcado como muestra parcial y estado_padron lo avisará.

Rendimiento medido

Sobre el padrón completo del 27/07/2026: 18,297,300 contribuyentes, índice de 2.14 GB (Windows 11, SSD, Python 3.14).

Operación

Tiempo

Plan de SQLite

consultar_ruc (clave primaria)

0.2 ms

SEARCH ... USING PRIMARY KEY

buscar_razon_social por prefijo

0.2 ms

SEARCH ... USING COVERING INDEX

buscar_razon_social por subcadena

~40 s

SCAN (inevitable, es un LIKE '%x%')

Ingesta completa (descarga + índice)

~22 min

Por qué el índice usa COLLATE NOCASE

La primera versión indexaba razon_social con la colación por defecto (BINARY) y buscaba con LIKE 'TEXTO%'. La búsqueda por prefijo tardaba 39,848 ms.

El motivo: el LIKE de SQLite es case-insensitive por defecto, y por eso no puede usar un índice BINARY — degeneraba en un SCAN de los 18.3 millones de filas. Había además un problema de correctitud: el padrón trae algunas razones sociales en minúscula, que una comparación binaria habría perdido en silencio.

La solución fue indexar con COLLATE NOCASE y resolver el prefijo como un rango (>= 'TEXTO' AND < 'TEXTO' + centinela) en lugar de un LIKE:

antes:  SCAN contribuyente USING COVERING INDEX idx_razon_social      39,848 ms
ahora:  SEARCH contribuyente USING COVERING INDEX idx_razon_nocase         0.2 ms

Hay un test que afirma el plan de ejecución, para que la regresión no pueda volver en silencio.

Conectarlo a Claude Code

claude mcp add sunat --scope user -- <ruta>\sunat-mcp\.venv\Scripts\python.exe -m sunat_mcp.server

O en claude_desktop_config.json:

{
  "mcpServers": {
    "sunat": {
      "command": "C:\\ruta\\a\\sunat-mcp\\.venv\\Scripts\\python.exe",
      "args": ["-m", "sunat_mcp.server"]
    }
  }
}

El índice se busca en data/padron.sqlite3. Para moverlo, define SUNAT_MCP_DB.

Fuente de los datos

Padrón Reducido del RUC, publicado por SUNAT como datos abiertos: https://www.sunat.gob.pe/descargaPRR/mrc137_padron_reducido.html

Archivo: padron_reducido_ruc.zippadron_reducido_ruc.txt Formato: 16 columnas separadas por |, codificación latin-1, - como dato ausente.

RUC | NOMBRE O RAZÓN SOCIAL | ESTADO DEL CONTRIBUYENTE | CONDICIÓN DE DOMICILIO |
UBIGEO | TIPO DE VÍA | NOMBRE DE VÍA | CÓDIGO DE ZONA | TIPO DE ZONA | NÚMERO |
INTERIOR | LOTE | DEPARTAMENTO | MANZANA | KILÓMETRO |

estado_padron expone el SHA-256 del ZIP ingerido y la fecha que informó SUNAT, para que puedas saber exactamente qué versión de los datos estás consultando.

Si SUNAT cambia el esquema, la ingesta se detiene con error en vez de escribir datos en columnas equivocadas.

Límites

Léelos antes de confiar en el resultado:

  • El padrón es una foto, no un servicio en vivo. SUNAT lo publica a diario: entre el 27 y el 28 de julio de 2026 el archivo pasó de 389,882,157 a 389,923,097 bytes. Un RUC creado o dado de baja después de tu última ingesta no se refleja. Por eso estado_padron expone el Last-Modified y el sha256 del ZIP concreto que se ingirió: no basta con saber que consultaste "el padrón", hay que saber cuál.

  • validar_ruc solo comprueba aritmética. Un RUC con dígito verificador correcto puede no existir. Son preguntas distintas.

  • El Padrón Reducido no trae todo. No incluye representantes legales, actividad económica CIIU detallada, teléfonos ni la condición de agente de retención.

  • La búsqueda por nombre es textual, no semántica. No corrige errores de tipeo ni entiende sinónimos. La búsqueda por prefijo es instantánea; la búsqueda por subcadena (%TEXTO) recorre los 18.3 millones de registros y puede tardar decenas de segundos. Es un costo que solo se paga si se pide explícitamente.

  • La dirección se reconstruye uniendo las 10 columnas de domicilio del padrón. No se valida contra ningún servicio de direcciones.

  • Esto no es asesoría tributaria. Para decisiones con efecto legal o contable, verifica en el portal oficial de SUNAT.

Tests

.\.venv\Scripts\python.exe -m pytest -q

Las pruebas generan su propio ZIP de padrón sintético con el mismo formato del oficial (16 columnas, |, latin-1, -). No dependen del archivo real de 372 MiB ni contienen datos de ningún contribuyente real, salvo RUCs que aparecen en la cabecera pública del padrón.

Incluyen una prueba de que la ingesta aborta si SUNAT cambia las columnas.

Licencia

MIT. Los datos del Padrón Reducido son de SUNAT y se rigen por sus propios términos.


Hecho por David Nochi — Ingeniero de IA Aplicada, Lima, Perú.

Available Tools

4 tools
buscar_razon_socialA

Busca contribuyentes cuya razon social coincida con texto.

Por defecto busca por PREFIJO, resuelto como un rango sobre el indice (milisegundos). Para buscar por subcadena antepone '%' al texto (p. ej. '%ANDES'), pero eso recorre los ~18 millones de registros y puede tardar decenas de segundos. La comparacion ignora mayusculas y minusculas.

ParametersJSON Schema
NameRequiredDescriptionDefault
textoYes
limiteNo

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description fully bears the burden. It discloses default prefix search using an index (milliseconds), substring search scanning all records (tens of seconds), and case-insensitive comparison. This is thorough behavioral disclosure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is three sentences, efficiently front-loaded with the main purpose followed by key details. Every sentence adds value without redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has 2 parameters and no output schema, the description covers the core behavior, performance trade-offs, and case sensitivity. It could mention that the result is a list of taxpayers, but that is implied by the purpose.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% for both parameters. The description explains 'texto' (the search text) but does not mention 'limite' (limit), which has a default of 10 but no explanation. The description partially compensates for the missing schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool searches for taxpayers by business name (razón social) matching a given text. It distinguishes from sibling tools like validar_ruc (validate RUC) and consultar_ruc (consult RUC), which have different purposes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explains default prefix search (fast) and substring search with '%' (slow, tens of seconds), giving clear context on when to use each mode. It does not explicitly exclude alternatives, but the sibling names indicate they cover other queries.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

consultar_rucA

Consulta un RUC en el Padron Reducido local: razon social, estado del contribuyente, condicion de domicilio, ubigeo y direccion fiscal.

Requiere haber construido el indice con python scripts/ingest.py. La consulta es local: el RUC no se envia a ningun servicio externo.

ParametersJSON Schema
NameRequiredDescriptionDefault
rucYes

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations exist, so the description carries full burden. It discloses the local nature and index prerequisite, but does not mention error handling, rate limits, or behavior for missing RUCs.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is three concise sentences. The first front-loads the primary purpose and returned data, the second gives a prerequisite, and the third clarifies privacy. No redundant or irrelevant text.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple one-parameter tool with no output schema, the description covers purpose, prerequisite, and local operation. It lacks details on error responses or index requirements, but is sufficient for basic usage.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Only one parameter (ruc) with schema description coverage 0%. The description implicitly defines it as a RUC number, adding minimal value beyond the tool name. No format or constraints are specified.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool consults a RUC locally and lists returned fields (razon social, estado, etc.). The verb 'consultar' and resource 'RUC' are specific. However, it does not explicitly differentiate from siblings like validar_ruc or buscar_razon_social.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides a prerequisite (building index with ingest.py) and notes the query is local. But it lacks explicit guidance on when to use this tool versus alternatives or when not to use it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

estado_padronA

Devuelve la procedencia del indice local: URL de origen, checksum SHA-256 del ZIP, fecha de publicacion de SUNAT, fecha de ingesta y numero de registros.

Sirve para saber que tan actualizado esta el dato antes de confiar en el.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Without annotations, the description carries full burden for behavioral disclosure. It enumerates the returned fields (URL, checksum, dates, records), implying a read-only operation. It could explicitly state it has no side effects, but the information provided is sufficient.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences long with no wasted words. Every sentence adds meaning: the first lists what is returned, the second gives the use case. Extremely efficient.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has no parameters and no output schema, the description adequately covers the purpose and output. It could note that the data is from a local index or mention update frequency, but it remains complete for its simplicity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With zero parameters and 100% schema coverage, the description adds value by explaining the output's purpose and content. It goes beyond the empty schema by detailing what 'procedencia' means, meeting the baseline expectation.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool returns provenance information (source URL, checksum, dates, record count) of the local padron index, which distinguishes it from sibling tools like validar_ruc that operate on RUC data.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explains the tool's purpose is to check data freshness before trusting it, providing clear usage context. However, it does not explicitly state when not to use it or mention alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

validar_rucA

Valida la estructura de un RUC peruano: 11 digitos, prefijo vigente y digito verificador segun el algoritmo modulo 11 de SUNAT.

Es una comprobacion aritmetica y offline: NO consulta ninguna base de datos. Un RUC valido aqui puede aun asi no existir en SUNAT o estar de baja; para eso usa consultar_ruc.

ParametersJSON Schema
NameRequiredDescriptionDefault
rucYes

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description fully covers behavioral traits: it is an arithmetic check, offline, and does not query any database. It honestly states limitations (valid here does not mean exists in SUNAT).

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences: first explains functionality, second clarifies limitations and usage. Every sentence adds value, no wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the single parameter, no output schema, and no annotations, the description is complete. It explains what it does, its limitations, and directs to an alternative. Adequate for a simple validation tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema has 0% coverage and only one parameter 'ruc'. The description mentions '11 digits' but does not explicitly link this to the parameter format expectations. Basic meaning is clear from context, but could be more explicit.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description explicitly states it validates the structure of a Peruvian RUC, specifying 11 digits, valid prefix, and checksum algorithm. It distinguishes from siblings by clarifying it's offline and does not query databases.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description clearly states when to use this tool (offline structural validation) and when not to (for actual existence, use consultar_ruc). It also notes that a valid result does not guarantee the RUC exists in SUNAT.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 4 tool updatesv0.1.0
    • First observedbuscar_razon_social
    • First observedconsultar_ruc
    • First observedestado_padron
    • First observedvalidar_ruc

TDQS

A4.2/5.0

Scored across 4 tools

Disambiguation5/5

Each tool targets a distinct operation: structural validation, database query, name search, and data freshness check. There is no overlap or ambiguity.

Naming Consistency5/5

All tool names use lower_snake_case and follow a clear verb_noun pattern (e.g., validar_ruc, consultar_ruc). The only exception is estado_padron, but it remains intuitive and consistent in style.

Tool Count5/5

With 4 tools covering the core workflows of RUC validation, query, name search, and data status, the count is well-scoped and each tool earns its place.

Completeness4/5

The tool set covers the primary use cases for an offline RUC service. A minor gap is the lack of a tool to update the local index programmatically, but the ingest script is documented separately.

Maintenance

ActivitySlowing
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    MCP server for Peruvian public-data lookups including SUNAT RUC registrations, BCRP exchange rates, and SEACE tenders. Provides official open-data access through tools for Claude, Cursor, and other MCP clients.
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    MCP server for the Russian state registries EGRUL (legal entities) and EGRIP (individual entrepreneurs), built on official Federal Tax Service open-data dumps. Self-hosted via local SQLite.
    8
    2
    MIT
  • F
    license
    A
    quality
    C
    maintenance
    MCP server that exposes SUNAT Peruvian tax agency datasets (RUC registry, electronic receipts, IGV agents) via CKAN API with streaming preview and automatic zip decompression.
    4
    1
    -