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
@@ -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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ douanecode-mcp = douanecode_mcp.server:main