Add comprehensive Claude Code configuration for Rust/Go projects, including: - CLAUDE.md with delegation heuristics and guardrails - Subagents: qwen3-worker (LM Studio), code-reviewer, task-planner - Skills for delegation rules and Rust/Go conventions - Hooks: post-write-lint, pre-bash-guard, subagent-stop-log - MCP server (stdio) to interface with LM Studio - README and technical report documenting the architecture
105 lines
3.3 KiB
Python
105 lines
3.3 KiB
Python
#!/usr/bin/env -S .claude/venv/bin/python3
|
|
"""
|
|
MCP server stdio exposant un outil `qwen3_task`.
|
|
Appelé par Claude Code via : claude mcp add --transport stdio qwen3 -- .claude/venv/bin/python3 .claude/mcp/qwen3-mcp/server.py
|
|
"""
|
|
import sys
|
|
import json
|
|
import requests
|
|
|
|
LMSTUDIO_URL = "http://localhost:1248/v1/chat/completions"
|
|
QWEN3_MODEL = "qwen/qwen3-coder-next"
|
|
|
|
TOOLS = [{
|
|
"name": "qwen3_task",
|
|
"description": "Délègue une tâche de codage atomique à Qwen3-Coder via LM Studio.",
|
|
"inputSchema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"task": {"type": "string", "description": "Description précise de la tâche"},
|
|
"files": {"type": "array", "items": {"type": "string"}, "description": "Chemins des fichiers concernés"}
|
|
},
|
|
"required": ["task"]
|
|
}
|
|
}]
|
|
|
|
|
|
def send(obj: dict):
|
|
print(json.dumps(obj), flush=True)
|
|
|
|
|
|
def call_qwen3(task: str, files: list[str]) -> str:
|
|
context = ""
|
|
for path in files:
|
|
try:
|
|
with open(path) as f:
|
|
context += f"--- {path} ---\n{f.read()}\n\n"
|
|
except OSError as e:
|
|
context += f"--- {path} --- ERREUR: {e}\n\n"
|
|
|
|
prompt = f"""Tu es un assistant de codage. Effectue la tâche suivante de manière précise.
|
|
|
|
TÂCHE: {task}
|
|
|
|
FICHIERS:
|
|
{context}
|
|
|
|
Retourne uniquement le code modifié ou généré, sans explication.
|
|
"""
|
|
resp = requests.post(
|
|
LMSTUDIO_URL,
|
|
json={
|
|
"model": QWEN3_MODEL,
|
|
"messages": [{"role": "user", "content": prompt}],
|
|
"temperature": 0.2,
|
|
"max_tokens": 4096,
|
|
},
|
|
timeout=120,
|
|
)
|
|
resp.raise_for_status()
|
|
return resp.json()["choices"][0]["message"]["content"]
|
|
|
|
|
|
def handle(req: dict):
|
|
method = req.get("method", "")
|
|
req_id = req.get("id")
|
|
|
|
if method == "initialize":
|
|
send({
|
|
"jsonrpc": "2.0",
|
|
"id": req_id,
|
|
"result": {
|
|
"protocolVersion": "2024-11-05",
|
|
"capabilities": {"tools": {}},
|
|
"serverInfo": {"name": "qwen3-mcp", "version": "1.0.0"},
|
|
},
|
|
})
|
|
elif method == "notifications/initialized":
|
|
pass # notification, pas de réponse
|
|
elif method == "tools/list":
|
|
send({"jsonrpc": "2.0", "id": req_id, "result": {"tools": TOOLS}})
|
|
elif method == "tools/call":
|
|
params = req.get("params", {})
|
|
name = params.get("name", "")
|
|
args = params.get("arguments", {})
|
|
if name == "qwen3_task":
|
|
try:
|
|
text = call_qwen3(args["task"], args.get("files", []))
|
|
send({"jsonrpc": "2.0", "id": req_id, "result": {"content": [{"type": "text", "text": text}]}})
|
|
except Exception as e:
|
|
send({"jsonrpc": "2.0", "id": req_id, "result": {"content": [{"type": "text", "text": f"ERROR: {e}"}], "isError": True}})
|
|
else:
|
|
send({"jsonrpc": "2.0", "id": req_id, "error": {"code": -32601, "message": f"Outil inconnu : {name}"}})
|
|
elif req_id is not None:
|
|
send({"jsonrpc": "2.0", "id": req_id, "error": {"code": -32601, "message": f"Méthode inconnue : {method}"}})
|
|
|
|
|
|
for line in sys.stdin:
|
|
line = line.strip()
|
|
if not line:
|
|
continue
|
|
try:
|
|
handle(json.loads(line))
|
|
except Exception as e:
|
|
print(json.dumps({"error": str(e)}), file=sys.stderr, flush=True)
|