@supersoniks/concorde 3.3.2 → 3.3.3
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.
- package/build-infos.json +1 -1
- package/concorde-core.bundle.js +2 -2
- package/concorde-core.es.js +2 -2
- package/dist/concorde-core.bundle.js +2 -2
- package/dist/concorde-core.es.js +2 -2
- package/package.json +1 -1
- package/src/core/decorators/subscriber/onAssign.ts +3 -1
- package/src/docs/_misc/on-assign.md +5 -5
- package/src/docs/search/docs-search.json +85 -0
- package/mcp-server/COMPARISON-MCP.md +0 -176
- package/mcp-server/README-MCP-NODEJS.md +0 -284
- package/mcp-server/README-MCP.md +0 -114
- package/mcp-server/README.md +0 -127
- package/mcp-server/TECHNICAL-DOCS.md +0 -269
- package/mcp-server/concorde-mcp-server.js +0 -859
- package/mcp-server/concorde-mcp-server.py +0 -801
- package/mcp-server/cursor-mcp-config-advanced.json +0 -68
- package/mcp-server/cursor-mcp-config-nodejs.json +0 -74
- package/mcp-server/cursor-mcp-config.json +0 -11
- package/mcp-server/install-mcp-nodejs.sh +0 -104
- package/mcp-server/install-mcp.sh +0 -62
- package/mcp-server/package-lock.json +0 -147
- package/mcp-server/package-mcp.json +0 -40
- package/mcp-server/package.json +0 -40
- package/mcp-server/test-mcp.js +0 -107
- package/mcp-server/test-mcp.py +0 -73
|
@@ -1,801 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Serveur MCP pour Concorde - Framework de composants Web
|
|
4
|
-
Permet à l'IA de Cursor d'accéder facilement à toute la documentation et au code de Concorde
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import json
|
|
8
|
-
import os
|
|
9
|
-
import re
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
from typing import Any, Dict, List, Optional, Union
|
|
12
|
-
import asyncio
|
|
13
|
-
from dataclasses import dataclass
|
|
14
|
-
from mcp.server import Server
|
|
15
|
-
from mcp.server.models import InitializationOptions
|
|
16
|
-
from mcp.server.stdio import stdio_server
|
|
17
|
-
from mcp.types import (
|
|
18
|
-
Resource,
|
|
19
|
-
Tool,
|
|
20
|
-
TextContent,
|
|
21
|
-
ImageContent,
|
|
22
|
-
EmbeddedResource,
|
|
23
|
-
CallToolRequest,
|
|
24
|
-
CallToolResult,
|
|
25
|
-
ListResourcesRequest,
|
|
26
|
-
ListResourcesResult,
|
|
27
|
-
ListToolsRequest,
|
|
28
|
-
ListToolsResult,
|
|
29
|
-
ReadResourceRequest,
|
|
30
|
-
ReadResourceResult
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
# Configuration du projet Concorde
|
|
34
|
-
CONCORDE_ROOT = Path(__file__).parent.parent
|
|
35
|
-
SRC_PATH = CONCORDE_ROOT / "src"
|
|
36
|
-
DOCS_PATH = CONCORDE_ROOT / "docs"
|
|
37
|
-
CORE_PATH = SRC_PATH / "core"
|
|
38
|
-
COMPONENTS_PATH = CORE_PATH / "components"
|
|
39
|
-
|
|
40
|
-
@dataclass
|
|
41
|
-
class ComponentInfo:
|
|
42
|
-
"""Information sur un composant Concorde"""
|
|
43
|
-
name: str
|
|
44
|
-
tag_name: str
|
|
45
|
-
category: str # 'ui' ou 'functional'
|
|
46
|
-
file_path: Path
|
|
47
|
-
doc_path: Optional[Path]
|
|
48
|
-
description: str
|
|
49
|
-
props: List[Dict[str, Any]]
|
|
50
|
-
examples: List[str]
|
|
51
|
-
|
|
52
|
-
class ConcordeMCPServer:
|
|
53
|
-
"""Serveur MCP pour Concorde"""
|
|
54
|
-
|
|
55
|
-
def __init__(self):
|
|
56
|
-
self.server = Server("concorde-mcp")
|
|
57
|
-
self.components: Dict[str, ComponentInfo] = {}
|
|
58
|
-
self.documentation: Dict[str, str] = {}
|
|
59
|
-
self._setup_handlers()
|
|
60
|
-
self._load_components()
|
|
61
|
-
self._load_documentation()
|
|
62
|
-
|
|
63
|
-
def _setup_handlers(self):
|
|
64
|
-
"""Configure les handlers MCP"""
|
|
65
|
-
|
|
66
|
-
@self.server.list_resources()
|
|
67
|
-
async def list_resources() -> ListResourcesResult:
|
|
68
|
-
"""Liste toutes les ressources disponibles"""
|
|
69
|
-
resources = []
|
|
70
|
-
|
|
71
|
-
# Ressources de composants
|
|
72
|
-
for comp_name, comp_info in self.components.items():
|
|
73
|
-
resources.append(Resource(
|
|
74
|
-
uri=f"concorde://component/{comp_name}",
|
|
75
|
-
name=f"Composant {comp_name}",
|
|
76
|
-
description=comp_info.description,
|
|
77
|
-
mimeType="text/markdown"
|
|
78
|
-
))
|
|
79
|
-
|
|
80
|
-
# Ressources de documentation
|
|
81
|
-
for doc_name, doc_content in self.documentation.items():
|
|
82
|
-
resources.append(Resource(
|
|
83
|
-
uri=f"concorde://docs/{doc_name}",
|
|
84
|
-
name=f"Documentation {doc_name}",
|
|
85
|
-
description=f"Documentation Concorde: {doc_name}",
|
|
86
|
-
mimeType="text/markdown"
|
|
87
|
-
))
|
|
88
|
-
|
|
89
|
-
# Ressources d'exemples
|
|
90
|
-
resources.append(Resource(
|
|
91
|
-
uri="concorde://examples/ui-components",
|
|
92
|
-
name="Exemples composants UI",
|
|
93
|
-
description="Exemples d'utilisation des composants UI",
|
|
94
|
-
mimeType="text/markdown"
|
|
95
|
-
))
|
|
96
|
-
|
|
97
|
-
resources.append(Resource(
|
|
98
|
-
uri="concorde://examples/functional-components",
|
|
99
|
-
name="Exemples composants fonctionnels",
|
|
100
|
-
description="Exemples d'utilisation des composants fonctionnels",
|
|
101
|
-
mimeType="text/markdown"
|
|
102
|
-
))
|
|
103
|
-
|
|
104
|
-
return ListResourcesResult(resources=resources)
|
|
105
|
-
|
|
106
|
-
@self.server.read_resource()
|
|
107
|
-
async def read_resource(uri: str) -> ReadResourceResult:
|
|
108
|
-
"""Lit une ressource"""
|
|
109
|
-
if uri.startswith("concorde://component/"):
|
|
110
|
-
comp_name = uri.replace("concorde://component/", "")
|
|
111
|
-
if comp_name in self.components:
|
|
112
|
-
comp_info = self.components[comp_name]
|
|
113
|
-
content = self._generate_component_doc(comp_info)
|
|
114
|
-
return ReadResourceResult(contents=[TextContent(type="text", text=content)])
|
|
115
|
-
|
|
116
|
-
elif uri.startswith("concorde://docs/"):
|
|
117
|
-
doc_name = uri.replace("concorde://docs/", "")
|
|
118
|
-
if doc_name in self.documentation:
|
|
119
|
-
return ReadResourceResult(contents=[TextContent(type="text", text=self.documentation[doc_name])])
|
|
120
|
-
|
|
121
|
-
elif uri == "concorde://examples/ui-components":
|
|
122
|
-
content = self._generate_ui_examples()
|
|
123
|
-
return ReadResourceResult(contents=[TextContent(type="text", text=content)])
|
|
124
|
-
|
|
125
|
-
elif uri == "concorde://examples/functional-components":
|
|
126
|
-
content = self._generate_functional_examples()
|
|
127
|
-
return ReadResourceResult(contents=[TextContent(type="text", text=content)])
|
|
128
|
-
|
|
129
|
-
return ReadResourceResult(contents=[TextContent(type="text", text="Ressource non trouvée")])
|
|
130
|
-
|
|
131
|
-
@self.server.list_tools()
|
|
132
|
-
async def list_tools() -> ListToolsResult:
|
|
133
|
-
"""Liste tous les outils disponibles"""
|
|
134
|
-
tools = [
|
|
135
|
-
Tool(
|
|
136
|
-
name="search_component",
|
|
137
|
-
description="Recherche un composant Concorde par nom ou fonctionnalité",
|
|
138
|
-
inputSchema={
|
|
139
|
-
"type": "object",
|
|
140
|
-
"properties": {
|
|
141
|
-
"query": {
|
|
142
|
-
"type": "string",
|
|
143
|
-
"description": "Terme de recherche (nom du composant, fonctionnalité, etc.)"
|
|
144
|
-
}
|
|
145
|
-
},
|
|
146
|
-
"required": ["query"]
|
|
147
|
-
}
|
|
148
|
-
),
|
|
149
|
-
Tool(
|
|
150
|
-
name="get_component_info",
|
|
151
|
-
description="Obtient des informations détaillées sur un composant spécifique",
|
|
152
|
-
inputSchema={
|
|
153
|
-
"type": "object",
|
|
154
|
-
"properties": {
|
|
155
|
-
"component_name": {
|
|
156
|
-
"type": "string",
|
|
157
|
-
"description": "Nom du composant (ex: button, modal, input)"
|
|
158
|
-
}
|
|
159
|
-
},
|
|
160
|
-
"required": ["component_name"]
|
|
161
|
-
}
|
|
162
|
-
),
|
|
163
|
-
Tool(
|
|
164
|
-
name="generate_component_code",
|
|
165
|
-
description="Génère du code HTML pour un composant Concorde avec ses propriétés",
|
|
166
|
-
inputSchema={
|
|
167
|
-
"type": "object",
|
|
168
|
-
"properties": {
|
|
169
|
-
"component_name": {
|
|
170
|
-
"type": "string",
|
|
171
|
-
"description": "Nom du composant"
|
|
172
|
-
},
|
|
173
|
-
"properties": {
|
|
174
|
-
"type": "object",
|
|
175
|
-
"description": "Propriétés du composant",
|
|
176
|
-
"additionalProperties": True
|
|
177
|
-
},
|
|
178
|
-
"content": {
|
|
179
|
-
"type": "string",
|
|
180
|
-
"description": "Contenu du composant (optionnel)"
|
|
181
|
-
}
|
|
182
|
-
},
|
|
183
|
-
"required": ["component_name"]
|
|
184
|
-
}
|
|
185
|
-
),
|
|
186
|
-
Tool(
|
|
187
|
-
name="get_form_example",
|
|
188
|
-
description="Génère un exemple de formulaire avec des composants Concorde",
|
|
189
|
-
inputSchema={
|
|
190
|
-
"type": "object",
|
|
191
|
-
"properties": {
|
|
192
|
-
"fields": {
|
|
193
|
-
"type": "array",
|
|
194
|
-
"description": "Liste des champs du formulaire",
|
|
195
|
-
"items": {
|
|
196
|
-
"type": "object",
|
|
197
|
-
"properties": {
|
|
198
|
-
"name": {"type": "string"},
|
|
199
|
-
"type": {"type": "string"},
|
|
200
|
-
"label": {"type": "string"},
|
|
201
|
-
"required": {"type": "boolean"}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
),
|
|
208
|
-
Tool(
|
|
209
|
-
name="get_theme_info",
|
|
210
|
-
description="Obtient des informations sur le système de thèmes Concorde",
|
|
211
|
-
inputSchema={
|
|
212
|
-
"type": "object",
|
|
213
|
-
"properties": {
|
|
214
|
-
"theme": {
|
|
215
|
-
"type": "string",
|
|
216
|
-
"description": "Nom du thème (light, dark, auto)",
|
|
217
|
-
"enum": ["light", "dark", "auto"]
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
),
|
|
222
|
-
Tool(
|
|
223
|
-
name="get_icons_list",
|
|
224
|
-
description="Liste toutes les icônes disponibles dans Concorde",
|
|
225
|
-
inputSchema={
|
|
226
|
-
"type": "object",
|
|
227
|
-
"properties": {
|
|
228
|
-
"library": {
|
|
229
|
-
"type": "string",
|
|
230
|
-
"description": "Bibliothèque d'icônes (core, iconoir, fontAwesome)",
|
|
231
|
-
"enum": ["core", "iconoir", "fontAwesome"]
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
)
|
|
236
|
-
]
|
|
237
|
-
return ListToolsResult(tools=tools)
|
|
238
|
-
|
|
239
|
-
@self.server.call_tool()
|
|
240
|
-
async def call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResult:
|
|
241
|
-
"""Exécute un outil"""
|
|
242
|
-
if name == "search_component":
|
|
243
|
-
return await self._search_component(arguments["query"])
|
|
244
|
-
elif name == "get_component_info":
|
|
245
|
-
return await self._get_component_info(arguments["component_name"])
|
|
246
|
-
elif name == "generate_component_code":
|
|
247
|
-
return await self._generate_component_code(
|
|
248
|
-
arguments["component_name"],
|
|
249
|
-
arguments.get("properties", {}),
|
|
250
|
-
arguments.get("content", "")
|
|
251
|
-
)
|
|
252
|
-
elif name == "get_form_example":
|
|
253
|
-
return await self._get_form_example(arguments.get("fields", []))
|
|
254
|
-
elif name == "get_theme_info":
|
|
255
|
-
return await self._get_theme_info(arguments.get("theme", "light"))
|
|
256
|
-
elif name == "get_icons_list":
|
|
257
|
-
return await self._get_icons_list(arguments.get("library", "core"))
|
|
258
|
-
else:
|
|
259
|
-
return CallToolResult(content=[TextContent(type="text", text=f"Outil inconnu: {name}")])
|
|
260
|
-
|
|
261
|
-
def _load_components(self):
|
|
262
|
-
"""Charge tous les composants Concorde"""
|
|
263
|
-
|
|
264
|
-
# Composants UI
|
|
265
|
-
ui_path = COMPONENTS_PATH / "ui"
|
|
266
|
-
for comp_dir in ui_path.iterdir():
|
|
267
|
-
if comp_dir.is_dir() and not comp_dir.name.startswith("_"):
|
|
268
|
-
self._load_component(comp_dir, "ui")
|
|
269
|
-
|
|
270
|
-
# Composants fonctionnels
|
|
271
|
-
functional_path = COMPONENTS_PATH / "functional"
|
|
272
|
-
for comp_dir in functional_path.iterdir():
|
|
273
|
-
if comp_dir.is_dir():
|
|
274
|
-
self._load_component(comp_dir, "functional")
|
|
275
|
-
|
|
276
|
-
def _load_component(self, comp_dir: Path, category: str):
|
|
277
|
-
"""Charge un composant spécifique"""
|
|
278
|
-
comp_name = comp_dir.name
|
|
279
|
-
tag_name = f"sonic-{comp_name}"
|
|
280
|
-
|
|
281
|
-
# Trouve le fichier principal
|
|
282
|
-
main_file = comp_dir / f"{comp_name}.ts"
|
|
283
|
-
if not main_file.exists():
|
|
284
|
-
return
|
|
285
|
-
|
|
286
|
-
# Trouve le fichier de documentation
|
|
287
|
-
doc_file = comp_dir / f"{comp_name}.md"
|
|
288
|
-
|
|
289
|
-
# Lit le contenu du composant
|
|
290
|
-
try:
|
|
291
|
-
with open(main_file, 'r', encoding='utf-8') as f:
|
|
292
|
-
content = f.read()
|
|
293
|
-
except:
|
|
294
|
-
return
|
|
295
|
-
|
|
296
|
-
# Extrait les propriétés du composant
|
|
297
|
-
props = self._extract_properties(content)
|
|
298
|
-
|
|
299
|
-
# Extrait la description
|
|
300
|
-
description = self._extract_description(content)
|
|
301
|
-
|
|
302
|
-
# Charge la documentation si elle existe
|
|
303
|
-
doc_content = ""
|
|
304
|
-
if doc_file.exists():
|
|
305
|
-
try:
|
|
306
|
-
with open(doc_file, 'r', encoding='utf-8') as f:
|
|
307
|
-
doc_content = f.read()
|
|
308
|
-
except:
|
|
309
|
-
pass
|
|
310
|
-
|
|
311
|
-
# Extrait les exemples de la documentation
|
|
312
|
-
examples = self._extract_examples(doc_content)
|
|
313
|
-
|
|
314
|
-
comp_info = ComponentInfo(
|
|
315
|
-
name=comp_name,
|
|
316
|
-
tag_name=tag_name,
|
|
317
|
-
category=category,
|
|
318
|
-
file_path=main_file,
|
|
319
|
-
doc_path=doc_file if doc_file.exists() else None,
|
|
320
|
-
description=description,
|
|
321
|
-
props=props,
|
|
322
|
-
examples=examples
|
|
323
|
-
)
|
|
324
|
-
|
|
325
|
-
self.components[comp_name] = comp_info
|
|
326
|
-
|
|
327
|
-
def _extract_properties(self, content: str) -> List[Dict[str, Any]]:
|
|
328
|
-
"""Extrait les propriétés du composant"""
|
|
329
|
-
props = []
|
|
330
|
-
|
|
331
|
-
# Recherche les décorateurs @property
|
|
332
|
-
property_pattern = r'@property\([^)]*\)\s+(\w+)(?::\s*([^=]+))?'
|
|
333
|
-
matches = re.findall(property_pattern, content)
|
|
334
|
-
|
|
335
|
-
for match in matches:
|
|
336
|
-
prop_name = match[0]
|
|
337
|
-
prop_type = match[1] if match[1] else "string"
|
|
338
|
-
|
|
339
|
-
# Recherche les commentaires JSDoc pour cette propriété
|
|
340
|
-
comment_pattern = rf'/\*\*\s*\n\s*\*\s*([^\n]+)'
|
|
341
|
-
comment_match = re.search(comment_pattern, content)
|
|
342
|
-
description = comment_match.group(1).strip() if comment_match else ""
|
|
343
|
-
|
|
344
|
-
props.append({
|
|
345
|
-
"name": prop_name,
|
|
346
|
-
"type": prop_type,
|
|
347
|
-
"description": description
|
|
348
|
-
})
|
|
349
|
-
|
|
350
|
-
return props
|
|
351
|
-
|
|
352
|
-
def _extract_description(self, content: str) -> str:
|
|
353
|
-
"""Extrait la description du composant"""
|
|
354
|
-
# Recherche les commentaires de classe
|
|
355
|
-
class_pattern = r'/\*\*\s*\n\s*\*\s*([^\n]+)'
|
|
356
|
-
match = re.search(class_pattern, content)
|
|
357
|
-
if match:
|
|
358
|
-
return match.group(1).strip()
|
|
359
|
-
|
|
360
|
-
# Recherche le nom de la classe
|
|
361
|
-
class_name_pattern = r'export class (\w+)'
|
|
362
|
-
match = re.search(class_name_pattern, content)
|
|
363
|
-
if match:
|
|
364
|
-
return f"Composant {match.group(1)}"
|
|
365
|
-
|
|
366
|
-
return "Composant Concorde"
|
|
367
|
-
|
|
368
|
-
def _extract_examples(self, doc_content: str) -> List[str]:
|
|
369
|
-
"""Extrait les exemples de la documentation"""
|
|
370
|
-
examples = []
|
|
371
|
-
|
|
372
|
-
# Recherche les blocs sonic-code
|
|
373
|
-
code_pattern = r'<sonic-code>(.*?)</sonic-code>'
|
|
374
|
-
matches = re.findall(code_pattern, doc_content, re.DOTALL)
|
|
375
|
-
|
|
376
|
-
for match in matches:
|
|
377
|
-
# Nettoie le contenu
|
|
378
|
-
example = match.strip()
|
|
379
|
-
if example:
|
|
380
|
-
examples.append(example)
|
|
381
|
-
|
|
382
|
-
return examples
|
|
383
|
-
|
|
384
|
-
def _load_documentation(self):
|
|
385
|
-
"""Charge la documentation générale"""
|
|
386
|
-
# Documentation des concepts de base
|
|
387
|
-
self.documentation["architecture"] = """
|
|
388
|
-
# Architecture Concorde
|
|
389
|
-
|
|
390
|
-
Concorde est un framework de composants Web basé sur Lit Element avec les concepts suivants :
|
|
391
|
-
|
|
392
|
-
## Mixins principaux
|
|
393
|
-
|
|
394
|
-
### Subscriber
|
|
395
|
-
- Permet de lier un composant à un publisher
|
|
396
|
-
- Gestion automatique des données réactives
|
|
397
|
-
- Support du data binding
|
|
398
|
-
|
|
399
|
-
### FormElement
|
|
400
|
-
- Gestion des formulaires
|
|
401
|
-
- Validation automatique
|
|
402
|
-
- Liaison avec les publishers de formulaire
|
|
403
|
-
|
|
404
|
-
### Fetcher
|
|
405
|
-
- Gestion des appels API
|
|
406
|
-
- Chargement automatique des données
|
|
407
|
-
- Support des filtres et pagination
|
|
408
|
-
|
|
409
|
-
## Système de thèmes
|
|
410
|
-
- Thèmes light/dark/auto
|
|
411
|
-
- Variables CSS personnalisables
|
|
412
|
-
- Support des couleurs sémantiques
|
|
413
|
-
|
|
414
|
-
## Composants disponibles
|
|
415
|
-
- Composants UI : boutons, inputs, modales, etc.
|
|
416
|
-
- Composants fonctionnels : fetch, list, router, etc.
|
|
417
|
-
- Système d'icônes intégré
|
|
418
|
-
"""
|
|
419
|
-
|
|
420
|
-
self.documentation["data-binding"] = """
|
|
421
|
-
# Data Binding Concorde
|
|
422
|
-
|
|
423
|
-
## Syntaxe
|
|
424
|
-
- `data-bind ::attribute="expression"` : Liaison d'attribut
|
|
425
|
-
- `data-bind ::inner-html="expression"` : Liaison de contenu HTML
|
|
426
|
-
- `data-bind ::text-content="expression"` : Liaison de contenu texte
|
|
427
|
-
|
|
428
|
-
## Expressions
|
|
429
|
-
- `$property` : Accès à une propriété du publisher
|
|
430
|
-
- `$property.subproperty` : Accès à une sous-propriété
|
|
431
|
-
- `function|$property` : Application d'une fonction
|
|
432
|
-
|
|
433
|
-
## Exemples
|
|
434
|
-
```html
|
|
435
|
-
<sonic-input data-bind ::value="$email"></sonic-input>
|
|
436
|
-
<span data-bind ::inner-html="$name"></span>
|
|
437
|
-
<div data-bind ::class="active|$isActive"></div>
|
|
438
|
-
```
|
|
439
|
-
"""
|
|
440
|
-
|
|
441
|
-
async def _search_component(self, query: str) -> CallToolResult:
|
|
442
|
-
"""Recherche un composant"""
|
|
443
|
-
results = []
|
|
444
|
-
query_lower = query.lower()
|
|
445
|
-
|
|
446
|
-
for comp_name, comp_info in self.components.items():
|
|
447
|
-
if (query_lower in comp_name.lower() or
|
|
448
|
-
query_lower in comp_info.description.lower() or
|
|
449
|
-
query_lower in comp_info.tag_name.lower()):
|
|
450
|
-
results.append(f"- **{comp_info.tag_name}** ({comp_info.category}): {comp_info.description}")
|
|
451
|
-
|
|
452
|
-
if results:
|
|
453
|
-
content = f"Composants trouvés pour '{query}':\n\n" + "\n".join(results)
|
|
454
|
-
else:
|
|
455
|
-
content = f"Aucun composant trouvé pour '{query}'"
|
|
456
|
-
|
|
457
|
-
return CallToolResult(content=[TextContent(type="text", text=content)])
|
|
458
|
-
|
|
459
|
-
async def _get_component_info(self, component_name: str) -> CallToolResult:
|
|
460
|
-
"""Obtient des informations sur un composant"""
|
|
461
|
-
if component_name not in self.components:
|
|
462
|
-
return CallToolResult(content=[TextContent(type="text", text=f"Composant '{component_name}' non trouvé")])
|
|
463
|
-
|
|
464
|
-
comp_info = self.components[component_name]
|
|
465
|
-
|
|
466
|
-
# Génère la documentation complète
|
|
467
|
-
content = f"""# {comp_info.tag_name}
|
|
468
|
-
|
|
469
|
-
**Catégorie:** {comp_info.category}
|
|
470
|
-
**Description:** {comp_info.description}
|
|
471
|
-
|
|
472
|
-
## Propriétés
|
|
473
|
-
|
|
474
|
-
"""
|
|
475
|
-
|
|
476
|
-
for prop in comp_info.props:
|
|
477
|
-
content += f"- **{prop['name']}** ({prop['type']}): {prop['description']}\n"
|
|
478
|
-
|
|
479
|
-
if comp_info.examples:
|
|
480
|
-
content += "\n## Exemples\n\n"
|
|
481
|
-
for i, example in enumerate(comp_info.examples[:3], 1): # Limite à 3 exemples
|
|
482
|
-
content += f"### Exemple {i}\n\n```html\n{example}\n```\n\n"
|
|
483
|
-
|
|
484
|
-
return CallToolResult(content=[TextContent(type="text", text=content)])
|
|
485
|
-
|
|
486
|
-
async def _generate_component_code(self, component_name: str, properties: Dict[str, Any], content: str = "") -> CallToolResult:
|
|
487
|
-
"""Génère du code pour un composant"""
|
|
488
|
-
if component_name not in self.components:
|
|
489
|
-
return CallToolResult(content=[TextContent(type="text", text=f"Composant '{component_name}' non trouvé")])
|
|
490
|
-
|
|
491
|
-
comp_info = self.components[component_name]
|
|
492
|
-
tag_name = comp_info.tag_name
|
|
493
|
-
|
|
494
|
-
# Génère les attributs
|
|
495
|
-
attributes = []
|
|
496
|
-
for key, value in properties.items():
|
|
497
|
-
if isinstance(value, bool):
|
|
498
|
-
if value:
|
|
499
|
-
attributes.append(key)
|
|
500
|
-
else:
|
|
501
|
-
attributes.append(f'{key}="{value}"')
|
|
502
|
-
|
|
503
|
-
# Génère le code HTML
|
|
504
|
-
if content:
|
|
505
|
-
html_code = f"<{tag_name} {' '.join(attributes)}>{content}</{tag_name}>"
|
|
506
|
-
else:
|
|
507
|
-
html_code = f"<{tag_name} {' '.join(attributes)}></{tag_name}>"
|
|
508
|
-
|
|
509
|
-
return CallToolResult(content=[TextContent(type="text", text=html_code)])
|
|
510
|
-
|
|
511
|
-
async def _get_form_example(self, fields: List[Dict[str, Any]]) -> CallToolResult:
|
|
512
|
-
"""Génère un exemple de formulaire"""
|
|
513
|
-
if not fields:
|
|
514
|
-
# Exemple par défaut
|
|
515
|
-
fields = [
|
|
516
|
-
{"name": "email", "type": "email", "label": "Email", "required": True},
|
|
517
|
-
{"name": "password", "type": "password", "label": "Mot de passe", "required": True},
|
|
518
|
-
{"name": "remember", "type": "checkbox", "label": "Se souvenir de moi", "required": False}
|
|
519
|
-
]
|
|
520
|
-
|
|
521
|
-
form_html = '<div formDataProvider="exampleForm">\n'
|
|
522
|
-
|
|
523
|
-
for field in fields:
|
|
524
|
-
field_name = field["name"]
|
|
525
|
-
field_type = field["type"]
|
|
526
|
-
field_label = field["label"]
|
|
527
|
-
field_required = field.get("required", False)
|
|
528
|
-
|
|
529
|
-
if field_type == "checkbox":
|
|
530
|
-
form_html += f' <sonic-checkbox name="{field_name}"'
|
|
531
|
-
if field_required:
|
|
532
|
-
form_html += " required"
|
|
533
|
-
form_html += f'>{field_label}</sonic-checkbox>\n'
|
|
534
|
-
else:
|
|
535
|
-
form_html += f' <sonic-input name="{field_name}" type="{field_type}" label="{field_label}"'
|
|
536
|
-
if field_required:
|
|
537
|
-
form_html += " required"
|
|
538
|
-
form_html += '></sonic-input>\n'
|
|
539
|
-
|
|
540
|
-
form_html += ' <sonic-form-actions>\n'
|
|
541
|
-
form_html += ' <sonic-button type="primary">Valider</sonic-button>\n'
|
|
542
|
-
form_html += ' <sonic-button variant="outline">Annuler</sonic-button>\n'
|
|
543
|
-
form_html += ' </sonic-form-actions>\n'
|
|
544
|
-
form_html += '</div>'
|
|
545
|
-
|
|
546
|
-
return CallToolResult(content=[TextContent(type="text", text=form_html)])
|
|
547
|
-
|
|
548
|
-
async def _get_theme_info(self, theme: str) -> CallToolResult:
|
|
549
|
-
"""Obtient des informations sur le thème"""
|
|
550
|
-
theme_info = {
|
|
551
|
-
"light": {
|
|
552
|
-
"description": "Thème clair par défaut",
|
|
553
|
-
"colors": {
|
|
554
|
-
"primary": "#1e293b",
|
|
555
|
-
"info": "#2563eb",
|
|
556
|
-
"danger": "#f43f5e",
|
|
557
|
-
"warning": "#f97316",
|
|
558
|
-
"success": "#14b8a6"
|
|
559
|
-
}
|
|
560
|
-
},
|
|
561
|
-
"dark": {
|
|
562
|
-
"description": "Thème sombre",
|
|
563
|
-
"colors": {
|
|
564
|
-
"primary": "#334155",
|
|
565
|
-
"info": "#3abff8",
|
|
566
|
-
"danger": "#f87272",
|
|
567
|
-
"warning": "#fbbd23",
|
|
568
|
-
"success": "#36d399"
|
|
569
|
-
}
|
|
570
|
-
},
|
|
571
|
-
"auto": {
|
|
572
|
-
"description": "Thème automatique basé sur les préférences système",
|
|
573
|
-
"colors": "Utilise les couleurs du thème sombre si l'utilisateur préfère le mode sombre"
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
info = theme_info.get(theme, theme_info["light"])
|
|
578
|
-
|
|
579
|
-
content = f"""# Thème {theme}
|
|
580
|
-
|
|
581
|
-
{info['description']}
|
|
582
|
-
|
|
583
|
-
## Utilisation
|
|
584
|
-
|
|
585
|
-
```html
|
|
586
|
-
<sonic-theme theme="{theme}">
|
|
587
|
-
<!-- Vos composants ici -->
|
|
588
|
-
</sonic-theme>
|
|
589
|
-
```
|
|
590
|
-
|
|
591
|
-
"""
|
|
592
|
-
|
|
593
|
-
if "colors" in info and isinstance(info["colors"], dict):
|
|
594
|
-
content += "## Couleurs\n\n"
|
|
595
|
-
for color_name, color_value in info["colors"].items():
|
|
596
|
-
content += f"- **{color_name}**: `{color_value}`\n"
|
|
597
|
-
|
|
598
|
-
return CallToolResult(content=[TextContent(type="text", text=content)])
|
|
599
|
-
|
|
600
|
-
async def _get_icons_list(self, library: str) -> CallToolResult:
|
|
601
|
-
"""Liste les icônes disponibles"""
|
|
602
|
-
icons_info = {
|
|
603
|
-
"core": [
|
|
604
|
-
"cancel", "check-circled-outline", "check", "emoji-puzzled",
|
|
605
|
-
"info-empty", "loader", "minus-small", "more-horiz", "more-vert",
|
|
606
|
-
"nav-arrow-down", "warning-circled-outline"
|
|
607
|
-
],
|
|
608
|
-
"iconoir": "Bibliothèque d'icônes externe avec de nombreuses icônes",
|
|
609
|
-
"fontAwesome": "Bibliothèque FontAwesome avec icônes solid et regular"
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
if library == "core":
|
|
613
|
-
icons = icons_info["core"]
|
|
614
|
-
content = f"""# Icônes Core Concorde
|
|
615
|
-
|
|
616
|
-
## Utilisation
|
|
617
|
-
|
|
618
|
-
```html
|
|
619
|
-
<sonic-icon library="core" name="nom-de-l-icone"></sonic-icon>
|
|
620
|
-
```
|
|
621
|
-
|
|
622
|
-
## Icônes disponibles
|
|
623
|
-
|
|
624
|
-
"""
|
|
625
|
-
for icon in icons:
|
|
626
|
-
content += f"- **{icon}**: `<sonic-icon library="core" name="{icon}"></sonic-icon>`\n"
|
|
627
|
-
else:
|
|
628
|
-
content = f"""# Bibliothèque d'icônes {library}
|
|
629
|
-
|
|
630
|
-
{icons_info[library]}
|
|
631
|
-
|
|
632
|
-
## Utilisation
|
|
633
|
-
|
|
634
|
-
```html
|
|
635
|
-
<sonic-icon library="{library}" name="nom-de-l-icone"></sonic-icon>
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
Pour les icônes FontAwesome, vous pouvez spécifier le préfixe :
|
|
639
|
-
```html
|
|
640
|
-
<sonic-icon library="fontAwesome" prefix="solid" name="home"></sonic-icon>
|
|
641
|
-
<sonic-icon library="fontAwesome" prefix="regular" name="user"></sonic-icon>
|
|
642
|
-
```
|
|
643
|
-
"""
|
|
644
|
-
|
|
645
|
-
return CallToolResult(content=[TextContent(type="text", text=content)])
|
|
646
|
-
|
|
647
|
-
def _generate_component_doc(self, comp_info: ComponentInfo) -> str:
|
|
648
|
-
"""Génère la documentation complète d'un composant"""
|
|
649
|
-
content = f"""# {comp_info.tag_name}
|
|
650
|
-
|
|
651
|
-
**Catégorie:** {comp_info.category}
|
|
652
|
-
**Description:** {comp_info.description}
|
|
653
|
-
|
|
654
|
-
## Propriétés
|
|
655
|
-
|
|
656
|
-
"""
|
|
657
|
-
|
|
658
|
-
for prop in comp_info.props:
|
|
659
|
-
content += f"- **{prop['name']}** ({prop['type']}): {prop['description']}\n"
|
|
660
|
-
|
|
661
|
-
if comp_info.examples:
|
|
662
|
-
content += "\n## Exemples\n\n"
|
|
663
|
-
for i, example in enumerate(comp_info.examples, 1):
|
|
664
|
-
content += f"### Exemple {i}\n\n```html\n{example}\n```\n\n"
|
|
665
|
-
|
|
666
|
-
return content
|
|
667
|
-
|
|
668
|
-
def _generate_ui_examples(self) -> str:
|
|
669
|
-
"""Génère des exemples de composants UI"""
|
|
670
|
-
content = """# Exemples de composants UI Concorde
|
|
671
|
-
|
|
672
|
-
## Bouton avec icône
|
|
673
|
-
|
|
674
|
-
```html
|
|
675
|
-
<sonic-button type="primary">
|
|
676
|
-
<sonic-icon library="core" name="check" slot="prefix"></sonic-icon>
|
|
677
|
-
Valider
|
|
678
|
-
</sonic-button>
|
|
679
|
-
```
|
|
680
|
-
|
|
681
|
-
## Formulaire complet
|
|
682
|
-
|
|
683
|
-
```html
|
|
684
|
-
<div formDataProvider="userForm">
|
|
685
|
-
<sonic-fieldset label="Informations utilisateur">
|
|
686
|
-
<sonic-form-layout>
|
|
687
|
-
<sonic-input name="firstName" label="Prénom" required></sonic-input>
|
|
688
|
-
<sonic-input name="lastName" label="Nom" required></sonic-input>
|
|
689
|
-
<sonic-input name="email" type="email" label="Email" required></sonic-input>
|
|
690
|
-
</sonic-form-layout>
|
|
691
|
-
</sonic-fieldset>
|
|
692
|
-
<sonic-form-actions>
|
|
693
|
-
<sonic-button type="primary">Enregistrer</sonic-button>
|
|
694
|
-
<sonic-button variant="outline">Annuler</sonic-button>
|
|
695
|
-
</sonic-form-actions>
|
|
696
|
-
</div>
|
|
697
|
-
```
|
|
698
|
-
|
|
699
|
-
## Modal avec contenu
|
|
700
|
-
|
|
701
|
-
```html
|
|
702
|
-
<sonic-modal id="exampleModal">
|
|
703
|
-
<sonic-modal-title>Confirmation</sonic-modal-title>
|
|
704
|
-
<sonic-modal-content>
|
|
705
|
-
Êtes-vous sûr de vouloir continuer ?
|
|
706
|
-
</sonic-modal-content>
|
|
707
|
-
<sonic-modal-actions>
|
|
708
|
-
<sonic-button hideModal>Annuler</sonic-button>
|
|
709
|
-
<sonic-button type="primary" hideModal>Confirmer</sonic-button>
|
|
710
|
-
</sonic-modal-actions>
|
|
711
|
-
</sonic-modal>
|
|
712
|
-
```
|
|
713
|
-
"""
|
|
714
|
-
return content
|
|
715
|
-
|
|
716
|
-
def _generate_functional_examples(self) -> str:
|
|
717
|
-
"""Génère des exemples de composants fonctionnels"""
|
|
718
|
-
content = """# Exemples de composants fonctionnels Concorde
|
|
719
|
-
|
|
720
|
-
## Chargement de données avec Fetch
|
|
721
|
-
|
|
722
|
-
```html
|
|
723
|
-
<sonic-fetch
|
|
724
|
-
serviceURL="https://api.example.com"
|
|
725
|
-
endPoint="users"
|
|
726
|
-
dataProvider="usersData">
|
|
727
|
-
</sonic-fetch>
|
|
728
|
-
|
|
729
|
-
<sonic-list dataProvider="usersData" props='$usersData'>
|
|
730
|
-
<template>
|
|
731
|
-
<div class="user-card">
|
|
732
|
-
<h3 data-bind ::inner-html="$name"></h3>
|
|
733
|
-
<p data-bind ::inner-html="$email"></p>
|
|
734
|
-
</div>
|
|
735
|
-
</template>
|
|
736
|
-
</sonic-list>
|
|
737
|
-
```
|
|
738
|
-
|
|
739
|
-
## Navigation avec Router
|
|
740
|
-
|
|
741
|
-
```html
|
|
742
|
-
<sonic-router>
|
|
743
|
-
<template data-route="#home">
|
|
744
|
-
<h1>Page d'accueil</h1>
|
|
745
|
-
</template>
|
|
746
|
-
<template data-route="#about">
|
|
747
|
-
<h1>À propos</h1>
|
|
748
|
-
</template>
|
|
749
|
-
<template data-route="#contact">
|
|
750
|
-
<h1>Contact</h1>
|
|
751
|
-
</template>
|
|
752
|
-
</sonic-router>
|
|
753
|
-
|
|
754
|
-
<nav>
|
|
755
|
-
<sonic-button href="#home">Accueil</sonic-button>
|
|
756
|
-
<sonic-button href="#about">À propos</sonic-button>
|
|
757
|
-
<sonic-button href="#contact">Contact</sonic-button>
|
|
758
|
-
</nav>
|
|
759
|
-
```
|
|
760
|
-
|
|
761
|
-
## États conditionnels
|
|
762
|
-
|
|
763
|
-
```html
|
|
764
|
-
<sonic-states dataProvider="userData" data-path="status">
|
|
765
|
-
<template data-value="loading">
|
|
766
|
-
<sonic-loader></sonic-loader>
|
|
767
|
-
</template>
|
|
768
|
-
<template data-value="success">
|
|
769
|
-
<div class="success-message">Données chargées avec succès</div>
|
|
770
|
-
</template>
|
|
771
|
-
<template data-value="error">
|
|
772
|
-
<sonic-alert type="danger">Erreur lors du chargement</sonic-alert>
|
|
773
|
-
</template>
|
|
774
|
-
</sonic-states>
|
|
775
|
-
```
|
|
776
|
-
"""
|
|
777
|
-
return content
|
|
778
|
-
|
|
779
|
-
async def run(self):
|
|
780
|
-
"""Lance le serveur MCP"""
|
|
781
|
-
async with stdio_server() as (read_stream, write_stream):
|
|
782
|
-
await self.server.run(
|
|
783
|
-
read_stream,
|
|
784
|
-
write_stream,
|
|
785
|
-
InitializationOptions(
|
|
786
|
-
server_name="concorde-mcp",
|
|
787
|
-
server_version="1.0.0",
|
|
788
|
-
capabilities=self.server.get_capabilities(
|
|
789
|
-
notification_options=None,
|
|
790
|
-
experimental_capabilities={}
|
|
791
|
-
)
|
|
792
|
-
)
|
|
793
|
-
)
|
|
794
|
-
|
|
795
|
-
async def main():
|
|
796
|
-
"""Point d'entrée principal"""
|
|
797
|
-
server = ConcordeMCPServer()
|
|
798
|
-
await server.run()
|
|
799
|
-
|
|
800
|
-
if __name__ == "__main__":
|
|
801
|
-
asyncio.run(main())
|