feat(memory): scope coding + tools record_lesson/get_context_pack + branchement git_sync
This commit is contained in:
+60
-1
@@ -8,7 +8,8 @@ from mcp.server.sse import SseServerTransport
|
|||||||
from mcp.types import Tool, TextContent
|
from mcp.types import Tool, TextContent
|
||||||
from app.auth import get_current_agent
|
from app.auth import get_current_agent
|
||||||
from app.scopes import has_scope_access, ALL_SCOPES
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -89,6 +90,38 @@ async def handle_list_tools() -> list[Tool]:
|
|||||||
},
|
},
|
||||||
"required": ["scope", "agent", "data"]
|
"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)
|
await set_rule(scope, existing)
|
||||||
return [TextContent(type="text", text=json.dumps({"status": "ok", "scope": scope, "updated_keys": list(data.keys())}))]
|
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"}))]
|
return [TextContent(type="text", text=json.dumps({"error": "Unknown tool"}))]
|
||||||
|
|
||||||
# FastMCP / SSE Integration
|
# FastMCP / SSE Integration
|
||||||
|
|||||||
Reference in New Issue
Block a user