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.
- lex_api_mx_mcp-0.1.1/.gitignore +99 -0
- lex_api_mx_mcp-0.1.1/LICENSE +21 -0
- lex_api_mx_mcp-0.1.1/PKG-INFO +94 -0
- lex_api_mx_mcp-0.1.1/README.md +69 -0
- lex_api_mx_mcp-0.1.1/pyproject.toml +43 -0
- lex_api_mx_mcp-0.1.1/server.json +40 -0
- lex_api_mx_mcp-0.1.1/src/lex_api_mx_mcp/__init__.py +3 -0
- lex_api_mx_mcp-0.1.1/src/lex_api_mx_mcp/server.py +241 -0
|
@@ -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,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()
|