From 906acfb1f9513a5a2292aac060f70869bcf8d30c Mon Sep 17 00:00:00 2001 From: bolbol Date: Thu, 30 Jul 2026 08:36:21 +0000 Subject: [PATCH] feat(memory): scope coding + tools record_lesson/get_context_pack + branchement git_sync --- app/routes/mcp.py | 61 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/app/routes/mcp.py b/app/routes/mcp.py index 143c1a3..1e9b858 100644 --- a/app/routes/mcp.py +++ b/app/routes/mcp.py @@ -8,7 +8,8 @@ from mcp.server.sse import SseServerTransport from mcp.types import Tool, TextContent from app.auth import get_current_agent from app.scopes import has_scope_access, ALL_SCOPES -from app.models import get_rule, get_all_rules, get_all_ports, get_port, set_rule, get_agent_by_key, update_key_last_used +from app.models import (get_rule, get_all_rules, get_all_ports, get_port, set_rule, get_agent_by_key, update_key_last_used, add_memory_entry, list_memory_entries) +from app.git_sync import push_scope_markdown logger = logging.getLogger(__name__) @@ -89,6 +90,38 @@ async def handle_list_tools() -> list[Tool]: }, "required": ["scope", "agent", "data"] } + ), + Tool( + name="record_lesson", + description=("Record a typed, durable memory entry (decision/constraint/best-practice/" + "common-error/do-not-use) in the coding scope. Call after a meaningful " + "decision, a repeated correction, or a rejected approach worth remembering."), + inputSchema={ + "type": "object", + "properties": { + "scope": {"type": "string", "description": "Scope, actuellement: coding"}, + "project": {"type": "string", "description": "Nom du repo/projet, omettre si transverse"}, + "type": {"type": "string", "enum": ["decision", "constraint", "best-practice", "common-error", "do-not-use"]}, + "title": {"type": "string", "description": "Resume court, 1 ligne"}, + "body": {"type": "string", "description": "1-3 phrases"}, + "tags": {"type": "array", "items": {"type": "string"}} + }, + "required": ["scope", "type", "title", "body"] + } + ), + Tool( + name="get_context_pack", + description=("Load only the memory entries relevant to the current task (selective " + "loading, not the whole scope) — call before substantial coding work."), + inputSchema={ + "type": "object", + "properties": { + "scope": {"type": "string", "description": "Scope, actuellement: coding"}, + "project": {"type": "string", "description": "Filtrer par projet ; omettre pour les entrees transverses"}, + "type": {"type": "string", "enum": ["decision", "constraint", "best-practice", "common-error", "do-not-use"]} + }, + "required": ["scope"] + } ) ] @@ -162,6 +195,32 @@ async def handle_call_tool(name: str, arguments: dict) -> list[TextContent]: await set_rule(scope, existing) return [TextContent(type="text", text=json.dumps({"status": "ok", "scope": scope, "updated_keys": list(data.keys())}))] + elif name == "record_lesson": + scope = arguments.get("scope") + if not has_scope_access(caller_agent, scope): + return [TextContent(type="text", text=json.dumps({"error": f"Agent {caller_agent} forbidden on scope {scope}"}))] + type_ = arguments.get("type") + title = arguments.get("title") + body = arguments.get("body") + if not (type_ and title and body): + return [TextContent(type="text", text=json.dumps({"error": "type, title et body sont requis"}))] + entry = await add_memory_entry( + scope=scope, type_=type_, title=title, body=body, created_by=caller_agent, + project=arguments.get("project"), tags=arguments.get("tags") + ) + git_result = await push_scope_markdown(scope) + entry["_git"] = git_result + return [TextContent(type="text", text=json.dumps(entry))] + + elif name == "get_context_pack": + scope = arguments.get("scope") + if not has_scope_access(caller_agent, scope): + return [TextContent(type="text", text=json.dumps({"error": f"Agent {caller_agent} forbidden on scope {scope}"}))] + entries = await list_memory_entries( + scope=scope, project=arguments.get("project"), type_=arguments.get("type"), status="active" + ) + return [TextContent(type="text", text=json.dumps(entries))] + return [TextContent(type="text", text=json.dumps({"error": "Unknown tool"}))] # FastMCP / SSE Integration