m5cjM dZddlmZddlZddlZddlZddlZddlZddlZddl m Z ddl m Z ddl mZddlmZmZmZmZmZmZddlmZmZmZdd lmZdd lmZdd lmZm Z dd l!m"Z"m#Z#hd Z$GddeZ%ee e&jOjPdz ejRjUddZ+ejRdZ,ejRdZ-dZ.e dZ/GddeZ0edegddge0ee"e+e"e+deded Z1e d!Z2d"Z3da4da5da6d#Z7d5d$Z8e d%Z9hd&Z:d'd(hZ;d)d*hZe1jd7d8d-Z@e1jd9d:d.ZAe1jd;d/ZBe1jd 127.0.0.1 claude.ai claude.com localhostc$eZdZdZdfd ZxZS) TrustedClientuClient il cui redirect_uri non è fissato a una stringa esatta: viene accettato qualsiasi URI con host in TRUSTED_REDIRECT_HOSTS. Il client_secret resta obbligatorio allo scambio del token, quindi l'autenticità non dipende da questo.cN||jtvr|St| |SN)hostTRUSTED_REDIRECT_HOSTSsupervalidate_redirect_uri)self redirect_uri __class__s 8/mnt/ssd/data/Dropbox/adrian/vault-secondbrain/server.pyr!z#TrustedClient.validate_redirect_uri.s-  # (9(9=S(S w,\::)r#z AnyUrl | Nonereturnr)__name__ __module__ __qualname____doc__r! __classcell__)r$s@r%rr)sY;;r&rz.env ISSUER_URLzhttps://vault.privcloud.devVAULT_OAUTH_CLIENT_IDVAULT_OAUTH_CLIENT_SECRETi'z*/mnt/ssd/data/adrian-ops/oauth_tokens.jsonceZdZdZddZddZddZddZddZddZ ddZ dd Z dd Z dd Z dd Zdd Zy)VaultOAuthProviderzProvider OAuth con persistenza token su disco. Client pre-registrato per Elon/Claude.ai + registrazione dinamica per Claude Code (RFC 7591). c i|_i|_i|_|jtt tt tdgddgdgdi|_y)Nz'https://claude.ai/api/mcp/auth_callbackauthorization_code refresh_tokencodeclient_secret_post) client_id client_secret redirect_uris grant_typesresponse_typestoken_endpoint_auth_method) _auth_codes_access_tokens_refresh_tokens _load_tokens CLIENT_IDr CLIENT_SECRETr_clients)r"s r%__init__zVaultOAuthProvider.__init__Cs^9;688:  }#+%&OPQ1?C &x+?  @  r&ctjsy tjtj }t j }|j dijD]3\}}|j dd|kDstdi||j|<5|j dijD]\}}tdi||j|<y#t$rYywxYw)N access_tokens expires_atrrefresh_tokens) TOKENS_PATHexistsjsonloads read_texttimegetitemsrr>r r? Exception)r"datanowtokds r%r@zVaultOAuthProvider._load_tokensTs!!#  ::k3356D))+C((?B7==? @Q55q)C//:/?Q/?D'', @((#3R8>>@ >Q,8,=1,=$$S) >   sA6C)AC)) C54C5c~ |jjDcic]\}}||jc}}|jjDcic]\}}||jc}}d}tj t j|ycc}}wcc}}w#t$rYywxYw)N)rFrH) r>rQ model_dumpr?rJ write_textrLdumpsrR)r"tvrSs r% _save_tokenszVaultOAuthProvider._save_tokensbs @D@S@S@Y@Y@[!\1!Q\\^"3!\AEAUAUA[A[A]"^A1alln#4"^D  " "4::d#3 4"]"^   s-B0B$ B0B*5.B0$ B00 B<;B<c@K|jj|Swr)rCrP)r"r7s r% get_clientzVaultOAuthProvider.get_clientls}}  ++c>K||j|j<ywr)rCr7)r" client_infos r%register_clientz"VaultOAuthProvider.register_clientos0; k++,sc tKtjd}t||jxsgt jdz|j |j |j|j|j|j|<tt|j||jSw)N iX)r5scopesrGr7code_challenger# redirect_uri_provided_explicitlyresource)r5state)secrets token_urlsaferrfrOr7rgr#rhrir=r strrj)r"clientparamsr5s r% authorizezVaultOAuthProvider.authorizets$$R(!2==&Byy{S(&&!00,,-3-T-T__ " &c&*=*=&>TQWQ]Q]^^sB6B8c@K|jj|Swr)r=rP)r"rnr3s r%load_authorization_codez*VaultOAuthProvider.load_authorization_codes ##$677r`c K|j|j=tjd}tjd}t ||j |j ttjtz|j|j|<t||j |j |j|<|jt|dt|dj!|j Sw)Nre)tokenr7rfrGrirtr7rfbearer  access_token token_type expires_inr4scope)r=r5rkrlrr7rfintrOACCESS_TOKEN_TTLrir>r r?r]rjoin)r"rnr3ryr4s r%exchange_authorization_codez.VaultOAuthProvider.exchange_authorization_codes   /44 5,,R0 --b1 ,7&&%,,499;)99:'00 - L)/;&&%,,/ ]+ %''((-445   sD D c@K|jj|Swr)r?rP)r"rnr4s r%load_refresh_tokenz%VaultOAuthProvider.load_refresh_tokens##'' 66r`c  K|j|j=tjd}tjd}t ||j |xs |j ttjtz|j|<t||j |xs |j |j|<|jt|dt|dj|xs |j Sw)Nre)rtr7rfrGrurvrwrx)r?rtrkrlrr7rfr}rOr~r>r r]rr)r"rnr4rfry new_refreshs r%exchange_refresh_tokenz)VaultOAuthProvider.exchange_refresh_tokens  !4!4 5,,R0 ++B/ ,7&&1]11499;)99: - L) -9&&1]11- [) %'%((69]%9%9:   sD DcK|jj|}|r;|jr/|jtjkr|j|=y|Swr)r>rPrGrO)r"rtats r%load_access_tokenz$VaultOAuthProvider.load_access_tokensL  $ $U + "--BMMDIIK$?##E* sAAcK|jjt|d|d|jjt|d|d|j yw)Nrt)r>popgetattrr?r])r"rts r% revoke_tokenzVaultOAuthProvider.revoke_tokensP w >E   !?F sA A"N)r'None)r7rmr'z!OAuthClientInformationFull | None)rbrr'r)rnrror r'rm)rnrr3rmr'zAuthorizationCode | None)rnrr3rr'r)rnrr4rmr'zRefreshToken | None)rnrr4r rfz list[str]r'r)rtrmr'zAccessToken | None)r(r)r*r+rDr@r]r_rcrprrrrrrrrIr&r%r1r1>s " ,; _808FI8 !8  0 FW  67 * $     :r&r1zvault-secondbrain)zvault.privcloud.devz 10.0.0.2:8765zlocalhost:8765) allowed_hostsallowed_originsz/mcpT)enabled) issuer_urlresource_server_urlclient_registration_optionsrevocation_options)transport_securityauth_server_providerauthz /mnt/ssd/data/adrian-ops/lancedbz;sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2ctIddlm}ddl}|ta|j t tatjda ttfS)Nr)SentenceTransformer documenti) _rag_modelsentence_transformersrlancedb RAG_MODELconnectrm LANCEDB_PATH _lance_db open_table _lance_tbl)rrs r%_get_ragrsF=(3 __S%67 ))+6 z !!r&c|jdd}tjdd|}tjdd|}|jS)u^Normalizza artefatti da estrazione PDF: tab→spazio, mid-word hyphen-newline, spazi multipli. rwz-\n(\w)z\1z {2,})replaceresubstrip)texts r%_normalize_chunkrsB <<c "D 66*eT *D 66(C &D ::<r&z(/mnt/ssd/data/vault-secondbrain/vault.db> entitàfilolimbopersonacantieredinamicaepisodio decisione agent_memoryrrz elon-memoryz kernel-elon)zmsg-e2a-zmsg-a2e-zack-e2a-zack-a2e-z ping-elonctjt}tj|_|j d|S)NzPRAGMA journal_mode=WAL)sqlite3rDB_PATHRow row_factoryexecute)conns r%_dbrs. ??7 #D{{DLL*+ Kr&c D tj|}t }|j d|fj }|jt}|r1|ddk(r)|tvr!|s|jtd|d|rd} n|r |ddk(rd} nd} | dk(rz|tvr=|jtd |d d jtt|tvr,t!|j#d kr6|jtd t!|j#d|j%dxsg} | rt'| D]\} } t)| t*s|jtd| d| j%ds|jtd| d| j%ddj#r|jtd| d| j%dd||d<|j d|| |tj,|df|j/|jd| d|d|S#tj$r}td|d}~wwxYw)u Crea o aggiorna un nodo nel DB. id: slug univoco (es. 'mario-rossi', 'progetto-alpha') tipo: cantiere | persona | filo | dinamica | episodio | decisione | entità | agent_memory | limbo body: corpo testuale (note, descrizione, contenuto libero) meta: JSON con campi strutturati es. '{"nome": "Mario Rossi", "stato": "aperto"}' Il tipo viene salvato in meta.tipo; il type DB è sempre 'nodo'. umeta non è JSON valido: Nz!SELECT type FROM items WHERE id=?typeosItem '7' è di sistema (type='os') e non modificabile via MCP.nodozTipo 'z!' non riconosciuto. Tipi validi: z, zbody troppo corto (z; chars, minimo 20). Descrivi il nodo in modo significativo. relazioniz relazioni[z%] deve essere un oggetto {id, label}.idz] manca di 'id'.labelu] (→ z@) ha label vuota. Specifica una label che descriva la relazione.tipozINSERT INTO items(id, type, body, file, meta) VALUES (?,?,?,NULL,?) ON CONFLICT(id) DO UPDATE SET type=excluded.type, body=excluded.body, meta=excluded.metaF) ensure_asciiOK: /)rLrMJSONDecodeError ValueErrorrrfetchone startswithOS_WRITABLE_PREFIX OS_WRITABLEclosePermissionError VAULT_TIPIrsortedVAULT_TIPI_UNGATEDlenrrP enumerate isinstancedictrZcommit) rrbodymeta meta_dicterexisting is_msg_ackdb_typerirels r% upsert_itemrs:JJt$  5D||?"GPPRH12JHV$,;1Fz rd*abcc hv&$.& z ! JJL? &Q[J\@]?^_  ) )4::< 2%  )#djjl*;)<=>> " k28bI' 2 FAs%c40 (:aS8_)`aa774= (:aS8H)IJJ777B/557 ((73774=/BMM IfLL K WdDJJyuEF   KKMJJL '!D62$ ''u   :4QC899:sI99J JJcHt}|jd|fj}||jt d|d|d}|j t }|dk(r)|tvr!|s|jtd|d|d xsd }|d k(r||z} n|j|} | d k(r|jtd |d | dkDr$|s"|jtd| d|d|r|j||} n|j||d} tj|dxsd} | jd} |dk(rZ| tvrRt!| j#dkr6|jtdt!| j#d|jd| |f|j%|jt!| dkDr| ddn| } d|dt!|dt!| d| S) u Modifica mirata del body di un nodo esistente, senza riscriverlo per intero. id: id del nodo da modificare (deve esistere già — usa upsert_item per crearne uno nuovo) old_string: testo esatto da sostituire nel body. Stringa vuota "" = append (new_string viene aggiunto in coda al body attuale, senza toccare il resto). new_string: testo con cui sostituire old_string (o da accodare, se old_string è "") replace_all: se True sostituisce tutte le occorrenze di old_string; se False (default) old_string deve essere univoco nel body, altrimenti errore (evita ambiguità) Usa questo strumento — non upsert_item — quando aggiungi, correggi o annoti un dettaglio su un nodo già esistente: evita di dover rileggere e ricostruire l'intero body, riducendo il rischio di perdere sfumature nella riformulazione. upsert_item resta per creare nodi nuovi o per una riscrittura completa e dichiarata (mai come sotto-prodotto di un update). z-SELECT type, body, meta FROM items WHERE id=?NItem non trovato: z(. Usa upsert_item per crearne uno nuovo.rrrrrrrz$old_string non trovato nel body di 'z7'. Verifica il testo esatto (spazi, maiuscole, a-capo).zold_string trovato z volte nel body di 'u\' — non univoco. Aggiungi più contesto per renderlo unico, oppure passa replace_all=True.r{}rrrzbody risultante troppo corto (z chars, minimo 20).z"UPDATE items SET body=? WHERE id=?i8rz aggiornato (z -> z chars). Coda body: ...)rrrrKeyErrorrrrrcountrrrLrMrPrrrr)r old_string new_string replace_allrrow existing_typerrnew_bodyrrrpreviews r% patch_itemr\s3" 5D ,,F N W W YC { +B4/WXYYKM12J;!6z rd*abcc v; "DR*$ :& A: JJL6rd;..  19[ JJL%eW,@E\\  ||J ;H||J A>H 3v;.$/I == D4/A#A x~~ 2 % JJL0X^^5E1F0GGZ[  LL5"~FKKMJJL!$X!4htuo(G "]3t9+T#h-@XY`Xa bbr&ct}|jd|fj}|jd||f|j|j |dk(rt d|d|S)z+Elimina un item e tutti i suoi edge dal DB.zDELETE FROM items WHERE id=?z&DELETE FROM edges WHERE src=? OR dst=?rrz Eliminato: )rrrowcountrrr)rrdeleteds r% delete_itemrsp 5Dll9B5AJJGLL9B8DKKMJJL!|+B4011  r&cx|jj}dD]+}tjd|d|st d|dt } |j |j}|Dcgc] }t|c}|jScc}w#|jwxYw)u Esegue una query SQL read-only sul vault.db. Solo SELECT. Schema (unica tabella reale): items(id TEXT, type TEXT, body TEXT, file TEXT, meta TEXT, created_at TEXT, updated_at TEXT) Valori di type: 'nodo' — nodi vault; tipo semantico in json_extract(meta,'$.tipo') tipi: cantiere | persona | filo | dinamica | episodio | decisione | entità relazioni in json_extract(meta,'$.relazioni') → array [{id, label}] 'os' — sistema: elon-memory (scrivibile), kernel-elon (scrivibile) Usa json_extract(meta,'$.campo') per leggere campi strutturati. Usa items_fts per full-text: SELECT * FROM items_fts WHERE items_fts MATCH 'query' Esempi: -- Cantieri aperti SELECT id, json_extract(meta,'$.nome') as nome, json_extract(meta,'$.stato') as stato FROM items WHERE type='nodo' AND json_extract(meta,'$.tipo')='cantiere' -- Nodi collegati a X (outgoing) SELECT j.value FROM items, json_each(json_extract(meta,'$.relazioni')) j WHERE id='vendita-palmanova' -- Chi punta a X (incoming) SELECT id FROM items i, json_each(json_extract(i.meta,'$.relazioni')) j WHERE json_extract(j.value,'$.id')='vendita-palmanova' AND type='nodo' )INSERTUPDATEDELETEDROPCREATEALTERATTACHz\bu/Query non consentita: solo SELECT è permesso (z rilevato)) rupperrsearchrrrfetchallrr)sql sql_upper forbiddenrrowsrs r% execute_queryr s< !!#IXf 999+R() 4NykYcde ef 5D||C ))+!%&AQ& ' s#B'=B"B'"B''B9__main__z10.0.0.2i=")rport)rrmr'rm)r'zsqlite3.Connection)rr) rrmrrmrrmrrmr'rm)F) rrmrrmrrmrboolr'rm)rrmr'rm)rrmr'z list[dict])Hr+ __future__rrLrrrkrrOpathlibrtypingrdotenvrmcp.server.auth.providerrrr r r r mcp.server.auth.settingsr rrmcp.server.fastmcprmcp.server.transport_securityrmcp.shared.authrrpydanticrrrr__file__resolveparentenvironrPr-rArBr~rJr1mcprrrrrrrrrrrrrtoolrrrr r(uvicornrunstreamable_http_apprIr&r%r!s$#  `_&CB'N;.; DN " " $ + +f 45 ZZ^^L*G H JJ. /  67 !?@ N9Nb0P67,- j)&*T':;$=d$K,T:   $67 L    " 9 : u ~.m, RG( G(T?c ?cD   & &\ zGKK'') Fr&