lex-api-mx-mcp 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,99 @@
1
+ # ----------------------------------------------------------------------
2
+ # Documentos internos: estrategia, roadmaps, contexto y dirección.
3
+ # Viven en internal/ y NO se suben. Respáldalos por tu cuenta.
4
+ # ----------------------------------------------------------------------
5
+ internal/
6
+ **/ROADMAP*.md
7
+ **/*GO_TO_MARKET*.md
8
+ **/*ESTRATEGIA*.md
9
+ **/*STRATEGY*.md
10
+ **/Contexto*.md
11
+ **/*direccion-oficial*
12
+ docs/source/
13
+ *.private.md
14
+ *.interno.md
15
+ NOTAS*.md
16
+ notes/
17
+
18
+ # ----------------------------------------------------------------------
19
+ # Secretos y configuración local
20
+ # ----------------------------------------------------------------------
21
+ .env
22
+ .env.*
23
+ !.env.example
24
+ *.pem
25
+ # Certificados intermedios públicos (sin llave privada) que el monitor necesita para TLS
26
+ !packages/ingestion/src/ingestion/certs/*.pem
27
+ *.key
28
+ credentials*.json
29
+ service-account*.json
30
+ .claude/settings.local.json
31
+
32
+ # ----------------------------------------------------------------------
33
+ # Desarrollo personal: borradores, pruebas manuales y temporales
34
+ # ----------------------------------------------------------------------
35
+ scratch/
36
+ sandbox/
37
+ playground-local/
38
+ tmp/
39
+ .tmp/
40
+ *.local.*
41
+ *.bak
42
+ *.orig
43
+ *.log
44
+ .tool-home/
45
+
46
+ # Bases de datos y volcados locales
47
+ *.sqlite
48
+ *.sqlite3
49
+ *.db
50
+ *.dump
51
+ *.sql.gz
52
+ pgdata/
53
+
54
+ # ----------------------------------------------------------------------
55
+ # Python y Node
56
+ # ----------------------------------------------------------------------
57
+ __pycache__/
58
+ *.pyc
59
+ .venv/
60
+ venv/
61
+ *.egg-info/
62
+ dist/
63
+ build/
64
+ node_modules/
65
+ .next/
66
+
67
+ # Pruebas, cobertura y linters
68
+ .pytest_cache/
69
+ .pytest-tmp/
70
+ .pytest-*/
71
+ .tmp-pytest-*/
72
+ .test-tmp-*/
73
+ pytest-cache-files-*/
74
+ .ruff_cache/
75
+ .mypy_cache/
76
+ .coverage
77
+ coverage.xml
78
+ htmlcov/
79
+ reports/validation/runtime_test_workspace_*/pytest/
80
+ # Salidas de scripts/run_local_e2e.py y del monitor (en Actions viajan como artefactos)
81
+ reports/e2e/
82
+ reports/monitor/
83
+
84
+ # Supabase local
85
+ supabase/.temp/
86
+ supabase/.branches/
87
+
88
+ # ----------------------------------------------------------------------
89
+ # Editores y sistema operativo
90
+ # ----------------------------------------------------------------------
91
+ .vscode/
92
+ .idea/
93
+ *.swp
94
+ *.swo
95
+ .DS_Store
96
+ Thumbs.db
97
+
98
+ # Documentos históricos archivados fuera del repositorio (se conservan en disco)
99
+ docs/archive/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lex API MX
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,94 @@
1
+ Metadata-Version: 2.5
2
+ Name: lex-api-mx-mcp
3
+ Version: 0.1.1
4
+ Summary: Servidor MCP de Lex API MX: legislación mexicana vigente y verificable para agentes de IA
5
+ Project-URL: Homepage, https://lexapi.mx
6
+ Project-URL: Documentation, https://lexapi.mx/docs
7
+ Project-URL: Repository, https://github.com/Julio-Patron/Lex-API-Mx
8
+ Author-email: Lex API MX <admin@lexapi.mx>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: ai-agents,legal,legislacion,leyes,mcp,mexico,model-context-protocol
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Natural Language :: Spanish
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Requires-Python: >=3.11
22
+ Requires-Dist: lex-api-mx-client<0.2,>=0.1.1
23
+ Requires-Dist: mcp<3,>=2.2
24
+ Description-Content-Type: text/markdown
25
+
26
+ <!-- mcp-name: io.github.Julio-Patron/lex-api-mx -->
27
+ # Lex API MX — servidor MCP
28
+
29
+ Servidor [Model Context Protocol](https://modelcontextprotocol.io) que conecta
30
+ Claude Desktop, Claude Code, Cursor o cualquier agente MCP con la legislación
31
+ federal mexicana vigente de [Lex API MX](https://lexapi.mx).
32
+
33
+ El servidor es un adaptador ligero y de código abierto (MIT): no contiene el
34
+ corpus. Cada llamada consulta la API de Lex API MX con tu clave y devuelve el
35
+ paquete normativo tal cual, con cita canónica, vigencia, fuente oficial y
36
+ checksum SHA-256 del texto.
37
+
38
+ ## Herramientas
39
+
40
+ | Herramienta | Endpoint | Para qué |
41
+ |---|---|---|
42
+ | `resolve_legal_citation` | `POST /api/v1/resolve` | Resolver una cita o situación a un paquete normativo verificable. |
43
+ | `search_mexican_law` | `GET /api/v1/articulos/search` | Buscar artículos vigentes por texto, norma o materia. |
44
+ | `get_norm_structure` | `GET /api/v1/normas/{slug}/estructura` | Recorrer el árbol de una norma. |
45
+
46
+ Todas exigen `fecha` (`"vigente"` o `YYYY-MM-DD`). Las de búsqueda y resolución
47
+ exigen además `jurisdiccion`; hoy la única cobertura es `"federal"`.
48
+
49
+ ## Instalación
50
+
51
+ Requiere [uv](https://docs.astral.sh/uv/) y una clave de Lex API MX. El plan
52
+ gratuito incluye 500 créditos al mes: crea tu cuenta con tu correo y genera la
53
+ clave en [lexapi.mx/#cuenta](https://lexapi.mx/#cuenta).
54
+
55
+ ```json
56
+ {
57
+ "mcpServers": {
58
+ "lex-api-mx": {
59
+ "command": "uvx",
60
+ "args": ["lex-api-mx-mcp"],
61
+ "env": { "LEX_API_MX_KEY": "tu_clave" }
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ - **Claude Desktop:** *Settings → Developer → Edit Config* y pega el bloque
68
+ anterior en `claude_desktop_config.json`; reinicia la aplicación.
69
+ - **Claude Code:**
70
+ `claude mcp add lex-api-mx --env LEX_API_MX_KEY=tu_clave -- uvx lex-api-mx-mcp`
71
+ - **Cursor:** el mismo bloque en `~/.cursor/mcp.json` (global) o
72
+ `.cursor/mcp.json` (proyecto).
73
+
74
+ Pruébalo preguntando: *¿Cuáles son las causas de rescisión de la relación de
75
+ trabajo sin responsabilidad para el patrón? Cita el artículo exacto.*
76
+
77
+ ## Variables de entorno
78
+
79
+ | Variable | Obligatoria | Valor por defecto |
80
+ |---|---|---|
81
+ | `LEX_API_MX_KEY` | Sí | — |
82
+ | `LEX_API_MX_BASE_URL` | No | `https://api.lexapi.mx` |
83
+
84
+ ## Errores
85
+
86
+ Los errores de la API llegan al modelo como errores de herramienta legibles:
87
+ clave inválida (401), permiso faltante (403), saldo agotado (402) y límite de
88
+ peticiones (429).
89
+
90
+ ## Código fuente
91
+
92
+ [github.com/Julio-Patron/Lex-API-Mx](https://github.com/Julio-Patron/Lex-API-Mx/tree/main/packages/lex-mcp-server)
93
+ (licencia MIT). También está en el [MCP Registry](https://registry.modelcontextprotocol.io)
94
+ como `io.github.Julio-Patron/lex-api-mx`.
@@ -0,0 +1,69 @@
1
+ <!-- mcp-name: io.github.Julio-Patron/lex-api-mx -->
2
+ # Lex API MX — servidor MCP
3
+
4
+ Servidor [Model Context Protocol](https://modelcontextprotocol.io) que conecta
5
+ Claude Desktop, Claude Code, Cursor o cualquier agente MCP con la legislación
6
+ federal mexicana vigente de [Lex API MX](https://lexapi.mx).
7
+
8
+ El servidor es un adaptador ligero y de código abierto (MIT): no contiene el
9
+ corpus. Cada llamada consulta la API de Lex API MX con tu clave y devuelve el
10
+ paquete normativo tal cual, con cita canónica, vigencia, fuente oficial y
11
+ checksum SHA-256 del texto.
12
+
13
+ ## Herramientas
14
+
15
+ | Herramienta | Endpoint | Para qué |
16
+ |---|---|---|
17
+ | `resolve_legal_citation` | `POST /api/v1/resolve` | Resolver una cita o situación a un paquete normativo verificable. |
18
+ | `search_mexican_law` | `GET /api/v1/articulos/search` | Buscar artículos vigentes por texto, norma o materia. |
19
+ | `get_norm_structure` | `GET /api/v1/normas/{slug}/estructura` | Recorrer el árbol de una norma. |
20
+
21
+ Todas exigen `fecha` (`"vigente"` o `YYYY-MM-DD`). Las de búsqueda y resolución
22
+ exigen además `jurisdiccion`; hoy la única cobertura es `"federal"`.
23
+
24
+ ## Instalación
25
+
26
+ Requiere [uv](https://docs.astral.sh/uv/) y una clave de Lex API MX. El plan
27
+ gratuito incluye 500 créditos al mes: crea tu cuenta con tu correo y genera la
28
+ clave en [lexapi.mx/#cuenta](https://lexapi.mx/#cuenta).
29
+
30
+ ```json
31
+ {
32
+ "mcpServers": {
33
+ "lex-api-mx": {
34
+ "command": "uvx",
35
+ "args": ["lex-api-mx-mcp"],
36
+ "env": { "LEX_API_MX_KEY": "tu_clave" }
37
+ }
38
+ }
39
+ }
40
+ ```
41
+
42
+ - **Claude Desktop:** *Settings → Developer → Edit Config* y pega el bloque
43
+ anterior en `claude_desktop_config.json`; reinicia la aplicación.
44
+ - **Claude Code:**
45
+ `claude mcp add lex-api-mx --env LEX_API_MX_KEY=tu_clave -- uvx lex-api-mx-mcp`
46
+ - **Cursor:** el mismo bloque en `~/.cursor/mcp.json` (global) o
47
+ `.cursor/mcp.json` (proyecto).
48
+
49
+ Pruébalo preguntando: *¿Cuáles son las causas de rescisión de la relación de
50
+ trabajo sin responsabilidad para el patrón? Cita el artículo exacto.*
51
+
52
+ ## Variables de entorno
53
+
54
+ | Variable | Obligatoria | Valor por defecto |
55
+ |---|---|---|
56
+ | `LEX_API_MX_KEY` | Sí | — |
57
+ | `LEX_API_MX_BASE_URL` | No | `https://api.lexapi.mx` |
58
+
59
+ ## Errores
60
+
61
+ Los errores de la API llegan al modelo como errores de herramienta legibles:
62
+ clave inválida (401), permiso faltante (403), saldo agotado (402) y límite de
63
+ peticiones (429).
64
+
65
+ ## Código fuente
66
+
67
+ [github.com/Julio-Patron/Lex-API-Mx](https://github.com/Julio-Patron/Lex-API-Mx/tree/main/packages/lex-mcp-server)
68
+ (licencia MIT). También está en el [MCP Registry](https://registry.modelcontextprotocol.io)
69
+ como `io.github.Julio-Patron/lex-api-mx`.
@@ -0,0 +1,43 @@
1
+ [project]
2
+ name = "lex-api-mx-mcp"
3
+ version = "0.1.1"
4
+ description = "Servidor MCP de Lex API MX: legislación mexicana vigente y verificable para agentes de IA"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [{ name = "Lex API MX", email = "admin@lexapi.mx" }]
9
+ requires-python = ">=3.11"
10
+ keywords = ["mcp", "model-context-protocol", "mexico", "legislacion", "leyes", "legal", "ai-agents"]
11
+ classifiers = [
12
+ "Development Status :: 4 - Beta",
13
+ "Intended Audience :: Developers",
14
+ "Natural Language :: Spanish",
15
+ "Operating System :: OS Independent",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.13",
20
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
21
+ ]
22
+ dependencies = [
23
+ "lex-api-mx-client>=0.1.1,<0.2",
24
+ "mcp>=2.2,<3",
25
+ ]
26
+
27
+ [project.urls]
28
+ Homepage = "https://lexapi.mx"
29
+ Documentation = "https://lexapi.mx/docs"
30
+ Repository = "https://github.com/Julio-Patron/Lex-API-Mx"
31
+
32
+ [project.scripts]
33
+ lex-api-mx-mcp = "lex_api_mx_mcp.server:main"
34
+
35
+ [tool.uv.sources]
36
+ lex-api-mx-client = { workspace = true }
37
+
38
+ [build-system]
39
+ requires = ["hatchling"]
40
+ build-backend = "hatchling.build"
41
+
42
+ [tool.hatch.build.targets.wheel]
43
+ packages = ["src/lex_api_mx_mcp"]
@@ -0,0 +1,40 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.Julio-Patron/lex-api-mx",
4
+ "title": "Lex API MX",
5
+ "description": "Legislación federal mexicana vigente con fuente, checksum y vigencia para agentes de IA",
6
+ "version": "0.1.1",
7
+ "websiteUrl": "https://lexapi.mx",
8
+ "repository": {
9
+ "url": "https://github.com/Julio-Patron/Lex-API-Mx",
10
+ "source": "github",
11
+ "subfolder": "packages/lex-mcp-server"
12
+ },
13
+ "packages": [
14
+ {
15
+ "registryType": "pypi",
16
+ "registryBaseUrl": "https://pypi.org",
17
+ "identifier": "lex-api-mx-mcp",
18
+ "version": "0.1.1",
19
+ "runtimeHint": "uvx",
20
+ "transport": {
21
+ "type": "stdio"
22
+ },
23
+ "environmentVariables": [
24
+ {
25
+ "name": "LEX_API_MX_KEY",
26
+ "description": "Clave de API; créala gratis en https://lexapi.mx/#cuenta",
27
+ "isRequired": true,
28
+ "isSecret": true,
29
+ "format": "string"
30
+ },
31
+ {
32
+ "name": "LEX_API_MX_BASE_URL",
33
+ "description": "Solo para desarrollo local; por defecto https://api.lexapi.mx",
34
+ "isRequired": false,
35
+ "format": "string"
36
+ }
37
+ ]
38
+ }
39
+ ]
40
+ }
@@ -0,0 +1,3 @@
1
+ """Servidor MCP de Lex API MX."""
2
+
3
+ __version__ = "0.1.1"
@@ -0,0 +1,241 @@
1
+ """Adaptador MCP sobre la API pública de Lex API MX.
2
+
3
+ El servidor no contiene corpus ni lógica normativa: traduce llamadas de
4
+ herramientas MCP a la API HTTP y devuelve sus paquetes JSON sin reescribirlos.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ import sys
11
+ from datetime import date, datetime
12
+ from typing import Annotated, Any, Literal
13
+ from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
14
+
15
+ import httpx
16
+ from lex_api_mx import LexApiClient, LexApiError
17
+ from mcp.server.mcpserver import MCPServer
18
+ from mcp.server.mcpserver.exceptions import ToolError
19
+ from mcp.types import ToolAnnotations
20
+ from pydantic import Field
21
+
22
+ from . import __version__
23
+
24
+ DEFAULT_BASE_URL = "https://api.lexapi.mx"
25
+ DASHBOARD_URL = "https://lexapi.mx/dashboard"
26
+
27
+ INSTRUCTIONS = """\
28
+ Lex API MX entrega legislación federal mexicana estructurada, versionada y con
29
+ fuente oficial. Reglas de uso:
30
+ 1. Antes de afirmar qué dice una ley mexicana, consulta estas herramientas.
31
+ 2. Cita exactamente la referencia canónica, el URL de la fuente oficial y el
32
+ checksum que devuelva la herramienta; no inventes artículos ni fracciones.
33
+ 3. Si la cobertura indica `outside_coverage` o no hay resultados, dilo
34
+ explícitamente; nunca presentes texto vigente como histórico.
35
+ 4. Usa `fecha="vigente"` salvo que el usuario pregunte por una fecha concreta.
36
+ """
37
+
38
+ Fecha = Annotated[
39
+ str,
40
+ Field(
41
+ pattern=r"^(vigente|\d{4}-\d{2}-\d{2})$",
42
+ description=(
43
+ "Fecha de vigencia a consultar: 'vigente' para la fecha actual en México, "
44
+ "o una fecha ISO YYYY-MM-DD si el usuario pregunta por un momento concreto."
45
+ ),
46
+ ),
47
+ ]
48
+ Jurisdiccion = Annotated[
49
+ Literal["federal"],
50
+ Field(description="Jurisdicción normativa. La cobertura actual es sólo federal."),
51
+ ]
52
+ Norma = Annotated[
53
+ str | None,
54
+ Field(
55
+ max_length=120,
56
+ description="Slug de la norma para acotar la búsqueda (ej. 'lft', 'cff', 'lisr').",
57
+ ),
58
+ ]
59
+
60
+ READ_ONLY = ToolAnnotations(read_only_hint=True, destructive_hint=False, idempotent_hint=True)
61
+
62
+
63
+ def today_in_mexico() -> date:
64
+ try:
65
+ return datetime.now(ZoneInfo("America/Mexico_City")).date()
66
+ except ZoneInfoNotFoundError:
67
+ return date.today()
68
+
69
+
70
+ def resolve_fecha(fecha: str) -> str:
71
+ if fecha == "vigente":
72
+ return today_in_mexico().isoformat()
73
+ try:
74
+ return date.fromisoformat(fecha).isoformat()
75
+ except ValueError as exc:
76
+ raise ToolError(f"Fecha inválida '{fecha}': usa 'vigente' o YYYY-MM-DD.") from exc
77
+
78
+
79
+ def describe_api_error(error: LexApiError) -> str:
80
+ if error.status_code == 402:
81
+ body = error.body if isinstance(error.body, dict) else {}
82
+ detail = body.get("detail")
83
+ if isinstance(detail, dict) and detail.get("message"):
84
+ return str(detail["message"])
85
+ return (
86
+ "Saldo agotado. El plan gratuito incluye 500 créditos al mes. Para más volumen "
87
+ f"o para integrar Lex API MX en tu producto, contrata un plan en {DASHBOARD_URL}"
88
+ )
89
+ if error.status_code == 401:
90
+ return "LEX_API_MX_KEY no es válida, expiró o fue revocada."
91
+ if error.status_code == 403:
92
+ return f"La API key no tiene el permiso requerido para esta operación: {error.detail}"
93
+ if error.status_code == 429:
94
+ return "Límite de peticiones por minuto alcanzado. Espera un momento y reintenta."
95
+ suffix = f" (request_id={error.request_id})" if error.request_id else ""
96
+ return f"Lex API MX respondió {error.status_code}: {error.detail}{suffix}"
97
+
98
+
99
+ def build_server(client: LexApiClient) -> MCPServer:
100
+ server = MCPServer(
101
+ name="lex-api-mx",
102
+ title="Lex API MX",
103
+ description="Legislación mexicana vigente, estructurada y verificable.",
104
+ instructions=INSTRUCTIONS,
105
+ website_url="https://lexapi.mx",
106
+ version=__version__,
107
+ )
108
+
109
+ def call(operation, **kwargs: Any) -> dict[str, Any]:
110
+ try:
111
+ result = operation(**kwargs)
112
+
113
+ # Protección de Context Window (aprox. 100k tokens max = ~400k caracteres)
114
+ import json
115
+
116
+ size = len(json.dumps(result))
117
+ if size > 400000:
118
+ raise ToolError(
119
+ "La respuesta es demasiado grande para el contexto del agente "
120
+ f"({size // 1024} KB). Por favor, acota tu búsqueda (reduce el límite "
121
+ "o establece include_text=False en get_norm_structure)."
122
+ )
123
+
124
+ return result
125
+ except LexApiError as exc:
126
+ raise ToolError(describe_api_error(exc)) from exc
127
+ except httpx.HTTPError as exc:
128
+ raise ToolError(
129
+ "No se pudo contactar a Lex API MX. Revisa tu conexión o LEX_API_MX_BASE_URL."
130
+ ) from exc
131
+
132
+ @server.tool(annotations=READ_ONLY)
133
+ def search_mexican_law(
134
+ query: Annotated[
135
+ str,
136
+ Field(
137
+ min_length=2,
138
+ max_length=500,
139
+ description="Términos a buscar en artículos vigentes (búsqueda full-text en español).",
140
+ ),
141
+ ],
142
+ fecha: Fecha,
143
+ jurisdiccion: Jurisdiccion,
144
+ norma: Norma = None,
145
+ materia: Annotated[
146
+ str | None,
147
+ Field(max_length=80, description="Materia para acotar (ej. 'laboral', 'fiscal')."),
148
+ ] = None,
149
+ limit: Annotated[int, Field(ge=1, le=50)] = 10,
150
+ ) -> dict[str, Any]:
151
+ """Busca artículos de leyes y reglamentos federales mexicanos vigentes a una fecha.
152
+
153
+ Cada resultado trae texto oficial, cita canónica, vigencia, fuente oficial y checksum.
154
+ """
155
+ return call(
156
+ client.search_articles,
157
+ query=query,
158
+ norm=norma,
159
+ matter=materia,
160
+ jurisdiction=jurisdiccion,
161
+ as_of=resolve_fecha(fecha),
162
+ limit=limit,
163
+ )
164
+
165
+ @server.tool(annotations=READ_ONLY)
166
+ def get_norm_structure(
167
+ norma: Annotated[
168
+ str,
169
+ Field(min_length=1, max_length=120, description="Slug de la norma (ej. 'lft')."),
170
+ ],
171
+ fecha: Fecha,
172
+ include_text: Annotated[
173
+ bool,
174
+ Field(
175
+ description="Incluir el texto de cada nodo. Respuestas grandes; úsalo con cuidado."
176
+ ),
177
+ ] = False,
178
+ ) -> dict[str, Any]:
179
+ """Devuelve el árbol de una norma federal: libros, títulos, capítulos, artículos y fracciones."""
180
+ return call(
181
+ client.norm_structure,
182
+ norm=norma,
183
+ as_of=resolve_fecha(fecha),
184
+ include_text=include_text,
185
+ )
186
+
187
+ @server.tool(annotations=READ_ONLY)
188
+ def resolve_legal_citation(
189
+ query: Annotated[
190
+ str,
191
+ Field(
192
+ min_length=2,
193
+ max_length=2000,
194
+ description=(
195
+ "Cita o situación a resolver, ej. 'Artículo 47 LFT' o "
196
+ "'causas de rescisión de la relación de trabajo sin responsabilidad del patrón'."
197
+ ),
198
+ ),
199
+ ],
200
+ fecha: Fecha,
201
+ jurisdiccion: Jurisdiccion,
202
+ norma: Norma = None,
203
+ limit: Annotated[int, Field(ge=1, le=20)] = 5,
204
+ ) -> dict[str, Any]:
205
+ """Resuelve una consulta o cita a un paquete normativo verificable.
206
+
207
+ Devuelve normas, nodos, citas canónicas, versiones vigentes, cobertura temporal,
208
+ fuente oficial y checksum. Es la herramienta preferida para fundamentar respuestas.
209
+ """
210
+ return call(
211
+ client.resolve,
212
+ query=query,
213
+ norm=norma,
214
+ jurisdiction=jurisdiccion,
215
+ as_of=resolve_fecha(fecha),
216
+ limit=limit,
217
+ )
218
+
219
+ return server
220
+
221
+
222
+ def main() -> None:
223
+ api_key = os.environ.get("LEX_API_MX_KEY", "").strip()
224
+ if not api_key:
225
+ print(
226
+ "Falta LEX_API_MX_KEY. Obtén una clave gratuita en "
227
+ f"{DASHBOARD_URL} y agrégala al bloque 'env' de tu cliente MCP.",
228
+ file=sys.stderr,
229
+ )
230
+ sys.exit(1)
231
+ base_url = os.environ.get("LEX_API_MX_BASE_URL", DEFAULT_BASE_URL)
232
+ with LexApiClient(
233
+ base_url=base_url,
234
+ api_key=api_key,
235
+ user_agent=f"lex-api-mx-mcp/{__version__}",
236
+ ) as client:
237
+ build_server(client).run("stdio")
238
+
239
+
240
+ if __name__ == "__main__":
241
+ main()