From 395381ae071e63cb1cda00a16828e3729e502a16 Mon Sep 17 00:00:00 2001 From: Nabil Derouiche Date: Fri, 26 Jun 2026 00:35:34 +0100 Subject: [PATCH] feat: write API + session protocol --- @eaDir/walkthrough.2.md@SynoEAStream | Bin 0 -> 163 bytes app/__pycache__/main.cpython-311.pyc | Bin 0 -> 2067 bytes app/routes/admin.py | Bin 4088 -> 4506 bytes app/routes/api.py | 27 +++- app/routes/mcp.py | 42 +++++- data/context_hub.db | Bin 45056 -> 45056 bytes requirements.txt | 1 + templates/CONCEPT.html | 186 +++++++++++++++++++++++++++ templates/dashboard.html | Bin 22656 -> 22509 bytes templates/login.html | 100 +++++++++++--- walkthrough.2.md | 26 ++++ 11 files changed, 361 insertions(+), 21 deletions(-) create mode 100644 @eaDir/walkthrough.2.md@SynoEAStream create mode 100644 app/__pycache__/main.cpython-311.pyc create mode 100644 templates/CONCEPT.html create mode 100644 walkthrough.2.md diff --git a/@eaDir/walkthrough.2.md@SynoEAStream b/@eaDir/walkthrough.2.md@SynoEAStream new file mode 100644 index 0000000000000000000000000000000000000000..1ca67288b7db96eab0a81071a284a1f896d5bcea GIT binary patch literal 163 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aUl?c_=|y<2;dkJ5(HHS(lG;wxzV&S oBE&_L^K8N*0IR3dSbI8Giy>?43^R5CR4EZdU?DOE{o3zKw+fjb^M zjDaUWG%y0sOFe0jk__yoknHiLLku1O(E!c(A)NULAR1we@t!nDY2w~QPacl)7N6w* z@JYSZC)dkikdlevj7aVgIs}8GzELF)wVCXT0}RQJ_)cE%oq{`_vZn1qr4XL=&2gqj z;uJ2#nIU)+X)}L|-PbkD<7>L-fARGVVAGqv?wiJ%X;BZv?3cGTHgB4QSoDF;s3vn+ zubDii6WesSY#X%~;BGpmUnO_;(uV8!^gI8f?fRX)*vF--rK_66W4`HIl*jK==9#W@ zju*et6rRy7%Y9IFnYm>;9-oLrgHb{q-_$LyXJJ!++a-N*o9YAt?ZwMpm?>8y3dtsk z$*AGGY!?K@(QVii7MWkK83Toejz{Prz}YoGUm*++%I~)#8o98C@UXs@zeP{wAH#`RNLh#*Qs`MVVA*c2Ecm<&|mO1 z(4VP^pR}K~#;wth2}3w69NY4@-+T=5NTXvAOe9m>t-K!9rS5Ub-C+f=h>!@!W&!cB zC}h>xrp6AN2UQWk6K^ZZ>11rONrLoTpv;9?z)g>D(5S{6)T_CUN6&G2C?^@b!_r;> z=3;3HK9+{NGt6~QEn62x@a&*#!SL*$E5U!y4w?^!=ZTtWY&CB_|1?mSTIy0;U24cDNd5!men+{E zlI!HBn;&$hFGo}er)h-quaJtRe*`)W(Ky1&nbdfPQWd9u<%3vILzpoQ1kLE979rnp}`Fk5)0jb~0J zj3-YKpjR@IX8RfyNwLR;{ldX)3+LN7FYJoKt|;s#`x+JDa)3&MQ|ur-^FI_H6@}lT P@LLpq*+E694V(C1k-OgY literal 0 HcmV?d00001 diff --git a/app/routes/admin.py b/app/routes/admin.py index ec1b612cbdaef5236037b8f0fbfe45c2fc6e940d..c965b25ba88670327134aefdceb64ebb3f505536 100644 GIT binary patch delta 315 zcmYk0K~BRk5JeTS03u3HfEXDGC#50{s4S2w=s6IIEXOGp!H#T4RTV|tf$TT|Tht@; z8Z0?PmyEr_oAJz_dH?UvKQF!yv*=_cT*2zPXtZ(L+vsC-7e5-^I$^F+N=U{UnX&Rs zstngi8Z*0597<6ED&e$bjwQo*Oo!iNa^^u67Qog_I{1&bNjm(DE)Uqc7=|PAp3i1! z4ESoj@)u9tWP;x*5XPOgRMyA86W>9x81- z4Wt5tHXi&OER1liQP7b9vWPY}Z7nRCg}^un75i7Q@wzWQN?S^Ytw;6S-RV5~1N~lT ACjbBd delta 25 ecmbQG{6n6dkpTqwHs~{Mwqxw)-JC99%m@HW6a}{c diff --git a/app/routes/api.py b/app/routes/api.py index 54a5e62..3858cfb 100644 --- a/app/routes/api.py +++ b/app/routes/api.py @@ -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} diff --git a/app/routes/mcp.py b/app/routes/mcp.py index e05fd87..ae671fd 100644 --- a/app/routes/mcp.py +++ b/app/routes/mcp.py @@ -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 diff --git a/data/context_hub.db b/data/context_hub.db index 46ce0d89507e20b1d4c193218f46696cb54716f1..f09b5a98b2a74e9d007da394399be96372fadf36 100644 GIT binary patch delta 410 zcmZp8z|`=7X@V3JW&+vcbzp+_RU?0B;7bCMcQ&DM7 zYB9qFUS?)_#>CQ;%#!$={B#Kh1_mx51_E9tW?81hg3S2r)XHLp6Pq0V^6{A}7#Uj` z8(Eo{@GoRAnAoVZ+1&rE0<#3S$7J>bT^8;JZV#Xh0^Idx5^M~T>c)!BK8~R-u9D|)zYK3;7n!p$Siz$4A)z`2ye0q81j9*!0zMpg##4%^MI zXK!On%SkLQiBHY}`aLziIJLMqGe1wsN(LNOH$*3K>|`1 t;27fUp`Z`s>4QU3zq&X%zaX_%A+anqSs_B#F~C#TJGD}2v;Dk-f&fWLZ)X4i delta 301 zcmZp8z|`=7X@V3JL(fDRCm^{oVWB=R0|Nsy{|yHIul&#XZ)_G6IK{8Q%gij#m{^*U zSrVUA>_pNWEzk(H6Tm7xj$LI#70 zjXImn{m&{ei*fIr%wC`iG_aF<^WJI?5pM3s4BU_T95|P9H~>X2b91yrF|sm+29R;Ptl-$g`_|l>rB`XD`jFOT9D}8-KOCvo)GYdUK z0|Px{V=ItKeXu$upoWyx;^d;tf|AVqJdl=vOkI$X3NC)Z3dNau*&q%`S#e2XNog@i gMNw&9US?jpQf+OsEfXt)a9ep!PHy#PpLtsZ0cNyYDgXcg diff --git a/requirements.txt b/requirements.txt index c365fdd..b8a8535 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ jinja2>=3.1.3 python-multipart>=0.0.9 mcp>=1.2.0 python-dotenv>=1.0.1 +sse-starlette>=1.6.5 diff --git a/templates/CONCEPT.html b/templates/CONCEPT.html new file mode 100644 index 0000000..ae40977 --- /dev/null +++ b/templates/CONCEPT.html @@ -0,0 +1,186 @@ + + + + + Bundled Page + + + + +
+ + + + C + + + + + CONCEPT + context.bolbol.tn:3093 + +
+
Unpacking...
+ + + + + + + + + + \ No newline at end of file diff --git a/templates/dashboard.html b/templates/dashboard.html index 6f38a3d4cebf0932979fd054884c68d254ed2728..e8c0977ffa9eff7da19835be1f19c8406ab08652 100644 GIT binary patch delta 661 zcmZ8e%St0b6eXY{K@k;Q41$+|AzctFah7S?7c+~Q#fSuq5V9z{tF=to-Bgv)kVxG4 z1^oxJa4Ql&KsWx4%xV_1n|f%B_NFg&&OPVcd#~;a&$orb*4=b_Vk+kJqP9L16Thct z(u{s1iqxQYSsu>K-b|7(O{9WIKSBUf5k;{KUuaER{fJu7@9Pe~2>Odt;c=><4J@&J z2znMUpi}E;DWlkIxBO!MZ{hQf>Si>JK?6YmrS$NGhN=Xc@Uy=A3ri(fa>U}U*C|7C z#(5dOBB(*#zIz=6m&hOfUA$k*YyR@^Y5Q?;AhZQJCrm+1Re0=`DkNhS5}mT7LO3rW z7xpbvG-?p|M5c*1#`rL{N$-{*#|JlBj>(ez&wlZEMA`)JwZ<%o1#xX=hS#mVHFJJy zPM{ygY{nHdL4$VVP9cBTUm0j2V6nF?1nFWX?O(fQsZbIj9oSY_U*|i~`^X^JfZj`9 z3mMa9W@#;Lo%v>~qrIV@7BPh^WJ@eL T3#nHvuUD%mzi#yN`GfR7=$_Nv delta 826 zcmb7C%}T>S7;K@Uh=Nimc>53}yOO3}yj0>3cu_$`(H=sCCEeIH+a&C+NRV3Z9`gi! z0x9+4(Kql#d>DkV8^IoAR3!RzX%6w;Pm$^tf zK}h%NrfF)-cLmWf+|I(_xPd^ND)4~$AQe&6$Vj_Hkq=(16_?xlr$>i|RD$`J%#F|l z)$(ktgNj00)ttb0*`)?8yfGZp%3dv?Tv8bK#Ch{KM+s$a(onhYAc0aOng8SX-b3-J zJl)Tz6u(wWy|bN{V%rLJU^;Lehz_0&CXgG#6da47$s{!h=g1l^kw)fH-6v`%juIp#KaZ*L9};;;TK{#iP-es~8SRNeG*p zmW_vd*`K=Y7mchALS0 uNY|I8mIniOgdmZ@WOgGj)q9yH_G(Kp)GijFQh^id*Qo&73{@20)q;2Plj}MF diff --git a/templates/login.html b/templates/login.html index ac85615..57011fb 100644 --- a/templates/login.html +++ b/templates/login.html @@ -1,25 +1,87 @@ {% extends "base.html" %} {% block content %} -
-