feat: write API + session protocol

This commit is contained in:
Nabil Derouiche
2026-06-26 00:35:34 +01:00
parent 9e7a974538
commit 395381ae07
11 changed files with 361 additions and 21 deletions
Binary file not shown.
+26 -1
View File
@@ -2,7 +2,7 @@ import json
from fastapi import APIRouter, Depends, HTTPException, Request
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, add_audit_log
from app.models import get_rule, get_all_rules, get_all_ports, add_audit_log, set_rule
router = APIRouter(prefix="/api")
@@ -48,3 +48,28 @@ async def search_rules(q: str, request: Request, agent: str = Depends(get_curren
results[scope] = content
return results
@router.patch("/rules/{scope}")
async def patch_scope_rules(scope: str, request: Request, agent: str = Depends(get_current_agent)):
if not has_scope_access(agent, scope):
await log_audit(request, agent, f"{scope} (WRITE FORBIDDEN)")
raise HTTPException(status_code=403, detail="Forbidden")
body = await request.json()
if not isinstance(body, dict):
raise HTTPException(status_code=400, detail="Body must be a JSON object")
existing = await get_rule(scope) or {}
existing.update(body)
await set_rule(scope, existing)
await log_audit(request, agent, f"{scope} (WRITE)")
return {"status": "ok", "scope": scope, "updated_keys": list(body.keys())}
@router.get("/context")
async def get_agent_context(request: Request, agent: str = Depends(get_current_agent)):
await log_audit(request, agent, "context (READ ALL)")
all_rules = await get_all_rules()
result = {}
for scope in ALL_SCOPES:
if has_scope_access(agent, scope):
result[scope] = all_rules.get(scope, {})
ports = await get_all_ports()
return {"agent": agent, "scopes": result, "ports": ports}
+41 -1
View File
@@ -7,7 +7,7 @@ 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
from app.models import get_rule, get_all_rules, get_all_ports, get_port, set_rule
logger = logging.getLogger(__name__)
@@ -68,6 +68,24 @@ async def handle_list_tools() -> list[Tool]:
},
"required": ["port"]
}
),
Tool(
name="get_my_context",
description="Get all scopes authorized for this agent — call at START of session",
inputSchema={"type": "object", "properties": {"agent": {"type": "string"}}, "required": ["agent"]}
),
Tool(
name="update_rule",
description="Merge-update a scope with new key-value data — call at END of session",
inputSchema={
"type": "object",
"properties": {
"scope": {"type": "string", "description": "Scope to update (infra/llm/nyora/perso/tt)"},
"agent": {"type": "string", "description": "Agent name for scope access check"},
"data": {"type": "object", "description": "Key-value pairs to merge into the scope"}
},
"required": ["scope", "agent", "data"]
}
)
]
@@ -124,6 +142,28 @@ async def handle_call_tool(name: str, arguments: dict) -> list[TextContent]:
else:
return [TextContent(type="text", text=json.dumps({"status": "free", "port": port}))]
elif name == "get_my_context":
agent_name = arguments.get("agent", "")
all_rules = await get_all_rules()
result = {}
for scope in ALL_SCOPES:
if has_scope_access(agent_name, scope):
result[scope] = all_rules.get(scope, {})
return [TextContent(type="text", text=json.dumps({"agent": agent_name, "scopes": result}))]
elif name == "update_rule":
scope = arguments.get("scope")
agent_name = arguments.get("agent", "")
data = arguments.get("data", {})
if not has_scope_access(agent_name, scope):
return [TextContent(type="text", text=json.dumps({"error": f"Agent {agent_name} forbidden on scope {scope}"}))]
if not isinstance(data, dict):
return [TextContent(type="text", text=json.dumps({"error": "data must be a JSON object"}))]
existing = await get_rule(scope) or {}
existing.update(data)
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({"error": "Unknown tool"}))]
# FastMCP / SSE Integration