@openread/mcp 0.0.1-test.6 → 0.0.1-test.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +3 -3
- package/package.json +2 -2
package/dist/cli.mjs
CHANGED
|
@@ -177,7 +177,7 @@ ${s}`)}return qE(r,t,i)}function Ic(r,e){let t=r.headings;if(t.length===0)throw
|
|
|
177
177
|
`};return n(u),new Promise(async h=>a=h);async function l({highWaterMark:h=2048*8,start:d=0,end:f=1/0}={}){let p=f-d;return d&&await u.seek(d),new pT.Readable({highWaterMark:h,async read(g){let b=g>p?g-p:g;p-=g;let[{data:m}]=await u.read(b);this.push(m),m.length<g&&this.push(null)}})}async function c({highWaterMark:h=2048*8,start:d=0}={}){return d&&await u.seek(d),new pT.Writable({highWaterMark:h,write(f,p,g){u.write(f).then(()=>g(),g)}})}}).catch(i)})}Object.assign(oh,{PostgresError:li,toPascal:hi,pascal:Im,toCamel:ci,camel:km,toKebab:di,kebab:Rm,fromPascal:Do,fromCamel:Bo,fromKebab:Fo,BigInt:{to:20,from:[20],parse:r=>BigInt(r),serialize:r=>r.toString()}});var gT=oh;function oh(r,e){let t=JB(r,e),n=t.no_subscribe||Nm(oh,{...t}),i=!1,s=Qt(),a=Qt(),o=Qt(),u=Qt(),l=Qt(),c=Qt(),h=Qt(),d=Qt(),f={connecting:a,reserved:o,closed:u,ended:l,open:c,busy:h,full:d},p=[...Array(t.max)].map(()=>Lm(t,f,{onopen:L,onend:M,onclose:N})),g=b(_);return Object.assign(g,{get parameters(){return t.parameters},largeObject:Mm.bind(null,g),subscribe:n,CLOSE:Es,END:Es,PostgresError:li,options:t,reserve:v,listen:m,begin:x,close:B,end:A}),g;function b(I){return I.debug=t.debug,Object.entries(t.types).reduce((q,[ie,pe])=>(q[ie]=W=>new xr(W,pe.to),q),H),Object.assign(J,{types:H,typed:H,unsafe:ee,notify:y,array:E,json:S,file:X}),J;function H(q,ie){return new xr(q,ie)}function J(q,...ie){return q&&Array.isArray(q.raw)?new Yt(q,ie,I,k):typeof q=="string"&&!ie.length?new As(t.transform.column.to?t.transform.column.to(q):q):new Mo(q,ie)}function ee(q,ie=[],pe={}){return arguments.length===2&&!Array.isArray(ie)&&(pe=ie,ie=[]),new Yt([q],ie,I,k,{prepare:!1,...pe,simple:"simple"in pe?pe.simple:ie.length===0})}function X(q,ie=[],pe={}){return arguments.length===2&&!Array.isArray(ie)&&(pe=ie,ie=[]),new Yt([],ie,$=>{ZB.readFile(q,"utf8",(G,Y)=>{if(G)return $.reject(G);$.strings=[Y],I($)})},k,{...pe,simple:"simple"in pe?pe.simple:ie.length===0})}}async function m(I,H,J){let ee={fn:H,onlisten:J},X=m.sql||(m.sql=oh({...t,max:1,idle_timeout:null,max_lifetime:null,fetch_types:!1,onclose(){Object.entries(m.channels).forEach(([$,{listeners:G}])=>{delete m.channels[$],Promise.all(G.map(Y=>m($,Y.fn,Y.onlisten).catch(()=>{})))})},onnotify($,G){$ in m.channels&&m.channels[$].listeners.forEach(Y=>Y.fn(G))}})),q=m.channels||(m.channels={});if(I in q){q[I].listeners.push(ee);let $=await q[I].result;return ee.onlisten&&ee.onlisten(),{state:$.state,unlisten:W}}q[I]={result:X`listen ${X.unsafe('"'+I.replace(/"/g,'""')+'"')}`,listeners:[ee]};let pe=await q[I].result;return ee.onlisten&&ee.onlisten(),{state:pe.state,unlisten:W};async function W(){if(I in q&&(q[I].listeners=q[I].listeners.filter($=>$!==ee),!q[I].listeners.length))return delete q[I],X`unlisten ${X.unsafe('"'+I.replace(/"/g,'""')+'"')}`}}async function y(I,H){return await g`select pg_notify(${I}, ${""+H})`}async function v(){let I=Qt(),H=c.length?c.shift():await new Promise((X,q)=>{let ie={reserve:X,reject:q};s.push(ie),u.length&&P(u.shift(),ie)});w(H,o),H.reserved=()=>I.length?H.execute(I.shift()):w(H,o),H.reserved.release=!0;let J=b(ee);return J.release=()=>{H.reserved=null,L(H)},J;function ee(X){H.queue===d?I.push(X):H.execute(X)||w(H,d)}}async function x(I,H){!H&&(H=I,I="");let J=Qt(),ee=0,X,q=null;try{return await g.unsafe("begin "+I.replace(/[^a-z ]/ig,""),[],{onexecute:pe}).execute(),await Promise.race([ie(X,H),new Promise((W,$)=>X.onclose=$)])}catch(W){throw W}async function ie(W,$,G){let Y=b(he);Y.savepoint=ye,Y.prepare=be=>q=be.replace(/[^a-z0-9$-_. ]/gi);let me,ne;G&&await Y`savepoint ${Y(G)}`;try{if(ne=await new Promise((be,de)=>{let Ae=$(Y);Promise.resolve(Array.isArray(Ae)?Promise.all(Ae):Ae).then(be,de)}),me)throw me}catch(be){throw await(G?Y`rollback to ${Y(G)}`:Y`rollback`),be instanceof li&&be.code==="25P02"&&me||be}return G||(q?await Y`prepare transaction '${Y.unsafe(q)}'`:await Y`commit`),ne;function ye(be,de){return be&&Array.isArray(be.raw)?ye(Ae=>Ae.apply(Ae,arguments)):(arguments.length===1&&(de=be,be=null),ie(W,de,"s"+ee+++(be?"_"+be:"")))}function he(be){be.catch(de=>me||(me=de)),W.queue===d?J.push(be):W.execute(be)||w(W,d)}}function pe(W){X=W,w(W,o),W.reserved=()=>J.length?W.execute(J.shift()):w(W,o)}}function w(I,H){return I.queue.remove(I),H.push(I),I.queue=H,H===c?I.idleTimer.start():I.idleTimer.cancel(),I}function S(I){return new xr(I,3802)}function E(I,H){return Array.isArray(I)?new xr(I,H||(I.length?eh(I)||25:0),t.shared.typeArrayMap):E(Array.from(arguments))}function _(I){if(i)return I.reject(Xe.connection("CONNECTION_ENDED",t,t));if(c.length)return T(c.shift(),I);if(u.length)return P(u.shift(),I);h.length?T(h.shift(),I):s.push(I)}function T(I,H){return I.execute(H)?w(I,h):w(I,d)}function k(I){return new Promise((H,J)=>{I.state?I.active?Lm(t).cancel(I.state,H,J):I.cancelled={resolve:H,reject:J}:(s.remove(I),I.cancelled=!0,I.reject(Xe.generic("57014","canceling statement due to user request")),H())})}async function A({timeout:I=null}={}){if(i)return i;await 1;let H;return i=Promise.race([new Promise(J=>I!==null&&(H=setTimeout(D,I*1e3,J))),Promise.all(p.map(J=>J.end()).concat(m.sql?m.sql.end({timeout:0}):[],n.sql?n.sql.end({timeout:0}):[]))]).then(()=>clearTimeout(H))}async function B(){await Promise.all(p.map(I=>I.end()))}async function D(I){for(await Promise.all(p.map(H=>H.terminate()));s.length;)s.shift().reject(Xe.connection("CONNECTION_DESTROYED",t));I()}function P(I,H){return w(I,a),I.connect(H),I}function M(I){w(I,l)}function L(I){if(s.length===0)return w(I,c);let H=Math.ceil(s.length/(a.length+1)),J=!0;for(;J&&s.length&&H-- >0;){let ee=s.shift();if(ee.reserve)return ee.reserve(I);J=I.execute(ee)}J?w(I,h):w(I,d)}function N(I,H){w(I,u),I.reserved=null,I.onclose&&(I.onclose(H),I.onclose=null),t.onclose&&t.onclose(I.id),s.length&&P(I,s.shift())}}function JB(r,e){if(r&&r.shared)return r;let t=process.env,n=(!r||typeof r=="string"?e:r)||{},{url:i,multihost:s}=iD(r),a=[...i.searchParams].reduce((d,[f,p])=>(d[f]=p,d),{}),o=n.hostname||n.host||s||i.hostname||t.PGHOST||"localhost",u=n.port||i.port||t.PGPORT||5432,l=n.user||n.username||i.username||t.PGUSERNAME||t.PGUSER||sD();n.no_prepare&&(n.prepare=!1),a.sslmode&&(a.ssl=a.sslmode,delete a.sslmode),"timeout"in n&&(console.log("The timeout option is deprecated, use idle_timeout instead"),n.idle_timeout=n.timeout),a.sslrootcert==="system"&&(a.ssl="verify-full");let c=["idle_timeout","connect_timeout","max_lifetime","max_pipeline","backoff","keep_alive"],h={max:globalThis.Cloudflare?3:10,ssl:!1,sslnegotiation:null,idle_timeout:null,connect_timeout:30,max_lifetime:rD,max_pipeline:100,backoff:tD,keep_alive:60,prepare:!0,debug:!1,fetch_types:!0,publications:"alltables",target_session_attrs:null};return{host:Array.isArray(o)?o:o.split(",").map(d=>d.split(":")[0]),port:Array.isArray(u)?u:o.split(",").map(d=>parseInt(d.split(":")[1]||u)),path:n.path||o.indexOf("/")>-1&&o+"/.s.PGSQL."+u,database:n.database||n.db||(i.pathname||"").slice(1)||t.PGDATABASE||l,user:l,pass:n.pass||n.password||i.password||t.PGPASSWORD||"",...Object.entries(h).reduce((d,[f,p])=>{let g=f in n?n[f]:f in a?a[f]==="disable"||a[f]==="false"?!1:a[f]:t["PG"+f.toUpperCase()]||p;return d[f]=typeof g=="string"&&c.includes(f)?+g:g,d},{}),connection:{application_name:t.PGAPPNAME||"postgres.js",...n.connection,...Object.entries(a).reduce((d,[f,p])=>(f in h||(d[f]=p),d),{})},types:n.types||{},target_session_attrs:eD(n,i,t),onnotice:n.onnotice,onnotify:n.onnotify,onclose:n.onclose,onparameter:n.onparameter,socket:n.socket,transform:nD(n.transform||{undefined:void 0}),parameters:{},shared:{retries:0,typeArrayMap:{}},...nT(n.types)}}function eD(r,e,t){let n=r.target_session_attrs||e.searchParams.get("target_session_attrs")||t.PGTARGETSESSIONATTRS;if(!n||["read-write","read-only","primary","standby","prefer-standby"].includes(n))return n;throw new Error("target_session_attrs "+n+" is not supported")}function tD(r){return(.5+Math.random()/2)*Math.min(3**r/100,20)}function rD(){return 60*(30+Math.random()*30)}function nD(r){return{undefined:r.undefined,column:{from:typeof r.column=="function"?r.column:r.column&&r.column.from,to:r.column&&r.column.to},value:{from:typeof r.value=="function"?r.value:r.value&&r.value.from,to:r.value&&r.value.to},row:{from:typeof r.row=="function"?r.row:r.row&&r.row.from,to:r.row&&r.row.to}}}function iD(r){if(!r||typeof r!="string")return{url:{searchParams:new Map}};let e=r;e=e.slice(e.indexOf("://")+3).split(/[?/]/)[0],e=decodeURIComponent(e.slice(e.indexOf("@")+1));let t=new URL(r.replace(e,e.split(",")[0]));return{url:{username:decodeURIComponent(t.username),password:decodeURIComponent(t.password),host:t.host,hostname:t.hostname,port:t.port,pathname:t.pathname,searchParams:t.searchParams},multihost:e.indexOf(",")>-1&&e}}function sD(){try{return QB.userInfo().username}catch{return process.env.USERNAME||process.env.USER||process.env.LOGNAME}}var Bm=null,mT=null;function aD(){let r=process.env.DB_POOL_SIZE;if(!r)return 1;let e=parseInt(r,10);return isNaN(e)||e<=0?(console.warn(`[db] Invalid DB_POOL_SIZE="${r}", using default 1`),1):(e>50&&console.warn(`[db] DB_POOL_SIZE=${e} is unusually high, consider reducing`),e)}function bT(){if(!Bm){let r=process.env.DATABASE_URL;if(!r)throw new Error("DATABASE_URL environment variable is required. Set it to your Postgres connection string.");let e=aD();mT=gT(r,{max:e,idle_timeout:20,connect_timeout:10,prepare:process.env.SUPABASE_POOLER!=="true",ssl:r.includes("localhost")||r.includes("127.0.0.1")?!1:"require"}),Bm=KA(mT)}return Bm}var DG=new Proxy({},{get(r,e){return Reflect.get(bT(),e)}});var Uo=2,rq=100*1024*1024,nq={maxEntries:50,maxSize:500*1024*1024,ttl:3600*1e3};var uD=oD("openread-mcp",{suffix:""}),lD=uD.cache,uh=class{cacheDir;initialized=!1;constructor(e){this.cacheDir=e?.cacheDir??lD}async ensureDir(){this.initialized||(await Tt.mkdir(this.cacheDir,{recursive:!0}),this.initialized=!0)}sanitizeId(e){return e.replace(/[\/\\\.]+/g,"_")}keyPath(e,t){return zo.join(this.cacheDir,`${this.sanitizeId(e)}-${this.sanitizeId(t)}.json`)}serializeBook(e){return{metadata:e.metadata,toc:e.toc,chapters:Object.fromEntries(e.chapters),spine:e.spine}}deserializeBook(e){if(!e.metadata||typeof e.metadata!="object"||!e.chapters||typeof e.chapters!="object"||!Array.isArray(e.spine))throw new Error("Corrupt cache entry: missing required fields");return{metadata:e.metadata,toc:e.toc??[],chapters:new Map(Object.entries(e.chapters)),spine:e.spine}}async getByBookId(e){await this.ensureDir();let t=await Tt.readdir(this.cacheDir),n=`${this.sanitizeId(e)}-`,i=t.filter(s=>s.startsWith(n)&&s.endsWith(".json"));for(let s of i){let a=zo.join(this.cacheDir,s);try{let o=await Tt.readFile(a,"utf-8"),u=JSON.parse(o);if(u.parserVersion!==Uo){await Tt.unlink(a).catch(()=>{});continue}return await Tt.utimes(a,new Date,new Date).catch(()=>{}),this.deserializeBook(u.book)}catch(o){o instanceof Error&&"code"in o&&o.code==="ENOENT"||process.stderr.write(`[DiskCache] Error reading cache file ${s}: ${o instanceof Error?o.message:o}
|
|
178
178
|
`);continue}}return null}async get(e,t){await this.ensureDir();let n=this.keyPath(e,t);try{let i=await Tt.readFile(n,"utf-8"),s=JSON.parse(i);return s.parserVersion!==Uo?(await Tt.unlink(n).catch(()=>{}),null):(await Tt.utimes(n,new Date,new Date).catch(()=>{}),this.deserializeBook(s.book))}catch(i){return i instanceof Error&&"code"in i&&i.code==="ENOENT"||process.stderr.write(`[DiskCache] Error reading cache for ${e}: ${i instanceof Error?i.message:i}
|
|
179
179
|
`),null}}async set(e,t,n){await this.ensureDir();let i=this.keyPath(e,t),s=`${i}.tmp`,a={parserVersion:Uo,cachedAt:Date.now(),book:this.serializeBook(n)};await Tt.writeFile(s,JSON.stringify(a),"utf-8"),await Tt.rename(s,i)}async clear(){await this.ensureDir();let e=await Tt.readdir(this.cacheDir);await Promise.all(e.filter(t=>t.endsWith(".json")).map(t=>Tt.unlink(zo.join(this.cacheDir,t)).catch(()=>{})))}async cleanupTempFiles(){await this.ensureDir();let t=(await Tt.readdir(this.cacheDir)).filter(n=>n.endsWith(".tmp"));return await Promise.all(t.map(n=>Tt.unlink(zo.join(this.cacheDir,n)).catch(()=>{}))),t.length}async getStats(){await this.ensureDir();let t=(await Tt.readdir(this.cacheDir)).filter(i=>i.endsWith(".json")),n=0;for(let i of t)try{let s=await Tt.stat(zo.join(this.cacheDir,i));n+=s.size}catch{}return{totalSize:n,entryCount:t.length,cacheDir:this.cacheDir}}};import{createHash as cD}from"node:crypto";import{createWriteStream as hD,promises as Dm}from"node:fs";import yT from"node:path";import dD from"node:os";import{pipeline as fD}from"node:stream/promises";import{Readable as pD}from"node:stream";function In(r){process.stderr.write(`[BookLoader] ${r}
|
|
180
|
-
`)}var lh=3,gD=1e3,vT=500*1024*1024,mD=300*1e3,wT=20,ch=class{constructor(e,t){this.auth=e;this.diskCache=t}memoryCache=new Map;async loadBook(e,t="epub"){let n=this.memoryCache.get(e);if(n)return In(`Memory cache hit: ${e}`),n;let i=await this.diskCache.getByBookId(e);if(i){if(In(`Disk cache hit: ${e}`),this.memoryCache.size>=wT){let s=this.memoryCache.keys().next().value;s!==void 0&&this.memoryCache.delete(s)}return this.memoryCache.set(e,i),i}return In(`Cache miss: ${e}, downloading...`),this.downloadAndParse(e,t)}async downloadAndParse(e,t){let n=null;for(let i=0;i<lh;i++){let s=await Dm.mkdtemp(yT.join(dD.tmpdir(),"openread-")),a=yT.join(s,`book.${t}`);try{let o=await this.requestSignedUrl(e);if(o.sizeBytes>vT)throw new Error(`Book ${e} is too large (${(o.sizeBytes/1024/1024).toFixed(0)}MB, max ${(vT/1024/1024).toFixed(0)}MB)`);await this.downloadToFile(o.url,a,o.sizeBytes);let u=await Dm.readFile(a),l=cD("sha256").update(u).digest("hex").slice(0,12);In(`Parsing ${t}: ${e}`);let c=await this.parseBuffer(u.buffer.slice(u.byteOffset,u.byteOffset+u.byteLength),t);if(this.diskCache.set(e,l,c).catch(h=>{In(`Disk cache write failed for book ${e}: ${h instanceof Error?h.message:h}`)}),this.memoryCache.size>=wT){let h=this.memoryCache.keys().next().value;h!==void 0&&this.memoryCache.delete(h)}return this.memoryCache.set(e,c),c}catch(o){n=o instanceof Error?o:new Error(String(o));let u=n.message;if(!(u==="SIGNED_URL_EXPIRED"||u.includes("fetch failed")))throw n;if(i<lh-1){let c=gD*Math.pow(2,i);In(`Retrying in ${c}ms (attempt ${i+2}/${lh})...`),await new Promise(h=>setTimeout(h,c))}}finally{await Dm.rm(s,{recursive:!0,force:!0}).catch(()=>{})}}throw new Error(`Failed to download book ${e} after ${lh} attempts: ${n?.message}`)}async requestSignedUrl(e){let t=await fetch(`${this.auth.getApiUrl()}/api/mcp/download-url`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.auth.getJwt()}`},body:JSON.stringify({bookId:e})});if(!t.ok){let n=await t.text().catch(()=>"");throw new Error(`Failed to get download URL (${t.status}): ${n}`)}return await t.json()}async downloadToFile(e,t,n){let i=Date.now();In(`Downloading ${(n/1024/1024).toFixed(1)}MB...`);let s=new AbortController,a=setTimeout(()=>s.abort(),mD);try{let o=await fetch(e,{signal:s.signal});if(!o.ok)throw o.status===403?new Error("SIGNED_URL_EXPIRED"):new Error(`Download failed: ${o.status}`);let u=o.body;if(!u)throw new Error("Empty response body");let l=pD.fromWeb(u),c=hD(t);await fD(l,c);let h=((Date.now()-i)/1e3).toFixed(1);In(`Download complete in ${h}s`)}finally{clearTimeout(a)}}async parseBuffer(e,t){switch(t){case"pdf":return Cc(e);case"mobi":case"azw3":return kc(e);case"txt":return Rl(e);default:return Il(e)}}async clearAll(){this.memoryCache.clear(),await this.diskCache.clear()}};var hh=class{constructor(e){this.
|
|
180
|
+
`)}var lh=3,gD=1e3,vT=500*1024*1024,mD=300*1e3,wT=20,ch=class{constructor(e,t){this.auth=e;this.diskCache=t}memoryCache=new Map;async loadBook(e,t="epub"){let n=this.memoryCache.get(e);if(n)return In(`Memory cache hit: ${e}`),n;let i=await this.diskCache.getByBookId(e);if(i){if(In(`Disk cache hit: ${e}`),this.memoryCache.size>=wT){let s=this.memoryCache.keys().next().value;s!==void 0&&this.memoryCache.delete(s)}return this.memoryCache.set(e,i),i}return In(`Cache miss: ${e}, downloading...`),this.downloadAndParse(e,t)}async downloadAndParse(e,t){let n=null;for(let i=0;i<lh;i++){let s=await Dm.mkdtemp(yT.join(dD.tmpdir(),"openread-")),a=yT.join(s,`book.${t}`);try{let o=await this.requestSignedUrl(e);if(o.sizeBytes>vT)throw new Error(`Book ${e} is too large (${(o.sizeBytes/1024/1024).toFixed(0)}MB, max ${(vT/1024/1024).toFixed(0)}MB)`);await this.downloadToFile(o.url,a,o.sizeBytes);let u=await Dm.readFile(a),l=cD("sha256").update(u).digest("hex").slice(0,12);In(`Parsing ${t}: ${e}`);let c=await this.parseBuffer(u.buffer.slice(u.byteOffset,u.byteOffset+u.byteLength),t);if(this.diskCache.set(e,l,c).catch(h=>{In(`Disk cache write failed for book ${e}: ${h instanceof Error?h.message:h}`)}),this.memoryCache.size>=wT){let h=this.memoryCache.keys().next().value;h!==void 0&&this.memoryCache.delete(h)}return this.memoryCache.set(e,c),c}catch(o){n=o instanceof Error?o:new Error(String(o));let u=n.message;if(!(u==="SIGNED_URL_EXPIRED"||u.includes("fetch failed")))throw n;if(i<lh-1){let c=gD*Math.pow(2,i);In(`Retrying in ${c}ms (attempt ${i+2}/${lh})...`),await new Promise(h=>setTimeout(h,c))}}finally{await Dm.rm(s,{recursive:!0,force:!0}).catch(()=>{})}}throw new Error(`Failed to download book ${e} after ${lh} attempts: ${n?.message}`)}async requestSignedUrl(e){let t=await fetch(`${this.auth.getApiUrl()}/api/mcp/download-url`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.auth.getJwt()}`},body:JSON.stringify({bookId:e})});if(!t.ok){let n=await t.text().catch(()=>"");throw new Error(`Failed to get download URL (${t.status}): ${n}`)}return await t.json()}async downloadToFile(e,t,n){let i=Date.now();In(`Downloading ${(n/1024/1024).toFixed(1)}MB...`);let s=new AbortController,a=setTimeout(()=>s.abort(),mD);try{let o=await fetch(e,{signal:s.signal});if(!o.ok)throw o.status===403?new Error("SIGNED_URL_EXPIRED"):new Error(`Download failed: ${o.status}`);let u=o.body;if(!u)throw new Error("Empty response body");let l=pD.fromWeb(u),c=hD(t);await fD(l,c);let h=((Date.now()-i)/1e3).toFixed(1);In(`Download complete in ${h}s`)}finally{clearTimeout(a)}}async parseBuffer(e,t){switch(t){case"pdf":return Cc(e);case"mobi":case"azw3":return kc(e);case"txt":return Rl(e);default:return Il(e)}}async clearAll(){this.memoryCache.clear(),await this.diskCache.clear()}};var hh=class{getClient;constructor(e){this.getClient=typeof e=="function"?e:()=>e}get supabase(){return this.getClient()}async getBookById(e,t){let{data:n,error:i}=await this.supabase.from("books").select("*").eq("id",e).eq("user_id",t).maybeSingle();if(i)throw new Error(`Database query failed: ${i.message}`);return n?xT(n):null}async listBooks(e,t,n){let i=this.supabase.from("books").select("*").eq("user_id",e).order("created_at",{ascending:!1});t!==void 0&&n!==void 0?i=i.range(n,n+t-1):t!==void 0&&(i=i.limit(t));let{data:s,error:a}=await i;if(a)throw new Error(`Database query failed: ${a.message}`);return(s??[]).map(xT)}async countBooks(e){let{count:t,error:n}=await this.supabase.from("books").select("*",{count:"exact",head:!0}).eq("user_id",e);if(n)throw new Error(`Database query failed: ${n.message}`);return t??0}};function xT(r){return{id:r.id,hash:r.book_hash,metaHash:r.meta_hash??null,title:r.title,author:r.author??null,format:r.format,sizeBytes:r.size_bytes!=null?Number(r.size_bytes):null,storagePath:r.storage_path??null,userId:r.user_id,createdAt:new Date(r.created_at),updatedAt:new Date(r.updated_at)}}function an(r){process.stderr.write(`[openread-mcp] ${r}
|
|
181
181
|
`)}var bD=5e3,ST=!1;async function yD(r){if(r.transport)try{await Promise.race([r.transport.close(),new Promise(e=>setTimeout(e,bD))])}catch(e){an(`Transport close error (may already be closed): ${e instanceof Error?e.message:e}`)}if(r.diskCache)try{let e=await r.diskCache.cleanupTempFiles();e>0&&an(`Cleaned up ${e} temporary cache files`)}catch(e){an(`Cache cleanup error (non-fatal): ${e instanceof Error?e.message:e}`)}if(r.authManager)try{r.authManager.destroy()}catch(e){an(`Auth cleanup error (non-fatal): ${e instanceof Error?e.message:e}`)}an("Shutdown complete")}function jo(r,e){ST||(ST=!0,an(`Shutting down: ${r}`),setImmediate(()=>{yD(e).catch(t=>{an(`Shutdown error: ${t}`)}).finally(()=>{process.exit(0)})}))}function ET(r){process.on("SIGINT",()=>jo("SIGINT received",r)),process.on("SIGTERM",()=>jo("SIGTERM received",r)),process.stdin.on("end",()=>jo("stdin closed (parent process exited)",r)),process.stdin.on("error",()=>jo("stdin error (parent process crashed)",r)),process.on("uncaughtException",e=>{an(`Uncaught exception: ${e.message}`),jo("uncaught exception",r)}),process.on("unhandledRejection",e=>{an(`Unhandled rejection: ${e}`)})}import{z as AT}from"zod";var CT=50,TT=100,_T=1,vD={limit:AT.number().int().min(_T).max(TT).optional().describe(`Maximum number of books to return (${_T}-${TT}, default ${CT})`),offset:AT.number().int().min(0).optional().describe("Number of books to skip for pagination (min 0, default 0)")};async function wD(r){let e=_e(),{userId:t,bookData:n}=e,i=r.limit??CT,s=r.offset??0,[a,o]=await Promise.all([n.listBooks(t,i,s),n.countBooks(t)]),u=a.map(l=>({id:l.id,title:l.title,author:l.author,format:l.format,addedAt:l.createdAt.toISOString()}));return{totalCount:o,books:u}}function kT(){ke("list_books","List books in the user's library. Returns { totalCount, books[] } where each book has id, title, author, format, and addedAt. Use totalCount to decide if you need to paginate (limit/offset). To search across all books by content, use search_library instead.",vD,wD)}import{z as xD}from"zod";var SD={bookId:xD.string().describe("The unique identifier (UUID) of the book to retrieve")};async function ED(r){let e=_e(),{bookData:t,userId:n}=e,i=await t.getBookById(r.bookId,n);return i||Ie("book",r.bookId),{id:i.id,title:i.title,author:i.author,format:i.format,sizeBytes:i.sizeBytes??null,addedAt:i.createdAt.toISOString(),description:null,publisher:null,publishedDate:null,isbn:null,language:null}}function IT(){ke("get_book_info","Get detailed metadata about a specific book",SD,ED)}import{z as AD}from"zod";var TD={bookId:AD.string().describe("The unique identifier (UUID) of the book")};function _D(r){return{id:r.id,title:r.title,index:r.index,href:r.href,level:r.level}}function CD(r){let e=[];function t(n){for(let i of n)e.push(_D(i)),i.children&&i.children.length>0&&t(i.children)}return t(r),e}async function kD(r){let e=_e(),{bookData:t,userId:n}=e,{bookId:i}=r,s=await t.getBookById(i,n);if(s||Ie("book",i),s.format!=="epub")throw new ge("INVALID_FORMAT",`get_table_of_contents only supports EPUB format. Book ${i} is ${s.format}`);if(!s.storagePath)throw new ge("BOOK_NOT_UPLOADED",`Book ${i} has no storage path`);let a=await e.loadBook(i,s.storagePath,s.format),o=CD(a.toc);return{chapters:o,totalEntries:o.length}}function RT(){ke("get_table_of_contents","Get the table of contents for a book. Returns a list of chapters/sections with their titles, IDs, and nesting levels. Use this to understand book structure before reading specific chapters.",TD,kD)}import{z as PT}from"zod";var OT=100*1024,ID={bookId:PT.string().describe("The unique identifier (UUID) of the book"),chapterId:PT.string().describe("The chapter identifier (from get_table_of_contents)")};async function RD(r){let e=_e(),{bookData:t,userId:n}=e,{bookId:i,chapterId:s}=r,a=await t.getBookById(i,n);if(a||Ie("book",i),a.format!=="epub")throw new ge("INVALID_FORMAT",`get_chapter only supports EPUB format. Book ${i} is ${a.format}`);if(!a.storagePath)throw new ge("BOOK_NOT_UPLOADED",`Book ${i} has no storage path`);let o=await e.loadBook(i,a.storagePath,a.format),u=o.chapters.get(s);u||Ie("chapter",s);let l=o.spine.indexOf(s),c=u.text,h=Buffer.byteLength(c,"utf8"),d=c,f=!1;if(h>OT){d=Buffer.from(c,"utf8").subarray(0,OT).toString("utf8");let b=d.length;d=d.substring(0,b),d+=`
|
|
182
182
|
|
|
183
183
|
[Content truncated. Total length: `+h+" bytes. Use get_page_range for reading in smaller sections.]",f=!0}return{bookId:i,chapterId:s,chapterIndex:l,chapterTitle:u.title,content:d,truncated:f,totalLength:h}}function LT(){ke("get_chapter","Get the full text content of a specific chapter. Returns plain text (not HTML) suitable for reading and analysis. Use get_table_of_contents first to find chapter IDs. Large chapters may be truncated at 100KB.",ID,RD)}import{z as dh}from"zod";var Fm=2e3,PD={bookId:dh.string().describe("The unique identifier (UUID) of the book"),chapterId:dh.string().describe("The chapter ID to read from"),startPage:dh.number().min(1).describe("Starting page number (1-indexed)"),endPage:dh.number().min(1).describe("Ending page number (1-indexed, inclusive)")};async function OD(r){let e=_e(),{bookData:t,userId:n}=e,{bookId:i,chapterId:s,startPage:a,endPage:o}=r;if(a>o)throw new ge("INVALID_FORMAT",`startPage (${a}) must be less than or equal to endPage (${o})`);let u=await t.getBookById(i,n);if(u||Ie("book",i),u.format!=="epub")throw new ge("INVALID_FORMAT",`get_page_range only supports EPUB format. Book ${i} is ${u.format}`);if(!u.storagePath)throw new ge("BOOK_NOT_UPLOADED",`Book ${i} has no storage path`);let c=(await e.loadBook(i,u.storagePath,u.format)).chapters.get(s);c||Ie("chapter",s);let h=c.text,d=Math.max(1,Math.ceil(h.length/Fm)),f=Math.max(1,Math.min(a,d)),p=Math.max(f,Math.min(o,d)),g=(f-1)*Fm,b=p*Fm,m=h.slice(g,b);return{bookId:i,chapterId:s,chapterTitle:c.title,startPage:f,endPage:p,totalPages:d,content:m}}function NT(){ke("get_page_range","Get text for a range of simulated pages within a chapter. Pages are approximately 2000 characters each. Use this to read through chapters in manageable chunks. Page numbers are clamped to valid range if out of bounds.",PD,OD)}import{z as fh}from"zod";var MT=100,$T=20,BT=50,DT=1,FT=2,LD={bookId:fh.string().describe("The unique identifier (UUID) of the book to search"),query:fh.string().min(FT).describe(`Search query (minimum ${FT} characters)`),caseSensitive:fh.boolean().optional().describe("Whether to perform case-sensitive search (default: false)"),limit:fh.number().int().min(DT).max(BT).optional().describe(`Maximum number of results to return (${DT}-${BT}, default ${$T})`)};function ND(r,e,t){let n=Math.max(0,e-MT),i=Math.min(r.length,e+t+MT),s=r.slice(n,i);return n>0&&(s="..."+s),i<r.length&&(s=s+"..."),s=s.replace(/\s+/g," ").trim(),s}function MD(r,e,t,n){let i=[],s=n?r.text:r.text.toLowerCase(),a=n?t:t.toLowerCase(),o=0;for(;o<s.length;){let u=s.indexOf(a,o);if(u===-1)break;let l=ND(r.text,u,t.length);i.push({chapterId:r.id,chapterTitle:r.title,chapterIndex:e,snippet:l,matchPosition:u}),o=u+1}return i}async function BD(r){let e=_e(),{bookData:t,userId:n}=e,{bookId:i,query:s,caseSensitive:a=!1,limit:o=$T}=r,u=await t.getBookById(i,n);if(u||Ie("book",i),!new Set(["epub","txt","md","pdf","mobi","azw3","fb2"]).has(u.format))throw new ge("INVALID_FORMAT",`search_book does not support ${u.format} format`);if(!u.storagePath)throw new ge("INVALID_FORMAT","Book has no stored file to search");let c=await e.loadBook(i,u.storagePath,u.format),h=[],d=0;for(let f=0;f<c.spine.length;f++){let p=c.spine[f],g=c.chapters.get(p);if(!g)continue;let b=MD(g,f,s,a);d+=b.length;for(let m of b)h.length<o&&h.push(m)}return{bookId:i,bookTitle:c.metadata.title,query:s,totalMatches:d,results:h}}function UT(){ke("search_book","Search for text within a specific book. Returns matching snippets with surrounding context. Case-insensitive by default. Use this to find specific passages, quotes, or topics within a book.",LD,BD)}import{z as $m}from"zod";var zT=100,VT=50,jT=100,HT=1,DD=10,WT=2,FD={query:$m.string().min(WT).describe(`Search query (minimum ${WT} characters)`),caseSensitive:$m.boolean().optional().describe("Whether to perform case-sensitive search (default: false)"),limit:$m.number().int().min(HT).max(jT).optional().describe(`Maximum total number of results to return (${HT}-${jT}, default ${VT})`)};function $D(r,e,t){let n=Math.max(0,e-zT),i=Math.min(r.length,e+t+zT),s=r.slice(n,i);return n>0&&(s="..."+s),i<r.length&&(s=s+"..."),s=s.replace(/\s+/g," ").trim(),s}function UD(r,e,t,n){let i=[],s=n?r.text:r.text.toLowerCase(),a=n?t:t.toLowerCase(),o=0;for(;o<s.length;){let u=s.indexOf(a,o);if(u===-1)break;let l=$D(r.text,u,t.length);i.push({chapterId:r.id,chapterTitle:r.title,chapterIndex:e,snippet:l,matchPosition:u}),o=u+1}return i}function zD(r,e,t,n){let i=[],s=0;for(let a=0;a<r.spine.length;a++){let o=r.spine[a],u=r.chapters.get(o);if(!u)continue;let l=UD(u,a,t,n);s+=l.length;for(let c of l)i.length<DD&&i.push(c)}return{bookId:e,bookTitle:r.metadata.title,matchCount:s,results:i}}async function jD(r){let e=_e(),{bookData:t,userId:n}=e,{query:i,caseSensitive:s=!1,limit:a=VT}=r,o=await t.listBooks(n),u=new Set(["epub","txt","md","pdf","mobi","azw3","fb2"]),l=o.filter(g=>u.has(g.format)&&g.storagePath!=null),c=[],h=0,d=0;for(let g of l)try{let b=await e.loadBook(g.id,g.storagePath,g.format);d++;let m=zD(b,g.id,i,s);m.matchCount>0&&(h+=m.matchCount,c.push(m))}catch(b){console.warn("[search-library] Failed to search book:",g.id,b);continue}c.sort((g,b)=>b.matchCount-g.matchCount);let f=0,p=[];for(let g of c){if(f>=a)break;let b=a-f,m=Math.min(g.results.length,b);m>0&&(p.push({...g,results:g.results.slice(0,m)}),f+=m)}return{query:i,totalMatches:h,booksSearched:d,booksWithMatches:c.length,results:p}}function GT(){ke("search_library","Search for text across all books in the user's library. Returns matching snippets grouped by book, sorted by match count. Use this to find which books contain specific topics, quotes, or terms.",FD,jD)}import{z as qT}from"zod";var HD={bookId:qT.string().describe("The unique identifier (UUID) of the book"),chapterId:qT.string().describe("The chapter identifier (from get_table_of_contents)")};async function WD(r){let e=_e(),{bookData:t,userId:n}=e,{bookId:i,chapterId:s}=r,a=await t.getBookById(i,n);if(a||Ie("book",i),a.format!=="epub")throw new ge("INVALID_FORMAT",`get_headings only supports EPUB format. Book ${i} is ${a.format}`);if(!a.storagePath)throw new ge("BOOK_NOT_UPLOADED",`Book ${i} has no storage path`);let u=(await e.loadBook(i,a.storagePath,a.format)).chapters.get(s);u||Ie("chapter",s);let l=u.headings.map(c=>{let d=u.text.slice(c.offset,c.offset+c.length).split(/\s+/).filter(Boolean).length;return{level:c.level,text:c.text,location:String(c.offset),wordCount:d}});return{bookId:i,chapterId:s,chapterTitle:u.title,headings:l}}function KT(){ke("get_headings","Get the heading hierarchy (h1-h6) for a chapter. Returns heading text, level, character offset location, and word count per section. Use this to understand chapter structure before fetching content. Typically ~500 bytes vs 100KB for full chapter.",HD,WD)}import{z as ph}from"zod";var VD={bookId:ph.string().describe("The unique identifier (UUID) of the book"),chapterId:ph.string().describe("The chapter identifier (from get_table_of_contents)"),heading:ph.string().optional().describe("Heading text to find (case-insensitive). Takes precedence over sectionIndex."),sectionIndex:ph.number().int().min(0).optional().describe("Zero-based index of the section within the chapter")};async function GD(r){let e=_e(),{bookData:t,userId:n}=e,{bookId:i,chapterId:s,heading:a,sectionIndex:o}=r,u=await t.getBookById(i,n);if(u||Ie("book",i),u.format!=="epub")throw new ge("INVALID_FORMAT",`get_section only supports EPUB format. Book ${i} is ${u.format}`);if(!u.storagePath)throw new ge("BOOK_NOT_UPLOADED",`Book ${i} has no storage path`);let c=(await e.loadBook(i,u.storagePath,u.format)).chapters.get(s);if(c||Ie("chapter",s),c.headings.length===0){let f=c.text,p=f.split(/\s+/).filter(Boolean).length;return{bookId:i,chapterId:s,heading:c.title,content:f,wordCount:p,nextHeading:null,sectionIndex:0,totalSections:1}}let d;try{a!==void 0?d=Vg(c,a):o!==void 0?d=Ic(c,o):d=Ic(c,0)}catch(f){throw new ge("SECTION_NOT_FOUND",f instanceof Error?f.message:String(f))}return{bookId:i,chapterId:s,heading:d.heading.text,content:d.content,wordCount:d.wordCount,nextHeading:d.nextHeading?.text??null,sectionIndex:d.heading.index,totalSections:c.headings.length}}function XT(){ke("get_section","Get the content of a single section within a chapter, identified by heading text or section index. Returns 1-5KB instead of 50-100KB for the full chapter. Use get_headings first to see available sections. If heading is not found, returns the list of available headings.",VD,GD)}import{z as Um}from"zod";var qD=20,KD=1,XD=50,YD={bookId:Um.string().describe("The unique identifier (UUID) of the book"),chapterId:Um.string().describe("The chapter identifier (from get_table_of_contents)"),limit:Um.number().int().min(KD).max(XD).optional().describe("Maximum number of key terms to return (1-50, default 20)")};async function QD(r){let e=_e(),{bookData:t,userId:n}=e,{bookId:i,chapterId:s,limit:a=qD}=r,o=await t.getBookById(i,n);if(o||Ie("book",i),o.format!=="epub")throw new ge("INVALID_FORMAT",`get_key_terms only supports EPUB format. Book ${i} is ${o.format}`);if(!o.storagePath)throw new ge("BOOK_NOT_UPLOADED",`Book ${i} has no storage path`);let l=(await e.loadBook(i,o.storagePath,o.format)).chapters.get(s);l||Ie("chapter",s);let d=l.keyTerms.slice(0,a).map(f=>({term:f.term,frequency:f.frequency,firstLocation:String(f.firstOffset)}));return{bookId:i,chapterId:s,chapterTitle:l.title,terms:d}}function YT(){ke("get_key_terms","Get the top key terms and concepts for a chapter. Returns term text, frequency, and first occurrence location. Use this to quickly assess chapter relevance before fetching content. Typically ~500 bytes for 20 terms.",YD,QD)}import{z as gh}from"zod";var ZD=500,JD={bookId:gh.string().describe("The unique identifier (UUID) of the book"),chapterId:gh.string().describe("The chapter identifier (from get_table_of_contents)"),offset:gh.number().int().min(0).describe("Character offset within the chapter text (from search results or heading locations)"),contextChars:gh.number().int().min(100).max(5e3).optional().describe("Number of characters of context before and after the offset (100-5000, default 500)")};function eF(r,e){let t=e;for(;t>0&&!/[.!?\n]/.test(r[t-1]);)t--;for(;t<r.length&&/\s/.test(r[t]);)t++;let n=e;for(;n<r.length&&!/[.!?\n]/.test(r[n]);)n++;return n<r.length&&n++,{start:t,end:n}}function tF(r,e,t){if(r.length===0)return t;let n=t;for(let i of r)if(i.offset<=e)n=i.text;else break;return n}async function rF(r){let e=_e(),{bookData:t,userId:n}=e,{bookId:i,chapterId:s,offset:a}=r,o=r.contextChars??ZD,u=await t.getBookById(i,n);if(u||Ie("book",i),u.format!=="epub")throw new ge("INVALID_FORMAT",`get_passage only supports EPUB format. Book ${i} is ${u.format}`);if(!u.storagePath)throw new ge("BOOK_NOT_UPLOADED",`Book ${i} has no storage path`);let c=(await e.loadBook(i,u.storagePath,u.format)).chapters.get(s);c||Ie("chapter",s);let h=c.text;if(a>=h.length)throw new ge("CHAPTER_NOT_FOUND",`Offset ${a} is beyond chapter text length. Valid range: 0-${h.length-1}`);let{start:d,end:f}=eF(h,a),p=h.slice(d,f),g=h.slice(Math.max(0,d-o),d),b=h.slice(f,Math.min(h.length,f+o)),m=tF(c.headings,a,c.title);return{bookId:i,chapterId:s,before:g,match:p,after:b,heading:m,offset:a,contextChars:o}}function QT(){ke("get_passage",'Get text around a specific location in a chapter with configurable context window. The offset comes from search results (matchPosition) or heading locations. Controls how much surrounding text to include (100-5000 chars, default 500). Like "expanding" a search result.',JD,rF)}import{z as nF}from"zod";var iF={bookId:nF.string().uuid().describe("The unique identifier (UUID) of the book")};async function sF(r){let e=_e(),{bookData:t,userId:n}=e,{bookId:i}=r,s=await t.getBookById(i,n);if(s||Ie("book",i),s.format!=="epub")throw new ge("INVALID_FORMAT",`book_overview only supports EPUB format. Book ${i} is ${s.format}`);if(!s.storagePath)throw new ge("BOOK_NOT_UPLOADED",`Book ${i} has no storage path`);let a=await e.loadBook(i,s.storagePath,s.format),o=[],u=0;for(let l=0;l<a.spine.length;l++){let c=a.spine[l],h=a.chapters.get(c);if(!h)continue;let d=h.text.split(/\s+/).filter(Boolean).length;u+=d,o.push({id:c,title:h.title,index:l,wordCount:d,headings:h.headings.map(f=>f.text),topTerms:h.keyTerms.slice(0,5).map(f=>f.term)})}return{bookId:i,title:a.metadata.title,author:a.metadata.author,totalChapters:o.length,totalWords:u,chapters:o}}function ZT(){ke("book_overview","Get complete book structure in one call: TOC with all chapter headings, top key terms, and word counts. Replaces calling get_toc + get_headings + get_key_terms per chapter. Output is ~3-5KB.",iF,sF)}import{z as JT}from"zod";function aF(){try{return gi().server.getClientCapabilities()?.sampling!==void 0}catch{return!1}}async function _s(r){if(!aF())return null;try{let n=(await gi().server.createMessage({messages:[{role:"user",content:{type:"text",text:r.content}}],systemPrompt:r.systemPrompt,includeContext:"none",maxTokens:r.maxTokens??1024,modelPreferences:{intelligencePriority:.8,speedPriority:.3,costPriority:.2}})).content;return Array.isArray(n)?n.find(s=>s.type==="text")?.text??null:n.type==="text"?n.text:null}catch(e){return process.stderr.write(`[openread-mcp] Sampling failed: ${e instanceof Error?e.message:String(e)}
|
|
@@ -227,5 +227,5 @@ ${m||" (No matches found)"}`}).join(`
|
|
|
227
227
|
===
|
|
228
228
|
|
|
229
229
|
`)}`,g=await _s({content:p,systemPrompt:pF,maxTokens:2048});if(g)return{queries:i,booksSearched:l,samplingUsed:!0,comparison:g}}return{queries:i,booksSearched:l,samplingUsed:!1,results:c}}function s_(){ke("compare_across_books","Compare how different books discuss the same topics. Provide 1-5 queries and optionally limit to specific books. With sampling: returns AI-synthesized comparison. Without sampling: returns match counts and top passages per query per book.",gF,bF)}var vF=/^orsk-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function Me(r){process.stderr.write(`[openread-mcp] ${r}
|
|
230
|
-
`)}function wF(){Me(""),Me("ERROR: OPENREAD_API_KEY environment variable is not set."),Me(""),Me("To get your API key:"),Me(" 1. Open Openread (app.openread.ai)"),Me(" 2. Go to Settings > API Keys"),Me(' 3. Click "Create API Key"'),Me(" 4. Copy the orsk-... key"),Me(""),Me("Then set it in your AI client config:"),Me(" {"),Me(' "mcpServers": {'),Me(' "openread": {'),Me(' "command": "npx",'),Me(' "args": ["-y", "@openread/mcp"],'),Me(' "env": { "OPENREAD_API_KEY": "orsk-..." }'),Me(" }"),Me(" }"),Me(" }"),Me("")}function xF(){let r=process.env.OPENREAD_API_KEY;return r||(wF(),process.exit(1)),vF.test(r)||(Me(`ERROR: Invalid API key format. Expected "orsk-{uuid}", got a ${r.length}-character string starting with "${r.slice(0,5)}..."`),process.exit(1)),r}function SF(){kT(),IT(),RT(),LT(),NT(),UT(),GT(),KT(),XT(),YT(),QT(),ZT(),t_(),n_(),s_()}async function EF(){(process.argv.includes("--version")||process.argv.includes("-v"))&&(process.stdout.write(`0.0.1-test.
|
|
231
|
-
`),process.exit(0));let r=xF();Me("Starting OpenRead MCP server..."),Me("Authenticating...");let e=new Wo(r);try{await e.initialize(),Me(`Authenticated as ${e.getUserId()}`)}catch(o){o instanceof Error&&Me(`Authentication failed: ${o.message}`),process.exit(1)}let t=new uh,n=new ch(e,t),i=new hh(e.getSupabaseClient());eb({userId:e.getUserId(),bookData:i,loadBook:(o,u,l)=>n.loadBook(o,l??"epub"),requestId:"cli"}),SF();let s=gi(),a=new yF;await s.connect(a),ET({authManager:e,diskCache:t,transport:a}),Me("MCP server ready. Waiting for requests...")}var AF=typeof process<"u"&&!process.env.VITEST;AF&&EF().catch(r=>{Me(`Fatal error: ${r instanceof Error?r.message:String(r)}`),process.exit(1)});export{vF as API_KEY_PATTERN,Me as log,EF as main,wF as printSetupInstructions,SF as registerAllTools,xF as validateApiKey};
|
|
230
|
+
`)}function wF(){Me(""),Me("ERROR: OPENREAD_API_KEY environment variable is not set."),Me(""),Me("To get your API key:"),Me(" 1. Open Openread (app.openread.ai)"),Me(" 2. Go to Settings > API Keys"),Me(' 3. Click "Create API Key"'),Me(" 4. Copy the orsk-... key"),Me(""),Me("Then set it in your AI client config:"),Me(" {"),Me(' "mcpServers": {'),Me(' "openread": {'),Me(' "command": "npx",'),Me(' "args": ["-y", "@openread/mcp"],'),Me(' "env": { "OPENREAD_API_KEY": "orsk-..." }'),Me(" }"),Me(" }"),Me(" }"),Me("")}function xF(){let r=process.env.OPENREAD_API_KEY;return r||(wF(),process.exit(1)),vF.test(r)||(Me(`ERROR: Invalid API key format. Expected "orsk-{uuid}", got a ${r.length}-character string starting with "${r.slice(0,5)}..."`),process.exit(1)),r}function SF(){kT(),IT(),RT(),LT(),NT(),UT(),GT(),KT(),XT(),YT(),QT(),ZT(),t_(),n_(),s_()}async function EF(){(process.argv.includes("--version")||process.argv.includes("-v"))&&(process.stdout.write(`0.0.1-test.8
|
|
231
|
+
`),process.exit(0));let r=xF();Me("Starting OpenRead MCP server..."),Me("Authenticating...");let e=new Wo(r);try{await e.initialize(),Me(`Authenticated as ${e.getUserId()}`)}catch(o){o instanceof Error&&Me(`Authentication failed: ${o.message}`),process.exit(1)}let t=new uh,n=new ch(e,t),i=new hh(()=>e.getSupabaseClient());eb({userId:e.getUserId(),bookData:i,loadBook:(o,u,l)=>n.loadBook(o,l??"epub"),requestId:"cli"}),SF();let s=gi(),a=new yF;await s.connect(a),ET({authManager:e,diskCache:t,transport:a}),Me("MCP server ready. Waiting for requests...")}var AF=typeof process<"u"&&!process.env.VITEST;AF&&EF().catch(r=>{Me(`Fatal error: ${r instanceof Error?r.message:String(r)}`),process.exit(1)});export{vF as API_KEY_PATTERN,Me as log,EF as main,wF as printSetupInstructions,SF as registerAllTools,xF as validateApiKey};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openread/mcp",
|
|
3
|
-
"version": "0.0.1-test.
|
|
3
|
+
"version": "0.0.1-test.8",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "MCP server for Openread - access your book library from AI assistants",
|
|
6
6
|
"keywords": ["mcp", "openread", "books", "ebook", "ai", "claude", "cursor"],
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"author": "Openread Contributors",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"bin": {
|
|
11
|
-
"
|
|
11
|
+
"mcp": "./dist/cli.mjs"
|
|
12
12
|
},
|
|
13
13
|
"files": ["dist/cli.mjs"],
|
|
14
14
|
"engines": {
|