douanecode-mcp 0.1.0__py3-none-any.whl
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.
|
File without changes
|
douanecode_mcp/server.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""DouaneCode MCP server.
|
|
2
|
+
|
|
3
|
+
Expose la recherche de codes douaniers (HS/NC8/TARIC) de douanecode.fr comme
|
|
4
|
+
outils MCP pour agents IA. Chaque appel est authentifié par la clé API du
|
|
5
|
+
client (plan Pro) via le header X-API-Key ; le backend applique le rate
|
|
6
|
+
limiting et la facturation par plan.
|
|
7
|
+
|
|
8
|
+
Transports :
|
|
9
|
+
- stdio (défaut) : usage local (Claude Desktop, Claude Code, Cursor...)
|
|
10
|
+
- streamable-http : hébergé (mcp.douanecode.fr), MCP_TRANSPORT=streamable-http
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
from typing import Any, Optional
|
|
16
|
+
|
|
17
|
+
import httpx
|
|
18
|
+
from mcp.server.fastmcp import FastMCP
|
|
19
|
+
|
|
20
|
+
API_BASE_URL = os.environ.get("DOUANECODE_API_URL", "https://douanecode.fr/api")
|
|
21
|
+
API_KEY_ENV = "DOUANECODE_API_KEY"
|
|
22
|
+
TIMEOUT_SECONDS = 15.0
|
|
23
|
+
|
|
24
|
+
mcp = FastMCP(
|
|
25
|
+
"douanecode",
|
|
26
|
+
instructions=(
|
|
27
|
+
"Recherche officielle de codes douaniers français/UE (HS, NC8, TARIC) : "
|
|
28
|
+
"classement tarifaire en langage naturel, taux de droits, mesures TARIC "
|
|
29
|
+
"et changements tarifaires. Nécessite une clé API DouaneCode Pro "
|
|
30
|
+
f"(variable d'environnement {API_KEY_ENV}, obtenue sur "
|
|
31
|
+
"https://douanecode.fr/compte)."
|
|
32
|
+
),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class DouaneCodeError(Exception):
|
|
37
|
+
"""Erreur API remontée telle quelle à l'agent appelant."""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _api_key() -> str:
|
|
41
|
+
key = os.environ.get(API_KEY_ENV, "").strip()
|
|
42
|
+
if not key:
|
|
43
|
+
raise DouaneCodeError(
|
|
44
|
+
f"Clé API absente : définissez {API_KEY_ENV}. "
|
|
45
|
+
"Créez une clé (plan Pro) sur https://douanecode.fr/compte."
|
|
46
|
+
)
|
|
47
|
+
return key
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async def _get(path: str, params: Optional[dict[str, Any]] = None) -> Any:
|
|
51
|
+
async with httpx.AsyncClient(
|
|
52
|
+
base_url=API_BASE_URL, timeout=TIMEOUT_SECONDS
|
|
53
|
+
) as client:
|
|
54
|
+
response = await client.get(
|
|
55
|
+
path,
|
|
56
|
+
params={k: v for k, v in (params or {}).items() if v is not None},
|
|
57
|
+
headers={"X-API-Key": _api_key()},
|
|
58
|
+
)
|
|
59
|
+
if response.status_code == 429:
|
|
60
|
+
raise DouaneCodeError(
|
|
61
|
+
"Limite quotidienne du plan atteinte. "
|
|
62
|
+
"Passez à un plan supérieur sur https://douanecode.fr/tarifs."
|
|
63
|
+
)
|
|
64
|
+
if response.status_code in (401, 403):
|
|
65
|
+
raise DouaneCodeError(
|
|
66
|
+
"Clé API invalide ou plan insuffisant (Pro requis) : "
|
|
67
|
+
"https://douanecode.fr/tarifs."
|
|
68
|
+
)
|
|
69
|
+
response.raise_for_status()
|
|
70
|
+
return response.json()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@mcp.tool()
|
|
74
|
+
async def search_customs_code(
|
|
75
|
+
query: str,
|
|
76
|
+
limit: int = 10,
|
|
77
|
+
chapter: Optional[str] = None,
|
|
78
|
+
leaf_only: bool = True,
|
|
79
|
+
) -> Any:
|
|
80
|
+
"""Trouve les codes douaniers (HS/NC8/TARIC) correspondant à une description
|
|
81
|
+
de produit en langage naturel.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
query: Description du produit (ex. "carte électronique pour trottinette",
|
|
85
|
+
"fromage râpé sous vide"). Français ou anglais.
|
|
86
|
+
limit: Nombre maximum de candidats retournés (1-100).
|
|
87
|
+
chapter: Filtre optionnel sur un chapitre SH à 2 chiffres (ex. "85").
|
|
88
|
+
leaf_only: Ne retourner que les codes feuilles déclarables (recommandé).
|
|
89
|
+
"""
|
|
90
|
+
return await _get(
|
|
91
|
+
"/search",
|
|
92
|
+
{"q": query, "limit": limit, "chapter": chapter, "leaf_only": leaf_only},
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@mcp.tool()
|
|
97
|
+
async def get_code_details(code: str) -> Any:
|
|
98
|
+
"""Retourne le détail d'un code douanier : libellés, hiérarchie, unités,
|
|
99
|
+
et informations de classement.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
code: Code SH/NC8/TARIC (4 à 10 chiffres, ex. "8517620000").
|
|
103
|
+
"""
|
|
104
|
+
return await _get(f"/code/{code.strip()}")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@mcp.tool()
|
|
108
|
+
async def get_duty_rates(code: str, origin: Optional[str] = None) -> Any:
|
|
109
|
+
"""Retourne les taux de droits de douane et mesures applicables à un code
|
|
110
|
+
(droits tiers, préférences, restrictions).
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
code: Code NC8/TARIC (8 à 10 chiffres).
|
|
114
|
+
origin: Code pays d'origine ISO-2 optionnel (ex. "CN", "US") pour les
|
|
115
|
+
taux préférentiels.
|
|
116
|
+
"""
|
|
117
|
+
return await _get(f"/duty/{code.strip()}", {"origin": origin})
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@mcp.tool()
|
|
121
|
+
async def get_tariff_changes(limit: int = 20) -> Any:
|
|
122
|
+
"""Liste les changements tarifaires récents (nomenclature, taux, mesures) —
|
|
123
|
+
utile pour la veille réglementaire douanière.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
limit: Nombre maximum de changements retournés.
|
|
127
|
+
"""
|
|
128
|
+
return await _get("/tariff-changes", {"limit": limit})
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def main() -> None:
|
|
132
|
+
transport = os.environ.get("MCP_TRANSPORT", "stdio")
|
|
133
|
+
if transport not in ("stdio", "streamable-http", "sse"):
|
|
134
|
+
print(f"Transport MCP inconnu : {transport}", file=sys.stderr)
|
|
135
|
+
sys.exit(1)
|
|
136
|
+
mcp.run(transport=transport)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
if __name__ == "__main__":
|
|
140
|
+
main()
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: douanecode-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server exposing DouaneCode customs code search (HS/NC8/TARIC) to AI agents
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Keywords: ai-agents,customs,douane,hs-code,mcp,taric
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Requires-Dist: httpx>=0.27
|
|
9
|
+
Requires-Dist: mcp>=1.2.0
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
12
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
13
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# douanecode-mcp
|
|
17
|
+
|
|
18
|
+
Serveur MCP officiel de [DouaneCode.fr](https://douanecode.fr) : donne aux
|
|
19
|
+
agents IA (Claude, Cursor, agents logistique/ERP/e-commerce) un accès outillé à
|
|
20
|
+
la recherche de codes douaniers HS/NC8/TARIC, aux taux de droits et à la veille
|
|
21
|
+
tarifaire.
|
|
22
|
+
|
|
23
|
+
**Modèle économique** : chaque appel exige une clé API DouaneCode (plan Pro,
|
|
24
|
+
99 €/an — tier Cabinet à venir). Le serveur est un wrapper mince ; le backend
|
|
25
|
+
existant applique authentification, rate limiting par plan et analytics.
|
|
26
|
+
|
|
27
|
+
## Outils exposés
|
|
28
|
+
|
|
29
|
+
| Outil | Description |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `search_customs_code` | Description produit en langage naturel → codes candidats |
|
|
32
|
+
| `get_code_details` | Détail d'un code (libellés, hiérarchie, unités) |
|
|
33
|
+
| `get_duty_rates` | Droits de douane + mesures, option pays d'origine |
|
|
34
|
+
| `get_tariff_changes` | Changements tarifaires récents (veille) |
|
|
35
|
+
|
|
36
|
+
## Usage local (Claude Desktop / Claude Code / Cursor)
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"mcpServers": {
|
|
41
|
+
"douanecode": {
|
|
42
|
+
"command": "uvx",
|
|
43
|
+
"args": ["douanecode-mcp"],
|
|
44
|
+
"env": { "DOUANECODE_API_KEY": "dc_..." }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Clé API : https://douanecode.fr/compte (plan Pro requis).
|
|
51
|
+
|
|
52
|
+
## Dev
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install -e ".[dev]"
|
|
56
|
+
pytest
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Déploiement hébergé
|
|
60
|
+
|
|
61
|
+
`Dockerfile` + `deploy/k8s.yaml` (mcp.douanecode.fr, transport streamable-http).
|
|
62
|
+
Voir la note dans le manifeste : le mode hébergé nécessite de lire la clé API
|
|
63
|
+
depuis les headers client (TODO) ; le mode stdio publié sur PyPI fonctionne
|
|
64
|
+
sans cela.
|
|
65
|
+
|
|
66
|
+
## Distribution (go-to-market)
|
|
67
|
+
|
|
68
|
+
1. Publier sur PyPI (`douanecode-mcp`) → utilisable via `uvx` immédiatement.
|
|
69
|
+
2. Lister sur les annuaires MCP (mcp.so, Smithery, PulseMCP, Glama) +
|
|
70
|
+
registre officiel `registry.modelcontextprotocol.io`.
|
|
71
|
+
3. Page https://douanecode.fr/mcp expliquant l'installation → conversion Pro.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
douanecode_mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
douanecode_mcp/server.py,sha256=s4L_7heUyfqAgwcpOIEKBktE9t2OLNITMdSPY0rM6Bc,4548
|
|
3
|
+
douanecode_mcp-0.1.0.dist-info/METADATA,sha256=0aKwHUJ8Vosel03-c2zVdqlNg0-zUIe-3FY1P8YRgVo,2302
|
|
4
|
+
douanecode_mcp-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
5
|
+
douanecode_mcp-0.1.0.dist-info/entry_points.txt,sha256=9PHHaHdhz8fmmV1C-AIQ6JxuXogbwNaiBxQjlPZwg8k,62
|
|
6
|
+
douanecode_mcp-0.1.0.dist-info/RECORD,,
|