Fix Python 3.14 incompatibility: Pin uvx to Python 3.13 for chroma-mcp (#171)

* Initial plan

* Initial exploration - no changes yet

Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>

* Add CLAUDE_MEM_PYTHON_VERSION env var defaulting to 3.13 for uvx/chroma-mcp to avoid onnxruntime Python 3.14 compatibility issues

Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>

* Fix code review comment: update placeholder issue URL to #170

Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>
This commit is contained in:
Copilot
2025-12-06 17:29:20 -05:00
committed by GitHub
parent 4eeb8391c6
commit 225424a19e
7 changed files with 13 additions and 5 deletions
+1 -1
View File
@@ -650,7 +650,7 @@ No previous sessions found for project "${e}".`}]};let t=[];t.push("# Recent Ses
`);return{content:[{type:"text",text:i}]}}catch(e){return{content:[{type:"text",text:`Search failed: ${e.message}`}],isError:!0}}}},{name:"get_context_timeline",description:'Get a unified timeline of context (observations, sessions, and prompts) around a specific point in time. All record types are interleaved chronologically. Useful for understanding "what was happening when X occurred". Returns depth_before records before anchor + anchor + depth_after records after (total: depth_before + 1 + depth_after mixed records).',inputSchema:c.object({anchor:c.union([c.number().describe("Observation ID to center timeline around"),c.string().describe("Session ID (format: S123) or ISO timestamp to center timeline around")]).describe('Anchor point: observation ID, session ID (e.g., "S123"), or ISO timestamp'),depth_before:c.number().min(0).max(50).default(10).describe("Number of records to retrieve before anchor, not including anchor (default: 10)"),depth_after:c.number().min(0).max(50).default(10).describe("Number of records to retrieve after anchor, not including anchor (default: 10)"),project:c.string().optional().describe("Filter by project name")}),handler:async s=>{try{let g=function(T){return new Date(T).toLocaleString("en-US",{month:"short",day:"numeric",year:"numeric"})},y=function(T){return new Date(T).toLocaleString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0})},v=function(T){return new Date(T).toLocaleString("en-US",{month:"short",day:"numeric",hour:"numeric",minute:"2-digit",hour12:!0})},x=function(T){return T?Math.ceil(T.length/4):0};var e=g,r=y,a=v,t=x;let{anchor:n,depth_before:i=10,depth_after:o=10,project:l}=s,u,d=n,f;if(typeof n=="number"){let T=ne.getObservationById(n);if(!T)return{content:[{type:"text",text:`Observation #${n} not found`}],isError:!0};u=T.created_at_epoch,f=ne.getTimelineAroundObservation(n,u,i,o,l)}else if(typeof n=="string")if(n.startsWith("S")||n.startsWith("#S")){let T=n.replace(/^#?S/,""),S=parseInt(T,10),I=ne.getSessionSummariesByIds([S]);if(I.length===0)return{content:[{type:"text",text:`Session #${S} not found`}],isError:!0};u=I[0].created_at_epoch,d=`S${S}`,f=ne.getTimelineAroundTimestamp(u,i,o,l)}else{let T=new Date(n);if(isNaN(T.getTime()))return{content:[{type:"text",text:`Invalid timestamp: ${n}`}],isError:!0};u=T.getTime(),f=ne.getTimelineAroundTimestamp(u,i,o,l)}else return{content:[{type:"text",text:'Invalid anchor: must be observation ID (number), session ID (e.g., "S123"), or ISO timestamp'}],isError:!0};let h=[...f.observations.map(T=>({type:"observation",data:T,epoch:T.created_at_epoch})),...f.sessions.map(T=>({type:"session",data:T,epoch:T.created_at_epoch})),...f.prompts.map(T=>({type:"prompt",data:T,epoch:T.created_at_epoch}))];h.sort((T,S)=>T.epoch-S.epoch);let p=cn(h,d,u,i,o);if(p.length===0)return{content:[{type:"text",text:`No context found around ${new Date(u).toLocaleString()} (${i} records before, ${o} records after)`}]};let P=[];P.push(`# Timeline around anchor: ${d}`),P.push(`**Window:** ${i} records before \u2192 ${o} records after | **Items:** ${p.length}`),P.push(""),P.push("**Legend:** \u{1F3AF} session-request | \u{1F534} bugfix | \u{1F7E3} feature | \u{1F504} refactor | \u2705 change | \u{1F535} discovery | \u{1F9E0} decision"),P.push("");let E=new Map;for(let T of p){let S=g(T.epoch);E.has(S)||E.set(S,[]),E.get(S).push(T)}let R=Array.from(E.entries()).sort((T,S)=>{let I=new Date(T[0]).getTime(),D=new Date(S[0]).getTime();return I-D});for(let[T,S]of R){P.push(`### ${T}`),P.push("");let I=null,D="",$=!1;for(let L of S){let C=typeof d=="number"&&L.type==="observation"&&L.data.id===d||typeof d=="string"&&d.startsWith("S")&&L.type==="session"&&`S${L.data.id}`===d;if(L.type==="session"){$&&(P.push(""),$=!1,I=null,D="");let k=L.data,N=k.request||"Session summary",A=`claude-mem://session-summary/${k.id}`,M=C?" \u2190 **ANCHOR**":"";P.push(`**\u{1F3AF} #S${k.id}** ${N} (${v(L.epoch)}) [\u2192](${A})${M}`),P.push("")}else if(L.type==="prompt"){$&&(P.push(""),$=!1,I=null,D="");let k=L.data,N=k.prompt.length>100?k.prompt.substring(0,100)+"...":k.prompt;P.push(`**\u{1F4AC} User Prompt #${k.prompt_number}** (${v(L.epoch)})`),P.push(`> ${N}`),P.push("")}else if(L.type==="observation"){let k=L.data,N="General";N!==I&&($&&P.push(""),P.push(`**${N}**`),P.push("| ID | Time | T | Title | Tokens |"),P.push("|----|------|---|-------|--------|"),I=N,$=!0,D="");let A="\u2022";switch(k.type){case"bugfix":A="\u{1F534}";break;case"feature":A="\u{1F7E3}";break;case"refactor":A="\u{1F504}";break;case"change":A="\u2705";break;case"discovery":A="\u{1F535}";break;case"decision":A="\u{1F9E0}";break}let M=y(L.epoch),X=k.title||"Untitled",W=x(k.narrative),Q=M!==D?M:"\u2033";D=M;let z=C?" \u2190 **ANCHOR**":"";P.push(`| #${k.id} | ${Q} | ${A} | ${X}${z} | ~${W} |`)}}$&&P.push("")}return{content:[{type:"text",text:P.join(`
`)}]}}catch(n){return{content:[{type:"text",text:`Timeline query failed: ${n.message}`}],isError:!0}}}},{name:"get_timeline_by_query",description:'Search for observations using natural language and get timeline context around the best match. Two modes: "auto" (default) automatically uses top result as timeline anchor; "interactive" returns top matches for you to choose from. This combines search + timeline into a single operation for faster context discovery.',inputSchema:c.object({query:c.string().describe("Natural language search query to find relevant observations"),mode:c.enum(["auto","interactive"]).default("auto").describe("auto: Automatically use top search result as timeline anchor. interactive: Show top N search results for manual anchor selection."),depth_before:c.number().min(0).max(50).default(10).describe("Number of timeline records before anchor (default: 10)"),depth_after:c.number().min(0).max(50).default(10).describe("Number of timeline records after anchor (default: 10)"),limit:c.number().min(1).max(20).default(5).describe("For interactive mode: number of top search results to display (default: 5)"),project:c.string().optional().describe("Filter by project name")}),handler:async s=>{try{let{query:n,mode:i="auto",depth_before:o=10,depth_after:l=10,limit:u=5,project:d}=s,f=[];if(we)try{console.error("[search-server] Using hybrid semantic search for timeline query");let h=await ze(n,100);if(console.error(`[search-server] Chroma returned ${h.ids.length} semantic matches`),h.ids.length>0){let p=Date.now()-7776e6,g=h.ids.filter((y,v)=>{let x=h.metadatas[v];return x&&x.created_at_epoch>p});console.error(`[search-server] ${g.length} results within 90-day window`),g.length>0&&(f=ne.getObservationsByIds(g,{orderBy:"date_desc",limit:i==="auto"?1:u}),console.error(`[search-server] Hydrated ${f.length} observations from SQLite`))}}catch(h){console.error("[search-server] Chroma query failed - no results (FTS5 fallback removed):",h.message)}if(f.length===0)return{content:[{type:"text",text:`No observations found matching "${n}". Try a different search query.`}]};if(i==="interactive"){let h=[];h.push("# Timeline Anchor Search Results"),h.push(""),h.push(`Found ${f.length} observation(s) matching "${n}"`),h.push(""),h.push("To get timeline context around any of these observations, use the `get_context_timeline` tool with the observation ID as the anchor."),h.push(""),h.push(`**Top ${f.length} matches:**`),h.push("");for(let p=0;p<f.length;p++){let g=f[p],y=g.title||`Observation #${g.id}`,v=new Date(g.created_at_epoch).toLocaleString(),x=g.type?`[${g.type}]`:"";h.push(`${p+1}. **${x} ${y}**`),h.push(` - ID: ${g.id}`),h.push(` - Date: ${v}`),g.subtitle&&h.push(` - ${g.subtitle}`),h.push(` - Source: claude-mem://observation/${g.id}`),h.push("")}return{content:[{type:"text",text:h.join(`
`)}]}}else{let v=function(I){return new Date(I).toLocaleString("en-US",{month:"short",day:"numeric",year:"numeric"})},x=function(I){return new Date(I).toLocaleString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0})},P=function(I){return new Date(I).toLocaleString("en-US",{month:"short",day:"numeric",hour:"numeric",minute:"2-digit",hour12:!0})},E=function(I){return I?Math.ceil(I.length/4):0};var e=v,r=x,a=P,t=E;let h=f[0];console.error(`[search-server] Auto mode: Using observation #${h.id} as timeline anchor`);let p=ne.getTimelineAroundObservation(h.id,h.created_at_epoch,o,l,d),g=[...p.observations.map(I=>({type:"observation",data:I,epoch:I.created_at_epoch})),...p.sessions.map(I=>({type:"session",data:I,epoch:I.created_at_epoch})),...p.prompts.map(I=>({type:"prompt",data:I,epoch:I.created_at_epoch}))];g.sort((I,D)=>I.epoch-D.epoch);let y=cn(g,h.id,0,o,l);if(y.length===0)return{content:[{type:"text",text:`Found observation #${h.id} matching "${n}", but no timeline context available (${o} records before, ${l} records after).`}]};let R=[];R.push(`# Timeline for query: "${n}"`),R.push(`**Anchor:** Observation #${h.id} - ${h.title||"Untitled"}`),R.push(`**Window:** ${o} records before \u2192 ${l} records after | **Items:** ${y.length}`),R.push(""),R.push("**Legend:** \u{1F3AF} session-request | \u{1F534} bugfix | \u{1F7E3} feature | \u{1F504} refactor | \u2705 change | \u{1F535} discovery | \u{1F9E0} decision"),R.push("");let T=new Map;for(let I of y){let D=v(I.epoch);T.has(D)||T.set(D,[]),T.get(D).push(I)}let S=Array.from(T.entries()).sort((I,D)=>{let $=new Date(I[0]).getTime(),L=new Date(D[0]).getTime();return $-L});for(let[I,D]of S){R.push(`### ${I}`),R.push("");let $=null,L="",C=!1;for(let k of D){let N=k.type==="observation"&&k.data.id===h.id;if(k.type==="session"){C&&(R.push(""),C=!1,$=null,L="");let A=k.data,M=A.request||"Session summary",X=`claude-mem://session-summary/${A.id}`;R.push(`**\u{1F3AF} #S${A.id}** ${M} (${P(k.epoch)}) [\u2192](${X})`),R.push("")}else if(k.type==="prompt"){C&&(R.push(""),C=!1,$=null,L="");let A=k.data,M=A.prompt.length>100?A.prompt.substring(0,100)+"...":A.prompt;R.push(`**\u{1F4AC} User Prompt #${A.prompt_number}** (${P(k.epoch)})`),R.push(`> ${M}`),R.push("")}else if(k.type==="observation"){let A=k.data,M="General";M!==$&&(C&&R.push(""),R.push(`**${M}**`),R.push("| ID | Time | T | Title | Tokens |"),R.push("|----|------|---|-------|--------|"),$=M,C=!0,L="");let X="\u2022";switch(A.type){case"bugfix":X="\u{1F534}";break;case"feature":X="\u{1F7E3}";break;case"refactor":X="\u{1F504}";break;case"change":X="\u2705";break;case"discovery":X="\u{1F535}";break;case"decision":X="\u{1F9E0}";break}let W=x(k.epoch),ee=A.title||"Untitled",Q=E(A.narrative),pe=W!==L?W:"\u2033";L=W;let Re=N?" \u2190 **ANCHOR**":"";R.push(`| #${A.id} | ${pe} | ${X} | ${ee}${Re} | ~${Q} |`)}}C&&R.push("")}return{content:[{type:"text",text:R.join(`
`)}]}}}catch(n){return{content:[{type:"text",text:`Timeline query failed: ${n.message}`}],isError:!0}}}}],dn=new Os({name:"claude-mem-search",version:"1.0.0"},{capabilities:{tools:{}}});dn.setRequestHandler(ca,async()=>({tools:Dl.map(s=>({name:s.name,description:s.description,inputSchema:tn(s.inputSchema)}))}));dn.setRequestHandler(ua,async s=>{let e=Dl.find(r=>r.name===s.params.name);if(!e)throw new Error(`Unknown tool: ${s.params.name}`);try{return await e.handler(s.params.arguments||{})}catch(r){return{content:[{type:"text",text:`Tool execution failed: ${r.message}`}],isError:!0}}});async function Cl(){if(console.error("[search-server] Shutting down..."),we)try{await we.close(),console.error("[search-server] Chroma client closed")}catch(s){console.error("[search-server] Error closing Chroma client:",s.message)}if(_e)try{_e.close(),console.error("[search-server] SessionSearch closed")}catch(s){console.error("[search-server] Error closing SessionSearch:",s.message)}if(ne)try{ne.close(),console.error("[search-server] SessionStore closed")}catch(s){console.error("[search-server] Error closing SessionStore:",s.message)}console.error("[search-server] Shutdown complete"),process.exit(0)}process.on("SIGTERM",Cl);process.on("SIGINT",Cl);async function Oh(){let s=new Is;await dn.connect(s),console.error("[search-server] Claude-mem search server started"),setTimeout(async()=>{try{console.error("[search-server] Initializing Chroma client...");let e=new $s({command:"uvx",args:["chroma-mcp","--client-type","persistent","--data-dir",xl],stderr:"ignore"}),r=new Ns({name:"claude-mem-search-chroma-client",version:"1.0.0"},{capabilities:{}});await r.connect(e),we=r,console.error("[search-server] Chroma client connected successfully")}catch(e){console.error("[search-server] Failed to initialize Chroma client:",e.message),console.error("[search-server] Vector search unavailable - text queries will return empty results (FTS5 fallback removed)"),console.error("[search-server] Install UVX/Python to enable vector search: https://docs.astral.sh/uv/getting-started/installation/"),we=null}},0)}Oh().catch(s=>{console.error("[search-server] Fatal error:",s),process.exit(1)});
`)}]}}}catch(n){return{content:[{type:"text",text:`Timeline query failed: ${n.message}`}],isError:!0}}}}],dn=new Os({name:"claude-mem-search",version:"1.0.0"},{capabilities:{tools:{}}});dn.setRequestHandler(ca,async()=>({tools:Dl.map(s=>({name:s.name,description:s.description,inputSchema:tn(s.inputSchema)}))}));dn.setRequestHandler(ua,async s=>{let e=Dl.find(r=>r.name===s.params.name);if(!e)throw new Error(`Unknown tool: ${s.params.name}`);try{return await e.handler(s.params.arguments||{})}catch(r){return{content:[{type:"text",text:`Tool execution failed: ${r.message}`}],isError:!0}}});async function Cl(){if(console.error("[search-server] Shutting down..."),we)try{await we.close(),console.error("[search-server] Chroma client closed")}catch(s){console.error("[search-server] Error closing Chroma client:",s.message)}if(_e)try{_e.close(),console.error("[search-server] SessionSearch closed")}catch(s){console.error("[search-server] Error closing SessionSearch:",s.message)}if(ne)try{ne.close(),console.error("[search-server] SessionStore closed")}catch(s){console.error("[search-server] Error closing SessionStore:",s.message)}console.error("[search-server] Shutdown complete"),process.exit(0)}process.on("SIGTERM",Cl);process.on("SIGINT",Cl);async function Oh(){let s=new Is;await dn.connect(s),console.error("[search-server] Claude-mem search server started"),setTimeout(async()=>{try{console.error("[search-server] Initializing Chroma client...");let e=process.env.CLAUDE_MEM_PYTHON_VERSION||"3.13",r=new $s({command:"uvx",args:["--python",e,"chroma-mcp","--client-type","persistent","--data-dir",xl],stderr:"ignore"}),a=new Ns({name:"claude-mem-search-chroma-client",version:"1.0.0"},{capabilities:{}});await a.connect(r),we=a,console.error("[search-server] Chroma client connected successfully")}catch(e){console.error("[search-server] Failed to initialize Chroma client:",e.message),console.error("[search-server] Vector search unavailable - text queries will return empty results (FTS5 fallback removed)"),console.error("[search-server] Install UVX/Python to enable vector search: https://docs.astral.sh/uv/getting-started/installation/"),we=null}},0)}Oh().catch(s=>{console.error("[search-server] Fatal error:",s),process.exit(1)});
/*! Bundled license information:
uri-js/dist/es5/uri.all.js:
File diff suppressed because one or more lines are too long